Commit 20ccae39452cdf85f57023c8a7f029b0545adfa7

Stefan Sperling 2020-07-21T14:19:32

cope with directory entries returned from readdir(3) with type DT_UNKNOWN Such directory entries need special handling to make our directory traversal code work on filesystems that do not support the d_type optimization. I found this problem because references stored in the refs/ directory were not shown by 'got log' and 'tog log' when a repository is mounted over NFS. helpful feedback + ok millert@

diff --git a/include/got_path.h b/include/got_path.h
index 57b823a..0c75579 100644
--- a/include/got_path.h
+++ b/include/got_path.h
@@ -21,6 +21,8 @@
 #define GOT_DEFAULT_DIR_MODE	(S_IFDIR | \
 	S_IRWXU | S_IRGRP|S_IXGRP | S_IROTH|S_IXOTH)
 
+struct dirent;
+
 /* Determine whether a path is an absolute path. */
 int got_path_is_absolute(const char *);
 
@@ -102,6 +104,20 @@ int got_path_dir_is_empty(const char *);
 /* dirname(3) with error handling and dynamically allocated result. */
 const struct got_error *got_path_dirname(char **, const char *);
 
+/*
+ * Obtain the file type of a given directory entry.
+ *
+ * If the entry has some type other than DT_UNKNOWN, resolve to this type.
+ *
+ * Otherwise, attempt to resolve the type of a DT_UNKNOWN directory
+ * entry with lstat(2), though the result may still be DT_UNKNOWN.
+ * This is a fallback to accommodate filesystems which do not provide
+ * directory entry type information.
+ * DT_UNKNOWN directory entries occur on NFS mounts without "readdir plus" RPC.
+ */
+const struct got_error *got_path_dirent_type(int *, const char *,
+    struct dirent *);
+
 /* basename(3) with dynamically allocated result. */
 const struct got_error *got_path_basename(char **, const char *);
 
diff --git a/lib/fileindex.c b/lib/fileindex.c
index 6566855..c3bd9dd 100644
--- a/lib/fileindex.c
+++ b/lib/fileindex.c
@@ -933,10 +933,23 @@ walk_dir(struct got_pathlist_entry **next, struct got_fileindex *fileindex,
 	struct dirent *de = dle->data;
 	DIR *subdir = NULL;
 	int subdirfd = -1;
+	int type;
 
 	*next = NULL;
 
-	if (de->d_type == DT_DIR) {
+	if (de->d_type == DT_UNKNOWN) {
+		/* Occurs on NFS mounts without "readdir plus" RPC. */
+		char *dir_path;
+		if (asprintf(&dir_path, "%s/%s", rootpath, path) == -1)
+			return got_error_from_errno("asprintf");
+		err = got_path_dirent_type(&type, dir_path, de);
+		free(dir_path);
+		if (err)
+			return err;
+	} else
+		type = de->d_type;
+
+	if (type == DT_DIR) {
 		char *subpath;
 		char *subdirpath;
 		struct got_pathlist_head subdirlist;
diff --git a/lib/path.c b/lib/path.c
index be971e6..549cac5 100644
--- a/lib/path.c
+++ b/lib/path.c
@@ -375,6 +375,53 @@ got_path_dirname(char **parent, const char *path)
 }
 
 const struct got_error *
+got_path_dirent_type(int *type, const char *path_parent, struct dirent *dent)
+{
+	const struct got_error *err = NULL;
+	char *path_child;
+	struct stat sb;
+
+	if (dent->d_type != DT_UNKNOWN) {
+		*type = dent->d_type;
+		return NULL;
+	}
+
+	*type = DT_UNKNOWN;
+
+	/*
+	 * This is a fallback to accommodate filesystems which do not
+	 * provide directory entry type information. DT_UNKNOWN directory
+	 * entries occur on NFS mounts without "readdir plus" RPC.
+	 */
+
+	if (asprintf(&path_child, "%s/%s", path_parent, dent->d_name) == -1)
+		return got_error_from_errno("asprintf");
+
+	if (lstat(path_child, &sb) == -1) {
+		err = got_error_from_errno2("lstat", path_child);
+		goto done;
+	}
+
+	if (S_ISFIFO(sb.st_mode))
+		*type = DT_FIFO;
+	else if (S_ISCHR(sb.st_mode))
+		*type = DT_CHR;
+	else if (S_ISDIR(sb.st_mode))
+		*type = DT_DIR;
+	else if (S_ISBLK(sb.st_mode))
+		*type = DT_BLK;
+	else if (S_ISLNK(sb.st_mode))
+		*type = DT_LNK;
+	else if (S_ISREG(sb.st_mode))
+		*type = DT_REG;
+	else if (S_ISSOCK(sb.st_mode))
+		*type = DT_SOCK;
+done:
+	free(path_child);
+	return err;
+}
+
+const struct got_error *
 got_path_basename(char **s, const char *path)
 {
 	char *base;
diff --git a/lib/reference.c b/lib/reference.c
index a4aa416..3c2632d 100644
--- a/lib/reference.c
+++ b/lib/reference.c
@@ -826,6 +826,7 @@ gather_on_disk_refs(struct got_reflist_head *refs, const char *path_refs,
 		struct dirent *dent;
 		struct got_reference *ref;
 		char *child;
+		int type;
 
 		dent = readdir(d);
 		if (dent == NULL)
@@ -835,7 +836,11 @@ gather_on_disk_refs(struct got_reflist_head *refs, const char *path_refs,
 		    strcmp(dent->d_name, "..") == 0)
 			continue;
 
-		switch (dent->d_type) {
+		err = got_path_dirent_type(&type, path_subdir, dent);
+		if (err)
+			break;
+
+		switch (type) {
 		case DT_REG:
 			err = open_ref(&ref, path_refs, subdir, dent->d_name,
 			    0);
diff --git a/lib/repository.c b/lib/repository.c
index 7531c15..dec9253 100644
--- a/lib/repository.c
+++ b/lib/repository.c
@@ -1579,6 +1579,7 @@ write_tree(struct got_object_id **new_tree_id, const char *path_dir,
 	nentries = 0;
 	while ((de = readdir(dir)) != NULL) {
 		int ignore = 0;
+		int type;
 
 		if (strcmp(de->d_name, ".") == 0 ||
 		    strcmp(de->d_name, "..") == 0)
@@ -1592,7 +1593,12 @@ write_tree(struct got_object_id **new_tree_id, const char *path_dir,
 		}
 		if (ignore)
 			continue;
-		if (de->d_type == DT_DIR) {
+
+		err = got_path_dirent_type(&type, path_dir, de);
+		if (err)
+			goto done;
+
+		if (type == DT_DIR) {
 			err = import_subdir(&new_te, de, path_dir,
 			    ignores, repo, progress_cb, progress_arg);
 			if (err) {
@@ -1601,7 +1607,7 @@ write_tree(struct got_object_id **new_tree_id, const char *path_dir,
 				err = NULL;
 				continue;
 			}
-		} else if (de->d_type == DT_REG) {
+		} else if (type == DT_REG) {
 			err = import_file(&new_te, de, path_dir, repo);
 			if (err)
 				goto done;