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
, wheremst3k
is 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
, wheremst3k
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.
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");
returnsNULL
iffilepath
is not the path to a real file, and a pointer to aFILE
structure opened in read-only mode otherwise.There is a special
FILE *
namedstdin
that is always open and 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, and a pointer to aFILE
structure opened in append-only mode otherwise.There is a special
FILE *
namedstdout
that is always open and displays to the terminal window.
Writing:
-
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 with corresponding complexity.
Reading:
-
size_t got = fread(buffer, sizeof(char), 4096, inbox);
reads up to 4096char
-sized values fromFILE *inbox
intobuffer
, and returns the number 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.
Permissions, etc:
-
chmod("/bigtemp/cso1-f22/mst3k.chat", 0666);
sets the permissions formst3k.chat
based 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
07
to 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
0666
is 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
getenv
to figure out the name of the user running the program. Don’t hard-code it or ask them.- optionally, if
argc
is 2, useargv[1]
instead so users can run as e.g../a.out tj0uva
to 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.
chmod
each file you touch to be0666
so 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.