/* ========================================================================= zfile - provides methods to work with files in a portable fashion. ------------------------------------------------------------------------- Copyright (c) 1991-2013 iMatix Corporation Copyright other contributors as noted in the AUTHORS file. This file is part of CZMQ, the high-level C binding for 0MQ: http://czmq.zeromq.org. This is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABIL- ITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . ========================================================================= */ /* @header The zfile class provides methods to work with disk files. A file object provides the modified date, current size, and type of the file. You can create a file object for a filename that does not yet exist. To read or write data from the file, use the input and output methods, and then read and write chunks. The output method lets you both read and write chunks, at any offset. Finally, this class provides portable symbolic links. If a filename ends in ".ln", the first line of text in the file is read, and used as the underlying file for read/write operations. This lets you manipulate (e.g.) copy symbolic links without copying the perhaps very large files they point to. @discuss This class is a new API, deprecating the old zfile class (which still exists but is implemented in zsys now). @end */ #include "../include/czmq.h" // Structure of our class struct _zfile_t { // Properties for all file objects char *fullname; // File name with path char *link; // Optional linked file bool exists; // true if file exists bool stable; // true if file is stable bool eof; // true if at end of file FILE *handle; // Read/write handle // Properties from files that exist on file system time_t modified; // Modification time off_t cursize; // Size of the file mode_t mode; // POSIX permission bits }; // -------------------------------------------------------------------------- // Constructor // If file exists, populates properties. CZMQ supports portable symbolic // links, which are files with the extension ".ln". A symbolic link is a // text file containing one line, the filename of a target file. Reading // data from the symbolic link actually reads from the target file. zfile_t * zfile_new (const char *path, const char *name) { zfile_t *self = (zfile_t *) zmalloc (sizeof (zfile_t)); // Format full path to file self->fullname = (char *) zmalloc (strlen (path) + strlen (name) + 2); sprintf (self->fullname, "%s/%s", path, name); // Resolve symbolic link if possible if (strlen (self->fullname) > 3 && streq (self->fullname + strlen (self->fullname) - 3, ".ln")) { FILE *handle = fopen (self->fullname, "r"); if (handle) { char buffer [256]; if (fgets (buffer, 256, handle)) { // We have the contents of the symbolic link if (buffer [strlen (buffer) - 1] == '\n') buffer [strlen (buffer) - 1] = 0; self->link = strdup (buffer); // Chop ".ln" off name for external use self->fullname [strlen (self->fullname) - 3] = 0; } fclose (handle); } } zfile_restat (self); return self; } // -------------------------------------------------------------------------- // Destroy a file item void zfile_destroy (zfile_t **self_p) { assert (self_p); if (*self_p) { zfile_t *self = *self_p; if (self->handle) fclose (self->handle); free (self->fullname); free (self->link); free (self); *self_p = NULL; } } // -------------------------------------------------------------------------- // Duplicate a file item zfile_t * zfile_dup (zfile_t *self) { zfile_t *copy; copy = (zfile_t *) zmalloc (sizeof (zfile_t)); copy->fullname = strdup (self->fullname); copy->modified = self->modified; copy->cursize = self->cursize; copy->link = self->link? strdup (self->link): NULL; copy->mode = self->mode; return copy; } // -------------------------------------------------------------------------- // Return file name, remove path if provided char * zfile_filename (zfile_t *self, char *path) { assert (self); char *name = self->fullname; if (path && strlen (self->fullname) >= strlen (path) && memcmp (self->fullname, path, strlen (path)) == 0) { name += strlen (path); if (*name == '/') name++; } return name; } // -------------------------------------------------------------------------- // Refresh file properties from disk; this is not done automatically // on access methods, otherwise it is not possible to compare directory // snapshots. void zfile_restat (zfile_t *self) { assert (self); struct stat stat_buf; char *real_name = self->link? self->link: self->fullname; if (stat (real_name, &stat_buf) == 0) { self->cursize = stat_buf.st_size; self->modified = stat_buf.st_mtime; self->mode = zsys_file_mode (real_name); self->stable = zsys_file_stable (real_name); } else { self->cursize = 0; self->modified = 0; self->mode = 0; self->stable = false; } } // -------------------------------------------------------------------------- // Return when the file was last modified. If you want this to reflect the // current situation, call zfile_restat before checking this property. time_t zfile_modified (zfile_t *self) { assert (self); return self->modified; } // -------------------------------------------------------------------------- // Return the last-known size of the file. If you want this to reflect the // current situation, call zfile_restat before checking this property. off_t zfile_cursize (zfile_t *self) { assert (self); return self->cursize; } // -------------------------------------------------------------------------- // Return true if the file is a directory. If you want this to reflect // any external changes, call zfile_restat before checking this property. bool zfile_is_directory (zfile_t *self) { assert (self); return (self->mode & S_IFDIR) != 0; } // -------------------------------------------------------------------------- // Return true if the file is a regular file. If you want this to reflect // any external changes, call zfile_restat before checking this property. bool zfile_is_regular (zfile_t *self) { assert (self); return (self->mode & S_IFREG) != 0; } // -------------------------------------------------------------------------- // Return true if the file is readable by this process. If you want this to // reflect any external changes, call zfile_restat before checking this // property. bool zfile_is_readable (zfile_t *self) { assert (self); return (self->mode & S_IREAD) != 0; } // -------------------------------------------------------------------------- // Return true if the file is writeable by this process. If you want this // to reflect any external changes, call zfile_restat before checking this // property. bool zfile_is_writeable (zfile_t *self) { assert (self); return (self->mode & S_IWRITE) != 0; } // -------------------------------------------------------------------------- // Check if file has stopped changing and can be safely processed. // If you want this to reflect the current situation, call zfile_restat // before checking this property. bool zfile_is_stable (zfile_t *self) { assert (self); return self->stable; } // -------------------------------------------------------------------------- // Remove the file void zfile_remove (zfile_t *self) { assert (self); // Restore ".ln" in file name if this was a symbolic link if (self->link) self->fullname [strlen (self->fullname)] = '.'; zfile_close (self); zsys_file_delete (self->fullname); } // -------------------------------------------------------------------------- // Open file for reading // Returns 0 if OK, -1 if not found or not accessible int zfile_input (zfile_t *self) { assert (self); if (self->handle) zfile_close (self); char *real_name = self->link? self->link: self->fullname; self->handle = fopen (real_name, "rb"); if (self->handle) { struct stat stat_buf; if (stat (real_name, &stat_buf) == 0) self->cursize = stat_buf.st_size; else self->cursize = 0; } return self->handle? 0: -1; } // -------------------------------------------------------------------------- // Open file for writing, creating directory if needed // File is created if necessary; chunks can be written to file at any // location. Returns 0 if OK, -1 if error. int zfile_output (zfile_t *self) { assert (self); // Wipe symbolic link if that's what the file was if (self->link) { free (self->link); self->link = NULL; } // Create file path if it doesn't exist char *file_path = strdup (self->fullname); char *last_slash = strrchr (file_path, '/'); if (last_slash) *last_slash = 0; if (zsys_dir_create (file_path)) return -1; free (file_path); // Create file if it doesn't exist if (self->handle) zfile_close (self); self->handle = fopen (self->fullname, "r+b"); if (!self->handle) { self->handle = fopen (self->fullname, "w+b"); if (!self->handle) self->handle = fopen (self->fullname, "w+b"); } return self->handle? 0: -1; } // -------------------------------------------------------------------------- // Read chunk from file at specified position. If this was the last chunk, // sets self->eof. Returns a null chunk in case of error. zchunk_t * zfile_read (zfile_t *self, size_t bytes, off_t offset) { assert (self); assert (self->handle); // Calculate real number of bytes to read if (offset > self->cursize) bytes = 0; else if (bytes > (size_t) (self->cursize - offset)) bytes = (size_t) (self->cursize - offset); int rc = fseek (self->handle, (long) offset, SEEK_SET); if (rc == -1) return NULL; self->eof = false; zchunk_t *chunk = zchunk_read (self->handle, bytes); if (chunk) self->eof = zchunk_size (chunk) < bytes; return chunk; } // -------------------------------------------------------------------------- // Write chunk to file at specified position // Return 0 if OK, else -1 int zfile_write (zfile_t *self, zchunk_t *chunk, off_t offset) { assert (self); assert (self->handle); int rc = fseek (self->handle, (long) offset, SEEK_SET); if (rc >= 0) rc = zchunk_write (chunk, self->handle); return rc; } // -------------------------------------------------------------------------- // Close file, if open void zfile_close (zfile_t *self) { assert (self); if (self->handle) { fclose (self->handle); zfile_restat (self); } self->handle = 0; } // -------------------------------------------------------------------------- // Return file handle, if opened FILE * zfile_handle (zfile_t *self) { assert (self); return self->handle; } // Deprecated API, moved to zsys class. The zfile class works with // an object instance, which is more consistent with the CLASS style // and lets us do more interesting things. These functions were // essentially about portability, so now sit in zsys. bool zfile_exists (const char *filename) { return zsys_file_exists (filename); } ssize_t zfile_size (const char *filename) { return zsys_file_size (filename); } mode_t zfile_mode (const char *filename) { return zsys_file_mode (filename); } int zfile_delete (const char *filename) { return zsys_file_delete (filename); } bool zfile_stable (const char *filename) { return zsys_file_stable (filename); } int zfile_mkdir (const char *pathname) { return zsys_dir_create (pathname); } int zfile_rmdir (const char *pathname) { return zsys_dir_delete (pathname); } void zfile_mode_private (void) { zsys_file_mode_private (); } void zfile_mode_default (void) { zsys_file_mode_default (); } // -------------------------------------------------------------------------- // Self test of this class int zfile_test (bool verbose) { printf (" * zfile: "); // @selftest zfile_t *file = zfile_new (".", "bilbo"); assert (streq (zfile_filename (file, "."), "bilbo")); assert (zfile_is_readable (file) == false); zfile_destroy (&file); // Create a test file in some random subdirectory file = zfile_new ("./this/is/a/test", "bilbo"); int rc = zfile_output (file); assert (rc == 0); zchunk_t *chunk = zchunk_new (NULL, 100); zchunk_fill (chunk, 0, 100); // Write 100 bytes at position 1,000,000 in the file rc = zfile_write (file, chunk, 1000000); assert (rc == 0); zchunk_destroy (&chunk); zfile_close (file); assert (zfile_is_readable (file)); assert (zfile_cursize (file) == 1000100); zfile_restat (file); assert (!zfile_is_stable (file)); zclock_sleep (1001); assert (!zfile_is_stable (file)); zfile_restat (file); assert (zfile_is_stable (file)); // Check we can read from file rc = zfile_input (file); assert (rc == 0); chunk = zfile_read (file, 1000100, 0); assert (chunk); assert (zchunk_size (chunk) == 1000100); zchunk_destroy (&chunk); zfile_close (file); // Try some fun with symbolic links zfile_t *link = zfile_new ("./this/is/a/test", "bilbo.ln"); rc = zfile_output (link); assert (rc == 0); fprintf (zfile_handle (link), "./this/is/a/test/bilbo\n"); zfile_destroy (&link); link = zfile_new ("./this/is/a/test", "bilbo.ln"); rc = zfile_input (link); assert (rc == 0); chunk = zfile_read (link, 1000100, 0); assert (chunk); assert (zchunk_size (chunk) == 1000100); zchunk_destroy (&chunk); zfile_destroy (&link); // Remove file and directory zdir_t *dir = zdir_new ("./this", NULL); assert (zdir_cursize (dir) == 2000200); zdir_remove (dir, true); assert (zdir_cursize (dir) == 0); zdir_destroy (&dir); // Check we can no longer read from file assert (zfile_is_readable (file)); zfile_restat (file); assert (!zfile_is_readable (file)); rc = zfile_input (file); assert (rc == -1); zfile_destroy (&file); // @end printf ("OK\n"); return 0; }