The C Programming Language, 2nd Edition, by Kernighan and Ritchie
Exercise 8.01 on page 184
Modify the fsize
program to print the other information contained in the inode entry.
Solution by Akil Adeshwar
/* Modify the fsize program to print the other information contained in the inode entry. */ #include <stdio.h> #include <string.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <stdlib.h> #include <dirent.h> #include <pwd.h> #define MAX_PATH 1024 #ifndef DIRSIZ #define DIRSIZ 14 #endif void dirwalk( char *dir,void (*fcn)(char *)){ char name[MAX_PATH]; struct dirent *dp; DIR *dfd; if((dfd = opendir(dir))==NULL){ puts("Error: Cannot open Directory"); return; } puts(dir); // Get each dir entry while((dp=readdir(dfd)) != NULL){ // Skip . and .. is redundant. if(strcmp(dp->d_name,".") == 0 || strcmp(dp->d_name,"..") ==0 ) continue; if(strlen(dir)+strlen(dp->d_name)+2 > sizeof(name)) puts("Error: Name too long!"); else{ sprintf(name,"%s/%s",dir,dp->d_name); // Call fsize (*fcn)(name); } } closedir(dfd); } void fsize(char *name){ struct stat stbuf; if(stat(name,&stbuf) == -1){ puts("Error: Cannot get file stats!"); return; } if((stbuf.st_mode & S_IFMT) == S_IFDIR){ dirwalk(name,fsize); } struct passwd *pwd = getpwuid(stbuf.st_uid); //print file name,size and owner printf("%81d %s Owner: %s\n",(int)stbuf.st_size,name,pwd->pw_name); } int main(int argc,char *argv[]){ if(argc==1) fsize("."); else while(--argc>0) fsize(*++argv); return 0; }
Solution by codybartfast
A little like 'ls -l
' this prints the user & group id, the size, the modified date and the name (but not permissions).
I did try and update the book's versions of open/read/closedir to work on my system (Debian 10.4 linux with wslfs
file system) but it seemed non trivial (reading dp->fd didn't seem to work as expected, it always returned the
requested amount of data (instead of terminating after n entries), but the returned data didn't seem to match the
structure of direct).
#include <stdio.h> #include <dirent.h> #include <string.h> #include <sys/stat.h> #include <time.h> void fsize(char *); void dirwalk(char *, void (*fcn)(char *)); int main(int argc, char *argv[]) { if (argc == 1) fsize("."); else while (--argc > 0) fsize(*++argv); return 0; } void fsize(char *name) { struct stat stbuf; struct tm mt; if (stat(name, &stbuf) == -1) { fprintf(stderr, "fsize: can't access %s\n", name); return; } if ((stbuf.st_mode & __S_IFMT) == __S_IFDIR) dirwalk(name, fsize); /* user/group id */ printf("%d %d ", stbuf.st_uid, stbuf.st_gid); /* size */ printf("%12ld ", stbuf.st_size); /* date */ mt = *localtime(&stbuf.st_mtime); printf("%4d-%02d-%02d %02d:%02d ", (1900 + mt.tm_year), mt.tm_mon, mt.tm_mday, mt.tm_hour, mt.tm_min); /* name */ printf("%s\n", name); } void dirwalk(char *dir, void (*fcn)(char *)) { char name[FILENAME_MAX]; struct dirent *dp; DIR *dfd; if ((dfd = opendir(dir)) == NULL) { fprintf(stderr, "dirwalk: can't open %s\n", dir); return; } while ((dp = readdir(dfd)) != NULL) { if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) continue; if (strlen(dir) + strlen(dp->d_name) + 2 > sizeof(name)) fprintf(stderr, "dirwalk: name %s/%s too long\n", dir, dp->d_name); else { sprintf(name, "%s/%s", dir, dp->d_name); (*fcn)(name); } } closedir(dfd); }
Sample output:
1000 1000 883 2020-06-04 19:16 ../ch7/ex-7-1-convert.c 1000 1000 766 2020-06-13 10:52 ../ch7/ex-7-2-print.c 1000 1000 2134 2020-06-14 08:57 ../ch7/ex-7-3-printf.c 1000 1000 2368 2020-06-15 07:44 ../ch7/ex-7-4-minscanf.c 1000 1000 1794 2020-06-15 12:04 ../ch7/ex-7-5-calc.c 1000 1000 645 2020-06-16 10:23 ../ch7/ex-7-6-comp-basic.c 1000 1000 1971 2020-06-16 13:00 ../ch7/ex-7-6-comp.c 1000 1000 2705 2020-06-17 17:08 ../ch7/ex-7-7-pattern.c 1000 1000 1878 2020-06-18 10:55 ../ch7/ex-7-8-paginate.c 1000 1000 2082 2020-06-19 13:05 ../ch7/ex-7-9-isupper.c
Latest code on github
Solution by anonymous
I was quite disappointed when I was not able to get the fsize program from the book working. I spent an inordinate amount of time troubleshooting, renaming things, and dealing with other problems so I had to give up. The book was written over three decades ago, so it should be no surprise that writing file system code using system calls and modern day headers would cause conflicts. The max file name size of 14 characters definitely shows its age, especially considering Solaris and AIX (successors to System V) support 255 character file names.
I eventually figured out what prevents the program from working. The majority of the program involves recreating structures used by the operating system and it relies on open() and read() to get the directory entries. The open system call works just fine, but read() returns an error every time. This is because read() doesn't supports file descriptors of directories. The manual pages for the read function mention that the errno will be set to EISDIR if an fd refers to a directory. I confirmed this behavior using this test program:
#include <stdio.h> #include <unistd.h> // replacement for "syscalls.h" #include <fcntl.h> // flags for read and write #include <sys/types.h> // typedefs #include <errno.h> int main() { int fd = open(".", O_RDONLY, 0); #ifndef DIRSIZ #define DIRSIZ 14 #endif struct direct // directory entry { ino_t d_ino; // inode number char d_name[DIRSIZ]; // long name does not have '\0' } dirbuf; // local directory structure printf("read return code == %d (-1 means an error occurred)\n", (int) read(fd, (char *) &dirbuf, sizeof(dirbuf))); printf("errno == %d EISDIR == %d\n", errno, EISDIR); return 0; }
The manual page suggested using readdir() instead, but the problem is that was the function I was trying to code. Plus, using the built-in function defeated the purpose of the program. Still, I gave it a shot, only to realize that if I used readdir(), I would have to get rid of almost all of the code from the book. readdir() requires a special DIR struct and returns a dirent and these structures are quite different than the custom ones the book wrote making the entire thing incompatible. As a last ditch effort, I looked into what readdir() does to see if I could just write it myself. After researching the function I found that readdir calls a system call called getdents. To add insult to injury glibc doesn't provide a wrapper for getdents. I could have kept going down this rabbit hole, figured out how to make a syscall directly for getdents, created the necessary structures required, etc, but I decided to move on.
My fsize program turned into an ls -lR like tool with less pretty output. I ended up borrowing code from both Akil Adeshwar and codybartfast to come up with my solution since I could get their programs to work. I then added the permissions, group name, hard link count, and file type information. I also increased the error handling of the program.
Here is my code
#include <stdio.h> #include <dirent.h> #include <sys/stat.h> #include <time.h> // for localtime() and struct tm #include <pwd.h> // for getpwuid() and struct passwd #include <grp.h> // for getgrgid() and struct group #include <string.h> // for strlen() /* Exercise 8-5. Modify the fsize program to print the other information contained in the inode entry. */ void statfile(char *path); // prints the stats of a file in a ls -l style format void dirwalk(char *path, void (*fp)(char *)); // fp is a function pointer. Used by calling function to specify a function to call when a file is found int main(int argc, char *argv[]) { if (argc == 1) // if no command line args, list current dir statfile("."); else // otherwise, list each argument given while (--argc > 0) statfile(*++argv); return 0; } void statfile(char *path) { struct stat stbuf; // stores the stat info struct tm *mt; // stores the last modified time struct passwd *pw; // used to get the user name struct group *g; // used to get the group name if (stat(path, &stbuf) == -1) // gets stat structure for the file at the specified path and stores it in stbuf. A return code of -1 indicates an error occurred { fprintf(stderr, "error: can't access %s\n", path); return; } if (path[strlen(path) - 1] == '/') path[strlen(path) - 1] = '\0'; // remove forward slash at end of string to clean up output // prints file type: file == -, directory == d, character device == c, block device == b, named pipe (FIFO) == p, symbolic link == l, socket == s, unknown type == ? #ifdef S_ISSOCK // S_ISSOCK might not be defined putchar((S_ISREG(stbuf.st_mode)) ? '-' : (S_ISDIR(stbuf.st_mode)) ? 'd' : (S_ISCHR(stbuf.st_mode)) ? 'c' : (S_ISBLK(stbuf.st_mode)) ? 'b' : (S_ISFIFO(stbuf.st_mode)) ? 'p' : (S_ISLNK(stbuf.st_mode)) ? 'l' : (S_ISSOCK(stbuf.st_mode)) ? 's' : '?'); #else putchar((S_ISREG(stbuf.st_mode)) ? '-' : (S_ISDIR(stbuf.st_mode)) ? 'd' : (S_ISCHR(stbuf.st_mode)) ? 'c' : (S_ISBLK(stbuf.st_mode)) ? 'b' : (S_ISFIFO(stbuf.st_mode)) ? 'p' : (S_ISLNK(stbuf.st_mode)) ? 'l' : '?'); #endif putchar((stbuf.st_mode & S_IRUSR) ? 'r' : '-'); // user read perms putchar((stbuf.st_mode & S_IWUSR) ? 'w' : '-'); // user write perms putchar((stbuf.st_mode & S_IXUSR) ? 'x' : '-'); // user execute perms putchar((stbuf.st_mode & S_IRGRP) ? 'r' : '-'); // group read perms putchar((stbuf.st_mode & S_IWGRP) ? 'w' : '-'); // group write perms putchar((stbuf.st_mode & S_IXGRP) ? 'x' : '-'); // group execute perms putchar((stbuf.st_mode & S_IROTH) ? 'r' : '-'); // other read perms putchar((stbuf.st_mode & S_IWOTH) ? 'w' : '-'); // other write perms putchar((stbuf.st_mode & S_IXOTH) ? 'x' : '-'); // other execute perms printf(" %3lu ", (long unsigned int) stbuf.st_nlink); // number of hard links to file if ((pw = getpwuid(stbuf.st_uid)) == NULL) // gets the user name based on the user id printf("%d ", stbuf.st_uid); // if it could not be retrieved, print uid else printf("%s ", pw->pw_name); // otherwise print user name if ((g = getgrgid(stbuf.st_gid)) == NULL) // gets the group name based on the group id printf("%d ", stbuf.st_gid); // if it could not be retrieved, print gid else printf("%s ", g->gr_name); // otherwise print group name printf("%13ld ", stbuf.st_size); // size in bytes if ((mt = localtime(&stbuf.st_mtime)) == NULL) // get the date. printf("%4d-%02d-%02d %02d:%02d ", 1970, 1, 1, 0, 0); // Displays Epoch time if an error occurred in localtime and it returned NULL else printf("%4d-%02d-%02d %02d:%02d ", 1900 + mt->tm_year, mt->tm_mon, mt->tm_mday, mt->tm_hour, mt->tm_min); // tm_year == the number of years since 1900 printf("%s\n", path); // print the path to the file including the parent directories if (S_ISDIR(stbuf.st_mode)) // if directory found, pass it to dirwalk. This is at the bottom of so statfile the order it is printed is somewhat alphabetical dirwalk(path, statfile); // dirwalk will look for another directory. If one it found, it will call statfile passing its path } void dirwalk(char *path, void (*fp)(char *)) { char name[FILENAME_MAX]; // stores path to the file DIR *dsp; // directory stream pointer returned from opendir. Passed to readdir to get the info on it struct dirent *dp; // directory entry pointer returned by readdir. Contains the name of the file returned if ((dsp = opendir(path)) == NULL) { fprintf(stderr, "error: can't open %s\n", path); return; } while ((dp = readdir(dsp)) != NULL) // get all files in the directory { if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) // skip . and .. to prevent an infinite loop continue; if (strlen(path) + strlen(dp->d_name) + 2 > FILENAME_MAX) // check to see if the new path exceeds FILENAME_MAX { fprintf(stderr, "error length of name %s/%s is %ld characters and max supported size is %ld\n", path, dp->d_name, (long unsigned int) strlen(path) + strlen(dp->d_name) + 1, (long unsigned int) FILENAME_MAX); } else { sprintf(name, "%s/%s", path, dp->d_name); // print the path/filename info into name and then passes it to statfile (*fp)(name); // call statfile once done to list info about the file } } closedir(dsp); }