revamp lib.[hc] functions into lib-lib/file.[hc].
[apps/madmutt.git] / lib-lib / file.c
diff --git a/lib-lib/file.c b/lib-lib/file.c
new file mode 100644 (file)
index 0000000..793ac10
--- /dev/null
@@ -0,0 +1,415 @@
+/*
+ *  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, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA 02110-1301, USA.
+ *
+ *  Copyright © 2006 Pierre Habouzit
+ */
+/*
+ * Copyright notice from original mutt:
+ * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
+ * Copyright (C) 1999-2000 Thomas Roessler <roessler@does-not-exist.org>
+ *
+ * This file is part of mutt-ng, see http://www.muttng.org/.
+ * It's licensed under the GNU General Public License,
+ * please see the file GPL in the top level source directory.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include "macros.h"
+#include "mem.h"
+#include "str.h"
+#include "file.h"
+
+#include "../lib/debug.h"
+#include "../lib/str.h"
+
+#ifndef O_NOFOLLOW
+#  define O_NOFOLLOW  0
+#endif
+
+/* FIXME: ugly to the max */
+extern short Umask;
+
+static int 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 -1;
+    }
+
+    return 0;
+}
+
+/****************************************************************************/
+/* fd ops                                                                   */
+/****************************************************************************/
+
+int safe_open(const char *path, int flags)
+{
+    struct stat osb, nsb;
+    int fd;
+
+    umask(Umask);
+
+    if ((fd = open(path, flags, 0666)) < 0)
+        return fd;
+
+    /* make sure the file is not symlink */
+    if (lstat (path, &osb) < 0 || fstat(fd, &nsb) < 0
+    ||  compare_stat(&osb, &nsb) == -1)
+    {
+        debug_print(1, ("%s is a symlink!\n", path));
+        close(fd);
+        return -1;
+    }
+
+    return (fd);
+}
+
+
+int safe_symlink(const char *oldpath, const char *newpath)
+{
+    struct stat osb, nsb;
+
+    if (!oldpath || !newpath)
+        return -1;
+
+    if (unlink(newpath) < 0 && errno != ENOENT)
+        return -1;
+
+    if (oldpath[0] == '/') {
+        if (symlink(oldpath, newpath) < 0)
+            return -1;
+    } else {
+        char abs_oldpath[_POSIX_PATH_MAX];
+        ssize_t len;
+
+        if (!getcwd(abs_oldpath, sizeof(abs_oldpath)))
+            return -1;
+
+        len = m_strcat(abs_oldpath, sizeof(abs_oldpath), "/");
+        if (len >= sizeof(abs_oldpath))
+            return -1;
+
+        len += m_strcpy(abs_oldpath + len, sizeof(abs_oldpath) - len, oldpath);
+        if (len >= sizeof(abs_oldpath))
+            return -1;
+
+        if (symlink (abs_oldpath, newpath) < 0)
+            return -1;
+    }
+
+    if (stat(oldpath, &osb) < 0
+    ||  stat(newpath, &nsb) < 0
+    ||  compare_stat(&osb, &nsb) < 0)
+    {
+        unlink (newpath);
+        return -1;
+    }
+
+    return 0;
+}
+
+/*
+ * This function is supposed to do 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.
+         *
+         */
+
+        if (errno == EXDEV)
+            return rename(src, target);
+
+        return -1;
+    }
+
+    /* Stat both links and check if they are equal. */
+
+    if (stat(src, &ssb) < 0 || stat(target, &tsb) < 0) {
+        return -1;
+    }
+
+    /* 
+     * pretend that the link failed because the target file
+     * did already exist.
+     */
+
+    if (compare_stat(&ssb, &tsb) == -1) {
+        errno = EEXIST;
+        return -1;
+    }
+
+    /*
+     * Unlink the original link.  Should we really ignore the return
+     * value here? XXX
+     */
+
+    unlink(src);
+    return 0;
+}
+
+void mutt_unlink(const char *s)
+{
+    struct stat sb, sb2;
+
+    if (lstat(s, &sb) == 0 && S_ISREG(sb.st_mode)) {
+        int fd;
+        FILE *f;
+
+        /* Defend against symlink attacks */
+
+        if ((fd = open(s, O_RDWR | O_NOFOLLOW)) < 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+"))) {
+            char buf[BUFSIZ];
+            unlink(s);
+
+            p_clear(buf, 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);
+            }
+            fclose (f);
+        }
+    }
+}
+
+
+/****************************************************************************/
+/* FILE* ops                                                                */
+/****************************************************************************/
+
+/* 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)
+{
+    /* first set the current umask */
+    umask(Umask);
+
+    if (mode[0] == 'w') {
+        int fd;
+        int flags = O_CREAT | O_EXCL | O_NOFOLLOW;
+
+        flags |= (mode[1] == '+' ? O_RDWR : O_WRONLY);
+
+        if ((fd = safe_open(path, flags)) < 0)
+            return (NULL);
+
+        return fdopen (fd, mode);
+    }
+
+    return fopen(path, mode);
+}
+
+int safe_fclose(FILE **f)
+{
+    int r = 0;
+
+    if (*f)
+        r = fclose (*f);
+    *f = NULL;
+    return r;
+}
+
+/* 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)
+{
+    size_t offset = 0;
+    char *ch;
+
+    if (!s) {
+        s = p_new(char, STRING);
+        *size = STRING;
+    }
+
+    for (;;) {
+        if (fgets(s + offset, *size - offset, fp) == NULL) {
+            p_delete(&s);
+            return NULL;
+        }
+        if ((ch = strchr(s + offset, '\n')) != NULL) {
+            (*line)++;
+            *ch = 0;
+            if (ch > s && *(ch - 1) == '\r')
+                *--ch = 0;
+            if (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 */
+                (*line)++;
+                return s;
+            } else {
+                ungetc (c, fp);         /* undo our dammage */
+                /* There wasn't room for the line -- increase ``s'' */
+                offset = *size - 1;     /* overwrite the terminating 0 */
+                *size += STRING;
+                p_realloc(&s, *size);
+            }
+        }
+    }
+}
+
+int mutt_copy_stream(FILE *fin, FILE *fout)
+{
+    char buf[BUFSIZ];
+    ssize_t l;
+
+    while ((l = fread(buf, 1, sizeof (buf), fin)) > 0) {
+        if (fwrite(buf, 1, l, fout) != l)
+            return -1;
+    }
+
+    return 0;
+}
+
+int mutt_copy_bytes(FILE *in, FILE *out, size_t size)
+{
+    char buf[BUFSIZ];
+
+    while (size > 0) {
+        ssize_t chunk = MIN(size, sizeof(buf));
+
+        if ((chunk = fread(buf, 1, chunk, in)) < 1)
+            break;
+        if (fwrite(buf, 1, chunk, out) != chunk) {
+            debug_print(1, ("fwrite() returned short byte count\n"));
+            return -1;
+        }
+        size -= chunk;
+    }
+
+    return 0;
+}
+
+
+/****************************************************************************/
+/* ligben-like funcs                                                        */
+/****************************************************************************/
+
+const char *mutt_basename(const char *f)
+{
+    const char *p = strrchr(f, '/');
+    return p ? p + 1 : f;
+}
+
+
+char *
+mutt_concat_path(char *d, ssize_t n, const char *dir, const char *fname)
+{
+    const char *fmt = "%s/%s";
+
+    if (!*fname || (*dir && dir[m_strlen(dir) - 1] == '/'))
+        fmt = "%s%s";
+
+    snprintf(d, n, fmt, dir, fname);
+    return d;
+}
+
+
+void mutt_sanitize_filename(char *s, short slash)
+{
+    static char safe_chars[] =
+        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+@{}._-:%/";
+
+    if (!s)
+        return;
+
+    for (; *s; s++) {
+        if ((slash && *s == '/') || !strchr (safe_chars, *s))
+            *s = '_';
+    }
+}
+
+/* prepare a file name to survive the shell's quoting rules.
+ * From the Unix programming FAQ by way of Liviu.
+ */
+
+/* FIXME: API is very wrong */
+ssize_t mutt_quote_filename(char *d, ssize_t l, const char *s)
+{
+    size_t i, j = 0;
+
+    if (!s) {
+        *d = '\0';
+        return 0;
+    }
+
+    /* leave some space for the trailing characters. */
+    l -= 6;
+
+    d[j++] = '\'';
+
+    for (i = 0; j < l && s[i]; i++) {
+        if (s[i] == '\'' || s[i] == '`') {
+            d[j++] = '\'';
+            d[j++] = '\\';
+            d[j++] = s[i];
+            d[j++] = '\'';
+        } else {
+            d[j++] = s[i];
+        }
+    }
+
+    d[j++] = '\'';
+    d[j] = '\0';
+
+    return j;
+}