|  | /* | 
|  | * | 
|  | * 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; | 
|  |  | 
|  | 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; | 
|  | } |