EEVblog Electronics Community Forum

Products => Computers => Programming => Topic started by: MikeK on October 04, 2023, 06:35:14 pm

Title: opendir() requires chdir()?
Post by: MikeK on October 04, 2023, 06:35:14 pm
I have a short program to output a directory's files.  If I open the current directory with opendir("."), it works fine.  But if I specify a path, it will list the filenames correctly, but the attributes are wrong.  The mtime is wrong, the size is wrong, and the directory flag is wrong (probably others as well).

I added chdir() as a fluke and it works.  But I'm not understanding why chdir() is necessary.  Shouldn't opendir() return the data for that directory?

(I've taken this code from a book which, presumably, was tested and worked without chdir().)

Code: [Select]
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <time.h>
#include <sys/stat.h>
#include <string.h>
#include <unistd.h>

int main(void) {
    DIR *folder;
    struct dirent *file;
    struct stat filestat;
    char s[6];
    char *dir = "/home/mike/python";

    folder = opendir(dir);
    chdir(dir);
    if (folder == NULL) {
        puts("Unable to read directory");
        exit(1);
    }

    while ((file=readdir(folder)) != NULL) {
        stat(file->d_name, &filestat);
       
        sprintf(s, "%5ld", filestat.st_size);
        printf("%-14s %s %s",
                file->d_name,
                S_ISDIR(filestat.st_mode) ?  "<DIR>" : s,
                ctime(&filestat.st_mtime));
    }
    closedir(folder);
    return(0);
}
Title: Re: opendir() requires chdir()?
Post by: Nominal Animal on October 04, 2023, 06:55:40 pm
I have a short program to output a directory's files.
Why don't you use the proper interface for that, scandir() (https://man7.org/linux/man-pages/man3/scandir.3.html) or nftw() (https://man7.org/linux/man-pages/man3/nftw.3.html)?

If I open the current directory with opendir("."), it works fine.  But if I specify a path, it will list the filenames correctly, but the attributes are wrong.
The d_name field of a struct dirent only contains the file name part.

When you do
    DIR *dir = opendir("/home/myself/python");
and some
    struct dirent *ent = readdir(dir);
you might have ent->d_name containing say example.py .
The problem is when you do
    stat(ent->d_name, &filestat)
Because relative paths (including plain file names) given to stat() refer to files and paths starting from the current working directory, you won't 'stat' the correct paths at all!
You are trying to obtain the information on files in the current working directory, that have the same name as files in the directory opened via opendir().

The solution is to either construct the full path by combining the directory you used, a slash, and the entry d_name field, or to use fstatat(dirfd(DIR), ent->d_name, &filestat, 0);.  However, if you do have fstatat() available, you also have scandir() available, in which case you're using the wrong tool for the job anyway.

Alternatively, you can just do a chdir(target-directory) first, and DIR *dir = opendir(".");.

And if you want to traverse entire directory trees, you'd better use nftw() (https://man7.org/linux/man-pages/man3/nftw.3.html), or if you have a Microsoft-like deep dislike towards anything POSIX, the BSD-originating fts interface (https://man7.org/linux/man-pages/man3/fts.3.html).

Other than in very specific cases like kernel pseudofilesystems and having to be compatible with Windows, you should never use opendir()/readdir() directly, because you won't handle the cases where the directories are modified during scanning anyway.  The proper interfaces are supposed to handle that sanely.
Title: Re: opendir() requires chdir()?
Post by: MikeK on October 04, 2023, 07:30:54 pm
Great.  Thank you.

EDIT: Yes, thanks for that thorough explanation.