mailing list of musl libc
 help / color / mirror / code / Atom feed
From: Rich Felker <dalias@libc.org>
To: musl@lists.openwall.com
Subject: [musl] realpath without procfs -- should be ready for inclusion
Date: Sun, 22 Nov 2020 17:56:21 -0500	[thread overview]
Message-ID: <20201122225619.GR534@brightrain.aerifal.cx> (raw)

[-- Attachment #1: Type: text/plain, Size: 1218 bytes --]

I put this aside for a while, but realpath without procfs was one of
the things I wanted to get in this release cycle, and I had it
essentially finished back in September. Here's the latest version.
Compared to the earlier draft, it handles // more consistently (//.
and //.. both resolve to //) and expands getcwd only as a final step
if needed, rather than doing it first. In the case where an absolute
symlink is reached via a relative path, this saves a syscall, avoids
repeated absolute path traversal at each step, and avoids spuriously
erroring out with ENAMETOOLONG when the working directory is deeper
than PATH_MAX but the absolute link target has a valid absolute
pathname.

To recap, the motivation for the rewrite without procfs dependency was
having things work before procfs is mounted or in chroot/namespace
where it's intentionally unavailable.

I originally considered keeping the procfs based version and only
using the new one as a fallback, but I discovered there are cases
(involving chroot, namespaces, etc.) where the answer from procfs is
wrong and validating it requires basically the same procedure as
implementing it manually (walking and performing readlink on each path
component).

Rich

[-- Attachment #2: realpath8.c --]
[-- Type: text/plain, Size: 2392 bytes --]

#define _GNU_SOURCE
#include <stdlib.h>
#include <limits.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>

static inline int at_dotdot(const char *end, size_t len)
{
	if (len<2) return 0;
	if (len>2 && end[-3]!='/') return 0;
	return end[-1]=='.' && end[-2]=='.';
}

char *realpath(const char *restrict filename, char *restrict resolved)
{
	char stack[PATH_MAX];
	char buf[resolved ? 1 : PATH_MAX];
	char *output = resolved ? resolved : buf;
	size_t p, q, l, cnt=0;

	l = strnlen(filename, sizeof stack + 1);
	if (!l) {
		errno = ENOENT;
		return 0;
	}
	if (l >= sizeof stack) goto toolong;
	p = sizeof stack - l - 1;
	q = 0;
	memcpy(stack+p, filename, l+1);

	while (stack[p]) {
		int up = 0;
		if (stack[p] == '/') {
			q=0;
			output[q++] = '/';
			p++;
			/* Initial // is special. */
			if (stack[p] == '/' && stack[p+1] != '/') {
				output[q++] = '/';
			}
			while (stack[p] == '/') p++;
			continue;
		}
		char *z = __strchrnul(stack+p, '/');
		l = z-(stack+p);
		if (l==1 && stack[p]=='.') {
			p += l;
			while (stack[p] == '/') p++;
			continue;
		}
		if (at_dotdot(stack+p+l, l)) {
			if (q && !at_dotdot(output+q, q)) {
				while(q && output[q-1]!='/') q--;
				if (q>1 && (q>2 || output[0]!='/')) q--;
				p += l;
				while (stack[p] == '/') p++;
				continue;
			}
			up = 1;
		}
		if (q && output[q-1] != '/') {
			if (!p) goto toolong;
			stack[--p] = '/';
			l++;
		}
		if (q+l >= PATH_MAX) goto toolong;
		memcpy(output+q, stack+p, l);
		output[q+l] = 0;
		p += l;
		if (up) goto notlink;
		ssize_t k = readlink(output, stack, p);
		if (k==-1) {
			if (errno == EINVAL) {
notlink:
				q += l;
				while (stack[p] == '/') p++;
				continue;
			}
			return 0;
		}
		if (k==p) goto toolong;
		if (++cnt == SYMLOOP_MAX) {
			errno = ELOOP;
			return 0;
		}
		p -= k;
		memmove(stack+p, stack, k);
	}

	output[q] = 0;

	if (output[0] != '/') {
		if (!getcwd(stack, sizeof stack)) return 0;
		l = strlen(stack);
		/* Cancel any initial .. components. */
		p = 0;
		while (q-p>=2 && at_dotdot(output+p+2, p+2)) {
			while(l>1 && stack[l-1]!='/') l--;
			if (l>1) l--;
			p += 2;
			if (p<q) p++;
		}
		if (q-p && stack[l-1]!='/') output[--p] = '/';
		if (l + (q-p) + 1 >= PATH_MAX) goto toolong;
		memmove(output + l, output + p, q - p + 1);
		memcpy(output, stack, l);
	}

	return resolved ? resolved : strdup(output);

toolong:
	errno = ENAMETOOLONG;
	return 0;
}

             reply	other threads:[~2020-11-22 22:56 UTC|newest]

Thread overview: 20+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-11-22 22:56 Rich Felker [this message]
2020-11-23  2:03 ` Alexey Izbyshev
2020-11-23  3:17   ` Érico Nogueira
2020-11-23  3:34     ` Rich Felker
2020-11-23  3:19   ` Rich Felker
2020-11-23 18:56     ` Rich Felker
2020-11-23 20:53       ` Rich Felker
2020-11-24  3:39         ` Alexey Izbyshev
2020-11-24  4:26           ` Rich Felker
2020-11-24  5:13             ` Alexey Izbyshev
2020-11-24  6:30               ` Rich Felker
2020-11-24  9:21                 ` Alexey Izbyshev
2020-11-24 14:35                   ` Rich Felker
2020-11-24 20:17                     ` Rich Felker
2020-11-25 15:02                   ` Rich Felker
2020-11-25 19:40                     ` Alexey Izbyshev
2020-11-24 20:31             ` Rich Felker
2020-11-25  5:40               ` Alexey Izbyshev
2020-11-25 15:03                 ` Rich Felker
2020-11-24  3:41     ` Alexey Izbyshev

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20201122225619.GR534@brightrain.aerifal.cx \
    --to=dalias@libc.org \
    --cc=musl@lists.openwall.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.vuxu.org/mirror/musl/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).