/src/dovecot/src/lib-fs/fs-api.c
Line | Count | Source |
1 | | /* Copyright (c) Dovecot authors, see top-level COPYING file */ |
2 | | |
3 | | #include "lib.h" |
4 | | #include "array.h" |
5 | | #include "module-dir.h" |
6 | | #include "llist.h" |
7 | | #include "str.h" |
8 | | #include "hash-method.h" |
9 | | #include "istream.h" |
10 | | #include "istream-seekable.h" |
11 | | #include "ostream.h" |
12 | | #include "stats-dist.h" |
13 | | #include "time-util.h" |
14 | | #include "settings.h" |
15 | | #include "istream-fs-stats.h" |
16 | | #include "fs-api-private.h" |
17 | | |
18 | | static bool fs_settings_check(void *_set, pool_t pool, const char **error_r); |
19 | | |
20 | | #undef DEF |
21 | | #define DEF(type, name) \ |
22 | | SETTING_DEFINE_STRUCT_##type(#name, name, struct fs_settings) |
23 | | static const struct setting_define fs_setting_defines[] = { |
24 | | DEF(STR, fs_name), |
25 | | DEF(STR, fs_driver), |
26 | | { .type = SET_FILTER_ARRAY, .key = "fs", |
27 | | .offset = offsetof(struct fs_settings, fs), |
28 | | .filter_array_field_name = "fs_name", }, |
29 | | |
30 | | SETTING_DEFINE_LIST_END |
31 | | }; |
32 | | static const struct fs_settings fs_default_settings = { |
33 | | .fs_name = "", |
34 | | .fs_driver = "", |
35 | | .fs = ARRAY_INIT, |
36 | | }; |
37 | | const struct setting_parser_info fs_setting_parser_info = { |
38 | | .name = "fs", |
39 | | |
40 | | .defines = fs_setting_defines, |
41 | | .defaults = &fs_default_settings, |
42 | | |
43 | | .struct_size = sizeof(struct fs_settings), |
44 | | .pool_offset1 = 1 + offsetof(struct fs_settings, pool), |
45 | | |
46 | | .check_func = fs_settings_check, |
47 | | }; |
48 | | |
49 | | static struct event_category event_category_fs = { |
50 | | .name = "fs" |
51 | | }; |
52 | | |
53 | | struct fs_api_module_register fs_api_module_register = { 0 }; |
54 | | |
55 | | static struct module *fs_modules = NULL; |
56 | | static ARRAY(const struct fs *) fs_classes; |
57 | | |
58 | | static void fs_classes_init(void); |
59 | | |
60 | | static struct event *fs_create_event(struct fs *fs, struct event *parent) |
61 | 0 | { |
62 | 0 | struct event *event; |
63 | |
|
64 | 0 | event = event_create(parent); |
65 | 0 | event_add_category(event, &event_category_fs); |
66 | 0 | event_set_append_log_prefix(event, |
67 | 0 | t_strdup_printf("fs-%s: ", fs->name)); |
68 | 0 | return event; |
69 | 0 | } |
70 | | |
71 | | void fs_class_register(const struct fs *fs_class) |
72 | 0 | { |
73 | 0 | if (!array_is_created(&fs_classes)) |
74 | 0 | fs_classes_init(); |
75 | 0 | array_push_back(&fs_classes, &fs_class); |
76 | 0 | } |
77 | | |
78 | | void fs_class_unregister(const struct fs *fs_class) |
79 | 0 | { |
80 | 0 | const struct fs *const *p; |
81 | |
|
82 | 0 | array_foreach(&fs_classes, p) { |
83 | 0 | if (*p == fs_class) { |
84 | 0 | array_delete(&fs_classes, |
85 | 0 | array_foreach_idx(&fs_classes, p), 1); |
86 | 0 | return; |
87 | 0 | } |
88 | 0 | } |
89 | 0 | i_panic("fs_class_unregister(): Class %s not found", fs_class->name); |
90 | 0 | } |
91 | | |
92 | | static void fs_classes_deinit(void) |
93 | 0 | { |
94 | 0 | array_free(&fs_classes); |
95 | 0 | } |
96 | | |
97 | | static void fs_classes_init(void) |
98 | 0 | { |
99 | 0 | i_array_init(&fs_classes, 8); |
100 | 0 | fs_class_register(&fs_class_dict); |
101 | 0 | fs_class_register(&fs_class_posix); |
102 | 0 | fs_class_register(&fs_class_randomfail); |
103 | 0 | fs_class_register(&fs_class_metawrap); |
104 | 0 | fs_class_register(&fs_class_sis); |
105 | 0 | fs_class_register(&fs_class_sis_queue); |
106 | 0 | fs_class_register(&fs_class_test); |
107 | 0 | lib_atexit_priority(fs_classes_deinit, LIB_ATEXIT_PRIORITY_LOW); |
108 | 0 | } |
109 | | |
110 | | static const struct fs *fs_class_find(const char *driver) |
111 | 0 | { |
112 | 0 | const struct fs *class; |
113 | |
|
114 | 0 | if (!array_is_created(&fs_classes)) |
115 | 0 | fs_classes_init(); |
116 | |
|
117 | 0 | array_foreach_elem(&fs_classes, class) { |
118 | 0 | if (strcmp(class->name, driver) == 0) |
119 | 0 | return class; |
120 | 0 | } |
121 | 0 | return NULL; |
122 | 0 | } |
123 | | |
124 | | static void fs_class_deinit_modules(void) |
125 | 0 | { |
126 | 0 | module_dir_unload(&fs_modules); |
127 | 0 | } |
128 | | |
129 | | static const char *fs_driver_module_name(const char *driver) |
130 | 0 | { |
131 | 0 | return t_str_replace(driver, '-', '_'); |
132 | 0 | } |
133 | | |
134 | | static void fs_class_try_load_plugin(const char *driver) |
135 | 0 | { |
136 | 0 | const char *module_names[] = { |
137 | 0 | t_strdup_printf("fs_%s", fs_driver_module_name(driver)), |
138 | 0 | NULL |
139 | 0 | }; |
140 | 0 | struct module *module; |
141 | 0 | struct module_dir_load_settings mod_set; |
142 | 0 | const struct fs *fs_class; |
143 | |
|
144 | 0 | i_zero(&mod_set); |
145 | 0 | mod_set.abi_version = DOVECOT_ABI_VERSION; |
146 | 0 | mod_set.ignore_missing = TRUE; |
147 | |
|
148 | 0 | fs_modules = module_dir_load_missing(fs_modules, MODULE_DIR, |
149 | 0 | module_names, &mod_set); |
150 | 0 | module_dir_init(fs_modules); |
151 | |
|
152 | 0 | module = module_dir_find(fs_modules, module_names[0]); |
153 | 0 | fs_class = module == NULL ? NULL : |
154 | 0 | module_get_symbol(module, t_strdup_printf( |
155 | 0 | "fs_class_%s", fs_driver_module_name(driver))); |
156 | 0 | if (fs_class != NULL) |
157 | 0 | fs_class_register(fs_class); |
158 | |
|
159 | 0 | lib_atexit(fs_class_deinit_modules); |
160 | 0 | } |
161 | | |
162 | | static int |
163 | | fs_alloc(const char *driver, struct event *event_parent, |
164 | | const struct fs_parameters *params, |
165 | | struct fs **fs_r, const char **error_r) |
166 | 0 | { |
167 | 0 | const struct fs *fs_class; |
168 | 0 | struct fs *fs; |
169 | 0 | const char *temp_dir, *temp_file_prefix; |
170 | |
|
171 | 0 | fs_class = fs_class_find(driver); |
172 | 0 | if (fs_class == NULL) { |
173 | 0 | T_BEGIN { |
174 | 0 | fs_class_try_load_plugin(driver); |
175 | 0 | } T_END; |
176 | 0 | fs_class = fs_class_find(driver); |
177 | 0 | } |
178 | 0 | if (fs_class == NULL) { |
179 | 0 | *error_r = t_strdup_printf("Unknown fs driver: %s", driver); |
180 | 0 | return -1; |
181 | 0 | } |
182 | | |
183 | 0 | fs = fs_class->v.alloc(); |
184 | 0 | fs->refcount = 1; |
185 | 0 | fs->enable_timing = params->enable_timing; |
186 | 0 | fs->username = i_strdup(params->username); |
187 | 0 | fs->session_id = i_strdup(params->session_id); |
188 | 0 | i_array_init(&fs->module_contexts, 5); |
189 | 0 | fs->event = fs_create_event(fs, event_parent); |
190 | 0 | event_set_ptr(fs->event, FS_EVENT_FIELD_FS, fs); |
191 | |
|
192 | 0 | temp_dir = params->temp_dir != NULL ? params->temp_dir : "/tmp"; |
193 | 0 | temp_file_prefix = params->temp_file_prefix != NULL ? |
194 | 0 | params->temp_file_prefix : ".temp.dovecot"; |
195 | 0 | fs->temp_path_prefix = i_strconcat(temp_dir, "/", temp_file_prefix, NULL); |
196 | |
|
197 | 0 | *fs_r = fs; |
198 | 0 | return 0; |
199 | 0 | } |
200 | | |
201 | | static bool fs_settings_check(void *_set, pool_t pool ATTR_UNUSED, |
202 | | const char **error_r ATTR_UNUSED) |
203 | 0 | { |
204 | 0 | struct fs_settings *set = _set; |
205 | |
|
206 | 0 | if (set->fs_driver[0] == '\0' && set->fs_name[0] != '\0') { |
207 | | /* default an empty fs_driver to fs_name, so it's possible to |
208 | | configure simply: fs driver { .. }, but to still allow the |
209 | | same driver to be used multiple times if necessary. */ |
210 | 0 | set->fs_driver = set->fs_name; |
211 | 0 | } |
212 | 0 | return TRUE; |
213 | 0 | } |
214 | | |
215 | | static int fs_init(struct event *event, |
216 | | const struct fs_parameters *params, |
217 | | const ARRAY_TYPE(const_string) *fs_list, |
218 | | unsigned int fs_list_idx, |
219 | | unsigned int *init_fs_last_list_idx, |
220 | | struct fs **fs_r, const char **error_r) |
221 | 0 | { |
222 | 0 | const struct fs_settings *fs_set; |
223 | 0 | struct fs *fs; |
224 | 0 | const char *fs_name, *error; |
225 | 0 | int ret; |
226 | |
|
227 | 0 | fs_name = array_idx_elem(fs_list, fs_list_idx); |
228 | 0 | if (settings_get_filter(event, "fs", fs_name, &fs_setting_parser_info, |
229 | 0 | 0, &fs_set, error_r) < 0) |
230 | 0 | return -1; |
231 | | |
232 | 0 | if (fs_set->fs_driver[0] == '\0') { |
233 | 0 | *error_r = "fs_driver is empty"; |
234 | 0 | settings_free(fs_set); |
235 | 0 | return -1; |
236 | 0 | } |
237 | | |
238 | 0 | event_add_str(event, "fs", fs_name); |
239 | 0 | settings_event_add_list_filter_name(event, "fs", fs_name); |
240 | |
|
241 | 0 | ret = fs_alloc(fs_set->fs_driver, event, params, &fs, error_r); |
242 | 0 | settings_free(fs_set); |
243 | 0 | if (ret < 0) |
244 | 0 | return -1; |
245 | | |
246 | 0 | fs->init_fs_list = fs_list; |
247 | 0 | fs->init_fs_list_idx = fs_list_idx; |
248 | 0 | fs->init_fs_last_list_idx = init_fs_last_list_idx; |
249 | 0 | *init_fs_last_list_idx = fs_list_idx; |
250 | 0 | T_BEGIN { |
251 | 0 | ret = fs->v.init(fs, params, &error); |
252 | 0 | } T_END_PASS_STR_IF(ret < 0, &error); |
253 | 0 | if (ret < 0) { |
254 | 0 | *error_r = t_strdup_printf("%s: %s", fs->name, error); |
255 | 0 | fs_unref(&fs); |
256 | 0 | return -1; |
257 | 0 | } |
258 | | /* fs's parent event points to the fs parent's event. This is normally |
259 | | wanted. However, we don't want the parent fs's settings to be read |
260 | | for this fs. We don't expect settings to be read anymore after |
261 | | init(). Drop settings_filter_name so if settings are attempted to be |
262 | | read later on, it will be obvious enough that it's not using any |
263 | | fs settings. */ |
264 | 0 | event_set_ptr(event, SETTINGS_EVENT_FILTER_NAME, NULL); |
265 | 0 | fs->init_fs_list = NULL; |
266 | 0 | *fs_r = fs; |
267 | 0 | return 0; |
268 | 0 | } |
269 | | |
270 | | int fs_init_auto(struct event *event, const struct fs_parameters *params, |
271 | | struct fs **fs_r, const char **error_r) |
272 | 0 | { |
273 | 0 | const struct fs_settings *fs_set; |
274 | 0 | struct fs *fs; |
275 | 0 | unsigned int last_list_idx; |
276 | 0 | int ret; |
277 | |
|
278 | 0 | if (settings_get(event, &fs_setting_parser_info, 0, |
279 | 0 | &fs_set, error_r) < 0) |
280 | 0 | return -1; |
281 | 0 | if (array_is_empty(&fs_set->fs)) { |
282 | 0 | settings_free(fs_set); |
283 | 0 | *error_r = "fs { .. } named list filter is missing"; |
284 | 0 | return 0; |
285 | 0 | } |
286 | | |
287 | 0 | event = event_create(event); |
288 | 0 | ret = fs_init(event, params, &fs_set->fs, 0, |
289 | 0 | &last_list_idx, &fs, error_r); |
290 | 0 | event_unref(&event); |
291 | |
|
292 | 0 | if (ret == 0 && last_list_idx + 1 < array_count(&fs_set->fs)) { |
293 | 0 | const char *fs_name_last = |
294 | 0 | array_idx_elem(&fs_set->fs, last_list_idx); |
295 | 0 | const char *fs_name_extra = |
296 | 0 | array_idx_elem(&fs_set->fs, last_list_idx + 1); |
297 | 0 | *error_r = t_strdup_printf( |
298 | 0 | "Extra fs %s { .. } named list filter - " |
299 | 0 | "the parent fs %s { .. } doesn't support a child fs", |
300 | 0 | fs_name_extra, fs_name_last); |
301 | 0 | settings_free(fs_set); |
302 | 0 | fs_unref(&fs); |
303 | 0 | return -1; |
304 | 0 | } |
305 | 0 | settings_free(fs_set); |
306 | 0 | if (ret < 0) |
307 | 0 | return -1; |
308 | 0 | *fs_r = fs; |
309 | 0 | return 1; |
310 | 0 | } |
311 | | |
312 | | int fs_init_parent(struct fs *fs, const struct fs_parameters *params, |
313 | | const char **error_r) |
314 | 0 | { |
315 | 0 | if (fs->init_fs_list_idx + 1 >= array_count(fs->init_fs_list)) { |
316 | 0 | *error_r = "Next fs { .. } named list filter is missing"; |
317 | 0 | return -1; |
318 | 0 | } |
319 | | |
320 | | /* Remove the parent fs's settings_filter_name while initializing a |
321 | | child fs, so the parent settings won't be attempted to be read. */ |
322 | 0 | char *old_filter = event_get_ptr(event_get_parent(fs->event), |
323 | 0 | SETTINGS_EVENT_FILTER_NAME); |
324 | 0 | event_set_ptr(event_get_parent(fs->event), |
325 | 0 | SETTINGS_EVENT_FILTER_NAME, NULL); |
326 | |
|
327 | 0 | struct event *event = event_create(fs->event); |
328 | | /* Drop the parent "fs-name: " prefix */ |
329 | 0 | event_drop_parent_log_prefixes(event, 1); |
330 | 0 | int ret = fs_init(event, params, |
331 | 0 | fs->init_fs_list, fs->init_fs_list_idx + 1, |
332 | 0 | fs->init_fs_last_list_idx, |
333 | 0 | &fs->parent, error_r); |
334 | 0 | event_unref(&event); |
335 | | /* Restore the old settings_filter_name, since the caller's init() |
336 | | could still need it. */ |
337 | 0 | event_set_ptr(event_get_parent(fs->event), |
338 | 0 | SETTINGS_EVENT_FILTER_NAME, old_filter); |
339 | 0 | return ret; |
340 | 0 | } |
341 | | |
342 | | void fs_deinit(struct fs **fs) |
343 | 0 | { |
344 | 0 | fs_unref(fs); |
345 | 0 | } |
346 | | |
347 | | void fs_ref(struct fs *fs) |
348 | 0 | { |
349 | 0 | i_assert(fs->refcount > 0); |
350 | | |
351 | 0 | fs->refcount++; |
352 | 0 | } |
353 | | |
354 | | void fs_unref(struct fs **_fs) |
355 | 0 | { |
356 | 0 | struct fs *fs = *_fs; |
357 | 0 | struct array module_contexts_arr; |
358 | 0 | unsigned int i; |
359 | |
|
360 | 0 | if (fs == NULL) |
361 | 0 | return; |
362 | | |
363 | 0 | module_contexts_arr = fs->module_contexts.arr; |
364 | |
|
365 | 0 | i_assert(fs->refcount > 0); |
366 | | |
367 | 0 | *_fs = NULL; |
368 | |
|
369 | 0 | if (--fs->refcount > 0) |
370 | 0 | return; |
371 | | |
372 | 0 | if (fs->files_open_count > 0) { |
373 | 0 | i_panic("fs-%s: %u files still open (first = %s)", |
374 | 0 | fs->name, fs->files_open_count, fs_file_path(fs->files)); |
375 | 0 | } |
376 | 0 | i_assert(fs->files == NULL); |
377 | | |
378 | 0 | if (fs->v.deinit != NULL) |
379 | 0 | fs->v.deinit(fs); |
380 | |
|
381 | 0 | fs_deinit(&fs->parent); |
382 | 0 | event_unref(&fs->event); |
383 | 0 | i_free(fs->username); |
384 | 0 | i_free(fs->session_id); |
385 | 0 | i_free(fs->temp_path_prefix); |
386 | 0 | for (i = 0; i < FS_OP_COUNT; i++) { |
387 | 0 | if (fs->stats.timings[i] != NULL) |
388 | 0 | stats_dist_deinit(&fs->stats.timings[i]); |
389 | 0 | } |
390 | 0 | T_BEGIN { |
391 | 0 | fs->v.free(fs); |
392 | 0 | } T_END; |
393 | 0 | array_free_i(&module_contexts_arr); |
394 | 0 | } |
395 | | |
396 | | struct fs *fs_get_parent(struct fs *fs) |
397 | 0 | { |
398 | 0 | return fs->parent; |
399 | 0 | } |
400 | | |
401 | | const char *fs_get_driver(struct fs *fs) |
402 | 0 | { |
403 | 0 | return fs->name; |
404 | 0 | } |
405 | | |
406 | | struct fs *fs_get_root_fs(struct fs *fs) |
407 | 0 | { |
408 | 0 | while (fs->parent != NULL) |
409 | 0 | fs = fs->parent; |
410 | 0 | return fs; |
411 | 0 | } |
412 | | |
413 | | const char *fs_get_root_driver(struct fs *fs) |
414 | 0 | { |
415 | 0 | return fs_get_root_fs(fs)->name; |
416 | 0 | } |
417 | | |
418 | | struct fs_file *fs_file_init(struct fs *fs, const char *path, int mode_flags) |
419 | 0 | { |
420 | 0 | return fs_file_init_with_event(fs, fs->event, path, mode_flags); |
421 | 0 | } |
422 | | |
423 | | struct fs_file *fs_file_init_with_event(struct fs *fs, struct event *event, |
424 | | const char *path, int mode_flags) |
425 | 0 | { |
426 | 0 | struct fs_file *file; |
427 | |
|
428 | 0 | i_assert(path != NULL); |
429 | 0 | i_assert((mode_flags & FS_OPEN_FLAG_ASYNC_NOQUEUE) == 0 || |
430 | 0 | (mode_flags & FS_OPEN_FLAG_ASYNC) != 0); |
431 | | |
432 | 0 | T_BEGIN { |
433 | 0 | file = fs->v.file_alloc(); |
434 | 0 | file->fs = fs; |
435 | 0 | file->flags = mode_flags & ENUM_NEGATE(FS_OPEN_MODE_MASK); |
436 | 0 | file->event = fs_create_event(fs, event); |
437 | 0 | event_set_ptr(file->event, FS_EVENT_FIELD_FS, fs); |
438 | 0 | event_set_ptr(file->event, FS_EVENT_FIELD_FILE, file); |
439 | 0 | fs->v.file_init(file, path, mode_flags & FS_OPEN_MODE_MASK, |
440 | 0 | mode_flags & ENUM_NEGATE(FS_OPEN_MODE_MASK)); |
441 | 0 | } T_END; |
442 | | |
443 | 0 | fs->files_open_count++; |
444 | 0 | DLLIST_PREPEND(&fs->files, file); |
445 | |
|
446 | 0 | fs_set_metadata(file, FS_METADATA_ORIG_PATH, path); |
447 | 0 | return file; |
448 | 0 | } |
449 | | |
450 | | void fs_file_deinit(struct fs_file **_file) |
451 | 0 | { |
452 | 0 | struct fs_file *file = *_file; |
453 | |
|
454 | 0 | if (file == NULL) |
455 | 0 | return; |
456 | | |
457 | 0 | i_assert(file->fs->files_open_count > 0); |
458 | | |
459 | 0 | *_file = NULL; |
460 | |
|
461 | 0 | fs_file_close(file); |
462 | |
|
463 | 0 | DLLIST_REMOVE(&file->fs->files, file); |
464 | 0 | file->fs->files_open_count--; |
465 | 0 | T_BEGIN { |
466 | 0 | file->fs->v.file_deinit(file); |
467 | 0 | } T_END; |
468 | 0 | } |
469 | | |
470 | | bool fs_file_equals(struct fs_file *file1, struct fs_file *file2) |
471 | 0 | { |
472 | 0 | while (file1->parent != NULL) |
473 | 0 | file1 = file1->parent; |
474 | 0 | while (file2->parent != NULL) |
475 | 0 | file2 = file2->parent; |
476 | |
|
477 | 0 | if (strcmp(file1->fs->name, file2->fs->name) != 0) |
478 | 0 | return FALSE; |
479 | 0 | if (file1->fs->v.file_equals == NULL) |
480 | 0 | return FALSE; |
481 | 0 | i_assert(file1->fs->v.file_equals == file2->fs->v.file_equals); |
482 | 0 | return file1->fs->v.file_equals(file1, file2); |
483 | 0 | } |
484 | | |
485 | | void fs_file_free(struct fs_file *file) |
486 | 0 | { |
487 | 0 | if (file->last_error_changed) { |
488 | | /* fs_set_error() used without ever accessing it via |
489 | | fs_file_last_error(). Log it to make sure it's not lost. |
490 | | Note that the errors are always set only to the file at |
491 | | the root of the parent hierarchy. */ |
492 | 0 | e_error(file->event, "%s (in file %s deinit)", |
493 | 0 | file->last_error, fs_file_path(file)); |
494 | 0 | } |
495 | |
|
496 | 0 | fs_file_deinit(&file->parent); |
497 | 0 | event_unref(&file->event); |
498 | 0 | pool_unref(&file->metadata_pool); |
499 | 0 | i_free(file->last_error); |
500 | 0 | } |
501 | | |
502 | | void fs_file_set_flags(struct fs_file *file, |
503 | | enum fs_open_flags add_flags, |
504 | | enum fs_open_flags remove_flags) |
505 | 0 | { |
506 | 0 | file->flags |= add_flags; |
507 | 0 | file->flags &= ENUM_NEGATE(remove_flags); |
508 | |
|
509 | 0 | if (file->parent != NULL) |
510 | 0 | fs_file_set_flags(file->parent, add_flags, remove_flags); |
511 | 0 | } |
512 | | |
513 | | void fs_file_close(struct fs_file *file) |
514 | 0 | { |
515 | 0 | if (file == NULL) |
516 | 0 | return; |
517 | | |
518 | 0 | i_assert(!file->writing_stream); |
519 | 0 | i_assert(file->output == NULL); |
520 | | |
521 | 0 | if (file->pending_read_input != NULL) |
522 | 0 | i_stream_unref(&file->pending_read_input); |
523 | 0 | if (file->seekable_input != NULL) |
524 | 0 | i_stream_unref(&file->seekable_input); |
525 | |
|
526 | 0 | if (file->copy_input != NULL) { |
527 | 0 | i_stream_unref(&file->copy_input); |
528 | 0 | fs_write_stream_abort_error(file, &file->copy_output, "fs_file_close(%s)", |
529 | 0 | o_stream_get_name(file->copy_output)); |
530 | 0 | } |
531 | 0 | i_free_and_null(file->write_digest); |
532 | 0 | if (file->fs->v.file_close != NULL) T_BEGIN { |
533 | 0 | file->fs->v.file_close(file); |
534 | 0 | } T_END; |
535 | | |
536 | | /* check this only after closing, because some of the fs backends keep |
537 | | the istream internally open and don't call the destroy-callback |
538 | | until after file_close() */ |
539 | 0 | i_assert(!file->istream_open); |
540 | 0 | } |
541 | | |
542 | | enum fs_properties fs_get_properties(struct fs *fs) |
543 | 0 | { |
544 | 0 | return fs->v.get_properties(fs); |
545 | 0 | } |
546 | | |
547 | | void fs_metadata_init(struct fs_file *file) |
548 | 0 | { |
549 | 0 | if (file->metadata_pool == NULL) { |
550 | 0 | i_assert(!array_is_created(&file->metadata)); |
551 | 0 | file->metadata_pool = pool_alloconly_create("fs metadata", 1024); |
552 | 0 | p_array_init(&file->metadata, file->metadata_pool, 8); |
553 | 0 | } |
554 | 0 | } |
555 | | |
556 | | void fs_metadata_init_or_clear(struct fs_file *file) |
557 | 0 | { |
558 | 0 | if (file->metadata_pool == NULL) |
559 | 0 | fs_metadata_init(file); |
560 | 0 | else T_BEGIN { |
561 | 0 | const struct fs_metadata *md; |
562 | 0 | ARRAY_TYPE(fs_metadata) internal_metadata; |
563 | |
|
564 | 0 | t_array_init(&internal_metadata, 4); |
565 | 0 | array_foreach(&file->metadata, md) { |
566 | 0 | if (strncmp(md->key, FS_METADATA_INTERNAL_PREFIX, |
567 | 0 | strlen(FS_METADATA_INTERNAL_PREFIX)) == 0) |
568 | 0 | array_push_back(&internal_metadata, md); |
569 | 0 | } |
570 | 0 | array_clear(&file->metadata); |
571 | 0 | array_append_array(&file->metadata, &internal_metadata); |
572 | 0 | } T_END; |
573 | 0 | } |
574 | | |
575 | | static struct fs_metadata * |
576 | | fs_metadata_find_md(const ARRAY_TYPE(fs_metadata) *metadata, |
577 | | const char *key) |
578 | 0 | { |
579 | 0 | struct fs_metadata *md; |
580 | |
|
581 | 0 | array_foreach_modifiable(metadata, md) { |
582 | 0 | if (strcmp(md->key, key) == 0) |
583 | 0 | return md; |
584 | 0 | } |
585 | 0 | return NULL; |
586 | 0 | } |
587 | | |
588 | | void fs_default_set_metadata(struct fs_file *file, |
589 | | const char *key, const char *value) |
590 | 0 | { |
591 | 0 | struct fs_metadata *metadata; |
592 | |
|
593 | 0 | fs_metadata_init(file); |
594 | 0 | metadata = fs_metadata_find_md(&file->metadata, key); |
595 | 0 | if (metadata == NULL) { |
596 | 0 | metadata = array_append_space(&file->metadata); |
597 | 0 | metadata->key = p_strdup(file->metadata_pool, key); |
598 | 0 | } |
599 | 0 | metadata->value = p_strdup(file->metadata_pool, value); |
600 | 0 | } |
601 | | |
602 | | const char *fs_metadata_find(const ARRAY_TYPE(fs_metadata) *metadata, |
603 | | const char *key) |
604 | 0 | { |
605 | 0 | const struct fs_metadata *md; |
606 | |
|
607 | 0 | if (!array_is_created(metadata)) |
608 | 0 | return NULL; |
609 | | |
610 | 0 | md = fs_metadata_find_md(metadata, key); |
611 | 0 | return md == NULL ? NULL : md->value; |
612 | 0 | } |
613 | | |
614 | | void fs_set_metadata(struct fs_file *file, const char *key, const char *value) |
615 | 0 | { |
616 | 0 | i_assert(key != NULL); |
617 | 0 | i_assert(value != NULL); |
618 | 0 | i_assert(strchr(key, '_') == NULL); |
619 | | |
620 | 0 | if (file->fs->v.set_metadata != NULL) T_BEGIN { |
621 | 0 | file->fs->v.set_metadata(file, key, value); |
622 | 0 | if (strncmp(key, FS_METADATA_INTERNAL_PREFIX, |
623 | 0 | strlen(FS_METADATA_INTERNAL_PREFIX)) == 0) { |
624 | | /* internal metadata change, which isn't stored. */ |
625 | 0 | } else { |
626 | 0 | file->metadata_changed = TRUE; |
627 | 0 | } |
628 | 0 | } T_END; |
629 | 0 | } |
630 | | |
631 | | static void fs_file_timing_start(struct fs_file *file, enum fs_op op) |
632 | 0 | { |
633 | 0 | if (!file->fs->enable_timing) |
634 | 0 | return; |
635 | 0 | if (file->timing_start[op].tv_sec == 0) |
636 | 0 | i_gettimeofday(&file->timing_start[op]); |
637 | 0 | } |
638 | | |
639 | | static void |
640 | | fs_timing_end(struct stats_dist **timing, const struct timeval *start_tv) |
641 | 0 | { |
642 | 0 | struct timeval now; |
643 | 0 | long long diff; |
644 | |
|
645 | 0 | i_gettimeofday(&now); |
646 | |
|
647 | 0 | diff = timeval_diff_usecs(&now, start_tv); |
648 | 0 | if (diff > 0) { |
649 | 0 | if (*timing == NULL) |
650 | 0 | *timing = stats_dist_init(); |
651 | 0 | stats_dist_add(*timing, diff); |
652 | 0 | } |
653 | 0 | } |
654 | | |
655 | | void fs_file_timing_end(struct fs_file *file, enum fs_op op) |
656 | 0 | { |
657 | 0 | if (!file->fs->enable_timing || file->timing_start[op].tv_sec == 0) |
658 | 0 | return; |
659 | | |
660 | 0 | fs_timing_end(&file->fs->stats.timings[op], &file->timing_start[op]); |
661 | | /* don't count this again */ |
662 | 0 | file->timing_start[op].tv_sec = 0; |
663 | 0 | } |
664 | | |
665 | | int fs_get_metadata_full(struct fs_file *file, |
666 | | enum fs_get_metadata_flags flags, |
667 | | const ARRAY_TYPE(fs_metadata) **metadata_r) |
668 | 0 | { |
669 | 0 | int ret; |
670 | |
|
671 | 0 | if (file->fs->v.get_metadata == NULL) { |
672 | 0 | if (array_is_created(&file->metadata)) { |
673 | | /* Return internal metadata. */ |
674 | 0 | *metadata_r = &file->metadata; |
675 | 0 | return 0; |
676 | 0 | } |
677 | 0 | fs_set_error(file->event, ENOTSUP, "Metadata not supported by backend"); |
678 | 0 | return -1; |
679 | 0 | } |
680 | 0 | if (!file->read_or_prefetch_counted && |
681 | 0 | !file->lookup_metadata_counted) { |
682 | 0 | if ((flags & FS_GET_METADATA_FLAG_LOADED_ONLY) == 0) { |
683 | 0 | file->lookup_metadata_counted = TRUE; |
684 | 0 | file->fs->stats.lookup_metadata_count++; |
685 | 0 | } |
686 | 0 | fs_file_timing_start(file, FS_OP_METADATA); |
687 | 0 | } |
688 | 0 | T_BEGIN { |
689 | 0 | ret = file->fs->v.get_metadata(file, flags, metadata_r); |
690 | 0 | } T_END; |
691 | 0 | if (!(ret < 0 && errno == EAGAIN)) |
692 | 0 | fs_file_timing_end(file, FS_OP_METADATA); |
693 | 0 | return ret; |
694 | 0 | } |
695 | | |
696 | | int fs_get_metadata(struct fs_file *file, |
697 | | const ARRAY_TYPE(fs_metadata) **metadata_r) |
698 | 0 | { |
699 | 0 | return fs_get_metadata_full(file, 0, metadata_r); |
700 | 0 | } |
701 | | |
702 | | int fs_lookup_metadata(struct fs_file *file, const char *key, |
703 | | const char **value_r) |
704 | 0 | { |
705 | 0 | const ARRAY_TYPE(fs_metadata) *metadata; |
706 | |
|
707 | 0 | if (fs_get_metadata(file, &metadata) < 0) |
708 | 0 | return -1; |
709 | 0 | *value_r = fs_metadata_find(metadata, key); |
710 | 0 | return *value_r != NULL ? 1 : 0; |
711 | 0 | } |
712 | | |
713 | | const char *fs_lookup_loaded_metadata(struct fs_file *file, const char *key) |
714 | 0 | { |
715 | 0 | const ARRAY_TYPE(fs_metadata) *metadata; |
716 | |
|
717 | 0 | if (fs_get_metadata_full(file, FS_GET_METADATA_FLAG_LOADED_ONLY, &metadata) < 0) |
718 | 0 | i_panic("FS_GET_METADATA_FLAG_LOADED_ONLY lookup can't fail"); |
719 | 0 | return fs_metadata_find(metadata, key); |
720 | 0 | } |
721 | | |
722 | | const char *fs_file_path(struct fs_file *file) |
723 | 0 | { |
724 | 0 | return file->fs->v.get_path == NULL ? file->path : |
725 | 0 | file->fs->v.get_path(file); |
726 | 0 | } |
727 | | |
728 | | struct fs *fs_file_fs(struct fs_file *file) |
729 | 0 | { |
730 | 0 | return file->fs; |
731 | 0 | } |
732 | | |
733 | | struct event *fs_file_event(struct fs_file *file) |
734 | 0 | { |
735 | 0 | return file->event; |
736 | 0 | } |
737 | | |
738 | | static struct fs_file *fs_file_get_error_file(struct fs_file *file) |
739 | 0 | { |
740 | | /* the error is always kept in the parent-most file */ |
741 | 0 | while (file->parent != NULL) |
742 | 0 | file = file->parent; |
743 | 0 | return file; |
744 | 0 | } |
745 | | |
746 | | static void ATTR_FORMAT(2, 0) |
747 | | fs_set_verror(struct event *event, const char *fmt, va_list args) |
748 | 0 | { |
749 | 0 | struct event *fs_event = event; |
750 | 0 | struct fs_file *file; |
751 | 0 | struct fs_iter *iter; |
752 | | |
753 | | /* NOTE: the event might be a passthrough event. We must log it exactly |
754 | | once so it gets freed. */ |
755 | | |
756 | | /* figure out if the error is for a file or iter */ |
757 | 0 | while ((file = event_get_ptr(fs_event, FS_EVENT_FIELD_FILE)) == NULL && |
758 | 0 | (iter = event_get_ptr(fs_event, FS_EVENT_FIELD_ITER)) == NULL) { |
759 | 0 | fs_event = event_get_parent(fs_event); |
760 | 0 | i_assert(fs_event != NULL); |
761 | 0 | } |
762 | | |
763 | 0 | char *new_error = i_strdup_vprintf(fmt, args); |
764 | | /* Don't flood the debug log with "Asynchronous operation in progress" |
765 | | messages. They tell nothing useful. */ |
766 | 0 | if (errno != EAGAIN) |
767 | 0 | e_debug(event, "%s", new_error); |
768 | 0 | else |
769 | 0 | event_send_abort(event); |
770 | | |
771 | | /* free old error after strdup in case args point to the old error */ |
772 | 0 | if (file != NULL) { |
773 | 0 | file = fs_file_get_error_file(file); |
774 | 0 | char *old_error = file->last_error; |
775 | |
|
776 | 0 | if (old_error == NULL) { |
777 | 0 | i_assert(!file->last_error_changed); |
778 | 0 | } else if (strcmp(old_error, new_error) == 0) { |
779 | | /* identical error - ignore */ |
780 | 0 | } else if (file->last_error_changed) { |
781 | | /* multiple fs_set_error() calls used without |
782 | | fs_file_last_error() in the middle. */ |
783 | 0 | e_error(file->event, "%s (overwriting error for file %s)", |
784 | 0 | old_error, fs_file_path(file)); |
785 | 0 | } |
786 | 0 | if (errno == EAGAIN || errno == ENOENT || errno == EEXIST || |
787 | 0 | errno == ENOTEMPTY) { |
788 | | /* These are (or can be) expected errors - don't log |
789 | | them if they have a missing fs_file_last_error() |
790 | | call */ |
791 | 0 | file->last_error_changed = FALSE; |
792 | 0 | } else { |
793 | 0 | file->last_error_changed = TRUE; |
794 | 0 | } |
795 | |
|
796 | 0 | i_free(file->last_error); |
797 | 0 | file->last_error = new_error; |
798 | 0 | } else { |
799 | 0 | i_assert(iter != NULL); |
800 | 0 | if (iter->last_error != NULL && |
801 | 0 | strcmp(iter->last_error, new_error) == 0) { |
802 | | /* identical error - ignore */ |
803 | 0 | } else if (iter->last_error != NULL) { |
804 | | /* multiple fs_set_error() calls before the iter |
805 | | finishes */ |
806 | 0 | e_error(iter->fs->event, "%s (overwriting error for file %s)", |
807 | 0 | iter->last_error, iter->path); |
808 | 0 | } |
809 | 0 | i_free(iter->last_error); |
810 | 0 | iter->last_error = new_error; |
811 | 0 | } |
812 | 0 | } |
813 | | |
814 | | const char *fs_file_last_error(struct fs_file *file) |
815 | 0 | { |
816 | 0 | struct fs_file *error_file = fs_file_get_error_file(file); |
817 | |
|
818 | 0 | error_file->last_error_changed = FALSE; |
819 | 0 | if (error_file->last_error == NULL) |
820 | 0 | return "BUG: Unknown file error"; |
821 | 0 | return error_file->last_error; |
822 | 0 | } |
823 | | |
824 | | bool fs_prefetch(struct fs_file *file, uoff_t length) |
825 | 0 | { |
826 | 0 | bool ret; |
827 | |
|
828 | 0 | if (!file->read_or_prefetch_counted) { |
829 | 0 | file->read_or_prefetch_counted = TRUE; |
830 | 0 | file->fs->stats.prefetch_count++; |
831 | 0 | fs_file_timing_start(file, FS_OP_PREFETCH); |
832 | 0 | } |
833 | 0 | T_BEGIN { |
834 | 0 | ret = file->fs->v.prefetch(file, length); |
835 | 0 | } T_END; |
836 | 0 | fs_file_timing_end(file, FS_OP_PREFETCH); |
837 | 0 | return ret; |
838 | 0 | } |
839 | | |
840 | | ssize_t fs_read_via_stream(struct fs_file *file, void *buf, size_t size) |
841 | 0 | { |
842 | 0 | const unsigned char *data; |
843 | 0 | size_t data_size; |
844 | 0 | ssize_t ret; |
845 | |
|
846 | 0 | i_assert(size > 0); |
847 | | |
848 | 0 | if (file->pending_read_input == NULL) |
849 | 0 | file->pending_read_input = fs_read_stream(file, size+1); |
850 | 0 | ret = i_stream_read_bytes(file->pending_read_input, &data, |
851 | 0 | &data_size, size); |
852 | 0 | if (ret == 0) { |
853 | 0 | fs_file_set_error_async(file); |
854 | 0 | return -1; |
855 | 0 | } |
856 | 0 | if (ret < 0 && file->pending_read_input->stream_errno != 0) { |
857 | 0 | fs_set_error(file->event, |
858 | 0 | file->pending_read_input->stream_errno, |
859 | 0 | "read(%s) failed: %s", |
860 | 0 | i_stream_get_name(file->pending_read_input), |
861 | 0 | i_stream_get_error(file->pending_read_input)); |
862 | 0 | } else { |
863 | 0 | ret = I_MIN(size, data_size); |
864 | 0 | if (ret > 0) |
865 | 0 | memcpy(buf, data, ret); |
866 | 0 | } |
867 | 0 | i_stream_unref(&file->pending_read_input); |
868 | 0 | return ret; |
869 | 0 | } |
870 | | |
871 | | ssize_t fs_read(struct fs_file *file, void *buf, size_t size) |
872 | 0 | { |
873 | 0 | int ret; |
874 | |
|
875 | 0 | if (!file->read_or_prefetch_counted) { |
876 | 0 | file->read_or_prefetch_counted = TRUE; |
877 | 0 | file->fs->stats.read_count++; |
878 | 0 | fs_file_timing_start(file, FS_OP_READ); |
879 | 0 | } |
880 | |
|
881 | 0 | if (file->fs->v.read != NULL) { |
882 | 0 | T_BEGIN { |
883 | 0 | ret = file->fs->v.read(file, buf, size); |
884 | 0 | } T_END; |
885 | 0 | if (!(ret < 0 && errno == EAGAIN)) |
886 | 0 | fs_file_timing_end(file, FS_OP_READ); |
887 | 0 | return ret; |
888 | 0 | } |
889 | | |
890 | | /* backend didn't bother to implement read(), but we can do it with |
891 | | streams. */ |
892 | 0 | return fs_read_via_stream(file, buf, size); |
893 | 0 | } |
894 | | |
895 | | static void fs_file_istream_destroyed(struct fs_file *file) |
896 | 0 | { |
897 | 0 | i_assert(file->istream_open); |
898 | | |
899 | 0 | file->istream_open = FALSE; |
900 | 0 | } |
901 | | |
902 | | struct istream *fs_read_stream(struct fs_file *file, size_t max_buffer_size) |
903 | 0 | { |
904 | 0 | struct istream *input, *inputs[2]; |
905 | 0 | const unsigned char *data; |
906 | 0 | size_t size; |
907 | 0 | ssize_t ret; |
908 | 0 | bool want_seekable = FALSE; |
909 | |
|
910 | 0 | if (!file->read_or_prefetch_counted) { |
911 | 0 | file->read_or_prefetch_counted = TRUE; |
912 | 0 | file->fs->stats.read_count++; |
913 | 0 | fs_file_timing_start(file, FS_OP_READ); |
914 | 0 | } |
915 | |
|
916 | 0 | if (file->seekable_input != NULL) { |
917 | | /* allow multiple open streams, each in a different position */ |
918 | 0 | input = i_stream_create_limit(file->seekable_input, UOFF_T_MAX); |
919 | 0 | i_stream_seek(input, 0); |
920 | 0 | return input; |
921 | 0 | } |
922 | 0 | i_assert(!file->istream_open); |
923 | 0 | T_BEGIN { |
924 | 0 | input = file->fs->v.read_stream(file, max_buffer_size); |
925 | 0 | } T_END; |
926 | 0 | if (input->stream_errno != 0) { |
927 | | /* read failed already */ |
928 | 0 | fs_file_timing_end(file, FS_OP_READ); |
929 | 0 | return input; |
930 | 0 | } |
931 | 0 | if (file->fs->enable_timing) { |
932 | 0 | struct istream *input2 = i_stream_create_fs_stats(input, file); |
933 | |
|
934 | 0 | i_stream_unref(&input); |
935 | 0 | input = input2; |
936 | 0 | } |
937 | |
|
938 | 0 | if ((file->flags & FS_OPEN_FLAG_SEEKABLE) != 0) |
939 | 0 | want_seekable = TRUE; |
940 | 0 | else if ((file->flags & FS_OPEN_FLAG_ASYNC) == 0 && !input->blocking) |
941 | 0 | want_seekable = TRUE; |
942 | |
|
943 | 0 | if (want_seekable && !input->seekable) { |
944 | | /* need to make the stream seekable */ |
945 | 0 | inputs[0] = input; |
946 | 0 | inputs[1] = NULL; |
947 | 0 | input = i_stream_create_seekable_path(inputs, max_buffer_size, |
948 | 0 | file->fs->temp_path_prefix); |
949 | 0 | i_stream_set_name(input, i_stream_get_name(inputs[0])); |
950 | 0 | i_stream_unref(&inputs[0]); |
951 | 0 | } |
952 | 0 | file->seekable_input = input; |
953 | 0 | i_stream_ref(file->seekable_input); |
954 | |
|
955 | 0 | if ((file->flags & FS_OPEN_FLAG_ASYNC) == 0 && !input->blocking) { |
956 | | /* read the whole input stream before returning */ |
957 | 0 | while ((ret = i_stream_read_more(input, &data, &size)) >= 0) { |
958 | 0 | i_stream_skip(input, size); |
959 | 0 | if (ret == 0) |
960 | 0 | fs_wait_async(file->fs); |
961 | 0 | } |
962 | 0 | i_stream_seek(input, 0); |
963 | 0 | } |
964 | 0 | file->istream_open = TRUE; |
965 | 0 | i_stream_add_destroy_callback(input, fs_file_istream_destroyed, file); |
966 | 0 | return input; |
967 | 0 | } |
968 | | |
969 | | int fs_write_via_stream(struct fs_file *file, const void *data, size_t size) |
970 | 0 | { |
971 | 0 | struct ostream *output; |
972 | 0 | ssize_t ret; |
973 | 0 | int err; |
974 | |
|
975 | 0 | if (!file->write_pending) { |
976 | 0 | output = fs_write_stream(file); |
977 | 0 | if ((ret = o_stream_send(output, data, size)) < 0) { |
978 | 0 | err = errno; |
979 | 0 | fs_write_stream_abort_error(file, &output, "fs_write(%s) failed: %s", |
980 | 0 | o_stream_get_name(output), |
981 | 0 | o_stream_get_error(output)); |
982 | 0 | errno = err; |
983 | 0 | return -1; |
984 | 0 | } |
985 | 0 | i_assert((size_t)ret == size); |
986 | 0 | ret = fs_write_stream_finish(file, &output); |
987 | 0 | } else { |
988 | 0 | ret = fs_write_stream_finish_async(file); |
989 | 0 | } |
990 | 0 | if (ret == 0) { |
991 | 0 | fs_file_set_error_async(file); |
992 | 0 | file->write_pending = TRUE; |
993 | 0 | return -1; |
994 | 0 | } |
995 | 0 | file->write_pending = FALSE; |
996 | 0 | return ret < 0 ? -1 : 0; |
997 | 0 | } |
998 | | |
999 | | int fs_write(struct fs_file *file, const void *data, size_t size) |
1000 | 0 | { |
1001 | 0 | int ret; |
1002 | |
|
1003 | 0 | if (file->fs->v.write != NULL) { |
1004 | 0 | fs_file_timing_start(file, FS_OP_WRITE); |
1005 | 0 | T_BEGIN { |
1006 | 0 | ret = file->fs->v.write(file, data, size); |
1007 | 0 | } T_END; |
1008 | 0 | if (!(ret < 0 && errno == EAGAIN)) { |
1009 | 0 | file->fs->stats.write_count++; |
1010 | 0 | file->fs->stats.write_bytes += size; |
1011 | 0 | fs_file_timing_end(file, FS_OP_WRITE); |
1012 | 0 | } |
1013 | 0 | return ret; |
1014 | 0 | } |
1015 | | |
1016 | | /* backend didn't bother to implement write(), but we can do it with |
1017 | | streams. */ |
1018 | 0 | return fs_write_via_stream(file, data, size); |
1019 | 0 | } |
1020 | | |
1021 | | struct ostream *fs_write_stream(struct fs_file *file) |
1022 | 0 | { |
1023 | 0 | i_assert(!file->writing_stream); |
1024 | 0 | i_assert(file->output == NULL); |
1025 | | |
1026 | 0 | file->writing_stream = TRUE; |
1027 | 0 | file->fs->stats.write_count++; |
1028 | 0 | T_BEGIN { |
1029 | 0 | file->fs->v.write_stream(file); |
1030 | 0 | } T_END; |
1031 | 0 | i_assert(file->output != NULL); |
1032 | 0 | o_stream_cork(file->output); |
1033 | 0 | return file->output; |
1034 | 0 | } |
1035 | | |
1036 | | static int fs_write_stream_finish_int(struct fs_file *file, bool success) |
1037 | 0 | { |
1038 | 0 | int ret; |
1039 | |
|
1040 | 0 | i_assert(file->writing_stream); |
1041 | | |
1042 | 0 | fs_file_timing_start(file, FS_OP_WRITE); |
1043 | 0 | T_BEGIN { |
1044 | 0 | ret = file->fs->v.write_stream_finish(file, success); |
1045 | 0 | } T_END; |
1046 | 0 | if (ret != 0) { |
1047 | 0 | fs_file_timing_end(file, FS_OP_WRITE); |
1048 | 0 | file->metadata_changed = FALSE; |
1049 | 0 | } else { |
1050 | | /* write didn't finish yet. this shouldn't happen if we |
1051 | | indicated a failure. */ |
1052 | 0 | i_assert(success); |
1053 | 0 | } |
1054 | 0 | if (ret != 0) { |
1055 | 0 | i_assert(file->output == NULL); |
1056 | 0 | file->writing_stream = FALSE; |
1057 | 0 | } |
1058 | 0 | return ret; |
1059 | 0 | } |
1060 | | |
1061 | | int fs_write_stream_finish(struct fs_file *file, struct ostream **output) |
1062 | 0 | { |
1063 | 0 | bool success = TRUE; |
1064 | 0 | int ret; |
1065 | |
|
1066 | 0 | i_assert(*output == file->output || *output == NULL); |
1067 | 0 | i_assert(output != &file->output); |
1068 | | |
1069 | 0 | *output = NULL; |
1070 | 0 | if (file->output != NULL) { |
1071 | 0 | o_stream_uncork(file->output); |
1072 | 0 | if ((ret = o_stream_finish(file->output)) <= 0) { |
1073 | 0 | i_assert(ret < 0); |
1074 | 0 | fs_set_error(file->event, file->output->stream_errno, |
1075 | 0 | "write(%s) failed: %s", |
1076 | 0 | o_stream_get_name(file->output), |
1077 | 0 | o_stream_get_error(file->output)); |
1078 | 0 | success = FALSE; |
1079 | 0 | } |
1080 | 0 | file->fs->stats.write_bytes += file->output->offset; |
1081 | 0 | } |
1082 | 0 | return fs_write_stream_finish_int(file, success); |
1083 | 0 | } |
1084 | | |
1085 | | int fs_write_stream_finish_async(struct fs_file *file) |
1086 | 0 | { |
1087 | 0 | return fs_write_stream_finish_int(file, TRUE); |
1088 | 0 | } |
1089 | | |
1090 | | /* Accepts both a caller-owned pointer (e.g. &output where output is a local |
1091 | | * variable) and &file->output itself. */ |
1092 | | static void fs_write_stream_abort(struct fs_file *file, struct ostream **output) |
1093 | 0 | { |
1094 | 0 | int ret; |
1095 | 0 | bool is_file_output = (output == &file->output); |
1096 | |
|
1097 | 0 | i_assert(*output == file->output); |
1098 | 0 | i_assert(file->output != NULL); |
1099 | | |
1100 | 0 | if (!is_file_output) |
1101 | 0 | *output = NULL; |
1102 | |
|
1103 | 0 | o_stream_abort(file->output); |
1104 | | /* make sure we don't have an old error lying around */ |
1105 | 0 | ret = fs_write_stream_finish_int(file, FALSE); |
1106 | |
|
1107 | 0 | if (is_file_output) |
1108 | 0 | *output = NULL; |
1109 | |
|
1110 | 0 | i_assert(ret != 0); |
1111 | 0 | } |
1112 | | |
1113 | | void fs_write_stream_abort_error(struct fs_file *file, struct ostream **output, const char *error_fmt, ...) |
1114 | 0 | { |
1115 | 0 | va_list args; |
1116 | 0 | va_start(args, error_fmt); |
1117 | 0 | fs_set_verror(file->event, error_fmt, args); |
1118 | | /* the error shouldn't be automatically logged if |
1119 | | fs_file_last_error() is no longer used */ |
1120 | 0 | fs_file_get_error_file(file)->last_error_changed = FALSE; |
1121 | 0 | fs_write_stream_abort(file, output); |
1122 | 0 | va_end(args); |
1123 | 0 | } |
1124 | | |
1125 | | void fs_write_stream_abort_parent(struct fs_file *file, struct ostream **output) |
1126 | 0 | { |
1127 | 0 | i_assert(file->parent != NULL); |
1128 | 0 | i_assert(fs_file_last_error(file->parent) != NULL); |
1129 | 0 | fs_write_stream_abort(file->parent, output); |
1130 | 0 | } |
1131 | | |
1132 | | void fs_write_set_hash(struct fs_file *file, const struct hash_method *method, |
1133 | | const void *digest) |
1134 | 0 | { |
1135 | 0 | file->write_digest_method = method; |
1136 | |
|
1137 | 0 | i_free(file->write_digest); |
1138 | 0 | file->write_digest = i_malloc(method->digest_size); |
1139 | 0 | memcpy(file->write_digest, digest, method->digest_size); |
1140 | 0 | } |
1141 | | |
1142 | | #undef fs_file_set_async_callback |
1143 | | void fs_file_set_async_callback(struct fs_file *file, |
1144 | | fs_file_async_callback_t *callback, |
1145 | | void *context) |
1146 | 0 | { |
1147 | 0 | if (file->fs->v.set_async_callback != NULL) |
1148 | 0 | file->fs->v.set_async_callback(file, callback, context); |
1149 | 0 | else |
1150 | 0 | callback(context); |
1151 | 0 | } |
1152 | | |
1153 | | void fs_wait_async(struct fs *fs) |
1154 | 0 | { |
1155 | | /* recursion not allowed */ |
1156 | 0 | i_assert(fs->prev_ioloop == NULL); |
1157 | | |
1158 | 0 | if (fs->v.wait_async != NULL) T_BEGIN { |
1159 | 0 | fs->prev_ioloop = current_ioloop; |
1160 | 0 | fs->v.wait_async(fs); |
1161 | 0 | i_assert(current_ioloop == fs->prev_ioloop); |
1162 | 0 | fs->prev_ioloop = NULL; |
1163 | 0 | } T_END; |
1164 | 0 | } |
1165 | | |
1166 | | bool fs_switch_ioloop(struct fs *fs) |
1167 | 0 | { |
1168 | 0 | bool ret = FALSE; |
1169 | |
|
1170 | 0 | if (fs->v.switch_ioloop != NULL) { |
1171 | 0 | T_BEGIN { |
1172 | 0 | ret = fs->v.switch_ioloop(fs); |
1173 | 0 | } T_END; |
1174 | 0 | } else if (fs->parent != NULL) { |
1175 | 0 | ret = fs_switch_ioloop(fs->parent); |
1176 | 0 | } |
1177 | 0 | return ret; |
1178 | 0 | } |
1179 | | |
1180 | | int fs_lock(struct fs_file *file, unsigned int secs, struct fs_lock **lock_r) |
1181 | 0 | { |
1182 | 0 | int ret; |
1183 | |
|
1184 | 0 | T_BEGIN { |
1185 | 0 | ret = file->fs->v.lock(file, secs, lock_r); |
1186 | 0 | } T_END; |
1187 | 0 | return ret; |
1188 | 0 | } |
1189 | | |
1190 | | void fs_unlock(struct fs_lock **_lock) |
1191 | 0 | { |
1192 | 0 | struct fs_lock *lock = *_lock; |
1193 | |
|
1194 | 0 | if (lock == NULL) |
1195 | 0 | return; |
1196 | | |
1197 | 0 | *_lock = NULL; |
1198 | 0 | T_BEGIN { |
1199 | 0 | lock->file->fs->v.unlock(lock); |
1200 | 0 | } T_END; |
1201 | 0 | } |
1202 | | |
1203 | | int fs_exists(struct fs_file *file) |
1204 | 0 | { |
1205 | 0 | struct stat st; |
1206 | 0 | int ret; |
1207 | |
|
1208 | 0 | if (file->fs->v.exists == NULL) { |
1209 | | /* fallback to stat() */ |
1210 | 0 | if (fs_stat(file, &st) == 0) |
1211 | 0 | return 1; |
1212 | 0 | else |
1213 | 0 | return errno == ENOENT ? 0 : -1; |
1214 | 0 | } |
1215 | 0 | fs_file_timing_start(file, FS_OP_EXISTS); |
1216 | 0 | T_BEGIN { |
1217 | 0 | ret = file->fs->v.exists(file); |
1218 | 0 | } T_END; |
1219 | 0 | if (!(ret < 0 && errno == EAGAIN)) { |
1220 | 0 | file->fs->stats.exists_count++; |
1221 | 0 | fs_file_timing_end(file, FS_OP_EXISTS); |
1222 | 0 | } |
1223 | 0 | return ret; |
1224 | 0 | } |
1225 | | |
1226 | | int fs_stat(struct fs_file *file, struct stat *st_r) |
1227 | 0 | { |
1228 | 0 | int ret; |
1229 | |
|
1230 | 0 | if (file->fs->v.stat == NULL) { |
1231 | 0 | fs_set_error(file->event, ENOTSUP, "fs_stat() not supported"); |
1232 | 0 | return -1; |
1233 | 0 | } |
1234 | | |
1235 | 0 | if (!file->read_or_prefetch_counted && |
1236 | 0 | !file->lookup_metadata_counted && !file->stat_counted) { |
1237 | 0 | file->stat_counted = TRUE; |
1238 | 0 | file->fs->stats.stat_count++; |
1239 | 0 | fs_file_timing_start(file, FS_OP_STAT); |
1240 | 0 | } |
1241 | 0 | T_BEGIN { |
1242 | 0 | ret = file->fs->v.stat(file, st_r); |
1243 | 0 | } T_END; |
1244 | 0 | if (!(ret < 0 && errno == EAGAIN)) |
1245 | 0 | fs_file_timing_end(file, FS_OP_STAT); |
1246 | 0 | return ret; |
1247 | 0 | } |
1248 | | |
1249 | | int fs_get_nlinks(struct fs_file *file, nlink_t *nlinks_r) |
1250 | 0 | { |
1251 | 0 | int ret; |
1252 | |
|
1253 | 0 | if (file->fs->v.get_nlinks == NULL) { |
1254 | 0 | struct stat st; |
1255 | |
|
1256 | 0 | if (fs_stat(file, &st) < 0) |
1257 | 0 | return -1; |
1258 | 0 | *nlinks_r = st.st_nlink; |
1259 | 0 | return 0; |
1260 | 0 | } |
1261 | | |
1262 | 0 | if (!file->read_or_prefetch_counted && |
1263 | 0 | !file->lookup_metadata_counted && !file->stat_counted) { |
1264 | 0 | file->stat_counted = TRUE; |
1265 | 0 | file->fs->stats.stat_count++; |
1266 | 0 | fs_file_timing_start(file, FS_OP_STAT); |
1267 | 0 | } |
1268 | 0 | T_BEGIN { |
1269 | 0 | ret = file->fs->v.get_nlinks(file, nlinks_r); |
1270 | 0 | } T_END; |
1271 | 0 | if (!(ret < 0 && errno == EAGAIN)) |
1272 | 0 | fs_file_timing_end(file, FS_OP_STAT); |
1273 | 0 | return ret; |
1274 | 0 | } |
1275 | | |
1276 | | int fs_default_copy(struct fs_file *src, struct fs_file *dest) |
1277 | 0 | { |
1278 | 0 | int tmp_errno; |
1279 | | /* we're going to be counting this as read+write, so don't update |
1280 | | copy_count */ |
1281 | 0 | dest->copy_counted = TRUE; |
1282 | |
|
1283 | 0 | if (dest->copy_src != NULL) { |
1284 | 0 | i_assert(src == NULL || src == dest->copy_src); |
1285 | 0 | if (dest->copy_output == NULL) { |
1286 | 0 | i_assert(dest->copy_input == NULL); |
1287 | 0 | if (fs_write_stream_finish_async(dest) <= 0) |
1288 | 0 | return -1; |
1289 | 0 | dest->copy_src = NULL; |
1290 | 0 | return 0; |
1291 | 0 | } |
1292 | 0 | } else { |
1293 | 0 | dest->copy_src = src; |
1294 | 0 | dest->copy_input = fs_read_stream(src, IO_BLOCK_SIZE); |
1295 | 0 | dest->copy_output = fs_write_stream(dest); |
1296 | 0 | } |
1297 | 0 | switch (o_stream_send_istream(dest->copy_output, dest->copy_input)) { |
1298 | 0 | case OSTREAM_SEND_ISTREAM_RESULT_FINISHED: |
1299 | 0 | break; |
1300 | 0 | case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT: |
1301 | 0 | case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT: |
1302 | 0 | fs_file_set_error_async(dest); |
1303 | 0 | return -1; |
1304 | 0 | case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT: |
1305 | 0 | fs_write_stream_abort_error(dest, &dest->copy_output, |
1306 | 0 | "read(%s) failed: %s", |
1307 | 0 | i_stream_get_name(dest->copy_input), |
1308 | 0 | i_stream_get_error(dest->copy_input)); |
1309 | 0 | errno = dest->copy_input->stream_errno; |
1310 | 0 | i_stream_unref(&dest->copy_input); |
1311 | 0 | return -1; |
1312 | 0 | case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT: |
1313 | | /* errno might not survive abort error */ |
1314 | 0 | tmp_errno = dest->copy_output->stream_errno; |
1315 | 0 | fs_write_stream_abort_error(dest, &dest->copy_output, |
1316 | 0 | "write(%s) failed: %s", |
1317 | 0 | o_stream_get_name(dest->copy_output), |
1318 | 0 | o_stream_get_error(dest->copy_output)); |
1319 | 0 | errno = tmp_errno; |
1320 | 0 | i_stream_unref(&dest->copy_input); |
1321 | 0 | return -1; |
1322 | 0 | } |
1323 | 0 | i_stream_unref(&dest->copy_input); |
1324 | 0 | if (fs_write_stream_finish(dest, &dest->copy_output) <= 0) |
1325 | 0 | return -1; |
1326 | 0 | dest->copy_src = NULL; |
1327 | 0 | return 0; |
1328 | 0 | } |
1329 | | |
1330 | | int fs_copy(struct fs_file *src, struct fs_file *dest) |
1331 | 0 | { |
1332 | 0 | int ret; |
1333 | |
|
1334 | 0 | i_assert(src->fs == dest->fs); |
1335 | | |
1336 | 0 | if (src->fs->v.copy == NULL) { |
1337 | 0 | fs_set_error(src->event, ENOTSUP, "fs_copy() not supported"); |
1338 | 0 | return -1; |
1339 | 0 | } |
1340 | | |
1341 | 0 | fs_file_timing_start(dest, FS_OP_COPY); |
1342 | 0 | T_BEGIN { |
1343 | 0 | ret = src->fs->v.copy(src, dest); |
1344 | 0 | } T_END; |
1345 | 0 | if (!(ret < 0 && errno == EAGAIN)) { |
1346 | 0 | fs_file_timing_end(dest, FS_OP_COPY); |
1347 | 0 | if (dest->copy_counted) |
1348 | 0 | dest->copy_counted = FALSE; |
1349 | 0 | else |
1350 | 0 | dest->fs->stats.copy_count++; |
1351 | 0 | dest->metadata_changed = FALSE; |
1352 | 0 | } |
1353 | 0 | return ret; |
1354 | 0 | } |
1355 | | |
1356 | | int fs_copy_finish_async(struct fs_file *dest) |
1357 | 0 | { |
1358 | 0 | int ret; |
1359 | |
|
1360 | 0 | T_BEGIN { |
1361 | 0 | ret = dest->fs->v.copy(NULL, dest); |
1362 | 0 | } T_END; |
1363 | 0 | if (!(ret < 0 && errno == EAGAIN)) { |
1364 | 0 | fs_file_timing_end(dest, FS_OP_COPY); |
1365 | 0 | if (dest->copy_counted) |
1366 | 0 | dest->copy_counted = FALSE; |
1367 | 0 | else |
1368 | 0 | dest->fs->stats.copy_count++; |
1369 | 0 | dest->metadata_changed = FALSE; |
1370 | 0 | } |
1371 | 0 | return ret; |
1372 | 0 | } |
1373 | | |
1374 | | int fs_rename(struct fs_file *src, struct fs_file *dest) |
1375 | 0 | { |
1376 | 0 | int ret; |
1377 | |
|
1378 | 0 | i_assert(src->fs == dest->fs); |
1379 | | |
1380 | 0 | fs_file_timing_start(dest, FS_OP_RENAME); |
1381 | 0 | T_BEGIN { |
1382 | 0 | ret = src->fs->v.rename(src, dest); |
1383 | 0 | } T_END; |
1384 | 0 | if (!(ret < 0 && errno == EAGAIN)) { |
1385 | 0 | dest->fs->stats.rename_count++; |
1386 | 0 | fs_file_timing_end(dest, FS_OP_RENAME); |
1387 | 0 | } |
1388 | 0 | return ret; |
1389 | 0 | } |
1390 | | |
1391 | | int fs_delete(struct fs_file *file) |
1392 | 0 | { |
1393 | 0 | int ret; |
1394 | |
|
1395 | 0 | fs_file_timing_start(file, FS_OP_DELETE); |
1396 | 0 | T_BEGIN { |
1397 | 0 | ret = file->fs->v.delete_file(file); |
1398 | 0 | } T_END; |
1399 | 0 | if (!(ret < 0 && errno == EAGAIN)) { |
1400 | 0 | file->fs->stats.delete_count++; |
1401 | 0 | fs_file_timing_end(file, FS_OP_DELETE); |
1402 | 0 | } |
1403 | 0 | return ret; |
1404 | 0 | } |
1405 | | |
1406 | | struct fs_iter * |
1407 | | fs_iter_init(struct fs *fs, const char *path, enum fs_iter_flags flags) |
1408 | 0 | { |
1409 | 0 | return fs_iter_init_with_event(fs, fs->event, path, flags); |
1410 | 0 | } |
1411 | | |
1412 | | struct fs_iter * |
1413 | | fs_iter_init_with_event(struct fs *fs, struct event *event, |
1414 | | const char *path, enum fs_iter_flags flags) |
1415 | 0 | { |
1416 | 0 | struct fs_iter *iter; |
1417 | 0 | struct timeval now = ioloop_timeval; |
1418 | |
|
1419 | 0 | i_assert((flags & FS_ITER_FLAG_OBJECTIDS) == 0 || |
1420 | 0 | (fs_get_properties(fs) & FS_PROPERTY_OBJECTIDS) != 0); |
1421 | | |
1422 | 0 | fs->stats.iter_count++; |
1423 | 0 | if (fs->enable_timing) |
1424 | 0 | i_gettimeofday(&now); |
1425 | 0 | if (fs->v.iter_init == NULL) |
1426 | 0 | iter = i_new(struct fs_iter, 1); |
1427 | 0 | else |
1428 | 0 | iter = fs->v.iter_alloc(); |
1429 | 0 | iter->fs = fs; |
1430 | 0 | iter->event = fs_create_event(fs, event); |
1431 | 0 | event_set_ptr(iter->event, FS_EVENT_FIELD_FS, fs); |
1432 | 0 | event_set_ptr(iter->event, FS_EVENT_FIELD_ITER, iter); |
1433 | 0 | if (fs->v.iter_init != NULL) T_BEGIN { |
1434 | 0 | iter->flags = flags; |
1435 | 0 | iter->path = i_strdup(path); |
1436 | 0 | fs->v.iter_init(iter, path, flags); |
1437 | 0 | } T_END; |
1438 | 0 | iter->start_time = now; |
1439 | 0 | DLLIST_PREPEND(&fs->iters, iter); |
1440 | 0 | return iter; |
1441 | 0 | } |
1442 | | |
1443 | | int fs_iter_deinit(struct fs_iter **_iter, const char **error_r) |
1444 | 0 | { |
1445 | 0 | struct fs_iter *iter = *_iter; |
1446 | 0 | struct fs *fs; |
1447 | 0 | struct event *event; |
1448 | 0 | int ret; |
1449 | |
|
1450 | 0 | if (iter == NULL) |
1451 | 0 | return 0; |
1452 | | |
1453 | 0 | fs = iter->fs; |
1454 | 0 | event = iter->event; |
1455 | |
|
1456 | 0 | *_iter = NULL; |
1457 | 0 | DLLIST_REMOVE(&fs->iters, iter); |
1458 | |
|
1459 | 0 | if (fs->v.iter_deinit == NULL) { |
1460 | 0 | fs_set_error(event, ENOTSUP, "FS iteration not supported"); |
1461 | 0 | ret = -1; |
1462 | 0 | } else T_BEGIN { |
1463 | 0 | ret = iter->fs->v.iter_deinit(iter); |
1464 | 0 | } T_END; |
1465 | 0 | if (ret < 0) |
1466 | 0 | *error_r = t_strdup(iter->last_error); |
1467 | 0 | i_free(iter->last_error); |
1468 | 0 | i_free(iter->path); |
1469 | 0 | i_free(iter); |
1470 | 0 | event_unref(&event); |
1471 | 0 | return ret; |
1472 | 0 | } |
1473 | | |
1474 | | const char *fs_iter_next(struct fs_iter *iter) |
1475 | 0 | { |
1476 | 0 | const char *ret; |
1477 | |
|
1478 | 0 | if (iter->fs->v.iter_next == NULL) |
1479 | 0 | return NULL; |
1480 | 0 | T_BEGIN { |
1481 | 0 | ret = iter->fs->v.iter_next(iter); |
1482 | 0 | } T_END; |
1483 | 0 | if (iter->start_time.tv_sec != 0 && |
1484 | 0 | (ret != NULL || !fs_iter_have_more(iter))) { |
1485 | | /* first result returned - count this as the finish time, since |
1486 | | we don't want to count the time caller spends on this |
1487 | | iteration. */ |
1488 | 0 | fs_timing_end(&iter->fs->stats.timings[FS_OP_ITER], &iter->start_time); |
1489 | | /* don't count this again */ |
1490 | 0 | iter->start_time.tv_sec = 0; |
1491 | 0 | } |
1492 | 0 | return ret; |
1493 | 0 | } |
1494 | | |
1495 | | #undef fs_iter_set_async_callback |
1496 | | void fs_iter_set_async_callback(struct fs_iter *iter, |
1497 | | fs_file_async_callback_t *callback, |
1498 | | void *context) |
1499 | 0 | { |
1500 | 0 | iter->async_callback = callback; |
1501 | 0 | iter->async_context = context; |
1502 | 0 | } |
1503 | | |
1504 | | bool fs_iter_have_more(struct fs_iter *iter) |
1505 | 0 | { |
1506 | 0 | return iter->async_have_more; |
1507 | 0 | } |
1508 | | |
1509 | | const struct fs_stats *fs_get_stats(struct fs *fs) |
1510 | 0 | { |
1511 | 0 | return &fs->stats; |
1512 | 0 | } |
1513 | | |
1514 | | void fs_set_error(struct event *event, int err, const char *fmt, ...) |
1515 | 0 | { |
1516 | 0 | va_list args; |
1517 | |
|
1518 | 0 | i_assert(err != 0); |
1519 | | |
1520 | 0 | errno = err; |
1521 | 0 | va_start(args, fmt); |
1522 | 0 | fs_set_verror(event, fmt, args); |
1523 | 0 | va_end(args); |
1524 | 0 | } |
1525 | | |
1526 | | void fs_set_error_errno(struct event *event, const char *fmt, ...) |
1527 | 0 | { |
1528 | 0 | va_list args; |
1529 | |
|
1530 | 0 | i_assert(errno != 0); |
1531 | | |
1532 | 0 | va_start(args, fmt); |
1533 | 0 | fs_set_verror(event, fmt, args); |
1534 | 0 | va_end(args); |
1535 | 0 | } |
1536 | | |
1537 | | void fs_file_set_error_async(struct fs_file *file) |
1538 | 0 | { |
1539 | 0 | fs_set_error(file->event, EAGAIN, "Asynchronous operation in progress"); |
1540 | 0 | } |
1541 | | |
1542 | | static uint64_t |
1543 | | fs_stats_count_ops(const struct fs_stats *stats, const enum fs_op ops[], |
1544 | | unsigned int ops_count) |
1545 | 0 | { |
1546 | 0 | uint64_t ret = 0; |
1547 | |
|
1548 | 0 | for (unsigned int i = 0; i < ops_count; i++) { |
1549 | 0 | if (stats->timings[ops[i]] != NULL) |
1550 | 0 | ret += stats_dist_get_sum(stats->timings[ops[i]]); |
1551 | 0 | } |
1552 | 0 | return ret; |
1553 | 0 | } |
1554 | | |
1555 | | uint64_t fs_stats_get_read_usecs(const struct fs_stats *stats) |
1556 | 0 | { |
1557 | 0 | const enum fs_op read_ops[] = { |
1558 | 0 | FS_OP_METADATA, FS_OP_PREFETCH, FS_OP_READ, FS_OP_EXISTS, |
1559 | 0 | FS_OP_STAT, FS_OP_ITER |
1560 | 0 | }; |
1561 | 0 | return fs_stats_count_ops(stats, read_ops, N_ELEMENTS(read_ops)); |
1562 | 0 | } |
1563 | | |
1564 | | uint64_t fs_stats_get_write_usecs(const struct fs_stats *stats) |
1565 | 0 | { |
1566 | 0 | const enum fs_op write_ops[] = { |
1567 | 0 | FS_OP_WRITE, FS_OP_COPY, FS_OP_DELETE |
1568 | 0 | }; |
1569 | 0 | return fs_stats_count_ops(stats, write_ops, N_ELEMENTS(write_ops)); |
1570 | 0 | } |
1571 | | |
1572 | | struct fs_file * |
1573 | | fs_file_init_parent(struct fs_file *parent, const char *path, |
1574 | | enum fs_open_mode mode, enum fs_open_flags flags) |
1575 | 0 | { |
1576 | 0 | return fs_file_init_with_event(parent->fs->parent, parent->event, |
1577 | 0 | path, (int)mode | (int)flags); |
1578 | 0 | } |
1579 | | |
1580 | | struct fs_iter * |
1581 | | fs_iter_init_parent(struct fs_iter *parent, |
1582 | | const char *path, enum fs_iter_flags flags) |
1583 | 0 | { |
1584 | 0 | return fs_iter_init_with_event(parent->fs->parent, parent->event, |
1585 | 0 | path, flags); |
1586 | 0 | } |
1587 | | |
1588 | | struct event *fs_get_event(struct fs *fs) |
1589 | 0 | { |
1590 | 0 | return fs->event; |
1591 | 0 | } |