Reputation: 3
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:
Upvotes: 0
Views: 1993
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
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
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