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