自定义 ls 实现中未对目录条目进行排序

问题描述 投票:0回答:1

我一直在尝试使用 C 中的系统调用来实现“ls”命令,但我遇到了一个小问题。无论我如何尝试,我似乎都无法对目录条目进行排序。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <time.h>
#include <sys/stat.h>
#include <pwd.h>
#include <stdbool.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <limits.h>

#define BUFF_MAX 1024

char* octal_to_str(int permissions){
    char octal_str[4];
    sprintf(octal_str, "%o", permissions);
    char perm_str[10] = {0};
    for (int i = 0; i < 3; i++){
        switch (octal_str[i]) {
        case '0':
            strcat(perm_str, "---");
            break;
        case '1':
            strcat(perm_str, "--x");
            break;
        case '2':
            strcat(perm_str, "-w-");
            break;
        case '3':
            strcat(perm_str, "-wx");
            break;
        case '4':
            strcat(perm_str, "r--");
            break;
        case '5':
            strcat(perm_str, "r-x");
            break;
        case '6':
            strcat(perm_str, "rw-");
            break;
        case '7':
            strcat(perm_str, "rwx");
            break;
        
        default:
            break;
        }
    }
    return strdup(perm_str);
}

char* add_dir_to_perms(bool is_dir,char* perms){
    char final[11] = {0};
    is_dir ? strcat(final, "d") : strcat(final, "-");
    return strdup(strcat(final, perms));
}

void list_verbose(const char *dirname, struct dirent* entry){
    char path[BUFF_MAX];
    snprintf(path, sizeof(path), "%s/%s", dirname, entry->d_name);
    
    struct stat file_stat;
    if(stat(path, &file_stat) == -1){
        perror("Error getting file status");
        return;
    }

    char* last_modified = ctime(&file_stat.st_mtime);
    last_modified[strlen(last_modified) - 1] = '\0';
    
    printf("%s %ld %s %s %ld %s %s\n",
        add_dir_to_perms(S_ISDIR(file_stat.st_mode),octal_to_str(file_stat.st_mode & 0777)),
        file_stat.st_nlink,
        getpwuid(file_stat.st_uid)->pw_name,
        getpwuid(file_stat.st_gid)->pw_name,
        file_stat.st_size,
        last_modified,
        entry->d_name
    );
}

void list(struct dirent* entry){
    printf("%s ", entry->d_name);
}

int entry_cmp(const void* a, const void* b){
    struct dirent* entry_a = (struct dirent*) a;
    struct dirent* entry_b = (struct dirent*) b;
    return strcasecmp(entry_a->d_name, entry_b->d_name);
}

void ls(const char* dirname, bool recurse, bool verbose){
    char path[BUFF_MAX];
    DIR* dir = opendir(dirname);
    char* subdir_names[BUFF_MAX];
    unsigned subdir_num = 0;
    if(dir == NULL) {
        perror("Error opening directory");
        exit(EXIT_FAILURE);
    }
    struct dirent* entry;

    struct dirent* entries[BUFF_MAX];
    unsigned entry_count = 0;

    struct stat file_stat;

    
    if(recurse) printf("%s:\n", dirname);
    while((entry = readdir(dir)) != NULL){

        if(entry->d_name[0] == '.') 
            continue;

        entries[entry_count++] = entry;

    }

    qsort(entries, entry_count, sizeof(struct dirent*), &entry_cmp);
    
    for(unsigned i = 0; i < entry_count; i++){
        entry = entries[i];
        if(verbose) 
            list_verbose(dirname, entry);
        else 
            list(entry);

        snprintf(path, sizeof(path), "%s/%s", dirname, entry->d_name);
        if(stat(path, &file_stat) == -1){
            perror("Error getting file status");
            return;
        }
        if(S_ISDIR(file_stat.st_mode)) subdir_names[subdir_num++] = strdup(path);
    }

    printf("\n\n");
    for(unsigned i = 0; recurse && i < subdir_num; i++) {
        ls(subdir_names[i], recurse, verbose);
        free(subdir_names[i]);
    }

    closedir(dir);
}


int main(int argc, char* argv[]){
    bool VERBOSE = false;
    bool RECURSIVE = false;

    if(argc < 1|| argc > 3){
        fprintf(stderr, "Usage: %s [-lR] <directory>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    int opt;
    while ((opt = getopt(argc, argv, "lR")) != -1) {
        switch (opt) {
            case 'l':
                VERBOSE = true;
                break;
            case 'R':
                RECURSIVE = true;
                break;
            default:  
                fprintf(stderr, "Usage: %s [-lR] <directory>\n", argv[0]);
                exit(EXIT_FAILURE);
        }
    }

    char* dirname;
    char buff[1024];
    if (optind < argc)
        dirname = argv[optind];
    else if ((argc == 3 && RECURSIVE && VERBOSE) ||(argc == 2 && RECURSIVE) || (argc == 2 && VERBOSE) || (argc == 1)){
        dirname = getcwd(buff, sizeof(buff));
    }else {
        fprintf(stderr, "No directory specified\n");
        exit(EXIT_FAILURE);
    }

    struct stat path_stat;
    if (stat(dirname, &path_stat) == -1) {
        perror("Error accessing directory");
        exit(EXIT_FAILURE);
    }

    if (!S_ISDIR(path_stat.st_mode)) {
        fprintf(stderr, "%s is not a directory\n", dirname);
        exit(EXIT_FAILURE);
    }

    ls(dirname, RECURSIVE, VERBOSE);

    return 0;
}

我尝试使用 gdb 来分析幕后发生的事情,但它似乎让我对目录条目名称感到困惑。

entry_cmp (a=0x7fffffffb160, b=0x7fffffffb168) at Ex-2/myls.c:89
89          struct dirent* entry_a = (struct dirent*) a;
(gdb) step
90          struct dirent* entry_b = (struct dirent*) b;
(gdb) step
91          return strcasecmp(entry_a->d_name, entry_b->d_name);
(gdb) p entry_a->d_name
$3 = "UUU\000\000\250\223UUUU\000\000\300\223UUUU\000\000ؓUUUU\000\000\360\223UUUU\000\000\b\224UUUU\000\000 \224UUUU\000\000\070\224UUUU\000\000P\224UUUU", '\000' <repeats 188 times>
(gdb) p entry_b->d_name
$4 = "UUU\000\000\300\223UUUU\000\000ؓUUUU\000\000\360\223UUUU\000\000\b\224UUUU\000\000 \224UUUU\000\000\070\224UUUU\000\000P\224UUUU", '\000' <repeats 196 times>

排序不进行的原因是什么?

c filesystems system-calls
1个回答
1
投票

从返回指针上的readdir

手册页

成功后,

readdir()

返回指向
dirent
结构的指针。 (此结构可能是静态分配的;请勿尝试对其进行
free(3)
。)

这里重要的一点是关于“静态分配”的。这意味着

readdir

 函数可能只有一个 
dirent
 结构,并且它不断返回指向同一对象的指针。通过打印所有指针的值,可以在调试器中轻松检查这一点。

这意味着您需要复制结构体,而不是指针。

解决方案是将

entries

更改为结构体对象数组,而不是指针,然后复制返回的结构体:

// ... struct dirent entries[BUFF_MAX]; // Array of objects, not pointers // ... entries[entry_count++] = *entry; // Copy the object, not the pointer // ...
    
© www.soinside.com 2019 - 2024. All rights reserved.