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/