Commit 5c02d2a555dc01ac06db5a47b8c81adf2bf3f8a6

Stefan Sperling 2021-09-26T17:40:10

for portability, handle errno variations upon open(2) failure with O_NOFOLLOW Problem pointed out by naddy for FreeBSD -portable. Discussed with millert, thomas adam, and naddy.

diff --git a/got/got.c b/got/got.c
index 66b864f..0bf2f90 100644
--- a/got/got.c
+++ b/got/got.c
@@ -4394,7 +4394,7 @@ print_diff(void *arg, unsigned char status, unsigned char staged_status,
 		if (dirfd != -1) {
 			fd = openat(dirfd, de_name, O_RDONLY | O_NOFOLLOW);
 			if (fd == -1) {
-				if (errno != ELOOP) {
+				if (!got_err_open_nofollow_on_symlink()) { 
 					err = got_error_from_errno2("openat",
 					    abspath);
 					goto done;
@@ -4407,7 +4407,7 @@ print_diff(void *arg, unsigned char status, unsigned char staged_status,
 		} else {
 			fd = open(abspath, O_RDONLY | O_NOFOLLOW);
 			if (fd == -1) {
-				if (errno != ELOOP) {
+				if (!got_err_open_nofollow_on_symlink()) {
 					err = got_error_from_errno2("open",
 					    abspath);
 					goto done;
diff --git a/include/got_error.h b/include/got_error.h
index f87c06c..e1becc0 100644
--- a/include/got_error.h
+++ b/include/got_error.h
@@ -427,3 +427,9 @@ const struct got_error *got_error_path(const char *, int);
  * additional arguments.
 */
 const struct got_error *got_error_fmt(int, const char *, ...);
+
+/*
+ * Check whether open(2) with O_NOFOLLOW failed on a symlink.
+ * This must be called directly after open(2) because it uses errno!
+ */
+int got_err_open_nofollow_on_symlink(void);
diff --git a/lib/error.c b/lib/error.c
index 72e0048..c4605e6 100644
--- a/lib/error.c
+++ b/lib/error.c
@@ -258,3 +258,22 @@ got_error_fmt(int code, const char *fmt, ...)
 
 	abort();
 }
+
+int
+got_err_open_nofollow_on_symlink(void)
+{
+	/*
+	 * Check whether open(2) with O_NOFOLLOW failed on a symlink.
+	 * Posix mandates ELOOP and OpenBSD follows it. Others return
+	 * different error codes. We carry this workaround to help the
+	 * portable version a little.
+	 */
+	return (errno == ELOOP
+#ifdef EMLINK
+	|| errno == EMLINK
+#endif
+#ifdef EFTYPE
+	|| errno == EFTYPE
+#endif
+	);
+}
diff --git a/lib/object_create.c b/lib/object_create.c
index d2c6246..1092bb0 100644
--- a/lib/object_create.c
+++ b/lib/object_create.c
@@ -129,7 +129,7 @@ got_object_blob_file_create(struct got_object_id **id, FILE **blobfile,
 
 	fd = open(ondisk_path, O_RDONLY | O_NOFOLLOW);
 	if (fd == -1) {
-		if (errno != ELOOP)
+		if (!got_err_open_nofollow_on_symlink())
 			return got_error_from_errno2("open", ondisk_path);
 
 		if (lstat(ondisk_path, &sb) == -1) {
diff --git a/lib/worktree.c b/lib/worktree.c
index 904fb23..0c6b89c 100644
--- a/lib/worktree.c
+++ b/lib/worktree.c
@@ -1284,7 +1284,7 @@ replace_existing_symlink(int *did_something, const char *ondisk_path,
 	 */
 	fd = open(ondisk_path, O_RDWR | O_EXCL | O_NOFOLLOW);
 	if (fd == -1) {
-		if (errno != ELOOP)
+		if (!got_err_open_nofollow_on_symlink())
 			return got_error_from_errno2("open", ondisk_path);
 
 		/* We are updating an existing on-disk symlink. */
@@ -1781,9 +1781,10 @@ get_file_status(unsigned char *status, struct stat *sb,
 		}
 	} else {
 		fd = open(abspath, O_RDONLY | O_NOFOLLOW);
-		if (fd == -1 && errno != ENOENT && errno != ELOOP)
+		if (fd == -1 && errno != ENOENT &&
+		    !got_err_open_nofollow_on_symlink())
 			return got_error_from_errno2("open", abspath);
-		else if (fd == -1 && errno == ELOOP) {
+		else if (fd == -1 && got_err_open_nofollow_on_symlink()) {
 			if (lstat(abspath, sb) == -1)
 				return got_error_from_errno2("lstat", abspath);
 		} else if (fd == -1 || fstat(fd, sb) == -1) {
@@ -3767,7 +3768,7 @@ worktree_status(struct got_worktree *worktree, const char *path,
 	fd = open(ondisk_path, O_RDONLY | O_NOFOLLOW | O_DIRECTORY);
 	if (fd == -1) {
 		if (errno != ENOTDIR && errno != ENOENT && errno != EACCES &&
-		    errno != ELOOP)
+		    !got_err_open_nofollow_on_symlink())
 			err = got_error_from_errno2("open", ondisk_path);
 		else {
 			if (!no_ignores) {
@@ -4473,7 +4474,7 @@ create_patched_content(char **path_outfile, int reverse_patch,
 	if (dirfd2 != -1) {
 		fd2 = openat(dirfd2, de_name2, O_RDONLY | O_NOFOLLOW);
 		if (fd2 == -1) {
-			if (errno != ELOOP) {
+			if (!got_err_open_nofollow_on_symlink()) {
 				err = got_error_from_errno2("openat", path2);
 				goto done;
 			}
@@ -4487,7 +4488,7 @@ create_patched_content(char **path_outfile, int reverse_patch,
 	} else {
 		fd2 = open(path2, O_RDONLY | O_NOFOLLOW);
 		if (fd2 == -1) {
-			if (errno != ELOOP) {
+			if (!got_err_open_nofollow_on_symlink()) {
 				err = got_error_from_errno2("open", path2);
 				goto done;
 			}