Msdos patch for aliased names

alknaff@innet.lu
Mon, 28 Jul 1997 22:51:17 +0200


The following patch repairs the problem with aliased names in the
Msdos filesystem. I.e. the following bug:

$ echo test >/mnt/xyz
$ cat /xyz
test
$ cat /Xyz
test
$ rm /xyz
$ cat /Xyz
test

The last cat /Xyz still finds the file, even though it has been
removed.

In order to avoid these problems, the filenames are not transformed
into "canonic" filenames, which allow only one representation per
valid MSDOS directory entry. Unfortunately, the name_translate_char
mechanism was not enough for that, because canonizing an MSDOS
filename may change its length (if for instance the basename was
longer than 8 characters, but also when using names without extension,
which can be represented with or without a trailing dot).

Moreover, the patch cleans up some instances of returning ENOENT
instead of transforming the dentry into a negative dentry

It also adds init_private_file which is needed for the binfmt modules
(unrelated to msdos)

Regards,

Alain

diff -ur 2.1.47/linux/fs/fat/fatfs_syms.c linux/fs/fat/fatfs_syms.c
--- 2.1.47/linux/fs/fat/fatfs_syms.c Fri Jul 25 22:45:50 1997
+++ linux/fs/fat/fatfs_syms.c Fri Jul 25 22:45:59 1997
@@ -47,6 +47,7 @@
EXPORT_SYMBOL(fat_uni2code);
EXPORT_SYMBOL(fat_unlock_creation);
EXPORT_SYMBOL(fat_write_inode);
+EXPORT_SYMBOL(fat_delete_inode);

int init_fat_fs(void)
{
diff -ur 2.1.47/linux/fs/msdos/namei.c linux/fs/msdos/namei.c
--- 2.1.47/linux/fs/msdos/namei.c Fri Jul 25 22:53:11 1997
+++ linux/fs/msdos/namei.c Mon Jul 28 22:38:35 1997
@@ -16,6 +16,8 @@
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/stat.h>
+#include <linux/mm.h>
+#include <linux/malloc.h>

#include <asm/uaccess.h>

@@ -201,10 +203,18 @@

PRINTK (("msdos_lookup\n"));

- if ((res = msdos_find(dir,dentry->d_name.name,dentry->d_name.len,&bh,&de,&ino)) < 0) {
+ if(!dir) {
d_add(dentry, NULL);
return 0;
}
+
+ if ((res = msdos_find(dir,dentry->d_name.name,dentry->d_name.len,&bh,&de,&ino)) < 0) {
+ if(res == -ENOENT) {
+ d_add(dentry, NULL);
+ res = 0;
+ }
+ return res;
+ }
PRINTK (("msdos_lookup 4\n"));
if (bh)
fat_brelse(sb, bh);
@@ -229,6 +239,7 @@
iput(inode);
if (!(inode = iget(next->i_sb,next->i_ino))) {
fat_fs_panic(dir->i_sb,"msdos_lookup: Can't happen");
+ d_add(dentry, NULL);
return -ENOENT;
}
}
@@ -248,6 +259,9 @@
struct msdos_dir_entry *de;
int res,ino;

+ if(!dir)
+ return -ENOENT;
+
*result = NULL;
if ((res = fat_scan(dir,NULL,&bh,&de,&ino,SCAN_ANY)) < 0) {
if (res != -ENOENT) return res;
@@ -383,7 +397,7 @@
if (!S_ISDIR(inode->i_mode)) goto rmdir_done;
res = -EBUSY;
if (dir->i_dev != inode->i_dev || dir == inode)
- goto rmdir_done;
+ goto rmdir_done;
res = msdos_empty(inode);
if (res)
goto rmdir_done;
@@ -583,7 +597,7 @@
struct inode *old_inode,*new_inode,*free_inode,*dotdot_inode;
struct dentry *walk;
int new_ino,free_ino,dotdot_ino;
- int error,exists,ino;
+ int error,exists;

if (old_dir->i_dev != new_dir->i_dev) return -EINVAL;
if (old_ino == new_dir->i_ino) return -EINVAL;
@@ -731,6 +745,52 @@
return error;
}

+static inline char toupper(char c)
+{
+ return (c >= 'a' && c <= 'z') ? c-32 : c;
+}
+
+#define NAME_ALLOC_LEN(len) ((len+16) & ~15)
+
+static int msdos_canonize(struct inode *inode, struct qstr *name)
+{
+ char *target, *newName;
+ const char *src, *end;
+ int i;
+
+ src = name->name;
+ end = name->name + name->len;
+
+ target = newName = kmalloc(NAME_ALLOC_LEN(name->len), GFP_KERNEL);
+ if(!target)
+ return -ENOSPC;
+
+ /* "hidden" dot */
+ if(*src == '.')
+ *target++ = *src++;
+
+ /* basename */
+ for(i=0; i<8; i++) {
+ if(src == end || !*src || *src == '.')
+ break;
+ *target++ = toupper(*src++);
+ }
+ /* skip everything until dot */
+ while(src != end && *src && *src != '.')
+ src++;
+
+ *target++='.';
+
+ /* extension */
+ for(i=0; i<3; i++) {
+ if(src == end || !*src || *src == '.')
+ break;
+ *target++ = toupper(*src++);
+ }
+ *target = '\0';
+ name->name = newName;
+ return 0;
+}

/* The public inode operations for the msdos fs */
struct inode_operations msdos_dir_inode_operations = {
@@ -750,7 +810,11 @@
NULL, /* writepage */
fat_bmap, /* bmap */
NULL, /* truncate */
- NULL /* permission */
+ NULL, /* permission */
+ NULL, /* smap */
+ NULL, /* updatepage */
+ NULL, /* revalidate */
+ msdos_canonize /* canonize */
};


diff -ur 2.1.47/linux/fs/namei.c linux/fs/namei.c
--- 2.1.47/linux/fs/namei.c Fri Jul 25 22:51:31 1997
+++ linux/fs/namei.c Mon Jul 28 22:14:29 1997
@@ -308,6 +308,28 @@
return result;
}

+static char calc_hash(struct dentry *base, struct qstr *this)
+{
+ int len;
+ unsigned long hash;
+ char c;
+ const char *name;
+
+ name = this->name;
+ hash = init_name_hash();
+ len = 0;
+ c = *name;
+ do {
+ len++; name++;
+ hash = partial_name_hash(c, hash);
+ c = *name;
+ } while (c && (c != '/'));
+
+ this->len = len;
+ this->hash = end_name_hash(hash);
+ return c;
+}
+
/* In difference to the former version, lookup() no longer eats the dir. */
static struct dentry * lookup(struct dentry * dir, struct qstr * name)
{
@@ -324,6 +346,15 @@
if (result)
goto done_noerror;

+ if(dir->d_inode && dir->d_inode->i_op->canonize_name) {
+ err = dir->d_inode->i_op->canonize_name(dir->d_inode,
+ name);
+ result = ERR_PTR(err);
+ if (err)
+ goto done_error;
+ calc_hash(dir, name);
+ }
+
result = cached_lookup(dir, name);
if (result)
goto done_noerror;
@@ -360,15 +391,6 @@
return dentry;
}

-/*
- * Allow a filesystem to translate the character set of
- * a file name. This allows for filesystems that are not
- * case-sensitive, for example.
- *
- * This is only a dummy define right now, but eventually
- * it might become something like "(parent)->d_charmap[c]"
- */
-#define name_translate_char(parent, c) (c)

/*
* Name resolution.
@@ -397,6 +419,7 @@
/* At this point we know we have a real path component. */
for(;;) {
int len;
+ const unsigned char *oname;
unsigned long hash;
struct qstr this;
char c, follow;
@@ -404,13 +427,12 @@
dentry = ERR_PTR(-ENOENT);
if (!base->d_inode)
break;
- this.name = name;
+ this.name = oname = name;
hash = init_name_hash();
len = 0;
c = *name;
do {
len++; name++;
- c = name_translate_char(base, c);
hash = partial_name_hash(c, hash);
c = *name;
} while (c && (c != '/'));
@@ -428,6 +450,10 @@
}

dentry = lookup(base, &this);
+
+ if(this.name != oname)
+ kfree(this.name);
+
if (IS_ERR(dentry))
break;

diff -ur 2.1.47/linux/include/linux/fs.h linux/include/linux/fs.h
--- 2.1.47/linux/include/linux/fs.h Fri Jul 25 22:52:47 1997
+++ linux/include/linux/fs.h Mon Jul 28 22:14:11 1997
@@ -564,6 +564,7 @@
int (*updatepage) (struct inode *, struct page *, const char *,
unsigned long, unsigned int, int);
int (*revalidate) (struct inode *);
+ int (*canonize_name) (struct inode *, struct qstr *);
};

struct super_operations {
diff -ur 2.1.47/linux/kernel/ksyms.c linux/kernel/ksyms.c
--- 2.1.47/linux/kernel/ksyms.c Fri Jul 25 22:48:20 1997
+++ linux/kernel/ksyms.c Fri Jul 25 22:47:43 1997
@@ -241,6 +241,7 @@
EXPORT_SYMBOL(search_binary_handler);
EXPORT_SYMBOL(prepare_binprm);
EXPORT_SYMBOL(remove_arg_zero);
+EXPORT_SYMBOL(init_private_file);

/* execution environment registration */
EXPORT_SYMBOL(lookup_exec_domain);