Programming Assignment 1
CS 30200 / ECE 46810
Operating Systems
Spring, 2022

This assignment makes use of the files contained in this zip file. This assignment is due Wednesday, January 26.

This assignment uses several programming and operating systems concepts that are simple but may be new to you. In this assignment you will use printf's format strings, the command-line, command-line arguments, the standard I/O streams, piping, environment variables, and a configuration file.

In this assignment you will write a C filter program that formats a jumbled stream of input numbers into nicely organized columns. To determine the parameters for the output formatting your program will make use of command-line arguments, environment variables, and a configuration file. To test your program you will use the Windows command-line.

Write a C program called filter.c that reads from standard input and writes to standard output. The input to your program will be a sequence of positive integers separated by random amounts of white space. The output of your program will be the input numbers grouped and formatted into columns.

Here is an example of a sequence of 91 input numbers.

   7038     9643                 8101          9718          4451        8252
  3817          697  9206                    92     1387                8195
            6356    1011                   347  8981   957         4635
6958             802                3976        1733          1678       7386
9675            9044   5409                  8084           2129         9634
         1091           1618      9533       6506           8909        5594
8            6148      7382                 8137 1435               574
645 2264                1030            8819       2353      1620
6125            3259       7661              2993               2525    3500
8821        4424                1095               3845     354      6962
9715   7373           1428            798 63                 1316          4003
7318       1399      4951            1403  8057              106
        556          9513         14                2345        7163   7844
1984           2119     8698 4095            3657         2382
                  6020 2665                    2822  9345  6            7504

Here are those 91 numbers grouped into groups of length 14, then formatted into 8 columns with each column being 7 characters wide (including the spaces), and with one blank line between each group of numbers.

   7038   9643   8101   9718   4451   8252   3817    697
   9206     92   1387   8195   6356   1011

    347   8981    957   4635   6958    802   3976   1733
   1678   7386   9675   9044   5409   8084

   2129   9634   1091   1618   9533   6506   8909   5594
      8   6148   7382   8137   1435    574

    645   2264   1030   8819   2353   1620   6125   3259
   7661   2993   2525   3500   8821   4424

   1095   3845    354   6962   9715   7373   1428    798
     63   1316   4003   7318   1399   4951

   1403   8057    106    556   9513     14   2345   7163
   7844   1984   2119   8698   4095   3657

   2382   6020   2665   2822   9345      6   7504

The filter.c program requires three parameters. One parameter determines how many input numbers get grouped together. Another parameter determines the number of output columns. And the third parameter determines how many characters wide each output column is (including space characters). The input numbers should be "right justified" in each output column, which means that all the numbers in a column will line up vertically at their ones place. Each group of numbers will be separated by a blank line.

The default number of output columns for filter.c is six. If the configuration file filter.cfg exits in the current directory, then the second integer value in that file overrides the default number of output columns. If there is an environment variable called CS302_COLUMNS, then the value of that variable overrides the default number of columns and the number of columns set by the configuration file (if it exits). If there is a second command-line argument, then that command-line argument overrides the environment variable (if there is one), the configuration file (if it exits), and the default number of columns.

The integers in an output column should all line up at their ones place. The number of characters from the ones place of one column to the ones place of the next column is called the "width" of a column. The default width for the columns is five. Not all of the integers in a column will have the same number of digits. Each integer in a column will be padded with spaces on the left of the integer to fill up its column. You determine the width of a column by calling printf with an appropriate format string. If the file filter.cfg exits in the current directory, then the first integer value in that file overrides the default column width. If there is an environment variable called CS302_WIDTH, then the value of that variable overrides the default column width and the column width set by the configuration file (if it exits). If there is a command-line parameter, then the first command-line parameter overrides the environment variable (if there is one), the configuration file (if it exits), and the default value of the column width.

The input numbers should be combined into groups with the members of each group being formatted into the appropriate number of columns and with a blank line separating each group. In filter.c the default length of a group should be 10. If the file filter.cfg exits in the current directory, then the third integer value in that file overrides the default length for groups. If there is an environment variable called CS302_LENGTH, then the value of that variable overrides the default length and the length set by the configuration file (if it exits). If there is a third command-line parameter, then that command-line parameter overrides the environment variable (if there is one), the configuration file (if it exits), and the default length for groups.

Your program should use the Standard C library function fopen() to open the configuration file filter.cfg (if it exits). Your program should use the function fscanf() to read the three integer values from the configuration file. To keep things simple, you can assume that if the configuration file exits, then it must contain three positive integer values. The configuration file is a text file, so the "integers" in it are actually strings, but the fscanf() function will automatically convert them to int values for you.

Your program should use the Standard C Library function getenv() to see if there are environment variables named CS302_WIDTH or CS302_COLUMNS or CS302_LENGTH. If any of these environment variables exists, then its string value should be converted to an integer value by using the Standard C Library function atoi() ("atoi" is an abbreviation of "ascii to integer").

Your program should get its command-line arguments by using the argc and argv parameters to your program's main() function (see also Chapter 9 from this book or Chapter 18 from this book). Command-line arguments, like environment variables, are always strings. So you need to use atoi() to convert a command-line argument into an int value.

Your program should read the sequence of input numbers from standard input by using the Standard C Library function scanf() (from the scan family of functions) and its associated formatting strings.

Your program should write formatted output numbers to standard output by using the Standard C Library function printf() and its associated formatting strings. (Hint: You will need to make use of the * character in your format strings in order to set the appropriate width.)

Formatted input and output is not as hard as it might seem by looking at the last few references. After a few examples, you quickly get the hang of it. Here is a nice printf summary for the Java version of printf, which is similar to the C version.

Here are some more examples. If the input stream to your program looks like this:

   7038     9643                 8101          9718          4451        8252
  3817          697  9206                    92     1387                8195
            6356    1011                   347  8981   957         4635
6958             802                3976        1733          1678       7386
9675            9044   5409                  8084           2129         9634
         1091           1618      9533       6506           8909        5594
8            6148      7382                 8137 1435               574
645 2264                1030            8819       2353      1620
6125            3259       7661              2993               2525    3500
8821        4424                1095               3845     354      6962
9715   7373           1428            798 63                 1316          4003
7318       1399      4951            1403  8057              106
        556          9513         14                2345        7163   7844
1984           2119     8698 4095            3657         2382
                  6020 2665                    2822  9345  6            7504

then the default way to format this is with six columns of width five and with ten numbers in each group (except for the last group for which there may not be enough input numbers).

 7038 9643 8101 9718 4451 8252
 3817  697 9206   92

 1387 8195 6356 1011  347 8981
  957 4635 6958  802

 3976 1733 1678 7386 9675 9044
 5409 8084 2129 9634

 1091 1618 9533 6506 8909 5594
    8 6148 7382 8137

 1435  574  645 2264 1030 8819
 2353 1620 6125 3259

 7661 2993 2525 3500 8821 4424
 1095 3845  354 6962

 9715 7373 1428  798   63 1316
 4003 7318 1399 4951

 1403 8057  106  556 9513   14
 2345 7163 7844 1984

 2119 8698 4095 3657 2382 6020
 2665 2822 9345    6

 7504

On the other hand, if we use five columns of width ten and groups of length 16, then the same 91 input numbers should produce the following output.

      7038      9643      8101      9718      4451
      8252      3817       697      9206        92
      1387      8195      6356      1011       347
      8981

       957      4635      6958       802      3976
      1733      1678      7386      9675      9044
      5409      8084      2129      9634      1091
      1618

      9533      6506      8909      5594         8
      6148      7382      8137      1435       574
       645      2264      1030      8819      2353
      1620

      6125      3259      7661      2993      2525
      3500      8821      4424      1095      3845
       354      6962      9715      7373      1428
       798

        63      1316      4003      7318      1399
      4951      1403      8057       106       556
      9513        14      2345      7163      7844
      1984

      2119      8698      4095      3657      2382
      6020      2665      2822      9345         6
      7504

In the zip file for this assignment you will find a program called source.c that you can use to test your program. The program source.c writes to standard output a stream of random numbers. The numbers are separated by random amounts of white space and there are a random number of random numbers on each line of output. You can test your filter.c program by "piping" the standard output of source.exe into the standard input of your filter.exe. For example, the first output shown just above might have been created by a command like the following.

     > source.exe | filter.exe

On the other hand, the second output above could have been produced by the following two shell commands.

     > set CS302_LENGTH=16
     > source.exe | filter.exe 10 5

or by the following single shell command.

     > source.exe | filter.exe 10 5 16

The last two commands used a pipe (the character '|'). Using a pipe in the last command is equivalent to the following two commands that use I/O redirection.

     > source.exe > temp
     > filter.exe 10 5 16 < temp

The first command "redirects" the standard output from source into a temporary file called temp and then the second command "redirects" the contents of temp into the standard input of filter. (How would you save the resulting output from the filter in a file called data.txt?) The piped version of the command has the advantage of not needing a temporary file.

The source.c program accepts three (optional) command-line arguments. The first command-line argument is an integer that determines how many lines of output source.exe should produce. The second command-line argument is an integer that determines how many numbers are on each output line. The third command-line argument is an integer that determines the range of the random numbers (the default range is from 0 to 9,999). So, for example, the following command-line will produce 10 lines of output, with three numbers per line, and the random numbers are all from the range 0 to 499.

     > source.exe 10 3 500

The following command-line pipes the randomly generated data directly into the filter.exe process.

     > source.exe 10 3 500 | filter.exe 4 10 20

The following command-line will save the randomly generated data in a file for possible use in testing your filter.exe program.

     > source.exe 10 3 500 > myData.txt

Without any command-line arguments, source.exe will produce a random number of output lines and each output line will contain a random number of numbers from the range 0 to 9,999.

In the zip file for this assignment you will also find a Windows executable program called filter_demo.exe (and a Linux/MacOS executable program called filter_demo) that you can use to demo this assignment on a Windows (respectively Linux or MacOS) computer.

Also in the zip file there are files data.txt, test_filter.cmd, and test_results_correct.txt that help you test your completed version of filter.c. Once you have filter.c written and compiled, you can double click on the file test_filter.cmd which will run your filter.exe program several times with the data from data.txt as stdin and gather all the results into the file test_results.txt which you can then compare with test_results_correct.txt. Your test_results.txt should be exactly the same as test_results_correct.txt.

Do not try to write filter.c all at once! Write it in stages. Break the problem down into sub-problems and solve them one at a time. For example, here is an outline of how you might go about attacking this problem.

If you are new to C and are having trouble, here is another strategy. Write this program in Java and get it debugged. Then translate it from Java into C. Use either C for Java Programmers or C for Java Programmers: A Primer to help you translate from Java to C.

Make sure you test your program under a wide variety of conditions. With and without a configuration file, with and without environment variables, with and without command-line arguments.

When you want to set an environment variable from the command-line, be careful not to use a command like the following, with extra spaces around the =.

     > set CS302_COLUMNS = 7

This creates an environment variable called "CS302_COLUMNS " (with a trailing space) and gives it the value " 7" (with a leading space).

When you set an environment variable, it only exits in the command window where you created it. So, for example, you can't create an environment variable in one command prompt window and then run your program from an IDE or an editor or another command prompt window.

Here is a list of the C library functions that you should use (with multiple versions of the documentation for each one).

Turn in a zip file called CS302Hw1Surname.zip (where Surname is your last name) containing your version of filter.c.

This assignment is due Wednesday, January 26.