This lab assignment was adapted by Prof. L. Felipe Perrone based on previous work by other CSCI 315 instructors. Permission to reuse this material in parts or in its entirety is granted provided that this credits note is not removed. Additional students files associated with this lab, as well as any existing solutions can be provided upon request by e-mail to perrone[at]bucknell[dot]edu
Copy to your lab directory all the files in:
~cs315/Labs/Lab10/
You should have now the following four files: file_stat.c, read_dir.c, read_dir.h, and Makefile.
Study the C source code in file_stat.c. The program is based on two calls: fstat(2) and fstatvfs(3); read the manual pages for these functions to learn what they do and how to use them.
The manual page for fstatvfs(3) shows that it returns various pieces of information on the underlying file system in which the given file resides. Using this call, you can learn the block size for the file system, the type of the file system, and the maximum length for a file name. The call returns all these data and more in an instance of struct statvfs (a pre-defined type), which you must have allocated previously. fstatvfs will receive a pointer to your instance of struct statvfs and fill up its various fields of file system information. As a self-check exercise, try to figure out whether this is a thread-safe call.
Reading about fstat(2), you will see that it returns a stat struct, which is another pre-defined type. This struct contains information such as the user id and the group id for the owner of the file, its protection bits (for user, group and other), the file size in numbers of blocks, and the times when it was last accessed, modified, and created. You must pass to fstat the pointer to an instance of stat struct, which you allocated previously; the system call will fill up the various fields with information on the specific file.
Reading the source code given to you, you will notice that you must open the file before you call either fstatvfs or fstat. The code contains missing portions that you will construct in this lab. Look for them where you find comments containing the string TO-DO. Once you have filled in the missing pieces, the output produced by your file_stat executable on file file_stat.c should be somewhat similar to what is presented below (minus the text in red):
$ ./file_stat file_stat.c
== FILE SYSTEM INFO ============================
file system fstatvfs() call successful
file system block size: 65536 <——————— For you to do
max. file name length: 255 <——————— For you to do
== FILE INFO ============================
file fstat() call successful
file protection bits = 0644
file protection string = rw-r–r– <————— For you to do
file protection mode (u:g:o) = 6:4:4 <————— For you to do
owner user name = perrone <———————– For you to do
owner group name = cs <————————— For you to do
mode = x <—– For you to do (x may be file, link, directory, socket, etc.)
time of last modification: Thu Nov 14 15:04:21 2014
time of last access: <————————— For you to do
time of status change: <————————— For you to do
First, work on file_stat.c and instrument it to print the information marked in red (that is, tackle the TO-DOs in the code). Next, augment the program to print the time of last access and the time of status change for the file given as command-line parameter. Note that you will need to read the man pages for getpwuid(3) and getgrgid(3) to learn how to translate numeric USER ID and GROUP IP to strings, respectively. Make sure to use versions of these calls which are thread-safe – Why you ask? Just to continue the practice of using functions that are thread-safe.
When you are done with this, you need to:
(2.1) Combine the programs file_stat.c with read_dir.c to create a new program called traverse.c, which will traverse a given directory tree, printing to the standard output the following information:
Note that the size of a file can be accessed from the struct stat returned by calling fstat(2). Read the program file_stat.c and the manual pages fstat(2) and lstat(2) for more information. Make sure to modify the Makefile given to you so that it will build the newly created traverse program.
(2.2) When you complete the previous step, run your program in a directory tree where there is no symbolic link and observe its behavior. Next, in a directory of your own, create a symbolic link which links it to its parent directory. Note that you are creating a loop in the directory graph. Run the program again and note what happens.
(2.3) To fix this problem, for each directory encountered, your program should figure out whether it is a symbolic link and display the appropriate data for the link without traversing it as it would do for a real directory. The manual page for lstat(2) contains information on how to identify whether a file is a symbolic link. Once this issue has been addressed, test the program using a directory with a symbolic link to a parent directory.
When you are done with this, you need to:
Create a file called answers.txt where you will respond to the items below. In three of the four items, you are asked for “an example of an operation” that accomplishes a certain goal. This means that you should provide a complete command line in Unix that causes the specified event to happen.
(3.1) Provide an example of an operation on a regular file which changes only its time of last status change.
(3.2) Provide an example of an operation on a regular file which changes its time of modification.
(3.3) Provide an example of an operation on a regular file which changes its time of last access.
(3.4) Provide an example of an application where knowing the maximum file name length is helpful. In this item, you can simply describe a scenario in which a given program needs to have the maximum length of file names.
When you are done with this, you need to:
We are used to working with files as mechanisms of data storage, most often. As you know, we can also use files for interprocess communication. In this problem, you will use a file to store the communication between two types of processes (or rather, programs): sender.c and receiver.c. The file will work very much like a unidirectional channel (a pipe), but one where there can be multiple writers and multiple readers. There is no skeleton code for this problem.
The sender will be a program that takes a message from the command line as a string delimited by double quotes. The sender receives this string and adds (append or prepend) it to a file called channel.txt that is shared with other senders and a receiver. You may have multiple instances of sender running concurrently at any time. In writing your sender and receiver, you may assume that the file channel.txt already exists: you can use the touch(1) command to create an empty file or cat or, yet, a text editor to that end. The command line invocation of the sender should be like:
> sender “this is my message”
The receiver will be a program that runs on an infinite loop printing to the screen always the last message that was appended to the shared file. You will have only one instance of receiver running at any point in time, but it will run concurrently with senders. Once a sender puts a new message in the shared channel.txt file, the receiver should print it out to standard output, as in:
receiver [msg arrival]: “this is my message”
Your senders and receiver processes need to guarantee that the messages written to and read from the file are recorded exactly as they are generated. That is, if your solution allows for the mixing up of two or more messages into the file, it is not a correct solution. By now, you might have realized that there are synchronization issues hidden in this problem statement. You should remember that the way out of this pickle is by the use of a synchronization construct like a semaphore.
Now, you used semaphores for processes related by birth earlier in the semester, but this is a different scenario. Your senders and receiver will need a semaphore that they all can see, that is, a named semaphore. Something that is like a file and that various independent processes can access. To create one of these structures, you will want to take a look at sem_open(3).
Feel empowered to make your own design decisions on how to solve this problem. Choose the file I/O API that makes things easier for your work (file descriptors or FILE *). Use comments to document your decisions in each of the programs you write.
Note: Named semaphores work just like any files. Once you run the program, named semaphores are created in the directory /dev/shm/, the semaphore names are preceded with “sem.” For example, if you use a named semaphore count in your program, a file named /dev/shm/sem.count will be created. These files are owned by you, no one else can read or write to them. Yet they reside in shared file space. As such you must delete these semaphores when you are done so not to pollute the environment. To avoid running into other people’s named semaphores (which you will not be able to access), you should also name your own semaphores including your user id or some other unique name to avoid these collisions.
When you are done with this, you need to:
Before turning in your work for grading, create a text file in your Lab 10 directory called submission.txt. In this file, provide a list to indicate to the grader, problem by problem, if you completed the problem and whether it works to specification. Wrap everything up by turning in this file: