xftw在./modutils-2.4.0/util/xftw.c中。
在ftw.c文件的开头有一大段注释,解释了这一族函数的由来。特摘录如下。
24 /*
25 modutils requires special processing during the file tree walk
26 of /lib/modules/<version> and any paths that the user specifies.
27 The standard ftw() does a blind walk of all paths and can end
28 up following the build symlink down the kernel source tree.
29 Although nftw() has the option to get more control such as not
30 automatically following symbolic links, even that is not enough
31 for modutils. The requirements are:
32
33 Paths must be directories or symlinks to directories.
34
35 Each directory is read and sorted into alphabetical order
36 before processing.
37
38 A directory is type 1 iff it was specified on a path statement
39 (either explicit or default) and the directory contains a
40 subdirectory with one of the known names and the directory name
41 does not end with "/kernel". Otherwise it is type 2.
42
43 In a type 1 directory, walk the kernel subdirectory if it exists,
44 then the old known names in their historical order then any
45 remaining directory entries in alphabetical order and finally any
46 non-directory entries in alphabetical order.
47
48 Entries in a type 1 directory are filtered against the "prune"
49 list. A type 1 directory can contain additional files which
50 are not modules nor symlinks to modules. The prune list skips
51 known additional files, if a distribution wants to store
52 additional text files in the top level directory they should be
53 added to the prune list.
54
55 A type 2 directory must contain only modules or symlinks to
56 modules. They are processed in alphabetical order, without
57 pruning. Symlinks to directories are an error in type 2
58 directories.
59
60 The user function is not called for type 1 directories, nor for
61 pruned entries. It is called for type 2 directories and their
62 contents. It is also called for any files left in a type 1
63 directory after pruning and processing type 2 subdirectories.
64 The user function never sees symlinks, they are resolved before
65 calling the function.
66
67 Why have different directory types? The original file tree
68 walk was not well defined. Some users specified each directory
69 individually, others just pointed at the top level directory.
70 Either version worked until the "build" symlink was added. Now
71 users who specify the top level directory end up running the
72 entire kernel source tree looking for modules, not nice. We
73 cannot just ignore symlinks because pcmcia uses symlinks to
74 modules for backwards compatibility.
75
76 Type 1 is when a user specifies the top level directory which needs
77 special processing, type 2 is individual subdirectories. But the
78 only way to tell the difference is by looking at the contents. The
79 "/kernel" directory introduced in 2.3.12 either contains nothing
80 (old make modules_install) or contains all the kernel modules using
81 the same tree structure as the source. Because "/kernel" can
82 contain old names but is really a type 2 directory, it is detected
83 as a special case.
84 */
这里面提到,一些应用程序会指定具体的路径,一些则只指定最上层的目录,在没有引入build符号链接前(build符号链接是引用内核头文件最可靠的方法,它会自动定位到正确的版本),linux原有的ftw和nftw函数都能工作,但是引入build符号链接后,指定最上层目录的应用程序将会遍历整个内核查找模块,在效率上不够好。因此,在这里区分出了2种目录。
一般来说,类型1对应于指定上层目录的情况。因为它包含非模块文件或不指向模块的符号链接,因此需要特殊对待——使用prune list过滤。遍历的次序是:内核子目录、按历史排序的已知的目录、按字母排序的目录、按字母排序的非目录项。类型1的目录遍历时,只有在完成prune list过滤后,才对剩余的文件使用用户提供的函数(类似于ftw中的fn参数)。
类型2的目录仅包含模块或指向模块的符号链接,一般来说,这就包括以/kernel结尾的目录路径。它们按字母顺序被依次处理,而且不使用prune list过滤,并且自始至终使用用户提供的函数进行处理。
对于这些目录的处理,有需要共同遵守的规则:
1) 路径必须指向目录或者是指向目录的符号链接。
2) 在处理前,目录需按字母排序。
现在回过头来看xftw,linux下ftw的对应物。首先看看prune_list。它定义在./modutils-2.4.0/util/alias.h中。
221 /*
222 * This is the list of pre-defined "prune"s,
223 * used to exclude paths from scan of /lib/modules.
224 * /etc/modules.conf can add entries but not remove them.
225 */
226 char *prune[] =
227 {
228 "modules.dep",
229 "modules.generic_string",
230 "modules.pcimap",
231 "modules.isapnpmap",
232 "modules.usbmap",
233 "modules.parportmap",
234 "System.map",
235 ".config",
236 "build", /* symlink to source tree */
237 "vmlinux",
238 "vmlinuz",
239 "bzImage",
240 "zImage",
241 ".rhkmvtag", /* wish RedHat had told me before they did this */
242 NULL /* marks the end of the list! */
421 };
310 /* Only external visible function. Decide on the type of directory and312
311 * process accordingly.
312 */
313 int xftw(const char *directory, xftw_func_t funcptr)
314 {
315 struct stat statbuf;
316 int ret, i, j, type;
317 xftw_tree_t *t;
318 struct xftw_dirent *c;
319
320 verbose("xftw starting at %s ", directory);
321 if (lstat(directory, &statbuf)) {
322 verbose("lstat on %s failed/n", directory);
323 return(0);
324 }
325 if (S_ISLNK(statbuf.st_mode)) {
326 char real[PATH_MAX];
327 verbose("resolving symlink to ");
328 if (!(directory = realpath(directory, real))) {
329 if (errno == ENOENT) {
330 verbose("%s: does not exist, dangling symlink ignored/n", real);
331 return(0);
332 }
333 perror("... failed");
334 return(-1);
335 }
336 verbose("%s ", directory);
337 if (lstat(directory, &statbuf)) {
338 error("lstat on %s failed ", directory);
339 perror("");
340 return(-1);
341 }
342 }
343 if (!S_ISDIR(statbuf.st_mode)) {
344 error("%s is not a directory/n", directory);
345 return(-1);
346 }
347 verbose("/n");
348
349 /* All returns after this point must be via cleanup */
350
351 if ((ret = xftw_readdir(directory, 0)))
352 goto cleanup;
353
354 t = tree; /* depth 0 */
355 type = 2;
356 for (i = 0 ; type == 2 && i < t->used; ++i) {
357 c = t->contents+i;
358 for (j = 0; tbtype[j]; ++j) {
359 if (strcmp(c->name, tbtype[j]) == 0 &&
360 S_ISDIR(c->statbuf.st_mode)) {
361 const char *p = directory + strlen(directory) - 1;
362 if (*p == '/')
363 --p;
364 if (p - directory >= 6 && strncmp(p-6, "/kernel", 7) == 0)
365 continue; /* "/kernel" path is a special case, type 2 */
366 type = 1; /* known subdirectory */
367 break;
368 }
369 }
370 }
371
372 if (type == 1) {
373 OPT_LIST *p;
374 /* prune entries in type 1 directories only */
375 for (i = 0 ; i < t->used; ++i) {
376 for (p = prunelist; p->name; ++p) {
377 c = t->contents+i;
378 if (strcmp(p->name, c->name) == 0) {
379 verbose("pruned %s/n", c->name);
380 *(c->name) = '/0'; /* ignore */
381 }
382 }
383 }
384 /* run known subdirectories first in historical order, "kernel" is now top of list */
385 for (i = 0 ; i < t->used; ++i) {
386 c = t->contents+i;
387 for (j = 0; tbtype[j]; ++j) {
388 if (*(c->name) &&
389 strcmp(c->name, tbtype[j]) == 0 &&
390 S_ISDIR(c->statbuf.st_mode)) {
391 if ((ret = xftw_type2(directory, c->name, 1, funcptr)))
392 goto cleanup;
393 *(c->name) = '/0'; /* processed */
394 }
395 }
396 }
397 /* any other directories left, in alphabetical order */
398 for (i = 0 ; i < t->used; ++i) {
399 c = t->contents+i;
400 if (*(c->name) &&
401 S_ISDIR(c->statbuf.st_mode)) {
402 if ((ret = xftw_type2(directory, c->name, 1, funcptr)))
403 goto cleanup;
404 *(c->name) = '/0'; /* processed */
405 }
406 }
407 /* anything else is passed to the user function */
408 for (i = 0 ; i < t->used; ++i) {
409 c = t->contents+i;
410 if (*(c->name)) {
411 verbose("%s found in type 1 directory %s/n", c->name, directory);
412 if ((ret = xftw_do_name(directory, c->name, &(c->statbuf), funcptr)))
413 goto cleanup;
414 *(c->name) = '/0'; /* processed */
415 }
416 }
417 }
418 else {
419 /* type 2 */
420 xftw_free_tree(0);
421 if ((ret = xftw_type2(directory, NULL, 0, funcptr)))
422 goto cleanup;
423 }
424
425 /* amazing, it all worked */
426 ret = 0;
427 cleanup:
428 for (i = 0; i < XFTW_MAXDEPTH; ++i)
429 xftw_free_tree(i);
430 return(ret);
431}
读过上面的注释,这个函数就不难理解,首先根据遍历的结果,构造一棵排序的树。这个树的定义也在同一文件。
104 struct xftw_dirent {
105 struct stat statbuf;
106 char *name;
107 char *fullname;
108 };
109
110 #define XFTW_MAXDEPTH 64 /* Maximum directory depth handled */
111
112 typedef struct {
113 struct xftw_dirent *contents;
114 int size;
115 int used;
116 } xftw_tree_t;
117
118 static xftw_tree_t tree[XFTW_MAXDEPTH];
tree数组的下标对应相应的路径深度。
312~333行的目的很明显,是将符号链接转为它实际指向的文件或目录,在这里只有目录可以接受。获取了目录的实际路径后,调用xftw_readdir,这个函数在同一文件。
217 /* Read a directory and sort it, ignoring "." and ".." */
218 static int xftw_readdir(const char *directory, int depth)
219 {
220 DIR *d;
221 struct dirent *ent;
222 verbose("xftw_readdir %s/n", directory);
223 if (!(d = opendir(directory))) {
224 perror(directory);
225 return(1);
226 }
227 while ((ent = readdir(d))) {
228 char *name;
229 struct xftw_dirent *f;
230 if (strcmp(ent->d_name, ".") == 0 ||
231 strcmp(ent->d_name, "..") == 0)
232 continue;
233 name = xftw_dir_name(directory, ent->d_name);
234 xftw_add_dirent(depth);
235 f = tree[depth].contents+tree[depth].used-1;
236 f->name = xstrdup(ent->d_name);
237 f->fullname = name; /* do not free name, it is in use */
238 if (lstat(name, &(f->statbuf))) {
239 perror(name);
240 return(1);
241 }
242 }
243 closedir(d);
244 qsort(tree[depth].contents, tree[depth].used, sizeof(*(tree[0].contents)), &xftw_sortdir);
245 return(0);
246 }
直到233行之前的代码都很简单,打开目录,忽略名为“.”和“..”子目录。然后使用xftw_dir_name合成这个子目录的路径。函数xftw_dir_name也在xftw.c中。
152 /* Concatenate directory name and entry name into one string.
153 * Note: caller must free result or leak.
154 */
155 static char *xftw_dir_name(const char *directory, const char *entry)
156 {
157 int i = strlen(directory);
158 char *name;
159 if (entry)
160 i += strlen(entry);
161 i += 2;
162 name = xmalloc(i);
163 strcpy(name, directory); /* safe, xmalloc */
164 if (*directory && entry)
165 strcat(name, "/"); /* safe, xmalloc */
166 if (entry)
167 strcat(name, entry); /* safe, xmalloc */
168 return(name);
169 }
函数xftw_add_dirent在必要时扩展xftw_tree_t中content的容量,也在同一文件。
135 /* Increment dirents used at this depth, resizing if necessary */
136 static void xftw_add_dirent(int depth)
137 {
138 xftw_tree_t *t = tree+depth;
139 int i, size = t->size;
140 if (++t->used < size)
141 return;
142 size += 10; /* arbitrary increment */
143 t->contents = xrealloc(t->contents, size*sizeof(*(t->contents)));
144 for (i = t->size; i < size; ++i) {
memset(&(t->contents[i].statbuf), 0, sizeof(t->contents[i].statbuf));
145 t->contents[i].name = NULL;
146 t->contents[i].fullname = NULL;
147 }
148 t->size = size;
149 }
回到函数xftw_readdir中,235~241行确认找到的路径有效,并保存于tree中。在遍历结束后,使用xftw_sortdir排序。该函数很简单,在同一文件里。
211 /* Sort directory entries into alphabetical order */
212 static int xftw_sortdir(const void *a, const void *b)
213 {
214 return(strcmp(((struct xftw_dirent *)a)->name, ((struct xftw_dirent *)b)->name));
213 }
从xftw_readdir返回时,第一层目录下的子目录全部排序保存在tree[0]中了。然后,在xftw函数的347行for循环里,开始检查是否类型1的目录。在这里,我们终于知道那些是所谓已知的目录名和所谓的历史顺序,也就是tbtype数组对应目录名和下标,这个数组在./modutils-2.4.0/util/alias.h。不过里面要除去“kernel”,包含它的目录属于第2类。
5 /*
6 * tbpath and tbtype are used to build the complete set of paths for finding
7 * modules, but only when we search for individual directories, they are not
8 * used for [boot] and [toplevel] searches.
9 */
10 static char *tbpath[] =
11 {
12 "/lib/modules",
13 NULL /* marks the end of the list! */
14 };
15
16 char *tbtype[] =
17 {
18 "kernel", /* as of 2.3.14 this must be first */19
20 "fs",
21 "net",
22 "scsi",
23 "block",
24 "cdrom",
25 "ipv4",
26 "ipv6",
27 "sound",
28 "fc4",
30 "video",
31 "misc",
32 "pcmcia",
33 "atm",
34 "usb",
35 "ide",
36 "ieee1394",
37 "mtd",
38 NULL /* marks the end of the list! */
39 };
221 /*
222 * This is the list of pre-defined "prune"s,
223 * used to exclude paths from scan of /lib/modules.
224 * /etc/modules.conf can add entries but not remove them.
225 */
226 char *prune[] =
227 {
228 "modules.dep",
229 "modules.generic_string",
230 "modules.pcimap",
231 "modules.isapnpmap",
232 "modules.usbmap",
233 "modules.parportmap",
234 "System.map",
235 ".config",
236 "build", /* symlink to source tree */
237 "vmlinux",
238 "vmlinuz",
239 "bzImage",
240 "zImage",
241 ".rhkmvtag", /* wish RedHat had told me before they did this */
242 NULL /* marks the end of the list! */
243 };
只要找到一个已知目录,就确定是第1类目录,立即跳出循环。然后在366行,按照注释说明,对照prunelist进行过滤。过滤完成后,376行的for循环首先处理已知目录。函数xftw_type2在同一文件里。
248 /* Process a type 2 directory */
249 int xftw_type2(const char *directory, const char *entry, int depth, xftw_func_t funcptr)
250 {
251 int ret, i;
252 xftw_tree_t *t = tree+depth;
253 struct stat statbuf;
254 char *dirname = xftw_dir_name(directory, entry);
255
256 verbose("type 2 %s/n", dirname);
257 if (depth > XFTW_MAXDEPTH) {
258 error("xftw_type2 exceeded maxdepth/n");
259 ret = 1;
260 goto cleanup;
261 }
262 if ((ret = xftw_readdir(dirname, depth)))
263 goto cleanup;
264
265 t = tree+depth;
266 /* user function sees type 2 directories */
267 if ((ret = lstat(dirname, &statbuf)) ||
268 (ret = xftw_do_name("", dirname, &statbuf, funcptr)))
269 goto cleanup;
270
271 /* user sees all contents of type 2 directory, no pruning */
272 for (i = 0; i < t->used; ++i) {
273 struct xftw_dirent *c = t->contents+i;
274 if (S_ISLNK(c->statbuf.st_mode)) {
275 if (!stat(c->name, &(c->statbuf))) {
276 if (S_ISDIR(c->statbuf.st_mode)) {
277 error("symlink to directory is not allowed, %s ignored/n", c->name);
278 *(c->name) = '/0'; /* ignore it */
279 }
280 }
281 }
282 if (!*(c->name))
283 continue;
284 if (S_ISDIR(c->statbuf.st_mode)) {
285 /* recursion is the curse of the programming classes */
286 ret = xftw_type2(dirname, c->name, depth+1, funcptr);
287 if (ret)
288 goto cleanup;
289 }
290 else if ((ret = xftw_do_name(dirname, c->name, &(c->statbuf), funcptr)))
291 goto cleanup;
292 *(c->name) = '/0'; /* processed */
293 }
294
295 ret = 0;
296 cleanup:
297 free(dirname);
298 return(ret);
299 }
254行构造一个完整路径。在255行我们可以知道,已知目录在这里是按第2类目录来处理的,根据注释这些目录里都是模块名或者指向模块的符号链接。查找目录层深是有限制的,这里是XFTW_MAXDEPTH,定义为64。只要不超过限制,就通过xftw_do_name函数在dirname路径内来查找目的模块。函数也在同一文件里。
171 /* Call the user function for a directory entry */
172 static int xftw_do_name(const char *directory, const char *entry, struct stat *sb, 173 xftw_func_t funcptr)
173 {
174 int ret = 0;
175 char *name = xftw_dir_name(directory, entry);
176
177 if (S_ISLNK(sb->st_mode)) {
178 char real[PATH_MAX], *newname;
179 verbose("resolving %s symlink to ", name);
180 if (!(newname = realpath(name, real))) {
181 if (errno == ENOENT) {
182 verbose("%s: does not exist, dangling symlink ignored/n", real);
183 goto cleanup;
184 }
185 perror("... failed");
186 goto cleanup;
187 }
188 verbose("%s ", newname);
189 if (lstat(newname, sb)) {
190 error("lstat on %s failed ", newname);
191 perror("");
192 goto cleanup;
193 }
194 free(name);
195 name = xstrdup(newname);
196 }
197
198 if (!S_ISREG(sb->st_mode) &&
199 !S_ISDIR(sb->st_mode)) {
200 error("%s is not plain file nor directory/n", name);
201 goto cleanup;
202 }
203
204 verbose("user function %s/n", name);
205 ret = (*funcptr)(name, sb);
206 cleanup:
207 free(name);
208 return(ret);
209 }
函数首先处理符号链接(见177行)。获得真实路径名后,通过传入的函数指针funcptr解析保存所求的路径。这个函数实际上就是config_add。前面已经看过。它通过filter_by_file,filter_by_dir来过滤、查找路径。如果config_add查找成功并且只查找第一个匹配,返回1。xftw_type2就结束了。不过在这里如果dirname为目录名,config_add会返回0。如果为普通文件,则对应的tree项为空。
在dirname为目录名情况下,继续进入272行的循环,分别处理dirname的子目录。首先确保符号链接不是指向目录。对于子目录递归调用xftw_type2,而对于文件则使用xftw_do_name处理。
回到xftw,注意每处理完一个tree项,该项的name都会被置空,防止重复处理。在389行就开始按字母顺序处理路径了。首先处理子目录,然后在399行处理文件。
如果在一开始的指定路径名里找不到包含已知目录名(除kernel外)的项,就认为是第2类型,直接使用xftw_type2处理。至此xftw完毕。
经过长途跋涉,现在终于返回到config_lstmod中。最终该函数完成在modpath中漫长的查找,在list中带着所期待的路径返回了。回到search_module_path,如果用base查找不到路径,就是用base.o再找一遍,如果还是不行而且允许使用zlib,则用base.o.gz再找。只要找到这些路径,search_module_path只返回第一个路径名(因此,查找的顺序很重要)。