Jump to: navigation, search

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);
}
Personal tools