1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
|
#ifndef FS_API_H
#define FS_API_H
struct stat;
struct fs;
struct fs_file;
struct fs_lock;
struct hash_method;
/* Metadata with this prefix shouldn't actually be sent to storage. */
#define FS_METADATA_INTERNAL_PREFIX ":/X-Dovecot-fs-api-"
/* fs_write*() may return a hex-encoded object ID after write is finished.
This can be later on used to optimize reads by setting it before reading
the file. */
#define FS_METADATA_OBJECTID FS_METADATA_INTERNAL_PREFIX"ObjectID"
/* Calling this before fs_write_stream_finish() allows renaming the filename.
This can be useful if you don't know the final filename before writing it
(e.g. filename contains the file size). The given filename must include the
full path also. */
#define FS_METADATA_WRITE_FNAME FS_METADATA_INTERNAL_PREFIX"WriteFilename"
/* Original path of the file. The path that's eventually visible to a fs
backend may be something different, e.g. object ID. This allows the backend
to still access the original path. */
#define FS_METADATA_ORIG_PATH FS_METADATA_INTERNAL_PREFIX"OrigPath"
enum fs_properties {
FS_PROPERTY_METADATA = 0x01,
FS_PROPERTY_LOCKS = 0x02,
FS_PROPERTY_FASTCOPY = 0x04,
FS_PROPERTY_RENAME = 0x08,
FS_PROPERTY_STAT = 0x10,
/* Iteration is possible */
FS_PROPERTY_ITER = 0x20,
/* Iteration always returns all of the files (instead of possibly
slightly out of date view) */
FS_PROPERTY_RELIABLEITER= 0x40,
/* Backend uses directories, which aren't automatically deleted
when its children are deleted. */
FS_PROPERTY_DIRECTORIES = 0x80,
FS_PROPERTY_WRITE_HASH_MD5 = 0x100,
FS_PROPERTY_WRITE_HASH_SHA256 = 0x200,
/* fs_copy() will copy the metadata if fs_set_metadata() hasn't
been explicitly called. */
FS_PROPERTY_COPY_METADATA = 0x400,
/* Backend support asynchronous file operations. */
FS_PROPERTY_ASYNC = 0x800,
/* Backend supports FS_ITER_FLAG_OBJECTIDS. */
FS_PROPERTY_OBJECTIDS = 0x1000,
/* fs_copy() is fast even when file's metadata is changed */
FS_PROPERTY_FASTCOPY_CHANGED_METADATA = 0x2000,
};
enum fs_open_mode {
/* Open only for reading, or fail with ENOENT if it doesn't exist */
FS_OPEN_MODE_READONLY,
/* Create a new file, fail with EEXIST if it already exists */
FS_OPEN_MODE_CREATE,
/* Create a new file with a new unique name. The generated name is a
128bit hex-encoded string. The fs_open()'s path parameter specifies
only the directory where the file is created to. */
FS_OPEN_MODE_CREATE_UNIQUE_128,
/* Create or replace a file */
FS_OPEN_MODE_REPLACE,
/* Append to existing file, fail with ENOENT if it doesn't exist */
FS_OPEN_MODE_APPEND
#define FS_OPEN_MODE_MASK 0x0f
};
enum fs_open_flags {
/* File is important and writing must call fsync() or have equivalent
behavior. */
FS_OPEN_FLAG_FSYNC = 0x10,
/* Asynchronous writes: fs_write() will fail with EAGAIN if it needs to
be called again (the retries can use size=0). For streams
fs_write_stream_finish() may request retrying with 0.
Asynchronous reads: fs_read() will fail with EAGAIN if it's not
finished and fs_read_stream() returns a nonblocking stream. */
FS_OPEN_FLAG_ASYNC = 0x20,
/* fs_read_stream() must return a seekable input stream */
FS_OPEN_FLAG_SEEKABLE = 0x40,
/* Backend should handle this file's operations immediately without
any additional command queueing. The caller is assumed to be the one
doing any rate limiting if needed. This flag can only be used with
ASYNC flag, synchronous requests are never queued. */
FS_OPEN_FLAG_ASYNC_NOQUEUE = 0x80
};
enum fs_iter_flags {
/* Iterate only directories, not files */
FS_ITER_FLAG_DIRS = 0x01,
/* Request asynchronous iteration. */
FS_ITER_FLAG_ASYNC = 0x02,
/* Instead of returning object names, return <objectid>/<object name>.
If this isn't supported, the <objectid> is returned empty. The
object IDs are always hex-encoded data. This flag can be used only
if FS_PROPERTY_OBJECTIDS is enabled. */
FS_ITER_FLAG_OBJECTIDS = 0x04,
/* Explicitly disable all caching for this iteration (if anything
happens to be enabled). This should be used only in situations where
the iteration is used to fix something that is broken, e.g. doveadm
force-resync. */
FS_ITER_FLAG_NOCACHE = 0x08
};
enum fs_op {
FS_OP_WAIT,
FS_OP_METADATA,
FS_OP_PREFETCH,
FS_OP_READ,
FS_OP_WRITE,
FS_OP_LOCK,
FS_OP_EXISTS,
FS_OP_STAT,
FS_OP_COPY,
FS_OP_RENAME,
FS_OP_DELETE,
FS_OP_ITER,
FS_OP_COUNT
};
struct fs_settings {
/* Username and session ID are mainly used for debugging/logging,
but may also be useful for other purposes if they exist (they
may be NULL). */
const char *username;
const char *session_id;
/* Dovecot instance's base_dir */
const char *base_dir;
/* Directory where temporary files can be created at any time
(e.g. /tmp or mail_temp_dir) */
const char *temp_dir;
/* SSL client settings. */
const struct ssl_iostream_settings *ssl_client_set;
/* Automatically try to rmdir() directories up to this path when
deleting files. */
const char *root_path;
/* When creating temporary files, use this prefix
(to avoid conflicts with existing files). */
const char *temp_file_prefix;
/* If the backend needs to do DNS lookups, use this dns_client for
them. */
struct dns_client *dns_client;
/* Parent event to use, unless overridden by
fs_file_init_with_event() */
struct event *event_parent;
/* Enable debugging */
bool debug;
/* Enable timing statistics */
bool enable_timing;
};
struct fs_stats {
/* Number of fs_prefetch() calls. Counted only if fs_read*() hasn't
already been called for the file (which would be pretty pointless
to do). */
unsigned int prefetch_count;
/* Number of fs_read*() calls. Counted only if fs_prefetch() hasn't
already been called for the file. */
unsigned int read_count;
/* Number of fs_lookup_metadata() calls. Counted only if neither
fs_read*() nor fs_prefetch() has been called for the file. */
unsigned int lookup_metadata_count;
/* Number of fs_stat() calls. Counted only if none of the above
has been called (because the stat result should be cached). */
unsigned int stat_count;
/* Number of fs_write*() calls. */
unsigned int write_count;
/* Number of fs_exists() calls, which actually went to the backend
instead of being handled by fs_stat() call due to fs_exists() not
being implemented. */
unsigned int exists_count;
/* Number of fs_delete() calls. */
unsigned int delete_count;
/* Number of fs_copy() calls. If backend doesn't implement copying
operation but falls back to regular read+write instead, this count
isn't increased but the read+write counters are. */
unsigned int copy_count;
/* Number of fs_rename() calls. */
unsigned int rename_count;
/* Number of fs_iter_init() calls. */
unsigned int iter_count;
/* Number of bytes written by fs_write*() calls. */
uint64_t write_bytes;
/* Cumulative sum of usecs spent on calls - set only if
fs_settings.enable_timing=TRUE */
struct stats_dist *timings[FS_OP_COUNT];
};
struct fs_metadata {
const char *key;
const char *value;
};
ARRAY_DEFINE_TYPE(fs_metadata, struct fs_metadata);
typedef void fs_file_async_callback_t(void *context);
int fs_init(const char *driver, const char *args,
const struct fs_settings *set,
struct fs **fs_r, const char **error_r);
/* helper for fs_init, accepts a filesystem string
that can come directly from config */
int fs_init_from_string(const char *str, const struct fs_settings *set,
struct fs **fs_r, const char **error_r);
/* same as fs_unref() */
void fs_deinit(struct fs **fs);
void fs_ref(struct fs *fs);
void fs_unref(struct fs **fs);
/* Returns the parent filesystem (if this is a wrapper fs) or NULL if
there's no parent. */
struct fs *fs_get_parent(struct fs *fs);
/* Returns the filesystem's driver name. */
const char *fs_get_driver(struct fs *fs);
/* Returns the root fs's driver name (bypassing all wrapper fses) */
const char *fs_get_root_driver(struct fs *fs);
struct fs_file *fs_file_init(struct fs *fs, const char *path, int mode_flags);
struct fs_file *fs_file_init_with_event(struct fs *fs, struct event *event,
const char *path, int mode_flags);
void fs_file_deinit(struct fs_file **file);
/* Change flags for a file (and its parents). */
void fs_file_set_flags(struct fs_file *file,
enum fs_open_flags add_flags,
enum fs_open_flags remove_flags);
/* If the file has an input streams open, close them. */
void fs_file_close(struct fs_file *file);
/* Return properties supported by backend. */
enum fs_properties fs_get_properties(struct fs *fs);
/* Add/replace metadata when saving a file. This makes sense only when the
file is being created/replaced. */
void fs_set_metadata(struct fs_file *file, const char *key, const char *value);
/* Return file's all metadata. */
int fs_get_metadata(struct fs_file *file,
const ARRAY_TYPE(fs_metadata) **metadata_r);
/* Wrapper to fs_get_metadata() to lookup a specific key. Returns 1 if value_r
is set, 0 if key wasn't found, -1 if error. */
int fs_lookup_metadata(struct fs_file *file, const char *key,
const char **value_r);
/* Try to find key from the currently set metadata (without refreshing it).
This is typically used e.g. after writing or copying a file to find some
extra metadata they may have set. It can also be used after
fs_get_metadata() or fs_lookup_metadata() to search within the looked up
metadata. */
const char *fs_lookup_loaded_metadata(struct fs_file *file, const char *key);
/* Returns the path given to fs_open(). If file was opened with
FS_OPEN_MODE_CREATE_UNIQUE_128 and the write has already finished,
return the path including the generated filename. */
const char *fs_file_path(struct fs_file *file);
/* Returns the file's fs. */
struct fs *fs_file_fs(struct fs_file *file);
/* Returns the file's event. */
struct event *fs_file_event(struct fs_file *file);
/* Return the error message for the last failed file operation. Each file
keeps track of its own errors. For failed copy/rename operations the "dest"
file contains the error. */
const char *fs_file_last_error(struct fs_file *file);
/* Try to asynchronously prefetch file into memory. Returns TRUE if file is
already in memory (i.e. caller should handle this file before prefetching
more), FALSE if not. The length is a hint of how much the caller expects
to read, but it may be more or less (0=whole file). */
bool fs_prefetch(struct fs_file *file, uoff_t length);
/* Returns >0 if something was read, -1 if error (errno is set). */
ssize_t fs_read(struct fs_file *file, void *buf, size_t size);
/* Returns a stream for reading from file. Multiple streams can be opened,
and caller must destroy the streams before closing the file. */
struct istream *fs_read_stream(struct fs_file *file, size_t max_buffer_size);
/* Returns 0 if ok, -1 if error (errno is set). Note: With CREATE/REPLACE mode
files you can call fs_write() only once, the file creation is finished by it.
CREATE can return EEXIST here, if the destination file was already created.
With APPEND mode each fs_write() atomically appends the given data to
file. */
int fs_write(struct fs_file *file, const void *data, size_t size);
/* Write to file via output stream. The stream will be destroyed by
fs_write_stream_finish/abort. The returned ostream is already corked and
it doesn't need to be uncorked. */
struct ostream *fs_write_stream(struct fs_file *file);
/* Finish writing via stream, calling also o_stream_flush() on the stream and
handling any pending errors. The file will be created/replaced/appended only
after this call, same as with fs_write(). Anything written to the stream
won't be visible earlier. Returns 1 if ok, 0 if async write isn't finished
yet (retry calling fs_write_stream_finish_async()), -1 if error */
int fs_write_stream_finish(struct fs_file *file, struct ostream **output);
int fs_write_stream_finish_async(struct fs_file *file);
/* Abort writing via stream. Anything written to the stream is discarded.
o_stream_ignore_last_errors() is called on the output stream so the caller
doesn't need to do it. This must not be called after
fs_write_stream_finish(), i.e. it can't be used to abort a pending async
write. */
void fs_write_stream_abort_error(struct fs_file *file, struct ostream **output, const char *error_fmt, ...) ATTR_FORMAT(3, 4);
/* Set a hash to the following write. The storage can then verify that the
input data matches the specified hash, or fail if it doesn't. Typically
implemented by Content-MD5 header. */
void fs_write_set_hash(struct fs_file *file, const struct hash_method *method,
const void *digest);
/* Call the specified callback whenever the file can be read/written to.
May call the callback immediately. */
void fs_file_set_async_callback(struct fs_file *file,
fs_file_async_callback_t *callback,
void *context);
#define fs_file_set_async_callback(file, callback, context) \
fs_file_set_async_callback(file, (fs_file_async_callback_t *)(callback), \
1 ? (context) : \
CALLBACK_TYPECHECK(callback, void (*)(typeof(context))))
/* Wait until some file can be read/written to more before returning.
It's an error to call this when there are no pending async operations. */
void fs_wait_async(struct fs *fs);
/* Switch the fs to the current ioloop. This can be used to do fs_wait_async()
among other IO work. Returns TRUE if there is actually some work that can
be waited on. */
bool fs_switch_ioloop(struct fs *fs) ATTR_NOWARN_UNUSED_RESULT;
/* Returns 1 if file exists, 0 if not, -1 if error occurred. */
int fs_exists(struct fs_file *file);
/* Delete a file. Returns 0 if file was actually deleted by us, -1 if error. */
int fs_delete(struct fs_file *file);
/* Returns 0 if ok, -1 if error occurred (e.g. errno=ENOENT).
All fs backends may not support all stat fields. */
int fs_stat(struct fs_file *file, struct stat *st_r);
/* Get number of links to the file. This is the same as using fs_stat()'s
st_nlinks field, except not all backends support returning it via fs_stat().
Returns 0 if ok, -1 if error occurred. */
int fs_get_nlinks(struct fs_file *file, nlink_t *nlinks_r);
/* Copy an object with possibly updated metadata. Destination parent
directories are created automatically. Returns 0 if ok, -1 if error
occurred. The "dest" file contains the error. */
int fs_copy(struct fs_file *src, struct fs_file *dest);
/* Try to finish asynchronous fs_copy(). Returns the same as fs_copy(). */
int fs_copy_finish_async(struct fs_file *dest);
/* Atomically rename a file. Destination parent directories are created
automatically. Returns 0 if ok, -1 if error occurred. The "dest" file
contains the error. */
int fs_rename(struct fs_file *src, struct fs_file *dest);
/* Exclusively lock a file. If file is already locked, wait for it for given
number of seconds (0 = fail immediately). Returns 1 if locked, 0 if wait
timed out, -1 if error. */
int fs_lock(struct fs_file *file, unsigned int secs, struct fs_lock **lock_r);
void fs_unlock(struct fs_lock **lock);
/* Iterate through all files or directories in the given directory.
Doesn't recurse to child directories. It's not an error to iterate a
nonexistent directory. */
struct fs_iter *
fs_iter_init(struct fs *fs, const char *path, enum fs_iter_flags flags);
struct fs_iter *
fs_iter_init_with_event(struct fs *fs, struct event *event,
const char *path, enum fs_iter_flags flags);
/* Returns 0 if ok, -1 if iteration failed. */
int fs_iter_deinit(struct fs_iter **iter, const char **error_r);
/* Returns the next filename. */
const char *fs_iter_next(struct fs_iter *iter);
/* For asynchronous iterations: Specify the callback that is called whenever
there's more data available for reading. */
void fs_iter_set_async_callback(struct fs_iter *iter,
fs_file_async_callback_t *callback,
void *context);
#define fs_iter_set_async_callback(iter, callback, context) \
fs_iter_set_async_callback(iter, (fs_file_async_callback_t *)(callback), \
1 ? (context) : \
CALLBACK_TYPECHECK(callback, void (*)(typeof(context))))
/* For asynchronous iterations: If fs_iter_next() returns NULL, use this
function to determine if you should wait for more data or finish up. */
bool fs_iter_have_more(struct fs_iter *iter);
/* Return the filesystem's fs_stats. Note that each wrapper filesystem keeps
track of its own fs_stats calls. You can use fs_get_parent() to get to the
filesystem whose stats you want to see. */
const struct fs_stats *fs_get_stats(struct fs *fs);
/* Helper functions to count number of usecs for read/write operations. */
uint64_t fs_stats_get_read_usecs(const struct fs_stats *stats);
uint64_t fs_stats_get_write_usecs(const struct fs_stats *stats);
#endif
|