123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417 |
- /*
- *
- * Copyright (C) 2011 Novell Inc.
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 as published by
- * the Free Software Foundation.
- */
- #include <linux/fs.h>
- #include <linux/slab.h>
- #include <linux/file.h>
- #include <linux/splice.h>
- #include <linux/xattr.h>
- #include <linux/security.h>
- #include <linux/uaccess.h>
- #include <linux/sched.h>
- #include <linux/namei.h>
- #include "overlayfs.h"
- #define OVL_COPY_UP_CHUNK_SIZE (1 << 20)
- int ovl_copy_xattr(struct dentry *old, struct dentry *new)
- {
- ssize_t list_size, size;
- char *buf, *name, *value;
- int error;
- if (!old->d_inode->i_op->getxattr ||
- !new->d_inode->i_op->getxattr)
- return 0;
- list_size = vfs_listxattr(old, NULL, 0);
- if (list_size <= 0) {
- if (list_size == -EOPNOTSUPP)
- return 0;
- return list_size;
- }
- buf = kzalloc(list_size, GFP_KERNEL);
- if (!buf)
- return -ENOMEM;
- error = -ENOMEM;
- value = kmalloc(XATTR_SIZE_MAX, GFP_KERNEL);
- if (!value)
- goto out;
- list_size = vfs_listxattr(old, buf, list_size);
- if (list_size <= 0) {
- error = list_size;
- goto out_free_value;
- }
- for (name = buf; name < (buf + list_size); name += strlen(name) + 1) {
- size = vfs_getxattr(old, name, value, XATTR_SIZE_MAX);
- if (size <= 0) {
- error = size;
- goto out_free_value;
- }
- error = vfs_setxattr(new, name, value, size, 0);
- if (error)
- goto out_free_value;
- }
- out_free_value:
- kfree(value);
- out:
- kfree(buf);
- return error;
- }
- static int ovl_copy_up_data(struct path *old, struct path *new, loff_t len)
- {
- struct file *old_file;
- struct file *new_file;
- loff_t old_pos = 0;
- loff_t new_pos = 0;
- int error = 0;
- if (len == 0)
- return 0;
- old_file = ovl_path_open(old, O_RDONLY);
- if (IS_ERR(old_file))
- return PTR_ERR(old_file);
- new_file = ovl_path_open(new, O_WRONLY);
- if (IS_ERR(new_file)) {
- error = PTR_ERR(new_file);
- goto out_fput;
- }
- /* FIXME: copy up sparse files efficiently */
- while (len) {
- size_t this_len = OVL_COPY_UP_CHUNK_SIZE;
- long bytes;
- if (len < this_len)
- this_len = len;
- if (signal_pending_state(TASK_KILLABLE, current)) {
- error = -EINTR;
- break;
- }
- bytes = do_splice_direct(old_file, &old_pos,
- new_file, &new_pos,
- this_len, SPLICE_F_MOVE);
- if (bytes <= 0) {
- error = bytes;
- break;
- }
- WARN_ON(old_pos != new_pos);
- len -= bytes;
- }
- fput(new_file);
- out_fput:
- fput(old_file);
- return error;
- }
- static char *ovl_read_symlink(struct dentry *realdentry)
- {
- int res;
- char *buf;
- struct inode *inode = realdentry->d_inode;
- mm_segment_t old_fs;
- res = -EINVAL;
- if (!inode->i_op->readlink)
- goto err;
- res = -ENOMEM;
- buf = (char *) __get_free_page(GFP_KERNEL);
- if (!buf)
- goto err;
- old_fs = get_fs();
- set_fs(get_ds());
- /* The cast to a user pointer is valid due to the set_fs() */
- res = inode->i_op->readlink(realdentry,
- (char __user *)buf, PAGE_SIZE - 1);
- set_fs(old_fs);
- if (res < 0) {
- free_page((unsigned long) buf);
- goto err;
- }
- buf[res] = '\0';
- return buf;
- err:
- return ERR_PTR(res);
- }
- static int ovl_set_timestamps(struct dentry *upperdentry, struct kstat *stat)
- {
- struct iattr attr = {
- .ia_valid =
- ATTR_ATIME | ATTR_MTIME | ATTR_ATIME_SET | ATTR_MTIME_SET,
- .ia_atime = stat->atime,
- .ia_mtime = stat->mtime,
- };
- return notify_change(upperdentry, &attr, NULL);
- }
- int ovl_set_attr(struct dentry *upperdentry, struct kstat *stat)
- {
- int err = 0;
- if (!S_ISLNK(stat->mode)) {
- struct iattr attr = {
- .ia_valid = ATTR_MODE,
- .ia_mode = stat->mode,
- };
- err = notify_change(upperdentry, &attr, NULL);
- }
- if (!err) {
- struct iattr attr = {
- .ia_valid = ATTR_UID | ATTR_GID,
- .ia_uid = stat->uid,
- .ia_gid = stat->gid,
- };
- err = notify_change(upperdentry, &attr, NULL);
- }
- if (!err)
- ovl_set_timestamps(upperdentry, stat);
- return err;
- }
- static int ovl_copy_up_locked(struct dentry *workdir, struct dentry *upperdir,
- struct dentry *dentry, struct path *lowerpath,
- struct kstat *stat, struct iattr *attr,
- const char *link)
- {
- struct inode *wdir = workdir->d_inode;
- struct inode *udir = upperdir->d_inode;
- struct dentry *newdentry = NULL;
- struct dentry *upper = NULL;
- umode_t mode = stat->mode;
- int err;
- newdentry = ovl_lookup_temp(workdir, dentry);
- err = PTR_ERR(newdentry);
- if (IS_ERR(newdentry))
- goto out;
- upper = lookup_one_len(dentry->d_name.name, upperdir,
- dentry->d_name.len);
- err = PTR_ERR(upper);
- if (IS_ERR(upper))
- goto out1;
- /* Can't properly set mode on creation because of the umask */
- stat->mode &= S_IFMT;
- err = ovl_create_real(wdir, newdentry, stat, link, NULL, true);
- stat->mode = mode;
- if (err)
- goto out2;
- if (S_ISREG(stat->mode)) {
- struct path upperpath;
- ovl_path_upper(dentry, &upperpath);
- BUG_ON(upperpath.dentry != NULL);
- upperpath.dentry = newdentry;
- err = ovl_copy_up_data(lowerpath, &upperpath, stat->size);
- if (err)
- goto out_cleanup;
- }
- err = ovl_copy_xattr(lowerpath->dentry, newdentry);
- if (err)
- goto out_cleanup;
- mutex_lock(&newdentry->d_inode->i_mutex);
- err = ovl_set_attr(newdentry, stat);
- if (!err && attr)
- err = notify_change(newdentry, attr, NULL);
- mutex_unlock(&newdentry->d_inode->i_mutex);
- if (err)
- goto out_cleanup;
- err = ovl_do_rename(wdir, newdentry, udir, upper, 0);
- if (err)
- goto out_cleanup;
- ovl_dentry_update(dentry, newdentry);
- newdentry = NULL;
- /*
- * Non-directores become opaque when copied up.
- */
- if (!S_ISDIR(stat->mode))
- ovl_dentry_set_opaque(dentry, true);
- out2:
- dput(upper);
- out1:
- dput(newdentry);
- out:
- return err;
- out_cleanup:
- ovl_cleanup(wdir, newdentry);
- goto out;
- }
- /*
- * Copy up a single dentry
- *
- * Directory renames only allowed on "pure upper" (already created on
- * upper filesystem, never copied up). Directories which are on lower or
- * are merged may not be renamed. For these -EXDEV is returned and
- * userspace has to deal with it. This means, when copying up a
- * directory we can rely on it and ancestors being stable.
- *
- * Non-directory renames start with copy up of source if necessary. The
- * actual rename will only proceed once the copy up was successful. Copy
- * up uses upper parent i_mutex for exclusion. Since rename can change
- * d_parent it is possible that the copy up will lock the old parent. At
- * that point the file will have already been copied up anyway.
- */
- int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry,
- struct path *lowerpath, struct kstat *stat,
- struct iattr *attr)
- {
- struct dentry *workdir = ovl_workdir(dentry);
- int err;
- struct kstat pstat;
- struct path parentpath;
- struct dentry *upperdir;
- struct dentry *upperdentry;
- const struct cred *old_cred;
- struct cred *override_cred;
- char *link = NULL;
- if (WARN_ON(!workdir))
- return -EROFS;
- ovl_path_upper(parent, &parentpath);
- upperdir = parentpath.dentry;
- err = vfs_getattr(&parentpath, &pstat);
- if (err)
- return err;
- if (S_ISLNK(stat->mode)) {
- link = ovl_read_symlink(lowerpath->dentry);
- if (IS_ERR(link))
- return PTR_ERR(link);
- }
- err = -ENOMEM;
- override_cred = prepare_creds();
- if (!override_cred)
- goto out_free_link;
- override_cred->fsuid = stat->uid;
- override_cred->fsgid = stat->gid;
- /*
- * CAP_SYS_ADMIN for copying up extended attributes
- * CAP_DAC_OVERRIDE for create
- * CAP_FOWNER for chmod, timestamp update
- * CAP_FSETID for chmod
- * CAP_CHOWN for chown
- * CAP_MKNOD for mknod
- */
- cap_raise(override_cred->cap_effective, CAP_SYS_ADMIN);
- cap_raise(override_cred->cap_effective, CAP_DAC_OVERRIDE);
- cap_raise(override_cred->cap_effective, CAP_FOWNER);
- cap_raise(override_cred->cap_effective, CAP_FSETID);
- cap_raise(override_cred->cap_effective, CAP_CHOWN);
- cap_raise(override_cred->cap_effective, CAP_MKNOD);
- old_cred = override_creds(override_cred);
- err = -EIO;
- if (lock_rename(workdir, upperdir) != NULL) {
- pr_err("overlayfs: failed to lock workdir+upperdir\n");
- goto out_unlock;
- }
- upperdentry = ovl_dentry_upper(dentry);
- if (upperdentry) {
- unlock_rename(workdir, upperdir);
- err = 0;
- /* Raced with another copy-up? Do the setattr here */
- if (attr) {
- mutex_lock(&upperdentry->d_inode->i_mutex);
- err = notify_change(upperdentry, attr, NULL);
- mutex_unlock(&upperdentry->d_inode->i_mutex);
- }
- goto out_put_cred;
- }
- err = ovl_copy_up_locked(workdir, upperdir, dentry, lowerpath,
- stat, attr, link);
- if (!err) {
- /* Restore timestamps on parent (best effort) */
- ovl_set_timestamps(upperdir, &pstat);
- }
- out_unlock:
- unlock_rename(workdir, upperdir);
- out_put_cred:
- revert_creds(old_cred);
- put_cred(override_cred);
- out_free_link:
- if (link)
- free_page((unsigned long) link);
- return err;
- }
- int ovl_copy_up(struct dentry *dentry)
- {
- int err;
- err = 0;
- while (!err) {
- struct dentry *next;
- struct dentry *parent;
- struct path lowerpath;
- struct kstat stat;
- enum ovl_path_type type = ovl_path_type(dentry);
- if (OVL_TYPE_UPPER(type))
- break;
- next = dget(dentry);
- /* find the topmost dentry not yet copied up */
- for (;;) {
- parent = dget_parent(next);
- type = ovl_path_type(parent);
- if (OVL_TYPE_UPPER(type))
- break;
- dput(next);
- next = parent;
- }
- ovl_path_lower(next, &lowerpath);
- err = vfs_getattr(&lowerpath, &stat);
- if (!err)
- err = ovl_copy_up_one(parent, next, &lowerpath, &stat, NULL);
- dput(parent);
- dput(next);
- }
- return err;
- }
|