2

I'm writing a basic program which takes in the name of a directory and opens it. Once I've opened it, I would like to classify files and subdirectories. I found code from a while ago which did something similar - but this is the output I'm getting:

[.] -> [0]
[..] -> [0]
[a.txt] -> [0]
[subDir] -> [0]

Where a.txt is a text fille and subDir is a directory, but both those values are returning 0. Not sure where I'm going wrong.

Full code:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <dirent.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>

void handleDirectory(DIR *dir);
int isFile(const char *path);

int main(int argc, char **argv)
{
    if (argc != 2){
        printf("Not enough arguments!\n");
        return 0; 
    }

    DIR *dir = opendir(argv[1]);
    if (dir == NULL){
      return 0;
    }

    handleDirectory(dir);
} // end of main 

void handleDirectory(DIR *dir){
    struct dirent *temp;
    while ((temp = readdir(dir)) != NULL) {
        printf("[%s] -> [%d]\n", temp->d_name, isFile(temp->d_name));
    }
} // end of handle directory method 

int isFile(const char *path)
{
    struct stat path_stat;
    lstat(path, &path_stat);
    return S_ISREG(path_stat.st_mode); 
}
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
econCodergirl
  • 315
  • 1
  • 3
  • 8
  • Please post code, errors, sample data or textual output here as plain-text, not as images that can be hard to read, can’t be copy-pasted to help test code or use in answers, and are barrier to those who depend on screen readers. You can edit your question to add the code in the body of your question. For easy formatting use the `{}` button to mark blocks of code, or indent with four spaces for the same effect. The contents of a **screenshot can’t be searched, run as code, or copied and edited to create a solution.** – tadman Nov 26 '20 at 01:39
  • Normally you'd use `fstat` instead of `lstat` unless you care about the specifics of a soft link. If you want to know if it's a directory, test vs. `S_IFDIR`. – tadman Nov 26 '20 at 01:41
  • @tadman when I used fstat, I got a compile error: warning: passing argument 1 of \u2018fstat\u2019 makes integer from pointer without a cast [-Wint-conversion] fstat(path, &path_stat); ^~~~ In file included from Asst2.c:8:0: /usr/include/x86_64-linux-gnu/sys/stat.h:210:12: note: expected \u2018int\u2019 but argument is of type \u2018const char *\u2019 extern int fstat (int __fd, struct stat *__buf) __THROW __nonnull ((2)); – econCodergirl Nov 26 '20 at 01:42
  • If all you have is the filename then `stat()` is the answer. Before you use these functions, please, check the documentation to be sure you're using them correctly. The arguments do not always make sense and can be very bizarre for a long list of both arbitrary and historical reasons. – tadman Nov 26 '20 at 01:45
  • Tried stat too and no difference :( @tadman – econCodergirl Nov 26 '20 at 01:46
  • That should work, so if there's something else amiss it's hard to say what. Is there anything weird about that file? Does it work on other files? – tadman Nov 26 '20 at 01:50
  • you could also use [stat](https://linux.die.net/man/2/stat) with `S_ISDIR` and `st_mode` to find the path is directory or file. – IrAM Nov 26 '20 at 01:51
  • You don't check the return value from `lstat()` — you've no idea what you're working with. Unless the directory is `.` (the current directory), it will fail with `ENOENT`. You have to build a complete path name for the file name read by `readdir()`, or equivalent. See the duplicate for details. – Jonathan Leffler Nov 26 '20 at 01:54
  • @IrAM Could you clarify what you mean? Thank you – econCodergirl Nov 26 '20 at 01:56
  • 1
    @tadman no, it doesn't work with any other files. The file is just a txt file and the subDir subdirectory – econCodergirl Nov 26 '20 at 01:56
  • @econCodergirl, read [Testing-File-Type](https://www.gnu.org/software/libc/manual/html_node/Testing-File-Type.html) , and its a posix – IrAM Nov 26 '20 at 02:03
  • Thanks, I think that makes sense. What duplicate? @JonathanLeffler – econCodergirl Nov 26 '20 at 02:04
  • 1
    The question is closed as a duplicate of two questions — the one I chose is [`stat()` error "no such file or directory" when file name is returned by `readdir()`](https://stackoverflow.com/questions/5125919/stat-error-no-such-file-or-directory-when-file-name-is-returned-by-readdir). – Jonathan Leffler Nov 26 '20 at 02:06
  • 1
    @JonathanLeffler Thanks so much - this is really helpful. But I'm just confused on how I could change my working directory to argv[1] using chdir(), or prepend argv[1] to each filename before calling stat(). – econCodergirl Nov 26 '20 at 02:08
  • If you're brave, you can use `if (chdir(argv[1]) != 0) { …oops… } else { …process files… }`. The bravery is necessary because if you need to switch to another directory (such as `argv[2]`) and that name is relative to the original directory, you have to know how to get back to where you started ([`fchdir()`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchdir.html) for example). The alternative requires a sufficiently modern POSIX-compliant system with functions such as [`fstatat()`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/fstatat.html). _[…continued…]_ – Jonathan Leffler Nov 26 '20 at 02:13
  • It's worth checking the return value of `stat()` to see if it's indicating an error. You probably need to specify the full path relative to your current working directory, so if this is in any other directory you're testing for `./a.txt` not `./subdir/a.txt`. You may have to expand that path, or change your current working directory if that's an option. – tadman Nov 26 '20 at 02:13
  • _[…continuation…]_ Or you can do it the old-fashioned way, and build a full name by concatenating `argv[1]`, a slash, and the name from `readdir()` to build the name of the file that you pass to `lstat()` or `stat()` (for example, `snprintf(buffer, sizeof(buffer), "%s/%s", argv[1], path)`, using the `path` parameter in `isFile()` — you should make `buffer` ridiculously big (4096 — any advance on 4K?) and strictly you should check that `snprintf()` succeeded). In all circumstances, you need to check that the `stat`-like operation succeeded before attempting to analyze the result. – Jonathan Leffler Nov 26 '20 at 02:15
  • Also remember that there are more file types than just 'file' or 'directory'; notably, you could run across 'symlink' or FIFO. You're relatively unlikely to come across most of the others — character special, block special, socket, and sundry platform-specific file types (Solaris ['door'](https://en.wikipedia.org/wiki/Doors_(computing))). – Jonathan Leffler Nov 26 '20 at 02:23

1 Answers1

2

As tadman suggested, stat() (or fstat() or lstat()) are good choices to query file type. st_mode in the returned "stat" struct tells you file type and mode: S_IFREG= "regular file", S_IFDIR= "directory", and so forth.

opendir()/readdir() is certainly a reasonable alternative. "d_type" in the "dirent" struct tells you file type: DT_REG= "regular file", DT_DIR= "directory", etc.

If you got a compile error calling fstat, then you need to 1) show us the code, and 2) copy/paste the exact error text.

The only thing your code is checking for is "st_mode == S_ISREG". At a minimum, I would update the code to check for sb.st_mode & S_IFMT, per this example:

https://man7.org/linux/man-pages/man2/stat.2.html

   #include <sys/types.h>
   #include <sys/stat.h>
   #include <stdint.h>
   #include <time.h>
   #include <stdio.h>
   #include <stdlib.h>
   #include <sys/sysmacros.h>

   int
   main(int argc, char *argv[])
   {
       struct stat sb;

       if (argc != 2) {
           fprintf(stderr, "Usage: %s <pathname>\n", argv[0]);
           exit(EXIT_FAILURE);
       }

       if (lstat(argv[1], &sb) == -1) {
           perror("lstat");
           exit(EXIT_FAILURE);
       }

       printf("ID of containing device:  [%jx,%jx]\n",
               (uintmax_t) major(sb.st_dev),
               (uintmax_t) minor(sb.st_dev));

       printf("File type:                ");

       switch (sb.st_mode & S_IFMT) {
       case S_IFBLK:  printf("block device\n");            break;
       case S_IFCHR:  printf("character device\n");        break;
       case S_IFDIR:  printf("directory\n");               break;
       case S_IFIFO:  printf("FIFO/pipe\n");               break;
       case S_IFLNK:  printf("symlink\n");                 break;
       case S_IFREG:  printf("regular file\n");            break;
       case S_IFSOCK: printf("socket\n");                  break;
       default:       printf("unknown?\n");                break;
       }

       printf("I-node number:            %ju\n", (uintmax_t) sb.st_ino);

       printf("Mode:                     %jo (octal)\n",
               (uintmax_t) sb.st_mode);

       printf("Link count:               %ju\n", (uintmax_t) sb.st_nlink);
       printf("Ownership:                UID=%ju   GID=%ju\n",
               (uintmax_t) sb.st_uid, (uintmax_t) sb.st_gid);

       printf("Preferred I/O block size: %jd bytes\n",
               (intmax_t) sb.st_blksize);
       printf("File size:                %jd bytes\n",
               (intmax_t) sb.st_size);
       printf("Blocks allocated:         %jd\n",
               (intmax_t) sb.st_blocks);

       printf("Last status change:       %s", ctime(&sb.st_ctime));
       printf("Last file access:         %s", ctime(&sb.st_atime));
       printf("Last file modification:   %s", ctime(&sb.st_mtime));

       exit(EXIT_SUCCESS);
   }
paulsm4
  • 114,292
  • 17
  • 138
  • 190
  • I didn't get any compiling errors - I tried both lstat and stat but both yield the same results. Could you clarify what you mean as for opendir() and readdir() ? @paulsm4 – econCodergirl Nov 26 '20 at 02:00
  • Q: Could you clarify what you mean as for opendir() and readdir()? A: You say you're using opendir(), then readdir(), to classify files and subdirectories. Fine. Each returns a DIR * "dirent",which you can use to check if that entry is a file, a directory or other. You don't *NEED* to make an extra call to "lstat()". Look at the links I cited above, or look at these examples: https://c-for-dummies.com/blog/?p=3252 – paulsm4 Nov 26 '20 at 03:34