]> granicus.if.org Git - neomutt/commitdiff
move the file functions to the library
authorRichard Russon <rich@flatcap.org>
Wed, 2 Aug 2017 14:49:32 +0000 (15:49 +0100)
committerRichard Russon <rich@flatcap.org>
Wed, 2 Aug 2017 16:24:28 +0000 (17:24 +0100)
lib.c
lib.h
lib/Makefile.am
lib/lib.h
lib/lib_file.c [new file with mode: 0644]
lib/lib_file.h [new file with mode: 0644]
po/POTFILES.in

diff --git a/lib.c b/lib.c
index e1df5d57ad5d302b3028a6af960d6c8d19fb906b..ed7882cc98a735ee458ef204957f0fc4a727cbe1 100644 (file)
--- a/lib.c
+++ b/lib.c
@@ -117,646 +117,6 @@ void mutt_nocurses_error(const char *fmt, ...)
   fputc('\n', stderr);
 }
 
-int safe_fclose(FILE **f)
-{
-  int r = 0;
-
-  if (*f)
-    r = fclose(*f);
-
-  *f = NULL;
-  return r;
-}
-
-int safe_fsync_close(FILE **f)
-{
-  int r = 0;
-
-  if (*f)
-  {
-    if (fflush(*f) || fsync(fileno(*f)))
-    {
-      int save_errno = errno;
-      r = -1;
-      safe_fclose(f);
-      errno = save_errno;
-    }
-    else
-      r = safe_fclose(f);
-  }
-
-  return r;
-}
-
-void mutt_unlink(const char *s)
-{
-  int fd;
-  int flags;
-  FILE *f = NULL;
-  struct stat sb, sb2;
-  char buf[2048];
-
-/* Defend against symlink attacks */
-
-#ifdef O_NOFOLLOW
-  flags = O_RDWR | O_NOFOLLOW;
-#else
-  flags = O_RDWR;
-#endif
-
-  if (lstat(s, &sb) == 0 && S_ISREG(sb.st_mode))
-  {
-    if ((fd = open(s, flags)) < 0)
-      return;
-
-    if ((fstat(fd, &sb2) != 0) || !S_ISREG(sb2.st_mode) ||
-        (sb.st_dev != sb2.st_dev) || (sb.st_ino != sb2.st_ino))
-    {
-      close(fd);
-      return;
-    }
-
-    if ((f = fdopen(fd, "r+")))
-    {
-      unlink(s);
-      memset(buf, 0, sizeof(buf));
-      while (sb.st_size > 0)
-      {
-        fwrite(buf, 1, MIN(sizeof(buf), sb.st_size), f);
-        sb.st_size -= MIN(sizeof(buf), sb.st_size);
-      }
-      safe_fclose(&f);
-    }
-  }
-}
-
-int mutt_copy_bytes(FILE *in, FILE *out, size_t size)
-{
-  char buf[2048];
-  size_t chunk;
-
-  while (size > 0)
-  {
-    chunk = (size > sizeof(buf)) ? sizeof(buf) : size;
-    if ((chunk = fread(buf, 1, chunk, in)) < 1)
-      break;
-    if (fwrite(buf, 1, chunk, out) != chunk)
-    {
-      return -1;
-    }
-    size -= chunk;
-  }
-
-  if (fflush(out) != 0)
-    return -1;
-  return 0;
-}
-
-int mutt_copy_stream(FILE *fin, FILE *fout)
-{
-  size_t l;
-  char buf[LONG_STRING];
-
-  while ((l = fread(buf, 1, sizeof(buf), fin)) > 0)
-  {
-    if (fwrite(buf, 1, l, fout) != l)
-      return -1;
-  }
-
-  if (fflush(fout) != 0)
-    return -1;
-  return 0;
-}
-
-static bool compare_stat(struct stat *osb, struct stat *nsb)
-{
-  if (osb->st_dev != nsb->st_dev || osb->st_ino != nsb->st_ino || osb->st_rdev != nsb->st_rdev)
-  {
-    return false;
-  }
-
-  return true;
-}
-
-int safe_symlink(const char *oldpath, const char *newpath)
-{
-  struct stat osb, nsb;
-
-  if (!oldpath || !newpath)
-    return -1;
-
-  if (unlink(newpath) == -1 && errno != ENOENT)
-    return -1;
-
-  if (oldpath[0] == '/')
-  {
-    if (symlink(oldpath, newpath) == -1)
-      return -1;
-  }
-  else
-  {
-    char abs_oldpath[_POSIX_PATH_MAX];
-
-    if ((getcwd(abs_oldpath, sizeof(abs_oldpath)) == NULL) ||
-        (strlen(abs_oldpath) + 1 + strlen(oldpath) + 1 > sizeof(abs_oldpath)))
-      return -1;
-
-    strcat(abs_oldpath, "/");
-    strcat(abs_oldpath, oldpath);
-    if (symlink(abs_oldpath, newpath) == -1)
-      return -1;
-  }
-
-  if (stat(oldpath, &osb) == -1 || stat(newpath, &nsb) == -1 || !compare_stat(&osb, &nsb))
-  {
-    unlink(newpath);
-    return -1;
-  }
-
-  return 0;
-}
-
-
-/**
- * safe_rename - NFS-safe renaming of files
- *
- * Warning: We don't check whether src and target are equal.
- */
-int safe_rename(const char *src, const char *target)
-{
-  struct stat ssb, tsb;
-
-  if (!src || !target)
-    return -1;
-
-  if (link(src, target) != 0)
-  {
-    /*
-     * Coda does not allow cross-directory links, but tells
-     * us it's a cross-filesystem linking attempt.
-     *
-     * However, the Coda rename call is allegedly safe to use.
-     *
-     * With other file systems, rename should just fail when
-     * the files reside on different file systems, so it's safe
-     * to try it here.
-     *
-     */
-
-    mutt_debug(1, "safe_rename: link (%s, %s) failed: %s (%d)\n", src, target,
-               strerror(errno), errno);
-
-    /*
-     * FUSE may return ENOSYS. VFAT may return EPERM. FreeBSD's
-     * msdosfs may return EOPNOTSUPP.  ENOTSUP can also appear.
-     */
-    if (errno == EXDEV || errno == ENOSYS || errno == EPERM
-#ifdef ENOTSUP
-        || errno == ENOTSUP
-#endif
-#ifdef EOPNOTSUPP
-        || errno == EOPNOTSUPP
-#endif
-        )
-    {
-      mutt_debug(1, "safe_rename: trying rename...\n");
-      if (rename(src, target) == -1)
-      {
-        mutt_debug(1, "safe_rename: rename (%s, %s) failed: %s (%d)\n", src,
-                   target, strerror(errno), errno);
-        return -1;
-      }
-      mutt_debug(1, "safe_rename: rename succeeded.\n");
-
-      return 0;
-    }
-
-    return -1;
-  }
-
-  /*
-   * Stat both links and check if they are equal.
-   */
-
-  if (lstat(src, &ssb) == -1)
-  {
-    mutt_debug(1, "safe_rename: can't stat %s: %s (%d)\n", src, strerror(errno), errno);
-    return -1;
-  }
-
-  if (lstat(target, &tsb) == -1)
-  {
-    mutt_debug(1, "safe_rename: can't stat %s: %s (%d)\n", src, strerror(errno), errno);
-    return -1;
-  }
-
-  /*
-   * pretend that the link failed because the target file
-   * did already exist.
-   */
-
-  if (!compare_stat(&ssb, &tsb))
-  {
-    mutt_debug(1, "safe_rename: stat blocks for %s and %s diverge; "
-                  "pretending EEXIST.\n",
-               src, target);
-    errno = EEXIST;
-    return -1;
-  }
-
-  /*
-   * Unlink the original link.  Should we really ignore the return
-   * value here? XXX
-   */
-
-  if (unlink(src) == -1)
-  {
-    mutt_debug(1, "safe_rename: unlink (%s) failed: %s (%d)\n", src, strerror(errno), errno);
-  }
-
-
-  return 0;
-}
-
-
-/**
- * mkwrapdir - Create a temporary directory next to a file name
- */
-static int mkwrapdir(const char *path, char *newfile, size_t nflen, char *newdir, size_t ndlen)
-{
-  const char *basename = NULL;
-  char parent[_POSIX_PATH_MAX];
-  char *p = NULL;
-
-  strfcpy(parent, NONULL(path), sizeof(parent));
-
-  if ((p = strrchr(parent, '/')))
-  {
-    *p = '\0';
-    basename = p + 1;
-  }
-  else
-  {
-    strfcpy(parent, ".", sizeof(parent));
-    basename = path;
-  }
-
-  snprintf(newdir, ndlen, "%s/%s", parent, ".muttXXXXXX");
-  if (mkdtemp(newdir) == NULL)
-  {
-    mutt_debug(1, "mkwrapdir: mkdtemp() failed\n");
-    return -1;
-  }
-
-  if (snprintf(newfile, nflen, "%s/%s", newdir, NONULL(basename)) >= nflen)
-  {
-    rmdir(newdir);
-    mutt_debug(1, "mkwrapdir: string was truncated\n");
-    return -1;
-  }
-  return 0;
-}
-
-/**
- * mutt_rmtree - remove a directory and everything under it
- */
-int mutt_rmtree(const char *path)
-{
-  DIR *dirp = NULL;
-  struct dirent *de = NULL;
-  char cur[_POSIX_PATH_MAX];
-  struct stat statbuf;
-  int rc = 0;
-
-  if (!(dirp = opendir(path)))
-  {
-    mutt_debug(1, "mutt_rmtree: error opening directory %s\n", path);
-    return -1;
-  }
-  while ((de = readdir(dirp)))
-  {
-    if ((strcmp(".", de->d_name) == 0) || (strcmp("..", de->d_name) == 0))
-      continue;
-
-    snprintf(cur, sizeof(cur), "%s/%s", path, de->d_name);
-    /* XXX make nonrecursive version */
-
-    if (stat(cur, &statbuf) == -1)
-    {
-      rc = 1;
-      continue;
-    }
-
-    if (S_ISDIR(statbuf.st_mode))
-      rc |= mutt_rmtree(cur);
-    else
-      rc |= unlink(cur);
-  }
-  closedir(dirp);
-
-  rc |= rmdir(path);
-
-  return rc;
-}
-
-static int put_file_in_place(const char *path, const char *safe_file, const char *safe_dir)
-{
-  int rv;
-
-  rv = safe_rename(safe_file, path);
-  unlink(safe_file);
-  rmdir(safe_dir);
-  return rv;
-}
-
-int safe_open(const char *path, int flags)
-{
-  struct stat osb, nsb;
-  int fd;
-
-  if (flags & O_EXCL)
-  {
-    char safe_file[_POSIX_PATH_MAX];
-    char safe_dir[_POSIX_PATH_MAX];
-
-    if (mkwrapdir(path, safe_file, sizeof(safe_file), safe_dir, sizeof(safe_dir)) == -1)
-      return -1;
-
-    if ((fd = open(safe_file, flags, 0600)) < 0)
-    {
-      rmdir(safe_dir);
-      return fd;
-    }
-
-    /* NFS and I believe cygwin do not handle movement of open files well */
-    close(fd);
-    if (put_file_in_place(path, safe_file, safe_dir) == -1)
-      return -1;
-  }
-
-  if ((fd = open(path, flags & ~O_EXCL, 0600)) < 0)
-    return fd;
-
-  /* make sure the file is not symlink */
-  if (lstat(path, &osb) < 0 || fstat(fd, &nsb) < 0 || !compare_stat(&osb, &nsb))
-  {
-    close(fd);
-    return -1;
-  }
-
-  return fd;
-}
-
-/**
- * safe_fopen - Call fopen() safely
- *
- * when opening files for writing, make sure the file doesn't already exist to
- * avoid race conditions.
- */
-FILE *safe_fopen(const char *path, const char *mode)
-{
-  if (mode[0] == 'w')
-  {
-    int fd;
-    int flags = O_CREAT | O_EXCL;
-
-#ifdef O_NOFOLLOW
-    flags |= O_NOFOLLOW;
-#endif
-
-    if (mode[1] == '+')
-      flags |= O_RDWR;
-    else
-      flags |= O_WRONLY;
-
-    if ((fd = safe_open(path, flags)) < 0)
-      return NULL;
-
-    return (fdopen(fd, mode));
-  }
-  else
-    return (fopen(path, mode));
-}
-
-static const char safe_chars[] =
-    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+@{}._-:%/";
-
-void mutt_sanitize_filename(char *f, short slash)
-{
-  if (!f)
-    return;
-
-  for (; *f; f++)
-  {
-    if ((slash && *f == '/') || !strchr(safe_chars, *f))
-      *f = '_';
-  }
-}
-
-/* these characters must be escaped in regular expressions */
-
-static const char rx_special_chars[] = "^.[$()|*+?{\\";
-
-int mutt_rx_sanitize_string(char *dest, size_t destlen, const char *src)
-{
-  while (*src && --destlen > 2)
-  {
-    if (strchr(rx_special_chars, *src))
-    {
-      *dest++ = '\\';
-      destlen--;
-    }
-    *dest++ = *src++;
-  }
-
-  *dest = '\0';
-
-  if (*src)
-    return -1;
-  else
-    return 0;
-}
-
-/**
- * mutt_read_line - Read a line from a file
- *
- * Read a line from ``fp'' into the dynamically allocated ``s'', increasing
- * ``s'' if necessary. The ending "\n" or "\r\n" is removed.  If a line ends
- * with "\", this char and the linefeed is removed, and the next line is read
- * too.
- */
-char *mutt_read_line(char *s, size_t *size, FILE *fp, int *line, int flags)
-{
-  size_t offset = 0;
-  char *ch = NULL;
-
-  if (!s)
-  {
-    s = safe_malloc(STRING);
-    *size = STRING;
-  }
-
-  while (true)
-  {
-    if (fgets(s + offset, *size - offset, fp) == NULL)
-    {
-      FREE(&s);
-      return NULL;
-    }
-    if ((ch = strchr(s + offset, '\n')) != NULL)
-    {
-      if (line)
-        (*line)++;
-      if (flags & MUTT_EOL)
-        return s;
-      *ch = 0;
-      if (ch > s && *(ch - 1) == '\r')
-        *--ch = 0;
-      if (!(flags & MUTT_CONT) || ch == s || *(ch - 1) != '\\')
-        return s;
-      offset = ch - s - 1;
-    }
-    else
-    {
-      int c;
-      c = getc(fp); /* This is kind of a hack. We want to know if the
-                        char at the current point in the input stream is EOF.
-                        feof() will only tell us if we've already hit EOF, not
-                        if the next character is EOF. So, we need to read in
-                        the next character and manually check if it is EOF. */
-      if (c == EOF)
-      {
-        /* The last line of fp isn't \n terminated */
-        if (line)
-          (*line)++;
-        return s;
-      }
-      else
-      {
-        ungetc(c, fp); /* undo our damage */
-        /* There wasn't room for the line -- increase ``s'' */
-        offset = *size - 1; /* overwrite the terminating 0 */
-        *size += STRING;
-        safe_realloc(&s, *size);
-      }
-    }
-  }
-}
-
-/**
- * mutt_quote_filename - Quote a filename to survive the shell's quoting rules
- *
- * From the Unix programming FAQ by way of Liviu.
- */
-size_t mutt_quote_filename(char *d, size_t l, const char *f)
-{
-  size_t j = 0;
-
-  if (!f)
-  {
-    *d = '\0';
-    return 0;
-  }
-
-  /* leave some space for the trailing characters. */
-  l -= 6;
-
-  d[j++] = '\'';
-
-  for (size_t i = 0; j < l && f[i]; i++)
-  {
-    if (f[i] == '\'' || f[i] == '`')
-    {
-      d[j++] = '\'';
-      d[j++] = '\\';
-      d[j++] = f[i];
-      d[j++] = '\'';
-    }
-    else
-      d[j++] = f[i];
-  }
-
-  d[j++] = '\'';
-  d[j] = '\0';
-
-  return j;
-}
-
-/**
- * mutt_concatn_path - Concatenate directory and filename
- * @param dst      Buffer for result
- * @param dstlen   Buffer length
- * @param dir      Directory
- * @param dirlen   Directory length
- * @param fname    Filename
- * @param fnamelen Filename length
- * @retval NULL Error
- * @retval ptr  Pointer to \a dst on success
- *
- * Write the concatenated pathname (dir + "/" + fname) into dst.
- * The slash is omitted when dir or fname is of 0 length.
- */
-char *mutt_concatn_path(char *dst, size_t dstlen, const char *dir,
-                        size_t dirlen, const char *fname, size_t fnamelen)
-{
-  size_t req;
-  size_t offset = 0;
-
-  if (dstlen == 0)
-    return NULL; /* probably should not mask errors like this */
-
-  /* size check */
-  req = dirlen + fnamelen + 1; /* +1 for the trailing nul */
-  if (dirlen && fnamelen)
-    req++; /* when both components are non-nul, we add a "/" in between */
-  if (req > dstlen)
-  { /* check for condition where the dst length is too short */
-    /* Two options here:
-     * 1) assert(0) or return NULL to signal error
-     * 2) copy as much of the path as will fit
-     * It doesn't appear that the return value is actually checked anywhere mutt_concat_path()
-     * is called, so we should just copy set dst to nul and let the calling function fail later.
-     */
-    dst[0] = 0; /* safe since we bail out early if dstlen == 0 */
-    return NULL;
-  }
-
-  if (dirlen)
-  { /* when dir is not empty */
-    memcpy(dst, dir, dirlen);
-    offset = dirlen;
-    if (fnamelen)
-      dst[offset++] = '/';
-  }
-  if (fnamelen)
-  { /* when fname is not empty */
-    memcpy(dst + offset, fname, fnamelen);
-    offset += fnamelen;
-  }
-  dst[offset] = 0;
-  return dst;
-}
-
-char *mutt_concat_path(char *d, const char *dir, const char *fname, size_t l)
-{
-  const char *fmt = "%s/%s";
-
-  if (!*fname || (*dir && dir[strlen(dir) - 1] == '/'))
-    fmt = "%s%s";
-
-  snprintf(d, l, fmt, dir, fname);
-  return d;
-}
-
-const char *mutt_basename(const char *f)
-{
-  const char *p = strrchr(f, '/');
-  if (p)
-    return p + 1;
-  else
-    return f;
-}
-
 const char *mutt_strsysexit(int e)
 {
   int i;
@@ -859,61 +219,3 @@ int mutt_inbox_cmp(const char *a, const char *b)
   return 0;
 }
 
-/**
- * mutt_mkdir - Recursively create directories
- * @param path Directories to create
- * @param mode Permissions for final directory
- * @retval    0  Success
- * @retval   -1  Error (errno set)
- *
- * Create a directory, creating the parents if necessary. (like mkdir -p)
- *
- * @note The permissions are only set on the final directory.
- *       The permissions of any parent directories are determined by the umask.
- *       (This is how "mkdir -p" behaves)
- */
-int mutt_mkdir(const char *path, mode_t mode)
-{
-  if (!path || !*path)
-  {
-    errno = EINVAL;
-    return -1;
-  }
-
-  errno = 0;
-  char *p = NULL;
-  char _path[PATH_MAX];
-  const size_t len = strlen(path);
-
-  if (len >= sizeof(_path))
-  {
-    errno = ENAMETOOLONG;
-    return -1;
-  }
-
-  struct stat sb;
-  if ((stat(path, &sb) == 0) && S_ISDIR(sb.st_mode))
-    return 0;
-
-  /* Create a mutable copy */
-  strfcpy(_path, path, sizeof(_path));
-
-  for (p = _path + 1; *p; p++)
-  {
-    if (*p != '/')
-      continue;
-
-    /* Temporarily truncate the path */
-    *p = '\0';
-
-    if ((mkdir(_path, S_IRWXU | S_IRWXG | S_IRWXO) != 0) && (errno != EEXIST))
-      return -1;
-
-    *p = '/';
-  }
-
-  if ((mkdir(_path, mode) != 0) && (errno != EEXIST))
-    return -1;
-
-  return 0;
-}
diff --git a/lib.h b/lib.h
index dac61a6843097010c4a675b459ee4f711d90d90b..0935b84f3dbb569a8c7d6efc042ad44c8326ccb1 100644 (file)
--- a/lib.h
+++ b/lib.h
 #include <string.h>
 #include <sys/types.h>
 
-#undef MAX
-#undef MIN
-#define MAX(a, b) ((a) < (b) ? (b) : (a))
-#define MIN(a, b) ((a) < (b) ? (a) : (b))
-
 /* Use this with care.  If the compiler can't see the array
  * definition, it obviously won't produce a correct result. */
 #define mutt_array_size(x) (sizeof(x) / sizeof((x)[0]))
@@ -61,38 +56,9 @@ extern int debuglevel_cmdline;
 #define S_ERR 127
 #define S_BKG 126
 
-/* Flags for mutt_read_line() */
-#define MUTT_CONT (1 << 0) /**< \-continuation */
-#define MUTT_EOL  (1 << 1) /**< don't strip `\n` / `\r\n` */
-
-/* The actual library functions. */
-
-FILE *safe_fopen(const char *path, const char *mode);
-
-char *mutt_concatn_path(char *dst, size_t dstlen, const char *dir,
-                        size_t dirlen, const char *fname, size_t fnamelen);
-char *mutt_concat_path(char *d, const char *dir, const char *fname, size_t l);
-char *mutt_read_line(char *s, size_t *size, FILE *fp, int *line, int flags);
-
-const char *mutt_basename(const char *f);
-
-int mutt_copy_stream(FILE *fin, FILE *fout);
-int mutt_copy_bytes(FILE *in, FILE *out, size_t size);
-int mutt_rx_sanitize_string(char *dest, size_t destlen, const char *src);
 int safe_asprintf(char **, const char *, ...);
-int safe_open(const char *path, int flags);
-int safe_rename(const char *src, const char *target);
-int safe_symlink(const char *oldpath, const char *newpath);
-int safe_fclose(FILE **f);
-int safe_fsync_close(FILE **f);
-int mutt_rmtree(const char *path);
-int mutt_mkdir(const char *path, mode_t mode);
-
-size_t mutt_quote_filename(char *d, size_t l, const char *f);
 
 void mutt_nocurses_error(const char *, ...);
-void mutt_sanitize_filename(char *f, short slash);
-void mutt_unlink(const char *s);
 int mutt_inbox_cmp(const char *a, const char *b);
 
 const char *mutt_strsysexit(int e);
index 81728a28126d8b2ea5c11283ebccd78f9c48b8b6..83526af1cf47a13719c84a6fcb8cfca6873d50c9 100644 (file)
@@ -3,12 +3,12 @@ include $(top_srcdir)/flymake.am
 
 AUTOMAKE_OPTIONS = 1.6 foreign
 
-EXTRA_DIST = lib.h lib_ascii.h lib_base64.h lib_date.h lib_debug.h lib_exit.h lib_md5.h lib_memory.h lib_message.h lib_sha1.h lib_string.h
+EXTRA_DIST = lib.h lib_ascii.h lib_base64.h lib_date.h lib_debug.h lib_exit.h lib_file.h lib_md5.h lib_memory.h lib_message.h lib_sha1.h lib_string.h
 
 AM_CPPFLAGS = -I$(top_srcdir)
 
 noinst_LIBRARIES = liblib.a
 noinst_HEADERS =
 
-liblib_a_SOURCES = lib_ascii.c lib_base64.c lib_date.c lib_debug.c lib_exit.c lib_md5.c lib_memory.c lib_message.c lib_sha1.c lib_string.c
+liblib_a_SOURCES = lib_ascii.c lib_base64.c lib_date.c lib_debug.c lib_exit.c lib_file.c lib_md5.c lib_memory.c lib_message.c lib_sha1.c lib_string.c
 
index ba9da80bb8a5e8adfa51e6835d2cbec452649e41..4986a69e48d3dc0fe62a742ecb0773c4b06bca4c 100644 (file)
--- a/lib/lib.h
+++ b/lib/lib.h
@@ -33,6 +33,7 @@
  * -# @subpage date
  * -# @subpage debug
  * -# @subpage exit
+ * -# @subpage file
  * -# @subpage md5
  * -# @subpage memory
  * -# @subpage message
@@ -48,6 +49,7 @@
 #include "lib_date.h"
 #include "lib_debug.h"
 #include "lib_exit.h"
+#include "lib_file.h"
 #include "lib_md5.h"
 #include "lib_memory.h"
 #include "lib_message.h"
diff --git a/lib/lib_file.c b/lib/lib_file.c
new file mode 100644 (file)
index 0000000..e335928
--- /dev/null
@@ -0,0 +1,872 @@
+/**
+ * @file
+ * File management functions
+ *
+ * @authors
+ * Copyright (C) 2017 Richard Russon <rich@flatcap.org>
+ *
+ * @copyright
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * This program 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 General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @page file File management functions
+ *
+ * Commonly used file/dir management routines.
+ *
+ * | Function                  | Description
+ * | :------------------------ | :-----------------------------------------------------------
+ * | mutt_basename()           | Find the last component for a pathname
+ * | mutt_concatn_path()       | Concatenate directory and filename
+ * | mutt_concat_path()        | Join a directory name and a filename
+ * | mutt_copy_bytes()         | Copy some content from one file to another
+ * | mutt_copy_stream()        | Copy the contents of one file into another
+ * | mutt_mkdir()              | Recursively create directories
+ * | mutt_quote_filename()     | Quote a filename to survive the shell's quoting rules
+ * | mutt_read_line()          | Read a line from a file
+ * | mutt_rmtree()             | Recursively remove a directory
+ * | mutt_rx_sanitize_string() | Escape any regex-magic characters in a string
+ * | mutt_sanitize_filename()  | Replace unsafe characters in a filename
+ * | mutt_unlink()             | Delete a file, carefully
+ * | safe_fclose()             | Close a FILE handle (and NULL the pointer)
+ * | safe_fopen()              | Call fopen() safely
+ * | safe_fsync_close()        | Flush the data, before closing a file (and NULL the pointer)
+ * | safe_open()               | Open a file
+ * | safe_rename()             | NFS-safe renaming of files
+ * | safe_symlink()            | Create a symlink
+ */
+
+#include "config.h"
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include "lib_file.h"
+#include "lib_debug.h"
+#include "lib_memory.h"
+#include "lib_string.h"
+
+/* these characters must be escaped in regular expressions */
+static const char rx_special_chars[] = "^.[$()|*+?{\\";
+
+static const char safe_chars[] =
+    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+@{}._-:%/";
+
+/**
+ * compare_stat - Compare the struct stat's of two files/dirs
+ * @param osb struct stat of the first file/dir
+ * @param nsb struct stat of the second file/dir
+ * @retval boolean
+ *
+ * This compares the device id (st_dev), inode number (st_ino) and special id
+ * (st_rdev) of the files/dirs.
+ */
+static bool compare_stat(struct stat *osb, struct stat *nsb)
+{
+  return ((osb->st_dev == nsb->st_dev) && (osb->st_ino == nsb->st_ino) &&
+          (osb->st_rdev == nsb->st_rdev));
+}
+
+/**
+ * mkwrapdir - Create a temporary directory next to a file name
+ * @param path    Existing filename
+ * @param newfile New filename
+ * @param nflen   Length of new filename
+ * @param newdir  New directory name
+ * @param ndlen   Length of new directory name
+ * @retval  0 Succes 0 Success
+ * @retval -1 Error
+ */
+static int mkwrapdir(const char *path, char *newfile, size_t nflen, char *newdir, size_t ndlen)
+{
+  const char *basename = NULL;
+  char parent[_POSIX_PATH_MAX];
+  char *p = NULL;
+
+  strfcpy(parent, NONULL(path), sizeof(parent));
+
+  p = strrchr(parent, '/');
+  if (p)
+  {
+    *p = '\0';
+    basename = p + 1;
+  }
+  else
+  {
+    strfcpy(parent, ".", sizeof(parent));
+    basename = path;
+  }
+
+  snprintf(newdir, ndlen, "%s/%s", parent, ".muttXXXXXX");
+  if (mkdtemp(newdir) == NULL)
+  {
+    mutt_debug(1, "mkwrapdir: mkdtemp() failed\n");
+    return -1;
+  }
+
+  if (snprintf(newfile, nflen, "%s/%s", newdir, NONULL(basename)) >= nflen)
+  {
+    rmdir(newdir);
+    mutt_debug(1, "mkwrapdir: string was truncated\n");
+    return -1;
+  }
+  return 0;
+}
+
+/**
+ * put_file_in_place - Move a file into place
+ * @param path      Destination path
+ * @param safe_file Current filename
+ * @param safe_dir  Current directory name
+ * @retval  0 Success
+ * @retval -1 Error, see errno
+ */
+static int put_file_in_place(const char *path, const char *safe_file, const char *safe_dir)
+{
+  int rv;
+
+  rv = safe_rename(safe_file, path);
+  unlink(safe_file);
+  rmdir(safe_dir);
+  return rv;
+}
+
+/**
+ * safe_fclose - Close a FILE handle (and NULL the pointer)
+ * @param f FILE handle to close
+ * @retval 0   Success
+ * @retval EOF Error, see errno
+ */
+int safe_fclose(FILE **f)
+{
+  int r = 0;
+
+  if (*f)
+    r = fclose(*f);
+
+  *f = NULL;
+  return r;
+}
+
+/**
+ * safe_fsync_close - Flush the data, before closing a file (and NULL the pointer)
+ * @param f FILE handle to close
+ * @retval 0   Success
+ * @retval EOF Error, see errno
+ */
+int safe_fsync_close(FILE **f)
+{
+  int r = 0;
+
+  if (*f)
+  {
+    if (fflush(*f) || fsync(fileno(*f)))
+    {
+      int save_errno = errno;
+      r = -1;
+      safe_fclose(f);
+      errno = save_errno;
+    }
+    else
+      r = safe_fclose(f);
+  }
+
+  return r;
+}
+
+/**
+ * mutt_unlink - Delete a file, carefully
+ * @param s Filename
+ *
+ * This won't follow symlinks.
+ */
+void mutt_unlink(const char *s)
+{
+  int fd;
+  int flags;
+  FILE *f = NULL;
+  struct stat sb, sb2;
+  char buf[2048];
+
+/* Defend against symlink attacks */
+
+#ifdef O_NOFOLLOW
+  flags = O_RDWR | O_NOFOLLOW;
+#else
+  flags = O_RDWR;
+#endif
+
+  if ((lstat(s, &sb) == 0) && S_ISREG(sb.st_mode))
+  {
+    fd = open(s, flags);
+    if (fd < 0)
+      return;
+
+    if ((fstat(fd, &sb2) != 0) || !S_ISREG(sb2.st_mode) ||
+        (sb.st_dev != sb2.st_dev) || (sb.st_ino != sb2.st_ino))
+    {
+      close(fd);
+      return;
+    }
+
+    f = fdopen(fd, "r+");
+    if (f)
+    {
+      unlink(s);
+      memset(buf, 0, sizeof(buf));
+      while (sb.st_size > 0)
+      {
+        fwrite(buf, 1, MIN(sizeof(buf), sb.st_size), f);
+        sb.st_size -= MIN(sizeof(buf), sb.st_size);
+      }
+      safe_fclose(&f);
+    }
+  }
+}
+
+/**
+ * mutt_copy_bytes - Copy some content from one file to another
+ * @param in   Source file
+ * @param out  Destination file
+ * @param size Maximum number of bytes to copy
+ * @retval  0 Success
+ * @retval -1 Error, see errno
+ */
+int mutt_copy_bytes(FILE *in, FILE *out, size_t size)
+{
+  char buf[2048];
+  size_t chunk;
+
+  while (size > 0)
+  {
+    chunk = (size > sizeof(buf)) ? sizeof(buf) : size;
+    chunk = fread(buf, 1, chunk, in);
+    if (chunk < 1)
+      break;
+    if (fwrite(buf, 1, chunk, out) != chunk)
+      return -1;
+
+    size -= chunk;
+  }
+
+  if (fflush(out) != 0)
+    return -1;
+  return 0;
+}
+
+/**
+ * mutt_copy_stream - Copy the contents of one file into another
+ * @param fin  Source file
+ * @param fout Destination file
+ * @retval  0 Success
+ * @retval -1 Error, see errno
+ */
+int mutt_copy_stream(FILE *fin, FILE *fout)
+{
+  size_t l;
+  char buf[LONG_STRING];
+
+  while ((l = fread(buf, 1, sizeof(buf), fin)) > 0)
+  {
+    if (fwrite(buf, 1, l, fout) != l)
+      return -1;
+  }
+
+  if (fflush(fout) != 0)
+    return -1;
+  return 0;
+}
+
+/**
+ * safe_symlink - Create a symlink
+ * @param oldpath Existing pathname
+ * @param newpath New pathname
+ * @retval  0 Success
+ * @retval -1 Error, see errno
+ */
+int safe_symlink(const char *oldpath, const char *newpath)
+{
+  struct stat osb, nsb;
+
+  if (!oldpath || !newpath)
+    return -1;
+
+  if ((unlink(newpath) == -1) && (errno != ENOENT))
+    return -1;
+
+  if (oldpath[0] == '/')
+  {
+    if (symlink(oldpath, newpath) == -1)
+      return -1;
+  }
+  else
+  {
+    char abs_oldpath[_POSIX_PATH_MAX];
+
+    if ((getcwd(abs_oldpath, sizeof(abs_oldpath)) == NULL) ||
+        ((strlen(abs_oldpath) + 1 + strlen(oldpath) + 1) > sizeof(abs_oldpath)))
+    {
+      return -1;
+    }
+
+    strcat(abs_oldpath, "/");
+    strcat(abs_oldpath, oldpath);
+    if (symlink(abs_oldpath, newpath) == -1)
+      return -1;
+  }
+
+  if ((stat(oldpath, &osb) == -1) || (stat(newpath, &nsb) == -1) || !compare_stat(&osb, &nsb))
+  {
+    unlink(newpath);
+    return -1;
+  }
+
+  return 0;
+}
+
+/**
+ * safe_rename - NFS-safe renaming of files
+ * @param src    Original filename
+ * @param target New filename
+ * @retval  0 Success
+ * @retval -1 Error, see errno
+ *
+ * Warning: We don't check whether src and target are equal.
+ */
+int safe_rename(const char *src, const char *target)
+{
+  struct stat ssb, tsb;
+
+  if (!src || !target)
+    return -1;
+
+  if (link(src, target) != 0)
+  {
+    /* Coda does not allow cross-directory links, but tells
+     * us it's a cross-filesystem linking attempt.
+     *
+     * However, the Coda rename call is allegedly safe to use.
+     *
+     * With other file systems, rename should just fail when
+     * the files reside on different file systems, so it's safe
+     * to try it here. */
+    mutt_debug(1, "safe_rename: link (%s, %s) failed: %s (%d)\n", src, target,
+               strerror(errno), errno);
+
+    /* FUSE may return ENOSYS. VFAT may return EPERM. FreeBSD's
+     * msdosfs may return EOPNOTSUPP.  ENOTSUP can also appear. */
+    if (errno == EXDEV || errno == ENOSYS || errno == EPERM
+#ifdef ENOTSUP
+        || errno == ENOTSUP
+#endif
+#ifdef EOPNOTSUPP
+        || errno == EOPNOTSUPP
+#endif
+        )
+    {
+      mutt_debug(1, "safe_rename: trying rename...\n");
+      if (rename(src, target) == -1)
+      {
+        mutt_debug(1, "safe_rename: rename (%s, %s) failed: %s (%d)\n", src,
+                   target, strerror(errno), errno);
+        return -1;
+      }
+      mutt_debug(1, "safe_rename: rename succeeded.\n");
+
+      return 0;
+    }
+
+    return -1;
+  }
+
+  /* Stat both links and check if they are equal. */
+
+  if (lstat(src, &ssb) == -1)
+  {
+    mutt_debug(1, "safe_rename: can't stat %s: %s (%d)\n", src, strerror(errno), errno);
+    return -1;
+  }
+
+  if (lstat(target, &tsb) == -1)
+  {
+    mutt_debug(1, "safe_rename: can't stat %s: %s (%d)\n", src, strerror(errno), errno);
+    return -1;
+  }
+
+  /* pretend that the link failed because the target file did already exist. */
+
+  if (!compare_stat(&ssb, &tsb))
+  {
+    mutt_debug(1, "safe_rename: stat blocks for %s and %s diverge; "
+                  "pretending EEXIST.\n",
+               src, target);
+    errno = EEXIST;
+    return -1;
+  }
+
+  /* Unlink the original link.
+   * Should we really ignore the return value here? XXX */
+  if (unlink(src) == -1)
+  {
+    mutt_debug(1, "safe_rename: unlink (%s) failed: %s (%d)\n", src, strerror(errno), errno);
+  }
+
+  return 0;
+}
+
+/**
+ * mutt_rmtree - Recursively remove a directory
+ * @param path Directory to delete
+ * @retval  0 Success
+ * @retval -1 Error, see errno
+ */
+int mutt_rmtree(const char *path)
+{
+  DIR *dirp = NULL;
+  struct dirent *de = NULL;
+  char cur[_POSIX_PATH_MAX];
+  struct stat statbuf;
+  int rc = 0;
+
+  dirp = opendir(path);
+  if (!dirp)
+  {
+    mutt_debug(1, "mutt_rmtree: error opening directory %s\n", path);
+    return -1;
+  }
+  while ((de = readdir(dirp)))
+  {
+    if ((strcmp(".", de->d_name) == 0) || (strcmp("..", de->d_name) == 0))
+      continue;
+
+    snprintf(cur, sizeof(cur), "%s/%s", path, de->d_name);
+    /* XXX make nonrecursive version */
+
+    if (stat(cur, &statbuf) == -1)
+    {
+      rc = 1;
+      continue;
+    }
+
+    if (S_ISDIR(statbuf.st_mode))
+      rc |= mutt_rmtree(cur);
+    else
+      rc |= unlink(cur);
+  }
+  closedir(dirp);
+
+  rc |= rmdir(path);
+
+  return rc;
+}
+
+/**
+ * safe_open - Open a file
+ * @param path  Pathname to open
+ * @param flags Flags, e.g. O_EXCL
+ * @retval >0 Success, file handle
+ * @retval -1 Error
+ */
+int safe_open(const char *path, int flags)
+{
+  struct stat osb, nsb;
+  int fd;
+
+  if (flags & O_EXCL)
+  {
+    char safe_file[_POSIX_PATH_MAX];
+    char safe_dir[_POSIX_PATH_MAX];
+
+    if (mkwrapdir(path, safe_file, sizeof(safe_file), safe_dir, sizeof(safe_dir)) == -1)
+      return -1;
+
+    fd = open(safe_file, flags, 0600);
+    if (fd < 0)
+    {
+      rmdir(safe_dir);
+      return fd;
+    }
+
+    /* NFS and I believe cygwin do not handle movement of open files well */
+    close(fd);
+    if (put_file_in_place(path, safe_file, safe_dir) == -1)
+      return -1;
+  }
+
+  fd = open(path, flags & ~O_EXCL, 0600);
+  if (fd < 0)
+    return fd;
+
+  /* make sure the file is not symlink */
+  if ((lstat(path, &osb) < 0 || fstat(fd, &nsb) < 0) || !compare_stat(&osb, &nsb))
+  {
+    close(fd);
+    return -1;
+  }
+
+  return fd;
+}
+
+/**
+ * safe_fopen - Call fopen() safely
+ * @param path Filename
+ * @param mode Mode e.g. "r" readonly; "w" read-write
+ * @retval ptr  FILE handle
+ * @retval NULL Error, see errno
+ *
+ * When opening files for writing, make sure the file doesn't already exist to
+ * avoid race conditions.
+ */
+FILE *safe_fopen(const char *path, const char *mode)
+{
+  if (mode[0] == 'w')
+  {
+    int fd;
+    int flags = O_CREAT | O_EXCL;
+
+#ifdef O_NOFOLLOW
+    flags |= O_NOFOLLOW;
+#endif
+
+    if (mode[1] == '+')
+      flags |= O_RDWR;
+    else
+      flags |= O_WRONLY;
+
+    fd = safe_open(path, flags);
+    if (fd < 0)
+      return NULL;
+
+    return (fdopen(fd, mode));
+  }
+  else
+    return (fopen(path, mode));
+}
+
+/**
+ * mutt_sanitize_filename - Replace unsafe characters in a filename
+ * @param f     Filename to make safe
+ * @param slash Replace '/' characters too
+ */
+void mutt_sanitize_filename(char *f, short slash)
+{
+  if (!f)
+    return;
+
+  for (; *f; f++)
+  {
+    if ((slash && *f == '/') || !strchr(safe_chars, *f))
+      *f = '_';
+  }
+}
+
+/**
+ * mutt_rx_sanitize_string - Escape any regex-magic characters in a string
+ * @param dest    Buffer for result
+ * @param destlen Length of buffer
+ * @param src     String to transform
+ * @retval  0 Success
+ * @retval -1 Error
+ */
+int mutt_rx_sanitize_string(char *dest, size_t destlen, const char *src)
+{
+  while (*src && (--destlen > 2))
+  {
+    if (strchr(rx_special_chars, *src))
+    {
+      *dest++ = '\\';
+      destlen--;
+    }
+    *dest++ = *src++;
+  }
+
+  *dest = '\0';
+
+  if (*src)
+    return -1;
+  else
+    return 0;
+}
+
+/**
+ * mutt_read_line - Read a line from a file
+ * @param[out] s     Buffer allocated on the head (optional)
+ * @param[in]  size  Length of buffer (optional)
+ * @param[in]  fp    File to read
+ * @param[out] line  Current line number
+ * @param[in]  flags Flags, e.g. #MUTT_CONT
+ * @retval ptr The allocated string
+ *
+ * Read a line from ``fp'' into the dynamically allocated ``s'', increasing
+ * ``s'' if necessary. The ending "\n" or "\r\n" is removed.  If a line ends
+ * with "\", this char and the linefeed is removed, and the next line is read
+ * too.
+ */
+char *mutt_read_line(char *s, size_t *size, FILE *fp, int *line, int flags)
+{
+  size_t offset = 0;
+  char *ch = NULL;
+
+  if (!s)
+  {
+    s = safe_malloc(STRING);
+    *size = STRING;
+  }
+
+  while (true)
+  {
+    if (fgets(s + offset, *size - offset, fp) == NULL)
+    {
+      FREE(&s);
+      return NULL;
+    }
+    if ((ch = strchr(s + offset, '\n')) != NULL)
+    {
+      if (line)
+        (*line)++;
+      if (flags & MUTT_EOL)
+        return s;
+      *ch = 0;
+      if ((ch > s) && (*(ch - 1) == '\r'))
+        *--ch = 0;
+      if (!(flags & MUTT_CONT) || (ch == s) || (*(ch - 1) != '\\'))
+        return s;
+      offset = ch - s - 1;
+    }
+    else
+    {
+      int c;
+      c = getc(fp); /* This is kind of a hack. We want to know if the
+                        char at the current point in the input stream is EOF.
+                        feof() will only tell us if we've already hit EOF, not
+                        if the next character is EOF. So, we need to read in
+                        the next character and manually check if it is EOF. */
+      if (c == EOF)
+      {
+        /* The last line of fp isn't \n terminated */
+        if (line)
+          (*line)++;
+        return s;
+      }
+      else
+      {
+        ungetc(c, fp); /* undo our damage */
+        /* There wasn't room for the line -- increase ``s'' */
+        offset = *size - 1; /* overwrite the terminating 0 */
+        *size += STRING;
+        safe_realloc(&s, *size);
+      }
+    }
+  }
+}
+
+/**
+ * mutt_quote_filename - Quote a filename to survive the shell's quoting rules
+ * @param d Buffer for the result
+ * @param l Length of buffer
+ * @param f String to convert
+ * @retval num Number of bytes written to the buffer
+ *
+ * From the Unix programming FAQ by way of Liviu.
+ */
+size_t mutt_quote_filename(char *d, size_t l, const char *f)
+{
+  size_t j = 0;
+
+  if (!f)
+  {
+    *d = '\0';
+    return 0;
+  }
+
+  /* leave some space for the trailing characters. */
+  l -= 6;
+
+  d[j++] = '\'';
+
+  for (size_t i = 0; (j < l) && f[i]; i++)
+  {
+    if ((f[i] == '\'') || (f[i] == '`'))
+    {
+      d[j++] = '\'';
+      d[j++] = '\\';
+      d[j++] = f[i];
+      d[j++] = '\'';
+    }
+    else
+      d[j++] = f[i];
+  }
+
+  d[j++] = '\'';
+  d[j] = '\0';
+
+  return j;
+}
+
+/**
+ * mutt_concatn_path - Concatenate directory and filename
+ * @param dst      Buffer for result
+ * @param dstlen   Buffer length
+ * @param dir      Directory
+ * @param dirlen   Directory length
+ * @param fname    Filename
+ * @param fnamelen Filename length
+ * @retval NULL Error
+ * @retval ptr  Pointer to \a dst on success
+ *
+ * Write the concatenated pathname (dir + "/" + fname) into dst.
+ * The slash is omitted when dir or fname is of 0 length.
+ */
+char *mutt_concatn_path(char *dst, size_t dstlen, const char *dir,
+                        size_t dirlen, const char *fname, size_t fnamelen)
+{
+  size_t req;
+  size_t offset = 0;
+
+  if (dstlen == 0)
+    return NULL; /* probably should not mask errors like this */
+
+  /* size check */
+  req = dirlen + fnamelen + 1; /* +1 for the trailing nul */
+  if (dirlen && fnamelen)
+    req++; /* when both components are non-nul, we add a "/" in between */
+  if (req > dstlen)
+  { /* check for condition where the dst length is too short */
+    /* Two options here:
+     * 1) assert(0) or return NULL to signal error
+     * 2) copy as much of the path as will fit
+     * It doesn't appear that the return value is actually checked anywhere mutt_concat_path()
+     * is called, so we should just copy set dst to nul and let the calling function fail later.
+     */
+    dst[0] = 0; /* safe since we bail out early if dstlen == 0 */
+    return NULL;
+  }
+
+  if (dirlen)
+  { /* when dir is not empty */
+    memcpy(dst, dir, dirlen);
+    offset = dirlen;
+    if (fnamelen)
+      dst[offset++] = '/';
+  }
+  if (fnamelen)
+  { /* when fname is not empty */
+    memcpy(dst + offset, fname, fnamelen);
+    offset += fnamelen;
+  }
+  dst[offset] = 0;
+  return dst;
+}
+
+/**
+ * mutt_concat_path - Join a directory name and a filename
+ * @param d     Buffer for the result
+ * @param dir   Directory name
+ * @param fname File name
+ * @param l     Length of buffer
+ * @retval ptr Destination buffer
+ *
+ * If both dir and fname are supplied, they are separated with '/'.
+ * If either is missing, then the other will be copied exactly.
+ */
+char *mutt_concat_path(char *d, const char *dir, const char *fname, size_t l)
+{
+  const char *fmt = "%s/%s";
+
+  if (!*fname || (*dir && dir[strlen(dir) - 1] == '/'))
+    fmt = "%s%s";
+
+  snprintf(d, l, fmt, dir, fname);
+  return d;
+}
+
+/**
+ * mutt_basename - Find the last component for a pathname
+ * @param f String to be examined
+ * @retval ptr Part of pathname after last '/' character
+ */
+const char *mutt_basename(const char *f)
+{
+  const char *p = strrchr(f, '/');
+  if (p)
+    return p + 1;
+  else
+    return f;
+}
+
+/**
+ * mutt_mkdir - Recursively create directories
+ * @param path Directories to create
+ * @param mode Permissions for final directory
+ * @retval    0  Success
+ * @retval   -1  Error (errno set)
+ *
+ * Create a directory, creating the parents if necessary. (like mkdir -p)
+ *
+ * @note The permissions are only set on the final directory.
+ *       The permissions of any parent directories are determined by the umask.
+ *       (This is how "mkdir -p" behaves)
+ */
+int mutt_mkdir(const char *path, mode_t mode)
+{
+  if (!path || !*path)
+  {
+    errno = EINVAL;
+    return -1;
+  }
+
+  errno = 0;
+  char *p = NULL;
+  char _path[PATH_MAX];
+  const size_t len = strlen(path);
+
+  if (len >= sizeof(_path))
+  {
+    errno = ENAMETOOLONG;
+    return -1;
+  }
+
+  struct stat sb;
+  if ((stat(path, &sb) == 0) && S_ISDIR(sb.st_mode))
+    return 0;
+
+  /* Create a mutable copy */
+  strfcpy(_path, path, sizeof(_path));
+
+  for (p = _path + 1; *p; p++)
+  {
+    if (*p != '/')
+      continue;
+
+    /* Temporarily truncate the path */
+    *p = '\0';
+
+    if ((mkdir(_path, S_IRWXU | S_IRWXG | S_IRWXO) != 0) && (errno != EEXIST))
+      return -1;
+
+    *p = '/';
+  }
+
+  if ((mkdir(_path, mode) != 0) && (errno != EEXIST))
+    return -1;
+
+  return 0;
+}
diff --git a/lib/lib_file.h b/lib/lib_file.h
new file mode 100644 (file)
index 0000000..c0c1365
--- /dev/null
@@ -0,0 +1,57 @@
+/**
+ * @file
+ * File management functions
+ *
+ * @authors
+ * Copyright (C) 2017 Richard Russon <rich@flatcap.org>
+ *
+ * @copyright
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * This program 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 General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _LIB_FILE_H
+#define _LIB_FILE_H
+
+#include <stdio.h>
+#include <sys/types.h>
+
+#undef MAX
+#undef MIN
+#define MAX(a, b) ((a) < (b) ? (b) : (a))
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+
+/* Flags for mutt_read_line() */
+#define MUTT_CONT (1 << 0) /**< \-continuation */
+#define MUTT_EOL  (1 << 1) /**< don't strip `\n` / `\r\n` */
+
+const char *mutt_basename(const char *f);                                                                                           
+char *      mutt_concatn_path(char *dst, size_t dstlen, const char *dir, size_t dirlen, const char *fname, size_t fnamelen);        
+char *      mutt_concat_path(char *d, const char *dir, const char *fname, size_t l);                                                
+int         mutt_copy_bytes(FILE *in, FILE *out, size_t size);                                                                      
+int         mutt_copy_stream(FILE *fin, FILE *fout);                                                                                
+int         mutt_mkdir(const char *path, mode_t mode);                                                                              
+size_t      mutt_quote_filename(char *d, size_t l, const char *f);                                                                  
+char *      mutt_read_line(char *s, size_t *size, FILE *fp, int *line, int flags);                                                  
+int         mutt_rmtree(const char *path);                                                                                          
+int         mutt_rx_sanitize_string(char *dest, size_t destlen, const char *src);                                                   
+void        mutt_sanitize_filename(char *f, short slash);                                                                           
+void        mutt_unlink(const char *s);                                                                                             
+int         safe_fclose(FILE **f);                                                                                                  
+FILE *      safe_fopen(const char *path, const char *mode);                                                                         
+int         safe_fsync_close(FILE **f);                                                                                             
+int         safe_open(const char *path, int flags);                                                                                 
+int         safe_rename(const char *src, const char *target);                                                                       
+int         safe_symlink(const char *oldpath, const char *newpath);                                                                 
+
+#endif /* _LIB_FILE_H */
index e927b9a6a15f4752c813a17ff801f71de546b632..821ed95b236c0cbfaacbcf2639c0b279b7cfa4a9 100644 (file)
@@ -63,6 +63,7 @@ lib/lib_base64.c
 lib/lib_date.c
 lib/lib_debug.c
 lib/lib_exit.c
+lib/lib_file.c
 lib/lib_md5.c
 lib/lib_memory.c
 lib/lib_message.c