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