Lab 10 - File-based Chat System
From now on, we’ll assume that you start lab by connecting to the CS portal and that you are familiar with the command line environment. If you have not been practicing with the terminal, we strongly encourage reviewing Lab 1.
We will also assume that you ran the setup script from that lab and have all modules (including clang and git) by default.
In this lab, we will be writing a chat system so that you can send messages to your friends and classmates and read messages they send you. We will be experimenting with reading and writing files as well as continuing to work with strings in C (char *
).
Lab Goals
After this lab, you should:
- Gain increasing familiariarity with C
- Understand more fully how strings (
char *
) work in C - Understand how files work in C
- Gain experience writing and debugging C
Lab Overview
We have reserved some mailbox space on the CS portal in the directory /p/cso1/mailbox
. For this lab, you will be writing a C program that will read a file in the mailbox (for your computingID) and display any messages you have. Then, your program will ask if the user wants to message another user and then write the message to that user’s mailbox file. Note: you should write your C code in your cso1-code
folder so that you can continue to check it into git.
More specifically, you will write a program in C that will do the following:
- If there is a file named
/p/cso1/mailbox/mst3k.chat
, wheremst3k
is your ID, the program will show that file’s contents to the user and then empty the file. That is, it will print the contents of the file to the screen, then update the file to be empty.- If the user has no messages, you may want to display a message stating that they have no messages.
- The program will ask the user for the computing ID of the person they want to send a message to. If the user just presses return (without entering a computing id), then the program will end and not ask them what message to send.
- If the user gave a computing id, the program will ask the user what message to send to that person.
- Lastly, the program will append your computing ID, followed by a colon, followed by the message, to the file
/p/cso1/mailbox/tj4uva.chat
, wheretj4uva
is replaced by the recipient’s ID, and change its permissions so others can read and write it.
You are welcome to assume that no string will be bigger than 4096 characters and use a global char buffer[4096]
instead of trying to handle malloc
, etc, in this lab. We’ll be talking about malloc
shortly after the exam.
You’ll probably find it useful to work in pairs so you can send each other messages.
Do NOT write or save any C code in the /p/cso1/mailbox
directory.
Useful Hints
Creating Your Chat File
You need to create your own chat file so that it can be read and written to by others.
First, you will need to navigate to the mailbox directory. Then, you can use either vim or nano to create your file.
cd /p/cso1/mailbox
nano mst3k.chat
Type “hello” in your chat file to ensure it can be saved.
Do NOT write or save any C code in the /p/cso1/mailbox
directory.
To return to your home directory:
cd ~
Feel free to write your code in any location where you usually work.
Getting your ID
Linux provides access to several “environment variables”. One of them (USER
) has, as its value, your current login ID. (For us, that is your computing ID). You can retrieve this using the getenv
function in stdlib.h
:
Example: The following will print your ID to the terminal, duplicating the behavior of the built-in whoami
command.
int main() {
char *me = getenv("USER");
puts(me);
return 0;
}
Building a string
We will need to concatenate and build larger strings than we have so far. There are two basic tools for building a string out of component parts. Either one will work fine for this task, and both of them require a pre-allocated destination buffer. Hint: you may want to create a buffer for the string you build by defining a char
array that is big enough, such as char buff[4096];
.
strcat
The strcat
function from string.h
is similar to +=
for strings in Java or Python.
Example: After running the following
buffer[0] = '\0';
strcat(buffer, "/p/cso1/mailbox/");
strcat(buffer, "mst3k");
strcat(buffer, ".chat");
the array buffer
contains a string with the following value: "/p/cso1/mailbox/mst3k.chat"
.
Hint: This might be helpful in putting together the messages to send and building the full path (directory) to read and write messages.
sprintf
The snprintf
function from stdio.h
does a formatted string construction, like a String’s .format()
method in Java or Python. It uses the same formatting specifiers as printf
, with both the power and complexity that that entails.
Example: After running the following
snprintf(buffer, 4096, "/p/%s/mailbox/%s.chat",
"cso1",
"mst3k",
);
the array buffer
contains "/p/cso1/mailbox/mst3k.chat"
. Because of the second parameter, it will not overrun 4096
characters, even if it were given very large strings to format.
Hint: This might be helpful in putting together the messages to send and building the full path (directory) to read and write messages.
Working with files
To work with files, you want to use the fopen()
function to open them, then access their contents, and then call fclose()
to close them. The following components will help:
Opening a file
-
FILE *inbox = fopen(filepath, "r");
returnsNULL
iffilepath
is not the path to a real file, otherwise it returns a pointer to aFILE
structure opened in read-only (for"r"
) mode.There is a special
FILE *
namedstdin
that is always open and it reads from what the user types into the terminal window. -
FILE *outbox = fopen(filepath, "a");
returnsNULL
iffilepath
exists but you are not allowed to write to it, otherwise it returns a pointer to aFILE
structure opened in append-only (for"a"
) mode.There is a special
FILE *
namedstdout
that is always open and displays to the terminal window.
Writing to a file
-
fwrite(buffer, sizeof(char), 23, outbox);
writes 23char
-sized values frombuffer
toFILE *outbox
. Use it if you know how many bytes you want to write. -
fprintf(outbox, "%s: %d\n", "mst3k", 2501);
writesmst3k: 2501
and a newline toFILE *outbox
. Likesnprintf
, this gives a lot of power (along with corresponding complexity).
Reading from a file
-
size_t got = fread(buffer, sizeof(char), 4096, inbox);
reads up to 4096char
-sized values fromFILE *inbox
intobuffer
, and returns the number ofchar
-sized values read (which is often less than 4096 ifinbox
did not have that many characters).This does not work well for
stdin
as theinbox
parameter, as you don’t generally know how many characters the user will type.fgets
is better for most user interactions. -
char *line = fgets(buffer, 4096, inbox);
reads one line of text fromFILE *inbox
, or 4096 characters, whichever is less. It returnsbuffer
on success andNULL
on failure. The returned string includes the newline, as e.g."mst3k\n"
This works well for
stdin
as theinbox
parameter, as users generally type one line at a time.
Handling file permissions, etc
In general, the files we write are owned by our user id and no one else can read or write to them. For this lab, we want students in our group to be able to write their messages to our mailboxes, so we’ll need to change permissions on the files we create. But we don’t want other people outside of uva to be able write to out messages.
-
chmod("/p/cso1/mailbox/mst3k.chat", 0660);
sets the permissions formst3k.chat
based on a bit-vector of flags0660
, specified in octal (hence the leading0
):- The three octal digits define permissions (in written order) for the owner, group, and others.
- If we consider the value of one octal digit in binary (e.g. change
06
to 1102), the bits mean may-read, may-write, and may-not-execute, in order. (A 1 means the permission is granted, a 0 means it is not granted). - We want the files used in the chat to be writeable by any user, so
0660
is a reasonable permission set.
-
truncate("/p/cso1/mailbox/mst3k.chat", 0);
truncates themst3k.chat
file so its new size is 0 bytes. This is useful in re-setting a chat file after the user has read its contents.
Testing tips
You can manually check for the existance and permissions of a file in the terminal by running
ls -l /p/cso1/mailbox/mst3k.chat
Note that many bugs end up creating the wrong file name; the following will list all files in the directory with the newest file last
ls -ltr /p/cso1/mailbox/
You can force-add a message to a mailbox by appending some text directly to a file in the terminal. You can do that with the echo
program and having bash redirect output so that it is appended to the file by running
echo "This is a message" >> /p/cso1/mailbox/mst3k.chat
chmod 660 /p/cso1/mailbox/mst3k.chat
You can read all messages in the terminal by using the less
program to display the contents of the file. less
works similarly to vim
, so type q
to exit the program. Open the mst3k.chat
mailbox file by running
less /p/cso1/mailbox/mst3k.chat
You may also want to just print out the contents of a mailbox file directly to the terminal. You can do this with the cat
command by running
cat /p/cso1/mailbox/mst3k.chat
NOTE: These can be useful in testing different aspects of your program independently. But don’t mess with other student’s mailboxes in this way without their permission.
If you try to read from a non-existent file, open a file in a non-existent directory, or access a file for which you do not have permissions, the functions you use will fail (and set errno
).
Example run
The following shows an example of how a pair of students, aa1a
and tj0uva
might chat with one another. In this example, the program gives some additional information, such as displaying if they have no messages and displaying a prompt for what they are asking of the user. What the user types is shown like this.
User aa1a does | User tj0uva does |
---|---|
$ ./a.out You have no new messages Who would you like to message? tj0uva What do you want to say? Hello, is this working? | |
$ ./a.out Your messages: aa1a: Hello, is this working? Who would you like to message? aa1a What do you want to say? Yes, works well! | |
$ ./a.out You have no new messages Who would you like to message? aa1a What do you want to say? How are you, by the way? | |
$ ./a.out Your messages: tj0uva: Yes, works well! tj0uva: How are you, by the way? Who would you like to message? |
The Big Picture
Here are some high-level notes and hints for the ideal implementation:
- Use
getenv
to figure out the name of the user running the program. Don’t hard-code it or ask them. - After showing the user their messages, truncate their mailbox file (empty it)
- Ask the user for who they want to message and their message to send
- If the user does not type an ID when asked, don’t ask for a message afterwards.
- Append the message to the recipient’s mailbox. Don’t erase the file first.
- Prepend the current user’s ID and a
": "
to each message you put in a mailbox so the recipient can see who wrote it.
- Prepend the current user’s ID and a
chmod
each file you touch to be0660
so that others can work with it too.
The following flowchart may also be useful:
Check-off with a TA
There is no autograder for this lab.
To check-off for this lab, show a TA
- Your currently implemented file chat program. Your program should be able to do the basics:
- read and display the current user’s messages
- prompt the user for a computing id and a message, then append that to the other user’s mailbox file <!–
- Note: you must show the TA your code, compiling your code, and then running it on the portal (providing the basic functionality above) for full credit.
- The TA may ask you to run your code multiple times to see the full functionality of your program, such as running with no messages, messaging yourself, messaging another computing id, etc. Please follow their instructions to check off your program for full credit. –>