Giuseppe Scrivano ddce687dfd
containerfs: rewrite it to use *at functions
do not use absolute paths when accessing files.  Use relative paths to
the layers root.  As part of the refactoring, also drop any cache of
the lower layers, working more similarly as overlay in the kernel.

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2018-07-03 20:28:36 +02:00

2596 lines
55 KiB
C

/* containers: Overlay Filesystem in Userspace
Copyright (C) 2018 Giuseppe Scrivano <giuseppe@scrivano.org>
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
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 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define _GNU_SOURCE
#define FUSE_USE_VERSION 31
#define _FILE_OFFSET_BITS 64
#include <config.h>
#include <fuse_lowlevel.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#include <limits.h>
#include <dirent.h>
#include <assert.h>
#include <errno.h>
#include <err.h>
#include <error.h>
#include <inttypes.h>
#include <fcntl.h>
#include <hash.h>
#include <sys/statvfs.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <fts.h>
#include <sys/sysmacros.h>
#include <sys/xattr.h>
#ifndef RENAME_EXCHANGE
# define RENAME_EXCHANGE (1 << 1)
# define RENAME_NOREPLACE (1 << 2)
#endif
#define ATTR_TIMEOUT 1000000000.0
#define ENTRY_TIMEOUT 1000000000.0
#define NODE_TO_INODE(x) ((fuse_ino_t) x)
#if defined(__GNUC__) && (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 6) && !defined __cplusplus
_Static_assert (sizeof (fuse_ino_t) >= sizeof (uintptr_t),
"fuse_ino_t too small to hold uintptr_t values!");
#else
struct _uintptr_to_must_hold_fuse_ino_t_dummy_struct
{
unsigned _uintptr_to_must_hold_fuse_ino_t:
((sizeof (fuse_ino_t) >= sizeof (uintptr_t)) ? 1 : -1);
};
#endif
struct lo_layer
{
struct lo_layer *next;
char *path;
int fd;
int low;
};
struct lo_mapping
{
struct lo_mapping *next;
unsigned int host;
unsigned int to;
unsigned int len;
};
struct lo_node
{
struct lo_node *parent;
Hash_table *children;
struct lo_layer *layer;
char *path;
char *name;
int lookups;
ino_t ino;
int rmfrom;
unsigned int present_lowerdir : 1;
unsigned int dirty : 1;
unsigned int do_unlink : 1;
unsigned int do_rmdir : 1;
unsigned int hidden : 1;
unsigned int whiteout : 1;
};
struct lo_data
{
struct fuse_session *se;
int debug;
char *uid_str;
char *gid_str;
struct lo_mapping *uid_mappings;
struct lo_mapping *gid_mappings;
char *lowerdir;
char *context;
char *upperdir;
char *workdir;
int workdir_fd;
struct lo_layer *layers;
struct lo_node *root_lower;
struct lo_node *root_upper;
};
static const struct fuse_opt lo_opts[] = {
{"context=%s",
offsetof (struct lo_data, context), 0},
{"lowerdir=%s",
offsetof (struct lo_data, lowerdir), 0},
{"upperdir=%s",
offsetof (struct lo_data, upperdir), 0},
{"workdir=%s",
offsetof (struct lo_data, workdir), 0},
{"uid=%s",
offsetof (struct lo_data, uid_str), 0},
{"gid=%s",
offsetof (struct lo_data, gid_str), 0},
FUSE_OPT_END
};
static struct lo_data *
lo_data (fuse_req_t req)
{
return (struct lo_data *) fuse_req_userdata (req);
}
static struct lo_mapping *
read_mappings (const char *str)
{
char *buf = NULL, *saveptr = NULL, *it, *endptr;
struct lo_mapping *tmp, *ret = NULL;
unsigned int a, b, c;
int state = 0;
buf = alloca (strlen (str) + 1);
strcpy (buf, str);
for (it = strtok_r (buf, ":", &saveptr); it; it = strtok_r (NULL, ":", &saveptr))
{
switch (state)
{
case 0:
a = strtol (it, &endptr, 10);
if (*endptr != 0)
error (EXIT_FAILURE, 0, "invalid mapping specified: %s", str);
state++;
break;
case 1:
b = strtol (it, &endptr, 10);
if (*endptr != 0)
error (EXIT_FAILURE, 0, "invalid mapping specified: %s", str);
state++;
break;
case 2:
c = strtol (it, &endptr, 10);
if (*endptr != 0)
error (EXIT_FAILURE, 0, "invalid mapping specified: %s", str);
state = 0;
tmp = malloc (sizeof (*tmp));
if (tmp == NULL)
return NULL;
tmp->next = ret;
tmp->host = a;
tmp->to = b;
tmp->len = c;
ret = tmp;
break;
}
}
if (state != 0)
error (EXIT_FAILURE, 0, "invalid mapping specified: %s", str);
return ret;
}
static void
free_mapping (struct lo_mapping *it)
{
struct lo_mapping *next = NULL;
for (; it; it = next)
{
next = it->next;
free (it);
}
}
/* Useful in a gdb session. */
void
dump_directory (struct lo_node *node)
{
struct lo_node *it;
if (node->children == NULL)
return;
for (it = hash_get_first (node->children); it; it = hash_get_next (node->children, it))
printf ("ENTRY: %s (%s)\n", it->name, it->path);
}
static bool
lo_debug (fuse_req_t req)
{
return lo_data (req)->debug != 0;
}
static void
lo_init (void *userdata, struct fuse_conn_info *conn)
{
conn->want |= FUSE_CAP_DONT_MASK | FUSE_CAP_SPLICE_READ | FUSE_CAP_SPLICE_MOVE;
conn->want &= ~FUSE_CAP_PARALLEL_DIROPS;
}
static struct lo_layer *
get_upper_layer (struct lo_data *lo)
{
return lo->layers;
}
static inline bool
node_dirp (struct lo_node *n)
{
return n->children != NULL;
}
static int
node_dirfd (struct lo_node *n)
{
if (n->hidden)
return n->rmfrom;
return n->layer->fd;
}
static bool
has_prefix (const char *str, const char *pref)
{
while (1)
{
if (*pref == '\0')
return true;
if (*str == '\0')
return false;
if (*pref != *str)
return false;
str++;
pref++;
}
return false;
}
static int
hide_node (struct lo_data *lo, struct lo_node *node, bool unlink_src)
{
char dest[PATH_MAX];
char *newpath;
static unsigned long counter = 1;
asprintf (&newpath, "%lu", counter++);
if (newpath == NULL)
{
unlink (dest);
return -1;
}
/* Might be leftover from a previous run. */
unlinkat (lo->workdir_fd, newpath, 0);
if (unlink_src)
{
if (renameat (node_dirfd (node), node->path, lo->workdir_fd, newpath) < 0)
{
free (newpath);
return -1;
}
}
else
{
if (linkat (node_dirfd (node), node->path, lo->workdir_fd, newpath, 0) < 0)
{
free (newpath);
return -1;
}
}
node->rmfrom = lo->workdir_fd;
free (node->path);
node->path = newpath;
node->hidden = 1;
node->parent = NULL;
if (node_dirp (node))
node->do_rmdir = 1;
else
node->do_unlink = 1;
return 0;
}
static unsigned int
find_mapping (unsigned int id, struct lo_mapping *mapping, bool direct)
{
if (mapping == NULL)
return id;
for (; mapping; mapping = mapping->next)
{
if (direct)
{
if (id >= mapping->host && id < mapping->host + mapping->len)
return mapping->to + (id - mapping->host);
}
else
{
if (id >= mapping->to && id < mapping->to + mapping->len)
return mapping->host + (id - mapping->to);
}
}
return 65534;
}
static uid_t
get_uid (struct lo_data *data, uid_t id)
{
return find_mapping (id, data->uid_mappings, false);
}
static uid_t
get_gid (struct lo_data *data, gid_t id)
{
return find_mapping (id, data->gid_mappings, false);
}
static int
rpl_stat (fuse_req_t req, struct lo_node *node, struct stat *st)
{
int ret;
struct lo_data *data = lo_data (req);
ret = fstatat (node_dirfd (node), node->path, st, AT_SYMLINK_NOFOLLOW);
if (ret < 0)
return ret;
st->st_uid = find_mapping (st->st_uid, data->uid_mappings, true);
st->st_gid = find_mapping (st->st_gid, data->gid_mappings, true);
st->st_ino = node->ino;
if (ret == 0 && node_dirp (node))
{
struct lo_node *it;
st->st_nlink = 2;
for (it = hash_get_first (node->children); it; it = hash_get_next (node->children, it))
{
if (node_dirp (it))
st->st_nlink++;
}
}
return ret;
}
static void
node_mark_all_free (void *p)
{
struct lo_node *it, *n = (struct lo_node *) p;
n->lookups = 0;
if (n->children)
{
for (it = hash_get_first (n->children); it; it = hash_get_next (n->children, it))
node_mark_all_free (it);
}
}
static void
node_free (void *p)
{
struct lo_node *n = (struct lo_node *) p;
if (n->parent)
{
if (hash_lookup (n->parent->children, n) == n)
hash_delete (n->parent->children, n);
n->parent->dirty = 1;
n->parent = NULL;
}
if (n->lookups > 0)
return;
if (n->children)
{
struct lo_node *it;
for (it = hash_get_first (n->children); it; it = hash_get_next (n->children, it))
it->parent = NULL;
hash_free (n->children);
n->children = NULL;
}
if (n->do_unlink)
unlinkat (n->rmfrom, n->path, 0);
if (n->do_rmdir)
unlinkat (n->rmfrom, n->path, AT_REMOVEDIR);
free (n->name);
free (n->path);
free (n);
return;
}
static void
do_forget (fuse_ino_t ino, uint64_t nlookup)
{
struct lo_node *n;
if (ino == FUSE_ROOT_ID)
return;
n = (struct lo_node *) ino;
n->lookups -= nlookup;
if (n->lookups <= 0)
node_free (n);
}
static void
lo_forget (fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
{
if (lo_debug (req))
fprintf (stderr, "lo_forget(ino=%" PRIu64 ", nlookup=%lu)\n",
ino, nlookup);
do_forget (ino, nlookup);
fuse_reply_none (req);
}
static size_t
node_hasher (const void *p, size_t s)
{
struct lo_node *n = (struct lo_node *) p;
return hash_string (n->name, s);
}
static bool
node_compare (const void *n1, const void *n2)
{
struct lo_node *node1 = (struct lo_node *) n1;
struct lo_node *node2 = (struct lo_node *) n2;
return strcmp (node1->name, node2->name) == 0 ? true : false;
}
static struct lo_node *
make_whiteout_node (const char *name)
{
struct lo_node *ret = calloc (1, sizeof (*ret));
if (ret == NULL)
{
errno = ENOMEM;
return NULL;
}
ret->name = strdup (name);
if (ret->name == NULL)
{
free (ret);
errno = ENOMEM;
return NULL;
}
ret->whiteout = 1;
return ret;
}
static struct lo_node *
make_lo_node (const char *path, struct lo_layer *layer, const char *name, ino_t ino, bool dir_p)
{
struct lo_node *ret = malloc (sizeof (*ret));
if (ret == NULL)
{
errno = ENOMEM;
return NULL;
}
ret->parent = NULL;
ret->lookups = 0;
ret->do_unlink = 0;
ret->hidden = 0;
ret->do_rmdir = 0;
ret->whiteout = 0;
ret->layer = layer;
ret->ino = ino;
ret->present_lowerdir = 0;
ret->name = strdup (name);
ret->rmfrom = 0;
ret->dirty = 1;
if (ret->name == NULL)
{
free (ret);
errno = ENOMEM;
return NULL;
}
if (has_prefix (path, "./") && path[2])
path += 2;
ret->path = strdup (path);
if (ret->path == NULL)
{
free (ret->name);
free (ret);
errno = ENOMEM;
return NULL;
}
if (!dir_p)
ret->children = NULL;
else
{
ret->children = hash_initialize (10, NULL, node_hasher, node_compare, node_free);
if (ret->children == NULL)
{
free (ret->path);
free (ret->name);
free (ret);
errno = ENOMEM;
return NULL;
}
}
if (ret->ino == 0)
{
struct stat st;
struct lo_layer *it;
for (it = layer; it; it = it->next)
{
if (fstatat (it->fd, ret->path, &st, AT_SYMLINK_NOFOLLOW) == 0)
ret->ino = st.st_ino;
}
}
return ret;
}
static struct lo_node *
insert_node (struct lo_node *parent, struct lo_node *item, bool replace)
{
struct lo_node *old = NULL, *prev_parent = item->parent;
int ret;
if (prev_parent)
{
if (hash_lookup (prev_parent->children, item) == item)
hash_delete (prev_parent->children, item);
}
if (replace)
{
old = hash_delete (parent->children, item);
if (old)
node_free (old);
}
ret = hash_insert_if_absent (parent->children, item, (const void **) &old);
if (ret < 0)
{
node_free (item);
errno = ENOMEM;
return NULL;
}
if (ret == 0)
{
node_free (item);
return old;
}
item->parent = parent;
return item;
}
static const char *
get_whiteout_name (const char *name, struct stat *st)
{
if (has_prefix (name, ".wh."))
return name + 4;
if ((st->st_mode & S_IFMT) == S_IFCHR
&& major (st->st_rdev) == 0
&& minor (st->st_rdev) == 0)
return name;
return NULL;
}
static struct lo_node *
load_dir (struct lo_data *lo, struct lo_node *n, struct lo_layer *layer, char *path, char *name)
{
DIR *dp;
struct dirent *dent;
struct stat st;
struct lo_layer *it;
if (!n)
{
n = make_lo_node (path, layer, name, 0, true);
if (n == NULL)
return NULL;
}
for (it = lo->layers; it; it = it->next)
{
int fd = openat (it->fd, path, O_DIRECTORY);
if (fd < 0)
continue;
dp = fdopendir (fd);
if (dp == NULL)
{
close (fd);
continue;
}
while (((dent = readdir (dp)) != NULL))
{
struct lo_node key;
const char *wh;
char path[PATH_MAX + 1];
struct lo_node *child = NULL;
key.name = dent->d_name;
if ((strcmp (dent->d_name, ".") == 0) || strcmp (dent->d_name, "..") == 0)
continue;
if (fstatat (fd, dent->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0)
{
closedir (dp);
return NULL;
}
child = hash_lookup (n->children, &key);
if (child)
{
/* Update the ino with the lowest one found. Ignore if the file
was deleted by intermediate layers, we only need to keep the same
inode after a copyup. */
child->ino = st.st_ino;
child->present_lowerdir = 1;
continue;
}
wh = get_whiteout_name (dent->d_name, &st);
if (wh)
{
child = make_whiteout_node (wh);
if (child == NULL)
{
errno = ENOMEM;
closedir (dp);
return NULL;
}
}
else
{
bool dirp = st.st_mode & S_IFDIR;
sprintf (path, "%s/%s", n->path, dent->d_name);
child = make_lo_node (path, it, dent->d_name, st.st_ino, dirp);
if (child == NULL)
{
errno = ENOMEM;
closedir (dp);
return NULL;
}
}
if (insert_node (n, child, false) == NULL)
{
errno = ENOMEM;
return NULL;
}
}
closedir (dp);
}
return n;
}
static void
free_layers (struct lo_layer *layers)
{
if (layers == NULL)
return;
free_layers (layers->next);
free (layers->path);
if (layers->fd >= 0)
close (layers->fd);
free (layers);
}
static struct lo_layer *
read_dirs (char *path, bool low, struct lo_layer *layers)
{
char *buf = NULL, *saveptr = NULL, *it;
if (path == NULL)
return NULL;
buf = strdup (path);
if (buf == NULL)
return NULL;
for (it = strtok_r (path, ":", &saveptr); it; it = strtok_r (NULL, ":", &saveptr))
{
char full_path[PATH_MAX + 1];
struct lo_layer *l = NULL;
if (realpath (it, full_path) < 0)
return NULL;
l = malloc (sizeof (*l));
if (l == NULL)
{
free_layers (layers);
return NULL;
}
l->path = strdup (full_path);
if (l->path == NULL)
{
free (l);
free_layers (layers);
return NULL;
}
l->fd = open (l->path, O_DIRECTORY);
if (l->fd < 0)
{
free (l->path);
free (l);
free_layers (layers);
return NULL;
}
l->low = low;
l->next = layers;
layers = l;
}
free (buf);
return layers;
}
static struct lo_node *
do_lookup_file (struct lo_data *lo, fuse_ino_t parent, const char *name)
{
struct lo_node key;
struct lo_node *node;
if (parent == FUSE_ROOT_ID)
node = lo->root_upper;
else
node = (struct lo_node *) parent;
if (node_dirp (node) && node->dirty)
{
node = load_dir (lo, node, node->layer, node->path, node->name);
if (node == NULL)
return NULL;
node->dirty = 0;
}
if (name == NULL)
return node;
key.name = (char *) name;
node = hash_lookup (node->children, &key);
if (node == NULL || node->whiteout)
return NULL;
return node;
}
static void
lo_lookup (fuse_req_t req, fuse_ino_t parent, const char *name)
{
struct fuse_entry_param e;
int err = 0;
struct lo_data *lo = lo_data (req);
struct lo_node *node;
if (lo_debug (req))
fprintf (stderr, "lo_lookup(parent=%" PRIu64 ", name=%s)\n",
parent, name);
memset (&e, 0, sizeof (e));
node = do_lookup_file (lo, parent, name);
if (node == NULL)
{
fuse_reply_err (req, ENOENT);
return;
}
err = rpl_stat (req, node, &e.attr);
if (err)
{
fuse_reply_err (req, errno);
return;
}
e.ino = NODE_TO_INODE (node);
node->lookups++;
e.attr_timeout = ATTR_TIMEOUT;
e.entry_timeout = ENTRY_TIMEOUT;
fuse_reply_entry (req, &e);
}
struct lo_dirp
{
struct lo_data *lo;
struct lo_node **tbl;
size_t tbl_size;
size_t offset;
};
static struct lo_dirp *
lo_dirp (struct fuse_file_info *fi)
{
return (struct lo_dirp *) (uintptr_t) fi->fh;
}
static void
lo_opendir (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
{
size_t counter = 0;
struct lo_node *node;
struct lo_data *lo = lo_data (req);
struct lo_node *it;
struct lo_dirp *d = calloc (1, sizeof (struct lo_dirp));
if (d == NULL)
{
errno = ENOENT;
goto out_errno;
}
node = do_lookup_file (lo, ino, NULL);
if (node == NULL)
{
errno = ENOENT;
goto out_errno;
}
if (! node_dirp (node))
{
errno = ENOTDIR;
goto out_errno;
}
d->offset = 0;
d->tbl_size = hash_get_n_entries (node->children) + 2;
d->tbl = malloc (sizeof (struct lo_node *) * d->tbl_size);
if (d->tbl == NULL)
{
errno = ENOMEM;
goto out_errno;
}
d->tbl[counter++] = node;
d->tbl[counter++] = node->parent;
for (it = hash_get_first (node->children); it; it = hash_get_next (node->children, it))
{
it->lookups++;
d->tbl[counter++] = it;
}
fi->fh = (uintptr_t) d;
fuse_reply_open (req, fi);
return;
out_errno:
if (d)
{
if (d->tbl)
free (d->tbl);
free (d);
}
fuse_reply_err (req, errno);
}
static void
lo_do_readdir (fuse_req_t req, fuse_ino_t ino, size_t size,
off_t offset, struct fuse_file_info *fi, int plus)
{
struct lo_dirp *d = lo_dirp (fi);
size_t remaining = size;
char *p, *buffer = calloc (size, 1);
if (buffer == NULL)
{
fuse_reply_err (req, ENOMEM);
return;
}
p = buffer;
for (;remaining > 0 && offset < d->tbl_size; offset++)
{
int ret;
size_t entsize;
struct stat st;
const char *name;
struct lo_node *node = d->tbl[offset];
if (node == NULL || node->whiteout)
continue;
ret = rpl_stat (req, node, &st);
if (ret < 0)
{
fuse_reply_err (req, errno);
goto exit;
}
if (offset == 0)
name = ".";
else if (offset == 1)
name = "..";
else
name = node->name;
if (!plus)
entsize = fuse_add_direntry (req, p, remaining, name, &st, offset + 1);
else
{
struct fuse_entry_param e;
memset (&e, 0, sizeof (e));
e.attr_timeout = ATTR_TIMEOUT;
e.entry_timeout = ENTRY_TIMEOUT;
e.ino = NODE_TO_INODE (node);
memcpy (&e.attr, &st, sizeof (st));
entsize = fuse_add_direntry_plus (req, p, remaining, name, &e, offset + 1);
if (entsize <= remaining)
{
/* First two entries are . and .. */
if (offset >= 2)
node->lookups++;
}
}
if (entsize > remaining)
break;
p += entsize;
remaining -= entsize;
}
fuse_reply_buf (req, buffer, size - remaining);
exit:
free (buffer);
}
static void
lo_readdir (fuse_req_t req, fuse_ino_t ino, size_t size,
off_t offset, struct fuse_file_info *fi)
{
lo_do_readdir (req, ino, size, offset, fi, 0);
}
static void
lo_readdirplus (fuse_req_t req, fuse_ino_t ino, size_t size,
off_t offset, struct fuse_file_info *fi)
{
lo_do_readdir (req, ino, size, offset, fi, 1);
}
static void
lo_releasedir (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
{
size_t s;
struct lo_dirp *d = lo_dirp (fi);
for (s = 2; s < d->tbl_size; s++)
{
struct lo_node *n = d->tbl[s];
do_forget (NODE_TO_INODE (n), 1);
}
free (d->tbl);
free (d);
fuse_reply_err (req, 0);
}
static void
lo_listxattr (fuse_req_t req, fuse_ino_t ino, size_t size)
{
ssize_t len;
struct lo_node *node;
struct lo_data *lo = lo_data (req);
char buf[1024];
int fd;
if (lo_debug (req))
fprintf (stderr, "lo_listxattr(ino=%" PRIu64 ", size=%zu)\n", ino, size);
node = do_lookup_file (lo, ino, NULL);
if (node == NULL)
{
fuse_reply_err (req, ENOENT);
return;
}
fd = openat (node_dirfd (node), node->path, O_RDONLY);
if (fd < 0)
{
fuse_reply_err (req, errno);
return;
}
len = flistxattr (fd, buf, sizeof (buf));
if (len < 0)
fuse_reply_err (req, errno);
else if (size == 0)
fuse_reply_xattr (req, len);
else if (len <= size)
fuse_reply_buf (req, buf, len);
close (fd);
}
static void
lo_getxattr (fuse_req_t req, fuse_ino_t ino, const char *name, size_t size)
{
ssize_t len;
struct lo_node *node;
struct lo_data *lo = lo_data (req);
char buf[1024];
int fd;
if (lo_debug (req))
fprintf (stderr, "lo_getxattr(ino=%" PRIu64 ", name=%s, size=%zu)\n", ino, name, size);
node = do_lookup_file (lo, ino, NULL);
if (node == NULL)
{
fuse_reply_err (req, ENOENT);
return;
}
fd = openat (node_dirfd (node), node->path, O_RDONLY);
if (fd < 0)
{
fuse_reply_err (req, errno);
return;
}
len = fgetxattr (fd, name, buf, sizeof (buf));
if (len < 0)
fuse_reply_err (req, errno);
else if (size == 0)
fuse_reply_xattr (req, len);
else if (len <= size)
fuse_reply_buf (req, buf, len);
close (fd);
}
static void
lo_access (fuse_req_t req, fuse_ino_t ino, int mask)
{
int ret;
struct lo_data *lo = lo_data (req);
struct lo_node *n = do_lookup_file (lo, ino, NULL);
if (lo_debug (req))
fprintf (stderr, "lo_access(ino=%" PRIu64 ", mask=%d)\n",
ino, mask);
ret = faccessat (node_dirfd (n), n->path, mask, AT_SYMLINK_NOFOLLOW);
fuse_reply_err (req, ret < 0 ? errno : 0);
}
static int
create_directory (struct lo_data *lo, struct lo_node *src)
{
int ret;
struct stat st;
if (src == NULL)
return 0;
if (src->layer == get_upper_layer (lo))
return 0;
ret = fstatat (node_dirfd (src), src->path, &st, AT_SYMLINK_NOFOLLOW);
if (ret < 0)
goto out;
ret = mkdirat (get_upper_layer (lo)->fd, src->path, st.st_mode);
if (ret < 0 && errno == EEXIST)
{
ret = 0;
goto out;
}
else if (ret < 0 && errno == ENOENT)
{
ret = create_directory (lo, src->parent);
if (ret != 0)
goto out;
ret = mkdirat (lo->layers[0].fd, src->path, st.st_mode);
if (ret < 0)
goto out;
ret = fchownat (lo->layers[0].fd, src->path, st.st_uid, st.st_gid, AT_SYMLINK_NOFOLLOW);
}
out:
if (ret == 0)
{
src->layer = get_upper_layer (lo);
if (src->parent)
{
char wh[PATH_MAX];
sprintf (wh, "%s/.wh.%s", src->path, src->name);
unlinkat (node_dirfd (src), wh, 0);
}
}
return ret;
}
static int
copyup (struct lo_data *lo, struct lo_node *node)
{
int saved_errno;
int ret = -1;
int dfd = -1, sfd = -1;
struct stat st;
int r;
const size_t buf_size = 1 << 20;
char *buf = NULL;
struct timespec times[2];
if (node->parent)
{
r = create_directory (lo, node->parent);
if (r < 0)
return r;
}
if (fstatat (node_dirfd (node), node->path, &st, AT_SYMLINK_NOFOLLOW) < 0)
goto exit;
if (node->parent)
{
char whpath[PATH_MAX + 10];
sprintf (whpath, "%s/.wh.%s", node->parent->path, node->name);
if (unlinkat (get_upper_layer (lo)->fd, whpath, 0) < 0 && errno != ENOENT)
goto exit;
}
if ((st.st_mode & S_IFMT) == S_IFDIR)
{
ret = create_directory (lo, node);
if (ret < 0)
goto exit;
goto success;
}
if ((st.st_mode & S_IFMT) == S_IFLNK)
{
char p[PATH_MAX + 1];
ret = readlinkat (node_dirfd (node), node->path, p, sizeof (p) - 1);
if (ret < 0)
goto exit;
p[ret] = '\0';
ret = symlinkat (p, get_upper_layer (lo)->fd, node->path);
if (ret < 0)
goto exit;
goto success;
}
sfd = openat (node_dirfd (node), node->path, O_RDONLY);
if (sfd < 0)
goto exit;
dfd = openat (get_upper_layer (lo)->fd, node->path, O_CREAT|O_WRONLY, st.st_mode);
if (dfd < 0)
goto exit;
ret = fchown (dfd, st.st_uid, st.st_gid);
if (ret < 0)
goto exit;
buf = malloc (buf_size);
if (buf == NULL)
goto exit;
for (;;)
{
int written;
int nread;
nread = TEMP_FAILURE_RETRY (read (sfd, buf, buf_size));
if (nread < 0)
goto exit;
if (nread == 0)
break;
written = 0;
{
r = TEMP_FAILURE_RETRY (write (dfd, buf + written, nread));
if (r < 0)
goto exit;
written += r;
nread -= r;
}
while (nread);
}
times[0] = st.st_atim;
times[1] = st.st_mtim;
if (futimens (dfd, times) < 0)
goto exit;
success:
ret = 0;
node->layer = get_upper_layer (lo);
exit:
saved_errno = errno;
free (buf);
if (sfd >= 0)
close (sfd);
if (dfd >= 0)
close (dfd);
errno = saved_errno;
return ret;
}
static struct lo_node *
get_node_up (struct lo_data *lo, struct lo_node *node)
{
int ret;
if (node->layer == get_upper_layer (lo))
return node;
ret = copyup (lo, node);
if (ret < 0)
return NULL;
assert (node->layer == get_upper_layer (lo));
return node;
}
static size_t
count_dir_entries (struct lo_node *node)
{
size_t c = 0;
struct lo_node *it;
for (it = hash_get_first (node->children); it; it = hash_get_next (node->children, it))
{
if (it->whiteout)
continue;
if (strcmp (it->name, ".") == 0)
continue;
if (strcmp (it->name, "..") == 0)
continue;
c++;
}
return c;
}
static int
update_paths (struct lo_node *node)
{
struct lo_node *it;
if (node == NULL)
return 0;
if (node->parent)
{
free (node->path);
if (asprintf (&node->path, "%s/%s", node->parent->path, node->name) < 0)
{
node->path = NULL;
return -1;
}
}
if (node->children)
{
for (it = hash_get_first (node->children); it; it = hash_get_next (node->children, it))
{
if (update_paths (it) < 0)
return -1;
}
}
return 0;
}
static void
do_rm (fuse_req_t req, fuse_ino_t parent, const char *name, bool dirp)
{
struct lo_node *node;
struct lo_data *lo = lo_data (req);
struct lo_node *pnode;
int fd;
int ret = 0;
char whiteout_path[PATH_MAX + 10];
struct lo_node key, *rm;
node = do_lookup_file (lo, parent, name);
if (node == NULL)
{
fuse_reply_err (req, ENOENT);
return;
}
if (node->layer == get_upper_layer (lo))
{
node->rmfrom = node->layer->fd;
if (! dirp)
node->do_unlink = 1;
else
{
DIR *dp;
size_t c = 0;
int fd = openat (get_upper_layer (lo)->fd, node->path, O_DIRECTORY);
if (node->children)
c = count_dir_entries (node);
if (c)
{
fuse_reply_err (req, ENOTEMPTY);
return;
}
if (fd >= 0)
{
dp = fdopendir (fd);
if (dp)
{
struct dirent *dent;
while (dp && ((dent = readdir (dp)) != NULL))
{
if (strcmp (dent->d_name, ".") == 0)
continue;
if (strcmp (dent->d_name, "..") == 0)
continue;
if (unlinkat (dirfd (dp), dent->d_name, 0) < 0)
unlinkat (dirfd (dp), dent->d_name, AT_REMOVEDIR);
}
closedir (dp);
}
}
node->do_rmdir = 1;
}
}
pnode = do_lookup_file (lo, parent, NULL);
if (pnode == NULL)
{
fuse_reply_err (req, ENOENT);
return;
}
pnode = get_node_up (lo, pnode);
if (pnode == NULL)
{
fuse_reply_err (req, ENOENT);
return;
}
sprintf (whiteout_path, "%s/.wh.%s", pnode->path, name);
fd = openat (get_upper_layer (lo)->fd, whiteout_path, O_CREAT|O_WRONLY, 0700);
if (fd < 0 && errno != EEXIST)
{
fuse_reply_err (req, errno);
return;
}
close (fd);
ret = 0;
key.name = (char *) name;
rm = hash_delete (pnode->children, &key);
if (rm)
{
hide_node (lo, rm, true);
node_free (rm);
}
fuse_reply_err (req, ret);
}
static void
lo_unlink (fuse_req_t req, fuse_ino_t parent, const char *name)
{
if (lo_debug (req))
fprintf (stderr, "lo_unlink(parent=%" PRIu64 ", name=%s)\n",
parent, name);
do_rm (req, parent, name, false);
}
static void
lo_rmdir (fuse_req_t req, fuse_ino_t parent, const char *name)
{
if (lo_debug (req))
fprintf (stderr, "lo_rmdir(parent=%" PRIu64 ", name=%s)\n",
parent, name);
do_rm (req, parent, name, true);
}
static void
lo_setxattr (fuse_req_t req, fuse_ino_t ino, const char *name,
const char *value, size_t size, int flags)
{
struct lo_data *lo = lo_data (req);
struct lo_node *node;
if (lo_debug (req))
fprintf (stderr, "lo_setxattr(ino=%" PRIu64 "s, name=%s, value=%s, size=%zu, flags=%d)\n", ino, name,
value, size, flags);
node = do_lookup_file (lo, ino, NULL);
if (node == NULL)
{
fuse_reply_err (req, ENOENT);
return;
}
node = get_node_up (lo, node);
if (node == NULL)
{
fuse_reply_err (req, errno);
return;
}
if (setxattr (node->path, name, value, size, flags) < 0)
{
fuse_reply_err (req, errno);
return;
}
fuse_reply_err (req, 0);
}
static void
lo_removexattr (fuse_req_t req, fuse_ino_t ino, const char *name)
{
struct lo_node *node;
struct lo_data *lo = lo_data (req);
if (lo_debug (req))
fprintf (stderr, "lo_removexattr(ino=%" PRIu64 "s, name=%s)\n", ino, name);
node = do_lookup_file (lo, ino, NULL);
if (node == NULL)
{
fuse_reply_err (req, ENOENT);
return;
}
node = get_node_up (lo, node);
if (node == NULL)
{
fuse_reply_err (req, errno);
return;
}
if (removexattr (node->path, name) < 0)
{
fuse_reply_err (req, errno);
return;
}
fuse_reply_err (req, 0);
}
static int
lo_do_open (fuse_req_t req, fuse_ino_t parent, const char *name, int flags, mode_t mode)
{
struct lo_data *lo = lo_data (req);
struct lo_node *n;
bool readonly = (flags & (O_APPEND | O_RDWR | O_WRONLY | O_CREAT | O_TRUNC)) == 0;
char path[PATH_MAX + 10];
int fd;
flags |= O_NOFOLLOW;
if (name && has_prefix (name, ".wh."))
{
errno = EINVAL;
return - 1;
}
n = do_lookup_file (lo, parent, name);
if (n && n->hidden)
{
n = NULL;
}
if (n && (flags & O_CREAT))
{
errno = EEXIST;
return -1;
}
if (!n)
{
struct lo_node *p;
if ((flags & O_CREAT) == 0)
{
errno = ENOENT;
return -1;
}
p = do_lookup_file (lo, parent, NULL);
if (p == NULL)
{
errno = ENOENT;
return -1;
}
p = get_node_up (lo, p);
if (p == NULL)
return -1;
sprintf (path, "%s/.wh.%s", p->path, name);
if (unlinkat (get_upper_layer (lo)->fd, path, 0) < 0 && errno != ENOENT)
return -1;
sprintf (path, "%s/%s", p->path, name);
if (unlinkat (get_upper_layer (lo)->fd, path, 0) < 0 && errno != ENOENT)
return -1;
fd = openat (get_upper_layer (lo)->fd, path, flags, mode);
if (fd < 0)
return -1;
n = make_lo_node (path, get_upper_layer (lo), name, 0, false);
if (n == NULL)
{
errno = ENOMEM;
close (fd);
return -1;
}
n = insert_node (p, n, true);
if (n == NULL)
{
errno = ENOMEM;
close (fd);
return -1;
}
return fd;
}
/* readonly, we can use both lowerdir and upperdir. */
if (readonly)
{
return openat (node_dirfd (n), n->path, flags, mode);
}
else
{
n = get_node_up (lo, n);
if (n == NULL)
return -1;
return openat (node_dirfd (n), n->path, flags, mode);
}
}
static void
lo_read (fuse_req_t req, fuse_ino_t ino, size_t size,
off_t offset, struct fuse_file_info *fi)
{
struct fuse_bufvec buf = FUSE_BUFVEC_INIT (size);
if (lo_debug (req))
fprintf (stderr, "lo_read(ino=%" PRIu64 ", size=%zd, "
"off=%lu)\n", ino, size, (unsigned long) offset);
buf.buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK;
buf.buf[0].fd = fi->fh;
buf.buf[0].pos = offset;
fuse_reply_data (req, &buf, FUSE_BUF_SPLICE_MOVE);
}
static void
lo_write_buf (fuse_req_t req, fuse_ino_t ino,
struct fuse_bufvec *in_buf, off_t off,
struct fuse_file_info *fi)
{
(void) ino;
ssize_t res;
struct fuse_bufvec out_buf = FUSE_BUFVEC_INIT (fuse_buf_size (in_buf));
out_buf.buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK;
out_buf.buf[0].fd = fi->fh;
out_buf.buf[0].pos = off;
if (lo_debug (req))
fprintf (stderr, "lo_write_buf(ino=%" PRIu64 ", size=%zd, off=%lu, fd=%d)\n",
ino, out_buf.buf[0].size, (unsigned long) off, (int) fi->fh);
errno = 0;
res = fuse_buf_copy (&out_buf, in_buf, 0);
if (res < 0)
fuse_reply_err (req, errno);
else
fuse_reply_write (req, (size_t) res);
}
static void
lo_release (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
{
(void) ino;
close (fi->fh);
fuse_reply_err (req, 0);
}
static int
do_getattr (fuse_req_t req, struct fuse_entry_param *e, struct lo_node *node)
{
int err = 0;
memset (e, 0, sizeof (*e));
err = rpl_stat (req, node, &e->attr);
if (err < 0)
return err;
e->ino = (fuse_ino_t) node;
e->attr_timeout = ATTR_TIMEOUT;
e->entry_timeout = ENTRY_TIMEOUT;
return 0;
}
static void
lo_create (fuse_req_t req, fuse_ino_t parent, const char *name,
mode_t mode, struct fuse_file_info *fi)
{
int fd;
struct fuse_entry_param e;
struct lo_data *lo = lo_data (req);
struct lo_node *node;
if (lo_debug (req))
fprintf (stderr, "lo_create(parent=%" PRIu64 ", name=%s)\n",
parent, name);
fi->flags = fi->flags | O_CREAT;
fd = lo_do_open (req, parent, name, fi->flags, mode);
if (fd < 0)
{
fuse_reply_err (req, errno);
return;
}
node = do_lookup_file (lo, parent, name);
if (node == NULL || do_getattr (req, &e, node) < 0)
{
close (fd);
fuse_reply_err (req, errno);
return;
}
fi->fh = fd;
node->lookups++;
fuse_reply_create (req, &e, fi);
}
static void
lo_open (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
{
int fd;
if (lo_debug (req))
fprintf (stderr, "lo_open(ino=%" PRIu64 "s)\n", ino);
fd = lo_do_open (req, ino, NULL, fi->flags, 0700);
if (fd < 0)
{
fuse_reply_err (req, errno);
return;
}
fi->fh = fd;
fuse_reply_open (req, fi);
}
static void
lo_getattr (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
{
struct lo_data *lo = lo_data (req);
struct lo_node *node;
struct fuse_entry_param e;
if (lo_debug (req))
fprintf (stderr, "lo_getattr(ino=%" PRIu64 "s)\n", ino);
node = do_lookup_file (lo, ino, NULL);
if (node == NULL)
{
fuse_reply_err (req, ENOENT);
return;
}
if (do_getattr (req, &e, node) < 0)
{
fuse_reply_err (req, errno);
return;
}
fuse_reply_attr (req, &e.attr, ENTRY_TIMEOUT);
}
static void
lo_setattr (fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, struct fuse_file_info *fi)
{
struct lo_data *lo = lo_data (req);
struct lo_node *node;
struct fuse_entry_param e;
struct stat old_st;
struct timespec times[2];
uid_t uid;
gid_t gid;
int dirfd;
if (lo_debug (req))
fprintf (stderr, "lo_setattr(ino=%" PRIu64 "s, to_set=%d)\n", ino, to_set);
node = do_lookup_file (lo, ino, NULL);
if (node == NULL)
{
fuse_reply_err (req, ENOENT);
return;
}
node = get_node_up (lo, node);
if (node == NULL)
{
fuse_reply_err (req, errno);
return;
}
dirfd = node_dirfd (node);
if (fstatat (dirfd, node->path, &old_st, AT_SYMLINK_NOFOLLOW) < 0)
{
fuse_reply_err (req, errno);
return;
}
if (to_set & FUSE_SET_ATTR_CTIME)
{
fuse_reply_err (req, EPERM);
return;
}
times[0].tv_sec = UTIME_OMIT;
times[1].tv_sec = UTIME_OMIT;
if (to_set & FUSE_SET_ATTR_ATIME)
times[0] = attr->st_atim;
else if (to_set & FUSE_SET_ATTR_ATIME_NOW)
times[0].tv_sec = UTIME_NOW;
if (to_set & FUSE_SET_ATTR_MTIME)
times[1] = attr->st_mtim;
else if (to_set & FUSE_SET_ATTR_MTIME_NOW)
times[1].tv_sec = UTIME_NOW;
if (times[0].tv_sec != UTIME_OMIT || times[1].tv_sec != UTIME_OMIT)
{
if ((utimensat (dirfd, node->path, times, AT_SYMLINK_NOFOLLOW) < 0))
{
fuse_reply_err (req, errno);
return;
}
}
if ((to_set & FUSE_SET_ATTR_MODE) && fchmodat (dirfd, node->path, attr->st_mode, 0) < 0)
{
fuse_reply_err (req, errno);
return;
}
if ((to_set & FUSE_SET_ATTR_SIZE))
{
int fd = openat (dirfd, node->path, O_WRONLY);
if (fd < 0)
{
fuse_reply_err (req, errno);
return;
}
if (ftruncate (fd, attr->st_size) < 0)
{
close (fd);
fuse_reply_err (req, errno);
return;
}
close (fd);
}
uid = old_st.st_uid;
gid = old_st.st_gid;
if (to_set & FUSE_SET_ATTR_UID)
uid = get_uid (lo, attr->st_uid);
if (to_set & FUSE_SET_ATTR_GID)
gid = get_gid (lo, attr->st_gid);
if (uid != old_st.st_uid || gid != old_st.st_gid)
{
if (fchownat (dirfd, node->path, uid, gid, AT_SYMLINK_NOFOLLOW) < 0)
{
fuse_reply_err (req, errno);
return;
}
}
if (do_getattr (req, &e, node) < 0)
{
fuse_reply_err (req, errno);
return;
}
fuse_reply_attr (req, &e.attr, ENTRY_TIMEOUT);
}
static void
lo_link (fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, const char *newname)
{
struct lo_data *lo = lo_data (req);
struct lo_node *node, *newparentnode, *destnode;
char path[PATH_MAX + 10];
int ret;
struct fuse_entry_param e;
if (lo_debug (req))
fprintf (stderr, "lo_link(ino=%" PRIu64 "s, newparent=%" PRIu64 "s, newname=%s)\n", ino, newparent, newname);
node = do_lookup_file (lo, newparent, newname);
if (node != NULL)
{
fuse_reply_err (req, EEXIST);
return;
}
node = do_lookup_file (lo, ino, NULL);
if (node == NULL)
{
fuse_reply_err (req, ENOENT);
return;
}
node = get_node_up (lo, node);
if (node == NULL)
{
fuse_reply_err (req, errno);
return;
}
newparentnode = do_lookup_file (lo, newparent, NULL);
if (newparentnode == NULL)
{
fuse_reply_err (req, ENOENT);
return;
}
destnode = do_lookup_file (lo, newparent, newname);
if (destnode)
{
fuse_reply_err (req, EEXIST);
return;
}
newparentnode = get_node_up (lo, newparentnode);
if (newparentnode == NULL)
{
fuse_reply_err (req, errno);
return;
}
sprintf (path, "%s/.wh.%s", newparentnode->path, newname);
if (unlinkat (node_dirfd (newparentnode), path, 0) < 0 && errno != ENOENT)
{
fuse_reply_err (req, errno);
return;
}
sprintf (path, "%s/%s", newparentnode->path, newname);
if (linkat (node_dirfd (newparentnode), node->path, node_dirfd (newparentnode), path, 0) < 0)
{
fuse_reply_err (req, errno);
return;
}
node = make_lo_node (path, get_upper_layer (lo), newname, node->ino, false);
if (node == NULL)
{
fuse_reply_err (req, ENOMEM);
return;
}
node = insert_node (newparentnode, node, true);
if (node == NULL)
{
fuse_reply_err (req, ENOMEM);
return;
}
memset (&e, 0, sizeof (e));
ret = rpl_stat (req, node, &e.attr);
if (ret)
{
fuse_reply_err (req, errno);
return;
}
e.ino = NODE_TO_INODE (node);
node->lookups++;
e.attr_timeout = ATTR_TIMEOUT;
e.entry_timeout = ENTRY_TIMEOUT;
fuse_reply_entry (req, &e);
}
static void
lo_symlink (fuse_req_t req, const char *link, fuse_ino_t parent, const char *name)
{
struct lo_data *lo = lo_data (req);
struct lo_node *pnode, *node;
char path[PATH_MAX + 10];
int ret;
struct fuse_entry_param e;
if (lo_debug (req))
fprintf (stderr, "lo_symlink(link=%s, ino=%" PRIu64 "s, name=%s)\n", link, parent, name);
pnode = do_lookup_file (lo, parent, NULL);
if (pnode == NULL)
{
fuse_reply_err (req, ENOENT);
return;
}
pnode = get_node_up (lo, pnode);
if (pnode == NULL)
{
fuse_reply_err (req, errno);
return;
}
node = do_lookup_file (lo, parent, name);
if (node != NULL)
{
fuse_reply_err (req, EEXIST);
return;
}
sprintf (path, "%s/.wh.%s", pnode->path, name);
if (unlinkat (get_upper_layer (lo)->fd, path, 0) < 0 && errno != ENOENT)
{
fuse_reply_err (req, errno);
return;
}
sprintf (path, "%s/%s", pnode->path, name);
ret = symlinkat (link, get_upper_layer (lo)->fd, path);
if (ret < 0)
{
fuse_reply_err (req, errno);
return;
}
node = make_lo_node (path, get_upper_layer (lo), name, 0, false);
if (node == NULL)
{
fuse_reply_err (req, ENOMEM);
return;
}
node = insert_node (pnode, node, true);
if (node == NULL)
{
fuse_reply_err (req, ENOMEM);
return;
}
memset (&e, 0, sizeof (e));
ret = rpl_stat (req, node, &e.attr);
if (ret)
{
fuse_reply_err (req, errno);
return;
}
e.ino = NODE_TO_INODE (node);
node->lookups++;
e.attr_timeout = ATTR_TIMEOUT;
e.entry_timeout = ENTRY_TIMEOUT;
fuse_reply_entry (req, &e);
}
static void
lo_flock (fuse_req_t req, fuse_ino_t ino,
struct fuse_file_info *fi, int op)
{
int ret, fd;
if (lo_debug (req))
fprintf (stderr, "lo_flock(ino=%" PRIu64 "s, op=%d)\n", ino, op);
fd = fi->fh;
ret = flock (fd, op);
fuse_reply_err (req, ret == 0 ? 0 : errno);
}
static void
lo_rename (fuse_req_t req, fuse_ino_t parent, const char *name,
fuse_ino_t newparent, const char *newname,
unsigned int flags)
{
struct lo_node *pnode, *node, *destnode, *destpnode;
struct lo_data *lo = lo_data (req);
int ret;
int saved_errno;
char path[PATH_MAX + 1];
int srcfd = -1;
int destfd = -1;
struct lo_node key, *rm = NULL;
if (lo_debug (req))
fprintf (stderr, "lo_rename(ino=%" PRIu64 "s, name=%s , ino=%" PRIu64 "s, name=%s)\n", parent, name, newparent, newname);
node = do_lookup_file (lo, parent, name);
if (node == NULL)
{
fuse_reply_err (req, ENOENT);
return;
}
if (node_dirp (node) && node->present_lowerdir)
{
fuse_reply_err (req, EXDEV);
return;
}
pnode = node->parent;
destpnode = do_lookup_file (lo, newparent, NULL);
destnode = NULL;
pnode = get_node_up (lo, pnode);
if (pnode == NULL)
goto error;
ret = openat (node_dirfd (pnode), pnode->path, O_DIRECTORY);
if (ret < 0)
goto error;
srcfd = ret;
destpnode = get_node_up (lo, destpnode);
if (destpnode == NULL)
goto error;
ret = openat (node_dirfd (destpnode), destpnode->path, O_DIRECTORY);
if (ret < 0)
goto error;
destfd = ret;
destnode = do_lookup_file (lo, newparent, newname);
node = get_node_up (lo, node);
if (node == NULL)
goto error;
if (flags & RENAME_EXCHANGE)
{
if (destnode == NULL)
{
errno = ENOENT;
goto error;
}
destnode = get_node_up (lo, destnode);
if (destnode == NULL)
goto error;
}
else
{
key.name = (char *) newname;
if (flags & RENAME_NOREPLACE)
{
rm = hash_lookup (destpnode->children, &key);
if (rm)
{
errno = EEXIST;
goto error;
}
}
if (destnode != NULL && node_dirp (destnode))
{
errno = EISDIR;
goto error;
}
rm = hash_lookup (destpnode->children, &key);
if (rm)
{
hash_delete (destpnode->children, rm);
if (rm->lookups > 0)
node_free (rm);
else
{
node_free (rm);
rm = NULL;
}
if (rm && hide_node (lo, rm, false) < 0)
goto error;
}
}
ret = syscall (SYS_renameat2, srcfd, name, destfd, newname, flags);
if (ret < 0)
goto error;
if (flags & RENAME_EXCHANGE)
{
struct lo_node *rm1, *rm2;
char *tmp;
rm1 = hash_delete (destpnode->children, destnode);
rm2 = hash_delete (pnode->children, node);
tmp = node->path;
node->path = destnode->path;
destnode->path = tmp;
tmp = node->name;
node->name = destnode->name;
destnode->name = tmp;
node = insert_node (destpnode, node, true);
if (node == NULL)
{
node_free (rm1);
node_free (rm2);
goto error;
}
destnode = insert_node (pnode, destnode, true);
if (destnode == NULL)
{
node_free (rm1);
node_free (rm2);
goto error;
}
if ((update_paths (node) < 0) || (update_paths (destnode) < 0))
goto error;
}
else
{
int fd;
sprintf (path, ".wh.%s", name);
fd = openat (srcfd, path, O_CREAT, 0700);
if (fd < 0)
goto error;
close (fd);
hash_delete (pnode->children, node);
free (node->name);
node->name = strdup (newname);
if (node->name == NULL)
goto error;
node = insert_node (destpnode, node, true);
if (node == NULL)
goto error;
if (update_paths (node) < 0)
goto error;
}
sprintf (path, ".wh.%s", newname);
if (unlinkat (destfd, path, 0) < 0 && errno != ENOENT)
goto error;
error:
saved_errno = errno;
if (srcfd >= 0)
close (srcfd);
if (destfd >= 0)
close (destfd);
errno = saved_errno;
fuse_reply_err (req, ret == 0 ? 0 : errno);
}
static void
lo_statfs (fuse_req_t req, fuse_ino_t ino)
{
int ret;
struct statvfs sfs;
struct lo_data *lo = lo_data (req);
if (lo_debug (req))
fprintf (stderr, "lo_statfs(ino=%" PRIu64 "s)\n", ino);
ret = statvfs (lo->upperdir, &sfs);
if (ret < 0)
{
fuse_reply_err (req, errno);
return;
}
fuse_reply_statfs (req, &sfs);
}
static void
lo_readlink (fuse_req_t req, fuse_ino_t ino)
{
struct lo_node *node;
struct lo_data *lo = lo_data (req);
int ret = 0;
char buf[PATH_MAX + 1];
if (lo_debug (req))
fprintf (stderr, "lo_readlink(ino=%" PRIu64 "s)\n", ino);
node = do_lookup_file (lo, ino, NULL);
if (node == NULL)
{
fuse_reply_err (req, ENOENT);
return;
}
ret = readlinkat (node_dirfd (node), node->path, buf, sizeof (buf));
if (ret == -1)
{
fuse_reply_err (req, errno);
return;
}
if (ret == sizeof (buf))
{
fuse_reply_err (req, ENAMETOOLONG);
return;
}
buf[ret] = '\0';
fuse_reply_readlink (req, buf);
}
static int
hide_all (struct lo_data *lo, struct lo_node *node)
{
char b[PATH_MAX];
struct lo_node *it;
for (it = hash_get_first (node->children); it; it = hash_get_next (node->children, it))
{
int fd;
sprintf (b, "%s/.wh.%s", node->path, it->name);
fd = openat (get_upper_layer (lo)->fd, b, O_CREAT, 0700);
if (fd < 0 && errno != EEXIST)
return fd;
close (fd);
}
return 0;
}
static void
lo_mkdir (fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode)
{
struct lo_node *node;
struct lo_data *lo = lo_data (req);
struct lo_node *pnode;
int ret = 0;
char path[PATH_MAX + 10];
char whiteout_path[PATH_MAX + 16];
struct fuse_entry_param e;
if (lo_debug (req))
fprintf (stderr, "lo_mkdir(ino=%" PRIu64 ", name=%s, mode=%d)\n",
parent, name, mode);
node = do_lookup_file (lo, parent, name);
if (node != NULL)
{
fuse_reply_err (req, EEXIST);
return;
}
pnode = do_lookup_file (lo, parent, NULL);
if (pnode == NULL)
{
fuse_reply_err (req, ENOENT);
return;
}
pnode = get_node_up (lo, pnode);
if (pnode == NULL)
{
fuse_reply_err (req, errno);
return;
}
sprintf (path, "%s/%s", pnode->path, name);
unlinkat (get_upper_layer (lo)->fd, path, AT_REMOVEDIR);
ret = mkdirat (get_upper_layer (lo)->fd, path, mode);
if (ret < 0)
{
fuse_reply_err (req, errno);
return;
}
node = make_lo_node (path, get_upper_layer (lo), name, 0, true);
if (node == NULL)
{
fuse_reply_err (req, ENOMEM);
return;
}
node = insert_node (pnode, node, true);
if (node == NULL)
{
fuse_reply_err (req, ENOMEM);
return;
}
ret = hide_all (lo, node);
if (ret < 0)
{
fuse_reply_err (req, errno);
return;
}
sprintf (whiteout_path, "%s/.wh.%s", pnode->path, name);
ret = unlinkat (get_upper_layer (lo)->fd, whiteout_path, 0);
if (ret < 0 && errno != ENOENT)
{
fuse_reply_err (req, errno);
return;
}
memset (&e, 0, sizeof (e));
ret = rpl_stat (req, node, &e.attr);
if (ret)
{
fuse_reply_err (req, errno);
return;
}
e.ino = NODE_TO_INODE (node);
e.attr_timeout = ATTR_TIMEOUT;
e.entry_timeout = ENTRY_TIMEOUT;
node->lookups++;
fuse_reply_entry (req, &e);
}
static void
lo_fsync (fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi)
{
int ret, fd;
if (lo_debug (req))
fprintf (stderr, "lo_fsync(ino=%" PRIu64 ", datasync=%d, fi=%p)\n",
ino, datasync, fi);
fd = fi->fh;
ret = datasync ? fdatasync (fd) : fsync (fd);
fuse_reply_err (req, ret == 0 ? 0 : errno);
}
static struct fuse_lowlevel_ops lo_oper = {
.statfs = lo_statfs,
.access = lo_access,
.getxattr = lo_getxattr,
.removexattr = lo_removexattr,
.setxattr = lo_setxattr,
.listxattr = lo_listxattr,
.init = lo_init,
.lookup = lo_lookup,
.forget = lo_forget,
.getattr = lo_getattr,
.readlink = lo_readlink,
.opendir = lo_opendir,
.readdir = lo_readdir,
.readdirplus = lo_readdirplus,
.releasedir = lo_releasedir,
.create = lo_create,
.open = lo_open,
.release = lo_release,
.read = lo_read,
.write_buf = lo_write_buf,
.unlink = lo_unlink,
.rmdir = lo_rmdir,
.setattr = lo_setattr,
.symlink = lo_symlink,
.rename = lo_rename,
.mkdir = lo_mkdir,
.link = lo_link,
.fsync = lo_fsync,
.flock = lo_flock,
};
static int
fuse_opt_proc (void *data, const char *arg, int key, struct fuse_args *outargs)
{
if (strcmp (arg, "-f") == 0)
return 1;
if (strcmp (arg, "--debug") == 0)
return 1;
/* Ignore unknown arguments. */
if (key == -1)
return 0;
return 1;
}
int
main (int argc, char *argv[])
{
struct fuse_args args = FUSE_ARGS_INIT (argc, argv);
struct fuse_session *se;
struct fuse_cmdline_opts opts;
struct lo_data lo = {.debug = 0,
.uid_mappings = NULL,
.gid_mappings = NULL,
.uid_str = NULL,
.gid_str = NULL,
.root_lower = NULL,
.root_upper = NULL,
.lowerdir = NULL,
};
int ret = -1;
if (fuse_opt_parse (&args, &lo, lo_opts, fuse_opt_proc) == -1)
return 1;
if (fuse_parse_cmdline (&args, &opts) != 0)
return 1;
if (opts.show_help)
{
printf ("usage: %s [options] <mountpoint>\n\n", argv[0]);
fuse_cmdline_help ();
fuse_lowlevel_help ();
ret = 0;
goto err_out1;
}
else if (opts.show_version)
{
printf ("FUSE library version %s\n", fuse_pkgversion ());
fuse_lowlevel_version ();
ret = 0;
goto err_out1;
}
lo.debug = opts.debug;
if (lo.upperdir == NULL)
error (EXIT_FAILURE, 0, "upperdir not specified");
else
{
char full_path[PATH_MAX + 1];
if (realpath (lo.upperdir, full_path) < 0)
goto err_out1;
lo.upperdir = strdup (full_path);
if (lo.upperdir == NULL)
goto err_out1;
}
printf ("UID=%s\n", lo.uid_str ? : "unchanged");
printf ("GID=%s\n", lo.gid_str ? : "unchanged");
printf ("UPPERDIR=%s\n", lo.upperdir);
printf ("WORKDIR=%s\n", lo.workdir);
printf ("LOWERDIR=%s\n", lo.lowerdir);
printf ("MOUNTPOINT=%s\n", opts.mountpoint);
lo.uid_mappings = lo.uid_str ? read_mappings (lo.uid_str) : NULL;
lo.gid_mappings = lo.gid_str ? read_mappings (lo.gid_str) : NULL;
lo.root_lower = NULL;
lo.layers = read_dirs (lo.lowerdir, true, NULL);
if (lo.layers == NULL)
error (EXIT_FAILURE, errno, "cannot read lower dirs");
lo.layers = read_dirs (lo.upperdir, false, lo.layers);
if (lo.layers == NULL)
error (EXIT_FAILURE, errno, "cannot read upper dir");
lo.root_upper = load_dir (&lo, NULL, get_upper_layer (&lo), ".", "");
if (lo.root_upper == NULL)
error (EXIT_FAILURE, errno, "cannot read upper dir");
lo.root_upper->lookups = 2;
if (lo.workdir == NULL)
error (EXIT_FAILURE, 0, "workdir not specified");
else
{
char path[PATH_MAX + 1];
if (realpath (lo.workdir, path) < 0)
goto err_out1;
mkdir (path, 0700);
strcat (path, "/work");
mkdir (path, 0700);
free (lo.workdir);
lo.workdir = strdup (path);
lo.workdir_fd = open (lo.workdir, O_DIRECTORY);
if (lo.workdir_fd < 0)
goto err_out1;
}
se = fuse_session_new (&args, &lo_oper, sizeof (lo_oper), &lo);
lo.se = se;
if (se == NULL)
goto err_out1;
if (fuse_set_signal_handlers (se) != 0)
goto err_out2;
if (fuse_session_mount (se, opts.mountpoint) != 0)
goto err_out3;
fuse_daemonize (opts.foreground);
ret = fuse_session_loop (se);
fuse_session_unmount (se);
err_out3:
fuse_remove_signal_handlers (se);
err_out2:
fuse_session_destroy (se);
err_out1:
node_mark_all_free (lo.root_upper);
node_free (lo.root_upper);
free_mapping (lo.uid_mappings);
free_mapping (lo.gid_mappings);
close (lo.workdir_fd);
free_layers (lo.layers);
free (opts.mountpoint);
fuse_opt_free_args (&args);
return ret ? 1 : 0;
}