2016-12-16 13:02:56 +03:00
/*
* Copyright ( C ) 2011 Novell Inc .
* Copyright ( C ) 2016 Red Hat , 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/namei.h>
# include <linux/xattr.h>
# include "overlayfs.h"
# include "ovl_entry.h"
2016-12-16 13:02:56 +03:00
struct ovl_lookup_data {
struct qstr name ;
bool is_dir ;
bool opaque ;
bool stop ;
bool last ;
} ;
2016-12-16 13:02:56 +03:00
static bool ovl_is_opaquedir ( struct dentry * dentry )
{
int res ;
char val ;
if ( ! d_is_dir ( dentry ) )
return false ;
res = vfs_getxattr ( dentry , OVL_XATTR_OPAQUE , & val , 1 ) ;
if ( res = = 1 & & val = = ' y ' )
return true ;
return false ;
}
2016-12-16 13:02:56 +03:00
static int ovl_lookup_single ( struct dentry * base , struct ovl_lookup_data * d ,
const char * name , unsigned int namelen ,
struct dentry * * ret )
{
struct dentry * this ;
int err ;
this = lookup_one_len_unlocked ( name , base , namelen ) ;
if ( IS_ERR ( this ) ) {
err = PTR_ERR ( this ) ;
this = NULL ;
if ( err = = - ENOENT | | err = = - ENAMETOOLONG )
goto out ;
goto out_err ;
}
if ( ! this - > d_inode )
goto put_and_out ;
if ( ovl_dentry_weird ( this ) ) {
/* Don't support traversing automounts and other weirdness */
err = - EREMOTE ;
goto out_err ;
}
if ( ovl_is_whiteout ( this ) ) {
d - > stop = d - > opaque = true ;
goto put_and_out ;
}
if ( ! d_can_lookup ( this ) ) {
d - > stop = true ;
if ( d - > is_dir )
goto put_and_out ;
goto out ;
}
d - > is_dir = true ;
if ( ! d - > last & & ovl_is_opaquedir ( this ) ) {
d - > stop = d - > opaque = true ;
goto out ;
}
out :
* ret = this ;
return 0 ;
put_and_out :
dput ( this ) ;
this = NULL ;
goto out ;
out_err :
dput ( this ) ;
return err ;
}
static int ovl_lookup_layer ( struct dentry * base , struct ovl_lookup_data * d ,
struct dentry * * ret )
{
return ovl_lookup_single ( base , d , d - > name . name , d - > name . len , ret ) ;
}
2016-12-16 13:02:56 +03:00
/*
* Returns next layer in stack starting from top .
* Returns - 1 if this is the last layer .
*/
int ovl_path_next ( int idx , struct dentry * dentry , struct path * path )
{
struct ovl_entry * oe = dentry - > d_fsdata ;
BUG_ON ( idx < 0 ) ;
if ( idx = = 0 ) {
ovl_path_upper ( dentry , path ) ;
if ( path - > dentry )
return oe - > numlower ? 1 : - 1 ;
idx + + ;
}
BUG_ON ( idx > oe - > numlower ) ;
* path = oe - > lowerstack [ idx - 1 ] ;
return ( idx < oe - > numlower ) ? idx + 1 : - 1 ;
}
struct dentry * ovl_lookup ( struct inode * dir , struct dentry * dentry ,
unsigned int flags )
{
struct ovl_entry * oe ;
const struct cred * old_cred ;
2016-12-16 13:02:56 +03:00
struct ovl_fs * ofs = dentry - > d_sb - > s_fs_info ;
2016-12-16 13:02:56 +03:00
struct ovl_entry * poe = dentry - > d_parent - > d_fsdata ;
struct path * stack = NULL ;
struct dentry * upperdir , * upperdentry = NULL ;
unsigned int ctr = 0 ;
struct inode * inode = NULL ;
bool upperopaque = false ;
struct dentry * this ;
unsigned int i ;
int err ;
2016-12-16 13:02:56 +03:00
struct ovl_lookup_data d = {
. name = dentry - > d_name ,
. is_dir = false ,
. opaque = false ,
. stop = false ,
. last = ! poe - > numlower ,
} ;
2016-12-16 13:02:56 +03:00
2016-12-16 13:02:56 +03:00
if ( dentry - > d_name . len > ofs - > namelen )
return ERR_PTR ( - ENAMETOOLONG ) ;
2016-12-16 13:02:56 +03:00
old_cred = ovl_override_creds ( dentry - > d_sb ) ;
upperdir = ovl_upperdentry_dereference ( poe ) ;
if ( upperdir ) {
2016-12-16 13:02:56 +03:00
err = ovl_lookup_layer ( upperdir , & d , & upperdentry ) ;
if ( err )
2016-12-16 13:02:56 +03:00
goto out ;
2016-12-16 13:02:56 +03:00
if ( upperdentry & & unlikely ( ovl_dentry_remote ( upperdentry ) ) ) {
dput ( upperdentry ) ;
err = - EREMOTE ;
goto out ;
2016-12-16 13:02:56 +03:00
}
2016-12-16 13:02:56 +03:00
upperopaque = d . opaque ;
2016-12-16 13:02:56 +03:00
}
2016-12-16 13:02:56 +03:00
if ( ! d . stop & & poe - > numlower ) {
2016-12-16 13:02:56 +03:00
err = - ENOMEM ;
2016-12-16 13:02:56 +03:00
stack = kcalloc ( poe - > numlower , sizeof ( struct path ) ,
GFP_TEMPORARY ) ;
2016-12-16 13:02:56 +03:00
if ( ! stack )
goto out_put_upper ;
}
2016-12-16 13:02:56 +03:00
for ( i = 0 ; ! d . stop & & i < poe - > numlower ; i + + ) {
2016-12-16 13:02:56 +03:00
struct path lowerpath = poe - > lowerstack [ i ] ;
2016-12-16 13:02:56 +03:00
d . last = i = = poe - > numlower - 1 ;
err = ovl_lookup_layer ( lowerpath . dentry , & d , & this ) ;
if ( err )
2016-12-16 13:02:56 +03:00
goto out_put ;
2016-12-16 13:02:56 +03:00
2016-12-16 13:02:56 +03:00
if ( ! this )
continue ;
stack [ ctr ] . dentry = this ;
stack [ ctr ] . mnt = lowerpath . mnt ;
ctr + + ;
}
oe = ovl_alloc_entry ( ctr ) ;
err = - ENOMEM ;
if ( ! oe )
goto out_put ;
if ( upperdentry | | ctr ) {
struct dentry * realdentry ;
struct inode * realinode ;
realdentry = upperdentry ? upperdentry : stack [ 0 ] . dentry ;
realinode = d_inode ( realdentry ) ;
err = - ENOMEM ;
if ( upperdentry & & ! d_is_dir ( upperdentry ) ) {
inode = ovl_get_inode ( dentry - > d_sb , realinode ) ;
} else {
inode = ovl_new_inode ( dentry - > d_sb , realinode - > i_mode ,
realinode - > i_rdev ) ;
if ( inode )
ovl_inode_init ( inode , realinode , ! ! upperdentry ) ;
}
if ( ! inode )
goto out_free_oe ;
ovl_copyattr ( realdentry - > d_inode , inode ) ;
}
revert_creds ( old_cred ) ;
oe - > opaque = upperopaque ;
oe - > __upperdentry = upperdentry ;
memcpy ( oe - > lowerstack , stack , sizeof ( struct path ) * ctr ) ;
kfree ( stack ) ;
dentry - > d_fsdata = oe ;
d_add ( dentry , inode ) ;
return NULL ;
out_free_oe :
kfree ( oe ) ;
out_put :
for ( i = 0 ; i < ctr ; i + + )
dput ( stack [ i ] . dentry ) ;
kfree ( stack ) ;
out_put_upper :
dput ( upperdentry ) ;
out :
revert_creds ( old_cred ) ;
return ERR_PTR ( err ) ;
}
bool ovl_lower_positive ( struct dentry * dentry )
{
struct ovl_entry * oe = dentry - > d_fsdata ;
struct ovl_entry * poe = dentry - > d_parent - > d_fsdata ;
const struct qstr * name = & dentry - > d_name ;
unsigned int i ;
bool positive = false ;
bool done = false ;
/*
* If dentry is negative , then lower is positive iff this is a
* whiteout .
*/
if ( ! dentry - > d_inode )
return oe - > opaque ;
/* Negative upper -> positive lower */
if ( ! oe - > __upperdentry )
return true ;
/* Positive upper -> have to look up lower to see whether it exists */
for ( i = 0 ; ! done & & ! positive & & i < poe - > numlower ; i + + ) {
struct dentry * this ;
struct dentry * lowerdir = poe - > lowerstack [ i ] . dentry ;
this = lookup_one_len_unlocked ( name - > name , lowerdir ,
name - > len ) ;
if ( IS_ERR ( this ) ) {
switch ( PTR_ERR ( this ) ) {
case - ENOENT :
case - ENAMETOOLONG :
break ;
default :
/*
* Assume something is there , we just couldn ' t
* access it .
*/
positive = true ;
break ;
}
} else {
if ( this - > d_inode ) {
positive = ! ovl_is_whiteout ( this ) ;
done = true ;
}
dput ( this ) ;
}
}
return positive ;
}