如何在macOS下用C语言判断文件夹是否为别名?
问题描述
我在macOS下编写了一个递归程序,用于在指定目录树中查找特定文件。当前遇到的问题是:当程序找到指向同一分支上层目录的文件夹别名时,会陷入无限递归,一段时间后因段错误崩溃(递归层数仅约252层,这个数值偏低)。我已尝试修改代码跳过符号链接,但仍会遍历这些别名。
目录结构示例
每行左侧为文件夹名称,右侧为其内容:
- folder1: folder2
- folder2: folder1(别名)
代码片段
(已补充完整,未包含内存释放和错误检查)
char *name = //target file name int myfind(){ DIR *curr_dir = opendir("."); struct dirent *next_entry; struct stat info; char *entry_name; char *path; while((next_entry = readdir(curr_dir)) != NULL){ entry_name = next_entry->d_name; //ignore . and .. if(strcmp(entry_name,".") == 0 || strcmp(entry_name,"..") == 0) continue; stat(entry_name, &info); if(strncmp(name, entry_name, 255) == 0){ path = realpath(entry_name, NULL); //print info about file found } if(S_ISDIR(info.st_mode) && !S_ISLNK(info.st_mode)){ char *prev_path = getwd(NULL); chdir(entry_name); myfind(); chdir(prev_path); } } }
解决方案
问题根源
stat()的局限性:你当前使用的stat()会自动解析符号链接(包括macOS的文件夹别名),返回的是目标目录的信息而非别名本身。这导致!S_ISLNK(info.st_mode)的判断失效,程序依旧会进入别名指向的目录,触发无限递归。- 栈溢出:递归层数达到252时崩溃,是因为进程栈空间有限,每次递归都会在栈上分配局部变量,层数过多会导致栈溢出,触发段错误。
修复方案
1. 替换stat()为lstat()
lstat()不会解析符号链接,能获取文件(包括别名、软链接)本身的属性,这样就能正确判断是否为链接文件,避免进入别名指向的目录。
2. 追踪已访问目录的inode和设备号
即使过滤了链接,若遇到硬链接目录(macOS下虽受限但仍需考虑)或别名指向已访问过的目录,仍可能触发循环。因此需要记录每个目录的st_dev(设备号)和st_ino(inode号),确保同一目录只被访问一次。
修改后的完整代码
#include <stdio.h> #include <dirent.h> #include <sys/stat.h> #include <string.h> #include <stdlib.h> #include <unistd.h> char *name = NULL; // 目标文件名,需外部赋值 // 记录已访问目录的设备号与inode号 typedef struct VisitedDir { dev_t dev; ino_t ino; struct VisitedDir *next; } VisitedDir; // 检查目录是否已访问过 int is_dir_visited(VisitedDir *head, dev_t dev, ino_t ino) { VisitedDir *curr = head; while (curr != NULL) { if (curr->dev == dev && curr->ino == ino) { return 1; } curr = curr->next; } return 0; } // 将目录添加到已访问列表 void add_visited_dir(VisitedDir **head, dev_t dev, ino_t ino) { VisitedDir *new_node = malloc(sizeof(VisitedDir)); if (!new_node) return; new_node->dev = dev; new_node->ino = ino; new_node->next = *head; *head = new_node; } // 释放已访问列表内存 void free_visited_dir(VisitedDir *head) { VisitedDir *curr = head; while (curr != NULL) { VisitedDir *next = curr->next; free(curr); curr = next; } } int myfind(VisitedDir **visited) { DIR *curr_dir = opendir("."); if (!curr_dir) return -1; struct dirent *next_entry; struct stat info; char *entry_name; char *path; // 记录当前目录到已访问列表 if (fstat(dirfd(curr_dir), &info) == 0) { if (!is_dir_visited(*visited, info.st_dev, info.st_ino)) { add_visited_dir(visited, info.st_dev, info.st_ino); } } while((next_entry = readdir(curr_dir)) != NULL) { entry_name = next_entry->d_name; if(strcmp(entry_name,".") == 0 || strcmp(entry_name,"..") == 0) continue; // 用lstat获取文件本身属性,不解析链接 if (lstat(entry_name, &info) == -1) continue; // 匹配目标文件 if(strncmp(name, entry_name, 255) == 0) { path = realpath(entry_name, NULL); if (path) { printf("找到文件: %s\n", path); free(path); } } // 仅处理未访问过的非链接目录 if(S_ISDIR(info.st_mode) && !S_ISLNK(info.st_mode)) { if (is_dir_visited(*visited, info.st_dev, info.st_ino)) { continue; } char *prev_path = getwd(NULL); if (!prev_path) continue; if (chdir(entry_name) == 0) { myfind(visited); chdir(prev_path); } free(prev_path); } } closedir(curr_dir); return 0; } // 程序入口 int main(int argc, char *argv[]) { if (argc < 2) { printf("使用方式: %s <目标文件名>\n", argv[0]); return 1; } name = argv[1]; VisitedDir *visited = NULL; myfind(&visited); free_visited_dir(visited); return 0; }
额外提示
- macOS的文件夹别名是Finder层面的特殊文件,本质是带资源分支的文件,但
lstat()仍能识别其为链接文件,因此过滤逻辑有效。 - 若要支持更深层次的目录遍历,可将递归实现改为迭代(用栈或队列存储待访问目录),彻底避免栈溢出问题。
内容的提问来源于stack exchange,提问作者Ernaldo
相关产品推荐
相关产品推荐

