patch-1.3.40 linux/fs/msdos/namei.c

Next file: linux/fs/proc/array.c
Previous file: linux/fs/msdos/misc.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v1.3.39/linux/fs/msdos/namei.c linux/fs/msdos/namei.c
@@ -2,6 +2,7 @@
  *  linux/fs/msdos/namei.c
  *
  *  Written 1992,1993 by Werner Almesberger
+ *  Hidden files 1995 by Albert Cahalan <albert@ccs.neu.edu> <adc@coe.neu.edu>
  */
 
 #include <linux/sched.h>
@@ -32,24 +33,31 @@
 static char bad_if_strict[] = "+=,; ";
 
 
-/* Formats an MS-DOS file name. Rejects invalid names. */
-
-static int msdos_format_name(char conv,const char *name,int len,char *res,
-  int dot_dirs)
+/***** Formats an MS-DOS file name. Rejects invalid names. */
+static int msdos_format_name(char conv,const char *name,int len,
+	char *res,int dot_dirs,char dotsOK)
+	/* conv is relaxed/normal/strict, name is proposed name,
+	 * len is the length of the proposed name, res is the result name,
+	 * dot_dirs is . and .. are OK, dotsOK is if hidden files get dots.
+	 */
 {
 	char *walk;
 	const char **reserved;
 	unsigned char c;
 	int space;
 
-	if (IS_FREE(name)) return -EINVAL;
 	if (name[0] == '.' && (len == 1 || (len == 2 && name[1] == '.'))) {
 		if (!dot_dirs) return -EEXIST;
 		memset(res+1,' ',10);
 		while (len--) *res++ = '.';
 		return 0;
 	}
-	space = 1; /* disallow names starting with a dot */
+	if (name[0] == '.') {  /* dotfile because . and .. already done */
+		if (!dotsOK) return -EINVAL;
+		/* Get rid of dot - test for it elsewhere */
+		name++; len--;
+	}
+	space = 1; /* disallow names that _really_ start with a dot */
 	c = 0;
 	for (walk = res; len && walk-res < 8; walk++) {
 	    	c = *name++;
@@ -58,9 +66,14 @@
 		if (conv == 's' && strchr(bad_if_strict,c)) return -EINVAL;
   		if (c >= 'A' && c <= 'Z' && conv == 's') return -EINVAL;
 		if (c < ' ' || c == ':' || c == '\\') return -EINVAL;
+/*  0xE5 is legal as a first character, but we must substitute 0x05     */
+/*  because 0xE5 marks deleted files.  Yes, DOS really does this.       */
+/*  It seems that Microsoft hacked DOS to support non-US characters     */
+/*  after the 0xE5 character was already in use to mark deleted files.  */
+		if((res==walk) && (c==0xE5)) c=0x05;
 		if (c == '.') break;
-		space = c == ' ';
-		*walk = c >= 'a' && c <= 'z' ? c-32 : c;
+		space = (c == ' ');
+		*walk = (c >= 'a' && c <= 'z') ? c-32 : c;
 	}
 	if (space) return -EINVAL;
 	if (conv == 's' && len && c != '.') {
@@ -93,21 +106,34 @@
 }
 
 
-/* Locates a directory entry. */
-
+/***** Locates a directory entry.  Uses unformatted name. */
 static int msdos_find(struct inode *dir,const char *name,int len,
     struct buffer_head **bh,struct msdos_dir_entry **de,int *ino)
 {
 	char msdos_name[MSDOS_NAME];
 	int res;
+	char dotsOK;
+	char scantype;
 
-	res = msdos_format_name(MSDOS_SB(dir->i_sb)->name_check,name,len, msdos_name,1);
+	dotsOK = MSDOS_SB(dir->i_sb)->dotsOK;
+	res = msdos_format_name(MSDOS_SB(dir->i_sb)->name_check,
+	    name,len, msdos_name,1,dotsOK);
 	if (res < 0)
 		return -ENOENT;
-	return msdos_scan(dir,msdos_name,bh,de,ino);
+	if((name[0]=='.') && dotsOK){
+	    switch(len){
+		case  0: panic("Empty name in msdos_find!");
+		case  1: scantype = SCAN_ANY;				break;
+		case  2: scantype = ((name[1]=='.')?SCAN_ANY:SCAN_HID); break;
+		default: scantype = SCAN_HID;
+	    }
+	} else {
+	    scantype = (dotsOK ? SCAN_NOTHID : SCAN_ANY);
+	}
+	return msdos_scan(dir,msdos_name,bh,de,ino,scantype);
 }
 
-
+/***** Get inode using directory and name */
 int msdos_lookup(struct inode *dir,const char *name,int len,
     struct inode **result)
 {
@@ -173,21 +199,20 @@
 }
 
 
-/* Creates a directory entry (name is already formatted). */
-
-static int msdos_create_entry(struct inode *dir,const char *name,int is_dir,
-    struct inode **result)
+/***** Creates a directory entry (name is already formatted). */
+static int msdos_create_entry(struct inode *dir, const char *name,
+    int is_dir, int is_hid, struct inode **result)
 {
 	struct super_block *sb = dir->i_sb;
 	struct buffer_head *bh;
 	struct msdos_dir_entry *de;
 	int res,ino;
 
-	if ((res = msdos_scan(dir,NULL,&bh,&de,&ino)) < 0) {
+	if ((res = msdos_scan(dir,NULL,&bh,&de,&ino,SCAN_ANY)) < 0) {
 		if (res != -ENOENT) return res;
 		if (dir->i_ino == MSDOS_ROOT_INO) return -ENOSPC;
 		if ((res = msdos_add_cluster(dir)) < 0) return res;
-		if ((res = msdos_scan(dir,NULL,&bh,&de,&ino)) < 0) return res;
+		if ((res = msdos_scan(dir,NULL,&bh,&de,&ino,SCAN_ANY)) < 0) return res;
 	}
 	/*
 	 * XXX all times should be set by caller upon successful completion.
@@ -197,6 +222,7 @@
 	memcpy(de->name,name,MSDOS_NAME);
 	memset(de->unused, 0, sizeof(de->unused));
 	de->attr = is_dir ? ATTR_DIR : ATTR_ARCH;
+	de->attr = is_hid ? (de->attr|ATTR_HIDDEN) : (de->attr&~ATTR_HIDDEN);
 	de->start = 0;
 	date_unix2dos(dir->i_mtime,&de->time,&de->date);
 	de->size = 0;
@@ -211,7 +237,7 @@
 	return 0;
 }
 
-
+/***** Create a file or directory */
 int msdos_create(struct inode *dir,const char *name,int len,int mode,
 	struct inode **result)
 {
@@ -219,22 +245,33 @@
 	struct buffer_head *bh;
 	struct msdos_dir_entry *de;
 	char msdos_name[MSDOS_NAME];
-	int ino,res;
+	int ino,res,is_hid;
 
 	if (!dir) return -ENOENT;
 	if ((res = msdos_format_name(MSDOS_SB(dir->i_sb)->name_check,name,len,
-	    msdos_name,0)) < 0) {
+	    msdos_name,0,MSDOS_SB(dir->i_sb)->dotsOK)) < 0) {
 		iput(dir);
 		return res;
 	}
+	is_hid = (name[0]=='.') && (msdos_name[0]!='.');
 	lock_creation();
-	if (msdos_scan(dir,msdos_name,&bh,&de,&ino) >= 0) {
+	/* Scan for existing file twice, so that creating a file fails
+	 * with -EINVAL if the other (dotfile/nondotfile) exists.
+	 * Else SCAN_ANY would do. Maybe use EACCES, EBUSY, ENOSPC, ENFILE?
+	 */
+	if (msdos_scan(dir,msdos_name,&bh,&de,&ino,SCAN_HID) >= 0) {
 		unlock_creation();
 		brelse(bh);
 		iput(dir);
-		return -EEXIST;
+		return is_hid ? -EEXIST : -EINVAL;
+ 	}
+	if (msdos_scan(dir,msdos_name,&bh,&de,&ino,SCAN_NOTHID) >= 0) {
+		unlock_creation();
+		brelse(bh);
+		iput(dir);
+		return is_hid ? -EINVAL : -EEXIST;
  	}
-	res = msdos_create_entry(dir,msdos_name,S_ISDIR(mode),result);
+	res = msdos_create_entry(dir,msdos_name,S_ISDIR(mode),is_hid,result);
 	unlock_creation();
 	iput(dir);
 	return res;
@@ -260,7 +297,7 @@
 
 #endif
 
-
+/***** Make a directory */
 int msdos_mkdir(struct inode *dir,const char *name,int len,int mode)
 {
 	struct super_block *sb = dir->i_sb;
@@ -268,21 +305,22 @@
 	struct msdos_dir_entry *de;
 	struct inode *inode,*dot;
 	char msdos_name[MSDOS_NAME];
-	int ino,res;
+	int ino,res,is_hid;
 
 	if ((res = msdos_format_name(MSDOS_SB(dir->i_sb)->name_check,name,len,
-	    msdos_name,0)) < 0) {
+	    msdos_name,0,MSDOS_SB(dir->i_sb)->dotsOK)) < 0) {
 		iput(dir);
 		return res;
 	}
+	is_hid = (name[0]=='.') && (msdos_name[0]!='.');
 	lock_creation();
-	if (msdos_scan(dir,msdos_name,&bh,&de,&ino) >= 0) {
+	if (msdos_scan(dir,msdos_name,&bh,&de,&ino,SCAN_ANY) >= 0) {
 		unlock_creation();
 		brelse(bh);
 		iput(dir);
 		return -EEXIST;
  	}
-	if ((res = msdos_create_entry(dir,msdos_name,1,&inode)) < 0) {
+	if ((res = msdos_create_entry(dir,msdos_name,1,is_hid,&inode)) < 0) {
 		unlock_creation();
 		iput(dir);
 		return res;
@@ -291,14 +329,14 @@
 	inode->i_nlink = 2; /* no need to mark them dirty */
 	MSDOS_I(inode)->i_busy = 1; /* prevent lookups */
 	if ((res = msdos_add_cluster(inode)) < 0) goto mkdir_error;
-	if ((res = msdos_create_entry(inode,MSDOS_DOT,1,&dot)) < 0)
+	if ((res = msdos_create_entry(inode,MSDOS_DOT,1,0,&dot)) < 0)
 		goto mkdir_error;
 	dot->i_size = inode->i_size; /* doesn't grow in the 2nd create_entry */
 	MSDOS_I(dot)->i_start = MSDOS_I(inode)->i_start;
 	dot->i_nlink = inode->i_nlink;
 	dot->i_dirt = 1;
 	iput(dot);
-	if ((res = msdos_create_entry(inode,MSDOS_DOTDOT,1,&dot)) < 0)
+	if ((res = msdos_create_entry(inode,MSDOS_DOTDOT,1,0,&dot)) < 0)
 		goto mkdir_error;
 	unlock_creation();
 	dot->i_size = dir->i_size;
@@ -318,7 +356,7 @@
 	return res;
 }
 
-
+/***** See if directory is empty */
 static int msdos_empty(struct inode *dir)
 {
 	struct super_block *sb = dir->i_sb;
@@ -344,7 +382,7 @@
 	return 0;
 }
 
-
+/***** Remove a directory */
 int msdos_rmdir(struct inode *dir,const char *name,int len)
 {
 	struct super_block *sb = dir->i_sb;
@@ -383,7 +421,7 @@
 	return res;
 }
 
-
+/***** Unlink a file */
 static int msdos_unlinkx(
 	struct inode *dir,
 	const char *name,
@@ -408,6 +446,10 @@
 		res = -EPERM;
 		goto unlink_done;
 	}
+	if (MSDOS_I(inode)->i_attrs & ATTR_SYS){
+		res = -EPERM;
+		goto unlink_done;
+	}
 	inode->i_nlink = 0;
 	inode->i_ctime = dir->i_ctime = dir->i_mtime = CURRENT_TIME;
 	MSDOS_I(inode)->i_busy = 1;
@@ -421,21 +463,22 @@
 	return res;
 }
 
+/***** Unlink, as called for msdosfs */
 int msdos_unlink(struct inode *dir,const char *name,int len)
 {
 	return msdos_unlinkx (dir,name,len,1);
 }
-/*
-	Special entry for umsdos
-*/
+
+/***** Unlink, as called for umsdosfs */
 int msdos_unlink_umsdos(struct inode *dir,const char *name,int len)
 {
 	return msdos_unlinkx (dir,name,len,0);
 }
 
+/***** Rename within a directory */
 static int rename_same_dir(struct inode *old_dir,char *old_name,
     struct inode *new_dir,char *new_name,struct buffer_head *old_bh,
-    struct msdos_dir_entry *old_de,int old_ino)
+    struct msdos_dir_entry *old_de,int old_ino,int is_hid)
 {
 	struct super_block *sb = old_dir->i_sb;
 	struct buffer_head *new_bh;
@@ -443,8 +486,8 @@
 	struct inode *new_inode,*old_inode;
 	int new_ino,exists,error;
 
-	if (!strncmp(old_name,new_name,MSDOS_NAME)) return 0;
-	exists = msdos_scan(new_dir,new_name,&new_bh,&new_de,&new_ino) >= 0;
+	if (!strncmp(old_name,new_name,MSDOS_NAME)) goto set_hid;
+	exists = msdos_scan(new_dir,new_name,&new_bh,&new_de,&new_ino,SCAN_ANY) >= 0;
 	if (*(unsigned char *) old_de->name == DELETED_FLAG) {
 		if (exists) brelse(new_bh);
 		return -ENOENT;
@@ -454,9 +497,14 @@
 			brelse(new_bh);
 			return -EIO;
 		}
-		error = S_ISDIR(new_inode->i_mode) ? (old_de->attr & ATTR_DIR) ?
-		    msdos_empty(new_inode) : -EPERM : (old_de->attr & ATTR_DIR)
-		    ? -EPERM : 0;
+		error = S_ISDIR(new_inode->i_mode)
+			? (old_de->attr & ATTR_DIR)
+				? msdos_empty(new_inode)
+				: -EPERM
+			: (old_de->attr & ATTR_DIR)
+				? -EPERM
+				: 0;
+		if (!error && (old_de->attr & ATTR_SYS)) error = -EPERM;
 		if (error) {
 			iput(new_inode);
 			brelse(new_bh);
@@ -475,19 +523,26 @@
 		brelse(new_bh);
 	}
 	memcpy(old_de->name,new_name,MSDOS_NAME);
+set_hid:
+	old_de->attr = is_hid
+		? (old_de->attr | ATTR_HIDDEN)
+		: (old_de->attr &~ ATTR_HIDDEN);
 	mark_buffer_dirty(old_bh, 1);
-	if (MSDOS_SB(old_dir->i_sb)->conversion == 'a') /* update binary info */
-		if ((old_inode = iget(old_dir->i_sb,old_ino)) != NULL) {
-			msdos_read_inode(old_inode);
-			iput(old_inode);
-		}
+	/* update binary info for conversion, i_attrs */
+	if ((old_inode = iget(old_dir->i_sb,old_ino)) != NULL) {
+		msdos_read_inode(old_inode);
+		MSDOS_I(old_inode)->i_attrs = is_hid
+			? (MSDOS_I(old_inode)->i_attrs |  ATTR_HIDDEN)
+			: (MSDOS_I(old_inode)->i_attrs &~ ATTR_HIDDEN);
+		iput(old_inode);
+	}
 	return 0;
 }
 
-
+/***** Rename across directories - a nonphysical move */
 static int rename_diff_dir(struct inode *old_dir,char *old_name,
     struct inode *new_dir,char *new_name,struct buffer_head *old_bh,
-    struct msdos_dir_entry *old_de,int old_ino)
+    struct msdos_dir_entry *old_de,int old_ino,int is_hid)
 {
 	struct super_block *sb = old_dir->i_sb;
 	struct buffer_head *new_bh,*free_bh,*dotdot_bh;
@@ -499,6 +554,7 @@
 	if (old_dir->i_dev != new_dir->i_dev) return -EINVAL;
 	if (old_ino == new_dir->i_ino) return -EINVAL;
 	if (!(walk = iget(new_dir->i_sb,new_dir->i_ino))) return -EIO;
+	/* prevent moving directory below itself */
 	while (walk->i_ino != MSDOS_ROOT_INO) {
 		ino = msdos_parent_ino(walk,1);
 		iput(walk);
@@ -507,13 +563,14 @@
 		if (!(walk = iget(new_dir->i_sb,ino))) return -EIO;
 	}
 	iput(walk);
-	while ((error = msdos_scan(new_dir,NULL,&free_bh,&free_de,&free_ino)) <
-	     0) {
+	/* find free spot */
+	while ((error = msdos_scan(new_dir,NULL,&free_bh,&free_de,&free_ino,
+	    SCAN_ANY)) < 0) {
 		if (error != -ENOENT) return error;
 		error = msdos_add_cluster(new_dir);
 		if (error) return error;
 	}
-	exists = msdos_scan(new_dir,new_name,&new_bh,&new_de,&new_ino) >= 0;
+	exists = msdos_scan(new_dir,new_name,&new_bh,&new_de,&new_ino,SCAN_ANY) >= 0;
 	if (!(old_inode = iget(old_dir->i_sb,old_ino))) {
 		brelse(free_bh);
 		if (exists) brelse(new_bh);
@@ -526,15 +583,20 @@
 		return -ENOENT;
 	}
 	new_inode = NULL; /* to make GCC happy */
-	if (exists) {
+	if (exists) {  /* Trash the old file! */
 		if (!(new_inode = iget(new_dir->i_sb,new_ino))) {
 			iput(old_inode);
 			brelse(new_bh);
 			return -EIO;
 		}
-		error = S_ISDIR(new_inode->i_mode) ? (old_de->attr & ATTR_DIR) ?
-		    msdos_empty(new_inode) : -EPERM : (old_de->attr & ATTR_DIR)
-		    ? -EPERM : 0;
+		error = S_ISDIR(new_inode->i_mode)
+			? (old_de->attr & ATTR_DIR)
+				? msdos_empty(new_inode)
+				: -EPERM
+			: (old_de->attr & ATTR_DIR)
+				? -EPERM
+				: 0;
+		if (!error && (old_de->attr & ATTR_SYS)) error = -EPERM;
 		if (error) {
 			iput(new_inode);
 			iput(old_inode);
@@ -549,6 +611,9 @@
 	}
 	memcpy(free_de,old_de,sizeof(struct msdos_dir_entry));
 	memcpy(free_de->name,new_name,MSDOS_NAME);
+	free_de->attr = is_hid
+		? (free_de->attr|ATTR_HIDDEN)
+		: (free_de->attr&~ATTR_HIDDEN);
 	if (!(free_inode = iget(new_dir->i_sb,free_ino))) {
 		free_de->name[0] = DELETED_FLAG;
 /*  Don't mark free_bh as dirty. Both states are supposed to be equivalent. */
@@ -580,7 +645,7 @@
 	}
 	if (S_ISDIR(old_inode->i_mode)) {
 		if ((error = msdos_scan(old_inode,MSDOS_DOTDOT,&dotdot_bh,
-		    &dotdot_de,&dotdot_ino)) < 0) goto rename_done;
+		    &dotdot_de,&dotdot_ino,SCAN_ANY)) < 0) goto rename_done;
 		if (!(dotdot_inode = iget(old_inode->i_sb,dotdot_ino))) {
 			brelse(dotdot_bh);
 			error = -EIO;
@@ -604,7 +669,7 @@
 	return error;
 }
 
-
+/***** Rename, a wrapper for rename_same_dir & rename_diff_dir */
 int msdos_rename(struct inode *old_dir,const char *old_name,int old_len,
 	struct inode *new_dir,const char *new_name,int new_len)
 {
@@ -613,19 +678,24 @@
 	struct buffer_head *old_bh;
 	struct msdos_dir_entry *old_de;
 	int old_ino,error;
+	int is_hid,old_hid; /* if new file and old file are hidden */
 
 	if ((error = msdos_format_name(MSDOS_SB(old_dir->i_sb)->name_check,
-	    old_name,old_len,old_msdos_name,1)) < 0) goto rename_done;
+	    old_name,old_len,old_msdos_name,1,MSDOS_SB(old_dir->i_sb)->dotsOK))
+	    < 0) goto rename_done;
 	if ((error = msdos_format_name(MSDOS_SB(new_dir->i_sb)->name_check,
-	    new_name,new_len,new_msdos_name,0)) < 0) goto rename_done;
+	    new_name,new_len,new_msdos_name,0,MSDOS_SB(new_dir->i_sb)->dotsOK))
+	    < 0) goto rename_done;
+	is_hid = (new_name[0]=='.') && (new_msdos_name[0]!='.');
+	old_hid = (old_name[0]=='.') && (old_msdos_name[0]!='.');
 	if ((error = msdos_scan(old_dir,old_msdos_name,&old_bh,&old_de,
-	    &old_ino)) < 0) goto rename_done;
+	    &old_ino,old_hid?SCAN_HID:SCAN_NOTHID)) < 0) goto rename_done;
 	lock_creation();
 	if (old_dir == new_dir)
 		error = rename_same_dir(old_dir,old_msdos_name,new_dir,
-		    new_msdos_name,old_bh,old_de,old_ino);
+		    new_msdos_name,old_bh,old_de,old_ino,is_hid);
 	else error = rename_diff_dir(old_dir,old_msdos_name,new_dir,
-		    new_msdos_name,old_bh,old_de,old_ino);
+		    new_msdos_name,old_bh,old_de,old_ino,is_hid);
 	unlock_creation();
 	brelse(old_bh);
 rename_done:

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov with Sam's (original) version
of this