remove most of the debug code: often makes the code unreadable, for little
[apps/madmutt.git] / lib-lib / file.c
1 /*
2  *  This program is free software; you can redistribute it and/or modify
3  *  it under the terms of the GNU General Public License as published by
4  *  the Free Software Foundation; either version 2 of the License, or (at
5  *  your option) any later version.
6  *
7  *  This program is distributed in the hope that it will be useful, but
8  *  WITHOUT ANY WARRANTY; without even the implied warranty of
9  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
10  *  General Public License for more details.
11  *
12  *  You should have received a copy of the GNU General Public License
13  *  along with this program; if not, write to the Free Software
14  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
15  *  MA 02110-1301, USA.
16  *
17  *  Copyright © 2006 Pierre Habouzit
18  */
19 /*
20  * Copyright notice from original mutt:
21  * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
22  * Copyright (C) 1999-2000 Thomas Roessler <roessler@does-not-exist.org>
23  *
24  * This file is part of mutt-ng, see http://www.muttng.org/.
25  * It's licensed under the GNU General Public License,
26  * please see the file GPL in the top level source directory.
27  */
28
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <fcntl.h>
32 #include <errno.h>
33 #include <unistd.h>
34 #include <limits.h>
35
36 #include "macros.h"
37 #include "mem.h"
38 #include "str.h"
39 #include "file.h"
40
41 #ifndef O_NOFOLLOW
42 #  define O_NOFOLLOW  0
43 #endif
44
45 /* FIXME: ugly to the max */
46 extern short Umask;
47
48 static int compare_stat(struct stat *osb, struct stat *nsb)
49 {
50     if (osb->st_dev  != nsb->st_dev || osb->st_ino != nsb->st_ino
51     || osb->st_rdev != nsb->st_rdev)
52     {
53         return -1;
54     }
55
56     return 0;
57 }
58
59 /****************************************************************************/
60 /* fd ops                                                                   */
61 /****************************************************************************/
62
63 int safe_open(const char *path, int flags)
64 {
65     struct stat osb, nsb;
66     int fd;
67
68     umask(Umask);
69
70     if ((fd = open(path, flags, 0666)) < 0)
71         return fd;
72
73     /* make sure the file is not symlink */
74     if (lstat (path, &osb) < 0 || fstat(fd, &nsb) < 0
75     ||  compare_stat(&osb, &nsb) == -1)
76     {
77         close(fd);
78         return -1;
79     }
80
81     return (fd);
82 }
83
84
85 int safe_symlink(const char *oldpath, const char *newpath)
86 {
87     struct stat osb, nsb;
88
89     if (!oldpath || !newpath)
90         return -1;
91
92     if (unlink(newpath) < 0 && errno != ENOENT)
93         return -1;
94
95     if (oldpath[0] == '/') {
96         if (symlink(oldpath, newpath) < 0)
97             return -1;
98     } else {
99         char abs_oldpath[_POSIX_PATH_MAX];
100         ssize_t len;
101
102         if (!getcwd(abs_oldpath, sizeof(abs_oldpath)))
103             return -1;
104
105         len = m_strcat(abs_oldpath, sizeof(abs_oldpath), "/");
106         if (len >= ssizeof(abs_oldpath))
107             return -1;
108
109         len += m_strcpy(abs_oldpath + len, sizeof(abs_oldpath) - len, oldpath);
110         if (len >= ssizeof(abs_oldpath))
111             return -1;
112
113         if (symlink (abs_oldpath, newpath) < 0)
114             return -1;
115     }
116
117     if (stat(oldpath, &osb) < 0
118     ||  stat(newpath, &nsb) < 0
119     ||  compare_stat(&osb, &nsb) < 0)
120     {
121         unlink (newpath);
122         return -1;
123     }
124
125     return 0;
126 }
127
128 /*
129  * This function is supposed to do nfs-safe renaming of files.
130  *
131  * Warning: We don't check whether src and target are equal.
132  */
133 int safe_rename(const char *src, const char *target)
134 {
135     struct stat ssb, tsb;
136
137     if (!src || !target)
138         return -1;
139
140     if (link(src, target) != 0) {
141         /*
142          * Coda does not allow cross-directory links, but tells
143          * us it's a cross-filesystem linking attempt.
144          *
145          * However, the Coda rename call is allegedly safe to use.
146          *
147          * With other file systems, rename should just fail when 
148          * the files reside on different file systems, so it's safe
149          * to try it here.
150          *
151          */
152
153         if (errno == EXDEV)
154             return rename(src, target);
155
156         return -1;
157     }
158
159     /* Stat both links and check if they are equal. */
160
161     if (stat(src, &ssb) < 0 || stat(target, &tsb) < 0) {
162         return -1;
163     }
164
165     /* 
166      * pretend that the link failed because the target file
167      * did already exist.
168      */
169
170     if (compare_stat(&ssb, &tsb) == -1) {
171         errno = EEXIST;
172         return -1;
173     }
174
175     /*
176      * Unlink the original link.  Should we really ignore the return
177      * value here? XXX
178      */
179
180     unlink(src);
181     return 0;
182 }
183
184 void mutt_unlink(const char *s)
185 {
186     struct stat sb, sb2;
187
188     if (lstat(s, &sb) == 0 && S_ISREG(sb.st_mode)) {
189         int fd;
190         FILE *f;
191
192         /* Defend against symlink attacks */
193
194         if ((fd = open(s, O_RDWR | O_NOFOLLOW)) < 0)
195             return;
196
197         if ((fstat(fd, &sb2) != 0) || !S_ISREG(sb2.st_mode)
198         || (sb.st_dev != sb2.st_dev) || (sb.st_ino != sb2.st_ino))
199         {
200             close (fd);
201             return;
202         }
203
204         if ((f = fdopen(fd, "r+"))) {
205             char buf[BUFSIZ];
206             unlink(s);
207
208             p_clear(buf, countof(buf));
209             while (sb.st_size > 0) {
210                 fwrite(buf, 1, MIN(ssizeof(buf), sb.st_size), f);
211                 sb.st_size -= MIN(ssizeof(buf), sb.st_size);
212             }
213             fclose (f);
214         }
215     }
216 }
217
218
219 /****************************************************************************/
220 /* FILE* ops                                                                */
221 /****************************************************************************/
222
223 /* when opening files for writing, make sure the file doesn't already exist
224    to avoid race conditions.                                                */
225 FILE *safe_fopen(const char *path, const char *mode)
226 {
227     /* first set the current umask */
228     umask(Umask);
229
230     if (mode[0] == 'w') {
231         int fd;
232         int flags = O_CREAT | O_EXCL | O_NOFOLLOW;
233
234         flags |= (mode[1] == '+' ? O_RDWR : O_WRONLY);
235
236         if ((fd = safe_open(path, flags)) < 0)
237             return (NULL);
238
239         return fdopen (fd, mode);
240     }
241
242     return fopen(path, mode);
243 }
244
245 int safe_fclose(FILE **f)
246 {
247     int r = 0;
248
249     if (*f)
250         r = fclose (*f);
251     *f = NULL;
252     return r;
253 }
254
255 /* Read a line from ``fp'' into the dynamically allocated ``s'',
256  * increasing ``s'' if necessary. The ending "\n" or "\r\n" is removed.
257  * If a line ends with "\", this char and the linefeed is removed,
258  * and the next line is read too.
259  */
260 char *mutt_read_line(char *s, ssize_t *size, FILE * fp, int *line)
261 {
262     ssize_t offset = 0;
263     char *ch;
264
265     if (!s) {
266         s = p_new(char, STRING);
267         *size = STRING;
268     }
269
270     for (;;) {
271         if (fgets(s + offset, *size - offset, fp) == NULL) {
272             p_delete(&s);
273             return NULL;
274         }
275         if ((ch = strchr(s + offset, '\n')) != NULL) {
276             (*line)++;
277             *ch = 0;
278             if (ch > s && *(ch - 1) == '\r')
279                 *--ch = 0;
280             if (ch == s || *(ch - 1) != '\\')
281                 return s;
282             offset = ch - s - 1;
283         } else {
284             int c;
285
286             c = getc (fp);            /* This is kind of a hack. We want to know if the
287                                          char at the current point in the input stream is EOF.
288                                          feof() will only tell us if we've already hit EOF, not
289                                          if the next character is EOF. So, we need to read in
290                                          the next character and manually check if it is EOF. */
291             if (c == EOF) {
292                 /* The last line of fp isn't \n terminated */
293                 (*line)++;
294                 return s;
295             } else {
296                 ungetc (c, fp);         /* undo our dammage */
297                 /* There wasn't room for the line -- increase ``s'' */
298                 offset = *size - 1;     /* overwrite the terminating 0 */
299                 *size += STRING;
300                 p_realloc(&s, *size);
301             }
302         }
303     }
304 }
305
306 int mutt_copy_stream(FILE *fin, FILE *fout)
307 {
308     char buf[BUFSIZ];
309     size_t l;
310
311     while ((l = fread(buf, 1, sizeof(buf), fin)) > 0) {
312         if (fwrite(buf, 1, l, fout) != l)
313             return -1;
314     }
315
316     return 0;
317 }
318
319 int mutt_copy_bytes(FILE *in, FILE *out, ssize_t size)
320 {
321     char buf[BUFSIZ];
322
323     while (size > 0) {
324         size_t chunk = MIN(size, ssizeof(buf));
325
326         if ((chunk = fread(buf, 1, chunk, in)) < 1)
327             break;
328         if (fwrite(buf, 1, chunk, out) != chunk) {
329             return -1;
330         }
331         size -= chunk;
332     }
333
334     return 0;
335 }
336
337
338 /****************************************************************************/
339 /* ligben-like funcs                                                        */
340 /****************************************************************************/
341
342 const char *mutt_basename(const char *f)
343 {
344     const char *p = strrchr(f, '/');
345     return p ? p + 1 : f;
346 }
347
348
349 char *
350 mutt_concat_path(char *d, ssize_t n, const char *dir, const char *fname)
351 {
352     int pos;
353
354     pos = m_strcpy(d, n, dir);
355     if (pos >= n - 1)
356         return d;
357
358     if (pos && d[pos - 1] != '/')
359         d[pos++] = '/';
360
361     m_strcpy(d + pos, n - pos, fname);
362     return d;
363 }
364
365
366 void mutt_sanitize_filename(char *s, short slash)
367 {
368     static char safe_chars[] =
369         "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+@{}._-:%/";
370
371     if (!s)
372         return;
373
374     for (; *s; s++) {
375         if ((slash && *s == '/') || !strchr (safe_chars, *s))
376             *s = '_';
377     }
378 }
379
380 /* prepare a file name to survive the shell's quoting rules.
381  * From the Unix programming FAQ by way of Liviu.
382  */
383
384 /* FIXME: API is very wrong */
385 ssize_t mutt_quote_filename(char *d, ssize_t l, const char *s)
386 {
387     ssize_t i, j = 0;
388
389     if (!s) {
390         *d = '\0';
391         return 0;
392     }
393
394     /* leave some space for the trailing characters. */
395     l -= 6;
396
397     d[j++] = '\'';
398
399     for (i = 0; j < l && s[i]; i++) {
400         if (s[i] == '\'' || s[i] == '`') {
401             d[j++] = '\'';
402             d[j++] = '\\';
403             d[j++] = s[i];
404             d[j++] = '\'';
405         } else {
406             d[j++] = s[i];
407         }
408     }
409
410     d[j++] = '\'';
411     d[j] = '\0';
412
413     return j;
414 }