/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:8;tab-width:8;coding:utf-8   -*-│
│ vi: set noet ft=c ts=8 sw=8 fenc=utf-8                                   :vi │
╚──────────────────────────────────────────────────────────────────────────────╝
│                                                                              │
│  Musl Libc                                                                   │
│  Copyright © 2005-2014 Rich Felker, et al.                                   │
│                                                                              │
│  Permission is hereby granted, free of charge, to any person obtaining       │
│  a copy of this software and associated documentation files (the             │
│  "Software"), to deal in the Software without restriction, including         │
│  without limitation the rights to use, copy, modify, merge, publish,         │
│  distribute, sublicense, and/or sell copies of the Software, and to          │
│  permit persons to whom the Software is furnished to do so, subject to       │
│  the following conditions:                                                   │
│                                                                              │
│  The above copyright notice and this permission notice shall be              │
│  included in all copies or substantial portions of the Software.             │
│                                                                              │
│  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,             │
│  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF          │
│  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.      │
│  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY        │
│  CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,        │
│  TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE           │
│  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                      │
│                                                                              │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/assert.h"
#include "libc/calls/calls.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/intrin/safemacros.h"
#include "libc/limits.h"
#include "libc/log/backtrace.internal.h"
#include "libc/mem/mem.h"
#include "libc/serialize.h"
#include "libc/str/str.h"
#include "libc/sysv/errfuns.h"
__static_yoink("musl_libc_notice");

#define SYMLOOP_MAX 40

// clang-format off

static inline int IsAlpha(int c)
{
	return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z');
}

static size_t GetSlashLen(const char *s)
{
	const char *s0 = s;
	while (*s == '/') s++;
	return s-s0;
}

static char *ResolvePath(char *d, const char *s, size_t n)
{
	if (d || (d = malloc(n+1))) {
		return memmove(d, s, n+1);
	} else {
		enomem();
		return 0;
	}
}

/**
 * Returns absolute pathname.
 *
 * This function removes `/./` and `/../` components. If any individual
 * path component is a symbolic link, then it'll be resolved. Any slash
 * characters that repeat (e.g. `//`) will collapse into one (i.e. `/`)
 *
 * This implementation is consistent with glibc, in that `"//"` becomes
 * `"/"` unlike Musl Libc, which considers that special (not sure why?)
 * This is the only change Cosmopolitan Libc made vs. Musl's realpath()
 * aside from also being permissive about backslashes, to help Windows.
 *
 * @param filename is the path that needs to be resolved
 * @param resolved needs PATH_MAX bytes, or NULL to use malloc()
 * @return resolved path, or NULL w/ errno
 * @raise EINVAL if `filename` is NULL
 * @raise ENOENT if `filename` is an empty string
 * @raise ENOMEM if `resolved` is NULL and malloc() failed
 * @raise ENOENT if `filename` didn't exist
 * @raise ENOTDIR if directory component existed that's not a directory
 * @raise ENOTDIR if base component ends with slash and is not a dir
 * @raise ENAMETOOLONG if filename resolution exceeded `PATH_MAX`
 * @raise ELOOP if too many symlinks were encountered
 */
char *realpath(const char *filename, char *resolved)
{
	ssize_t rc;
	int e, up, check_dir=0;
	size_t k, p, q, l, l0, cnt=0, nup=0;
	char output[PATH_MAX], stack[PATH_MAX+1], *z;

	if (!filename) {
		einval();
		return 0;
	}

	/* Normalize windows paths before proceeding. */
	if (IsWindows()) {
		int c, i = 0;

		/* Turn backslash into slash. */
		while ((c = *filename++)) {
			if (i == PATH_MAX - 1)
				goto toolong;
			if (c == '\\')
				c = '/';
			output[i++] = c;
		}
		output[i] = 0;

		/* Turn paths like "C:" into "/C"
		 * Turn paths like "C:/..." into "/C/..." */
		if (IsAlpha(output[0]) && output[1] == ':' &&
		    (!output[2] || output[2] == '/')) {
			output[1] = output[0];
			output[0] = '/';
		}

		filename = output;
	}

	/* Copy the path and handle ZipOS. */
	l = strnlen(filename, sizeof stack);
	if (!l) {
		enoent();
		return 0;
	}
	if (l >= PATH_MAX) goto toolong;
	if (l >= 4 && READ32LE(filename) == READ32LE("/zip") &&
	    (!filename[4] || filename[4] == '/')) {
		return ResolvePath(resolved, filename, l);
	}
	p = sizeof stack - l - 1;
	q = 0;
	memcpy(stack+p, filename, l+1);

	/* Main loop. Each iteration pops the next part from stack of
	 * remaining path components and consumes any slashes that follow.
	 * If not a link, it's moved to output; if a link, contents are
	 * pushed to the stack. */
restart:
	for (; ; p+=GetSlashLen(stack+p)) {
		/* If stack starts with /, the whole component is / or //
		 * and the output state must be reset. */
		if (stack[p] == '/') {
			check_dir=0;
			nup=0;
			q=0;
			output[q++] = '/';
			p++;
			continue;
		}

		z = strchrnul(stack+p, '/');
		l0 = l = z-(stack+p);

		if (!l && !check_dir) break;

		/* Skip any . component but preserve check_dir status. */
		if (l==1 && stack[p]=='.') {
			p += l;
			continue;
		}

		/* Copy next component onto output at least temporarily, to
		 * call readlink, but wait to advance output position until
		 * determining it's not a link. */
		if (q && output[q-1] != '/') {
			if (!p) goto toolong;
			stack[--p] = '/';
			l++;
		}
		if (q+l >= PATH_MAX) goto toolong;
		if (l) memcpy(output+q, stack+p, l);
		output[q+l] = 0;
		p += l;

		up = 0;
		if (l0==2 && stack[p-2]=='.' && stack[p-1]=='.') {
			up = 1;
			/* Any non-.. path components we could cancel start
			 * after nup repetitions of the 3-byte string "../";
			 * if there are none, accumulate .. components to
			 * later apply to cwd, if needed. */
			if (q <= 3*nup) {
				nup++;
				q += l;
				continue;
			}
			/* When previous components are already known to be
			 * directories, processing .. can skip readlink. */
			if (!check_dir) goto skip_readlink;
		}
		e = errno;
		if ((rc = readlink(output, stack, p)) == -1) {
			if (errno != EINVAL) return 0;
			errno = e; /* [jart] undirty errno if not a symlink */
skip_readlink:
			check_dir = 0;
			if (up) {
				while(q && output[q-1] != '/') q--;
				if (q>1 && (q>2 || output[0] != '/')) q--;
				continue;
			}
			if (l0) q += l;
			check_dir = stack[p];
			continue;
		}
		k = rc;
		npassert(k <= p);
		if (k==p)
			goto toolong;
		if (!k) {
			errno = ENOENT;
			return 0;
		}
		if (++cnt == SYMLOOP_MAX) {
			errno = ELOOP;
			return 0;
		}

		/* If link contents end in /, strip any slashes already on
		 * stack to avoid /->// or //->/// or spurious toolong. */
		if (stack[k-1] == '/') {
			while (stack[p] == '/')
				p++;
		}
		p -= k;
		memmove(stack+p, stack, k);

		/* Skip the stack advancement in case we have a new
		 * absolute base path. */
		goto restart;
	}

 	output[q] = 0;

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

	return ResolvePath(resolved, output, q);

toolong:
	enametoolong();
	return 0;
}
