To "show" that we've been wasting more time discussing what to
do, than that it would cost to implement it, I implemented the
kernel variant of the undelete feature.
Features:
- Only works when a .wastebasket is in the root of the partition.
- Only "root" can now delete files.
- If a user has the right to delete a file, it will be moved
to the wastebasket regardless of the permissions of the wastebasket.
If you don't want security holes to others, you should
make the wastebasket mode 700.
I moved a few functions around. Therefore they are replicated
in the patch. The actual patch is only a few lines.
I don't really know what functions "silently" iput inodes I pass
them. I think I've got things right. Anybody care to comment?
(is it ok to do "inode->i_count++" if you want to keep an inode
that a called function happens to free?)
Roger.
Here is the patch:
--------------------------------------------------------------
--- namei.c Mon Dec 11 05:56:35 1995
+++ /usr/src/linux/fs/ext2/namei.c Sun Jun 23 17:37:45 1996
@@ -24,6 +24,7 @@
#include <linux/string.h>
#include <linux/locks.h>
+#define CONFIG_EXT2_UNDELETE
/*
* comment out this line if you want names > EXT2_NAME_LEN chars to be
* truncated. Else they will be disallowed.
@@ -687,201 +688,6 @@
return retval;
}
-int ext2_unlink (struct inode * dir, const char * name, int len)
-{
- int retval;
- struct inode * inode;
- struct buffer_head * bh;
- struct ext2_dir_entry * de;
-
-repeat:
- if (!dir)
- return -ENOENT;
- retval = -ENOENT;
- inode = NULL;
- bh = ext2_find_entry (dir, name, len, &de);
- if (!bh)
- goto end_unlink;
- if (!(inode = iget (dir->i_sb, de->inode)))
- goto end_unlink;
- if (inode->i_sb->dq_op)
- inode->i_sb->dq_op->initialize (inode, -1);
- retval = -EPERM;
- if (S_ISDIR(inode->i_mode))
- goto end_unlink;
- if (IS_APPEND(inode) || IS_IMMUTABLE(inode))
- goto end_unlink;
- if (de->inode != inode->i_ino) {
- iput(inode);
- brelse(bh);
- current->counter = 0;
- schedule();
- goto repeat;
- }
- if ((dir->i_mode & S_ISVTX) && !fsuser() &&
- current->fsuid != inode->i_uid &&
- current->fsuid != dir->i_uid)
- goto end_unlink;
- if (!inode->i_nlink) {
- ext2_warning (inode->i_sb, "ext2_unlink",
- "Deleting nonexistent file (%lu), %d",
- inode->i_ino, inode->i_nlink);
- inode->i_nlink = 1;
- }
- retval = ext2_delete_entry (de, bh);
- if (retval)
- goto end_unlink;
- dir->i_version = ++event;
- mark_buffer_dirty(bh, 1);
- if (IS_SYNC(dir)) {
- ll_rw_block (WRITE, 1, &bh);
- wait_on_buffer (bh);
- }
- dir->i_ctime = dir->i_mtime = CURRENT_TIME;
- dir->i_dirt = 1;
- inode->i_nlink--;
- inode->i_dirt = 1;
- inode->i_ctime = dir->i_ctime;
- retval = 0;
-end_unlink:
- brelse (bh);
- iput (inode);
- iput (dir);
- return retval;
-}
-
-int ext2_symlink (struct inode * dir, const char * name, int len,
- const char * symname)
-{
- struct ext2_dir_entry * de;
- struct inode * inode = NULL;
- struct buffer_head * bh = NULL, * name_block = NULL;
- char * link;
- int i, err;
- int l;
- char c;
-
- if (!(inode = ext2_new_inode (dir, S_IFLNK, &err))) {
- iput (dir);
- return err;
- }
- inode->i_mode = S_IFLNK | S_IRWXUGO;
- inode->i_op = &ext2_symlink_inode_operations;
- for (l = 0; l < inode->i_sb->s_blocksize - 1 &&
- symname [l]; l++)
- ;
- if (l >= sizeof (inode->u.ext2_i.i_data)) {
-
- ext2_debug ("l=%d, normal symlink\n", l);
-
- name_block = ext2_bread (inode, 0, 1, &err);
- if (!name_block) {
- iput (dir);
- inode->i_nlink--;
- inode->i_dirt = 1;
- iput (inode);
- return err;
- }
- link = name_block->b_data;
- } else {
- link = (char *) inode->u.ext2_i.i_data;
-
- ext2_debug ("l=%d, fast symlink\n", l);
-
- }
- i = 0;
- while (i < inode->i_sb->s_blocksize - 1 && (c = *(symname++)))
- link[i++] = c;
- link[i] = 0;
- if (name_block) {
- mark_buffer_dirty(name_block, 1);
- brelse (name_block);
- }
- inode->i_size = i;
- inode->i_dirt = 1;
-
- bh = ext2_find_entry (dir, name, len, &de);
- if (bh) {
- inode->i_nlink--;
- inode->i_dirt = 1;
- iput (inode);
- brelse (bh);
- iput (dir);
- return -EEXIST;
- }
- bh = ext2_add_entry (dir, name, len, &de, &err);
- if (!bh) {
- inode->i_nlink--;
- inode->i_dirt = 1;
- iput (inode);
- iput (dir);
- return err;
- }
- de->inode = inode->i_ino;
- dir->i_version = ++event;
- dcache_add(dir, de->name, de->name_len, de->inode);
- mark_buffer_dirty(bh, 1);
- if (IS_SYNC(dir)) {
- ll_rw_block (WRITE, 1, &bh);
- wait_on_buffer (bh);
- }
- brelse (bh);
- iput (dir);
- iput (inode);
- return 0;
-}
-
-int ext2_link (struct inode * oldinode, struct inode * dir,
- const char * name, int len)
-{
- struct ext2_dir_entry * de;
- struct buffer_head * bh;
- int err;
-
- if (S_ISDIR(oldinode->i_mode)) {
- iput (oldinode);
- iput (dir);
- return -EPERM;
- }
- if (IS_APPEND(oldinode) || IS_IMMUTABLE(oldinode)) {
- iput (oldinode);
- iput (dir);
- return -EPERM;
- }
- if (oldinode->i_nlink >= EXT2_LINK_MAX) {
- iput (oldinode);
- iput (dir);
- return -EMLINK;
- }
- bh = ext2_find_entry (dir, name, len, &de);
- if (bh) {
- brelse (bh);
- iput (dir);
- iput (oldinode);
- return -EEXIST;
- }
- bh = ext2_add_entry (dir, name, len, &de, &err);
- if (!bh) {
- iput (dir);
- iput (oldinode);
- return err;
- }
- de->inode = oldinode->i_ino;
- dir->i_version = ++event;
- dcache_add(dir, de->name, de->name_len, de->inode);
- mark_buffer_dirty(bh, 1);
- if (IS_SYNC(dir)) {
- ll_rw_block (WRITE, 1, &bh);
- wait_on_buffer (bh);
- }
- brelse (bh);
- iput (dir);
- oldinode->i_nlink++;
- oldinode->i_ctime = CURRENT_TIME;
- oldinode->i_dirt = 1;
- iput (oldinode);
- return 0;
-}
static int subdir (struct inode * new_inode, struct inode * old_inode)
{
@@ -1085,6 +891,250 @@
return retval;
}
+
+int ext2_unlink (struct inode * dir, const char * name, int len)
+{
+ int retval;
+ struct inode * inode;
+ struct buffer_head * bh;
+ struct ext2_dir_entry * de;
+
+repeat:
+ if (!dir)
+ return -ENOENT;
+ retval = -ENOENT;
+ inode = NULL;
+ bh = ext2_find_entry (dir, name, len, &de);
+ if (!bh)
+ goto end_unlink;
+ if (!(inode = iget (dir->i_sb, de->inode)))
+ goto end_unlink;
+ if (inode->i_sb->dq_op)
+ inode->i_sb->dq_op->initialize (inode, -1);
+ retval = -EPERM;
+ if (S_ISDIR(inode->i_mode))
+ goto end_unlink;
+ if (IS_APPEND(inode) || IS_IMMUTABLE(inode))
+ goto end_unlink;
+ if (de->inode != inode->i_ino) {
+ iput(inode);
+ brelse(bh);
+ current->counter = 0;
+ schedule();
+ goto repeat;
+ }
+ if ((dir->i_mode & S_ISVTX) && !fsuser() &&
+ current->fsuid != inode->i_uid &&
+ current->fsuid != dir->i_uid)
+ goto end_unlink;
+ if (!inode->i_nlink) {
+ ext2_warning (inode->i_sb, "ext2_unlink",
+ "Deleting nonexistent file (%lu), %d",
+ inode->i_ino, inode->i_nlink);
+ inode->i_nlink = 1;
+ }
+#ifdef CONFIG_EXT2_UNDELETE
+ if (!fsuser ()) { /* "root" uses the old unlink */
+ if (inode->i_sb) {
+ struct inode *wb,*tmp;
+ const char *new_name;
+ char buf[EXT2_NAME_LEN];
+ int new_len;
+ int version;
+
+ inode->i_sb->s_mounted->i_count++;
+ if (ext2_lookup (inode->i_sb->s_mounted,".wastebasket",12,&wb) == 0) {
+ printk ("inode_c=%d, dir_c=%d,wb_c=%d\n",inode->i_count,dir->i_count,wb->i_count);
+ new_name=name;
+ new_len = len;
+ version = 0;
+ wb->i_count++;
+ while (ext2_lookup (wb,new_name,new_len,&tmp) == 0) {
+ iput (tmp);
+ sprintf (buf,"%s_%d",name,version++);
+ new_name = buf;
+ new_len = strlen (buf);
+ wb->i_count++;
+ }
+
+ printk ("moveing %s to wastebasket as %s.\n",name,new_name);
+ while (dir->i_sb->u.ext2_sb.s_rename_lock)
+ sleep_on (&dir->i_sb->u.ext2_sb.s_rename_wait);
+ dir->i_sb->u.ext2_sb.s_rename_lock = 1;
+
+ retval = do_ext2_rename (dir, name, len, wb, new_name, new_len);
+
+ dir->i_sb->u.ext2_sb.s_rename_lock = 0;
+ wake_up (&dir->i_sb->u.ext2_sb.s_rename_wait);
+ printk ("inode_c=%d, dir_c=%d,wb_c=%d\n",inode->i_count,dir->i_count,wb->i_count);
+ brelse (bh);
+ iput (inode);
+ return retval;
+#ifdef DEBUG_UNDELETE
+ } else {
+ printk ("No wastebasket. Old unlink for %s....\n",name);
+#endif
+ }
+ } else {
+ printk ("Warning: Undelete can't find a superblock for inode %ld\n"
+ "Deleting file normally.\n",inode->i_ino);
+ }
+ }
+#endif
+ retval = ext2_delete_entry (de, bh);
+ if (retval)
+ goto end_unlink;
+ dir->i_version = ++event;
+ mark_buffer_dirty(bh, 1);
+ if (IS_SYNC(dir)) {
+ ll_rw_block (WRITE, 1, &bh);
+ wait_on_buffer (bh);
+ }
+ dir->i_ctime = dir->i_mtime = CURRENT_TIME;
+ dir->i_dirt = 1;
+ inode->i_nlink--;
+ inode->i_dirt = 1;
+ inode->i_ctime = dir->i_ctime;
+ retval = 0;
+end_unlink:
+ brelse (bh);
+ iput (inode);
+ iput (dir);
+ return retval;
+}
+
+int ext2_symlink (struct inode * dir, const char * name, int len,
+ const char * symname)
+{
+ struct ext2_dir_entry * de;
+ struct inode * inode = NULL;
+ struct buffer_head * bh = NULL, * name_block = NULL;
+ char * link;
+ int i, err;
+ int l;
+ char c;
+
+ if (!(inode = ext2_new_inode (dir, S_IFLNK, &err))) {
+ iput (dir);
+ return err;
+ }
+ inode->i_mode = S_IFLNK | S_IRWXUGO;
+ inode->i_op = &ext2_symlink_inode_operations;
+ for (l = 0; l < inode->i_sb->s_blocksize - 1 &&
+ symname [l]; l++)
+ ;
+ if (l >= sizeof (inode->u.ext2_i.i_data)) {
+
+ ext2_debug ("l=%d, normal symlink\n", l);
+
+ name_block = ext2_bread (inode, 0, 1, &err);
+ if (!name_block) {
+ iput (dir);
+ inode->i_nlink--;
+ inode->i_dirt = 1;
+ iput (inode);
+ return err;
+ }
+ link = name_block->b_data;
+ } else {
+ link = (char *) inode->u.ext2_i.i_data;
+
+ ext2_debug ("l=%d, fast symlink\n", l);
+
+ }
+ i = 0;
+ while (i < inode->i_sb->s_blocksize - 1 && (c = *(symname++)))
+ link[i++] = c;
+ link[i] = 0;
+ if (name_block) {
+ mark_buffer_dirty(name_block, 1);
+ brelse (name_block);
+ }
+ inode->i_size = i;
+ inode->i_dirt = 1;
+
+ bh = ext2_find_entry (dir, name, len, &de);
+ if (bh) {
+ inode->i_nlink--;
+ inode->i_dirt = 1;
+ iput (inode);
+ brelse (bh);
+ iput (dir);
+ return -EEXIST;
+ }
+ bh = ext2_add_entry (dir, name, len, &de, &err);
+ if (!bh) {
+ inode->i_nlink--;
+ inode->i_dirt = 1;
+ iput (inode);
+ iput (dir);
+ return err;
+ }
+ de->inode = inode->i_ino;
+ dir->i_version = ++event;
+ dcache_add(dir, de->name, de->name_len, de->inode);
+ mark_buffer_dirty(bh, 1);
+ if (IS_SYNC(dir)) {
+ ll_rw_block (WRITE, 1, &bh);
+ wait_on_buffer (bh);
+ }
+ brelse (bh);
+ iput (dir);
+ iput (inode);
+ return 0;
+}
+
+int ext2_link (struct inode * oldinode, struct inode * dir,
+ const char * name, int len)
+{
+ struct ext2_dir_entry * de;
+ struct buffer_head * bh;
+ int err;
+
+ if (S_ISDIR(oldinode->i_mode)) {
+ iput (oldinode);
+ iput (dir);
+ return -EPERM;
+ }
+ if (IS_APPEND(oldinode) || IS_IMMUTABLE(oldinode)) {
+ iput (oldinode);
+ iput (dir);
+ return -EPERM;
+ }
+ if (oldinode->i_nlink >= EXT2_LINK_MAX) {
+ iput (oldinode);
+ iput (dir);
+ return -EMLINK;
+ }
+ bh = ext2_find_entry (dir, name, len, &de);
+ if (bh) {
+ brelse (bh);
+ iput (dir);
+ iput (oldinode);
+ return -EEXIST;
+ }
+ bh = ext2_add_entry (dir, name, len, &de, &err);
+ if (!bh) {
+ iput (dir);
+ iput (oldinode);
+ return err;
+ }
+ de->inode = oldinode->i_ino;
+ dir->i_version = ++event;
+ dcache_add(dir, de->name, de->name_len, de->inode);
+ mark_buffer_dirty(bh, 1);
+ if (IS_SYNC(dir)) {
+ ll_rw_block (WRITE, 1, &bh);
+ wait_on_buffer (bh);
+ }
+ brelse (bh);
+ iput (dir);
+ oldinode->i_nlink++;
+ oldinode->i_ctime = CURRENT_TIME;
+ oldinode->i_dirt = 1;
+ iput (oldinode);
+ return 0;
+}
/*
* Ok, rename also locks out other renames, as they can change the parent of
* a directory, and we don't want any races. Other races are checked for by
-- ** Q: What's the difference between MicroSoft Windows and a virus? ** ** A: Apart from the fact that virusses install easier, none. ** ** EMail: R.E.Wolff@et.tudelft.nl * Tel +31-15-2783643 or +31-15-2137459 ** *** <a href="http://einstein.et.tudelft.nl/~wolff/">my own homepage</a> ***