Line | Count | Source (jump to first uncovered line) |
1 | | /** |
2 | | * @file |
3 | | * File management functions |
4 | | * |
5 | | * @authors |
6 | | * Copyright (C) 2017 Reis Radomil |
7 | | * Copyright (C) 2017-2023 Richard Russon <rich@flatcap.org> |
8 | | * Copyright (C) 2017-2023 Pietro Cerutti <gahr@gahr.ch> |
9 | | * Copyright (C) 2018 Federico Kircheis <federico.kircheis@gmail.com> |
10 | | * Copyright (C) 2018-2019 Ian Zimmerman <itz@no-use.mooo.com> |
11 | | * Copyright (C) 2023 наб <nabijaczleweli@nabijaczleweli.xyz> |
12 | | * |
13 | | * @copyright |
14 | | * This program is free software: you can redistribute it and/or modify it under |
15 | | * the terms of the GNU General Public License as published by the Free Software |
16 | | * Foundation, either version 2 of the License, or (at your option) any later |
17 | | * version. |
18 | | * |
19 | | * This program is distributed in the hope that it will be useful, but WITHOUT |
20 | | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
21 | | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
22 | | * details. |
23 | | * |
24 | | * You should have received a copy of the GNU General Public License along with |
25 | | * this program. If not, see <http://www.gnu.org/licenses/>. |
26 | | */ |
27 | | |
28 | | /** |
29 | | * @page mutt_file File management functions |
30 | | * |
31 | | * Commonly used file/dir management routines. |
32 | | * |
33 | | * The following unused functions were removed: |
34 | | * - mutt_file_chmod() |
35 | | * - mutt_file_chmod_rm() |
36 | | */ |
37 | | |
38 | | #include "config.h" |
39 | | #include <ctype.h> |
40 | | #include <errno.h> |
41 | | #include <fcntl.h> |
42 | | #include <libgen.h> |
43 | | #include <limits.h> |
44 | | #include <stdbool.h> |
45 | | #include <stdio.h> |
46 | | #include <stdlib.h> |
47 | | #include <string.h> |
48 | | #include <sys/stat.h> |
49 | | #include <unistd.h> |
50 | | #include <utime.h> |
51 | | #include <wchar.h> |
52 | | #include "file.h" |
53 | | #include "buffer.h" |
54 | | #include "charset.h" |
55 | | #include "date.h" |
56 | | #include "logging2.h" |
57 | | #include "memory.h" |
58 | | #include "message.h" |
59 | | #include "path.h" |
60 | | #include "pool.h" |
61 | | #include "string2.h" |
62 | | #ifdef USE_FLOCK |
63 | | #include <sys/file.h> |
64 | | #endif |
65 | | |
66 | | /// These characters must be escaped in regular expressions |
67 | | static const char RxSpecialChars[] = "^.[$()|*+?{\\"; |
68 | | |
69 | | /// Set of characters <=0x7F that are safe to use in filenames |
70 | | const char FilenameSafeChars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+@{}._-:%/"; |
71 | | |
72 | 0 | #define MAX_LOCK_ATTEMPTS 5 |
73 | | |
74 | | /* This is defined in POSIX:2008 which isn't a build requirement */ |
75 | | #ifndef O_NOFOLLOW |
76 | | #define O_NOFOLLOW 0 |
77 | | #endif |
78 | | |
79 | | /** |
80 | | * stat_equal - Compare the struct stat's of two files/dirs |
81 | | * @param st_old struct stat of the first file/dir |
82 | | * @param st_new struct stat of the second file/dir |
83 | | * @retval true They match |
84 | | * |
85 | | * This compares the device id (st_dev), inode number (st_ino) and special id |
86 | | * (st_rdev) of the files/dirs. |
87 | | */ |
88 | | static bool stat_equal(struct stat *st_old, struct stat *st_new) |
89 | 0 | { |
90 | 0 | return (st_old->st_dev == st_new->st_dev) && (st_old->st_ino == st_new->st_ino) && |
91 | 0 | (st_old->st_rdev == st_new->st_rdev); |
92 | 0 | } |
93 | | |
94 | | /** |
95 | | * mkwrapdir - Create a temporary directory next to a file name |
96 | | * @param path Existing filename |
97 | | * @param newfile New filename |
98 | | * @param newdir New directory name |
99 | | * @retval 0 Success |
100 | | * @retval -1 Error |
101 | | */ |
102 | | static int mkwrapdir(const char *path, struct Buffer *newfile, struct Buffer *newdir) |
103 | 0 | { |
104 | 0 | const char *basename = NULL; |
105 | 0 | int rc = 0; |
106 | |
|
107 | 0 | struct Buffer parent = buf_make(PATH_MAX); |
108 | 0 | buf_strcpy(&parent, NONULL(path)); |
109 | |
|
110 | 0 | char *p = strrchr(parent.data, '/'); |
111 | 0 | if (p) |
112 | 0 | { |
113 | 0 | *p = '\0'; |
114 | 0 | basename = p + 1; |
115 | 0 | } |
116 | 0 | else |
117 | 0 | { |
118 | 0 | buf_strcpy(&parent, "."); |
119 | 0 | basename = path; |
120 | 0 | } |
121 | |
|
122 | 0 | buf_printf(newdir, "%s/%s", buf_string(&parent), ".muttXXXXXX"); |
123 | 0 | if (!mkdtemp(newdir->data)) |
124 | 0 | { |
125 | 0 | mutt_debug(LL_DEBUG1, "mkdtemp() failed\n"); |
126 | 0 | rc = -1; |
127 | 0 | goto cleanup; |
128 | 0 | } |
129 | | |
130 | 0 | buf_printf(newfile, "%s/%s", newdir->data, NONULL(basename)); |
131 | |
|
132 | 0 | cleanup: |
133 | 0 | buf_dealloc(&parent); |
134 | 0 | return rc; |
135 | 0 | } |
136 | | |
137 | | /** |
138 | | * put_file_in_place - Move a file into place |
139 | | * @param path Destination path |
140 | | * @param safe_file Current filename |
141 | | * @param safe_dir Current directory name |
142 | | * @retval 0 Success |
143 | | * @retval -1 Error, see errno |
144 | | */ |
145 | | static int put_file_in_place(const char *path, const char *safe_file, const char *safe_dir) |
146 | 0 | { |
147 | 0 | int rc; |
148 | |
|
149 | 0 | rc = mutt_file_safe_rename(safe_file, path); |
150 | 0 | unlink(safe_file); |
151 | 0 | rmdir(safe_dir); |
152 | 0 | return rc; |
153 | 0 | } |
154 | | |
155 | | /** |
156 | | * mutt_file_fclose_full - Close a FILE handle (and NULL the pointer) |
157 | | * @param[out] fp FILE handle to close |
158 | | * @param[in] file Source file |
159 | | * @param[in] line Source line number |
160 | | * @param[in] func Source function |
161 | | * @retval 0 Success |
162 | | * @retval EOF Error, see errno |
163 | | */ |
164 | | int mutt_file_fclose_full(FILE **fp, const char *file, int line, const char *func) |
165 | 0 | { |
166 | 0 | if (!fp || !*fp) |
167 | 0 | return 0; |
168 | | |
169 | 0 | int fd = fileno(*fp); |
170 | 0 | int rc = fclose(*fp); |
171 | |
|
172 | 0 | if (rc == 0) |
173 | 0 | MuttLogger(0, file, line, func, LL_DEBUG2, "File closed (fd=%d)\n", fd); |
174 | 0 | else |
175 | 0 | MuttLogger(0, file, line, func, LL_DEBUG2, "File close failed (fd=%d), errno=%d, %s\n", |
176 | 0 | fd, errno, strerror(errno)); |
177 | |
|
178 | 0 | *fp = NULL; |
179 | 0 | return rc; |
180 | 0 | } |
181 | | |
182 | | /** |
183 | | * mutt_file_fsync_close - Flush the data, before closing a file (and NULL the pointer) |
184 | | * @param[out] fp FILE handle to close |
185 | | * @retval 0 Success |
186 | | * @retval EOF Error, see errno |
187 | | */ |
188 | | int mutt_file_fsync_close(FILE **fp) |
189 | 0 | { |
190 | 0 | if (!fp || !*fp) |
191 | 0 | return 0; |
192 | | |
193 | 0 | int rc = 0; |
194 | |
|
195 | 0 | if (fflush(*fp) || fsync(fileno(*fp))) |
196 | 0 | { |
197 | 0 | int save_errno = errno; |
198 | 0 | rc = -1; |
199 | 0 | mutt_file_fclose(fp); |
200 | 0 | errno = save_errno; |
201 | 0 | } |
202 | 0 | else |
203 | 0 | { |
204 | 0 | rc = mutt_file_fclose(fp); |
205 | 0 | } |
206 | |
|
207 | 0 | return rc; |
208 | 0 | } |
209 | | |
210 | | /** |
211 | | * mutt_file_unlink - Delete a file, carefully |
212 | | * @param s Filename |
213 | | * |
214 | | * This won't follow symlinks. |
215 | | */ |
216 | | void mutt_file_unlink(const char *s) |
217 | 0 | { |
218 | 0 | if (!s) |
219 | 0 | return; |
220 | | |
221 | 0 | struct stat st = { 0 }; |
222 | | /* Defend against symlink attacks */ |
223 | |
|
224 | 0 | const bool is_regular_file = (lstat(s, &st) == 0) && S_ISREG(st.st_mode); |
225 | 0 | if (!is_regular_file) |
226 | 0 | return; |
227 | | |
228 | 0 | const int fd = open(s, O_RDWR | O_NOFOLLOW); |
229 | 0 | if (fd < 0) |
230 | 0 | return; |
231 | | |
232 | 0 | struct stat st2 = { 0 }; |
233 | 0 | if ((fstat(fd, &st2) != 0) || !S_ISREG(st2.st_mode) || |
234 | 0 | (st.st_dev != st2.st_dev) || (st.st_ino != st2.st_ino)) |
235 | 0 | { |
236 | 0 | close(fd); |
237 | 0 | return; |
238 | 0 | } |
239 | | |
240 | 0 | unlink(s); |
241 | 0 | close(fd); |
242 | 0 | } |
243 | | |
244 | | /** |
245 | | * mutt_file_copy_bytes - Copy some content from one file to another |
246 | | * @param fp_in Source file |
247 | | * @param fp_out Destination file |
248 | | * @param size Maximum number of bytes to copy |
249 | | * @retval 0 Success |
250 | | * @retval -1 Error, see errno |
251 | | */ |
252 | | int mutt_file_copy_bytes(FILE *fp_in, FILE *fp_out, size_t size) |
253 | 0 | { |
254 | 0 | if (!fp_in || !fp_out) |
255 | 0 | return -1; |
256 | | |
257 | 0 | while (size > 0) |
258 | 0 | { |
259 | 0 | char buf[2048] = { 0 }; |
260 | 0 | size_t chunk = (size > sizeof(buf)) ? sizeof(buf) : size; |
261 | 0 | chunk = fread(buf, 1, chunk, fp_in); |
262 | 0 | if (chunk < 1) |
263 | 0 | break; |
264 | 0 | if (fwrite(buf, 1, chunk, fp_out) != chunk) |
265 | 0 | return -1; |
266 | | |
267 | 0 | size -= chunk; |
268 | 0 | } |
269 | | |
270 | 0 | if (fflush(fp_out) != 0) |
271 | 0 | return -1; |
272 | 0 | return 0; |
273 | 0 | } |
274 | | |
275 | | /** |
276 | | * mutt_file_copy_stream - Copy the contents of one file into another |
277 | | * @param fp_in Source file |
278 | | * @param fp_out Destination file |
279 | | * @retval num Success, number of bytes copied |
280 | | * @retval -1 Error, see errno |
281 | | */ |
282 | | int mutt_file_copy_stream(FILE *fp_in, FILE *fp_out) |
283 | 0 | { |
284 | 0 | if (!fp_in || !fp_out) |
285 | 0 | return -1; |
286 | | |
287 | 0 | size_t total = 0; |
288 | 0 | size_t l; |
289 | 0 | char buf[1024] = { 0 }; |
290 | |
|
291 | 0 | while ((l = fread(buf, 1, sizeof(buf), fp_in)) > 0) |
292 | 0 | { |
293 | 0 | if (fwrite(buf, 1, l, fp_out) != l) |
294 | 0 | return -1; |
295 | 0 | total += l; |
296 | 0 | } |
297 | | |
298 | 0 | if (fflush(fp_out) != 0) |
299 | 0 | return -1; |
300 | 0 | return total; |
301 | 0 | } |
302 | | |
303 | | /** |
304 | | * mutt_file_symlink - Create a symlink |
305 | | * @param oldpath Existing pathname |
306 | | * @param newpath New pathname |
307 | | * @retval 0 Success |
308 | | * @retval -1 Error, see errno |
309 | | */ |
310 | | int mutt_file_symlink(const char *oldpath, const char *newpath) |
311 | 0 | { |
312 | 0 | struct stat st_old = { 0 }; |
313 | 0 | struct stat st_new = { 0 }; |
314 | |
|
315 | 0 | if (!oldpath || !newpath) |
316 | 0 | return -1; |
317 | | |
318 | 0 | if ((unlink(newpath) == -1) && (errno != ENOENT)) |
319 | 0 | return -1; |
320 | | |
321 | 0 | if (oldpath[0] == '/') |
322 | 0 | { |
323 | 0 | if (symlink(oldpath, newpath) == -1) |
324 | 0 | return -1; |
325 | 0 | } |
326 | 0 | else |
327 | 0 | { |
328 | 0 | struct Buffer abs_oldpath = buf_make(PATH_MAX); |
329 | |
|
330 | 0 | if (!mutt_path_getcwd(&abs_oldpath)) |
331 | 0 | { |
332 | 0 | buf_dealloc(&abs_oldpath); |
333 | 0 | return -1; |
334 | 0 | } |
335 | | |
336 | 0 | buf_addch(&abs_oldpath, '/'); |
337 | 0 | buf_addstr(&abs_oldpath, oldpath); |
338 | 0 | if (symlink(buf_string(&abs_oldpath), newpath) == -1) |
339 | 0 | { |
340 | 0 | buf_dealloc(&abs_oldpath); |
341 | 0 | return -1; |
342 | 0 | } |
343 | | |
344 | 0 | buf_dealloc(&abs_oldpath); |
345 | 0 | } |
346 | | |
347 | 0 | if ((stat(oldpath, &st_old) == -1) || (stat(newpath, &st_new) == -1) || |
348 | 0 | !stat_equal(&st_old, &st_new)) |
349 | 0 | { |
350 | 0 | unlink(newpath); |
351 | 0 | return -1; |
352 | 0 | } |
353 | | |
354 | 0 | return 0; |
355 | 0 | } |
356 | | |
357 | | /** |
358 | | * mutt_file_safe_rename - NFS-safe renaming of files |
359 | | * @param src Original filename |
360 | | * @param target New filename |
361 | | * @retval 0 Success |
362 | | * @retval -1 Error, see errno |
363 | | * |
364 | | * Warning: We don't check whether src and target are equal. |
365 | | */ |
366 | | int mutt_file_safe_rename(const char *src, const char *target) |
367 | 0 | { |
368 | 0 | struct stat st_src = { 0 }; |
369 | 0 | struct stat st_target = { 0 }; |
370 | 0 | int link_errno; |
371 | |
|
372 | 0 | if (!src || !target) |
373 | 0 | return -1; |
374 | | |
375 | 0 | if (link(src, target) != 0) |
376 | 0 | { |
377 | 0 | link_errno = errno; |
378 | | |
379 | | /* It is historically documented that link can return -1 if NFS |
380 | | * dies after creating the link. In that case, we are supposed |
381 | | * to use stat to check if the link was created. |
382 | | * |
383 | | * Derek Martin notes that some implementations of link() follow a |
384 | | * source symlink. It might be more correct to use stat() on src. |
385 | | * I am not doing so to minimize changes in behavior: the function |
386 | | * used lstat() further below for 20 years without issue, and I |
387 | | * believe was never intended to be used on a src symlink. */ |
388 | 0 | if ((lstat(src, &st_src) == 0) && (lstat(target, &st_target) == 0) && |
389 | 0 | (stat_equal(&st_src, &st_target) == 0)) |
390 | 0 | { |
391 | 0 | mutt_debug(LL_DEBUG1, "link (%s, %s) reported failure: %s (%d) but actually succeeded\n", |
392 | 0 | src, target, strerror(errno), errno); |
393 | 0 | goto success; |
394 | 0 | } |
395 | | |
396 | 0 | errno = link_errno; |
397 | | |
398 | | /* Coda does not allow cross-directory links, but tells |
399 | | * us it's a cross-filesystem linking attempt. |
400 | | * |
401 | | * However, the Coda rename call is allegedly safe to use. |
402 | | * |
403 | | * With other file systems, rename should just fail when |
404 | | * the files reside on different file systems, so it's safe |
405 | | * to try it here. */ |
406 | 0 | mutt_debug(LL_DEBUG1, "link (%s, %s) failed: %s (%d)\n", src, target, |
407 | 0 | strerror(errno), errno); |
408 | | |
409 | | /* FUSE may return ENOSYS. VFAT may return EPERM. FreeBSD's |
410 | | * msdosfs may return EOPNOTSUPP. ENOTSUP can also appear. */ |
411 | 0 | if ((errno == EXDEV) || (errno == ENOSYS) || errno == EPERM |
412 | 0 | #ifdef ENOTSUP |
413 | 0 | || errno == ENOTSUP |
414 | 0 | #endif |
415 | 0 | #ifdef EOPNOTSUPP |
416 | 0 | || errno == EOPNOTSUPP |
417 | 0 | #endif |
418 | 0 | ) |
419 | 0 | { |
420 | 0 | mutt_debug(LL_DEBUG1, "trying rename\n"); |
421 | 0 | if (rename(src, target) == -1) |
422 | 0 | { |
423 | 0 | mutt_debug(LL_DEBUG1, "rename (%s, %s) failed: %s (%d)\n", src, target, |
424 | 0 | strerror(errno), errno); |
425 | 0 | return -1; |
426 | 0 | } |
427 | 0 | mutt_debug(LL_DEBUG1, "rename succeeded\n"); |
428 | |
|
429 | 0 | return 0; |
430 | 0 | } |
431 | | |
432 | 0 | return -1; |
433 | 0 | } |
434 | | |
435 | | /* Remove the stat_equal() check, because it causes problems with maildir |
436 | | * on filesystems that don't properly support hard links, such as sshfs. The |
437 | | * filesystem creates the link, but the resulting file is given a different |
438 | | * inode number by the sshfs layer. This results in an infinite loop |
439 | | * creating links. */ |
440 | | #if 0 |
441 | | /* Stat both links and check if they are equal. */ |
442 | | if (lstat(src, &st_src) == -1) |
443 | | { |
444 | | mutt_debug(LL_DEBUG1, "#1 can't stat %s: %s (%d)\n", src, strerror(errno), errno); |
445 | | return -1; |
446 | | } |
447 | | |
448 | | if (lstat(target, &st_target) == -1) |
449 | | { |
450 | | mutt_debug(LL_DEBUG1, "#2 can't stat %s: %s (%d)\n", src, strerror(errno), errno); |
451 | | return -1; |
452 | | } |
453 | | |
454 | | /* pretend that the link failed because the target file did already exist. */ |
455 | | |
456 | | if (!stat_equal(&st_src, &st_target)) |
457 | | { |
458 | | mutt_debug(LL_DEBUG1, "stat blocks for %s and %s diverge; pretending EEXIST\n", src, target); |
459 | | errno = EEXIST; |
460 | | return -1; |
461 | | } |
462 | | #endif |
463 | | |
464 | 0 | success: |
465 | | /* Unlink the original link. |
466 | | * Should we really ignore the return value here? XXX */ |
467 | 0 | if (unlink(src) == -1) |
468 | 0 | { |
469 | 0 | mutt_debug(LL_DEBUG1, "unlink (%s) failed: %s (%d)\n", src, strerror(errno), errno); |
470 | 0 | } |
471 | |
|
472 | 0 | return 0; |
473 | 0 | } |
474 | | |
475 | | /** |
476 | | * mutt_file_rmtree - Recursively remove a directory |
477 | | * @param path Directory to delete |
478 | | * @retval 0 Success |
479 | | * @retval -1 Error, see errno |
480 | | */ |
481 | | int mutt_file_rmtree(const char *path) |
482 | 0 | { |
483 | 0 | if (!path) |
484 | 0 | return -1; |
485 | | |
486 | 0 | struct dirent *de = NULL; |
487 | 0 | struct stat st = { 0 }; |
488 | 0 | int rc = 0; |
489 | |
|
490 | 0 | DIR *dir = mutt_file_opendir(path, MUTT_OPENDIR_NONE); |
491 | 0 | if (!dir) |
492 | 0 | { |
493 | 0 | mutt_debug(LL_DEBUG1, "error opening directory %s\n", path); |
494 | 0 | return -1; |
495 | 0 | } |
496 | | |
497 | | /* We avoid using the buffer pool for this function, because it |
498 | | * invokes recursively to an unknown depth. */ |
499 | 0 | struct Buffer cur = buf_make(PATH_MAX); |
500 | |
|
501 | 0 | while ((de = readdir(dir))) |
502 | 0 | { |
503 | 0 | if ((mutt_str_equal(".", de->d_name)) || (mutt_str_equal("..", de->d_name))) |
504 | 0 | continue; |
505 | | |
506 | 0 | buf_printf(&cur, "%s/%s", path, de->d_name); |
507 | | /* XXX make nonrecursive version */ |
508 | |
|
509 | 0 | if (stat(buf_string(&cur), &st) == -1) |
510 | 0 | { |
511 | 0 | rc = 1; |
512 | 0 | continue; |
513 | 0 | } |
514 | | |
515 | 0 | if (S_ISDIR(st.st_mode)) |
516 | 0 | rc |= mutt_file_rmtree(buf_string(&cur)); |
517 | 0 | else |
518 | 0 | rc |= unlink(buf_string(&cur)); |
519 | 0 | } |
520 | 0 | closedir(dir); |
521 | |
|
522 | 0 | rc |= rmdir(path); |
523 | |
|
524 | 0 | buf_dealloc(&cur); |
525 | 0 | return rc; |
526 | 0 | } |
527 | | |
528 | | /** |
529 | | * mutt_file_rotate - Rotate a set of numbered files |
530 | | * @param path Template filename |
531 | | * @param count Maximum number of files |
532 | | * @retval ptr Name of the 0'th file |
533 | | * |
534 | | * Given a template 'temp', rename files numbered 0 to (count-1). |
535 | | * |
536 | | * Rename: |
537 | | * - ... |
538 | | * - temp1 -> temp2 |
539 | | * - temp0 -> temp1 |
540 | | */ |
541 | | const char *mutt_file_rotate(const char *path, int count) |
542 | 0 | { |
543 | 0 | if (!path) |
544 | 0 | return NULL; |
545 | | |
546 | 0 | struct Buffer *old_file = buf_pool_get(); |
547 | 0 | struct Buffer *new_file = buf_pool_get(); |
548 | | |
549 | | /* rotate the old debug logs */ |
550 | 0 | for (count -= 2; count >= 0; count--) |
551 | 0 | { |
552 | 0 | buf_printf(old_file, "%s%d", path, count); |
553 | 0 | buf_printf(new_file, "%s%d", path, count + 1); |
554 | 0 | (void) rename(buf_string(old_file), buf_string(new_file)); |
555 | 0 | } |
556 | |
|
557 | 0 | path = buf_strdup(old_file); |
558 | 0 | buf_pool_release(&old_file); |
559 | 0 | buf_pool_release(&new_file); |
560 | |
|
561 | 0 | return path; |
562 | 0 | } |
563 | | |
564 | | /** |
565 | | * mutt_file_open - Open a file |
566 | | * @param path Pathname to open |
567 | | * @param flags Flags, e.g. O_EXCL |
568 | | * @retval >0 Success, file handle |
569 | | * @retval -1 Error |
570 | | */ |
571 | | int mutt_file_open(const char *path, uint32_t flags) |
572 | 0 | { |
573 | 0 | if (!path) |
574 | 0 | return -1; |
575 | | |
576 | 0 | int fd; |
577 | 0 | struct Buffer safe_file = buf_make(0); |
578 | 0 | struct Buffer safe_dir = buf_make(0); |
579 | |
|
580 | 0 | if (flags & O_EXCL) |
581 | 0 | { |
582 | 0 | buf_alloc(&safe_file, PATH_MAX); |
583 | 0 | buf_alloc(&safe_dir, PATH_MAX); |
584 | |
|
585 | 0 | if (mkwrapdir(path, &safe_file, &safe_dir) == -1) |
586 | 0 | { |
587 | 0 | fd = -1; |
588 | 0 | goto cleanup; |
589 | 0 | } |
590 | | |
591 | 0 | fd = open(buf_string(&safe_file), flags, 0600); |
592 | 0 | if (fd < 0) |
593 | 0 | { |
594 | 0 | rmdir(buf_string(&safe_dir)); |
595 | 0 | goto cleanup; |
596 | 0 | } |
597 | | |
598 | | /* NFS and I believe cygwin do not handle movement of open files well */ |
599 | 0 | close(fd); |
600 | 0 | if (put_file_in_place(path, buf_string(&safe_file), buf_string(&safe_dir)) == -1) |
601 | 0 | { |
602 | 0 | fd = -1; |
603 | 0 | goto cleanup; |
604 | 0 | } |
605 | 0 | } |
606 | | |
607 | 0 | fd = open(path, flags & ~O_EXCL, 0600); |
608 | 0 | if (fd < 0) |
609 | 0 | goto cleanup; |
610 | | |
611 | | /* make sure the file is not symlink */ |
612 | 0 | struct stat st_old = { 0 }; |
613 | 0 | struct stat st_new = { 0 }; |
614 | 0 | if (((lstat(path, &st_old) < 0) || (fstat(fd, &st_new) < 0)) || |
615 | 0 | !stat_equal(&st_old, &st_new)) |
616 | 0 | { |
617 | 0 | close(fd); |
618 | 0 | fd = -1; |
619 | 0 | goto cleanup; |
620 | 0 | } |
621 | | |
622 | 0 | cleanup: |
623 | 0 | buf_dealloc(&safe_file); |
624 | 0 | buf_dealloc(&safe_dir); |
625 | |
|
626 | 0 | return fd; |
627 | 0 | } |
628 | | |
629 | | /** |
630 | | * mutt_file_opendir - Open a directory |
631 | | * @param path Directory path |
632 | | * @param mode See MuttOpenDirMode |
633 | | * @retval ptr DIR handle |
634 | | * @retval NULL Error, see errno |
635 | | */ |
636 | | DIR *mutt_file_opendir(const char *path, enum MuttOpenDirMode mode) |
637 | 0 | { |
638 | 0 | if ((mode == MUTT_OPENDIR_CREATE) && (mutt_file_mkdir(path, S_IRWXU) == -1)) |
639 | 0 | { |
640 | 0 | return NULL; |
641 | 0 | } |
642 | 0 | errno = 0; |
643 | 0 | return opendir(path); |
644 | 0 | } |
645 | | |
646 | | /** |
647 | | * mutt_file_fopen_full - Call fopen() safely |
648 | | * @param path Filename |
649 | | * @param mode Mode e.g. "r" readonly; "w" read-write |
650 | | * @param file Source file |
651 | | * @param line Source line number |
652 | | * @param func Source function |
653 | | * @retval ptr FILE handle |
654 | | * @retval NULL Error, see errno |
655 | | * |
656 | | * When opening files for writing, make sure the file doesn't already exist to |
657 | | * avoid race conditions. |
658 | | */ |
659 | | FILE *mutt_file_fopen_full(const char *path, const char *mode, const char *file, |
660 | | int line, const char *func) |
661 | 0 | { |
662 | 0 | if (!path || !mode) |
663 | 0 | return NULL; |
664 | | |
665 | 0 | FILE *fp = NULL; |
666 | 0 | if (mode[0] == 'w') |
667 | 0 | { |
668 | 0 | uint32_t flags = O_CREAT | O_EXCL | O_NOFOLLOW; |
669 | |
|
670 | 0 | if (mode[1] == '+') |
671 | 0 | flags |= O_RDWR; |
672 | 0 | else |
673 | 0 | flags |= O_WRONLY; |
674 | |
|
675 | 0 | int fd = mutt_file_open(path, flags); |
676 | 0 | if (fd >= 0) |
677 | 0 | { |
678 | 0 | fp = fdopen(fd, mode); |
679 | 0 | } |
680 | 0 | } |
681 | 0 | else |
682 | 0 | { |
683 | 0 | fp = fopen(path, mode); |
684 | 0 | } |
685 | |
|
686 | 0 | if (fp) |
687 | 0 | { |
688 | 0 | MuttLogger(0, file, line, func, LL_DEBUG2, "File opened (fd=%d): %s\n", |
689 | 0 | fileno(fp), path); |
690 | 0 | } |
691 | 0 | else |
692 | 0 | { |
693 | 0 | MuttLogger(0, file, line, func, LL_DEBUG2, "File open failed (errno=%d, %s): %s\n", |
694 | 0 | errno, strerror(errno), path); |
695 | 0 | } |
696 | |
|
697 | 0 | return fp; |
698 | 0 | } |
699 | | |
700 | | /** |
701 | | * mutt_file_sanitize_filename - Replace unsafe characters in a filename |
702 | | * @param path Filename to make safe |
703 | | * @param slash Replace '/' characters too |
704 | | */ |
705 | | void mutt_file_sanitize_filename(char *path, bool slash) |
706 | 0 | { |
707 | 0 | if (!path) |
708 | 0 | return; |
709 | | |
710 | 0 | size_t size = strlen(path); |
711 | |
|
712 | 0 | wchar_t c; |
713 | 0 | mbstate_t mbstate = { 0 }; |
714 | 0 | for (size_t consumed; size && (consumed = mbrtowc(&c, path, size, &mbstate)); |
715 | 0 | size -= consumed, path += consumed) |
716 | 0 | { |
717 | 0 | switch (consumed) |
718 | 0 | { |
719 | 0 | case ICONV_ILLEGAL_SEQ: |
720 | 0 | mbstate = (mbstate_t){ 0 }; |
721 | 0 | consumed = 1; |
722 | 0 | memset(path, '_', consumed); |
723 | 0 | break; |
724 | | |
725 | 0 | case ICONV_BUF_TOO_SMALL: |
726 | 0 | consumed = size; |
727 | 0 | memset(path, '_', consumed); |
728 | 0 | break; |
729 | | |
730 | 0 | default: |
731 | 0 | if ((slash && (c == L'/')) || ((c <= 0x7F) && !strchr(FilenameSafeChars, c))) |
732 | 0 | { |
733 | 0 | memset(path, '_', consumed); |
734 | 0 | } |
735 | 0 | break; |
736 | 0 | } |
737 | 0 | } |
738 | 0 | } |
739 | | |
740 | | /** |
741 | | * mutt_file_sanitize_regex - Escape any regex-magic characters in a string |
742 | | * @param dest Buffer for result |
743 | | * @param src String to transform |
744 | | * @retval 0 Success |
745 | | * @retval -1 Error |
746 | | */ |
747 | | int mutt_file_sanitize_regex(struct Buffer *dest, const char *src) |
748 | 0 | { |
749 | 0 | if (!dest || !src) |
750 | 0 | return -1; |
751 | | |
752 | 0 | buf_reset(dest); |
753 | 0 | while (*src != '\0') |
754 | 0 | { |
755 | 0 | if (strchr(RxSpecialChars, *src)) |
756 | 0 | buf_addch(dest, '\\'); |
757 | 0 | buf_addch(dest, *src++); |
758 | 0 | } |
759 | |
|
760 | 0 | return 0; |
761 | 0 | } |
762 | | |
763 | | /** |
764 | | * mutt_file_seek - Wrapper for fseeko with error handling |
765 | | * @param[in] fp File to seek |
766 | | * @param[in] offset Offset |
767 | | * @param[in] whence Seek mode |
768 | | * @retval true Seek was successful |
769 | | * @retval false Seek failed |
770 | | */ |
771 | | bool mutt_file_seek(FILE *fp, LOFF_T offset, int whence) |
772 | 115k | { |
773 | 115k | if (!fp) |
774 | 0 | { |
775 | 0 | return false; |
776 | 0 | } |
777 | | |
778 | 115k | if (fseeko(fp, offset, whence) != 0) |
779 | 0 | { |
780 | 0 | mutt_perror(_("Failed to seek file: %s"), strerror(errno)); |
781 | 0 | return false; |
782 | 0 | } |
783 | | |
784 | 115k | return true; |
785 | 115k | } |
786 | | |
787 | | /** |
788 | | * mutt_file_read_line - Read a line from a file |
789 | | * @param[out] line Buffer allocated on the head (optional) |
790 | | * @param[in] size Length of buffer |
791 | | * @param[in] fp File to read |
792 | | * @param[out] line_num Current line number (optional) |
793 | | * @param[in] flags Flags, e.g. #MUTT_RL_CONT |
794 | | * @retval ptr The allocated string |
795 | | * |
796 | | * Read a line from "fp" into the dynamically allocated "line", increasing |
797 | | * "line" if necessary. The ending "\n" or "\r\n" is removed. If a line ends |
798 | | * with "\", this char and the linefeed is removed, and the next line is read |
799 | | * too. |
800 | | */ |
801 | | char *mutt_file_read_line(char *line, size_t *size, FILE *fp, int *line_num, ReadLineFlags flags) |
802 | 0 | { |
803 | 0 | if (!size || !fp) |
804 | 0 | return NULL; |
805 | | |
806 | 0 | size_t offset = 0; |
807 | 0 | char *ch = NULL; |
808 | |
|
809 | 0 | if (!line) |
810 | 0 | { |
811 | 0 | *size = 256; |
812 | 0 | line = mutt_mem_malloc(*size); |
813 | 0 | } |
814 | |
|
815 | 0 | while (true) |
816 | 0 | { |
817 | 0 | if (!fgets(line + offset, *size - offset, fp)) |
818 | 0 | { |
819 | 0 | FREE(&line); |
820 | 0 | return NULL; |
821 | 0 | } |
822 | 0 | ch = strchr(line + offset, '\n'); |
823 | 0 | if (ch) |
824 | 0 | { |
825 | 0 | if (line_num) |
826 | 0 | (*line_num)++; |
827 | 0 | if (flags & MUTT_RL_EOL) |
828 | 0 | return line; |
829 | 0 | *ch = '\0'; |
830 | 0 | if ((ch > line) && (*(ch - 1) == '\r')) |
831 | 0 | *--ch = '\0'; |
832 | 0 | if (!(flags & MUTT_RL_CONT) || (ch == line) || (*(ch - 1) != '\\')) |
833 | 0 | return line; |
834 | 0 | offset = ch - line - 1; |
835 | 0 | } |
836 | 0 | else |
837 | 0 | { |
838 | 0 | int c; |
839 | 0 | c = getc(fp); /* This is kind of a hack. We want to know if the |
840 | | char at the current point in the input stream is EOF. |
841 | | feof() will only tell us if we've already hit EOF, not |
842 | | if the next character is EOF. So, we need to read in |
843 | | the next character and manually check if it is EOF. */ |
844 | 0 | if (c == EOF) |
845 | 0 | { |
846 | | /* The last line of fp isn't \n terminated */ |
847 | 0 | if (line_num) |
848 | 0 | (*line_num)++; |
849 | 0 | return line; |
850 | 0 | } |
851 | 0 | else |
852 | 0 | { |
853 | 0 | ungetc(c, fp); /* undo our damage */ |
854 | | /* There wasn't room for the line -- increase "line" */ |
855 | 0 | offset = *size - 1; /* overwrite the terminating 0 */ |
856 | 0 | *size += 256; |
857 | 0 | mutt_mem_realloc(&line, *size); |
858 | 0 | } |
859 | 0 | } |
860 | 0 | } |
861 | 0 | } |
862 | | |
863 | | /** |
864 | | * mutt_file_iter_line - Iterate over the lines from an open file pointer |
865 | | * @param iter State of iteration including ptr to line |
866 | | * @param fp File pointer to read from |
867 | | * @param flags Same as mutt_file_read_line() |
868 | | * @retval true Data read |
869 | | * @retval false On eof |
870 | | * |
871 | | * This is a slightly cleaner interface for mutt_file_read_line() which avoids |
872 | | * the eternal C loop initialization ugliness. Use like this: |
873 | | * |
874 | | * ``` |
875 | | * struct MuttFileIter iter = { 0 }; |
876 | | * while (mutt_file_iter_line(&iter, fp, flags)) |
877 | | * { |
878 | | * do_stuff(iter.line, iter.line_num); |
879 | | * } |
880 | | * ``` |
881 | | */ |
882 | | bool mutt_file_iter_line(struct MuttFileIter *iter, FILE *fp, ReadLineFlags flags) |
883 | 0 | { |
884 | 0 | if (!iter) |
885 | 0 | return false; |
886 | | |
887 | 0 | char *p = mutt_file_read_line(iter->line, &iter->size, fp, &iter->line_num, flags); |
888 | 0 | if (!p) |
889 | 0 | return false; |
890 | 0 | iter->line = p; |
891 | 0 | return true; |
892 | 0 | } |
893 | | |
894 | | /** |
895 | | * mutt_file_map_lines - Process lines of text read from a file pointer |
896 | | * @param func Callback function to call for each line, see mutt_file_map_t |
897 | | * @param user_data Arbitrary data passed to "func" |
898 | | * @param fp File pointer to read from |
899 | | * @param flags Same as mutt_file_read_line() |
900 | | * @retval true All data mapped |
901 | | * @retval false "func" returns false |
902 | | */ |
903 | | bool mutt_file_map_lines(mutt_file_map_t func, void *user_data, FILE *fp, ReadLineFlags flags) |
904 | 0 | { |
905 | 0 | if (!func || !fp) |
906 | 0 | return false; |
907 | | |
908 | 0 | struct MuttFileIter iter = { 0 }; |
909 | 0 | while (mutt_file_iter_line(&iter, fp, flags)) |
910 | 0 | { |
911 | 0 | if (!(*func)(iter.line, iter.line_num, user_data)) |
912 | 0 | { |
913 | 0 | FREE(&iter.line); |
914 | 0 | return false; |
915 | 0 | } |
916 | 0 | } |
917 | 0 | return true; |
918 | 0 | } |
919 | | |
920 | | /** |
921 | | * mutt_file_quote_filename - Quote a filename to survive the shell's quoting rules |
922 | | * @param filename String to convert |
923 | | * @param buf Buffer for the result |
924 | | * @param buflen Length of buffer |
925 | | * @retval num Bytes written to the buffer |
926 | | * |
927 | | * From the Unix programming FAQ by way of Liviu. |
928 | | */ |
929 | | size_t mutt_file_quote_filename(const char *filename, char *buf, size_t buflen) |
930 | 0 | { |
931 | 0 | if (!buf) |
932 | 0 | return 0; |
933 | | |
934 | 0 | if (!filename) |
935 | 0 | { |
936 | 0 | *buf = '\0'; |
937 | 0 | return 0; |
938 | 0 | } |
939 | | |
940 | 0 | size_t j = 0; |
941 | | |
942 | | /* leave some space for the trailing characters. */ |
943 | 0 | buflen -= 6; |
944 | |
|
945 | 0 | buf[j++] = '\''; |
946 | |
|
947 | 0 | for (size_t i = 0; (j < buflen) && filename[i]; i++) |
948 | 0 | { |
949 | 0 | if ((filename[i] == '\'') || (filename[i] == '`')) |
950 | 0 | { |
951 | 0 | buf[j++] = '\''; |
952 | 0 | buf[j++] = '\\'; |
953 | 0 | buf[j++] = filename[i]; |
954 | 0 | buf[j++] = '\''; |
955 | 0 | } |
956 | 0 | else |
957 | 0 | { |
958 | 0 | buf[j++] = filename[i]; |
959 | 0 | } |
960 | 0 | } |
961 | |
|
962 | 0 | buf[j++] = '\''; |
963 | 0 | buf[j] = '\0'; |
964 | |
|
965 | 0 | return j; |
966 | 0 | } |
967 | | |
968 | | /** |
969 | | * buf_quote_filename - Quote a filename to survive the shell's quoting rules |
970 | | * @param buf Buffer for the result |
971 | | * @param filename String to convert |
972 | | * @param add_outer If true, add 'single quotes' around the result |
973 | | */ |
974 | | void buf_quote_filename(struct Buffer *buf, const char *filename, bool add_outer) |
975 | 0 | { |
976 | 0 | if (!buf || !filename) |
977 | 0 | return; |
978 | | |
979 | 0 | buf_reset(buf); |
980 | 0 | if (add_outer) |
981 | 0 | buf_addch(buf, '\''); |
982 | |
|
983 | 0 | for (; *filename != '\0'; filename++) |
984 | 0 | { |
985 | 0 | if ((*filename == '\'') || (*filename == '`')) |
986 | 0 | { |
987 | 0 | buf_addch(buf, '\''); |
988 | 0 | buf_addch(buf, '\\'); |
989 | 0 | buf_addch(buf, *filename); |
990 | 0 | buf_addch(buf, '\''); |
991 | 0 | } |
992 | 0 | else |
993 | 0 | { |
994 | 0 | buf_addch(buf, *filename); |
995 | 0 | } |
996 | 0 | } |
997 | |
|
998 | 0 | if (add_outer) |
999 | 0 | buf_addch(buf, '\''); |
1000 | 0 | } |
1001 | | |
1002 | | /** |
1003 | | * mutt_file_mkdir - Recursively create directories |
1004 | | * @param path Directories to create |
1005 | | * @param mode Permissions for final directory |
1006 | | * @retval 0 Success |
1007 | | * @retval -1 Error (errno set) |
1008 | | * |
1009 | | * Create a directory, creating the parents if necessary. (like mkdir -p) |
1010 | | * |
1011 | | * @note The permissions are only set on the final directory. |
1012 | | * The permissions of any parent directories are determined by the umask. |
1013 | | * (This is how "mkdir -p" behaves) |
1014 | | */ |
1015 | | int mutt_file_mkdir(const char *path, mode_t mode) |
1016 | 0 | { |
1017 | 0 | if (!path || (*path == '\0')) |
1018 | 0 | { |
1019 | 0 | errno = EINVAL; |
1020 | 0 | return -1; |
1021 | 0 | } |
1022 | | |
1023 | 0 | errno = 0; |
1024 | 0 | char tmp_path[PATH_MAX] = { 0 }; |
1025 | 0 | const size_t len = strlen(path); |
1026 | |
|
1027 | 0 | if (len >= sizeof(tmp_path)) |
1028 | 0 | { |
1029 | 0 | errno = ENAMETOOLONG; |
1030 | 0 | return -1; |
1031 | 0 | } |
1032 | | |
1033 | 0 | struct stat st = { 0 }; |
1034 | 0 | if ((stat(path, &st) == 0) && S_ISDIR(st.st_mode)) |
1035 | 0 | return 0; |
1036 | | |
1037 | | /* Create a mutable copy */ |
1038 | 0 | mutt_str_copy(tmp_path, path, sizeof(tmp_path)); |
1039 | |
|
1040 | 0 | for (char *p = tmp_path + 1; *p; p++) |
1041 | 0 | { |
1042 | 0 | if (*p != '/') |
1043 | 0 | continue; |
1044 | | |
1045 | | /* Temporarily truncate the path */ |
1046 | 0 | *p = '\0'; |
1047 | |
|
1048 | 0 | if ((mkdir(tmp_path, S_IRWXU | S_IRWXG | S_IRWXO) != 0) && (errno != EEXIST)) |
1049 | 0 | return -1; |
1050 | | |
1051 | 0 | *p = '/'; |
1052 | 0 | } |
1053 | | |
1054 | 0 | if ((mkdir(tmp_path, mode) != 0) && (errno != EEXIST)) |
1055 | 0 | return -1; |
1056 | | |
1057 | 0 | return 0; |
1058 | 0 | } |
1059 | | |
1060 | | /** |
1061 | | * mutt_file_decrease_mtime - Decrease a file's modification time by 1 second |
1062 | | * @param fp Filename |
1063 | | * @param st struct stat for the file (optional) |
1064 | | * @retval num Updated Unix mtime |
1065 | | * @retval -1 Error, see errno |
1066 | | * |
1067 | | * If a file's mtime is NOW, then set it to 1 second in the past. |
1068 | | */ |
1069 | | time_t mutt_file_decrease_mtime(const char *fp, struct stat *st) |
1070 | 0 | { |
1071 | 0 | if (!fp) |
1072 | 0 | return -1; |
1073 | | |
1074 | 0 | struct utimbuf utim = { 0 }; |
1075 | 0 | struct stat st2 = { 0 }; |
1076 | 0 | time_t mtime; |
1077 | |
|
1078 | 0 | if (!st) |
1079 | 0 | { |
1080 | 0 | if (stat(fp, &st2) == -1) |
1081 | 0 | return -1; |
1082 | 0 | st = &st2; |
1083 | 0 | } |
1084 | | |
1085 | 0 | mtime = st->st_mtime; |
1086 | 0 | if (mtime == mutt_date_now()) |
1087 | 0 | { |
1088 | 0 | mtime -= 1; |
1089 | 0 | utim.actime = mtime; |
1090 | 0 | utim.modtime = mtime; |
1091 | 0 | int rc; |
1092 | 0 | do |
1093 | 0 | { |
1094 | 0 | rc = utime(fp, &utim); |
1095 | 0 | } while ((rc == -1) && (errno == EINTR)); |
1096 | |
|
1097 | 0 | if (rc == -1) |
1098 | 0 | return -1; |
1099 | 0 | } |
1100 | | |
1101 | 0 | return mtime; |
1102 | 0 | } |
1103 | | |
1104 | | /** |
1105 | | * mutt_file_set_mtime - Set the modification time of one file from another |
1106 | | * @param from Filename whose mtime should be copied |
1107 | | * @param to Filename to update |
1108 | | */ |
1109 | | void mutt_file_set_mtime(const char *from, const char *to) |
1110 | 0 | { |
1111 | 0 | if (!from || !to) |
1112 | 0 | return; |
1113 | | |
1114 | 0 | struct utimbuf utim = { 0 }; |
1115 | 0 | struct stat st = { 0 }; |
1116 | |
|
1117 | 0 | if (stat(from, &st) != -1) |
1118 | 0 | { |
1119 | 0 | utim.actime = st.st_mtime; |
1120 | 0 | utim.modtime = st.st_mtime; |
1121 | 0 | utime(to, &utim); |
1122 | 0 | } |
1123 | 0 | } |
1124 | | |
1125 | | /** |
1126 | | * mutt_file_touch_atime - Set the access time to current time |
1127 | | * @param fd File descriptor of the file to alter |
1128 | | * |
1129 | | * This is just as read() would do on !noatime. |
1130 | | * Silently ignored if futimens() isn't supported. |
1131 | | */ |
1132 | | void mutt_file_touch_atime(int fd) |
1133 | 0 | { |
1134 | 0 | #ifdef HAVE_FUTIMENS |
1135 | 0 | struct timespec times[2] = { { 0, UTIME_NOW }, { 0, UTIME_OMIT } }; |
1136 | 0 | futimens(fd, times); |
1137 | 0 | #endif |
1138 | 0 | } |
1139 | | |
1140 | | /** |
1141 | | * mutt_file_chmod_add - Add permissions to a file |
1142 | | * @param path Filename |
1143 | | * @param mode the permissions to add |
1144 | | * @retval num Same as chmod(2) |
1145 | | * |
1146 | | * Adds the given permissions to the file. Permissions not mentioned in mode |
1147 | | * will stay as they are. This function resembles the `chmod ugoa+rwxXst` |
1148 | | * command family. Example: |
1149 | | * |
1150 | | * mutt_file_chmod_add(path, S_IWUSR | S_IWGRP | S_IWOTH); |
1151 | | * |
1152 | | * will add write permissions to path but does not alter read and other |
1153 | | * permissions. |
1154 | | * |
1155 | | * @sa mutt_file_chmod_add_stat() |
1156 | | */ |
1157 | | int mutt_file_chmod_add(const char *path, mode_t mode) |
1158 | 0 | { |
1159 | 0 | return mutt_file_chmod_add_stat(path, mode, NULL); |
1160 | 0 | } |
1161 | | |
1162 | | /** |
1163 | | * mutt_file_chmod_add_stat - Add permissions to a file |
1164 | | * @param path Filename |
1165 | | * @param mode the permissions to add |
1166 | | * @param st struct stat for the file (optional) |
1167 | | * @retval num Same as chmod(2) |
1168 | | * |
1169 | | * Same as mutt_file_chmod_add() but saves a system call to stat() if a |
1170 | | * non-NULL stat structure is given. Useful if the stat structure of the file |
1171 | | * was retrieved before by the calling code. Example: |
1172 | | * |
1173 | | * struct stat st; |
1174 | | * stat(path, &st); |
1175 | | * // ... do something else with st ... |
1176 | | * mutt_file_chmod_add_stat(path, S_IWUSR | S_IWGRP | S_IWOTH, st); |
1177 | | * |
1178 | | * @sa mutt_file_chmod_add() |
1179 | | */ |
1180 | | int mutt_file_chmod_add_stat(const char *path, mode_t mode, struct stat *st) |
1181 | 0 | { |
1182 | 0 | if (!path) |
1183 | 0 | return -1; |
1184 | | |
1185 | 0 | struct stat st2 = { 0 }; |
1186 | |
|
1187 | 0 | if (!st) |
1188 | 0 | { |
1189 | 0 | if (stat(path, &st2) == -1) |
1190 | 0 | return -1; |
1191 | 0 | st = &st2; |
1192 | 0 | } |
1193 | 0 | return chmod(path, st->st_mode | mode); |
1194 | 0 | } |
1195 | | |
1196 | | /** |
1197 | | * mutt_file_chmod_rm_stat - Remove permissions from a file |
1198 | | * @param path Filename |
1199 | | * @param mode the permissions to remove |
1200 | | * @param st struct stat for the file (optional) |
1201 | | * @retval num Same as chmod(2) |
1202 | | * |
1203 | | * Same as mutt_file_chmod_rm() but saves a system call to stat() if a non-NULL |
1204 | | * stat structure is given. Useful if the stat structure of the file was |
1205 | | * retrieved before by the calling code. Example: |
1206 | | * |
1207 | | * struct stat st; |
1208 | | * stat(path, &st); |
1209 | | * // ... do something else with st ... |
1210 | | * mutt_file_chmod_rm_stat(path, S_IWUSR | S_IWGRP | S_IWOTH, st); |
1211 | | * |
1212 | | * @sa mutt_file_chmod_rm() |
1213 | | */ |
1214 | | int mutt_file_chmod_rm_stat(const char *path, mode_t mode, struct stat *st) |
1215 | 0 | { |
1216 | 0 | if (!path) |
1217 | 0 | return -1; |
1218 | | |
1219 | 0 | struct stat st2 = { 0 }; |
1220 | |
|
1221 | 0 | if (!st) |
1222 | 0 | { |
1223 | 0 | if (stat(path, &st2) == -1) |
1224 | 0 | return -1; |
1225 | 0 | st = &st2; |
1226 | 0 | } |
1227 | 0 | return chmod(path, st->st_mode & ~mode); |
1228 | 0 | } |
1229 | | |
1230 | | #if defined(USE_FCNTL) |
1231 | | /** |
1232 | | * mutt_file_lock - (Try to) Lock a file using fcntl() |
1233 | | * @param fd File descriptor to file |
1234 | | * @param excl If true, try to lock exclusively |
1235 | | * @param timeout If true, Retry #MAX_LOCK_ATTEMPTS times |
1236 | | * @retval 0 Success |
1237 | | * @retval -1 Failure |
1238 | | * |
1239 | | * Use fcntl() to lock a file. |
1240 | | * |
1241 | | * Use mutt_file_unlock() to unlock the file. |
1242 | | */ |
1243 | | int mutt_file_lock(int fd, bool excl, bool timeout) |
1244 | 0 | { |
1245 | 0 | struct stat st = { 0 }, prev_sb = { 0 }; |
1246 | 0 | int count = 0; |
1247 | 0 | int attempt = 0; |
1248 | |
|
1249 | 0 | struct flock lck = { 0 }; |
1250 | 0 | lck.l_type = excl ? F_WRLCK : F_RDLCK; |
1251 | 0 | lck.l_whence = SEEK_SET; |
1252 | |
|
1253 | 0 | while (fcntl(fd, F_SETLK, &lck) == -1) |
1254 | 0 | { |
1255 | 0 | mutt_debug(LL_DEBUG1, "fcntl errno %d\n", errno); |
1256 | 0 | if ((errno != EAGAIN) && (errno != EACCES)) |
1257 | 0 | { |
1258 | 0 | mutt_perror("fcntl"); |
1259 | 0 | return -1; |
1260 | 0 | } |
1261 | | |
1262 | 0 | if (fstat(fd, &st) != 0) |
1263 | 0 | st.st_size = 0; |
1264 | |
|
1265 | 0 | if (count == 0) |
1266 | 0 | prev_sb = st; |
1267 | | |
1268 | | /* only unlock file if it is unchanged */ |
1269 | 0 | if ((prev_sb.st_size == st.st_size) && (++count >= (timeout ? MAX_LOCK_ATTEMPTS : 0))) |
1270 | 0 | { |
1271 | 0 | if (timeout) |
1272 | 0 | mutt_error(_("Timeout exceeded while attempting fcntl lock")); |
1273 | 0 | return -1; |
1274 | 0 | } |
1275 | | |
1276 | 0 | prev_sb = st; |
1277 | |
|
1278 | 0 | mutt_message(_("Waiting for fcntl lock... %d"), ++attempt); |
1279 | 0 | sleep(1); |
1280 | 0 | } |
1281 | | |
1282 | 0 | return 0; |
1283 | 0 | } |
1284 | | |
1285 | | /** |
1286 | | * mutt_file_unlock - Unlock a file previously locked by mutt_file_lock() |
1287 | | * @param fd File descriptor to file |
1288 | | * @retval 0 Always |
1289 | | */ |
1290 | | int mutt_file_unlock(int fd) |
1291 | 0 | { |
1292 | 0 | struct flock unlockit = { 0 }; |
1293 | 0 | unlockit.l_type = F_UNLCK; |
1294 | 0 | unlockit.l_whence = SEEK_SET; |
1295 | 0 | (void) fcntl(fd, F_SETLK, &unlockit); |
1296 | |
|
1297 | 0 | return 0; |
1298 | 0 | } |
1299 | | #elif defined(USE_FLOCK) |
1300 | | /** |
1301 | | * mutt_file_lock - (Try to) Lock a file using flock() |
1302 | | * @param fd File descriptor to file |
1303 | | * @param excl If true, try to lock exclusively |
1304 | | * @param timeout If true, Retry #MAX_LOCK_ATTEMPTS times |
1305 | | * @retval 0 Success |
1306 | | * @retval -1 Failure |
1307 | | * |
1308 | | * Use flock() to lock a file. |
1309 | | * |
1310 | | * Use mutt_file_unlock() to unlock the file. |
1311 | | */ |
1312 | | int mutt_file_lock(int fd, bool excl, bool timeout) |
1313 | | { |
1314 | | struct stat st = { 0 }, prev_sb = { 0 }; |
1315 | | int rc = 0; |
1316 | | int count = 0; |
1317 | | int attempt = 0; |
1318 | | |
1319 | | while (flock(fd, (excl ? LOCK_EX : LOCK_SH) | LOCK_NB) == -1) |
1320 | | { |
1321 | | if (errno != EWOULDBLOCK) |
1322 | | { |
1323 | | mutt_perror("flock"); |
1324 | | rc = -1; |
1325 | | break; |
1326 | | } |
1327 | | |
1328 | | if (fstat(fd, &st) != 0) |
1329 | | st.st_size = 0; |
1330 | | |
1331 | | if (count == 0) |
1332 | | prev_sb = st; |
1333 | | |
1334 | | /* only unlock file if it is unchanged */ |
1335 | | if ((prev_sb.st_size == st.st_size) && (++count >= (timeout ? MAX_LOCK_ATTEMPTS : 0))) |
1336 | | { |
1337 | | if (timeout) |
1338 | | mutt_error(_("Timeout exceeded while attempting flock lock")); |
1339 | | rc = -1; |
1340 | | break; |
1341 | | } |
1342 | | |
1343 | | prev_sb = st; |
1344 | | |
1345 | | mutt_message(_("Waiting for flock attempt... %d"), ++attempt); |
1346 | | sleep(1); |
1347 | | } |
1348 | | |
1349 | | /* release any other locks obtained in this routine */ |
1350 | | if (rc != 0) |
1351 | | { |
1352 | | flock(fd, LOCK_UN); |
1353 | | } |
1354 | | |
1355 | | return rc; |
1356 | | } |
1357 | | |
1358 | | /** |
1359 | | * mutt_file_unlock - Unlock a file previously locked by mutt_file_lock() |
1360 | | * @param fd File descriptor to file |
1361 | | * @retval 0 Always |
1362 | | */ |
1363 | | int mutt_file_unlock(int fd) |
1364 | | { |
1365 | | flock(fd, LOCK_UN); |
1366 | | return 0; |
1367 | | } |
1368 | | #else |
1369 | | #error "You must select a locking mechanism via USE_FCNTL or USE_FLOCK" |
1370 | | #endif |
1371 | | |
1372 | | /** |
1373 | | * mutt_file_unlink_empty - Delete a file if it's empty |
1374 | | * @param path File to delete |
1375 | | */ |
1376 | | void mutt_file_unlink_empty(const char *path) |
1377 | 0 | { |
1378 | 0 | if (!path) |
1379 | 0 | return; |
1380 | | |
1381 | 0 | struct stat st = { 0 }; |
1382 | |
|
1383 | 0 | int fd = open(path, O_RDWR); |
1384 | 0 | if (fd == -1) |
1385 | 0 | return; |
1386 | | |
1387 | 0 | if (mutt_file_lock(fd, true, true) == -1) |
1388 | 0 | { |
1389 | 0 | close(fd); |
1390 | 0 | return; |
1391 | 0 | } |
1392 | | |
1393 | 0 | if ((fstat(fd, &st) == 0) && (st.st_size == 0)) |
1394 | 0 | unlink(path); |
1395 | |
|
1396 | 0 | mutt_file_unlock(fd); |
1397 | 0 | close(fd); |
1398 | 0 | } |
1399 | | |
1400 | | /** |
1401 | | * mutt_file_rename - Rename a file |
1402 | | * @param oldfile Old filename |
1403 | | * @param newfile New filename |
1404 | | * @retval 0 Success |
1405 | | * @retval 1 Old file doesn't exist |
1406 | | * @retval 2 New file already exists |
1407 | | * @retval 3 Some other error |
1408 | | * |
1409 | | * @note on access(2) use No dangling symlink problems here due to |
1410 | | * mutt_file_fopen(). |
1411 | | */ |
1412 | | int mutt_file_rename(const char *oldfile, const char *newfile) |
1413 | 0 | { |
1414 | 0 | if (!oldfile || !newfile) |
1415 | 0 | return -1; |
1416 | 0 | if (access(oldfile, F_OK) != 0) |
1417 | 0 | return 1; |
1418 | 0 | if (access(newfile, F_OK) == 0) |
1419 | 0 | return 2; |
1420 | | |
1421 | 0 | FILE *fp_old = mutt_file_fopen(oldfile, "r"); |
1422 | 0 | if (!fp_old) |
1423 | 0 | return 3; |
1424 | 0 | FILE *fp_new = mutt_file_fopen(newfile, "w"); |
1425 | 0 | if (!fp_new) |
1426 | 0 | { |
1427 | 0 | mutt_file_fclose(&fp_old); |
1428 | 0 | return 3; |
1429 | 0 | } |
1430 | 0 | mutt_file_copy_stream(fp_old, fp_new); |
1431 | 0 | mutt_file_fclose(&fp_new); |
1432 | 0 | mutt_file_fclose(&fp_old); |
1433 | 0 | mutt_file_unlink(oldfile); |
1434 | 0 | return 0; |
1435 | 0 | } |
1436 | | |
1437 | | /** |
1438 | | * mutt_file_read_keyword - Read a keyword from a file |
1439 | | * @param file File to read |
1440 | | * @param buf Buffer to store the keyword |
1441 | | * @param buflen Length of the buf |
1442 | | * @retval ptr Start of the keyword |
1443 | | * |
1444 | | * Read one line from the start of a file. |
1445 | | * Skip any leading whitespace and extract the first token. |
1446 | | */ |
1447 | | char *mutt_file_read_keyword(const char *file, char *buf, size_t buflen) |
1448 | 0 | { |
1449 | 0 | FILE *fp = mutt_file_fopen(file, "r"); |
1450 | 0 | if (!fp) |
1451 | 0 | return NULL; |
1452 | | |
1453 | 0 | buf = fgets(buf, buflen, fp); |
1454 | 0 | mutt_file_fclose(&fp); |
1455 | |
|
1456 | 0 | if (!buf) |
1457 | 0 | return NULL; |
1458 | | |
1459 | 0 | SKIPWS(buf); |
1460 | 0 | char *start = buf; |
1461 | |
|
1462 | 0 | while ((*buf != '\0') && !isspace(*buf)) |
1463 | 0 | buf++; |
1464 | |
|
1465 | 0 | *buf = '\0'; |
1466 | |
|
1467 | 0 | return start; |
1468 | 0 | } |
1469 | | |
1470 | | /** |
1471 | | * mutt_file_check_empty - Is the mailbox empty |
1472 | | * @param path Path to mailbox |
1473 | | * @retval 1 Mailbox is empty |
1474 | | * @retval 0 Mailbox is not empty |
1475 | | * @retval -1 Error |
1476 | | */ |
1477 | | int mutt_file_check_empty(const char *path) |
1478 | 0 | { |
1479 | 0 | if (!path) |
1480 | 0 | return -1; |
1481 | | |
1482 | 0 | struct stat st = { 0 }; |
1483 | 0 | if (stat(path, &st) == -1) |
1484 | 0 | return -1; |
1485 | | |
1486 | 0 | return st.st_size == 0; |
1487 | 0 | } |
1488 | | |
1489 | | /** |
1490 | | * buf_file_expand_fmt_quote - Replace `%s` in a string with a filename |
1491 | | * @param dest Buffer for the result |
1492 | | * @param fmt printf-like format string |
1493 | | * @param src Filename to substitute |
1494 | | * |
1495 | | * This function also quotes the file to prevent shell problems. |
1496 | | */ |
1497 | | void buf_file_expand_fmt_quote(struct Buffer *dest, const char *fmt, const char *src) |
1498 | 0 | { |
1499 | 0 | struct Buffer tmp = buf_make(PATH_MAX); |
1500 | |
|
1501 | 0 | buf_quote_filename(&tmp, src, true); |
1502 | 0 | mutt_file_expand_fmt(dest, fmt, buf_string(&tmp)); |
1503 | 0 | buf_dealloc(&tmp); |
1504 | 0 | } |
1505 | | |
1506 | | /** |
1507 | | * mutt_file_expand_fmt - Replace `%s` in a string with a filename |
1508 | | * @param dest Buffer for the result |
1509 | | * @param fmt printf-like format string |
1510 | | * @param src Filename to substitute |
1511 | | */ |
1512 | | void mutt_file_expand_fmt(struct Buffer *dest, const char *fmt, const char *src) |
1513 | 0 | { |
1514 | 0 | if (!dest || !fmt || !src) |
1515 | 0 | return; |
1516 | | |
1517 | 0 | const char *p = NULL; |
1518 | 0 | bool found = false; |
1519 | |
|
1520 | 0 | buf_reset(dest); |
1521 | |
|
1522 | 0 | for (p = fmt; *p; p++) |
1523 | 0 | { |
1524 | 0 | if (*p == '%') |
1525 | 0 | { |
1526 | 0 | switch (p[1]) |
1527 | 0 | { |
1528 | 0 | case '%': |
1529 | 0 | buf_addch(dest, *p++); |
1530 | 0 | break; |
1531 | 0 | case 's': |
1532 | 0 | found = true; |
1533 | 0 | buf_addstr(dest, src); |
1534 | 0 | p++; |
1535 | 0 | break; |
1536 | 0 | default: |
1537 | 0 | buf_addch(dest, *p); |
1538 | 0 | break; |
1539 | 0 | } |
1540 | 0 | } |
1541 | 0 | else |
1542 | 0 | { |
1543 | 0 | buf_addch(dest, *p); |
1544 | 0 | } |
1545 | 0 | } |
1546 | | |
1547 | 0 | if (!found) |
1548 | 0 | { |
1549 | 0 | buf_addch(dest, ' '); |
1550 | 0 | buf_addstr(dest, src); |
1551 | 0 | } |
1552 | 0 | } |
1553 | | |
1554 | | /** |
1555 | | * mutt_file_get_size - Get the size of a file |
1556 | | * @param path File to measure |
1557 | | * @retval num Size in bytes |
1558 | | * @retval 0 Error |
1559 | | */ |
1560 | | long mutt_file_get_size(const char *path) |
1561 | 0 | { |
1562 | 0 | if (!path) |
1563 | 0 | return 0; |
1564 | | |
1565 | 0 | struct stat st = { 0 }; |
1566 | 0 | if (stat(path, &st) != 0) |
1567 | 0 | return 0; |
1568 | | |
1569 | 0 | return st.st_size; |
1570 | 0 | } |
1571 | | |
1572 | | /** |
1573 | | * mutt_file_get_size_fp - Get the size of a file |
1574 | | * @param fp FILE* to measure |
1575 | | * @retval num Size in bytes |
1576 | | * @retval 0 Error |
1577 | | */ |
1578 | | long mutt_file_get_size_fp(FILE *fp) |
1579 | 0 | { |
1580 | 0 | if (!fp) |
1581 | 0 | return 0; |
1582 | | |
1583 | 0 | struct stat st = { 0 }; |
1584 | 0 | if (fstat(fileno(fp), &st) != 0) |
1585 | 0 | return 0; |
1586 | | |
1587 | 0 | return st.st_size; |
1588 | 0 | } |
1589 | | |
1590 | | /** |
1591 | | * mutt_file_timespec_compare - Compare to time values |
1592 | | * @param a First time value |
1593 | | * @param b Second time value |
1594 | | * @retval -1 a precedes b |
1595 | | * @retval 0 a and b are identical |
1596 | | * @retval 1 b precedes a |
1597 | | */ |
1598 | | int mutt_file_timespec_compare(struct timespec *a, struct timespec *b) |
1599 | 0 | { |
1600 | 0 | if (!a || !b) |
1601 | 0 | return 0; |
1602 | 0 | if (a->tv_sec < b->tv_sec) |
1603 | 0 | return -1; |
1604 | 0 | if (a->tv_sec > b->tv_sec) |
1605 | 0 | return 1; |
1606 | | |
1607 | 0 | if (a->tv_nsec < b->tv_nsec) |
1608 | 0 | return -1; |
1609 | 0 | if (a->tv_nsec > b->tv_nsec) |
1610 | 0 | return 1; |
1611 | 0 | return 0; |
1612 | 0 | } |
1613 | | |
1614 | | /** |
1615 | | * mutt_file_get_stat_timespec - Read the stat() time into a time value |
1616 | | * @param dest Time value to populate |
1617 | | * @param st stat info |
1618 | | * @param type Type of stat info to read, e.g. #MUTT_STAT_ATIME |
1619 | | */ |
1620 | | void mutt_file_get_stat_timespec(struct timespec *dest, struct stat *st, enum MuttStatType type) |
1621 | 0 | { |
1622 | 0 | if (!dest || !st) |
1623 | 0 | return; |
1624 | | |
1625 | 0 | dest->tv_sec = 0; |
1626 | 0 | dest->tv_nsec = 0; |
1627 | |
|
1628 | 0 | switch (type) |
1629 | 0 | { |
1630 | 0 | case MUTT_STAT_ATIME: |
1631 | 0 | dest->tv_sec = st->st_atime; |
1632 | 0 | #ifdef HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC |
1633 | 0 | dest->tv_nsec = st->st_atim.tv_nsec; |
1634 | 0 | #endif |
1635 | 0 | break; |
1636 | 0 | case MUTT_STAT_MTIME: |
1637 | 0 | dest->tv_sec = st->st_mtime; |
1638 | | #ifdef HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC |
1639 | | dest->tv_nsec = st->st_mtim.tv_nsec; |
1640 | | #endif |
1641 | 0 | break; |
1642 | 0 | case MUTT_STAT_CTIME: |
1643 | 0 | dest->tv_sec = st->st_ctime; |
1644 | | #ifdef HAVE_STRUCT_STAT_ST_CTIM_TV_NSEC |
1645 | | dest->tv_nsec = st->st_ctim.tv_nsec; |
1646 | | #endif |
1647 | 0 | break; |
1648 | 0 | } |
1649 | 0 | } |
1650 | | |
1651 | | /** |
1652 | | * mutt_file_stat_timespec_compare - Compare stat info with a time value |
1653 | | * @param st stat info |
1654 | | * @param type Type of stat info, e.g. #MUTT_STAT_ATIME |
1655 | | * @param b Time value |
1656 | | * @retval -1 a precedes b |
1657 | | * @retval 0 a and b are identical |
1658 | | * @retval 1 b precedes a |
1659 | | */ |
1660 | | int mutt_file_stat_timespec_compare(struct stat *st, enum MuttStatType type, |
1661 | | struct timespec *b) |
1662 | 0 | { |
1663 | 0 | if (!st || !b) |
1664 | 0 | return 0; |
1665 | | |
1666 | 0 | struct timespec a = { 0 }; |
1667 | |
|
1668 | 0 | mutt_file_get_stat_timespec(&a, st, type); |
1669 | 0 | return mutt_file_timespec_compare(&a, b); |
1670 | 0 | } |
1671 | | |
1672 | | /** |
1673 | | * mutt_file_stat_compare - Compare two stat infos |
1674 | | * @param st1 First stat info |
1675 | | * @param st1_type Type of first stat info, e.g. #MUTT_STAT_ATIME |
1676 | | * @param st2 Second stat info |
1677 | | * @param st2_type Type of second stat info, e.g. #MUTT_STAT_ATIME |
1678 | | * @retval -1 a precedes b |
1679 | | * @retval 0 a and b are identical |
1680 | | * @retval 1 b precedes a |
1681 | | */ |
1682 | | int mutt_file_stat_compare(struct stat *st1, enum MuttStatType st1_type, |
1683 | | struct stat *st2, enum MuttStatType st2_type) |
1684 | 0 | { |
1685 | 0 | if (!st1 || !st2) |
1686 | 0 | return 0; |
1687 | | |
1688 | 0 | struct timespec a = { 0 }; |
1689 | 0 | struct timespec b = { 0 }; |
1690 | |
|
1691 | 0 | mutt_file_get_stat_timespec(&a, st1, st1_type); |
1692 | 0 | mutt_file_get_stat_timespec(&b, st2, st2_type); |
1693 | 0 | return mutt_file_timespec_compare(&a, &b); |
1694 | 0 | } |
1695 | | |
1696 | | /** |
1697 | | * mutt_file_resolve_symlink - Resolve a symlink in place |
1698 | | * @param buf Input/output path |
1699 | | */ |
1700 | | void mutt_file_resolve_symlink(struct Buffer *buf) |
1701 | 0 | { |
1702 | 0 | struct stat st = { 0 }; |
1703 | 0 | int rc = lstat(buf_string(buf), &st); |
1704 | 0 | if ((rc != -1) && S_ISLNK(st.st_mode)) |
1705 | 0 | { |
1706 | 0 | char path[PATH_MAX] = { 0 }; |
1707 | 0 | if (realpath(buf_string(buf), path)) |
1708 | 0 | { |
1709 | 0 | buf_strcpy(buf, path); |
1710 | 0 | } |
1711 | 0 | } |
1712 | 0 | } |
1713 | | |
1714 | | /** |
1715 | | * mutt_file_save_str - Save a string to a file |
1716 | | * @param fp Open file to save to |
1717 | | * @param str String to save |
1718 | | * @retval num Bytes written to file |
1719 | | */ |
1720 | | size_t mutt_file_save_str(FILE *fp, const char *str) |
1721 | 0 | { |
1722 | 0 | if (!fp) |
1723 | 0 | return 0; |
1724 | | |
1725 | 0 | size_t len = mutt_str_len(str); |
1726 | 0 | if (len == 0) |
1727 | 0 | return 0; |
1728 | | |
1729 | 0 | return fwrite(str, 1, len, fp); |
1730 | 0 | } |