Assignment 2 advice/faq

Written by Julie Zelenski

Advice for assignment 2

The program that you will write does not requires creating a large amount of new code, but it can take some time to get acclimated to the terse, cryptic nature of C and work through the inevitable bugs that result from C's ubiquitous use of pointers, arrays, and strings. We cannot over-emphasize how important it is to dedicate yourself to habits that allow you to do your best work. All that your CS106 instructors taught you about the value of decomposition, incremental development, readability, and early-and-often testing is even more true in the world of C. Read our best practices for completing cs107 assignments.

Common questions about assign2


Am I to use strtok for this assignment

No. For this assignment, you are writing scan_token as the better replacement for strtok. Your mywhich program should call scan_token, not strtok.

Should I separate out all the directories from searchpath and store into an array of strings?

No. Attempting to first tokenize the searchpath into directories and only then process the directories adds the complication of allocating/managing/deallocating an intermediate memory structure for the array and strings, which we don't want you to do. The preferred approach is "tokenize-and-test". Searching for a command should tokenize a directory from the searchpath and test for the presence of a matching executable in that directory. If not found, tokenize the next directory and test, and so on until all directories in the searchpath have been examined. In this way, the memory needs are simplified to just what is needed to process a single directory at a time. If searching for multiple commands, you will repeat the tokenization of search path for each command and that's just fine.

How can I assemble a full path from a directory and command name?

Declare a stack buffer of a large size into which you will construct the full path. The appropriate value for "large size" is the constant PATH_MAX defined in the include file <limits.h>. PATH_MAX is the system's limit on the maximum length of a full path (including the null terminator). Now you want to fill the buffer with the concatenation of the directory, a forward slash, and command name. My preferred technique for this is to use one combined sprintf to write to the buffer, but you could also do it via a combination of strcpy/strcat and/or array-indexing to assign a single character.

How can I test if a file is an executable?

The library function access() will do the job nicely (man access). Given a full path and mode, the function reports whether you can access that path for that mode. The mode to check for is a combination of R_OK and X_OK. This verifies that you have permission to read the file and the file is executable. Be sure to carefully read the man page so you know how to properly interpret the return value from a call to access.

One other minor detail is that a path that is a directory may test as readable and executable, and thus appear to be an executable file to mywhich. There is further filesystem information you can use (man stat) to distinguish directories from files, but we don't ask you to do this. mywhich should just use access to filter results to those matching entries that are readable/executable, without concern for if those matches are files or directories. This is the behavior exhibited by the sample solution.

I'm not sure if a particular use of mywhich is valid or whether the executables my program finds are the correct ones. How can I verify my program's behavior is correct?

Compare to our solution! For any command ./mywhich arguments, try the same invocation on the solution samples/mywhich_soln arguments and validate that your program has the same behavior as the solution. You can also use custom sanitycheck (sanitycheck instructions) to automate comparisons between your program and the solution.

Do I need malloc/free for this assignment?

No. You do not need to call malloc/free/strdup/etc., and we ask that you do not use heap allocation on this assignment.

I'm getting compiler warnings about initialization discards 'const' qualifier from pointer target type. How do I resolve these?

The const qualifier on a declaration is an indication that type is only to be read, not written. A const char * and a char * are not quite the same type. You can read characters from either, but only the non-const version allows the characters to be changed. While it is perfectly fine to supply a non-const pointer where a const is expected (no cast required or recommended), the inverse will raise a warning from the compiler. Throwing down a typecast to const would quiet the compiler, but that's not the appropriate fix. If write-access is required, a read-only pointer is not going to work and a cast only serves to cover up the mistake. You should instead be fixing the logic to supply the correct pointer. If write-access is not needed, then the simple and correct choice is to add the const qualifier to the declaration.

What can scan_token assume about the validity of its arguments?

We will only test the function on valid arguments. More specifically this means:

You may aspire to bullet-proof your function, for example, rejecting a NULL value for p_input or complaining if the client passes a buf with a mismatch buflen. Although we salute your intentions, it is impossible to make good on them. Determining whether a pointer is valid is not solvable in general. Rejecting p_input == NULL will catch exactly that one case, but doesn't detect all other invalid addresses. Any measure to detect bad pointers will be half-hearted at best. As the implementer, you have little choice but to assume the client will supply valid addresses and must write your code accordingly.

Is it allowable to call string.h functions?

Yes! For this assignment, use of getenv and strtok/strsep is prohibited since you are writing your own versions of those functions, but the rest of the standard library is at your disposal and its use is strongly preferred over re-implementing its functionality. The functions in the standard library are already written, tested, debugged, and highly optimized. What's not to like? One important consideration, though, is to choose the appropriate function to use. As one example, there are several different functions that do variants of string compare/search (strstr, strchr, strncmp, strspn and so on). Rather than pick a function at random and wedge it into what you need, be sure to choose the approach that most directly accomplishes the task at hand.