/* This file is part of GNUnet. Copyright (C) 2001--2013, 2016, 2018 GNUnet e.V. GNUnet is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. GNUnet is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . SPDX-License-Identifier: AGPL3.0-or-later */ /** * @file util/disk.c * @brief disk IO convenience methods * @author Christian Grothoff * @author Nils Durner */ #include "platform.h" #include "disk.h" #include "gnunet_strings_lib.h" #include "gnunet_disk_lib.h" #define LOG(kind, ...) GNUNET_log_from (kind, "util-disk", __VA_ARGS__) #define LOG_STRERROR(kind, syscall) \ GNUNET_log_from_strerror (kind, "util-disk", syscall) #define LOG_STRERROR_FILE(kind, syscall, filename) \ GNUNET_log_from_strerror_file (kind, "util-disk", syscall, filename) /** * Block size for IO for copying files. */ #define COPY_BLK_SIZE 65536 #include #if HAVE_SYS_VFS_H #include #endif #if HAVE_SYS_PARAM_H #include #endif #if HAVE_SYS_MOUNT_H #include #endif #if HAVE_SYS_STATVFS_H #include #endif #ifndef S_ISLNK #define _IFMT 0170000 /* type of file */ #define _IFLNK 0120000 /* symbolic link */ #define S_ISLNK(m) (((m) & _IFMT) == _IFLNK) #endif /** * Handle used to manage a pipe. */ struct GNUNET_DISK_PipeHandle { /** * File descriptors for the pipe. * One or both of them could be NULL. */ struct GNUNET_DISK_FileHandle *fd[2]; }; /** * Closure for the recursion to determine the file size * of a directory. */ struct GetFileSizeData { /** * Set to the total file size. */ uint64_t total; /** * GNUNET_YES if symbolic links should be included. */ int include_sym_links; /** * GNUNET_YES if mode is file-only (return total == -1 for directories). */ int single_file_mode; }; /** * Translate GNUnet-internal permission bitmap to UNIX file * access permission bitmap. * * @param perm file permissions, GNUnet style * @return file permissions, UNIX style */ static int translate_unix_perms (enum GNUNET_DISK_AccessPermissions perm) { int mode; mode = 0; if (perm & GNUNET_DISK_PERM_USER_READ) mode |= S_IRUSR; if (perm & GNUNET_DISK_PERM_USER_WRITE) mode |= S_IWUSR; if (perm & GNUNET_DISK_PERM_USER_EXEC) mode |= S_IXUSR; if (perm & GNUNET_DISK_PERM_GROUP_READ) mode |= S_IRGRP; if (perm & GNUNET_DISK_PERM_GROUP_WRITE) mode |= S_IWGRP; if (perm & GNUNET_DISK_PERM_GROUP_EXEC) mode |= S_IXGRP; if (perm & GNUNET_DISK_PERM_OTHER_READ) mode |= S_IROTH; if (perm & GNUNET_DISK_PERM_OTHER_WRITE) mode |= S_IWOTH; if (perm & GNUNET_DISK_PERM_OTHER_EXEC) mode |= S_IXOTH; return mode; } /** * Iterate over all files in the given directory and * accumulate their size. * * @param cls closure of type `struct GetFileSizeData` * @param fn current filename we are looking at * @return #GNUNET_SYSERR on serious errors, otherwise #GNUNET_OK */ static int getSizeRec (void *cls, const char *fn) { struct GetFileSizeData *gfsd = cls; #if defined(HAVE_STAT64) && \ ! (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64) struct stat64 buf; if (0 != stat64 (fn, &buf)) { LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_DEBUG, "stat64", fn); return GNUNET_SYSERR; } #else struct stat buf; if (0 != stat (fn, &buf)) { LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_DEBUG, "stat", fn); return GNUNET_SYSERR; } #endif if ((S_ISDIR (buf.st_mode)) && (gfsd->single_file_mode == GNUNET_YES)) { errno = EISDIR; return GNUNET_SYSERR; } if ((! S_ISLNK (buf.st_mode)) || (gfsd->include_sym_links == GNUNET_YES)) gfsd->total += buf.st_size; if ((S_ISDIR (buf.st_mode)) && (0 == access (fn, X_OK)) && ((! S_ISLNK (buf.st_mode)) || (gfsd->include_sym_links == GNUNET_YES))) { if (GNUNET_SYSERR == GNUNET_DISK_directory_scan (fn, &getSizeRec, gfsd)) return GNUNET_SYSERR; } return GNUNET_OK; } /** * Checks whether a handle is invalid * * @param h handle to check * @return #GNUNET_YES if invalid, #GNUNET_NO if valid */ int GNUNET_DISK_handle_invalid (const struct GNUNET_DISK_FileHandle *h) { return ((! h) || (h->fd == -1)) ? GNUNET_YES : GNUNET_NO; } /** * Get the size of an open file. * * @param fh open file handle * @param size where to write size of the file * @return #GNUNET_OK on success, #GNUNET_SYSERR on error */ int GNUNET_DISK_file_handle_size (struct GNUNET_DISK_FileHandle *fh, off_t *size) { struct stat sbuf; if (0 != fstat (fh->fd, &sbuf)) return GNUNET_SYSERR; *size = sbuf.st_size; return GNUNET_OK; } /** * Move the read/write pointer in a file * * @param h handle of an open file * @param offset position to move to * @param whence specification to which position the offset parameter relates to * @return the new position on success, #GNUNET_SYSERR otherwise */ off_t GNUNET_DISK_file_seek (const struct GNUNET_DISK_FileHandle *h, off_t offset, enum GNUNET_DISK_Seek whence) { if (h == NULL) { errno = EINVAL; return GNUNET_SYSERR; } static int t[] = { SEEK_SET, SEEK_CUR, SEEK_END }; return lseek (h->fd, offset, t[whence]); } /** * Get the size of the file (or directory) of the given file (in * bytes). * * @param filename name of the file or directory * @param size set to the size of the file (or, * in the case of directories, the sum * of all sizes of files in the directory) * @param include_symbolic_links should symbolic links be * included? * @param single_file_mode #GNUNET_YES to only get size of one file * and return #GNUNET_SYSERR for directories. * @return #GNUNET_SYSERR on error, #GNUNET_OK on success */ int GNUNET_DISK_file_size (const char *filename, uint64_t *size, int include_symbolic_links, int single_file_mode) { struct GetFileSizeData gfsd; int ret; GNUNET_assert (size != NULL); gfsd.total = 0; gfsd.include_sym_links = include_symbolic_links; gfsd.single_file_mode = single_file_mode; ret = getSizeRec (&gfsd, filename); *size = gfsd.total; return ret; } /** * Obtain some unique identifiers for the given file * that can be used to identify it in the local system. * This function is used between GNUnet processes to * quickly check if two files with the same absolute path * are actually identical. The two processes represent * the same peer but may communicate over the network * (and the file may be on an NFS volume). This function * may not be supported on all operating systems. * * @param filename name of the file * @param dev set to the device ID * @param ino set to the inode ID * @return #GNUNET_OK on success */ int GNUNET_DISK_file_get_identifiers (const char *filename, uint64_t *dev, uint64_t *ino) { #if HAVE_STAT { struct stat sbuf; if (0 != stat (filename, &sbuf)) { return GNUNET_SYSERR; } *ino = (uint64_t) sbuf.st_ino; } #else *ino = 0; #endif #if HAVE_STATVFS { struct statvfs fbuf; if (0 != statvfs (filename, &fbuf)) { return GNUNET_SYSERR; } *dev = (uint64_t) fbuf.f_fsid; } #elif HAVE_STATFS { struct statfs fbuf; if (0 != statfs (filename, &fbuf)) { return GNUNET_SYSERR; } *dev = ((uint64_t) fbuf.f_fsid.val[0]) << 32 || ((uint64_t) fbuf.f_fsid.val[1]); } #else *dev = 0; #endif return GNUNET_OK; } /** * Create the name for a temporary file or directory from a template. * * @param t template (without XXXXX or "/tmp/") * @return name ready for passing to 'mktemp' or 'mkdtemp', NULL on error */ static char * mktemp_name (const char *t) { const char *tmpdir; char *tmpl; char *fn; if ((t[0] != '/') && (t[0] != '\\')) { /* FIXME: This uses system codepage on W32, not UTF-8 */ tmpdir = getenv ("TMPDIR"); if (NULL == tmpdir) tmpdir = getenv ("TMP"); if (NULL == tmpdir) tmpdir = getenv ("TEMP"); if (NULL == tmpdir) tmpdir = "/tmp"; GNUNET_asprintf (&tmpl, "%s/%s%s", tmpdir, t, "XXXXXX"); } else { GNUNET_asprintf (&tmpl, "%s%s", t, "XXXXXX"); } fn = tmpl; return fn; } /** * Update POSIX permissions mask of a file on disk. If both argumets * are #GNUNET_NO, the file is made world-read-write-executable (777). * * @param fn name of the file to update * @param require_uid_match #GNUNET_YES means 700 * @param require_gid_match #GNUNET_YES means 770 unless @a require_uid_match is set */ void GNUNET_DISK_fix_permissions (const char *fn, int require_uid_match, int require_gid_match) { mode_t mode; if (GNUNET_YES == require_uid_match) mode = S_IRUSR | S_IWUSR | S_IXUSR; else if (GNUNET_YES == require_gid_match) mode = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP; else mode = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IWOTH | S_IXOTH; if (0 != chmod (fn, mode)) GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "chmod", fn); } /** * Create an (empty) temporary directory on disk. If the given name is not * an absolute path, the current 'TMPDIR' will be prepended. In any case, * 6 random characters will be appended to the name to create a unique * filename. * * @param t component to use for the name; * does NOT contain "XXXXXX" or "/tmp/". * @return NULL on error, otherwise name of fresh * file on disk in directory for temporary files */ char * GNUNET_DISK_mkdtemp (const char *t) { char *fn; mode_t omask; omask = umask (S_IWGRP | S_IWOTH | S_IRGRP | S_IROTH); fn = mktemp_name (t); if (fn != mkdtemp (fn)) { LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_ERROR, "mkdtemp", fn); GNUNET_free (fn); umask (omask); return NULL; } umask (omask); return fn; } /** * Move a file out of the way (create a backup) by * renaming it to "orig.NUM~" where NUM is the smallest * number that is not used yet. * * @param fil name of the file to back up */ void GNUNET_DISK_file_backup (const char *fil) { size_t slen; char *target; unsigned int num; slen = strlen (fil) + 20; target = GNUNET_malloc (slen); num = 0; do { GNUNET_snprintf (target, slen, "%s.%u~", fil, num++); } while (0 == access (target, F_OK)); if (0 != rename (fil, target)) GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "rename", fil); GNUNET_free (target); } /** * Create an (empty) temporary file on disk. If the given name is not * an absolute path, the current 'TMPDIR' will be prepended. In any case, * 6 random characters will be appended to the name to create a unique * filename. * * @param t component to use for the name; * does NOT contain "XXXXXX" or "/tmp/". * @return NULL on error, otherwise name of fresh * file on disk in directory for temporary files */ char * GNUNET_DISK_mktemp (const char *t) { int fd; char *fn; mode_t omask; omask = umask (S_IWGRP | S_IWOTH | S_IRGRP | S_IROTH); fn = mktemp_name (t); if (-1 == (fd = mkstemp (fn))) { LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_ERROR, "mkstemp", fn); GNUNET_free (fn); umask (omask); return NULL; } umask (omask); if (0 != close (fd)) LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_WARNING, "close", fn); return fn; } /** * Test if @a fil is a directory and listable. Optionally, also check if the * directory is readable. Will not print an error message if the directory does * not exist. Will log errors if #GNUNET_SYSERR is returned (i.e., a file exists * with the same name). * * @param fil filename to test * @param is_readable #GNUNET_YES to additionally check if @a fil is readable; * #GNUNET_NO to disable this check * @return #GNUNET_YES if yes, #GNUNET_NO if not; #GNUNET_SYSERR if it * does not exist or stat'ed */ int GNUNET_DISK_directory_test (const char *fil, int is_readable) { struct stat filestat; int ret; ret = stat (fil, &filestat); if (ret != 0) { if (errno != ENOENT) LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_WARNING, "stat", fil); return GNUNET_SYSERR; } if (! S_ISDIR (filestat.st_mode)) { LOG (GNUNET_ERROR_TYPE_INFO, "A file already exits with the same name %s\n", fil); return GNUNET_NO; } if (GNUNET_YES == is_readable) ret = access (fil, R_OK | X_OK); else ret = access (fil, X_OK); if (ret < 0) { LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_WARNING, "access", fil); return GNUNET_NO; } return GNUNET_YES; } /** * Check that fil corresponds to a filename * (of a file that exists and that is not a directory). * * @param fil filename to check * @return #GNUNET_YES if yes, #GNUNET_NO if not a file, #GNUNET_SYSERR if something * else (will print an error message in that case, too). */ int GNUNET_DISK_file_test (const char *fil) { struct stat filestat; int ret; char *rdir; rdir = GNUNET_STRINGS_filename_expand (fil); if (rdir == NULL) return GNUNET_SYSERR; ret = stat (rdir, &filestat); if (ret != 0) { if (errno != ENOENT) { LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_WARNING, "stat", rdir); GNUNET_free (rdir); return GNUNET_SYSERR; } GNUNET_free (rdir); return GNUNET_NO; } if (! S_ISREG (filestat.st_mode)) { GNUNET_free (rdir); return GNUNET_NO; } if (access (rdir, F_OK) < 0) { LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_WARNING, "access", rdir); GNUNET_free (rdir); return GNUNET_SYSERR; } GNUNET_free (rdir); return GNUNET_YES; } /** * Implementation of "mkdir -p" * * @param dir the directory to create * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure */ int GNUNET_DISK_directory_create (const char *dir) { char *rdir; unsigned int len; unsigned int pos; unsigned int pos2; int ret = GNUNET_OK; rdir = GNUNET_STRINGS_filename_expand (dir); if (rdir == NULL) { GNUNET_break (0); return GNUNET_SYSERR; } len = strlen (rdir); pos = 1; /* skip heading '/' */ /* Check which low level directories already exist */ pos2 = len; rdir[len] = DIR_SEPARATOR; while (pos <= pos2) { if (DIR_SEPARATOR == rdir[pos2]) { rdir[pos2] = '\0'; ret = GNUNET_DISK_directory_test (rdir, GNUNET_NO); if (GNUNET_NO == ret) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Creating directory `%s' failed", rdir); GNUNET_free (rdir); return GNUNET_SYSERR; } rdir[pos2] = DIR_SEPARATOR; if (GNUNET_YES == ret) { pos2++; break; } } pos2--; } rdir[len] = '\0'; if (pos < pos2) pos = pos2; /* Start creating directories */ while (pos <= len) { if ((rdir[pos] == DIR_SEPARATOR) || (pos == len)) { rdir[pos] = '\0'; ret = GNUNET_DISK_directory_test (rdir, GNUNET_NO); if (GNUNET_NO == ret) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Creating directory `%s' failed", rdir); GNUNET_free (rdir); return GNUNET_SYSERR; } if (GNUNET_SYSERR == ret) { ret = mkdir (rdir, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); /* 755 */ if ((ret != 0) && (errno != EEXIST)) { LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_ERROR, "mkdir", rdir); GNUNET_free (rdir); return GNUNET_SYSERR; } } rdir[pos] = DIR_SEPARATOR; } pos++; } GNUNET_free (rdir); return GNUNET_OK; } /** * Create the directory structure for storing a file. * * @param filename name of a file in the directory * @returns #GNUNET_OK on success, * #GNUNET_SYSERR on failure, * #GNUNET_NO if the directory * exists but is not writeable for us */ int GNUNET_DISK_directory_create_for_file (const char *filename) { char *rdir; size_t len; int ret; int eno; rdir = GNUNET_STRINGS_filename_expand (filename); if (NULL == rdir) { errno = EINVAL; return GNUNET_SYSERR; } if (0 == access (rdir, W_OK)) { GNUNET_free (rdir); return GNUNET_OK; } len = strlen (rdir); while ((len > 0) && (rdir[len] != DIR_SEPARATOR)) len--; rdir[len] = '\0'; /* The empty path is invalid and in this case refers to / */ if (0 == len) { GNUNET_free (rdir); rdir = GNUNET_strdup ("/"); } ret = GNUNET_DISK_directory_create (rdir); if ((GNUNET_OK == ret) && (0 != access (rdir, W_OK))) ret = GNUNET_NO; eno = errno; GNUNET_free (rdir); errno = eno; return ret; } /** * Read the contents of a binary file into a buffer. * * @param h handle to an open file * @param result the buffer to write the result to * @param len the maximum number of bytes to read * @return the number of bytes read on success, #GNUNET_SYSERR on failure */ ssize_t GNUNET_DISK_file_read (const struct GNUNET_DISK_FileHandle *h, void *result, size_t len) { if (NULL == h) { errno = EINVAL; return GNUNET_SYSERR; } return read (h->fd, result, len); } /** * Read the contents of a binary file into a buffer. * Guarantees not to block (returns GNUNET_SYSERR and sets errno to EAGAIN * when no data can be read). * * @param h handle to an open file * @param result the buffer to write the result to * @param len the maximum number of bytes to read * @return the number of bytes read on success, #GNUNET_SYSERR on failure */ ssize_t GNUNET_DISK_file_read_non_blocking (const struct GNUNET_DISK_FileHandle *h, void *result, size_t len) { if (NULL == h) { errno = EINVAL; return GNUNET_SYSERR; } int flags; ssize_t ret; /* set to non-blocking, read, then set back */ flags = fcntl (h->fd, F_GETFL); if (0 == (flags & O_NONBLOCK)) (void) fcntl (h->fd, F_SETFL, flags | O_NONBLOCK); ret = read (h->fd, result, len); if (0 == (flags & O_NONBLOCK)) { int eno = errno; (void) fcntl (h->fd, F_SETFL, flags); errno = eno; } return ret; } /** * Read the contents of a binary file into a buffer. * * @param fn file name * @param result the buffer to write the result to * @param len the maximum number of bytes to read * @return number of bytes read, #GNUNET_SYSERR on failure */ ssize_t GNUNET_DISK_fn_read (const char *fn, void *result, size_t len) { struct GNUNET_DISK_FileHandle *fh; ssize_t ret; int eno; fh = GNUNET_DISK_file_open (fn, GNUNET_DISK_OPEN_READ, GNUNET_DISK_PERM_NONE); if (NULL == fh) return GNUNET_SYSERR; ret = GNUNET_DISK_file_read (fh, result, len); eno = errno; GNUNET_assert (GNUNET_OK == GNUNET_DISK_file_close (fh)); errno = eno; return ret; } /** * Write a buffer to a file. * * @param h handle to open file * @param buffer the data to write * @param n number of bytes to write * @return number of bytes written on success, #GNUNET_SYSERR on error */ ssize_t GNUNET_DISK_file_write (const struct GNUNET_DISK_FileHandle *h, const void *buffer, size_t n) { if (NULL == h) { errno = EINVAL; return GNUNET_SYSERR; } return write (h->fd, buffer, n); } /** * Write a buffer to a file, blocking, if necessary. * * @param h handle to open file * @param buffer the data to write * @param n number of bytes to write * @return number of bytes written on success, #GNUNET_SYSERR on error */ ssize_t GNUNET_DISK_file_write_blocking (const struct GNUNET_DISK_FileHandle *h, const void *buffer, size_t n) { if (NULL == h) { errno = EINVAL; return GNUNET_SYSERR; } int flags; ssize_t ret; /* set to blocking, write, then set back */ flags = fcntl (h->fd, F_GETFL); if (0 != (flags & O_NONBLOCK)) (void) fcntl (h->fd, F_SETFL, flags - O_NONBLOCK); ret = write (h->fd, buffer, n); if (0 == (flags & O_NONBLOCK)) (void) fcntl (h->fd, F_SETFL, flags); return ret; } /** * Write a buffer to a file. If the file is longer than the * number of bytes that will be written, it will be truncated. * * @param fn file name * @param buffer the data to write * @param n number of bytes to write * @param mode file permissions * @return number of bytes written on success, #GNUNET_SYSERR on error */ ssize_t GNUNET_DISK_fn_write (const char *fn, const void *buffer, size_t n, enum GNUNET_DISK_AccessPermissions mode) { struct GNUNET_DISK_FileHandle *fh; ssize_t ret; fh = GNUNET_DISK_file_open (fn, GNUNET_DISK_OPEN_WRITE | GNUNET_DISK_OPEN_TRUNCATE | GNUNET_DISK_OPEN_CREATE, mode); if (! fh) return GNUNET_SYSERR; ret = GNUNET_DISK_file_write (fh, buffer, n); GNUNET_assert (GNUNET_OK == GNUNET_DISK_file_close (fh)); return ret; } /** * Scan a directory for files. * * @param dir_name the name of the directory * @param callback the method to call for each file, * can be NULL, in that case, we only count * @param callback_cls closure for @a callback * @return the number of files found, #GNUNET_SYSERR on error or * ieration aborted by callback returning #GNUNET_SYSERR */ int GNUNET_DISK_directory_scan (const char *dir_name, GNUNET_FileNameCallback callback, void *callback_cls) { DIR *dinfo; struct dirent *finfo; struct stat istat; int count = 0; int ret; char *name; char *dname; unsigned int name_len; unsigned int n_size; GNUNET_assert (NULL != dir_name); dname = GNUNET_STRINGS_filename_expand (dir_name); if (NULL == dname) return GNUNET_SYSERR; while ((strlen (dname) > 0) && (dname[strlen (dname) - 1] == DIR_SEPARATOR)) dname[strlen (dname) - 1] = '\0'; if (0 != stat (dname, &istat)) { LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_WARNING, "stat", dname); GNUNET_free (dname); return GNUNET_SYSERR; } if (! S_ISDIR (istat.st_mode)) { LOG (GNUNET_ERROR_TYPE_WARNING, _ ("Expected `%s' to be a directory!\n"), dir_name); GNUNET_free (dname); return GNUNET_SYSERR; } errno = 0; dinfo = opendir (dname); if ((EACCES == errno) || (NULL == dinfo)) { LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_WARNING, "opendir", dname); if (NULL != dinfo) closedir (dinfo); GNUNET_free (dname); return GNUNET_SYSERR; } name_len = 256; n_size = strlen (dname) + name_len + strlen (DIR_SEPARATOR_STR) + 1; name = GNUNET_malloc (n_size); while (NULL != (finfo = readdir (dinfo))) { if ((0 == strcmp (finfo->d_name, ".")) || (0 == strcmp (finfo->d_name, ".."))) continue; if (NULL != callback) { if (name_len < strlen (finfo->d_name)) { GNUNET_free (name); name_len = strlen (finfo->d_name); n_size = strlen (dname) + name_len + strlen (DIR_SEPARATOR_STR) + 1; name = GNUNET_malloc (n_size); } /* dname can end in "/" only if dname == "/"; * if dname does not end in "/", we need to add * a "/" (otherwise, we must not!) */ GNUNET_snprintf (name, n_size, "%s%s%s", dname, (0 == strcmp (dname, DIR_SEPARATOR_STR)) ? "" : DIR_SEPARATOR_STR, finfo->d_name); ret = callback (callback_cls, name); if (GNUNET_OK != ret) { closedir (dinfo); GNUNET_free (name); GNUNET_free (dname); if (GNUNET_NO == ret) return count; return GNUNET_SYSERR; } } count++; } closedir (dinfo); GNUNET_free (name); GNUNET_free (dname); return count; } /** * Function that removes the given directory by calling * #GNUNET_DISK_directory_remove(). * * @param unused not used * @param fn directory to remove * @return #GNUNET_OK */ static int remove_helper (void *unused, const char *fn) { (void) unused; (void) GNUNET_DISK_directory_remove (fn); return GNUNET_OK; } /** * Remove all files in a directory (rm -r). Call with * caution. * * @param filename the file to remove * @return #GNUNET_OK on success, #GNUNET_SYSERR on error */ int GNUNET_DISK_directory_remove (const char *filename) { struct stat istat; if (NULL == filename) { GNUNET_break (0); return GNUNET_SYSERR; } if (0 != lstat (filename, &istat)) return GNUNET_NO; /* file may not exist... */ (void) chmod (filename, S_IWUSR | S_IRUSR | S_IXUSR); if (0 == unlink (filename)) return GNUNET_OK; if ((errno != EISDIR) && /* EISDIR is not sufficient in all cases, e.g. * sticky /tmp directory may result in EPERM on BSD. * So we also explicitly check "isDirectory" */ (GNUNET_YES != GNUNET_DISK_directory_test (filename, GNUNET_YES))) { LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_WARNING, "rmdir", filename); return GNUNET_SYSERR; } if (GNUNET_SYSERR == GNUNET_DISK_directory_scan (filename, &remove_helper, NULL)) return GNUNET_SYSERR; if (0 != rmdir (filename)) { LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_WARNING, "rmdir", filename); return GNUNET_SYSERR; } return GNUNET_OK; } /** * Copy a file. * * @param src file to copy * @param dst destination file name * @return #GNUNET_OK on success, #GNUNET_SYSERR on error */ int GNUNET_DISK_file_copy (const char *src, const char *dst) { char *buf; uint64_t pos; uint64_t size; size_t len; ssize_t sret; struct GNUNET_DISK_FileHandle *in; struct GNUNET_DISK_FileHandle *out; if (GNUNET_OK != GNUNET_DISK_file_size (src, &size, GNUNET_YES, GNUNET_YES)) { GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "stat", src); return GNUNET_SYSERR; } pos = 0; in = GNUNET_DISK_file_open (src, GNUNET_DISK_OPEN_READ, GNUNET_DISK_PERM_NONE); if (! in) { GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "open", src); return GNUNET_SYSERR; } out = GNUNET_DISK_file_open (dst, GNUNET_DISK_OPEN_WRITE | GNUNET_DISK_OPEN_CREATE | GNUNET_DISK_OPEN_FAILIFEXISTS, GNUNET_DISK_PERM_USER_READ | GNUNET_DISK_PERM_USER_WRITE | GNUNET_DISK_PERM_GROUP_READ | GNUNET_DISK_PERM_GROUP_WRITE); if (! out) { GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "open", dst); GNUNET_DISK_file_close (in); return GNUNET_SYSERR; } buf = GNUNET_malloc (COPY_BLK_SIZE); while (pos < size) { len = COPY_BLK_SIZE; if (len > size - pos) len = size - pos; sret = GNUNET_DISK_file_read (in, buf, len); if ((sret < 0) || (len != (size_t) sret)) goto FAIL; sret = GNUNET_DISK_file_write (out, buf, len); if ((sret < 0) || (len != (size_t) sret)) goto FAIL; pos += len; } GNUNET_free (buf); GNUNET_DISK_file_close (in); GNUNET_DISK_file_close (out); return GNUNET_OK; FAIL: GNUNET_free (buf); GNUNET_DISK_file_close (in); GNUNET_DISK_file_close (out); return GNUNET_SYSERR; } /** * @brief Removes special characters as ':' from a filename. * @param fn the filename to canonicalize */ void GNUNET_DISK_filename_canonicalize (char *fn) { char *idx; char c; for (idx = fn; *idx; idx++) { c = *idx; if ((c == '/') || (c == '\\') || (c == ':') || (c == '*') || (c == '?') || (c == '"') || (c == '<') || (c == '>') || (c == '|') ) { *idx = '_'; } } } /** * @brief Change owner of a file * * @param filename name of file to change the owner of * @param user name of the new owner * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure */ int GNUNET_DISK_file_change_owner (const char *filename, const char *user) { struct passwd *pws; pws = getpwnam (user); if (NULL == pws) { LOG (GNUNET_ERROR_TYPE_ERROR, _ ("Cannot obtain information about user `%s': %s\n"), user, strerror (errno)); return GNUNET_SYSERR; } if (0 != chown (filename, pws->pw_uid, pws->pw_gid)) { LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_WARNING, "chown", filename); return GNUNET_SYSERR; } return GNUNET_OK; } /** * Open a file. Note that the access permissions will only be * used if a new file is created and if the underlying operating * system supports the given permissions. * * @param fn file name to be opened * @param flags opening flags, a combination of GNUNET_DISK_OPEN_xxx bit flags * @param perm permissions for the newly created file, use * #GNUNET_DISK_PERM_NONE if a file could not be created by this * call (because of flags) * @return IO handle on success, NULL on error */ struct GNUNET_DISK_FileHandle * GNUNET_DISK_file_open (const char *fn, enum GNUNET_DISK_OpenFlags flags, enum GNUNET_DISK_AccessPermissions perm) { char *expfn; struct GNUNET_DISK_FileHandle *ret; int oflags; int mode; int fd; expfn = GNUNET_STRINGS_filename_expand (fn); if (NULL == expfn) return NULL; mode = 0; if (GNUNET_DISK_OPEN_READWRITE == (flags & GNUNET_DISK_OPEN_READWRITE)) oflags = O_RDWR; /* note: O_RDWR is NOT always O_RDONLY | O_WRONLY */ else if (flags & GNUNET_DISK_OPEN_READ) oflags = O_RDONLY; else if (flags & GNUNET_DISK_OPEN_WRITE) oflags = O_WRONLY; else { GNUNET_break (0); GNUNET_free (expfn); return NULL; } if (flags & GNUNET_DISK_OPEN_FAILIFEXISTS) oflags |= (O_CREAT | O_EXCL); if (flags & GNUNET_DISK_OPEN_TRUNCATE) oflags |= O_TRUNC; if (flags & GNUNET_DISK_OPEN_APPEND) oflags |= O_APPEND; if (GNUNET_NO == GNUNET_DISK_file_test (fn)) { if (flags & GNUNET_DISK_OPEN_CREATE) { (void) GNUNET_DISK_directory_create_for_file (expfn); oflags |= O_CREAT; mode = translate_unix_perms (perm); } } fd = open (expfn, oflags #if O_CLOEXEC | O_CLOEXEC #endif | O_LARGEFILE, mode); if (fd == -1) { if (0 == (flags & GNUNET_DISK_OPEN_FAILIFEXISTS)) LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_WARNING, "open", expfn); else LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_DEBUG, "open", expfn); GNUNET_free (expfn); return NULL; } ret = GNUNET_new (struct GNUNET_DISK_FileHandle); ret->fd = fd; GNUNET_free (expfn); return ret; } /** * Close an open file. * * @param h file handle * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise */ int GNUNET_DISK_file_close (struct GNUNET_DISK_FileHandle *h) { int ret; if (h == NULL) { errno = EINVAL; return GNUNET_SYSERR; } ret = GNUNET_OK; if (close (h->fd) != 0) { LOG_STRERROR (GNUNET_ERROR_TYPE_WARNING, "close"); ret = GNUNET_SYSERR; } GNUNET_free (h); return ret; } /** * Get a handle from a native integer FD. * * @param fno native integer file descriptor * @return file handle corresponding to the descriptor, NULL on error */ struct GNUNET_DISK_FileHandle * GNUNET_DISK_get_handle_from_int_fd (int fno) { struct GNUNET_DISK_FileHandle *fh; if ((((off_t) -1) == lseek (fno, 0, SEEK_CUR)) && (EBADF == errno)) return NULL; /* invalid FD */ fh = GNUNET_new (struct GNUNET_DISK_FileHandle); fh->fd = fno; return fh; } /** * Get a handle from a native streaming FD. * * @param fd native streaming file descriptor * @return file handle corresponding to the descriptor */ struct GNUNET_DISK_FileHandle * GNUNET_DISK_get_handle_from_native (FILE *fd) { int fno; fno = fileno (fd); if (-1 == fno) return NULL; return GNUNET_DISK_get_handle_from_int_fd (fno); } /** * Handle for a memory-mapping operation. */ struct GNUNET_DISK_MapHandle { /** * Address where the map is in memory. */ void *addr; /** * Number of bytes mapped. */ size_t len; }; #ifndef MAP_FAILED #define MAP_FAILED ((void *) -1) #endif /** * Map a file into memory * * @param h open file handle * @param m handle to the new mapping * @param access access specification, GNUNET_DISK_MAP_TYPE_xxx * @param len size of the mapping * @return pointer to the mapped memory region, NULL on failure */ void * GNUNET_DISK_file_map (const struct GNUNET_DISK_FileHandle *h, struct GNUNET_DISK_MapHandle **m, enum GNUNET_DISK_MapType access, size_t len) { if (NULL == h) { errno = EINVAL; return NULL; } int prot; prot = 0; if (access & GNUNET_DISK_MAP_TYPE_READ) prot = PROT_READ; if (access & GNUNET_DISK_MAP_TYPE_WRITE) prot |= PROT_WRITE; *m = GNUNET_new (struct GNUNET_DISK_MapHandle); (*m)->addr = mmap (NULL, len, prot, MAP_SHARED, h->fd, 0); GNUNET_assert (NULL != (*m)->addr); if (MAP_FAILED == (*m)->addr) { GNUNET_free (*m); return NULL; } (*m)->len = len; return (*m)->addr; } /** * Unmap a file * @param h mapping handle * @return GNUNET_OK on success, GNUNET_SYSERR otherwise */ int GNUNET_DISK_file_unmap (struct GNUNET_DISK_MapHandle *h) { int ret; if (h == NULL) { errno = EINVAL; return GNUNET_SYSERR; } ret = munmap (h->addr, h->len) != -1 ? GNUNET_OK : GNUNET_SYSERR; GNUNET_free (h); return ret; } /** * Write file changes to disk * @param h handle to an open file * @return GNUNET_OK on success, GNUNET_SYSERR otherwise */ int GNUNET_DISK_file_sync (const struct GNUNET_DISK_FileHandle *h) { if (h == NULL) { errno = EINVAL; return GNUNET_SYSERR; } #if ! defined(__linux__) || ! defined(GNU) return fsync (h->fd) == -1 ? GNUNET_SYSERR : GNUNET_OK; #else return fdatasync (h->fd) == -1 ? GNUNET_SYSERR : GNUNET_OK; #endif } /** * Creates an interprocess channel * * @param blocking_read creates an asynchronous pipe for reading if set to GNUNET_NO * @param blocking_write creates an asynchronous pipe for writing if set to GNUNET_NO * @param inherit_read inherit the parent processes stdin (only for windows) * @param inherit_write inherit the parent processes stdout (only for windows) * @return handle to the new pipe, NULL on error */ struct GNUNET_DISK_PipeHandle * GNUNET_DISK_pipe (int blocking_read, int blocking_write, int inherit_read, int inherit_write) { int fd[2]; int ret; int eno; (void) inherit_read; (void) inherit_write; ret = pipe (fd); if (ret == -1) { eno = errno; LOG_STRERROR (GNUNET_ERROR_TYPE_ERROR, "pipe"); errno = eno; return NULL; } return GNUNET_DISK_pipe_from_fd (blocking_read, blocking_write, fd); } /** * Creates a pipe object from a couple of file descriptors. * Useful for wrapping existing pipe FDs. * * @param blocking_read creates an asynchronous pipe for reading if set to GNUNET_NO * @param blocking_write creates an asynchronous pipe for writing if set to GNUNET_NO * @param fd an array of two fd values. One of them may be -1 for read-only or write-only pipes * * @return handle to the new pipe, NULL on error */ struct GNUNET_DISK_PipeHandle * GNUNET_DISK_pipe_from_fd (int blocking_read, int blocking_write, int fd[2]) { struct GNUNET_DISK_PipeHandle *p; p = GNUNET_new (struct GNUNET_DISK_PipeHandle); int ret; int flags; int eno = 0; /* make gcc happy */ ret = 0; if (fd[0] >= 0) { p->fd[0] = GNUNET_new (struct GNUNET_DISK_FileHandle); p->fd[0]->fd = fd[0]; if (! blocking_read) { flags = fcntl (fd[0], F_GETFL); flags |= O_NONBLOCK; if (0 > fcntl (fd[0], F_SETFL, flags)) { ret = -1; eno = errno; } } flags = fcntl (fd[0], F_GETFD); flags |= FD_CLOEXEC; if (0 > fcntl (fd[0], F_SETFD, flags)) { ret = -1; eno = errno; } } if (fd[1] >= 0) { p->fd[1] = GNUNET_new (struct GNUNET_DISK_FileHandle); p->fd[1]->fd = fd[1]; if (! blocking_write) { flags = fcntl (fd[1], F_GETFL); flags |= O_NONBLOCK; if (0 > fcntl (fd[1], F_SETFL, flags)) { ret = -1; eno = errno; } } flags = fcntl (fd[1], F_GETFD); flags |= FD_CLOEXEC; if (0 > fcntl (fd[1], F_SETFD, flags)) { ret = -1; eno = errno; } } if (ret == -1) { errno = eno; LOG_STRERROR (GNUNET_ERROR_TYPE_ERROR, "fcntl"); if (p->fd[0]->fd >= 0) GNUNET_break (0 == close (p->fd[0]->fd)); if (p->fd[1]->fd >= 0) GNUNET_break (0 == close (p->fd[1]->fd)); GNUNET_free_non_null (p->fd[0]); GNUNET_free_non_null (p->fd[1]); GNUNET_free (p); errno = eno; return NULL; } return p; } /** * Closes an interprocess channel * * @param p pipe to close * @param end which end of the pipe to close * @return GNUNET_OK on success, GNUNET_SYSERR otherwise */ int GNUNET_DISK_pipe_close_end (struct GNUNET_DISK_PipeHandle *p, enum GNUNET_DISK_PipeEnd end) { int ret = GNUNET_OK; if (end == GNUNET_DISK_PIPE_END_READ) { if (p->fd[0]) { ret = GNUNET_DISK_file_close (p->fd[0]); p->fd[0] = NULL; } } else if (end == GNUNET_DISK_PIPE_END_WRITE) { if (p->fd[1]) { ret = GNUNET_DISK_file_close (p->fd[1]); p->fd[1] = NULL; } } return ret; } /** * Detaches one of the ends from the pipe. * Detached end is a fully-functional FileHandle, it will * not be affected by anything you do with the pipe afterwards. * Each end of a pipe can only be detched from it once (i.e. * it is not duplicated). * * @param p pipe to detach an end from * @param end which end of the pipe to detach * @return Detached end on success, NULL on failure * (or if that end is not present or is closed). */ struct GNUNET_DISK_FileHandle * GNUNET_DISK_pipe_detach_end (struct GNUNET_DISK_PipeHandle *p, enum GNUNET_DISK_PipeEnd end) { struct GNUNET_DISK_FileHandle *ret = NULL; if (end == GNUNET_DISK_PIPE_END_READ) { if (p->fd[0]) { ret = p->fd[0]; p->fd[0] = NULL; } } else if (end == GNUNET_DISK_PIPE_END_WRITE) { if (p->fd[1]) { ret = p->fd[1]; p->fd[1] = NULL; } } return ret; } /** * Closes an interprocess channel * * @param p pipe to close * @return GNUNET_OK on success, GNUNET_SYSERR otherwise */ int GNUNET_DISK_pipe_close (struct GNUNET_DISK_PipeHandle *p) { int ret = GNUNET_OK; int read_end_close; int write_end_close; int read_end_close_errno; int write_end_close_errno; read_end_close = GNUNET_DISK_pipe_close_end (p, GNUNET_DISK_PIPE_END_READ); read_end_close_errno = errno; write_end_close = GNUNET_DISK_pipe_close_end (p, GNUNET_DISK_PIPE_END_WRITE); write_end_close_errno = errno; GNUNET_free (p); if (GNUNET_OK != read_end_close) { errno = read_end_close_errno; ret = read_end_close; } else if (GNUNET_OK != write_end_close) { errno = write_end_close_errno; ret = write_end_close; } return ret; } /** * Get the handle to a particular pipe end * * @param p pipe * @param n end to access * @return handle for the respective end */ const struct GNUNET_DISK_FileHandle * GNUNET_DISK_pipe_handle (const struct GNUNET_DISK_PipeHandle *p, enum GNUNET_DISK_PipeEnd n) { switch (n) { case GNUNET_DISK_PIPE_END_READ: case GNUNET_DISK_PIPE_END_WRITE: return p->fd[n]; default: GNUNET_break (0); return NULL; } } /** * Retrieve OS file handle * @internal * @param fh GNUnet file descriptor * @param dst destination buffer * @param dst_len length of dst * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise */ int GNUNET_DISK_internal_file_handle_ (const struct GNUNET_DISK_FileHandle *fh, void *dst, size_t dst_len) { if (NULL == fh) return GNUNET_SYSERR; if (dst_len < sizeof(int)) return GNUNET_SYSERR; *((int *) dst) = fh->fd; return GNUNET_OK; } /** * Helper function for #GNUNET_DISK_purge_cfg_dir. * * @param cls a `const char *` with the option to purge * @param cfg our configuration * @return #GNUNET_OK on success */ static int purge_cfg_dir (void *cls, const struct GNUNET_CONFIGURATION_Handle *cfg) { const char *option = cls; char *tmpname; if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename (cfg, "PATHS", option, &tmpname)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "PATHS", option); return GNUNET_NO; } if (GNUNET_SYSERR == GNUNET_DISK_directory_remove (tmpname)) { GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "remove", tmpname); GNUNET_free (tmpname); return GNUNET_OK; } GNUNET_free (tmpname); return GNUNET_OK; } /** * Remove the directory given under @a option in * section [PATHS] in configuration under @a cfg_filename * * @param cfg_filename configuration file to parse * @param option option with the dir name to purge */ void GNUNET_DISK_purge_cfg_dir (const char *cfg_filename, const char *option) { GNUNET_break (GNUNET_OK == GNUNET_CONFIGURATION_parse_and_run (cfg_filename, &purge_cfg_dir, (void *) option)); } /* end of disk.c */