README.md · master · CSE 320 / hw4-doc · GitLab
https://gitlab02.cs.stonybrook.edu/cse320/hw4-doc/blob/master/README.md 1/7
Bring in README.md from the development repo.
Gene Stark authored 2 weeks ago
693a78dc
The goal of this assignment is to become familiar with low-level Unix/POSIX system calls related to processes, signal handling, files, and I/O
redirection. You will implement a printer spooler program, called imprimer , that accepts user requests to queue files for printing, cancel printing
requests, pause and resume print jobs, show the status of printers and print jobs, and set up pipelines to convert queued files of one type to the
type of file accepted by an available printer.
After completing this assignment, you should:
Understand process execution: forking, executing, and reaping.
Understand signal handling.
Understand the use of "dup" to perform I/O redirection.
Have a more advanced understanding of Unix commands and the command line.
Have gained experience with C libraries and system calls.
Have enhanced your C programming abilities.
We strongly recommend that you check the return codes of all system calls and library functions. This will help you catch errors.
BEAT UP YOUR OWN CODE! Use a "monkey at a typewriter" approach to testing it and make sure that no sequence of operations, no
matter how ridiculous it may seem, can crash the program.
Your code should NEVER crash, and we will deduct points every time your program crashes during grading. Especially make sure that you
have avoided race conditions involving process termination and reaping that might result in "flaky" behavior. If you notice odd behavior you
don't understand: INVESTIGATE.
You should use the debug macro provided to you in the base code. That way, when your program is compiled without -DDEBUG , all of your
debugging output will vanish, preventing you from losing points due to superfluous output.
When writing your program, try to comment as much as possible and stay consistent with code formatting. Keep your code
organized, and don't be afraid to introduce new source files if/when appropriate.
This assignment will involve the use of many system calls and library functions that you probably haven't used before. As such, it is imperative
that you become comfortable looking up function specifications using the man command.
The man command stands for "manual" and takes the name of a function or command (programs) as an argument. For example, if I didn't know
how the fork(2) system call worked, I would type man fork into my terminal. This would bring up the manual for the fork(2) system call.
Navigating through a man page once it is open can be weird if you're not familiar with these types of applications. To scroll up and
down, you simply use the up arrow key and down arrow key or j and k, respectively. To exit the page, simply type q. That having been
said, long man pages may look like a wall of text. So it's useful to be able to search through a page. This can be done by typing the / key,
followed by your search phrase, and then hitting enter. Note that man pages are displayed with a program known as less . For more
information about navigating the man pages with less , run man less in your terminal.
Now, you may have noticed the 2 in fork(2) . This indicates the section in which the man page for fork(2) resides. Here is a list of the man
page sections and what they are for.
Section Contents
1 User Commands (Programs)
README.md 31.2 KB
Homework 4 Printer Spooler - CSE 320 - Fall 2018
Professor Eugene Stark
Due Date: Friday 11/16/2018 @ 11:59pm
Introduction
Takeaways
Hints and Tips
Reading Man Pages
11/13/2018 README.md · master · CSE 320 / hw4-doc · GitLab
https://gitlab02.cs.stonybrook.edu/cse320/hw4-doc/blob/master/README.md 2/7
Section Contents
2 System Calls
3 C Library Functions
4 Devices and Special Files
5 File Formats and Conventions
6 Games et. al
7 Miscellanea
8 System Administration Tools and Daemons
From the table above, we can see that fork(2) belongs to the system call section of the man pages. This is important because there are
functions like printf which have multiple entries in different sections of the man pages. If you type man printf into your terminal, the man
program will start looking for that name starting from section 1. If it can't find it, it'll go to section 2, then section 3 and so on. However, there is
actually a Bash user command called printf , so instead of getting the man page for the printf(3) function which is located in stdio.h , we
get the man page for the Bash user command printf(1) . If you specifically wanted the function from section 3 of the man pages, you would
enter man 3 printf into your terminal.
Remember this: man pages are your bread and butter. Without them, you will have a very difficult time with this assignment.
Fetch and merge the base code for hw4 as described in hw0 . You can find it at this link: https://gitlab02.cs.stonybrook.edu/cse320/hw4
NOTE: For this assignment, you need to run the following command in order for your Makefile to work:
$ sudo apt-get install libreadline-dev
The sudo password for your VM is cse320 unless you changed it.
The above command installs the GNU readline library, which is a software library that provides line-editing and history capabilites for
interactive programs with a command-line interface. It allows users to move the cursor, search the command history, control a kill ring (which is
just a more flexible version of a copy/paste clipboard) and use tab completion on a text terminal. We highly recommend that you use it for this
assignment.
Here is the structure of the base code:
.
├── .gitlab-ci.yml
└── hw4
├── include
│ ├── debug.h
│ └── imprimer.h
├── lib
│ └── imp_util.o
├── Makefile
├── rsrc
│ └── imprimer.cmd
├── src
│ └── main.c
├── tests
└── util
├── printer
├── show_printers.sh
└── stop_printers.sh
If you run make , the code should compile correctly, resulting in an executable bin/imprimer . If you run this program, it doesn't do very much,
because there is very little code -- you have to write it!
When started, imprimer should present the user with a command-line interface with the following prompt
imp>
Getting Started
Imprimer : Functional Specification
Command-Line Interface
11/13/2018 README.md · master · CSE 320 / hw4-doc · GitLab
https://gitlab02.cs.stonybrook.edu/cse320/hw4-doc/blob/master/README.md 3/7
Typing a blank line should should simply cause the prompt to be repeated, without any other printout or action by the program. Non-blank lines
are interpreted as commands to be executed. Imprimer commands have a simple syntax, in which each command consists of a sequence of
"words", which contain no whitespace characters, separated by sequences of one or more whitespace characters. The first word of each command
is a keyword that names the command. Any remaining words are the arguments to the command. Imprimer should understand the following
commands, with arguments as indicated. Square brackets are not part of the arguments; they merely identify arguments that are optional.
Miscellaneous commands
help
quit
Configuration commands
type file_type
printer printer_name file_type
conversion file_type1 file_type2 conversion_program [arg1 arg2 ...]
Informational commands
printers
jobs
Spooling commands
print file_name [printer1 printer2 ...]
cancel job_number
pause job_number
resume job_number
disable printer_name
enable printer_name
The help command takes no arguments, and it responds by printing a message that lists all of the types of commands understood by the
program.
The quit command takes no arguments and causes execution to terminate.
The type command declares file_type to be a file type to be supported by the program. Possible examples (but not an exhaustive list) of file
types are: pdf (Adobe PDF), ps (Adobe Postscript), txt (text), png (PNG image files), etc. A file will be presumed to be of a particular type
when it has an extension that matches that type. For example, foo.txt will be presumed to be a text file, if txt has previously been declared
using the type command. Files whose names do not having an extension that matches a declared type are considered of unknown type and are
to be rejected if an attempt is made to spool them for printing. Essentially any identifier can be used as a file type -- they may (but aren't
required to) correspond to "known" file types that have standards, are supported by other programs, etc.
The printer command declares the existence of a printer named printer_name, which is capable of printing files of type file_type. The
printer_name is just an identifier, such as Alice . Each printer is only capable of printing files of the (one) type that has been declared for it, and
your program should take care not to send a printer the wrong type of file.
The conversion command declares that files of type file_type1 can be converted into file_type2 by running program conversion_program with
any arguments that have been indicated. It is assumed that conversion_program reads input of type file_type1 from the standard input and
writes output of type file_type2 to the standard output, so that it is suitable for use in a pipeline consisting of possibly several such programs. For
example, on your Linux Mint VM:
The command pdf2ps - - can be used to convert PDF read from the standard input to Postscript on the standard output.
The command pbmtext can be used to convert text read from the standard input to a Portable Bitmap (pbm) file on the standard output.
The command pbmtoascii can be used to convert a Portable Bitmap (pbm) file read from the standard input to an ASCII graphics (i.e. text)
file on the standard output.
The command pbmtog3 can be used to convert a Portable Bitmap (pbm) file read from the standard input to a Group 3 FAX file (g3).
There are many others: some of them work well together and others do not. For many of these commands there are also corresponding
commands that convert formats in the reverse direction.
The printers command prints a report on the current status of the declared printers, one printer per line. For example:
imp> printers
PRINTER, 0, alice, ps, disabled, idle
PRINTER, 1, bob, pcl, disabled, idle
The jobs command prints a similar status report for the print jobs that have been queued. For example:
imp> jobs
JOB 0 22 Oct 2018 16:08:11 pdf queued 22 Oct 2018 16:08:11 0 foo pdf ffffffff
11/13/2018 README.md · master · CSE 320 / hw4-doc · GitLab
https://gitlab02.cs.stonybrook.edu/cse320/hw4-doc/blob/master/README.md 4/7
JOB, 0, 22 Oct 2018 16:08:11, pdf, queued, 22 Oct 2018 16:08:11, 0, , foo.pdf, ffffffff
JOB, 1, 22 Oct 2018 16:08:16, ps, queued, 22 Oct 2018 16:08:16, 0, , bar.ps, ffffffff
JOB, 2, 22 Oct 2018 16:08:34, txt, queued, 22 Oct 2018 16:08:34, 0, , mumble.txt, ffffffff
The formats of these status reports will be generated by functions that have been provided for you, as discussed in more detail below. You must
use the provided functions, which will make it easier for us to automate some of the testing of your program.
The print command sets up a job for printing file_name. The specified file name must have an extension that identifies it as one of the file types
that have previously been declared with the type command. If optional printer names are specified, then these printers must previously have
been declared using the printer command, and they define the set of eligible printers for this job. Only a printer in the set of eligible printers for
a job should be used for printing that jobs. Moreover, an eligible printer can only be used to print a job if there is a way to convert the file in the
job to the type that can be printed by that printer. If no printer name is specified in the print command, then any declared printer is an eligible
printer.
The cancel command cancels an existing job. If the job is currently being processed, then any processes in the conversion pipeline for that job
are terminated (by sending a SIGTERM signal to their process group).
The pause command pauses a job that is currently being processed. Processes in the conversion pipeline for that job are stopped (by sending a
SIGSTOP signal to their process group).
The resume command resumes a job that was previously paused. Processes in the conversion pipeline for that job are continued (by sending a
SIGCONT signal to their process group).
The disable command sets the state of a specified printer to "disabled". This does not affect the status of any job currently being processed by
that printer, but a disabled printer is not eligible to accept any further jobs until it has been re-enabled using the enable commnd.
The enable command sets the state of a specified printer to "enabled". When a printer becomes enabled, if there is a pending job that can now
be processed by the newly enabled printer, then processing is immediately started for one such job.
Your program must produce output in the following situations, which do not necessarily occur in direct response to a user command:
Whenever the status of a printer has changed. In this case, the output must consist of a status line showing the new status of that printer,
formatted using the function ( imp_format_printer_status() ) we provide for this purpose, as described for the printers command above.
Whenever the status of a job has changed. In this case, the output must consist of a status line showing the new status of that job, formatted
using the function ( imp_format_job_status() ) we provide for this purpose, as described for the jobs command above.
Whenever an error occurs while executing a user command. In this case, the output must consist of a single line containing an error message
that has been formatted using the function ( imp_format_error_message() ) that we provide for this purpose.
Your program is permitted to emit output in addition to that specified above, but any such output must occur on a line that does not start with
" PRINTER ", " JOB ", ERROR , or " imp> , so that we can filter it out if we need to.
The normal mode of operation of imprimer is as an interactive application. However, it can also be run in batch mode, in which it reads
commands from a command file. If imprimer is started as follows:
$ imprimer -i command_file
then it begins by reading and executing commands from command_file until EOF, at which point it presents the normal prompt and executes
commands interactively. Normally this feature would be used to cause configuration commands (declarations of types, printers, and conversions)
to be read from a command file, rather than typed each time. If a quit command appears in the command file, then the program terminates
without entering interactive mode. This can be used to run a series of commands completely automatically without user intervention.
If imprimer is started with the " -o output_file" option, then any output it produces that would normally appear on the terminal is to be
redirected instead to the specified output file.
If the program is run in interactive mode (the default), then it should use the readline() function to read commands from the user. If the
program is run in batch mode, then readline cannot be used, so in this case the program will have to read commands using either the standard
I/O library or low-level Unix I/O.
The purpose of imprimer is to process print jobs that are queued by the user. Each time there is a change in status of a job or printer as a result
of a user command or the completion of a job being processed, imprimer must scan the set of queued jobs to see if there are any that can now
be processed, and if so, start them. In order for a job to be processed, there must exist a printer that is enabled and not busy, the printer must be
in the eligible_printers set for that job, and there must be a way to convert the type of file in the job to the type of file the printer is capable
of printing. If these conditions hold, then the job status is set to RUNNING , the chosen printer is set to "busy" and the chosen_printer field of the
JOB structure is set to point to the PRINTER that has been selected. A group of processes called conversion pipeline is set up to run a series of
programs that will convert the type of file in the job to the type of file that the printer can print. This is described further below.
Program Output
Batch Mode
Reading Input
Processing Print Jobs
11/13/2018 README.md · master · CSE 320 / hw4-doc · GitLab
https://gitlab02.cs.stonybrook.edu/cse320/hw4-doc/blob/master/README.md 5/7
A job will exist at any given time in one of various states, the possibilities for which are defined by the JOB_STATUS enum in imprimer.h . These
states and their meanings are:
QUEUED -- The job has been created and is ready for processing. A job will persist in this state only as long as there are no printers in the set
of eligible printers for that job that can be used to print the job. As soon as an eligible printer (of an appropriate type) becomes available, the
job will transition to the RUNNING state.
RUNNING -- An eligible printer of an appropriate type has been chosen for the job and a conversion pipeline has been created to convert the
file in the job to the type of file that the printer is capable of printing. The chosen printer must be among the printers in the
eligible_printers set for that job. For a job to be started on a printer, the printer must be "enabled" and "not busy". The printer status is
changed to "busy", and it stays that way as long as the job is RUNNING .
PAUSED -- A job that was previously RUNNING has temporarily been stopped by sending a SIGSTOP signal to the process group of the
processes in the conversion pipeline. A job in the PAUSED state will remain in that state until a resume command has been issued by the
user. This will cause a SIGCONT signal to be sent to the process group of the conversion pipeline.
??The state of a job should not be changed immediately when the user issues a pause command. Instead, the SIGSTOP signal
should first be sent and the state of the job changed from RUNNING to PAUSED only when a SIGCHLD signal has subsequently been
received and a call to the waitpid() function returns showing WIFSTOPPED true of the process status. Similarly, the state of a job
should not be changed immediately when the user issues a resume command, but only once a SIGCHLD signal has been received
and a subsequent call to waitpid() returns showing WIFCONTINUED true of the process status.
COMPLETED -- A job enters this state from the RUNNING state once processing has completed and the processes in the conversion pipeline
have terminated normally. Once in the COMPLETED state, a job will remain in the queue so that its status can be inspected, but it will be
ineligible for further processing. A job will remain in the queue until just after the execution of the first user command that is issued after the
job has been completed for one minute. At that point the job will be deleted from the queue and freed.
ABORTED -- A job enters this state from the RUNNING state if one or more processes in the job pipeline terminate abnormally. Once having
entered the ABORTED state, a job is treated similarly to a COMPLETED job as described above.
The imprimer program must install a SIGCHLD handler so that it can be notified immediately upon completion of a job being processed. The
handler must appropriately update the job and printer status information and start any further jobs in the queue that can now be processed by
virtue of the printer having become available.
Note that you will need to use sigprocmask() to block signals at appropriate times, to avoid races between the handler and the
main program, the occurrence of which which will result in indeterminate behavior.
Each time the status of a job or printer changes, your program must immediately print a corresponding status line (created using the
imp_format_job_status() or imp_format_printer_status() functions). This is so that so that it is evident (to those grading your program) what
and when state changes have occurred.
In order to determine whether a particular printer can be used to service a particular job, it will be necessary to determine whether there is a
sequence of conversions that can be used to transform the type of the file in the job to the type of file the printer can print. To determine this,
you will need to maintain a suitable data structure (e.g. a matrix) to record the information supplied by the user in the form of conversion
commands, and you will need to use a suitable algorithm (e.g. breadth-first search) to search for a path of conversions between the two file types.
If it exists, then the path of conversions (and the associated conversion commands) forms the basis for setting up the conversion pipeline to
process the job.
Creation of a conversion pipeline should be begun by the main program forking a single process to serve as the "master" process for the
pipeline. This process should use setpgid() to set its process group ID to its own process ID. The master process will then fork one child process
for each link in the conversion path between the type of the file in the job and the type of file that the chosen printer can print. Redirection
should be used so that the standard input of the first process in the pipeline is the file to be printed and the standard output of the last process
in the pipeline is the chosen printer (for which a file descriptor has been obtained using imp_connect_to_printer() ). In addition, the pipe()
and dup() (or dup2() ) system calls should be used to arrange to connect the standard output of each intermediate process in the pipeline to
the standard input of the next process. Each process in the pipeline will execute (using execve() ) one of the conversion commands (previously
declared by the user using the conversion command) to convert the file read on its standard input to the type required by the next process in
the pipeline.
It is possible that the type of the queued file is the same as the type of file the printer can print. In this case, no conversion is
required, and the conversion program will consist of the master process and a single child process, which should execute the program
/bin/cat with no arguments.
The master process of a conversion pipeline is used to simplify the interaction of the conversion pipeline with the main process. Since the master
process creates its own process group before forking the child processes, all the child processes will exist in that process group. The processes in
the pipeline can therefore be paused and resumed by using killpg() to send a SIGSTOP or SIGCONT to that process group. Only the master
process is a child of the main process, so the main process only has to keep track of the process ID for the master process of each conversion
pipeline that it starts. The master process of a conversion pipeline will need to keep track of its child processes, and to use waitpid() to reap
them and collect their exit status. If any child process terminates by a signal or with a nonzero exit status, then the conversion pipeline will be
deemed to have failed and the master process should exit with a nonzero exit status. The main process should interpret the nonzero exit status as
Conversion Pipelines
11/13/2018 README.md · master · CSE 320 / hw4-doc · GitLab
https://gitlab02.cs.stonybrook.edu/cse320/hw4-doc/blob/master/README.md 6/7
an indication that the job has failed, and it should set the job to the ABORTED state. If all of the child processes in a conversion pipeline terminate
normally with zero exit status, then the master process should also terminate normally with zero exit status. The main process should interpret
this situation as an indication that the job has succeeded, and it should set the job the the COMPLETED state.
Important: You must create the processes in a conversion pipeline using calls to fork() and execve() . You must not use the
system() function, nor use any form of shell in order to create the pipeline, as the purpose of the assignment is to giving you
experience with using the system calls involved in doing this.
The imprimer.h header file that we have provided defines function prototypes for the functions you are to use to format output for your
program and to make connections to printers. It also contains definitions of some constants and data types related to these functions.
Do not make any changes to imprimer.h . It will be replaced during grading, and if you change it, you will get a zero!
We have provided you with an object file imp_util.o (in the lib directory), which will be automatically linked with your program. This contains
several functions, whose prototypes are given in the imprimer.h header file, which you should use as follows:
char *imp_format_printer_status(PRINTER *printer, char *buf, size_t size);
char *imp_format_job_status(JOB *job, char *buf, size_t size);
char *imp_format_error_message(char *msg);
You must use these functions to format required output by your program. The reason for this is so that everyone's program produces output
in a uniform format that we might have a fighting chance to process automatically. The output looks a bit odd, but you will note that it is in
comma-separated-value (CSV) format that can be readily parsed. These functions take a buffer that you supply, along with its size, and they
return a pointer to that same buffer.
int imp_connect_to_printer(PRINTER *printer);
This is the function you must use to connect to a printer. If successful, it returns a file descriptor to be used to send data to the printer; if
unsuccessful, -1 is returned. If the printer is not currently "up", then it will be started (see about the printer program below).
In order to interface with the above functions, the header file imprimer.h defines structure types PRINTER and JOB . You must pass in instances
of these structures that have all fields properly initialized. The meaning of each of the fields is documented in the comments in the imprimer.h
file. Each of these structures also has an additional other field, which can be used to point to arbitrary information of your own choosing should
you have a need to do so. The functions above ignore the value of this field, so there is no harm if you don't initialize it.
The printer program we have provided (in the util directory) simulates a printer that you can connect to and send data to. It doesn't actually
"print" anything, but it does log any files you send to it in spool directory. It also maintains a debug log in that directory, in case it is necessary
to get some idea of what the printer has been doing.
A printer is automatically started when you try to connect to it using the imp_connect_to_printer() function, if it is not already up. You can also
start a printer "manually" by a command of the following form:
$ util/printer [-d] [-f] PRINTER_NAME FILE_TYPE
This starts a printer with name PRINTER_NAME , which is capable of printing files of type FILE_TYPE . Each printer that is started must have a
unique name; if you try to start a second printer with the same name as an existing printer, the second command will fail. Once started, printers
stay "up" until they are explicitly stopped, You can stop all printers using the command make stop_printers . The command make
show_printers can be used to show you the printers that are currently up.
The optional -d and -f arguments to the printer command are used to cause the printer to exhibit some random behavior. If -d is
specified, then random delays might occur during "printing". If -f is specified, then the printer will be "flaky", which means that it might
disconnect at random times, causing the conversion pipeline to fail. The imp_connect_to_printer() function has a flags argument that can
also be used to specify these flags. The flags only take effect when the printer is first started; once a printer is "up", the flags passed when
connecting to it have no further effect. The flags should be the bitwise "or" of one or more of PRINTER_NORMAL , PRINTER_DELAY , and
PRINTER_FLAKY .
The util directory contains shell scripts show_printers.sh and stop_printers.sh . These are most easily invoked using make show_printers
or make stop_printers , though they can also be run directly.
The rsrc directory contains a file imprimer.cmd . This is a sample file that contains some imprimer configuration commands that can be read
using the -i option. For example:
Provided Components
The imprimer.h Header File
The imp_util.o Library
The printer Program
The show_printers.sh and stop_printers.sh Shell Scripts
The imprimer.cmd File
11/13/2018 README.md · master · CSE 320 / hw4-doc · GitLab
https://gitlab02.cs.stonybrook.edu/cse320/hw4-doc/blob/master/README.md 7/7
The spool directory is created by make in order to store various files created by the "printers". For example, if a printer is started with name
alice , then spool/alice.log will contain debug log information, spool/alice.pid will contain the process ID of the printer process (for use
by stop_printers.sh ), spool/alice.sock will be a "socket" that is used by imp_connect_to_printer() to connect the printer. Also, each time
a file is "printed" the data that was received is stored in a separately named file in this directory. The spool directory is not removed by a normal
make clean . To remove the spool directory and all its contents, you can use make clean_spool .
$ imprimer -i rsrc/imprimer.cmd
At some point after this assignment has initially been handed out, I will probably make available either a list of "recommended" commands
to use in a conversion pipeline, or I will make available some "dummy" commands for testing purposes. Not having these commands
available should not stop you from getting started; you can always test your program using cat as a "conversion" command.
I am considering making things more interesting and realistic by adding an additional display command to imprimer (similar to the
printer command) together with a corresponding display program (similar to the printer program). The purpose of this would be to
provide a way for files to be actually "printed" by running a command that displays them graphically in a window. If I decide to do this, I will
make an announcement about it and update this document.
As usual, make sure your homework compiles before submitting. Test it carefully to be sure that doesn't crash or exhibit "flaky" behavior due to
race conditions. Use valgrind to check for memory errors and leaks. Besides --leak-check=full , also use the option --track-fds=yes to
check whether your program is leaking file descriptors because they haven't been properly closed. You might also want to look into the
valgrind --trace-children and related options.
Submit your work using git submit as usual. This homework's tag is: hw4 .
版权所有:编程辅导网 2021 All Rights Reserved 联系方式:QQ:99515681 微信:codinghelp 电子信箱:99515681@qq.com
免责声明:本站部分内容从网络整理而来,只供参考!如有版权问题可联系本站删除。