Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 13 Aug 2003 14:03:53 +0200 (CEST)
From:      Simon Barner <barner@in.tum.de>
To:        FreeBSD-gnats-submit@FreeBSD.org
Subject:   bin/55539: [patch] Parse fstab(5) with spaces in path names
Message-ID:  <20030813120353.F39FE38CF4@zi025.glhnet.mhn.de>
Resent-Message-ID: <200308131210.h7DCAFPh074651@freefall.freebsd.org>

next in thread | raw e-mail | index | archive | help

>Number:         55539
>Category:       bin
>Synopsis:       [patch] Parse fstab(5) with spaces in path names
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    freebsd-bugs
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:
>Class:          change-request
>Submitter-Id:   current-users
>Arrival-Date:   Wed Aug 13 05:10:15 PDT 2003
>Closed-Date:
>Last-Modified:
>Originator:     Simon Barner
>Release:        FreeBSD 4.8-STABLE i386
>Organization:
>Environment:
System: FreeBSD zi025.glhnet.mhn.de 4.8-STABLE FreeBSD 4.8-STABLE #0: Thu Aug 7 04:04:01 CEST 2003 toor@zi025.glhnet.mhn.de:/usr/src/sys/compile/KISTE i386

>Description:

At the present, it is impossible to specify file systems and/or mount
points that contain white spaces in fstab(5).

The attached patch extends the getfsent(3) function in order to handle
both escaped and quoted white space correctly.

It also updates the fstab(5) man page (please review it thoroughly since
English is not my mother tongue - thanks).

Example for a valid mount point:

/a\ rather\ long/and/complicated\ path
"/a rather long/and/complicated path"

You can find a discussion on freebsd-hackers@ about this issue here:

http://freebsd.rambler.ru/bsdmail/freebsd-hackers_2003/msg04083.html

This problem report superseedes misc/37569, which was filed about one
year ago, and which implements the double quote variant only (the
originator of that PR mentioned in the above thread that it would be OK to
close his PR, if _some_ solution hit the CVS tree.

>How-To-Repeat:

You can use this tiny program to test your getfsent(3) implementation.
With the current one, fstab-lines with white space will raise an error.

#include <fstab.h>
#include <stdio.h>

int main (int argc, char *argv[]) {
        struct fstab *f;
        
        while (0 != (f = getfsent ())) {
                printf ("\"%s\" \"%s\" \"%s\" %d %d\n", f->fs_spec, f->fs_file,
                f->fs_vfstype, f->fs_freq, f->fs_passno);
        }
        
        return 0;
}

>Fix:
This patch to src/lib/libc/gen/fstab.c employes the extentions described
above. It applies to both -stable and -current. One hunk of the patch to
src/share/man/man5/fstab.5 will fail, since the man page does not
contain a list of valid file system types any more (I added smbfs to
that list).

--- lib/libc/gen/fstab.c.orig	Fri Aug  1 17:18:00 2003
+++ lib/libc/gen/fstab.c	Thu Aug  7 15:46:39 2003
@@ -84,6 +84,60 @@
 	_fs_fstab.fs_spec = buf;
 }
 
+/*
+ * Gets a token from a string *s, that is either empty or is separated by
+ * a set of delimiters *delim.
+ * Characters that are in *delim, can occur in the token if the are escaped,
+ * i.e. have a '\' prepended. The character '\' itself is encoded as '\\'.
+ * *s can have a trailing comment (indicated by a '#'), which will cause the
+ * characters after the '#' to be ignored. To encode a '#' within a token,
+ * use '\#'.
+ *
+ * If a token is found, gtok sets the last character after its end
+ * to '\0' and returns a pointer it. Otherwise the return value is NULL.
+ * As a side effect, the input string *s modified and points to the next
+ * character after the end of the current token, i.e. after the '\0'.
+ */
+char *gtok(char **s, char const *delim)
+{
+	int quoted, escaped;
+	static char const esc_set[] = {  't',  'r',  'n',  'a', 0 };
+	static char const esc_rep[] = { '\t', '\r', '\n', '\a', 0 };
+	char *tok, *r, *w, *p;
+
+	if (!s || !*s || !*(tok = *s + strspn(*s, delim)) || *tok == '#')
+		return NULL;
+
+	for (quoted = escaped = 0, r = w = tok; *r; r++) {
+		if (!escaped) {
+			if (*r == '\\') {
+				escaped = 1;
+				continue;
+			}
+			if (*r == '\"') {
+				quoted ^= -1;
+				continue;
+			}
+			if (!quoted) {
+				if (strchr(delim, *r)) {
+					r++;
+					break;
+				}
+			}
+		} else {
+			escaped = 0;
+			if ((p = strchr(esc_set, *r)) != NULL) {
+				*w++ = esc_rep[p - esc_set];
+				continue;
+			}
+		}
+		*w++ = *r;
+	}
+	*w = 0;
+	*s = r;
+	return tok;
+}
+
 static int
 fstabscan()
 {
@@ -91,21 +145,73 @@
 #define	MAXLINELENGTH	1024
 	static char line[MAXLINELENGTH];
 	char subline[MAXLINELENGTH];
-	int typexx;
+	int typexx, escaped=0, quoted=0, ws_sep=0;
 
 	for (;;) {
 
 		if (!(p = fgets(line, sizeof(line), _fs_fp)))
 			return(0);
-/* OLD_STYLE_FSTAB */
 		++LineNo;
-		if (*line == '#' || *line == '\n')
-			continue;
-		if (!strpbrk(p, " \t")) {
-			_fs_fstab.fs_spec = strsep(&p, ":\n");
-			_fs_fstab.fs_file = strsep(&p, ":\n");
+		
+		/* Detect whether line is in old or new fstab style */
+		for (cp=p; *cp != '\n'; ++cp) {
+			if (*cp == '\\') {
+			    escaped = (escaped ? 0 : 1);
+			    continue;
+			}
+			if (!escaped) {
+			    /* Quotes */
+			    if (*cp == '\"') {
+			    	quoted = (quoted ? 0 : 1);
+				continue;
+			    }
+			    if (quoted)
+			    	continue;
+			    /* new white separator found */
+			    if (cp > p && strspn (cp, " \n\r\t") &&
+ 				!strspn(cp-1, " \t"))
+				++ws_sep;
+			    
+			    /* #-comment found */
+			    if (*cp == '#') {
+			    	*cp = '\0';
+				/* ignore white spaces in front of a comment */
+				if (cp > p && strspn(cp-1, " \t") && 
+				    ws_sep > 0)
+				    ws_sep--;
+				    break;
+			    }
+			} else
+			    escaped = 0;
+		}
+		/* open quotes and unfinished escape-sequences are bad */
+		if (quoted || escaped)
+		    goto bad;
+		/* ignore trailing white spaces */
+	        if (*(cp + strspn (cp, " \t")) == '\n' && ws_sep > 0)
+		    --ws_sep;
+		   
+		/* No white space separators found => OLD_STYLE_FSTAB */
+		if (ws_sep == 0) {
+			/*
+			 * line consists only of white spaces
+			 * (evtl. + #-comment)
+			 */
+			if (strspn (p, " \t"))
+				continue;
+			/*
+			 * Now read the different values (gtok will convert
+			 * escape seq.). Format is:
+			 *  <fs_spec>:<fs_file>:<fs_type>:<freq>:<passno>
+			 * ':' itself can be encodes as '\:'
+			 */
+			if (!(_fs_fstab.fs_spec = gtok(&p, ":\n\r")))
+				continue;
+			if (!(_fs_fstab.fs_file = gtok(&p, ":\n\r"))) {
+				goto bad;
+			}
 			fixfsfile();
-			_fs_fstab.fs_type = strsep(&p, ":\n");
+			_fs_fstab.fs_type = gtok(&p, ":\n\r");
 			if (_fs_fstab.fs_type) {
 				if (!strcmp(_fs_fstab.fs_type, FSTAB_XX))
 					continue;
@@ -113,46 +219,43 @@
 				_fs_fstab.fs_vfstype =
 				    strcmp(_fs_fstab.fs_type, FSTAB_SW) ?
 				    "ufs" : "swap";
-				if ((cp = strsep(&p, ":\n")) != NULL) {
+				if ((cp = gtok(&p, ":\n\r")) != NULL) {
 					_fs_fstab.fs_freq = atoi(cp);
-					if ((cp = strsep(&p, ":\n")) != NULL) {
+					if ((cp = gtok(&p, " \n\r\t")) != NULL) {
 						_fs_fstab.fs_passno = atoi(cp);
+						if (gtok (&p, " \n\r\t"))
+						    goto bad;
+						    
 						return(1);
 					}
 				}
 			}
 			goto bad;
 		}
-/* OLD_STYLE_FSTAB */
-		while ((cp = strsep(&p, " \t\n")) != NULL && *cp == '\0')
-			;
-		_fs_fstab.fs_spec = cp;
-		if (!_fs_fstab.fs_spec || *_fs_fstab.fs_spec == '#')
+		
+		/* At least one white space sep. found => NEW_STYLE_FSTAB */
+		if (!(_fs_fstab.fs_spec = gtok(&p, " \n\r\t")))
 			continue;
-		while ((cp = strsep(&p, " \t\n")) != NULL && *cp == '\0')
-			;
-		_fs_fstab.fs_file = cp;
+		if (!(_fs_fstab.fs_file = gtok(&p, " \n\r\t")))
+			goto bad;
 		fixfsfile();
-		while ((cp = strsep(&p, " \t\n")) != NULL && *cp == '\0')
-			;
-		_fs_fstab.fs_vfstype = cp;
-		while ((cp = strsep(&p, " \t\n")) != NULL && *cp == '\0')
-			;
-		_fs_fstab.fs_mntops = cp;
-		if (_fs_fstab.fs_mntops == NULL)
+		if (!(_fs_fstab.fs_vfstype = gtok(&p, " \n\r\t")))
+			goto bad;
+		if (!(_fs_fstab.fs_mntops = gtok(&p, " \n\r\t")))
 			goto bad;
 		_fs_fstab.fs_freq = 0;
 		_fs_fstab.fs_passno = 0;
-		while ((cp = strsep(&p, " \t\n")) != NULL && *cp == '\0')
-			;
+		cp = gtok(&p, " \n\r\t");
 		if (cp != NULL) {
 			_fs_fstab.fs_freq = atoi(cp);
-			while ((cp = strsep(&p, " \t\n")) != NULL && *cp == '\0')
-				;
+			cp = gtok(&p, " \n\r\t");
 			if (cp != NULL)
 				_fs_fstab.fs_passno = atoi(cp);
 		}
 		strcpy(subline, _fs_fstab.fs_mntops);
+		if (gtok (&p, " \n\r\t"))
+		    goto bad;
+
 		p = subline;
 		for (typexx = 0, cp = strsep(&p, ","); cp;
 		     cp = strsep(&p, ",")) {
--- share/man/man5/fstab.5.orig	Tue Aug 12 22:55:10 2003
+++ share/man/man5/fstab.5	Wed Aug 13 13:01:28 2003
@@ -79,6 +79,33 @@
 describes the mount point for the filesystem.
 For swap partitions, this field should be specified as ``none''.
 .Pp
+Both the
+.Fa fs_spec
+and the
+.Fa fs_file
+field may contain white spaces, with must be protected in a shell-like
+manner. In detail, this means that white spaces (especially blanks and
+tabs) can be encoded in the following ways:
+.Bl -tag -width indent -offset indent
+.It Em escape sequences
+White spaces preceded with a ``\\'' are handled as a part of the path
+specification and not as a delimiter. The ``\\'' character itself can
+be written as ``\\\\''.
+.Pp
+Example: ``/example\\ path''
+.It Em double quotes
+Another possibility is to enclose the path specification in double
+quotes ``"''.
+.Pp
+Example: ``"/example path"''
+.El
+.Pp
+It should be noted, that system administrators should avoid white spaces
+in file system and mount point specifications in order to keep things
+simple and clean. The ability to handle such path names is only indented to
+provide compatibility with systems, where white spaces in path names are
+common (mostly SMB/CIFS shares).
+.Pp
 The third field,
 .Pq Fa fs_vfstype ,
 describes the type of the filesystem.
@@ -109,6 +136,8 @@
 .\" maybe also say Rock Ridge extensions are handled ?
 .It Em procfs
 a file system for accessing process data
+.It Em smbfs
+SMB/CIFS compatible network shares
 .El
 .Pp
 The fourth field,
>Release-Note:
>Audit-Trail:
>Unformatted:



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20030813120353.F39FE38CF4>