]>
Commit | Line | Data |
---|---|---|
7c6b6602 | 1 | /* |
7387863d DDAG |
2 | * FUSE: Filesystem in Userspace |
3 | * Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu> | |
4 | * | |
5 | * This program can be distributed under the terms of the GNU GPLv2. | |
6 | * See the file COPYING. | |
7 | */ | |
7c6b6602 | 8 | |
7387863d | 9 | /* |
7c6b6602 DDAG |
10 | * |
11 | * This file system mirrors the existing file system hierarchy of the | |
12 | * system, starting at the root file system. This is implemented by | |
13 | * just "passing through" all requests to the corresponding user-space | |
14 | * libc functions. In contrast to passthrough.c and passthrough_fh.c, | |
15 | * this implementation uses the low-level API. Its performance should | |
16 | * be the least bad among the three, but many operations are not | |
17 | * implemented. In particular, it is not possible to remove files (or | |
18 | * directories) because the code necessary to defer actual removal | |
19 | * until the file is not opened anymore would make the example much | |
20 | * more complicated. | |
21 | * | |
22 | * When writeback caching is enabled (-o writeback mount option), it | |
23 | * is only possible to write to files for which the mounting user has | |
24 | * read permissions. This is because the writeback cache requires the | |
25 | * kernel to be able to issue read requests for all files (which the | |
26 | * passthrough filesystem cannot satisfy if it can't read the file in | |
27 | * the underlying filesystem). | |
28 | * | |
29 | * Compile with: | |
30 | * | |
7387863d DDAG |
31 | * gcc -Wall passthrough_ll.c `pkg-config fuse3 --cflags --libs` -o |
32 | * passthrough_ll | |
7c6b6602 DDAG |
33 | * |
34 | * ## Source code ## | |
35 | * \include passthrough_ll.c | |
36 | */ | |
37 | ||
09863ebc DDAG |
38 | #include "qemu/osdep.h" |
39 | #include "fuse_lowlevel.h" | |
7c6b6602 | 40 | #include <assert.h> |
7387863d | 41 | #include <dirent.h> |
7c6b6602 DDAG |
42 | #include <errno.h> |
43 | #include <inttypes.h> | |
7387863d | 44 | #include <limits.h> |
7c6b6602 | 45 | #include <pthread.h> |
7387863d DDAG |
46 | #include <stdbool.h> |
47 | #include <stddef.h> | |
48 | #include <stdio.h> | |
49 | #include <stdlib.h> | |
50 | #include <string.h> | |
7c6b6602 DDAG |
51 | #include <sys/file.h> |
52 | #include <sys/xattr.h> | |
7387863d | 53 | #include <unistd.h> |
7c6b6602 DDAG |
54 | |
55 | #include "passthrough_helpers.h" | |
56 | ||
7387863d DDAG |
57 | /* |
58 | * We are re-using pointers to our `struct lo_inode` and `struct | |
59 | * lo_dirp` elements as inodes. This means that we must be able to | |
60 | * store uintptr_t values in a fuse_ino_t variable. The following | |
61 | * incantation checks this condition at compile time. | |
62 | */ | |
63 | #if defined(__GNUC__) && \ | |
64 | (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 6) && \ | |
65 | !defined __cplusplus | |
7c6b6602 | 66 | _Static_assert(sizeof(fuse_ino_t) >= sizeof(uintptr_t), |
7387863d | 67 | "fuse_ino_t too small to hold uintptr_t values!"); |
7c6b6602 | 68 | #else |
7387863d DDAG |
69 | struct _uintptr_to_must_hold_fuse_ino_t_dummy_struct { |
70 | unsigned _uintptr_to_must_hold_fuse_ino_t | |
71 | : ((sizeof(fuse_ino_t) >= sizeof(uintptr_t)) ? 1 : -1); | |
72 | }; | |
7c6b6602 DDAG |
73 | #endif |
74 | ||
75 | struct lo_inode { | |
7387863d DDAG |
76 | struct lo_inode *next; /* protected by lo->mutex */ |
77 | struct lo_inode *prev; /* protected by lo->mutex */ | |
78 | int fd; | |
79 | bool is_symlink; | |
80 | ino_t ino; | |
81 | dev_t dev; | |
82 | uint64_t refcount; /* protected by lo->mutex */ | |
7c6b6602 DDAG |
83 | }; |
84 | ||
85 | enum { | |
7387863d DDAG |
86 | CACHE_NEVER, |
87 | CACHE_NORMAL, | |
88 | CACHE_ALWAYS, | |
7c6b6602 DDAG |
89 | }; |
90 | ||
91 | struct lo_data { | |
7387863d DDAG |
92 | pthread_mutex_t mutex; |
93 | int debug; | |
94 | int writeback; | |
95 | int flock; | |
96 | int xattr; | |
97 | const char *source; | |
98 | double timeout; | |
99 | int cache; | |
100 | int timeout_set; | |
101 | struct lo_inode root; /* protected by lo->mutex */ | |
7c6b6602 DDAG |
102 | }; |
103 | ||
104 | static const struct fuse_opt lo_opts[] = { | |
7387863d DDAG |
105 | { "writeback", offsetof(struct lo_data, writeback), 1 }, |
106 | { "no_writeback", offsetof(struct lo_data, writeback), 0 }, | |
107 | { "source=%s", offsetof(struct lo_data, source), 0 }, | |
108 | { "flock", offsetof(struct lo_data, flock), 1 }, | |
109 | { "no_flock", offsetof(struct lo_data, flock), 0 }, | |
110 | { "xattr", offsetof(struct lo_data, xattr), 1 }, | |
111 | { "no_xattr", offsetof(struct lo_data, xattr), 0 }, | |
112 | { "timeout=%lf", offsetof(struct lo_data, timeout), 0 }, | |
113 | { "timeout=", offsetof(struct lo_data, timeout_set), 1 }, | |
114 | { "cache=never", offsetof(struct lo_data, cache), CACHE_NEVER }, | |
115 | { "cache=auto", offsetof(struct lo_data, cache), CACHE_NORMAL }, | |
116 | { "cache=always", offsetof(struct lo_data, cache), CACHE_ALWAYS }, | |
117 | ||
118 | FUSE_OPT_END | |
7c6b6602 DDAG |
119 | }; |
120 | ||
121 | static struct lo_data *lo_data(fuse_req_t req) | |
122 | { | |
7387863d | 123 | return (struct lo_data *)fuse_req_userdata(req); |
7c6b6602 DDAG |
124 | } |
125 | ||
126 | static struct lo_inode *lo_inode(fuse_req_t req, fuse_ino_t ino) | |
127 | { | |
7387863d DDAG |
128 | if (ino == FUSE_ROOT_ID) { |
129 | return &lo_data(req)->root; | |
130 | } else { | |
131 | return (struct lo_inode *)(uintptr_t)ino; | |
132 | } | |
7c6b6602 DDAG |
133 | } |
134 | ||
135 | static int lo_fd(fuse_req_t req, fuse_ino_t ino) | |
136 | { | |
7387863d | 137 | return lo_inode(req, ino)->fd; |
7c6b6602 DDAG |
138 | } |
139 | ||
140 | static bool lo_debug(fuse_req_t req) | |
141 | { | |
7387863d | 142 | return lo_data(req)->debug != 0; |
7c6b6602 DDAG |
143 | } |
144 | ||
7387863d | 145 | static void lo_init(void *userdata, struct fuse_conn_info *conn) |
7c6b6602 | 146 | { |
7387863d DDAG |
147 | struct lo_data *lo = (struct lo_data *)userdata; |
148 | ||
149 | if (conn->capable & FUSE_CAP_EXPORT_SUPPORT) { | |
150 | conn->want |= FUSE_CAP_EXPORT_SUPPORT; | |
151 | } | |
152 | ||
153 | if (lo->writeback && conn->capable & FUSE_CAP_WRITEBACK_CACHE) { | |
154 | if (lo->debug) { | |
155 | fuse_log(FUSE_LOG_DEBUG, "lo_init: activating writeback\n"); | |
156 | } | |
157 | conn->want |= FUSE_CAP_WRITEBACK_CACHE; | |
158 | } | |
159 | if (lo->flock && conn->capable & FUSE_CAP_FLOCK_LOCKS) { | |
160 | if (lo->debug) { | |
161 | fuse_log(FUSE_LOG_DEBUG, "lo_init: activating flock locks\n"); | |
162 | } | |
163 | conn->want |= FUSE_CAP_FLOCK_LOCKS; | |
164 | } | |
7c6b6602 DDAG |
165 | } |
166 | ||
167 | static void lo_getattr(fuse_req_t req, fuse_ino_t ino, | |
7387863d | 168 | struct fuse_file_info *fi) |
7c6b6602 | 169 | { |
7387863d DDAG |
170 | int res; |
171 | struct stat buf; | |
172 | struct lo_data *lo = lo_data(req); | |
7c6b6602 | 173 | |
7387863d | 174 | (void)fi; |
7c6b6602 | 175 | |
7387863d DDAG |
176 | res = |
177 | fstatat(lo_fd(req, ino), "", &buf, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); | |
178 | if (res == -1) { | |
179 | return (void)fuse_reply_err(req, errno); | |
180 | } | |
7c6b6602 | 181 | |
7387863d | 182 | fuse_reply_attr(req, &buf, lo->timeout); |
7c6b6602 DDAG |
183 | } |
184 | ||
185 | static int utimensat_empty_nofollow(struct lo_inode *inode, | |
7387863d | 186 | const struct timespec *tv) |
7c6b6602 | 187 | { |
7387863d DDAG |
188 | int res; |
189 | char procname[64]; | |
190 | ||
191 | if (inode->is_symlink) { | |
192 | res = utimensat(inode->fd, "", tv, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); | |
193 | if (res == -1 && errno == EINVAL) { | |
194 | /* Sorry, no race free way to set times on symlink. */ | |
195 | errno = EPERM; | |
196 | } | |
197 | return res; | |
198 | } | |
199 | sprintf(procname, "/proc/self/fd/%i", inode->fd); | |
200 | ||
201 | return utimensat(AT_FDCWD, procname, tv, 0); | |
7c6b6602 DDAG |
202 | } |
203 | ||
204 | static void lo_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, | |
7387863d | 205 | int valid, struct fuse_file_info *fi) |
7c6b6602 | 206 | { |
7387863d DDAG |
207 | int saverr; |
208 | char procname[64]; | |
209 | struct lo_inode *inode = lo_inode(req, ino); | |
210 | int ifd = inode->fd; | |
211 | int res; | |
212 | ||
213 | if (valid & FUSE_SET_ATTR_MODE) { | |
214 | if (fi) { | |
215 | res = fchmod(fi->fh, attr->st_mode); | |
216 | } else { | |
217 | sprintf(procname, "/proc/self/fd/%i", ifd); | |
218 | res = chmod(procname, attr->st_mode); | |
219 | } | |
220 | if (res == -1) { | |
221 | goto out_err; | |
222 | } | |
223 | } | |
224 | if (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID)) { | |
225 | uid_t uid = (valid & FUSE_SET_ATTR_UID) ? attr->st_uid : (uid_t)-1; | |
226 | gid_t gid = (valid & FUSE_SET_ATTR_GID) ? attr->st_gid : (gid_t)-1; | |
227 | ||
228 | res = fchownat(ifd, "", uid, gid, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); | |
229 | if (res == -1) { | |
230 | goto out_err; | |
231 | } | |
232 | } | |
233 | if (valid & FUSE_SET_ATTR_SIZE) { | |
234 | if (fi) { | |
235 | res = ftruncate(fi->fh, attr->st_size); | |
236 | } else { | |
237 | sprintf(procname, "/proc/self/fd/%i", ifd); | |
238 | res = truncate(procname, attr->st_size); | |
239 | } | |
240 | if (res == -1) { | |
241 | goto out_err; | |
242 | } | |
243 | } | |
244 | if (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) { | |
245 | struct timespec tv[2]; | |
246 | ||
247 | tv[0].tv_sec = 0; | |
248 | tv[1].tv_sec = 0; | |
249 | tv[0].tv_nsec = UTIME_OMIT; | |
250 | tv[1].tv_nsec = UTIME_OMIT; | |
251 | ||
252 | if (valid & FUSE_SET_ATTR_ATIME_NOW) { | |
253 | tv[0].tv_nsec = UTIME_NOW; | |
254 | } else if (valid & FUSE_SET_ATTR_ATIME) { | |
255 | tv[0] = attr->st_atim; | |
256 | } | |
257 | ||
258 | if (valid & FUSE_SET_ATTR_MTIME_NOW) { | |
259 | tv[1].tv_nsec = UTIME_NOW; | |
260 | } else if (valid & FUSE_SET_ATTR_MTIME) { | |
261 | tv[1] = attr->st_mtim; | |
262 | } | |
263 | ||
264 | if (fi) { | |
265 | res = futimens(fi->fh, tv); | |
266 | } else { | |
267 | res = utimensat_empty_nofollow(inode, tv); | |
268 | } | |
269 | if (res == -1) { | |
270 | goto out_err; | |
271 | } | |
272 | } | |
273 | ||
274 | return lo_getattr(req, ino, fi); | |
7c6b6602 DDAG |
275 | |
276 | out_err: | |
7387863d DDAG |
277 | saverr = errno; |
278 | fuse_reply_err(req, saverr); | |
7c6b6602 DDAG |
279 | } |
280 | ||
281 | static struct lo_inode *lo_find(struct lo_data *lo, struct stat *st) | |
282 | { | |
7387863d DDAG |
283 | struct lo_inode *p; |
284 | struct lo_inode *ret = NULL; | |
285 | ||
286 | pthread_mutex_lock(&lo->mutex); | |
287 | for (p = lo->root.next; p != &lo->root; p = p->next) { | |
288 | if (p->ino == st->st_ino && p->dev == st->st_dev) { | |
289 | assert(p->refcount > 0); | |
290 | ret = p; | |
291 | ret->refcount++; | |
292 | break; | |
293 | } | |
294 | } | |
295 | pthread_mutex_unlock(&lo->mutex); | |
296 | return ret; | |
7c6b6602 DDAG |
297 | } |
298 | ||
299 | static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name, | |
7387863d | 300 | struct fuse_entry_param *e) |
7c6b6602 | 301 | { |
7387863d DDAG |
302 | int newfd; |
303 | int res; | |
304 | int saverr; | |
305 | struct lo_data *lo = lo_data(req); | |
306 | struct lo_inode *inode; | |
307 | ||
308 | memset(e, 0, sizeof(*e)); | |
309 | e->attr_timeout = lo->timeout; | |
310 | e->entry_timeout = lo->timeout; | |
311 | ||
312 | newfd = openat(lo_fd(req, parent), name, O_PATH | O_NOFOLLOW); | |
313 | if (newfd == -1) { | |
314 | goto out_err; | |
315 | } | |
316 | ||
317 | res = fstatat(newfd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); | |
318 | if (res == -1) { | |
319 | goto out_err; | |
320 | } | |
321 | ||
322 | inode = lo_find(lo_data(req), &e->attr); | |
323 | if (inode) { | |
324 | close(newfd); | |
325 | newfd = -1; | |
326 | } else { | |
327 | struct lo_inode *prev, *next; | |
328 | ||
329 | saverr = ENOMEM; | |
330 | inode = calloc(1, sizeof(struct lo_inode)); | |
331 | if (!inode) { | |
332 | goto out_err; | |
333 | } | |
334 | ||
335 | inode->is_symlink = S_ISLNK(e->attr.st_mode); | |
336 | inode->refcount = 1; | |
337 | inode->fd = newfd; | |
338 | inode->ino = e->attr.st_ino; | |
339 | inode->dev = e->attr.st_dev; | |
340 | ||
341 | pthread_mutex_lock(&lo->mutex); | |
342 | prev = &lo->root; | |
343 | next = prev->next; | |
344 | next->prev = inode; | |
345 | inode->next = next; | |
346 | inode->prev = prev; | |
347 | prev->next = inode; | |
348 | pthread_mutex_unlock(&lo->mutex); | |
349 | } | |
350 | e->ino = (uintptr_t)inode; | |
351 | ||
352 | if (lo_debug(req)) { | |
353 | fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n", | |
354 | (unsigned long long)parent, name, (unsigned long long)e->ino); | |
355 | } | |
356 | ||
357 | return 0; | |
7c6b6602 DDAG |
358 | |
359 | out_err: | |
7387863d DDAG |
360 | saverr = errno; |
361 | if (newfd != -1) { | |
362 | close(newfd); | |
363 | } | |
364 | return saverr; | |
7c6b6602 DDAG |
365 | } |
366 | ||
367 | static void lo_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) | |
368 | { | |
7387863d DDAG |
369 | struct fuse_entry_param e; |
370 | int err; | |
371 | ||
372 | if (lo_debug(req)) { | |
373 | fuse_log(FUSE_LOG_DEBUG, "lo_lookup(parent=%" PRIu64 ", name=%s)\n", | |
374 | parent, name); | |
375 | } | |
376 | ||
377 | err = lo_do_lookup(req, parent, name, &e); | |
378 | if (err) { | |
379 | fuse_reply_err(req, err); | |
380 | } else { | |
381 | fuse_reply_entry(req, &e); | |
382 | } | |
7c6b6602 DDAG |
383 | } |
384 | ||
385 | static void lo_mknod_symlink(fuse_req_t req, fuse_ino_t parent, | |
7387863d DDAG |
386 | const char *name, mode_t mode, dev_t rdev, |
387 | const char *link) | |
7c6b6602 | 388 | { |
7387863d DDAG |
389 | int res; |
390 | int saverr; | |
391 | struct lo_inode *dir = lo_inode(req, parent); | |
392 | struct fuse_entry_param e; | |
7c6b6602 | 393 | |
7387863d | 394 | saverr = ENOMEM; |
7c6b6602 | 395 | |
7387863d | 396 | res = mknod_wrapper(dir->fd, name, link, mode, rdev); |
7c6b6602 | 397 | |
7387863d DDAG |
398 | saverr = errno; |
399 | if (res == -1) { | |
400 | goto out; | |
401 | } | |
7c6b6602 | 402 | |
7387863d DDAG |
403 | saverr = lo_do_lookup(req, parent, name, &e); |
404 | if (saverr) { | |
405 | goto out; | |
406 | } | |
7c6b6602 | 407 | |
7387863d DDAG |
408 | if (lo_debug(req)) { |
409 | fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n", | |
410 | (unsigned long long)parent, name, (unsigned long long)e.ino); | |
411 | } | |
7c6b6602 | 412 | |
7387863d DDAG |
413 | fuse_reply_entry(req, &e); |
414 | return; | |
7c6b6602 DDAG |
415 | |
416 | out: | |
7387863d | 417 | fuse_reply_err(req, saverr); |
7c6b6602 DDAG |
418 | } |
419 | ||
7387863d DDAG |
420 | static void lo_mknod(fuse_req_t req, fuse_ino_t parent, const char *name, |
421 | mode_t mode, dev_t rdev) | |
7c6b6602 | 422 | { |
7387863d | 423 | lo_mknod_symlink(req, parent, name, mode, rdev, NULL); |
7c6b6602 DDAG |
424 | } |
425 | ||
426 | static void lo_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name, | |
7387863d | 427 | mode_t mode) |
7c6b6602 | 428 | { |
7387863d | 429 | lo_mknod_symlink(req, parent, name, S_IFDIR | mode, 0, NULL); |
7c6b6602 DDAG |
430 | } |
431 | ||
7387863d DDAG |
432 | static void lo_symlink(fuse_req_t req, const char *link, fuse_ino_t parent, |
433 | const char *name) | |
7c6b6602 | 434 | { |
7387863d | 435 | lo_mknod_symlink(req, parent, name, S_IFLNK, 0, link); |
7c6b6602 DDAG |
436 | } |
437 | ||
438 | static int linkat_empty_nofollow(struct lo_inode *inode, int dfd, | |
7387863d | 439 | const char *name) |
7c6b6602 | 440 | { |
7387863d DDAG |
441 | int res; |
442 | char procname[64]; | |
7c6b6602 | 443 | |
7387863d DDAG |
444 | if (inode->is_symlink) { |
445 | res = linkat(inode->fd, "", dfd, name, AT_EMPTY_PATH); | |
446 | if (res == -1 && (errno == ENOENT || errno == EINVAL)) { | |
447 | /* Sorry, no race free way to hard-link a symlink. */ | |
448 | errno = EPERM; | |
449 | } | |
450 | return res; | |
451 | } | |
7c6b6602 | 452 | |
7387863d | 453 | sprintf(procname, "/proc/self/fd/%i", inode->fd); |
7c6b6602 | 454 | |
7387863d | 455 | return linkat(AT_FDCWD, procname, dfd, name, AT_SYMLINK_FOLLOW); |
7c6b6602 DDAG |
456 | } |
457 | ||
458 | static void lo_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent, | |
7387863d | 459 | const char *name) |
7c6b6602 | 460 | { |
7387863d DDAG |
461 | int res; |
462 | struct lo_data *lo = lo_data(req); | |
463 | struct lo_inode *inode = lo_inode(req, ino); | |
464 | struct fuse_entry_param e; | |
465 | int saverr; | |
466 | ||
467 | memset(&e, 0, sizeof(struct fuse_entry_param)); | |
468 | e.attr_timeout = lo->timeout; | |
469 | e.entry_timeout = lo->timeout; | |
470 | ||
471 | res = linkat_empty_nofollow(inode, lo_fd(req, parent), name); | |
472 | if (res == -1) { | |
473 | goto out_err; | |
474 | } | |
475 | ||
476 | res = fstatat(inode->fd, "", &e.attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); | |
477 | if (res == -1) { | |
478 | goto out_err; | |
479 | } | |
480 | ||
481 | pthread_mutex_lock(&lo->mutex); | |
482 | inode->refcount++; | |
483 | pthread_mutex_unlock(&lo->mutex); | |
484 | e.ino = (uintptr_t)inode; | |
485 | ||
486 | if (lo_debug(req)) { | |
487 | fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n", | |
488 | (unsigned long long)parent, name, (unsigned long long)e.ino); | |
489 | } | |
490 | ||
491 | fuse_reply_entry(req, &e); | |
492 | return; | |
7c6b6602 DDAG |
493 | |
494 | out_err: | |
7387863d DDAG |
495 | saverr = errno; |
496 | fuse_reply_err(req, saverr); | |
7c6b6602 DDAG |
497 | } |
498 | ||
499 | static void lo_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name) | |
500 | { | |
7387863d | 501 | int res; |
7c6b6602 | 502 | |
7387863d | 503 | res = unlinkat(lo_fd(req, parent), name, AT_REMOVEDIR); |
7c6b6602 | 504 | |
7387863d | 505 | fuse_reply_err(req, res == -1 ? errno : 0); |
7c6b6602 DDAG |
506 | } |
507 | ||
508 | static void lo_rename(fuse_req_t req, fuse_ino_t parent, const char *name, | |
7387863d DDAG |
509 | fuse_ino_t newparent, const char *newname, |
510 | unsigned int flags) | |
7c6b6602 | 511 | { |
7387863d | 512 | int res; |
7c6b6602 | 513 | |
7387863d DDAG |
514 | if (flags) { |
515 | fuse_reply_err(req, EINVAL); | |
516 | return; | |
517 | } | |
7c6b6602 | 518 | |
7387863d | 519 | res = renameat(lo_fd(req, parent), name, lo_fd(req, newparent), newname); |
7c6b6602 | 520 | |
7387863d | 521 | fuse_reply_err(req, res == -1 ? errno : 0); |
7c6b6602 DDAG |
522 | } |
523 | ||
524 | static void lo_unlink(fuse_req_t req, fuse_ino_t parent, const char *name) | |
525 | { | |
7387863d | 526 | int res; |
7c6b6602 | 527 | |
7387863d | 528 | res = unlinkat(lo_fd(req, parent), name, 0); |
7c6b6602 | 529 | |
7387863d | 530 | fuse_reply_err(req, res == -1 ? errno : 0); |
7c6b6602 DDAG |
531 | } |
532 | ||
533 | static void unref_inode(struct lo_data *lo, struct lo_inode *inode, uint64_t n) | |
534 | { | |
7387863d DDAG |
535 | if (!inode) { |
536 | return; | |
537 | } | |
538 | ||
539 | pthread_mutex_lock(&lo->mutex); | |
540 | assert(inode->refcount >= n); | |
541 | inode->refcount -= n; | |
542 | if (!inode->refcount) { | |
543 | struct lo_inode *prev, *next; | |
544 | ||
545 | prev = inode->prev; | |
546 | next = inode->next; | |
547 | next->prev = prev; | |
548 | prev->next = next; | |
549 | ||
550 | pthread_mutex_unlock(&lo->mutex); | |
551 | close(inode->fd); | |
552 | free(inode); | |
553 | ||
554 | } else { | |
555 | pthread_mutex_unlock(&lo->mutex); | |
556 | } | |
7c6b6602 DDAG |
557 | } |
558 | ||
559 | static void lo_forget_one(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup) | |
560 | { | |
7387863d DDAG |
561 | struct lo_data *lo = lo_data(req); |
562 | struct lo_inode *inode = lo_inode(req, ino); | |
7c6b6602 | 563 | |
7387863d DDAG |
564 | if (lo_debug(req)) { |
565 | fuse_log(FUSE_LOG_DEBUG, " forget %lli %lli -%lli\n", | |
566 | (unsigned long long)ino, (unsigned long long)inode->refcount, | |
567 | (unsigned long long)nlookup); | |
568 | } | |
7c6b6602 | 569 | |
7387863d | 570 | unref_inode(lo, inode, nlookup); |
7c6b6602 DDAG |
571 | } |
572 | ||
573 | static void lo_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup) | |
574 | { | |
7387863d DDAG |
575 | lo_forget_one(req, ino, nlookup); |
576 | fuse_reply_none(req); | |
7c6b6602 DDAG |
577 | } |
578 | ||
579 | static void lo_forget_multi(fuse_req_t req, size_t count, | |
7387863d | 580 | struct fuse_forget_data *forgets) |
7c6b6602 | 581 | { |
7387863d | 582 | int i; |
7c6b6602 | 583 | |
7387863d DDAG |
584 | for (i = 0; i < count; i++) { |
585 | lo_forget_one(req, forgets[i].ino, forgets[i].nlookup); | |
586 | } | |
587 | fuse_reply_none(req); | |
7c6b6602 DDAG |
588 | } |
589 | ||
590 | static void lo_readlink(fuse_req_t req, fuse_ino_t ino) | |
591 | { | |
7387863d DDAG |
592 | char buf[PATH_MAX + 1]; |
593 | int res; | |
7c6b6602 | 594 | |
7387863d DDAG |
595 | res = readlinkat(lo_fd(req, ino), "", buf, sizeof(buf)); |
596 | if (res == -1) { | |
597 | return (void)fuse_reply_err(req, errno); | |
598 | } | |
7c6b6602 | 599 | |
7387863d DDAG |
600 | if (res == sizeof(buf)) { |
601 | return (void)fuse_reply_err(req, ENAMETOOLONG); | |
602 | } | |
7c6b6602 | 603 | |
7387863d | 604 | buf[res] = '\0'; |
7c6b6602 | 605 | |
7387863d | 606 | fuse_reply_readlink(req, buf); |
7c6b6602 DDAG |
607 | } |
608 | ||
609 | struct lo_dirp { | |
7387863d DDAG |
610 | DIR *dp; |
611 | struct dirent *entry; | |
612 | off_t offset; | |
7c6b6602 DDAG |
613 | }; |
614 | ||
615 | static struct lo_dirp *lo_dirp(struct fuse_file_info *fi) | |
616 | { | |
7387863d | 617 | return (struct lo_dirp *)(uintptr_t)fi->fh; |
7c6b6602 DDAG |
618 | } |
619 | ||
7387863d DDAG |
620 | static void lo_opendir(fuse_req_t req, fuse_ino_t ino, |
621 | struct fuse_file_info *fi) | |
7c6b6602 | 622 | { |
7387863d DDAG |
623 | int error = ENOMEM; |
624 | struct lo_data *lo = lo_data(req); | |
625 | struct lo_dirp *d; | |
626 | int fd; | |
627 | ||
628 | d = calloc(1, sizeof(struct lo_dirp)); | |
629 | if (d == NULL) { | |
630 | goto out_err; | |
631 | } | |
632 | ||
633 | fd = openat(lo_fd(req, ino), ".", O_RDONLY); | |
634 | if (fd == -1) { | |
635 | goto out_errno; | |
636 | } | |
637 | ||
638 | d->dp = fdopendir(fd); | |
639 | if (d->dp == NULL) { | |
640 | goto out_errno; | |
641 | } | |
642 | ||
643 | d->offset = 0; | |
644 | d->entry = NULL; | |
645 | ||
646 | fi->fh = (uintptr_t)d; | |
647 | if (lo->cache == CACHE_ALWAYS) { | |
648 | fi->keep_cache = 1; | |
649 | } | |
650 | fuse_reply_open(req, fi); | |
651 | return; | |
7c6b6602 DDAG |
652 | |
653 | out_errno: | |
7387863d | 654 | error = errno; |
7c6b6602 | 655 | out_err: |
7387863d DDAG |
656 | if (d) { |
657 | if (fd != -1) { | |
658 | close(fd); | |
659 | } | |
660 | free(d); | |
661 | } | |
662 | fuse_reply_err(req, error); | |
7c6b6602 DDAG |
663 | } |
664 | ||
665 | static int is_dot_or_dotdot(const char *name) | |
666 | { | |
7387863d DDAG |
667 | return name[0] == '.' && |
668 | (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')); | |
7c6b6602 DDAG |
669 | } |
670 | ||
671 | static void lo_do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, | |
7387863d | 672 | off_t offset, struct fuse_file_info *fi, int plus) |
7c6b6602 | 673 | { |
7387863d DDAG |
674 | struct lo_dirp *d = lo_dirp(fi); |
675 | char *buf; | |
676 | char *p; | |
677 | size_t rem = size; | |
678 | int err; | |
679 | ||
680 | (void)ino; | |
681 | ||
682 | buf = calloc(1, size); | |
683 | if (!buf) { | |
684 | err = ENOMEM; | |
685 | goto error; | |
686 | } | |
687 | p = buf; | |
688 | ||
689 | if (offset != d->offset) { | |
690 | seekdir(d->dp, offset); | |
691 | d->entry = NULL; | |
692 | d->offset = offset; | |
693 | } | |
694 | while (1) { | |
695 | size_t entsize; | |
696 | off_t nextoff; | |
697 | const char *name; | |
698 | ||
699 | if (!d->entry) { | |
700 | errno = 0; | |
701 | d->entry = readdir(d->dp); | |
702 | if (!d->entry) { | |
703 | if (errno) { /* Error */ | |
704 | err = errno; | |
705 | goto error; | |
706 | } else { /* End of stream */ | |
707 | break; | |
708 | } | |
709 | } | |
710 | } | |
711 | nextoff = d->entry->d_off; | |
712 | name = d->entry->d_name; | |
713 | fuse_ino_t entry_ino = 0; | |
714 | if (plus) { | |
715 | struct fuse_entry_param e; | |
716 | if (is_dot_or_dotdot(name)) { | |
717 | e = (struct fuse_entry_param){ | |
718 | .attr.st_ino = d->entry->d_ino, | |
719 | .attr.st_mode = d->entry->d_type << 12, | |
720 | }; | |
721 | } else { | |
722 | err = lo_do_lookup(req, ino, name, &e); | |
723 | if (err) { | |
724 | goto error; | |
725 | } | |
726 | entry_ino = e.ino; | |
727 | } | |
728 | ||
729 | entsize = fuse_add_direntry_plus(req, p, rem, name, &e, nextoff); | |
730 | } else { | |
731 | struct stat st = { | |
732 | .st_ino = d->entry->d_ino, | |
733 | .st_mode = d->entry->d_type << 12, | |
734 | }; | |
735 | entsize = fuse_add_direntry(req, p, rem, name, &st, nextoff); | |
736 | } | |
737 | if (entsize > rem) { | |
738 | if (entry_ino != 0) { | |
739 | lo_forget_one(req, entry_ino, 1); | |
740 | } | |
741 | break; | |
742 | } | |
743 | ||
744 | p += entsize; | |
745 | rem -= entsize; | |
746 | ||
747 | d->entry = NULL; | |
748 | d->offset = nextoff; | |
749 | } | |
7c6b6602 DDAG |
750 | |
751 | err = 0; | |
752 | error: | |
7387863d DDAG |
753 | /* |
754 | * If there's an error, we can only signal it if we haven't stored | |
755 | * any entries yet - otherwise we'd end up with wrong lookup | |
756 | * counts for the entries that are already in the buffer. So we | |
757 | * return what we've collected until that point. | |
758 | */ | |
759 | if (err && rem == size) { | |
760 | fuse_reply_err(req, err); | |
761 | } else { | |
762 | fuse_reply_buf(req, buf, size - rem); | |
763 | } | |
7c6b6602 DDAG |
764 | free(buf); |
765 | } | |
766 | ||
767 | static void lo_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, | |
7387863d | 768 | off_t offset, struct fuse_file_info *fi) |
7c6b6602 | 769 | { |
7387863d | 770 | lo_do_readdir(req, ino, size, offset, fi, 0); |
7c6b6602 DDAG |
771 | } |
772 | ||
773 | static void lo_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size, | |
7387863d | 774 | off_t offset, struct fuse_file_info *fi) |
7c6b6602 | 775 | { |
7387863d | 776 | lo_do_readdir(req, ino, size, offset, fi, 1); |
7c6b6602 DDAG |
777 | } |
778 | ||
7387863d DDAG |
779 | static void lo_releasedir(fuse_req_t req, fuse_ino_t ino, |
780 | struct fuse_file_info *fi) | |
7c6b6602 | 781 | { |
7387863d DDAG |
782 | struct lo_dirp *d = lo_dirp(fi); |
783 | (void)ino; | |
784 | closedir(d->dp); | |
785 | free(d); | |
786 | fuse_reply_err(req, 0); | |
7c6b6602 DDAG |
787 | } |
788 | ||
789 | static void lo_create(fuse_req_t req, fuse_ino_t parent, const char *name, | |
7387863d | 790 | mode_t mode, struct fuse_file_info *fi) |
7c6b6602 | 791 | { |
7387863d DDAG |
792 | int fd; |
793 | struct lo_data *lo = lo_data(req); | |
794 | struct fuse_entry_param e; | |
795 | int err; | |
796 | ||
797 | if (lo_debug(req)) { | |
798 | fuse_log(FUSE_LOG_DEBUG, "lo_create(parent=%" PRIu64 ", name=%s)\n", | |
799 | parent, name); | |
800 | } | |
801 | ||
802 | fd = openat(lo_fd(req, parent), name, (fi->flags | O_CREAT) & ~O_NOFOLLOW, | |
803 | mode); | |
804 | if (fd == -1) { | |
805 | return (void)fuse_reply_err(req, errno); | |
806 | } | |
807 | ||
808 | fi->fh = fd; | |
809 | if (lo->cache == CACHE_NEVER) { | |
810 | fi->direct_io = 1; | |
811 | } else if (lo->cache == CACHE_ALWAYS) { | |
812 | fi->keep_cache = 1; | |
813 | } | |
814 | ||
815 | err = lo_do_lookup(req, parent, name, &e); | |
816 | if (err) { | |
817 | fuse_reply_err(req, err); | |
818 | } else { | |
819 | fuse_reply_create(req, &e, fi); | |
820 | } | |
7c6b6602 DDAG |
821 | } |
822 | ||
823 | static void lo_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync, | |
7387863d | 824 | struct fuse_file_info *fi) |
7c6b6602 | 825 | { |
7387863d DDAG |
826 | int res; |
827 | int fd = dirfd(lo_dirp(fi)->dp); | |
828 | (void)ino; | |
829 | if (datasync) { | |
830 | res = fdatasync(fd); | |
831 | } else { | |
832 | res = fsync(fd); | |
833 | } | |
834 | fuse_reply_err(req, res == -1 ? errno : 0); | |
7c6b6602 DDAG |
835 | } |
836 | ||
837 | static void lo_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) | |
838 | { | |
7387863d DDAG |
839 | int fd; |
840 | char buf[64]; | |
841 | struct lo_data *lo = lo_data(req); | |
842 | ||
843 | if (lo_debug(req)) { | |
844 | fuse_log(FUSE_LOG_DEBUG, "lo_open(ino=%" PRIu64 ", flags=%d)\n", ino, | |
845 | fi->flags); | |
846 | } | |
847 | ||
848 | /* | |
849 | * With writeback cache, kernel may send read requests even | |
850 | * when userspace opened write-only | |
851 | */ | |
852 | if (lo->writeback && (fi->flags & O_ACCMODE) == O_WRONLY) { | |
853 | fi->flags &= ~O_ACCMODE; | |
854 | fi->flags |= O_RDWR; | |
855 | } | |
856 | ||
857 | /* | |
858 | * With writeback cache, O_APPEND is handled by the kernel. | |
859 | * This breaks atomicity (since the file may change in the | |
860 | * underlying filesystem, so that the kernel's idea of the | |
861 | * end of the file isn't accurate anymore). In this example, | |
862 | * we just accept that. A more rigorous filesystem may want | |
863 | * to return an error here | |
864 | */ | |
865 | if (lo->writeback && (fi->flags & O_APPEND)) { | |
866 | fi->flags &= ~O_APPEND; | |
867 | } | |
868 | ||
869 | sprintf(buf, "/proc/self/fd/%i", lo_fd(req, ino)); | |
870 | fd = open(buf, fi->flags & ~O_NOFOLLOW); | |
871 | if (fd == -1) { | |
872 | return (void)fuse_reply_err(req, errno); | |
873 | } | |
874 | ||
875 | fi->fh = fd; | |
876 | if (lo->cache == CACHE_NEVER) { | |
877 | fi->direct_io = 1; | |
878 | } else if (lo->cache == CACHE_ALWAYS) { | |
879 | fi->keep_cache = 1; | |
880 | } | |
881 | fuse_reply_open(req, fi); | |
7c6b6602 DDAG |
882 | } |
883 | ||
7387863d DDAG |
884 | static void lo_release(fuse_req_t req, fuse_ino_t ino, |
885 | struct fuse_file_info *fi) | |
7c6b6602 | 886 | { |
7387863d | 887 | (void)ino; |
7c6b6602 | 888 | |
7387863d DDAG |
889 | close(fi->fh); |
890 | fuse_reply_err(req, 0); | |
7c6b6602 DDAG |
891 | } |
892 | ||
893 | static void lo_flush(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) | |
894 | { | |
7387863d DDAG |
895 | int res; |
896 | (void)ino; | |
897 | res = close(dup(fi->fh)); | |
898 | fuse_reply_err(req, res == -1 ? errno : 0); | |
7c6b6602 DDAG |
899 | } |
900 | ||
901 | static void lo_fsync(fuse_req_t req, fuse_ino_t ino, int datasync, | |
7387863d | 902 | struct fuse_file_info *fi) |
7c6b6602 | 903 | { |
7387863d DDAG |
904 | int res; |
905 | (void)ino; | |
1b209805 VG |
906 | int fd; |
907 | char *buf; | |
908 | ||
909 | fuse_log(FUSE_LOG_DEBUG, "lo_fsync(ino=%" PRIu64 ", fi=0x%p)\n", ino, | |
910 | (void *)fi); | |
911 | ||
912 | if (!fi) { | |
913 | res = asprintf(&buf, "/proc/self/fd/%i", lo_fd(req, ino)); | |
914 | if (res == -1) { | |
915 | return (void)fuse_reply_err(req, errno); | |
916 | } | |
917 | ||
918 | fd = open(buf, O_RDWR); | |
919 | free(buf); | |
920 | if (fd == -1) { | |
921 | return (void)fuse_reply_err(req, errno); | |
922 | } | |
923 | } else { | |
924 | fd = fi->fh; | |
925 | } | |
926 | ||
7387863d | 927 | if (datasync) { |
1b209805 | 928 | res = fdatasync(fd); |
7387863d | 929 | } else { |
1b209805 VG |
930 | res = fsync(fd); |
931 | } | |
932 | if (!fi) { | |
933 | close(fd); | |
7387863d DDAG |
934 | } |
935 | fuse_reply_err(req, res == -1 ? errno : 0); | |
7c6b6602 DDAG |
936 | } |
937 | ||
7387863d DDAG |
938 | static void lo_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t offset, |
939 | struct fuse_file_info *fi) | |
7c6b6602 | 940 | { |
7387863d | 941 | struct fuse_bufvec buf = FUSE_BUFVEC_INIT(size); |
7c6b6602 | 942 | |
7387863d DDAG |
943 | if (lo_debug(req)) { |
944 | fuse_log(FUSE_LOG_DEBUG, | |
945 | "lo_read(ino=%" PRIu64 ", size=%zd, " | |
946 | "off=%lu)\n", | |
947 | ino, size, (unsigned long)offset); | |
948 | } | |
7c6b6602 | 949 | |
7387863d DDAG |
950 | buf.buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK; |
951 | buf.buf[0].fd = fi->fh; | |
952 | buf.buf[0].pos = offset; | |
7c6b6602 | 953 | |
8c3fe75e | 954 | fuse_reply_data(req, &buf); |
7c6b6602 DDAG |
955 | } |
956 | ||
957 | static void lo_write_buf(fuse_req_t req, fuse_ino_t ino, | |
7387863d DDAG |
958 | struct fuse_bufvec *in_buf, off_t off, |
959 | struct fuse_file_info *fi) | |
7c6b6602 | 960 | { |
7387863d DDAG |
961 | (void)ino; |
962 | ssize_t res; | |
963 | struct fuse_bufvec out_buf = FUSE_BUFVEC_INIT(fuse_buf_size(in_buf)); | |
964 | ||
965 | out_buf.buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK; | |
966 | out_buf.buf[0].fd = fi->fh; | |
967 | out_buf.buf[0].pos = off; | |
968 | ||
969 | if (lo_debug(req)) { | |
970 | fuse_log(FUSE_LOG_DEBUG, | |
971 | "lo_write(ino=%" PRIu64 ", size=%zd, off=%lu)\n", ino, | |
972 | out_buf.buf[0].size, (unsigned long)off); | |
973 | } | |
974 | ||
8c3fe75e | 975 | res = fuse_buf_copy(&out_buf, in_buf); |
7387863d DDAG |
976 | if (res < 0) { |
977 | fuse_reply_err(req, -res); | |
978 | } else { | |
979 | fuse_reply_write(req, (size_t)res); | |
980 | } | |
7c6b6602 DDAG |
981 | } |
982 | ||
983 | static void lo_statfs(fuse_req_t req, fuse_ino_t ino) | |
984 | { | |
7387863d DDAG |
985 | int res; |
986 | struct statvfs stbuf; | |
987 | ||
988 | res = fstatvfs(lo_fd(req, ino), &stbuf); | |
989 | if (res == -1) { | |
990 | fuse_reply_err(req, errno); | |
991 | } else { | |
992 | fuse_reply_statfs(req, &stbuf); | |
993 | } | |
7c6b6602 DDAG |
994 | } |
995 | ||
7387863d DDAG |
996 | static void lo_fallocate(fuse_req_t req, fuse_ino_t ino, int mode, off_t offset, |
997 | off_t length, struct fuse_file_info *fi) | |
7c6b6602 | 998 | { |
7387863d DDAG |
999 | int err = EOPNOTSUPP; |
1000 | (void)ino; | |
7c6b6602 | 1001 | |
9776457c | 1002 | #ifdef CONFIG_FALLOCATE |
7387863d DDAG |
1003 | err = fallocate(fi->fh, mode, offset, length); |
1004 | if (err < 0) { | |
1005 | err = errno; | |
1006 | } | |
7c6b6602 | 1007 | |
9776457c | 1008 | #elif defined(CONFIG_POSIX_FALLOCATE) |
7387863d DDAG |
1009 | if (mode) { |
1010 | fuse_reply_err(req, EOPNOTSUPP); | |
1011 | return; | |
1012 | } | |
7c6b6602 | 1013 | |
7387863d | 1014 | err = posix_fallocate(fi->fh, offset, length); |
7c6b6602 DDAG |
1015 | #endif |
1016 | ||
7387863d | 1017 | fuse_reply_err(req, err); |
7c6b6602 DDAG |
1018 | } |
1019 | ||
1020 | static void lo_flock(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, | |
7387863d | 1021 | int op) |
7c6b6602 | 1022 | { |
7387863d DDAG |
1023 | int res; |
1024 | (void)ino; | |
7c6b6602 | 1025 | |
7387863d | 1026 | res = flock(fi->fh, op); |
7c6b6602 | 1027 | |
7387863d | 1028 | fuse_reply_err(req, res == -1 ? errno : 0); |
7c6b6602 DDAG |
1029 | } |
1030 | ||
1031 | static void lo_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name, | |
7387863d | 1032 | size_t size) |
7c6b6602 | 1033 | { |
7387863d DDAG |
1034 | char *value = NULL; |
1035 | char procname[64]; | |
1036 | struct lo_inode *inode = lo_inode(req, ino); | |
1037 | ssize_t ret; | |
1038 | int saverr; | |
1039 | ||
1040 | saverr = ENOSYS; | |
1041 | if (!lo_data(req)->xattr) { | |
1042 | goto out; | |
1043 | } | |
1044 | ||
1045 | if (lo_debug(req)) { | |
1046 | fuse_log(FUSE_LOG_DEBUG, | |
1047 | "lo_getxattr(ino=%" PRIu64 ", name=%s size=%zd)\n", ino, name, | |
1048 | size); | |
1049 | } | |
1050 | ||
1051 | if (inode->is_symlink) { | |
1052 | /* Sorry, no race free way to getxattr on symlink. */ | |
1053 | saverr = EPERM; | |
1054 | goto out; | |
1055 | } | |
1056 | ||
1057 | sprintf(procname, "/proc/self/fd/%i", inode->fd); | |
1058 | ||
1059 | if (size) { | |
1060 | value = malloc(size); | |
1061 | if (!value) { | |
1062 | goto out_err; | |
1063 | } | |
1064 | ||
1065 | ret = getxattr(procname, name, value, size); | |
1066 | if (ret == -1) { | |
1067 | goto out_err; | |
1068 | } | |
1069 | saverr = 0; | |
1070 | if (ret == 0) { | |
1071 | goto out; | |
1072 | } | |
1073 | ||
1074 | fuse_reply_buf(req, value, ret); | |
1075 | } else { | |
1076 | ret = getxattr(procname, name, NULL, 0); | |
1077 | if (ret == -1) { | |
1078 | goto out_err; | |
1079 | } | |
1080 | ||
1081 | fuse_reply_xattr(req, ret); | |
1082 | } | |
7c6b6602 | 1083 | out_free: |
7387863d DDAG |
1084 | free(value); |
1085 | return; | |
7c6b6602 DDAG |
1086 | |
1087 | out_err: | |
7387863d | 1088 | saverr = errno; |
7c6b6602 | 1089 | out: |
7387863d DDAG |
1090 | fuse_reply_err(req, saverr); |
1091 | goto out_free; | |
7c6b6602 DDAG |
1092 | } |
1093 | ||
1094 | static void lo_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size) | |
1095 | { | |
7387863d DDAG |
1096 | char *value = NULL; |
1097 | char procname[64]; | |
1098 | struct lo_inode *inode = lo_inode(req, ino); | |
1099 | ssize_t ret; | |
1100 | int saverr; | |
1101 | ||
1102 | saverr = ENOSYS; | |
1103 | if (!lo_data(req)->xattr) { | |
1104 | goto out; | |
1105 | } | |
1106 | ||
1107 | if (lo_debug(req)) { | |
1108 | fuse_log(FUSE_LOG_DEBUG, "lo_listxattr(ino=%" PRIu64 ", size=%zd)\n", | |
1109 | ino, size); | |
1110 | } | |
1111 | ||
1112 | if (inode->is_symlink) { | |
1113 | /* Sorry, no race free way to listxattr on symlink. */ | |
1114 | saverr = EPERM; | |
1115 | goto out; | |
1116 | } | |
1117 | ||
1118 | sprintf(procname, "/proc/self/fd/%i", inode->fd); | |
1119 | ||
1120 | if (size) { | |
1121 | value = malloc(size); | |
1122 | if (!value) { | |
1123 | goto out_err; | |
1124 | } | |
1125 | ||
1126 | ret = listxattr(procname, value, size); | |
1127 | if (ret == -1) { | |
1128 | goto out_err; | |
1129 | } | |
1130 | saverr = 0; | |
1131 | if (ret == 0) { | |
1132 | goto out; | |
1133 | } | |
1134 | ||
1135 | fuse_reply_buf(req, value, ret); | |
1136 | } else { | |
1137 | ret = listxattr(procname, NULL, 0); | |
1138 | if (ret == -1) { | |
1139 | goto out_err; | |
1140 | } | |
1141 | ||
1142 | fuse_reply_xattr(req, ret); | |
1143 | } | |
7c6b6602 | 1144 | out_free: |
7387863d DDAG |
1145 | free(value); |
1146 | return; | |
7c6b6602 DDAG |
1147 | |
1148 | out_err: | |
7387863d | 1149 | saverr = errno; |
7c6b6602 | 1150 | out: |
7387863d DDAG |
1151 | fuse_reply_err(req, saverr); |
1152 | goto out_free; | |
7c6b6602 DDAG |
1153 | } |
1154 | ||
1155 | static void lo_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name, | |
7387863d | 1156 | const char *value, size_t size, int flags) |
7c6b6602 | 1157 | { |
7387863d DDAG |
1158 | char procname[64]; |
1159 | struct lo_inode *inode = lo_inode(req, ino); | |
1160 | ssize_t ret; | |
1161 | int saverr; | |
7c6b6602 | 1162 | |
7387863d DDAG |
1163 | saverr = ENOSYS; |
1164 | if (!lo_data(req)->xattr) { | |
1165 | goto out; | |
1166 | } | |
7c6b6602 | 1167 | |
7387863d DDAG |
1168 | if (lo_debug(req)) { |
1169 | fuse_log(FUSE_LOG_DEBUG, | |
1170 | "lo_setxattr(ino=%" PRIu64 ", name=%s value=%s size=%zd)\n", | |
1171 | ino, name, value, size); | |
1172 | } | |
7c6b6602 | 1173 | |
7387863d DDAG |
1174 | if (inode->is_symlink) { |
1175 | /* Sorry, no race free way to setxattr on symlink. */ | |
1176 | saverr = EPERM; | |
1177 | goto out; | |
1178 | } | |
7c6b6602 | 1179 | |
7387863d | 1180 | sprintf(procname, "/proc/self/fd/%i", inode->fd); |
7c6b6602 | 1181 | |
7387863d DDAG |
1182 | ret = setxattr(procname, name, value, size, flags); |
1183 | saverr = ret == -1 ? errno : 0; | |
7c6b6602 DDAG |
1184 | |
1185 | out: | |
7387863d | 1186 | fuse_reply_err(req, saverr); |
7c6b6602 DDAG |
1187 | } |
1188 | ||
1189 | static void lo_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name) | |
1190 | { | |
7387863d DDAG |
1191 | char procname[64]; |
1192 | struct lo_inode *inode = lo_inode(req, ino); | |
1193 | ssize_t ret; | |
1194 | int saverr; | |
7c6b6602 | 1195 | |
7387863d DDAG |
1196 | saverr = ENOSYS; |
1197 | if (!lo_data(req)->xattr) { | |
1198 | goto out; | |
1199 | } | |
7c6b6602 | 1200 | |
7387863d DDAG |
1201 | if (lo_debug(req)) { |
1202 | fuse_log(FUSE_LOG_DEBUG, "lo_removexattr(ino=%" PRIu64 ", name=%s)\n", | |
1203 | ino, name); | |
1204 | } | |
7c6b6602 | 1205 | |
7387863d DDAG |
1206 | if (inode->is_symlink) { |
1207 | /* Sorry, no race free way to setxattr on symlink. */ | |
1208 | saverr = EPERM; | |
1209 | goto out; | |
1210 | } | |
7c6b6602 | 1211 | |
7387863d | 1212 | sprintf(procname, "/proc/self/fd/%i", inode->fd); |
7c6b6602 | 1213 | |
7387863d DDAG |
1214 | ret = removexattr(procname, name); |
1215 | saverr = ret == -1 ? errno : 0; | |
7c6b6602 DDAG |
1216 | |
1217 | out: | |
7387863d | 1218 | fuse_reply_err(req, saverr); |
7c6b6602 DDAG |
1219 | } |
1220 | ||
1221 | #ifdef HAVE_COPY_FILE_RANGE | |
1222 | static void lo_copy_file_range(fuse_req_t req, fuse_ino_t ino_in, off_t off_in, | |
7387863d DDAG |
1223 | struct fuse_file_info *fi_in, fuse_ino_t ino_out, |
1224 | off_t off_out, struct fuse_file_info *fi_out, | |
1225 | size_t len, int flags) | |
7c6b6602 | 1226 | { |
7387863d DDAG |
1227 | ssize_t res; |
1228 | ||
1229 | if (lo_debug(req)) | |
1230 | fuse_log(FUSE_LOG_DEBUG, | |
1231 | "lo_copy_file_range(ino=%" PRIu64 "/fd=%lu, " | |
1232 | "off=%lu, ino=%" PRIu64 "/fd=%lu, " | |
1233 | "off=%lu, size=%zd, flags=0x%x)\n", | |
1234 | ino_in, fi_in->fh, off_in, ino_out, fi_out->fh, off_out, len, | |
1235 | flags); | |
1236 | ||
1237 | res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len, flags); | |
1238 | if (res < 0) { | |
1239 | fuse_reply_err(req, -errno); | |
1240 | } else { | |
1241 | fuse_reply_write(req, res); | |
1242 | } | |
7c6b6602 DDAG |
1243 | } |
1244 | #endif | |
1245 | ||
1246 | static void lo_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence, | |
7387863d | 1247 | struct fuse_file_info *fi) |
7c6b6602 | 1248 | { |
7387863d DDAG |
1249 | off_t res; |
1250 | ||
1251 | (void)ino; | |
1252 | res = lseek(fi->fh, off, whence); | |
1253 | if (res != -1) { | |
1254 | fuse_reply_lseek(req, res); | |
1255 | } else { | |
1256 | fuse_reply_err(req, errno); | |
1257 | } | |
7c6b6602 DDAG |
1258 | } |
1259 | ||
1260 | static struct fuse_lowlevel_ops lo_oper = { | |
7387863d DDAG |
1261 | .init = lo_init, |
1262 | .lookup = lo_lookup, | |
1263 | .mkdir = lo_mkdir, | |
1264 | .mknod = lo_mknod, | |
1265 | .symlink = lo_symlink, | |
1266 | .link = lo_link, | |
1267 | .unlink = lo_unlink, | |
1268 | .rmdir = lo_rmdir, | |
1269 | .rename = lo_rename, | |
1270 | .forget = lo_forget, | |
1271 | .forget_multi = lo_forget_multi, | |
1272 | .getattr = lo_getattr, | |
1273 | .setattr = lo_setattr, | |
1274 | .readlink = lo_readlink, | |
1275 | .opendir = lo_opendir, | |
1276 | .readdir = lo_readdir, | |
1277 | .readdirplus = lo_readdirplus, | |
1278 | .releasedir = lo_releasedir, | |
1279 | .fsyncdir = lo_fsyncdir, | |
1280 | .create = lo_create, | |
1281 | .open = lo_open, | |
1282 | .release = lo_release, | |
1283 | .flush = lo_flush, | |
1284 | .fsync = lo_fsync, | |
1285 | .read = lo_read, | |
1286 | .write_buf = lo_write_buf, | |
1287 | .statfs = lo_statfs, | |
1288 | .fallocate = lo_fallocate, | |
1289 | .flock = lo_flock, | |
1290 | .getxattr = lo_getxattr, | |
1291 | .listxattr = lo_listxattr, | |
1292 | .setxattr = lo_setxattr, | |
1293 | .removexattr = lo_removexattr, | |
7c6b6602 | 1294 | #ifdef HAVE_COPY_FILE_RANGE |
7387863d | 1295 | .copy_file_range = lo_copy_file_range, |
7c6b6602 | 1296 | #endif |
7387863d | 1297 | .lseek = lo_lseek, |
7c6b6602 DDAG |
1298 | }; |
1299 | ||
1300 | int main(int argc, char *argv[]) | |
1301 | { | |
7387863d DDAG |
1302 | struct fuse_args args = FUSE_ARGS_INIT(argc, argv); |
1303 | struct fuse_session *se; | |
1304 | struct fuse_cmdline_opts opts; | |
1305 | struct lo_data lo = { .debug = 0, .writeback = 0 }; | |
1306 | int ret = -1; | |
1307 | ||
1308 | /* Don't mask creation mode, kernel already did that */ | |
1309 | umask(0); | |
1310 | ||
1311 | pthread_mutex_init(&lo.mutex, NULL); | |
1312 | lo.root.next = lo.root.prev = &lo.root; | |
1313 | lo.root.fd = -1; | |
1314 | lo.cache = CACHE_NORMAL; | |
1315 | ||
1316 | if (fuse_parse_cmdline(&args, &opts) != 0) { | |
1317 | return 1; | |
1318 | } | |
1319 | if (opts.show_help) { | |
67aab022 | 1320 | printf("usage: %s [options]\n\n", argv[0]); |
7387863d | 1321 | fuse_cmdline_help(); |
4ff075f7 | 1322 | printf(" -o source=PATH shared directory tree\n"); |
7387863d DDAG |
1323 | fuse_lowlevel_help(); |
1324 | ret = 0; | |
1325 | goto err_out1; | |
1326 | } else if (opts.show_version) { | |
1327 | fuse_lowlevel_version(); | |
1328 | ret = 0; | |
1329 | goto err_out1; | |
1330 | } | |
1331 | ||
7387863d DDAG |
1332 | if (fuse_opt_parse(&args, &lo, lo_opts, NULL) == -1) { |
1333 | return 1; | |
1334 | } | |
1335 | ||
1336 | lo.debug = opts.debug; | |
1337 | lo.root.refcount = 2; | |
1338 | if (lo.source) { | |
1339 | struct stat stat; | |
1340 | int res; | |
1341 | ||
1342 | res = lstat(lo.source, &stat); | |
1343 | if (res == -1) { | |
1344 | fuse_log(FUSE_LOG_ERR, "failed to stat source (\"%s\"): %m\n", | |
1345 | lo.source); | |
1346 | exit(1); | |
1347 | } | |
1348 | if (!S_ISDIR(stat.st_mode)) { | |
1349 | fuse_log(FUSE_LOG_ERR, "source is not a directory\n"); | |
1350 | exit(1); | |
1351 | } | |
1352 | ||
1353 | } else { | |
1354 | lo.source = "/"; | |
1355 | } | |
1356 | lo.root.is_symlink = false; | |
1357 | if (!lo.timeout_set) { | |
1358 | switch (lo.cache) { | |
1359 | case CACHE_NEVER: | |
1360 | lo.timeout = 0.0; | |
1361 | break; | |
1362 | ||
1363 | case CACHE_NORMAL: | |
1364 | lo.timeout = 1.0; | |
1365 | break; | |
1366 | ||
1367 | case CACHE_ALWAYS: | |
1368 | lo.timeout = 86400.0; | |
1369 | break; | |
1370 | } | |
1371 | } else if (lo.timeout < 0) { | |
1372 | fuse_log(FUSE_LOG_ERR, "timeout is negative (%lf)\n", lo.timeout); | |
1373 | exit(1); | |
1374 | } | |
1375 | ||
1376 | lo.root.fd = open(lo.source, O_PATH); | |
1377 | if (lo.root.fd == -1) { | |
1378 | fuse_log(FUSE_LOG_ERR, "open(\"%s\", O_PATH): %m\n", lo.source); | |
1379 | exit(1); | |
1380 | } | |
1381 | ||
1382 | se = fuse_session_new(&args, &lo_oper, sizeof(lo_oper), &lo); | |
1383 | if (se == NULL) { | |
1384 | goto err_out1; | |
1385 | } | |
1386 | ||
1387 | if (fuse_set_signal_handlers(se) != 0) { | |
1388 | goto err_out2; | |
1389 | } | |
1390 | ||
67aab022 | 1391 | if (fuse_session_mount(se) != 0) { |
7387863d DDAG |
1392 | goto err_out3; |
1393 | } | |
1394 | ||
1395 | fuse_daemonize(opts.foreground); | |
1396 | ||
1397 | /* Block until ctrl+c or fusermount -u */ | |
1398 | if (opts.singlethread) { | |
1399 | ret = fuse_session_loop(se); | |
1400 | } else { | |
1401 | ret = fuse_session_loop_mt(se, opts.clone_fd); | |
1402 | } | |
1403 | ||
1404 | fuse_session_unmount(se); | |
7c6b6602 | 1405 | err_out3: |
7387863d | 1406 | fuse_remove_signal_handlers(se); |
7c6b6602 | 1407 | err_out2: |
7387863d | 1408 | fuse_session_destroy(se); |
7c6b6602 | 1409 | err_out1: |
7387863d | 1410 | fuse_opt_free_args(&args); |
7c6b6602 | 1411 | |
7387863d DDAG |
1412 | if (lo.root.fd >= 0) { |
1413 | close(lo.root.fd); | |
1414 | } | |
7c6b6602 | 1415 | |
7387863d | 1416 | return ret ? 1 : 0; |
7c6b6602 | 1417 | } |