stevennash
stevennash

Reputation: 3

stat() giving wrong information

I'm using a loop to print the information of each file in a directory to recreate the ls shell function as a C program. When comparing the information from the program to the correct information from the ls command, the results from the program (using stat()) are very wrong.

Here's all of my code for the program:

#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <unistd.h>

int main(int argc, char* params[])
{
  void printTable(char filepath[], int s, int b);

    // If no modifiers, send error
    if(argc == 1) {
      printf("Error: no directory specified. \n");
      exit(1);
      return 0;
    }
    // If only a directory is provided
    if(argc ==2) {
      printTable(params[1], 0, 0);
    } // end of argc == 2

    // If there are 4 modifiers
    else if(argc == 4) {
    }
    return 0;
}

void printTable (char filepath[], int s, int b) {
  DIR *dp;
  struct dirent *dir;
  struct stat fileStats;
  if((dp = opendir(filepath)) == NULL) {
    fprintf(stderr, "Cannot open directory. \n");
    exit(1);
  }
  printf("Processing files in the directory: %s\n", filepath);
  printf("inode \t Type \t UID \t GID \t SIZE \t       Filename \t                 Modification date \n");
  while((dir = readdir(dp)) != NULL ) {
    if(dir->d_ino != 0 && fileStats.st_ino > 0 && stat(dir->d_name, &fileStats) < 0) {
      // Print the inode
      printf("%d \t ", fileStats.st_ino);
      // Print type
      if(dir->d_type == DT_BLK)
        printf("BLK \t ");
      else if(dir->d_type == DT_CHR)
        printf("CHR \t ");
      else if(dir->d_type == DT_DIR)
        printf("DIR \t ");
      else if(dir->d_type == DT_FIFO)
        printf("FIFO \t ");
      else if(dir->d_type == DT_LNK)
        printf("LNK \t ");
      else if(dir->d_type == DT_SOCK)
        printf("SOCK \t ");
      else if(dir->d_type == DT_REG)
        printf("REG \t ");
      else
        printf("UNKOWN \t ");
      // Print UID
      printf("%d \t ", fileStats.st_uid);
      // Print GID
      printf("%d \t ", fileStats.st_gid);
      // Print SIZE
      printf("%jd bytes \t ", fileStats.st_size);
      // Print file name
      printf("%25s \t ", dir->d_name);
      // Print date modified
      printf("%20s \n", ctime(&fileStats.st_mtime));
    }
  }
  struct tm *lt = localtime(&fileStats.st_mtime);
  int diff = difftime(time(NULL), mktime(lt));
  printf("%d \n", diff);
  closedir(dp);
}

Here's the results from the shell command (this directory has nothing to do with this program): shell output

Here's the results from running the program:

program output

Upvotes: 0

Views: 1993

Answers (3)

Jonathan Leffler
Jonathan Leffler

Reputation: 753475

If your platform has sufficient support for POSIX 2008, you can use the fstatat() and dirfd() functions to good effect. This will work on current versions of BSD, macOS, Linux, AIX, HP-UX, Solaris — at least.

This code is similar to the code in the question, but it doesn't attempt to replicate the way it decodes the file type, etc.

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

int main(int argc, char **argv)
{
    for (int i = 1; i < argc; i++)
    {
        const char *dir = argv[i];
        DIR *dp = opendir(dir);
        if (dp == 0)
        {
            fprintf(stderr, "%s: failed to open directory %s: %d %s\n",
                    argv[0], dir, errno, strerror(errno));
            continue;
        }
        /* dirfd(): POSIX 2008 - OK on BSD, macOS, Linux, AIX, HP-UX, Solaris */
        int fd = dirfd(dp);
        struct dirent *file;
        while ((file = readdir(dp)) != 0)
        {
            struct stat sb;
            /* 0 argument could be AT_SYMLINK_NOFOLLOW */
            if (fstatat(fd, file->d_name, &sb, 0) == 0)
            {
                /* Some of the conversion specifiers may be incorrect for some systems */
                /* Inode number, size, time in particular */
                printf("%10llu  %5d  %5d  %7o  %3d  %9lld  %10ld  %10ld  %10ld  %s/%s\n",
                       sb.st_ino, sb.st_uid, sb.st_gid, sb.st_mode, sb.st_nlink, sb.st_size,
                       sb.st_mtime, sb.st_atime, sb.st_ctime, dir, file->d_name);
            }
            else
                fprintf(stderr, "%s: failed to stat() %s/%s: %d %s\n",
                    argv[0], dir, file->d_name, errno, strerror(errno));
        }
        closedir(dp);
    }

    return 0;
}

Note that this does not need to (and therefore doesn't) use chdir() at all. Using fstatat() allows you specify "interpret the file name relative to the given directory", where the directory is identified by a file descriptor.

Given a suitable error reporting package (so the name of the program will be included in the errors automatically), I'd package the loop body as a function. However, the argv[0] references in the error messages make that inconvenient. For error reporting, I would use the code that is available in my SOQ (Stack Overflow Questions) repository on GitHub as files stderr.c and stderr.h in the src/libsoq sub-directory.

Sample output (from a Mac — the 10-digit inode numbers are bigger than on most systems):

$ at67 bin ~/src/ule
4296808809    501     20    40755   33       1056  1577029162  1583165629  1577029162  bin/.
4296808746    501     20    40755  208       6656  1583164216  1583165629  1583164216  bin/..
4296811200    501     20   100755    1       1266  1515986057  1582224384  1582216636  bin/rfn
4296811205    501     20   100755    1       1266  1515986057  1583164235  1582216636  bin/rfn-c
4305347192    501     20   100755    1        246  1524096284  1582224384  1582216636  bin/soqvg
4297537255    501     20   100755    1       3813  1579639563  1582830967  1582216636  bin/pipe-rot
4296811199    501     20   100755    1        233  1515695843  1582224384  1582216636  bin/sow
4298720660    501     20   100755    1        627  1517875149  1582224384  1582216636  bin/so-getchar
4296811201    501     20   100755    1        218  1515695843  1582224384  1582216636  bin/ddpr
4296811210    501     20   100755    1       1266  1515986057  1582224384  1582216636  bin/rfn-pl
4296808811    501     20   100644    1        490  1510874880  1578595253  1510874880  bin/README.md
4296811204    501     20   100755    1       2278  1515695843  1582224384  1582216636  bin/fixin
4296811203    501     20   100755    1       2835  1576997332  1582224384  1582216636  bin/so-books
4296811196    501     20   100755    1        617  1515695843  1582224388  1582216636  bin/wso
4296811197    501     20   100755    1         85  1515695843  1583165629  1582216636  bin/so
4296808810    501     20   100644    1         92  1510874880  1579561480  1510874880  bin/.gitignore
4296811193    501     20   100755    1        200  1515695843  1582224388  1582216636  bin/posixcmd
4296811206    501     20   100755    1       1266  1515986057  1582224384  1582216636  bin/rfn-h
4451766334    501     20   100755    1        507  1576997332  1582224384  1582216636  bin/so-esql
4297012073    501     20   100755    1        937  1582216633  1583164235  1582216636  bin/sscce
4296811202    501     20   100755    1        522  1515695843  1582224384  1582216636  bin/so-late
4296811209    501     20   100755    1       1266  1515986057  1582224384  1582216636  bin/rfn-sql
4297507309    501     20   100755    1        848  1526264352  1582224384  1582216636  bin/so-stderr
4296811194    501     20   100755    1        206  1515695843  1582224388  1582216636  bin/posixfun
4342190418    501     20   100755    1       1227  1541833786  1582224384  1582216636  bin/so-quotes
4298078558    501     20   100755    1        722  1515695843  1582224384  1582216636  bin/soa
4296811198    501     20   100755    1         92  1515695843  1582224384  1582216636  bin/sops
4356366344    501     20   100755    1        454  1545845134  1582644937  1582216636  bin/so-reformat-c
4296811208    501     20   100755    1       1266  1515986057  1582224384  1582216636  bin/rfn-cpp
4298720661    501     20   100755    1        700  1576997332  1582224384  1582216636  bin/so-c-reserved
4296811207    501     20   100755    1       1266  1515986057  1582656633  1582216636  bin/rfn-sh
4296811195    501     20   100755    1        255  1515695843  1582224388  1582216636  bin/posixhdr
4451855327    501     20   100755    1        780  1577029658  1582224384  1582216636  bin/so-dotarrow
4296916142    501     20    40755   15        480  1574117045  1583165629  1574117045  /Users/jonathanleffler/src/ule/.
4296713088    501     20    40755   97       3104  1575746582  1583165539  1575746582  /Users/jonathanleffler/src/ule/..
4296917945    501     20   100444    1          7  1473056744  1578608259  1510993969  /Users/jonathanleffler/src/ule/ule-test-nnl
4296917947    501     20   100644    1       6148  1418098863  1578608259  1510994007  /Users/jonathanleffler/src/ule/.DS_Store
4296917957    501     20   100755    1      44824  1473056806  1578608437  1513032846  /Users/jonathanleffler/src/ule/ule-v1.4
4296917948    501     20   100444    1         15  1473056679  1578608259  1510993969  /Users/jonathanleffler/src/ule/ule-test-nul
4296917949    501     20   100640    1         43  1418023230  1578608259  1510993969  /Users/jonathanleffler/src/ule/ule-test-mix
4296917951    501     20   100644    1        745  1436058130  1578608259  1510993969  /Users/jonathanleffler/src/ule/ule.notes
4296917952    501     20   100640    1         33  1418023230  1578608259  1510993969  /Users/jonathanleffler/src/ule/ule-test-unx
4296917953    501     20   100640    1         33  1418023230  1578608259  1510993969  /Users/jonathanleffler/src/ule/ule-test-dos
4296917954    501     20    40755   11        352  1541802114  1578608839  1541802114  /Users/jonathanleffler/src/ule/RCS
4441180506    501     20   100444    1       6726  1574116649  1578608259  1574117045  /Users/jonathanleffler/src/ule/ule.c
4296917956    501     20    40755    3         96  1437532230  1578611808  1510994007  /Users/jonathanleffler/src/ule/ule.dSYM
4297282884    501     20   100755    1      60160  1513033250  1582552763  1513033250  /Users/jonathanleffler/src/ule/ule
4296917958    501     20   100640    1         30  1425237329  1578608259  1510993969  /Users/jonathanleffler/src/ule/ule-test-mac
$

If your system doesn't have dirfd() but does have fstatat(), you can probably use these functions to open and close the directory instead. (An early revision of the code above used a variation on this code — but it quickly gets messy configuring it, and dirfd() makes it all unnecessary.). You'd use dir_open(dir) in place of dirfd(dp), and add dir_close(fd) before or after closedir(dp).

#include <fcntl.h>
#include <unistd.h>

int dir_open(const char *name)
{
    return open(name, O_RDONLY | O_DIRECTORY);
}

int dir_close(int fd)
{
    return close(fd);
}

If your system has O_SEARCH, use that in place of O_RDONLY (O_SEARCH is not available on macOS Mojave 10.14.6, for example, where I did my testing). If your system doesn't have O_DIRECTORY, omit that term. These are just some of the details that make configuring this messy.

On the whole, if you have fstatat(), you're likely to have dirfd() too.

Upvotes: 2

Hristo Iliev
Hristo Iliev

Reputation: 74365

stat(2) returns 0 on success and yet you are testing whether stat(dir->d_name, &fileStats) < 0. So your code prints something whenever stat(2) fails to actually stat the file. Also, the way the conditions are chained, fileStats.st_ino > 0 is an expression involving an uninitialised variable in the first iteration.

The most viable reason for stat(2) failing is that you are not listing the current directory. The d_name member of the dirent structure is the name of the file in the directory, which is not the full path but the path relative to the directory path. When you pass it to stat(2), it will only work if the file is in the current working directory (cwd) of the process. If it is not, as in your case, the stat(2) call will fail. What you have to do is build the full path by concatenating filepath and dir->d_name with the path separator / in between:

char fullpath[PATH_MAX];

sprintf(fullpath, "%s/%s", filepath, dir->d_name);
stat(fullpath, &fileStats);

Or, alternatively, use chdir(2) to change the working directory before the loop:

char olddir[PATH_MAX];

// Save current working directory
getcwd(olddir, PATH_MAX);
// Change working directory to `filepath`
chdir(filepath);

// Do your loop here

// Restore old working directory
chdir(olddir);

Now stat(2) will work correctly with relative paths, i.e., just file names.

Upvotes: 1

kiran Biradar
kiran Biradar

Reputation: 12732

stat(dir->d_name, &fileStats) < 0

should be

stat(dir->d_name, &fileStats) == 0

You are printing the content only when there is an error.

stat returns:

On success, zero is returned. On error, -1 is returned, and errno is set appropriately.

Upvotes: 0

Related Questions