Coverage Report

Created: 2024-02-25 06:12

/src/neomutt/mutt/file.c
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
}