Skip site navigation (1)Skip section navigation (2)
Date:      Thu, 19 Mar 2009 00:44:47 +0100
From:      Luigi Rizzo <rizzo@iet.unipi.it>
To:        Ivan Voras <ivoras@freebsd.org>
Cc:        freebsd-arch@freebsd.org
Subject:   Re: Magic symlinks redux
Message-ID:  <20090318234447.GA21963@onelab2.iet.unipi.it>
In-Reply-To: <49C17BB3.1010702@freebsd.org>
References:  <g8kv7v$sp2$1@ger.gmane.org> <49C17BB3.1010702@freebsd.org>

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

--fdj2RfSjLxBAspz7
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

On Wed, Mar 18, 2009 at 11:54:43PM +0100, Ivan Voras wrote:
> Hi,
> 
> I started doing something that would be nicely solved by magic symlinks
> so I remembered this thread. It started some 6+ months ago here:
> 
> http://lists.freebsd.org/pipermail/freebsd-arch/2008-August/008473.html
> 
> Is there any new development?

at the time i did some quick tests on probably the same code
as you reference below, and it is really trivial (attached).
I do remember a discussion of a more complete implementation
but was many times more complex and more intrusive and I
am afraid that such a thing will never happen.

BTW today i hit a problem where some form of magic symlinks
would be useful.
Say you are preparing a disk image in a subtree
of your filesystem, and your image contains symlinks with absolute
paths: to do things right, you should navigate the subtree in
a chroot'ed environment, but chroot() requires root privs.

If we had some form of magic symlinks that support a per-process
remapping, one could e.g. set a variable that is prepended to
the target of absolute symlinks, and achieve the same effect of a
chroot without the need for root privs...

	cheers
	luigi

> Ivan Voras wrote:
> > I was reading about new things in NetBSD, and one thing caught my
> > attention: per-user /tmp. See
> > http://www.feyrer.de/NetBSD/bx/blosxom.cgi/nb_20080714_0251.html for
> > example.
> > 
> > Google says that a discussion about magic symlinks happens every now and
> > then in FreeBSD but nothing really gets done. I found this
> > implementation which looks like it's for 7.0:
> > 
> > http://butcher.heavennet.ru/patches/kernel/magiclinks/
> > 
> > As far as I understand the VFS (which isn't much...) this looks like an
> > trivial patch, and it's compatible with NetBSD. Since I'm interested in
> > this (specifically for the per-user /tmp and maybe similar gadgetry),
> > I'd like to nurse this patch into the tree, if there are no objections
> > (of course, I'll bug anyone I can find who knows VFS to review it :) ).
> > 




--fdj2RfSjLxBAspz7
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="20090211-symlinks.diff"

Index: sys/kern/vfs_lookup.c
===================================================================
--- sys/kern/vfs_lookup.c	(revision 189113)
+++ sys/kern/vfs_lookup.c	(working copy)
@@ -45,6 +45,7 @@
 #include <sys/kernel.h>
 #include <sys/lock.h>
 #include <sys/mutex.h>
+#include <sys/jail.h>	// XXX symlinks
 #include <sys/namei.h>
 #include <sys/vnode.h>
 #include <sys/mount.h>
@@ -87,6 +88,123 @@
 }
 SYSINIT(vfs, SI_SUB_VFS, SI_ORDER_SECOND, nameiinit, NULL);
 
+#ifdef MAGICLINKS
+static int vfs_magiclinks = 1;
+#else
+static int vfs_magiclinks = 1;
+#endif
+SYSCTL_INT(_vfs, OID_AUTO, magiclinks, CTLFLAG_RW, &vfs_magiclinks, 0,
+	"Whether \"magic\" symlinks are expanded");
+
+/* looks up a string returns the match len or 0 */
+static int
+s_match(const char *key, int keylen, const char *haystack, const char *end)
+{
+	if (haystack + keylen >= end || haystack[keylen] != '}')
+		return 0;
+	if (strncmp(key, haystack, keylen))
+		return 0;
+	return keylen;
+}
+#define	MATCH(str) s_match(str, sizeof(str) - 1, src, end)
+
+static char *
+s_subst(char *dst, const char *max, const char *value, int len)
+{
+	if (value == dst) {	/* already copied, locate end of string */
+		while (*dst)
+			dst++;
+		return dst;
+	}
+	/* check size, copy and replace */
+	if (dst + len > max) /* overflow */
+		return NULL;
+	bcopy(value, dst, len);
+	dst += len;
+	return dst;
+}
+
+/*
+ * Substitute replacement text for 'magic' strings in symlinks.
+ * Looks for "@{string}", where <string> is a
+ * recognized 'magic' string.  Replaces the original with the
+ * appropriate replacement text.  (Note that in some cases the
+ * replacement text may have zero length.)
+ * Assume *len is at least 3.
+ */
+static void
+symlink_magic(struct thread *td, char *cp, int *len)
+{
+	char *src, *dst, *tmp, *end = cp + *len, *max;
+	int change = 0;
+
+	/* quick return if nothing to replace */
+	for (src = cp; src < end - 1; src++) {
+		if (src[0] == '@' && src[1] == '{')
+			break;
+	}
+	if (src == end - 1)	/* no replacement */
+		return;
+
+	/* allocate a buffer for the replacement */
+	dst = tmp = uma_zalloc(namei_zone, M_WAITOK);
+	if (dst == NULL) {	/* no space for replacement */
+		printf("zalloc fail in %s\n", __FUNCTION__);
+		return;
+	}
+	max = dst + MAXPATHLEN - 1;
+	for (src = cp; src < end - 1 && dst < max - 1;) {
+		int l;
+		if (src[0] != '@' || src[1] != '{') {
+			*dst++ = *src++;	/* copy current char */
+			continue;
+		}
+		src += 2;	/* skip @{ */
+
+printf("replace magic at %s\n", src);
+		/*
+		 * The following checks should be ordered according
+		 * to frequency of use.
+		 */
+		if ( (l = MATCH("machine_arch")) ) {
+			dst = s_subst(dst, max, MACHINE_ARCH, sizeof(MACHINE_ARCH) - 1);
+		} else if ( (l= MATCH("machine")) ) {
+			dst = s_subst(dst, max, MACHINE_ARCH, sizeof(MACHINE_ARCH) - 1);
+		} else if ( (l= MATCH("hostname")) ) {
+			getcredhostname(td->td_ucred, dst, max - dst);
+			dst = s_subst(dst, max, dst, 0);
+		} else if ( (l= MATCH("osrelease")) ) {
+			dst = s_subst(dst, max, osrelease, strlen(osrelease));
+		} else if ( (l= MATCH("kernel_ident")) ) {
+			dst = s_subst(dst, max, kern_ident, strlen(kern_ident));
+		} else if ( (l= MATCH("domainname")) ) {
+			dst = s_subst(dst, max, domainname, strlen(domainname));
+		} else if ( (l= MATCH("ostype")) ) {
+			dst = s_subst(dst, max, ostype, strlen(ostype));
+		}
+		if (dst == NULL)	/* overflow */
+			break;
+		if (l == 0) { /* no match, restore original */
+			*dst++ = '@';
+			*dst++ = '{';
+			continue;
+		}
+		/* otherwise skip original name and } */
+		src += l + 1;
+		change = 1;
+	}
+	if (change && dst) {
+		if (src < end)	/* copy last char */
+			*dst++ = *src;
+		*dst = '\0';
+		printf("translating into %s\n", tmp);
+		*len = dst - tmp;
+		bcopy(tmp, cp, *len);
+	}
+	uma_zfree(namei_zone, tmp);
+}
+#undef MATCH
+
 static int lookup_shared = 0;
 SYSCTL_INT(_vfs, OID_AUTO, lookup_shared, CTLFLAG_RW, &lookup_shared, 0,
     "Enables/Disables shared locks for path name translation");
@@ -280,6 +398,8 @@
 			error = ENOENT;
 			break;
 		}
+		if (vfs_magiclinks && linklen >3) /* at least @{} in the symlink */
+			symlink_magic(td, cp, &linklen);
 		if (linklen + ndp->ni_pathlen >= MAXPATHLEN) {
 			if (ndp->ni_pathlen > 1)
 				uma_zfree(namei_zone, cp);

--fdj2RfSjLxBAspz7--



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