pax_global_header00006660000000000000000000000064117170000550014506gustar00rootroot0000000000000052 comment=f5bfe358e20d51fbb25d5bbd30b528f1d508439f fuse-posixovl-1.2.20120215+gitf5bfe35/000077500000000000000000000000001171700005500167255ustar00rootroot00000000000000fuse-posixovl-1.2.20120215+gitf5bfe35/.gitignore000066400000000000000000000002421171700005500207130ustar00rootroot00000000000000*.o .deps Makefile Makefile.in /autom4te.cache /aclocal.m4 /compile /config.* /configure /depcomp /install-sh /lntest /missing /mount.posixovl /socket /stamp-h1 fuse-posixovl-1.2.20120215+gitf5bfe35/INSTALL.txt000066400000000000000000000005611171700005500205760ustar00rootroot00000000000000 Installation when using a GIT snapshot ========================================== Requires: autoconf, automake # (plus anything below) Commands: ./autogen.sh; # (plus anything below) Installation when using a tarball ===================================== Requires: fuse-devel >= 2.6.5 libattr-devel Commands: mkdir obj; cd obj; ../configure; make; fuse-posixovl-1.2.20120215+gitf5bfe35/Makefile.am000066400000000000000000000013641171700005500207650ustar00rootroot00000000000000# -*- Makefile -*- AM_CPPFLAGS = ${regular_CPPFLAGS} AM_CFLAGS = $(regular_CFLAGS) $(libfuse_CFLAGS) man_MANS = posixovl.1 sbin_PROGRAMS = mount.posixovl EXTRA_PROGRAMS = socket lntest mount_posixovl_SOURCES = posixovl.c mount_posixovl_LDADD = $(libfuse_LIBS) .PHONY: tarball tarball: rm -Rf /tmp/${PACKAGE_TARNAME}-${PACKAGE_VERSION}; pushd ${top_srcdir} && git-archive --prefix=${PACKAGE_TARNAME}-${PACKAGE_VERSION}/ HEAD | tar -C /tmp -x && popd; pushd /tmp/${PACKAGE_TARNAME}-${PACKAGE_VERSION} && ./autogen.sh && popd; tar -C /tmp -cjf ${PACKAGE_TARNAME}-${PACKAGE_VERSION}.tar.bz2 --owner=root --group=root ${PACKAGE_TARNAME}-${PACKAGE_VERSION}/; rm -Rf /tmp/${PACKAGE_TARNAME}-${PACKAGE_VERSION}; fuse-posixovl-1.2.20120215+gitf5bfe35/autogen.sh000077500000000000000000000000651171700005500207270ustar00rootroot00000000000000#!/bin/bash autoreconf -fi; rm -Rf autom4te*.cache; fuse-posixovl-1.2.20120215+gitf5bfe35/configure.ac000066400000000000000000000030221171700005500212100ustar00rootroot00000000000000AC_INIT([posixovl], [1.2]) AC_PREREQ([2.61]) AC_CONFIG_HEADERS([config.h]) AC_PROG_INSTALL AM_INIT_AUTOMAKE([-Wall foreign subdir-objects]) AC_PROG_CC AM_PROG_CC_C_O regular_CPPFLAGS="-D_FORTIFY_SOURCE=2 -D_LARGEFILE_SOURCE=1 -D_LARGE_FILES \ -D_FILE_OFFSET_BITS=64 -D_REENTRANT"; regular_CFLAGS="-std=c99 -Wall -Waggregate-return \ -Wmissing-declarations -Wmissing-prototypes -Wredundant-decls \ -Wshadow -Wstrict-prototypes -Winline -pipe" AC_SUBST([regular_CPPFLAGS]) AC_SUBST([regular_CFLAGS]) AC_CHECK_HEADER(attr/xattr.h, [], AC_MSG_ERROR([libattr-devel not installed])) # FUSE >= 2.7.0 got subtype name support. # 2.6.5 does not, but has a similar thing. PKG_CHECK_MODULES([libfuse], [fuse >= 2.7.0], [], [ PKG_CHECK_MODULES([libfuse], [fuse >= 2.6.5], [ AC_DEFINE([HAVE_JUST_FUSE_2_6_5], [1], [Define if you have a FUSE version >= 2.6.5 but < 2.7.0]) ]) ]) # Minium requirements for utimensat(): Linux 2.6.22-rc1 and Glibc 2.6 AC_MSG_CHECKING([for utimensat()]) # # We need to check for __NR_utimensat too, because Glibc would # offer a wrapper otherwise. # AC_LINK_IFELSE([AC_LANG_SOURCE([ #define _ATFILE_SOURCE 1 #include #include #include static unsigned int x = __NR_utimensat; static int (*y)(int, const char *, const struct timespec *, int) = utimensat; int main(void) { return 0; } ])], [ AC_DEFINE([HAVE_UTIMENSAT], [1], [Define if utimensat() function is available]) AC_MSG_RESULT([yes]) ], [ AC_MSG_RESULT([no]) ]) AC_CONFIG_FILES([Makefile]) AC_OUTPUT fuse-posixovl-1.2.20120215+gitf5bfe35/fs-test000077500000000000000000000026171171700005500202460ustar00rootroot00000000000000#!/bin/bash -x function announce() { set +x; if [ -t 0 ]; then echo -en "\e[1;37;44m"; fi; echo -en "$1"; if [ -t 0 ]; then echo -e "\e[0m"; fi; set -x; } # # So that the root tests can also be run when - for some reason - # you do not have UID 0. (`do_root_tests=1 ./fs-test`) # if [ -z "$do_root_tests" ]; then [ "`id -u`" -eq 0 ]; do_root_tests=$[!$?]; fi; set -e; trap "echo failed" EXIT; announce "mass links (trigger fuse cache artifact)"; echo hello world >w0; ln w0 w1; ln w1 w2; ln w1 w3; ln w1 w4; ln w1 w5; ln w1 w6; ln w5 w7; ln w5 w8; ls -li; usleep 600000; ls -li; rm -f w[012345678]; announce "FIFO hardlink"; mkfifo h1; ln h1 h2; # -- FUSE cheats us -- test does not work #result=$( # echo -n "test1" >h1; # echo -n "test2" >h2; # head -n1 h2; #); #[ "$result" == "test1test2" -o "$result" == "test2test1" ]; announce "hardlink FIFO deletion"; rm -f h1; [ -e h2 ]; rm -f h2; announce "creating tmp files"; echo f1 >f1; echo f2 >f2; echo f3 >f3; announce "linking"; ln -sf f1 q1; ln -f f2 q2; announce "verifying linked data ... "; cat f1 | grep -q f1; cat f2 | grep -q f2; announce "truncate"; >f2; [ ! -s f2 ]; if [ $do_root_tests -eq 1 ]; then announce "changing ownerships and permissions"; chown 0:0 f1 f2 f3; chmod ago+x f1 f2 f3; chmod ago-x f1 f2 f3; announce "special device creation"; mknod null c 1 3; rm null; fi; rm f1 f2 f3 q1 q2; trap "" EXIT; announce "All done"; fuse-posixovl-1.2.20120215+gitf5bfe35/lntest.c000066400000000000000000000017071171700005500204070ustar00rootroot00000000000000 #undef NDEBUG #include #include #include #include #include #include #include char *do_mmap(char *path, int *fd, off_t *size) { int err; struct stat st; char *p; *fd = open(path, O_RDWR); assert(*fd >= 0); err = fstat(*fd, &st); assert(!err); *size = st.st_size; // fuse doesn't support writable + shared p = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, *fd, 0); assert(p != MAP_FAILED); return p; } int main(int argc, char *argv[]) { int err, fd1, fd2; char *p, *q; off_t size; ssize_t ssz; err = link(argv[1], argv[2]); assert(!err); p = do_mmap(argv[1], &fd1, &size); q = do_mmap(argv[2], &fd2, &size); assert(!memcmp(p, q, size)); ssz = write(fd1, "a", 1); assert(ssz == 1); munmap(q, size); // close(fd2); system("cat f2"); assert(!memcmp(p, q, size)); ssz = write(fd2, "b", 1); assert(ssz == 1); assert(!memcmp(p, q, size)); return 0; } fuse-posixovl-1.2.20120215+gitf5bfe35/posixovl.1000066400000000000000000000061401171700005500206730ustar00rootroot00000000000000.TH posixovl 1 "2010-12-07" "posixovl" "posixovl" .SH Name .PP posixovl \(em The POSIX Overlay Filesystem .SH Syntax .PP \fBmount.posixovl\fP [\fB\-F\fP] [\fB\-S\fP \fIsource_dir\fP] \fItarget_dir\fP [\fB\-\-\fP \fIfuse_opts\fP] .SH Description .PP posixovl provides a filesystem view that supports various POSIX operations while using an otherwise incapable lower filesystem. Filesystems of various degrees of POSIXness can be utilitzed. VFAT is a common denominator when it comes to cross-compatibility, though NTFS \(em its features are unused in Linux \(em would be another possibility. Secondly, potent native POSIX-style filesystems can also be used, though the actual use of doing that remains to be discovered. .SH Options .PP If no source directory is given, the target directory specifies both source and target (mountpoint), yielding an "overmount". .TP \fB\-F\fP Disable permission and ownership checks on the lower filesystem. Normally these would be used to check for POSIX filesystems mounted inside a non-POSIX tree. (For example, where /vfat is vfat, and /vfat/xfs is a POSIX-behaving filesystem.) .SH Supported operations .PP posixovl will emulate the following calls if the lower filesystem is incapable of supporting these operations: .IP \(bu 4 chmod .IP \(bu 4 chown .IP \(bu 4 hard links .IP \(bu 4 mkfifo .IP \(bu 4 mknod .IP \(bu 4 symbolic links .PP The following currently only work in passthrough mode (when using a lower filesystem that already supports these): .IP \(bu 4 Linux ACLs/xattrs .PP Missing suppot: .IP \(bu 4 Case-sensitivity .SH Notes .PP Using posixovl on an already POSIX-behaving filesystem (e.g. XFS) incurs some issues, since detecting whether a path is POSIX behaving or not is difficult. Hence, the following decision was made: .IP \(bu 4 permissions will be set to the default permissions (see below) unless a HCB (hidden control block, the metadata files for posixovl) is found that can override these .IP \(bu 4 all lower-level files will be operated on/created with the user who inititated the mount .PP If no HCB exists for a file or directory, the default permissions are 644 and 755, respectively. The owner and group of the inode will be the owner/group of the real file. .PP Each non-regular, non-directory virtual file will have a zero-size real file. (Simplifies handling, and makes it apprarent the object exists when using other OS.) .PP `df` will show: .nf $ df -Tah Filesystem Type Size Used Avail Use% Mounted on /dev/hda5 vfat 5.9G 2.1G 3.9G 35% /windows/D posix-overlay(/windows/D) fuse.posixovl 5.9G 2.1G 3.9G 35% /windows/D .fi .PP In general, posixovl does not handle case-insensitivity of the underlying filesystem (in case of vfat, for example). If you create a file "X0" on vfat, it is usually lowercased to "x0", which may break some software, namely X.org. In order to make vfat behave more POSIX-like, the following mount options are recommended: .PP $ mount -t vfat /dev/hda1 /windows/D -o check=s,shortname=mixed .SH Authors .PP posixovl and this manpage were written by Jan Engelhardt. .PP Development of posixovl was sponsored by Slax (http://www.slax.org/). fuse-posixovl-1.2.20120215+gitf5bfe35/posixovl.c000066400000000000000000001337151171700005500207660ustar00rootroot00000000000000/* * posixovl - POSIX overlay filesystem * Copyright © Jan Engelhardt , 2007 - 2008 * * Development of posixovl sponsored by Slax (http://www.slax.org/) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. */ #define _ATFILE_SOURCE 1 #define _GNU_SOURCE 1 #define FUSE_USE_VERSION 26 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #ifndef S_IRUGO # define S_IRUGO (S_IRUSR | S_IRGRP | S_IROTH) # define S_IWUGO (S_IWUSR | S_IWGRP | S_IWOTH) # define S_IXUGO (S_IXUSR | S_IXGRP | S_IXOTH) #endif #ifndef S_IRWXUGO # define S_IRWXUGO (S_IRUGO | S_IWUGO | S_IXUGO) #endif #define S_IFHARDLNK (S_IFLNK | S_ISVTX) #define S_IFSOFTLNK (S_IFLNK | S_IRWXUGO) #define S_ISHARDLNK(mode) ((mode) == S_IFHARDLNK) #define S_ISSOFTLNK(mode) ((mode) == S_IFSOFTLNK) #define static_cast(type, x) ((type)(x)) /* GLIBC device number handling. From ttyrpld. */ #define COMPAT_MAJOR(dev) \ static_cast(unsigned long, ((dev) & 0xFFF00) >> 8) #define COMPAT_MINOR(dev) \ static_cast(unsigned long, ((dev) & 0xFF) | \ (((dev) & 0xFFF00000) >> 12)) #define COMPAT_MKDEV(major, minor) \ static_cast(unsigned long, ((minor) & 0xFF) | \ (((minor) & 0xFFF00) << 12) | (((major) & 0xFFF) << 8)) /* Buggers */ #ifndef NDEBUG # define should_never_happen() \ do { \ fprintf(stderr, "CRITICAL: Should NEVER happen! %s:%u\n", \ __FILE__, __LINE__); \ abort(); \ } while (false) # define should_not_happen() \ do { \ fprintf(stderr, "WARNING: Should not happen! %s:%u\n", \ __FILE__, __LINE__); \ } while (false) #else # define should_never_happen() do {} while (false) # define should_not_happen() do {} while (false) #endif #define hcb_got_busted(path) \ fprintf(stderr, "HCB %s got busted\n", (path)) /* Definitions */ #define HCB_PREFIX1 ".pxovl" /* extra vfat idiocy */ #define HCB_PREFIX ".pxovl." #define HCB_PREFIX_LEN (sizeof(HCB_PREFIX) - 1) #define HL_DNODE_PREFIX ".pxovd." #define HL_DNODE_PREFIX_LEN (sizeof(HL_DNODE_PREFIX) - 1) #define HL_INODE_PREFIX ".pxovn." #define HL_INODE_PREFIX_LEN (sizeof(HL_INODE_PREFIX) - 1) enum { /* extra errno codes */ ENOENT_HCB = 4096, }; struct ll_hcb { char buf[PATH_MAX], new_target[PATH_MAX]; const char *target; mode_t mode; nlink_t nlink; uid_t uid; gid_t gid; dev_t rdev; size_t size; }; struct hcb { char path[PATH_MAX]; struct ll_hcb ll; struct stat sb; int fd; }; /* Global */ static mode_t default_mode = S_IRUGO | S_IWUSR; static unsigned int assume_vfat, single_threaded; static const char *root_dir; static int root_fd; static pthread_mutex_t posixovl_protect = PTHREAD_MUTEX_INITIALIZER; static inline int retcode_int(int code) { return (code >= 0) ? code : -errno; } static inline ssize_t retcode_ssize(ssize_t code) { return (code >= 0) ? code : -errno; } static int lock_read(int fd) { static const struct flock fl = { .l_type = F_RDLCK, .l_whence = SEEK_SET, .l_start = 0, .l_len = 0, }; if (single_threaded) return 0; return fcntl(fd, F_SETLKW, &fl); } static int lock_write(int fd) { static const struct flock fl = { .l_type = F_WRLCK, .l_whence = SEEK_SET, .l_start = 0, .l_len = 0, }; if (single_threaded) return 0; return fcntl(fd, F_SETLK, &fl); } /* * at - turn a virtual path into a relative (to root_fd) one */ static __attribute__((pure)) const char *at(const char *in) { if (*in != '/') should_never_happen(); if (in[1] == '\0') return "."; return in + 1; } static char *strlcpy(char *dest, const char *src, size_t n) { strncpy(dest, src, n); dest[n-1] = '\0'; return dest; } /* * __hl_dtoi - build the HL.I-node path from the HL.D-node path */ static void __hl_dtoi(char *dest, size_t destsize, const char *src) { char *last, *p; strlcpy(dest, src, destsize); last = dest; while ((p = strstr(last, "/" HL_DNODE_PREFIX)) != NULL) last = p + 1; memcpy(last, HL_INODE_PREFIX, HL_INODE_PREFIX_LEN); } #define hl_dtoi(dest, src) __hl_dtoi((dest), sizeof(dest), (src)) /* * __real_to_hcb - build the hidden control block (HCB) path from a real path */ static int __real_to_hcb(char *dest, size_t destsize, const char *src) { const char *directory_part = src; const char *filename_part; struct stat sb; int ret; /* * For directories, the HCB must be stored within the directory, * not "alongside" it. This is because there is no possible parallel * entry (on the same filesystem) for the root directory. */ ret = fstatat(root_fd, at(src), &sb, AT_SYMLINK_NOFOLLOW); if (ret == 0 && S_ISDIR(sb.st_mode)) { if (strcmp(src, "/") == 0) /* Make sure there is only one slash */ ret = snprintf(dest, destsize, "/" HCB_PREFIX); else ret = snprintf(dest, destsize, "%s/" HCB_PREFIX, src); if (ret > destsize) return -ENAMETOOLONG; return 0; } filename_part = strrchr(src, '/'); if (filename_part++ == NULL) should_never_happen(); if (strncmp(filename_part, HL_DNODE_PREFIX, HL_DNODE_PREFIX_LEN) == 0) ret = snprintf(dest, destsize, "%.*s" HL_INODE_PREFIX "%s", (int)(filename_part - directory_part), directory_part, filename_part + HL_DNODE_PREFIX_LEN); else ret = snprintf(dest, destsize, "%.*s" HCB_PREFIX "%s", (int)(filename_part - directory_part), directory_part, filename_part); if (ret > destsize) return -ENAMETOOLONG; return 0; } #define real_to_hcb(dest, src) __real_to_hcb((dest), sizeof(dest), (src)) /* * ll_hcb_read - lowlevel read of HCB * @path: path to HCB (used for debug and unlink) * @info: destination structure * @fd: fd to read from */ static int ll_hcb_read(const char *path, struct ll_hcb *info, int fd) { const char *s_mode, *s_nlink, *s_uid, *s_gid, *s_rdev; char *toul_ptr = NULL; ssize_t ret; if ((ret = read(fd, info->buf, sizeof(info->buf) - 1)) < 0) return -errno; info->buf[ret] = '\0'; if (ret == 0) return -ENOENT; info->size = ret; s_mode = info->buf; s_nlink = strchr(info->buf, ' '); if (s_nlink++ == NULL) goto busted; s_uid = strchr(s_nlink, ' '); if (s_uid++ == NULL) goto busted; s_gid = strchr(s_uid, ' '); if (s_gid++ == NULL) goto busted; s_rdev = strchr(s_gid, ' '); if (s_rdev++ == NULL) goto busted; info->target = strchr(s_rdev, ' '); if (info->target++ == NULL) goto busted; info->mode = strtoul(s_mode, NULL, 8); info->nlink = strtoul(s_nlink, NULL, 0); info->uid = strtoul(s_uid, NULL, 0); info->gid = strtoul(s_gid, NULL, 0); info->rdev = strtoul(s_rdev, &toul_ptr, 0); if (toul_ptr == NULL || *toul_ptr != ':') goto busted; ++toul_ptr; info->rdev = COMPAT_MKDEV(info->rdev, strtoul(toul_ptr, NULL, 0)); strncpy(info->new_target, info->target, sizeof(info->new_target)); info->new_target[sizeof(info->new_target)-1] = '\0'; return 0; busted: hcb_got_busted(path); unlinkat(root_fd, at(path), 0); return -EINVAL; } /* * ll_hcb_write - lowlevel write of HCB * @path: path to HCB (used for debug and unlink) * @info: source structure * @fd: fd to write to * * Recalculates @info->buf from the structure and writes it out. */ static int ll_hcb_write(const char *path, struct ll_hcb *info, int fd) { ssize_t ret; size_t z; if (lseek(fd, 0, SEEK_SET) < 0) return -errno; if (ftruncate(fd, 0) < 0) should_not_happen(); ret = snprintf(info->buf, sizeof(info->buf), "%o %u %lu %lu %lu:%lu %s", static_cast(unsigned int, info->mode), static_cast(unsigned int, info->nlink), static_cast(unsigned long, info->uid), static_cast(unsigned long, info->gid), COMPAT_MAJOR(info->rdev), COMPAT_MINOR(info->rdev), info->new_target); if (ret >= sizeof(info->buf)) return -EIO; z = strlen(info->buf); ret = write(fd, info->buf, z); if (ret < 0) return -errno; if (ret != z) { hcb_got_busted(path); unlinkat(root_fd, at(path), 0); return -EIO; } return 0; } /* * hcb_new - create new HCB * @path: file path (not HCB path) * @cb: destination structure * @reuse: reuse HCB (may have been filled in previously, * by a failed hcb_get() for example) */ static int hcb_new(const char *path, struct hcb *cb, unsigned int reuse) { int ret; if (reuse) { if (cb->fd >= 0) should_not_happen(); } else { memset(cb, 0, sizeof(*cb)); cb->fd = -1; if ((ret = real_to_hcb(cb->path, path)) < 0) return ret; if (fstatat(root_fd, at(path), &cb->sb, AT_SYMLINK_NOFOLLOW) < 0 && errno != ENOENT) return -errno; if (!S_ISDIR(cb->sb.st_mode)) cb->sb.st_mode &= ~S_IXUGO; } cb->ll.mode = cb->sb.st_mode; cb->ll.nlink = cb->sb.st_nlink; cb->ll.uid = cb->sb.st_uid; cb->ll.gid = cb->sb.st_gid; cb->ll.rdev = cb->sb.st_rdev; return 0; } /* * hcb_get - read HCB * @path: L0 file path * @cb: destination struct * * Read @path's HCB into @cb. This does not follow hardlinks. * To distinguish whether @path or the HCB was not found, hcb_get() will * return -ENOENT when @path was not found, and -ENOENT_HCB when the HCB * was not found. */ static int hcb_get(const char *path, struct hcb *cb) { int ret; memset(cb, 0, sizeof(*cb)); cb->fd = -1; /* Get inode number, size and times from the L0 file */ if (fstatat(root_fd, at(path), &cb->sb, AT_SYMLINK_NOFOLLOW) < 0) return -errno; cb->sb.st_mode &= S_IFMT; cb->sb.st_mode |= default_mode; if (S_ISDIR(cb->sb.st_mode)) cb->sb.st_mode |= S_IXUGO; if ((ret = real_to_hcb(cb->path, path)) < 0) return ret; cb->fd = openat(root_fd, at(cb->path), O_RDWR); if (cb->fd < 0 && errno == EACCES) /* Retry read-only */ cb->fd = openat(root_fd, at(cb->path), O_RDONLY); if (cb->fd < 0) { if (errno == ENOENT) return -ENOENT_HCB; else return -errno; } if (lock_read(cb->fd) < 0) { ret = -errno; close(cb->fd); return ret; } ret = ll_hcb_read(cb->path, &cb->ll, cb->fd); if (ret < 0) { close(cb->fd); return ret; } /* and extra attributes from the L0 HCB */ cb->sb.st_mode = cb->ll.mode; cb->sb.st_nlink = cb->ll.nlink; cb->sb.st_uid = cb->ll.uid; cb->sb.st_gid = cb->ll.gid; cb->sb.st_rdev = cb->ll.rdev; if (!S_ISREG(cb->ll.mode) && !S_ISDIR(cb->ll.mode)) cb->sb.st_size = cb->ll.size; return 0; } /* * hcb_put - release HCB * @cb: data * * Close the file descriptor to the HCB. This does _not_ flush any changes. * (Also because whether a change was made is not recorded. Explicitly call * hcb_update().) */ static void hcb_put(const struct hcb *cb) { if (cb->fd < 0) should_not_happen(); close(cb->fd); } /* * hcb_deref - dereference an S_IFHARDLNK HCB * @cb: data * * If the HCB is an S_IFHARDLNK, follow it and update attributes. * Non-hardlinks are ignored and success is returned. */ static int hcb_deref(struct hcb *cb) { struct stat sb; int ret; if (cb->fd < 0) should_never_happen(); if (!S_ISHARDLNK(cb->ll.mode)) return 0; close(cb->fd); if (fstatat(root_fd, at(cb->ll.target), &sb, AT_SYMLINK_NOFOLLOW) < 0) return -errno; /* Some things are stored on the L1 file... */ cb->sb.st_ino = sb.st_ino; cb->sb.st_size = sb.st_size; cb->sb.st_atime = sb.st_atime; cb->sb.st_ctime = sb.st_ctime; cb->sb.st_mtime = sb.st_mtime; hl_dtoi(cb->path, cb->ll.target); cb->fd = openat(root_fd, at(cb->path), O_RDWR); if (cb->fd < 0) return -errno; if (lock_read(cb->fd) < 0) { ret = -errno; close(cb->fd); return ret; } ret = ll_hcb_read(cb->path, &cb->ll, cb->fd); if (ret < 0) { close(cb->fd); return ret; } /* ...and some on the L1 HCB */ cb->sb.st_mode = cb->ll.mode; cb->sb.st_nlink = cb->ll.nlink; cb->sb.st_uid = cb->ll.uid; cb->sb.st_gid = cb->ll.gid; cb->sb.st_rdev = cb->ll.rdev; if (!S_ISREG(cb->ll.mode) && !S_ISDIR(cb->ll.mode)) cb->sb.st_size = cb->ll.size; return 0; } /* * hcb_get_deref - shortcut for hcb_get()+hcb_deref() * @path: virtual path * @cb: destination structure * * Retrieve the lowest HCB. */ static int hcb_get_deref(const char *path, struct hcb *cb) { int ret; if ((ret = hcb_get(path, cb)) < 0) return ret; if ((ret = hcb_deref(cb)) < 0) return ret; return 0; } /* * hcb_update - write HCB * @cb: data * * Write back the HCB with possibly changed data. hcb_put() is called * afterwards because that's what is usually intended. */ static int hcb_update(struct hcb *cb) { int ret; if (cb->fd < 0) { /* When this HCB was created using hcb_new() */ cb->fd = openat(root_fd, at(cb->path), O_RDWR | O_CREAT | O_EXCL, S_IRUGO | S_IWUSR); if (cb->fd < 0) return -errno; } if (lock_write(cb->fd) < 0) { ret = -errno; close(cb->fd); return ret; } ret = ll_hcb_write(cb->path, &cb->ll, cb->fd); hcb_put(cb); return ret; } /* * hcb_lookup - shortcut for hcb_get()+hcb_put() * @path: virtual path * @cb: destination structure * * Do a standard HCB lookup with hardlink following. */ static int hcb_lookup(const char *path, struct hcb *cb) { int ret; if ((ret = hcb_get(path, cb)) < 0) return ret; hcb_put(cb); return 0; } /* * hcb_lookup_deref - shortcut for hcb_get_deref()+hcb_put() * @path: virtual path * @cb: destination structure * * Do a standard HCB lookup with hardlink following. */ static int hcb_lookup_deref(const char *path, struct hcb *cb) { int ret; if ((ret = hcb_get_deref(path, cb)) < 0) return ret; hcb_put(cb); return 0; } /* * hcb_lookup_readdir - * @dir: working directory * @name: file * @info: destination structure * * Combines the working directory @dir with @name (to form an absolute path), * then retrieves the HCB. */ static int hcb_lookup_readdir(const char *dir, const char *name, struct hcb *info) { char path[PATH_MAX]; int ret; /* * Ensure that @path does not have two leading slashes or * the *at() logic does not do the right thing. */ if (dir[1] == '\0') { /* * First character is always a slash, so if the second one is * '\0', it must be "/". I am trying to optimize here. */ path[0] = '/'; strlcpy(&path[1], name, sizeof(path) - 1); ret = strlen(name) + 1; } else { ret = snprintf(path, sizeof(path), "%s/%s", dir, name); } if (ret >= sizeof(path)) return -ENAMETOOLONG; if ((ret = hcb_get_deref(path, info)) < 0) return ret; hcb_put(info); return 0; } static __attribute__((pure)) bool is_resv_name(const char *name) { return strncmp(name, HCB_PREFIX, HCB_PREFIX_LEN) == 0 || strncmp(name, HL_DNODE_PREFIX, HL_DNODE_PREFIX_LEN) == 0 || strncmp(name, HL_INODE_PREFIX, HL_INODE_PREFIX_LEN) == 0 || strcmp(name, HCB_PREFIX1) == 0; } static __attribute__((pure)) bool is_resv(const char *path) { const char *file = strrchr(path, '/'); if (file++ == NULL) should_never_happen(); return is_resv_name(file); } /* * supports_owners - check whether @path can do that * @path: path to existing file * @uid: uid to change to (used for figuring out) * @gid: gid to change to * @restore: restore permissions after check * * This has to be looked up on a per-path basis, because it is possible to * mount a filesystem supporting permissions on a directory on a filesystem * that does not, as in, for example, the following case: * mount -t vfat /dev/foo /mnt * mount -t xfs /dev/bar /mnt/sub * mount.posixovl /mnt * * Note that on a filesystem which supports owners, our fchownat() will * always succeed (or always fail), because the kernel checks for * capability rather than FSUID. (Good thing.) */ static bool __supports_owners(const char *path, uid_t uid, gid_t gid, bool restore) { struct stat orig_sb, new_sb; uid_t work_uid = -1; gid_t work_gid = -1; if (fstatat(root_fd, at(path), &orig_sb, AT_SYMLINK_NOFOLLOW) < 0) { perror("fstatat"); return false; } if (uid == -1) work_uid = orig_sb.st_uid; else if (orig_sb.st_uid != uid) work_uid = uid; else if (uid == 0) work_uid = -2; else should_not_happen(); if (gid == -1) work_gid = orig_sb.st_gid; else if (orig_sb.st_gid != gid) work_gid = gid; else if (uid == 0) work_gid = -2; else should_not_happen(); if (fchownat(root_fd, at(path), work_uid, work_gid, AT_SYMLINK_NOFOLLOW) < 0) return false; if (fstatat(root_fd, at(path), &new_sb, AT_SYMLINK_NOFOLLOW) < 0) { perror("fstatat"); return false; } if (restore) if (fchownat(root_fd, at(path), orig_sb.st_uid, orig_sb.st_gid, AT_SYMLINK_NOFOLLOW) < 0) should_not_happen(); return new_sb.st_uid == work_uid && new_sb.st_gid == work_gid; } static bool supports_owners(const char *path, uid_t uid, gid_t gid, bool restore) { if (assume_vfat) return false; return __supports_owners(path, uid, gid, restore); } /* * supports_permissions - check whether @path can do that * @path: existing path to file * * Does not restore the original mode. */ static bool __supports_permissions(const char *path) { struct stat orig_sb, new_sb; mode_t work_mode; if (fstatat(root_fd, at(path), &orig_sb, AT_SYMLINK_NOFOLLOW) < 0) { /* literally BUG() */ perror("fstatat"); return false; } /* Pick some magic */ work_mode = (orig_sb.st_mode ^ S_IRUSR ^ S_IXGRP) & ~S_IROTH; if (fchmodat(root_fd, at(path), work_mode, AT_SYMLINK_NOFOLLOW) < 0) return false; if (fstatat(root_fd, at(path), &new_sb, AT_SYMLINK_NOFOLLOW) < 0) { perror("fstatat"); return false; } return new_sb.st_mode == work_mode; } static bool supports_permissions(const char *path) { if (assume_vfat) return false; return __supports_permissions(path); } static char *xfrm_to_disk(const char *read_ptr) { unsigned int needed = 0; const char *next; char *out, *out_ptr; for (next = read_ptr; *next != '\0'; ++next) switch (*next) { case '\\': case ':': case '*': case '?': case '\"': case '<': case '>': case '|': needed += strlen("%(XX)"); break; /* Path separator ('/') is not to be encoded! */ default: ++needed; break; } out = out_ptr = malloc(needed + 1); if (out == NULL) return NULL; do { next = strpbrk(read_ptr, "\\:*?\"<>|"); if (next == NULL) { strcpy(out_ptr, read_ptr); out_ptr += strlen(read_ptr); break; } else if (read_ptr != next) { strncpy(out_ptr, read_ptr, next - read_ptr); out_ptr += next - read_ptr; } sprintf(out_ptr, "%%(%02X)", *next); out_ptr += strlen("%(XX)"); read_ptr = next + 1; } while (*read_ptr != '\0'); *out_ptr = '\0'; return out; } static const unsigned char xfrm_table[] = { ['0'] = 0, ['1'] = 1, ['2'] = 2, ['3'] = 3, ['4'] = 4, ['5'] = 5, ['6'] = 6, ['7'] = 7, ['8'] = 8, ['9'] = 9, ['A'] = 10, ['B'] = 11, ['C'] = 12, ['D'] = 13, ['E'] = 14, ['F'] = 15, }; static char *xfrm_from_disk(const char *read_ptr) { const char *seek_ptr = read_ptr, *next; char *out = malloc(strlen(read_ptr) + 1), *out_ptr = out; if (out == NULL) return NULL; do { next = strstr(seek_ptr, "%("); if (next == NULL) { strcpy(out_ptr, read_ptr); out_ptr += strlen(read_ptr); break; } if (!isxdigit(next[2]) || !isxdigit(next[3]) || next[4] != ')') { seek_ptr = next + 1; continue; } strncpy(out_ptr, read_ptr, next - read_ptr); out_ptr += next - read_ptr; read_ptr = seek_ptr = next + strlen("%(XX)"); *out_ptr++ = (xfrm_table[toupper(next[2])] << 4) | xfrm_table[toupper(next[3])]; } while (*read_ptr != '\0'); *out_ptr = '\0'; return out; } static int posixovl_chmod(const char *path, mode_t mode) { struct hcb info; int ret; if (is_resv(path)) return -ENOENT; ret = hcb_get_deref(path, &info); if (ret == -ENOENT_HCB) { if (supports_permissions(path)) return retcode_int(fchmodat(root_fd, at(path), mode, AT_SYMLINK_NOFOLLOW)); if ((ret = hcb_new(path, &info, 1)) < 0) return ret; /* nlink already set (hcb_new() stat'ed @path) */ } else if (ret < 0) { return ret; } info.ll.mode = mode; return hcb_update(&info); } static int posixovl1_chmod(const char *path, mode_t mode) { char *xpath = xfrm_to_disk(path); int ret = posixovl_chmod(xpath, mode); free(xpath); return ret; } static int posixovl_chown(const char *path, uid_t uid, gid_t gid) { struct hcb info; int ret; if (is_resv(path)) return -ENOENT; ret = hcb_get_deref(path, &info); if (ret == -ENOENT_HCB) { if (supports_owners(path, uid, gid, false)) return retcode_int(fchownat(root_fd, at(path), uid, gid, AT_SYMLINK_NOFOLLOW)); if ((ret = hcb_new(path, &info, 1)) < 0) return ret; /* nlink already set (hcb_new() stat'ed @path) */ } else if (ret < 0) { return ret; } if (uid != (uid_t)-1) info.ll.uid = uid; if (gid != (gid_t)-1) info.ll.gid = gid; return hcb_update(&info); } static int posixovl1_chown(const char *path, uid_t uid, gid_t gid) { char *xpath = xfrm_to_disk(path); int ret = posixovl_chown(xpath, uid, gid); free(xpath); return ret; } static int posixovl_close(const char *path, struct fuse_file_info *filp) { return retcode_int(close(filp->fh)); } static __attribute__((pure)) bool could_be_too_long(const char *path) { /* Longest possible case is S_ISDIR: /path/.pxovl. */ return strlen(path) + 1 + HCB_PREFIX_LEN >= PATH_MAX; } /* * parent_owner_match - * @path: path, of which the parent is to be checked * @uid: uid to test * * Checks whether @path's parent is owned by @uid. * @path denotes a path on the real volume, hence no HCB lookup here. */ static bool parent_owner_match(const char *path, uid_t uid) { struct stat sb; int ret; ret = fstatat(root_fd, at(path), &sb, AT_SYMLINK_NOFOLLOW); if (ret < 0) { should_not_happen(); return false; } return sb.st_uid == uid; } static int posixovl_create(const char *path, mode_t mode, struct fuse_file_info *filp) { const struct fuse_context *ctx; struct hcb cb; int fd, ret; if (is_resv(path)) return -EPERM; if (could_be_too_long(path)) return -ENAMETOOLONG; fd = openat(root_fd, at(path), filp->flags, mode); if (fd < 0) return -errno; filp->fh = fd; /* * Default file permissions do not trigger creation of a HCB. * We need (rather: want) a HCB if the fsuid is different from * the owner of the underlying mount, if owners are not * supported. * Fuse oddity: @mode includes S_IFREG (contraty to mkdir()) */ ctx = fuse_get_context(); if (((mode & ~S_IWUSR) != (S_IFREG | (default_mode & ~S_IWUSR)) && !supports_permissions(path)) || (!parent_owner_match(path, ctx->uid) && !supports_owners(path, ctx->uid, ctx->gid, true))) { if ((ret = hcb_new(path, &cb, 0)) < 0) return 0; /* nlink already set (hcb_new() stat'ed @path) */ cb.ll.mode = mode; cb.ll.uid = ctx->uid; cb.ll.gid = ctx->gid; hcb_update(&cb); } return 0; } static int posixovl1_create(const char *path, mode_t mode, struct fuse_file_info *filp) { char *xpath = xfrm_to_disk(path); int ret = posixovl_create(xpath, mode, filp); free(xpath); return ret; } static int posixovl_ftruncate(const char *path, off_t length, struct fuse_file_info *filp) { return retcode_int(ftruncate(filp->fh, length)); } /* * hl_demote - collapse S_IFHARDLNK into normal file * @l0_file: * @l0_hcb: * @l1_file: * @l1_hcb: * * Unlink the L0 files and move the L1 ones into L0's place. * Caller *must* ensure that the L1 HCB has nlink=1, or all other * hardlink slaves will break. */ static int hl_demote(const char *l0_file, const char *l0_hcb, const char *l1_file, const char *l1_hcb) { int ret = 0; pthread_mutex_lock(&posixovl_protect); if (unlinkat(root_fd, at(l0_file), 0) < 0) { pthread_mutex_unlock(&posixovl_protect); return -errno; } unlinkat(root_fd, at(l0_hcb), 0); if (renameat(root_fd, at(l1_hcb), root_fd, at(l0_hcb)) < 0) { pthread_mutex_unlock(&posixovl_protect); ret = -errno; fprintf(stderr, "%s: rename %s -> %s failed: %s\n", __func__, l1_hcb, l0_hcb, strerror(errno)); return ret; } if (renameat(root_fd, at(l1_file), root_fd, at(l0_file)) < 0) { ret = -errno; fprintf(stderr, "%s: rename %s -> %s failed: %s\n", __func__, l1_file, l0_file, strerror(errno)); } pthread_mutex_unlock(&posixovl_protect); return ret; } /* * hl_try_demote - try to collapse a one-link hardlink net into one file * @path: real path * * Check if @path is a S_IFHARDLNK and if its link count is 1, demote the * four-file set (virtual file, L0 HCB, data file, L1 HCB) into a two-file * set (virtual + HCB). */ static int hl_try_demote(const char *path) { struct hcb info_l0, info_l1; int ret; ret = hcb_lookup(path, &info_l0); if (ret == -ENOENT_HCB) return 0; if (ret < 0) return ret; if (!S_ISHARDLNK(info_l0.ll.mode)) return 0; memcpy(&info_l1, &info_l0, sizeof(info_l0)); if ((ret = hcb_deref(&info_l1)) < 0) return ret; hcb_put(&info_l1); if (info_l1.ll.nlink != 1) return 0; return hl_demote(path, info_l0.path, info_l0.ll.target, info_l1.path); } static int posixovl_getattr(const char *path, struct stat *sb) { struct hcb info; int ret; if (is_resv(path)) return -ENOENT; ret = hcb_lookup_deref(path, &info); if (ret < 0 && ret != -ENOENT_HCB && ret != -EACCES) return ret; memcpy(sb, &info.sb, sizeof(*sb)); return 0; } static int posixovl1_getattr(const char *path, struct stat *sb) { char *xpath = xfrm_to_disk(path); int ret = posixovl_getattr(xpath, sb); free(xpath); return ret; } static int posixovl_getxattr(const char *path, const char *name, char *value, size_t size) { return retcode_int(lgetxattr(at(path), name, value, size)); } static int posixovl1_fgetattr(const char *path, struct stat *sb, struct fuse_file_info *filp) { /* * Need to use the normal getattr because we need to check for the * HCB too, not just @filp->fh. */ return posixovl1_getattr(path, sb); } static void *posixovl_init(struct fuse_conn_info *conn) { /* * There is no fopendirat(), we need to use fchdir() and * opendir(relative_path) instead. */ if (fchdir(root_fd) < 0) abort(); return NULL; } /* * hl_promote - transform file into hardlink master * @l0_path: path to real file * @orig_info: L0 HCB * @l0_hcb_exists: what it says */ static int hl_promote(const char *l0_path, const struct hcb *orig_info, bool l0_hcb_exists) { char l1_path[PATH_MAX], l1_hcb[PATH_MAX]; struct stat work_sb; struct hcb new_info; int fd, ret; /* * Create a unique ID. Note that there may be underlying filesystems * where inode numbers are dynamically generated. Hence they may * overlap with posixovl IDs (encoded into the filename) from a * previous mount, hence we rand() should the ID already exist. */ work_sb.st_ino = orig_info->sb.st_ino; do { snprintf(l1_path, sizeof(l1_path), "/" HL_DNODE_PREFIX "%lu", static_cast(unsigned long, work_sb.st_ino)); snprintf(l1_hcb, sizeof(l1_hcb), "/" HL_INODE_PREFIX "%lu", static_cast(unsigned long, work_sb.st_ino)); if (fstatat(root_fd, at(l1_path), &work_sb, AT_SYMLINK_NOFOLLOW) == 0) { work_sb.st_ino = rand(); continue; } if (errno == ENOENT) /* ok, can use this ID */ break; return -errno; } while (true); /* Move L0 to L1 */ ret = renameat(root_fd, at(l0_path), root_fd, at(l1_path)); if (ret < 0) return -errno; /* move L0 HCB to L1 HCB */ if (l0_hcb_exists) { ret = renameat(root_fd, at(orig_info->path), root_fd, at(l1_hcb)); if (ret < 0) { ret = -errno; goto out; } /* nlink already ok */ } else { if ((ret = hcb_new(l1_path, &new_info, 0)) < 0) goto out; if ((ret = hcb_update(&new_info)) < 0) goto out; } /* initialize first link */ if ((ret = hcb_new(l0_path, &new_info, 0)) < 0) goto out2; new_info.ll.mode = S_IFHARDLNK; new_info.ll.nlink = 1; strlcpy(new_info.ll.new_target, l1_path, sizeof(new_info.ll.new_target)); hcb_update(&new_info); /* instantiate first link into readdir visibility */ fd = openat(root_fd, at(l0_path), O_WRONLY | O_CREAT | O_EXCL, 0); if (fd < 0) { ret = -errno; goto out3; } close(fd); return 0; out3: unlinkat(root_fd, at(orig_info->path), 0); out2: if (l0_hcb_exists) renameat(root_fd, at(l1_hcb), root_fd, at(orig_info->path)); out: renameat(root_fd, at(l1_path), root_fd, at(l0_path)); return ret; } /* * hl_up_nlink - increase nlink count of hardlink master * @l1_path: name of the L1 file * (Could have used the L0 file, but using L1 saves a derefernce, * which has already been performed in the caller anyway) */ static int hl_up_nlink(const char *l1_path) { struct hcb cb; int ret; if ((ret = hcb_get(l1_path, &cb)) < 0) return ret; if (S_ISHARDLNK(cb.ll.mode)) should_not_happen(); else ++cb.ll.nlink; return hcb_update(&cb); } /* * hl_drop - drop nlink count of hardlink master * @l1_path: name of the L1 file * * Drop the nlink of the hardlink master by one, and if it reaches zero, * unlink the D-node. */ static int hl_drop(const char *l1_path) { struct hcb cb; int ret; if ((ret = hcb_get(l1_path, &cb)) < 0) return ret; if (S_ISHARDLNK(cb.ll.mode)) { should_not_happen(); hcb_put(&cb); return 0; } if (cb.ll.nlink == 0) should_not_happen(); if (cb.ll.nlink == 1) { hcb_put(&cb); pthread_mutex_lock(&posixovl_protect); unlinkat(root_fd, at(l1_path), 0); unlinkat(root_fd, at(cb.path), 0); pthread_mutex_unlock(&posixovl_protect); return 0; } --cb.ll.nlink; return hcb_update(&cb); } /* * hl_instantiate - * @oldpath: * @newpath: * * This is perhaps the most expensive operation among all. */ static int hl_instantiate(const char *oldpath, const char *newpath) { struct hcb cb_old, cb_new; int fd, ret; pthread_mutex_lock(&posixovl_protect); ret = hcb_lookup(oldpath, &cb_old); if (ret == -ENOENT_HCB || (ret == 0 && !S_ISHARDLNK(cb_old.ll.mode))) { /* * If no HCB attached or if not a hardlink slave... */ if ((ret = hl_promote(oldpath, &cb_old, ret != -ENOENT_HCB)) < 0) goto unlock_and_out; /* * Relookup to get the L1 file path */ if ((ret = hcb_lookup(oldpath, &cb_old)) < 0) goto unlock_and_out; } else if (ret < 0) { pthread_mutex_unlock(&posixovl_protect); return -errno; } pthread_mutex_unlock(&posixovl_protect); /* now we can do the Nth link */ if ((ret = hl_up_nlink(cb_old.ll.target)) < 0) return ret; if ((ret = hcb_new(newpath, &cb_new, 0)) < 0) goto out; cb_new.ll.mode = S_IFHARDLNK; cb_new.ll.nlink = 1; strlcpy(cb_new.ll.new_target, cb_old.ll.target, sizeof(cb_new.ll.new_target)); if ((ret = hcb_update(&cb_new)) < 0) goto out; fd = openat(root_fd, at(newpath), O_WRONLY | O_CREAT | O_EXCL, 0); if (fd < 0) { ret = -errno; goto out2; } close(fd); return 0; out2: unlinkat(root_fd, at(cb_new.path), 0); out: hl_drop(cb_old.ll.target); return ret; unlock_and_out: pthread_mutex_unlock(&posixovl_protect); return ret; } static int posixovl_link(const char *oldpath, const char *newpath) { int ret; if (is_resv(oldpath)) return -ENOENT; if (is_resv(newpath)) return -EPERM; if (could_be_too_long(oldpath) || could_be_too_long(newpath)) return -ENAMETOOLONG; /* * Kernel/FUSE already takes care of prohibiting hardlinking * directories. We never get to see these. */ if (!assume_vfat) { ret = linkat(root_fd, at(oldpath), root_fd, at(newpath), 0); if (ret < 0 && errno != EPERM) return ret; else if (ret >= 0) return 0; } return hl_instantiate(oldpath, newpath); } static int posixovl1_link(const char *oldpath, const char *newpath) { char *xoldpath = xfrm_to_disk(oldpath); char *xnewpath = xfrm_to_disk(newpath); int ret = posixovl_link(xoldpath, xnewpath); free(xoldpath); free(xnewpath); return ret; } static int posixovl_listxattr(const char *path, char *list, size_t size) { return retcode_int(llistxattr(at(path), list, size)); } static int posixovl_mkdir(const char *path, mode_t mode) { const struct fuse_context *ctx; struct hcb cb; int ret; if (is_resv(path)) return -EPERM; if (could_be_too_long(path)) return -ENAMETOOLONG; ret = mkdirat(root_fd, at(path), mode); if (ret < 0) return -errno; /* FUSE oddity: @mode does not include S_IFDIR */ ctx = fuse_get_context(); if (((mode & ~S_IWUSR) != ((default_mode & ~S_IWUSR) | S_IXUGO) && !supports_permissions(path)) || (!parent_owner_match(path, ctx->uid) && !supports_owners(path, ctx->uid, ctx->gid, true))) { if ((ret = hcb_new(path, &cb, 0)) < 0) return 0; /* nlink already set (hcb_new() stat'ed @path) */ cb.ll.mode = S_IFDIR | mode; cb.ll.uid = ctx->uid; cb.ll.gid = ctx->gid; hcb_update(&cb); } return 0; } static int posixovl1_mkdir(const char *path, mode_t mode) { char *xpath = xfrm_to_disk(path); int ret = posixovl_mkdir(xpath, mode); free(xpath); return ret; } static int posixovl_mknod(const char *path, mode_t mode, dev_t rdev) { const struct fuse_context *ctx; struct hcb info; int fd, ret; if (is_resv(path)) return -EPERM; if (!assume_vfat) { ret = mknodat(root_fd, at(path), mode, rdev); if (ret < 0 && errno != EPERM) return ret; else if (ret >= 0) return 0; } /* * The HCB is created first - since that one does not show up in * readdir() and is not accessible either. * Same goes for posixovl_symlink(). */ if ((ret = hcb_new(path, &info, 0)) < 0) return ret; ctx = fuse_get_context(); info.ll.mode = mode; info.ll.nlink = 1; info.ll.uid = ctx->uid; info.ll.gid = ctx->gid; info.ll.rdev = rdev; if ((ret = hcb_update(&info)) < 0) return ret; fd = openat(root_fd, at(path), O_WRONLY | O_CREAT | O_EXCL, 0); if (fd < 0) { ret = -errno; unlinkat(root_fd, at(info.path), 0); } close(fd); return ret; } static int posixovl1_mknod(const char *path, mode_t mode, dev_t rdev) { char *xpath = xfrm_to_disk(path); int ret = posixovl_mknod(xpath, mode, rdev); free(xpath); return ret; } static int posixovl_open(const char *path, struct fuse_file_info *filp) { struct hcb info; int fd, ret; if (is_resv(path)) return -ENOENT; if ((filp->flags & O_ACCMODE) == O_WRONLY || (filp->flags & O_ACCMODE) == O_RDWR) if ((ret = hl_try_demote(path)) < 0) return ret; ret = hcb_lookup(path, &info); if (ret < 0 && ret != -ENOENT_HCB) return ret; if (ret == 0 && S_ISHARDLNK(info.ll.mode)) fd = openat(root_fd, at(info.ll.target), filp->flags); else /* * no need to handle non-regular files -- kernel and fuse do * that for us. */ fd = openat(root_fd, at(path), filp->flags); if (fd < 0) return -errno; filp->fh = fd; return 0; } static int posixovl1_open(const char *path, struct fuse_file_info *filp) { char *xpath = xfrm_to_disk(path); int ret = posixovl_open(xpath, filp); free(xpath); return ret; } static int posixovl_read(const char *path, char *buffer, size_t size, off_t offset, struct fuse_file_info *filp) { return retcode_ssize(pread(filp->fh, buffer, size, offset)); } static int posixovl_readdir(const char *path, void *buffer, fuse_fill_dir_t filldir, off_t offset, struct fuse_file_info *filp) { const struct dirent *dentry; struct hcb info; int ret = 0; DIR *ptr; char *x; if (is_resv(path)) return -ENOENT; /* * Current working directory is root_fd (per posixovl_init()). * Let's hope opendir(relative_path) works. */ if ((ptr = opendir(at(path))) == NULL) return -errno; while ((dentry = readdir(ptr)) != NULL) { if (is_resv_name(dentry->d_name)) continue; ret = hcb_lookup_readdir(path, dentry->d_name, &info); if (ret < 0 && ret != -ENOENT_HCB && ret != -EACCES) break; ret = 0; x = xfrm_from_disk((char *)dentry->d_name); if ((*filldir)(buffer, x, &info.sb, 0) > 0) break; free(x); } closedir(ptr); return ret; } static int posixovl1_readdir(const char *path, void *buffer, fuse_fill_dir_t filldir, off_t offset, struct fuse_file_info *filp) { char *xpath = xfrm_to_disk(path); int ret = posixovl_readdir(xpath, buffer, filldir, offset, filp); free(xpath); return ret; } static int posixovl_readlink(const char *path, char *dest, size_t size) { struct hcb info; int ret; if (is_resv(path)) return -ENOENT; ret = readlinkat(root_fd, at(path), dest, size - 1); if (ret < 0 && errno != EINVAL) { return ret; } else if (ret >= 0) { dest[ret] = '\0'; return 0; } ret = hcb_lookup_deref(path, &info); if (ret == -ENOENT_HCB) return -EINVAL; /* not a symlink */ else if (ret < 0) return ret; if (!S_ISLNK(info.ll.mode)) return -EINVAL; /* not a symbolic link */ memset(dest, 0, size); strlcpy(dest, info.ll.target, size); return 0; } static int posixovl1_readlink(const char *path, char *dest, size_t size) { char *xpath = xfrm_to_disk(path); int ret = posixovl_readlink(xpath, dest, size); free(xpath); return ret; } static int posixovl_removexattr(const char *path, const char *name) { return retcode_int(lremovexattr(path, name)); } static int posixovl_rename(const char *oldpath, const char *newpath) { char new_hcbpath[PATH_MAX]; struct hcb old_info; int ret, ret_2; if (is_resv(oldpath)) return -ENOENT; if (is_resv(newpath)) return -EPERM; if (could_be_too_long(newpath)) return -ENAMETOOLONG; ret = hcb_lookup(oldpath, &old_info); if (ret == -ENOENT_HCB || S_ISDIR(old_info.sb.st_mode)) return retcode_int(renameat(root_fd, at(oldpath), root_fd, at(newpath))); else if (ret < 0) return ret; if ((ret = real_to_hcb(new_hcbpath, newpath)) < 0) return ret; pthread_mutex_lock(&posixovl_protect); ret_2 = renameat(root_fd, at(oldpath), root_fd, at(newpath)); if (ret_2 < 0) { ret = -errno; pthread_mutex_unlock(&posixovl_protect); return ret; } ret_2 = renameat(root_fd, at(old_info.path), root_fd, at(new_hcbpath)); if (ret_2 < 0) { /* !@#$%^& - error. Need to rename old file back. */ ret = -errno; if (renameat(root_fd, at(newpath), root_fd, at(oldpath)) < 0) { /* Even that failed. Keep new name, but kill HCB. */ unlinkat(root_fd, at(old_info.path), 0); hcb_got_busted(old_info.path); } pthread_mutex_unlock(&posixovl_protect); return ret; } pthread_mutex_unlock(&posixovl_protect); return 0; } static int posixovl1_rename(const char *oldpath, const char *newpath) { char *xoldpath = xfrm_to_disk(oldpath); char *xnewpath = xfrm_to_disk(newpath); int ret = posixovl_rename(xoldpath, xnewpath); free(xoldpath); free(xnewpath); return ret; } static int posixovl_rmdir(const char *path) { struct hcb info; int ret; if (is_resv(path)) return -ENOENT; ret = hcb_lookup(path, &info); if (ret == 0 && unlinkat(root_fd, at(info.path), 0) < 0) return -errno; return retcode_int(unlinkat(root_fd, at(path), AT_REMOVEDIR)); } static int posixovl1_rmdir(const char *path) { char *xpath = xfrm_to_disk(path); int ret = posixovl_rmdir(xpath); free(xpath); return ret; } static int posixovl_setxattr(const char *path, const char *name, const char *value, size_t size, int flags) { return retcode_int(lsetxattr(at(path), name, value, size, flags)); } static int posixovl_statfs(const char *path, struct statvfs *sb) { if (fstatvfs(root_fd, sb) < 0) return -errno; sb->f_fsid = 0; return 0; } static int posixovl_symlink(const char *oldpath, const char *newpath) { const struct fuse_context *ctx; struct hcb info; int fd, ret; if (is_resv(newpath)) return -EPERM; if (!assume_vfat) { ret = symlinkat(oldpath, root_fd, at(newpath)); if (ret < 0 && errno != EPERM) return -errno; else if (ret >= 0) return 0; } /* symlink() not supported on @path */ if ((ret = hcb_new(newpath, &info, 0)) < 0) return ret; ctx = fuse_get_context(); info.ll.mode = S_IFSOFTLNK; info.ll.nlink = 1; info.ll.uid = ctx->uid; info.ll.gid = ctx->gid; strlcpy(info.ll.new_target, oldpath, sizeof(info.ll.new_target)); if ((ret = hcb_update(&info)) < 0) return ret; fd = openat(root_fd, at(newpath), O_WRONLY | O_CREAT | O_EXCL, 0); if (fd < 0) { ret = -errno; unlinkat(root_fd, at(info.path), 0); } else { close(fd); } return ret; } static int posixovl1_symlink(const char *oldpath, const char *newpath) { char *xoldpath = xfrm_to_disk(oldpath); char *xnewpath = xfrm_to_disk(newpath); int ret = posixovl_symlink(xoldpath, xnewpath); free(xoldpath); free(xnewpath); return ret; } static int posixovl_truncate(const char *path, off_t length) { struct hcb info; int fd, ret; if (is_resv(path)) return -ENOENT; if ((ret = hl_try_demote(path)) < 0) return ret; ret = hcb_lookup_deref(path, &info); if (ret < 0 && ret != -ENOENT_HCB) return -errno; else if (ret == 0 && !S_ISREG(info.ll.mode) && !S_ISDIR(info.ll.mode)) /* * A HCB was found. But truncating special * files (e.g. /dev/zero) is invalid. */ return -EINVAL; ret = hcb_lookup(path, &info); if (ret < 0 && ret != -ENOENT_HCB) return ret; /* * There is no ftruncateat(), so need to use openat()+ftruncate() here. */ if (ret == 0 && S_ISHARDLNK(info.ll.mode)) fd = openat(root_fd, at(info.ll.target), O_WRONLY); else fd = openat(root_fd, at(path), O_WRONLY); if (fd < 0) return -errno; ret = 0; if (ftruncate(fd, length) < 0) ret = -errno; close(fd); return ret; } static int posixovl1_truncate(const char *path, off_t len) { char *xpath = xfrm_to_disk(path); int ret = posixovl_truncate(xpath, len); free(xpath); return ret; } static int posixovl_unlink(const char *path) { struct hcb info; int ret, h_ret; if (is_resv(path)) return -ENOENT; /* * Need to unlink the real file first so that the potential case * "HCB non-existant but real file existant" does not happen in * readdir(). */ h_ret = hcb_lookup(path, &info); if (h_ret < 0 && h_ret != -ENOENT_HCB) return h_ret; ret = unlinkat(root_fd, at(path), 0); if (ret < 0) return -errno; if (h_ret == 0) { unlinkat(root_fd, at(info.path), 0); if (S_ISHARDLNK(info.ll.mode)) hl_drop(info.ll.target); } return 0; } static int posixovl1_unlink(const char *path) { char *xpath = xfrm_to_disk(path); int ret = posixovl_unlink(xpath); free(xpath); return ret; } static int posixovl_utimens(const char *path, const struct timespec *ts) { struct hcb info; int ret; #ifndef HAVE_UTIMENSAT struct timeval tv[2]; #endif if (is_resv(path)) return -ENOENT; #ifndef HAVE_UTIMENSAT tv[0].tv_sec = ts[0].tv_sec; tv[0].tv_usec = ts[0].tv_nsec / 1000; tv[1].tv_sec = ts[1].tv_sec; tv[1].tv_usec = ts[1].tv_nsec / 1000; #endif ret = hcb_lookup(path, &info); if (ret < 0 && ret != -ENOENT_HCB) return ret; /* * The time attributes are always applied to the plain file, * never the special file. * (Until a filesystem that cannot store times comes along.) * In case of S_IFHARDLNK, the .pxovd. file carries the stamp. */ #ifndef HAVE_UTIMENSAT if (ret == 0 && S_ISHARDLNK(info.ll.mode)) ret = futimesat(root_fd, at(info.ll.target), tv); else ret = futimesat(root_fd, at(path), tv); #else if (ret == 0 && S_ISHARDLNK(info.ll.mode)) ret = utimensat(root_fd, at(info.ll.target), ts, AT_SYMLINK_NOFOLLOW); else ret = utimensat(root_fd, at(path), ts, AT_SYMLINK_NOFOLLOW); #endif return retcode_int(ret); } static int posixovl1_utimens(const char *path, const struct timespec *ts) { char *xpath = xfrm_to_disk(path); int ret = posixovl_utimens(xpath, ts); free(xpath); return ret; } static int posixovl_write(const char *path, const char *buffer, size_t size, off_t offset, struct fuse_file_info *filp) { return retcode_ssize(pwrite(filp->fh, buffer, size, offset)); } static bool user_allow_other(void) { bool ret = false; char buf[64]; FILE *fp; if ((fp = fopen("/etc/fuse.conf", "r")) == NULL) return false; while (fgets(buf, sizeof(buf), fp) != NULL) /* no fancy line ending checks or anything */ if (strncmp(buf, "user_allow_other", sizeof("user_allow_other") - 1) == 0) { ret = true; break; } fclose(fp); return ret; } static const struct fuse_operations posixovl_ops = { .chmod = posixovl1_chmod, .chown = posixovl1_chown, .create = posixovl1_create, .fgetattr = posixovl1_fgetattr, .ftruncate = posixovl_ftruncate, .getattr = posixovl1_getattr, .getxattr = posixovl_getxattr, .init = posixovl_init, .link = posixovl1_link, .listxattr = posixovl_listxattr, .mkdir = posixovl1_mkdir, .mknod = posixovl1_mknod, .open = posixovl1_open, .read = posixovl_read, .readdir = posixovl1_readdir, .readlink = posixovl1_readlink, .release = posixovl_close, .removexattr = posixovl_removexattr, .rename = posixovl1_rename, .rmdir = posixovl1_rmdir, .setxattr = posixovl_setxattr, .statfs = posixovl_statfs, .symlink = posixovl1_symlink, .truncate = posixovl1_truncate, .unlink = posixovl1_unlink, .utimens = posixovl1_utimens, .write = posixovl_write, }; static void usage(const char *p) { fprintf(stderr, "Usage: %s [-F] [-S source] mountpoint [-- fuseoptions]\n", p); exit(EXIT_FAILURE); } int main(int argc, char **argv) { char **aptr, **new_argv; int new_argc = 0, original_wd, c; char xargs[256]; while ((c = getopt(argc, argv, "1FS:")) > 0) { switch (c) { case '1': single_threaded = true; break; case 'F': assume_vfat = true; break; case 'S': root_dir = optarg; break; default: usage(*argv); return EXIT_FAILURE; } } if (argv[optind] == NULL) usage(*argv); if (root_dir == NULL) root_dir = argv[optind]; umask(0); if ((root_fd = open(root_dir, O_DIRECTORY)) < 0) { fprintf(stderr, "Could not open(\"%s\"): %s\n", root_dir, strerror(errno)); return EXIT_FAILURE; } original_wd = open(".", O_DIRECTORY); new_argv = malloc(sizeof(char *) * (argc + 5 - optind)); new_argv[new_argc++] = argv[0]; #ifdef HAVE_JUST_FUSE_2_6_5 snprintf(xargs, sizeof(xargs), "-oattr_timeout=0,default_permissions,use_ino,nonempty,dev," "fsname=posix-overlay(%s)", root_dir); #else snprintf(xargs, sizeof(xargs), "-oattr_timeout=0,default_permissions,use_ino,nonempty,dev," "fsname=posix-overlay(%s),subtype=posixovl", root_dir); #endif new_argv[new_argc++] = xargs; if (user_allow_other()) new_argv[new_argc++] = "-oallow_other"; if (single_threaded) new_argv[new_argc++] = "-s"; for (aptr = &argv[optind]; *aptr != NULL; ++aptr) new_argv[new_argc++] = *aptr; new_argv[new_argc] = NULL; c = fuse_main(new_argc, (char **)new_argv, &posixovl_ops, NULL); if (fchdir(original_wd) < 0) /* ignore */{} return c; } fuse-posixovl-1.2.20120215+gitf5bfe35/socket.c000066400000000000000000000041741171700005500203670ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include int main(int argc, const char **argv) { struct sockaddr_un sock1, sock2, sock_c; const char *path1, *path2; int fd1, fd2, fdc, ret; socklen_t socklen = sizeof(sock_c); char buf[80]; if (argc < 3) { fprintf(stderr, "Usage: %s PATH1 PATH2\n", *argv); return EXIT_FAILURE; } path1 = argv[1]; path2 = argv[2]; if (strlen(path1) > sizeof(sock1.sun_path)) { fprintf(stderr, "%s: %s: %s\n", *argv, path1, strerror(ENAMETOOLONG)); return EXIT_FAILURE; } if (strlen(path2) > sizeof(sock2.sun_path)) { fprintf(stderr, "%s: %s: %s\n", *argv, path2, strerror(ENAMETOOLONG)); return EXIT_FAILURE; } fd1 = socket(PF_LOCAL, SOCK_STREAM, 0); if (fd1 < 0) { perror("socket"); return EXIT_FAILURE; } fd2 = socket(PF_LOCAL, SOCK_STREAM, 0); if (fd2 < 0) { perror("socket"); return EXIT_FAILURE; } memset(&sock1, 0, sizeof(sock1)); memset(&sock2, 0, sizeof(sock2)); memset(&sock_c, 0, sizeof(sock_c)); sock1.sun_family = PF_LOCAL; sock2.sun_family = PF_LOCAL; strncpy(sock1.sun_path, path1, sizeof(sock1.sun_path)); strncpy(sock2.sun_path, path2, sizeof(sock2.sun_path)); if (bind(fd1, (const void *)&sock1, sizeof(sock1)) < 0) { perror("bind"); return EXIT_FAILURE; } if (bind(fd2, (const void *)&sock2, sizeof(sock2)) < 0) { perror("bind"); return EXIT_FAILURE; } if (listen(fd1, SOMAXCONN) < 0) { perror("listen"); return EXIT_FAILURE; } fcntl(fd1, F_SETFL, fcntl(fd1, F_GETFL) | O_NONBLOCK); if (connect(fd2, (const void *)&sock1, sizeof(sock1)) < 0) { perror("connect"); return EXIT_FAILURE; } if ((fdc = accept(fd1, (void *)&sock_c, &socklen)) < 0) { perror("accept"); return EXIT_FAILURE; } fcntl(fd2, F_SETFL, fcntl(fd2, F_GETFL) | O_NONBLOCK); fcntl(fdc, F_SETFL, fcntl(fdc, F_GETFL) | O_NONBLOCK); write(fdc, "Hello World\n", 12); ret = read(fd2, buf, 80); if (ret > 0) printf("%.*s", ret, buf); close(fdc); close(fd2); close(fd1); return EXIT_SUCCESS; }