diff options
author | Ralf Baechle <ralf@linux-mips.org> | 1994-11-28 11:59:19 +0000 |
---|---|---|
committer | <ralf@linux-mips.org> | 1994-11-28 11:59:19 +0000 |
commit | 1513ff9b7899ab588401c89db0e99903dbf5f886 (patch) | |
tree | f69cc81a940a502ea23d664c3ffb2d215a479667 /fs/umsdos |
Import of Linus's Linux 1.1.68
Diffstat (limited to 'fs/umsdos')
-rw-r--r-- | fs/umsdos/Makefile | 44 | ||||
-rw-r--r-- | fs/umsdos/README | 84 | ||||
-rw-r--r-- | fs/umsdos/check.c | 55 | ||||
-rw-r--r-- | fs/umsdos/dir.c | 706 | ||||
-rw-r--r-- | fs/umsdos/emd.c | 505 | ||||
-rw-r--r-- | fs/umsdos/file.c | 103 | ||||
-rw-r--r-- | fs/umsdos/inode.c | 513 | ||||
-rw-r--r-- | fs/umsdos/ioctl.c | 259 | ||||
-rw-r--r-- | fs/umsdos/mangle.c | 478 | ||||
-rw-r--r-- | fs/umsdos/namei.c | 1043 | ||||
-rw-r--r-- | fs/umsdos/notes | 17 | ||||
-rw-r--r-- | fs/umsdos/rdir.c | 239 | ||||
-rw-r--r-- | fs/umsdos/symlink.c | 145 |
13 files changed, 4191 insertions, 0 deletions
diff --git a/fs/umsdos/Makefile b/fs/umsdos/Makefile new file mode 100644 index 000000000..cfba11e63 --- /dev/null +++ b/fs/umsdos/Makefile @@ -0,0 +1,44 @@ +# +# Makefile for the umsdos unix-like filesystem routines. +# +# Note! Dependencies are done automagically by 'make dep', which also +# removes any old dependencies. DON'T put your own dependencies here +# unless it's something special (ie not a .c file). +# +# Note 2! The CFLAGS definitions are now in the main makefile... + +ifndef CONFIG_UMSDOS_FS +CFLAGS := $(CFLAGS) -DMODULE +endif + +.c.s: + $(CC) $(CFLAGS) -S $< +.c.o: + $(CC) $(CFLAGS) -c $< +.s.o: + $(AS) -o $*.o $< + +OBJS= dir.o emd.o file.o inode.o ioctl.o mangle.o namei.o\ + rdir.o symlink.o #check.o + +umsdos.o: $(OBJS) + $(LD) -r -o umsdos.o $(OBJS) + +clean: + rm -f core *.o *.a *.s + +dep: + $(CPP) -M *.c > .depend + +p: + proto *.c >/usr/include/linux/umsdos_fs.p + +doc: + nadoc -i -p umsdos.doc - /tmp/umsdos.mpg + +# +# include a dependency file if one exists +# +ifeq (.depend,$(wildcard .depend)) +include .depend +endif diff --git a/fs/umsdos/README b/fs/umsdos/README new file mode 100644 index 000000000..4ce8b4148 --- /dev/null +++ b/fs/umsdos/README @@ -0,0 +1,84 @@ +Very short explanation for the impatient!!! + +Umsdos is a file system driver that run on top the MSDOS fs driver. +It is written by Jacques Gelinas (jacques@solucorp.qc.ca) + +Umsdos is not a file system per se, but a twist to make a boring +one into a useful one. + +It gives you: + + long file name + Permissions and owner + Links + Special files (devices, pipe...) + All is need to be a linux root fs. + +There is plenty of documentation on it in the source. A formated document +made from those comments is available from +sunsite.unc.edu:/pub/Linux/ALPHA/umsdos + +Mostly... + +You mount a DOS partition like this + +mount -t umsdos /dev/hda3 /mnt + ^ +---------| + +All option are passed to the msdos drivers. Option like uid,gid etc are +given to msdos. + +The default behavior of Umsdos is to do the same thing as the msdos driver +mostly passing commands to it without much processing. Again, this is +the default. After doing the mount on a DOS partition, nothing special +happen. This is why all mount options are passed to the Msdos fs driver. + +Umsdos use a special DOS file --linux-.--- to store the information +which can't be handle by the normal MsDOS file system. This is the trick. + +--linux-.--- is optional. There is one per directory. + +**** If --linux-.--- is missing, then Umsdos process the directory the + same way the msdos driver do. Short file name, no goodies, default + owner and permissions. So each directory may have or not this + --linux-.--- + +Now, how to get those --linux-.---. + +\begin joke_section + + Well send me a directory content + and I will send you one customised for you. + $5 per directory. Add any applicable taxes. +\end joke_section + +A utility umssync creates those and maintain them. It is available +from the same directory above (sunsite) in the file umsdos_progs-0.3.tar.gz. +A compiled version is available in umsdos-0.3a.bin.tar.gz. + +So in our example, after mounting mnt, we do + +umssync . + +This will promote this directory (a recursive option is available) to full +umsdos capabilities (long name ...). A ls -l before and after won't show +much difference however. The file which were there are still there. But now +you can do all this: + + chmod 644 * + chown you.your_groupe * + ls >THIS_IS.A.VERY.LONG.NAME + ln -s toto tata + ls -l + +Once a directory is promoted, all subdirectory created will inherit that +promotion. + +What happen if you boot DOS and create files in those promoted directories ? +Umsdos won't notice new files, but will signal removed file (it won't crash). +Using umssync in /etc/rc will make sure the DOS directory is in sync with +the --linux-.---. + +Hope this helps! + diff --git a/fs/umsdos/check.c b/fs/umsdos/check.c new file mode 100644 index 000000000..d1102b4ce --- /dev/null +++ b/fs/umsdos/check.c @@ -0,0 +1,55 @@ +#include <asm/system.h> + +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/head.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/ptrace.h> +#include <linux/mman.h> + +extern unsigned long high_memory; + +static int check_one_table(unsigned long * page_dir) +{ + unsigned long pg_table = *page_dir; + + if (!pg_table) + return 0; + if (pg_table >= high_memory || !(pg_table & PAGE_PRESENT)) { + return 1; + } + return 0; +} + +/* + * This function frees up all page tables of a process when it exits. + */ +void check_page_tables(void) +{ + unsigned long pg_dir; + static int err = 0; + + int stack_level = (long)(&pg_dir)-current->kernel_stack_page; + if (stack_level < 1500) printk ("** %d ** ",stack_level); + pg_dir = current->tss.cr3; + if (mem_map[MAP_NR(pg_dir)] > 1) { + return; + } + if (err == 0){ + unsigned long *page_dir = (unsigned long *) pg_dir; + unsigned long *base = page_dir; + int i; + for (i = 0 ; i < PTRS_PER_PAGE ; i++,page_dir++){ + int notok = check_one_table(page_dir); + if (notok){ + err++; + printk ("|%d| ",page_dir-base); + } + } + if (err) printk ("Erreur MM %d\n",err); + } +} + diff --git a/fs/umsdos/dir.c b/fs/umsdos/dir.c new file mode 100644 index 000000000..2a668d102 --- /dev/null +++ b/fs/umsdos/dir.c @@ -0,0 +1,706 @@ +/* + * linux/fs/umsdos/dir.c + * + * Written 1993 by Jacques Gelinas + * Inspired from linux/fs/msdos/... : Werner Almesberger + * + * Extended MS-DOS directory handling functions + */ + +#include <asm/segment.h> + +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/fs.h> +#include <linux/msdos_fs.h> +#include <linux/errno.h> +#include <linux/stat.h> +#include <linux/limits.h> +#include <linux/umsdos_fs.h> +#include <linux/malloc.h> + +#define PRINTK(x) +#define Printk(x) printk x + +#define UMSDOS_SPECIAL_DIRFPOS 3 +extern struct inode *pseudo_root; +/* + So grep * doesn't complain in the presence of directories. +*/ +int UMSDOS_dir_read(struct inode *inode,struct file *filp,char *buf, + int count) +{ + return -EISDIR; +} +/* + Read count directory entries from directory filp + Return a negative value from linux/errno.h. + Return > 0 if success (the length of the file name). + + This function is used by the normal readdir VFS entry point and by + some function who try to find out info on a file from a pure MSDOS + inode. See umsdos_locate_ancestor() below. +*/ +static int umsdos_readdir_x( + struct inode *dir, /* Point to a description of the super block */ + struct file *filp, /* Point to a directory which is read */ + struct dirent *dirent, /* Will hold count directory entry */ + int dirent_in_fs, /* dirent point in user's space ? */ + int count, + struct umsdos_dirent *u_entry, /* Optional umsdos entry */ + int follow_hlink, + off_t *pt_f_pos) /* will hold the offset of the entry in EMD */ +{ + int ret = 0; + + umsdos_startlookup(dir); + if (filp->f_pos == UMSDOS_SPECIAL_DIRFPOS + && dir == pseudo_root + && dirent_in_fs){ + /* + We don't need to simulate this pseudo directory + when umsdos_readdir_x is called for internal operation + of umsdos. This is why dirent_in_fs is tested + */ + /* #Specification: pseudo root / directory /DOS + When umsdos operates in pseudo root mode (C:\linux is the + linux root), it simulate a directory /DOS which points to + the real root of the file system. + */ + put_fs_long(dir->i_sb->s_mounted->i_ino,&dirent->d_ino); + memcpy_tofs (dirent->d_name,"DOS",3); + put_fs_byte(0,dirent->d_name+3); + put_fs_word (3,&dirent->d_reclen); + if (u_entry != NULL) u_entry->flags = 0; + ret = 3; + filp->f_pos++; + }else if (filp->f_pos < 2 + || (dir != dir->i_sb->s_mounted && filp->f_pos == 32)){ + /* #Specification: readdir / . and .. + The msdos filesystem manage the . and .. entry properly + so the EMD file won't hold any info about it. + + In readdir, we assume that for the root directory + the read position will be 0 for ".", 1 for "..". For + a non root directory, the read position will be 0 for "." + and 32 for "..". + */ + /* + This is a trick used by the msdos file system (fs/msdos/dir.c) + to manage . and .. for the root directory of a file system. + Since there is no such entry in the root, fs/msdos/dir.c + use the following: + + if f_pos == 0, return ".". + if f_pos == 1, return "..". + + So let msdos handle it + + Since umsdos entries are much larger, we share the same f_pos. + if f_pos is 0 or 1 or 32, we are clearly looking at . and + .. + + As soon as we get f_pos == 2 or f_pos == 64, then back to + 0, but this time we are reading the EMD file. + + Well, not so true. The problem, is that UMSDOS_REC_SIZE is + also 64, so as soon as we read the first record in the + EMD, we are back at offset 64. So we set the offset + to UMSDOS_SPECIAL_DIRFPOS(3) as soon as we have read the + .. entry from msdos. + */ + ret = msdos_readdir(dir,filp,dirent,count); + if (filp->f_pos == 64) filp->f_pos = UMSDOS_SPECIAL_DIRFPOS; + if (u_entry != NULL) u_entry->flags = 0; + }else{ + struct inode *emd_dir = umsdos_emd_dir_lookup(dir,0); + if (emd_dir != NULL){ + if (filp->f_pos <= UMSDOS_SPECIAL_DIRFPOS+1) filp->f_pos = 0; + PRINTK (("f_pos %ld i_size %d\n",filp->f_pos,emd_dir->i_size)); + ret = 0; + while (filp->f_pos < emd_dir->i_size){ + struct umsdos_dirent entry; + off_t cur_f_pos = filp->f_pos; + if (umsdos_emd_dir_readentry (emd_dir,filp,&entry)!=0){ + ret = -EIO; + break; + }else if (entry.name_len != 0){ + /* #Specification: umsdos / readdir + umsdos_readdir() should fill a struct dirent with + an inode number. The cheap way to get it is to + do a lookup in the MSDOS directory for each + entry processed by the readdir() function. + This is not very efficient, but very simple. The + other way around is to maintain a copy of the inode + number in the EMD file. This is a problem because + this has to be maintained in sync using tricks. + Remember that MSDOS (the OS) does not update the + modification time (mtime) of a directory. There is + no easy way to tell that a directory was modified + during a DOS session and synchronise the EMD file. + + Suggestion welcome. + + So the easy way is used! + */ + struct umsdos_info info; + struct inode *inode; + int lret; + umsdos_parse (entry.name,entry.name_len,&info); + info.f_pos = cur_f_pos; + *pt_f_pos = cur_f_pos; + umsdos_manglename (&info); + lret = umsdos_real_lookup (dir,info.fake.fname + ,info.fake.len,&inode); + PRINTK (("Cherche inode de %s lret %d flags %d\n" + ,info.fake.fname,lret,entry.flags)); + if (lret == 0 + && (entry.flags & UMSDOS_HLINK) + && follow_hlink){ + struct inode *rinode; + lret = umsdos_hlink2inode (inode,&rinode); + inode = rinode; + } + if (lret == 0){ + /* #Specification: pseudo root / reading real root + The pseudo root (/linux) is logically + erased from the real root. This mean that + ls /DOS, won't show "linux". This avoids + infinite recursion /DOS/linux/DOS/linux while + walking the file system. + */ + if (inode != pseudo_root){ + PRINTK (("Trouve ino %d ",inode->i_ino)); + if (dirent_in_fs){ + put_fs_long(inode->i_ino,&dirent->d_ino); + memcpy_tofs (dirent->d_name,entry.name + ,entry.name_len); + put_fs_byte(0,dirent->d_name+entry.name_len); + put_fs_word (entry.name_len + ,&dirent->d_reclen); + /* In this case, the caller only needs */ + /* flags */ + if (u_entry != NULL){ + u_entry->flags = entry.flags; + } + }else{ + dirent->d_ino = inode->i_ino; + memcpy (dirent->d_name,entry.name + ,entry.name_len); + dirent->d_name[entry.name_len] = '\0'; + dirent->d_reclen = entry.name_len; + if (u_entry != NULL) *u_entry = entry; + } + ret = entry.name_len; + iput (inode); + break; + } + iput (inode); + }else{ + /* #Specification: umsdos / readdir / not in MSDOS + During a readdir operation, if the file is not + in the MSDOS directory anymore, the entry is + removed from the EMD file silently. + */ + ret = umsdos_writeentry (dir,emd_dir,&info,1); + if (ret != 0){ + break; + } + } + } + } + iput(emd_dir); + } + } + umsdos_endlookup(dir); + PRINTK (("read dir %p pos %d ret %d\n",dir,filp->f_pos,ret)); + return ret; +} +/* + Read count directory entries from directory filp + Return a negative value from linux/errno.h. + Return > 0 if success (the length of the file name). +*/ +static int UMSDOS_readdir( + struct inode *dir, /* Point to a description of the super block */ + struct file *filp, /* Point to a directory which is read */ + struct dirent *dirent, /* Will hold count directory entry */ + int count) +{ + int ret = -ENOENT; + while (1){ + struct umsdos_dirent entry; + off_t f_pos; + ret = umsdos_readdir_x (dir,filp,dirent,1,count,&entry,1,&f_pos); + if (ret <= 0 || !(entry.flags & UMSDOS_HIDDEN)) break; + } + return ret; +} +/* + Complete the inode content with info from the EMD file +*/ +void umsdos_lookup_patch ( + struct inode *dir, + struct inode *inode, + struct umsdos_dirent *entry, + off_t emd_pos) +{ + /* + This function modify the state of a dir inode. It decides + if the dir is a umsdos dir or a dos dir. This is done + deeper in umsdos_patch_inode() called at the end of this function. + + umsdos_patch_inode() may block because it is doing disk access. + At the same time, another process may get here to initialise + the same dir inode. There is 3 cases. + + 1-The inode is already initialised. We do nothing. + 2-The inode is not initialised. We lock access and do it. + 3-Like 2 but another process has lock the inode, so we try + to lock it and right after check if initialisation is still + needed. + + + Thanks to the mem option of the kernel command line, it was + possible to consistently reproduce this problem by limiting + my mem to 4 meg and running X. + */ + /* + Do this only if the inode is freshly read, because we will lose + the current (updated) content. + */ + /* + A lookup of a mount point directory yield the inode into + the other fs, so we don't care about initialising it. iget() + does this automatically. + */ + if (inode->i_sb == dir->i_sb && !umsdos_isinit(inode)){ + if (S_ISDIR(inode->i_mode)) umsdos_lockcreate(inode); + if (!umsdos_isinit(inode)){ + /* #Specification: umsdos / lookup / inode info + After successfully reading an inode from the MSDOS + filesystem, we use the EMD file to complete it. + We update the following field. + + uid, gid, atime, ctime, mtime, mode. + + We rely on MSDOS for mtime. If the file + was modified during an MSDOS session, at least + mtime will be meaningful. We do this only for regular + file. + + We don't rely on MSDOS for mtime for directory because + the MSDOS directory date is creation time (strange + MSDOS behavior) which fit nowhere in the three UNIX + time stamp. + */ + if (S_ISREG(entry->mode)) entry->mtime = inode->i_mtime; + inode->i_mode = entry->mode; + inode->i_rdev = entry->rdev; + inode->i_atime = entry->atime; + inode->i_ctime = entry->ctime; + inode->i_mtime = entry->mtime; + inode->i_uid = entry->uid; + inode->i_gid = entry->gid; + /* #Specification: umsdos / i_nlink + The nlink field of an inode is maintain by the MSDOS file system + for directory and by UMSDOS for other file. The logic is that + MSDOS is already figuring out what to do for directories and + does nothing for other files. For MSDOS, there are no hard link + so all file carry nlink==1. UMSDOS use some info in the + EMD file to plug the correct value. + */ + if (!S_ISDIR(entry->mode)){ + if (entry->nlink > 0){ + inode->i_nlink = entry->nlink; + }else{ + printk ("UMSDOS: lookup_patch entry->nlink < 1 ???\n"); + } + } + umsdos_patch_inode(inode,dir,emd_pos); + } + if (S_ISDIR(inode->i_mode)) umsdos_unlockcreate(inode); +if (inode->u.umsdos_i.i_emd_owner==0) printk ("emd_owner still 0 ???\n"); + } +} +/* + Locate entry of an inode in a directory. + Return 0 or a negative error code. + + Normally, this function must succeed. It means a strange corruption + in the file system if not. +*/ +int umsdos_inode2entry ( + struct inode *dir, + struct inode *inode, + struct umsdos_dirent *entry) /* Will hold the entry */ +{ + int ret = -ENOENT; + if (inode == pseudo_root){ + /* + Quick way to find the name. + Also umsdos_readdir_x won't show /linux anyway + */ + memcpy (entry->name,UMSDOS_PSDROOT_NAME,UMSDOS_PSDROOT_LEN+1); + entry->name_len = UMSDOS_PSDROOT_LEN; + ret = 0; + }else{ + struct inode *emddir = umsdos_emd_dir_lookup(dir,0); + iput (emddir); + if (emddir == NULL){ + /* This is a DOS directory */ + struct file filp; + filp.f_reada = 1; + filp.f_pos = 0; + while (1){ + struct dirent dirent; + if (umsdos_readdir_kmem (dir,&filp,&dirent,1) <= 0){ + printk ("UMSDOS: can't locate inode %ld in DOS directory???\n" + ,inode->i_ino); + }else if (dirent.d_ino == inode->i_ino){ + ret = 0; + memcpy (entry->name,dirent.d_name,dirent.d_reclen); + entry->name[dirent.d_reclen] = '\0'; + entry->name_len = dirent.d_reclen; + inode->u.umsdos_i.i_dir_owner = dir->i_ino; + inode->u.umsdos_i.i_emd_owner = 0; + umsdos_setup_dir_inode(inode); + break; + } + } + }else{ + /* skip . and .. see umsdos_readdir_x() */ + struct file filp; + filp.f_reada = 1; + filp.f_pos = UMSDOS_SPECIAL_DIRFPOS; + while (1){ + struct dirent dirent; + off_t f_pos; + if (umsdos_readdir_x(dir,&filp,&dirent + ,0,1,entry,0,&f_pos) <= 0){ + printk ("UMSDOS: can't locate inode %ld in EMD file???\n" + ,inode->i_ino); + break; + }else if (dirent.d_ino == inode->i_ino){ + ret = 0; + umsdos_lookup_patch (dir,inode,entry,f_pos); + break; + } + } + } + } + return ret; +} +/* + Locate the parent of a directory and the info on that directory + Return 0 or a negative error code. +*/ +static int umsdos_locate_ancestor ( + struct inode *dir, + struct inode **result, + struct umsdos_dirent *entry) +{ + int ret; + umsdos_patch_inode (dir,NULL,0); + ret = umsdos_real_lookup (dir,"..",2,result); + PRINTK (("result %d %x ",ret,*result)); + if (ret == 0){ + struct inode *adir = *result; + ret = umsdos_inode2entry (adir,dir,entry); + } + PRINTK (("\n")); + return ret; +} +/* + Build the path name of an inode (relative to the file system. + This function is need to set (pseudo) hard link. + + It uses the same strategy as the standard getcwd(). +*/ +int umsdos_locate_path ( + struct inode *inode, + char *path) +{ + int ret = 0; + struct inode *dir = inode; + char *bpath = (char*)kmalloc(PATH_MAX,GFP_KERNEL); + if (bpath == NULL){ + ret = -ENOMEM; + }else{ + struct umsdos_dirent entry; + char *ptbpath = bpath+PATH_MAX-1; + *ptbpath = '\0'; + PRINTK (("locate_path mode %x ",inode->i_mode)); + if (!S_ISDIR(inode->i_mode)){ + ret = umsdos_get_dirowner (inode,&dir); + PRINTK (("locate_path ret %d ",ret)); + if (ret == 0){ + ret = umsdos_inode2entry (dir,inode,&entry); + if (ret == 0){ + ptbpath -= entry.name_len; + memcpy (ptbpath,entry.name,entry.name_len); + PRINTK (("ptbpath :%s: ",ptbpath)); + } + } + }else{ + dir->i_count++; + } + if (ret == 0){ + while (dir != dir->i_sb->s_mounted){ + struct inode *adir; + ret = umsdos_locate_ancestor (dir,&adir,&entry); + iput (dir); + dir = NULL; + PRINTK (("ancestor %d ",ret)); + if (ret == 0){ + *--ptbpath = '/'; + ptbpath -= entry.name_len; + memcpy (ptbpath,entry.name,entry.name_len); + dir = adir; + PRINTK (("ptbpath :%s: ",ptbpath)); + }else{ + break; + } + } + } + strcpy (path,ptbpath); + kfree (bpath); + } + PRINTK (("\n")); + iput (dir); + return ret; +} + +/* + Return != 0 if an entry is the pseudo DOS entry in the pseudo root. +*/ +int umsdos_is_pseudodos ( + struct inode *dir, + const char *name, + int len) +{ + /* #Specification: pseudo root / DOS hard coded + The pseudo sub-directory DOS in the pseudo root is hard coded. + The name is DOS. This is done this way to help standardised + the umsdos layout. The idea is that from now on /DOS is + a reserved path and nobody will think of using such a path + for a package. + */ + return dir == pseudo_root + && len == 3 + && name[0] == 'D' && name[1] == 'O' && name[2] == 'S'; +} +/* + Check if a file exist in the current directory. + Return 0 if ok, negative error code if not (ex: -ENOENT). +*/ +static int umsdos_lookup_x ( + struct inode *dir, + const char *name, + int len, + struct inode **result, /* Will hold inode of the file, if successful */ + int nopseudo) /* Don't care about pseudo root mode */ +{ + int ret = -ENOENT; + *result = NULL; + umsdos_startlookup(dir); + if (len == 1 && name[0] == '.'){ + *result = dir; + dir->i_count++; + ret = 0; + }else if (len == 2 && name[0] == '.' && name[1] == '.'){ + if (pseudo_root != NULL && dir == pseudo_root->i_sb->s_mounted){ + /* #Specification: pseudo root / .. in real root + Whenever a lookup is those in the real root for + the directory .., and pseudo root is active, the + pseudo root is returned. + */ + ret = 0; + *result = pseudo_root; + pseudo_root->i_count++; + }else{ + /* #Specification: locating .. / strategy + We use the msdos filesystem to locate the parent directory. + But it is more complicated than that. + + We have to step back even further to + get the parent of the parent, so we can get the EMD + of the parent of the parent. Using the EMD file, we can + locate all the info on the parent, such a permissions + and owner. + */ + ret = umsdos_real_lookup (dir,"..",2,result); + PRINTK (("ancestor ret %d dir %p *result %p ",ret,dir,*result)); + if (ret == 0 + && *result != dir->i_sb->s_mounted + && *result != pseudo_root){ + struct inode *aadir; + struct umsdos_dirent entry; + ret = umsdos_locate_ancestor (*result,&aadir,&entry); + iput (aadir); + } + } + }else if (umsdos_is_pseudodos(dir,name,len)){ + /* #Specification: pseudo root / lookup(DOS) + A lookup of DOS in the pseudo root will always succeed + and return the inode of the real root. + */ + *result = dir->i_sb->s_mounted; + (*result)->i_count++; + ret = 0; + }else{ + struct umsdos_info info; + ret = umsdos_parse (name,len,&info); + if (ret == 0) ret = umsdos_findentry (dir,&info,0); + PRINTK (("lookup %s pos %d ret %d len %d ",info.fake.fname,info.f_pos,ret + ,info.fake.len)); + if (ret == 0){ + /* #Specification: umsdos / lookup + A lookup for a file is done in two step. First, we locate + the file in the EMD file. If not present, we return + an error code (-ENOENT). If it is there, we repeat the + operation on the msdos file system. If this fails, it means + that the file system is not in sync with the emd file. + We silently remove this entry from the emd file, + and return ENOENT. + */ + struct inode *inode; + ret = umsdos_real_lookup (dir,info.fake.fname,info.fake.len,result); + inode = *result; + if (inode == NULL){ + printk ("UMSDOS: Erase entry %s, out of sync with MsDOS\n" + ,info.fake.fname); + umsdos_delentry (dir,&info,S_ISDIR(info.entry.mode)); + }else{ + umsdos_lookup_patch (dir,inode,&info.entry,info.f_pos); + PRINTK (("lookup ino %d flags %d\n",inode->i_ino + ,info.entry.flags)); + if (info.entry.flags & UMSDOS_HLINK){ + ret = umsdos_hlink2inode (inode,result); + } + if (*result == pseudo_root && !nopseudo){ + /* #Specification: pseudo root / dir lookup + For the same reason as readdir, a lookup in /DOS for + the pseudo root directory (linux) will fail. + */ + /* + This has to be allowed for resolving hard link + which are recorded independently of the pseudo-root + mode. + */ + iput (pseudo_root); + *result = NULL; + ret = -ENOENT; + } + } + } + } + umsdos_endlookup(dir); + iput (dir); + return ret; +} +/* + Check if a file exist in the current directory. + Return 0 if ok, negative error code if not (ex: -ENOENT). +*/ +int UMSDOS_lookup ( + struct inode *dir, + const char *name, + int len, + struct inode **result) /* Will hold inode of the file, if successful */ +{ + return umsdos_lookup_x(dir,name,len,result,0); +} +/* + Locate the inode pointed by a (pseudo) hard link + Return 0 if ok, a negative error code if not. +*/ +int umsdos_hlink2inode (struct inode *hlink, struct inode **result) +{ + int ret = -EIO; + char *path = (char*)kmalloc(PATH_MAX,GFP_KERNEL); + *result = NULL; + if (path == NULL){ + ret = -ENOMEM; + iput (hlink); + }else{ + struct file filp; + filp.f_reada = 1; + filp.f_pos = 0; + PRINTK (("hlink2inode ")); + if (umsdos_file_read_kmem (hlink,&filp,path,hlink->i_size) + ==hlink->i_size){ + struct inode *dir; + char *pt = path; + dir = hlink->i_sb->s_mounted; + path[hlink->i_size] = '\0'; + iput (hlink); + dir->i_count++; + while (1){ + char *start = pt; + int len; + while (*pt != '\0' && *pt != '/') pt++; + len = (int)(pt - start); + if (*pt == '/') *pt++ = '\0'; + if (dir->u.umsdos_i.i_emd_dir == 0){ + /* This is a DOS directory */ + ret = msdos_lookup(dir,start,len,result); + }else{ + ret = umsdos_lookup_x(dir,start,len,result,1); + } + PRINTK (("h2n lookup :%s: -> %d ",start,ret)); + if (ret == 0 && *pt != '\0'){ + dir = *result; + }else{ + break; + } + } + }else{ + iput (hlink); + } + PRINTK (("hlink2inode ret = %d %p -> %p\n",ret,hlink,*result)); + kfree (path); + } + return ret; +} + +static struct file_operations umsdos_dir_operations = { + NULL, /* lseek - default */ + UMSDOS_dir_read, /* read */ + NULL, /* write - bad */ + UMSDOS_readdir, /* readdir */ + NULL, /* select - default */ + UMSDOS_ioctl_dir, /* ioctl - default */ + NULL, /* mmap */ + NULL, /* no special open code */ + NULL, /* no special release code */ + NULL /* fsync */ +}; + +struct inode_operations umsdos_dir_inode_operations = { + &umsdos_dir_operations, /* default directory file-ops */ + UMSDOS_create, /* create */ + UMSDOS_lookup, /* lookup */ + UMSDOS_link, /* link */ + UMSDOS_unlink, /* unlink */ + UMSDOS_symlink, /* symlink */ + UMSDOS_mkdir, /* mkdir */ + UMSDOS_rmdir, /* rmdir */ + UMSDOS_mknod, /* mknod */ + UMSDOS_rename, /* rename */ + NULL, /* readlink */ + NULL, /* follow_link */ + NULL, /* bmap */ + NULL, /* truncate */ + NULL /* permission */ +}; + + + + + + + + + + diff --git a/fs/umsdos/emd.c b/fs/umsdos/emd.c new file mode 100644 index 000000000..e4d6a9470 --- /dev/null +++ b/fs/umsdos/emd.c @@ -0,0 +1,505 @@ +/* + * linux/fs/umsdos/emd.c + * + * Written 1993 by Jacques Gelinas + * + * Extended MS-DOS directory handling functions + */ +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/kernel.h> +#include <asm/segment.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/msdos_fs.h> +#include <linux/umsdos_fs.h> + +#define PRINTK(x) +#define Printk(x) printk x + +int umsdos_readdir_kmem( + struct inode *inode, + struct file *filp, + struct dirent *dirent, + int count) +{ + int ret; + int old_fs = get_fs(); + set_fs (KERNEL_DS); + ret = msdos_readdir(inode,filp,dirent,count); + set_fs (old_fs); + return ret; +} +/* + Read a file into kernel space memory +*/ +int umsdos_file_read_kmem( + struct inode *inode, + struct file *filp, + char *buf, + int count) +{ + int ret; + int old_fs = get_fs(); + set_fs (KERNEL_DS); + ret = msdos_file_read(inode,filp,buf,count); + set_fs (old_fs); + return ret; +} +/* + Write to a file from kernel space +*/ +int umsdos_file_write_kmem( + struct inode *inode, + struct file *filp, + char *buf, + int count) +{ + int ret; + int old_fs = get_fs(); + set_fs (KERNEL_DS); + ret = msdos_file_write(inode,filp,buf,count); + set_fs (old_fs); + return ret; +} + + +/* + Write a block of bytes into one EMD file. + The block of data is NOT in user space. + + Return 0 if ok, a negative error code if not. +*/ +int umsdos_emd_dir_write ( + struct inode *emd_dir, + struct file *filp, + char *buf, /* buffer in kernel memory, not in user space */ + int count) +{ + int written; + filp->f_flags = 0; + written = umsdos_file_write_kmem (emd_dir,filp,buf,count); + return written != count ? -EIO : 0; +} +/* + Read a block of bytes from one EMD file. + The block of data is NOT in user space. + Return 0 if ok, -EIO if any error. +*/ +int umsdos_emd_dir_read ( + struct inode *emd_dir, + struct file *filp, + char *buf, /* buffer in kernel memory, not in user space */ + int count) +{ + int ret = 0; + int sizeread; + filp->f_flags = 0; + sizeread = umsdos_file_read_kmem (emd_dir,filp,buf,count); + if (sizeread != count){ + printk ("UMSDOS: problem with EMD file. Can't read\n"); + ret = -EIO; + } + return ret; + +} +/* + Locate the EMD file in a directory and optionally, creates it. + + Return NULL if error. If ok, dir->u.umsdos_i.emd_inode +*/ +struct inode *umsdos_emd_dir_lookup(struct inode *dir, int creat) +{ + struct inode *ret = NULL; + if (dir->u.umsdos_i.i_emd_dir != 0){ + ret = iget (dir->i_sb,dir->u.umsdos_i.i_emd_dir); + PRINTK (("deja trouve %d %x [%d] " + ,dir->u.umsdos_i.i_emd_dir,ret,ret->i_count)); + }else{ + umsdos_real_lookup (dir,UMSDOS_EMD_FILE,UMSDOS_EMD_NAMELEN,&ret); + PRINTK (("emd_dir_lookup ")); + if (ret != NULL){ + PRINTK (("Find --linux ")); + dir->u.umsdos_i.i_emd_dir = ret->i_ino; + }else if (creat){ + int code; + PRINTK (("avant create ")); + dir->i_count++; + code = msdos_create (dir,UMSDOS_EMD_FILE,UMSDOS_EMD_NAMELEN + ,S_IFREG|0777,&ret); + PRINTK (("Creat EMD code %d ret %x ",code,ret)); + if (ret != NULL){ + dir->u.umsdos_i.i_emd_dir = ret->i_ino; + }else{ + printk ("UMSDOS: Can't create EMD file\n"); + } + } + } + if (ret != NULL){ + /* Disable UMSDOS_notify_change() for EMD file */ + ret->u.umsdos_i.i_emd_owner = 0xffffffff; + } + return ret; +} + +/* + Read an entry from the EMD file. + Support variable length record. + Return -EIO if error, 0 if ok. +*/ +int umsdos_emd_dir_readentry ( + struct inode *emd_dir, + struct file *filp, + struct umsdos_dirent *entry) +{ + int ret = umsdos_emd_dir_read(emd_dir,filp,(char*)entry,UMSDOS_REC_SIZE); + if (ret == 0){ + /* Variable size record. Maybe, we have to read some more */ + int recsize = umsdos_evalrecsize (entry->name_len); + if (recsize > UMSDOS_REC_SIZE){ + ret = umsdos_emd_dir_read(emd_dir,filp + ,((char*)entry)+UMSDOS_REC_SIZE,recsize - UMSDOS_REC_SIZE); + + } + } + return ret; +} +/* + Write an entry in the EMD file. + Return 0 if ok, -EIO if some error. +*/ +int umsdos_writeentry ( + struct inode *dir, + struct inode *emd_dir, + struct umsdos_info *info, + int free_entry) /* This entry is deleted, so Write all 0's */ +{ + int ret = 0; + struct file filp; + struct umsdos_dirent *entry = &info->entry; + struct umsdos_dirent entry0; + if (free_entry){ + /* #Specification: EMD file / empty entries + Unused entry in the EMD file are identify + by the name_len field equal to 0. However to + help future extension (or bug correction :-( ), + empty entries are filled with 0. + */ + memset (&entry0,0,sizeof(entry0)); + entry = &entry0; + }else if (entry->name_len > 0){ + memset (entry->name+entry->name_len,'\0' + ,sizeof(entry->name)-entry->name_len); + /* #Specification: EMD file / spare bytes + 10 bytes are unused in each record of the EMD. They + are set to 0 all the time. So it will be possible + to do new stuff and rely on the state of those + bytes in old EMD file around. + */ + memset (entry->spare,0,sizeof(entry->spare)); + } + filp.f_pos = info->f_pos; + filp.f_reada = 0; + ret = umsdos_emd_dir_write(emd_dir,&filp,(char*)entry,info->recsize); + if (ret != 0){ + printk ("UMSDOS: problem with EMD file. Can't write\n"); + }else{ + dir->i_ctime = dir->i_mtime = CURRENT_TIME; + dir->i_dirt = 1; + } + return ret; +} + +#define CHUNK_SIZE (8*UMSDOS_REC_SIZE) +struct find_buffer{ + char buffer[CHUNK_SIZE]; + int pos; /* read offset in buffer */ + int size; /* Current size of buffer */ + struct file filp; +}; + +/* + Fill the read buffer and take care of the byte remaining inside. + Unread bytes are simply move to the beginning. + + Return -ENOENT if EOF, 0 if ok, a negative error code if any problem. +*/ +static int umsdos_fillbuf ( + struct inode *inode, + struct find_buffer *buf) +{ + int ret = -ENOENT; + int mustmove = buf->size - buf->pos; + int mustread; + int remain; + if (mustmove > 0){ + memcpy (buf->buffer,buf->buffer+buf->pos,mustmove); + } + buf->pos = 0; + mustread = CHUNK_SIZE - mustmove; + remain = inode->i_size - buf->filp.f_pos; + if (remain < mustread) mustread = remain; + if (mustread > 0){ + ret = umsdos_emd_dir_read (inode,&buf->filp,buf->buffer+mustmove + ,mustread); + if (ret == 0) buf->size = mustmove + mustread; + }else if (mustmove){ + buf->size = mustmove; + ret = 0; + } + return ret; +} + +/* + General search, locate a name in the EMD file or an empty slot to + store it. if info->entry.name_len == 0, search the first empty + slot (of the proper size). + + Caller must do iput on *pt_emd_dir. + + Return 0 if found, -ENOENT if not found, another error code if + other problem. + + So this routine is used to either find an existing entry or to + create a new one, while making sure it is a new one. After you + get -ENOENT, you make sure the entry is stuffed correctly and + call umsdos_writeentry(). + + To delete an entry, you find it, zero out the entry (memset) + and call umsdos_writeentry(). + + All this to say that umsdos_writeentry must be call after this + function since it rely on the f_pos field of info. +*/ +static int umsdos_find ( + struct inode *dir, + struct umsdos_info *info, /* Hold name and name_len */ + /* Will hold the entry found */ + struct inode **pt_emd_dir) /* Will hold the emd_dir inode */ + /* or NULL if not found */ +{ + /* #Specification: EMD file structure + The EMD file uses a fairly simple layout. It is made of records + (UMSDOS_REC_SIZE == 64). When a name can't be written is a single + record, multiple contiguous record are allocated. + */ + int ret = -ENOENT; + struct inode *emd_dir = umsdos_emd_dir_lookup(dir,1); + if (emd_dir != NULL){ + struct umsdos_dirent *entry = &info->entry; + int recsize = info->recsize; + struct { + off_t posok; /* Position available to store the entry */ + int found; /* A valid empty position has been found */ + off_t one; /* One empty position -> maybe <- large enough */ + int onesize; /* size of empty region starting at one */ + }empty; + /* Read several entries at a time to speed up the search */ + struct find_buffer buf; + buf.pos = 0; + buf.size = 0; + buf.filp.f_pos = 0; + buf.filp.f_reada = 1; + empty.found = 0; + empty.posok = emd_dir->i_size; + empty.onesize = 0; + while (1){ + struct umsdos_dirent *rentry = (struct umsdos_dirent*) + (buf.buffer + buf.pos); + int file_pos = buf.filp.f_pos - buf.size + buf.pos; + if (buf.pos == buf.size){ + ret = umsdos_fillbuf (emd_dir,&buf); + if (ret < 0){ + /* Not found, so note where it can be added */ + info->f_pos = empty.posok; + break; + } + }else if (rentry->name_len == 0){ + /* We are looking for an empty section at least */ + /* recsize large */ + if (entry->name_len == 0){ + info->f_pos = file_pos; + ret = 0; + break; + }else if (!empty.found){ + if (empty.onesize == 0){ + /* This is the first empty record of a section */ + empty.one = file_pos; + } + /* grow the empty section */ + empty.onesize += UMSDOS_REC_SIZE; + if (empty.onesize == recsize){ + /* here is a large enough section */ + empty.posok = empty.one; + empty.found = 1; + } + } + buf.pos += UMSDOS_REC_SIZE; + }else{ + int entry_size = umsdos_evalrecsize(rentry->name_len); + if (buf.pos+entry_size > buf.size){ + ret = umsdos_fillbuf (emd_dir,&buf); + if (ret < 0){ + /* Not found, so note where it can be added */ + info->f_pos = empty.posok; + break; + } + }else{ + empty.onesize = 0; /* Reset the free slot search */ + if (entry->name_len == rentry->name_len + && memcmp(entry->name,rentry->name,rentry->name_len) + ==0){ + info->f_pos = file_pos; + *entry = *rentry; + ret = 0; + break; + }else{ + buf.pos += entry_size; + } + } + } + } + umsdos_manglename(info); + } + *pt_emd_dir = emd_dir; + return ret; +} +/* + Add a new entry in the emd file + Return 0 if ok or a negative error code. + Return -EEXIST if the entry already exist. + + Complete the information missing in info. +*/ +int umsdos_newentry ( + struct inode *dir, + struct umsdos_info *info) +{ + struct inode *emd_dir; + int ret = umsdos_find (dir,info,&emd_dir); + if (ret == 0){ + ret = -EEXIST; + }else if (ret == -ENOENT){ + ret = umsdos_writeentry(dir,emd_dir,info,0); + PRINTK (("umsdos_newentry EDM ret = %d\n",ret)); + } + iput (emd_dir); + return ret; +} +/* + Create a new hidden link. + Return 0 if ok, an error code if not. +*/ +int umsdos_newhidden ( + struct inode *dir, + struct umsdos_info *info) +{ + struct inode *emd_dir; + int ret; + umsdos_parse ("..LINK",6,info); + info->entry.name_len = 0; + ret = umsdos_find (dir,info,&emd_dir); + iput (emd_dir); + if (ret == -ENOENT || ret == 0){ + /* #Specification: hard link / hidden name + When a hard link is created, the original file is renamed + to a hidden name. The name is "..LINKNNN" where NNN is a + number define from the entry offset in the EMD file. + */ + info->entry.name_len = sprintf (info->entry.name,"..LINK%ld" + ,info->f_pos); + ret = 0; + } + return ret; +} +/* + Remove an entry from the emd file + Return 0 if ok, a negative error code otherwise. + + Complete the information missing in info. +*/ +int umsdos_delentry ( + struct inode *dir, + struct umsdos_info *info, + int isdir) +{ + struct inode *emd_dir; + int ret = umsdos_find (dir,info,&emd_dir); + if (ret == 0){ + if (info->entry.name_len != 0){ + if ((isdir != 0) != (S_ISDIR(info->entry.mode) != 0)){ + if (S_ISDIR(info->entry.mode)){ + ret = -EISDIR; + }else{ + ret = -ENOTDIR; + } + }else{ + ret = umsdos_writeentry(dir,emd_dir,info,1); + } + } + } + iput(emd_dir); + return ret; +} + + +/* + Verify is a EMD directory is empty. + Return 0 if not empty + 1 if empty + 2 if empty, no EMD file. +*/ +int umsdos_isempty (struct inode *dir) +{ + int ret = 2; + struct inode *emd_dir = umsdos_emd_dir_lookup(dir,0); + /* If the EMD file does not exist, it is certainly empty :-) */ + if (emd_dir != NULL){ + struct file filp; + /* Find an empty slot */ + filp.f_pos = 0; + filp.f_reada = 1; + filp.f_flags = O_RDONLY; + ret = 1; + while (filp.f_pos < emd_dir->i_size){ + struct umsdos_dirent entry; + if (umsdos_emd_dir_readentry(emd_dir,&filp,&entry)!=0){ + ret = 0; + break; + }else if (entry.name_len != 0){ + ret = 0; + break; + } + } + iput (emd_dir); + } + return ret; +} + +/* + Locate an entry in a EMD directory. + Return 0 if ok, errcod if not, generally -ENOENT. +*/ +int umsdos_findentry ( + struct inode *dir, + struct umsdos_info *info, + int expect) /* 0: anything */ + /* 1: file */ + /* 2: directory */ +{ + struct inode *emd_dir; + int ret = umsdos_find (dir,info,&emd_dir); + if (ret == 0){ + if (expect != 0){ + if (S_ISDIR(info->entry.mode)){ + if (expect != 2) ret = -EISDIR; + }else if (expect == 2){ + ret = -ENOTDIR; + } + } + } + iput (emd_dir); + return ret; +} + diff --git a/fs/umsdos/file.c b/fs/umsdos/file.c new file mode 100644 index 000000000..d292ea3c2 --- /dev/null +++ b/fs/umsdos/file.c @@ -0,0 +1,103 @@ +/* + * linux/fs/umsdos/file.c + * + * Written 1993 by Jacques Gelinas + * inspired from linux/fs/msdos/file.c Werner Almesberger + * + * Extended MS-DOS regular file handling primitives + */ + +#include <asm/segment.h> +#include <asm/system.h> + +#include <linux/sched.h> +#include <linux/fs.h> +#include <linux/msdos_fs.h> +#include <linux/errno.h> +#include <linux/fcntl.h> +#include <linux/stat.h> +#include <linux/msdos_fs.h> +#include <linux/umsdos_fs.h> + + +#define PRINTK(x) +#define Printk(x) printk x +/* + Read a file into user space memory +*/ +static int UMSDOS_file_read( + struct inode *inode, + struct file *filp, + char *buf, + int count) +{ + /* We have to set the access time because msdos don't care */ + int ret = msdos_file_read(inode,filp,buf,count); + inode->i_atime = CURRENT_TIME; + inode->i_dirt = 1; + return ret; +} +/* + Write a file from user space memory +*/ +static int UMSDOS_file_write( + struct inode *inode, + struct file *filp, + char *buf, + int count) +{ + return msdos_file_write(inode,filp,buf,count); +} +/* + Truncate a file to 0 length. +*/ +static void UMSDOS_truncate(struct inode *inode) +{ + PRINTK (("UMSDOS_truncate\n")); + msdos_truncate (inode); + inode->i_ctime = inode->i_mtime = CURRENT_TIME; + inode->i_dirt = 1; +} +/* + See inode.c + + Some entry point are filled dynamically with function pointers + from the msdos file_operations and file_inode_operations. + + The idea is to have the code as independent as possible from + the msdos file system. +*/ + +struct file_operations umsdos_file_operations = { + NULL, /* lseek - default */ + UMSDOS_file_read, /* read */ + UMSDOS_file_write, /* write */ + NULL, /* readdir - bad */ + NULL, /* select - default */ + NULL, /* ioctl - default */ + generic_mmap, /* mmap */ + NULL, /* no special open is needed */ + NULL, /* release */ + file_fsync /* fsync */ +}; + +struct inode_operations umsdos_file_inode_operations = { + &umsdos_file_operations, /* default file operations */ + NULL, /* create */ + NULL, /* lookup */ + NULL, /* link */ + NULL, /* unlink */ + NULL, /* symlink */ + NULL, /* mkdir */ + NULL, /* rmdir */ + NULL, /* mknod */ + NULL, /* rename */ + NULL, /* readlink */ + NULL, /* follow_link */ + NULL, /* bmap */ + UMSDOS_truncate,/* truncate */ + NULL, /* permission */ + msdos_smap /* smap */ +}; + + diff --git a/fs/umsdos/inode.c b/fs/umsdos/inode.c new file mode 100644 index 000000000..40f7feb68 --- /dev/null +++ b/fs/umsdos/inode.c @@ -0,0 +1,513 @@ +/* + * linux/fs/umsdos/inode.c + * + * Written 1993 by Jacques Gelinas + * Inspired from linux/fs/msdos/... by Werner Almesberger + * + */ + +#include <linux/fs.h> +#include <linux/msdos_fs.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <asm/segment.h> +#include <linux/string.h> +#include <linux/ctype.h> +#include <linux/stat.h> +#include <linux/umsdos_fs.h> + +#ifdef MODULE + #include <linux/module.h> + #include "../../tools/version.h" +#endif + +struct inode *pseudo_root=NULL; /* Useful to simulate the pseudo DOS */ + /* directory. See UMSDOS_readdir_x() */ + +/* #Specification: convention / PRINTK Printk and printk + Here is the convention for the use of printk inside fs/umsdos + + printk carry important message (error or status). + Printk is for debugging (it is a macro defined at the beginning of + most source. + PRINTK is a nulled Printk macro. + + This convention makes the source easier to read, and Printk easier + to shut off. +*/ +#define PRINTK(x) +#define Printk(x) printk x + + +void UMSDOS_put_inode(struct inode *inode) +{ + PRINTK (("put inode %x owner %x pos %d dir %x\n",inode + ,inode->u.umsdos_i.i_emd_owner,inode->u.umsdos_i.pos + ,inode->u.umsdos_i.i_emd_dir)); + msdos_put_inode(inode); +} + + +void UMSDOS_put_super(struct super_block *sb) +{ + msdos_put_super(sb); + #ifdef MODULE + MOD_DEC_USE_COUNT; + #endif +} + + +void UMSDOS_statfs(struct super_block *sb,struct statfs *buf) +{ + msdos_statfs(sb,buf); +} + + +/* + Call msdos_lookup, but set back the original msdos function table. + Return 0 if ok, or a negative error code if not. +*/ +int umsdos_real_lookup ( + struct inode *dir, + const char *name, + int len, + struct inode **result) /* Will hold inode of the file, if successful */ +{ + int ret; + dir->i_count++; + ret = msdos_lookup (dir,name,len,result); + return ret; +} +/* + Complete the setup of an directory inode. + First, it completes the function pointers, then + it locates the EMD file. If the EMD is there, then plug the + umsdos function table. If not, use the msdos one. +*/ +void umsdos_setup_dir_inode (struct inode *inode) +{ + inode->u.umsdos_i.i_emd_dir = 0; + { + struct inode *emd_dir = umsdos_emd_dir_lookup (inode,0); + extern struct inode_operations umsdos_rdir_inode_operations; + inode->i_op = emd_dir != NULL + ? &umsdos_dir_inode_operations + : &umsdos_rdir_inode_operations; + iput (emd_dir); + } +} +/* + Add some info into an inode so it can find its owner quickly +*/ +void umsdos_set_dirinfo( + struct inode *inode, + struct inode *dir, + off_t f_pos) +{ + struct inode *emd_owner = umsdos_emd_dir_lookup(dir,1); + inode->u.umsdos_i.i_dir_owner = dir->i_ino; + inode->u.umsdos_i.i_emd_owner = emd_owner->i_ino; + iput (emd_owner); + inode->u.umsdos_i.pos = f_pos; +} +/* + Tells if an Umsdos inode has been "patched" once. + Return != 0 if so. +*/ +int umsdos_isinit (struct inode *inode) +{ +#if 1 + return inode->u.umsdos_i.i_emd_owner != 0; +#elif 0 + return inode->i_atime != 0; +#else + return inode->i_count > 1; +#endif +} +/* + Connect the proper tables in the inode and add some info. +*/ +void umsdos_patch_inode ( + struct inode *inode, + struct inode *dir, /* May be NULL */ + off_t f_pos) +{ + /* + This function is called very early to setup the inode, somewhat + too early (called by UMSDOS_read_inode). At this point, we can't + do to much, such as lookup up EMD files and so on. This causes + confusion in the kernel. This is why some initialisation + will be done when dir != NULL only. + + UMSDOS do run piggy back on top of msdos fs. It looks like something + is missing in the VFS to accommodate stacked fs. Still unclear what + (quite honestly). + + Well, maybe one! A new entry "may_unmount" which would allow + the stacked fs to allocate some inode permanently and release + them at the end. Doing that now introduce a problem. unmount + always fail because some inodes are in use. + */ + if (!umsdos_isinit(inode)){ + inode->u.umsdos_i.i_emd_dir = 0; + if (S_ISREG(inode->i_mode)){ + static char is_init = 0; + if (!is_init){ + /* + I don't want to change the msdos file system code + so I get the address of some subroutine dynamically + once. + */ + umsdos_file_inode_operations.bmap = inode->i_op->bmap; + inode->i_op = &umsdos_file_inode_operations; + is_init = 1; + } + inode->i_op = &umsdos_file_inode_operations; + }else if (S_ISDIR(inode->i_mode)){ + if (dir != NULL){ + umsdos_setup_dir_inode(inode); + } + }else if (S_ISLNK(inode->i_mode)){ + inode->i_op = &umsdos_symlink_inode_operations; + }else if (S_ISCHR(inode->i_mode)){ + inode->i_op = &chrdev_inode_operations; + }else if (S_ISBLK(inode->i_mode)){ + inode->i_op = &blkdev_inode_operations; + }else if (S_ISFIFO(inode->i_mode)){ + init_fifo(inode); + } + if (dir != NULL){ + /* #Specification: inode / umsdos info + The first time an inode is seen (inode->i_count == 1), + the inode number of the EMD file which control this inode + is tagged to this inode. It allows operation such + as notify_change to be handled. + */ + /* + This is done last because it also control the + status of umsdos_isinit() + */ + umsdos_set_dirinfo (inode,dir,f_pos); + } + }else if (dir != NULL){ + /* + Test to see if the info is maintained. + This should be removed when the file system will be proven. + */ + struct inode *emd_owner = umsdos_emd_dir_lookup(dir,1); + iput (emd_owner); + if (emd_owner->i_ino != inode->u.umsdos_i.i_emd_owner){ + printk ("UMSDOS: *** EMD_OWNER ??? *** ino = %ld %ld <> %ld " + ,inode->i_ino,emd_owner->i_ino,inode->u.umsdos_i.i_emd_owner); + } + } +} +/* + Get the inode of the directory which owns this inode. + Return 0 if ok, -EIO if error. +*/ +int umsdos_get_dirowner( + struct inode *inode, + struct inode **result) /* Hold NULL if any error */ + /* else, the inode of the directory */ +{ + int ret = -EIO; + unsigned long ino = inode->u.umsdos_i.i_dir_owner; + *result = NULL; + if (ino == 0){ + printk ("UMSDOS: umsdos_get_dirowner ino == 0\n"); + }else{ + struct inode *dir = *result = iget(inode->i_sb,ino); + if (dir != NULL){ + umsdos_patch_inode (dir,NULL,0); + ret = 0; + } + } + return ret; +} +/* + Load an inode from disk. +*/ +void UMSDOS_read_inode(struct inode *inode) +{ + PRINTK (("read inode %x ino = %d ",inode,inode->i_ino)); + msdos_read_inode(inode); + PRINTK (("ino = %d %d\n",inode->i_ino,inode->i_count)); + if (S_ISDIR(inode->i_mode) + && (inode->u.umsdos_i.u.dir_info.creating != 0 + || inode->u.umsdos_i.u.dir_info.looking != 0 + || inode->u.umsdos_i.u.dir_info.p != NULL)){ + Printk (("read inode %d %d %p\n" + ,inode->u.umsdos_i.u.dir_info.creating + ,inode->u.umsdos_i.u.dir_info.looking + ,inode->u.umsdos_i.u.dir_info.p)); + } + /* #Specification: Inode / post initialisation + To completely initialise an inode, we need access to the owner + directory, so we can locate more info in the EMD file. This is + not available the first time the inode is access, we use + a value in the inode to tell if it has been finally initialised. + + At first, we have tried testing i_count but it was causing + problem. It is possible that two or more process use the + newly accessed inode. While the first one block during + the initialisation (probably while reading the EMD file), the + others believe all is well because i_count > 1. They go banana + with a broken inode. See umsdos_lookup_patch and umsdos_patch_inode. + */ + umsdos_patch_inode(inode,NULL,0); +} + +/* + Update the disk with the inode content +*/ +void UMSDOS_write_inode(struct inode *inode) +{ + struct iattr newattrs; + + PRINTK (("UMSDOS_write_inode emd %d\n",inode->u.umsdos_i.i_emd_owner)); + msdos_write_inode(inode); + newattrs.ia_mtime = inode->i_mtime; + newattrs.ia_atime = inode->i_atime; + newattrs.ia_ctime = inode->i_ctime; + newattrs.ia_valid = ATTR_MTIME | ATTR_ATIME | ATTR_CTIME; + /* + UMSDOS_notify_change is convenient to call here + to update the EMD entry associated with this inode. + But it has the side effect to re"dirt" the inode. + */ + UMSDOS_notify_change (inode, &newattrs); + inode->i_dirt = 0; +} + +int UMSDOS_notify_change(struct inode *inode, struct iattr *attr) +{ + int ret = 0; + + if ((ret = inode_change_ok(inode, attr)) != 0) + return ret; + + if (inode->i_nlink > 0){ + /* #Specification: notify_change / i_nlink > 0 + notify change is only done for inode with nlink > 0. An inode + with nlink == 0 is no longer associated with any entry in + the EMD file, so there is nothing to update. + */ + unsigned long i_emd_owner = inode->u.umsdos_i.i_emd_owner; + if (inode == inode->i_sb->s_mounted){ + /* #Specification: root inode / attributes + I don't know yet how this should work. Normally + the attributes (permissions bits, owner, times) of + a directory are stored in the EMD file of its parent. + + One thing we could do is store the attributes of the root + inode in its own EMD file. A simple entry named "." could + be used for this special case. It would be read once + when the file system is mounted and update in + UMSDOS_notify_change() (right here). + + I am not sure of the behavior of the root inode for + a real UNIX file system. For now, this is a nop. + */ + }else if (i_emd_owner != 0xffffffff && i_emd_owner != 0){ + /* This inode is not a EMD file nor an inode used internally + by MSDOS, so we can update its status. + See emd.c + */ + struct inode *emd_owner = iget (inode->i_sb,i_emd_owner); + PRINTK (("notify change %p ",inode)); + if (emd_owner == NULL){ + printk ("UMSDOS: emd_owner = NULL ???"); + ret = -EPERM; + }else{ + struct file filp; + struct umsdos_dirent entry; + filp.f_pos = inode->u.umsdos_i.pos; + filp.f_reada = 0; + PRINTK (("pos = %d ",filp.f_pos)); + /* Read only the start of the entry since we don't touch */ + /* the name */ + ret = umsdos_emd_dir_read (emd_owner,&filp,(char*)&entry + ,UMSDOS_REC_SIZE); + if (ret == 0){ + if (attr->ia_valid & ATTR_UID) + entry.uid = attr->ia_uid; + if (attr->ia_valid & ATTR_GID) + entry.gid = attr->ia_gid; + if (attr->ia_valid & ATTR_MODE) + entry.mode = attr->ia_mode; + if (attr->ia_valid & ATTR_ATIME) + entry.atime = attr->ia_atime; + if (attr->ia_valid & ATTR_MTIME) + entry.mtime = attr->ia_mtime; + if (attr->ia_valid & ATTR_CTIME) + entry.ctime = attr->ia_ctime; + + entry.nlink = inode->i_nlink; + filp.f_pos = inode->u.umsdos_i.pos; + ret = umsdos_emd_dir_write (emd_owner,&filp,(char*)&entry + ,UMSDOS_REC_SIZE); + + PRINTK (("notify pos %d ret %d nlink %d " + ,inode->u.umsdos_i.pos + ,ret,entry.nlink)); + /* #Specification: notify_change / msdos fs + notify_change operation are done only on the + EMD file. The msdos fs is not even called. + */ + } + iput (emd_owner); + } + PRINTK (("\n")); + } + } + if (ret == 0) + inode_setattr(inode, attr); + return ret; +} + +/* #Specification: function name / convention + A simple convention for function name has been used in + the UMSDOS file system. First all function use the prefix + umsdos_ to avoid name clash with other part of the kernel. + + And standard VFS entry point use the prefix UMSDOS (upper case) + so it's easier to tell them apart. +*/ + +static struct super_operations umsdos_sops = { + UMSDOS_read_inode, + UMSDOS_notify_change, + UMSDOS_write_inode, + UMSDOS_put_inode, + UMSDOS_put_super, + NULL, /* added in 0.96c */ + UMSDOS_statfs, + NULL +}; + +/* + Read the super block of an Extended MS-DOS FS. +*/ +struct super_block *UMSDOS_read_super( + struct super_block *s, + void *data, + int silent) +{ + /* #Specification: mount / options + Umsdos run on top of msdos. Currently, it supports no + mount option, but happily pass all option received to + the msdos driver. I am not sure if all msdos mount option + make sense with Umsdos. Here are at least those who + are useful. + uid= + gid= + + These options affect the operation of umsdos in directories + which do not have an EMD file. They behave like normal + msdos directory, with all limitation of msdos. + */ + struct super_block *sb = msdos_read_super(s,data,silent); + printk ("UMSDOS Alpha 0.5a (compatibility level %d.%d, fast msdos)\n" + ,UMSDOS_VERSION,UMSDOS_RELEASE); + if (sb != NULL){ + sb->s_op = &umsdos_sops; + PRINTK (("umsdos_read_super %p\n",sb->s_mounted)); + umsdos_setup_dir_inode (sb->s_mounted); + PRINTK (("End umsdos_read_super\n")); + if (s == super_blocks){ + /* #Specification: pseudo root / mount + When a umsdos fs is mounted, a special handling is done + if it is the root partition. We check for the presence + of the file /linux/etc/init or /linux/etc/rc. + If one is there, we do a chroot("/linux"). + + We check both because (see init/main.c) the kernel + try to exec init at different place and if it fails + it tries /bin/sh /etc/rc. To be consistent with + init/main.c, many more test would have to be done + to locate init. Any complain ? + + The chroot is done manually in init/main.c but the + info (the inode) is located at mount time and store + in a global variable (pseudo_root) which is used at + different place in the umsdos driver. There is no + need to store this variable elsewhere because it + will always be one, not one per mount. + + This feature allows the installation + of a linux system within a DOS system in a subdirectory. + + A user may install its linux stuff in c:\linux + avoiding any clash with existing DOS file and subdirectory. + When linux boots, it hides this fact, showing a normal + root directory with /etc /bin /tmp ... + + The word "linux" is hardcoded in /usr/include/linux/umsdos_fs.h + in the macro UMSDOS_PSDROOT_NAME. + */ + + struct inode *pseudo; + Printk (("Mounting root\n")); + if (umsdos_real_lookup (sb->s_mounted,UMSDOS_PSDROOT_NAME + ,UMSDOS_PSDROOT_LEN,&pseudo)==0 + && S_ISDIR(pseudo->i_mode)){ + struct inode *etc = NULL; + struct inode *rc = NULL; + Printk (("/%s is there\n",UMSDOS_PSDROOT_NAME)); + if (umsdos_real_lookup (pseudo,"etc",3,&etc)==0 + && S_ISDIR(etc->i_mode)){ + struct inode *init; + Printk (("/%s/etc is there\n",UMSDOS_PSDROOT_NAME)); + if ((umsdos_real_lookup (etc,"init",4,&init)==0 + && S_ISREG(init->i_mode)) + || (umsdos_real_lookup (etc,"rc",2,&rc)==0 + && S_ISREG(rc->i_mode))){ + umsdos_setup_dir_inode (pseudo); + Printk (("Activating pseudo root /%s\n",UMSDOS_PSDROOT_NAME)); + pseudo_root = pseudo; + pseudo->i_count++; + pseudo = NULL; + } + iput (init); + iput (rc); + } + iput (etc); + } + iput (pseudo); + } + #ifdef MODULE + MOD_INC_USE_COUNT; + #endif + } + return sb; +} + + +#ifdef MODULE + +char kernel_version[] = UTS_RELEASE; + +static struct file_system_type umsdos_fs_type = { + UMSDOS_read_super, "umsdos", 1, NULL +}; + +int init_module(void) +{ + register_filesystem(&umsdos_fs_type); + return 0; +} + +void cleanup_module(void) +{ + if (MOD_IN_USE) + printk("Umsdos: file system in use, remove delayed\n"); + else + { + unregister_filesystem(&umsdos_fs_type); + } +} + +#endif + diff --git a/fs/umsdos/ioctl.c b/fs/umsdos/ioctl.c new file mode 100644 index 000000000..972571796 --- /dev/null +++ b/fs/umsdos/ioctl.c @@ -0,0 +1,259 @@ +/* + * linux/fs/umsdos/ioctl.c + * + * Written 1993 by Jacques Gelinas + * + * Extended MS-DOS ioctl directory handling functions + */ +#include <asm/segment.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/fs.h> +#include <linux/msdos_fs.h> +#include <linux/umsdos_fs.h> + +#define PRINTK(x) +#define Printk(x) printk x + +/* + Perform special function on a directory +*/ +int UMSDOS_ioctl_dir ( + struct inode *dir, + struct file *filp, + unsigned int cmd, + unsigned long data) +{ + int ret = -EPERM; + /* #Specification: ioctl / acces + Only root (effective id) is allowed to do IOCTL on directory + in UMSDOS. EPERM is returned for other user. + */ + if (current->euid == 0 + || cmd == UMSDOS_GETVERSION){ + struct umsdos_ioctl *idata = (struct umsdos_ioctl *)data; + ret = -EINVAL; + /* #Specification: ioctl / prototypes + The official prototype for the umsdos ioctl on directory + is: + + int ioctl ( + int fd, // File handle of the directory + int cmd, // command + struct umsdos_ioctl *data) + + The struct and the commands are defined in linux/umsdos_fs.h. + + umsdos_progs/umsdosio.c provide an interface in C++ to all + these ioctl. umsdos_progs/udosctl is a small utility showing + all this. + + These ioctl generally allow one to work on the EMD or the + DOS directory independently. These are essential to implement + the synchronise. + */ + PRINTK (("ioctl %d ",cmd)); + if (cmd == UMSDOS_GETVERSION){ + /* #Specification: ioctl / UMSDOS_GETVERSION + The field version and release of the structure + umsdos_ioctl are filled with the version and release + number of the fs code in the kernel. This will allow + some form of checking. Users won't be able to run + incompatible utility such as the synchroniser (umssync). + umsdos_progs/umsdosio.c enforce this checking. + + Return always 0. + */ + put_fs_byte (UMSDOS_VERSION,&idata->version); + put_fs_byte (UMSDOS_RELEASE,&idata->release); + ret = 0; + }else if (cmd == UMSDOS_READDIR_DOS){ + /* #Specification: ioctl / UMSDOS_READDIR_DOS + One entry is read from the DOS directory at the current + file position. The entry is put as is in the dos_dirent + field of struct umsdos_ioctl. + + Return > 0 if success. + */ + ret = msdos_readdir(dir,filp,&idata->dos_dirent,1); + }else if (cmd == UMSDOS_READDIR_EMD){ + /* #Specification: ioctl / UMSDOS_READDIR_EMD + One entry is read from the EMD at the current + file position. The entry is put as is in the umsdos_dirent + field of struct umsdos_ioctl. The corresponding mangled + DOS entry name is put in the dos_dirent field. + + All entries are read including hidden links. Blank + entries are skipped. + + Return > 0 if success. + */ + struct inode *emd_dir = umsdos_emd_dir_lookup (dir,0); + if (emd_dir != NULL){ + while (1){ + if (filp->f_pos >= emd_dir->i_size){ + ret = 0; + break; + }else{ + struct umsdos_dirent entry; + off_t f_pos = filp->f_pos; + ret = umsdos_emd_dir_readentry (emd_dir,filp,&entry); + if (ret < 0){ + break; + }else if (entry.name_len > 0){ + struct umsdos_info info; + ret = entry.name_len; + umsdos_parse (entry.name,entry.name_len,&info); + info.f_pos = f_pos; + umsdos_manglename(&info); + memcpy_tofs(&idata->umsdos_dirent,&entry + ,sizeof(entry)); + memcpy_tofs(&idata->dos_dirent.d_name + ,info.fake.fname,info.fake.len+1); + break; + } + } + } + iput (emd_dir); + }else{ + /* The absence of the EMD is simply seen as an EOF */ + ret = 0; + } + }else if (cmd == UMSDOS_INIT_EMD){ + /* #Specification: ioctl / UMSDOS_INIT_EMD + The UMSDOS_INIT_EMD command make sure the EMD + exist for a directory. If it does not, it is + created. Also, it makes sure the directory functions + table (struct inode_operations) is set to the UMSDOS + semantic. This mean that umssync may be applied to + an "opened" msdos directory, and it will change behavior + on the fly. + + Return 0 if success. + */ + extern struct inode_operations umsdos_rdir_inode_operations; + struct inode *emd_dir = umsdos_emd_dir_lookup (dir,1); + ret = emd_dir != NULL; + iput (emd_dir); + + dir->i_op = ret + ? &umsdos_dir_inode_operations + : &umsdos_rdir_inode_operations; + }else{ + struct umsdos_ioctl data; + memcpy_fromfs (&data,idata,sizeof(data)); + if (cmd == UMSDOS_CREAT_EMD){ + /* #Specification: ioctl / UMSDOS_CREAT_EMD + The umsdos_dirent field of the struct umsdos_ioctl is used + as is to create a new entry in the EMD of the directory. + The DOS directory is not modified. + No validation is done (yet). + + Return 0 if success. + */ + struct umsdos_info info; + /* This makes sure info.entry and info in general is correctly */ + /* initialised */ + memcpy (&info.entry,&data.umsdos_dirent + ,sizeof(data.umsdos_dirent)); + umsdos_parse (data.umsdos_dirent.name + ,data.umsdos_dirent.name_len,&info); + ret = umsdos_newentry (dir,&info); + }else if (cmd == UMSDOS_UNLINK_EMD){ + /* #Specification: ioctl / UMSDOS_UNLINK_EMD + The umsdos_dirent field of the struct umsdos_ioctl is used + as is to remove an entry from the EMD of the directory. + No validation is done (yet). The mode field is used + to validate S_ISDIR or S_ISREG. + + Return 0 if success. + */ + struct umsdos_info info; + /* This makes sure info.entry and info in general is correctly */ + /* initialised */ + memcpy (&info.entry,&data.umsdos_dirent + ,sizeof(data.umsdos_dirent)); + umsdos_parse (data.umsdos_dirent.name + ,data.umsdos_dirent.name_len,&info); + ret = umsdos_delentry (dir,&info + ,S_ISDIR(data.umsdos_dirent.mode)); + }else if (cmd == UMSDOS_UNLINK_DOS){ + /* #Specification: ioctl / UMSDOS_UNLINK_DOS + The dos_dirent field of the struct umsdos_ioctl is used to + execute a msdos_unlink operation. The d_name and d_reclen + fields are used. + + Return 0 if success. + */ + dir->i_count++; + ret = msdos_unlink (dir,data.dos_dirent.d_name + ,data.dos_dirent.d_reclen); + }else if (cmd == UMSDOS_RMDIR_DOS){ + /* #Specification: ioctl / UMSDOS_RMDIR_DOS + The dos_dirent field of the struct umsdos_ioctl is used to + execute a msdos_unlink operation. The d_name and d_reclen + fields are used. + + Return 0 if success. + */ + dir->i_count++; + ret = msdos_rmdir (dir,data.dos_dirent.d_name + ,data.dos_dirent.d_reclen); + }else if (cmd == UMSDOS_STAT_DOS){ + /* #Specification: ioctl / UMSDOS_STAT_DOS + The dos_dirent field of the struct umsdos_ioctl is + used to execute a stat operation in the DOS directory. + The d_name and d_reclen fields are used. + + The following field of umsdos_ioctl.stat are filled. + + st_ino,st_mode,st_size,st_atime,st_mtime,st_ctime, + Return 0 if success. + */ + struct inode *inode; + ret = umsdos_real_lookup (dir,data.dos_dirent.d_name + ,data.dos_dirent.d_reclen,&inode); + if (ret == 0){ + data.stat.st_ino = inode->i_ino; + data.stat.st_mode = inode->i_mode; + data.stat.st_size = inode->i_size; + data.stat.st_atime = inode->i_atime; + data.stat.st_ctime = inode->i_ctime; + data.stat.st_mtime = inode->i_mtime; + memcpy_tofs (&idata->stat,&data.stat,sizeof(data.stat)); + iput (inode); + } + }else if (cmd == UMSDOS_DOS_SETUP){ + /* #Specification: ioctl / UMSDOS_DOS_SETUP + The UMSDOS_DOS_SETUP ioctl allow changing the + default permission of the MsDOS file system driver + on the fly. The MsDOS driver apply global permission + to every file and directory. Normally these permissions + are controlled by a mount option. This is not + available for root partition, so a special utility + (umssetup) is provided to do this, normally in + /etc/rc.local. + + Be aware that this apply ONLY to MsDOS directory + (those without EMD --linux-.---). Umsdos directory + have independent (standard) permission for each + and every file. + + The field umsdos_dirent provide the information needed. + umsdos_dirent.uid and gid sets the owner and group. + umsdos_dirent.mode set the permissions flags. + */ + dir->i_sb->u.msdos_sb.fs_uid = data.umsdos_dirent.uid; + dir->i_sb->u.msdos_sb.fs_gid = data.umsdos_dirent.gid; + dir->i_sb->u.msdos_sb.fs_umask = data.umsdos_dirent.mode; + ret = 0; + } + } + } + PRINTK (("ioctl return %d\n",ret)); + return ret; +} + + + diff --git a/fs/umsdos/mangle.c b/fs/umsdos/mangle.c new file mode 100644 index 000000000..1f59447e9 --- /dev/null +++ b/fs/umsdos/mangle.c @@ -0,0 +1,478 @@ +/* + * linux/fs/umsdos/mangle.c + * + * Written 1993 by Jacques Gelinas + * + * Control the mangling of file name to fit msdos name space. + * Many optimisation by GLU == dglaude@is1.vub.ac.be (GLAUDE DAVID) +*/ +#include <linux/errno.h> +#include <linux/ctype.h> +#include <linux/string.h> +#include <linux/kernel.h> +#include <linux/umsdos_fs.h> + +/* + Complete the mangling of the MSDOS fake name + based on the position of the entry in the EMD file. + + Simply complete the job of umsdos_parse; fill the extension. + + Beware that info->f_pos must be set. +*/ +void umsdos_manglename (struct umsdos_info *info) +{ + if (info->msdos_reject){ + /* #Specification: file name / non MSDOS conforming / mangling + Each non MSDOS conforming file has a special extension + build from the entry position in the EMD file. + + This number is then transform in a base 32 number, where + each digit is expressed like hexadecimal number, using + digit and letter, except it uses 22 letters from 'a' to 'v'. + The number 32 comes from 2**5. It is faster to split a binary + number using a base which is a power of two. And I was 32 + when I started this project. Pick your answer :-) . + + If the result is '0', it is replace with '_', simply + to make it odd. + + This is true for the first two character of the extension. + The last one is taken from a list of odd character, which + are: + + { } ( ) ! ` ^ & @ + + With this scheme, we can produce 9216 ( 9* 32 * 32) + different extensions which should not clash with any useful + extension already popular or meaningful. Since most directory + have much less than 32 * 32 files in it, the first character + of the extension of any mangle name will be {. + + Here are the reason to do this (this kind of mangling). + + -The mangling is deterministic. Just by the extension, we + are able to locate the entry in the EMD file. + + -By keeping to beginning of the file name almost unchanged, + we are helping the MSDOS user. + + -The mangling produces names not too ugly, so an msdos user + may live with it (remember it, type it, etc...). + + -The mangling produces names ugly enough so no one will + ever think of using such a name in real life. This is not + fool proof. I don't think there is a total solution to this. + */ + union { + int entry_num; + struct { + unsigned num1:5,num2:5,num3:5; + }num; + } u; + char *pt = info->fake.fname + info->fake.len; + /* lookup for encoding the last character of the extension */ + /* It contain valid character after the ugly one to make sure */ + /* even if someone overflow the 32 * 32 * 9 limit, it still do */ + /* something */ + #define SPECIAL_MANGLING '{','}','(',')','!','`','^','&','@' + static char lookup3[]={ + SPECIAL_MANGLING, + /* This is the start of lookup12 */ + '_','1','2','3','4','5','6','7','8','9', + 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o', + 'p','q','r','s','t','u','v' + }; + #define lookup12 (lookup3+9) + u.entry_num = info->f_pos / UMSDOS_REC_SIZE; + if (u.entry_num > (9* 32 * 32)){ + printk ("UMSDOS: More than 9216 file in a directory.\n" + "This may break the mangling strategy.\n" + "Not a killer problem. See doc.\n"); + } + *pt++ = '.'; + *pt++ = lookup3 [u.num.num3]; + *pt++ = lookup12[u.num.num2]; + *pt++ = lookup12[u.num.num1]; + *pt = '\0'; /* help doing printk */ + info->fake.len += 4; + info->msdos_reject = 0; /* Avoid mangling twice */ + } +} + +/* + Evaluate the record size needed to store of name of len character. + The value returned is a multiple of UMSDOS_REC_SIZE. +*/ +int umsdos_evalrecsize (int len) +{ + struct umsdos_dirent dirent; + int nbrec = 1+((len-1+(dirent.name-(char*)&dirent)) + / UMSDOS_REC_SIZE); + return nbrec * UMSDOS_REC_SIZE; + /* + GLU This should be inlined or something to speed it up to the max. + GLU nbrec is absolutely not needed to return the value. + */ +} +#ifdef TEST +int umsdos_evalrecsize_old (int len) +{ + struct umsdos_dirent dirent; + int size = len + (dirent.name-(char*)&dirent); + int nbrec = size / UMSDOS_REC_SIZE; + int extra = size % UMSDOS_REC_SIZE; + if (extra > 0) nbrec++; + return nbrec * UMSDOS_REC_SIZE; +} +#endif +/* + Fill the struct info with the full and msdos name of a file + Return 0 if all is ok, a negative error code otherwise. +*/ +int umsdos_parse ( + const char *fname, + int len, + struct umsdos_info *info) +{ + int ret = -ENAMETOOLONG; + /* #Specification: file name / too long + If a file name exceed UMSDOS maxima, the file name is silently + truncated. This makes it conformant with the other file system + of Linux (minix and ext2 at least). + */ + if (len > UMSDOS_MAXNAME) len = UMSDOS_MAXNAME; + { + const char *firstpt=NULL; /* First place we saw a . in fname */ + /* #Specification: file name / non MSDOS conforming / base length 0 + file name beginning with a period '.' are invalid for MsDOS. + It needs absolutely a base name. So the file name is mangled + */ + int ivldchar = fname[0] == '.';/* At least one invalid character */ + int msdos_len = len; + int base_len; + /* + cardinal_per_size tells if there exist at least one + DOS pseudo devices on length n. See the test below. + */ + static const char cardinal_per_size[9]={ + 0, 0, 0, 1, 1, 0, 1, 0, 1 + }; + /* + lkp translate all character to acceptable character (for DOS). + When lkp[n] == n, it means also it is an acceptable one. + So it serve both as a flag and as a translator. + */ + static char lkp[256]; + static char is_init=0; + if (!is_init){ + /* + Initialisation of the array is easier and less error prone + like this. + */ + int i; + static char *spc = "\"*+,/:;<=>?[\\]|~"; + is_init = 1; + for (i=0; i<=32; i++) lkp[i] = '#'; + for (i=33; i<'A'; i++) lkp[i] = (char)i; + for (i='A'; i<='Z'; i++) lkp[i] = (char)(i+('a'-'A')); + for (i='Z'+1; i<127; i++) lkp[i] = (char)i; + for (i=128; i<256; i++) lkp[i] = '#'; + + lkp['.'] = '_'; + while (*spc != '\0') lkp[(unsigned char)(*spc++)] = '#'; + } + /* GLU + file name which are longer than 8+'.'+3 are invalid for MsDOS. + So the file name is to be mangled no more test needed. + This Speed Up for long and very long name. + The position of the last point is no more necessary anyway. + */ + if (len<=(8+1+3)){ + const char *pt = fname; + const char *endpt = fname + len; + while (pt < endpt){ + if (*pt == '.'){ + if (firstpt != NULL){ + /* 2 . in a file name. Reject */ + ivldchar = 1; + break; + }else{ + int extlen = (int)(endpt - pt); + firstpt = pt; + if (firstpt - fname > 8){ + /* base name longer than 8: reject */ + ivldchar = 1; + break; + }else if (extlen > 4){ + /* Extension longer than 4 (including .): reject */ + ivldchar = 1; + break; + }else if (extlen == 1){ + /* #Specification: file name / non MSDOS conforming / last char == . + If the last character of a file name is + a period, mangling is applied. MsDOS do + not support those file name. + */ + ivldchar = 1; + break; + }else if (extlen == 4){ + /* #Specification: file name / non MSDOS conforming / mangling clash + To avoid clash with the umsdos mangling, any file + with a special character as the first character + of the extension will be mangled. This solve the + following problem: + + touch FILE + # FILE is invalid for DOS, so mangling is applied + # file.{_1 is created in the DOS directory + touch file.{_1 + # To UMSDOS file point to a single DOS entry. + # So file.{_1 has to be mangled. + */ + static char special[]={ + SPECIAL_MANGLING,'\0' + }; + if (strchr(special,firstpt[1])!= NULL){ + ivldchar = 1; + break; + } + } + } + }else if (lkp[(unsigned char)(*pt)] != *pt){ + ivldchar = 1; + break; + } + pt++; + } + }else{ + ivldchar = 1; + } + if (ivldchar + || (firstpt == NULL && len > 8) + || (len == UMSDOS_EMD_NAMELEN + && memcmp(fname,UMSDOS_EMD_FILE,UMSDOS_EMD_NAMELEN)==0)){ + /* #Specification: file name / --linux-.--- + The name of the EMD file --linux-.--- is map to a mangled + name. So UMSDOS does not restrict its use. + */ + /* #Specification: file name / non MSDOS conforming / mangling + Non MSDOS conforming file name must use some alias to fit + in the MSDOS name space. + + The strategy is simple. The name is simply truncated to + 8 char. points are replace with underscore and a + number is given as an extension. This number correspond + to the entry number in the EMD file. The EMD file + only need to carry the real name. + + Upper case is also convert to lower case. + Control character are converted to #. + Space are converted to #. + The following character are also converted to #. + " * + , / : ; < = > ? [ \ ] | ~ + + Sometime, the problem is not in MsDOS itself but in + command.com. + */ + int i; + char *pt = info->fake.fname; + base_len = msdos_len = (msdos_len>8) ? 8 : msdos_len; + /* + There is no '.' any more so we know for a fact that + the base length is the length. + */ + memcpy (info->fake.fname,fname,msdos_len); + for (i=0; i<msdos_len; i++, pt++) *pt = lkp[(unsigned char)(*pt)]; + *pt = '\0'; /* GLU C'est sur on a un 0 a la fin */ + info->msdos_reject = 1; + /* + The numeric extension is added only when we know + the position in the EMD file, in umsdos_newentry(), + umsdos_delentry(), and umsdos_findentry(). + See umsdos_manglename(). + */ + }else{ + /* Conforming MSDOS file name */ + strcpy (info->fake.fname,fname); /* GLU C'est sur on a un 0 a la fin */ + info->msdos_reject = 0; + base_len = firstpt != NULL ? (int)(firstpt - fname) : len; + } + if (cardinal_per_size[base_len]){ + /* #Specification: file name / MSDOS devices / mangling + To avoid unreachable file from MsDOS, any MsDOS conforming + file with a basename equal to one of the MsDOS pseudo + devices will be mangled. + + If a file such as "prn" was created, it would be unreachable + under MsDOS because prn is assumed to be the printer, even + if the file does have an extension. + + Since the extension is unimportant to MsDOS, we must patch + the basename also. We simply insert a minus '-'. To avoid + conflict with valid file with a minus in front (such as + "-prn"), we add an mangled extension like any other + mangled file name. + + Here is the list of DOS pseudo devices: + + "prn","con","aux","nul", + "lpt1","lpt2","lpt3","lpt4", + "com1","com2","com3","com4", + "clock$" + + and some standard ones for common DOS programs + + "emmxxxx0","xmsxxxx0","setverxx" + + (Thanks to Chris Hall <CAH17@PHOENIX.CAMBRIDGE.AC.UK> + for pointing these to me). + + Is there one missing ? + */ + /* This table must be ordered by length */ + static const char *tbdev[]={ + "prn","con","aux","nul", + "lpt1","lpt2","lpt3","lpt4", + "com1","com2","com3","com4", + "clock$", + "emmxxxx0","xmsxxxx0","setverxx" + }; + /* Tell where to find in tbdev[], the first name of */ + /* a certain length */ + static const char start_ind_dev[9]={ + 0, 0, 0, 4, 12, 12, 13, 13, 16 + }; + char basen[9]; + int i; + for (i=start_ind_dev[base_len-1]; i<start_ind_dev[base_len]; i++){ + if (memcmp(info->fake.fname,tbdev[i],base_len)==0){ + memcpy (basen,info->fake.fname,base_len); + basen[base_len] = '\0'; /* GLU C'est sur on a un 0 a la fin */ + /* + GLU On ne fait cela que si necessaire, on essaye d'etre le + GLU simple dans le cas general (le plus frequent). + */ + info->fake.fname[0] = '-'; + strcpy (info->fake.fname+1,basen); /* GLU C'est sur on a un 0 a la fin */ + msdos_len = (base_len==8) ? 8 : base_len + 1; + info->msdos_reject = 1; + break; + } + } + } + info->fake.fname[msdos_len] = '\0'; /* Help doing printk */ + /* GLU Ce zero devrais deja y etre ! (invariant ?) */ + info->fake.len = msdos_len; + /* Pourquoi ne pas utiliser info->fake.len partout ??? plus long ?*/ + memcpy (info->entry.name,fname,len); + info->entry.name_len = len; + ret = 0; + } + /* + Evaluate how many record are needed to store this entry. + */ + info->recsize = umsdos_evalrecsize (len); + return ret; +} + +#ifdef TEST + +struct MANG_TEST{ + char *fname; /* Name to validate */ + int msdos_reject; /* Expected msdos_reject flag */ + char *msname; /* Expected msdos name */ +}; + +struct MANG_TEST tb[]={ + "hello", 0, "hello", + "hello.1", 0, "hello.1", + "hello.1_", 0, "hello.1_", + "prm", 0, "prm", + +#ifdef PROPOSITION + "HELLO", 1, "hello", + "Hello.1", 1, "hello.1", + "Hello.c", 1, "hello.c", +#elseif +/* + Je trouve les trois exemples ci-dessous tres "malheureux". + Je propose de mettre en minuscule dans un passe preliminaire, + et de tester apres si il y a d'autres caracters "mechants". + Bon, je ne l'ai pas fait, parceque ce n'est pas si facilement + modifiable que ca. Mais c'est pour le principe. + Evidemment cela augmente les chances de "Collision", + par exemple: entre "HELLO" et "Hello", mais ces problemes + peuvent etre traiter ailleur avec les autres collisions. +*/ + "HELLO", 1, "hello", + "Hello.1", 1, "hello_1", + "Hello.c", 1, "hello_c", +#endif + + "hello.{_1", 1, "hello_{_", + "hello\t", 1, "hello#", + "hello.1.1", 1, "hello_1_", + "hel,lo", 1, "hel#lo", + "Salut.Tu.vas.bien?", 1, "salut_tu", + ".profile", 1, "_profile", + ".xv", 1, "_xv", + "toto.", 1, "toto_", + "clock$.x", 1, "-clock$", + "emmxxxx0", 1, "-emmxxxx", + "emmxxxx0.abcd", 1, "-emmxxxx", + "aux", 1, "-aux", + "prn", 1, "-prn", + "prn.abc", 1, "-prn", + "PRN", 1, "-prn", +/* +GLU ATTENTION : Le resultat de ceux-ci sont differents avec ma version +GLU du mangle par rapport au mangle originale. +GLU CAUSE: La maniere de calculer la variable baselen. +GLU Pour toi c'est toujours 3 +GLU Pour moi c'est respectivement 7, 8 et 8 +*/ + "PRN.abc", 1, "prn_abc", + "Prn.abcd", 1, "prn_abcd", + "prn.abcd", 1, "prn_abcd", + "Prn.abcdefghij", 1, "prn_abcd" +}; + +int main (int argc, char *argv[]) +{ + int i,rold,rnew; + printf ("Testing the umsdos_parse.\n"); + for (i=0; i<sizeof(tb)/sizeof(tb[0]); i++){ + struct MANG_TEST *pttb = tb+i; + struct umsdos_info info; + int ok = umsdos_parse (pttb->fname,strlen(pttb->fname),&info); + if (strcmp(info.fake.fname,pttb->msname)!=0){ + printf ("**** %s -> ",pttb->fname); + printf ("%s <> %s\n",info.fake.fname,pttb->msname); + }else if (info.msdos_reject != pttb->msdos_reject){ + printf ("**** %s -> %s ",pttb->fname,pttb->msname); + printf ("%d <> %d\n",info.msdos_reject,pttb->msdos_reject); + }else{ + printf (" %s -> %s %d\n",pttb->fname,pttb->msname + ,pttb->msdos_reject); + } + } + printf ("Testing the new umsdos_evalrecsize."); + for (i=0; i<UMSDOS_MAXNAME ; i++){ + rnew=umsdos_evalrecsize (i); + rold=umsdos_evalrecsize_old (i); + if (!(i%UMSDOS_REC_SIZE)){ + printf ("\n%d:\t",i); + } + if (rnew!=rold){ + printf ("**** %d newres: %d != %d \n", i, rnew, rold); + }else{ + printf("."); + } + } + printf ("\nEnd of Testing.\n"); + + return 0; +} + +#endif diff --git a/fs/umsdos/namei.c b/fs/umsdos/namei.c new file mode 100644 index 000000000..567039e14 --- /dev/null +++ b/fs/umsdos/namei.c @@ -0,0 +1,1043 @@ +/* + * linux/fs/umsdos/namei.c + * + * Written 1993 by Jacques Gelinas + * Inspired from linux/fs/msdos/... by Werner Almesberger + * + * Maintain and access the --linux alternate directory file. +*/ +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/stat.h> +#include <linux/string.h> +#include <linux/msdos_fs.h> +#include <linux/umsdos_fs.h> +#include <linux/malloc.h> + +#define PRINTK(x) +#define Printk(x) printk x + +#if 1 +/* + Wait for creation exclusivity. + Return 0 if the dir was already available. + Return 1 if a wait was necessary. + When 1 is return, it means a wait was done. It does not + mean the directory is available. +*/ +static int umsdos_waitcreate(struct inode *dir) +{ + int ret = 0; + if (dir->u.umsdos_i.u.dir_info.creating + && dir->u.umsdos_i.u.dir_info.pid != current->pid){ + sleep_on(&dir->u.umsdos_i.u.dir_info.p); + ret = 1; + } + return ret; +} +/* + Wait for any lookup process to finish +*/ +static void umsdos_waitlookup (struct inode *dir) +{ + while (dir->u.umsdos_i.u.dir_info.looking){ + sleep_on(&dir->u.umsdos_i.u.dir_info.p); + } +} +/* + Lock all other process out of this directory. +*/ +void umsdos_lockcreate (struct inode *dir) +{ + /* #Specification: file creation / not atomic + File creation is a two step process. First we create (allocate) + an entry in the EMD file and then (using the entry offset) we + build a unique name for MSDOS. We create this name in the msdos + space. + + We have to use semaphore (sleep_on/wake_up) to prevent lookup + into a directory when we create a file or directory and to + prevent creation while a lookup is going on. Since many lookup + may happen at the same time, the semaphore is a counter. + + Only one creation is allowed at the same time. This protection + may not be necessary. The problem arise mainly when a lookup + or a readdir is done while a file is partially created. The + lookup process see that as a "normal" problem and silently + erase the file from the EMD file. Normal because a file + may be erased during a MSDOS session, but not removed from + the EMD file. + + The locking is done on a directory per directory basis. Each + directory inode has its wait_queue. + + For some operation like hard link, things even get worse. Many + creation must occur at once (atomic). To simplify the design + a process is allowed to recursively lock the directory for + creation. The pid of the locking process is kept along with + a counter so a second level of locking is granted or not. + */ + /* + Wait for any creation process to finish except + if we (the process) own the lock + */ + while (umsdos_waitcreate(dir)!=0); + dir->u.umsdos_i.u.dir_info.creating++; + dir->u.umsdos_i.u.dir_info.pid = current->pid; + umsdos_waitlookup (dir); +} +/* + Lock all other process out of those two directories. +*/ +static void umsdos_lockcreate2 (struct inode *dir1, struct inode *dir2) +{ + /* + We must check that both directory are available before + locking anyone of them. This is to avoid some deadlock. + Thanks to dglaude@is1.vub.ac.be (GLAUDE DAVID) for pointing + this to me. + */ + while (1){ + if (umsdos_waitcreate(dir1)==0 + && umsdos_waitcreate(dir2)==0){ + /* We own both now */ + dir1->u.umsdos_i.u.dir_info.creating++; + dir1->u.umsdos_i.u.dir_info.pid = current->pid; + dir2->u.umsdos_i.u.dir_info.creating++; + dir2->u.umsdos_i.u.dir_info.pid = current->pid; + break; + } + } + umsdos_waitlookup(dir1); + umsdos_waitlookup(dir2); +} +/* + Wait until creation is finish in this directory. +*/ +void umsdos_startlookup (struct inode *dir) +{ + while (umsdos_waitcreate (dir) != 0); + dir->u.umsdos_i.u.dir_info.looking++; +} +void check_page_tables(void); + +/* + Unlock the directory. +*/ +void umsdos_unlockcreate (struct inode *dir) +{ + dir->u.umsdos_i.u.dir_info.creating--; + if (dir->u.umsdos_i.u.dir_info.creating < 0){ + printk ("UMSDOS: dir->u.umsdos_i.u.dir_info.creating < 0: %d" + ,dir->u.umsdos_i.u.dir_info.creating); + } + wake_up (&dir->u.umsdos_i.u.dir_info.p); +} +/* + Tell directory lookup is over. +*/ +void umsdos_endlookup (struct inode *dir) +{ + dir->u.umsdos_i.u.dir_info.looking--; + if (dir->u.umsdos_i.u.dir_info.looking < 0){ + printk ("UMSDOS: dir->u.umsdos_i.u.dir_info.looking < 0: %d" + ,dir->u.umsdos_i.u.dir_info.looking); + } + wake_up (&dir->u.umsdos_i.u.dir_info.p); +} +#else +static void umsdos_lockcreate (struct inode *dir){} +static void umsdos_lockcreate2 (struct inode *dir1, struct inode *dir2){} +void umsdos_startlookup (struct inode *dir){} +static void umsdos_unlockcreate (struct inode *dir){} +void umsdos_endlookup (struct inode *dir){} +#endif +static int umsdos_nevercreat( + struct inode *dir, + const char *name, /* Name of the file to add */ + int len, + int errcod) /* Length of the name */ +{ + int ret = 0; + if (umsdos_is_pseudodos(dir,name,len)){ + /* #Specification: pseudo root / any file creation /DOS + The pseudo sub-directory /DOS can't be created! + EEXIST is returned. + + The pseudo sub-directory /DOS can't be removed! + EPERM is returned. + */ + ret = -EPERM; + ret = errcod; + }else if (name[0] == '.' + && (len == 1 || (len == 2 && name[1] == '.'))){ + /* #Specification: create / . and .. + If one try to creates . or .., it always fail and return + EEXIST. + + If one try to delete . or .., it always fail and return + EPERM. + + This should be test at the VFS layer level to avoid + duplicating this in all file systems. Any comments ? + */ + ret = errcod; + } + return ret; +} + +/* + Add a new file (ordinary or special) into the alternate directory. + The file is added to the real MSDOS directory. If successful, it + is then added to the EDM file. + + Return the status of the operation. 0 mean success. +*/ +static int umsdos_create_any ( + struct inode *dir, + const char *name, /* Name of the file to add */ + int len, /* Length of the name */ + int mode, /* Permission bit + file type ??? */ + int rdev, /* major, minor or 0 for ordinary file */ + /* and symlinks */ + char flags, + struct inode **result) /* Will hold the inode of the newly created */ + /* file */ +{ + int ret = umsdos_nevercreat(dir,name,len,-EEXIST); + if (ret == 0){ + struct umsdos_info info; + ret = umsdos_parse (name,len,&info); + *result = NULL; + if (ret == 0){ + info.entry.mode = mode; + info.entry.rdev = rdev; + info.entry.flags = flags; + info.entry.uid = current->fsuid; + info.entry.gid = (dir->i_mode & S_ISGID) + ? dir->i_gid : current->fsgid; + info.entry.ctime = info.entry.atime = info.entry.mtime + = CURRENT_TIME; + info.entry.nlink = 1; + umsdos_lockcreate(dir); + ret = umsdos_newentry (dir,&info); + if (ret == 0){ + dir->i_count++; + ret = msdos_create (dir,info.fake.fname,info.fake.len + ,S_IFREG|0777,result); + if (ret == 0){ + struct inode *inode = *result; + umsdos_lookup_patch (dir,inode,&info.entry,info.f_pos); + PRINTK (("inode %p[%d] ",inode,inode->i_count)); + PRINTK (("Creation OK: [%d] %s %d pos %d\n",dir->i_ino + ,info.fake.fname,current->pid,info.f_pos)); + }else{ + /* #Specification: create / file exist in DOS + Here is a situation. Trying to create a file with + UMSDOS. The file is unknown to UMSDOS but already + exist in the DOS directory. + + Here is what we are NOT doing: + + We could silently assume that everything is fine + and allows the creation to succeed. + + It is possible not all files in the partition + are mean to be visible from linux. By trying to create + those file in some directory, one user may get access + to those file without proper permissions. Looks like + a security hole to me. Off course sharing a file system + with DOS is some kind of security hole :-) + + So ? + + We return EEXIST in this case. + The same is true for directory creation. + */ + if (ret == -EEXIST){ + printk ("UMSDOS: out of sync, Creation error [%ld], " + "deleting %s %d %d pos %ld\n",dir->i_ino + ,info.fake.fname,-ret,current->pid,info.f_pos); + } + umsdos_delentry (dir,&info,0); + } + PRINTK (("umsdos_create %s ret = %d pos %d\n" + ,info.fake.fname,ret,info.f_pos)); + } + umsdos_unlockcreate(dir); + } + } + iput (dir); + return ret; +} +/* + Initialise the new_entry from the old for a rename operation. + (Only useful for umsdos_rename_f() below). +*/ +static void umsdos_ren_init( + struct umsdos_info *new_info, + struct umsdos_info *old_info, + int flags) /* 0 == copy flags from old_name */ + /* != 0, this is the value of flags */ +{ + new_info->entry.mode = old_info->entry.mode; + new_info->entry.rdev = old_info->entry.rdev; + new_info->entry.uid = old_info->entry.uid; + new_info->entry.gid = old_info->entry.gid; + new_info->entry.ctime = old_info->entry.ctime; + new_info->entry.atime = old_info->entry.atime; + new_info->entry.mtime = old_info->entry.mtime; + new_info->entry.flags = flags ? flags : old_info->entry.flags; + new_info->entry.nlink = old_info->entry.nlink; +} + +#define chkstk() \ + if (STACK_MAGIC != *(unsigned long *)current->kernel_stack_page){\ + printk(KERN_ALERT "UMSDOS: %s magic %x != %lx ligne %d\n" \ + , current->comm,STACK_MAGIC \ + ,*(unsigned long *)current->kernel_stack_page \ + ,__LINE__); \ + } + +/* + Rename a file (move) in the file system. +*/ +static int umsdos_rename_f( + struct inode * old_dir, + const char * old_name, + int old_len, + struct inode * new_dir, + const char * new_name, + int new_len, + int flags) /* 0 == copy flags from old_name */ + /* != 0, this is the value of flags */ +{ + int ret = EPERM; + struct umsdos_info old_info; + int old_ret = umsdos_parse (old_name,old_len,&old_info); + struct umsdos_info new_info; + int new_ret = umsdos_parse (new_name,new_len,&new_info); +chkstk(); + PRINTK (("umsdos_rename %d %d ",old_ret,new_ret)); + if (old_ret == 0 && new_ret == 0){ + umsdos_lockcreate2(old_dir,new_dir); +chkstk(); + PRINTK (("old findentry ")); + ret = umsdos_findentry(old_dir,&old_info,0); +chkstk(); + PRINTK (("ret %d ",ret)); + if (ret == 0){ + PRINTK (("new newentry ")); + umsdos_ren_init(&new_info,&old_info,flags); + ret = umsdos_newentry (new_dir,&new_info); +chkstk(); + PRINTK (("ret %d %d ",ret,new_info.fake.len)); + if (ret == 0){ + PRINTK (("msdos_rename ")); + old_dir->i_count++; + new_dir->i_count++; /* Both inode are needed later */ + ret = msdos_rename (old_dir + ,old_info.fake.fname,old_info.fake.len + ,new_dir + ,new_info.fake.fname,new_info.fake.len); +chkstk(); + PRINTK (("after m_rename ret %d ",ret)); + if (ret != 0){ + umsdos_delentry (new_dir,&new_info + ,S_ISDIR(new_info.entry.mode)); +chkstk(); + }else{ + ret = umsdos_delentry (old_dir,&old_info + ,S_ISDIR(old_info.entry.mode)); +chkstk(); + if (ret == 0){ + /* + This UMSDOS_lookup does not look very useful. + It makes sure that the inode of the file will + be correctly setup (umsdos_patch_inode()) in + case it is already in use. + + Not very efficient ... + */ + struct inode *inode; + new_dir->i_count++; + PRINTK (("rename lookup len %d %d -- ",new_len,new_info.entry.flags)); + ret = UMSDOS_lookup (new_dir,new_name,new_len + ,&inode); +chkstk(); + if (ret != 0){ + printk ("UMSDOS: partial rename for file %s\n" + ,new_info.entry.name); + }else{ + /* + Update f_pos so notify_change will succeed + if the file was already in use. + */ + umsdos_set_dirinfo (inode,new_dir,new_info.f_pos); +chkstk(); + iput (inode); + } + } + } + } + } + umsdos_unlockcreate(old_dir); + umsdos_unlockcreate(new_dir); + } + iput (old_dir); + iput (new_dir); + PRINTK (("\n")); + return ret; +} +/* + Setup un Symbolic link or a (pseudo) hard link + Return a negative error code or 0 if ok. +*/ +static int umsdos_symlink_x( + struct inode * dir, + const char * name, + int len, + const char * symname, /* name will point to this path */ + int mode, + char flags) +{ + /* #Specification: symbolic links / strategy + A symbolic link is simply a file which hold a path. It is + implemented as a normal MSDOS file (not very space efficient :-() + + I see 2 different way to do it. One is to place the link data + in unused entry of the EMD file. The other is to have a separate + file dedicated to hold all symbolic links data. + + Lets go for simplicity... + */ + struct inode *inode; + int ret; + dir->i_count++; /* We keep the inode in case we need it */ + /* later */ + ret = umsdos_create_any (dir,name,len,mode,0,flags,&inode); + PRINTK (("umsdos_symlink ret %d ",ret)); + if (ret == 0){ + int len = strlen(symname); + struct file filp; + filp.f_pos = 0; + /* Make the inode acceptable to MSDOS */ + ret = umsdos_file_write_kmem (inode,&filp,(char*)symname,len); + iput (inode); + if (ret >= 0){ + if (ret != len){ + ret = -EIO; + printk ("UMSDOS: " + "Can't write symbolic link data\n"); + }else{ + ret = 0; + } + } + if (ret != 0){ + UMSDOS_unlink (dir,name,len); + dir = NULL; + } + } + iput (dir); + PRINTK (("\n")); + return ret; +} +/* + Setup un Symbolic link. + Return a negative error code or 0 if ok. +*/ +int UMSDOS_symlink( + struct inode * dir, + const char * name, + int len, + const char * symname) /* name will point to this path */ +{ + return umsdos_symlink_x (dir,name,len,symname,S_IFLNK|0777,0); +} +/* + Add a link to an inode in a directory +*/ +int UMSDOS_link ( + struct inode * oldinode, + struct inode * dir, + const char * name, + int len) +{ + /* #Specification: hard link / strategy + Well ... hard link are difficult to implement on top of an + MsDOS fat file system. Unlike UNIX file systems, there are no + inode. A directory entry hold the functionality of the inode + and the entry. + + We will used the same strategy as a normal Unix file system + (with inode) except we will do it symbolically (using paths). + + Because anything can happen during a DOS session (defragment, + directory sorting, etc...), we can't rely on MsDOS pseudo + inode number to record the link. For this reason, the link + will be done using hidden symbolic links. The following + scenario illustrate how it work. + + Given a file /foo/file + + ln /foo/file /tmp/file2 + + become internally + + mv /foo/file /foo/-LINK1 + ln -s /foo/-LINK1 /foo/file + ln -s /foo/-LINK1 /tmp/file2 + + Using this strategy, we can operate on /foo/file or /foo/file2. + We can remove one and keep the other, like a normal Unix hard link. + We can rename /foo/file or /tmp/file2 independently. + + The entry -LINK1 will be hidden. It will hold a link count. + When all link are erased, the hidden file is erased too. + */ + /* #Specification: weakness / hard link + The strategy for hard link introduces a side effect that + may or may not be acceptable. Here is the sequence + + mkdir subdir1 + touch subdir1/file + mkdir subdir2 + ln subdir1/file subdir2/file + rm subdir1/file + rmdir subdir1 + rmdir: subdir1: Directory not empty + + This happen because there is an invisible file (--link) in + subdir1 which is referenced by subdir2/file. + + Any idea ? + */ + /* #Specification: weakness / hard link / rename directory + Another weakness of hard link come from the fact that + it is based on hidden symbolic links. Here is an example. + + mkdir /subdir1 + touch /subdir1/file + mkdir /subdir2 + ln /subdir1/file subdir2/file + mv /subdir1 subdir3 + ls -l /subdir2/file + + Since /subdir2/file is a hidden symbolic link + to /subdir1/..hlinkNNN, accessing it will fail since + /subdir1 does not exist anymore (has been renamed). + */ + int ret = 0; + if (S_ISDIR(oldinode->i_mode)){ + /* #Specification: hard link / directory + A hard link can't be made on a directory. EPERM is returned + in this case. + */ + ret = -EPERM; + }else if ((ret = umsdos_nevercreat(dir,name,len,-EPERM))==0){ + struct inode *olddir; + ret = umsdos_get_dirowner(oldinode,&olddir); + PRINTK (("umsdos_link dir_owner = %d -> %p [%d] " + ,oldinode->u.umsdos_i.i_dir_owner,olddir,olddir->i_count)); + if (ret == 0){ + struct umsdos_dirent entry; + umsdos_lockcreate2(dir,olddir); + ret = umsdos_inode2entry (olddir,oldinode,&entry); + if (ret == 0){ + PRINTK (("umsdos_link :%s: ino %d flags %d " + ,entry.name + ,oldinode->i_ino,entry.flags)); + if (!(entry.flags & UMSDOS_HIDDEN)){ + /* #Specification: hard link / first hard link + The first time a hard link is done on a file, this + file must be renamed and hidden. Then an internal + symbolic link must be done on the hidden file. + + The second link is done after on this hidden file. + + It is expected that the Linux MSDOS file system + keeps the same pseudo inode when a rename operation + is done on a file in the same directory. + */ + struct umsdos_info info; + ret = umsdos_newhidden (olddir,&info); + if (ret == 0){ + olddir->i_count+=2; + PRINTK (("olddir[%d] ",olddir->i_count)); + ret = umsdos_rename_f (olddir,entry.name + ,entry.name_len + ,olddir,info.entry.name,info.entry.name_len + ,UMSDOS_HIDDEN); + if (ret == 0){ + char *path = (char*)kmalloc(PATH_MAX,GFP_KERNEL); + if (path == NULL){ + ret = -ENOMEM; + }else{ + PRINTK (("olddir[%d] ",olddir->i_count)); + ret = umsdos_locate_path (oldinode,path); + PRINTK (("olddir[%d] ",olddir->i_count)); + if (ret == 0){ + olddir->i_count++; + ret = umsdos_symlink_x (olddir + ,entry.name + ,entry.name_len,path + ,S_IFREG|0777,UMSDOS_HLINK); + if (ret == 0){ + dir->i_count++; + ret = umsdos_symlink_x (dir,name,len + ,path + ,S_IFREG|0777,UMSDOS_HLINK); + } + } + kfree (path); + } + } + } + }else{ + char *path = (char*)kmalloc(PATH_MAX,GFP_KERNEL); + if (path == NULL){ + ret = -ENOMEM; + }else{ + ret = umsdos_locate_path (oldinode,path); + if (ret == 0){ + dir->i_count++; + ret = umsdos_symlink_x (dir,name,len,path + ,S_IFREG|0777,UMSDOS_HLINK); + } + kfree (path); + } + } + } + umsdos_unlockcreate(olddir); + umsdos_unlockcreate(dir); + } + iput (olddir); + } + if (ret == 0){ + struct iattr newattrs; + oldinode->i_nlink++; + newattrs.ia_valid = 0; + ret = UMSDOS_notify_change(oldinode, &newattrs); + } + iput (oldinode); + iput (dir); + PRINTK (("umsdos_link %d\n",ret)); + return ret; +} +/* + Add a new file into the alternate directory. + The file is added to the real MSDOS directory. If successful, it + is then added to the EDM file. + + Return the status of the operation. 0 mean success. +*/ +int UMSDOS_create ( + struct inode *dir, + const char *name, /* Name of the file to add */ + int len, /* Length of the name */ + int mode, /* Permission bit + file type ??? */ + struct inode **result) /* Will hold the inode of the newly created */ + /* file */ +{ + return umsdos_create_any (dir,name,len,mode,0,0,result); +} +/* + Add a sub-directory in a directory +*/ +int UMSDOS_mkdir( + struct inode * dir, + const char * name, + int len, + int mode) +{ + int ret = umsdos_nevercreat(dir,name,len,-EEXIST); + if (ret == 0){ + struct umsdos_info info; + ret = umsdos_parse (name,len,&info); + PRINTK (("umsdos_mkdir %d\n",ret)); + if (ret == 0){ + info.entry.mode = mode | S_IFDIR; + info.entry.rdev = 0; + info.entry.uid = current->fsuid; + info.entry.gid = (dir->i_mode & S_ISGID) + ? dir->i_gid : current->fsgid; + info.entry.ctime = info.entry.atime = info.entry.mtime + = CURRENT_TIME; + info.entry.flags = 0; + umsdos_lockcreate(dir); + info.entry.nlink = 1; + ret = umsdos_newentry (dir,&info); + PRINTK (("newentry %d ",ret)); + if (ret == 0){ + dir->i_count++; + ret = msdos_mkdir (dir,info.fake.fname,info.fake.len,mode); + if (ret != 0){ + umsdos_delentry (dir,&info,1); + /* #Specification: mkdir / Directory already exist in DOS + We do the same thing as for file creation. + For all user it is an error. + */ + }else{ + /* #Specification: mkdir / umsdos directory / create EMD + When we created a new sub-directory in a UMSDOS + directory (one with full UMSDOS semantic), we + create immediately an EMD file in the new + sub-directory so it inherit UMSDOS semantic. + */ + struct inode *subdir; + ret = umsdos_real_lookup (dir,info.fake.fname + ,info.fake.len,&subdir); + if (ret == 0){ + struct inode *result; + ret = msdos_create (subdir,UMSDOS_EMD_FILE + ,UMSDOS_EMD_NAMELEN,S_IFREG|0777,&result); + subdir = NULL; + iput (result); + } + if (ret < 0){ + printk ("UMSDOS: Can't create empty --linux-.---\n"); + } + iput (subdir); + } + } + umsdos_unlockcreate(dir); + } + } + PRINTK (("umsdos_mkdir %d\n",ret)); + iput (dir); + return ret; +} +/* + Add a new device special file into a directory. +*/ +int UMSDOS_mknod( + struct inode * dir, + const char * name, + int len, + int mode, + int rdev) +{ + /* #Specification: Special files / strategy + Device special file, pipes, etc ... are created like normal + file in the msdos file system. Of course they remain empty. + + One strategy was to create those files only in the EMD file + since they were not important for MSDOS. The problem with + that, is that there were not getting inode number allocated. + The MSDOS filesystems is playing a nice game to fake inode + number, so why not use it. + + The absence of inode number compatible with those allocated + for ordinary files was causing major trouble with hard link + in particular and other parts of the kernel I guess. + */ + struct inode *inode; + int ret = umsdos_create_any (dir,name,len,mode,rdev,0,&inode); + iput (inode); + return ret; +} + +/* + Remove a sub-directory. +*/ +int UMSDOS_rmdir( + struct inode * dir, + const char * name, + int len) +{ + /* #Specification: style / iput strategy + In the UMSDOS project, I am trying to apply a single + programming style regarding inode management. Many + entry point are receiving an inode to act on, and must + do an iput() as soon as they are finished with + the inode. + + For simple case, there is no problem. When you introduce + error checking, you end up with many iput placed around the + code. + + The coding style I use all around is one where I am trying + to provide independent flow logic (I don't know how to + name this). With this style, code is easier to understand + but you rapidly get iput() all around. Here is an exemple + of what I am trying to avoid. + + if (a){ + ... + if(b){ + ... + } + ... + if (c){ + // Complex state. Was b true ? + ... + } + ... + } + // Weird state + if (d){ + // ... + } + // Was iput finally done ? + return status; + + Here is the style I am using. Still sometime I do the + first when things are very simple (or very complicated :-( ) + + if (a){ + if (b){ + ... + }else if (c){ + // A single state gets here + } + }else if (d){ + ... + } + return status; + + Again, while this help clarifying the code, I often get a lot + of iput(), unlike the first style, where I can place few + "strategic" iput(). "strategic" also mean, more difficult + to place. + + So here is the style I will be using from now on in this project. + There is always an iput() at the end of a function (which has + to do an iput()). One iput by inode. There is also one iput() + at the places where a successful operation is achieved. This + iput() is often done by a sub-function (often from the msdos + file system). So I get one too many iput() ? At the place + where an iput() is done, the inode is simply nulled, disabling + the last one. + + if (a){ + if (b){ + ... + }else if (c){ + msdos_rmdir(dir,...); + dir = NULL; + } + }else if (d){ + ... + } + iput (dir); + return status; + + Note that the umsdos_lockcreate() and umsdos_unlockcreate() function + pair goes against this practice of "forgetting" the inode as soon + as possible. + */ + int ret = umsdos_nevercreat(dir,name,len,-EPERM); + if (ret == 0){ + struct inode *sdir; + dir->i_count++; + ret = UMSDOS_lookup (dir,name,len,&sdir); + PRINTK (("rmdir lookup %d ",ret)); + if (ret == 0){ + int empty; + umsdos_lockcreate(dir); + if (sdir->i_count > 1){ + ret = -EBUSY; + }else if ((empty = umsdos_isempty (sdir)) != 0){ + PRINTK (("isempty %d i_count %d ",empty,sdir->i_count)); + if (empty == 1){ + /* We have to removed the EMD file */ + ret = msdos_unlink(sdir,UMSDOS_EMD_FILE + ,UMSDOS_EMD_NAMELEN); + sdir = NULL; + } + /* sdir must be free before msdos_rmdir() */ + iput (sdir); + sdir = NULL; + PRINTK (("isempty ret %d nlink %d ",ret,dir->i_nlink)); + if (ret == 0){ + struct umsdos_info info; + dir->i_count++; + umsdos_parse (name,len,&info); + /* The findentry is there only to complete */ + /* the mangling */ + umsdos_findentry (dir,&info,2); + ret = msdos_rmdir (dir,info.fake.fname + ,info.fake.len); + if (ret == 0){ + ret = umsdos_delentry (dir,&info,1); + } + } + }else{ + /* + The subdirectory is not empty, so leave it there + */ + ret = -ENOTEMPTY; + } + iput(sdir); + umsdos_unlockcreate(dir); + } + } + iput (dir); + PRINTK (("umsdos_rmdir %d\n",ret)); + return ret; +} +/* + Remove a file from the directory. +*/ +int UMSDOS_unlink ( + struct inode * dir, + const char * name, + int len) +{ + struct umsdos_info info; + int ret = umsdos_nevercreat(dir,name,len,-EPERM); + if (ret == 0){ + ret = umsdos_parse (name,len,&info); + if (ret == 0){ + umsdos_lockcreate(dir); + ret = umsdos_findentry(dir,&info,1); + if (ret == 0){ + PRINTK (("UMSDOS_unlink %s ",info.fake.fname)); + if (info.entry.flags & UMSDOS_HLINK){ + /* #Specification: hard link / deleting a link + When we deletes a file, and this file is a link + we must subtract 1 to the nlink field of the + hidden link. + + If the count goes to 0, we delete this hidden + link too. + */ + /* + First, get the inode of the hidden link + using the standard lookup function. + */ + struct inode *inode; + dir->i_count++; + ret = UMSDOS_lookup (dir,name,len,&inode); + if (ret == 0){ + PRINTK (("unlink nlink = %d ",inode->i_nlink)); + inode->i_nlink--; + if (inode->i_nlink == 0){ + struct inode *hdir = iget(inode->i_sb + ,inode->u.umsdos_i.i_dir_owner); + struct umsdos_dirent entry; + ret = umsdos_inode2entry (hdir,inode,&entry); + if (ret == 0){ + ret = UMSDOS_unlink (hdir,entry.name + ,entry.name_len); + }else{ + iput (hdir); + } + }else{ + struct iattr newattrs; + newattrs.ia_valid = 0; + ret = UMSDOS_notify_change (inode, &newattrs); + } + iput (inode); + } + } + if (ret == 0){ + ret = umsdos_delentry (dir,&info,0); + if (ret == 0){ + PRINTK (("Avant msdos_unlink %s ",info.fake.fname)); + dir->i_count++; + ret = msdos_unlink_umsdos (dir,info.fake.fname + ,info.fake.len); + PRINTK (("msdos_unlink %s %o ret %d ",info.fake.fname + ,info.entry.mode,ret)); + } + } + } + umsdos_unlockcreate(dir); + } + } + iput (dir); + PRINTK (("umsdos_unlink %d\n",ret)); + return ret; +} + +/* + Rename a file (move) in the file system. +*/ +int UMSDOS_rename( + struct inode * old_dir, + const char * old_name, + int old_len, + struct inode * new_dir, + const char * new_name, + int new_len) +{ + /* #Specification: weakness / rename + There is a case where UMSDOS rename has a different behavior + than normal UNIX file system. Renaming an open file across + directory boundary does not work. Renaming an open file within + a directory does work however. + + The problem (not sure) is in the linux VFS msdos driver. + I believe this is not a bug but a design feature, because + an inode number represent some sort of directory address + in the MSDOS directory structure. So moving the file into + another directory does not preserve the inode number. + */ + int ret = umsdos_nevercreat(new_dir,new_name,new_len,-EEXIST); + if (ret == 0){ + /* umsdos_rename_f eat the inode and we may need those later */ + old_dir->i_count++; + new_dir->i_count++; + ret = umsdos_rename_f (old_dir,old_name,old_len,new_dir,new_name + ,new_len,0); + if (ret == -EEXIST){ + /* #Specification: rename / new name exist + If the destination name already exist, it will + silently be removed. EXT2 does it this way + and this is the spec of SUNOS. So does UMSDOS. + + If the destination is an empty directory it will + also be removed. + */ + /* #Specification: rename / new name exist / possible flaw + The code to handle the deletion of the target (file + and directory) use to be in umsdos_rename_f, surrounded + by proper directory locking. This was insuring that only + one process could achieve a rename (modification) operation + in the source and destination directory. This was also + insuring the operation was "atomic". + + This has been changed because this was creating a kernel + stack overflow (stack is only 4k in the kernel). To avoid + the code doing the deletion of the target (if exist) has + been moved to a upper layer. umsdos_rename_f is tried + once and if it fails with EEXIST, the target is removed + and umsdos_rename_f is done again. + + This makes the code cleaner and (not sure) solve a + deadlock problem one tester was experiencing. + + The point is to mention that possibly, the semantic of + "rename" may be wrong. Anyone dare to check that :-) + Be aware that IF it is wrong, to produce the problem you + will need two process trying to rename a file to the + same target at the same time. Again, I am not sure it + is a problem at all. + */ + /* This is not super efficient but should work */ + new_dir->i_count++; + ret = UMSDOS_unlink (new_dir,new_name,new_len); +chkstk(); + PRINTK (("rename unlink ret %d %d -- ",ret,new_len)); + if (ret == -EISDIR){ + new_dir->i_count++; + ret = UMSDOS_rmdir (new_dir,new_name,new_len); +chkstk(); + PRINTK (("rename rmdir ret %d -- ",ret)); + } + if (ret == 0){ + ret = umsdos_rename_f (old_dir,old_name,old_len + ,new_dir,new_name,new_len,0); + new_dir = old_dir = NULL; + } + } + } + iput (new_dir); + iput (old_dir); + return ret; +} + diff --git a/fs/umsdos/notes b/fs/umsdos/notes new file mode 100644 index 000000000..3c47d1f4f --- /dev/null +++ b/fs/umsdos/notes @@ -0,0 +1,17 @@ +This file contain idea and things I don't want to forget + +Possible bug in fs/read_write.c +Function sys_readdir() + + There is a call the verify_area that does not take in account + the count parameter. I guess it should read + + error = verify_area(VERIFY_WRITE, dirent, count*sizeof (*dirent)); + + instead of + + error = verify_area(VERIFY_WRITE, dirent, sizeof (*dirent)); + + Of course, now , count is always 1 + + diff --git a/fs/umsdos/rdir.c b/fs/umsdos/rdir.c new file mode 100644 index 000000000..d7272ed96 --- /dev/null +++ b/fs/umsdos/rdir.c @@ -0,0 +1,239 @@ +/* + * linux/fs/umsdos/rdir.c + * + * Written 1994 by Jacques Gelinas + * + * Extended MS-DOS directory pure MS-DOS handling functions + * (For directory without EMD file). + */ + +#include <asm/segment.h> + +#include <linux/sched.h> +#include <linux/fs.h> +#include <linux/msdos_fs.h> +#include <linux/errno.h> +#include <linux/stat.h> +#include <linux/limits.h> +#include <linux/umsdos_fs.h> +#include <linux/malloc.h> + +#define PRINTK(x) +#define Printk(x) printk x + + +extern struct inode *pseudo_root; + +static int UMSDOS_rreaddir ( + struct inode *dir, + struct file *filp, + struct dirent *dirent, + int count) +{ + int ret = 0; + while (1){ + ret = msdos_readdir(dir,filp,dirent,count); + if (ret == 5 + && pseudo_root != NULL + && dir->i_sb->s_mounted == pseudo_root->i_sb->s_mounted){ + /* + In pseudo root mode, we must eliminate logically + the directory linux from the real root. + */ + char name[5]; + memcpy_fromfs (name,dirent->d_name,5); + if (memcmp(name,UMSDOS_PSDROOT_NAME,UMSDOS_PSDROOT_LEN)!=0) break; + }else{ + if (pseudo_root != NULL + && ret == 2 + && dir == dir->i_sb->s_mounted + && dir == pseudo_root->i_sb->s_mounted){ + char name[2]; + memcpy_fromfs (name,dirent->d_name,2); + if (name[0] == '.' && name[1] == '.'){ + put_fs_long (pseudo_root->i_ino,&dirent->d_ino); + } + } + break; + } + } + return ret; +} + +int UMSDOS_rlookup( + struct inode *dir, + const char *name, + int len, + struct inode **result) /* Will hold inode of the file, if successful */ +{ + int ret; + if (pseudo_root != NULL + && len == 2 + && name[0] == '.' + && name[1] == '.' + && dir == dir->i_sb->s_mounted + && dir == pseudo_root->i_sb->s_mounted){ + *result = pseudo_root; + pseudo_root->i_count++; + ret = 0; + /* #Specification: pseudo root / DOS/.. + In the real root directory (c:\), the directory .. + is the pseudo root (c:\linux). + */ + }else{ + ret = umsdos_real_lookup (dir,name,len,result); + if (ret == 0){ + struct inode *inode = *result; + if (inode == pseudo_root){ + /* #Specification: pseudo root / DOS/linux + Even in the real root directory (c:\), the directory + /linux won't show + */ + ret = -ENOENT; + iput (pseudo_root); + *result = NULL; + }else if (S_ISDIR(inode->i_mode)){ + /* We must place the proper function table */ + /* depending if this is a MsDOS directory or an UMSDOS directory */ + umsdos_setup_dir_inode(inode); + } + } + } + iput (dir); + return ret; +} + +static int UMSDOS_rrmdir ( + struct inode *dir, + const char *name, + int len) +{ + /* #Specification: dual mode / rmdir in a DOS directory + In a DOS (not EMD in it) directory, we use a reverse strategy + compared with an Umsdos directory. We assume that a subdirectory + of a DOS directory is also a DOS directory. This is not always + true (umssync may be used anywhere), but make sense. + + So we call msdos_rmdir() directly. If it failed with a -ENOTEMPTY + then we check if it is a Umsdos directory. We check if it is + really empty (only . .. and --linux-.--- in it). If it is true + we remove the EMD and do a msdos_rmdir() again. + + In a Umsdos directory, we assume all subdirectory are also + Umsdos directory, so we check the EMD file first. + */ + int ret; + if (umsdos_is_pseudodos(dir,name,len)){ + /* #Specification: pseudo root / rmdir /DOS + The pseudo sub-directory /DOS can't be removed! + This is done even if the pseudo root is not a Umsdos + directory anymore (very unlikely), but an accident (under + MsDOS) is always possible. + + EPERM is returned. + */ + ret = -EPERM; + }else{ + umsdos_lockcreate (dir); + dir->i_count++; + ret = msdos_rmdir (dir,name,len); + if (ret == -ENOTEMPTY){ + struct inode *sdir; + dir->i_count++; + ret = UMSDOS_rlookup (dir,name,len,&sdir); + PRINTK (("rrmdir lookup %d ",ret)); + if (ret == 0){ + int empty; + if ((empty = umsdos_isempty (sdir)) != 0){ + PRINTK (("isempty %d i_count %d ",empty,sdir->i_count)); + if (empty == 2){ + /* + Not a Umsdos directory, so the previous msdos_rmdir + was not lying :-) + */ + ret = -ENOTEMPTY; + }else if (empty == 1){ + /* We have to removed the EMD file */ + ret = msdos_unlink(sdir,UMSDOS_EMD_FILE + ,UMSDOS_EMD_NAMELEN); + sdir = NULL; + if (ret == 0){ + dir->i_count++; + ret = msdos_rmdir (dir,name,len); + } + } + }else{ + ret = -ENOTEMPTY; + } + iput (sdir); + } + } + umsdos_unlockcreate (dir); + } + iput (dir); + return ret; +} + +/* #Specification: dual mode / introduction + One goal of UMSDOS is to allow a practical and simple coexistence + between MsDOS and Linux in a single partition. Using the EMD file + in each directory, UMSDOS add Unix semantics and capabilities to + normal DOS file system. To help and simplify coexistence, here is + the logic related to the EMD file. + + If it is missing, then the directory is managed by the MsDOS driver. + The names are limited to DOS limits (8.3). No links, no device special + and pipe and so on. + + If it is there, it is the directory. If it is there but empty, then + the directory looks empty. The utility umssync allows synchronisation + of the real DOS directory and the EMD. + + Whenever umssync is applied to a directory without EMD, one is + created on the fly. The directory is promoted to full unix semantic. + Of course, the ls command will show exactly the same content as before + the umssync session. + + It is believed that the user/admin will promote directories to unix + semantic as needed. + + The strategy to implement this is to use two function table (struct + inode_operations). One for true UMSDOS directory and one for directory + with missing EMD. + + Functions related to the DOS semantic (but aware of UMSDOS) generally + have a "r" prefix (r for real) such as UMSDOS_rlookup, to differentiate + from the one with full UMSDOS semantic. +*/ +static struct file_operations umsdos_rdir_operations = { + NULL, /* lseek - default */ + UMSDOS_dir_read, /* read */ + NULL, /* write - bad */ + UMSDOS_rreaddir, /* readdir */ + NULL, /* select - default */ + UMSDOS_ioctl_dir, /* ioctl - default */ + NULL, /* mmap */ + NULL, /* no special open code */ + NULL, /* no special release code */ + NULL /* fsync */ +}; + +struct inode_operations umsdos_rdir_inode_operations = { + &umsdos_rdir_operations, /* default directory file-ops */ + msdos_create, /* create */ + UMSDOS_rlookup, /* lookup */ + NULL, /* link */ + msdos_unlink, /* unlink */ + NULL, /* symlink */ + msdos_mkdir, /* mkdir */ + UMSDOS_rrmdir, /* rmdir */ + NULL, /* mknod */ + msdos_rename, /* rename */ + NULL, /* readlink */ + NULL, /* follow_link */ + NULL, /* bmap */ + NULL, /* truncate */ + NULL /* permission */ +}; + + diff --git a/fs/umsdos/symlink.c b/fs/umsdos/symlink.c new file mode 100644 index 000000000..1b1e561c2 --- /dev/null +++ b/fs/umsdos/symlink.c @@ -0,0 +1,145 @@ +/* + * linux/fs/umsdos/file.c + * + * Written 1992 by Jacques Gelinas + * inspired from linux/fs/msdos/file.c Werner Almesberger + * + * Extended MS-DOS regular file handling primitives + */ + +#include <asm/segment.h> +#include <asm/system.h> + +#include <linux/sched.h> +#include <linux/fs.h> +#include <linux/msdos_fs.h> +#include <linux/errno.h> +#include <linux/fcntl.h> +#include <linux/stat.h> +#include <linux/umsdos_fs.h> +#include <linux/malloc.h> + +#define PRINTK(x) +#define Printk(x) printk x +/* + Read the data associate with the symlink. + Return length read in buffer or a negative error code. +*/ +static int umsdos_readlink_x ( + struct inode *inode, + char *buffer, + int (*msdos_read)(struct inode *, struct file *, char *, int), + int bufsiz) +{ + int ret = inode->i_size; + struct file filp; + filp.f_pos = 0; + filp.f_reada = 0; + if (ret > bufsiz) ret = bufsiz; + if ((*msdos_read) (inode, &filp, buffer,ret) != ret){ + ret = -EIO; + } + return ret; +} +/* + Follow a symbolic link chain by calling open_namei recursively + until an inode is found. + + Return 0 if ok, or a negative error code if not. +*/ +static int UMSDOS_follow_link( + struct inode * dir, + struct inode * inode, + int flag, + int mode, + struct inode ** res_inode) +{ + int ret = -ELOOP; + *res_inode = NULL; + if (current->link_count < 5) { + char *path = (char*)kmalloc(PATH_MAX,GFP_KERNEL); + if (path == NULL){ + ret = -ENOMEM; + }else{ + if (!dir) { + dir = current->fs[1].root; + dir->i_count++; + } + if (!inode){ + PRINTK (("symlink: inode = NULL\n")); + ret = -ENOENT; + }else if (!S_ISLNK(inode->i_mode)){ + PRINTK (("symlink: Not ISLNK\n")); + *res_inode = inode; + inode = NULL; + ret = 0; + }else{ + ret = umsdos_readlink_x (inode,path + ,umsdos_file_read_kmem,PATH_MAX-1); + if (ret > 0){ + path[ret] = '\0'; + PRINTK (("follow :%s: %d ",path,ret)); + iput(inode); + inode = NULL; + current->link_count++; + ret = open_namei(path,flag,mode,res_inode,dir); + current->link_count--; + dir = NULL; + }else{ + ret = -EIO; + } + } + kfree (path); + } + } + iput(inode); + iput(dir); + PRINTK (("follow_link ret %d\n",ret)); + return ret; +} + +static int UMSDOS_readlink(struct inode * inode, char * buffer, int buflen) +{ + int ret = -EINVAL; + if (S_ISLNK(inode->i_mode)) { + ret = umsdos_readlink_x (inode,buffer,msdos_file_read,buflen); + } + PRINTK (("readlink %d %x bufsiz %d\n",ret,inode->i_mode,buflen)); + iput(inode); + return ret; + +} + +static struct file_operations umsdos_symlink_operations = { + NULL, /* lseek - default */ + NULL, /* read */ + NULL, /* write */ + NULL, /* readdir - bad */ + NULL, /* select - default */ + NULL, /* ioctl - default */ + NULL, /* mmap */ + NULL, /* no special open is needed */ + NULL, /* release */ + NULL /* fsync */ +}; + +struct inode_operations umsdos_symlink_inode_operations = { + &umsdos_symlink_operations, /* default file operations */ + NULL, /* create */ + NULL, /* lookup */ + NULL, /* link */ + NULL, /* unlink */ + NULL, /* symlink */ + NULL, /* mkdir */ + NULL, /* rmdir */ + NULL, /* mknod */ + NULL, /* rename */ + UMSDOS_readlink, /* readlink */ + UMSDOS_follow_link, /* follow_link */ + NULL, /* bmap */ + NULL, /* truncate */ + NULL /* permission */ +}; + + + |