Reputation: 25
I am using Raspbian and this is a part of an exercise for my advanced C programming in Linux environments. I have to open a directory and list its content, checking if entries are subdirectories or files.
Here is what I did:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include <string.h>
#include <sys/stat.h>
//declarations
int main(int argc, char ** argv){
if(argc != 2){
fprintf(stderr, "Wrong number of arguments\n");
exit(EXIT_FAILURE);
}
DIR *dir;
dir = opendir(argv[1]);
if(!dir){
fprintf(stderr, "Error: unable to open directory\n");
exit(EXIT_FAILURE);
}
struct dirent * entry;
struct stat filestat;
while((entry = readdir(dir))){
if(!(strcmp(entry->d_name, ".") == 0) && !(strcmp(entry->d_name,"..") == 0)){
stat(entry->d_name, &filestat);
//printf("S_ISREG(%s) value is:%d\n", entry->d_name, S_ISREG(filestat.st_mode));
//printf("S_ISDIR(%s) value is:%d\n", entry->d_name, S_ISDIR(filestat.st_mode));
if(S_ISDIR(filestat.st_mode) == 0){
printf("Dir: %s\n", entry->d_name);
} else {
printf("File: %s\n", entry->d_name);
}
}
}
closedir(dir);
printf("END\n");
return EXIT_SUCCESS;
}
Now, it works if I am working with relative paths. If I am working on absolute paths, S_ISREG and S_ISDIR macros always return 0. What am I doing wrong?
Upvotes: 1
Views: 728
Reputation: 156
Use
if (fstatat(dirfd(dir), entry->d_name, &filestat, AT_SYMLINK_NOFOLLOW) == -1) {
fprintf(stderr, "Cannot stat %s/%s: %s.\n",
argv[1], entry->d_name, strerror(errno));
/* Optionally, exit(EXIT_FAILURE); */
} else {
/* Directory entry information in filestat */
}
As explained in the man 3 readdir manual page, if entry->d_type == DT_UNKNOWN
, you need to do the above fstatat()
to obtain the type.
This is because some filesystems do not provide the information in the directory entries, and return d_type == DT_UNKNOWN
for all directory entries. All applications are required to handle DT_UNKNOWN correctly.
Note that you need
#define _POSIX_C_SOURCE 200809L
#define _ATFILE_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
at the beginning of your file, to tell the C library to expose the functions (fstatat()
and strerror()
).
However, opendir()
/readdir()
/closedir()
is the wrong way to check directory contents, because the contents may change during your traversal. You would be better off using [scandir()](https://man7.org/linux/man-pages/man3/scandir.3.html9, glob() (if searching for files matching a pattern), or nftw() (if traversing entire trees) which are all POSIX.1 standard functions provided by POSIXy C libraries (basically all except Windows).
An exercise claiming to show how to list files in a directory and using opendir()
/readdir()
/closedir()
is at best misleading, because it leaves all the complexity – handling changes to the directory contents, like renamed files – to you the writer, without telling you such work is necessary! Yes, two decades ago filesystems were typically so simple that opendir()
/readdir()
/closedir()
worked without issues, but that is not the case anymore. The other functions listed above are supposed to handle such cases gracefully, so you the programmer shouldn't need to do it yourself.
Here is one way to properly implement the directory scan in Linux:
#define _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE
#include <stdlib.h>
#include <ftw.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
static int report_entry(const char *path,
const struct stat *info,
int typeflag,
struct FTW *ftwbuf)
{
/* If you need it, entry name is (path + ftwbuf->base). */
switch (typeflag) {
case FTW_D: /* Directory */
printf("%s is a directory\n", path);
/* If this is the path given to nftw(), recurse into it: */
if (ftwbuf->level == 0)
return FTW_CONTINUE;
/* Do not recurse into any other directories. */
return FTW_SKIP_SUBTREE;
case FTW_DNR: /* Directory, but no access to contents */
printf("%s is a directory but there is no read access\n", path);
return FTW_CONTINUE;
case FTW_DP: /* Directory that was already mentioned */
return FTW_CONTINUE;
case FTW_F: /* Regular file */
printf("%s is a file\n", path);
return FTW_CONTINUE;
case FTW_SL: /* Symlink, and FTW_PHYS was set for nftw() */
printf("%s is a symbolic link\n", path);
return FTW_CONTINUE;
case FTW_SLN: /* Symlink to a nonexistent file */
/* This will NOT be reported if FTW_PHYS is set in the nftw() call. */
printf("%s is a symbolic link to a nonexistent file\n", path);
return FTW_CONTINUE;
case FTW_NS: /* stat() failed */
printf("%s is unknown, and cannot be stat()'d\n", path);
return FTW_CONTINUE;
default: /* Should never occur */
printf("%s is of unknown an unexpected type (%d)\n", path, typeflag);
return FTW_STOP;
}
}
/* Number of file descriptors nftw() is allowed to use.
It mostly matters to applications that use many file descriptors,
like service daemons (servers). If nftw() runs out, it slows down,
but does not fail. In Linux, processes usually have at least
a thousand file descriptors available, so 64 is very conservative. */
#ifndef NFTW_FDS
#define NFTW_FDS 64
#endif
int report_directory(const char *path)
{
int result;
if (!path || !*path) {
/* No path specified; invalid parameter */
errno = EINVAL;
return -1;
}
result = nftw(path, report_entry, NFTW_FDS, FTW_ACTIONRETVAL | FTW_PHYS);
if (result == -1) {
/* nftw() error, errno set. */
return -1;
} else
if (result == FTW_STOP) {
/* report_entry() returned FTW_STOP. We assume error was already printed. */
errno = 0;
return -1;
} else
if (result != 0) {
/* Unexpected error */
errno = EIO;
return -1;
}
/* Done successfully. */
return 0;
}
int main(int argc, char *argv[])
{
if (argc != 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
const char *thisname = (argc >= 1 && argv && argv[0]) ? argv[0] : "(this)";
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", thisname);
fprintf(stderr, " %s DIRECTORY-OR-FILE\n", thisname);
fprintf(stderr, "\n");
return (argc == 2) ? EXIT_SUCCESS : EXIT_FAILURE;
}
if (report_directory(argv[1]) != 0) {
if (errno) {
fprintf(stderr, "Error: %s: %s.\n", argv[1], strerror(errno));
}
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
Upvotes: 1
Reputation: 21
It seems by doing stat(entry -> d_name, &filestat) you're giving as directory path just the name of the folder you're looking at, which should work if it belongs to the directory from which the file is being executed but might not work with absolute paths
Also, you are not checking if stat is producing any error, which might also be an issue here - or at least provide some insight to the real problem
you might want to try concatenating your path to entry->d_name and use that when you call stat instead, e.g.
char fullPath[MAX_LEN];
while((entry = readdir(dir))){
if(!(strcmp(entry->d_name, ".") == 0) && !(strcmp(entry->d_name,"..") == 0)){
sprintf(fullPath, "%s/%s", argv[1], entry->dname);
if(stat(fullPath, &filestat) < 0){
//handle error
}
if(S_ISDIR(filestat.st_mode) == 0){
printf("Dir: %s\n", entry->d_name);
} else {
printf("File: %s\n", entry->d_name);
}
}
}
where MAX_LEN is some predefined constant
Upvotes: 2
Reputation: 50778
Try this:
while((entry = readdir(dir))){
if(!(strcmp(entry->d_name, ".") == 0) && !(strcmp(entry->d_name,"..") == 0)){
if(entry->d_type == DT_DIR){
printf("Dir: %s\n", entry->d_name);
} else {
printf("File: %s\n", entry->d_name);
}
}
}
It might not work on your platform though, read the readdir documentation for more information.
Upvotes: 0