Coverage Report

Created: 2025-03-11 06:49

/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 <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
}