通过系统调用实现简单版本的readdir

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

我正在读以下著名的书。

  • B. W. Kernighan 和 D. M. Ritchie,《C 编程语言》,第二版,Prentice Hall,1988 年。

在最后一章第 8.6 节中,作者给出了一个程序示例,该程序打印目录及其可能的子目录中“所有”文件的大小。下面是对书中给出的代码稍作修改。通过这个例子,作者试图借助 系统调用

opendir
readdir
closedir
open 来展示众所周知的函数 read
close
stat
 的实现。 
fstat
以下代码与书中原始代码之间的主要区别在于,在函数中

read_dir

作者使用了

while (read(dp->fd, (char *) &dirbuf, sizeof(dirbuf)) == sizeof(dirbuf))

我已替换为

while (ld = read_dirent(dp->fd))

事实上,作者使用 
read

在每次循环迭代中获取目录条目,并用它填充

dirbuf
。显然,由于不同的文件系统和目录条目结构,这在现代 Linux 内核上不起作用,正如
here
所指出的。我想实现一个 read_dirent 函数,该函数在每次调用时获取一个目录条目并返回指向该条目的指针。为此,我发现我必须通过阅读
this
使用
getdents
系统调用。我当前版本的 read_dirent 仅适用于没有子目录的目录。我有一些扩展这个的想法,但我不确定它们是否真的有效。这是问题。

通过较低级别的系统调用(不一定是
read_dirent

,如果您知道其他事情)来实现

getdents
,这也适合于
教学
目的,什么是有效的方法?

代码

这是一个文件中的全部代码。我已经对此进行了测试,它可以编译,没有任何错误。我尝试通过

/*----*/

形式的注释来分隔不同的部分。您可以通过像

gdb
这样的调试器跟踪整个过程来获得良好的洞察力。
/* This program prints sizes of all files within a directory, recursively. */

void fsize(char *);

int main(int argc, char **argv)
{
    // Default is the current directory.
    if (argc == 1)
        fsize(".");
    else
        while (--argc > 0)
            fsize(*++argv);
    
    return 0;
}

/*-------------------------------------------------------------------------*/

#include <stdio.h>
#include <sys/stat.h>

void dirwalk(char *, void (*)(char *));

// Print size of a file.
void fsize(char *name)
{
    struct stat stbuf;

    if (stat(name, &stbuf) == -1)
    {
        fprintf(stderr, "fsize: cannot access %s\n", name);
        return;
    }
    if (S_ISDIR(stbuf.st_mode))
    {
        dirwalk(name, fsize);
        return;
    }
    printf("%8ld %s\n", stbuf.st_size, name);
}

/*-------------------------------------------------------------------------*/

#define MAXPATH 1024
#define NAMEMAX 256                 // Longest filename component; system-dependent

typedef struct                      // Portable directory entry
{
    long ino;                       // Inode number
    char name[NAMEMAX + 1];         // Name
} DirEnt;

typedef struct                      // Minimal Dir; no buffering, etc
{
    int    fd;                      // File descriptor for directory
    DirEnt d;                       // The directory entry
} Dir;

Dir *open_dir(char *);
DirEnt *read_dir(Dir *);
void close_dir(Dir *);

/*-------------------------------------------------------------------------*/

#include <string.h>

// Apply fcn to all files in dir.
void dirwalk(char *dir, void(*fcn)(char *))
{
    char name[MAXPATH];
    DirEnt *dp;
    Dir *dfd;

    if ((dfd = open_dir(dir)) == NULL)
    {
        fprintf(stderr, "dirwalk: cannot open %s\n", dir);
        return;
    }
    while ((dp = read_dir(dfd)) != NULL)
    {
        // Skip self and parent directories.
        if (strcmp(dp->name, ".") == 0 || strcmp(dp->name, "..") == 0)
            continue;
        // Plus 2 accounts for for / and the null terminator.
        if (strlen(dir) + strlen(dp->name) + 2 > sizeof(name))
            fprintf(stderr, "dirwalk: name %s/%s too long\n", dir, dp->name);
        else
        {
            sprintf(name, "%s/%s", dir, dp->name);
            (*fcn)(name);
        }
    }
    close_dir(dfd);
}

/*-------------------------------------------------------------------------*/

#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/syscall.h>

#define BUFSIZE 1024

// Open a directory for read_dir calls.
Dir *open_dir(char *dirname)
{
    int fd;
    struct stat stbuf;
    Dir *dp;

    if ((fd = open(dirname, O_RDONLY, 0)) == -1 ||
        fstat(fd, &stbuf) == -1 ||
        !S_ISDIR(stbuf.st_mode) ||
        (dp = (Dir *)(malloc(sizeof(Dir)))) == NULL)
    {
        return NULL;
    }
    dp->fd = fd;

    return dp;
}

// Close directory opened by opendir.
void close_dir(Dir *dp)
{
    if (dp)
    {
        close(dp->fd);
        free(dp);
    }
}

/*-------------------------------------------------------------------------*/

typedef struct linux_dirent         // Local directory entry 
{   
    long           d_ino;           // Inode number
    off_t          d_off;           // Offset to next linux_dirent
    unsigned short d_reclen;        // Length of this linux_dirent
    char           d_name[];        // Name
} LinuxDirEnt;

LinuxDirEnt *read_dirent(int);

/*-------------------------------------------------------------------------*/

// Read directory enteries in sequence.
DirEnt *read_dir(Dir *dp)
{
    // Local directory structure
    LinuxDirEnt *ld;
    // Portable directory structure           
    static DirEnt d;
    
    while (ld = read_dirent(dp->fd))
    {
        // Slot is not in use.
        if (ld->d_ino == 0)
            continue;
        d.ino = ld->d_ino;
        strncpy(d.name, ld->d_name, NAMEMAX);
        // Ensure termination.
        d.name[NAMEMAX] = '\0';
        return &d;
    }
    return NULL;
}

LinuxDirEnt *read_dirent(int fd)
{
    static int nread = 0;
    static int bpos = 0;
    static char buf[BUFSIZE];
    static LinuxDirEnt *d;
    
    if (nread == 0)
    {
        nread = syscall(SYS_getdents, fd, buf, BUFSIZE);
        if (nread == 0)
            return NULL;
        bpos = 0;
    }
    else
        bpos += d->d_reclen;
    d = (LinuxDirEnt *)(buf + bpos);
    nread -= d->d_reclen;

    return d;
}


c linux directory filesystems system-calls
1个回答
0
投票

#include <stdio.h> #include <unistd.h> #include <dirent.h> #include <sys/stat.h> off_t count(char *path, off_t *nfiles, off_t *ndirs) { off_t size = 0; char newpath[PATH_MAX+1]; DIR *d; struct dirent *dir; struct stat st; d = opendir(path); if (d) { while ((dir = readdir(d)) != NULL) { if(dir -> d_name[0] == '.' && ( dir -> d_name[1] == 0 || dir -> d_name[1] == '.' )) continue; switch(dir -> d_type) { case DT_REG: *nfiles += 1; strcpy(newpath, path); strcat(newpath, "/"); strcat(newpath, dir -> d_name); if(!stat(newpath, &st)) { size += st.st_size; } else { /* handle error */ } break; case DT_DIR: *ndirs += 1; strcpy(newpath, path); strcat(newpath, "/"); strcat(newpath, dir -> d_name); size += count(newpath, nfiles, ndirs); break; default: break; } } closedir(d); } return size; } int main(void) { off_t ndirs = 0, nfiles = 0, size; size = count(".", &nfiles, &ndirs); printf("Total %zu bytes in %zu files placed in %zu directories\n", (size_t)size, (size_t)nfiles, (size_t)ndirs); }

https://godbolt.org/z/dbqMETb1c

© www.soinside.com 2019 - 2024. All rights reserved.