Lecture 6b - Unix Programming Odds and Ends (Errors, Standards, Streams, Buffering)
Lecture 6b - Unix Programming Odds and Ends (Errors, Standards, Streams, Buffering)
Based in part on Advanced Programming in the Unix Environment (3ed), Stevens and Rago
Announcements
Schedule for this week (Feb 12-16)
ffi
Operating system standards
• POSIX is a family of standards for Unix-style operating systems, initially developed by the IEEE
• It stands for Portable Operating System Interface (for Unix, presumably)
• It de ned the services that an operating system must provide in order to be ‘‘POSIX compliant’’
• "POSIX compliance" is conferred through formal (and apparently expensive) certi cation
• O cially POSIX-certi ed OS include
• Server-oriented OS (e.g. AIX - an IBM Unix system)
• macOS (Apple's desktop OS based on FreeBSD Unix)
• EulerOS (Huawei's Linux distro)
• etc
ffi
fi
fi
The POSIX standard in Unix
• Most Unix-like systems are o cially "mostly compliant" with the POSIX standard
• In practice, they are either made small modi cations where they wanted to, or didn't bother
with the o cial certi cation because of costs
• This includes
• Linux
• Android
• etc.
ffi
fi
ffi
fi
Specifying the POSIX standard
• When compiling your code, you may need to specify the minimum version of POSIX that your
code is compatible with - and that must be supported by the libraries available to the
compiler
ff
Specifying the POSIX standard
• In our case, if we want to compile our code with no implementation-de ned constants in the
Linux image running in Docker, we need to de ne the constant _POSIX_C_SOURCE and
associate a version of POSIX with it
• By setting _POSIX_C_SOURCE to 200809L we state that we only want to use the POSIX
2008-09 speci cation
• Nothing newer, no implementation-de ned extensions
fi
fi
fi
Specifying the POSIX standard
fi
Error handling approaches
• Functions that perform relatively complex tasks and can fail for a variety of reasons follow a general
pattern, regardless of the language they are written in
• They often need to return a "thing" on success ( le descriptor, socket, rendering context, etc.) or to
perform a task (e.g. initialize a pipe)
• If an error occurs, they need to pass the error details to the caller, so the error can be identi ed and
properly handled
ffi
fi
Error handling approaches
fl
fi
Error handling in Unix
• C has none of these mechanisms, so it defaults to using global state - some global variables
• When a process nishes executing a system function in kernel mode, it returns to user-mode
with the desired results and a return value, which is normally 0 for success or -1 for error.
• Some functions return a pointer - which points to a valid object on success and is NULL on error
• In case of error, the external global variable errno (de ned in errno.h) contains an ERROR
code which identi es the error.
• For example, the open function returns either a non-negative le descriptor if all is well, or −1
if an error occurs
• An error from open has about 15 possible errno values: le does not exist, permission problem,
etc..
fi
fi
errno - the magic error "variable"
• The le errno.h de nes the symbol errno and constants for each value that errno can
assume.
• Each of these constants begins with the character E
• E.g. if errno is equal to the constant EACCES, this indicates a permission problem, such as
insu cient permission to open the requested le
• In this course, we do not need to know what these constants are - we should not access
errno directly
ffi
fi
fi
fi
fi
errno - the magic error "variable"
• We can use strerror (string.h) to get a string representation of the error based on its
code
#include <string.h>
• We can pass the string returned by stressor to some other function that displays it on the
screen - or sends it to STDERR, to be more speci c
• see errorExample1.c
Turning errno into something readable
Option 2: perror
• Each login process - i.e. you opening an instance of the shell as a speci c user - opens three
le streams on its terminal:
• stdin for input (standard input)
• stdout for output (standard input)
• stderr for error output (standard error)
• Defaults are
• stdin is user input from the terminal (e.g. typing some characters)
• stdout is for normal output to the terminal/screen (e.g. that's what printf does by default)
• stderr is for abnormal output - i.e. errors. By default, it is also sent to the terminal/screen
fi
Review / preview: std... file streams
Each is a pointer to a FILE structure in the execution image’s heap
FILE *stdin //points to FILE structure
char fbuf[SIZE]
int counter, index, etc.
int fd = 0; // fd[0] in PROC <== from KEYBOARD
• E.g.
• write(STDOUT_FILENO, "I am normal output\n", 20);
• write(STDERR_FILENO, "I indicate an error\n", 21);
fi
Stream redirection
• A very common use for stream redirection is ignoring errors from a shell commands
• E.g. we want to nd out where the header unistd.h is
• $ find / -name unistd.h
• will generate a lot of errors (why?)
• hard to tell errors and output apart
• $ find / -name unistd.h 2>/dev/null
• Will pipe all error messages to the special le /dev/null - the Unix garbage bin
• We will only see the proper output, i.e. locations of all les named unistd.h in the directories
that out user has read access to
fi
fi
Standard streams and buffering
When to bu er
• As a result, typical C standard I/O is bu ered - e.g. reading from STDIN or writing to STDOUT
• So printf and scans are bu ering by default
ff
ff
ff
ff
ff
ff
ff
ff
ffi
ff
Standard streams and buffering
When not to bu er
• Bu ered output means that there will be a delay between the time your output function
returns and the time when the output appears on the screen
• Also, when debugging concurrent code (processes, threads) output from multiple concurrent
sources will be mixed together
• Having it be bu ered makes it very di cult to when exactly each message was printed
• Output may be printed out of order
• E.g.
• Process 1 calls printf
• write to bu er, OS scheduler puts it to sleep
• Process 2 calls printf
• write to bu er, gets placed on the screen
• bu er from Process 1 nally gets written to the screen
ff
ff
ff
ff
fi
ff
ffi
Standard streams and buffering
Who does what
• C standard library
• stdin and stdout are bu ered
• stderr is unbu ered
• Unix read / write
• Unbu ered by default
• In this course, we will be debugging and dealing with concurrency a lot
• Unbu ered output is nice for clarify and debugging, so we usually want our errors and debug
messages to be unbu ered
ff
ff
ff
ff
ff
Unbuffered output options
C library options
• If we do not care about keeping STDOUT and STDERR separate, we can just use
fprintf(stderr,...)
• unbu ered, one line of code, but everything goes to STDERR
• If we do want to keep the streams separate, we use
int fflush(FILE* stream)
• If the given stream was open for writing (or if it was open for updating and the last i/o operation
was an output operation) any unwritten data in its output bu er is written to the le
• Returns 0 on success, EOF on error
• E.g.
printf("Hello world\n");
fflush(stdout);
ff
Buffering in the example code
• E.g. in signalDebug.c
• Errors related to registering a handler for SIGUSR2 are written to STDERR
• Normal output is written to STDOUT
• I will sometimes also use write for unbu ered debug/error messages
• Or fprintf(stderr, ...) - which is OK for errors, but could be considered sloppy for
debugging
ff