libfuse: fuse-3.9.4/example/passthrough_ll.c Source File

libfuse

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