415 lines
		
	
	
	
		
			9.1 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
		
		
			
		
	
	
			415 lines
		
	
	
	
		
			9.1 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
| 
								 | 
							
								/*
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * 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 (type != OVL_PATH_LOWER)
							 | 
						||
| 
								 | 
							
											break;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										next = dget(dentry);
							 | 
						||
| 
								 | 
							
										/* find the topmost dentry not yet copied up */
							 | 
						||
| 
								 | 
							
										for (;;) {
							 | 
						||
| 
								 | 
							
											parent = dget_parent(next);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											type = ovl_path_type(parent);
							 | 
						||
| 
								 | 
							
											if (type != OVL_PATH_LOWER)
							 | 
						||
| 
								 | 
							
												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;
							 | 
						||
| 
								 | 
							
								}
							 |