src/unionfs.c
author Radek Podgorny <radek@podgorny.cz>
Thu, 20 May 2010 13:04:08 +0200
changeset 426 2e0d3d1c9c6f
parent 419 c64ad6aebf7b
child 433 244e274ba08c
child 450 a1693d1f3a3b
permissions -rw-r--r--
Added tag v0.24 for changeset d657e8acbb5b
     1 /*
     2 *
     3 * This is offered under a BSD-style license. This means you can use the code for whatever you
     4 * desire in any way you may want but you MUST NOT forget to give me appropriate credits when
     5 * spreading your work which is based on mine. Something like "original implementation by Radek
     6 * Podgorny" should be fine.
     7 *
     8 * License: BSD-style license
     9 * Copyright: Radek Podgorny <radek@podgorny.cz>,
    10 *            Bernd Schubert <bernd-schubert@gmx.de>
    11 */
    12 
    13 #ifdef linux
    14 	// For pread()/pwrite()
    15 	#define _XOPEN_SOURCE 500
    16 #endif
    17 
    18 #include <fuse.h>
    19 #include <stdio.h>
    20 #include <stdlib.h>
    21 #include <string.h>
    22 #include <strings.h>
    23 #include <unistd.h>
    24 #include <fcntl.h>
    25 #include <dirent.h>
    26 #include <errno.h>
    27 #include <sys/types.h>
    28 #include <sys/time.h>
    29 #ifdef linux
    30 	#include <sys/vfs.h>
    31 #else
    32 	#include <sys/statvfs.h>
    33 #endif
    34 
    35 #ifdef HAVE_SETXATTR
    36 	#include <sys/xattr.h>
    37 #endif
    38 
    39 #include "unionfs.h"
    40 #include "opts.h"
    41 #include "stats.h"
    42 #include "debug.h"
    43 #include "findbranch.h"
    44 #include "general.h"
    45 
    46 #include "unlink.h"
    47 #include "rmdir.h"
    48 #include "readdir.h"
    49 #include "cow.h"
    50 
    51 
    52 static struct fuse_opt unionfs_opts[] = {
    53 	FUSE_OPT_KEY("--help", KEY_HELP),
    54 	FUSE_OPT_KEY("--version", KEY_VERSION),
    55 	FUSE_OPT_KEY("-h", KEY_HELP),
    56 	FUSE_OPT_KEY("-V", KEY_VERSION),
    57 	FUSE_OPT_KEY("stats", KEY_STATS),
    58 	FUSE_OPT_KEY("cow", KEY_COW),
    59 	FUSE_OPT_KEY("noinitgroups", KEY_NOINITGROUPS),
    60 	FUSE_OPT_KEY("statfs_omit_ro", KEY_STATFS_OMIT_RO),
    61 	FUSE_OPT_KEY("chroot=%s,", KEY_CHROOT),
    62 	FUSE_OPT_KEY("max_files=%s", KEY_MAX_FILES),
    63 	FUSE_OPT_END
    64 };
    65 
    66 
    67 static int unionfs_chmod(const char *path, mode_t mode) {
    68 	DBG_IN();
    69 
    70 	int i = find_rw_branch_cow(path);
    71 	if (i == -1) return -errno;
    72 
    73 	char p[PATHLEN_MAX];
    74 	snprintf(p, PATHLEN_MAX, "%s%s", uopt.branches[i].path, path);
    75 
    76 	int res = chmod(p, mode);
    77 	if (res == -1) return -errno;
    78 
    79 	return 0;
    80 }
    81 
    82 static int unionfs_chown(const char *path, uid_t uid, gid_t gid) {
    83 	DBG_IN();
    84 
    85 	int i = find_rw_branch_cow(path);
    86 	if (i == -1) return -errno;
    87 
    88 	char p[PATHLEN_MAX];
    89 	snprintf(p, PATHLEN_MAX, "%s%s", uopt.branches[i].path, path);
    90 
    91 	int res = lchown(p, uid, gid);
    92 	if (res == -1) return -errno;
    93 
    94 	return 0;
    95 }
    96 
    97 /**
    98  * unionfs implementation of the create call
    99  * libfuse will call this to create regular files
   100  */
   101 static int unionfs_create(const char *path, mode_t mode, struct fuse_file_info *fi) {
   102 	DBG_IN();
   103 
   104 	int i = find_rw_branch_cutlast(path);
   105 	if (i == -1) return -errno;
   106 
   107 	char p[PATHLEN_MAX];
   108 	snprintf(p, PATHLEN_MAX, "%s%s", uopt.branches[i].path, path);
   109 
   110 	// NOTE: We should do:
   111 	//       Create the file with mode=0 first, otherwise we might create
   112 	//       a file as root + x-bit + suid bit set, which might be used for
   113 	//       security racing!
   114 	int res = open(p, fi->flags, 0);
   115 	if (res == -1) return -errno;
   116 
   117 	set_owner(p); // no error check, since creating the file succeeded
   118 
   119 	// NOW, that the file has the proper owner we may set the requested mode
   120 	fchmod(res, mode);
   121 
   122 	fi->fh = res;
   123 	remove_hidden(path, i);
   124 
   125 	return 0;
   126 }
   127 
   128 
   129 /**
   130  * flush may be called multiple times for an open file, this must not really
   131  * close the file. This is important if used on a network filesystem like NFS
   132  * which flush the data/metadata on close()
   133  */
   134 static int unionfs_flush(const char *path, struct fuse_file_info *fi) {
   135 	DBG_IN();
   136 
   137 	if (uopt.stats_enabled && strcmp(path, STATS_FILENAME) == 0) return 0;
   138 
   139 	int fd = dup(fi->fh);
   140 
   141 	if (fd == -1) {
   142 		// What to do now?
   143 		if (fsync(fi->fh) == -1) return -EIO;
   144 
   145 		return -errno;
   146 	}
   147 
   148 	int res = close(fd);
   149 	if (res == -1) return -errno;
   150 
   151 	return 0;
   152 }
   153 
   154 /**
   155  * Just a stub. This method is optional and can safely be left unimplemented
   156  */
   157 static int unionfs_fsync(const char *path, int isdatasync, struct fuse_file_info *fi) {
   158 	DBG_IN();
   159 
   160 	if (uopt.stats_enabled && strcmp(path, STATS_FILENAME) == 0) return 0;
   161 
   162 	int res;
   163 	if (isdatasync) {
   164 #if _POSIX_SYNCHRONIZED_IO + 0 > 0
   165 		res = fdatasync(fi->fh);
   166 #else
   167 		res = fsync(fi->fh);
   168 #endif
   169 	} else {
   170 		res = fsync(fi->fh);
   171 	}
   172 
   173 	if (res == -1)  return -errno;
   174 
   175 	return 0;
   176 }
   177 
   178 static int unionfs_getattr(const char *path, struct stat *stbuf) {
   179 	DBG_IN();
   180 
   181 	if (uopt.stats_enabled && strcmp(path, STATS_FILENAME) == 0) {
   182 		memset(stbuf, 0, sizeof(stbuf));
   183 		stbuf->st_mode = S_IFREG | 0444;
   184 		stbuf->st_nlink = 1;
   185 		stbuf->st_size = STATS_SIZE;
   186 		return 0;
   187 	}
   188 
   189 	int i = find_rorw_branch(path);
   190 	if (i == -1) return -errno;
   191 
   192 	char p[PATHLEN_MAX];
   193 	snprintf(p, PATHLEN_MAX, "%s%s", uopt.branches[i].path, path);
   194 
   195 	int res = lstat(p, stbuf);
   196 	if (res == -1) return -errno;
   197 
   198 	/* This is a workaround for broken gnu find implementations. Actually,
   199 	 * n_links is not defined at all for directories by posix. However, it
   200 	 * seems to be common for filesystems to set it to one if the actual value
   201 	 * is unknown. Since nlink_t is unsigned and since these broken implementations
   202 	 * always substract 2 (for . and ..) this will cause an underflow, setting
   203 	 * it to max(nlink_t).
   204 	 */
   205 	if (S_ISDIR(stbuf->st_mode)) stbuf->st_nlink = 1;
   206 
   207 	return 0;
   208 }
   209 
   210 /**
   211  * init method
   212  * called before first access to the filesystem
   213  */
   214 static void * unionfs_init(struct fuse_conn_info *conn) {
   215 	// just to prevent the compiler complaining about unused variables
   216 	(void) conn->max_readahead;
   217 
   218 	// we only now (from unionfs_init) may go into the chroot, since otherwise
   219 	// fuse_main() will fail to open /dev/fuse and to call mount
   220 	if (uopt.chroot) {
   221 		int res = chroot(uopt.chroot);
   222 		if (res) {
   223 			usyslog("Chdir to %s failed: %s ! Aborting!\n",
   224 				 uopt.chroot, strerror(errno));
   225 			exit(1);
   226 		}
   227 	}
   228 	return NULL;
   229 }
   230 
   231 static int unionfs_link(const char *from, const char *to) {
   232 	DBG_IN();
   233 
   234 	// hardlinks do not work across different filesystems so we need a copy of from first
   235 	int i = find_rw_branch_cow(from);
   236 	if (i == -1) return -errno;
   237 
   238 	// FIXME, we actually MUST COW to i
   239 	int j = find_rw_branch_cutlast(to);
   240 	if (j == -1) return -errno;
   241 
   242 	char f[PATHLEN_MAX], t[PATHLEN_MAX];
   243 	snprintf(f, PATHLEN_MAX, "%s%s", uopt.branches[i].path, from);
   244 	snprintf(t, PATHLEN_MAX, "%s%s", uopt.branches[j].path, to);
   245 
   246 	int res = link(f, t);
   247 	if (res == -1) return -errno;
   248 
   249 	// no need for set_owner(), since owner and permissions are copied over by link()
   250 
   251 	remove_hidden(to, i); // remove hide file (if any)
   252 	return 0;
   253 }
   254 
   255 /**
   256  * unionfs mkdir() implementation
   257  *
   258  * NOTE: Never delete whiteouts directories here, since this will just
   259  *       make already hidden sub-branches visible again.
   260  */
   261 static int unionfs_mkdir(const char *path, mode_t mode) {
   262 	DBG_IN();
   263 
   264 	int i = find_rw_branch_cutlast(path);
   265 	if (i == -1) return -errno;
   266 
   267 	char p[PATHLEN_MAX];
   268 	snprintf(p, PATHLEN_MAX, "%s%s", uopt.branches[i].path, path);
   269 
   270 	int res = mkdir(p, 0);
   271 	if (res == -1) return -errno;
   272 
   273 	set_owner(p); // no error check, since creating the file succeeded
   274         // NOW, that the file has the proper owner we may set the requested mode
   275         chmod(p, mode);
   276 
   277 	return 0;
   278 }
   279 
   280 static int unionfs_mknod(const char *path, mode_t mode, dev_t rdev) {
   281 	DBG_IN();
   282 
   283 	int i = find_rw_branch_cutlast(path);
   284 	if (i == -1) return -errno;
   285 
   286 	char p[PATHLEN_MAX];
   287 	snprintf(p, PATHLEN_MAX, "%s%s", uopt.branches[i].path, path);
   288 
   289 	int file_type = mode & S_IFMT;
   290 	int file_perm = mode & (S_PROT_MASK);
   291 
   292 	int res = -1;
   293 	if ((file_type) == S_IFREG) {
   294 		// under FreeBSD, only the super-user can create ordinary files using mknod
   295 		// Actually this workaround should not be required any more
   296 		// since we now have the unionfs_create() method
   297 		// So can we remove it?
   298 
   299 		usyslog (LOG_INFO, "deprecated mknod workaround, tell the unionfs-fuse authors if you see this!\n");
   300 
   301 		res = creat(p, 0);
   302 		if (res > 0 && close(res) == -1) usyslog(LOG_WARNING, "Warning, cannot close file\n");
   303 	} else {
   304 		res = mknod(p, file_type, rdev);
   305 	}
   306 
   307 	if (res == -1) return -errno;
   308 
   309 	set_owner(p); // no error check, since creating the file succeeded
   310 	// NOW, that the file has the proper owner we may set the requested mode
   311 	chmod(p, file_perm);
   312 
   313 	remove_hidden(path, i);
   314 
   315 	return 0;
   316 }
   317 
   318 static int unionfs_open(const char *path, struct fuse_file_info *fi) {
   319 	DBG_IN();
   320 
   321 	if (uopt.stats_enabled && strcmp(path, STATS_FILENAME) == 0) {
   322 		if ((fi->flags & 3) == O_RDONLY) {
   323 			// This makes exec() fail
   324 			//fi->direct_io = 1;
   325 			return 0;
   326 		}
   327 		return -EACCES;
   328 	}
   329 
   330 	int i;
   331 	if (fi->flags & (O_WRONLY | O_RDWR)) {
   332 		i = find_rw_branch_cutlast(path);
   333 	} else {
   334 		i = find_rorw_branch(path);
   335 	}
   336 
   337 	if (i == -1) return -errno;
   338 
   339 	char p[PATHLEN_MAX];
   340 	snprintf(p, PATHLEN_MAX, "%s%s", uopt.branches[i].path, path);
   341 
   342 	int fd = open(p, fi->flags);
   343 	if (fd == -1) return -errno;
   344 
   345 	if (fi->flags & (O_WRONLY | O_RDWR)) {
   346 		// There might have been a hide file, but since we successfully
   347 		// wrote to the real file, a hide file must not exist anymore
   348 		remove_hidden(path, i);
   349 	}
   350 
   351 	// This makes exec() fail
   352 	//fi->direct_io = 1;
   353 	fi->fh = (unsigned long)fd;
   354 
   355 	return 0;
   356 }
   357 
   358 static int unionfs_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi) {
   359 	DBG_IN();
   360 
   361 	if (uopt.stats_enabled && strcmp(path, STATS_FILENAME) == 0) {
   362 		char out[STATS_SIZE] = "";
   363 		stats_sprint(&stats, out);
   364 
   365 		off_t s = size;
   366 		if (offset < (off_t) strlen(out)) {
   367 			if (s > (off_t) strlen(out) - offset) s = strlen(out)-offset;
   368 			memcpy(buf, out+offset, s);
   369 		} else {
   370 			s = 0;
   371 		}
   372 
   373 		return s;
   374 	}
   375 
   376 	int res = pread(fi->fh, buf, size, offset);
   377 
   378 	if (res == -1) return -errno;
   379 
   380 	if (uopt.stats_enabled) stats_add_read(&stats, size);
   381 
   382 	return res;
   383 }
   384 
   385 static int unionfs_readlink(const char *path, char *buf, size_t size) {
   386 	DBG_IN();
   387 
   388 	int i = find_rorw_branch(path);
   389 	if (i == -1) return -errno;
   390 
   391 	char p[PATHLEN_MAX];
   392 	snprintf(p, PATHLEN_MAX, "%s%s", uopt.branches[i].path, path);
   393 
   394 	int res = readlink(p, buf, size - 1);
   395 
   396 	if (res == -1) return -errno;
   397 
   398 	buf[res] = '\0';
   399 
   400 	return 0;
   401 }
   402 
   403 static int unionfs_release(const char *path, struct fuse_file_info *fi) {
   404 	DBG_IN();
   405 
   406 	if (uopt.stats_enabled && strcmp(path, STATS_FILENAME) == 0) return 0;
   407 
   408 	int res = close(fi->fh);
   409 	if (res == -1) return -errno;
   410 
   411 	return 0;
   412 }
   413 
   414 /**
   415  * unionfs rename function
   416  * TODO: If we rename a directory on a read-only branch, we need to copy over
   417  *       all files to the renamed directory on the read-write branch.
   418  */
   419 static int unionfs_rename(const char *from, const char *to) {
   420 	DBG_IN();
   421 
   422 	bool is_dir = false; // is 'from' a file or directory
   423 
   424 	int j = find_rw_branch_cutlast(to);
   425 	if (j == -1) return -errno;
   426 
   427 	int i = find_rorw_branch(from);
   428 	if (i == -1) return -errno;
   429 
   430 	if (!uopt.branches[i].rw) {
   431 		i = find_rw_branch_cow(from);
   432 		if (i == -1) return -errno;
   433 	}
   434 
   435 	if (i != j) {
   436 		usyslog(LOG_ERR, "%s: from and to are on different writable branches %d vs %d, which"
   437 		       "is not supported yet.\n", __func__, i, j);
   438 		return -EXDEV;
   439 	}
   440 
   441 	char f[PATHLEN_MAX], t[PATHLEN_MAX];
   442 	snprintf(f, PATHLEN_MAX, "%s%s", uopt.branches[i].path, from);
   443 	snprintf(t, PATHLEN_MAX, "%s%s", uopt.branches[i].path, to);
   444 
   445 	filetype_t ftype = path_is_dir(f);
   446 	if (ftype == NOT_EXISTING)
   447 		return -ENOENT;
   448 	else if (ftype == IS_DIR)
   449 		is_dir = true;
   450 
   451 	int res;
   452 	if (!uopt.branches[i].rw) {
   453 		// since original file is on a read-only branch, we copied the from file to a writable branch,
   454 		// but since we will rename from, we also need to hide the from file on the read-only branch
   455 		if (is_dir)
   456 			res = hide_dir(from, i);
   457 		else
   458 			res = hide_file(from, i);
   459 		if (res) return -errno;
   460 	}
   461 
   462 	res = rename(f, t);
   463 
   464 	if (res == -1) {
   465 		int err = errno; // unlink() might overwrite errno
   466 		// if from was on a read-only branch we copied it, but now rename failed so we need to delete it
   467 		if (!uopt.branches[i].rw) {
   468 			if (unlink(f))
   469 				usyslog(LOG_ERR, "%s: cow of %s succeeded, but rename() failed and now "
   470 				       "also unlink()  failed\n", __func__, from);
   471 
   472 			if (remove_hidden(from, i))
   473 				usyslog(LOG_ERR, "%s: cow of %s succeeded, but rename() failed and now "
   474 				       "also removing the whiteout  failed\n", __func__, from);
   475 		}
   476 		return -err;
   477 	}
   478 
   479 	if (uopt.branches[i].rw) {
   480 		// A lower branch still *might* have a file called 'from', we need to delete this.
   481 		// We only need to do this if we have been on a rw-branch, since we created
   482 		// a whiteout for read-only branches anyway.
   483 		if (is_dir)
   484 			maybe_whiteout(from, i, WHITEOUT_DIR);
   485 		else
   486 			maybe_whiteout(from, i, WHITEOUT_FILE);
   487 	}
   488 
   489 	remove_hidden(to, i); // remove hide file (if any)
   490 	return 0;
   491 }
   492 
   493 /**
   494  * Wrapper function to convert the result of statfs() to statvfs()
   495  * libfuse uses statvfs, since it conforms to POSIX. Unfortunately,
   496  * glibc's statvfs parses /proc/mounts, which then results in reading
   497  * the filesystem itself again - which would result in a deadlock.
   498  * TODO: BSD/MacOSX
   499  */
   500 static int statvfs_local(const char *path, struct statvfs *stbuf) {
   501 #ifdef linux
   502 	/* glibc's statvfs walks /proc/mounts and stats entries found there
   503 	 * in order to extract their mount flags, which may deadlock if they
   504 	 * are mounted under the unionfs. As a result, we have to do this
   505 	 * ourselves.
   506 	 */
   507 	struct statfs stfs;
   508 	int res = statfs(path, &stfs);
   509 	if (res == -1) return res;
   510 
   511 	memset(stbuf, 0, sizeof(*stbuf));
   512 	stbuf->f_bsize = stfs.f_bsize;
   513 	if (stfs.f_frsize)
   514 		stbuf->f_frsize = stfs.f_frsize;
   515 	else
   516 		stbuf->f_frsize = stfs.f_bsize;
   517 	stbuf->f_blocks = stfs.f_blocks;
   518 	stbuf->f_bfree = stfs.f_bfree;
   519 	stbuf->f_bavail = stfs.f_bavail;
   520 	stbuf->f_files = stfs.f_files;
   521 	stbuf->f_ffree = stfs.f_ffree;
   522 	stbuf->f_favail = stfs.f_ffree; /* nobody knows */
   523 	stbuf->f_fsid = stfs.f_fsid.__val[0];
   524 
   525 	/* We don't worry about flags, exactly because this would
   526 	 * require reading /proc/mounts, and avoiding that and the
   527 	 * resulting deadlocks is exactly what we're trying to avoid
   528 	 * by doing this rather than using statvfs.
   529 	 */
   530 	stbuf->f_flag = 0;
   531 	stbuf->f_namemax = stfs.f_namelen;
   532 
   533 	return 0;
   534 #else
   535 	return statvfs(path, stbuf);
   536 #endif
   537 }
   538 
   539 
   540 
   541 /**
   542  * statvs implementation
   543  * TODO: fsid: It would be optimal, if we would store a once generated random
   544  *	       fsid. But what if the same branch with the fsid used for different 
   545  *	       unions? Is the present way ok for most cases?
   546  */
   547 static int unionfs_statfs(const char *path, struct statvfs *stbuf) {
   548 	(void)path;
   549 
   550 	DBG_IN();
   551 
   552 	int first = 1;
   553 
   554 	dev_t devno[uopt.nbranches];
   555 
   556 	int i = 0;
   557 	for (i = 0; i < uopt.nbranches; i++) {
   558 		struct statvfs stb;
   559 		int res = statvfs_local(uopt.branches[i].path, &stb);
   560 		if (res == -1) continue;
   561 
   562 		struct stat st;
   563 		res = stat(uopt.branches[i].path, &st);
   564 		if (res == -1) continue;
   565 		devno[i] = st.st_dev;
   566 
   567 		if (first) {
   568 			memcpy(stbuf, &stb, sizeof(*stbuf));
   569 			first = 0;
   570 			stbuf->f_fsid = stb.f_fsid << 8;
   571 			continue;
   572 		}
   573 
   574 		// Eliminate same devices
   575 		int j = 0;
   576 		for (j = 0; j < i; j ++) {
   577 			if (st.st_dev == devno[j]) break;
   578 		}
   579 
   580 		if (j == i) {
   581 			// Filesystem can have different block sizes -> normalize to first's block size
   582 			double ratio = (double)stb.f_bsize / (double)stbuf->f_bsize;
   583 
   584 			if (uopt.branches[i].rw) {
   585 				stbuf->f_blocks += stb.f_blocks * ratio;
   586 				stbuf->f_bfree += stb.f_bfree * ratio;
   587 				stbuf->f_bavail += stb.f_bavail * ratio;
   588 
   589 				stbuf->f_files += stb.f_files;
   590 				stbuf->f_ffree += stb.f_ffree;
   591 				stbuf->f_favail += stb.f_favail;
   592 			} else if (!uopt.statfs_omit_ro) {
   593 				// omitting the RO branches is not correct regarding
   594 				// the block counts but it actually fixes the 
   595 				// percentage of free space. so, let the user decide.
   596 				stbuf->f_blocks += stb.f_blocks * ratio;
   597 				stbuf->f_files  += stb.f_files;
   598 			}
   599 
   600 			if (!(stb.f_flag & ST_RDONLY)) stbuf->f_flag &= ~ST_RDONLY;
   601 			if (!(stb.f_flag & ST_NOSUID)) stbuf->f_flag &= ~ST_NOSUID;
   602 
   603 			if (stb.f_namemax < stbuf->f_namemax) stbuf->f_namemax = stb.f_namemax;
   604 
   605 			// we don't care about overflows, the fsid just should be different
   606 			// from other fsids
   607 			stbuf->f_fsid += stb.f_fsid;
   608 		}
   609 	}
   610 
   611 	return 0;
   612 }
   613 
   614 static int unionfs_symlink(const char *from, const char *to) {
   615 	DBG_IN();
   616 
   617 	int i = find_rw_branch_cutlast(to);
   618 	if (i == -1) return -errno;
   619 
   620 	char t[PATHLEN_MAX];
   621 	snprintf(t, PATHLEN_MAX, "%s%s", uopt.branches[i].path, to);
   622 
   623 	int res = symlink(from, t);
   624 	if (res == -1) return -errno;
   625 
   626 	set_owner(t); // no error check, since creating the file succeeded
   627 
   628 	remove_hidden(to, i); // remove hide file (if any)
   629 	return 0;
   630 }
   631 
   632 static int unionfs_truncate(const char *path, off_t size) {
   633 	DBG_IN();
   634 
   635 	int i = find_rw_branch_cow(path);
   636 	if (i == -1) return -errno;
   637 
   638 	char p[PATHLEN_MAX];
   639 	snprintf(p, PATHLEN_MAX, "%s%s", uopt.branches[i].path, path);
   640 
   641 	int res = truncate(p, size);
   642 
   643 	if (res == -1) return -errno;
   644 
   645 	return 0;
   646 }
   647 
   648 static int unionfs_utimens(const char *path, const struct timespec ts[2]) {
   649 	DBG_IN();
   650 
   651 	if (uopt.stats_enabled && strcmp(path, STATS_FILENAME) == 0) return 0;
   652 
   653 	int i = find_rw_branch_cow(path);
   654 	if (i == -1) return -errno;
   655 
   656 	char p[PATHLEN_MAX];
   657 	snprintf(p, PATHLEN_MAX, "%s%s", uopt.branches[i].path, path);
   658 
   659 	struct timeval tv[2];
   660         tv[0].tv_sec  = ts[0].tv_sec;
   661         tv[0].tv_usec = ts[0].tv_nsec / 1000;
   662         tv[1].tv_sec  = ts[1].tv_sec;
   663         tv[1].tv_usec = ts[1].tv_nsec / 1000;
   664 
   665 	int res = utimes(p, tv);
   666 
   667 	if (res == -1) return -errno;
   668 
   669 	return 0;
   670 }
   671 
   672 static int unionfs_write(const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *fi) {
   673 	(void)path;
   674 
   675 	DBG_IN();
   676 
   677 	int res = pwrite(fi->fh, buf, size, offset);
   678 	if (res == -1) return -errno;
   679 
   680 	if (uopt.stats_enabled) stats_add_written(&stats, size);
   681 
   682 	return res;
   683 }
   684 
   685 #ifdef HAVE_SETXATTR
   686 static int unionfs_getxattr(const char *path, const char *name, char *value, size_t size) {
   687 	DBG_IN();
   688 
   689 	int i = find_rorw_branch(path);
   690 	if (i == -1) return -errno;
   691 
   692 	char p[PATHLEN_MAX];
   693 	snprintf(p, PATHLEN_MAX, "%s%s", uopt.branches[i].path, path);
   694 
   695 	int res = lgetxattr(p, name, value, size);
   696 
   697 	if (res == -1) return -errno;
   698 
   699 	return res;
   700 }
   701 
   702 static int unionfs_listxattr(const char *path, char *list, size_t size) {
   703 	DBG_IN();
   704 
   705 	int i = find_rorw_branch(path);
   706 	if (i == -1) return -errno;
   707 
   708 	char p[PATHLEN_MAX];
   709 	snprintf(p, PATHLEN_MAX, "%s%s", uopt.branches[i].path, path);
   710 
   711 	int res = llistxattr(p, list, size);
   712 
   713 	if (res == -1) return -errno;
   714 
   715 	return res;
   716 }
   717 
   718 static int unionfs_removexattr(const char *path, const char *name) {
   719 	DBG_IN();
   720 
   721 	int i = find_rw_branch_cow(path);
   722 	if (i == -1) return -errno;
   723 
   724 	char p[PATHLEN_MAX];
   725 	snprintf(p, PATHLEN_MAX, "%s%s", uopt.branches[i].path, path);
   726 
   727 	int res = lremovexattr(p, name);
   728 
   729 	if (res == -1) return -errno;
   730 
   731 	return res;
   732 }
   733 
   734 static int unionfs_setxattr(const char *path, const char *name, const char *value, size_t size, int flags) {
   735 	DBG_IN();
   736 
   737 	int i = find_rw_branch_cow(path);
   738 	if (i == -1) return -errno;
   739 
   740 	char p[PATHLEN_MAX];
   741 	snprintf(p, PATHLEN_MAX, "%s%s", uopt.branches[i].path, path);
   742 
   743 	int res = lsetxattr(p, name, value, size, flags);
   744 
   745 	if (res == -1) return -errno;
   746 
   747 	return res;
   748 }
   749 #endif // HAVE_SETXATTR
   750 
   751 static struct fuse_operations unionfs_oper = {
   752 	.chmod	= unionfs_chmod,
   753 	.chown	= unionfs_chown,
   754 	.create 	= unionfs_create,
   755 	.flush	= unionfs_flush,
   756 	.fsync	= unionfs_fsync,
   757 	.getattr	= unionfs_getattr,
   758 	.init		= unionfs_init,
   759 	.link		= unionfs_link,
   760 	.mkdir	= unionfs_mkdir,
   761 	.mknod	= unionfs_mknod,
   762 	.open	= unionfs_open,
   763 	.read	= unionfs_read,
   764 	.readlink	= unionfs_readlink,
   765 	.readdir	= unionfs_readdir,
   766 	.release	= unionfs_release,
   767 	.rename	= unionfs_rename,
   768 	.rmdir	= unionfs_rmdir,
   769 	.statfs	= unionfs_statfs,
   770 	.symlink	= unionfs_symlink,
   771 	.truncate	= unionfs_truncate,
   772 	.unlink	= unionfs_unlink,
   773 	.utimens	= unionfs_utimens,
   774 	.write	= unionfs_write,
   775 #ifdef HAVE_SETXATTR
   776 	.getxattr		= unionfs_getxattr,
   777 	.listxattr		= unionfs_listxattr,
   778 	.removexattr	= unionfs_removexattr,
   779 	.setxattr		= unionfs_setxattr,
   780 #endif
   781 };
   782 
   783 int main(int argc, char *argv[]) {
   784 	struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
   785 
   786 	int res = debug_init();
   787 	if (res != 0) return res;
   788 
   789 	uopt_init();
   790 
   791 	if (fuse_opt_parse(&args, NULL, unionfs_opts, unionfs_opt_proc) == -1) return 1;
   792 
   793 	if (!uopt.doexit) {
   794 		if (uopt.nbranches == 0) {
   795 			printf("You need to specify at least one branch!\n");
   796 			return 1;
   797 		}
   798 
   799 		if (uopt.stats_enabled) stats_init(&stats);
   800 	}
   801 
   802 	// enable fuse permission checks, we need to set this, even we we are
   803 	// not root, since we don't have our own access() function
   804 	if (fuse_opt_add_arg(&args, "-odefault_permissions")) {
   805 		fprintf(stderr, "Severe failure, can't enable permssion checks, aborting!\n");
   806 		exit(1);
   807 	}
   808 	unionfs_post_opts();
   809 
   810 	umask(0);
   811 	res = fuse_main(args.argc, args.argv, &unionfs_oper, NULL);
   812 	return uopt.doexit ? uopt.retval : res;
   813 }