diff -ruN linux-2.6.11.11.orig/fs/Kconfig linux-2.6.11.11/fs/Kconfig --- linux-2.6.11.11.orig/fs/Kconfig 2005-05-27 07:06:46.000000000 +0200 +++ linux-2.6.11.11/fs/Kconfig 2005-06-04 19:04:52.000000000 +0200 @@ -375,6 +375,28 @@ depends on XFS_QUOTA || QUOTA default y +config DELETED + bool "VFS /deleted support" + help + Move deleted files to /deleted directory on each mounted filesystem + instead of unlinking. Deleted files are grouped by N hours to separate + directories. + + If you want to use this feature for getting ability to undelete files + you need answer Y here and create directory 'deleted' accessible only + for user 'root' at root directory of every mounted filesystem which + you want to have undelete ability. + + You also need user space utility for undeleting files and user space + daemon which periodicaly checks free space on mounted filesystems and + if needed removes older files from 'deleted' directory. + Those utilities can be found at . + +config DELETED_HOURS + int "Grouping deleted files by N hours" + depends on DELETED + default 1 + config DNOTIFY bool "Dnotify support" if EMBEDDED default y diff -ruN linux-2.6.11.11.orig/fs/Makefile linux-2.6.11.11/fs/Makefile --- linux-2.6.11.11.orig/fs/Makefile 2005-05-27 07:06:46.000000000 +0200 +++ linux-2.6.11.11/fs/Makefile 2005-06-04 18:40:39.000000000 +0200 @@ -45,6 +45,8 @@ obj-y += devpts/ obj-$(CONFIG_PROFILING) += dcookies.o + +obj-$(CONFIG_DELETED) += dare.o # Do not add any filesystems before this line obj-$(CONFIG_REISERFS_FS) += reiserfs/ diff -ruN linux-2.6.11.11.orig/fs/dcache.c linux-2.6.11.11/fs/dcache.c --- linux-2.6.11.11.orig/fs/dcache.c 2005-05-27 07:06:46.000000000 +0200 +++ linux-2.6.11.11/fs/dcache.c 2005-06-06 01:35:39.000000000 +0200 @@ -1439,6 +1439,24 @@ return res; } +/* write full pathname into buffer and return start of pathname */ +char * d_path_relative(struct dentry *dentry, struct vfsmount *vfsmnt, + char *buf, int buflen) +{ + char *res; + struct vfsmount *rootmnt; + struct dentry *root; + + rootmnt = mntget(vfsmnt); + root = dget(dentry->d_sb->s_root); + spin_lock(&dcache_lock); + res = __d_path(dentry, vfsmnt, root, rootmnt, buf, buflen); + spin_unlock(&dcache_lock); + dput(root); + mntput(rootmnt); + return res; +} + /* * NOTE! The user-level library version returns a * character pointer. The kernel system call just diff -ruN linux-2.6.11.11.orig/fs/dare.c linux-2.6.11.11/fs/dare.c --- linux-2.6.11.11.orig/fs/dare.c 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6.11.11/fs/dare.c 2005-06-06 01:35:36.000000000 +0200 @@ -0,0 +1,398 @@ + +#define DELETED_DIR "deleted" +// #define CONFIG_DELETED_HOURS 1 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +// #include +#include + +char * d_path_relative(struct dentry *dentry, struct vfsmount *vfsmnt, + char *buf, int buflen); + +static DECLARE_MUTEX(deleted_mutex); + +static inline struct vfsmount *find_inode_mnt(struct inode *inode) +{ + struct namespace *ns; + struct vfsmount *mnt = NULL; + + /* Okay, we need to find the vfsmount by looking + * at the namespace now. + */ + ns = current->namespace; + down_read(&ns->sem); + + list_for_each_entry(mnt, &ns->list, mnt_list) { + if (mnt->mnt_sb == inode->i_sb) { + mntget(mnt); + goto out; + } + } + + out: + up_read(&ns->sem); + + return mnt; +} + +static int is_offspring( struct dentry * dir, struct dentry * offspring ) +{ + /* FIXME: do locking */ + /* maybe add some depth limitation eg. /deleted/xxx/yyy */ + while( offspring->d_parent!=NULL && offspring->d_parent!=offspring ) + { + if( offspring->d_parent==dir ) + return(1); + offspring=offspring->d_parent; + } + return(0); +} + +struct proc_priv { + uid_t fsuid; + gid_t fsgid; + kernel_cap_t cap_effective; +}; + +static void save_proc_priv( struct proc_priv *p ) +{ + p->fsuid=current->fsuid; + p->fsgid=current->fsgid; + cap_t(p->cap_effective) = cap_t(current->cap_effective); +} + +static void restore_proc_priv( struct proc_priv *p ) +{ + current->fsuid=p->fsuid; + current->fsgid=p->fsgid; + cap_t(current->cap_effective) = cap_t(p->cap_effective); +} + +static void set_proc_priv( void ) +{ + current->fsuid=0; + current->fsgid=0; + cap_t(current->cap_effective) = to_cap_t(CAP_FS_MASK); +} + +static int write_to_file( struct file *file, char *buf, int len ) +{ + mm_segment_t old_fs; + int result; + loff_t pos; + + if( buf==NULL ) + return(0); + old_fs = get_fs(); + set_fs(get_ds()); + /* The cast to a user pointer is valid due to the set_fs() */ + pos=file->f_pos; + result = vfs_write(file,buf,len,&pos); + file->f_pos=pos; + set_fs(old_fs); + return(result); +} + +static int write_path_to_file( struct file *file, struct dentry *dentry, struct vfsmount *mnt ) +{ char *fn; + char *page = (char *) __get_free_page(GFP_USER); + + if( page!=NULL ) + { + fn=d_path_relative(dentry,mnt,page,PAGE_SIZE); + if( fn!=NULL ) + write_to_file(file,fn,strlen(fn)); + free_page((unsigned long) page); + return(0); + } + return(0); +} + +/* + * deleted_move( struct inode *dir, struct dentry * dentry ) + * Must be called with down(&dir->i_sem); + */ +int deleted_move( struct inode *dir, struct dentry * dentry ) +{ + struct dentry *deldir; + struct proc_priv proc_priv; + struct timeval now; + int num; + char buf[64]; + struct dentry *d,*f; + struct iattr newattrs; + struct file *file; + struct vfsmount *mnt; + unsigned long ino; + int ret = -ENOENT; + + /* We move only a valid dentry */ + + if( !dentry->d_inode ) + return(ret); + + +// printk("ZZZ: start\n"); + /* Save current process privileges and set root privileges */ + + save_proc_priv(&proc_priv); + set_proc_priv(); + + + /* search for /deleted directory */ + + deldir=lookup_one_len(DELETED_DIR,dentry->d_sb->s_root,strlen(DELETED_DIR)); + if( IS_ERR(deldir) ) + { + printk("deleted_move: Error 1 (%d) [patent inode %ld]\n",(int)(deldir),dentry->d_sb->s_root->d_inode->i_ino); + goto Out0; + } + if( !deldir->d_inode ) + { + /* /deleted does not exist */ + goto Out; + } + if( !S_ISDIR(deldir->d_inode->i_mode) ) + { + /* /deleted is not a directory! */ +/* printk("deleted_move: /deleted is not a directory!\n"); */ + goto Out; + } + if( deldir==dentry || is_offspring(deldir,dentry) ) + { + /* Deleting inside /deleted */ + /* printk("deleted_move: Deleting inside /deleted\n"); */ + goto Out; + } + +// printk("ZZZ: 1\n"); + + /* search for /deleted/