Coverage Report

Created: 2026-08-08 06:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib-fs/fs-posix.c
Line
Count
Source
1
/* Copyright (c) Dovecot authors, see top-level COPYING file */
2
3
#include "lib.h"
4
#include "buffer.h"
5
#include "str.h"
6
#include "guid.h"
7
#include "istream.h"
8
#include "ostream.h"
9
#include "safe-mkstemp.h"
10
#include "mkdir-parents.h"
11
#include "write-full.h"
12
#include "file-lock.h"
13
#include "file-dotlock.h"
14
#include "time-util.h"
15
#include "settings.h"
16
#include "fs-api-private.h"
17
18
#include <stdio.h>
19
#include <unistd.h>
20
#include <dirent.h>
21
#include <fcntl.h>
22
#include <sys/stat.h>
23
24
0
#define FS_POSIX_DOTLOCK_STALE_TIMEOUT_SECS (60*10)
25
0
#define MAX_MKDIR_RETRY_COUNT 5
26
27
enum fs_posix_lock_method {
28
  FS_POSIX_LOCK_METHOD_FLOCK,
29
  FS_POSIX_LOCK_METHOD_DOTLOCK
30
};
31
32
struct fs_posix_settings {
33
  pool_t pool;
34
  const char *fs_posix_lock_method;
35
  const char *fs_posix_prefix;
36
  unsigned int fs_posix_mode;
37
  bool fs_posix_autodelete_empty_directories;
38
  bool fs_posix_fsync;
39
  bool fs_posix_accurate_mtime;
40
41
  enum fs_posix_lock_method parsed_lock_method;
42
};
43
44
struct posix_fs {
45
  struct fs fs;
46
  char *temp_file_prefix, *root_path;
47
  size_t temp_file_prefix_len;
48
  const struct fs_posix_settings *set;
49
};
50
51
struct posix_fs_file {
52
  struct fs_file file;
53
  char *temp_path, *full_path;
54
  int fd;
55
  enum fs_open_mode open_mode;
56
  enum fs_open_flags open_flags;
57
58
  buffer_t *write_buf;
59
60
  bool seek_to_beginning;
61
};
62
63
struct posix_fs_lock {
64
  struct fs_lock lock;
65
  struct file_lock *file_lock;
66
  struct dotlock *dotlock;
67
};
68
69
struct posix_fs_iter {
70
  struct fs_iter iter;
71
  char *path;
72
  DIR *dir;
73
  int err;
74
};
75
76
static bool fs_posix_settings_check(void *_set, pool_t pool, const char **error_r);
77
78
#undef DEF
79
#define DEF(type, name) \
80
  SETTING_DEFINE_STRUCT_##type(#name, name, struct fs_posix_settings)
81
static const struct setting_define fs_posix_setting_defines[] = {
82
  DEF(ENUM, fs_posix_lock_method),
83
  DEF(STR, fs_posix_prefix),
84
  DEF(UINT_OCT, fs_posix_mode),
85
  DEF(BOOL_HIDDEN, fs_posix_autodelete_empty_directories),
86
  DEF(BOOL, fs_posix_fsync),
87
  DEF(BOOL, fs_posix_accurate_mtime),
88
89
  SETTING_DEFINE_LIST_END
90
};
91
static const struct fs_posix_settings fs_posix_default_settings = {
92
  .fs_posix_lock_method = "flock:dotlock",
93
  .fs_posix_prefix = "",
94
  .fs_posix_mode = 0600,
95
  .fs_posix_autodelete_empty_directories = FALSE,
96
  .fs_posix_fsync = TRUE,
97
  .fs_posix_accurate_mtime = FALSE,
98
};
99
100
const struct setting_parser_info fs_posix_setting_parser_info = {
101
  .name = "fs_posix",
102
103
  .defines = fs_posix_setting_defines,
104
  .defaults = &fs_posix_default_settings,
105
106
  .struct_size = sizeof(struct fs_posix_settings),
107
  .pool_offset1 = 1 + offsetof(struct fs_posix_settings, pool),
108
  .check_func = fs_posix_settings_check,
109
};
110
111
static struct fs *fs_posix_alloc(void)
112
0
{
113
0
  struct posix_fs *fs;
114
115
0
  fs = i_new(struct posix_fs, 1);
116
0
  fs->fs = fs_class_posix;
117
0
  return &fs->fs;
118
0
}
119
120
static bool fs_posix_settings_check(void *_set, pool_t pool ATTR_UNUSED,
121
            const char **error_r)
122
0
{
123
0
  struct fs_posix_settings *set = _set;
124
125
0
  if (strcmp(set->fs_posix_lock_method, "flock") == 0)
126
0
    set->parsed_lock_method = FS_POSIX_LOCK_METHOD_FLOCK;
127
0
  else if (strcmp(set->fs_posix_lock_method, "dotlock") == 0)
128
0
    set->parsed_lock_method = FS_POSIX_LOCK_METHOD_DOTLOCK;
129
0
  else {
130
0
    *error_r = "Invalid fs_posix_lock_method";
131
0
    return FALSE;
132
0
  }
133
0
  return TRUE;
134
0
}
135
136
static void fs_posix_init_common(struct posix_fs *fs,
137
         const struct fs_parameters *params)
138
0
{
139
0
  fs->temp_file_prefix = params->temp_file_prefix != NULL ?
140
0
    i_strdup(params->temp_file_prefix) : i_strdup("temp.dovecot.");
141
0
  fs->temp_file_prefix_len = strlen(fs->temp_file_prefix);
142
0
  fs->root_path = i_strdup(params->root_path);
143
0
}
144
145
static int
146
fs_posix_init(struct fs *_fs, const struct fs_parameters *params,
147
        const char **error_r)
148
0
{
149
0
  struct posix_fs *fs = container_of(_fs, struct posix_fs, fs);
150
151
0
  if (settings_get(_fs->event, &fs_posix_setting_parser_info, 0,
152
0
       &fs->set, error_r) < 0)
153
0
    return -1;
154
155
0
  fs_posix_init_common(fs, params);
156
0
  return 0;
157
0
}
158
159
static void fs_posix_free(struct fs *_fs)
160
0
{
161
0
  struct posix_fs *fs = container_of(_fs, struct posix_fs, fs);
162
163
0
  settings_free(fs->set);
164
0
  i_free(fs->temp_file_prefix);
165
0
  i_free(fs->root_path);
166
0
  i_free(fs);
167
0
}
168
169
static enum fs_properties fs_posix_get_properties(struct fs *_fs)
170
0
{
171
0
  struct posix_fs *fs = container_of(_fs, struct posix_fs, fs);
172
0
  enum fs_properties props =
173
0
    FS_PROPERTY_LOCKS | FS_PROPERTY_FASTCOPY | FS_PROPERTY_RENAME |
174
0
    FS_PROPERTY_STAT | FS_PROPERTY_ITER | FS_PROPERTY_RELIABLEITER;
175
176
  /* FS_PROPERTY_DIRECTORIES is not returned normally because fs_delete()
177
     automatically rmdir()s parents. For backwards compatibility
178
     (especially with SIS code) we'll do it that way, but optionally with
179
     "dirs" parameter enable them. This is especially important to be
180
     able to use doveadm fs commands to delete empty directories. */
181
0
  if (!fs->set->fs_posix_autodelete_empty_directories)
182
0
    props |= FS_PROPERTY_DIRECTORIES;
183
0
  return props;
184
0
}
185
186
static int
187
fs_posix_get_mode(struct posix_fs_file *file, const char *path, mode_t *mode_r)
188
0
{
189
0
  struct posix_fs *fs = (struct posix_fs *)file->file.fs;
190
0
  struct stat st;
191
0
  const char *p;
192
193
0
  *mode_r = fs->set->fs_posix_mode;
194
195
  /* This function is used to get mode of the parent directory, so path
196
     is never the same as file->path. The file is used just to set the
197
     errors. */
198
0
  while (stat(path, &st) < 0) {
199
0
    if (errno != ENOENT) {
200
0
      fs_set_error_errno(file->file.event,
201
0
             "stat(%s) failed: %m", path);
202
0
      return -1;
203
0
    }
204
0
    p = strrchr(path, '/');
205
0
    if (p != NULL)
206
0
      path = t_strdup_until(path, p);
207
0
    else if (strcmp(path, ".") != 0)
208
0
      path = ".";
209
0
    else
210
0
      return 0;
211
0
  }
212
0
  if ((st.st_mode & S_ISGID) != 0) {
213
    /* setgid set - copy mode from parent */
214
0
    *mode_r = st.st_mode & 0666;
215
0
  }
216
0
  return 0;
217
0
}
218
219
static int fs_posix_mkdir_parents(struct posix_fs_file *file, const char *path)
220
0
{
221
0
  const char *dir, *fname;
222
0
  mode_t mode, dir_mode;
223
224
0
  fname = strrchr(path, '/');
225
0
  if (fname == NULL)
226
0
    return 1;
227
0
  dir = t_strdup_until(path, fname);
228
229
0
  if (fs_posix_get_mode(file, dir, &mode) < 0)
230
0
    return -1;
231
0
  dir_mode = mode;
232
0
  if ((dir_mode & 0600) != 0) dir_mode |= 0100;
233
0
  if ((dir_mode & 0060) != 0) dir_mode |= 0010;
234
0
  if ((dir_mode & 0006) != 0) dir_mode |= 0001;
235
236
0
  if (mkdir_parents(dir, dir_mode) == 0)
237
0
    return 0;
238
0
  else if (errno == EEXIST)
239
0
    return 1;
240
0
  else {
241
0
    fs_set_error_errno(file->file.event,
242
0
           "mkdir_parents(%s) failed: %m", dir);
243
0
    return -1;
244
0
  }
245
0
}
246
247
static int fs_posix_rmdir_parents(struct posix_fs_file *file, const char *path)
248
0
{
249
0
  struct posix_fs *fs = (struct posix_fs *)file->file.fs;
250
0
  const char *p;
251
252
0
  if (!fs->set->fs_posix_autodelete_empty_directories)
253
0
    return 0;
254
0
  if (fs->root_path == NULL && fs->set->fs_posix_prefix[0] == '\0')
255
0
    return 0;
256
257
0
  while ((p = strrchr(path, '/')) != NULL) {
258
0
    path = t_strdup_until(path, p);
259
0
    if ((fs->root_path != NULL && strcmp(path, fs->root_path) == 0) ||
260
0
        str_begins_with(fs->set->fs_posix_prefix, path))
261
0
      break;
262
0
    if (rmdir(path) == 0) {
263
      /* success, continue to parent */
264
0
    } else if (errno == ENOTEMPTY || errno == EEXIST) {
265
      /* there are other entries in this directory */
266
0
      break;
267
0
    } else if (errno == EBUSY || errno == ENOENT) {
268
      /* some other not-unexpected error */
269
0
      break;
270
0
    } else {
271
0
      fs_set_error_errno(file->file.event,
272
0
             "rmdir(%s) failed: %m", path);
273
0
      return -1;
274
0
    }
275
0
  }
276
0
  return 0;
277
0
}
278
279
static int fs_posix_create(struct posix_fs_file *file)
280
0
{
281
0
  struct posix_fs *fs = container_of(file->file.fs, struct posix_fs, fs);
282
0
  string_t *str = t_str_new(256);
283
0
  const char *slash;
284
0
  unsigned int try_count = 0;
285
0
  mode_t mode;
286
0
  int fd;
287
288
0
  i_assert(file->temp_path == NULL);
289
290
0
  if ((slash = strrchr(file->full_path, '/')) != NULL) {
291
0
    str_append_data(str, file->full_path, slash - file->full_path);
292
0
    if (fs_posix_get_mode(file, str_c(str), &mode) < 0)
293
0
      return -1;
294
0
    str_append_c(str, '/');
295
0
  } else {
296
0
    if (fs_posix_get_mode(file, ".", &mode) < 0)
297
0
      return -1;
298
0
  }
299
0
  str_append(str, fs->temp_file_prefix);
300
301
0
  fd = safe_mkstemp_hostpid(str, mode, (uid_t)-1, (gid_t)-1);
302
0
  while (fd == -1 && errno == ENOENT &&
303
0
         try_count <= MAX_MKDIR_RETRY_COUNT) {
304
0
    if (fs_posix_mkdir_parents(file, str_c(str)) < 0)
305
0
      return -1;
306
0
    fd = safe_mkstemp_hostpid(str, mode, (uid_t)-1, (gid_t)-1);
307
0
    try_count++;
308
0
  }
309
0
  if (fd == -1) {
310
0
    fs_set_error_errno(file->file.event,
311
0
           "safe_mkstemp(%s) failed: %m", str_c(str));
312
0
    return -1;
313
0
  }
314
0
  file->temp_path = i_strdup(str_c(str));
315
0
  return fd;
316
0
}
317
318
static int fs_posix_open(struct posix_fs_file *file)
319
0
{
320
0
  const char *path = file->full_path;
321
322
0
  i_assert(file->fd == -1);
323
324
0
  switch (file->open_mode) {
325
0
  case FS_OPEN_MODE_READONLY:
326
0
    file->fd = open(path, O_RDONLY);
327
0
    if (file->fd == -1)
328
0
      fs_set_error_errno(file->file.event,
329
0
             "open(%s) failed: %m", path);
330
0
    break;
331
0
  case FS_OPEN_MODE_APPEND:
332
0
    file->fd = open(path, O_RDWR | O_APPEND);
333
0
    if (file->fd == -1)
334
0
      fs_set_error_errno(file->file.event,
335
0
             "open(%s) failed: %m", path);
336
0
    break;
337
0
  case FS_OPEN_MODE_CREATE_UNIQUE_128:
338
0
  case FS_OPEN_MODE_CREATE:
339
0
  case FS_OPEN_MODE_REPLACE:
340
0
    T_BEGIN {
341
0
      file->fd = fs_posix_create(file);
342
0
    } T_END;
343
0
    break;
344
0
  }
345
0
  if (file->fd == -1)
346
0
    return -1;
347
0
  return 0;
348
0
}
349
350
static struct fs_file *fs_posix_file_alloc(void)
351
0
{
352
0
  struct posix_fs_file *file = i_new(struct posix_fs_file, 1);
353
0
  return &file->file;
354
0
}
355
356
static void
357
fs_posix_file_init(struct fs_file *_file, const char *path,
358
       enum fs_open_mode mode, enum fs_open_flags flags)
359
0
{
360
0
  struct posix_fs_file *file =
361
0
    container_of(_file, struct posix_fs_file, file);
362
0
  struct posix_fs *fs = container_of(_file->fs, struct posix_fs, fs);
363
0
  guid_128_t guid;
364
0
  size_t path_len = strlen(path);
365
366
0
  if (path_len > 0 && path[path_len-1] == '/') {
367
    /* deleting "path/" (used e.g. by doveadm fs delete) - strip
368
       out the trailing "/" since it doesn't work well with NFS. */
369
0
    path = t_strndup(path, path_len-1);
370
0
  }
371
372
0
  if (mode != FS_OPEN_MODE_CREATE_UNIQUE_128)
373
0
    file->file.path = i_strdup(path);
374
0
  else {
375
0
    guid_128_generate(guid);
376
0
    file->file.path = i_strdup_printf("%s/%s", path,
377
0
              guid_128_to_string(guid));
378
0
  }
379
0
  file->full_path = i_strconcat(fs->set->fs_posix_prefix,
380
0
              file->file.path, NULL);
381
0
  file->open_mode = mode;
382
0
  file->open_flags = flags;
383
0
  file->fd = -1;
384
0
}
385
386
static void fs_posix_file_close(struct fs_file *_file)
387
0
{
388
0
  struct posix_fs_file *file =
389
0
    container_of(_file, struct posix_fs_file, file);
390
391
0
  if (file->fd != -1 && file->file.output == NULL) {
392
0
    if (close(file->fd) < 0) {
393
0
      e_error(_file->event, "close(%s) failed: %m",
394
0
        file->full_path);
395
0
    }
396
0
    file->fd = -1;
397
0
  }
398
0
}
399
400
static bool fs_posix_file_equals(struct fs_file *_file1, struct fs_file *_file2)
401
0
{
402
0
  struct posix_fs_file *file1 =
403
0
    container_of(_file1, struct posix_fs_file, file);
404
0
  struct posix_fs_file *file2 =
405
0
    container_of(_file2, struct posix_fs_file, file);
406
0
  return strcmp(file1->full_path, file2->full_path) == 0;
407
0
}
408
409
static void fs_posix_file_deinit(struct fs_file *_file)
410
0
{
411
0
  struct posix_fs_file *file =
412
0
    container_of(_file, struct posix_fs_file, file);
413
414
0
  i_assert(_file->output == NULL);
415
416
0
  switch (file->open_mode) {
417
0
  case FS_OPEN_MODE_READONLY:
418
0
  case FS_OPEN_MODE_APPEND:
419
0
    break;
420
0
  case FS_OPEN_MODE_CREATE_UNIQUE_128:
421
0
  case FS_OPEN_MODE_CREATE:
422
0
  case FS_OPEN_MODE_REPLACE:
423
0
    if (file->temp_path == NULL)
424
0
      break;
425
    /* failed to create/replace this. delete the temp file */
426
0
    if (unlink(file->temp_path) < 0) {
427
0
      e_error(_file->event, "unlink(%s) failed: %m",
428
0
        file->temp_path);
429
0
    }
430
0
    break;
431
0
  }
432
433
0
  fs_file_free(_file);
434
0
  i_free(file->temp_path);
435
0
  i_free(file->full_path);
436
0
  i_free(file->file.path);
437
0
  i_free(file);
438
0
}
439
440
static int fs_posix_open_for_read(struct posix_fs_file *file)
441
0
{
442
0
  i_assert(file->file.output == NULL);
443
0
  i_assert(file->temp_path == NULL);
444
445
0
  if (file->fd == -1) {
446
0
    if (fs_posix_open(file) < 0)
447
0
      return -1;
448
0
  }
449
0
  return 0;
450
0
}
451
452
static bool fs_posix_prefetch(struct fs_file *_file, uoff_t length ATTR_UNUSED)
453
0
{
454
0
  struct posix_fs_file *file =
455
0
    container_of(_file, struct posix_fs_file, file);
456
457
0
  if (fs_posix_open_for_read(file) < 0)
458
0
    return TRUE;
459
460
/* HAVE_POSIX_FADVISE alone isn't enough for CentOS 4.9 */
461
0
#if defined(HAVE_POSIX_FADVISE) && defined(POSIX_FADV_WILLNEED)
462
0
  if ((errno = posix_fadvise(file->fd, 0, (off_t)length, POSIX_FADV_WILLNEED)) != 0) {
463
0
    e_error(_file->event, "posix_fadvise(%s) failed: %m", file->full_path);
464
0
    return TRUE;
465
0
  }
466
0
#endif
467
0
  return FALSE;
468
0
}
469
470
static ssize_t fs_posix_read(struct fs_file *_file, void *buf, size_t size)
471
0
{
472
0
  struct posix_fs_file *file =
473
0
    container_of(_file, struct posix_fs_file, file);
474
0
  ssize_t ret;
475
476
0
  if (fs_posix_open_for_read(file) < 0)
477
0
    return -1;
478
479
0
  if (file->seek_to_beginning) {
480
0
    file->seek_to_beginning = FALSE;
481
0
    if (lseek(file->fd, 0, SEEK_SET) < 0) {
482
0
      fs_set_error_errno(_file->event,
483
0
             "lseek(%s, 0) failed: %m",
484
0
             file->full_path);
485
0
      return -1;
486
0
    }
487
0
  }
488
489
0
  ret = read(file->fd, buf, size);
490
0
  if (ret < 0)
491
0
    fs_set_error_errno(_file->event, "read(%s) failed: %m",
492
0
           file->full_path);
493
0
  fs_posix_file_close(_file);
494
0
  return ret;
495
0
}
496
497
static struct istream *
498
fs_posix_read_stream(struct fs_file *_file, size_t max_buffer_size)
499
0
{
500
0
  struct posix_fs_file *file =
501
0
    container_of(_file, struct posix_fs_file, file);
502
0
  struct istream *input;
503
0
  int fd_dup;
504
505
0
  if (fs_posix_open_for_read(file) < 0)
506
0
    input = i_stream_create_error_str(errno, "%s", fs_file_last_error(_file));
507
0
  else if ((fd_dup = dup(file->fd)) == -1)
508
0
    input = i_stream_create_error_str(errno, "dup() failed: %m");
509
0
  else {
510
    /* The stream could live even after the fs_file.
511
       Don't use file->fd directly, because the fd may still be
512
       used for other purposes. It's especially important for files
513
       that were just created. */
514
0
    input = i_stream_create_fd_autoclose(&fd_dup, max_buffer_size);
515
0
  }
516
0
  i_stream_set_name(input, file->full_path);
517
0
  return input;
518
0
}
519
520
static void fs_posix_write_rename_if_needed(struct posix_fs_file *file)
521
0
{
522
0
  struct posix_fs *fs = container_of(file->file.fs, struct posix_fs, fs);
523
0
  const char *new_fname;
524
525
0
  new_fname = fs_metadata_find(&file->file.metadata, FS_METADATA_WRITE_FNAME);
526
0
  if (new_fname == NULL)
527
0
    return;
528
529
0
  i_free(file->file.path);
530
0
  file->file.path = i_strdup(new_fname);
531
532
0
  i_free(file->full_path);
533
0
  file->full_path = i_strconcat(fs->set->fs_posix_prefix,
534
0
              file->file.path, NULL);
535
0
}
536
537
static int fs_posix_write_finish_link(struct posix_fs_file *file)
538
0
{
539
0
  unsigned int try_count = 0;
540
0
  int ret;
541
542
0
  ret = link(file->temp_path, file->full_path);
543
0
  while (ret < 0 && errno == ENOENT &&
544
0
         try_count <= MAX_MKDIR_RETRY_COUNT) {
545
0
    if (fs_posix_mkdir_parents(file, file->full_path) < 0)
546
0
      return -1;
547
0
    ret = link(file->temp_path, file->full_path);
548
0
    try_count++;
549
0
  }
550
0
  if (ret < 0) {
551
0
    fs_set_error_errno(file->file.event, "link(%s, %s) failed: %m",
552
0
           file->temp_path, file->full_path);
553
0
  }
554
0
  return ret;
555
0
}
556
557
static int fs_posix_write_finish(struct posix_fs_file *file)
558
0
{
559
0
  struct posix_fs *fs = container_of(file->file.fs, struct posix_fs, fs);
560
0
  unsigned int try_count = 0;
561
0
  int ret, old_errno;
562
563
0
  if ((file->open_flags & FS_OPEN_FLAG_FSYNC) != 0 &&
564
0
      fs->set->fs_posix_fsync) {
565
0
    if (fdatasync(file->fd) < 0) {
566
0
      fs_set_error_errno(file->file.event,
567
0
             "fdatasync(%s) failed: %m",
568
0
             file->full_path);
569
0
      return -1;
570
0
    }
571
0
  }
572
0
  if (fs->set->fs_posix_accurate_mtime) {
573
    /* Linux updates the mtime timestamp only on timer interrupts.
574
       This isn't anywhere close to being microsecond precision.
575
       If requested, use utimes() to explicitly set a more accurate
576
       mtime. */
577
0
    struct timeval tv[2];
578
0
    i_gettimeofday(&tv[0]);
579
0
    tv[1] = tv[0];
580
0
    if ((utimes(file->temp_path, tv)) < 0) {
581
0
      fs_set_error_errno(file->file.event,
582
0
             "utimes(%s) failed: %m",
583
0
             file->temp_path);
584
0
      return -1;
585
0
    }
586
0
  }
587
588
0
  fs_posix_write_rename_if_needed(file);
589
0
  switch (file->open_mode) {
590
0
  case FS_OPEN_MODE_CREATE_UNIQUE_128:
591
0
  case FS_OPEN_MODE_CREATE:
592
0
    ret = fs_posix_write_finish_link(file);
593
0
    old_errno = errno;
594
0
    if (unlink(file->temp_path) < 0) {
595
0
      fs_set_error_errno(file->file.event,
596
0
             "unlink(%s) failed: %m",
597
0
             file->temp_path);
598
0
    }
599
0
    errno = old_errno;
600
0
    if (ret < 0) {
601
0
      fs_posix_file_close(&file->file);
602
0
      i_free_and_null(file->temp_path);
603
0
      return -1;
604
0
    }
605
0
    break;
606
0
  case FS_OPEN_MODE_REPLACE:
607
0
    ret = rename(file->temp_path, file->full_path);
608
0
    while (ret < 0 && errno == ENOENT &&
609
0
           try_count <= MAX_MKDIR_RETRY_COUNT) {
610
0
      if (fs_posix_mkdir_parents(file, file->full_path) < 0)
611
0
        return -1;
612
0
      ret = rename(file->temp_path, file->full_path);
613
0
      try_count++;
614
0
    }
615
0
    if (ret < 0) {
616
0
      fs_set_error_errno(file->file.event,
617
0
             "rename(%s, %s) failed: %m",
618
0
             file->temp_path, file->full_path);
619
0
      return -1;
620
0
    }
621
0
    break;
622
0
  default:
623
0
    i_unreached();
624
0
  }
625
0
  i_free_and_null(file->temp_path);
626
0
  file->seek_to_beginning = TRUE;
627
  /* allow opening the file after writing to it */
628
0
  file->open_mode = FS_OPEN_MODE_READONLY;
629
0
  return 0;
630
0
}
631
632
static int fs_posix_write(struct fs_file *_file, const void *data, size_t size)
633
0
{
634
0
  struct posix_fs_file *file =
635
0
    container_of(_file, struct posix_fs_file, file);
636
0
  ssize_t ret;
637
638
0
  if (file->fd == -1) {
639
0
    if (fs_posix_open(file) < 0)
640
0
      return -1;
641
0
    i_assert(file->fd != -1);
642
0
  }
643
644
0
  if (file->open_mode != FS_OPEN_MODE_APPEND) {
645
0
    if (write_full(file->fd, data, size) < 0) {
646
0
      fs_set_error_errno(_file->event, "write(%s) failed: %m",
647
0
             file->full_path);
648
0
      return -1;
649
0
    }
650
0
    return fs_posix_write_finish(file);
651
0
  }
652
653
  /* atomic append - it should either succeed or fail */
654
0
  ret = write(file->fd, data, size);
655
0
  if (ret < 0) {
656
0
    fs_set_error_errno(_file->event, "write(%s) failed: %m",
657
0
           file->full_path);
658
0
    return -1;
659
0
  }
660
0
  if ((size_t)ret != size) {
661
0
    fs_set_error(_file->event, ENOSPC,
662
0
           "write(%s) returned %zu/%zu",
663
0
           file->full_path, (size_t)ret, size);
664
0
    return -1;
665
0
  }
666
0
  return 0;
667
0
}
668
669
static void fs_posix_write_stream(struct fs_file *_file)
670
0
{
671
0
  struct posix_fs_file *file =
672
0
    container_of(_file, struct posix_fs_file, file);
673
674
0
  i_assert(_file->output == NULL);
675
676
0
  if (file->open_mode == FS_OPEN_MODE_APPEND) {
677
0
    file->write_buf = buffer_create_dynamic(default_pool, 1024*32);
678
0
    _file->output = o_stream_create_buffer(file->write_buf);
679
0
  } else if (file->fd == -1 && fs_posix_open(file) < 0) {
680
0
    _file->output = o_stream_create_error_str(errno, "%s",
681
0
      fs_file_last_error(_file));
682
0
  } else {
683
0
    i_assert(file->fd != -1);
684
0
    _file->output = o_stream_create_fd_file(file->fd,
685
0
              UOFF_T_MAX, FALSE);
686
0
  }
687
0
  o_stream_set_name(_file->output, file->full_path);
688
0
}
689
690
static int fs_posix_write_stream_finish(struct fs_file *_file, bool success)
691
0
{
692
0
  struct posix_fs_file *file =
693
0
    container_of(_file, struct posix_fs_file, file);
694
0
  int ret = success ? 0 : -1;
695
696
0
  o_stream_destroy(&_file->output);
697
698
0
  switch (file->open_mode) {
699
0
  case FS_OPEN_MODE_APPEND:
700
0
    if (ret == 0) {
701
0
      ret = fs_posix_write(_file, file->write_buf->data,
702
0
               file->write_buf->used);
703
0
    }
704
0
    buffer_free(&file->write_buf);
705
0
    break;
706
0
  case FS_OPEN_MODE_CREATE:
707
0
  case FS_OPEN_MODE_CREATE_UNIQUE_128:
708
0
  case FS_OPEN_MODE_REPLACE:
709
0
    if (ret == 0)
710
0
      ret = fs_posix_write_finish(file);
711
0
    break;
712
0
  case FS_OPEN_MODE_READONLY:
713
0
    i_unreached();
714
0
  }
715
0
  return ret < 0 ? -1 : 1;
716
0
}
717
718
static int
719
fs_posix_lock(struct fs_file *_file, unsigned int secs, struct fs_lock **lock_r)
720
0
{
721
0
  struct posix_fs_file *file =
722
0
    container_of(_file, struct posix_fs_file, file);
723
0
  struct posix_fs *fs = container_of(_file->fs, struct posix_fs, fs);
724
0
  struct dotlock_settings dotlock_set;
725
0
  struct posix_fs_lock fs_lock, *ret_lock;
726
0
  int ret = -1;
727
728
0
  i_zero(&fs_lock);
729
0
  fs_lock.lock.file = _file;
730
731
0
  switch (fs->set->parsed_lock_method) {
732
0
  case FS_POSIX_LOCK_METHOD_FLOCK: {
733
#ifndef HAVE_FLOCK
734
    fs_set_error(_file->event, ENOTSUP,
735
           "flock() not supported by OS (for file %s)",
736
           file->full_path);
737
#else
738
0
    const char *error;
739
0
    struct file_lock_settings lock_set = {
740
0
      .lock_method = FILE_LOCK_METHOD_FLOCK,
741
0
    };
742
0
    if (secs == 0) {
743
0
      ret = file_try_lock(file->fd, file->full_path, F_WRLCK,
744
0
              &lock_set, &fs_lock.file_lock,
745
0
              &error);
746
0
    } else {
747
0
      ret = file_wait_lock(file->fd, file->full_path, F_WRLCK,
748
0
               &lock_set, secs,
749
0
               &fs_lock.file_lock, &error);
750
0
    }
751
0
    if (ret < 0) {
752
0
      fs_set_error_errno(_file->event, "flock(%s) failed: %s",
753
0
             file->full_path, error);
754
0
    }
755
0
#endif
756
0
    break;
757
0
  }
758
0
  case FS_POSIX_LOCK_METHOD_DOTLOCK:
759
0
    i_zero(&dotlock_set);
760
0
    dotlock_set.stale_timeout = FS_POSIX_DOTLOCK_STALE_TIMEOUT_SECS;
761
0
    dotlock_set.use_excl_lock = TRUE;
762
0
    dotlock_set.timeout = secs;
763
764
0
    ret = file_dotlock_create(&dotlock_set, file->full_path,
765
0
            secs == 0 ? 0 :
766
0
            DOTLOCK_CREATE_FLAG_NONBLOCK,
767
0
            &fs_lock.dotlock);
768
0
    if (ret < 0) {
769
0
      fs_set_error_errno(_file->event,
770
0
             "file_dotlock_create(%s) failed: %m",
771
0
             file->full_path);
772
0
    }
773
0
    break;
774
0
  }
775
0
  if (ret <= 0)
776
0
    return ret;
777
778
0
  ret_lock = i_new(struct posix_fs_lock, 1);
779
0
  *ret_lock = fs_lock;
780
0
  *lock_r = &ret_lock->lock;
781
0
  return 1;
782
0
}
783
784
static void fs_posix_unlock(struct fs_lock *_lock)
785
0
{
786
0
  struct posix_fs_lock *lock =
787
0
    container_of(_lock, struct posix_fs_lock, lock);
788
789
0
  if (lock->file_lock != NULL)
790
0
    file_unlock(&lock->file_lock);
791
0
  if (lock->dotlock != NULL)
792
0
    file_dotlock_delete(&lock->dotlock);
793
0
  i_free(lock);
794
0
}
795
796
static int fs_posix_exists(struct fs_file *_file)
797
0
{
798
0
  struct posix_fs_file *file =
799
0
    container_of(_file, struct posix_fs_file, file);
800
0
  struct stat st;
801
802
0
  if (stat(file->full_path, &st) < 0) {
803
0
    if (errno != ENOENT) {
804
0
      fs_set_error_errno(_file->event, "stat(%s) failed: %m",
805
0
             file->full_path);
806
0
      return -1;
807
0
    }
808
0
    return 0;
809
0
  }
810
0
  return 1;
811
0
}
812
813
static int fs_posix_stat(struct fs_file *_file, struct stat *st_r)
814
0
{
815
0
  struct posix_fs_file *file =
816
0
    container_of(_file, struct posix_fs_file, file);
817
818
  /* in case output != NULL it means that we're still writing to the file
819
     and fs_stat() shouldn't stat the unfinished file. this is done by
820
     fs-sis after fs_copy(). */
821
0
  if (file->fd != -1 && _file->output == NULL) {
822
0
    if (fstat(file->fd, st_r) < 0) {
823
0
      fs_set_error_errno(_file->event, "fstat(%s) failed: %m",
824
0
             file->full_path);
825
0
      return -1;
826
0
    }
827
0
  } else {
828
0
    if (stat(file->full_path, st_r) < 0) {
829
0
      fs_set_error_errno(_file->event, "stat(%s) failed: %m",
830
0
             file->full_path);
831
0
      return -1;
832
0
    }
833
0
  }
834
  /* Used by obox to set MAIL_FETCH_REFCOUNT_ID for lazy_expunge: */
835
0
  fs_default_set_metadata(_file, FS_METADATA_OBJECTID,
836
0
    t_strdup_printf("%u:%u:%llu",
837
0
        major(st_r->st_dev), minor(st_r->st_dev),
838
0
        (unsigned long long)st_r->st_ino));
839
0
  return 0;
840
0
}
841
842
static int fs_posix_copy(struct fs_file *_src, struct fs_file *_dest)
843
0
{
844
0
  struct posix_fs_file *src =
845
0
    container_of(_src, struct posix_fs_file, file);
846
0
  struct posix_fs_file *dest =
847
0
    container_of(_dest, struct posix_fs_file, file);
848
0
  unsigned int try_count = 0;
849
0
  int ret;
850
851
0
  fs_posix_write_rename_if_needed(dest);
852
0
  ret = link(src->full_path, dest->full_path);
853
0
  if (ret < 0 && errno == EEXIST &&
854
0
      dest->open_mode == FS_OPEN_MODE_REPLACE) {
855
    /* destination file already exists - replace it */
856
0
    i_unlink_if_exists(dest->full_path);
857
0
    ret = link(src->full_path, dest->full_path);
858
0
  }
859
0
  while (ret < 0 && errno == ENOENT &&
860
0
         try_count <= MAX_MKDIR_RETRY_COUNT) {
861
0
    if (fs_posix_mkdir_parents(dest, dest->full_path) < 0)
862
0
      return -1;
863
0
    ret = link(src->full_path, dest->full_path);
864
0
    try_count++;
865
0
  }
866
0
  if (ret < 0) {
867
0
    fs_set_error_errno(_src->event, "link(%s, %s) failed: %m",
868
0
           src->full_path, dest->full_path);
869
0
    return -1;
870
0
  }
871
0
  return 0;
872
0
}
873
874
static int fs_posix_rename(struct fs_file *_src, struct fs_file *_dest)
875
0
{
876
0
  struct posix_fs_file *src =
877
0
    container_of(_src, struct posix_fs_file, file);
878
0
  struct posix_fs_file *dest =
879
0
    container_of(_dest, struct posix_fs_file, file);
880
0
  unsigned int try_count = 0;
881
0
  int ret;
882
883
0
  ret = rename(src->full_path, dest->full_path);
884
0
  while (ret < 0 && errno == ENOENT &&
885
0
         try_count <= MAX_MKDIR_RETRY_COUNT) {
886
0
    if (fs_posix_mkdir_parents(dest, dest->full_path) < 0)
887
0
      return -1;
888
0
    ret = rename(src->full_path, dest->full_path);
889
0
    try_count++;
890
0
  }
891
0
  if (ret < 0) {
892
0
    fs_set_error_errno(_src->event, "rename(%s, %s) failed: %m",
893
0
           src->full_path, dest->full_path);
894
0
    return -1;
895
0
  }
896
0
  return 0;
897
0
}
898
899
static int fs_posix_delete(struct fs_file *_file)
900
0
{
901
0
  struct posix_fs_file *file =
902
0
    container_of(_file, struct posix_fs_file, file);
903
904
0
  if (unlink(file->full_path) < 0) {
905
0
    if (!UNLINK_EISDIR(errno)) {
906
0
      fs_set_error_errno(_file->event, "unlink(%s) failed: %m",
907
0
             file->full_path);
908
0
      return -1;
909
0
    }
910
    /* attempting to delete a directory. convert it to rmdir()
911
       automatically. */
912
0
    if (rmdir(file->full_path) < 0) {
913
0
      fs_set_error_errno(_file->event, "rmdir(%s) failed: %m",
914
0
             file->full_path);
915
0
      return -1;
916
0
    }
917
0
  }
918
0
  (void)fs_posix_rmdir_parents(file, file->full_path);
919
0
  return 0;
920
0
}
921
922
static struct fs_iter *fs_posix_iter_alloc(void)
923
0
{
924
0
  struct posix_fs_iter *iter = i_new(struct posix_fs_iter, 1);
925
0
  return &iter->iter;
926
0
}
927
928
static void
929
fs_posix_iter_init(struct fs_iter *_iter, const char *path,
930
       enum fs_iter_flags flags ATTR_UNUSED)
931
0
{
932
0
  struct posix_fs_iter *iter =
933
0
    container_of(_iter, struct posix_fs_iter, iter);
934
0
  struct posix_fs *fs = container_of(_iter->fs, struct posix_fs, fs);
935
936
0
  iter->path = i_strconcat(fs->set->fs_posix_prefix, path, NULL);
937
0
  if (iter->path[0] == '\0') {
938
0
    i_free(iter->path);
939
0
    iter->path = i_strdup(".");
940
0
  }
941
0
  iter->dir = opendir(iter->path);
942
0
  if (iter->dir == NULL && errno != ENOENT) {
943
0
    iter->err = errno;
944
0
    fs_set_error_errno(_iter->event,
945
0
           "opendir(%s) failed: %m", iter->path);
946
0
  }
947
0
}
948
949
static bool fs_posix_iter_want(struct posix_fs_iter *iter, const char *fname)
950
0
{
951
0
  bool ret;
952
953
0
  T_BEGIN {
954
0
    const char *path = t_strdup_printf("%s/%s", iter->path, fname);
955
0
    struct stat st;
956
957
0
    if (stat(path, &st) < 0 &&
958
0
        lstat(path, &st) < 0)
959
0
      ret = FALSE;
960
0
    else if (!S_ISDIR(st.st_mode))
961
0
      ret = (iter->iter.flags & FS_ITER_FLAG_DIRS) == 0;
962
0
    else
963
0
      ret = (iter->iter.flags & FS_ITER_FLAG_DIRS) != 0;
964
0
  } T_END;
965
0
  return ret;
966
0
}
967
968
static const char *fs_posix_iter_next(struct fs_iter *_iter)
969
0
{
970
0
  struct posix_fs_iter *iter =
971
0
    container_of(_iter, struct posix_fs_iter, iter);
972
0
  struct posix_fs *fs = container_of(_iter->fs, struct posix_fs, fs);
973
0
  struct dirent *d;
974
975
0
  if (iter->dir == NULL)
976
0
    return NULL;
977
978
0
  errno = 0;
979
0
  for (; (d = readdir(iter->dir)) != NULL; errno = 0) {
980
0
    if (strcmp(d->d_name, ".") == 0 ||
981
0
        strcmp(d->d_name, "..") == 0)
982
0
      continue;
983
0
    if (strncmp(d->d_name, fs->temp_file_prefix,
984
0
          fs->temp_file_prefix_len) == 0)
985
0
      continue;
986
0
#ifdef HAVE_DIRENT_D_TYPE
987
0
    switch (d->d_type) {
988
0
    case DT_UNKNOWN:
989
0
      if (fs_posix_iter_want(iter, d->d_name))
990
0
        return d->d_name;
991
0
      break;
992
0
    case DT_DIR:
993
0
      if ((iter->iter.flags & FS_ITER_FLAG_DIRS) != 0)
994
0
        return d->d_name;
995
0
      break;
996
0
    default:
997
0
      if ((iter->iter.flags & FS_ITER_FLAG_DIRS) == 0)
998
0
        return d->d_name;
999
0
      break;
1000
0
    }
1001
#else
1002
    if (fs_posix_iter_want(iter, d->d_name))
1003
      return d->d_name;
1004
#endif
1005
0
  }
1006
0
  if (errno != 0) {
1007
0
    iter->err = errno;
1008
0
    fs_set_error_errno(_iter->event,
1009
0
           "readdir(%s) failed: %m", iter->path);
1010
0
  }
1011
0
  return NULL;
1012
0
}
1013
1014
static int fs_posix_iter_deinit(struct fs_iter *_iter)
1015
0
{
1016
0
  struct posix_fs_iter *iter =
1017
0
    container_of(_iter, struct posix_fs_iter, iter);
1018
0
  int ret = 0;
1019
1020
0
  if (iter->dir != NULL && closedir(iter->dir) < 0 && iter->err == 0) {
1021
0
    iter->err = errno;
1022
0
    fs_set_error_errno(_iter->event,
1023
0
           "closedir(%s) failed: %m", iter->path);
1024
0
  }
1025
0
  if (iter->err != 0) {
1026
0
    errno = iter->err;
1027
0
    ret = -1;
1028
0
  }
1029
  i_free(iter->path);
1030
0
  return ret;
1031
0
}
1032
1033
const struct fs fs_class_posix = {
1034
  .name = "posix",
1035
  .v = {
1036
    .alloc = fs_posix_alloc,
1037
    .init = fs_posix_init,
1038
    .deinit = NULL,
1039
    .free = fs_posix_free,
1040
    .get_properties = fs_posix_get_properties,
1041
    .file_alloc = fs_posix_file_alloc,
1042
    .file_init = fs_posix_file_init,
1043
    .file_deinit = fs_posix_file_deinit,
1044
    .file_close = fs_posix_file_close,
1045
    .file_equals = fs_posix_file_equals,
1046
    .get_path = NULL,
1047
    .set_async_callback = NULL,
1048
    .wait_async = NULL,
1049
    .set_metadata = fs_default_set_metadata,
1050
    .get_metadata = NULL,
1051
    .prefetch = fs_posix_prefetch,
1052
    .read = fs_posix_read,
1053
    .read_stream = fs_posix_read_stream,
1054
    .write = fs_posix_write,
1055
    .write_stream = fs_posix_write_stream,
1056
    .write_stream_finish = fs_posix_write_stream_finish,
1057
    .lock = fs_posix_lock,
1058
    .unlock = fs_posix_unlock,
1059
    .exists = fs_posix_exists,
1060
    .stat = fs_posix_stat,
1061
    .copy = fs_posix_copy,
1062
    .rename = fs_posix_rename,
1063
    .delete_file = fs_posix_delete,
1064
    .iter_alloc = fs_posix_iter_alloc,
1065
    .iter_init = fs_posix_iter_init,
1066
    .iter_next = fs_posix_iter_next,
1067
    .iter_deinit = fs_posix_iter_deinit,
1068
    .switch_ioloop = NULL,
1069
    .get_nlinks = NULL,
1070
  }
1071
};