From 8a9e44668e292dba143e393d2887d3a5014eb66b Mon Sep 17 00:00:00 2001
From: Benno Schulenberg <bensberg@telfort.nl>
Date: Wed, 30 Mar 2022 17:05:23 +0200
Subject: [PATCH] tweaks: make use of realpath() in our get_full_path()
 function

Since 2008, realpath() with NULL as second parameter is part of the
POSIX standard.  Operating systems have had nearly fourteen years to
catch up -- this should be time enough.

Using realpath() saves seventy lines of code and gets rid of some
weirdly complicated logic plus unwanted calls of chdir().

Signed-off-by: Benno Schulenberg <bensberg@telfort.nl>
---
 src/files.c | 106 +++++++++-------------------------------------------
 1 file changed, 18 insertions(+), 88 deletions(-)

diff --git a/src/files.c b/src/files.c
index 9c598e1f..6641de01 100644
--- a/src/files.c
+++ b/src/files.c
@@ -1345,106 +1345,36 @@ void do_execute(void)
  * absolute path (plus filename) when the path exists, and NULL when not. */
 char *get_full_path(const char *origpath)
 {
-	char *allocation, *here, *target, *last_slash;
-	char *just_filename = NULL;
-	int attempts = 0;
+	char *untilded, *target, *slash;
 	struct stat fileinfo;
-	bool path_only;
 
 	if (origpath == NULL)
 		return NULL;
 
-	allocation = nmalloc(PATH_MAX + 1);
-	here = getcwd(allocation, PATH_MAX + 1);
+	untilded = real_dir_from_tilde(origpath);
+	target = realpath(untilded, NULL);
+	slash = strrchr(untilded, '/');
 
-	/* If getting the current directory failed, go up one level and try again,
-	 * until we find an existing directory, and use that as the current one. */
-	while (here == NULL && attempts < 20) {
-		IGNORE_CALL_RESULT(chdir(".."));
-		here = getcwd(allocation, PATH_MAX + 1);
-		attempts++;
-	}
-
-	/* If we found a directory, make sure its path ends in a slash. */
-	if (here != NULL) {
-		if (strcmp(here, "/") != 0) {
-			here = nrealloc(here, strlen(here) + 2);
-			strcat(here, "/");
-		}
-	} else {
-		here = copy_of("");
-		free(allocation);
-	}
-
-	target = real_dir_from_tilde(origpath);
-
-	/* Determine whether the target path refers to a directory.  If statting
-	 * target fails, however, assume that it refers to a new, unsaved buffer. */
-	path_only = (stat(target, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode));
-
-	/* If the target is a directory, make sure its path ends in a slash. */
-	if (path_only) {
-		size_t length = strlen(target);
-
-		if (target[length - 1] != '/') {
-			target = nrealloc(target, length + 2);
-			strcat(target, "/");
-		}
-	}
-
-	last_slash = strrchr(target, '/');
-
-	/* If the target path does not contain a slash, then it is a bare filename
-	 * and must therefore be located in the working directory. */
-	if (last_slash == NULL) {
-		just_filename = target;
-		target = here;
-	} else {
-		/* If target contains a filename, separate the two. */
-		if (!path_only) {
-			just_filename = copy_of(last_slash + 1);
-			*(last_slash + 1) = '\0';
-		}
-
-		/* If we can't change to the target directory, give up.  Otherwise,
-		 * get the canonical path to this target directory. */
-		if (chdir(target) == -1) {
-			free(target);
-			target = NULL;
-		} else {
-			free(target);
-
-			allocation = nmalloc(PATH_MAX + 1);
-			target = getcwd(allocation, PATH_MAX + 1);
-
-			/* If we got a result, make sure it ends in a slash.
-			 * Otherwise, ensure that we return NULL. */
-			if (target != NULL) {
-				if (strcmp(target, "/") != 0) {
-					target = nrealloc(target, strlen(target) + 2);
-					strcat(target, "/");
-				}
-			} else {
-				path_only = TRUE;
-				free(allocation);
-			}
+	/* If realpath() returned NULL, try without the last component,
+	 * as this can be a file that does not exist yet. */
+	if (!target && slash && slash[1]) {
+		*slash = '\0';
+		target = realpath(untilded, NULL);
 
-			/* Finally, go back to where we were before.  We don't check
-			 * for an error, since we can't do anything if we get one. */
-			IGNORE_CALL_RESULT(chdir(here));
+		/* Upon success, re-add the last component of the original path. */
+		if (target) {
+			target = nrealloc(target, strlen(target) + strlen(slash + 1) + 1);
+			strcat(target, slash + 1);
 		}
-
-		free(here);
 	}
 
-	/* If we were given more than a bare path, concatenate the target path
-	 * with the filename portion to get the full, absolute file path. */
-	if (!path_only && target != NULL) {
-		target = nrealloc(target, strlen(target) + strlen(just_filename) + 1);
-		strcat(target, just_filename);
+	/* Ensure that a directory path ends with a slash. */
+	if (target && stat(target, &fileinfo) == 0 && S_ISDIR(fileinfo.st_mode)) {
+		target = nrealloc(target, strlen(target) + 2);
+		strcat(target, "/");
 	}
 
-	free(just_filename);
+	free(untilded);
 
 	return target;
 }
-- 
2.34.1

