我的C函数在系统目录上递归,在大的输入上递归。我怎样才能解决这个问题?
我试图编写一个程序,搜索给定目录及其所有子目录和文件(以及子目录的子目录和文件等),并打印出具有给定权限集的所有文件(我的C函数在系统目录上递归,在大的输入上递归。我怎样才能解决这个问题?,c,recursion,segmentation-fault,stack-overflow,directory-structure,C,Recursion,Segmentation Fault,Stack Overflow,Directory Structure,我试图编写一个程序,搜索给定目录及其所有子目录和文件(以及子目录的子目录和文件等),并打印出具有给定权限集的所有文件(int target\u perm) 它可以在较小的输入上正常工作,但当它必须在包含大量文件的目录上递归时,会返回分段错误(内核转储)。Valgrind揭示这是由于堆栈溢出造成的 有没有什么方法可以修复我的函数,使它可以处理任意大的目录 void recurse_dir(struct stat *sb, struct dirent *de, DIR *dr, int target
int target\u perm
)
它可以在较小的输入上正常工作,但当它必须在包含大量文件的目录上递归时,会返回分段错误(内核转储)
。Valgrind揭示这是由于堆栈溢出造成的
有没有什么方法可以修复我的函数,使它可以处理任意大的目录
void recurse_dir(struct stat *sb, struct dirent *de, DIR *dr, int target_perm, char* curr_path) {
if ((strcmp(".", de->d_name) != 0) && (strcmp("..", de->d_name) != 0)) {
char full_file_name[strlen(curr_path) + strlen(de->d_name)+1];
strcpy(full_file_name, curr_path);
strcpy(full_file_name + strlen(curr_path), de->d_name);
full_file_name[strlen(curr_path) + strlen(de->d_name)] = '\0';
if (stat(full_file_name, sb) < 0) {
fprintf(stderr, "Error: Cannot stat '%s'. %s\n", full_file_name, strerror(errno));
} else {
char* curr_perm_str = permission_string(sb);
int curr_perm = permission_string_to_bin(curr_perm_str);
free(curr_perm_str);
if ((curr_perm == target_perm )) {
printf("%s\n", full_file_name);
}
if (S_ISDIR(sb->st_mode)) {
DIR *dp;
struct dirent *dent;
struct stat b;
dp = opendir(full_file_name);
char new_path[PATH_MAX];
strcpy(new_path, full_file_name);
new_path[strlen(full_file_name)] ='/';
new_path[strlen(full_file_name)+1] ='\0';
if (dp != NULL) {
if ((dent = readdir(dp)) != NULL) {
recurse_dir(&b, dent, dp, target_perm, new_path);
}
closedir(dp);
} else {
fprintf(stderr, "Error: Cannot open directory '%s'. %s.\n", de->d_name, strerror(errno));
}
}
}
}
if ((de = readdir(dr)) != NULL) {
recurse_dir(sb, de, dr, target_perm, curr_path);
}
}
void recurse\u dir(struct stat*sb,struct dirent*de,dir*dr,int target\u perm,char*curr\u path){
如果((strcmp(“.”,de->d_name)!=0)和&(strcmp(“…”,de->d_name)!=0)){
char full_file_name[strlen(curr_path)+strlen(de->d_name)+1];
strcpy(完整文件名、当前路径);
strcpy(完整文件名+strlen(当前路径),de->d\u名);
完整文件名[strlen(curr_path)+strlen(de->d_name)]='\0';
if(stat(完整文件名,sb)<0){
fprintf(stderr,“错误:无法统计“%s”。%s\n”,完整的文件名,strerror(errno));
}否则{
char*curr\u perm\u str=权限字符串(sb);
int curr\u perm=权限字符串(curr\u perm\u str);
免费(curr_perm_str);
如果((电流=目标电流)){
printf(“%s\n”,完整文件名);
}
如果(S_ISDIR(sb->st_模式)){
DIR*dp;
结构方向*凹痕;
结构统计b;
dp=opendir(完整文件名);
char new_path[path_MAX];
strcpy(新路径、完整文件名);
新路径[strlen(完整文件名)]='/';
新路径[strlen(完整文件名)+1]='\0';
如果(dp!=NULL){
如果((dent=readdir(dp))!=NULL){
递归目录(&b、凹痕、dp、目标路径、新路径);
}
closedir(dp);
}否则{
fprintf(stderr,“错误:无法打开目录“%s”。%s.\n”,de->d_name,strerror(errno));
}
}
}
}
如果((de=readdir(dr))!=NULL){
递归目录(sb、de、dr、目标路径、当前路径);
}
}
这里的问题实际上不是递归,尽管我在下面已经解决了这个特定的问题。问题是,目录层次结构可能包含符号链接,这些链接使某些目录成为其父目录的别名。Ubuntu安装的一个示例:
$ ls -ld /usr/bin/X11
lrwxrwxrwx 1 root root 1 Jan 25 2018 /usr/bin/X11 -> .
$ # Just for clarity:
$ readlink -f /usr/bin/X11
usr/bin
因此,一旦遇到/usr/bin/X11
,就会进入一个无限循环。这将迅速耗尽堆栈,但摆脱递归不会解决问题,因为无限循环仍然是无限循环
您需要做的是:
- 避免使用以下符号链接,或
- (更好)避免使用解析为目录的符号链接,或
- 跟踪递归扫描期间遇到的所有目录,并检查以确保尚未检查任何新目录
struct stat
中的filetype字段),但它们将无法列出您可能感兴趣的一些文件(例如,当符号链接解析为您正在检查的目录结构之外的目录时)
一旦你解决了以上问题,你可能会考虑这些建议:
/usr
层次结构中文件的最大深度是16。)但使用的堆栈量是堆栈帧大小和最大递归深度的乘积,因此,如果堆栈帧较大,则递归容量较小char new_path[PATH_MAX];
将PATH\u MAX
字节添加到每个堆栈帧(在我的系统上,是4k)。这是对VLA完整文件名的补充。值得一提的是,我在64位Linux系统上编译了您的函数,发现堆栈帧大小是4280字节加上VLA的大小(为了对齐,四舍五入到16的倍数)。假设一个合理的文件层次结构在限制范围内,那么可能不会使用超过150Kb的堆栈。但是,如果您的系统具有更大的值PATH\u MAX
(在任何情况下,都不能依赖该值作为文件路径的最大大小),则该值可能会显著增加
好的样式要求为这些变量使用动态分配的内存。但更好的方法是避免使用这么多不同的缓冲区
strlen
函数需要扫描所有字节以查找NUL终止符。与高级语言中的字符串对象不同,C字符串不包含任何长度指示。所以当你这样做的时候:
char full_file_name[strlen(curr_path) + strlen(de->d_name)+1];
strcpy(full_file_name, curr_path);
strcpy(full_file_name + strlen(curr_path), de->d_name);
full_file_name[strlen(curr_path) + strlen(de->d_name)] = '\0';
最后,即使这些字符串的长度不会改变,也会扫描三次curr\u path
和两次de->d\u name
。与其这样做,不如将长度保存在局部变量中,以便可以重用
或者,您可以找到另一种连接字符串的方法。一个简单的可能性是动态分配内存
char* full_file_name;
asprintf(&full_file_name, "%s%s", curr_path, de->d_name);
char* newpath;
asprintf("%s/", full_file_path);
char* full_file_name;
int full_file_name_len = asprintf(&full_file_name, "%s%s\0",
curr_path, de->d_name);
if (full_file_name_len < 0) { /* handle error */ }
--full_file_name_len; /* Bytes written includes the \0 in the format */
/* Much later, instead of creating new_path: */
if (dp != NULL) {
full_file_name[full_file_name_len - 1] = '/';
if ((dent = readdir(dp)) != NULL) {
recurse_dir(&b, dent, dp, target_perm, full_file_name);
}
full_file_name[full_file_name_len - 1] = '\0';
closedir(dp);
}
char* curr_perm_str = permission_string(sb);
int curr_perm = permission_string_to_bin(curr_perm_str);
free(curr_perm_str);
int curr_perm = sb->st_mode & (S_IRWXU|S_IRWXG|S_IRWXO);
int curr_perm = sb->st_mode
& (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO);