Lab 9 - File-based Chat System
When you log into a department server you have access to a large public shared space called /bigtemp. I have placed in that directory a folder called /bigtemp/cso1-f22 which we will use like a wall of mail cubbyholes for this assignment.
Outline
You will write a program that will do the following:
-
If there is a file named
/bigtemp/cso1-f22/mst3k.chat, wheremst3kis replaced by your ID, the program will show that file’s contents to the user and then empty the file. -
The program will ask the user the computing ID of the person they want to send a message to.
-
The program will ask the user what message to send them.
-
The program will append your ID, followed by a colon, followed by the message, to the file
/bigtemp/cso1-f22/mst3k.chat, wheremst3kis 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.
You’ll probably find it useful to work in pairs so you can send each other messages.
Useful pieces
Getting your ID
Linux provides access to several “environment variables”. One of them (USER) has, as its value, your current login 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
There are two basic tools for building a string out of component parts. Either one will work fine for this task, and both Both of them require a pre-allocated destination buffer.
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, "/bigtemp/cso1-f22/");
strcat(buffer, "mst3k");
strcat(buffer, ".chat");
the array buffer contains "/bigtemp/cso1-f22/mst3k.chat"
sprintf
The snprintf function from stdio.h does a formatted string construction, like the 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, "/bigtemp/%s/%s.chat",
"cso1-f22",
"mst3k",
);
the array buffer contains "/bigtemp/cso1-f22/mst3k.chat". It will not overrun 4096 characters, even if it were given very large strings to format.
Working with files
To work with files, you want to fopen them, access their contents, and then fclose them. The following components will help:
Opening:
-
FILE *inbox = fopen(filepath, "r");returnsNULLiffilepathis not the path to a real file, and a pointer to aFILEstructure opened in read-only mode otherwise.There is a special
FILE *namedstdinthat is always open and reads from what the user types into the terminal window. -
FILE *outbox = fopen(filepath, "a");returnsNULLiffilepathexists but you are not allowed to write to it, and a pointer to aFILEstructure opened in append-only mode otherwise.There is a special
FILE *namedstdoutthat is always open and displays to the terminal window.
Writing:
-
fwrite(buffer, sizeof(char), 23, outbox);writes 23char-sized values frombuffertoFILE *outbox. Use it if you know how many bytes you want to write. -
fprintf(outbox, "%s: %d\n", "mst3k", 2501);writesmst3k: 2501and a newline toFILE *outbox. Likesnprintf, this gives a lot of power with corresponding complexity.
Reading:
-
size_t got = fread(buffer, sizeof(char), 4096, inbox);reads up to 4096char-sized values fromFILE *inboxintobuffer, and returns the number read (which is often less than 4096 ifinboxdid not have that many characters).This does not work well for
stdinas theinboxparameter, as you don’t generally know how many characters the user will type.fgetsis 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 returnsbufferon success andNULLon failure. The returned string includes the newline, as e.g."mst3k\n"This works well for
stdinas theinboxparameter, as users generally type one line at a time.
Permissions, etc:
-
chmod("/bigtemp/cso1-f22/mst3k.chat", 0666);sets the permissions formst3k.chatbased on a bit-vector of flags, specified in octal (hence the leading0):- The three octal digits are (in written order) the owner, group, and others permissions.
- If re-written in binary (e.g. change
07to 1112), the bits mean may-read, may-write, and may-execute, in order. - We want the files used in the chat to be writeable by any user, so
0666is a reasonable permission set.
-
truncate("/bigtemp/cso1-f22/mst3k.chat", 0);truncates a 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 by running
ls -l /bigtemp/cso1-f22/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 /bigtemp/cso1-f22/
You can force-add a message to a mailbox by running
echo "This is a message" >> /bigtemp/cso1-f22/mst3k.chat
chmod 666 /bigtemp/cso1-f22/mst3k.chat
You can read all messages by running
less /bigtemp/cso1-f22/mst3k.chat
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 how a pair of students, aa1a and tj0uva might chat with one another. 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? |
Some notes for the ideal implementation:
- Use
getenvto figure out the name of the user running the program. Don’t hard-code it or ask them.- optionally, if
argcis 2, useargv[1]instead so users can run as e.g../a.out tj0uvato pretend to be TJ. If you do that, don’t go erasing other student’s mailboxes…
- optionally, if
- After showing the user their messages, truncate their mailbox
- Append messages to the recipient’s mailbox. Don’t erase the file first.
chmodeach file you touch to be0666so that others can work with it too.- Prepend the current user’s ID and a
": "to each message you put in a mailbox so the recipient can see who wrote it. - If the user does not type an ID when asked, don’t ask for a message afterwards.
You don’t need to do all of that to check off (showing the TA a basic implementation is enough), but we encourage you to work on all of the above features to better prepare you for future assignments.