为什么readdir()系统调用不应该按照应有的方式工作(意外输出)?

时间:2014-05-14 19:41:21

标签: c unix operating-system filesystems

我正在写一个C程序,

void printdir (char*);

int main () {
    printf ("Directory scan of /home: \n");
    printdir ("/home/fahad/");
    exit (0);
}

void printdir (char *dir) {
    struct dirent *entry;
    DIR *dp = opendir (dir);

    if (dp == NULL) {
        fprintf (stderr, "Cannot open dir:%s\n", dir);
        return;
    }

    chdir (dir);
    while ((entry = readdir(dp)) != NULL)
        printf ("%s\n",entry -> d_name);
    closedir (dp);
}

有趣的是,它以意想不到的方式显示输出。 考虑到在UNIX中创建目录时的事实。前两个条目在此目录中创建,一个是.,另一个是..。所以基本上他们的inode数字应该小于通过mkdir ()open ()创建的目录条目(分别用于目录和文件)。

我的问题是,readdir ()系统调用以什么顺序读取目录条目?因为我不会先输入...。 为什么会这样?

3 个答案:

答案 0 :(得分:3)

尝试跳过“。”和“......”条目如下:

DIR* dirp;
struct dirent  *dp=NULL;
char* fname;
if( !(dirp=opendir(dname)) ) {
    int ec=errno;
    printf("completed:-1:cannot opendir %s (%d)\n",dname,ec);
    return(-1);
}
while ((dp = readdir(dirp)) != NULL) {
    if( strcmp(dp->d_name,".")==0 ) continue;
    if( strcmp(dp->d_name,"..")==0 ) continue;
    fname=dp->d_name;
    sprintf(pathname,"%s/%s",dname,fname);
}

请参阅this answer,其中指出由于订单未被声明为可预测,因此不应假设任何订单。上面的代码将给出一个如何处理(避免)这些条目的示例(在遍历目录层次结构的典型用例中)。订单可能基于目录inode中出现的文件的顺序。

答案 1 :(得分:2)

readdir()不会以任何特定顺序返回条目。正如其他人所提到的,订单将取决于所讨论的特定文件系统。

例如,Berkeley UFS文件系统使用未排序的链接列表。请参阅http://ptgmedia.pearsoncmg.com/images/0131482092/samplechapter/mcdougall_ch15.pdf第744页的direct结构说明。目录的二进制内容由可变长度记录流组成,每个记录包含inode编号,记录长度,字符串长度(文件名)和字符串数据本身。 readdir()通过遍历链表(使用记录长度来了解每条记录相对于前一条记录的开始位置)并返回它找到的任何内容来工作。

通常不会优化记录列表,因此文件名按创建文件的顺序显示在列表中(或多或少)。但不完全是,因为如果它们足够小以适合,那么孔(由删除的文件产生)将填充新的文件名。

现在,并非所有文件系统都以UFS的方式表示目录。将目录数据保存在二叉树中的文件系统可以选择将readdir()实现为该树的有序遍历,该树将呈现按其用作树的键的任何属性排序的文件。或者它可能使用预订遍历,它不会按排序顺序返回记录。

由于应用程序无法知道文件系统实现的性质(并且每个已安装的卷都可能使用不同的文件系统),因此应用程序不应该假设任何关于文件系统的顺序readdir()返回的条目。如果他们要求对条目进行排序,他们必须将整个目录读入内存并进行自己的排序。

这就是为什么,例如,ls命令在针对大目录运行时可能需要很长时间才能显示输出。它需要对整个名称列表进行排序(并确定最长的名称,以便计算列宽),然后才能显示任何输出。这也是ls -1U(在一列中禁用排序和显示)将立即在这些目录上产生输出的原因。

答案 2 :(得分:2)

在此目录中创建前两个条目。 ...其他是...所以基本上他们的inode数量应该小于通过mkdir()或open()创建的目录条目(分别用于目录和文件)。

是的,您对inode编号的理解是正确的。为了验证这一点,我们可以写 简单的c ++程序,用于在地图中存储inode / name。

 std::map<ino_t, std::string>  entries;
 std::pair<ino_t, std::string> en;
 while ((entry = readdir(dp)) != NULL) {
          en.first = entry->d_ino;
          en.second = entry->d_name;
          entries.insert(en);
        printf ("%s\n",entry -> d_name);
  }


  "entries in GDB"
  ================
  [5114862] = "..",
  [5114987] = ".",
  [5115243] = "taop",
  [5115623] = "c++11_study",
  [5115651] = "volume-3",
  [5115884] = "gtkmm",
  [5116513] = "basic",
  [5116733] = "program",
  [5116794] = "bakwas",
  [5116813] = "a.out",
  [5116818] = "foo",

这样我们就可以验证inode数量的顺序和“。” &安培; “......”不到 其他目录&amp;文件条目。

我的问题是,readdir()系统调用以什么顺序读取目录条目?因为我没有先得到谁。并且......为什么会这样?

来自本书 “由W. Richard Stevens在UNIX®环境中进行高级编程” , 我们可以得到以下内容:

opendir函数初始化事物,以便第一个readdir读取目录中的第一个条目。目录中条目的顺序取决于实现,通常不是按字母顺序排列的。所以他们的订单没有定义,对于上面的程序,readdir()按以下顺序给出。

Output from readdir()
=====================
c++11_study
taop
volume-3
basic
.
gtkmm
foo
program
a.out
..
bakwas