使用C语言在POSIX中操作目录

6
我会尽力协助你完成一项Linux课程的家庭作业。在多次尝试之后,我决定寻求帮助。考虑到我是本学期的远程学生,无法前往校园接受辅导。这项作业要求我们编写一个程序,与POSIX中的“pwd”命令执行相同的基本功能,即显示当前目录的绝对路径。除主函数外,我们需要使用三个函数。同时,我们不允许使用“getcwd”命令。下面列出了这些函数及其作用:
- “inum_to_filename”:接受三个参数(要转换的i节点号,指向写入名称的缓冲区的指针和缓冲区的大小)。返回值为空。具体实现如下: 1.打开当前目录, 2.读取第一个目录条目, 3.如果当前目录的inode与传递的inode匹配,则将名称复制到缓冲区并返回。 4.否则读取下一个目录条目并重复上一步。 - “filename_to_inum”:接受一个参数(表示文件名的char *)。返回相应的i节点号。具体实现如下: 1.将文件的inode信息读入内存中的结构体。 2.如果有任何问题,请显示相应的错误信息。 3.从结构体中返回i节点号。 - “display_path”:接受一个参数(当前工作目录的i节点)。返回值为空。具体实现如下: 1.创建一个字符数组,用作目录名称的缓冲区。 2.使用“filename_to_inode”获取父目录的inode。 3.如果父目录inode等于当前inode,则已到达根目录并可以返回。 4.否则,切换到父目录并使用“inum_to_filename”查找传递到函数的inode的名称。使用步骤1中的缓冲区进行存储。 5.递归调用“display_path”以显示绝对路径。
以下是代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>

void inum_to_filename (int inode_arg, char *pathBuffer, int size_arg) {
    DIR *dir_ptr = opendir(".");
    struct dirent *dirent_ptr = readdir(dir_ptr);
    int counter = 0;

    while (counter != 1) {
        if (inode_arg == dirent_ptr->d_ino) {

            strcat(pathBuffer, "/");
            strcat(pathBuffer, dirent_ptr->d_name);

            counter = counter + 1;
            return;

        } else {
            dirent_ptr = readdir(dir_ptr);
        }
    }

    closedir(dir_ptr);
}

int filename_to_inum (char *src) {
    int res = 0;
    struct stat info;
    int result = stat(src, &info);

    if (result != 0) {
        fprintf(stderr, "Cannot stat ");
        perror(src);
        exit(EXIT_FAILURE);
    } else {
        res = info.st_ino;
    }

    return res;
} 

void display_path (int ino_src) {
    int bufSize = 4096;
    char pathBuffer[bufSize];
    int ino_prnt = filename_to_inum("..");

    if (ino_src == ino_prnt) {
        //print for test
        inum_to_filename(ino_src, pathBuffer, bufSize);
        printf("%s", pathBuffer);
        return;
    } else {
        //print for test
        chdir("..");
        inum_to_filename(ino_src, pathBuffer, bufSize);
        display_path(ino_prnt);
        printf("%s", pathBuffer);
    }
}

int main (int argc, char *argv[]) {
    int c_ino = filename_to_inum(".");
    display_path(c_ino);
    printf("\n");
}

目前显示的是“/./MyName”,其中MyName是服务器上我的个人目录。这就是我正在运行程序的目录。当使用pwd时,返回“/home/MyName”。我不确定下一步如何正确获取绝对路径。


这是一个提示。您正在递归调用display_path函数。在每次调用和返回后,pathBuffer变量会发生什么? - Michael Albers
你需要确保在从 inum_to_filename() 返回之前关闭你打开的目录,否则会泄漏 DIR 描述符。(顺便提一下,根目录的父目录的 inode 也是根目录的 inode。也就是说,/. 的 inode 与 /.. 相同,通常是 inode 号为 2。) - Jonathan Leffler
@MichaelAlbers 它会在每次调用时重新创建吗?而在这种情况下,由于它被调用了两次,第二次创建将覆盖第一次创建的内容? - Monobus
@Monobus:仅在函数失败的情况下才会出现这种情况。在while循环中有一个早期返回,当函数成功(大多数情况下)时使用它,并且它不会关闭目录。始终查看每个退出路径。 - Jonathan Leffler
@MichaelAlbers 我明白你的意思。我已经将其设置为静态,但它所起的作用只是反转数组的顺序,因此现在它显示/MyName/。我最大的问题是为什么它返回相对路径'.'而不是父目录的实际名称。 - Monobus
显示剩余4条评论
2个回答

1
This is really nice assignment :). 我阅读并尝试了您的代码,它几乎是正确的。有两个小问题导致了不正确的行为。
第一个问题
当display_path到达根文件夹时,您不需要调用inum_to_filename并打印文件夹的名称,因为您已经在前一次迭代中打印了路径的第一个文件夹。这可以防止您的代码在路径开头显示"./"。也就是说,if条件变成了:
 if (ino_src == ino_prnt) {
        return;
    } else {
        chdir("..");
        inum_to_filename(ino_src, pathBuffer, bufSize);
        display_path(ino_prnt);
        printf("%s", pathBuffer);
    }

第二个问题:

您没有正确初始化保存目录名称的缓冲区。这会导致显示随机值。要解决此问题,您可以使用memset将缓冲区的初始值设置为零。

void inum_to_filename (int inode_arg, char *pathBuffer, int size_arg) {
    DIR *dir_ptr = opendir(".");
    struct dirent *dirent_ptr = readdir(dir_ptr);
    int counter = 0;

    memset(pathBuffer, 0, size_arg);

    while (counter != 1) {
     ...
    }

    closedir(dir_ptr);
}

完整的可运行代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>

void inum_to_filename (int inode_arg, char *pathBuffer, int size_arg) {
    DIR *dir_ptr = opendir(".");
    struct dirent *dirent_ptr = readdir(dir_ptr);
    int counter = 0;

    memset(pathBuffer, 0, size_arg);

    while (counter != 1) {
        if (inode_arg == dirent_ptr->d_ino) {

            strcat(pathBuffer, "/");
            strcat(pathBuffer, dirent_ptr->d_name);

            counter = counter + 1;
            return;

        } else {
            dirent_ptr = readdir(dir_ptr);
        }
    }

    closedir(dir_ptr);
}

int filename_to_inum (char *src) {
    int res = 0;
    struct stat info;
    int result = stat(src, &info);

    if (result != 0) {
        fprintf(stderr, "Cannot stat ");
        perror(src);
        exit(EXIT_FAILURE);
    } else {
        res = info.st_ino;
    }

    return res;
} 

/*
  - Create an array of characters to use as a buffer for the name of the directory.
  - Get the inode for the parent directory using filename_to_inode.
  - If the parent inode is equal to the current inode, we have reached root and can return.
  - Otherwise, change to the parent directory and use inum_to_filename to find the name for 
    the inode that was passed into the function. Use the buffer from step 1 to store it.
  - Recursively call display_path to display the absolute path.
*/

void display_path (int ino_src) {
    int bufSize = 4096;
    char pathBuffer[bufSize];
    int ino_prnt = filename_to_inum("..");

    if (ino_src == ino_prnt) {
        return;
    } else {
        chdir("..");
        inum_to_filename(ino_src, pathBuffer, bufSize);
        display_path(ino_prnt);
        printf("%s", pathBuffer);
    }
}

int main (int argc, char *argv[]) {
    int c_ino = filename_to_inum(".");
    display_path(c_ino);
    printf("\n");
}

输出:

ubuntu@ubuntu-VirtualBox:~/dev$ vi pwd.c 
ubuntu@ubuntu-VirtualBox:~/dev$ gcc pwd.c 
ubuntu@ubuntu-VirtualBox:~/dev$ ./a.out 
/home/ubuntu/dev
ubuntu@ubuntu-VirtualBox:~/dev$ pwd
/home/ubuntu/dev
ubuntu@ubuntu-VirtualBox:~/dev$ 

你当前的目录是什么?你能粘贴完整路径吗?你在服务器上尝试过我的完整代码吗?很奇怪,我在我的Linux和MacOSx笔记本电脑上都尝试过,两者都正常工作。 - Giuseppe Pes
你使用的是哪个文件系统? - Giuseppe Pes
这是一台学校服务器。运行pwd命令时,它会返回/home/MyName。 - Monobus
你尝试过在另一个目录中吗?可能与用户权限有关? - Giuseppe Pes
1
生成不完整路径的问题可能是挂载点——每个挂载的文件系统中的顶级目录都有inode 2(通常情况下——我的Mac上的记忆棒根目录的inode为1),因此您停止得太早了。解决这个问题的一种方法(通常情况下)是跟踪st_inost_dev并确保两者匹配。 - Jonathan Leffler
显示剩余3条评论

1
代码大部分是按照正确顺序打印一个名称的方式设置的,因此主要问题在于使用了strcat()而不是strcpy()。另外,在开始时检测是否在根目录中非常重要;如果没有检测到,在当前目录为根目录时可能会得到类似/.或类似内容(具体取决于打印方式)的结果。
您的代码版本包括:
  • 压缩了inum_to_filename()中的循环,但也增加了错误报告。请记住,进程可以在其无权访问的目录中运行(通常需要一个setuid程序,尽管权限可以在程序启动后更改)。在这种情况下,它可能无法打开..(或.)。

  • 丢失变量count;它没有起到有用的作用。使用赋值和测试习惯用法可以使代码包含单个对readdir()的调用。

  • 使用strcpy()而不是strcat()

  • 使用类型ino_t存储inode号码。使用size_t表示大小。

  • 减少filename_to_inum()中间变量的数量。

  • 请注意,if (ino_src == ino_prnt)语句体中的代码适用于根目录;如果没有测试输出,则什么也不会发生。

  • 请注意,else部分的打印是操作的主要部分,而不仅仅是测试打印。

  • 检查错误chdir("..");

  • main()中检测根目录。

  • 请注意,此代码不适合直接重写为函数,因为它成功时会更改进程的当前目录为/

修改后的代码:

#include <assert.h>
#include <dirent.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

static void inum_to_filename(ino_t inode_arg, char *pathBuffer, size_t size_arg)
{
    assert(size_arg > 0);
    DIR *dir_ptr = opendir(".");
    if (dir_ptr == 0)
    {
        fprintf(stderr, "Failed to open directory '.' (%d: %s)\n", errno, strerror(errno));
        exit(EXIT_FAILURE);
    }
    struct dirent *dirent_ptr;

    while ((dirent_ptr = readdir(dir_ptr)) != 0)
    {
        if (inode_arg == dirent_ptr->d_ino)
        {
            if (strlen(dirent_ptr->d_name) >= size_arg)
            {
                fprintf(stderr, "File name %s too long (%zu vs %zu max)\n",
                        dirent_ptr->d_name, strlen(dirent_ptr->d_name), size_arg);
                exit(EXIT_FAILURE);
            }
            strcpy(pathBuffer, dirent_ptr->d_name);
            break;
        }
    }

    closedir(dir_ptr);
}

static ino_t filename_to_inum(char *src)
{
    struct stat info;

    if (stat(src, &info) != 0)
    {
        fprintf(stderr, "Cannot stat ");
        perror(src);
        exit(EXIT_FAILURE);
    }
    return info.st_ino;
}

static void display_path(ino_t ino_src)
{
    size_t bufSize = 4096;
    char pathBuffer[bufSize];
    ino_t ino_prnt = filename_to_inum("..");

    if (ino_src == ino_prnt)
    {
        // print for test
        inum_to_filename(ino_src, pathBuffer, bufSize);
        printf("%s", "(root): /\n");
    }
    else
    {
        // print for real
        if (chdir("..") != 0)
        {
            fprintf(stderr, "Failed to chdir to .. (%d: %s)\n",
                    errno, strerror(errno));
        }
        inum_to_filename(ino_src, pathBuffer, bufSize);
        display_path(ino_prnt);
        printf("/%s", pathBuffer);
    }
}

int main(void)
{
    ino_t c_ino = filename_to_inum(".");
    ino_t r_ino = filename_to_inum("/");
    if (r_ino == c_ino)
        putchar('/');
    else
        display_path(c_ino);
    printf("\n");
}

毫无疑问,还有其他解决方法。

注意:在工作目录为/Volumes/CRUZER/Sub-Directory的存储设备中,此方法会让我感到困扰。在扫描/Volumes时无法找到inode(意外的是1),而我还没有找出原因。我的一个程序——一个getpwd实现——运行良好;另一个则遇到了不同的问题。我相信我会弄清楚所有这些问题。在Mac OS X 10.10.5上使用GCC 5.1.0进行测试。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接