Subject: kern/1781: 'magic' symbolic link expansion
To: None <gnats-bugs@gnats.netbsd.org>
From: Chris G. Demetriou <cgd@NetBSD.ORG>
List: netbsd-bugs
Date: 11/22/1995 20:30:00
>Number:         1781
>Category:       kern
>Synopsis:       system-specific special expansions in symlinks.
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    kern-bug-people (Kernel Bug People)
>State:          open
>Class:          change-request
>Submitter-Id:   net
>Arrival-Date:   Wed Nov 22 20:50:08 1995
>Last-Modified:
>Originator:     Chris G. Demetriou
>Organization:
Kernel Hackers 'r' Us
>Release:        NetBSD-current (trunk), 11/22/95
>Environment:
System: NetBSD sun-lamp.pc.cs.cmu.edu 1.0A NetBSD 1.0A (SUN_LAMP) #9: Wed Nov 22 16:05:06 EST 1995 cgd@sun-lamp.pc.cs.cmu.edu:/usr/src/sys/arch/i386/compile/SUN_LAMP i386


>Description:
	Expansion of 'special' strings in symlinks into system-specific
	values is a very useful feature in some situations, like:

		(1) when booting diskless clients (to help find various
		    configuration files, etc., when sharing the same
		    root partition), and

		(2) when trying to mount root from a CD-ROM that
		    contains binaries for several architectures.

	NetBSD currently has no such facility, and, because of the
	problems with doing multiple-architecture CD-ROMs, it arguably
	should.  I've implemented solution that enables "magic"
	symlink expansion on a per-file system basis.  They are enabled
	by mounting a file system with the 'magiclinks' mount option,
	which sets the MNT_MAGICLINKS mount flag.

	On file systems with the MNT_MAGICLINKS flag set, the name
	lookup code replaces the "magic" strings in symlinks with
	the appropriate replacement text.  Magic strings are
	of the form "@name", and must end at the end of a pathname
	component.  That is, assuming "@foo" is a valid magic
	string, symlinks containing:
		"@foo"			would be matched
		"@foo/bar"		would be matched
		"bar@foo"		would be matched
		"@foobar"		would not be matched
	etc.  The entire "@name" string is replaced by the
	replacement text.  If, for some reason the replacement
	text is empty, the string will be replaced by an
	empty string.  This can lead to strange results
	if the magic string is the only thing in the symlink,
	but they're the same as you get when using a sylink
	that points to "" (i.e. which is empty).

	To get this behaviour for the root file system at
	'mountroot' time, the ROOTFS_MAGICLINKS kernel option
	is provided.  If used, the root file system will be
	mounted with MNT_MAGICLINKS set.

	The "magic strings" that are supported by my implementation
	(the diff for which is included below) are:

		@machine_arch		value of MACHINE_ARCH for the system.

		@machine		value of MACHINE for the system.

		@hostname		the system hostname, as set
					with sethostname().

		@osrelease		the release number of the OS
					as defined in /sys/conf/newvers.sh
					(e.g. "1.1" for NetBSD 1.1)....

		@kernel_ident		the kernel config file name
					as defined in /sys/conf/newvers.sh
					(e.g. "GENERIC" for the kernel
					compiles from a kernel config named
					"GENERIC")

		@domainname		the system domainname, as set
					with setdomainname().

		@ostype			the name of the OS.  For NetBSD,
					this is "NetBSD".  I've included
					this so that if other operating
					systems use this code, they can
					fill in their own OS name.
					This could, for instance,
					make alternately booting NetBSD
					and FreeBSD on a single disk
					easier.

	As noted, if this is picked up for NetBSD, it'd be nice to see
	other OS's use the same magic strings and semantics, if not
	the same implementation, if they desire similar functionality.

>How-To-Repeat:
	[ Not applicable. ]

>Fix:
	Apply the following patch.  (NOTE: if you don't have
	the patch in pr 1776 applied to your /bin/sh, it
	will do bad things when traversing 'magic' symlinks.)

	The files changed by the patch are:

	sys/sys/mount.h
		add MNT_MAGICLINKS flag.

	sbin/mount/mntopts.h
	sbin/mount/mount.c
		add a mapping between the 'magiclinks' mount
		option and the MNT_MAGICLINKS mount flag.

	sys/kern/vfs_lookup.c
		do magic link substitution for symlinks on file
		systems that have MNT_MAGICLINKS set.

	sys/kern/vfs_syscalls.c
		Recognize the MNT_MAGICLINKS options, and
		allow it to be used.

	sys/kern/init_main.c
		implement ROOTFS_MAGICLINKS.

	sys/conf/newvers.sh
		add 'kernel_ident' variable, to support
		@kernel_ident.

	bin/ln/symlink.7
	lib/libc/sys/mount.2
	sbin/mount/mount.8
		documentation.

Index: bin/ln/symlink.7
===================================================================
RCS file: /a/cvsroot/src/bin/ln/symlink.7,v
retrieving revision 1.3
diff -c -r1.3 symlink.7
*** symlink.7	1995/03/21 09:06:13	1.3
--- symlink.7	1995/11/23 01:04:57
***************
*** 412,417 ****
--- 412,464 ----
  or
  .Fl P
  options.
+ .Sh MAGIC SYMLINKS
+ Symlinks in file systems with the
+ .Li MNT_MAGICLINKS
+ flag set have
+ .Dq magic
+ patterns in symlinks expanded.  Those patterns begin with
+ .Dq @
+ (an at-sign), and end at the end of the pathname component
+ (i.e. at the next
+ .Dq / ,
+ or at the end of the symbolic link if there are no more slashes).
+ The following patterns are supported:
+ .Bl -tag -width @machine_arch
+ .It @domainname
+ Expands to the machine's domain name, as set by
+ .Xr setdomainname 3 .
+ .It @hostname
+ Expands to the machine's host name, as set by
+ .Xr sethostname 3 .
+ .It @kernel_ident
+ Expands to the name of the
+ .Xr config 8
+ file used to generate the running kernel.
+ .It @machine
+ Expands to the value of
+ .Li MACHINE
+ for the system (also, the same as 
+ .Xr make 1 's
+ .Li ${MACHINE}
+ variable).
+ .It @machine_arch
+ Expands to the value of
+ .Li MACHINE_ARCH
+ for the system (also, the same as 
+ .Xr make 1 's
+ .Li ${MACHINE_ARCH}
+ variable).
+ .It @osrelease
+ Expands to the operating system release of the running kernel.
+ .It @ostype
+ Expands to the operating system type of the running kernel.
+ (This will always be
+ .Dq NetBSD
+ for
+ NetBSD
+ systems.)
+ .El
  .Sh SEE ALSO
  .Xr chflags 1 ,
  .Xr chgrp 1 ,
***************
*** 426,431 ****
--- 473,479 ----
  .Xr rm 1 ,
  .Xr tar 1 ,
  .Xr lstat 2 ,
+ .Xr mount 2 ,
  .Xr readlink 2 ,
  .Xr rename 2 ,
  .Xr unlink 2 ,
Index: lib/libc/sys/mount.2
===================================================================
RCS file: /a/cvsroot/src/lib/libc/sys/mount.2,v
retrieving revision 1.9
diff -c -r1.9 mount.2
*** mount.2	1995/10/12 15:41:07	1.9
--- mount.2	1995/11/23 01:04:58
***************
*** 78,83 ****
--- 78,89 ----
  may be specified to
  suppress default semantics which affect file system access.
  .Bl -tag -width MNT_SYNCHRONOUS
+ .It Dv MNT_MACIGLINKS
+ Expand special strings (beginning with
+ .Dq @ )
+ when traversing symbolic links.  See
+ .Xr symlink 7
+ for a list of supported strings.
  .It Dv MNT_RDONLY
  The file system should be treated as read-only;
  Even the super-user may not write on it.
Index: sbin/mount/mntopts.h
===================================================================
RCS file: /a/cvsroot/src/sbin/mount/mntopts.h,v
retrieving revision 1.3
diff -c -r1.3 mntopts.h
*** mntopts.h	1995/03/18 14:56:59	1.3
--- mntopts.h	1995/11/23 01:04:58
***************
*** 43,48 ****
--- 43,49 ----
  
  /* User-visible MNT_ flags. */
  #define MOPT_ASYNC		{ "async",	0, MNT_ASYNC }
+ #define MOPT_MAGICLINKS		{ "magiclinks", 0, MNT_MAGICLINKS }
  #define MOPT_NODEV		{ "dev",	1, MNT_NODEV }
  #define MOPT_NOEXEC		{ "exec",	1, MNT_NOEXEC }
  #define MOPT_NOSUID		{ "suid",	1, MNT_NOSUID }
***************
*** 74,79 ****
--- 75,81 ----
  	MOPT_USERQUOTA,							\
  	MOPT_GROUPQUOTA,						\
  	MOPT_FSTAB_COMPAT,						\
+ 	MOPT_MAGICLINKS,						\
  	MOPT_NODEV,							\
  	MOPT_NOEXEC,							\
  	MOPT_NOSUID,							\
Index: sbin/mount/mount.8
===================================================================
RCS file: /a/cvsroot/src/sbin/mount/mount.8,v
retrieving revision 1.11
diff -c -r1.11 mount.8
*** mount.8	1995/07/12 06:23:21	1.11
--- mount.8	1995/11/23 01:04:58
***************
*** 128,133 ****
--- 128,139 ----
  .Fl f ;
  forces the revocation of write access when trying to downgrade
  a filesystem mount status from read-write to read-only.
+ .It magiclinks
+ Expand special strings (beginning with
+ .Dq @ )
+ when traversing symbolic links.  See
+ .Xr symlink 7
+ for a list of supported strings.
  .It nodev
  Do not interpret character or block special devices on the file system.
  This option is useful for a server that has file systems containing
Index: sbin/mount/mount.c
===================================================================
RCS file: /a/cvsroot/src/sbin/mount/mount.c,v
retrieving revision 1.24
diff -c -r1.24 mount.c
*** mount.c	1995/11/18 03:34:29	1.24
--- mount.c	1995/11/23 01:04:58
***************
*** 90,95 ****
--- 90,96 ----
  	{ MNT_EXPORTANON,	1,	"anon uid mapping" },
  	{ MNT_EXRDONLY,		1,	"exported read-only" },
  	{ MNT_LOCAL,		0,	"local" },
+ 	{ MNT_MAGICLINKS,	0,	"magiclinks" },
  	{ MNT_NODEV,		0,	"nodev" },
  	{ MNT_NOEXEC,		0,	"noexec" },
  	{ MNT_NOSUID,		0,	"nosuid" },
Index: sys/conf/newvers.sh
===================================================================
RCS file: /a/cvsroot/src/sys/conf/newvers.sh,v
retrieving revision 1.17
diff -c -r1.17 newvers.sh
*** newvers.sh	1994/07/10 22:26:35	1.17
--- newvers.sh	1995/11/23 01:05:00
***************
*** 54,58 ****
--- 54,59 ----
    "char version[] = \
      \"${ost} ${osr} (${id}) #${v}: ${t}\\n    ${u}@${h}:${d}\\n\";" \
    >> vers.c
+ echo "char kernel_ident[] = \"${id}\";" >> vers.c
  
  echo `expr ${v} + 1` > version
Index: sys/kern/init_main.c
===================================================================
RCS file: /a/cvsroot/src/sys/kern/init_main.c,v
retrieving revision 1.78
diff -c -r1.78 init_main.c
*** init_main.c	1995/10/07 06:28:05	1.78
--- init_main.c	1995/11/23 01:05:00
***************
*** 296,301 ****
--- 296,304 ----
  	if ((*mountroot)())
  		panic("cannot mount root");
  	mountlist.cqh_first->mnt_flag |= MNT_ROOTFS;
+ #ifdef ROOTFS_MAGICLINKS
+ 	mountlist.cqh_first->mnt_flag |= MNT_MAGICLINKS;
+ #endif
  	mountlist.cqh_first->mnt_op->vfs_refcount++;
  
  	/* Get the vnode for '/'.  Set fdp->fd_fd.fd_cdir to reference it. */
Index: sys/kern/vfs_lookup.c
===================================================================
RCS file: /a/cvsroot/src/sys/kern/vfs_lookup.c,v
retrieving revision 1.15
diff -c -r1.15 vfs_lookup.c
*** vfs_lookup.c	1995/03/08 01:20:50	1.15
--- vfs_lookup.c	1995/11/23 01:05:00
***************
*** 42,47 ****
--- 42,48 ----
  
  #include <sys/param.h>
  #include <sys/systm.h>
+ #include <sys/kernel.h>
  #include <sys/syslimits.h>
  #include <sys/time.h>
  #include <sys/namei.h>
***************
*** 56,61 ****
--- 57,64 ----
  #include <sys/ktrace.h>
  #endif
  
+ int	symlink_magic __P((char *cp, int *len));
+ 
  /*
   * Convert a pathname into a pointer to a locked inode.
   *
***************
*** 184,190 ****
  			break;
  		}
  		linklen = MAXPATHLEN - auio.uio_resid;
! 		if (linklen + ndp->ni_pathlen >= MAXPATHLEN) {
  			if (ndp->ni_pathlen > 1)
  				free(cp, M_NAMEI);
  			error = ENAMETOOLONG;
--- 187,199 ----
  			break;
  		}
  		linklen = MAXPATHLEN - auio.uio_resid;
! 		/*
! 		 * Do symlink substitution, if appropriate, and
! 		 * check length for potential overflow.
! 		 */
! 		if ((ndp->ni_vp->v_mount->mnt_flag & MNT_MAGICLINKS
! 		      && symlink_magic(cp, &linklen)) ||
! 		    (linklen + ndp->ni_pathlen >= MAXPATHLEN)) {
  			if (ndp->ni_pathlen > 1)
  				free(cp, M_NAMEI);
  			error = ENAMETOOLONG;
***************
*** 205,210 ****
--- 214,307 ----
  	vput(ndp->ni_vp);
  	ndp->ni_vp = NULL;
  	return (error);
+ }
+ 
+ /*
+  * Substitute replacement text for 'magic' strings in symlinks.
+  * Returns 0 if successful, and returns non-zero if an error
+  * occurs.  (Currently, the only possible error is running
+  * out of temporary pathname space.)
+  *
+  * Looks for "@<string>" and "@<string>/", where <string> is a
+  * recognized 'magic' string.  Replaces the "@<string>" with
+  * the appropriate replacement text.  (Note that in some
+  * cases the replacement text may have zero length.)
+  *
+  * This would have been table driven, but the variance in
+  * replacement strings (& replacement string lengths) made
+  * that impractical.
+  */
+ 
+ #define MATCH(str)						\
+ 		((i + (sizeof(str) - 1) == *len) ||		\
+ 		    ((i + (sizeof(str) - 1) < *len) &&		\
+ 		      (cp[i + sizeof(str) - 1] == '/'))) &&	\
+ 		!strncmp((str), &cp[i], sizeof(str) - 1)
+ 
+ #define SUBSTITUTE(m, s, sl)					\
+ 		if ((newlen + (sl)) > MAXPATHLEN)		\
+ 			return (1);				\
+ 		i += sizeof(m) - 1;				\
+ 		bcopy((s), &tmp[newlen], (sl));			\
+ 		newlen += (sl);					\
+ 		change = 1;
+ 
+ int
+ symlink_magic(cp, len)
+ 	char *cp;
+ 	int *len;
+ {
+ 	char tmp[MAXPATHLEN];
+ 	int change, i, newlen;
+ 
+ 	for (change = i = newlen = 0; i < *len; ) {
+ 		if (cp[i] != '@')
+ 			tmp[newlen++] = cp[i++];
+ 		else {
+ 			i++;
+ 			/*
+ 			 * The following checks should be ordered
+ 			 * according to frequency of use.
+ 			 */
+ 			if (MATCH("machine_arch")) {
+ 				SUBSTITUTE("machine_arch", MACHINE_ARCH,
+ 				    sizeof(MACHINE_ARCH) - 1);
+ 			} else if (MATCH("machine")) {
+ 				SUBSTITUTE("machine", MACHINE,
+ 				    sizeof(MACHINE) - 1);
+ 			} else if (MATCH("hostname")) {
+ 				SUBSTITUTE("hostname", hostname,
+ 				    hostnamelen);
+ 			} else if (MATCH("osrelease")) {
+ 				extern char osrelease[];
+ 
+ 				SUBSTITUTE("osrelease", osrelease,
+ 				    strlen(osrelease));
+ 			} else if (MATCH("kernel_ident")) {
+ 				extern char kernel_ident[];
+ 
+ 				SUBSTITUTE("kernel_ident", kernel_ident,
+ 				    strlen(kernel_ident));
+ 			} else if (MATCH("domainname")) {
+ 				SUBSTITUTE("domainname", domainname,
+ 				    domainnamelen);
+ 			} else if (MATCH("ostype")) {
+ 				extern char ostype[];
+ 
+ 				SUBSTITUTE("ostype", ostype,
+ 				    strlen(ostype));
+ 			} else
+ 				tmp[newlen++] = '@';
+ 		}
+ 	}
+ 
+ 	if (!change)
+ 		return (0);
+ 
+ 	bcopy(tmp, cp, newlen);
+ 	*len = newlen;
+ 
+ 	return (0);
  }
  
  /*
Index: sys/kern/vfs_syscalls.c
===================================================================
RCS file: /a/cvsroot/src/sys/kern/vfs_syscalls.c,v
retrieving revision 1.59
diff -c -r1.59 vfs_syscalls.c
*** vfs_syscalls.c	1995/11/11 22:00:18	1.59
--- vfs_syscalls.c	1995/11/23 01:05:02
***************
*** 228,236 ****
  	else if (mp->mnt_flag & MNT_RDONLY)
  		mp->mnt_flag |= MNT_WANTRDWR;
  	mp->mnt_flag &=~ (MNT_NOSUID | MNT_NOEXEC | MNT_NODEV |
! 	    MNT_SYNCHRONOUS | MNT_UNION | MNT_ASYNC);
  	mp->mnt_flag |= SCARG(uap, flags) & (MNT_NOSUID | MNT_NOEXEC |
! 	    MNT_NODEV | MNT_SYNCHRONOUS | MNT_UNION | MNT_ASYNC);
  	/*
  	 * Mount the filesystem.
  	 */
--- 228,237 ----
  	else if (mp->mnt_flag & MNT_RDONLY)
  		mp->mnt_flag |= MNT_WANTRDWR;
  	mp->mnt_flag &=~ (MNT_NOSUID | MNT_NOEXEC | MNT_NODEV |
! 	    MNT_SYNCHRONOUS | MNT_UNION | MNT_ASYNC | MNT_MAGICLINKS);
  	mp->mnt_flag |= SCARG(uap, flags) & (MNT_NOSUID | MNT_NOEXEC |
! 	    MNT_NODEV | MNT_SYNCHRONOUS | MNT_UNION | MNT_ASYNC |
! 	    MNT_MAGICLINKS);
  	/*
  	 * Mount the filesystem.
  	 */
Index: sys/sys/mount.h
===================================================================
RCS file: /a/cvsroot/src/sys/sys/mount.h,v
retrieving revision 1.44
diff -c -r1.44 mount.h
*** mount.h	1995/11/11 22:00:21	1.44
--- mount.h	1995/11/23 01:05:03
***************
*** 119,125 ****
  };
  
  /*
!  * Mount flags.
   *
   * Unmount uses MNT_FORCE flag.
   */
--- 119,125 ----
  };
  
  /*
!  * Mount flags: FLAGS MAY BE OUT OF ORDER, for backward compatibility.
   *
   * Unmount uses MNT_FORCE flag.
   */
***************
*** 130,135 ****
--- 130,136 ----
  #define	MNT_NODEV	0x00000010	/* don't interpret special files */
  #define	MNT_UNION	0x00000020	/* union with underlying filesystem */
  #define	MNT_ASYNC	0x00000040	/* file system written asynchronously */
+ #define	MNT_MAGICLINKS	0x00008000	/* interpret symlinks for magic names */
  
  /*
   * exported mount flags.


>Audit-Trail:
>Unformatted: