1 /*
2 ** crawl_folders.c -- crawl folder hierarchy
3 **
4 ** This code is Copyright (c) 2008, by the authors of nmh.  See the
5 ** COPYRIGHT file in the root directory of the nmh distribution for
6 ** complete copyright information.
7 */
8 
9 #include <h/mh.h>
10 #include <h/crawl_folders.h>
11 #include <h/utils.h>
12 #include <dirent.h>
13 #include <sys/stat.h>
14 
15 struct crawl_context {
16 	int max;    /*
17 	            ** number of folders we can hold in the folders array;
18 	            ** increased by CRAWL_NUMFOLDERS at a time
19 	            */
20 	int total;  /* how many `folders' actually has */
21 	char **folders;  /* the array of folders */
22 	int start;
23 	int foldp;
24 };
25 
26 /*
27 ** Add the folder name into the
28 ** list in a sorted fashion.
29 */
30 
31 static void
add_folder(char * fold,struct crawl_context * crawl)32 add_folder(char *fold, struct crawl_context *crawl)
33 {
34 	int i, j;
35 
36 	/* if necessary, reallocate the space for folder names */
37 	if (crawl->foldp >= crawl->max) {
38 		crawl->max += CRAWL_NUMFOLDERS;
39 		crawl->folders = mh_xrealloc(crawl->folders,
40 			crawl->max * sizeof(char *));
41 	}
42 
43 	for (i = crawl->start; i < crawl->foldp; i++)
44 		if (strcmp(fold, crawl->folders[i]) < 0) {
45 			for (j = crawl->foldp - 1; j >= i; j--)
46 				crawl->folders[j + 1] = crawl->folders[j];
47 			crawl->foldp++;
48 			crawl->folders[i] = fold;
49 			return;
50 		}
51 
52 	crawl->total++;
53 	crawl->folders[crawl->foldp++] = fold;
54 }
55 
56 static void
add_children(char * name,struct crawl_context * crawl)57 add_children(char *name, struct crawl_context *crawl)
58 {
59 	char *prefix, *child;
60 	struct stat st;
61 	struct dirent *dp;
62 	DIR * dd;
63 	int child_is_folder;
64 
65 	if (!(dd = opendir(name))) {
66 		admonish(name, "unable to read directory ");
67 		return;
68 	}
69 
70 	if (strcmp(name, ".") == 0) {
71 		prefix = mh_xstrdup("");
72 	} else {
73 		prefix = concat(name, "/", (void *)NULL);
74 	}
75 
76 	while ((dp = readdir(dd))) {
77 		/*
78 		** If the system supports it, try to skip processing of
79 		** children we know are not directories or symlinks.
80 		*/
81 		child_is_folder = -1;
82 #if defined(HAVE_STRUCT_DIRENT_D_TYPE)
83 		if (dp->d_type == DT_DIR) {
84 			child_is_folder = 1;
85 		} else if (dp->d_type != DT_LNK && dp->d_type != DT_UNKNOWN) {
86 			continue;
87 		}
88 #endif
89 		if (strcmp(dp->d_name, ".")==0 ||
90 				strcmp(dp->d_name, "..")==0) {
91 			continue;
92 		}
93 		child = concat(prefix, dp->d_name, (void *)NULL);
94 		/*
95 		** If we have no d_type or d_type is DT_LNK or DT_UNKNOWN,
96 		** stat the child to see what it is.
97 		*/
98 		if (child_is_folder == -1) {
99 			child_is_folder = (stat(child, &st) != -1 && S_ISDIR(st.st_mode));
100 		}
101 		if (child_is_folder) {
102 			/* add_folder saves child in the list, don't free it */
103 			add_folder(child, crawl);
104 		} else {
105 			mh_free0(&child);
106 		}
107 	}
108 
109 	closedir(dd);
110 	mh_free0(&prefix);
111 }
112 
113 static void
crawl_folders_body(struct crawl_context * crawl,char * dir,crawl_callback_t * callback,void * baton)114 crawl_folders_body(struct crawl_context *crawl, char *dir,
115 	crawl_callback_t *callback, void *baton)
116 {
117 	int i;
118 	int os = crawl->start;
119 	int of = crawl->foldp;
120 
121 	crawl->start = crawl->foldp;
122 
123 	add_children(dir, crawl);
124 
125 	for (i = crawl->start; i < crawl->foldp; i++) {
126 		char *fold = crawl->folders[i];
127 		int crawl_children = 1;
128 
129 		if (callback != NULL) {
130 			crawl_children = callback(fold, baton);
131 		}
132 
133 		if (crawl_children) {
134 			crawl_folders_body(crawl, fold, callback, baton);
135 		}
136 	}
137 
138 	crawl->start = os;
139 	crawl->foldp = of;
140 }
141 
142 void
crawl_folders(char * dir,crawl_callback_t * callback,void * baton)143 crawl_folders(char *dir, crawl_callback_t *callback, void *baton)
144 {
145 	struct crawl_context *crawl = mh_xcalloc(1, sizeof(*crawl));
146 	crawl->max = CRAWL_NUMFOLDERS;
147 	crawl->total = crawl->start = crawl->foldp = 0;
148 	crawl->folders = mh_xcalloc(crawl->max, sizeof(*crawl->folders));
149 
150 	crawl_folders_body(crawl, dir, callback, baton);
151 
152 	/*
153 	** Note that we "leak" the folder names, on the assumption that the
154 	** caller is using them.
155 	*/
156 	mh_free0(&(crawl->folders));
157 	mh_free0(&crawl);
158 }
159