[linux-cifs-client] [PATCH] mount.cifs: fix several problems when mounting subdirectories of shares

Jeff Layton jlayton at redhat.com
Tue Nov 13 14:04:33 GMT 2007


This is essentially the same patch as I posted yesterday. The only
difference is that I added the replace_char helper function and now
have the code call it instead of doing the conversion internally.

Thoughts?

-------[snip]-------

CIFS has a few problems when mounting subdirectories of shares:

a) mount.cifs assumes that the prefixpath will always begin with a
forward slash. If it begins with a backslash, then it fails to parse out
the prefixpath and leaves it appended to the sharename. This causes the
mount to fail.

b) if the prefixpath uses '/' as a delimiter, it doesn't convert that to
a "native" prefixpath ('\\' delimiter). The kernel will blindly stuff
this prefix into the beginning of a path when it builds one from a dentry,
and this confuses windows servers (samba doesn't seem to care).

c) When you mount a subdir of a share, mount.cifs munges the device string
so that you can't tell what the prefixpath is. So if I mount:

//server/share/p1/p2/p3

..then /proc/mounts and mtab will show only:

//server/share

d) If the client has to retry the mount with an uppercase sharename, it
doesn't also uppercase the prefixpath (not sure if that's a real issue,
but it seems inconsistent).

The following patch fixes all of these problems. It separates the
"share_name" from the "device_name", and passes the share_name as the
unc= string, and the device_name as the first arg to mount(), and to
setmntent().

It also changes mount.cifs to use '\\' exclusively as a delimiter for
the unc= and prefixpath= options, and to use '/' exclusively as a
delimiter in the device string (seemingly necessary since the kernel
doesn't deal well with backslashes in the device string).

Signed-off-by: Jeff Layton <jlayton at redhat.com>
---
 source/client/mount.cifs.c |   90 +++++++++++++++++++++++++++++++-------------
 1 files changed, 64 insertions(+), 26 deletions(-)

diff --git a/source/client/mount.cifs.c b/source/client/mount.cifs.c
index eb45ae5..d21b0fe 100644
--- a/source/client/mount.cifs.c
+++ b/source/client/mount.cifs.c
@@ -62,6 +62,8 @@
 #define MS_BIND 4096
 #endif
 
+#define MAX_UNC_LEN 1024
+
 #define CONST_DISCARD(type, ptr)      ((type) ((void *) (ptr)))
 
 const char *thisprogram;
@@ -73,7 +75,6 @@ static int got_ip = 0;
 static int got_unc = 0;
 static int got_uid = 0;
 static int got_gid = 0;
-static int free_share_name = 0;
 static char * user_name = NULL;
 static char * mountpassword = NULL;
 char * domain_name = NULL;
@@ -747,17 +748,27 @@ static char * check_for_domain(char **ppuser)
 	return domainnm;
 }
 
+/* replace all occurances of "from" in a string with "to" */
+static void replace_char(char *string, char from, char to)
+{
+	while (string) {
+		string = strchr(string, from);
+		if (string)
+			*string = to;
+	}
+}
+
 /* Note that caller frees the returned buffer if necessary */
 static char * parse_server(char ** punc_name)
 {
 	char * unc_name = *punc_name;
-	int length = strnlen(unc_name,1024);
+	int length = strnlen(unc_name, MAX_UNC_LEN);
 	char * share;
 	char * ipaddress_string = NULL;
 	struct hostent * host_entry = NULL;
 	struct in_addr server_ipaddr;
 
-	if(length > 1023) {
+	if(length > (MAX_UNC_LEN - 1)) {
 		printf("mount error: UNC name too long");
 		return NULL;
 	}
@@ -776,7 +787,6 @@ static char * parse_server(char ** punc_name)
 			/* check for nfs syntax ie server:share */
 			share = strchr(unc_name,':');
 			if(share) {
-				free_share_name = 1;
 				*punc_name = (char *)malloc(length+3);
 				if(*punc_name == NULL) {
 					/* put the original string back  if 
@@ -784,9 +794,9 @@ static char * parse_server(char ** punc_name)
 					*punc_name = unc_name;
 					return NULL;
 				}
-					
 				*share = '/';
 				strncpy((*punc_name)+2,unc_name,length);
+				free(unc_name);
 				unc_name = *punc_name;
 				unc_name[length+2] = 0;
 				goto continue_unc_parsing;
@@ -797,18 +807,21 @@ static char * parse_server(char ** punc_name)
 			}
 		} else {
 continue_unc_parsing:
-			unc_name[0] = '/';
-			unc_name[1] = '/';
+			unc_name[0] = '\\';
+			unc_name[1] = '\\';
 			unc_name += 2;
-			if ((share = strchr(unc_name, '/')) || 
-				(share = strchr(unc_name,'\\'))) {
+
+			/* convert any '/' in unc to '\\' */
+			replace_char(unc_name, '/', '\\');
+
+			if ((share = strchr(unc_name,'\\'))) {
 				*share = 0;  /* temporarily terminate the string */
 				share += 1;
 				if(got_ip == 0) {
 					host_entry = gethostbyname(unc_name);
 				}
-				*(share - 1) = '/'; /* put the slash back */
-				if ((prefixpath = strchr(share, '/'))) {
+				*(share - 1) = '\\'; /* put delimiter back */
+				if ((prefixpath = strchr(share, '\\'))) {
 					*prefixpath = 0;  /* permanently terminate the string */
 					if (!strlen(++prefixpath))
 						prefixpath = NULL; /* this needs to be done explicitly */
@@ -873,6 +886,25 @@ static struct option longopts[] = {
 	{ NULL, 0, NULL, 0 }
 };
 
+/* convert a string to uppercase. return false if the string
+ * wasn't ASCII or was a NULL ptr */
+static int
+uppercase_string(char *string)
+{
+	if (!string)
+		return 0;
+
+	while (*string) {
+		/* check for unicode */
+		if ((unsigned char) string[0] & 0x80)
+			return 0;
+		*string = toupper((unsigned char) *string);
+		string++;
+	}
+
+	return 1;
+}
+
 int main(int argc, char ** argv)
 {
 	int c;
@@ -885,6 +917,7 @@ int main(int argc, char ** argv)
 	char * options = NULL;
 	char * resolved_path = NULL;
 	char * temp;
+	char * dev_name;
 	int rc;
 	int rsize = 0;
 	int wsize = 0;
@@ -920,8 +953,16 @@ int main(int argc, char ** argv)
 	printf(" node: %s machine: %s sysname %s domain %s\n", sysinfo.nodename,sysinfo.machine,sysinfo.sysname,sysinfo.domainname);
 #endif */
 	if(argc > 2) {
-		share_name = argv[1];
+		dev_name = argv[1];
+		share_name = strndup(argv[1], MAX_UNC_LEN);
+		if (share_name == NULL) {
+			fprintf(stderr, "%s: %s", argv[0], strerror(ENOMEM));
+			exit(1);
+		}
 		mountpoint = argv[2];
+	} else {
+		mount_cifs_usage();
+		exit(1);
 	}
 
 	/* add sharename in opts string as unc= parm */
@@ -1061,7 +1102,7 @@ int main(int argc, char ** argv)
 		}
 	}
 
-	if((argc < 3) || (share_name == NULL) || (mountpoint == NULL)) {
+	if((argc < 3) || (dev_name == NULL) || (mountpoint == NULL)) {
 		mount_cifs_usage();
 		exit(1);
 	}
@@ -1219,10 +1260,12 @@ mount_retry:
 	}	
 	if(verboseflag)
 		printf("\nmount.cifs kernel mount options %s \n",options);
-	if(mount(share_name, mountpoint, "cifs", flags, options)) {
-	/* remember to kill daemon on error */
-		char * tmp;
 
+	/* convert all '\\' to '/' so that /proc/mounts looks pretty */
+	replace_char(dev_name, '\\', '/');
+
+	if(mount(dev_name, mountpoint, "cifs", flags, options)) {
+	/* remember to kill daemon on error */
 		switch (errno) {
 		case 0:
 			printf("mount failed but no error number set\n");
@@ -1233,12 +1276,9 @@ mount_retry:
 		case ENXIO:
 			if(retry == 0) {
 				retry = 1;
-				tmp = share_name;
-				while (*tmp && !(((unsigned char)tmp[0]) & 0x80)) {
-					*tmp = toupper((unsigned char)*tmp);
-		        		tmp++;
-				}
-				if(!*tmp) {
+				if (uppercase_string(dev_name) &&
+				    uppercase_string(share_name) &&
+				    uppercase_string(prefixpath)) {
 					printf("retrying with upper case share name\n");
 					goto mount_retry;
 				}
@@ -1252,7 +1292,7 @@ mount_retry:
 	} else {
 		pmntfile = setmntent(MOUNTED, "a+");
 		if(pmntfile) {
-			mountent.mnt_fsname = share_name;
+			mountent.mnt_fsname = dev_name;
 			mountent.mnt_dir = mountpoint; 
 			mountent.mnt_type = CONST_DISCARD(char *,"cifs"); 
 			mountent.mnt_opts = (char *)malloc(220);
@@ -1312,9 +1352,7 @@ mount_exit:
 		free(resolved_path);
 	}
 
-	if(free_share_name) {
-		free(share_name);
-		}
+	free(share_name);
 	return rc;
 }
 
-- 
1.5.3.3



More information about the linux-cifs-client mailing list