我正在读以下著名的书。
在最后一章第 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;
}
#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);
}