Introduction
Announcements

Schedule
Labs
Assignments
TA office hours

Tests, exam

Topic videos
Some course notes
Extra problems
Lecture recordings

Discussion board

Grades so far

More to think about

Now that you've spent a bunch of time on the four assignments in this course, here are a few further things to implement as (optional) exercises. (VERY optional ā€” I'm sure you don't have time for them all ā€” focus on areas you're interested in (for interest), or you think you're weaker in (for exam preparation or interest).)

Also see a bunch of suggested problems on various topics in https://www.teach.cs.toronto.edu/~ajr/209/probs/.

General

Obviously, fix any bugs or write any unimplemented parts of your programs (have a look at the automated test results for ideas).

After that, look at my sample solutions. If they are much simpler and/or shorter than yours, see how to simplify your program in this way.

Implement any obvious variations which seem interesting.

Assignment one

Re rmlink, write the related "delink" program, which turns a symlink into a copy of the file it points to. One way to do it is using the "readlink" command, which gives you the content of a symbolic link. Another way to do it is to copy the symlink first. (Try both ways!)
(n.b. there is no point using realpath. You don't need a "canonical" name for the file, you just need where this symlink directly points, and that's what you can use for "cp", and the OS will do the rest. If you copy the symlink first, the OS can do even more for you.)
Make sure your program works with files with spaces in their names, and with spaces in the symlink target string, including spaces at the beginning or ends, or multiple spaces.
To be safe, test your program not only with symlinks you don't care about, but also, with those symlinks pointing to files you also don't care about.

My 'dist' solution uses unix tools to "canonicalize" the input into a form which is easy to process. What other domains could you apply this strategy to?

The "grep" in my 'dist' solution deals with the situation where the input begins with whitespace. Experiment with this. If your program does not work when the input begins with whitespace, fix it, in this way or some other way.

Improve error-checking and -handling throughout your 'adv' program, both for user input and for the files in the database.

Write an adv-checker tool which makes sure that a given adv database is correctly composed, in terms of having 'choice' and 'story' files in all required places.

Assignment two

Your crypt.c most likely terminates as soon as any of the input files is problematic. Make it continue to process the rest of the input files after calling perror(), but nevertheless the error will cause it to return a non-zero exit status at the end, even if the last file is processed successfully.

Use what you learned about time_t and localtime in whatyear.c to write something like the "cal" command, but requiring two arguments and always outputting just one month. Unlike the real cal, your program will only work for months which are within the range of a time_t.

In findempty, you probably exited when you encountered an error (e.g. from opendir() or lstat()), because otherwise it's a bit trickier to get the exit status right. But it's not actually that tricky. Just use a global variable, initialized to zero, then set it to 1 if an error occurs but don't exit. Then you can keep traversing, while still eventually exiting with exit status one. Implement and test this.

As an experiment, change findempty's lstat() call to stat() instead (which is wrong), and then use a symlink to make it go into an infinite loop. (Except that the loop will probably terminate with "too many open files".)

Your findempty, if implemented in the obvious way, will get a "too many open files" condition for sufficiently-deeply-nested directories, although the nesting would have to be very deep indeed. How would you avoid this, i.e. manage to do the closedir() before doing the recursive call? (This is probably useful only as a thought exercise; I don't think it's probably worth the substantial work of actually implementing it.)

Use malloc() to avoid any a priori limit on path name length in findempty.c, but make sure that you do not have a memory leak (that is, any allocated memory should be freed, unless the program is about to exit). Can you minimize the number of malloc() calls by reusing the previously-mallocd area if it's big enough? (Malloc() is very slow.)

Assignment three

As always, fix anything you didn't get working by the submission deadline.

In tree.c, to avoid issues with partial read()s (of the kind we took such pains to deal with in assignment four), the message is always a fixed, small size. Change this to use a terminator of some kind (\n will do) and write an appropriate loop such that a line of arbitrary size is correctly read. (Then increase the maximum value size from a paltry 10 characters to something larger like 1024.)

In tree.c, if a child process malfunctions and never sends the reply back up the tree, the root process (and all intermediate processes in the tree) will wait indefinitely for it, and it won't be possible to type in a new message to send on its way. Use select() between the two data sources (in the root process, it's stdin and the pipe from the child; in other processes, it's the pipe from the parent and the pipe from the child) so as to enable a new message to be typed in this case. (To test your code, do something like making a particular process discard every third message.)

If in tree.c you output a lot of debugging information if the āˆ’v flag is present, then when you signal end-of-file, the shell prompt should still follow all of this chatter, not be intermingled with it. If your assignment doesn't already get this right (test it with a large tree to be sure), then fix it.

Assignment four

Make chatbridge.c select() not only on all servers' fds, but also on 0 (stdin). If there is input from stdin, it is a command. There will be only one command (so that all you need to do is notice stdin input and read a line; you can ignore what the input line is). This command will list all servers and all known users on each one.

Add a further command (also on stdin) to connect to a new server. (If you used an array of servers as my starter code steered you towards, you'll need either to change this to a linked list, or just to store the maximum size of the array as malloced in main() and refuse to exceed that (or slightly better, malloc ten extras in the first place).)

Alternatively (or in addition), make chatbridge deal with servers' going away better. When it gets end-of-file from a server, it should remove that server from data structures so that it can continue with the remaining nāˆ’1 servers. Once no more servers are connected, it exits.

Chatsvr.c doesn't deal with reads of partial lines properly, so that you had to invent dealing with that with multiple connections for your chatbridge.c. A possible exercise is to add this to chatsvr.c. Note that you need a separate "buf" and "bytes_in_buf" for each connection (so put them in the struct client).

Also on the topic of dealing with reads of partial lines properly, in chatclient.c, which does deal with reads of partial lines properly, there is a comment "Is the buffer full even though we don't yet have a whole line?" What's this about? Remove this paragraph (the code following that comment, up to a blank line) and figure out what to send to the client (as you "play server" with nc) to make it go into a tight infinite loop.

Try to crash your program or chatsvr by sending lots of data, or connecting and disconnecting quickly, or whatever else you can think of. More challenging task: Fix the problems you uncover.