--- /dev/null
+PLEASE TAKE NOTE!
+=================
+
+If you have difficulty building the programs, then the problem is most
+likely one of the following:
+
+a) A configuration issue on your system (e.g., do you have the 'libacl'
+ library installed?).
+b) You are using a system with an older Linux kernel or an older
+ version of glibc. If this is so, your system may not provide a
+ more recent system call or library function, or the glibc headers
+ may not provide a needed function declaration or constant
+ definition.
+
+I can't really help with fixing these problems. However, do take
+a look at the online code FAQ (http://man7.org/tlpi/code/faq.html)
+for some frequently asked questions about the code. You may find
+the answer to your problem there.
+
+If, after ensuring that the problem is not a configuration issue on your
+system (did you try compiling the program(s) on a different system?) and
+checking the FAQ, you still have a problem *and* you have a solution, then
+please do let me know, and I will publish the fix on the web site.
+
+
+Unpacking the code tarball
+==========================
+
+Unpacking the tarball with the command
+
+ tar xfz tlpi-YYMMDD-xxxx.tar.gz
+
+will create a directory tree 'tlpi-xxxx' containing all of the source code
+("xxxx" will be either "dist" or "book" depending on which version of the
+source code tarball you downloaded).
+
+
+Building the programs on Linux
+==============================
+
+(For instructions on building the programs on other UNIX
+implementations, see the notes lower down in this file.)
+
+The methods described below involve the use of 'make'. If you need
+more information on 'make', try the command 'man make' or 'info make'.
+
+
+Method A - Building all programs in all directories
+---------------------------------------------------
+
+Go into the 'tlpi' subdirectory, and type 'make':
+
+ cd tlpi-xxxx
+ make
+
+This will build all programs in all subdirectories.
+
+
+Method B - Build programs in individual directories
+---------------------------------------------------
+
+1) First, build the library used by all programs:
+
+ cd lib
+ make # This will make libtlpi.a and place
+ # it in the parent directory
+
+2) Build programs using the 'Makefile' in each subdirectory.
+
+In each subdirectory, there is a file called 'Makefile' that can be
+used with the 'make' command to build the programs in that directory.
+To build a particular program you can simply say:
+
+ make progname
+
+where 'progname' is one of the C (main) programs in the directory.
+Thus, to build the executable for the program 'copy.c' you would use
+the command:
+
+ make copy
+
+Each makefile also contains the following special targets (which are
+named according to the usual 'make' conventions):
+
+all This target will build all programs in the subdirectory.
+ Simply enter the command:
+
+ make all
+
+clean This target cleans up, by removing all executables and object
+ files created by the makefile:
+
+ make clean
+
+
+Building the programs on other UNIX implementations
+===================================================
+
+I have gone to some effort to ensure that, where possible, the programs
+work on other UNIX implementations also.
+
+Obviously, some of the example programs employ Linux-specific features,
+and won't compile on other UNIX implementations. As a first indication
+of what works on Linux, and what may work elsewhere, look in the
+makefiles in each directory. Most of the makefiles define two macros:
+
+LINUX_EXE These are programs that probably won't work
+ on anything other than Linux.
+
+GEN_EXE These programs may compile on other UNIX implementations.
+ I say "may", because of course not all implementations
+ provide exactly the same features. The presence of a
+ program in this list indicates that it compiles and runs
+ on at least some UNIX implementations other than Linux.
+
+
+Instructions
+------------
+
+1. Edit Makefile.inc in the 'tlpi' root directory to modify the
+ definitions of the CFLAGS and LDLIBS macros (and possibly other
+ macros depending on your version of make(1)) as appropriate.
+ Probably you'll need to define CFLAGS as follows:
+
+ CFLAGS += -g -I${TLPI_INCL_DIR}
+
+ The setting of LDLIBS is a bit harder to determine. As well as
+ listing the libary for this book, you should include '-l' linker
+ options for any other libraries that the programs may need.
+ For example, the following is suitable on Solaris 8:
+
+ LDLIBS = ${TLPI_LIB} -lsocket -lnsl -lrt -ldl -lm -lresolv
+
+ **** NOTE: Under the 'tlpi' root directory you'll find a few
+ sample replacement files for 'Makefile.inc', named
+ 'Makefile.inc.*'. You may be able to simply copy the
+ appropriate file to 'Makefile.inc'.
+
+2. Try the following to build all of the GEN_EXE programs:
+
+ cd tlpi-xxxx
+ make -k allgen
+
+ The '-k' option tells 'make' to build as much as possible, so that if
+ a particular program won't compile, 'make' goes on to attempt to
+ build the remaining programs.
--- /dev/null
+This file summarizes the changes that have been made since publication
+to the program examples printed in "The Linux Programming Interface".
+Background on some of these changes can be found in the online errata
+for TLPI, available at http://man7.org/tlpi/errata/.
+
+2010-11-13
+ sockets/us_abstract_bind.c
+ The code was improved as per the errata for page 1176.
+
+2011-01-17
+ timers/real_timer.c
+ A mistake in the ordering of the code in main() was fixed.
+ See the erratum for page 483.
+
+2011-02-17
+ psem/thread_incr_psem.c
+ Fixed an error in a comment. See the erratum for page 1102.
+
+2011-04-05
+ threads/thread_multijoin.c.
+ Fixed a race condition. See the erratum for page 649.
+ threads/prod_condvar.c
+ Fixed a race condition. The problem was similar to that
+ described in the erratum for page 649.
+ threads/prod_no_condvar.c
+ Fixed a race condition. The problem was similar to that
+ described in the erratum for page 649.
+
+2011-04-19
+ altio/demo_sigio.c
+ Fixed a race condition. See the erratum for page 1349.
+ daemons/daemon_SIGHUP.c
+ Fixed a race condition. See the erratum for page 774.
+
+2011-05-18
+ signals/t_kill.c
+ Fixed an error in a diagnostic message. See the erratum
+ for page 406.
+
+2011-07-06
+ Makefile
+ Added missing "memalloc" to the directory list.
+ memalloc/free_and_sbrk.c
+ Added feature test macro definition (_BSD_SOURCE).
+ See the erratum for page 142.
+
+2011-08-11
+ acl/acl_view.c
+ Fixed a small bug as per the erratum for page 336.
+
+2011-09-04
+ pipes/popen_glob.c
+ procexec/make_zombie.c
+ sockets/inet_sockets.c
+ Removed an unnecessary assignment statement that added
+ a terminating null byte to the string buffer output by
+ snprintf(). See the erratum for page 555.
+
+2011-12-05
+ dirs_links/t_unlink.c
+ Added a comment referring to the erratum for page 348.
+
+2011-12-06
+ pty/unbuffer.c
+ Change parent exit status when read() returns <= 0
+ from EXIT_FAILURE to EXIT_SUCCESS.
+
+2011-12-13
+ signals/catch_rtsigs.c
+ Restore 3 lines that were accidentally omitted at the
+ end of this program. See the erratum for page 463.
+
+2011-12-31
+ threads/thread_incr.c
+ Make global variable 'glob' volatile, so that the program
+ more easily produces "incorrect" behavior, even in the face
+ of compiler optimizations. See the erratum for page 632.
+
+2012-02-16
+ lib/alt_functions.c
+ Fix indentation in ALT_posix_openpt().
+
+2012-04-03
+ lib/Makefile
+ lib/Build_ename.sh
+ Refactor, so that Build_ename.sh produces output on stdout,
+ rather than to a named file.
+
+2012-04-06
+ lib/itimerspec_from_str.c
+ Conditionally make the content of this file empty if
+ compiling on MacOSX, since that operating system doesn't
+ define the 'itimerspec' structure.
+ (Only in "dist" version of code.)
+ Makefile.inc.MacOSX
+ Remove '-lrt' from the IMPL_LDLIBS definition, since there
+ is no librt on MacOSX.
+
+2012-05-04
+ fileio/seek_io.c
+ Fix a typo in comment at top of the program.
+ (Only in "dist" version of code.)
+
+2012-05-11
+ psem/Makefile
+ Correct the makefile to use "cc -pthreads" for POSIX
+ semaphores. Formerly, the makefile used "cc -lrt", but
+ recent toolchain changes mean that that this no longer works
+ ("cc -pthreads" always worked.) See the erratum for page 1061.
+ (Thanks to Robert P. J. Day.)
+ pshm/Makefile
+ Correct a bug in the makefile that caused link errors.
+ (Formerly, the makefile worked, but this was fortuitous;
+ recent toolchain changes revealed the bug.)
+
+2012-05-13
+ pshm/README
+ Fix a wordo.
+
+2012-05-20
+ README
+ Various small fixes.
+ (Thanks to Robert P. J. Day.)
+
+2012-05-25
+ BUILDING
+ Various small fixes.
+ (Thanks to Robert P. J. Day.)
+
+2012-05-26
+ memalloc/free_and_sbrk.c
+ Fix an error in the code comments at the top of the program.
+ (Only in "dist" version of code.)
+ (Thanks to Robert P. J. Day.)
+
+2012-05-31
+ svmsg/svmsg_info.c
+ svsem/svsem_info.c
+ svshm/svshm_info.c
+ Eliminate unnecessary inclusion of <sys/ipc.h> header file.
+ (Thanks to Robert P. J. Day.)
+
+2012-06-05
+ COPYING
+ Renamed to COPYING.agpl-v3
+ Added new files:
+ COPYING.gpl-v3
+ Copy of the GNU General Public License, version 3
+ COPYING.lgpl-v3
+ Copy of the GNU Lesser General Public License, version 3
+ lib/*
+ Changed the license of the library functions in the /lib
+ directory to GNU Lesser General Public License, version 3.
+ sockets/read_line_buf.c
+ sockets/read_line_buf.h
+ Created links for the sockets/read_line_buf.{c,h} files in the
+ lib/ directory, so that these files are licensed LGPLv3.
+
+ README
+ Updated to note the licensing of the library functions.
+
+2012-07-05
+ filesys/t_statfs.c
+ filesys/t_statvfs.c
+ Changed several printf() statements to use unsigned types for
+ various fields, since (in the case of the 'statvfs' structure at
+ least) these fields are specified as unsigned in SUSv[34].
+ (The 'statfs' structure isn't covered by SUSv[34], but using
+ unsigned types seems reasonable and safe.)
+
+2012-07-22
+ filelock/create_pid_file.c
+ Small wording fix in a comment.
+ (Only in "dist" version of code.)
+ procexec/execlp.c
+ Small wording fix in a comment.
+ svshm/svshm_xfr_reader.c
+ Small wording fix in a comment.
+ (Only in "dist" version of code.)
+ sysinfo/procfs_pidmax.c
+ Small wording fix in a comment.
+ (Only in "dist" version of code.)
+ threads/one_time_init.c
+ Small wording fix in a comment.
+
+2012-07-26
+ filebuff/direct_read.c
+ Fix an error in comment describing program arguments.
+ (Thanks to Jason Orendorff.)
+ (Only in "dist" version of code.)
+
+2012-08-03
+ loginacct/dump_utmpx.c
+ loginacct/utmpx_login.c
+ loginacct/view_lastlog.c
+ Remove unneeded casts.
+ See the errata for pages 824, 829, and 831.
+
+2012-09-10
+ loginacct/dump_utmpx.c
+ Fix error introduced in 2012-08-03 changes.
+ altio/epoll_input.c
+ Improve a comment.
+ See the erratum for page 1363.
+
+2012-09-27
+ svsem/Makefile
+ Fixed a bug in the Makefile that causes svsem_demo.c
+ not to be built.
+ (Thanks to Jinnan Wang.)
+
+2012-09-30
+ timers/real_timer.c
+ Use NULL instead of 0 for last argument of setitimer() call.
+ See the erratum for page 484.
+ (Thanks to Trevor Woerner.)
+
+2012-10-02
+ Makefile.inc
+ Add "-Wno-unused-but-set-variable" to compiler flags
+ (the IMPL_CFLAGS macro), to prevent compilation warnings
+ in three of the example programs. (Those warnings do not
+ correspond to real problems in the code.) The
+ "-Wunused-but-set-variable" flag was added (and turned on
+ by default) in GCC 4.6, which was released on 2011-03-35
+ (i.e., after TLPI was written).
+
+2012-10-15
+ loginacct/dump_utmpx.c
+ loginacct/utmpx_login.c
+ loginacct/view_lastlog.c
+ Revert the changes of 2012-08-03. These were made overlooking
+ the fact that in the 'utmpx' structure, 'tv_sec' is defined as
+ being of type 'int32_t', not 'time_t', so that the 2012-08-03
+ changes in fact cause warnings on 64-bit systems.
+ As a consequence, three (as yet unapplied) errata
+ for pages 824, 829, and 831 are removed.
+
+2012-10-18
+ procexec/demo_clone.c
+ Minor fix to a comment (s/will affect/may affect/).
+ Declare the variable that holds return status of write()
+ as 'ssize_t'.
+ (Thanks to Trevor Woerner.)
+
+2012-12-17
+ sockets/i6d_ucase_sv.c
+ Fix typo in a comment.
+ (Only in "dist" version of code.)
+ (Thanks to Kanak Kshetri.)
+ sockets/i6d_ucase_cl.c
+ Fix typo in a comment.
+ (Only in "dist" version of code.)
+
+2012-12-24
+ procexec/demo_clone.c
+ Add a comment at the top of the program clarifying that the
+ user must select a valid set of flags for the clone() call.
+ (Thanks to Jeffrey Thompson.)
+
+2013-01-02
+ altio/poll_pipes.c
+ Fix typos in two errExit() string arguments.
+ See the erratum for page 1340.
+
+2013-02-11
+ sockets/id_echo_sv.c
+ Remove unneeded use of the variable 'addrlen'.
+ See the erratum for page 1241.
+
+2013-03-09
+ procpri/sched_set.c
+ Fix a bug in the handling of the command-line arguments.
+ See the erratum for page 743.
+
+2013-03-10
+ procpri/Makefile
+ Move demo_sched_fifo from GEN_EXE to LINUX_EXE.
+ (Thanks to Antonio Jose Rodrigues.)
+ timers/Makefile
+ Remove demo_timerfd from GEN_EXE target.
+ (Thanks to Antonio Jose Rodrigues.)
+
+2013-03-11
+ sysinfo/t_uname.c
+ Make definition of _GNU_SOURCE conditional on __linux__.
+ See the erratum for page 230.
+
+2013-03-18
+ threads/detached_attrib.c
+ Fix a typo in a string:
+ errExitEN(s, "pthread_attr_getdetachstate");
+ ==>
+ errExitEN(s, "pthread_attr_setdetachstate");
+
+ Interestingly, this error was not present in the printed
+ version of the code (page 628 of TLPI).
+ (Thanks to Kanak Kshetri.)
+
+2013-04-05
+ svmsg/Makefile
+ Fix a typo that meant that 'svmsg_send' was not compiled.
+ (Thanks to George Yoshida.)
+
+2013-06-05
+ xattr/xattr_view.c
+ Fix error-handling code for usage diagnostic.
+ See the erratum for page 317.
+
+2013-07-11
+ timers/itimerspec_from_str.c
+ Change itimerspecFromStr() so that it does not modify
+ its string argument.
+ See the errata for pages 502 and 503.
+
+2013-09-13
+ procexec/acct_on.c
+ Add missing argument to the call to usageErr();
+ See the erratum for page 592.
+ (Thanks to Liu Jiaming.)
+
+2013-09-17
+ mmap/mmcopy.c
+ Correct argument pasted to msync() (s/src/dst/).
+ (Thanks to Robert P. J. Day.)
+
+2013-09-18
+ filebuff/write_bytes.c
+ Minor to changes to comments at top of the program.
+ filebuff/Makefile
+ Add entries to produce the non-vanilla flavors of the
+ write_bytes program:
+ write_bytes_fdatasync (do fdatasync() after each write)
+ write_bytes_fsync (do fsync() after each write)
+ write_bytes_o_fsync (open the file with O_SYNC)
+
+2013-09-22
+ procpri/t_setpriority.c
+ Fix typo in a diagnostic message.
+ See the erratum for page 737.
+
+2013-09-23
+ procpri/demo_sched_fifo.c
+ s/useCPU("child ")/useCPU("parent")/ near the end of
+ the program.
+ (Thanks to Liu Jiaming.)
+
+2013-09-23
+ README
+ Fixed a few typos.
+ (Thanks to Robert P. J. Day.)
+
+2013-10-18
+ svmsg/svmsg_file_client.c
+ Fix typo in a comment.
+ svmsg/svmsg_file_client.c
+ Remove a redundant msgctl(IPC_RMID) operation.
+ See the erratum for page 961.
+ (Thanks to Liu Jiaming.)
+
+2013-10-22
+ pipes/popen_glob.c
+ s/==/=/ inside a printf() string.
+ See the erratum for page 905.
+ (Thanks to Liu Jiaming.)
+
+2013-10-22
+ mmap/mmcat.c
+ mmap/mmcopy.c
+ Add code to check for a zero-length input file.
+ See the erratum for page 1023.
+ (Thanks to Liu Jiaming.)
+
+2013-10-25
+ filelock/t_flock.c
+ Fix an error in the message printed by
+ <span class="func">usageErr()</span>.
+ See the erratum for page 1121.
+ (Thanks to Liu Jiaming.)
+
+2013-10-30
+ mmap/mmcat.c
+ mmap/mmcopy.c
+ Fix a typo syntax error introduced in 2013-10-22 edits.
+ (Thanks to Yongzhi Pan.)
+
+2013-10-30
+ pipes/pipe_ls_wc.c
+ Fix a typo in comment at top of the program.
+ (Only in "dist" version of code.)
+
+2013-11-01
+ sockets/ud_ucase.h
+ Fix a wordo in comment.
+ See the erratum for page 1171.
+ (Thanks to Liu Jiaming.)
+
+2013-11-07
+ sockets/id_echo_cl.c
+ Fix a typo in usageErr() error message.
+ See the erratum for page 1242.
+ (Thanks to Liu Jiaming.)
+
+2013-11-11
+ altio/poll_pipes.c
+ Fix a bogus comment, and a glitch in a printf() call.
+ See the erratum for page 1341.
+ (Thanks to Liu Jiaming.)
+ altio/self_pipe.c
+ Fix a division error in the last printf() call
+ (divide by 1000, not 10000).
+ (Thanks to Liu Jiaming.)
+ altio/t_select.c
+ Fix a division error in the last printf() call
+ (divide by 1000, not 10000).
+ See the erratum for page 1336.
+ (Thanks to Liu Jiaming.)
+2013-11-5
+ dirs_links/nftw_dir_tree.c
+ Fix wordo in a comment
+ (Only in "dist" version of code.)
+ dirs_links/view_symlink.c
+ Added a comment explaining use of lstat().
+ (Only in "dist" version of code.)
+
+2013-11-25
+ pmsg/mq_notify_thread.c
+ Remove an unnecessary call to pthread_exit().
+ See the erratum for page 1082.
+
+2013-12-04
+ threads/thread_incr_mutex,c
+ Add "volatile" qualifier to declaration of 'glob'
+ See the erratum for page 636.
+ (Thanks to Arnaud Frugier.)
+
+2013-12-05
+ sockets/is_echo_sv.c
+ sockets/is_echo_v2_sv.c
+ sockets/scm_cred_recv.c
+ sockets/scm_rights_recv.c
+ sockets/socknames.c
+ sockets/us_xfr_sv.c
+ sockets/us_xfr_v2_sv.c
+ Change last argument of accept() from 'NULL' to '0'.
+
+2013-12-05
+ sockets/i6d_ucase_cl.c
+ sockets/ud_ucase_cl.c
+ Change last argument of recvfrom() from 'NULL' to '0'.
+
+2014-01-02
+ shlibs/dynload.c
+ Add a comment explaining SUSv4 TC1 changes that permit more
+ natural casts of function pointers returned by dlsym().
+ See the "update" erratum for page 864.
+ (Only in "dist" version of code.)
+
+2014-03-12
+ Makefile.inc
+ Add "-D_DEFAULT_SOURCE" to IMPL_CFLAGS. This avoids the
+
+ # warning "_BSD_SOURCE and _SVID_SOURCE are
+ deprecated, use _DEFAULT_SOURCE"
+
+ warnings that are produced when compiling code that
+ defines _SVID_SOURCE or _BSD_SOURCE against glibc headers
+ from version 2.20 onward.
+ sysinfo/procfs_user_exe.c
+ Move inclusion of our header files (ugid_functions.h and
+ tlpi_hdr.h) to follow other #include lines (as is done in
+ all other code).
+ vmem/madvise_dontneed.c
+ Fix a typo in a comment.
+ threads/thread_cleanup.c
+ Add mutex locking around assignment to 'glob'; see the
+ erratum for page 679.
+ (Only in "dist" version of code.)
+ (Thanks to Jingtian Zhang.)
+
+2014-05-21
+ inotify/demo_inotify.c
+ Properly align 'buf' on an 8-byte boundary.
+ See the erratum for page 383.
+ (Thanks to Matt Wojciak and Heinrich Schuchardt.)
+
+2014-05-31
+ Makefile
+ Add 'filebuff' and 'syslim' directories
+
+2014-06-20
+ timers/timed_read.c
+ Fix an off-by-one error in the read() call.
+
+2014-07-09
+ inotify/inotify_dtree.c
+ inotify/rand_dtree.c
+ New files: an application that provides a thorough-going
+ demonstration of the use of inotify for monitoring directory
+ subtrees, and an associated test program.
+
+2014-07-10
+ pmsg/Makefile
+ timers/Makefile
+ Minor fixes
+
+2014-07-24
+ sockets/i6d_ucase_cl.c
+ sockets/ud_ucase_cl.c
+ Change last argument of recvfrom() from "0" to "NULL".
+ (With this change, the code now matches that shown in the book.)
+ sockets/is_echo_sv.c
+ sockets/socknames.c
+ sockets/us_xfr_sv.c
+ Change last argument of accept() from "0" to "NULL".
+ (With this change, the code now matches that shown in the book.)
+ sockets/is_echo_v2_sv.c
+ sockets/scm_cred_recv.c
+ sockets/scm_rights_recv.c
+ sockets/us_xfr_v2_sv.c
+ Change last argument of accept() from "0" to "NULL".
+
+2014-11-05
+ pmsg/pmsg_create.c
+ Change the default value assigned to 'attr.mq_msgsize'.
+ See the erratum for page 1069.
+
+2014-11-12
+ acl/Makefile
+ Simplify Makefile by including 'libacl' in LDLIBS.
+ cap/Makefile
+ Remove crufty, unneeded rule; and simplify the Makefile
+ by including 'libcrypt' in LDLIBS.
+ pmsg/Makefile
+ Remove unneeded rule for building 'mq_notify_thread'.
+ psem/Makefile
+ Remove unneeded rule
+ progconc/Makefile
+ Remove unneeded rule for building 'syscall_speed'.
+ timers/Makefile
+ Most of the programs in this directory must be linked
+ against the realtime library, librt; simplify the Makefile
+ by linking all of the programs against that library.
+
+2014-11-14
+ Makefile.inc
+ Remove "-Wno-unused-but-set-variable" from IMPL_CFLAGS.
+ Remove redundant "-Wpointer-arith", which is anyway enabled
+ by -pedantic.
+ Remove unneeded "-Wno-format-y2k".
+ Remove unneeded "-Wno-unused-parameter".
+ progconc/syscall_speed.c
+ Minor change so that -Wunused-but-set-variable does not
+ give a warning.
+ signals/demo_SIGFPE.c
+ Minor change so that -Wunused-but-set-variable does not
+ give a warning.
+ signals/t_sigsuspend.c
+ Minor change so that -Wunused-but-set-variable does not
+ give a warning.
+ (Only in "dist" version of code.)
+
+2014-11-15
+ lib/*
+ Various files in this directory that were hard links
+ to files that were also linked in other directories
+ are now symbolic links.
+
+2014-11-22
+ threads/pthread_barrier_demo.c
+ New program demonstrating use of POSIX threads barriers API.
+ threads/thread_incr_rwlock.c
+ New program demonstrating use of POSIX threads rwlocks.
+ threads/thread_incr_spinlock.c
+ New program demonstrating use of POSIX threads spinlocks.
+ threads/thread_incr_mutex.c
+ Update header comments to refer to new spinlocks and
+ rwlocks programs.
+ threads/Makefile
+ Update to include targets for new program.
+
+2014-11-29
+ altio/poll_pipes.c
+ Change the 'timeout' argument in the poll() call from -1 to 0.
+ (See the erratum for page 1341.)
+
+2014-11-30
+ procexec/execlp.c
+ Minor comment and whitespace fixes.
+
+2014-12-01
+ pmsg/mq_notify_sigwaitinfo.c
+ Minor layout fix and removal of a redundant comment.
+ pmsg/mq_notify_sig.c
+ pmsg/mq_notify_thread.c
+ Remove a redundant comment.
+ (Only in "dist" version of code.)
+ pmsg/mq_notify_via_signal.c
+ pmsg/mq_notify_via_thread.c
+ New files demonstrating message queue notification via
+ signals and via threads.
+ pmsg/mq_notify_sigwaitinfo.c
+ pmsg/mq_notify_sig.c
+ pmsg/mq_notify_thread.c
+ Add comments noting that these programs do not handle the case
+ where a message is already on the queue by the time the first
+ attempt is made to register for message notification, along
+ with reference to code examples that addess this point.
+
+2014-12-03
+ sockets/list_host_addresses.c
+ A new small program demonstrating the use of getifaddrs(3).
+ socket/Makefile
+ Add list_host_addresses.c.
+
+2014-12-09
+ threads/thread_lock_speed.c
+ A new program allowing the performance of mutexes and
+ spin locks to be compared in a few different scenarios.
+
+2014-12-12
+ dirs_links/file_type_stats.c
+ Handle non-stat()-able files correctly.
+ dirs_links
+ Rework so that 'all' target includes examples using nftw()
+
+2014-12-17
+ README
+ The source code licensing for the "main" program examples
+ has changed from GNU Affero GPLv3 or later to plain
+ GNU GPLv3 or later.
+
+2015-01-26
+ filelock/i_fcntl_locking.c
+ Added support for the OFD locking commands (F_OFD_SETLK,
+ F_OFD_SETLKW, F_OFD_GETLKW) that were added to Linux in
+ version 3.15.
+ (Only in "dist" version of code.)
+
+2015-01-28
+ cap/check_password_caps.c
+ Fix a typo in a comment.
+ (Only in "dist" version of code.)
+
+2015-01-30
+ threads/thread_multijoin.c
+ Fix minor whitespace error.
+ (See the erratum for page 651.)
+
+2015-03-13
+ shlibs/dynload.c
+ Switch to using the more natural cast permitted by
+ POSIX.1-2008 TC1 (2013). See also the erratum for page 864.
+ (Only in "dist" version of code.)
+
+2015-03-13
+ shlibs/dynload.c
+ Tweak comment on dlsym() cast permitted by
+ POSIX.1-2008 TC1 (2013).
+ (Only in "dist" version of code.)
+
+2015-03-25
+ namespaces/Makefile
+ namespaces/demo_userns.c
+ namespaces/demo_uts_namespaces.c
+ namespaces/hostname.c
+ namespaces/multi_pidns.c
+ namespaces/ns_child_exec.c
+ namespaces/ns_exec.c
+ namespaces/ns_run.c
+ namespaces/orphan.c
+ namespaces/pidns_init_sleep.c
+ namespaces/simple_init.c
+ namespaces/t_setns_userns.c
+ namespaces/unshare.c
+ namespaces/userns_child_exec.c
+ namespaces/userns_setns_test.c
+ Added various namespaces-related code, covered in my article
+ series on LWN.net, start at https://lwn.net/Articles/531114/
+
+2015-03-25
+ Makefile
+ README
+ Adjusted for addition of "namespaces" subdirectory
+
+2015-04-21
+ inotify/rand_dtree.c
+ Add "#define _XOPEN_SOURCE 500" so that the program
+ builds cleanly from the command line. (It already built
+ fine using the Makefile.)
+ namespaces/README
+ Add a README explaining the origin of these programs.
+ sockets/scm_cred_recv.c
+ sockets/scm_cred_send.c
+ sockets/scm_rights_recv.c
+ sockets/scm_rights_send.c
+ Add a comment explaining use of a union to force alignment.
+
+2015-05-09
+ sysinfo/procfs_pidmax.c
+ lseek() to start of /proc/sys/kernel/pidmax.c file before
+ writing to it. (See the erratum for page 228.)
+
+2015-09-09
+ seccomp/libseccomp_demo.c
+ seccomp/seccomp_control_open.c
+ seccomp/seccomp_deny_open.c
+ seccomp/seccomp_perf.c
+ Added some seccomp examples. For more information see my
+ slides on seccomp at http://man7.org/conf and Jake Edge's
+ LWN.net write-up of one of my seccomp presentations at
+ http://lwn.net/Articles/656307/.
+
+2015-10-28
+ pmsg/mq_notify_via_thread.c
+ Rework code to remove a race condition.
+
+2015-11-05
+ signal/intquit.c
+ signal/ouch.c
+ signal/sig_receiver.c:
+ Add a comment explaining that sigaction() is preferred over
+ signal() when establishing signal handlers.
+ (Only in "dist" version of code.)
+
+2016-04-01
+ procexec/t_execle.c
+ Add an argument to the execle() call.
+
+2016-04-14
+ sockets/is_seqnum_sv.c
+ sockets/is_seqnum_v2_sv.c
+ Remove unneeded '&' in argument to write().
+
+2016-04-20
+ files/t_stat.c
+ Use <sys/sysmacros.h> instead of <sys/types.h>
+ to get the definitions of major() and minor().
+
+2016-04-29
+ */Makefile
+ Fix misnamed macro (LPLIB --> TLPI_LIB).
+ (Thanks to Ivo Tisch.)
+ lib/Makefile
+ Better dependency checking
+
+2016-05-05
+ psem/Makefile
+ Fix the target logic for psem_timedwait, so that it builds
+ correctly on systems where the vDSO doesn't export
+ clock_gettime(). (the x86 vDSO does export clock_gettime(),
+ which hid the error when building there.)
+
+2016-05-11
+ namespaces/userns_child_exec.c
+ Check that a command line argument is supplied.
+
+2016-05-13
+ shlibs/dynload.c
+ Remove unneeded check for NULL symbol value.
+ (See the erratum for page 865.)
+
+2016-06-05
+ filesys/t_mount.c
+ Support MS_LAZYTIME and MS_RELATIME flags
+ (Only in "dist" version of code.)
+
+2016-06-28
+ namespaces/unshare.c
+ Fix typo in usage() message.
+
+2016-07-01
+ psem/psem_timedwait.c
+ threads/thread_multijoin.c
+ Use slightly better text ('num-secs' vs 'nsecs') in
+ "usage" message.
+
+2016-07-25
+ sockets/ud_ucase_sv.c
+ sockets/us_xfr_sv.c
+ Add a length check for the server socket pathname.
+ (Only in "dist" version of code.)
+ (See the errata for pages 1168 and 1172.)
+
+2016-12-14
+ namespaces/userns_overview.go
+ Add a Go program that introspects the user namespace
+ hierarchy of the system.
+
+2017-04-19
+ sockets/us_abstract_bind.c
+ Fix a wording error in a comment (\0abc ==> /0xyz).
+
+2017-05-03
+ namespaces/userns_child_exec.c
+ Close unneeded file descriptor before doing execve().
+
+2017-06-09
+ loginacct/dump_utmpx.c
+ loginacct/view_lastlog.c
+ Fix handling of 'time_t' fields for cases where
+ fields may actually be smaller than 'time_t'.
+ (See the errata for pages 825 and 831.)
+
+2017-06-12
+ threads/thread_lock_speed.c
+ Add '-q' command-line option to cause the program to operate
+ quietly.
+
+2017-07-14
+ namespaces/ns_child_exec.c
+ Instead of '&argv[0]', use the simpler equivalent 'argv'
+
+2017-08-15
+ getopt/t_getopt.c
+ Add '__attribute__ ((__noreturn__))' to declaration of
+ usageError() to prevent "this statement may fall through"
+ warnings from "gcc -Wimplicit-fallthrough" in switch()
+ statement in main().
+ (Only in "dist" version of code.)
+
+2017-08-15
+ inotify/inotify_dtree.c
+ Eliminate "’snprintf’ output may be truncated before the last
+ format character" warning from "gcc -Wformat-truncation".
+
+2017-08-15
+ inotify/rand_dtree.c
+ Eliminate compiler warning about redefinition of
+ _XOPEN_SOURCE.
+
+2017-10-01
+ threads/thread_lock_speed.c
+ Add a alarm timer to prevent runaway/forgotten process
+ from burning CPU time forever
+
+2017-10-02
+ inotify/inotify_dtree.c
+ Remove some superfluous signal-related code.
+ (Thanks to Mirko Parthey.)
+
+2017-10-16
+ namespaces/multi_pidns.c
+ Allocate stacks for the child processes on the heap rather
+ than in static memory. Marcos Paulo de Souza pointed out
+ that the children were being killed by SIGSEGV after they
+ had completed the sleep() calls. (Some further investigation
+ showed that all children except the *last* are killed with
+ SIGSEGV.) It appears that they are killed after the child
+ start function returns. The problem goes away if the
+ children are allocated stacks in separate memory areas by
+ calling malloc() (which is the change made in this patch)
+ or in separate statically allocated buffers.
+
+ The reason that the children were killed is based on (my
+ misunderstanding of) the subtleties of the magic done
+ in the glibc clone() wrapper function. (See, for example,
+ the x86-64 implementation in the glibc source file
+ sysdeps/unix/sysv/linux/x86_64/clone.S). The
+ previous code was relying on the fact that the parent's
+ memory was duplicated in the child during the clone() system
+ call, and the assumption that that duplicated memory could be
+ used in the child. However, before executing the clone()
+ system call, the clone() wrapper function saves some
+ information (that will be used by the child) onto the stack.
+ This happens in the address space of the parent, before the
+ memory is duplicated in the system call. Since the previous
+ code was making use of the same statically allocated buffer
+ (i.e., the same address as was used for the parent's stack)
+ for the child stack, the consequence was that the steps in
+ the clone() wrapper function were corrupting the stack of the
+ *parent* process, which ultimately resulted in (all but the
+ last of) the child processes crashing.
+
+2017-10-17
+ namespaces/demo_userns.c
+ namespaces/demo_uts_namespaces.c
+ namespaces/ns_child_exec.c
+ namespaces/pidns_init_sleep.c
+ namespaces/userns_cap_sig_expt.c
+ namespaces/userns_child_exec.c
+ namespaces/userns_setns_test.c
+ Allocate child stack on heap, rather than statically.
+ Although this is not strictly necessary in these programs
+ (since only one clone() child is created in each of the
+ programs), using dynamically allocated memory is good
+ pedagogical practice, to prevent problems such as were
+ revealed in the namespaces/multi_pidns.c program.
--- /dev/null
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU Affero General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<http://www.gnu.org/licenses/>.
--- /dev/null
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
--- /dev/null
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
--- /dev/null
+# Makefile to build all programs in all subdirectories
+#
+# DIRS is a list of all subdirectories containing makefiles
+# (The library directory is first so that the library gets built first)
+#
+
+DIRS = lib \
+ acl altio \
+ cap \
+ daemons dirs_links \
+ filebuff fileio filelock files filesys getopt \
+ inotify \
+ loginacct \
+ memalloc \
+ mmap \
+ pgsjc pipes pmsg \
+ proc proccred procexec procpri procres \
+ progconc \
+ psem pshm pty \
+ shlibs \
+ signals sockets \
+ svipc svmsg svsem svshm \
+ sysinfo \
+ syslim \
+ threads time timers tty \
+ users_groups \
+ vmem \
+ xattr
+
+# The "namespaces" directory is deliberately excluded from the above
+# list because much of the code requires a fairly recent kernel and
+# userspace to build. Nevertheless, there is a Makefile in that directory.
+
+BUILD_DIRS = ${DIRS}
+
+
+# Dummy targets for building and clobbering everything in all subdirectories
+
+all:
+ echo ${BUILD_DIRS}
+ @ for dir in ${BUILD_DIRS}; do (cd $${dir}; ${MAKE}) ; done
+
+allgen:
+ @ for dir in ${BUILD_DIRS}; do (cd $${dir}; ${MAKE} allgen) ; done
+
+clean:
+ @ for dir in ${BUILD_DIRS}; do (cd $${dir}; ${MAKE} clean) ; done
--- /dev/null
+# Makefile.inc - common definitions used by all makefiles
+
+TLPI_DIR = ..
+TLPI_LIB = ${TLPI_DIR}/libtlpi.a
+TLPI_INCL_DIR = ${TLPI_DIR}/lib
+
+LINUX_LIBRT = -lrt
+LINUX_LIBDL = -ldl
+LINUX_LIBACL = -lacl
+LINUX_LIBCRYPT = -lcrypt
+LINUX_LIBCAP = -lcap
+
+# "-Wextra" is a more descriptive synonym for "-W", but only
+# available in more recent gcc versions
+
+# Defining _DEFAULT_SOURCE is a workaround to avoid the warnings that
+# would otherwise be produced when compiling code that defines _BSD_SOURCE
+# or _SVID_SOURCE against glibc headers in version 2.20 and later.
+# (The alternative would be to replace each instance of "#define _SVID_SOURCE"
+# or "#define _BSD_SOURCE" in the example programs with
+# "#define _DEFAULT_SOURCE".)
+
+IMPL_CFLAGS = -std=c99 -D_XOPEN_SOURCE=600 \
+ -D_DEFAULT_SOURCE \
+ -g -I${TLPI_INCL_DIR} \
+ -pedantic \
+ -Wall \
+ -W \
+ -Wmissing-prototypes \
+ -Wno-sign-compare \
+ -Wno-unused-parameter
+
+CFLAGS = ${IMPL_CFLAGS}
+
+IMPL_THREAD_FLAGS = -pthread
+
+IMPL_LDLIBS = ${TLPI_LIB} -lm
+
+LDLIBS = ${IMPL_LDLIBS}
+
+RM = rm -f
--- /dev/null
+# Makefile.inc - common definitions used by all makefiles
+# FreeBSD version
+
+TLPI_DIR = ..
+TLPI_LIB = ${TLPI_DIR}/libtlpi.a
+TLPI_INCL_DIR = ${TLPI_DIR}/lib
+
+IMPL_CFLAGS += -g -I${TLPI_INCL_DIR}
+CFLAGS = ${IMPL_CFLAGS}
+IMPL_THREAD_FLAGS = -pthread
+
+IMPL_LDLIBS = ${TLPI_LIB} -lcrypt -lm
+LDLIBS = ${IMPL_LDLIBS}
+
+RM = rm -f
--- /dev/null
+# Makefile.inc - common definitions used by all makefiles
+# HP-UX Version
+
+TLPI_DIR = ..
+TLPI_LIB = ${TLPI_DIR}/libtlpi.a
+TLPI_INCL_DIR = ${TLPI_DIR}/lib
+
+LINUX_LIBRT = -lrt
+LINUX_LIBDL = -ldl
+LINUX_LIBACL = -lacl
+LINUX_LIBCRYPT = -lcrypt
+LINUX_LIBCAP = -lcap
+
+IMPL_CFLAGS = -g -I${TLPI_INCL_DIR} -D_XOPEN_SOURCE_EXTENDED
+CFLAGS = ${IMPL_CFLAGS}
+IMPL_THREAD_FLAGS = -mt
+
+IMPL_LDLIBS = ${TLPI_LIB} -lm
+LDFLAGS = ${IMPL_LDLIBS}
+LDLIBS = ${IMPL_LDLIBS}
+
+RM = rm -f
--- /dev/null
+# Makefile.inc - common definitions used by all makefiles
+# Mac OS (Lion) version
+
+TLPI_DIR = ..
+TLPI_LIB = ${TLPI_DIR}/libtlpi.a
+TLPI_INCL_DIR = ${TLPI_DIR}/lib
+
+IMPL_CFLAGS += -g -I${TLPI_INCL_DIR}
+CFLAGS = ${IMPL_CFLAGS}
+IMPL_THREAD_FLAGS =
+
+IMPL_LDLIBS = ${TLPI_LIB} -lm
+LDLIBS = ${IMPL_LDLIBS}
+
+RM = rm -f
--- /dev/null
+# Makefile.inc - common definitions used by all makefiles
+# Solaris version
+
+TLPI_DIR = ..
+TLPI_LIB = ${TLPI_DIR}/libtlpi.a
+TLPI_INCL_DIR = ${TLPI_DIR}/lib
+
+IMPL_CFLAGS += -g -I${TLPI_INCL_DIR} -D__EXTENSIONS__ -D_XOPEN_SOURCE=600 -D_XPG4_2 -D_POSIX_C_SOURCE=199506
+CFLAGS = ${IMPL_CFLAGS}
+IMPL_THREAD_FLAGS = -mt
+
+IMPL_LDLIBS = ${TLPI_LIB} -lsocket -lnsl -lrt -ldl -lm -lresolv
+LDLIBS = ${IMPL_LDLIBS}
+
+RM = rm -f
--- /dev/null
+# Makefile.inc - common definitions used by all makefiles
+# Tru64 version
+
+TLPI_DIR = ..
+TLPI_LIB = ${TLPI_DIR}/libtlpi.a
+TLPI_INCL_DIR = ${TLPI_DIR}/lib
+
+IMPL_CFLAGS = -g -I${TLPI_INCL_DIR} -D_POSIX_PII_SOCKET -D_OSF_SOURCE
+CFLAGS = ${IMPL_CFLAGS}
+IMPL_THREAD_FLAGS = -pthread
+
+IMPL_LDLIBS = ${TLPI_LIB} -lrt -lm
+LOADLIBES = ${IMPL_LDLIBS}
+LDLIBS = ${IMPL_LDLIBS}
+
+RM = rm -f
--- /dev/null
+Gidday!
+
+This is the code for the book "The Linux Programming Interface"
+and this is a note from me, the author, Michael Kerrisk.
+
+For instructions on building the programs, see the file BUILDING.
+
+For notes on changes that have been made to the code since it was
+published in the book, see the file CHANGES.
+
+
+Source code licensing
+=====================
+
+All complete programs provided in this distribution are covered by
+the GNU General Public License (Version 3), a copy of which is
+contained in the file COPYING.gpl-v3, which should have arrived with
+this tarball. The library functions (in the lib/ directory) are
+covered by the GNU Lesser General Public License (Version 3); see the
+file COPYING.lgpl-v3 provided with this tarball.
+
+
+A note on the source code
+=========================
+
+The source code is available in two versions: "dist" and "book".
+The "book" version contains the program source files as published in
+the book. The source files in the "dist" version contain extra code
+beyond that published in the book. The differences between the "dist"
+and "book" versions are as follows:
+
+a) The "dist" versions of some programs contain extra comments.
+ These additional comments were stripped out of the printed version
+ to make the published versions of the programs shorter. (The book
+ itself contains text describing the operation of the programs.)
+
+b) In a few cases, some changes have been incorporated into the
+ "dist" versions to make it possible to compile programs on UNIX
+ implementations other than Linux, so that you can try them out
+ on other implementations if you wish. Where necessary, the
+ additional code is conditionally compiled using the following
+ platform-specific macros:
+
+ __linux__ Defined on Linux
+ __sun Defined on Solaris
+ __FreeBSD__ Defined on FreeBSD
+ __NetBSD__ Defined on NetBSD
+ __OpenBSD__ Defined on OpenBSD
+ __APPLE__ Defined on Mac OS X
+ __hpux Defined on HP-UX
+ __osf__ Defined on Tru64 UNIX (formerly DEC OSF1)
+ __sgi Defined on Irix
+ _AIX Defined on AIX
+
+c) In the "dist" version, some programs have extra functionality beyond
+ that in the "book" versions. Where this is significant, comments in
+ the programs explain the differences.
+
+
+Subdirectories
+==============
+
+Under the 'tlpi' directory are a number of subdirectories. Each
+subdirectory corresponds to one or more chapters of the book.
+The following paragraphs give brief notes on the contents of
+each subdirectory.
+
+Note that in some cases, files are (hard) linked to appear in more than
+one directory. This is particularly the case for each of the files in
+the 'lib' directory, most of which are also linked in the directory
+of the chapter relating to that file.
+
+Directory Files for Chapter...
+
+lib This contains library routines used by other
+ programs. The tlpi_hdr.h and error_functions.*
+ files are located here.
+
+progconc 3 (System Programming Concepts)
+
+fileio 4 and 5 (File I/O)
+
+proc 6 (Processes)
+
+memalloc 7 (Memory Allocation)
+
+users_groups 8 (Users and Groups)
+
+proccred 9 (Process Credentials)
+
+time 10 (Time)
+
+syslim 11 (System Limits and Options)
+
+sysinfo 12 (System and Process Information)
+
+filebuff 13 (File I/O Buffering)
+
+filesys 14 (File Systems)
+
+files 15 (File Attributes)
+
+xattr 16 (Extended Attributes)
+
+acl 17 (Access Control Lists)
+
+dirs_links 18 (Directories and Links)
+
+inotify 19 (Monitoring File Events)
+
+signals 20 to 22 (Signals)
+
+timers 23 (Timers and Sleeping)
+
+procexec 24 (Process Creation), 25 (Process Termination),
+ 26 (Monitoring Child Processes), 27 (Program Execution),
+ and 28 (Further Details on Process Creation and Program
+ Execution)
+
+threads 29 to 33 (POSIX Threads)
+
+pgsjc 34 (Process Groups, Sessions, and Job Control)
+
+procpri 35 (Process Priorities and Scheduling)
+
+procres 36 (Process Resources)
+
+daemons 37 (Daemons)
+
+cap 39 (Capabilities)
+
+loginacct 40 (Login Accounting)
+
+shlibs 41 and 42 (Shared Libraries)
+
+pipes 44 (Pipes and FIFOs)
+
+svipc 45 (System V IPC)
+
+svmsg 46 (System V Message Queues)
+
+svsem 47 (System V Semaphores)
+
+svshm 48 (System V Shared Memory)
+
+mmap 49 (Memory Mappings)
+
+vmem 50 (Virtual Memory Operations)
+
+pmsg 52 (POSIX Message Queues)
+
+psem 53 (POSIX Semaphores)
+
+pshm 54 (POSIX Shared Memory)
+
+filelock 55 (File Locking)
+
+sockets 56 to 61 (Sockets and Network Programming)
+
+tty 62 (Terminals)
+
+altio 63 (Alternative I/O Models)
+
+pty 64 (Pseudoterminals)
+
+getopt Appendix B: Parsing Command-Line Options
+
+In addition, the following supplementary code is included (relating
+to topics NOT covered in TLPI):
+
+namespaces Code examples for namespaces, mainly related to my LWN.net
+ article series starting at https://lwn.net/Articles/531114/
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE =
+
+LINUX_EXE = acl_update acl_view
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+LDLIBS = ${IMPL_LDLIBS} ${LINUX_LIBACL}
+ # All of the programs in this directory need the
+ # ACL library, libacl.
+
+clean :
+ ${RM} ${EXE} *.o
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 17 */
+
+/* acl_update.c
+
+ Perform updates on the access control lists (ACLs) of files named in the
+ command line. This program provides a subset of the functionality of the
+ setfacl(1) command. For usage, see usageError() below.
+
+ This program is Linux-specific. ACLs are supported since Linux 2.6.
+ To build this program, you must have the ACL library (libacl) installed
+ on your system.
+*/
+#include <sys/acl.h>
+#include "ugid_functions.h"
+#include "tlpi_hdr.h"
+
+#define MAX_ENTRIES 10000 /* Maximum entries that we can handle in an ACL */
+
+struct AccessControlEntry { /* Represent a single ACL entry */
+ acl_tag_t tag; /* Tag type */
+ id_t qual; /* Optional tag qualifier (UID or GID) */
+ int perms; /* Permissions bit mask */
+};
+
+static void
+usageError(char *progName, char *msg, Boolean shortUsage)
+{
+ if (msg != NULL)
+ fprintf(stderr, "%s\n", msg);
+
+ if (shortUsage) {
+ fprintf(stderr, "Type '%s --help' for usage information\n", progName);
+ exit(EXIT_FAILURE);
+ }
+
+ fprintf(stderr, "Usage: %s -m acl [-d] [-n] file...\n", progName);
+ fprintf(stderr, " or: %s -x acl [-d] [-n] file...\n", progName);
+ fprintf(stderr, " or: %s -k file...\n\n", progName);
+ fprintf(stderr, " or: %s -V acl\n\n", progName);
+#define fpe(msg) fprintf(stderr, " " msg);
+ fpe("-m Modify/create ACL entries\n");
+ fpe("-x Remove ACL entries\n");
+ fpe("-k Remove default ACL\n\n");
+ fpe("-V Check validity of 'acl'\n\n");
+ fpe("'acl' consists of one or more comma-separated entries of the form:\n");
+ fpe("\n tag:[qualifier][:[perms]]\n\n");
+ fpe(" 'perms' are specified only for -m\n\n");
+ fpe("-d Apply operation to default ACL\n");
+ fpe("-n Don't recalculate mask entry\n");
+ fpe(" NOTE: if you specify this option and have specified\n");
+ fpe(" the -m option, then you may encounter errors if the\n");
+ fpe(" file does not already have a mask entry.\n");
+ exit(EXIT_FAILURE);
+}
+
+/* Parse an ACL entry specification (the null-terminated string
+ provided in 'entryStr') of the form:
+
+ tag:[qualifier][:[permissions]]
+
+ returning the parsed information in the structure 'ace'.
+
+ If 'permsReqd' is set, then the ACL entries must contain
+ permission specifications (i.e., a colon followed by at least
+ one of [-rwx], otherwise they must not.
+
+ Return TRUE if the specification parsed okay, or FALSE otherwise. */
+
+static Boolean
+parseEntrySpec(char *entryStr, struct AccessControlEntry *ace,
+ Boolean permsReqd)
+{
+ char *colon1, *colon2;
+ Boolean hasQual; /* Is optional qualifier present? */
+ Boolean hasPerms; /* Are permissions specified? */
+
+ colon1 = strchr(entryStr, ':');
+ if (colon1 == NULL) {
+ fprintf(stderr, "Missing initial colon in ACL entry: %s\n", entryStr);
+ return FALSE;
+ }
+
+ hasQual = *(colon1 + 1) != '\0' && *(colon1 + 1) != ':';
+
+ *colon1 = '\0'; /* Add terminator to tag type */
+
+ colon2 = strchr(colon1 + 1, ':');
+ hasPerms = colon2 != NULL && *(colon2 + 1) != '\0';
+
+ if (hasPerms && !permsReqd) {
+ fprintf(stderr, "Cannot specify permissions here\n");
+ return FALSE;
+ }
+
+ if (!hasPerms && permsReqd) {
+ fprintf(stderr, "Must specify permissions\n");
+ return FALSE;
+ }
+
+ /* Determine tag type, depending on tag string and presence
+ of qualifier after the first ':' */
+
+ if (strcmp(entryStr, "u") == 0 || strcmp(entryStr, "user") == 0)
+ ace->tag = hasQual ? ACL_USER : ACL_USER_OBJ;
+ else if (strcmp(entryStr, "g") == 0 || strcmp(entryStr, "group") == 0)
+ ace->tag = hasQual ? ACL_GROUP : ACL_GROUP_OBJ;
+ else if (strcmp(entryStr, "o") == 0 || strcmp(entryStr, "other") == 0)
+ ace->tag = ACL_OTHER;
+ else if (strcmp(entryStr, "m") == 0 || strcmp(entryStr, "mask") == 0)
+ ace->tag = ACL_MASK;
+ else {
+ fprintf(stderr, "Bad tag: %s\n", entryStr);
+ return FALSE;
+ }
+
+ /* For ACL_USER and ACL_GROUP tags, extract a UID / GID from qualifier */
+
+ if (colon2 != NULL)
+ *colon2 = '\0'; /* Add terminator to qualifier */
+
+ ace->qual = 0;
+
+ if (ace->tag == ACL_USER) {
+ ace->qual = userIdFromName(colon1 + 1);
+ if (ace->qual == -1) {
+ fprintf(stderr, "Bad user ID: %s\n", colon1 + 1);
+ return FALSE;
+ }
+ } else if (ace->tag == ACL_GROUP) {
+ ace->qual = groupIdFromName(colon1 + 1);
+ if (ace->qual == -1) {
+ fprintf(stderr, "Bad group ID: %s\n", colon1 + 1);
+ return FALSE;
+ }
+ }
+
+ /* If a permissions string was present, return it as a bit mask */
+
+ if (hasPerms) {
+ char *p;
+
+ ace->perms = 0;
+
+ /* We're not too thorough here -- we don't check for multiple
+ instances of [-rwx], or check if the permissions string is
+ longer than three characters... */
+
+ for (p = colon2 + 1; *p != '\0'; p++) {
+ if (*p == 'r')
+ ace->perms |= ACL_READ;
+ else if (*p == 'w')
+ ace->perms |= ACL_WRITE;
+ else if (*p == 'x')
+ ace->perms |= ACL_EXECUTE;
+ else if (*p != '-') {
+ fprintf(stderr, "Bad character in permissions "
+ "string: %c\n", *p);
+ return FALSE;
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+/* Parse a text form ACL, returning information about the entries in
+ 'aclist'. On success, return the number of ACL entries found; on
+ error return -1. */
+
+static int
+parseACL(char *aclStr, struct AccessControlEntry aclist[],
+ Boolean permsReqd)
+{
+ char *nextEntry, *comma;
+ int n;
+
+ n = 0;
+ for (nextEntry = aclStr; ; nextEntry = comma + 1) {
+
+ if (n >= MAX_ENTRIES) {
+ fprintf(stderr, "Too many entries in ACL\n");
+ return -1;
+ }
+
+ comma = strchr(nextEntry, ',');
+ if (comma != NULL)
+ *comma = '\0';
+
+ if (!parseEntrySpec(nextEntry, &aclist[n], permsReqd))
+ return -1;
+
+ if (comma == NULL) /* This was the last entry */
+ break;
+
+ n++;
+ }
+
+ return n + 1;
+}
+
+/* Find the the ACL entry in 'acl' corresponding to the tag type and
+ qualifier in 'tag' and 'id'. Return the matching entry, or NULL
+ if no entry was found. */
+
+static acl_entry_t
+findEntry(acl_t acl, acl_tag_t tag, id_t qaul)
+{
+ acl_entry_t entry;
+ acl_tag_t entryTag;
+ uid_t *uidp;
+ gid_t *gidp;
+ int ent, s;
+
+ for (ent = ACL_FIRST_ENTRY; ; ent = ACL_NEXT_ENTRY) {
+ s = acl_get_entry(acl, ent, &entry);
+ if (s == -1)
+ errExit("acl_get_entry");
+
+ if (s == 0)
+ return NULL;
+
+ if (acl_get_tag_type(entry, &entryTag) == -1)
+ errExit("acl_get_tag_type");
+
+ if (tag == entryTag) {
+ if (tag == ACL_USER) {
+ uidp = acl_get_qualifier(entry);
+ if (uidp == NULL)
+ errExit("acl_get_qualifier");
+
+ if (qaul == *uidp) {
+ if (acl_free(uidp) == -1)
+ errExit("acl_free");
+ return entry;
+ } else {
+ if (acl_free(uidp) == -1)
+ errExit("acl_free");
+ }
+
+ } else if (tag == ACL_GROUP) {
+ gidp = acl_get_qualifier(entry);
+ if (gidp == NULL)
+ errExit("acl_get_qualifier");
+
+ if (qaul == *gidp) {
+ if (acl_free(gidp) == -1)
+ errExit("acl_free");
+ return entry;
+ } else {
+ if (acl_free(gidp) == -1)
+ errExit("acl_free");
+ }
+
+ } else {
+ return entry;
+ }
+ }
+ }
+}
+
+/* Set the permissions in 'perms' in the ACL entry 'entry' */
+
+static void
+setPerms(acl_entry_t entry, int perms)
+{
+ acl_permset_t permset;
+
+ if (acl_get_permset(entry, &permset) == -1)
+ errExit("acl_get_permset");
+
+ if (acl_clear_perms(permset) == -1)
+ errExit("acl_clear_perms");
+
+ if (perms & ACL_READ)
+ if (acl_add_perm(permset, ACL_READ) == -1)
+ errExit("acl_add_perm");
+ if (perms & ACL_WRITE)
+ if (acl_add_perm(permset, ACL_WRITE) == -1)
+ errExit("acl_add_perm");
+ if (perms & ACL_EXECUTE)
+ if (acl_add_perm(permset, ACL_EXECUTE) == -1)
+ errExit("acl_add_perm");
+
+ if (acl_set_permset(entry, permset) == -1)
+ errExit("acl_set_permset");
+}
+
+int
+main(int argc, char *argv[])
+{
+ Boolean recalcMask, useDefaultACL;
+ Boolean modifyACL, removeACL, removeDefaultACL, checkValidity;
+ int optCnt, j, opt, numEntries, en;
+ acl_type_t type;
+ char *aclSpec;
+ acl_t acl;
+ acl_entry_t entry;
+ struct AccessControlEntry aclist[MAX_ENTRIES];
+
+ if (argc < 2 || strcmp(argv[1], "--help") == 0)
+ usageError(argv[0], NULL, FALSE);
+
+ /* Parse command-line options */
+
+ recalcMask = TRUE;
+ useDefaultACL = FALSE;
+ modifyACL = FALSE;
+ removeACL = FALSE;
+ checkValidity = FALSE;
+ removeDefaultACL = FALSE;
+ optCnt = 0;
+
+ while ((opt = getopt(argc, argv, "m:x:kdnV:")) != -1) {
+ switch (opt) {
+ case 'm':
+ modifyACL = TRUE;
+ aclSpec = optarg;
+ optCnt++;
+ break;
+
+ case 'x':
+ removeACL = TRUE;
+ aclSpec = optarg;
+ optCnt++;
+ break;
+
+ case 'k':
+ removeDefaultACL = TRUE;
+ optCnt++;
+ break;
+
+ case 'V':
+ checkValidity = TRUE;
+ aclSpec = optarg;
+ optCnt++;
+ break;
+
+ case 'd':
+ useDefaultACL = TRUE;
+ break;
+
+ case 'n':
+ recalcMask = FALSE;
+ break;
+
+ default:
+ usageError(argv[0], "Bad option\n", TRUE);
+ break;
+ }
+ }
+
+ if (optCnt != 1)
+ usageError(argv[0], "Specify exactly one of -m, -x, -k, or -V\n", TRUE);
+
+ if (checkValidity && useDefaultACL)
+ usageError(argv[0], "Can't specify -d with -V\n", TRUE);
+
+ if (checkValidity) {
+ if (parseACL(aclSpec, aclist, TRUE) == -1) {
+ fatal("Bad ACL entry specification");
+ } else {
+ printf("ACL is valid\n");
+ exit(EXIT_SUCCESS);
+ }
+ }
+
+ if (modifyACL || removeACL) {
+ numEntries = parseACL(aclSpec, aclist, modifyACL);
+ if (numEntries == -1)
+ usageError(argv[0], "Bad ACL specification\n", TRUE);
+ }
+
+ type = useDefaultACL ? ACL_TYPE_DEFAULT : ACL_TYPE_ACCESS;
+
+ /* Perform the operation on each file argument */
+
+ for (j = optind; j < argc; j++) {
+ if (removeDefaultACL) {
+ if (acl_delete_def_file(argv[j]) == -1)
+ errExit("acl_delete_def_file: %s", argv[j]);
+
+ } else if (modifyACL || removeACL) {
+
+ acl = acl_get_file(argv[j], type);
+ if (acl == NULL)
+ errExit("acl_get_file");
+
+ /* Apply each of the entries in 'aclist' to the
+ current file */
+
+ for (en = 0; en < numEntries; en++) {
+ entry = findEntry(acl, aclist[en].tag, aclist[en].qual);
+
+ if (removeACL) {
+ if (entry != NULL)
+ if (acl_delete_entry(acl, entry) == -1)
+ errExit("acl_delete_entry");
+
+ } else { /* modifyACL */
+
+ if (entry == NULL) {
+
+ /* Entry didn't exist in ACL -- create a new
+ entry with required tag and qualifier */
+
+ if (acl_create_entry(&acl, &entry) == -1)
+ errExit("acl_create_entry");
+ if (acl_set_tag_type(entry, aclist[en].tag) == -1)
+ errExit("acl_set_tag_type");
+ if (aclist[en].tag == ACL_USER ||
+ aclist[en].tag == ACL_GROUP)
+ if (acl_set_qualifier(entry,
+ &aclist[en].qual) == -1)
+ errExit("acl_set_qualifier");
+ }
+
+ setPerms(entry, aclist[en].perms);
+ }
+
+ /* Recalculate the mask entry if requested */
+
+ if (recalcMask)
+ if (acl_calc_mask(&acl) == -1)
+ errExit("acl_calc_mask");
+
+ /* Update the file ACL */
+
+ if (acl_valid(acl) == -1)
+ errExit("acl_valid");
+
+ if (acl_set_file(argv[j], type, acl) == -1)
+ errExit("acl_set_file");
+ }
+
+ if (acl_free(acl) == -1)
+ errExit("acl_free");
+ } else {
+ fatal("Bad logic!");
+ }
+ }
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 17-1 */
+
+/* acl_view.c
+
+ Display the access control list (ACL) on a file.
+
+ Usage: acl_view [-d] file
+
+ If the '-d' option is specified, then the default ACL is displayed (and
+ 'file' must be a directory), otherwise the access ACL is displayed.
+
+ This program is Linux-specific. ACLs are supported since Linux 2.6.
+ To build this program, you must have the ACL library (libacl) installed
+ on your system.
+*/
+#include <acl/libacl.h>
+#include <sys/acl.h>
+#include "ugid_functions.h"
+#include "tlpi_hdr.h"
+
+static void
+usageError(char *progName)
+{
+ fprintf(stderr, "Usage: %s [-d] filename\n", progName);
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ acl_t acl;
+ acl_type_t type;
+ acl_entry_t entry;
+ acl_tag_t tag;
+ uid_t *uidp;
+ gid_t *gidp;
+ acl_permset_t permset;
+ char *name;
+ int entryId, permVal, opt;
+
+ type = ACL_TYPE_ACCESS;
+ while ((opt = getopt(argc, argv, "d")) != -1) {
+ switch (opt) {
+ case 'd': type = ACL_TYPE_DEFAULT; break;
+ case '?': usageError(argv[0]);
+ }
+ }
+
+ if (optind + 1 != argc)
+ usageError(argv[0]);
+
+ acl = acl_get_file(argv[optind], type);
+ if (acl == NULL)
+ errExit("acl_get_file");
+
+ /* Walk through each entry in this ACL */
+
+ for (entryId = ACL_FIRST_ENTRY; ; entryId = ACL_NEXT_ENTRY) {
+
+ if (acl_get_entry(acl, entryId, &entry) != 1)
+ break; /* Exit on error or no more entries */
+
+ /* Retrieve and display tag type */
+
+ if (acl_get_tag_type(entry, &tag) == -1)
+ errExit("acl_get_tag_type");
+
+ printf("%-12s", (tag == ACL_USER_OBJ) ? "user_obj" :
+ (tag == ACL_USER) ? "user" :
+ (tag == ACL_GROUP_OBJ) ? "group_obj" :
+ (tag == ACL_GROUP) ? "group" :
+ (tag == ACL_MASK) ? "mask" :
+ (tag == ACL_OTHER) ? "other" : "???");
+
+ /* Retrieve and display optional tag qualifier */
+
+ if (tag == ACL_USER) {
+ uidp = acl_get_qualifier(entry);
+ if (uidp == NULL)
+ errExit("acl_get_qualifier");
+
+ name = userNameFromId(*uidp);
+ if (name == NULL)
+ printf("%-8d ", *uidp);
+ else
+ printf("%-8s ", name);
+
+ if (acl_free(uidp) == -1)
+ errExit("acl_free");
+
+ } else if (tag == ACL_GROUP) {
+ gidp = acl_get_qualifier(entry);
+ if (gidp == NULL)
+ errExit("acl_get_qualifier");
+
+ name = groupNameFromId(*gidp);
+ if (name == NULL)
+ printf("%-8d ", *gidp);
+ else
+ printf("%-8s ", name);
+
+ if (acl_free(gidp) == -1)
+ errExit("acl_free");
+
+ } else {
+ printf(" ");
+ }
+
+ /* Retrieve and display permissions */
+
+ if (acl_get_permset(entry, &permset) == -1)
+ errExit("acl_get_permset");
+
+ permVal = acl_get_perm(permset, ACL_READ);
+ if (permVal == -1)
+ errExit("acl_get_perm - ACL_READ");
+ printf("%c", (permVal == 1) ? 'r' : '-');
+ permVal = acl_get_perm(permset, ACL_WRITE);
+ if (permVal == -1)
+ errExit("acl_get_perm - ACL_WRITE");
+ printf("%c", (permVal == 1) ? 'w' : '-');
+ permVal = acl_get_perm(permset, ACL_EXECUTE);
+ if (permVal == -1)
+ errExit("acl_get_perm - ACL_EXECUTE");
+ printf("%c", (permVal == 1) ? 'x' : '-');
+
+ printf("\n");
+ }
+
+ if (acl_free(acl) == -1)
+ errExit("acl_free");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE = demo_sigio poll_pipes select_mq self_pipe t_select
+
+LINUX_EXE = epoll_input
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+clean :
+ ${RM} ${EXE} *.o
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 63-3 */
+
+/* demo_sigio.c
+
+ A trivial example of the use of signal-driven I/O.
+*/
+#include <signal.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <termios.h>
+#include "tty_functions.h" /* Declaration of ttySetCbreak() */
+#include "tlpi_hdr.h"
+
+static volatile sig_atomic_t gotSigio = 0;
+ /* Set nonzero on receipt of SIGIO */
+
+static void
+sigioHandler(int sig)
+{
+ gotSigio = 1;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int flags, j, cnt;
+ struct termios origTermios;
+ char ch;
+ struct sigaction sa;
+ Boolean done;
+
+ /* Establish handler for "I/O possible" signal */
+
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ sa.sa_handler = sigioHandler;
+ if (sigaction(SIGIO, &sa, NULL) == -1)
+ errExit("sigaction");
+
+ /* Set owner process that is to receive "I/O possible" signal */
+
+ if (fcntl(STDIN_FILENO, F_SETOWN, getpid()) == -1)
+ errExit("fcntl(F_SETOWN)");
+
+ /* Enable "I/O possible" signaling and make I/O nonblocking
+ for file descriptor */
+
+ flags = fcntl(STDIN_FILENO, F_GETFL);
+ if (fcntl(STDIN_FILENO, F_SETFL, flags | O_ASYNC | O_NONBLOCK) == -1)
+ errExit("fcntl(F_SETFL)");
+
+ /* Place terminal in cbreak mode */
+
+ if (ttySetCbreak(STDIN_FILENO, &origTermios) == -1)
+ errExit("ttySetCbreak");
+
+ for (done = FALSE, cnt = 0; !done ; cnt++) {
+ for (j = 0; j < 100000000; j++)
+ continue; /* Slow main loop down a little */
+
+ if (gotSigio) { /* Is input available? */
+ gotSigio = 0;
+
+ /* Read all available input until error (probably EAGAIN)
+ or EOF (not actually possible in cbreak mode) or a
+ hash (#) character is read */
+
+ while (read(STDIN_FILENO, &ch, 1) > 0 && !done) {
+ printf("cnt=%d; read %c\n", cnt, ch);
+ done = ch == '#';
+ }
+ }
+ }
+
+ /* Restore original terminal settings */
+
+ if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &origTermios) == -1)
+ errExit("tcsetattr");
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 63-5 */
+
+/* epoll_input.c
+
+ Example of the use of the Linux epoll API.
+
+ Usage: epoll_input file...
+
+ This program opens all of the files named in its command-line arguments
+ and monitors the resulting file descriptors for input events.
+
+ This program is Linux (2.6 and later) specific.
+*/
+#include <sys/epoll.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+#define MAX_BUF 1000 /* Maximum bytes fetched by a single read() */
+#define MAX_EVENTS 5 /* Maximum number of events to be returned from
+ a single epoll_wait() call */
+
+int
+main(int argc, char *argv[])
+{
+ int epfd, ready, fd, s, j, numOpenFds;
+ struct epoll_event ev;
+ struct epoll_event evlist[MAX_EVENTS];
+ char buf[MAX_BUF];
+
+ if (argc < 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s file...\n", argv[0]);
+
+ epfd = epoll_create(argc - 1);
+ if (epfd == -1)
+ errExit("epoll_create");
+
+ /* Open each file on command line, and add it to the "interest
+ list" for the epoll instance */
+
+ for (j = 1; j < argc; j++) {
+ fd = open(argv[j], O_RDONLY);
+ if (fd == -1)
+ errExit("open");
+ printf("Opened \"%s\" on fd %d\n", argv[j], fd);
+
+ ev.events = EPOLLIN; /* Only interested in input events */
+ ev.data.fd = fd;
+ if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev) == -1)
+ errExit("epoll_ctl");
+ }
+
+ numOpenFds = argc - 1;
+
+ while (numOpenFds > 0) {
+
+ /* Fetch up to MAX_EVENTS items from the ready list of the
+ epoll instance */
+
+ printf("About to epoll_wait()\n");
+ ready = epoll_wait(epfd, evlist, MAX_EVENTS, -1);
+ if (ready == -1) {
+ if (errno == EINTR)
+ continue; /* Restart if interrupted by signal */
+ else
+ errExit("epoll_wait");
+ }
+
+ printf("Ready: %d\n", ready);
+
+ /* Deal with returned list of events */
+
+ for (j = 0; j < ready; j++) {
+ printf(" fd=%d; events: %s%s%s\n", evlist[j].data.fd,
+ (evlist[j].events & EPOLLIN) ? "EPOLLIN " : "",
+ (evlist[j].events & EPOLLHUP) ? "EPOLLHUP " : "",
+ (evlist[j].events & EPOLLERR) ? "EPOLLERR " : "");
+
+ if (evlist[j].events & EPOLLIN) {
+ s = read(evlist[j].data.fd, buf, MAX_BUF);
+ if (s == -1)
+ errExit("read");
+ printf(" read %d bytes: %.*s\n", s, s, buf);
+
+ } else if (evlist[j].events & (EPOLLHUP | EPOLLERR)) {
+
+ /* After the epoll_wait(), EPOLLIN and EPOLLHUP may both have
+ been set. But we'll only get here, and thus close the file
+ descriptor, if EPOLLIN was not set. This ensures that all
+ outstanding input (possibly more than MAX_BUF bytes) is
+ consumed (by further loop iterations) before the file
+ descriptor is closed. */
+
+ printf(" closing fd %d\n", evlist[j].data.fd);
+ if (close(evlist[j].data.fd) == -1)
+ errExit("close");
+ numOpenFds--;
+ }
+ }
+ }
+
+ printf("All file descriptors closed; bye\n");
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 63-2 */
+
+/* poll_pipes.c
+
+ Example of the use of poll() to monitor multiple file descriptors.
+
+ Usage: poll_pipes num-pipes [num-writes]
+ def = 1
+
+ Create 'num-pipes' pipes, and perform 'num-writes' writes to
+ randomly selected pipes. Then use poll() to inspect the read ends
+ of the pipes to see which pipes are readable.
+*/
+#include <time.h>
+#include <poll.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int numPipes, j, ready, randPipe, numWrites;
+ int (*pfds)[2]; /* File descriptors for all pipes */
+ struct pollfd *pollFd;
+
+ if (argc < 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s num-pipes [num-writes]\n", argv[0]);
+
+ /* Allocate the arrays that we use. The arrays are sized according
+ to the number of pipes specified on command line */
+
+ numPipes = getInt(argv[1], GN_GT_0, "num-pipes");
+
+ pfds = calloc(numPipes, sizeof(int [2]));
+ if (pfds == NULL)
+ errExit("calloc");
+ pollFd = calloc(numPipes, sizeof(struct pollfd));
+ if (pollFd == NULL)
+ errExit("calloc");
+
+ /* Create the number of pipes specified on command line */
+
+ for (j = 0; j < numPipes; j++)
+ if (pipe(pfds[j]) == -1)
+ errExit("pipe %d", j);
+
+ /* Perform specified number of writes to random pipes */
+
+ numWrites = (argc > 2) ? getInt(argv[2], GN_GT_0, "num-writes") : 1;
+
+ srandom((int) time(NULL));
+ for (j = 0; j < numWrites; j++) {
+ randPipe = random() % numPipes;
+ printf("Writing to fd: %3d (read fd: %3d)\n",
+ pfds[randPipe][1], pfds[randPipe][0]);
+ if (write(pfds[randPipe][1], "a", 1) == -1)
+ errExit("write %d", pfds[randPipe][1]);
+ }
+
+ /* Build the file descriptor list to be supplied to poll(). This list
+ is set to contain the file descriptors for the read ends of all of
+ the pipes. */
+
+ for (j = 0; j < numPipes; j++) {
+ pollFd[j].fd = pfds[j][0];
+ pollFd[j].events = POLLIN;
+ }
+
+ ready = poll(pollFd, numPipes, 0);
+ if (ready == -1)
+ errExit("poll");
+
+ printf("poll() returned: %d\n", ready);
+
+ /* Check which pipes have data available for reading */
+
+ for (j = 0; j < numPipes; j++)
+ if (pollFd[j].revents & POLLIN)
+ printf("Readable: %3d\n", pollFd[j].fd);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 63-3 */
+
+/* select_mq.c
+
+ Usage: select_mq msqid...
+
+ Demonstrate how we can use a child process in conjunction with
+ select() in order to wait for input on a file descriptor (in this
+ case the terminal) and on a message queue.
+
+ This program allows us to monitor multiple message queues by
+ creating a separate child for each queue named on its command line.
+
+ For experimenting, you may find it useful to use the msg_create.c
+ and msg_send.c programs from the System V IPC chapter.
+*/
+#include <sys/time.h>
+#if ! defined(__hpux)
+/* HP-UX 11 doesn't have this header file */
+#include <sys/select.h>
+#endif
+#include <sys/msg.h>
+#include <signal.h>
+#include <stddef.h>
+#include "tlpi_hdr.h"
+
+#define BUF_SIZE 200
+
+/* Total size of the 'pbuf' struct must not exceed PIPE_BUF, otherwise
+ writes by multiple children may not be atomic, with the result that
+ messages are broken up and interleaved. */
+
+#define MAX_MTEXT 400
+
+struct pbuf {
+ int msqid; /* Origin of message */
+ int len; /* Number of bytes used in mtext */
+ long mtype; /* Message type */
+ char mtext[MAX_MTEXT]; /* Message body */
+};
+
+/* Function called by child: monitors message queue identified by
+ 'msqid', copying every message to the pipe identified by 'fd'. */
+
+static void
+childMon(int msqid, int fd)
+{
+ struct pbuf pmsg;
+ ssize_t msgLen;
+ size_t wlen;
+
+ for (;;) {
+ msgLen = msgrcv(msqid, &pmsg.mtype, MAX_MTEXT, 0, 0);
+ if (msgLen == -1)
+ errExit("msgrcv");
+
+ /* We add some info to the message read by msgrcv() before
+ writing to the pipe. */
+
+ pmsg.msqid = msqid;
+ pmsg.len = msgLen; /* So parent knows how much to read from pipe */
+
+ wlen = offsetof(struct pbuf, mtext) + msgLen;
+ /* Or: wlen = &pmsg.mtext - &pmsg + msgLen */
+
+ if (write(fd, &pmsg, wlen) != wlen)
+ fatal("partial/failed write to pipe");
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ fd_set readfds;
+ int ready, nfds, j;
+ int pfd[2]; /* Pipe used to transfer messages from
+ children to parent */
+ ssize_t numRead;
+ char buf[BUF_SIZE];
+ struct pbuf pmsg;
+
+ if (argc < 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s msqid...\n", argv[0]);
+
+ if (pipe(pfd) == -1)
+ errExit("pipe");
+
+ /* Create one child for each message queue being monitored */
+
+ for (j = 1; j < argc; j++) {
+ switch (fork()) {
+ case -1:
+ errMsg("fork");
+ killpg(0, SIGTERM);
+ _exit(EXIT_FAILURE); /* NOTREACHED */
+
+ case 0:
+ childMon(getInt(argv[j], 0, "msqid"), pfd[1]);
+ _exit(EXIT_FAILURE); /* NOTREACHED */
+
+ default:
+ break;
+ }
+ }
+
+ /* Parent falls through to here */
+
+ for (;;) {
+ FD_ZERO(&readfds);
+ FD_SET(STDIN_FILENO, &readfds);
+ FD_SET(pfd[0], &readfds);
+ nfds = max(STDIN_FILENO, pfd[0]) + 1;
+
+ ready = select(nfds, &readfds, NULL, NULL, NULL);
+ if (ready == -1)
+ errExit("select");
+
+ /* Check if terminal fd is ready */
+
+ if (FD_ISSET(STDIN_FILENO, &readfds)) {
+ numRead = read(STDIN_FILENO, buf, BUF_SIZE - 1);
+ if (numRead == -1)
+ errExit("read stdin");
+
+ buf[numRead] = '\0';
+ printf("Read from terminal: %s", buf);
+ if (numRead > 0 && buf[numRead - 1] != '\n')
+ printf("\n");
+ }
+
+ /* Check if pipe fd is ready */
+
+ if (FD_ISSET(pfd[0], &readfds)) {
+ numRead = read(pfd[0], &pmsg, offsetof(struct pbuf, mtext));
+ if (numRead == -1)
+ errExit("read pipe");
+ if (numRead == 0)
+ fatal("EOF on pipe");
+
+ numRead = read(pfd[0], &pmsg.mtext, pmsg.len);
+ if (numRead == -1)
+ errExit("read pipe");
+ if (numRead == 0)
+ fatal("EOF on pipe");
+
+ printf("MQ %d: type=%ld length=%d <%.*s>\n", pmsg.msqid,
+ pmsg.mtype, pmsg.len, pmsg.len, pmsg.mtext);
+ }
+ }
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 63-9 */
+
+/* self_pipe.c
+
+ Employ the self-pipe trick so that we can avoid race conditions while both
+ selecting on a set of file descriptors and also waiting for a signal.
+
+ Usage as shown in synopsis below; for example:
+
+ self_pipe - 0
+*/
+#include <sys/time.h>
+#if ! defined(__hpux) /* HP-UX 11 doesn't have this header file */
+#include <sys/select.h>
+#endif
+#include <fcntl.h>
+#include <signal.h>
+#include "tlpi_hdr.h"
+
+static int pfd[2]; /* File descriptors for pipe */
+
+static void
+handler(int sig)
+{
+ int savedErrno; /* In case we change 'errno' */
+
+ savedErrno = errno;
+ if (write(pfd[1], "x", 1) == -1 && errno != EAGAIN)
+ errExit("write");
+ errno = savedErrno;
+}
+
+int
+main(int argc, char *argv[])
+{
+ fd_set readfds;
+ int ready, nfds, flags;
+ struct timeval timeout;
+ struct timeval *pto;
+ struct sigaction sa;
+ char ch;
+ int fd, j;
+
+ if (argc < 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s {timeout|-} fd...\n"
+ "\t\t('-' means infinite timeout)\n", argv[0]);
+
+ /* Initialize 'timeout', 'readfds', and 'nfds' for select() */
+
+ if (strcmp(argv[1], "-") == 0) {
+ pto = NULL; /* Infinite timeout */
+ } else {
+ pto = &timeout;
+ timeout.tv_sec = getLong(argv[1], 0, "timeout");
+ timeout.tv_usec = 0; /* No microseconds */
+ }
+
+ nfds = 0;
+
+ /* Build the 'readfds' from the fd numbers given in command line */
+
+ FD_ZERO(&readfds);
+ for (j = 2; j < argc; j++) {
+ fd = getInt(argv[j], 0, "fd");
+ if (fd >= FD_SETSIZE)
+ cmdLineErr("file descriptor exceeds limit (%d)\n", FD_SETSIZE);
+
+ if (fd >= nfds)
+ nfds = fd + 1; /* Record maximum fd + 1 */
+ FD_SET(fd, &readfds);
+ }
+
+ /* Create pipe before establishing signal handler to prevent race */
+
+ if (pipe(pfd) == -1)
+ errExit("pipe");
+
+ FD_SET(pfd[0], &readfds); /* Add read end of pipe to 'readfds' */
+ nfds = max(nfds, pfd[0] + 1); /* And adjust 'nfds' if required */
+
+ /* Make read and write ends of pipe nonblocking */
+
+ flags = fcntl(pfd[0], F_GETFL);
+ if (flags == -1)
+ errExit("fcntl-F_GETFL");
+ flags |= O_NONBLOCK; /* Make read end nonblocking */
+ if (fcntl(pfd[0], F_SETFL, flags) == -1)
+ errExit("fcntl-F_SETFL");
+
+ flags = fcntl(pfd[1], F_GETFL);
+ if (flags == -1)
+ errExit("fcntl-F_GETFL");
+ flags |= O_NONBLOCK; /* Make write end nonblocking */
+ if (fcntl(pfd[1], F_SETFL, flags) == -1)
+ errExit("fcntl-F_SETFL");
+
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART; /* Restart interrupted reads()s */
+ sa.sa_handler = handler;
+ if (sigaction(SIGINT, &sa, NULL) == -1)
+ errExit("sigaction");
+
+ while ((ready = select(nfds, &readfds, NULL, NULL, pto)) == -1 &&
+ errno == EINTR)
+ continue; /* Restart if interrupted by signal */
+ if (ready == -1) /* Unexpected error */
+ errExit("select");
+
+ if (FD_ISSET(pfd[0], &readfds)) { /* Handler was called */
+ printf("A signal was caught\n");
+
+ for (;;) { /* Consume bytes from pipe */
+ if (read(pfd[0], &ch, 1) == -1) {
+ if (errno == EAGAIN)
+ break; /* No more bytes */
+ else
+ errExit("read"); /* Some other error */
+ }
+
+ /* Perform any actions that should be taken in response to signal */
+ }
+ }
+
+ /* Examine file descriptor sets returned by select() to see
+ which other file descriptors are ready */
+
+ printf("ready = %d\n", ready);
+ for (j = 2; j < argc; j++) {
+ fd = getInt(argv[j], 0, "fd");
+ printf("%d: %s\n", fd, FD_ISSET(fd, &readfds) ? "r" : "");
+ }
+
+ /* And check if read end of pipe is ready */
+
+ printf("%d: %s (read end of pipe)\n", pfd[0],
+ FD_ISSET(pfd[0], &readfds) ? "r" : "");
+
+ if (pto != NULL)
+ printf("timeout after select(): %ld.%03ld\n",
+ (long) timeout.tv_sec, (long) timeout.tv_usec / 1000);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 63-1 */
+
+/* t_select.c
+
+ Example of the use of the select() system call to monitor multiple
+ file descriptors.
+
+ Usage as shown in usageError().
+*/
+#include <sys/time.h>
+#if ! defined(__hpux)
+/* HP-UX 11 doesn't have this header file */
+#include <sys/select.h>
+#endif
+#include "tlpi_hdr.h"
+
+static void
+usageError(const char *progName)
+{
+ fprintf(stderr, "Usage: %s {timeout|-} fd-num[rw]...\n", progName);
+ fprintf(stderr, " - means infinite timeout; \n");
+ fprintf(stderr, " r = monitor for read\n");
+ fprintf(stderr, " w = monitor for write\n\n");
+ fprintf(stderr, " e.g.: %s - 0rw 1w\n", progName);
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ fd_set readfds, writefds;
+ int ready, nfds, fd, numRead, j;
+ struct timeval timeout;
+ struct timeval *pto;
+ char buf[10]; /* Large enough to hold "rw\0" */
+
+ if (argc < 2 || strcmp(argv[1], "--help") == 0)
+ usageError(argv[0]);
+
+ /* Timeout for select() is specified in argv[1] */
+
+ if (strcmp(argv[1], "-") == 0) {
+ pto = NULL; /* Infinite timeout */
+ } else {
+ pto = &timeout;
+ timeout.tv_sec = getLong(argv[1], 0, "timeout");
+ timeout.tv_usec = 0; /* No microseconds */
+ }
+
+ /* Process remaining arguments to build file descriptor sets */
+
+ nfds = 0;
+ FD_ZERO(&readfds);
+ FD_ZERO(&writefds);
+
+ for (j = 2; j < argc; j++) {
+ numRead = sscanf(argv[j], "%d%2[rw]", &fd, buf);
+ if (numRead != 2)
+ usageError(argv[0]);
+ if (fd >= FD_SETSIZE)
+ cmdLineErr("file descriptor exceeds limit (%d)\n", FD_SETSIZE);
+
+ if (fd >= nfds)
+ nfds = fd + 1; /* Record maximum fd + 1 */
+ if (strchr(buf, 'r') != NULL)
+ FD_SET(fd, &readfds);
+ if (strchr(buf, 'w') != NULL)
+ FD_SET(fd, &writefds);
+ }
+
+ /* We've built all of the arguments; now call select() */
+
+ ready = select(nfds, &readfds, &writefds, NULL, pto);
+ /* Ignore exceptional events */
+ if (ready == -1)
+ errExit("select");
+
+ /* Display results of select() */
+
+ printf("ready = %d\n", ready);
+ for (fd = 0; fd < nfds; fd++)
+ printf("%d: %s%s\n", fd, FD_ISSET(fd, &readfds) ? "r" : "",
+ FD_ISSET(fd, &writefds) ? "w" : "");
+
+ if (pto != NULL)
+ printf("timeout after select(): %ld.%03ld\n",
+ (long) timeout.tv_sec, (long) timeout.tv_usec / 1000);
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE =
+
+LINUX_EXE = check_password_caps
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+LDLIBS = ${IMPL_LDLIBS} ${LINUX_LIBCAP} ${LINUX_LIBCRYPT}
+ # The only program we build here needs libcap and libcrypt
+
+clean :
+ ${RM} ${EXE} *.o
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 39-1 */
+
+/* check_password_caps.c
+
+ This program provides an example of the use of capabilities to create a
+ program that performs a task that requires privilges, but operates without
+ the full power of 'root'. The program reads a username and password and
+ checks if they are valid by authenticating against the (shadow) password
+ file.
+
+ The program executable file must be installed with the CAP_DAC_READ_SEARCH
+ permitted capability, as follows:
+
+ $ sudo setcap "cap_dac_read_search=p" check_password_caps
+
+ This program is Linux-specific.
+
+ See also check_password.c.
+*/
+#define _BSD_SOURCE /* Get getpass() declaration from <unistd.h> */
+#ifndef _XOPEN_SOURCE
+#define _XOPEN_SOURCE /* Get crypt() declaration from <unistd.h> */
+#endif
+#include <sys/capability.h>
+#include <unistd.h>
+#include <limits.h>
+#include <pwd.h>
+#include <shadow.h>
+#include "tlpi_hdr.h"
+
+/* Change setting of capability in caller's effective capabilities */
+
+static int
+modifyCap(int capability, int setting)
+{
+ cap_t caps;
+ cap_value_t capList[1];
+
+ /* Retrieve caller's current capabilities */
+
+ caps = cap_get_proc();
+ if (caps == NULL)
+ return -1;
+
+ /* Change setting of 'capability' in the effective set of 'caps'. The
+ third argument, 1, is the number of items in the array 'capList'. */
+
+ capList[0] = capability;
+ if (cap_set_flag(caps, CAP_EFFECTIVE, 1, capList, setting) == -1) {
+ cap_free(caps);
+ return -1;
+ }
+
+ /* Push modified capability sets back to kernel, to change
+ caller's capabilities */
+
+ if (cap_set_proc(caps) == -1) {
+ cap_free(caps);
+ return -1;
+ }
+
+ /* Free the structure that was allocated by libcap */
+
+ if (cap_free(caps) == -1)
+ return -1;
+
+ return 0;
+}
+
+static int /* Raise capability in caller's effective set */
+raiseCap(int capability)
+{
+ return modifyCap(capability, CAP_SET);
+}
+
+/* An analogous dropCap() (unneeded in this program), could be
+ defined as: modifyCap(capability, CAP_CLEAR); */
+
+static int /* Drop all capabilities from all sets */
+dropAllCaps(void)
+{
+ cap_t empty;
+ int s;
+
+ empty = cap_init();
+ if (empty == NULL)
+ return -1;
+
+ s = cap_set_proc(empty);
+
+ if (cap_free(empty) == -1)
+ return -1;
+
+ return s;
+}
+
+int
+main(int argc, char *argv[])
+{
+ char *username, *password, *encrypted, *p;
+ struct passwd *pwd;
+ struct spwd *spwd;
+ Boolean authOk;
+ size_t len;
+ long lnmax;
+
+ /* Determine size of buffer required for a username, and allocate it */
+
+ lnmax = sysconf(_SC_LOGIN_NAME_MAX);
+ if (lnmax == -1) /* If limit is indeterminate */
+ lnmax = 256; /* make a guess */
+
+ username = malloc(lnmax);
+ if (username == NULL)
+ errExit("malloc");
+
+ printf("Username: ");
+ fflush(stdout);
+ if (fgets(username, lnmax, stdin) == NULL)
+ exit(EXIT_FAILURE); /* Exit on EOF */
+
+ len = strlen(username);
+ if (username[len - 1] == '\n')
+ username[len - 1] = '\0'; /* Remove trailing '\n' */
+
+ /* Look up password record for username */
+
+ pwd = getpwnam(username);
+ if (pwd == NULL)
+ fatal("couldn't get password record");
+
+ /* Only raise CAP_DAC_READ_SEARCH for as long as we need it */
+
+ if (raiseCap(CAP_DAC_READ_SEARCH) == -1)
+ fatal("raiseCap() failed");
+
+ /* Look up shadow password record for username */
+
+ spwd = getspnam(username);
+ if (spwd == NULL && errno == EACCES)
+ fatal("no permission to read shadow password file");
+
+ /* At this point, we won't need any more capabilities,
+ so drop all capabilities from all sets */
+
+ if (dropAllCaps() == -1)
+ fatal("dropAllCaps() failed");
+
+ if (spwd != NULL) /* If there is a shadow password record */
+ pwd->pw_passwd = spwd->sp_pwdp; /* Use the shadow password */
+
+ password = getpass("Password: ");
+
+ /* Encrypt password and erase cleartext version immediately */
+
+ encrypted = crypt(password, pwd->pw_passwd);
+ for (p = password; *p != '\0'; )
+ *p++ = '\0';
+
+ if (encrypted == NULL)
+ errExit("crypt");
+
+ authOk = strcmp(encrypted, pwd->pw_passwd) == 0;
+ if (!authOk) {
+ printf("Incorrect password\n");
+ exit(EXIT_FAILURE);
+ }
+
+ printf("Successfully authenticated: UID=%ld\n", (long) pwd->pw_uid);
+
+ /* Now do authenticated work... */
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE = daemon_SIGHUP t_syslog test_become_daemon
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+clean :
+ ${RM} ${EXE} *.o
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 37-2 */
+
+/* become_daemon.c
+
+ A function encapsulating the steps in becoming a daemon.
+*/
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "become_daemon.h"
+#include "tlpi_hdr.h"
+
+int /* Returns 0 on success, -1 on error */
+becomeDaemon(int flags)
+{
+ int maxfd, fd;
+
+ switch (fork()) { /* Become background process */
+ case -1: return -1;
+ case 0: break; /* Child falls through... */
+ default: _exit(EXIT_SUCCESS); /* while parent terminates */
+ }
+
+ if (setsid() == -1) /* Become leader of new session */
+ return -1;
+
+ switch (fork()) { /* Ensure we are not session leader */
+ case -1: return -1;
+ case 0: break;
+ default: _exit(EXIT_SUCCESS);
+ }
+
+ if (!(flags & BD_NO_UMASK0))
+ umask(0); /* Clear file mode creation mask */
+
+ if (!(flags & BD_NO_CHDIR))
+ chdir("/"); /* Change to root directory */
+
+ if (!(flags & BD_NO_CLOSE_FILES)) { /* Close all open files */
+ maxfd = sysconf(_SC_OPEN_MAX);
+ if (maxfd == -1) /* Limit is indeterminate... */
+ maxfd = BD_MAX_CLOSE; /* so take a guess */
+
+ for (fd = 0; fd < maxfd; fd++)
+ close(fd);
+ }
+
+ if (!(flags & BD_NO_REOPEN_STD_FDS)) {
+ close(STDIN_FILENO); /* Reopen standard fd's to /dev/null */
+
+ fd = open("/dev/null", O_RDWR);
+
+ if (fd != STDIN_FILENO) /* 'fd' should be 0 */
+ return -1;
+ if (dup2(STDIN_FILENO, STDOUT_FILENO) != STDOUT_FILENO)
+ return -1;
+ if (dup2(STDIN_FILENO, STDERR_FILENO) != STDERR_FILENO)
+ return -1;
+ }
+
+ return 0;
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 37-1 */
+
+/* become_daemon.h
+
+ Header file for become_daemon.c.
+*/
+#ifndef BECOME_DAEMON_H /* Prevent double inclusion */
+#define BECOME_DAEMON_H
+
+/* Bit-mask values for 'flags' argument of becomeDaemon() */
+
+#define BD_NO_CHDIR 01 /* Don't chdir("/") */
+#define BD_NO_CLOSE_FILES 02 /* Don't close all open files */
+#define BD_NO_REOPEN_STD_FDS 04 /* Don't reopen stdin, stdout, and
+ stderr to /dev/null */
+#define BD_NO_UMASK0 010 /* Don't do a umask(0) */
+
+#define BD_MAX_CLOSE 8192 /* Maximum file descriptors to close if
+ sysconf(_SC_OPEN_MAX) is indeterminate */
+
+int becomeDaemon(int flags);
+
+#endif
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 37-3 */
+
+/* daemon_SIGHUP.c
+
+ Demonstrate the use of SIGHUP as a mechanism to tell a daemon to
+ reread its configuration file and reopen its log file.
+
+ In the version of this code printed in the book, logOpen(), logClose(),
+ logMessage(), and readConfigFile() were omitted for brevity. The version
+ of the code in this file is complete, and can be compiled and run.
+*/
+#include <sys/stat.h>
+#include <signal.h>
+#include "become_daemon.h"
+#include "tlpi_hdr.h"
+
+static const char *LOG_FILE = "/tmp/ds.log";
+static const char *CONFIG_FILE = "/tmp/ds.conf";
+
+#include <time.h>
+#include <stdarg.h>
+
+static FILE *logfp; /* Log file stream */
+
+/* Write a message to the log file. Handle variable length argument
+ lists, with an initial format string (like printf(3), but without
+ a trailing newline). Precede each message with a timestamp. */
+
+static void
+logMessage(const char *format, ...)
+{
+ va_list argList;
+ const char *TIMESTAMP_FMT = "%F %X"; /* = YYYY-MM-DD HH:MM:SS */
+#define TS_BUF_SIZE sizeof("YYYY-MM-DD HH:MM:SS") /* Includes '\0' */
+ char timestamp[TS_BUF_SIZE];
+ time_t t;
+ struct tm *loc;
+
+ t = time(NULL);
+ loc = localtime(&t);
+ if (loc == NULL ||
+ strftime(timestamp, TS_BUF_SIZE, TIMESTAMP_FMT, loc) == 0)
+ fprintf(logfp, "???Unknown time????: ");
+ else
+ fprintf(logfp, "%s: ", timestamp);
+
+ va_start(argList, format);
+ vfprintf(logfp, format, argList);
+ fprintf(logfp, "\n");
+ va_end(argList);
+}
+
+/* Open the log file 'logFilename' */
+
+static void
+logOpen(const char *logFilename)
+{
+ mode_t m;
+
+ m = umask(077);
+ logfp = fopen(logFilename, "a");
+ umask(m);
+
+ /* If opening the log fails we can't display a message... */
+
+ if (logfp == NULL)
+ exit(EXIT_FAILURE);
+
+ setbuf(logfp, NULL); /* Disable stdio buffering */
+
+ logMessage("Opened log file");
+}
+
+/* Close the log file */
+
+static void
+logClose(void)
+{
+ logMessage("Closing log file");
+ fclose(logfp);
+}
+
+/* (Re)initialize from configuration file. In a real application
+ we would of course have some daemon initialization parameters in
+ this file. In this dummy version, we simply read a single line
+ from the file and write it to the log. */
+
+static void
+readConfigFile(const char *configFilename)
+{
+ FILE *configfp;
+#define SBUF_SIZE 100
+ char str[SBUF_SIZE];
+
+ configfp = fopen(configFilename, "r");
+ if (configfp != NULL) { /* Ignore nonexistent file */
+ if (fgets(str, SBUF_SIZE, configfp) == NULL)
+ str[0] = '\0';
+ else
+ str[strlen(str) - 1] = '\0'; /* Strip trailing '\n' */
+ logMessage("Read config file: %s", str);
+ fclose(configfp);
+ }
+}
+
+static volatile sig_atomic_t hupReceived = 0;
+ /* Set nonzero on receipt of SIGHUP */
+
+static void
+sighupHandler(int sig)
+{
+ hupReceived = 1;
+}
+
+int
+main(int argc, char *argv[])
+{
+ const int SLEEP_TIME = 15; /* Time to sleep between messages */
+ int count = 0; /* Number of completed SLEEP_TIME intervals */
+ int unslept; /* Time remaining in sleep interval */
+ struct sigaction sa;
+
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ sa.sa_handler = sighupHandler;
+ if (sigaction(SIGHUP, &sa, NULL) == -1)
+ errExit("sigaction");
+
+ if (becomeDaemon(0) == -1)
+ errExit("becomeDaemon");
+
+ logOpen(LOG_FILE);
+ readConfigFile(CONFIG_FILE);
+
+ unslept = SLEEP_TIME;
+
+ for (;;) {
+ unslept = sleep(unslept); /* Returns > 0 if interrupted */
+
+ if (hupReceived) { /* If we got SIGHUP... */
+ hupReceived = 0; /* Get ready for next SIGHUP */
+ logClose();
+ logOpen(LOG_FILE);
+ readConfigFile(CONFIG_FILE);
+ }
+
+ if (unslept == 0) { /* On completed interval */
+ count++;
+ logMessage("Main: %d", count);
+ unslept = SLEEP_TIME; /* Reset interval */
+ }
+ }
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 37-1 */
+
+/* t_syslog.c
+
+ Demonstrate the use of syslog(3) to write arbitrary messages to
+ the system log. Usage is as shown in usageError() below.
+*/
+#include <syslog.h>
+#include "tlpi_hdr.h"
+
+static void
+usageError(const char *progName)
+{
+ fprintf(stderr, "Usage: %s [-p] [-e] [-l level] \"message\"\n", progName);
+ fprintf(stderr, " -p log PID\n");
+ fprintf(stderr, " -e log to stderr also\n");
+ fprintf(stderr, " -l level (g=EMERG; a=ALERT; c=CRIT; e=ERR\n");
+ fprintf(stderr, " w=WARNING; n=NOTICE; i=INFO; d=DEBUG)\n");
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int level, options, opt;
+
+ options = 0;
+ level = LOG_INFO;
+
+ while ((opt = getopt(argc, argv, "l:pe")) != -1) {
+ switch (opt) {
+ case 'l':
+ switch (optarg[0]) {
+ case 'a': level = LOG_ALERT; break;
+ case 'c': level = LOG_CRIT; break;
+ case 'e': level = LOG_ERR; break;
+ case 'w': level = LOG_WARNING; break;
+ case 'n': level = LOG_NOTICE; break;
+ case 'i': level = LOG_INFO; break;
+ case 'd': level = LOG_DEBUG; break;
+ default: cmdLineErr("Bad facility: %c\n", optarg[0]);
+ }
+ break;
+
+ case 'p':
+ options |= LOG_PID;
+ break;
+
+#if ! defined(__hpux) && ! defined(__sun)
+
+ /* Not on HP-UX 11 or Solaris 8 */
+
+ case 'e':
+ options |= LOG_PERROR;
+ break;
+#endif
+
+ default:
+ fprintf(stderr, "Bad option\n");
+ usageError(argv[0]);
+ }
+ }
+
+ if (argc != optind + 1)
+ usageError(argv[0]);
+
+ openlog(argv[0], options, LOG_USER);
+ syslog(LOG_USER | level, "%s", argv[optind]);
+ closelog();
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 37 */
+
+/* test_become_daemon.c
+
+ Test our becomeDaemon() function.
+*/
+#include "become_daemon.h"
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ becomeDaemon(0);
+
+ /* Normally a daemon would live forever; we just sleep for a while */
+
+ sleep((argc > 1) ? getInt(argv[1], GN_GT_0, "sleep-time") : 20);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE = bad_symlink file_type_stats list_files list_files_readdir_r \
+ nftw_dir_tree t_dirbasename t_unlink view_symlink
+
+LINUX_EXE =
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+clean :
+ ${RM} ${EXE} *.o
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 18-2 */
+
+/* bad_symlink.c
+
+ The following code demonstrates a mistake in using symlink(): the link
+ is created with an incorrect relative path, and the subsequent chmod()
+ call fails. Note: symbolic links are interpreted relative to the directory
+ in which they reside, not the current directory of the process.
+*/
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int fd;
+
+ if (mkdir("test", S_IRUSR | S_IWUSR | S_IXUSR) == -1)
+ errExit("mkdir");
+ if (chdir("test") == -1)
+ errExit("chdir");
+ fd = open("myfile", O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
+ if (fd == -1)
+ errExit("open");
+ if (close(fd) == -1)
+ errExit("close");
+ if (symlink("myfile", "../mylink") == -1)
+ errExit("symlink");
+ if (chmod("../mylink", S_IRUSR) == -1)
+ errExit("chmod");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 18-7 */
+
+/* file_type_stats.c
+
+ An example of the use of nftw(): traverse the directory tree named in the
+ command line, and print out statistics about the types of file in the tree.
+*/
+#if defined(__sun)
+#define _XOPEN_SOURCE 500 /* Solaris 8 needs it this way */
+#else
+#if ! defined(_XOPEN_SOURCE) || _XOPEN_SOURCE < 600
+#define _XOPEN_SOURCE 600 /* Get nftw() and S_IFSOCK declarations */
+#endif
+#endif
+#include <ftw.h>
+#include "tlpi_hdr.h"
+
+static int numReg = 0, numDir = 0, numSymLk = 0, numSocket = 0,
+ numFifo = 0, numChar = 0, numBlock = 0,
+ numNonstatable = 0;
+
+static int
+countFile(const char *path, const struct stat *sb, int flag, struct FTW *ftwb)
+
+{
+ if (flag == FTW_NS) {
+ numNonstatable++;
+ return 0;
+ }
+
+ switch (sb->st_mode & S_IFMT) {
+ case S_IFREG: numReg++; break;
+ case S_IFDIR: numDir++; break;
+ case S_IFCHR: numChar++; break;
+ case S_IFBLK: numBlock++; break;
+ case S_IFLNK: numSymLk++; break;
+ case S_IFIFO: numFifo++; break;
+ case S_IFSOCK: numSocket++; break;
+ }
+ return 0; /* Always tell nftw() to continue */
+}
+
+static void
+printStats(const char *msg, int num, int numFiles)
+{
+ printf("%-15s %6d %6.1f%%\n", msg, num, num * 100.0 / numFiles);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int numFiles; /* Total number of files */
+
+ if (argc != 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s dir-path\n", argv[0]);
+
+ /* Traverse directory tree counting files; don't follow symbolic links */
+
+ if (nftw(argv[1], &countFile, 20, FTW_PHYS) == -1) {
+ perror("nftw");
+ exit(EXIT_FAILURE);
+ }
+
+ numFiles = numReg + numDir + numSymLk + numSocket +
+ numFifo + numChar + numBlock + numNonstatable;
+
+ if (numFiles == 0) {
+ printf("No files found\n");
+ } else {
+ printf("Total files: %6d\n", numFiles);
+ printStats("Regular:", numReg, numFiles);
+ printStats("Directory:", numDir, numFiles);
+ printStats("Char device:", numChar, numFiles);
+ printStats("Block device:", numBlock, numFiles);
+ printStats("Symbolic link:", numSymLk, numFiles);
+ printStats("FIFO:", numFifo, numFiles);
+ printStats("Socket:", numSocket, numFiles);
+ printStats("Non-statable:", numNonstatable, numFiles);
+ }
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 18-2 */
+
+/* list_files.c
+
+ Demonstrate the use of opendir() and related functions to list files
+ in a directory.
+
+ Walk through each directory named on the command line (current directory
+ if none are specified) to display a list of the files it contains.
+
+ Usage: list_files [dir...]
+*/
+#if defined(__APPLE__)
+ /* Darwin requires this header before including <dirent.h> */
+#include <sys/types.h>
+#endif
+#include <dirent.h>
+#include "tlpi_hdr.h"
+
+static void /* List all files in directory 'dirpath' */
+listFiles(const char *dirpath)
+{
+ DIR *dirp;
+ struct dirent *dp;
+ Boolean isCurrent; /* True if 'dirpath' is "." */
+
+ isCurrent = strcmp(dirpath, ".") == 0;
+
+ dirp = opendir(dirpath);
+ if (dirp == NULL) {
+ errMsg("opendir failed on '%s'", dirpath);
+ return;
+ }
+
+ /* For each entry in this directory, print directory + filename */
+
+ for (;;) {
+ errno = 0; /* To distinguish error from end-of-directory */
+ dp = readdir(dirp);
+ if (dp == NULL)
+ break;
+
+ if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0)
+ continue; /* Skip . and .. */
+
+ if (!isCurrent)
+ printf("%s/", dirpath);
+ printf("%s\n", dp->d_name);
+ }
+
+ if (errno != 0)
+ errExit("readdir");
+
+ if (closedir(dirp) == -1)
+ errMsg("closedir");
+}
+
+int
+main(int argc, char *argv[])
+{
+ if (argc > 1 && strcmp(argv[1], "--help") == 0)
+ usageErr("%s [dir-path...]\n", argv[0]);
+
+ if (argc == 1) /* No arguments - use current directory */
+ listFiles(".");
+ else
+ for (argv++; *argv; argv++)
+ listFiles(*argv);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 18-4 */
+
+/* list_files_readdir_r.c
+
+ Demonstrate the use of opendir() and readdir_r() to list files
+ in a directory.
+
+ Usage: list_files_readdir_r [dir...]
+
+ Walks through each directory named on the command line (current directory
+ if none are specified) to display a list of the files it contains.
+
+ See also list_files_readdir.c.
+*/
+#if defined(__APPLE__)
+ /* Darwin requires this header before including <dirent.h> */
+#include <sys/types.h>
+#endif
+#include <limits.h>
+#include <dirent.h>
+#include <stddef.h>
+#include "tlpi_hdr.h"
+
+static void /* List all files in directory 'dirpath' */
+listFiles(const char *dirpath)
+{
+ DIR *dirp;
+ Boolean isCurrent; /* True if 'dirpath' is "." */
+ struct dirent *result, *entryp;
+ int nameMax;
+
+ isCurrent = strcmp(dirpath, ".") == 0;
+
+ /* On Linux, NAME_MAX is defined in <limits.h>. However, this limit
+ may vary across file systems, so we really should use pathconf()
+ to find the true limit for this file system. */
+
+ nameMax = pathconf(dirpath, _PC_NAME_MAX);
+ if (nameMax == -1) /* Indeterminate or error */
+ nameMax = 255; /* So take a guess */
+
+ entryp = malloc(offsetof(struct dirent, d_name) + nameMax + 1);
+ if (entryp == NULL)
+ errExit("malloc");
+
+ /* Open the directory - on failure print an error and return */
+
+ dirp = opendir(dirpath);
+ if (dirp == NULL) {
+ errMsg("opendir failed on '%s'", dirpath);
+ return;
+ }
+
+ /* Look at each of the entries in this directory */
+
+ for (;;) {
+ errno = readdir_r(dirp, entryp, &result);
+ if (errno != 0)
+ errExit("readdir_r");
+
+ if (result == NULL) /* End of stream */
+ break;
+
+ /* Skip . and .. */
+
+ if (strcmp(entryp->d_name, ".") == 0 ||
+ strcmp(entryp->d_name, "..") == 0)
+ continue;
+
+ /* Print directory + filename */
+
+ if (!isCurrent) printf("%s/", dirpath);
+ printf("%s\n", entryp->d_name);
+ }
+
+ if (closedir(dirp) == -1)
+ errMsg("closedir");
+}
+
+int
+main(int argc, char *argv[])
+{
+ if (argc == 1) /* No arguments - use current directory */
+ listFiles(".");
+ else
+ for (argv++; *argv; argv++)
+ listFiles(*argv);
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 18-3 */
+
+/* nftw_dir_tree.c
+
+ Demonstrate the use of nftw(3). Walk though the directory tree specified
+ on the command line (or the current working directory if no directory
+ is specified on the command line), displaying an indented hierarchy
+ of files in the tree. For each file, display:
+
+ * a letter indicating the file type (using the same letters
+ as "ls -l"), as obtained using stat(2);
+ * a string indicating the file type, as supplied by nftw(); and
+ * the file's i-node number.
+*/
+#if defined(__sun)
+#define _XOPEN_SOURCE 500 /* Solaris 8 needs it this way */
+#else
+#if ! defined(_XOPEN_SOURCE) || _XOPEN_SOURCE < 600
+#define _XOPEN_SOURCE 600 /* Get nftw() and S_IFSOCK declarations */
+#endif
+#endif
+#include <ftw.h>
+#include "tlpi_hdr.h"
+
+static void
+usageError(const char *progName, const char *msg)
+{
+ if (msg != NULL)
+ fprintf(stderr, "%s\n", msg);
+ fprintf(stderr, "Usage: %s [-d] [-m] [-p] [directory-path]\n", progName);
+ fprintf(stderr, "\t-d Use FTW_DEPTH flag\n");
+ fprintf(stderr, "\t-m Use FTW_MOUNT flag\n");
+ fprintf(stderr, "\t-p Use FTW_PHYS flag\n");
+ exit(EXIT_FAILURE);
+}
+
+static int /* Function called by nftw() */
+dirTree(const char *pathname, const struct stat *sbuf, int type,
+ struct FTW *ftwb)
+{
+ switch (sbuf->st_mode & S_IFMT) { /* Print file type */
+ case S_IFREG: printf("-"); break;
+ case S_IFDIR: printf("d"); break;
+ case S_IFCHR: printf("c"); break;
+ case S_IFBLK: printf("b"); break;
+ case S_IFLNK: printf("l"); break;
+ case S_IFIFO: printf("p"); break;
+ case S_IFSOCK: printf("s"); break;
+ default: printf("?"); break; /* Should never happen (on Linux) */
+ }
+
+ printf(" %s ",
+ (type == FTW_D) ? "D " : (type == FTW_DNR) ? "DNR" :
+ (type == FTW_DP) ? "DP " : (type == FTW_F) ? "F " :
+ (type == FTW_SL) ? "SL " : (type == FTW_SLN) ? "SLN" :
+ (type == FTW_NS) ? "NS " : " ");
+
+ if (type != FTW_NS)
+ printf("%7ld ", (long) sbuf->st_ino);
+ else
+ printf(" ");
+
+ printf(" %*s", 4 * ftwb->level, ""); /* Indent suitably */
+ printf("%s\n", &pathname[ftwb->base]); /* Print basename */
+ return 0; /* Tell nftw() to continue */
+}
+
+int
+main(int argc, char *argv[])
+{
+ int flags, opt;
+
+ flags = 0;
+ while ((opt = getopt(argc, argv, "dmp")) != -1) {
+ switch (opt) {
+ case 'd': flags |= FTW_DEPTH; break;
+ case 'm': flags |= FTW_MOUNT; break;
+ case 'p': flags |= FTW_PHYS; break;
+ default: usageError(argv[0], NULL);
+ }
+ }
+
+ if (argc > optind + 1)
+ usageError(argv[0], NULL);
+
+ if (nftw((argc > optind) ? argv[optind] : ".", dirTree, 10, flags) == -1) {
+ perror("nftw");
+ exit(EXIT_FAILURE);
+ }
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 18-5 */
+
+/* t_dirbasename.c
+
+ Demonstrate the use of dirname() and basename() to break a pathname
+ into directory and filename components.
+
+ Usage: t_dirbasename path...
+
+ The program calls dirname() and basename() for each of the pathnames
+ supplied on the command-line.
+*/
+#include <libgen.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ char *t1, *t2;
+ int j;
+
+ for (j = 1; j < argc; j++) {
+ t1 = strdup(argv[j]);
+ if (t1 == NULL)
+ errExit("strdup");
+ t2 = strdup(argv[j]);
+ if (t2 == NULL)
+ errExit("strdup");
+
+ printf("%s ==> %s + %s\n", argv[j], dirname(t1), basename(t2));
+
+ free(t1);
+ free(t2);
+ }
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 18-1 */
+
+/* t_unlink.c
+
+ Demonstrate that, when a file is unlinked, it is not actually removed from
+ the file system until after any open descriptors referring to it are closed.
+
+ Usage: t_unlink file
+*/
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+#define CMD_SIZE 200
+#define BUF_SIZE 1024
+
+int
+main(int argc, char *argv[])
+{
+ int fd, j, numBlocks;
+ char shellCmd[CMD_SIZE]; /* Command to be passed to system() */
+ char buf[BUF_SIZE]; /* Random bytes to write to file */
+
+ if (argc < 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s temp-file [num-1kB-blocks] \n", argv[0]);
+
+ numBlocks = (argc > 2) ? getInt(argv[2], GN_GT_0, "num-1kB-blocks")
+ : 100000;
+
+ /* O_EXCL so that we ensure we create a new file */
+
+ fd = open(argv[1], O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
+ if (fd == -1)
+ errExit("open");
+
+ if (unlink(argv[1]) == -1) /* Remove filename */
+ errExit("unlink");
+
+ for (j = 0; j < numBlocks; j++) /* Write lots of junk to file */
+ if (write(fd, buf, BUF_SIZE) != BUF_SIZE)
+ fatal("partial/failed write");
+
+ snprintf(shellCmd, CMD_SIZE, "df -k `dirname %s`", argv[1]);
+ system(shellCmd); /* View space used in file system */
+
+ if (close(fd) == -1) /* File is now destroyed */
+ errExit("close");
+ printf("********** Closed file descriptor\n");
+
+ /* See the erratum for page 348 at http://man7.org/tlpi/errata/.
+ Depending on factors such as random scheduler decisions and the
+ size of the file created, the 'df' command executed by the second
+ system() call below does may not show a change in the amount
+ of disk space consumed, because the blocks of the closed file
+ have not yet been freed by the kernel. If this is the case,
+ then inserting a sleep(1) call here should be sufficient to
+ ensure that the the file blocks have been freed by the time
+ of the second 'df' command.
+ */
+
+ system(shellCmd); /* Review space used in file system */
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 18-4 */
+
+/* view_symlink.c
+
+ Demonstrate the use of readlink() and realpath() to read and display
+ the contents of a symbolic link.
+*/
+#include <sys/stat.h>
+#include <limits.h> /* For definition of PATH_MAX */
+#include "tlpi_hdr.h"
+
+#define BUF_SIZE PATH_MAX
+
+int
+main(int argc, char *argv[])
+{
+ struct stat statbuf;
+ char buf[BUF_SIZE];
+ ssize_t numBytes;
+
+ if (argc != 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s pathname\n", argv[0]);
+
+ /* User lstat() to check whether the supplied pathname is
+ a symbolic link. Alternatively, we could have checked to
+ whether readlink() failed with EINVAL. */
+
+ if (lstat(argv[1], &statbuf) == -1)
+ errExit("lstat");
+
+ if (!S_ISLNK(statbuf.st_mode))
+ fatal("%s is not a symbolic link", argv[1]);
+
+ numBytes = readlink(argv[1], buf, BUF_SIZE - 1);
+ if (numBytes == -1)
+ errExit("readlink");
+ buf[numBytes] = '\0'; /* Add terminating null byte */
+ printf("readlink: %s --> %s\n", argv[1], buf);
+
+ if (realpath(argv[1], buf) == NULL)
+ errExit("realpath");
+ printf("realpath: %s --> %s\n", argv[1], buf);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE = copy mix23_linebuff mix23io \
+ write_bytes \
+ write_bytes_fdatasync \
+ write_bytes_fsync \
+ write_bytes_o_sync
+
+LINUX_EXE = direct_read
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+clean :
+ ${RM} ${EXE} *.o
+
+write_bytes_fdatasync : write_bytes.c
+ ${CC} -DUSE_FDATASYNC -o $@ write_bytes.c ${CFLAGS} ${LDLIBS}
+
+write_bytes_fsync : write_bytes.c
+ ${CC} -DUSE_FSYNC -o $@ write_bytes.c ${CFLAGS} ${LDLIBS}
+
+write_bytes_o_sync : write_bytes.c
+ ${CC} -DUSE_O_SYNC -o $@ write_bytes.c ${CFLAGS} ${LDLIBS}
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 4-1 */
+
+/* copy.c
+
+ Copy the file named argv[1] to a new file named in argv[2].
+*/
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+#ifndef BUF_SIZE /* Allow "cc -D" to override definition */
+#define BUF_SIZE 1024
+#endif
+
+int
+main(int argc, char *argv[])
+{
+ int inputFd, outputFd, openFlags;
+ mode_t filePerms;
+ ssize_t numRead;
+ char buf[BUF_SIZE];
+
+ if (argc != 3 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s old-file new-file\n", argv[0]);
+
+ /* Open input and output files */
+
+ inputFd = open(argv[1], O_RDONLY);
+ if (inputFd == -1)
+ errExit("opening file %s", argv[1]);
+
+ openFlags = O_CREAT | O_WRONLY | O_TRUNC;
+ filePerms = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP |
+ S_IROTH | S_IWOTH; /* rw-rw-rw- */
+ outputFd = open(argv[2], openFlags, filePerms);
+ if (outputFd == -1)
+ errExit("opening file %s", argv[2]);
+
+ /* Transfer data until we encounter end of input or an error */
+
+ while ((numRead = read(inputFd, buf, BUF_SIZE)) > 0)
+ if (write(outputFd, buf, numRead) != numRead)
+ fatal("couldn't write whole buffer");
+ if (numRead == -1)
+ errExit("read");
+
+ if (close(inputFd) == -1)
+ errExit("close input");
+ if (close(outputFd) == -1)
+ errExit("close output");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 13-1 */
+
+/* direct_read.c
+
+ Demonstrate the use of O_DIRECT to perform I/O bypassing the buffer cache
+ ("direct I/O").
+
+ Usage: direct_read file length [offset [alignment]]
+
+ This program is Linux-specific.
+*/
+#define _GNU_SOURCE /* Obtain O_DIRECT definition from <fcntl.h> */
+#include <fcntl.h>
+#include <malloc.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int fd;
+ ssize_t numRead;
+ size_t length, alignment;
+ off_t offset;
+ char *buf;
+
+ if (argc < 3 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s file length [offset [alignment]]\n", argv[0]);
+
+ length = getLong(argv[2], GN_ANY_BASE, "length");
+ offset = (argc > 3) ? getLong(argv[3], GN_ANY_BASE, "offset") : 0;
+ alignment = (argc > 4) ? getLong(argv[4], GN_ANY_BASE, "alignment") : 4096;
+
+ fd = open(argv[1], O_RDONLY | O_DIRECT);
+ if (fd == -1)
+ errExit("open");
+
+ /* memalign() allocates a block of memory aligned on an address that
+ is a multiple of its first argument. By specifying this argument as
+ 2 * 'alignment' and then adding 'alignment' to the returned pointer,
+ we ensure that 'buf' is aligned on a non-power-of-two multiple of
+ 'alignment'. We do this to ensure that if, for example, we ask
+ for a 256-byte aligned buffer, we don't accidentally get
+ a buffer that is also aligned on a 512-byte boundary. */
+
+ buf = memalign(alignment * 2, length + alignment);
+ if (buf == NULL)
+ errExit("memalign");
+
+ buf += alignment;
+
+ if (lseek(fd, offset, SEEK_SET) == -1)
+ errExit("lseek");
+
+ numRead = read(fd, buf, length);
+ if (numRead == -1)
+ errExit("read");
+ printf("Read %ld bytes\n", (long) numRead);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 13-4 */
+
+/* mix23_linebuff.c
+
+ Illustrates the impact of stdio buffering when using stdio library
+ functions and I/O system calls to work on the same file. Observe the
+ difference in output when running this program with output directed
+ to a terminal and again with output directed to a file.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+int
+main(int argc, char *argv[])
+{
+ printf("If I had more time, \n");
+ write(STDOUT_FILENO, "I would have written you a shorter letter.\n", 43);
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 13 */
+
+/* mix23io.c
+
+ Illustrates the impact of stdio buffering when using stdio library functions
+ and I/O system calls to work on the same file.
+
+ Try running this program (with stdout directed to the terminal) without and
+ with a command-line argument (any string).
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+int
+main(int argc, char *argv[])
+{
+ printf("To man the world is twofold, ");
+ if (argc > 1)
+ printf("\n");
+ write(STDOUT_FILENO, "in accordance with his twofold attitude.\n", 41);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 13 */
+
+/* write_bytes.c
+
+ Write bytes to a file. (A simple program for file I/O benchmarking.)
+
+ Usage: write_bytes file num-bytes buf-size
+
+ Writes 'num-bytes' bytes to 'file', using a buffer size of 'buf-size'
+ for each write().
+
+ If compiled with -DUSE_O_SYNC, open the file with the O_SYNC flag,
+ so that all data and metadata changes are flushed to the disk.
+
+ If compiled with -DUSE_FDATASYNC, perform an fdatasync() after each write,
+ so that data--and possibly metadata--changes are flushed to the disk.
+
+ If compiled with -DUSE_FSYNC, perform an fsync() after each write, so that
+ data and metadata are flushed to the disk.
+*/
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ size_t bufSize, numBytes, thisWrite, totWritten;
+ char *buf;
+ int fd, openFlags;
+
+ if (argc != 4 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s file num-bytes buf-size\n", argv[0]);
+
+ numBytes = getLong(argv[2], GN_GT_0, "num-bytes");
+ bufSize = getLong(argv[3], GN_GT_0, "buf-size");
+
+ buf = malloc(bufSize);
+ if (buf == NULL)
+ errExit("malloc");
+
+ openFlags = O_CREAT | O_WRONLY;
+
+#if defined(USE_O_SYNC) && defined(O_SYNC)
+ openFlags |= O_SYNC;
+#endif
+
+ fd = open(argv[1], openFlags, S_IRUSR | S_IWUSR);
+ if (fd == -1)
+ errExit("open");
+
+ for (totWritten = 0; totWritten < numBytes;
+ totWritten += thisWrite) {
+ thisWrite = min(bufSize, numBytes - totWritten);
+
+ if (write(fd, buf, thisWrite) != thisWrite)
+ fatal("partial/failed write");
+
+#ifdef USE_FSYNC
+ if (fsync(fd))
+ errExit("fsync");
+#endif
+#ifdef USE_FDATASYNC
+ if (fdatasync(fd))
+ errExit("fdatasync");
+#endif
+ }
+
+ if (close(fd) == -1)
+ errExit("close");
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE = atomic_append bad_exclusive_open copy \
+ multi_descriptors seek_io t_readv t_truncate
+
+LINUX_EXE = large_file
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+
+clean :
+ ${RM} ${EXE} *.o
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 5-3 */
+
+/* atomic_append.c
+
+ Demonstrate the difference between using nonatomic lseek()+write()
+ and O_APPEND when writing to a file.
+
+ Usage: file num-bytes [x]
+
+ The program write 'num-bytes' bytes to 'file' a byte at a time. If
+ no additional command-line argument is supplied, the program opens the
+ file with the O_APPEND flag. If a command-line argument is supplied, the
+ O_APPEND is omitted when calling open(), and the program calls lseek()
+ to seek to the end of the file before calling write(). This latter
+ technique is vulnerable to a race condition, where data is lost because
+ the lseek() + write() steps are not atomic. This can be demonstrated
+ by looking at the size of the files produced by these two commands:
+
+ atomic_append f1 1000000 & atomic_append f1 1000000
+
+ atomic_append f2 1000000 x & atomic_append f2 1000000 x
+*/
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int numBytes, j, flags, fd;
+ Boolean useLseek;
+
+ if (argc < 3 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s file num-bytes [x]\n"
+ " 'x' means use lseek() instead of O_APPEND\n",
+ argv[0]);
+
+ useLseek = argc > 3;
+ flags = useLseek ? 0 : O_APPEND;
+ numBytes = getInt(argv[2], 0, "num-bytes");
+
+ fd = open(argv[1], O_RDWR | O_CREAT | flags, S_IRUSR | S_IWUSR);
+ if (fd == -1)
+ errExit("open");
+
+ for (j = 0; j < numBytes; j++) {
+ if (useLseek)
+ if (lseek(fd, 0, SEEK_END) == -1)
+ errExit("lseek");
+ if (write(fd, "x", 1) != 1)
+ fatal("write() failed");
+ }
+
+ printf("%ld done\n", (long) getpid());
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 5-1 */
+
+/* bad_exclusive_open.c
+
+ The following code shows why we need the open() O_EXCL flag.
+
+ This program tries ensure that it is the one that creates the file
+ named in its command-line argument. It does this by trying to open()
+ the filename once without the O_CREAT flag (if this open() succeeds
+ then the program know it is not the creator of the file), and if
+ that open() fails, it calls open() a second time, with the O_CREAT flag.
+
+ If the first open() fails, the program assumes that it is the creator
+ of the file. However this may not be true: some other process may have
+ created the file between the two calls to open().
+*/
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int fd;
+
+ if (argc < 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s file\n", argv[0]);
+
+ fd = open(argv[1], O_WRONLY); /* Open 1: check if file exists */
+ if (fd != -1) { /* Open succeeded */
+ printf("[PID %ld] File \"%s\" already exists\n",
+ (long) getpid(), argv[1]);
+ close(fd);
+ } else {
+ if (errno != ENOENT) { /* Failed for unexpected reason */
+ errExit("open");
+ } else {
+ printf("[PID %ld] File \"%s\" doesn't exist yet\n",
+ (long) getpid(), argv[1]);
+ if (argc > 2) { /* Delay between check and create */
+ sleep(5); /* Suspend execution for 5 seconds */
+ printf("[PID %ld] Done sleeping\n", (long) getpid());
+ }
+ fd = open(argv[1], O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
+ if (fd == -1)
+ errExit("open");
+
+ printf("[PID %ld] Created file \"%s\" exclusively\n",
+ (long) getpid(), argv[1]); /* MAY NOT BE TRUE! */
+ }
+ }
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 4-1 */
+
+/* copy.c
+
+ Copy the file named argv[1] to a new file named in argv[2].
+*/
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+#ifndef BUF_SIZE /* Allow "cc -D" to override definition */
+#define BUF_SIZE 1024
+#endif
+
+int
+main(int argc, char *argv[])
+{
+ int inputFd, outputFd, openFlags;
+ mode_t filePerms;
+ ssize_t numRead;
+ char buf[BUF_SIZE];
+
+ if (argc != 3 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s old-file new-file\n", argv[0]);
+
+ /* Open input and output files */
+
+ inputFd = open(argv[1], O_RDONLY);
+ if (inputFd == -1)
+ errExit("opening file %s", argv[1]);
+
+ openFlags = O_CREAT | O_WRONLY | O_TRUNC;
+ filePerms = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP |
+ S_IROTH | S_IWOTH; /* rw-rw-rw- */
+ outputFd = open(argv[2], openFlags, filePerms);
+ if (outputFd == -1)
+ errExit("opening file %s", argv[2]);
+
+ /* Transfer data until we encounter end of input or an error */
+
+ while ((numRead = read(inputFd, buf, BUF_SIZE)) > 0)
+ if (write(outputFd, buf, numRead) != numRead)
+ fatal("couldn't write whole buffer");
+ if (numRead == -1)
+ errExit("read");
+
+ if (close(inputFd) == -1)
+ errExit("close input");
+ if (close(outputFd) == -1)
+ errExit("close output");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 5-3 */
+
+/* large_file.c
+
+ Demonstrate the use of the (obsolete) Large File System API.
+
+ This program is Linux-specific.
+*/
+#define _LARGEFILE64_SOURCE
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int fd;
+ off64_t off;
+
+ if (argc != 3 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s pathname offset\n", argv[0]);
+
+ fd = open64(argv[1], O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
+ if (fd == -1)
+ errExit("open64");
+
+ off = atoll(argv[2]);
+ if (lseek64(fd, off, SEEK_SET) == -1)
+ errExit("lseek64");
+
+ if (write(fd, "test", 4) == -1)
+ errExit("write");
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 5-6 */
+
+/* multi_descriptors.c
+
+ Show the interaction of multiple descriptors accessing the same
+ file (some via the same shared open file table entry).
+*/
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int fd1, fd2, fd3;
+#define file "a"
+ char cmd[] = "cat " file "; echo";
+
+ fd1 = open(file, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+ if (fd1 == -1)
+ errExit("open fd1");
+ fd2 = dup(fd1);
+ if (fd2 == -1)
+ errExit("dup");
+ fd3 = open(file, O_RDWR);
+ if (fd3 == -1)
+ errExit("open fd3");
+
+ /* 'fd1' and 'fd2' share same open file table entry (and thus file
+ offset). 'fd3' has its own open file table entry, and thus a
+ separate file offset. */
+
+ if (write(fd1, "Hello,", 6) == -1)
+ errExit("write1");
+ system(cmd);
+ if (write(fd2, " world", 6) == -1)
+ errExit("write2");
+ system(cmd);
+ if (lseek(fd2, 0, SEEK_SET) == -1)
+ errExit("lseek");
+ if (write(fd1, "HELLO,", 6) == -1)
+ errExit("write3");
+ system(cmd);
+ if (write(fd3, "Gidday", 6) == -1)
+ errExit("write4");
+ system(cmd);
+
+ if (close(fd1) == -1)
+ errExit("close output");
+ if (close(fd2) == -1)
+ errExit("close output");
+ if (close(fd3) == -1)
+ errExit("close output");
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 4-3 */
+
+/* seek_io.c
+
+ Demonstrate the use of lseek() and file I/O system calls.
+
+ Usage: seek_io file {r<length>|R<length>|w<string>|s<offset>}...
+
+ This program opens the file named on its command line, and then performs
+ the file I/O operations specified by its remaining command-line arguments:
+
+ r<length> Read 'length' bytes from the file at current
+ file offset, displaying them as text.
+
+ R<length> Read 'length' bytes from the file at current
+ file offset, displaying them in hex.
+
+ w<string> Write 'string' at current file offset.
+
+ s<offset> Set the file offset to 'offset'.
+
+ Example:
+
+ seek_io myfile wxyz s1 r2
+*/
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ size_t len;
+ off_t offset;
+ int fd, ap, j;
+ char *buf;
+ ssize_t numRead, numWritten;
+
+ if (argc < 3 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s file {r<length>|R<length>|w<string>|s<offset>}...\n",
+ argv[0]);
+
+ fd = open(argv[1], O_RDWR | O_CREAT,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP |
+ S_IROTH | S_IWOTH); /* rw-rw-rw- */
+ if (fd == -1)
+ errExit("open");
+
+ for (ap = 2; ap < argc; ap++) {
+ switch (argv[ap][0]) {
+ case 'r': /* Display bytes at current offset, as text */
+ case 'R': /* Display bytes at current offset, in hex */
+ len = getLong(&argv[ap][1], GN_ANY_BASE, argv[ap]);
+
+ buf = malloc(len);
+ if (buf == NULL)
+ errExit("malloc");
+
+ numRead = read(fd, buf, len);
+ if (numRead == -1)
+ errExit("read");
+
+ if (numRead == 0) {
+ printf("%s: end-of-file\n", argv[ap]);
+ } else {
+ printf("%s: ", argv[ap]);
+ for (j = 0; j < numRead; j++) {
+ if (argv[ap][0] == 'r')
+ printf("%c", isprint((unsigned char) buf[j]) ?
+ buf[j] : '?');
+ else
+ printf("%02x ", (unsigned int) buf[j]);
+ }
+ printf("\n");
+ }
+
+ free(buf);
+ break;
+
+ case 'w': /* Write string at current offset */
+ numWritten = write(fd, &argv[ap][1], strlen(&argv[ap][1]));
+ if (numWritten == -1)
+ errExit("write");
+ printf("%s: wrote %ld bytes\n", argv[ap], (long) numWritten);
+ break;
+
+ case 's': /* Change file offset */
+ offset = getLong(&argv[ap][1], GN_ANY_BASE, argv[ap]);
+ if (lseek(fd, offset, SEEK_SET) == -1)
+ errExit("lseek");
+ printf("%s: seek succeeded\n", argv[ap]);
+ break;
+
+ default:
+ cmdLineErr("Argument must start with [rRws]: %s\n", argv[ap]);
+ }
+ }
+
+ if (close(fd) == -1)
+ errExit("close");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 5-2 */
+
+/* t_readv.c
+
+ Demonstrate the use of the readv() system call to perform "gather I/O".
+
+ (This program is merely intended to provide a code snippet for the book;
+ unless you construct a suitably formatted input file, it can't be
+ usefully executed.)
+*/
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int fd;
+ struct iovec iov[3];
+ struct stat myStruct; /* First buffer */
+ int x; /* Second buffer */
+#define STR_SIZE 100
+ char str[STR_SIZE]; /* Third buffer */
+ ssize_t numRead, totRequired;
+
+ if (argc != 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s file\n", argv[0]);
+
+ fd = open(argv[1], O_RDONLY);
+ if (fd == -1)
+ errExit("open");
+
+ totRequired = 0;
+
+ iov[0].iov_base = &myStruct;
+ iov[0].iov_len = sizeof(struct stat);
+ totRequired += iov[0].iov_len;
+
+ iov[1].iov_base = &x;
+ iov[1].iov_len = sizeof(x);
+ totRequired += iov[1].iov_len;
+
+ iov[2].iov_base = str;
+ iov[2].iov_len = STR_SIZE;
+ totRequired += iov[2].iov_len;
+
+ numRead = readv(fd, iov, 3);
+ if (numRead == -1)
+ errExit("readv");
+
+ if (numRead < totRequired)
+ printf("Read fewer bytes than requested\n");
+
+ printf("total bytes requested: %ld; bytes read: %ld\n",
+ (long) totRequired, (long) numRead);
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 5 */
+
+/* t_truncate.c
+
+ Demonstrate the use of the truncate() system call to truncate the file
+ named in argv[1] to the length specified in argv[2]
+*/
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ if (argc != 3 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s file length\n", argv[0]);
+
+ if (truncate(argv[1], getLong(argv[2], GN_ANY_BASE, "length")) == -1)
+ errExit("truncate");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE = i_fcntl_locking t_flock
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+clean :
+ ${RM} ${EXE} *.o
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 55-4 */
+
+/* create_pid_file.c
+
+ Implement a function that can be used by a daemon (or indeed any program)
+ to ensure that only one instance of the program is running.
+*/
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "region_locking.h" /* For lockRegion() */
+#include "create_pid_file.h" /* Declares createPidFile() and
+ defines CPF_CLOEXEC */
+#include "tlpi_hdr.h"
+
+#define BUF_SIZE 100 /* Large enough to hold maximum PID as string */
+
+/* Open/create the file named in 'pidFile', lock it, optionally set the
+ close-on-exec flag for the file descriptor, write our PID into the file,
+ and (in case the caller is interested) return the file descriptor
+ referring to the locked file. The caller is responsible for deleting
+ 'pidFile' file (just) before process termination. 'progName' should be the
+ name of the calling program (i.e., argv[0] or similar), and is used only for
+ diagnostic messages. If we can't open 'pidFile', or we encounter some other
+ error, then we print an appropriate diagnostic and terminate. */
+
+int
+createPidFile(const char *progName, const char *pidFile, int flags)
+{
+ int fd;
+ char buf[BUF_SIZE];
+
+ fd = open(pidFile, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
+ if (fd == -1)
+ errExit("Could not open PID file %s", pidFile);
+
+ if (flags & CPF_CLOEXEC) {
+
+ /* Set the close-on-exec file descriptor flag */
+
+ /* Instead of the following steps, we could (on Linux) have opened the
+ file with O_CLOEXEC flag. However, not all systems support open()
+ O_CLOEXEC (which was standardized only in SUSv4), so instead we use
+ fcntl() to set the close-on-exec flag after opening the file */
+
+ flags = fcntl(fd, F_GETFD); /* Fetch flags */
+ if (flags == -1)
+ errExit("Could not get flags for PID file %s", pidFile);
+
+ flags |= FD_CLOEXEC; /* Turn on FD_CLOEXEC */
+
+ if (fcntl(fd, F_SETFD, flags) == -1) /* Update flags */
+ errExit("Could not set flags for PID file %s", pidFile);
+ }
+
+ if (lockRegion(fd, F_WRLCK, SEEK_SET, 0, 0) == -1) {
+ if (errno == EAGAIN || errno == EACCES)
+ fatal("PID file '%s' is locked; probably "
+ "'%s' is already running", pidFile, progName);
+ else
+ errExit("Unable to lock PID file '%s'", pidFile);
+ }
+
+ if (ftruncate(fd, 0) == -1)
+ errExit("Could not truncate PID file '%s'", pidFile);
+
+ snprintf(buf, BUF_SIZE, "%ld\n", (long) getpid());
+ if (write(fd, buf, strlen(buf)) != strlen(buf))
+ fatal("Writing to PID file '%s'", pidFile);
+
+ return fd;
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Header file for Listing 55-4 */
+
+/* create_pid_file.h
+
+ Header file for create_pid_file.c.
+*/
+#ifndef CREATE_PID_FILE_H /* Prevent accidental double inclusion */
+#define CREATE_PID_FILE_H
+
+#define CPF_CLOEXEC 1
+
+int createPidFile(const char *progName, const char *pidFile, int flags);
+
+#endif
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 55-2 */
+
+/* i_fcntl_locking.c
+
+ Usage: i_fcntl_locking file...
+
+ where 'file...' is a list of files on which to place locks - the user is
+ then prompted to interactively enter commands to test/place locks on
+ regions of the files.
+
+ NOTE: The version of the program provided here is an enhanced version
+ of that provided in the book. In particular, this version:
+
+ 1) handles multiple file name arguments, allowing locks to be
+ applied to any of the named files,
+ 2) displays information about whether advisory or mandatory
+ locking is in effect on each file, and
+ 3) allows the use of OFD locks, a type of file lock added in
+ Linux 3.15.
+*/
+#define _GNU_SOURCE /* To get definitions of 'OFD' locking commands */
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+#define MAX_LINE 100
+
+#ifdef __linux__
+#ifndef F_OFD_GETLK /* In case we are on a system with glibc version
+ earlier than 2.20 */
+#define F_OFD_GETLK 36
+#define F_OFD_SETLK 37
+#define F_OFD_SETLKW 38
+#endif
+#endif
+
+/* Return TRUE if mandatory locking is enabled for fd. */
+
+static Boolean
+mandLockingEnabled(int fd)
+{
+ /* Mandatory locking is enabled for a file if the set-group-ID bit is on
+ but group-execute permission is off (a combination that under earlier
+ versions of UNIX had no useful meaning). If mandatory locking is enabled
+ for a file, then attempts to perform write(2) or read(2) calls on locked
+ regions of files will block until the lock is removed (or if the I/O
+ call is nonblocking it will return immediately with an error). */
+
+ struct stat sb;
+
+ if (fstat(fd, &sb) == -1)
+ errExit("stat");
+ return (sb.st_mode & S_ISGID) != 0 && (sb.st_mode & S_IXGRP) == 0;
+}
+
+static void
+displayCmdFmt(int argc, char *argv[], const int fdList[])
+{
+ int j;
+
+ if (argc == 2) { /* Only a single filename argument */
+ printf("\nFormat: cmd lock start length [whence]\n\n");
+ } else {
+ printf("\nFormat: %scmd lock start length [whence]\n\n",
+ (argc > 2) ? "file-num " : "");
+ printf(" file-num is a number from the following list\n");
+ for (j = 1; j < argc; j++)
+ printf(" %2d %-10s [%s locking]\n", j, argv[j],
+ mandLockingEnabled(fdList[j]) ? "mandatory" :
+ "advisory");
+ }
+ printf(" 'cmd' is 'g' (GETLK), 's' (SETLK), or 'w' (SETLKW)\n");
+#ifdef __linux__
+ printf(" or for OFD locks: 'G' (OFD_GETLK), 'S' (OFD_SETLK), or "
+ "'W' (OFD_SETLKW)\n");
+#endif
+ printf(" 'lock' is 'r' (READ), 'w' (WRITE), or 'u' (UNLOCK)\n");
+ printf(" 'start' and 'length' specify byte range to lock\n");
+ printf(" 'whence' is 's' (SEEK_SET, default), 'c' (SEEK_CUR), "
+ "or 'e' (SEEK_END)\n\n");
+ /* Of course, SEEK_CUR is redundant since we can't
+ change the file offset in this program... */
+}
+
+int
+main(int argc, char *argv[])
+{
+ int fd, numRead, cmd, status;
+ char lock, cmdCh, whence, line[MAX_LINE];
+ struct flock fl;
+ long long len, st;
+ int *fdList;
+ int fileNum, j;
+
+ if (argc < 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s file...\n", argv[0]);
+
+ fdList = calloc(argc, sizeof(int));
+ if (fdList == NULL)
+ errExit("calloc");
+
+ for (j = 1; j < argc; j++) {
+ fdList[j] = open(argv[j], O_RDWR);
+ if (fdList[j] == -1)
+ errExit("open (%s)", argv[j]);
+ }
+
+ /* Inform user what type of locking is in effect for each file. */
+
+ printf("File Locking\n");
+ printf("---- -------\n");
+
+ for (j = 1; j < argc; j++)
+ printf("%-10s %s\n", argv[j], mandLockingEnabled(fdList[j]) ?
+ "mandatory" : "advisory");
+ printf("\n");
+
+ printf("Enter ? for help\n");
+
+ for (;;) { /* Prompt for locking command and carry it out */
+ printf("PID=%ld> ", (long) getpid());
+ fflush(stdout);
+
+ if (fgets(line, MAX_LINE, stdin) == NULL) /* EOF */
+ exit(EXIT_SUCCESS);
+ line[strlen(line) - 1] = '\0'; /* Remove trailing '\n' */
+
+ if (*line == '\0')
+ continue; /* Skip blank lines */
+
+ if (line[0] == '?') {
+ displayCmdFmt(argc, argv, fdList);
+ continue;
+ }
+
+ whence = 's'; /* In case not otherwise filled in */
+
+ if (argc == 2) { /* Just 1 file arg on command line? */
+ fileNum = 1; /* Then no need to read a file number */
+ numRead = sscanf(line, " %c %c %lld %lld %c",
+ &cmdCh, &lock, &st, &len, &whence);
+ } else {
+ numRead = sscanf(line, "%d %c %c %lld %lld %c",
+ &fileNum, &cmdCh, &lock, &st, &len, &whence);
+ }
+
+ fl.l_start = st;
+ fl.l_len = len;
+
+ if (fileNum < 1 || fileNum >= argc) {
+ printf("File number must be in range 1 to %d\n", argc-1);
+ continue;
+ }
+
+ fd = fdList[fileNum];
+
+ if (!((numRead >= 4 && argc == 2) || (numRead >= 5 && argc > 2)) ||
+ strchr("gswGSW", cmdCh) == NULL ||
+ strchr("rwu", lock) == NULL || strchr("sce", whence) == NULL) {
+ printf("Invalid command!\n");
+ continue;
+ }
+
+ cmd =
+#ifdef __linux__
+ (cmdCh == 'G') ? F_OFD_GETLK : (cmdCh == 'S') ? F_OFD_SETLK :
+ (cmdCh == 'W') ? F_OFD_SETLKW :
+#endif
+ (cmdCh == 'g') ? F_GETLK : (cmdCh == 's') ? F_SETLK : F_SETLKW;
+#ifdef __linux__
+ fl.l_pid = 0; /* Required for 'OFD' locking commands */
+#endif
+ fl.l_type = (lock == 'r') ? F_RDLCK : (lock == 'w') ? F_WRLCK : F_UNLCK;
+ fl.l_whence = (whence == 'c') ? SEEK_CUR :
+ (whence == 'e') ? SEEK_END : SEEK_SET;
+
+ status = fcntl(fd, cmd, &fl); /* Perform request... */
+
+ if (cmd == F_GETLK
+#ifdef __linux__
+ || cmd == F_OFD_GETLK
+#endif
+ ) {
+ if (status == -1) {
+ errMsg("fcntl");
+ } else {
+ if (fl.l_type == F_UNLCK)
+ printf("[PID=%ld] Lock can be placed\n", (long) getpid());
+ else /* Locked out by someone else */
+ printf("[PID=%ld] Denied by %s lock on %lld:%lld "
+ "(held by PID %ld)\n", (long) getpid(),
+ (fl.l_type == F_RDLCK) ? "READ" : "WRITE",
+ (long long) fl.l_start,
+ (long long) fl.l_len, (long) fl.l_pid);
+ }
+ } else {
+ if (status == 0)
+ printf("[PID=%ld] %s\n", (long) getpid(),
+ (lock == 'u') ? "unlocked" : "got lock");
+ else if (errno == EAGAIN || errno == EACCES)
+ printf("[PID=%ld] failed (incompatible lock)\n",
+ (long) getpid());
+ else if (errno == EDEADLK) /* F_SETLKW */
+ printf("[PID=%ld] failed (deadlock)\n", (long) getpid());
+ else
+ errMsg("fcntl");
+ }
+ }
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 55-3 */
+
+/* region_locking.c
+
+ Some useful functions for file region (fcntl()) locking.
+*/
+#include <fcntl.h>
+#include "region_locking.h" /* Declares functions defined here */
+
+/* Lock a file region (private; public interfaces below) */
+
+static int
+lockReg(int fd, int cmd, int type, int whence, int start, off_t len)
+{
+ struct flock fl;
+
+ fl.l_type = type;
+ fl.l_whence = whence;
+ fl.l_start = start;
+ fl.l_len = len;
+
+ return fcntl(fd, cmd, &fl);
+}
+
+int /* Lock a file region using nonblocking F_SETLK */
+lockRegion(int fd, int type, int whence, int start, int len)
+{
+ return lockReg(fd, F_SETLK, type, whence, start, len);
+}
+
+int /* Lock a file region using blocking F_SETLKW */
+lockRegionWait(int fd, int type, int whence, int start, int len)
+{
+ return lockReg(fd, F_SETLKW, type, whence, start, len);
+}
+
+/* Test if a file region is lockable. Return 0 if lockable, or
+ PID of process holding incompatible lock, or -1 on error. */
+
+pid_t
+regionIsLocked(int fd, int type, int whence, int start, int len)
+{
+ struct flock fl;
+
+ fl.l_type = type;
+ fl.l_whence = whence;
+ fl.l_start = start;
+ fl.l_len = len;
+
+ if (fcntl(fd, F_GETLK, &fl) == -1)
+ return -1;
+
+ return (fl.l_type == F_UNLCK) ? 0 : fl.l_pid;
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Header file for Listing 55-3 */
+
+/* region_locking.h
+
+ Header file for region_locking.c.
+*/
+#ifndef REGION_LOCKING_H
+#define REGION_LOCKING_H
+
+#include <sys/types.h>
+
+int lockRegion(int fd, int type, int whence, int start, int len);
+
+int lockRegionWait(int fd, int type, int whence, int start, int len);
+
+pid_t regionIsLocked(int fd, int type, int whence, int start, int len);
+
+#endif
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 55-1 */
+
+/* t_flock.c
+
+ Demonstrate the use of flock() to place file locks.
+*/
+#include <sys/file.h>
+#include <fcntl.h>
+#include "curr_time.h" /* Declaration of currTime() */
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int fd, lock;
+ const char *lname;
+
+ if (argc < 3 || strcmp(argv[1], "--help") == 0 ||
+ strchr("sx", argv[2][0]) == NULL)
+ usageErr("%s file lock [sleep-time]\n"
+ " 'lock' is 's' (shared) or 'x' (exclusive)\n"
+ " optionally followed by 'n' (nonblocking)\n"
+ " 'sleep-time' specifies time to hold lock\n", argv[0]);
+
+ lock = (argv[2][0] == 's') ? LOCK_SH : LOCK_EX;
+ if (argv[2][1] == 'n')
+ lock |= LOCK_NB;
+
+ fd = open(argv[1], O_RDONLY); /* Open file to be locked */
+ if (fd == -1)
+ errExit("open");
+
+ lname = (lock & LOCK_SH) ? "LOCK_SH" : "LOCK_EX";
+
+ printf("PID %ld: requesting %s at %s\n", (long) getpid(), lname,
+ currTime("%T"));
+
+ if (flock(fd, lock) == -1) {
+ if (errno == EWOULDBLOCK)
+ fatal("PID %ld: already locked - bye!", (long) getpid());
+ else
+ errExit("flock (PID=%ld)", (long) getpid());
+ }
+
+ printf("PID %ld: granted %s at %s\n", (long) getpid(), lname,
+ currTime("%T"));
+
+ sleep((argc > 3) ? getInt(argv[3], GN_NONNEG, "sleep-time") : 10);
+
+ printf("PID %ld: releasing %s at %s\n", (long) getpid(), lname,
+ currTime("%T"));
+ if (flock(fd, LOCK_UN) == -1)
+ errExit("flock");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE = t_chown t_stat t_umask t_utime t_utimes
+
+LINUX_EXE = chiflag
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+clean :
+ ${RM} ${EXE} *.o
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 15-7 */
+
+/* chiflag.c
+
+ Change i-node flags (sometimes also known as ext2 extended file attributes)
+ for files named on the command line. Usage is as shown in usageError() below.
+
+ This program won't build on a system that takes its <linux/fs.h> from
+ a kernel earlier than 2.6.19. You'll instead need to do something
+ like including <linux/ext2_fs.h> and replacing all of the FS_* names
+ below by EXT2_*.
+
+ This program is Linux-specific.
+*/
+#define _GNU_SOURCE
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <linux/fs.h>
+#include "tlpi_hdr.h"
+
+static void
+usageError(const char *progName)
+{
+ fprintf(stderr, "Usage: %s {+-=}{attrib-chars} file...\n\n", progName);
+#define fpe(str) fprintf(stderr, " " str) /* Shorter! */
+ fpe("+ add attribute; - remove attribute; "
+ "= set attributes absolutely\n\n");
+ fpe("'attrib-chars' contains one or more of:\n");
+ fpe(" a Force open() to include O_APPEND "
+ "(privilege required)\n");
+ fpe(" A Do not update last access time\n");
+ fpe(" c Compress (requires e2compr package)\n");
+ fpe(" d Do not backup with dump(8)\n");
+ fpe(" D Synchronous directory updates\n");
+ fpe(" i Immutable (privilege required)\n");
+ fpe(" j Enable ext3/ext4 data journaling\n");
+ fpe(" s Secure deletion (not implemented)\n");
+ fpe(" S Synchronous file updates\n");
+ fpe(" t Disable tail-packing (Reiserfs only)\n");
+ fpe(" T Mark as top-level directory for Orlov algorithm\n");
+ fpe(" u Undelete (not implemented)\n");
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int attr, oldAttr, j, fd;
+ char *p;
+
+ if (argc < 3 || strchr("+-=", argv[1][0]) == NULL ||
+ strcmp(argv[1], "--help") == 0)
+ usageError(argv[0]);
+
+ /* Build bit mask based on attribute string in argv[1] */
+
+ attr = 0;
+ for (p = &argv[1][1]; *p != '\0'; p++) {
+ switch (*p) {
+ case 'a': attr |= FS_APPEND_FL; break;
+ case 'A': attr |= FS_NOATIME_FL; break;
+ case 'c': attr |= FS_COMPR_FL; break;
+ case 'd': attr |= FS_NODUMP_FL; break;
+ case 'D': attr |= FS_DIRSYNC_FL; break;
+ case 'i': attr |= FS_IMMUTABLE_FL; break;
+ case 'j': attr |= FS_JOURNAL_DATA_FL; break;
+ case 's': attr |= FS_SECRM_FL; break;
+ case 'S': attr |= FS_SYNC_FL; break;
+ case 't': attr |= FS_NOTAIL_FL; break;
+ case 'T': attr |= FS_TOPDIR_FL; break;
+ case 'u': attr |= FS_UNRM_FL; break;
+ default: usageError(argv[0]);
+ }
+ }
+
+ /* Open each filename in turn, and change its attributes */
+
+ for (j = 2; j < argc; j++) {
+ fd = open(argv[j], O_RDONLY);
+ if (fd == -1) { /* Most likely error is nonexistent file */
+ errMsg("open: %s", argv[j]);
+ continue;
+ }
+
+ /* If argv[1] starts with + or -, then retrieve existing
+ attribute bit mask and modify as required */
+
+ if (argv[1][0] == '+' || argv[1][0] == '-') {
+ if (ioctl(fd, FS_IOC_GETFLAGS, &oldAttr) == -1)
+ errExit("ioctl1: %s", argv[j]);
+ attr = (*argv[1] == '-') ? (oldAttr & ~attr) : (oldAttr | attr);
+ }
+
+ if (ioctl(fd, FS_IOC_SETFLAGS, &attr) == -1)
+ errExit("ioctl2: %s", argv[j]);
+ if (close(fd) == -1)
+ errExit("close");
+ }
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 15-4 */
+
+/* file_perms.c
+
+ Return a string representation of a file permissions mask.
+*/
+#include <sys/stat.h>
+#include <stdio.h>
+#include "file_perms.h" /* Interface for this implementation */
+
+#define STR_SIZE sizeof("rwxrwxrwx")
+
+char * /* Return ls(1)-style string for file permissions mask */
+filePermStr(mode_t perm, int flags)
+{
+ static char str[STR_SIZE];
+
+ /* If FP_SPECIAL was specified, we emulate the trickery of ls(1) in
+ returning set-user-ID, set-group-ID, and sticky bit information in
+ the user/group/other execute fields. This is made more complex by
+ the fact that the case of the character displayed for this bits
+ depends on whether the corresponding execute bit is on or off. */
+
+ snprintf(str, STR_SIZE, "%c%c%c%c%c%c%c%c%c",
+ (perm & S_IRUSR) ? 'r' : '-', (perm & S_IWUSR) ? 'w' : '-',
+ (perm & S_IXUSR) ?
+ (((perm & S_ISUID) && (flags & FP_SPECIAL)) ? 's' : 'x') :
+ (((perm & S_ISUID) && (flags & FP_SPECIAL)) ? 'S' : '-'),
+ (perm & S_IRGRP) ? 'r' : '-', (perm & S_IWGRP) ? 'w' : '-',
+ (perm & S_IXGRP) ?
+ (((perm & S_ISGID) && (flags & FP_SPECIAL)) ? 's' : 'x') :
+ (((perm & S_ISGID) && (flags & FP_SPECIAL)) ? 'S' : '-'),
+ (perm & S_IROTH) ? 'r' : '-', (perm & S_IWOTH) ? 'w' : '-',
+ (perm & S_IXOTH) ?
+ (((perm & S_ISVTX) && (flags & FP_SPECIAL)) ? 't' : 'x') :
+ (((perm & S_ISVTX) && (flags & FP_SPECIAL)) ? 'T' : '-'));
+
+ return str;
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 15-3 */
+
+/* file_perms.h
+
+ Header file for file_perms.c.
+*/
+#ifndef FILE_PERMS_H
+#define FILE_PERMS_H
+
+#include <sys/types.h>
+
+#define FP_SPECIAL 1 /* Include set-user-ID, set-group-ID, and sticky
+ bit information in returned string */
+
+char *filePermStr(mode_t perm, int flags);
+
+#endif
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 15-2 */
+
+/* t_chown.c
+
+ Demonstrate the use of the chown() system call to change the owner
+ and group of a file.
+
+ Usage: t_chown owner group [file...]
+
+ Either or both of owner and/or group can be specified as "-" to
+ leave them unchanged.
+*/
+#include <pwd.h>
+#include <grp.h>
+#include "ugid_functions.h" /* Declarations of userIdFromName()
+ and groupIdFromName() */
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ uid_t uid;
+ gid_t gid;
+ int j;
+ Boolean errFnd;
+
+ if (argc < 3 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s owner group [file...]\n"
+ " owner or group can be '-', "
+ "meaning leave unchanged\n", argv[0]);
+
+ if (strcmp(argv[1], "-") == 0) { /* "-" ==> don't change owner */
+ uid = -1;
+ } else { /* Turn user name into UID */
+ uid = userIdFromName(argv[1]);
+ if (uid == -1)
+ fatal("No such user (%s)", argv[1]);
+ }
+
+ if (strcmp(argv[2], "-") == 0) { /* "-" ==> don't change group */
+ gid = -1;
+ } else { /* Turn group name into GID */
+ gid = groupIdFromName(argv[2]);
+ if (gid == -1)
+ fatal("No group user (%s)", argv[1]);
+ }
+
+ /* Change ownership of all files named in remaining arguments */
+
+ errFnd = FALSE;
+ for (j = 3; j < argc; j++) {
+ if (chown(argv[j], uid, gid) == -1) {
+ errMsg("chown: %s", argv[j]);
+ errFnd = TRUE;
+ }
+ }
+
+ exit(errFnd ? EXIT_FAILURE : EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 15-1 */
+
+/* t_stat.c
+
+ A program that displays the information returned by the stat()/lstat()
+ system calls.
+
+ Usage: t_stat [-l] file
+
+ The '-l' option indicates that lstat() rather than stat() should be used.
+*/
+#include <sys/sysmacros.h>
+#if defined(_AIX)
+#define _BSD
+#endif
+#if defined(__sgi) || defined(__sun) /* Some systems need this */
+#include <sys/mkdev.h> /* To get major() and minor() */
+#endif
+#if defined(__hpux) /* Other systems need this */
+#include <sys/mknod.h>
+#endif
+#include <sys/stat.h>
+#include <time.h>
+#include "file_perms.h"
+#include "tlpi_hdr.h"
+
+static void
+displayStatInfo(const struct stat *sb)
+{
+ printf("File type: ");
+
+ switch (sb->st_mode & S_IFMT) {
+ case S_IFREG: printf("regular file\n"); break;
+ case S_IFDIR: printf("directory\n"); break;
+ case S_IFCHR: printf("character device\n"); break;
+ case S_IFBLK: printf("block device\n"); break;
+ case S_IFLNK: printf("symbolic (soft) link\n"); break;
+ case S_IFIFO: printf("FIFO or pipe\n"); break;
+ case S_IFSOCK: printf("socket\n"); break;
+ default: printf("unknown file type?\n"); break;
+ }
+
+ printf("Device containing i-node: major=%ld minor=%ld\n",
+ (long) major(sb->st_dev), (long) minor(sb->st_dev));
+
+ printf("I-node number: %ld\n", (long) sb->st_ino);
+
+ printf("Mode: %lo (%s)\n",
+ (unsigned long) sb->st_mode, filePermStr(sb->st_mode, 0));
+
+ if (sb->st_mode & (S_ISUID | S_ISGID | S_ISVTX))
+ printf(" special bits set: %s%s%s\n",
+ (sb->st_mode & S_ISUID) ? "set-UID " : "",
+ (sb->st_mode & S_ISGID) ? "set-GID " : "",
+ (sb->st_mode & S_ISVTX) ? "sticky " : "");
+
+ printf("Number of (hard) links: %ld\n", (long) sb->st_nlink);
+
+ printf("Ownership: UID=%ld GID=%ld\n",
+ (long) sb->st_uid, (long) sb->st_gid);
+
+ if (S_ISCHR(sb->st_mode) || S_ISBLK(sb->st_mode))
+ printf("Device number (st_rdev): major=%ld; minor=%ld\n",
+ (long) major(sb->st_rdev), (long) minor(sb->st_rdev));
+
+ printf("File size: %lld bytes\n", (long long) sb->st_size);
+ printf("Optimal I/O block size: %ld bytes\n", (long) sb->st_blksize);
+ printf("512B blocks allocated: %lld\n", (long long) sb->st_blocks);
+
+ printf("Last file access: %s", ctime(&sb->st_atime));
+ printf("Last file modification: %s", ctime(&sb->st_mtime));
+ printf("Last status change: %s", ctime(&sb->st_ctime));
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct stat sb;
+ Boolean statLink; /* True if "-l" specified (i.e., use lstat) */
+ int fname; /* Location of filename argument in argv[] */
+
+ statLink = (argc > 1) && strcmp(argv[1], "-l") == 0;
+ /* Simple parsing for "-l" */
+ fname = statLink ? 2 : 1;
+
+ if (fname >= argc || (argc > 1 && strcmp(argv[1], "--help") == 0))
+ usageErr("%s [-l] file\n"
+ " -l = use lstat() instead of stat()\n", argv[0]);
+
+ if (statLink) {
+ if (lstat(argv[fname], &sb) == -1)
+ errExit("lstat");
+ } else {
+ if (stat(argv[fname], &sb) == -1)
+ errExit("stat");
+ }
+
+ displayStatInfo(&sb);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 15-5 */
+
+/* t_umask.c
+
+ Demonstrate the affect of umask() in conjunction with open() and mkdir().
+*/
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "file_perms.h"
+#include "tlpi_hdr.h"
+
+#define MYFILE "myfile"
+#define MYDIR "mydir"
+#define FILE_PERMS (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)
+#define DIR_PERMS (S_IRWXU | S_IRWXG | S_IRWXO)
+#define UMASK_SETTING (S_IWGRP | S_IXGRP | S_IWOTH | S_IXOTH)
+
+int
+main(int argc, char *argv[])
+{
+ int fd;
+ struct stat sb;
+ mode_t u;
+
+ umask(UMASK_SETTING);
+
+ fd = open(MYFILE, O_RDWR | O_CREAT | O_EXCL, FILE_PERMS);
+ if (fd == -1)
+ errExit("open-%s", MYFILE);
+ if (mkdir(MYDIR, DIR_PERMS) == -1)
+ errExit("mkdir-%s", MYDIR);
+
+ u = umask(0); /* Retrieves (and clears) umask value */
+
+ if (stat(MYFILE, &sb) == -1)
+ errExit("stat-%s", MYFILE);
+ printf("Requested file perms: %s\n", filePermStr(FILE_PERMS, 0));
+ printf("Process umask: %s\n", filePermStr(u, 0));
+ printf("Actual file perms: %s\n\n", filePermStr(sb.st_mode, 0));
+
+ if (stat(MYDIR, &sb) == -1)
+ errExit("stat-%s", MYDIR);
+ printf("Requested dir. perms: %s\n", filePermStr(DIR_PERMS, 0));
+ printf("Process umask: %s\n", filePermStr(u, 0));
+ printf("Actual dir. perms: %s\n", filePermStr(sb.st_mode, 0));
+
+ if (unlink(MYFILE) == -1)
+ errMsg("unlink-%s", MYFILE);
+ if (rmdir(MYDIR) == -1)
+ errMsg("rmdir-%s", MYDIR);
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 15 */
+
+/* t_utime.c
+
+ Demonstrate the use of utime(): set the last modification time on a file to
+ be the same as the last access time.
+
+ Usage: t_utime file
+
+ See also t_utimes.c.
+*/
+#include <sys/stat.h>
+#include <utime.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ char *pathname;
+ struct stat sb;
+ struct utimbuf utb;
+
+ if (argc != 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s file\n", argv[0]);
+
+ pathname = argv[1];
+
+ if (stat(pathname, &sb) == -1) /* Retrieve current file times */
+ errExit("stat");
+
+ utb.actime = sb.st_atime; /* Leave access time unchanged */
+ utb.modtime = sb.st_atime; /* Make modify time same as access time */
+ if (utime(pathname, &utb) == -1) /* Update file times */
+ errExit("utime");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 15 */
+
+/* t_utimes.c
+
+ Demonstrate the use of utimes(): set the last modification time on a file to
+ be the same as the last access time, but tweak the microsecond components of
+ the two timestamps.
+
+ Usage: t_utimes file
+
+ See also t_utime.c.
+*/
+#include <sys/stat.h>
+#include <sys/time.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ struct stat sb;
+ struct timeval tv[2];
+
+ if (argc != 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s file\n", argv[0]);
+
+ if (stat(argv[1], &sb) == -1) /* Retrieve current file times */
+ errExit("stat");
+
+ tv[0].tv_sec = sb.st_atime; /* Leave atime seconds unchanged */
+ tv[0].tv_usec = 223344; /* Change microseconds for atime */
+ tv[1].tv_sec = sb.st_atime; /* mtime seconds == atime seconds */
+ tv[1].tv_usec = 667788; /* mtime microseconds */
+
+ if (utimes(argv[1], tv) == -1)
+ errExit("utimes");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE = t_statvfs
+
+LINUX_EXE = t_statfs t_mount t_umount
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+clean :
+ ${RM} ${EXE} *.o
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 14-1 */
+
+/* t_mount.c
+
+ Demonstrate the use of mount(2) to create a mount point.
+
+ Usage: as described in usageError()
+
+ Examples:
+ t_mount -t ext3 /dev/sda12 /testfs
+
+ t_mount -t ext2 -f r -o nogrpid /dev/sda9 /mydir ext2
+ (The "r" for flags specifies that the file system
+ is to be mounted read-only.)
+
+ This program is Linux-specific.
+*/
+#include <sys/mount.h>
+#include "tlpi_hdr.h"
+
+#ifndef MS_DIRSYNC /* May not be defined in older glibc headers */
+#define MS_DIRSYNC 128
+#endif
+
+#ifndef MS_BIND /* May not be defined in older glibc headers */
+#define MS_BIND 4096
+#endif
+
+#ifndef MS_MOVE /* May not be defined in older glibc headers */
+#define MS_MOVE 8192
+#endif
+
+#ifndef MS_REC /* May not be defined in older glibc headers */
+#define MS_REC 16384
+#endif
+
+#ifndef MS_UNBINDABLE /* May not be defined in older glibc headers */
+#define MS_UNBINDABLE (1<<17) /* change to unbindable */
+#endif
+
+#ifndef MS_PRIVATE /* May not be defined in older glibc headers */
+#define MS_PRIVATE (1<<18) /* change to private */
+#endif
+
+#ifndef MS_SLAVE /* May not be defined in older glibc headers */
+#define MS_SLAVE (1<<19) /* change to slave */
+#endif
+
+#ifndef MS_SHARED /* May not be defined in older glibc headers */
+#define MS_SHARED (1<<20) /* change to shared */
+#endif
+
+#ifndef MS_LAZYTIME
+#define MS_LAZYTIME (1<<25)
+#endif
+
+static void
+usageError(const char *progName, const char *msg)
+{
+ if (msg != NULL)
+ fprintf(stderr, "%s", msg);
+
+ fprintf(stderr, "Usage: %s [options] source target\n\n", progName);
+ fprintf(stderr, "Available options:\n");
+#define fpe(str) fprintf(stderr, " " str) /* Shorter! */
+ fpe("-t fstype [e.g., 'ext2' or 'reiserfs']\n");
+ fpe("-o data [file system-dependent options,\n");
+ fpe(" e.g., 'bsdgroups' for ext2]\n");
+ fpe("-f mountflags can include any of:\n");
+#define fpe2(str) fprintf(stderr, " " str)
+ fpe2("b - MS_BIND create a bind mount\n");
+ fpe2("d - MS_DIRSYNC synchronous directory updates\n");
+ fpe2("l - MS_MANDLOCK permit mandatory locking\n");
+ fpe2("L - MS_LAZYATIME lazy atime updates\n");
+ fpe2("m - MS_MOVE atomically move subtree\n");
+ fpe2("A - MS_NOATIME don't update atime (last access time)\n");
+ fpe2("V - MS_NODEV don't permit device access\n");
+ fpe2("D - MS_NODIRATIME don't update atime on directories\n");
+ fpe2("E - MS_NOEXEC don't allow executables\n");
+ fpe2("S - MS_NOSUID disable set-user/group-ID programs\n");
+ fpe2("p - MS_PRIVATE mark as private\n");
+ fpe2("r - MS_RDONLY read-only mount\n");
+ fpe2("c - MS_REC recursive mount\n");
+ fpe2("T - MS_RELATIME relative atime updates\n");
+ fpe2("R - MS_REMOUNT remount\n");
+ fpe2("h - MS_SHARED mark as shared\n");
+ fpe2("v - MS_SLAVE mark as slave\n");
+ fpe2("s - MS_SYNCHRONOUS make writes synchronous\n");
+ fpe2("u - MS_UNBINDABLE mark as unbindable\n");
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ unsigned long flags;
+ char *data, *fstype;
+ int j, opt;
+
+ flags = 0;
+ data = NULL;
+ fstype = NULL;
+
+ while ((opt = getopt(argc, argv, "o:t:f:")) != -1) {
+ switch (opt) {
+ case 'o':
+ data = optarg;
+ break;
+
+ case 't':
+ fstype = optarg;
+ break;
+
+ case 'f':
+ for (j = 0; j < strlen(optarg); j++) {
+
+ /* In this version of the program we support more flags than
+ in the version of the program shown in the book */
+
+ switch (optarg[j]) {
+ case 'b': flags |= MS_BIND; break;
+ case 'd': flags |= MS_DIRSYNC; break;
+ case 'l': flags |= MS_MANDLOCK; break;
+ case 'm': flags |= MS_MOVE; break;
+ case 'A': flags |= MS_NOATIME; break;
+ case 'V': flags |= MS_NODEV; break;
+ case 'D': flags |= MS_NODIRATIME; break;
+ case 'E': flags |= MS_NOEXEC; break;
+ case 'S': flags |= MS_NOSUID; break;
+ case 'p': flags |= MS_PRIVATE; break;
+ case 'r': flags |= MS_RDONLY; break;
+ case 'c': flags |= MS_REC; break;
+ case 'T': flags |= MS_RELATIME; break;
+ case 'R': flags |= MS_REMOUNT; break;
+ case 'h': flags |= MS_SHARED; break;
+ case 'v': flags |= MS_SLAVE; break;
+ case 's': flags |= MS_SYNCHRONOUS; break;
+ case 'u': flags |= MS_UNBINDABLE; break;
+ default: usageError(argv[0], NULL);
+ }
+ }
+ break;
+
+ default:
+ usageError(argv[0], NULL);
+ }
+ }
+
+ if (argc != optind + 2)
+ usageError(argv[0], "Wrong number of arguments\n");
+
+ if (mount(argv[optind], argv[optind + 1], fstype, flags, data) == -1)
+ errExit("mount");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 14 */
+
+/* t_statfs.c
+
+ Demonstrate the use of statfs() to retrieve information about
+ a mounted file system.
+
+ This program is Linux-specific.
+
+ See also t_statvfs.c.
+*/
+#include <sys/statfs.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ struct statfs sfs;
+
+ if (argc != 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s path\n", argv[0]);
+
+ if (statfs(argv[1], &sfs) == -1)
+ errExit("statfs");
+
+ printf("File system type: %#lx\n",
+ (unsigned long) sfs.f_type);
+ printf("Optimal I/O block size: %lu\n",
+ (unsigned long) sfs.f_bsize);
+ printf("Total data blocks: %lu\n",
+ (unsigned long) sfs.f_blocks);
+ printf("Free data blocks: %lu\n",
+ (unsigned long) sfs.f_bfree);
+ printf("Free blocks for nonsuperuser: %lu\n",
+ (unsigned long) sfs.f_bavail);
+ printf("Total i-nodes: %lu\n",
+ (unsigned long) sfs.f_files);
+ printf("File system ID: %#x, %#x\n",
+ (unsigned) sfs.f_fsid.__val[0], (unsigned) sfs.f_fsid.__val[1]);
+ printf("Free i-nodes: %lu\n",
+ (unsigned long) sfs.f_ffree);
+ printf("Maximum file name length: %lu\n",
+ (unsigned long) sfs.f_namelen);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 14 */
+
+/* t_statvfs.c
+
+ Demonstrate the use of statvfs() to retrieve information about
+ a mounted file system.
+
+ See also t_statfs.c.
+*/
+#include <sys/statvfs.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ struct statvfs sb;
+
+ if (argc != 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s path\n", argv[0]);
+
+ if (statvfs(argv[1], &sb) == -1)
+ errExit("statvfs");
+
+ printf("Block size %lu\n", sb.f_bsize);
+ printf("Fundamental block size %lu\n", sb.f_frsize);
+ printf("Total blocks (in above units) %lu\n",
+ (unsigned long) sb.f_blocks);
+ printf("Free blocks for priv. proc. %lu\n",
+ (unsigned long) sb.f_bfree);
+ printf("Free blocks for unpriv. proc. %lu\n",
+ (unsigned long) sb.f_bavail);
+ printf("Total number of i-nodes %lu\n",
+ (unsigned long) sb.f_files);
+ printf("Free i-nodes for priv. proc. %lu\n",
+ (unsigned long) sb.f_ffree);
+ printf("Free i-nodes for nonpriv. proc. %lu\n",
+ (unsigned long) sb.f_favail);
+ printf("File system ID %#lx\n", sb.f_fsid);
+ printf("Flags %#lx\n", sb.f_flag);
+ printf("Maximum filename length %lu\n", sb.f_namemax);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 14 */
+
+/* t_umount.c
+
+ Demonstrate the use of the umount() system call to unmount a mount point.
+
+ Usage: t_umount mount-point
+
+ This program is Linux-specific.
+*/
+#include <sys/mount.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ if (argc != 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s mount-point\n", argv[0]);
+
+ if (umount(argv[1]) == -1)
+ errExit("umount");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE = t_getopt
+
+LINUX_EXE =
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+clean :
+ ${RM} ${EXE} *.o
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing B-1 */
+
+/* t_getopt.c
+
+ Demonstrate the use of getopt(3) to parse command-line options.
+*/
+#include <ctype.h>
+#include "tlpi_hdr.h"
+
+#define printable(ch) (isprint((unsigned char) ch) ? ch : '#')
+
+#ifdef __GNUC__
+__attribute__((noreturn)) /* Prevent "this statement may fall through"
+ warnings from "gcc -Wimplicit-fallthrough"
+ in switch() statement in main(). */
+#endif
+static void /* Print "usage" message and exit */
+usageError(char *progName, char *msg, int opt)
+{
+ if (msg != NULL && opt != 0)
+ fprintf(stderr, "%s (-%c)\n", msg, printable(opt));
+ fprintf(stderr, "Usage: %s [-p arg] [-x]\n", progName);
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int opt, xfnd;
+ char *pstr;
+
+ xfnd = 0;
+ pstr = NULL;
+
+ while ((opt = getopt(argc, argv, ":p:x")) != -1) {
+ printf("opt =%3d (%c); optind = %d", opt, printable(opt), optind);
+ if (opt == '?' || opt == ':')
+ printf("; optopt =%3d (%c)", optopt, printable(optopt));
+ printf("\n");
+
+ switch (opt) {
+ case 'p': pstr = optarg; break;
+ case 'x': xfnd++; break;
+ case ':': usageError(argv[0], "Missing argument", optopt);
+ case '?': usageError(argv[0], "Unrecognized option", optopt);
+ default: fatal("Unexpected case in switch()");
+ }
+ }
+
+ if (xfnd != 0)
+ printf("-x was specified (count=%d)\n", xfnd);
+ if (pstr != NULL)
+ printf("-p was specified with the value \"%s\"\n", pstr);
+ if (optind < argc)
+ printf("First nonoption argument is \"%s\" at argv[%d]\n",
+ argv[optind], optind);
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE =
+
+LINUX_EXE = demo_inotify dnotify inotify_dtree rand_dtree
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allftw : ${FTW_EXE}
+
+allgen : ${GEN_EXE}
+
+clean :
+ ${RM} ${EXE} *.o
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 19-1 */
+
+/* demo_inotify.c
+
+ Demonstrate the use of the inotify API.
+
+ Usage: demo_inotify pathname...
+
+ The program monitors each of the files specified on the command line for all
+ possible file events.
+
+ This program is Linux-specific. The inotify API is available in Linux 2.6.13
+ and later.
+*/
+#include <sys/inotify.h>
+#include <limits.h>
+#include "tlpi_hdr.h"
+
+static void /* Display information from inotify_event structure */
+displayInotifyEvent(struct inotify_event *i)
+{
+ printf(" wd =%2d; ", i->wd);
+ if (i->cookie > 0)
+ printf("cookie =%4d; ", i->cookie);
+
+ printf("mask = ");
+ if (i->mask & IN_ACCESS) printf("IN_ACCESS ");
+ if (i->mask & IN_ATTRIB) printf("IN_ATTRIB ");
+ if (i->mask & IN_CLOSE_NOWRITE) printf("IN_CLOSE_NOWRITE ");
+ if (i->mask & IN_CLOSE_WRITE) printf("IN_CLOSE_WRITE ");
+ if (i->mask & IN_CREATE) printf("IN_CREATE ");
+ if (i->mask & IN_DELETE) printf("IN_DELETE ");
+ if (i->mask & IN_DELETE_SELF) printf("IN_DELETE_SELF ");
+ if (i->mask & IN_IGNORED) printf("IN_IGNORED ");
+ if (i->mask & IN_ISDIR) printf("IN_ISDIR ");
+ if (i->mask & IN_MODIFY) printf("IN_MODIFY ");
+ if (i->mask & IN_MOVE_SELF) printf("IN_MOVE_SELF ");
+ if (i->mask & IN_MOVED_FROM) printf("IN_MOVED_FROM ");
+ if (i->mask & IN_MOVED_TO) printf("IN_MOVED_TO ");
+ if (i->mask & IN_OPEN) printf("IN_OPEN ");
+ if (i->mask & IN_Q_OVERFLOW) printf("IN_Q_OVERFLOW ");
+ if (i->mask & IN_UNMOUNT) printf("IN_UNMOUNT ");
+ printf("\n");
+
+ if (i->len > 0)
+ printf(" name = %s\n", i->name);
+}
+
+#define BUF_LEN (10 * (sizeof(struct inotify_event) + NAME_MAX + 1))
+
+int
+main(int argc, char *argv[])
+{
+ int inotifyFd, wd, j;
+ char buf[BUF_LEN] __attribute__ ((aligned(8)));
+ ssize_t numRead;
+ char *p;
+ struct inotify_event *event;
+
+ if (argc < 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s pathname...\n", argv[0]);
+
+ inotifyFd = inotify_init(); /* Create inotify instance */
+ if (inotifyFd == -1)
+ errExit("inotify_init");
+
+ /* For each command-line argument, add a watch for all events */
+
+ for (j = 1; j < argc; j++) {
+ wd = inotify_add_watch(inotifyFd, argv[j], IN_ALL_EVENTS);
+ if (wd == -1)
+ errExit("inotify_add_watch");
+
+ printf("Watching %s using wd %d\n", argv[j], wd);
+ }
+
+ for (;;) { /* Read events forever */
+ numRead = read(inotifyFd, buf, BUF_LEN);
+ if (numRead == 0)
+ fatal("read() from inotify fd returned 0!");
+
+ if (numRead == -1)
+ errExit("read");
+
+ printf("Read %ld bytes from inotify fd\n", (long) numRead);
+
+ /* Process all of the events in buffer returned by read() */
+
+ for (p = buf; p < buf + numRead; ) {
+ event = (struct inotify_event *) p;
+ displayInotifyEvent(event);
+
+ p += sizeof(struct inotify_event) + event->len;
+ }
+ }
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 19 */
+
+/* dnotify.c
+
+ Demonstrate the use of the (obsolete) dnotify feature to obtain directory
+ change notifications. (Modern programs should use inotify instead of
+ dnotify. The inotify API is available in Linux 2.6.13 and later.)
+
+ Usage is as shown in usageError() below. An example is the following:
+
+ dnotify dir1:a xyz/dir2:acdM
+
+ See also demo_inotify.c.
+
+ This program is Linux-specific.
+*/
+#define _GNU_SOURCE /* To get DN_* constants from <fcntl.h> */
+#include <fcntl.h>
+#include <signal.h>
+#include "tlpi_hdr.h"
+
+static void /* Print (optional) message and usage info, then exit */
+usageError(const char *progName, const char *msg)
+{
+ if (msg != NULL)
+ fprintf(stderr, "%s", msg);
+
+ fprintf(stderr, "Usage: %s directory:[events]...\n", progName);
+ fprintf(stderr, " Events are:\n"
+ " a - access; A - attrib; c - create; d - delete\n"
+ " m - modify; r - rename; M - multishot\n"
+ " (default is all events)\n");
+ exit(EXIT_FAILURE);
+}
+
+static void
+handler(int sig, siginfo_t *si, void *ucontext)
+{
+ printf("Got event on descriptor %d\n", si->si_fd);
+ /* UNSAFE (see Section 21.1.2) */
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct sigaction sa;
+ int fd, events, fnum;
+ const int NOTIFY_SIG = SIGRTMIN;
+ char *p;
+
+ if (argc < 2 || strcmp(argv[1], "--help") == 0)
+ usageError(argv[0], NULL);
+
+ /* Establish handler for notification signal */
+
+ sa.sa_sigaction = handler;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_SIGINFO; /* So handler gets siginfo_t arg. */
+ if (sigaction(NOTIFY_SIG, &sa, NULL) == -1)
+ errExit("sigaction");
+
+ for (fnum = 1; fnum < argc; fnum++) {
+ p = strchr(argv[fnum], ':'); /* Look for optional ':' */
+
+ if (p == NULL) { /* Default is all events + multishot */
+ events = DN_ACCESS | DN_ATTRIB | DN_CREATE | DN_DELETE |
+ DN_MODIFY | DN_RENAME | DN_MULTISHOT;
+ } else { /* ':' present, parse event chars */
+ *p = '\0'; /* Terminates directory component */
+ events = 0;
+ for (p++; *p != '\0'; p++) {
+ switch (*p) {
+ case 'a': events |= DN_ACCESS; break;
+ case 'A': events |= DN_ATTRIB; break;
+ case 'c': events |= DN_CREATE; break;
+ case 'd': events |= DN_DELETE; break;
+ case 'm': events |= DN_MODIFY; break;
+ case 'r': events |= DN_RENAME; break;
+ case 'M': events |= DN_MULTISHOT; break;
+ default: usageError(argv[0], "Bad event character\n");
+ }
+ }
+ }
+
+ /* Obtain a file descriptor for the directory to be monitored */
+
+ fd = open(argv[fnum], O_RDONLY);
+ if (fd == -1)
+ errExit("open");
+ printf("opened '%s' as file descriptor %d\n", argv[fnum], fd);
+
+ /* Use alternate signal instead of SIGIO for dnotify events */
+
+ if (fcntl(fd, F_SETSIG, NOTIFY_SIG) == -1)
+ errExit("fcntl - F_SETSIG");
+
+ /* Enable directory change notifications */
+
+ if (fcntl(fd, F_NOTIFY, events) == -1)
+ errExit("fcntl-F_NOTIFY");
+ printf("events: %o\n", (unsigned int) events);
+ }
+
+ for (;;)
+ pause(); /* Wait for events */
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 19 */
+
+/* inotify_dtree.c
+
+ This is an example application to demonstrate the robust use of the
+ inotify API.
+
+ The goal of the application is to maintain an internal representation
+ ("a cache") of the directory trees named on its command line. To keep
+ the application shorter, just the directories are represented, not the
+ files, but dealing with the latter is simpler in any case.
+
+ As directories are added, removed, and renamed in the subtrees, the
+ resulting inotify events are used to maintain an internal representation
+ of the directory trees that remains consistent with the filesystem.
+ The program also provides a command-line interface that allows the user
+ to perform tasks such as dumping the current state of the cache and
+ running a consistency check of the cache against the current state of
+ the directory tree(s).
+
+ Testing of this program is ongoing, and bug reports (to mtk@man7.org)
+ are welcome.
+
+ The rand_dtree.c program can be used to stress test the operation
+ of this program.
+
+ See also the article
+ "Filesystem notification, part 2: A deeper investigation of inotify"
+ July 2014
+ https://lwn.net/Articles/605128/
+*/
+
+/* Known limitations
+ - Pathnames longer than PATH_MAX are not handled.
+*/
+
+#define _GNU_SOURCE
+#include <sys/select.h>
+#include <sys/stat.h>
+#include <limits.h>
+#include <sys/select.h>
+#include <sys/inotify.h>
+#include <fcntl.h>
+#include <ftw.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \
+ } while (0)
+
+/* logMessage() flags */
+
+#define VB_BASIC 1 /* Basic messages */
+#define VB_NOISY 2 /* Verbose messages */
+
+static int verboseMask;
+static int checkCache;
+static int dumpCache;
+static int readBufferSize = 0;
+static char *stopFile;
+static int abortOnCacheProblem;
+
+static FILE *logfp = NULL;
+
+static int inotifyReadCnt = 0; /* Counts number of read()s from
+ inotify file descriptor */
+
+static const int INOTIFY_READ_BUF_LEN =
+ (100 * (sizeof(struct inotify_event) + NAME_MAX + 1));
+
+static void dumpCacheToLog(void);
+
+/* Something went badly wrong. Create a 'stop' file to signal the
+ 'rand_dtree' processes to stop, dump a copy of the cache to the
+ log file, and abort. */
+
+__attribute__ ((__noreturn__))
+static void
+createStopFileAndAbort(void)
+{
+ open(stopFile, O_CREAT | O_RDWR, 0600);
+ dumpCacheToLog();
+ abort();
+}
+
+/* Write a log message. The message is sent to none, either, or both of
+ stderr and the log file, depending on 'vb_mask' and whether a log file
+ has been specified via command-line options . */
+
+static void
+logMessage(int vb_mask, const char *format, ...)
+{
+ va_list argList;
+
+ /* Write message to stderr if 'vb_mask' is zero, or matches one
+ of the bits in 'verboseMask' */
+
+ if ((vb_mask == 0) || (vb_mask & verboseMask)) {
+ va_start(argList, format);
+ vfprintf(stderr, format, argList);
+ va_end(argList);
+ }
+
+ /* If we have a log file, write the message there */
+
+ if (logfp != NULL) {
+ va_start(argList, format);
+ vfprintf(logfp, format, argList);
+ va_end(argList);
+ }
+}
+
+/***********************************************************************/
+
+/* Display some information about an inotify event. (Used when
+ when we are doing verbose logging.) */
+
+static void
+displayInotifyEvent(struct inotify_event *ev)
+{
+ logMessage(VB_NOISY, "==> wd = %d; ", ev->wd);
+ if (ev->cookie > 0)
+ logMessage(VB_NOISY, "cookie = %4d; ", ev->cookie);
+
+ logMessage(VB_NOISY, "mask = ");
+
+ if (ev->mask & IN_ISDIR)
+ logMessage(VB_NOISY, "IN_ISDIR ");
+
+ if (ev->mask & IN_CREATE)
+ logMessage(VB_NOISY, "IN_CREATE ");
+
+ if (ev->mask & IN_DELETE_SELF)
+ logMessage(VB_NOISY, "IN_DELETE_SELF ");
+
+ if (ev->mask & IN_MOVE_SELF)
+ logMessage(VB_NOISY, "IN_MOVE_SELF ");
+ if (ev->mask & IN_MOVED_FROM)
+ logMessage(VB_NOISY, "IN_MOVED_FROM ");
+ if (ev->mask & IN_MOVED_TO)
+ logMessage(VB_NOISY, "IN_MOVED_TO ");
+
+ if (ev->mask & IN_IGNORED)
+ logMessage(VB_NOISY, "IN_IGNORED ");
+ if (ev->mask & IN_Q_OVERFLOW)
+ logMessage(VB_NOISY, "IN_Q_OVERFLOW ");
+ if (ev->mask & IN_UNMOUNT)
+ logMessage(VB_NOISY, "IN_UNMOUNT ");
+
+ logMessage(VB_NOISY, "\n");
+
+ if (ev->len > 0)
+ logMessage(VB_NOISY, " name = %s\n", ev->name);
+}
+
+/***********************************************************************/
+
+/* Data structures and functions for the watch list cache */
+
+/* We use a very simple data structure for caching watched directory
+ paths: a dynamically sized array that is searched linearly. Not
+ efficient, but our main goal is to demonstrate the use of inotify. */
+
+struct watch {
+ int wd; /* Watch descriptor (-1 if slot unused) */
+ char path[PATH_MAX]; /* Cached pathname */
+};
+
+struct watch *wlCache = NULL; /* Array of cached items */
+
+static int cacheSize = 0; /* Current size of the array */
+
+/* Deallocate the watch cache */
+
+static void
+freeCache(void)
+{
+ free(wlCache);
+ cacheSize = 0;
+ wlCache = NULL;
+}
+
+/* Check that all pathnames in the cache are valid, and refer
+ to directories */
+
+static void
+checkCacheConsistency(void)
+{
+ int failures, j;
+ struct stat sb;
+
+ failures = 0;
+ for (j = 0; j < cacheSize; j++) {
+ if (wlCache[j].wd >= 0) {
+ if (lstat(wlCache[j].path, &sb) == -1) {
+ logMessage(0,
+ "checkCacheConsistency: stat: "
+ "[slot = %d; wd = %d] %s: %s\n",
+ j, wlCache[j].wd, wlCache[j].path, strerror(errno));
+ failures++;
+ } else if (!S_ISDIR(sb.st_mode)) {
+ logMessage(0, "checkCacheConsistency: %s is not a directory\n",
+ wlCache[j].path);
+ exit(EXIT_FAILURE);
+ }
+ }
+ }
+
+ if (failures > 0)
+ logMessage(VB_NOISY, "checkCacheConsistency: %d failures\n",
+ failures);
+}
+
+/* Check whether the cache contains the watch descriptor 'wd'.
+ If found, return the slot number, otherwise return -1. */
+
+static int
+findWatch(int wd)
+{
+ int j;
+
+ for (j = 0; j < cacheSize; j++)
+ if (wlCache[j].wd == wd)
+ return j;
+
+ return -1;
+}
+
+/* Find and return the cache slot for the watch descriptor 'wd'.
+ The caller expects this watch descriptor to exist. If it does not,
+ there is a problem, which is signaled by the -1 return. */
+
+static int
+findWatchChecked(int wd)
+{
+ int slot;
+
+ slot = findWatch(wd);
+
+ if (slot >= 0)
+ return slot;
+
+ logMessage(0, "Could not find watch %d\n", wd);
+
+ /* With multiple renamers there are still rare cases where
+ the cache is missing entries after a 'Could not find watch'
+ event. It looks as though this is because of races with nftw(),
+ since the cache is (occasionally) re-created with fewer
+ entries than there are objects in the tree(s). Returning
+ -1 to our caller identifies that there's a problem, and the
+ caller should probably trigger a cache rebuild. */
+
+ if (abortOnCacheProblem) {
+ createStopFileAndAbort();
+ } else {
+ return -1;
+ }
+}
+
+/* Mark a cache entry as unused */
+
+static void
+markCacheSlotEmpty(int slot)
+{
+ logMessage(VB_NOISY,
+ " markCacheSlotEmpty: slot = %d; wd = %d; path = %s\n",
+ slot, wlCache[slot].wd, wlCache[slot].path);
+
+ wlCache[slot].wd = -1;
+ wlCache[slot].path[0] = '\0';
+}
+
+/* Find a free slot in the cache */
+
+static int
+findEmptyCacheSlot(void)
+{
+ int j;
+ const int ALLOC_INCR = 200;
+
+ for (j = 0; j < cacheSize; j++)
+ if (wlCache[j].wd == -1)
+ return j;
+
+ /* No free slot found; resize cache */
+
+ cacheSize += ALLOC_INCR;
+
+ wlCache = realloc(wlCache, cacheSize * sizeof(struct watch));
+ if (wlCache == NULL)
+ errExit("realloc");
+
+ for (j = cacheSize - ALLOC_INCR; j < cacheSize; j++)
+ markCacheSlotEmpty(j);
+
+ return cacheSize - ALLOC_INCR; /* Return first slot in
+ newly allocated space */
+}
+
+/* Add an item to the cache */
+
+static int
+addWatchToCache(int wd, const char *pathname)
+{
+ int slot;
+
+ slot = findEmptyCacheSlot();
+
+ wlCache[slot].wd = wd;
+ strncpy(wlCache[slot].path, pathname, PATH_MAX);
+
+ return slot;
+}
+
+/* Return the cache slot that corresponds to a particular pathname,
+ or -1 if the pathname is not in the cache */
+
+static int
+pathnameToCacheSlot(const char *pathname)
+{
+ int j;
+
+ for (j = 0; j < cacheSize; j++)
+ if (wlCache[j].wd >= 0 && strcmp(wlCache[j].path, pathname) == 0)
+ return j;
+
+ return -1;
+}
+
+/* Is 'pathname' in the watch cache? */
+
+static int
+pathnameInCache(const char *pathname)
+{
+ return pathnameToCacheSlot(pathname) >= 0;
+}
+
+/* Dump contents of watch cache to the log file */
+
+static void
+dumpCacheToLog(void)
+{
+ int cnt, j;
+
+ cnt = 0;
+
+ for (j = 0; j < cacheSize; j++) {
+ if (wlCache[j].wd >= 0) {
+ fprintf(logfp, "%d: wd = %d; %s\n", j,
+ wlCache[j].wd, wlCache[j].path);
+ cnt++;
+ }
+ }
+
+ fprintf(logfp, "Total entries: %d\n", cnt);
+}
+
+/***********************************************************************/
+
+/* Data structures and functions for dealing with the directory pathnames
+ provided as command-line arguments. These directories form the roots of
+ the trees that we will monitor */
+
+static char **rootDirPaths; /* List of pathnames supplied on command line */
+static int numRootDirs; /* Number of pathnames supplied on command line */
+static int ignoreRootDirs; /* Number of command-line pathnames that
+ we've ceased to monitor */
+static struct stat *rootDirStat;
+ /* stat(2) structures for croot directories */
+
+/* Duplicate the pathnames supplied on the command line, perform
+ some sanity checking along the way */
+
+static void
+copyRootDirPaths(char *argv[])
+{
+ char **p;
+ int j, k;
+ struct stat sb;
+
+ p = argv;
+ numRootDirs = 0;
+
+ /* Count the number of root paths, and check that the paths are valid */
+
+ for (p = argv; *p != NULL; p++) {
+
+ /* Check that command-line arguments are directories */
+
+ if (lstat(*p, &sb) == -1) {
+ fprintf(stderr, "lstat() failed on '%s'\n", *p);
+ exit(EXIT_FAILURE);
+ }
+
+ if (! S_ISDIR(sb.st_mode)) {
+ fprintf(stderr, "'%s' is not a directory\n", *p);
+ exit(EXIT_FAILURE);
+ }
+
+ numRootDirs++;
+ }
+
+ /* Create a copy of the root directory pathnames */
+
+ rootDirPaths = calloc(numRootDirs, sizeof(char *));
+ if (rootDirPaths == NULL)
+ errExit("calloc");
+
+ rootDirStat = calloc(numRootDirs, sizeof(struct stat));
+ if (rootDirPaths == NULL)
+ errExit("calloc");
+
+ for (j = 0; j < numRootDirs; j++) {
+ rootDirPaths[j] = strdup(argv[j]);
+ if (rootDirPaths[j] == NULL)
+ errExit("strdup");
+
+ /* If the same filesystem object appears more than once in the
+ command line, this will cause confusion if we later try to zap
+ an object from the set of root paths. So, reject such
+ duplicates now. Note that we can't just do simple string
+ comparisons of the arguments, since different pathname strings
+ may refer to the same filesystem object (e.g., "mydir" and
+ "./mydir"). So, we use stat() to compare i-node numbers and
+ containing device IDs. */
+
+ if (lstat(argv[j], &rootDirStat[j]) == -1)
+ errExit("lstat");
+
+ for (k = 0; k < j; k++) {
+ if ((rootDirStat[j].st_ino == rootDirStat[k].st_ino) &&
+ (rootDirStat[j].st_dev == rootDirStat[k].st_dev)) {
+
+ fprintf(stderr, "Duplicate filesystem objects: %s, %s\n",
+ argv[j], argv[k]);
+ exit(EXIT_FAILURE);
+ }
+ }
+ }
+
+ ignoreRootDirs = 0;
+}
+
+/* Return the address of the element in 'rootDirPaths' that points
+ to a string matching 'path', or NULL if there is no match */
+
+static char **
+findRootDirPath(const char *path)
+{
+ int j;
+
+ for (j = 0; j < numRootDirs; j++)
+ if (rootDirPaths[j] != NULL && strcmp(path, rootDirPaths[j]) == 0)
+ return &rootDirPaths[j];
+
+ return NULL;
+}
+
+/* Is 'path' one of the pathnames that was listed on the command line? */
+
+static int
+isRootDirPath(const char *path)
+{
+ return findRootDirPath(path) != NULL;
+}
+
+/* We've ceased to monitor a root directory pathname (probably because it
+ was renamed), so zap this pathname from the root path list */
+
+static void
+zapRootDirPath(const char *path)
+{
+ char **p;
+
+ printf("zapRootDirPath: %s\n", path);
+
+ p = findRootDirPath(path);
+ if (p == NULL) {
+ fprintf(stderr, "zapRootDirPath(): path not found!\n");
+ exit(EXIT_FAILURE);
+ }
+
+ *p = NULL;
+ ignoreRootDirs++;
+ if (ignoreRootDirs == numRootDirs) {
+ fprintf(stderr, "No more root paths left to monitor; bye!\n");
+ exit(EXIT_SUCCESS);
+ }
+}
+
+/***********************************************************************/
+
+/* Below is a function called by nftw() to traverse a directory tree.
+ The function adds a watch for each directory in the tree. Each
+ successful call to this function should return 0 to indicate to
+ nftw() that the tree traversal should continue. */
+
+/* The usual hack for nftw()... We can't pass arguments to the
+ function invoked by nftw(), so we use these global variables to
+ exchange information with the function. */
+
+static int dirCnt; /* Count of directories added to watch list */
+static int ifd; /* Inotify file descriptor */
+
+static int
+traverseTree(const char *pathname, const struct stat *sb, int tflag,
+ struct FTW *ftwbuf)
+{
+ int wd, slot, flags;
+
+ if (! S_ISDIR(sb->st_mode))
+ return 0; /* Ignore nondirectory files */
+
+ /* Create a watch for this directory */
+
+ flags = IN_CREATE | IN_MOVED_FROM | IN_MOVED_TO | IN_DELETE_SELF;
+
+ if (isRootDirPath(pathname))
+ flags |= IN_MOVE_SELF;
+
+ wd = inotify_add_watch(ifd, pathname, flags | IN_ONLYDIR);
+ if (wd == -1) {
+
+ /* By the time we come to create a watch, the directory might
+ already have been deleted or renamed, in which case we'll get
+ an ENOENT error. In that case, we log the error, but
+ carry on execution. Other errors are unexpected, and if we
+ hit them, we give up. */
+
+ logMessage(VB_BASIC, "inotify_add_watch: %s: %s\n",
+ pathname, strerror(errno));
+ if (errno == ENOENT)
+ return 0;
+ else
+ exit(EXIT_FAILURE);
+ }
+
+ if (findWatch(wd) >= 0) {
+
+ /* This watch descriptor is already in the cache;
+ nothing more to do. */
+
+ logMessage(VB_BASIC, "WD %d already in cache (%s)\n", wd, pathname);
+ return 0;
+ }
+
+ dirCnt++;
+
+ /* Cache information about the watch */
+
+ slot = addWatchToCache(wd, pathname);
+
+ /* Print the name of the current directory */
+
+ logMessage(VB_NOISY, " watchDir: wd = %d [cache slot: %d]; %s\n",
+ wd, slot, pathname);
+
+ return 0;
+}
+
+/* Add the directory in 'pathname' to the watch list of the inotify
+ file descriptor 'inotifyFd'. The process is recursive: watch items
+ are also created for all of the subdirectories of 'pathname'.
+ Returns number of watches/cache entries added for this subtree. */
+
+static int
+watchDir(int inotifyFd, const char *pathname)
+{
+ dirCnt = 0;
+ ifd = inotifyFd;
+
+ /* Use FTW_PHYS to avoid following soft links to directories (which
+ could lead us in circles) */
+
+ /* By the time we come to process 'pathname', it may already have
+ been deleted, so we log errors from nftw(), but keep on going */
+
+ if (nftw(pathname, traverseTree, 20, FTW_PHYS) == -1)
+ logMessage(VB_BASIC,
+ "nftw: %s: %s (directory probably deleted before we "
+ "could watch)\n", pathname, strerror(errno));
+
+ return dirCnt;
+}
+
+/* Add watches and cache entries for a subtree, logging a message
+ noting the number entries added. */
+
+static void
+watchSubtree(int inotifyFd, char *path)
+{
+ int cnt;
+
+ cnt = watchDir(inotifyFd, path);
+
+ logMessage(VB_BASIC, " watchSubtree: %s: %d entries added\n",
+ path, cnt);
+}
+
+/***********************************************************************/
+
+/* The directory oldPathPrefix/oldName was renamed to
+ newPathPrefix/newName. Fix up cache entries for
+ oldPathPrefix/oldName and all of its subdirectories
+ to reflect the change. */
+
+static void
+rewriteCachedPaths(const char *oldPathPrefix, const char *oldName,
+ const char *newPathPrefix, const char *newName)
+{
+ char fullPath[PATH_MAX], newPrefix[PATH_MAX];
+ char newPath[PATH_MAX];
+ size_t len;
+ int j;
+
+ snprintf(fullPath, sizeof(fullPath), "%s/%s", oldPathPrefix, oldName);
+ snprintf(newPrefix, sizeof(newPrefix), "%s/%s", newPathPrefix, newName);
+ len = strlen(fullPath);
+
+ logMessage(VB_BASIC, "Rename: %s ==> %s\n", fullPath, newPrefix);
+
+ for (j = 0; j < cacheSize; j++) {
+ if (strncmp(fullPath, wlCache[j].path, len) == 0 &&
+ (wlCache[j].path[len] == '/' ||
+ wlCache[j].path[len] == '\0')) {
+ snprintf(newPath, sizeof(newPath), "%s%s", newPrefix,
+ &wlCache[j].path[len]);
+
+ strncpy(wlCache[j].path, newPath, PATH_MAX);
+
+ logMessage(VB_NOISY, " wd %d [cache slot %d] ==> %s\n",
+ wlCache[j].wd, j, newPath);
+ }
+ }
+}
+
+/* Zap watches and cache entries for directory 'path' and all of its
+ subdirectories. Returns number of entries that we (tried to) zap,
+ or -1 if an inotify_rm_watch() call failed. */
+
+static int
+zapSubtree(int inotifyFd, char *path)
+{
+ size_t len;
+ int j;
+ int cnt;
+ char *pn;
+
+ logMessage(VB_NOISY, "Zapping subtree: %s\n", path);
+
+ len = strlen(path);
+
+ /* The argument we receive might be a pointer to a pathname string
+ that is actually stored in the cache. If we zap that pathname
+ part way through scanning the whole cache, then chaos results.
+ So, create a temporary copy. */
+
+ pn = strdup(path);
+
+ cnt = 0;
+
+ for (j = 0; j < cacheSize; j++) {
+ if (wlCache[j].wd >= 0) {
+ if (strncmp(pn, wlCache[j].path, len) == 0 &&
+ (wlCache[j].path[len] == '/' ||
+ wlCache[j].path[len] == '\0')) {
+
+ logMessage(VB_NOISY,
+ " removing watch: wd = %d (%s)\n",
+ wlCache[j].wd, wlCache[j].path);
+
+ if (inotify_rm_watch(inotifyFd, wlCache[j].wd) == -1) {
+ logMessage(0, "inotify_rm_watch wd = %d (%s): %s\n",
+ wlCache[j].wd, wlCache[j].path, strerror(errno));
+
+ /* When we have multiple renamers, sometimes
+ inotify_rm_watch() fails. In this case, we force a
+ cache rebuild by returning -1.
+ (TODO: Is there a better solution?) */
+
+ cnt = -1;
+ break;
+ }
+
+ markCacheSlotEmpty(j);
+ cnt++;
+ }
+ }
+ }
+
+ free(pn);
+ return cnt;
+}
+
+/* When the cache is in an unrecoverable state, we discard the current
+ inotify file descriptor ('oldInotifyFd') and create a new one (returned
+ as the function result), and zap and rebuild the cache.
+
+ If 'oldInotifyFd' is -1, this is the initial build of the cache, or an
+ explicitly requested cache rebuild, so we are a little less verbose,
+ and we reset 'reinitCnt'. */
+
+static int
+reinitialize(int oldInotifyFd)
+{
+ int inotifyFd;
+ static int reinitCnt;
+ int cnt, j;
+
+ if (oldInotifyFd >= 0) {
+ close(oldInotifyFd);
+
+ reinitCnt++;
+ logMessage(0, "Reinitializing cache and inotify FD (reinitCnt = %d)\n",
+ reinitCnt);
+
+ } else {
+ logMessage(0, "Initializing cache\n");
+ reinitCnt = 0;
+ }
+
+ inotifyFd = inotify_init();
+ if (inotifyFd == -1)
+ errExit("inotify_init");
+
+ logMessage(VB_BASIC, " new inotifyFd = %d\n", inotifyFd);
+
+ freeCache();
+
+ for (j = 0; j < numRootDirs; j++)
+ if (rootDirPaths[j] != NULL)
+ watchSubtree(inotifyFd, rootDirPaths[j]);
+
+ cnt = 0;
+ for (j = 0; j < cacheSize; j++)
+ if (wlCache[j].wd >= 0)
+ cnt++;
+
+ if (oldInotifyFd >= 0)
+ logMessage(0, "Rebuilt cache with %d entries\n", cnt);
+
+ return inotifyFd;
+}
+
+/* Process the next inotify event in the buffer specified by 'buf'
+ and 'bufSize'. In most cases, a single event is consumed, but
+ if there is an IN_MOVED_FROM+IN_MOVED_TO pair that share a cookie
+ value, both events are consumed.
+
+ Returns the number of bytes in the event(s) consumed from 'buf'. */
+
+static size_t
+processNextInotifyEvent(int *inotifyFd, char *buf, int bufSize, int firstTry)
+{
+ char fullPath[PATH_MAX + NAME_MAX];
+ struct inotify_event *ev;
+ size_t evLen;
+ int evCacheSlot;
+
+ ev = (struct inotify_event *) buf;
+
+ displayInotifyEvent(ev);
+
+ if (ev->wd != -1 && !(ev->mask & IN_IGNORED)) {
+
+ /* IN_Q_OVERFLOW has (ev->wd == -1) */
+ /* Skip IN_IGNORED, since it will come after an event
+ that has already zapped the corresponding cache entry */
+
+ /* Cache consistency check; see the discussion
+ of "intra-tree" rename() events */
+
+ evCacheSlot = findWatchChecked(ev->wd);
+ if (evCacheSlot == -1) {
+
+ /* Cache reached an inconsistent state */
+
+ *inotifyFd = reinitialize(*inotifyFd);
+
+ /* Discard all remaining events in current read() buffer */
+
+ return INOTIFY_READ_BUF_LEN;
+ }
+ }
+
+ evLen = sizeof(struct inotify_event) + ev->len;
+
+ if ((ev->mask & IN_ISDIR) &&
+ (ev->mask & (IN_CREATE | IN_MOVED_TO))) {
+
+ /* A new subdirectory was created, or a subdirectory was
+ renamed into the tree; create watches for it, and all
+ of its subdirectories. */
+
+ snprintf(fullPath, sizeof(fullPath), "%s/%s",
+ wlCache[evCacheSlot].path, ev->name);
+
+ logMessage(VB_BASIC, "Directory creation on wd %d: %s\n",
+ ev->wd, fullPath);
+
+ /* We only watch the new subtree if it has not already been cached.
+
+ This deals with a race condition:
+ * On the one hand, the following steps might occur:
+ 1. The "child" directory is created.
+ 2. The "grandchild" directory is created
+ 3. We receive an IN_CREATE event for the creation of the
+ "child" and create a watch and a cache entry for it.
+ 4. To handle the possibility that step 2 came before
+ step 3, we recursively walk through the descendants of
+ the "child" directory, adding any subdirectories to
+ the cache.
+ * On the other hand, the following steps might occur:
+ 1. The "child" directory is created.
+ 3. We receive an IN_CREATE event for the creation of the
+ "child" and create a watch and a cache entry for it.
+ 3. The "grandchild" directory is created
+ 4. During the recursive walk through the descendants of
+ the "child" directory, we cache the "grandchild" and
+ add a watch for it.
+ 5. We receive the IN_CREATE event for the creation of
+ the "grandchild". At this point, we should NOT create
+ a cache entry and watch for the "grandchild" because
+ they already exist. (Creating the watch for the
+ second time is harmless, but adding a second cache
+ for the grandchild would leave the cache in a confused
+ state.) */
+
+ if (!pathnameInCache(fullPath))
+ watchSubtree(*inotifyFd, fullPath);
+
+ } else if (ev->mask & IN_DELETE_SELF) {
+
+ /* A directory was deleted. Remove the corresponding item from
+ the cache. */
+
+ logMessage(VB_BASIC, "Clearing watchlist item %d (%s)\n",
+ ev->wd, wlCache[evCacheSlot].path);
+
+ if (isRootDirPath(wlCache[evCacheSlot].path))
+ zapRootDirPath(wlCache[evCacheSlot].path);
+
+ markCacheSlotEmpty(evCacheSlot);
+ /* No need to remove the watch; that happens automatically */
+
+ } else if ((ev->mask & (IN_MOVED_FROM | IN_ISDIR)) ==
+ (IN_MOVED_FROM | IN_ISDIR)) {
+
+ /* We have a "moved from" event. To know how to deal with it, we
+ need to determine whether there is a following "moved to"
+ event with a matching cookie value (i.e., an "intra-tree"
+ rename() where the source and destination are inside our
+ monitored trees). If there is not, then we are dealing
+ with a rename() out of our monitored tree(s).
+
+ We assume that if this is an "intra-tree" rename() event, then
+ the "moved to" event is the next event in the buffer returned
+ by the current read(). (If we are already at the last event in
+ this buffer, then we ask our caller to read a bit more, in
+ the hope of getting the following IN_MOVED_TO event in the
+ next read().)
+
+ In most cases, the assumption holds. However, where multiple
+ processes are manipulating the tree, we can can get event
+ sequences such as the following:
+
+ IN_MOVED_FROM (rename(x) by process A)
+ IN_MOVED_FROM (rename(y) by process B)
+ IN_MOVED_TO (rename(y) by process B)
+ IN_MOVED_TO (rename(x) by process A)
+
+ In principle, there may be arbitrarily complex variations on
+ the above theme. Our assumption that related IN_MOVED_FROM
+ and IN_MOVED_TO events are consecutive is broken by such
+ scenarios.
+
+ We could try to resolve this issue by extending the window
+ we use to search for IN_MOVED_TO events beyond the next item
+ in the queue. But this must be done heuristically (e.g.,
+ limiting the window to N events or to events read within
+ X milliseconds), because sometimes we will have an unmatched
+ IN_MOVED_FROM events that result from out-of-tree renames.
+ The heuristic approach is therefore unavoidably racy: there
+ is always a chance that we will fail to match up an
+ IN_MOVED_FROM+IN_MOVED_TO event pair.
+
+ So, this program takes the simple approach of assuming
+ that an IN_MOVED_FROM+IN_MOVED_TO pair occupy consecutive
+ events in the buffer returned by read().
+
+ When that assumption is wrong (and we therefore fail
+ to recognize an intra-tree rename() event), then
+ the rename will be treated as separate "moved from" and
+ "moved to" events, with the result that some watch items
+ and cache entries are zapped and re-created. This causes
+ the watch descriptors in our cache to become inconsistent
+ with the watch descriptors in as yet unread events,
+ because the watches are re-created with different watch
+ descriptor numbers.
+
+ Once such an inconsistency occurs, then, at some later point,
+ we will do a lookup for a watch descriptor returned by
+ inotify, and find that it is not in our cache. When that
+ happens, we reinitialize our cache with a fresh set of watch
+ descriptors and re-create the inotify file descriptor, in
+ order to bring our cache back into consistency with the
+ filesystem. An alternative would be to cache the cookies of
+ the (recent) IN_MOVED_FROM events for which which we did not
+ find a matching IN_MOVED_TO event, and rebuild our watch
+ cache when we find an IN_MOVED_TO event whose cookie matches
+ one of the cached cookies. Yet another approach when we
+ detect an out-of-tree rename would be to reinitialize the
+ cache and create a new inotify file descriptor.
+ (TODO: consider the fact that for a rename event, there
+ won't be other events for the object between IN_MOVED_FROM
+ and IN_MOVED_TO.)
+
+ Rebuilding the watch cache is expensive if the monitored
+ tree is large. So, there is a trade-off between how much
+ effort we want to go to to avoid cache rebuilds versus
+ how much effort we want to devote to matching up
+ IN_MOVED_FROM+IN_MOVED_TO event pairs. At the one extreme
+ we would do no search ahead for IN_MOVED_TO, with the result
+ that every rename() potentially could trigger a cache
+ rebuild. Limiting the search window to just the following
+ event is a compromise that catches the vast majority of
+ intra-tree renames and triggers relatively few cache rebuilds.
+ */
+
+ struct inotify_event *nextEv;
+
+ nextEv = (struct inotify_event *) (buf + evLen);
+
+ if (((char *) nextEv < buf + bufSize) &&
+ (nextEv->mask & IN_MOVED_TO) &&
+ (nextEv->cookie == ev->cookie)) {
+
+ int nextEvCacheSlot;
+
+ /* We have a rename() event. We need to fix up the
+ cached pathnames for the corresponding directory
+ and all of its subdirectories. */
+
+ nextEvCacheSlot = findWatchChecked(nextEv->wd);
+
+ if (nextEvCacheSlot == -1) {
+
+ /* Cache reached an inconsistent state */
+
+ *inotifyFd = reinitialize(*inotifyFd);
+
+ /* Discard all remaining events in current read() buffer */
+
+ return INOTIFY_READ_BUF_LEN;
+ }
+
+ rewriteCachedPaths(wlCache[evCacheSlot].path, ev->name,
+ wlCache[nextEvCacheSlot].path, nextEv->name);
+
+ /* We have also processed the next (IN_MOVED_TO) event,
+ so skip over it */
+
+ evLen += sizeof(struct inotify_event) + nextEv->len;
+
+ } else if (((char *) nextEv < buf + bufSize) || !firstTry) {
+
+ /* We got a "moved from" event without an accompanying
+ "moved to" event. The directory has been moved
+ outside the tree we are monitoring. We need to
+ remove the watches and zap the cache entries for
+ the moved directory and all of its subdirectories. */
+
+ logMessage(VB_NOISY, "MOVED_OUT: %p %p\n",
+ wlCache[evCacheSlot].path, ev->name);
+ logMessage(VB_NOISY, "firstTry = %d; remaining bytes = %d\n",
+ firstTry, buf + bufSize - (char *) nextEv);
+ snprintf(fullPath, sizeof(fullPath), "%s/%s",
+ wlCache[evCacheSlot].path, ev->name);
+
+ if (zapSubtree(*inotifyFd, fullPath) == -1) {
+
+ /* Cache reached an inconsistent state */
+
+ *inotifyFd = reinitialize(*inotifyFd);
+
+ /* Discard all remaining events in current read() buffer */
+
+ return INOTIFY_READ_BUF_LEN;
+ }
+
+ } else {
+ logMessage(VB_NOISY, "HANGING IN_MOVED_FROM\n");
+
+ return -1; /* Tell our caller to do another read() */
+ }
+
+ } else if (ev->mask & IN_Q_OVERFLOW) {
+
+ static int overflowCnt = 0;
+
+ overflowCnt++;
+
+ logMessage(0, "Queue overflow (%d) (inotifyReadCnt = %d)\n",
+ overflowCnt, inotifyReadCnt);
+
+ /* When the queue overflows, some events are lost, at which
+ point we've lost any chance of keeping our cache consistent
+ with the state of the filesystem. So, discard this inotify
+ file descriptor and create a new one, and zap and rebuild
+ the cache. */
+
+ *inotifyFd = reinitialize(*inotifyFd);
+
+ /* Discard all remaining events in current read() buffer */
+
+ evLen = INOTIFY_READ_BUF_LEN;
+
+ } else if (ev->mask & IN_UNMOUNT) {
+
+ /* When a filesystem is unmounted, each of the watches on the
+ is dropped, and an unmount and an ignore event are generated.
+ There's nothing left for us to monitor, so we just zap the
+ corresponding cache entry. */
+
+ logMessage(0, "Filesystem unmounted: %s\n",
+ wlCache[evCacheSlot].path);
+
+ markCacheSlotEmpty(evCacheSlot);
+ /* No need to remove the watch; that happens automatically */
+
+ } else if (ev->mask & IN_MOVE_SELF &&
+ isRootDirPath(wlCache[evCacheSlot].path)) {
+
+ /* If the root path moves to a new location in the same
+ filesystem, then all cached pathnames become invalid, and we
+ have no direct way of knowing the new name of the root path.
+ We could in theory find the new name by caching the i-node of
+ the root path on start-up and then trying to find a pathname
+ that corresponds to that i-node. Instead, we'll keep things
+ simple, and just cease monitoring it. */
+
+ logMessage(0, "Root path moved: %s\n",
+ wlCache[evCacheSlot].path);
+
+ zapRootDirPath(wlCache[evCacheSlot].path);
+
+ if (zapSubtree(*inotifyFd, wlCache[evCacheSlot].path) == -1) {
+
+ /* Cache reached an inconsistent state */
+
+ *inotifyFd = reinitialize(*inotifyFd);
+
+ /* Discard all remaining events in current read() buffer */
+
+ return INOTIFY_READ_BUF_LEN;
+ }
+ }
+
+ if (checkCache)
+ checkCacheConsistency();
+
+ if (dumpCache)
+ dumpCacheToLog();
+
+ return evLen;
+}
+
+static void
+alarmHandler(int sig)
+{
+ return; /* Just interrupt read() */
+}
+
+/* Read a block of events from the inotify file descriptor, 'inotifyFd'.
+ Process the events relating to directories in the subtree we are
+ monitoring, in order to keep our cached view of the subtree in sync
+ with the filesystem. */
+
+static void
+processInotifyEvents(int *inotifyFd)
+{
+ char buf[INOTIFY_READ_BUF_LEN]
+ __attribute__ ((aligned(__alignof__(struct inotify_event))));
+ ssize_t numRead, nr;
+ char *evp;
+ size_t cnt;
+ int evLen;
+ int firstTry;
+ int j;
+ struct sigaction sa;
+
+ /* SIGALRM handler is designed simply to interrupt read() */
+
+ sigemptyset(&sa.sa_mask);
+ sa.sa_handler = alarmHandler;
+ sa.sa_flags = 0;
+ if (sigaction(SIGALRM, &sa, NULL) == -1)
+ errExit("sigaction");
+
+ firstTry = 1;
+
+ /* Read some events from inotify file descriptor */
+
+ cnt = (readBufferSize > 0) ? readBufferSize : INOTIFY_READ_BUF_LEN;
+ numRead = read(*inotifyFd, buf, cnt);
+ if (numRead == -1)
+ errExit("read");
+ if (numRead == 0) {
+ fprintf(stderr, "read() from inotify fd returned 0!");
+ exit(EXIT_FAILURE);
+ }
+
+ inotifyReadCnt++;
+
+ logMessage(VB_NOISY,
+ "\n==========> Read %d: got %zd bytes\n",
+ inotifyReadCnt, numRead);
+
+ /* Process each event in the buffer returned by read() */
+
+ for (evp = buf; evp < buf + numRead; ) {
+ evLen = processNextInotifyEvent(inotifyFd, evp,
+ buf + numRead - evp, firstTry);
+
+ if (evLen > 0) {
+ evp += evLen;
+ firstTry = 1;
+ } else {
+
+ /* We got here because an IN_MOVED_FROM event was found at
+ the end of a previously read buffer and that event may be
+ part of an "intra-tree" rename(), meaning that we should
+ check if there is a subsequent IN_MOVED_TO event with the
+ same cookie value. We left that event unprocessed and we
+ will now try to read some more events, delaying for a
+ short time, to give the associated IN_MOVED_IN event (if
+ there is one) a chance to arrive. However, we only want
+ to do this once: if the read() below fails to gather
+ further events, then when we reprocess the IN_MOVED_FROM
+ we should treat it as though this is an out-of-tree
+ rename(). Thus, we set 'firstTry' to 0 for the next
+ processNextInotifyEvent() call. */
+
+ int savedErrno;
+
+ firstTry = 0;
+
+ numRead = buf + numRead - evp;
+
+ /* Shuffle remaining bytes to start of buffer */
+
+ for (j = 0; j < numRead; j++)
+ buf[j] = evp[j];
+
+ /* Set a timeout for read(). Some rough testing suggests
+ that a 2-millisecond timeout is sufficient to ensure
+ that, in around 99.8% of cases, we get the IN_MOVED_TO
+ event (if there is one) that matched an IN_MOVED_FROM
+ event, even in a highly dynamic directory tree. This
+ number may, of course, warrant tuning on different
+ hardware and in environments with different filesystem
+ activity levels. */
+
+ ualarm(2000, 0);
+
+ nr = read(*inotifyFd, buf + numRead,
+ INOTIFY_READ_BUF_LEN - numRead);
+
+ savedErrno = errno; /* In case ualarm() should change errno */
+ ualarm(0, 0); /* Cancel alarm */
+ errno = savedErrno;
+
+ if (nr == -1 && errno != EINTR)
+ errExit("read");
+ if (nr == 0) {
+ fprintf(stderr, "read() from inotify fd returned 0!");
+ exit(EXIT_FAILURE);
+ }
+
+ if (errno != -1) {
+ numRead += nr;
+ inotifyReadCnt++;
+
+ logMessage(VB_NOISY,
+ "\n==========> SECONDARY Read %d: got %zd bytes\n",
+ inotifyReadCnt, nr);
+
+ } else { /* EINTR */
+ logMessage(VB_NOISY,
+ "\n==========> SECONDARY Read got nothing\n");
+ }
+
+ evp = buf; /* Start again at beginning of buffer */
+ }
+ }
+}
+
+/***********************************************************************/
+
+/* We allow some simple interactive commands, mainly to check the
+ operation of the program */
+
+static void
+executeCommand(int *inotifyFd)
+{
+ const int MAX_LINE = 100;
+ ssize_t numRead;
+ char line[MAX_LINE], arg[MAX_LINE];
+ char cmd;
+ int j, cnt, ns, failures;
+ struct stat sb;
+ FILE *fp;
+
+ numRead = read(STDIN_FILENO, line, MAX_LINE);
+ if (numRead <= 0) {
+ printf("bye!\n");
+ exit(EXIT_FAILURE);
+ }
+
+ line[numRead - 1] = '\0';
+
+ if (strlen(line) == 0)
+ return;
+
+ ns = sscanf(line, "%c %s\n", &cmd, arg);
+
+ switch (cmd) {
+
+ case 'a': /* Add/refresh a subtree */
+
+ cnt = zapSubtree(*inotifyFd, arg);
+ if (cnt == 0) {
+ logMessage(VB_BASIC, "Adding new subtree: %s\n", arg);
+ } else {
+ logMessage(VB_BASIC, "Zapped: %s, %d entries\n", arg, cnt);
+ }
+
+ watchSubtree(*inotifyFd, arg);
+ break;
+
+ case 'c': /* Check that all cached pathnames exist */
+ case 'C':
+
+ cnt = 0;
+ failures = 0;
+ for (j = 0; j < cacheSize; j++) {
+ if (wlCache[j].wd >= 0) {
+ if (lstat(wlCache[j].path, &sb) == -1) {
+ if (cmd == 'c')
+ logMessage(VB_BASIC,
+ "stat: [slot = %d; wd = %d] %s: %s\n",
+ j, wlCache[j].wd, wlCache[j].path,
+ strerror(errno));
+ failures++;
+ } else if (!S_ISDIR(sb.st_mode)) {
+ if (cmd == 'c')
+ logMessage(0, "%s is not a directory\n",
+ wlCache[j].path);
+ exit(EXIT_FAILURE);
+ } else {
+ if (cmd == 'c')
+ logMessage(VB_NOISY,
+ "OK: [slot = %d; wd = %d] %s\n",
+ j, wlCache[j].wd, wlCache[j].path);
+ cnt++;
+ }
+ }
+ }
+
+ logMessage(0, "Successfully verified %d entries\n", cnt);
+ logMessage(0, "Failures: %d\n", failures);
+ break;
+
+ case 'l': /* List entries in the cache */
+
+ cnt = 0;
+
+ for (j = 0; j < cacheSize; j++) {
+ if (wlCache[j].wd >= 0) {
+ logMessage(0, "%d: %d %s\n", j, wlCache[j].wd,
+ wlCache[j].path);
+ cnt++;
+ }
+ }
+
+ logMessage(VB_BASIC, "Total entries: %d\n", cnt);
+ break;
+
+ case 'q': /* Quit */
+
+ exit(EXIT_SUCCESS);
+
+ case 'v': /* Set log verbosity level */
+
+ if (ns == 2)
+ verboseMask = atoi(arg);
+ else {
+ verboseMask = !verboseMask;
+ printf("%s\n", verboseMask ? "on" : "off");
+ }
+ break;
+
+ case 'd': /* Toggle cache dumping */
+
+ dumpCache = !dumpCache;
+ printf("%s\n", dumpCache ? "on" : "off");
+ break;
+
+ case 'x': /* Set toggle checking */
+
+ checkCache = !checkCache;
+ printf("%s\n", checkCache ? "on" : "off");
+ break;
+
+ case 'w': /* Write directory list to file */
+
+ /* We can compare the output from the below against the output
+ from "find DIR -type d" to check whether the contents of the
+ cache are consistent with the state of the filesystem */
+
+ fp = fopen(arg, "w+");
+ if (fp == NULL)
+ perror("fopen");
+
+ for (j = 0; j < cacheSize; j++)
+ if (wlCache[j].wd >= 0)
+ fprintf(fp, "%s\n", wlCache[j].path);
+
+ fclose(fp);
+ break;
+
+ case 'z': /* Stop watching a subtree, and zap its cache entries */
+
+ cnt = zapSubtree(*inotifyFd, arg);
+ logMessage(VB_BASIC, "Zapped: %s, %d entries\n", arg, cnt);
+ break;
+
+ case '0': /* Rebuild cache */
+ close(*inotifyFd);
+ *inotifyFd = reinitialize(-1);
+ break;
+
+ default:
+ printf("Unrecognized command: %c\n", cmd);
+ printf("Commands:\n");
+ printf("0 Rebuild cache\n");
+ printf("a path Add/refresh pathname watches and cache\n");
+ printf("c Verify cached pathnames\n");
+ printf("d Toggle cache dumping\n");
+ printf("l List cached pathnames\n");
+ printf("q Quit\n");
+ printf("v [n] Toggle/set verbose level for messages to stderr\n");
+ printf(" 0 = no messages\n");
+ printf(" 1 = basic messages\n");
+ printf(" 2 = verbose messages\n");
+ printf(" 3 = basic and verbose messages\n");
+ printf("w file Write directory list to file\n");
+ printf("x Toggle cache checking\n");
+ printf("z path Zap pathname and watches from cache\n");
+ break;
+ }
+}
+
+/***********************************************************************/
+
+static void
+usageError(const char *pname)
+{
+ fprintf(stderr, "Usage: %s [options] directory-path\n\n",
+ pname);
+ fprintf(stderr, " -v lvl Display logging information\n");
+ fprintf(stderr, " -l file Send logging information to a file\n");
+ fprintf(stderr, " -x Check cache consistency after each "
+ "operation\n");
+ fprintf(stderr, " -d Dump cache to log after every operation\n");
+ fprintf(stderr, " -b size Set buffer size for read() from "
+ "inotify FD\n");
+ fprintf(stderr, " -a file Abort when cache inconsistency detected, "
+ "and create 'stop' file\n");
+
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ fd_set rfds;
+ int opt;
+ int inotifyFd;
+
+ /* Parse command-line options */
+
+ verboseMask = 0;
+ checkCache = 0;
+ dumpCache = 0;
+ stopFile = NULL;
+ abortOnCacheProblem = 0;
+
+ while ((opt = getopt(argc, argv, "a:dxl:v:b:")) != -1) {
+ switch (opt) {
+
+ case 'a':
+ abortOnCacheProblem = 1;
+ stopFile = optarg;
+ break;
+
+ case 'x':
+ checkCache = 1;
+ break;
+
+ case 'd':
+ dumpCache = 1;
+ break;
+
+ case 'v':
+ verboseMask = atoi(optarg);
+ break;
+
+ case 'b':
+ readBufferSize = atoi(optarg);
+ break;
+
+ case 'l':
+ logfp = fopen(optarg, "w+");
+ if (logfp == NULL)
+ errExit("fopen");
+ setbuf(logfp, NULL);
+ break;
+
+ default:
+ usageError(argv[0]);
+ }
+ }
+
+ if (optind >= argc)
+ usageError(argv[0]);
+
+ /* Save a copy of the directories on the command line */
+
+ copyRootDirPaths(&argv[optind]);
+
+ /* Create an inotify instance and populate it with entries for
+ directory named on command line */
+
+ inotifyFd = reinitialize(-1);
+
+ /* Loop to handle inotify events and keyboard commands */
+
+ printf("%s> ", argv[0]);
+ fflush(stdout);
+
+ for (;;) {
+ FD_ZERO(&rfds);
+ FD_SET(STDIN_FILENO, &rfds);
+ FD_SET(inotifyFd, &rfds);
+ if (select(inotifyFd + 1, &rfds, NULL, NULL, NULL) == -1)
+ errExit("select");
+
+ if (FD_ISSET(STDIN_FILENO, &rfds)) {
+ executeCommand(&inotifyFd);
+
+ printf("%s> ", argv[0]);
+ fflush(stdout);
+ }
+
+ if (FD_ISSET(inotifyFd, &rfds))
+ processInotifyEvents(&inotifyFd);
+ }
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 19 */
+
+/* rand_dtree.c
+
+ A test program to use in conjunction with inotify_dtree.c.
+
+ This program randomly creates, deletes, or renames
+ subdirectories underneath the pathname specified in its
+ sole command-line argument.
+*/
+#if ! defined(_XOPEN_SOURCE) || _XOPEN_SOURCE < 700
+#undef _XOPEN_SOURCE
+#define _XOPEN_SOURCE 500
+#endif
+#include <stdarg.h>
+#include <limits.h>
+#include <ftw.h>
+#include "tlpi_hdr.h"
+
+#define DLIM 60
+
+/* Hack! We can't pass arguments to the function invoked by nftw(),
+ so we use these global variables to exchange information with
+ the function */
+
+static int dcnt;
+static char **dirList = NULL;
+static int dlSize = 0;
+static const int D_INCR = 1000;
+
+static int
+traverseTree(const char *pathname, const struct stat *sb, int tflag,
+ struct FTW *ftwbuf)
+{
+ if (! S_ISDIR(sb->st_mode))
+ return 0;
+
+ //printf("%s\n", pathname);
+
+ if (dcnt >= dlSize) {
+ dlSize += D_INCR;
+ dirList = realloc(dirList, dlSize * sizeof(char *));
+ if (dirList == NULL)
+ errExit("realloc");
+ }
+
+ dirList[dcnt] = strdup(pathname);
+
+ dcnt++;
+
+ return 0;
+}
+
+static int
+getDirList(const char *pathname)
+{
+ dcnt = 0;
+
+ if (nftw(pathname, traverseTree, 20, FTW_PHYS) == -1)
+ errMsg("nftw: %s", pathname);
+
+ return dcnt;
+}
+
+static FILE *logfp = NULL;
+
+static void
+logMessage(const char *format, ...)
+{
+ va_list argList;
+
+ va_start(argList, format);
+
+ if (logfp != NULL)
+ vfprintf(logfp, format, argList);
+
+ va_end(argList);
+}
+
+static void
+usageError(char *pname)
+{
+ fprintf(stderr, "Usage: %s [options] dirpath {c|d|m}\n\n", pname);
+ fprintf(stderr, "Perform random operations in the "
+ "directory tree 'dirpath'\n");
+ fprintf(stderr, " c == create directories\n");
+ fprintf(stderr, " d == delete directories\n");
+ fprintf(stderr, " m == rename directories\n\n");
+ fprintf(stderr, "Options:\n");
+ fprintf(stderr, " -l logfile Record activity in log file\n");
+ fprintf(stderr, " -m maxops Do at most 'maxops' operations "
+ "(default is unlimited)\n");
+ fprintf(stderr, " -s usecs Sleep 'usecs' microseconds "
+ "between each operation\n");
+ fprintf(stderr, " -z stopfile Immediately stop when the file "
+ "'stopfile' is created\n");
+ exit(EXIT_FAILURE);
+}
+
+#define MARKER_STRING "--"
+
+int
+main(int argc, char *argv[])
+{
+ int j;
+ char *to_move;
+ int opcnt, maxops;
+ char path[PATH_MAX];
+ char tfile[PATH_MAX];
+ char target[PATH_MAX];
+ char spath[PATH_MAX];
+ char *p;
+ int nslashes;
+ int opt;
+ int usecs;
+ char *stopFile;
+ int scnt;
+
+ opcnt = 0;
+
+ srandom(0);
+
+ stopFile = NULL;
+ maxops = 0;
+ usecs = 1;
+ while ((opt = getopt(argc, argv, "l:m:s:z:")) != -1) {
+ switch (opt) {
+ case 's':
+ usecs = atoi(optarg);
+ break;
+
+ case 'z':
+ stopFile = optarg;
+ break;
+
+ case 'm':
+ maxops = atoi(optarg);
+ break;
+
+ case 'l':
+ logfp = fopen(optarg, "w+");
+ if (logfp == NULL)
+ errExit("fopen");
+ setbuf(logfp, NULL);
+ break;
+
+ default:
+ usageError(argv[0]);
+ }
+ }
+
+ if (optind + 1 >= argc)
+ usageError(argv[0]);
+
+ opcnt = 0;
+
+ for (;;) {
+
+ getDirList(argv[optind]);
+
+ switch (argv[optind + 1][0]) {
+ case 'c':
+ snprintf(path, sizeof(path), "%s/%ld%s%s_%d",
+ dirList[random() % dcnt],
+ (long) getpid() % 100, MARKER_STRING, "cr", opcnt);
+ if (strlen(path) > DLIM)
+ continue;
+ nslashes = 0;
+ for (p = path; *p; p++)
+ if (*p == '/')
+ nslashes++;
+
+ if (nslashes > 1)
+ if (random() % nslashes > 0)
+ continue;
+
+ if (mkdir(path, 0700) == 0)
+ logMessage("mkdir: %s\n", path);
+
+ scnt = 1;
+ while ((random() % 3) < 2) {
+
+ snprintf(spath, sizeof(path), "%s/%ld%s%s%d_%d", path,
+ (long) getpid() % 100,
+ MARKER_STRING, "scr", scnt, opcnt);
+ if (strlen(spath) > DLIM)
+ break;
+
+ if (mkdir(spath, 0700) == 0)
+ logMessage("mkdir: %s\n", spath);
+
+ strncpy(path, spath, PATH_MAX);
+ path[PATH_MAX - 1] = '\0';
+ scnt++;
+ }
+ break;
+
+ case 'd':
+ if (dcnt == 0)
+ continue;
+
+ snprintf(path, sizeof(path), "%s", dirList[random() % dcnt]);
+ while (strstr(path, MARKER_STRING) != NULL) {
+
+ if (rmdir(path) == -1)
+ break;
+ logMessage("rmdir: %s\n", path);
+
+ p = strrchr(path, '/');
+ if (p == NULL)
+ break;
+
+ *p = '\0';
+ }
+ break;
+
+ case 'm':
+ if (dcnt < 3)
+ continue;
+
+ to_move = dirList[random() % dcnt];
+
+ if (strstr(to_move, MARKER_STRING) != NULL) {
+ p = strrchr(to_move, '/');
+ snprintf(tfile, sizeof(tfile), "%s", p + 1);
+ p = strstr(tfile, "__ren");
+ if (p != NULL)
+ *p = '\0';
+ snprintf(target, sizeof(target), "%s/%s__ren%04d-%ld",
+ dirList[random() % dcnt],
+ tfile, opcnt, (long) getpid());
+
+ if (strlen(target) > DLIM)
+ break;
+
+ if (rename(to_move, target) == 0)
+ logMessage("rename: %s ==> %s\n", to_move, target);
+ }
+
+ break;
+ }
+
+ for (j = 0; j < dcnt; j++) {
+ free(dirList[j]);
+ dirList[j] = NULL;
+ }
+
+ opcnt++;
+
+ usleep(usecs);
+
+ if (maxops > 0 && opcnt >= maxops)
+ break;
+
+ if (access(stopFile, F_OK) == 0)
+ break;
+ }
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+#!/bin/sh
+#
+# Create a new version of the file ename.c.inc by parsing symbolic
+# error names defined in errno.h
+#
+echo '#include <errno.h>' | cpp -dM |
+sed -n -e '/#define *E/s/#define *//p' |sort -k2n |
+awk '
+BEGIN {
+ entries_per_line = 4
+ line_len = 68;
+ last = 0;
+ varname =" enames";
+ print "static char *ename[] = {";
+ line = " /* 0 */ \"\"";
+}
+
+{
+ if ($2 ~ /^E[A-Z0-9]*$/) { # These entries are sorted at top
+ synonym[$1] = $2;
+ } else {
+ while (last + 1 < $2) {
+ last++;
+ line = line ", ";
+ if (length(line ename) > line_len || last == 1) {
+ print line;
+ line = " /* " last " */ ";
+ line = sprintf(" /* %3d */ ", last);
+ }
+ line = line "\"" "\"" ;
+ }
+ last = $2;
+ ename = $1;
+ for (k in synonym)
+ if (synonym[k] == $1) ename = ename "/" k;
+
+ line = line ", ";
+ if (length(line ename) > line_len || last == 1) {
+ print line;
+ line = " /* " last " */ ";
+ line = sprintf(" /* %3d */ ", last);;
+ }
+ line = line "\"" ename "\"" ;
+ }
+}
+END {
+ print line;
+ print "};"
+ print "";
+ print "#define MAX_ENAME " last;
+}
+'
+
--- /dev/null
+# Makefile to build library used by all programs
+#
+# This make file relies on the assumption that each C file in this
+# directory belongs in the library
+#
+# This makefile is very simple so that every version of make
+# should be able to handle it
+#
+include ../Makefile.inc
+
+# The library build is "brute force" -- we don't bother with
+# dependency checking.
+
+allgen : ${TLPI_LIB}
+
+${TLPI_LIB} : *.c ename.c.inc
+ ${CC} -c -g ${CFLAGS} *.c
+ ${RM} ${TLPI_LIB}
+ ${AR} rs ${TLPI_LIB} *.o
+
+ename.c.inc :
+ sh Build_ename.sh > ename.c.inc
+ echo 1>&2 "ename.c.inc built"
+
+clean :
+ ${RM} *.o ename.c.inc ${TLPI_LIB}
+
--- /dev/null
+A small design note... Many of the library functions defined in the
+source code modules in this directory handle errors from system calls
+and C library functions by simply terminating the process. This
+isn't acceptable design for a "real world" suite of library functions;
+I did things this way to keep the source code simpler and shorter.
+A properly designed function should indicate an error to its caller
+using a status argument or some special function return value.
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 3 */
+
+/* ALT_functions.c
+
+ Some library functions on Linux are not available on other UNIX
+ implementations. Below are some implementations (in some cases quite
+ minimal) of those functions used by our programs.
+
+ Each of these functions has a name of the form ALT_xxx() where xxx() is the
+ function being replaced. (#defines are used elsewhere to convert the
+ standard names into these alternate forms.)
+*/
+#include <stdio.h>
+#include <fcntl.h>
+#include "alt_functions.h"
+
+/* A very minimal implementation of strsignal()... */
+
+#define BUF_SIZE 100
+
+char *
+ALT_strsignal(int sig)
+{
+ static char buf[BUF_SIZE]; /* Not thread-safe */
+
+ snprintf(buf, BUF_SIZE, "SIG-%d", sig);
+ return buf;
+}
+
+/* A very minimal implementation of hstrerror()... */
+
+char *
+ALT_hstrerror(int err)
+{
+ static char buf[BUF_SIZE]; /* Not thread-safe */
+
+ snprintf(buf, BUF_SIZE, "hstrerror-%d", err);
+ return buf;
+}
+
+/* posix_openpt() is simple to implement */
+
+int
+ALT_posix_openpt(int flags)
+{
+ return open("/dev/ptmx", flags);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 3 */
+
+/* alt_functions.h
+
+ Header file for alt_functions.c.
+*/
+#ifndef ALT_FUNCTIONS_H
+#define ALT_FUNCTIONS_H /* Prevent accidental double inclusion */
+
+#if defined(__osf__) || defined(__hpux) || defined(_AIX) || \
+ defined(__sgi) || defined(__APPLE__)
+#define strsignal(sig) ALT_strsignal(sig)
+#endif
+char *ALT_strsignal(int sig);
+
+#if defined(__hpux) || defined(__osf__)
+#define hstrerror(err) ALT_hstrerror(err)
+#endif
+char *ALT_hstrerror(int sig);
+
+#if defined(__hpux) || defined(__osf__)
+#define posix_openpt(flags) ALT_posix_openpt(flags)
+#endif
+int ALT_posix_openpt(int flags);
+
+#endif
--- /dev/null
+../daemons/become_daemon.c
\ No newline at end of file
--- /dev/null
+../daemons/become_daemon.h
\ No newline at end of file
--- /dev/null
+../svsem/binary_sems.c
\ No newline at end of file
--- /dev/null
+../svsem/binary_sems.h
\ No newline at end of file
--- /dev/null
+../filelock/create_pid_file.c
\ No newline at end of file
--- /dev/null
+../filelock/create_pid_file.h
\ No newline at end of file
--- /dev/null
+../time/curr_time.c
\ No newline at end of file
--- /dev/null
+../time/curr_time.h
\ No newline at end of file
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 3-3 */
+
+/* error_functions.c
+
+ Some standard error handling routines used by various programs.
+*/
+#include <stdarg.h>
+#include "error_functions.h"
+#include "tlpi_hdr.h"
+#include "ename.c.inc" /* Defines ename and MAX_ENAME */
+
+#ifdef __GNUC__ /* Prevent 'gcc -Wall' complaining */
+__attribute__ ((__noreturn__)) /* if we call this function as last */
+#endif /* statement in a non-void function */
+static void
+terminate(Boolean useExit3)
+{
+ char *s;
+
+ /* Dump core if EF_DUMPCORE environment variable is defined and
+ is a nonempty string; otherwise call exit(3) or _exit(2),
+ depending on the value of 'useExit3'. */
+
+ s = getenv("EF_DUMPCORE");
+
+ if (s != NULL && *s != '\0')
+ abort();
+ else if (useExit3)
+ exit(EXIT_FAILURE);
+ else
+ _exit(EXIT_FAILURE);
+}
+
+/* Diagnose 'errno' error by:
+
+ * outputting a string containing the error name (if available
+ in 'ename' array) corresponding to the value in 'err', along
+ with the corresponding error message from strerror(), and
+
+ * outputting the caller-supplied error message specified in
+ 'format' and 'ap'. */
+
+static void
+outputError(Boolean useErr, int err, Boolean flushStdout,
+ const char *format, va_list ap)
+{
+#define BUF_SIZE 500
+ char buf[BUF_SIZE], userMsg[BUF_SIZE], errText[BUF_SIZE];
+
+ vsnprintf(userMsg, BUF_SIZE, format, ap);
+
+ if (useErr)
+ snprintf(errText, BUF_SIZE, " [%s %s]",
+ (err > 0 && err <= MAX_ENAME) ?
+ ename[err] : "?UNKNOWN?", strerror(err));
+ else
+ snprintf(errText, BUF_SIZE, ":");
+
+ snprintf(buf, BUF_SIZE, "ERROR%s %s\n", errText, userMsg);
+
+ if (flushStdout)
+ fflush(stdout); /* Flush any pending stdout */
+ fputs(buf, stderr);
+ fflush(stderr); /* In case stderr is not line-buffered */
+}
+
+/* Display error message including 'errno' diagnostic, and
+ return to caller */
+
+void
+errMsg(const char *format, ...)
+{
+ va_list argList;
+ int savedErrno;
+
+ savedErrno = errno; /* In case we change it here */
+
+ va_start(argList, format);
+ outputError(TRUE, errno, TRUE, format, argList);
+ va_end(argList);
+
+ errno = savedErrno;
+}
+
+/* Display error message including 'errno' diagnostic, and
+ terminate the process */
+
+void
+errExit(const char *format, ...)
+{
+ va_list argList;
+
+ va_start(argList, format);
+ outputError(TRUE, errno, TRUE, format, argList);
+ va_end(argList);
+
+ terminate(TRUE);
+}
+
+/* Display error message including 'errno' diagnostic, and
+ terminate the process by calling _exit().
+
+ The relationship between this function and errExit() is analogous
+ to that between _exit(2) and exit(3): unlike errExit(), this
+ function does not flush stdout and calls _exit(2) to terminate the
+ process (rather than exit(3), which would cause exit handlers to be
+ invoked).
+
+ These differences make this function especially useful in a library
+ function that creates a child process that must then terminate
+ because of an error: the child must terminate without flushing
+ stdio buffers that were partially filled by the caller and without
+ invoking exit handlers that were established by the caller. */
+
+void
+err_exit(const char *format, ...)
+{
+ va_list argList;
+
+ va_start(argList, format);
+ outputError(TRUE, errno, FALSE, format, argList);
+ va_end(argList);
+
+ terminate(FALSE);
+}
+
+/* The following function does the same as errExit(), but expects
+ the error number in 'errnum' */
+
+void
+errExitEN(int errnum, const char *format, ...)
+{
+ va_list argList;
+
+ va_start(argList, format);
+ outputError(TRUE, errnum, TRUE, format, argList);
+ va_end(argList);
+
+ terminate(TRUE);
+}
+
+/* Print an error message (without an 'errno' diagnostic) */
+
+void
+fatal(const char *format, ...)
+{
+ va_list argList;
+
+ va_start(argList, format);
+ outputError(FALSE, 0, TRUE, format, argList);
+ va_end(argList);
+
+ terminate(TRUE);
+}
+
+/* Print a command usage error message and terminate the process */
+
+void
+usageErr(const char *format, ...)
+{
+ va_list argList;
+
+ fflush(stdout); /* Flush any pending stdout */
+
+ fprintf(stderr, "Usage: ");
+ va_start(argList, format);
+ vfprintf(stderr, format, argList);
+ va_end(argList);
+
+ fflush(stderr); /* In case stderr is not line-buffered */
+ exit(EXIT_FAILURE);
+}
+
+/* Diagnose an error in command-line arguments and
+ terminate the process */
+
+void
+cmdLineErr(const char *format, ...)
+{
+ va_list argList;
+
+ fflush(stdout); /* Flush any pending stdout */
+
+ fprintf(stderr, "Command-line usage error: ");
+ va_start(argList, format);
+ vfprintf(stderr, format, argList);
+ va_end(argList);
+
+ fflush(stderr); /* In case stderr is not line-buffered */
+ exit(EXIT_FAILURE);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 3-2 */
+
+/* error_functions.h
+
+ Header file for error_functions.c.
+*/
+#ifndef ERROR_FUNCTIONS_H
+#define ERROR_FUNCTIONS_H
+
+/* Error diagnostic routines */
+
+void errMsg(const char *format, ...);
+
+#ifdef __GNUC__
+
+ /* This macro stops 'gcc -Wall' complaining that "control reaches
+ end of non-void function" if we use the following functions to
+ terminate main() or some other non-void function. */
+
+#define NORETURN __attribute__ ((__noreturn__))
+#else
+#define NORETURN
+#endif
+
+void errExit(const char *format, ...) NORETURN ;
+
+void err_exit(const char *format, ...) NORETURN ;
+
+void errExitEN(int errnum, const char *format, ...) NORETURN ;
+
+void fatal(const char *format, ...) NORETURN ;
+
+void usageErr(const char *format, ...) NORETURN ;
+
+void cmdLineErr(const char *format, ...) NORETURN ;
+
+#endif
--- /dev/null
+../svsem/event_flags.c
\ No newline at end of file
--- /dev/null
+../svsem/event_flags.h
\ No newline at end of file
--- /dev/null
+../files/file_perms.c
\ No newline at end of file
--- /dev/null
+../files/file_perms.h
\ No newline at end of file
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 3-6 */
+
+/* get_num.c
+
+ Functions to process numeric command-line arguments.
+*/
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <errno.h>
+#include "get_num.h"
+
+/* Print a diagnostic message that contains a function name ('fname'),
+ the value of a command-line argument ('arg'), the name of that
+ command-line argument ('name'), and a diagnostic error message ('msg'). */
+
+static void
+gnFail(const char *fname, const char *msg, const char *arg, const char *name)
+{
+ fprintf(stderr, "%s error", fname);
+ if (name != NULL)
+ fprintf(stderr, " (in %s)", name);
+ fprintf(stderr, ": %s\n", msg);
+ if (arg != NULL && *arg != '\0')
+ fprintf(stderr, " offending text: %s\n", arg);
+
+ exit(EXIT_FAILURE);
+}
+
+/* Convert a numeric command-line argument ('arg') into a long integer,
+ returned as the function result. 'flags' is a bit mask of flags controlling
+ how the conversion is done and what diagnostic checks are performed on the
+ numeric result; see get_num.h for details.
+
+ 'fname' is the name of our caller, and 'name' is the name associated with
+ the command-line argument 'arg'. 'fname' and 'name' are used to print a
+ diagnostic message in case an error is detected when processing 'arg'. */
+
+static long
+getNum(const char *fname, const char *arg, int flags, const char *name)
+{
+ long res;
+ char *endptr;
+ int base;
+
+ if (arg == NULL || *arg == '\0')
+ gnFail(fname, "null or empty string", arg, name);
+
+ base = (flags & GN_ANY_BASE) ? 0 : (flags & GN_BASE_8) ? 8 :
+ (flags & GN_BASE_16) ? 16 : 10;
+
+ errno = 0;
+ res = strtol(arg, &endptr, base);
+ if (errno != 0)
+ gnFail(fname, "strtol() failed", arg, name);
+
+ if (*endptr != '\0')
+ gnFail(fname, "nonnumeric characters", arg, name);
+
+ if ((flags & GN_NONNEG) && res < 0)
+ gnFail(fname, "negative value not allowed", arg, name);
+
+ if ((flags & GN_GT_0) && res <= 0)
+ gnFail(fname, "value must be > 0", arg, name);
+
+ return res;
+}
+
+/* Convert a numeric command-line argument string to a long integer. See the
+ comments for getNum() for a description of the arguments to this function. */
+
+long
+getLong(const char *arg, int flags, const char *name)
+{
+ return getNum("getLong", arg, flags, name);
+}
+
+/* Convert a numeric command-line argument string to an integer. See the
+ comments for getNum() for a description of the arguments to this function. */
+
+int
+getInt(const char *arg, int flags, const char *name)
+{
+ long res;
+
+ res = getNum("getInt", arg, flags, name);
+
+ if (res > INT_MAX || res < INT_MIN)
+ gnFail("getInt", "integer out of range", arg, name);
+
+ return (int) res;
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 3-5 */
+
+/* get_num.h
+
+ Header file for get_num.c.
+*/
+#ifndef GET_NUM_H
+#define GET_NUM_H
+
+#define GN_NONNEG 01 /* Value must be >= 0 */
+#define GN_GT_0 02 /* Value must be > 0 */
+
+ /* By default, integers are decimal */
+#define GN_ANY_BASE 0100 /* Can use any base - like strtol(3) */
+#define GN_BASE_8 0200 /* Value is expressed in octal */
+#define GN_BASE_16 0400 /* Value is expressed in hexadecimal */
+
+long getLong(const char *arg, int flags, const char *name);
+
+int getInt(const char *arg, int flags, const char *name);
+
+#endif
--- /dev/null
+../sockets/inet_sockets.c
\ No newline at end of file
--- /dev/null
+../sockets/inet_sockets.h
\ No newline at end of file
--- /dev/null
+../timers/itimerspec_from_str.c
\ No newline at end of file
--- /dev/null
+../timers/itimerspec_from_str.h
\ No newline at end of file
--- /dev/null
+../procres/print_rlimit.c
\ No newline at end of file
--- /dev/null
+../procres/print_rlimit.h
\ No newline at end of file
--- /dev/null
+../procres/print_rusage.c
\ No newline at end of file
--- /dev/null
+../procres/print_rusage.h
\ No newline at end of file
--- /dev/null
+../procexec/print_wait_status.c
\ No newline at end of file
--- /dev/null
+../procexec/print_wait_status.h
\ No newline at end of file
--- /dev/null
+../pty/pty_fork.c
\ No newline at end of file
--- /dev/null
+../pty/pty_fork.h
\ No newline at end of file
--- /dev/null
+../pty/pty_master_open.c
\ No newline at end of file
--- /dev/null
+../pty/pty_master_open.h
\ No newline at end of file
--- /dev/null
+../sockets/rdwrn.c
\ No newline at end of file
--- /dev/null
+../sockets/rdwrn.h
\ No newline at end of file
--- /dev/null
+../sockets/read_line.c
\ No newline at end of file
--- /dev/null
+../sockets/read_line.h
\ No newline at end of file
--- /dev/null
+../sockets/read_line_buf.c
\ No newline at end of file
--- /dev/null
+../sockets/read_line_buf.h
\ No newline at end of file
--- /dev/null
+../filelock/region_locking.c
\ No newline at end of file
--- /dev/null
+../filelock/region_locking.h
\ No newline at end of file
--- /dev/null
+../svsem/semun.h
\ No newline at end of file
--- /dev/null
+../signals/signal.c
\ No newline at end of file
--- /dev/null
+../signals/signal_functions.c
\ No newline at end of file
--- /dev/null
+../signals/signal_functions.h
\ No newline at end of file
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 3-1 */
+
+/* tlpi_hdr.h
+
+ Standard header file used by nearly all of our example programs.
+*/
+#ifndef TLPI_HDR_H
+#define TLPI_HDR_H /* Prevent accidental double inclusion */
+
+#include <sys/types.h> /* Type definitions used by many programs */
+#include <stdio.h> /* Standard I/O functions */
+#include <stdlib.h> /* Prototypes of commonly used library functions,
+ plus EXIT_SUCCESS and EXIT_FAILURE constants */
+#include <unistd.h> /* Prototypes for many system calls */
+#include <errno.h> /* Declares errno and defines error constants */
+#include <string.h> /* Commonly used string-handling functions */
+
+#include "get_num.h" /* Declares our functions for handling numeric
+ arguments (getInt(), getLong()) */
+
+#include "error_functions.h" /* Declares our error-handling functions */
+
+/* Unfortunately some UNIX implementations define FALSE and TRUE -
+ here we'll undefine them */
+
+#ifdef TRUE
+#undef TRUE
+#endif
+
+#ifdef FALSE
+#undef FALSE
+#endif
+
+typedef enum { FALSE, TRUE } Boolean;
+
+#define min(m,n) ((m) < (n) ? (m) : (n))
+#define max(m,n) ((m) > (n) ? (m) : (n))
+
+/* Some systems don't define 'socklen_t' */
+
+#if defined(__sgi)
+typedef int socklen_t;
+#endif
+
+#if defined(__sun)
+#include <sys/file.h> /* Has definition of FASYNC */
+#endif
+
+#if ! defined(O_ASYNC) && defined(FASYNC)
+/* Some systems define FASYNC instead of O_ASYNC */
+#define O_ASYNC FASYNC
+#endif
+
+#if defined(MAP_ANON) && ! defined(MAP_ANONYMOUS)
+/* BSD derivatives usually have MAP_ANON, not MAP_ANONYMOUS */
+#define MAP_ANONYMOUS MAP_ANON
+
+#endif
+
+#if ! defined(O_SYNC) && defined(O_FSYNC)
+/* Some implementations have O_FSYNC instead of O_SYNC */
+#define O_SYNC O_FSYNC
+#endif
+
+#if defined(__FreeBSD__)
+
+/* FreeBSD uses these alternate names for fields in the sigval structure */
+
+#define sival_int sigval_int
+#define sival_ptr sigval_ptr
+#endif
+
+#endif
--- /dev/null
+../tty/tty_functions.c
\ No newline at end of file
--- /dev/null
+../tty/tty_functions.h
\ No newline at end of file
--- /dev/null
+../users_groups/ugid_functions.c
\ No newline at end of file
--- /dev/null
+../users_groups/ugid_functions.h
\ No newline at end of file
--- /dev/null
+../sockets/unix_sockets.c
\ No newline at end of file
--- /dev/null
+../sockets/unix_sockets.h
\ No newline at end of file
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE =
+
+LINUX_EXE = dump_utmpx utmpx_login view_lastlog
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+clean :
+ ${RM} ${EXE} *.o
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 40-2 */
+
+/* dump_utmpx.c
+
+ Display the contents of the utmp-style file named on the command line.
+
+ This version of the program differs from that which appears in the book in
+ that it prints extra information from each utmpx record.
+
+ This program is Linux-specific.
+*/
+#define _GNU_SOURCE
+#include <time.h>
+#include <utmpx.h>
+#include <paths.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ struct utmpx *ut;
+ struct in_addr in;
+
+ if (argc > 1 && strcmp(argv[1], "--help") == 0)
+ usageErr("%s [utmp-pathname]\n", argv[0]);
+
+ if (argc > 1) /* Use alternate file if supplied */
+ if (utmpxname(argv[1]) == -1)
+ errExit("utmpxname");
+
+ setutxent();
+
+ printf("user type PID line id host ");
+ printf("term exit session address date/time\n");
+
+ while ((ut = getutxent()) != NULL) { /* Sequential scan to EOF */
+ printf("%-8s ", ut->ut_user);
+ printf("%-9.9s ",
+ (ut->ut_type == EMPTY) ? "EMPTY" :
+ (ut->ut_type == RUN_LVL) ? "RUN_LVL" :
+ (ut->ut_type == BOOT_TIME) ? "BOOT_TIME" :
+ (ut->ut_type == NEW_TIME) ? "NEW_TIME" :
+ (ut->ut_type == OLD_TIME) ? "OLD_TIME" :
+ (ut->ut_type == INIT_PROCESS) ? "INIT_PR" :
+ (ut->ut_type == LOGIN_PROCESS) ? "LOGIN_PR" :
+ (ut->ut_type == USER_PROCESS) ? "USER_PR" :
+ (ut->ut_type == DEAD_PROCESS) ? "DEAD_PR" : "???");
+ printf("(%1d) ", ut->ut_type);
+ printf("%5ld %-6.6s %-3.5s %-9.9s ", (long) ut->ut_pid,
+ ut->ut_line, ut->ut_id, ut->ut_host);
+ printf("%3d %3d ", ut->ut_exit.e_termination, ut->ut_exit.e_exit);
+ printf("%8ld ", (long) ut->ut_session);
+
+ /* Display IPv4 address */
+
+ in.s_addr = ut->ut_addr_v6[0];
+ printf(" %-15.15s ", inet_ntoa(in));
+ time_t tv_sec = ut->ut_tv.tv_sec;
+ printf("%s", ctime((time_t *) &tv_sec));
+ }
+
+ endutxent();
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 40-3 */
+
+/* utmpx_login.c
+
+ Demonstrate the steps required to update the utmp and wtmp files on user
+ login and logout.
+
+ Note: updating utmp and wtmp (normally) requires root privileges.
+
+ This program is Linux-specific.
+*/
+#define _GNU_SOURCE
+#include <time.h>
+#include <utmpx.h>
+#include <paths.h> /* Definitions of _PATH_UTMP and _PATH_WTMP */
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ struct utmpx ut;
+ char *devName;
+
+ if (argc < 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s username [sleep-time]\n", argv[0]);
+
+ /* Initialize login record for utmp and wtmp files */
+
+ memset(&ut, 0, sizeof(struct utmpx));
+ ut.ut_type = USER_PROCESS; /* This is a user login */
+ strncpy(ut.ut_user, argv[1], sizeof(ut.ut_user));
+ if (time((time_t *) &ut.ut_tv.tv_sec) == -1)
+ errExit("time"); /* Stamp with current time */
+ ut.ut_pid = getpid();
+
+ /* Set ut_line and ut_id based on the terminal associated with
+ 'stdin'. This code assumes terminals named "/dev/[pt]t[sy]*".
+ The "/dev/" dirname is 5 characters; the "[pt]t[sy]" filename
+ prefix is 3 characters (making 8 characters in all). */
+
+ devName = ttyname(STDIN_FILENO);
+ if (devName == NULL)
+ errExit("ttyname");
+ if (strlen(devName) <= 8) /* Should never happen */
+ fatal("Terminal name is too short: %s", devName);
+
+ strncpy(ut.ut_line, devName + 5, sizeof(ut.ut_line));
+ strncpy(ut.ut_id, devName + 8, sizeof(ut.ut_id));
+
+ printf("Creating login entries in utmp and wtmp\n");
+ printf(" using pid %ld, line %.*s, id %.*s\n",
+ (long) ut.ut_pid, (int) sizeof(ut.ut_line), ut.ut_line,
+ (int) sizeof(ut.ut_id), ut.ut_id);
+
+ setutxent(); /* Rewind to start of utmp file */
+ if (pututxline(&ut) == NULL) /* Write login record to utmp */
+ errExit("pututxline");
+ updwtmpx(_PATH_WTMP, &ut); /* Append login record to wtmp */
+
+ /* Sleep a while, so we can examine utmp and wtmp files */
+
+ sleep((argc > 2) ? getInt(argv[2], GN_NONNEG, "sleep-time") : 15);
+
+ /* Now do a "logout"; use values from previously initialized 'ut',
+ except for changes below */
+
+ ut.ut_type = DEAD_PROCESS; /* Required for logout record */
+ time((time_t *) &ut.ut_tv.tv_sec); /* Stamp with logout time */
+ memset(&ut.ut_user, 0, sizeof(ut.ut_user));
+ /* Logout record has null username */
+
+ printf("Creating logout entries in utmp and wtmp\n");
+ setutxent(); /* Rewind to start of utmp file */
+ if (pututxline(&ut) == NULL) /* Overwrite previous utmp record */
+ errExit("pututxline");
+ updwtmpx(_PATH_WTMP, &ut); /* Append logout record to wtmp */
+
+ endutxent();
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 40-4 */
+
+/* view_lastlog.c
+
+ Display lastlogin entries for users named on command line.
+
+ This program is Linux-specific.
+*/
+#include <time.h>
+#include <lastlog.h>
+#include <paths.h> /* Definition of _PATH_LASTLOG */
+#include <fcntl.h>
+#include "ugid_functions.h" /* Declaration of userIdFromName() */
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ struct lastlog llog;
+ int fd, j;
+ uid_t uid;
+
+ if (argc > 1 && strcmp(argv[1], "--help") == 0)
+ usageErr("%s [username...]\n", argv[0]);
+
+ fd = open(_PATH_LASTLOG, O_RDONLY);
+ if (fd == -1)
+ errExit("open");
+
+ for (j = 1; j < argc; j++) {
+ uid = userIdFromName(argv[j]);
+ if (uid == -1) {
+ printf("No such user: %s\n", argv[j]);
+ continue;
+ }
+
+ if (lseek(fd, uid * sizeof(struct lastlog), SEEK_SET) == -1)
+ errExit("lseek");
+
+ if (read(fd, &llog, sizeof(struct lastlog)) <= 0) {
+ printf("read failed for %s\n", argv[j]); /* EOF or error */
+ continue;
+ }
+
+ time_t ll_time = llog.ll_time;
+ printf("%-8.8s %-6.6s %-20.20s %s", argv[j], llog.ll_line,
+ llog.ll_host, ctime((time_t *) &ll_time));
+ }
+
+ close(fd);
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE = free_and_sbrk
+
+LINUX_EXE =
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+clean :
+ ${RM} ${EXE} *.o
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 7-1 */
+
+/* free_and_sbrk.c
+
+ Test if free(3) actually lowers the program break.
+
+ Usage: free_and_sbrk num-allocs block-size [step [min [max]]]
+
+ Try: free_and_sbrk 1000 10240 2 1 1000
+ free_and_sbrk 1000 10240 1 1 999
+ free_and_sbrk 1000 10240 1 500 1000
+
+ (Only the last of these should see the program break lowered.)
+*/
+#define _BSD_SOURCE
+#include "tlpi_hdr.h"
+
+#define MAX_ALLOCS 1000000
+
+int
+main(int argc, char *argv[])
+{
+ char *ptr[MAX_ALLOCS];
+ int freeStep, freeMin, freeMax, blockSize, numAllocs, j;
+
+ printf("\n");
+
+ if (argc < 3 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s num-allocs block-size [step [min [max]]]\n", argv[0]);
+
+ numAllocs = getInt(argv[1], GN_GT_0, "num-allocs");
+ if (numAllocs > MAX_ALLOCS)
+ cmdLineErr("num-allocs > %d\n", MAX_ALLOCS);
+
+ blockSize = getInt(argv[2], GN_GT_0 | GN_ANY_BASE, "block-size");
+
+ freeStep = (argc > 3) ? getInt(argv[3], GN_GT_0, "step") : 1;
+ freeMin = (argc > 4) ? getInt(argv[4], GN_GT_0, "min") : 1;
+ freeMax = (argc > 5) ? getInt(argv[5], GN_GT_0, "max") : numAllocs;
+
+ if (freeMax > numAllocs)
+ cmdLineErr("free-max > num-allocs\n");
+
+ printf("Initial program break: %10p\n", sbrk(0));
+
+ printf("Allocating %d*%d bytes\n", numAllocs, blockSize);
+ for (j = 0; j < numAllocs; j++) {
+ ptr[j] = malloc(blockSize);
+ if (ptr[j] == NULL)
+ errExit("malloc");
+ }
+
+ printf("Program break is now: %10p\n", sbrk(0));
+
+ printf("Freeing blocks from %d to %d in steps of %d\n",
+ freeMin, freeMax, freeStep);
+ for (j = freeMin - 1; j < freeMax; j += freeStep)
+ free(ptr[j]);
+
+ printf("After free(), program break is: %10p\n", sbrk(0));
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE = anon_mmap mmcat mmcopy t_mmap
+
+LINUX_EXE = t_remap_file_pages
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+clean :
+ ${RM} ${EXE} *.o
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 49-3 */
+
+/* anon_mmap.c
+
+ Demonstrate how to share a region of mapped memory between a parent and
+ child process without having to create a mapped file, either through the
+ creation of an anonymous memory mapping or through the mapping of /dev/zero.
+*/
+#ifdef USE_MAP_ANON
+#define _BSD_SOURCE /* Get MAP_ANONYMOUS definition */
+#endif
+#include <sys/wait.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int *addr; /* Pointer to shared memory region */
+
+ /* Parent creates mapped region prior to calling fork() */
+
+#ifdef USE_MAP_ANON /* Use MAP_ANONYMOUS */
+ addr = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_ANONYMOUS, -1, 0);
+ if (addr == MAP_FAILED)
+ errExit("mmap");
+
+#else /* Map /dev/zero */
+ int fd;
+
+ fd = open("/dev/zero", O_RDWR);
+ if (fd == -1)
+ errExit("open");
+
+ addr = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (addr == MAP_FAILED)
+ errExit("mmap");
+
+ if (close(fd) == -1) /* No longer needed */
+ errExit("close");
+#endif
+
+ *addr = 1; /* Initialize integer in mapped region */
+
+ switch (fork()) { /* Parent and child share mapping */
+ case -1:
+ errExit("fork");
+
+ case 0: /* Child: increment shared integer and exit */
+ printf("Child started, value = %d\n", *addr);
+ (*addr)++;
+ if (munmap(addr, sizeof(int)) == -1)
+ errExit("munmap");
+ exit(EXIT_SUCCESS);
+
+ default: /* Parent: wait for child to terminate */
+ if (wait(NULL) == -1)
+ errExit("wait");
+ printf("In parent, value = %d\n", *addr);
+ if (munmap(addr, sizeof(int)) == -1)
+ errExit("munmap");
+ exit(EXIT_SUCCESS);
+ }
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 49-1 */
+
+/* mmcat.c
+
+ Use mmap() plus write() to display the contents of a file (specified
+ as a command-line argument) on standard output.
+*/
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ char *addr;
+ int fd;
+ struct stat sb;
+
+ if (argc != 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s file\n", argv[0]);
+
+ fd = open(argv[1], O_RDONLY);
+ if (fd == -1)
+ errExit("open");
+
+ /* Obtain the size of the file and use it to specify the size of
+ the mapping and the size of the buffer to be written */
+
+ if (fstat(fd, &sb) == -1)
+ errExit("fstat");
+
+ /* Handle zero-length file specially, since specifying a size of
+ zero to mmap() will fail with the error EINVAL */
+
+ if (sb.st_size == 0)
+ exit(EXIT_SUCCESS);
+
+ addr = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ if (addr == MAP_FAILED)
+ errExit("mmap");
+
+ if (write(STDOUT_FILENO, addr, sb.st_size) != sb.st_size)
+ fatal("partial/failed write");
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 49-1 */
+
+/* mmcopy.c
+
+ Copy the contents of one file to another file, using memory mappings.
+
+ Usage mmcopy source-file dest-file
+*/
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ char *src, *dst;
+ int fdSrc, fdDst;
+ struct stat sb;
+
+ if (argc != 3)
+ usageErr("%s source-file dest-file\n", argv[0]);
+
+ fdSrc = open(argv[1], O_RDONLY);
+ if (fdSrc == -1)
+ errExit("open");
+
+ /* Use fstat() to obtain size of file: we use this to specify the
+ size of the two mappings */
+
+ if (fstat(fdSrc, &sb) == -1)
+ errExit("fstat");
+
+ /* Handle zero-length file specially, since specifying a size of
+ zero to mmap() will fail with the error EINVAL */
+
+ if (sb.st_size == 0)
+ exit(EXIT_SUCCESS);
+
+ src = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fdSrc, 0);
+ if (src == MAP_FAILED)
+ errExit("mmap");
+
+ fdDst = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+ if (fdDst == -1)
+ errExit("open");
+
+ if (ftruncate(fdDst, sb.st_size) == -1)
+ errExit("ftruncate");
+
+ dst = mmap(NULL, sb.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fdDst, 0);
+ if (dst == MAP_FAILED)
+ errExit("mmap");
+
+ memcpy(dst, src, sb.st_size); /* Copy bytes between mappings */
+ if (msync(dst, sb.st_size, MS_SYNC) == -1)
+ errExit("msync");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 49-2 */
+
+/* t_mmap.c
+
+ Demonstrate the use of mmap() to create a shared file mapping.
+*/
+#include <sys/mman.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+#define MEM_SIZE 10
+
+int
+main(int argc, char *argv[])
+{
+ char *addr;
+ int fd;
+
+ if (argc < 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s file [new-value]\n", argv[0]);
+
+ fd = open(argv[1], O_RDWR);
+ if (fd == -1)
+ errExit("open");
+
+ addr = mmap(NULL, MEM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (addr == MAP_FAILED)
+ errExit("mmap");
+
+ if (close(fd) == -1) /* No longer need 'fd' */
+ errExit("close");
+
+ printf("Current string=%.*s\n", MEM_SIZE, addr);
+ /* Secure practice: output at most MEM_SIZE bytes */
+
+ if (argc > 2) { /* Update contents of region */
+ if (strlen(argv[2]) >= MEM_SIZE)
+ cmdLineErr("'new-value' too large\n");
+
+ memset(addr, 0, MEM_SIZE); /* Zero out region */
+ strncpy(addr, argv[2], MEM_SIZE - 1);
+ if (msync(addr, MEM_SIZE, MS_SYNC) == -1)
+ errExit("msync");
+
+ printf("Copied \"%s\" to shared memory\n", argv[2]);
+ }
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 49 */
+
+/* t_remap_file_pages.c
+
+ Demonstrate the use of the Linux remap_file_pages() system call to create
+ a nonlinear mapping.
+
+ This program is Linux-specific. The remap_file_pages() system call is
+ supported since kernel 2.6.
+*/
+#define _GNU_SOURCE /* Get remap_file_pages() declaration
+ from <sys/mman.h> */
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int fd, j;
+ char ch;
+ long pageSize;
+ char *addr;
+
+ fd = open("/tmp/tfile", O_CREAT | O_TRUNC | O_RDWR, S_IRUSR | S_IWUSR);
+ if (fd == -1)
+ errExit("open");
+
+ pageSize = sysconf(_SC_PAGESIZE);
+ if (pageSize == -1)
+ fatal("Couldn't determine page size");
+
+ for (ch = 'a'; ch < 'd'; ch++)
+ for (j = 0; j < pageSize; j++)
+ write(fd, &ch, 1);
+
+ system("od -a /tmp/tfile");
+
+ addr = mmap(0, 3 * pageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (addr == MAP_FAILED)
+ errExit("mmap");
+
+ printf("Mapped at address %p\n", addr);
+
+ /* The three pages of the file -- 0 1 2 -- are currently mapped
+ linearly. Now we rearrange the mapping to 2 1 0. */
+
+ if (remap_file_pages(addr, pageSize, 0, 2, 0) == -1)
+ errExit("remap_file_pages");
+ if (remap_file_pages(addr + 2 * pageSize, pageSize, 0, 0, 0) == -1)
+ errExit("remap_file_pages");
+
+ /* Now we modify the contents of the mapping */
+
+ for (j = 0; j < 0x100; j++) /* Modifies page 2 of file */
+ *(addr + j) = '0';
+ for (j = 0; j < 0x100; j++) /* Modifies page 0 of file */
+ *(addr + 2 * pageSize + j) = '2';
+
+ system("od -a /tmp/tfile");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE = orphan
+
+LINUX_EXE = \
+ demo_userns \
+ demo_uts_namespaces \
+ hostname \
+ multi_pidns \
+ ns_child_exec \
+ ns_exec \
+ ns_run \
+ pidns_init_sleep \
+ simple_init \
+ t_setns_userns \
+ unshare \
+ userns_child_exec \
+ userns_setns_test
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+clean :
+ ${RM} ${EXE} *.o
+
+showall :
+ @ echo ${EXE}
+
+demo_userns: demo_userns.o
+ ${CC} -o $@ demo_userns.o ${CFLAGS} ${LDLIBS} ${LINUX_LIBCAP}
+
+t_setns_userns: t_setns_userns.o
+ ${CC} -o $@ t_setns_userns.o ${CFLAGS} ${LDLIBS} ${LINUX_LIBCAP}
+
+unshare: unshare.o
+ ${CC} -o $@ unshare.o ${CFLAGS} ${LDLIBS} ${LINUX_LIBCAP}
+
+userns_child_exec: userns_child_exec.o
+ ${CC} -o $@ userns_child_exec.o ${CFLAGS} ${LDLIBS} ${LINUX_LIBCAP}
+
+userns_setns_test: userns_setns_test.o
+ ${CC} -o $@ userns_setns_test.o ${CFLAGS} ${LDLIBS} ${LINUX_LIBCAP}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter Z */
+
+/* demo_userns.c
+
+ Demonstrate the use of the clone() CLONE_NEWUSER flag.
+
+ Link with "-lcap" and make sure that the "libcap-devel" (or
+ similar) package is installed on the system.
+
+ See https://lwn.net/Articles/532593/
+*/
+#define _GNU_SOURCE
+#include <sys/capability.h>
+#include <sys/wait.h>
+#include <sched.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \
+ } while (0)
+
+static int /* Startup function for cloned child */
+childFunc(void *arg)
+{
+ cap_t caps;
+
+ for (;;) {
+ printf("eUID = %ld; eGID = %ld; ",
+ (long) geteuid(), (long) getegid());
+
+ caps = cap_get_proc();
+ printf("capabilities: %s\n", cap_to_text(caps, NULL));
+
+ if (arg == NULL)
+ break;
+
+ sleep(5);
+ }
+
+ return 0;
+}
+
+#define STACK_SIZE (1024 * 1024)
+
+int
+main(int argc, char *argv[])
+{
+ pid_t pid;
+ char *child_stack;
+
+ child_stack = malloc(STACK_SIZE);
+ if (child_stack == NULL)
+ errExit("malloc");
+
+ /* Create child; child commences execution in childFunc() */
+
+ pid = clone(childFunc, child_stack + STACK_SIZE, /* Assume stack
+ grows downward */
+ CLONE_NEWUSER | SIGCHLD, argv[1]);
+ if (pid == -1)
+ errExit("clone");
+
+ printf("PID of child: %ld\n", (long) pid);
+
+ /* Parent falls through to here. Wait for child. */
+
+ if (waitpid(pid, NULL, 0) == -1)
+ errExit("waitpid");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter Z */
+
+/* demo_uts_namespaces.c
+
+ Demonstrate the operation of UTS namespaces.
+
+ See https://lwn.net/Articles/531245/
+*/
+#define _GNU_SOURCE
+#include <sys/wait.h>
+#include <sys/utsname.h>
+#include <sched.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+/* A simple error-handling function: print an error message based
+ on the value in 'errno' and terminate the calling process */
+
+#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \
+ } while (0)
+
+static int /* Start function for cloned child */
+childFunc(void *arg)
+{
+ struct utsname uts;
+
+ /* Change hostname in UTS namespace of child */
+
+ if (sethostname(arg, strlen(arg)) == -1)
+ errExit("sethostname");
+
+ /* Retrieve and display hostname */
+
+ if (uname(&uts) == -1)
+ errExit("uname");
+ printf("uts.nodename in child: %s\n", uts.nodename);
+
+ /* Keep the namespace open for a while, by sleeping.
+ This allows some experimentation--for example, another
+ process might join the namespace. */
+
+ sleep(1000);
+
+ return 0; /* Terminates child */
+}
+
+#define STACK_SIZE (1024 * 1024) /* Stack size for cloned child */
+
+int
+main(int argc, char *argv[])
+{
+ pid_t child_pid;
+ struct utsname uts;
+ char *child_stack;
+
+ if (argc < 2) {
+ fprintf(stderr, "Usage: %s <child-hostname>\n", argv[0]);
+ exit(EXIT_SUCCESS);
+ }
+
+ child_stack = malloc(STACK_SIZE);
+ if (child_stack == NULL)
+ errExit("malloc");
+
+ /* Create a child that has its own UTS namespace;
+ the child commences execution in childFunc() */
+
+ child_pid = clone(childFunc,
+ child_stack + STACK_SIZE, /* Points to start of
+ downwardly growing stack */
+ CLONE_NEWUTS | SIGCHLD, argv[1]);
+ if (child_pid == -1)
+ errExit("clone");
+ printf("PID of child created by clone() is %ld\n", (long) child_pid);
+
+ /* Parent falls through to here */
+
+ sleep(1); /* Give child time to change its hostname */
+
+ /* Display the hostname in parent's UTS namespace. This will be
+ different from the hostname in child's UTS namespace. */
+
+ if (uname(&uts) == -1)
+ errExit("uname");
+ printf("uts.nodename in parent: %s\n", uts.nodename);
+
+ if (waitpid(child_pid, NULL, 0) == -1) /* Wait for child */
+ errExit("waitpid");
+ printf("child has terminated\n");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter Z */
+
+/* hostname.c
+
+ Display or change the system hostname.
+
+ Usage: hostname [new-host-name]
+*/
+#define _BSD_SOURCE
+#include <sys/param.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#define BUF_SIZE (MAXHOSTNAMELEN + 1)
+
+int
+main(int argc, char *argv[])
+{
+ char buf[BUF_SIZE];
+
+ if (argc > 1) {
+ if (sethostname(argv[1], strlen(argv[1])) == -1) {
+ perror("sethostname");
+ exit(EXIT_FAILURE);
+ }
+ } else {
+ if (gethostname(buf, BUF_SIZE) == -1) {
+ perror("gethostname");
+ exit(EXIT_FAILURE);
+ }
+ printf("%s\n", buf);
+ }
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter Z */
+
+/* multi_pidns.c
+
+ Create a series of child processes in nested PID namespaces.
+
+ See https://lwn.net/Articles/531419/
+*/
+#define _GNU_SOURCE
+#include <sched.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <signal.h>
+#include <stdio.h>
+#include <limits.h>
+#include <sys/mount.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+/* A simple error-handling function: print an error message based
+ on the value in 'errno' and terminate the calling process */
+
+#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \
+ } while (0)
+
+#define STACK_SIZE (1024 * 1024)
+
+/* Recursively create a series of child process in nested PID namespaces.
+ 'arg' is an integer that counts down to 0 during the recursion.
+ When the counter reaches 0, recursion stops and the tail child
+ executes the sleep(1) program. */
+
+static int
+childFunc(void *arg)
+{
+ static int first_call = 1;
+ long level = (long) arg;
+ char *child_stack;
+
+ if (!first_call) {
+
+ /* Unless this is the first recursive call to childFunc()
+ (i.e., we were invoked from main()), mount a procfs
+ for the current PID namespace */
+
+ char mount_point[PATH_MAX];
+
+ snprintf(mount_point, PATH_MAX, "/proc%c", (char) ('0' + level));
+
+ mkdir(mount_point, 0555); /* Create directory for mount point */
+ if (mount("proc", mount_point, "proc", 0, NULL) == -1)
+ errExit("mount");
+ printf("Mounting procfs at %s\n", mount_point);
+ }
+
+ first_call = 0;
+
+ if (level > 0) {
+
+ /* Recursively invoke childFunc() to create another child in a
+ nested PID namespace */
+
+ level--;
+ pid_t child_pid;
+
+ child_stack = malloc(STACK_SIZE);
+ if (child_stack == NULL)
+ errExit("malloc");
+
+ child_pid = clone(childFunc,
+ child_stack + STACK_SIZE, /* Points to start of
+ downwardly growing stack */
+ CLONE_NEWPID | SIGCHLD, (void *) level);
+
+ if (child_pid == -1)
+ errExit("clone");
+
+ if (waitpid(child_pid, NULL, 0) == -1) /* Wait for child */
+ errExit("waitpid");
+
+ free(child_stack);
+ } else {
+
+ /* Tail end of recursion: execute sleep(1) */
+
+ printf("Final child sleeping\n");
+ execlp("sleep", "sleep", "1000", (char *) NULL);
+ errExit("execlp");
+ }
+
+ return 0;
+}
+
+int
+main(int argc, char *argv[])
+{
+ long levels;
+
+ levels = (argc > 1) ? atoi(argv[1]) : 5;
+ childFunc((void *) levels);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter Z */
+
+/* ns_child_exec.c
+
+ Copyright 2013, Michael Kerrisk
+ Licensed under GNU General Public License v2 or later
+
+ Create a child process that executes a shell command in new namespace(s).
+
+ See https://lwn.net/Articles/532748/
+*/
+#define _GNU_SOURCE
+#include <sched.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <stdio.h>
+
+#ifndef CLONE_NEWCGROUP /* Added in Linux 4.6 */
+#define CLONE_NEWCGROUP 0x02000000
+#endif
+
+/* A simple error-handling function: print an error message based
+ on the value in 'errno' and terminate the calling process */
+
+#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \
+ } while (0)
+
+static void
+usage(char *pname)
+{
+ fprintf(stderr, "Usage: %s [options] cmd [arg...]\n", pname);
+ fprintf(stderr, "Options can be:\n");
+ fprintf(stderr, " -C new cgroup namespace\n");
+ fprintf(stderr, " -i new IPC namespace\n");
+ fprintf(stderr, " -m new mount namespace\n");
+ fprintf(stderr, " -n new network namespace\n");
+ fprintf(stderr, " -p new PID namespace\n");
+ fprintf(stderr, " -u new UTS namespace\n");
+ fprintf(stderr, " -U new user namespace\n");
+ fprintf(stderr, " -v Display verbose messages\n");
+ exit(EXIT_FAILURE);
+}
+
+static int /* Start function for cloned child */
+childFunc(void *arg)
+{
+ char **argv = arg;
+
+ execvp(argv[0], argv);
+ errExit("execvp");
+}
+
+#define STACK_SIZE (1024 * 1024)
+
+int
+main(int argc, char *argv[])
+{
+ int flags, opt, verbose;
+ pid_t child_pid;
+ char *child_stack;
+
+ flags = 0;
+ verbose = 0;
+
+ /* Parse command-line options. The initial '+' character in
+ the final getopt() argument prevents GNU-style permutation
+ of command-line options. That's useful, since sometimes
+ the 'command' to be executed by this program itself
+ has command-line options. We don't want getopt() to treat
+ those as options to this program. */
+
+ while ((opt = getopt(argc, argv, "+CimnpuUv")) != -1) {
+ switch (opt) {
+ case 'C': flags |= CLONE_NEWCGROUP; break;
+ case 'i': flags |= CLONE_NEWIPC; break;
+ case 'm': flags |= CLONE_NEWNS; break;
+ case 'n': flags |= CLONE_NEWNET; break;
+ case 'p': flags |= CLONE_NEWPID; break;
+ case 'u': flags |= CLONE_NEWUTS; break;
+ case 'U': flags |= CLONE_NEWUSER; break;
+ case 'v': verbose = 1; break;
+ default: usage(argv[0]);
+ }
+ }
+
+ if (optind >= argc)
+ usage(argv[0]);
+
+ child_stack = malloc(STACK_SIZE);
+ if (child_stack == NULL)
+ errExit("malloc");
+
+ child_pid = clone(childFunc,
+ child_stack + STACK_SIZE,
+ flags | SIGCHLD, &argv[optind]);
+ if (child_pid == -1)
+ errExit("clone");
+
+ if (verbose)
+ printf("%s: PID of child created by clone() is %ld\n",
+ argv[0], (long) child_pid);
+
+ /* Parent falls through to here */
+
+ if (waitpid(child_pid, NULL, 0) == -1) /* Wait for child */
+ errExit("waitpid");
+
+ if (verbose)
+ printf("%s: terminating\n", argv[0]);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter Z */
+
+/* ns_exec.c
+
+ Join a namespace using setns() and execute a command in the namespace.
+ This is program is similar in concept to nsenter(1) (however, that
+ program allows multiple namespaces to be joined), but has a less
+ command-line interface.
+
+ See https://lwn.net/Articles/531381/
+*/
+#define _GNU_SOURCE
+#include <fcntl.h>
+#include <sched.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <sys/wait.h>
+
+/* A simple error-handling function: print an error message based
+ on the value in 'errno' and terminate the calling process */
+
+#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \
+ } while (0)
+
+int
+main(int argc, char *argv[])
+{
+ int fd;
+
+ if (argc < 3) {
+ fprintf(stderr, "%s /proc/PID/ns/FILE cmd [arg...]\n", argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ fd = open(argv[1], O_RDONLY); /* Get descriptor for namespace */
+ if (fd == -1)
+ errExit("open");
+
+ if (setns(fd, 0) == -1) /* Join that namespace */
+ errExit("setns");
+
+ execvp(argv[2], &argv[2]); /* Execute a command in namespace */
+ errExit("execvp");
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter Z */
+
+/* ns_run.c
+
+ Join one or more namespaces using setns() and execute a command in
+ those namespaces, possibly inside a child process.
+
+ This program is similar in concept to nsenter(1), but has a
+ different command-line interface.
+
+ See https://lwn.net/Articles/532748/
+*/
+#define _GNU_SOURCE
+#include <fcntl.h>
+#include <sched.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/wait.h>
+
+/* A simple error-handling function: print an error message based
+ on the value in 'errno' and terminate the calling process */
+
+#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \
+ } while (0)
+
+static void
+usage(char *pname)
+{
+ fprintf(stderr, "Usage: %s [-f] [-n /proc/PID/ns/FILE] cmd [arg...]\n",
+ pname);
+ fprintf(stderr, "\t-f Execute command in child process\n");
+ fprintf(stderr, "\t-n Join specified namespace\n");
+
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int fd, opt, do_fork;
+ pid_t pid;
+
+ /* Parse command-line options. The initial '+' character in
+ the final getopt() argument prevents GNU-style permutation
+ of command-line options. That's useful, since sometimes
+ the 'command' to be executed by this program itself
+ has command-line options. We don't want getopt() to treat
+ those as options to this program. */
+
+ do_fork = 0;
+ while ((opt = getopt(argc, argv, "+fn:")) != -1) {
+ switch (opt) {
+
+ case 'n': /* Join a namespace */
+ fd = open(optarg, O_RDONLY); /* Get descriptor for namespace */
+ if (fd == -1)
+ errExit("open");
+
+ if (setns(fd, 0) == -1) /* Join that namespace */
+ errExit("setns");
+ break;
+
+ case 'f':
+ do_fork = 1;
+ break;
+
+ default:
+ usage(argv[0]);
+ }
+ }
+
+ if (argc <= optind)
+ usage(argv[0]);
+
+ /* If the "-f" option was specified, execute the supplied command
+ in a child process. This is mainly useful when working with PID
+ namespaces, since setns() to a PID namespace only places
+ (subsequently created) child processes in the names, and
+ does not affect the PID namespace membership of the caller. */
+
+ if (do_fork) {
+ pid = fork();
+ if (pid == -1)
+ errExit("fork");
+
+ if (pid != 0) { /* Parent */
+ if (waitpid(-1, NULL, 0) == -1) /* Wait for child */
+ errExit("waitpid");
+ exit(EXIT_SUCCESS);
+ }
+
+ /* Child falls through to code below */
+ }
+
+ execvp(argv[optind], &argv[optind]);
+ errExit("execvp");
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter Z */
+
+/* orphan.c
+
+ Copyright 2013, Michael Kerrisk
+ Licensed under GNU General Public License v2 or later
+
+ Demonstrate that a child becomes orphaned (and is adopted by init(1),
+ whose PID is 1) when its parent exits.
+
+ See https://lwn.net/Articles/532748/
+*/
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+int
+main(int argc, char *argv[])
+{
+ pid_t pid;
+
+ pid = fork();
+ if (pid == -1) {
+ perror("fork");
+ exit(EXIT_FAILURE);
+ }
+
+ if (pid != 0) { /* Parent */
+ printf("Parent (PID=%ld) created child with PID %ld\n",
+ (long) getpid(), (long) pid);
+ printf("Parent (PID=%ld; PPID=%ld) terminating\n",
+ (long) getpid(), (long) getppid());
+ exit(EXIT_SUCCESS);
+ }
+
+ /* Child falls through to here */
+
+ do {
+ usleep(100000);
+ } while (getppid() != 1); /* Am I an orphan yet? */
+
+ printf("\nChild (PID=%ld) now an orphan (parent PID=%ld)\n",
+ (long) getpid(), (long) getppid());
+
+ sleep(1);
+
+ printf("Child (PID=%ld) terminating\n", (long) getpid());
+ _exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter Z */
+
+/* pidns_init_sleep.c
+
+ A simple demonstration of PID namespaces.
+
+ See https://lwn.net/Articles/531419/
+*/
+#define _GNU_SOURCE
+#include <sched.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+#include <sys/mount.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <signal.h>
+#include <stdio.h>
+
+/* A simple error-handling function: print an error message based
+ on the value in 'errno' and terminate the calling process */
+
+#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \
+ } while (0)
+
+static int /* Start function for cloned child */
+childFunc(void *arg)
+{
+ printf("childFunc(): PID = %ld\n", (long) getpid());
+ printf("childFunc(): PPID = %ld\n", (long) getppid());
+
+ char *mount_point = arg;
+
+ if (mount_point != NULL) {
+ mkdir(mount_point, 0555); /* Create directory for mount point */
+ if (mount("proc", mount_point, "proc", 0, NULL) == -1)
+ errExit("mount");
+ printf("Mounting procfs at %s\n", mount_point);
+ }
+
+ execlp("sleep", "sleep", "600", (char *) NULL);
+ errExit("execlp"); /* Only reached if execlp() fails */
+}
+
+#define STACK_SIZE (1024 * 1024)
+
+int
+main(int argc, char *argv[])
+{
+ pid_t child_pid;
+ char *child_stack;
+
+ child_stack = malloc(STACK_SIZE);
+ if (child_stack == NULL)
+ errExit("malloc");
+
+ child_pid = clone(childFunc,
+ child_stack + STACK_SIZE, /* Points to start of
+ downwardly growing stack */
+ CLONE_NEWPID | SIGCHLD, argv[1]);
+
+ if (child_pid == -1)
+ errExit("clone");
+
+ printf("PID returned by clone(): %ld\n", (long) child_pid);
+
+ if (waitpid(child_pid, NULL, 0) == -1) /* Wait for child */
+ errExit("waitpid");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter Z */
+
+/* simple_init.c
+
+ A simple init(1)-style program to be used as the init program in
+ a PID namespace. The program reaps the status of its children and
+ provides a simple shell facility for executing commands.
+
+ See https://lwn.net/Articles/532748/
+*/
+#define _GNU_SOURCE
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <wordexp.h>
+#include <errno.h>
+#include <sys/wait.h>
+#include <sys/mount.h>
+
+#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \
+ } while (0)
+
+static int verbose = 0;
+
+/* Display wait status (from waitpid() or similar) given in 'wstatus' */
+
+/* SIGCHLD handler: reap child processes as they change state */
+
+static void
+child_handler(int sig)
+{
+ pid_t pid;
+ int wstatus;
+
+ /* WUNTRACED and WCONTINUED allow waitpid() to catch stopped and
+ continued children (in addition to terminated children) */
+
+ while ((pid = waitpid(-1, &wstatus,
+ WNOHANG | WUNTRACED | WCONTINUED)) != 0) {
+ if (pid == -1) {
+ if (errno == ECHILD) /* No more children */
+ break;
+ else
+ perror("waitpid"); /* Unexpected error */
+ }
+
+ if (verbose)
+ printf("\tinit: SIGCHLD handler: PID %ld terminated\n",
+ (long) pid);
+ }
+}
+
+/* Perform word expansion on string in 'cmd', allocating and
+ returning a vector of words on success or NULL on failure */
+
+static char **
+expand_words(char *cmd)
+{
+ char **arg_vec;
+ int s;
+ wordexp_t pwordexp;
+
+ s = wordexp(cmd, &pwordexp, 0);
+ if (s != 0) {
+ fprintf(stderr, "Word expansion failed.\n"
+ "\tNote that only simple "
+ "commands plus arguments are supported\n"
+ "\t(no pipelines, I/O redirection, and so on)\n");
+ return NULL;
+ }
+
+ arg_vec = calloc(pwordexp.we_wordc + 1, sizeof(char *));
+ if (arg_vec == NULL)
+ errExit("calloc");
+
+ for (s = 0; s < pwordexp.we_wordc; s++)
+ arg_vec[s] = pwordexp.we_wordv[s];
+
+ arg_vec[pwordexp.we_wordc] = NULL;
+
+ return arg_vec;
+}
+
+static void
+usage(char *pname)
+{
+ fprintf(stderr, "Usage: %s [-v] [-p proc-mount]\n", pname);
+ fprintf(stderr, "\t-v Provide verbose logging\n");
+ fprintf(stderr, "\t-p proc-mount Mount a procfs at specified path\n");
+
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct sigaction sa;
+#define CMD_SIZE 10000
+ char cmd[CMD_SIZE];
+ pid_t pid;
+ int opt;
+ char *proc_path;
+
+ proc_path = NULL;
+ while ((opt = getopt(argc, argv, "p:v")) != -1) {
+ switch (opt) {
+ case 'p': proc_path = optarg; break;
+ case 'v': verbose = 1; break;
+ default: usage(argv[0]);
+ }
+ }
+
+ sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_handler = child_handler;
+ if (sigaction(SIGCHLD, &sa, NULL) == -1)
+ errExit("sigaction");
+
+ if (verbose)
+ printf("\tinit: my PID is %ld\n", (long) getpid());
+
+ /* Performing terminal operations while not being the foreground
+ process group for the terminal generates a SIGTTOU that stops the
+ process. However our init "shell" needs to be able to perform
+ such operations (just like a normal shell), so we ignore that
+ signal, which allows the operations to proceed successfully. */
+
+ signal(SIGTTOU, SIG_IGN);
+ /* Become leader of a new process group and make that process
+ group the foreground process group for the terminal */
+
+ if (setpgid(0, 0) == -1)
+ errExit("setpgid");;
+ if (tcsetpgrp(STDIN_FILENO, getpgrp()) == -1)
+ errExit("tcsetpgrp-child");
+
+ /* If the user asked to mount a procfs, mount it at the specified path */
+
+ if (proc_path != NULL) {
+
+ /* Some distributions enable mount propagation (mount --make-shared)
+ by default. This would cause the mount that we create here to
+ propagate to other namespaces. If we were mounting the
+ procfs for this new PID namespace at "/proc" (which is typical),
+ then this would hide the original "/proc" mount point in the
+ intial namespace, which we probably don't want, since it will
+ confuse a lot of system tools. To prevent propagation from
+ occurring, we need to mark the mount point either as a slave
+ mount or as a private mount.
+
+ For further information on this topic, see the kernel source
+ file Documentation/filesystems/sharedsubtree.txt and the
+ mount(8) man page */
+
+ if (verbose)
+ printf("Making %s a private mount\n", proc_path);
+
+ /* EINVAL is the case that occurs if 'proc_path' exists but is
+ not (yet) a mount point */
+
+ if (mount("none", proc_path, NULL, MS_SLAVE, NULL) == -1 &&
+ errno != EINVAL)
+ perror("mount-make-slave-/");
+
+ if (verbose)
+ printf("Mounting procfs at %s\n", proc_path);
+
+ if (mount("proc", proc_path, "proc", 0, NULL) == -1)
+ errExit("mount-procfs");
+ }
+
+ /* Loop executing "shell" commands. Note that our shell facility is
+ very simple: it handles simple commands with arguments, and
+ performs wordexp() expansions (globbing, variable and command
+ substitution, tilde expansion, and quote removal). Complex
+ commands (pipelines, ||, &&) and I/O redirections, and
+ standard shell features are not supported. */
+
+ while (1) {
+
+ /* Read a shell command; exit on end of file */
+
+ printf("init$ ");
+ if (fgets(cmd, CMD_SIZE, stdin) == NULL) {
+ if (verbose)
+ printf("\tinit: exiting");
+ printf("\n");
+ break;
+ }
+
+ if (cmd[strlen(cmd) - 1] == '\n')
+ cmd[strlen(cmd) - 1] = '\0'; /* Strip trailing '\n' */
+
+ if (strlen(cmd) == 0)
+ continue; /* Ignore empty commands */
+
+ pid = fork(); /* Create child process */
+ if (pid == -1) {
+ perror("fork");
+ break;
+ }
+
+ if (pid == 0) { /* Child */
+ char **arg_vec;
+
+ arg_vec = expand_words(cmd);
+ if (arg_vec == NULL) /* Word expansion failed */
+ exit(EXIT_FAILURE);
+
+ /* Make child the leader of a new process group and
+ make that process group the foreground process
+ group for the terminal */
+
+ if (setpgid(0, 0) == -1)
+ errExit("setpgid");;
+ if (tcsetpgrp(STDIN_FILENO, getpgrp()) == -1)
+ errExit("tcsetpgrp-child");
+
+ /* Child executes shell command and terminates */
+
+ execvp(arg_vec[0], arg_vec);
+ errExit("execvp"); /* Only reached if execvp() fails */
+ }
+
+ /* Parent falls through to here */
+
+ if (verbose)
+ printf("\tinit: created child %ld\n", (long) pid);
+
+ pause(); /* Will be interrupted by signal handler */
+
+ /* After child changes state, ensure that the 'init' program
+ is the foreground process group for the terminal */
+
+ if (tcsetpgrp(STDIN_FILENO, getpgrp()) == -1)
+ errExit("tcsetpgrp-parent");
+ }
+
+ /* If we mounted a procfs earlier, unmount it before terminating */
+
+ if (proc_path != NULL) {
+ if (verbose)
+ printf("Unmounting procfs at %s\n", proc_path);
+ if (umount(proc_path) == -1)
+ errExit("umount-procfs");
+ }
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter Z */
+
+/* t_setns.c
+
+ Attempt to join a user namespace using setns(), displaying
+ process's credentials and capabilities before and after setns().
+*/
+#define _GNU_SOURCE
+#include <fcntl.h>
+#include <sched.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/capability.h>
+
+#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \
+ } while (0)
+
+static void
+display_creds_and_caps(char *msg)
+{
+ cap_t caps;
+
+ printf("%seUID = %ld; eGID = %ld; ", msg,
+ (long) geteuid(), (long) getegid());
+
+ caps = cap_get_proc();
+ printf("capabilities: %s\n", cap_to_text(caps, NULL));
+}
+
+int
+main(int argc, char *argv[])
+{
+ int fd;
+
+ if (argc < 2) {
+ fprintf(stderr, "Usage: %s /proc/PID/ns/FILE\n", argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ display_creds_and_caps("Initial:\n");
+ printf("\n");
+
+ fd = open(argv[1], O_RDONLY); /* Get descriptor for namespace */
+ if (fd == -1)
+ errExit("open");
+
+ if (setns(fd, CLONE_NEWUSER) == -1) /* Join that namespace */
+ errExit("setns-1");
+
+ display_creds_and_caps("After setns():\n");
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter Z */
+
+/* unshare.c
+
+ A simple implementation of the unshare(1) command: unshare
+ namespaces and execute a command.
+
+ See https://lwn.net/Articles/531381/
+*/
+#define _GNU_SOURCE
+#include <sched.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/wait.h>
+
+#ifndef CLONE_NEWCGROUP /* Added in Linux 4.6 */
+#define CLONE_NEWCGROUP 0x02000000
+#endif
+
+/* A simple error-handling function: print an error message based
+ on the value in 'errno' and terminate the calling process */
+
+#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \
+ } while (0)
+
+static void
+usage(char *pname)
+{
+ fprintf(stderr, "Usage: %s [options] cmd [arg...]\n", pname);
+ fprintf(stderr, "Options can be:\n");
+ fprintf(stderr, " -f fork() before executing cmd "
+ "(useful when unsharing PID namespace)\n");
+ fprintf(stderr, " -C unshare cgroup namespace\n");
+ fprintf(stderr, " -i unshare IPC namespace\n");
+ fprintf(stderr, " -m unshare mount namespace\n");
+ fprintf(stderr, " -n unshare network namespace\n");
+ fprintf(stderr, " -p unshare PID namespace\n");
+ fprintf(stderr, " -u unshare UTS namespace\n");
+ fprintf(stderr, " -U unshare user namespace\n");
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int flags, do_fork, opt;
+
+ flags = 0;
+ do_fork = 0;
+ while ((opt = getopt(argc, argv, "CfimnpuU")) != -1) {
+ switch (opt) {
+ case 'f': do_fork = 1; break;
+ case 'C': flags |= CLONE_NEWCGROUP; break;
+ case 'i': flags |= CLONE_NEWIPC; break;
+ case 'm': flags |= CLONE_NEWNS; break;
+ case 'n': flags |= CLONE_NEWNET; break;
+ case 'p': flags |= CLONE_NEWPID; break;
+ case 'u': flags |= CLONE_NEWUTS; break;
+ case 'U': flags |= CLONE_NEWUSER; break;
+ default: usage(argv[0]);
+ }
+ }
+
+ if (optind >= argc)
+ usage(argv[0]);
+
+ if (unshare(flags) == -1)
+ errExit("unshare");
+
+ /* If we are unsharing the PID namespace, then the caller is *not*
+ moved into the new namespace. Instead, only the children are moved
+ into the namespace. Therefore, we support an option that causes
+ the program to call fork() before executing the specified program,
+ in order to create a new child that will be created in a new PID
+ namespace. */
+
+ if (do_fork) {
+ if (fork()) {
+ wait(NULL); /* Parent waits for child to complete */
+ exit(EXIT_SUCCESS);
+ }
+
+ /* Child falls through to execute command */
+ }
+
+ execvp(argv[optind], &argv[optind]);
+ errExit("execvp");
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter Z */
+
+/* userns_child_exec.c
+
+ Create a child process that executes a shell command in new
+ namespace(s); allow UID and GID mappings to be specified when
+ creating a user namespace.
+
+ See https://lwn.net/Articles/532593/
+*/
+#define _GNU_SOURCE
+#include <sched.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <limits.h>
+#include <errno.h>
+
+#ifndef CLONE_NEWCGROUP /* Added in Linux 4.6 */
+#define CLONE_NEWCGROUP 0x02000000
+#endif
+
+/* A simple error-handling function: print an error message based
+ on the value in 'errno' and terminate the calling process */
+
+#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \
+ } while (0)
+
+struct child_args {
+ char **argv; /* Command to be executed by child, with args */
+ int pipe_fd[2]; /* Pipe used to synchronize parent and child */
+};
+
+static int verbose;
+
+static void
+usage(char *pname)
+{
+ fprintf(stderr, "Usage: %s [options] cmd [arg...]\n\n", pname);
+ fprintf(stderr, "Create a child process that executes a shell "
+ "command in a new user namespace,\n"
+ "and possibly also other new namespace(s).\n\n");
+ fprintf(stderr, "Options can be:\n\n");
+#define fpe(str) fprintf(stderr, " %s", str);
+ fpe("-C New cgroup namespace\n");
+ fpe("-i New IPC namespace\n");
+ fpe("-m New mount namespace\n");
+ fpe("-n New network namespace\n");
+ fpe("-p New PID namespace\n");
+ fpe("-u New UTS namespace\n");
+ fpe("-U New user namespace\n");
+ fpe("-M uid_map Specify UID map for user namespace\n");
+ fpe("-G gid_map Specify GID map for user namespace\n");
+ fpe("-D Do not write \"deny\" to /proc/PID/setgroups before\n");
+ fpe(" updating GID map\n");
+ fpe("-z Map user's UID and GID to 0 in user namespace\n");
+ fpe(" (equivalent to: -M '0 <uid> 1' -G '0 <gid> 1')\n");
+ fpe("-v Display verbose messages\n");
+ fpe("\n");
+ fpe("If -z, -M, or -G is specified, -U is required.\n");
+ fpe("It is not permitted to specify both -z and either -M or -G.\n");
+ fpe("\n");
+ fpe("Map strings for -M and -G consist of records of the form:\n");
+ fpe("\n");
+ fpe(" ID-inside-ns ID-outside-ns len\n");
+ fpe("\n");
+ fpe("A map string can contain multiple records, separated"
+ " by commas;\n");
+ fpe("the commas are replaced by newlines before writing"
+ " to map files.\n");
+
+ exit(EXIT_FAILURE);
+}
+
+/* Update the mapping file 'map_file', with the value provided in
+ 'mapping', a string that defines a UID or GID mapping. A UID or
+ GID mapping consists of one or more newline-delimited records
+ of the form:
+
+ ID_inside-ns ID-outside-ns length
+
+ Requiring the user to supply a string that contains newlines is
+ of course inconvenient for command-line use. Thus, we permit the
+ use of commas to delimit records in this string, and replace them
+ with newlines before writing the string to the file. */
+
+static void
+update_map(char *mapping, char *map_file)
+{
+ int fd, j;
+ size_t map_len; /* Length of 'mapping' */
+
+ /* Replace commas in mapping string with newlines */
+
+ map_len = strlen(mapping);
+ for (j = 0; j < map_len; j++)
+ if (mapping[j] == ',')
+ mapping[j] = '\n';
+
+ fd = open(map_file, O_RDWR);
+ if (fd == -1) {
+ fprintf(stderr, "ERROR: open %s: %s\n", map_file, strerror(errno));
+ return;
+ //exit(EXIT_FAILURE);
+ }
+
+ if (write(fd, mapping, map_len) != map_len) {
+ fprintf(stderr, "ERROR: write %s: %s\n", map_file, strerror(errno));
+ //exit(EXIT_FAILURE);
+ }
+
+ close(fd);
+}
+
+/* Linux 3.19 made a change in the handling of setgroups(2) and the
+ 'gid_map' file to address a security issue. The issue allowed
+ *unprivileged* users to employ user namespaces in order to drop
+ groups from their supplementary group list using setgroups(2).
+ (Formerly, this possibility was available only to privileged
+ processes.) The effect was to create possibilities for unprivileged
+ process to access files for which they would not otherwise have had
+ permission. (For further details, see the user_namespaces(7) man
+ page.)
+
+ The upshot of the 3.19 changes is that in order for a process lacking
+ suitable privileges (i.e., one that lacks the CAP_SETGID capability
+ in the parent user namespace) to update the 'gid_maps' file, use of the
+ setgroups() system call in this user namespace must first be
+ disabled by writing "deny" to one of the /proc/PID/setgroups files
+ for this namespace. That is the purpose of the following function. */
+
+static void
+proc_setgroups_write(pid_t child_pid, char *str)
+{
+ char setgroups_path[PATH_MAX];
+ int fd;
+
+ snprintf(setgroups_path, PATH_MAX, "/proc/%ld/setgroups",
+ (long) child_pid);
+
+ fd = open(setgroups_path, O_RDWR);
+ if (fd == -1) {
+
+ /* We may be on a system that doesn't support
+ /proc/PID/setgroups. In that case, the file won't exist,
+ and the system won't impose the restrictions that Linux 3.19
+ added. That's fine: we don't need to do anything in order
+ to permit 'gid_map' to be updated.
+
+ However, if the error from open() was something other than
+ the ENOENT error that is expected for that case, let the
+ user know. */
+
+ if (errno != ENOENT)
+ fprintf(stderr, "ERROR: open %s: %s\n", setgroups_path,
+ strerror(errno));
+ return;
+ }
+
+ if (verbose)
+ printf("Writing to \"%s\" to %s\n", str, setgroups_path);
+
+ if (write(fd, str, strlen(str)) == -1)
+ fprintf(stderr, "ERROR: write %s: %s\n", setgroups_path,
+ strerror(errno));
+
+ close(fd);
+}
+
+static int /* Start function for cloned child */
+childFunc(void *arg)
+{
+ struct child_args *args = (struct child_args *) arg;
+ char ch;
+
+ /* Wait until the parent has updated the UID and GID mappings.
+ See the comment in main(). We wait for end of file on a
+ pipe that will be closed by the parent process once it has
+ updated the mappings. */
+
+ close(args->pipe_fd[1]); /* Close our descriptor for the write
+ end of the pipe so that we see EOF
+ when parent closes its descriptor */
+ if (read(args->pipe_fd[0], &ch, 1) != 0) {
+ fprintf(stderr,
+ "Failure in child: read from pipe returned != 0\n");
+ exit(EXIT_FAILURE);
+ }
+
+ close(args->pipe_fd[0]); /* We no longer need the pipe */
+
+ /* Execute a shell command */
+
+ if (verbose)
+ printf("About to exec: %s\n", args->argv[0]);
+
+ execvp(args->argv[0], args->argv);
+ errExit("execvp");
+}
+
+#define STACK_SIZE (1024 * 1024)
+
+int
+main(int argc, char *argv[])
+{
+ int flags, opt, map_zero, deny_setgroups;
+ pid_t child_pid;
+ struct child_args args;
+ char *uid_map, *gid_map;
+ const int MAP_BUF_SIZE = 100;
+ char map_buf[MAP_BUF_SIZE];
+ char map_path[PATH_MAX];
+ char *child_stack;
+
+ /* Parse command-line options. The initial '+' character in
+ the final getopt(3) argument prevents GNU-style permutation
+ of command-line options. Preventing that is useful, since
+ sometimes the 'command' to be executed by this program itself
+ has command-line options. We don't want getopt() to treat
+ those as options to this program. */
+
+ flags = 0;
+ verbose = 0;
+ gid_map = NULL;
+ uid_map = NULL;
+ map_zero = 0;
+ deny_setgroups = 1;
+ while ((opt = getopt(argc, argv, "+CimnpuvzM:G:DU")) != -1) {
+ switch (opt) {
+ case 'C': flags |= CLONE_NEWCGROUP; break;
+ case 'i': flags |= CLONE_NEWIPC; break;
+ case 'm': flags |= CLONE_NEWNS; break;
+ case 'n': flags |= CLONE_NEWNET; break;
+ case 'p': flags |= CLONE_NEWPID; break;
+ case 'u': flags |= CLONE_NEWUTS; break;
+ case 'v': verbose = 1; break;
+ case 'z': map_zero = 1; break;
+ case 'M': uid_map = optarg; break;
+ case 'G': gid_map = optarg; break;
+ case 'D': deny_setgroups = 0; break;
+ case 'U': flags |= CLONE_NEWUSER; break;
+ default: usage(argv[0]);
+ }
+ }
+
+ /* -M or -G without -U is nonsensical */
+
+ if (((uid_map != NULL || gid_map != NULL || map_zero) &&
+ !(flags & CLONE_NEWUSER)) ||
+ (map_zero && (uid_map != NULL || gid_map != NULL)))
+ usage(argv[0]);
+
+ if (optind >= argc)
+ usage(argv[0]);
+
+ args.argv = &argv[optind];
+
+ /* We use a pipe to synchronize the parent and child, in order to
+ ensure that the parent sets the UID and GID maps before the child
+ calls execve(). This ensures that the child maintains its
+ capabilities during the execve() in the common case where we
+ want to map the child's effective user ID to 0 in the new user
+ namespace. Without this synchronization, the child would lose
+ its capabilities if it performed an execve() with nonzero
+ user IDs (see the capabilities(7) man page for details of the
+ transformation of a process's capabilities during execve()). */
+
+ if (pipe(args.pipe_fd) == -1)
+ errExit("pipe");
+
+ /* Create the child in new namespace(s) */
+
+ child_stack = malloc(STACK_SIZE);
+ if (child_stack == NULL)
+ errExit("malloc");
+
+ child_pid = clone(childFunc, child_stack + STACK_SIZE,
+ flags | SIGCHLD, &args);
+ if (child_pid == -1)
+ errExit("clone");
+
+ /* Parent falls through to here */
+
+ if (verbose)
+ printf("%s: PID of child created by clone() is %ld\n",
+ argv[0], (long) child_pid);
+
+ /* Update the UID and GID maps in the child */
+
+ if (uid_map != NULL || map_zero) {
+ snprintf(map_path, PATH_MAX, "/proc/%ld/uid_map",
+ (long) child_pid);
+ if (map_zero) {
+ snprintf(map_buf, MAP_BUF_SIZE, "0 %ld 1", (long) getuid());
+ uid_map = map_buf;
+ }
+ update_map(uid_map, map_path);
+ }
+
+ if (gid_map != NULL || map_zero) {
+ if (deny_setgroups)
+ proc_setgroups_write(child_pid, "deny");
+
+ snprintf(map_path, PATH_MAX, "/proc/%ld/gid_map",
+ (long) child_pid);
+ if (map_zero) {
+ snprintf(map_buf, MAP_BUF_SIZE, "0 %ld 1", (long) getgid());
+ gid_map = map_buf;
+ }
+ update_map(gid_map, map_path);
+ }
+
+ /* Close the write end of the pipe, to signal to the child that we
+ have updated the UID and GID maps */
+
+ close(args.pipe_fd[1]);
+
+ if (waitpid(child_pid, NULL, 0) == -1) /* Wait for child */
+ errExit("waitpid");
+
+ if (verbose)
+ printf("%s: terminating\n", argv[0]);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/* userns_overview.go
+
+ Display a hierarchical view of the user namespaces on the
+ system along with the member processes for each namespace.
+ This requires features new in Linux 4.9. See the
+ namespaces(7) man page.
+ (http://man7.org/linux/man-pages/man7/namespaces.7.html)
+*/
+
+package main
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "sort"
+ "strconv"
+ "strings"
+ "syscall"
+ "unsafe"
+)
+
+// A namespace is identified by device ID and inode number
+
+type NamespaceID struct {
+ device uint64 // dev_t
+ inode_num uint64 // ino_t
+}
+
+// A namespace has associated attributes: a set of
+// child namespaces and a set of member processes
+
+type NamespaceAttribs struct {
+ children []NamespaceID // Child namespaces
+ pids []int // Member processes
+}
+
+// The following map records all of the namespaces that
+// we find on the system
+
+var NSList = make(map[NamespaceID]*NamespaceAttribs)
+
+// Along the way, we'll discover the ancestor of all user
+// namespaces (the root of the user namespace hierarchy).
+
+var initialNS NamespaceID
+
+// AddNamespace adds a PID to the list of PIDs associated with
+// the user namespace referred to by 'namespaceFD'.
+//
+// The set of namespaces is recorded in the 'NSList' map.
+// If the map does not yet contain an entry corresponding to
+// 'namespaceFD', then an entry is created. This process is
+// recursive: if the parent of the user namespace referred
+// to by 'namespaceFD' does not have an entry in 'NSList'
+// then an entry is created for the parent, and the namespace
+// referred to by 'namespaceFD' is made a child of that namespace.
+//
+// When called recursively to create the ancestor namespace
+// entries, this function is called with 'pid' as -1, meaning
+// that no PID needs to be added for this namespace entry.
+//
+// The return value of the function is the ID of the namespace
+// entry (i.e., the device ID and inode number corresponding to
+// the user namespace file referred to by 'namespaceFD').
+
+func AddNamespace(namespaceFD int, pid int) NamespaceID {
+ //const NS_GET_USERNS = 0xb701
+ const NS_GET_PARENT = 0xb702 // ioctl() to get namespace parent
+ var sb syscall.Stat_t
+ var err error
+
+ // Obtain the device ID and inode number of the namespace
+ // file. These values together form the key for the 'NSList'
+ // map entry.
+
+ err = syscall.Fstat(namespaceFD, &sb)
+ if err != nil {
+ fmt.Println("syscall.Fstat(): ", err)
+ os.Exit(1)
+ }
+
+ ns := *new(NamespaceID)
+ ns = NamespaceID{sb.Dev, sb.Ino}
+
+ if _, fnd := NSList[ns]; fnd {
+
+ // Namespace already exists; nothing to do
+
+ } else {
+
+ // Namespace entry does not yet exist; create it
+
+ np := new(NamespaceAttribs)
+ NSList[ns] = np
+
+ // Get file descriptor for parent user namespace
+
+ r, _, e := syscall.Syscall(syscall.SYS_IOCTL,
+ uintptr(namespaceFD), uintptr(NS_GET_PARENT), 0)
+ parentFD := (int)((uintptr)(unsafe.Pointer(r)))
+
+ if parentFD == -1 {
+ switch e {
+ case syscall.EPERM:
+ // This is the initial NS; remember it
+ initialNS = ns
+ case syscall.ENOTTY:
+ fmt.Println("This kernel doesn't support " +
+ "namespace introspection")
+ os.Exit(1)
+ default:
+ // Unexpected error; bail
+ fmt.Println("ioctl()", e)
+ os.Exit(1)
+ }
+
+ } else {
+
+ // We have a parent user namespace; make sure it
+ // has an entry in the map. No need to add any
+ // PID for the parent entry.
+
+ par := AddNamespace(parentFD, -1)
+
+ // Make the current namespace entry ('ns') a child of
+ // the parent namespace entry
+
+ NSList[par].children = append(NSList[par].children, ns)
+
+ syscall.Close(parentFD)
+ }
+ }
+
+ // Add PID to PID list for this namespace entry
+
+ if pid > 0 {
+ NSList[ns].pids = append(NSList[ns].pids, pid)
+ }
+
+ return ns
+}
+
+// ProcessProcFile processes a single /proc/PID entry, creating
+// a namespace entry for this PID's /proc/PID/ns/user file
+// (and, as necessary, namespace entries for all ancestor namespaces
+// going back to the initial user namespace).
+// 'name' is the name of a PID directory under /proc.
+
+func ProcessProcFile(name string) {
+ var namespaceFD int
+ var err error
+
+ // Obtain a file descriptor that refers to the user namespace
+ // of this process
+
+ namespaceFD, err = syscall.Open("/proc/"+name+"/ns/user",
+ syscall.O_RDONLY, 0)
+
+ if namespaceFD < 0 {
+ fmt.Println("Open: ", namespaceFD, err)
+ os.Exit(1)
+ }
+
+ pid, _ := strconv.Atoi(name)
+
+ AddNamespace(namespaceFD, pid)
+
+ syscall.Close(namespaceFD)
+}
+
+// DisplayNamespaceTree() recursively displays the namespace
+// tree rooted at 'ns'. 'level' is our current level in the
+// tree, and is used for producing suitably indented output.
+
+func DisplayNamespaceTree(ns NamespaceID, level int) {
+
+ prefix := strings.Repeat(" ", level*4)
+
+ // Display the namespace ID (device ID + inode number)
+
+ fmt.Print(prefix)
+ fmt.Println(ns)
+
+ // Print a sorted list of the PIDs that are members of this
+ // namespace. We do a bit of a dance here to produce a list
+ // of PIDs that is suitably wrapped and indented, rather than
+ // a long single-line list.
+
+ sort.Ints(NSList[ns].pids)
+ base := len(prefix) + 25
+ col := base
+ for i, p := range NSList[ns].pids {
+ if i == 0 || col >= 80 && col > base+32 {
+ col = base
+ if i > 0 {
+ fmt.Println()
+ }
+ fmt.Print(prefix)
+ fmt.Print(" ")
+ if i == 0 {
+ fmt.Print("PIDs: ")
+ } else {
+ fmt.Print(" ")
+ }
+ }
+ fmt.Print(strconv.Itoa(p) + " ")
+ col += len(strconv.Itoa(p)) + 1
+ }
+ fmt.Println()
+
+ // Recursively display the child namespaces
+
+ for _, v := range NSList[ns].children {
+ DisplayNamespaceTree(v, level+1)
+ }
+}
+
+func main() {
+
+ // Fetch a list of files from /proc
+
+ files, err := ioutil.ReadDir("/proc")
+ if err != nil {
+ fmt.Println("ioutil.Readdir(): ", err)
+ os.Exit(1)
+ }
+
+ // Process each /proc/PID (PID starts with a digit)
+
+ for _, f := range files {
+ if f.Name()[0] >= '0' && f.Name()[0] <= '9' {
+ ProcessProcFile(f.Name())
+ }
+ }
+
+ // Display the namespace tree rooted at the initial
+ // user namespace
+
+ DisplayNamespaceTree(initialNS, 0)
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter Z */
+
+/* userns_setns_test.c
+
+ Open a /proc/PID/ns/user namespace file specified on the command
+ line, and then create a child process in a new user namespace.
+ Both processes then try to setns() into the namespace identified
+ on the command line. The setns() system call requires
+ CAP_SYS_ADMIN in the target namespace.
+
+ See https://lwn.net/Articles/540087/
+*/
+#define _GNU_SOURCE
+#include <fcntl.h>
+#include <sched.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/wait.h>
+#include <sys/capability.h>
+
+/* A simple error-handling function: print an error message based
+ on the value in 'errno' and terminate the calling process */
+
+#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \
+ } while (0)
+
+static void
+display_creds_and_caps(char *msg)
+{
+ cap_t caps;
+
+ printf("%s eUID = %ld; eGID = %ld; ", msg,
+ (long) geteuid(), (long) getegid());
+
+ caps = cap_get_proc();
+ printf("capabilities: %s\n", cap_to_text(caps, NULL));
+}
+
+/* Try to join the user namespace identified by the file
+ descriptor 'fd'. 'pname' is a per-process string that
+ the caller can use to distinguish information messages
+ displayed by this function */
+
+static void
+test_setns(char *pname, int fd)
+{
+ char path[PATH_MAX];
+ ssize_t s;
+
+ /* Display caller's user namespace ID */
+
+ s = readlink("/proc/self/ns/user", path, PATH_MAX);
+ if (s == -1)
+ errExit("readlink");
+
+ printf("%s readlink(\"/proc/self/ns/user\") ==> %s\n", pname, path);
+
+ /* Attempt to join the user namespace specified by 'fd' */
+
+ if (setns(fd, CLONE_NEWUSER) == -1)
+ printf("%s setns() failed: %s\n", pname, strerror(errno));
+ else {
+ printf("%s setns() succeeded\n", pname);
+ display_creds_and_caps(pname);
+ }
+}
+
+static int /* Start function for cloned child */
+childFunc(void *arg)
+{
+ long fd = (long) arg;
+
+ usleep(100000); /* Avoid intermingling with parent's output */
+
+ /* Test whether setns() is possible from the child user namespace */
+
+ test_setns("child: ", fd);
+
+ return 0;
+}
+
+#define STACK_SIZE (1024 * 1024)
+
+int
+main(int argc, char *argv[])
+{
+ pid_t child_pid;
+ long fd;
+ char *child_stack;
+
+ if (argc != 2) {
+ fprintf(stderr, "Usage: %s /proc/PID/ns/user]\n", argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ /* Open user namespace file specified on command line */
+
+ fd = open(argv[1], O_RDONLY);
+ if (fd == -1)
+ errExit("open");
+
+ /* Create child process in new user namespace */
+
+ child_stack = malloc(STACK_SIZE);
+ if (child_stack == NULL)
+ errExit("malloc");
+
+ child_pid = clone(childFunc, child_stack + STACK_SIZE,
+ CLONE_NEWUSER | SIGCHLD, (void *) fd);
+ if (child_pid == -1)
+ errExit("clone");
+
+ /* Test whether setns() is possible from the parent user namespace */
+
+ test_setns("parent:", fd);
+ printf("\n");
+
+ if (waitpid(child_pid, NULL, 0) == -1) /* Wait for child */
+ errExit("waitpid");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE = catch_SIGHUP disc_SIGHUP job_mon \
+ orphaned_pgrp_SIGHUP handling_SIGTSTP t_setsid
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+clean :
+ ${RM} ${EXE} *.o
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 34-3 */
+
+/* catch_SIGHUP.c
+
+ Catch the SIGHUP signal and display a message.
+
+ Usage: catch_SIGHUP [x] [ > logfile 2>&1 ]
+*/
+#if ! defined(_XOPEN_SOURCE) || _XOPEN_SOURCE < 500
+#define _XOPEN_SOURCE 500
+#endif
+#include <unistd.h>
+#include <signal.h>
+#include "tlpi_hdr.h"
+
+static void
+handler(int sig)
+{
+}
+
+int
+main(int argc, char *argv[])
+{
+ pid_t childPid;
+ struct sigaction sa;
+
+ setbuf(stdout, NULL); /* Make stdout unbuffered */
+
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sa.sa_handler = handler;
+ if (sigaction(SIGHUP, &sa, NULL) == -1)
+ errExit("sigaction");
+
+ childPid = fork();
+ if (childPid == -1)
+ errExit("fork");
+
+ if (childPid == 0 && argc > 1)
+ if (setpgid(0, 0) == -1) /* Move to new process group */
+ errExit("setpgid");
+
+ printf("PID=%ld; PPID=%ld; PGID=%ld; SID=%ld\n", (long) getpid(),
+ (long) getppid(), (long) getpgrp(), (long) getsid(0));
+
+ alarm(60); /* An unhandled SIGALRM ensures this process
+ will die if nothing else terminates it */
+ for(;;) { /* Wait for signals */
+ pause();
+ printf("%ld: caught SIGHUP\n", (long) getpid());
+ }
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 34-4 */
+
+/* disc_SIGHUP.c
+
+ This program demonstrates that when a "terminal disconnect" occurs, SIGHUP
+ is sent to all the members of the foreground process group for this terminal.
+
+ Try using the following command to run this program in an X-window, and then
+ closing the window:
+
+ exec disc_SIGHUP > sig.log 2>&1
+
+ (Since the above will replace the shell with this program, it will be the
+ controlling process for the terminal.)
+*/
+#define _GNU_SOURCE /* Get strsignal() declaration from <string.h> */
+#include <string.h>
+#include <signal.h>
+#include "tlpi_hdr.h"
+
+static void /* Handler for SIGHUP */
+handler(int sig)
+{
+ printf("PID %ld: caught signal %2d (%s)\n", (long) getpid(),
+ sig, strsignal(sig));
+ /* UNSAFE (see Section 21.1.2) */
+}
+
+int
+main(int argc, char *argv[])
+{
+ pid_t parentPid, childPid;
+ int j;
+ struct sigaction sa;
+
+ if (argc < 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s {d|s}... [ > sig.log 2>&1 ]\n", argv[0]);
+
+ setbuf(stdout, NULL); /* Make stdout unbuffered */
+
+ parentPid = getpid();
+ printf("PID of parent process is: %ld\n", (long) parentPid);
+ printf("Foreground process group ID is: %ld\n",
+ (long) tcgetpgrp(STDIN_FILENO));
+
+ for (j = 1; j < argc; j++) { /* Create child processes */
+ childPid = fork();
+ if (childPid == -1)
+ errExit("fork");
+
+ if (childPid == 0) { /* If child... */
+ if (argv[j][0] == 'd') /* 'd' --> to different pgrp */
+ if (setpgid(0, 0) == -1)
+ errExit("setpgid");
+
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sa.sa_handler = handler;
+ if (sigaction(SIGHUP, &sa, NULL) == -1)
+ errExit("sigaction");
+ break; /* Child exits loop */
+ }
+ }
+
+ /* All processes fall through to here */
+
+ alarm(60); /* Ensure each process eventually terminates */
+
+ printf("PID=%ld PGID=%ld\n", (long) getpid(), (long) getpgrp());
+ for (;;)
+ pause(); /* Wait for signals */
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 34-6 */
+
+/* handling_SIGTSTP.c
+
+ Demonstrate the correct way to catch SIGTSTP and raise it again (so that a
+ parent process that is monitoring this program can see that it was stopped
+ by SIGTSTP).
+*/
+#include <signal.h>
+#include "tlpi_hdr.h"
+
+static void /* Handler for SIGTSTP */
+tstpHandler(int sig)
+{
+ sigset_t tstpMask, prevMask;
+ int savedErrno;
+ struct sigaction sa;
+
+ savedErrno = errno; /* In case we change 'errno' here */
+
+ printf("Caught SIGTSTP\n"); /* UNSAFE (see Section 21.1.2) */
+
+ if (signal(SIGTSTP, SIG_DFL) == SIG_ERR)
+ errExit("signal"); /* Set handling to default */
+
+ raise(SIGTSTP); /* Generate a further SIGTSTP */
+
+ /* Unblock SIGTSTP; the pending SIGTSTP immediately suspends the program */
+
+ sigemptyset(&tstpMask);
+ sigaddset(&tstpMask, SIGTSTP);
+ if (sigprocmask(SIG_UNBLOCK, &tstpMask, &prevMask) == -1)
+ errExit("sigprocmask");
+
+ /* Execution resumes here after SIGCONT */
+
+ if (sigprocmask(SIG_SETMASK, &prevMask, NULL) == -1)
+ errExit("sigprocmask"); /* Reblock SIGTSTP */
+
+ sigemptyset(&sa.sa_mask); /* Reestablish handler */
+ sa.sa_flags = SA_RESTART;
+ sa.sa_handler = tstpHandler;
+ if (sigaction(SIGTSTP, &sa, NULL) == -1)
+ errExit("sigaction");
+
+ printf("Exiting SIGTSTP handler\n");
+ errno = savedErrno;
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct sigaction sa;
+
+ /* Only establish handler for SIGTSTP if it is not being ignored */
+
+ if (sigaction(SIGTSTP, NULL, &sa) == -1)
+ errExit("sigaction");
+
+ if (sa.sa_handler != SIG_IGN) {
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ sa.sa_handler = tstpHandler;
+ if (sigaction(SIGTSTP, &sa, NULL) == -1)
+ errExit("sigaction");
+ }
+
+ for (;;) { /* Wait for signals */
+ pause();
+ printf("Main\n");
+ }
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 34-5 */
+
+/* job_mon.c
+
+ This program is useful for:
+
+ - demonstrating the order in which the shell creates the processes in a
+ pipeline and how it assigns a process group to these processes, and
+
+ - monitoring some of the job control signals sent to a process (group).
+
+ The program displays its PID, parent PID and process group.
+
+ Try running a pipeline consisting of a series of these commands:
+
+ job_mon | job_mon | job_mon
+
+ This will demonstrate the assignment of process groups and PIDs to
+ each process in the pipeline.
+
+ Try running this pipeline under different shells and also in the background
+ (pipeline &) to see the effect this has.
+
+ You can also try typing control-C (^C) to demonstrate that this is
+ interpreted by the terminal driver as meaning "send a signal to all the jobs
+ in the foreground process group".
+*/
+#define _GNU_SOURCE /* Get declaration of strsignal() from <string.h> */
+#include <string.h>
+#include <signal.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+static int cmdNum; /* Our position in pipeline */
+
+static void /* Handler for various signals */
+handler(int sig)
+{
+ /* UNSAFE: This handler uses non-async-signal-safe functions
+ (fprintf(), strsignal(); see Section 21.1.2) */
+
+ if (getpid() == getpgrp()) /* If process group leader */
+ fprintf(stderr, "Terminal FG process group: %ld\n",
+ (long) tcgetpgrp(STDERR_FILENO));
+ fprintf(stderr, "Process %ld (%d) received signal %d (%s)\n",
+ (long) getpid(), cmdNum, sig, strsignal(sig));
+
+ /* If we catch SIGTSTP, it won't actually stop us. Therefore we
+ raise SIGSTOP so we actually get stopped. */
+
+ if (sig == SIGTSTP)
+ raise(SIGSTOP);
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct sigaction sa;
+
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ sa.sa_handler = handler;
+ if (sigaction(SIGINT, &sa, NULL) == -1)
+ errExit("sigaction");
+ if (sigaction(SIGTSTP, &sa, NULL) == -1)
+ errExit("sigaction");
+ if (sigaction(SIGCONT, &sa, NULL) == -1)
+ errExit("sigaction");
+
+ /* If stdin is a terminal, this is the first process in pipeline:
+ print a heading and initialize message to be sent down pipe */
+
+ if (isatty(STDIN_FILENO)) {
+ fprintf(stderr, "Terminal FG process group: %ld\n",
+ (long) tcgetpgrp(STDIN_FILENO));
+ fprintf(stderr, "Command PID PPID PGRP SID\n");
+ cmdNum = 0;
+
+ } else { /* Not first in pipeline, so read message from pipe */
+ if (read(STDIN_FILENO, &cmdNum, sizeof(cmdNum)) <= 0)
+ fatal("read got EOF or error");
+ }
+
+ cmdNum++;
+ fprintf(stderr, "%4d %5ld %5ld %5ld %5ld\n", cmdNum,
+ (long) getpid(), (long) getppid(),
+ (long) getpgrp(), (long) getsid(0));
+
+ /* If not the last process, pass a message to the next process */
+
+ if (!isatty(STDOUT_FILENO)) /* If not tty, then should be pipe */
+ if (write(STDOUT_FILENO, &cmdNum, sizeof(cmdNum)) == -1)
+ errMsg("write");
+
+ for(;;) /* Wait for signals */
+ pause();
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 34-7 */
+
+/* orphaned_pgrp_SIGHUP.c
+
+ Usage: orphaned_pgrp_SIGHUP {s|p} ...
+
+ (e.g.: orphaned_pgrp_SIGHUP s p p)
+
+ Creates an orphaned process group containing one process for each
+ command-line argument. If the command-line argument corresponding to this
+ child is 's', then the child stops itself by raising SIGSTOP. If the
+ command-line argument is 'p' then the child does a pause().
+
+ This program can be used to show that when a process group that contains
+ stopped children becomes orphaned, then all members of the process group are
+ sent a SIGHUP signal, to inform them that they have been disconnected from
+ their session, followed by a SIGCONT signal, to ensure that they resume
+ execution. Try running the following commands and observing the difference
+ in output:
+
+ orphaned_pgrp_SIGHUP s p
+ orphaned_pgrp_SIGHUP p p
+*/
+#define _GNU_SOURCE /* Get declaration of strsignal() from <string.h> */
+#include <string.h>
+#include <signal.h>
+#include "tlpi_hdr.h"
+
+static void /* Signal handler */
+handler(int sig)
+{
+ printf("PID=%ld: caught signal %d (%s)\n", (long) getpid(),
+ sig, strsignal(sig)); /* UNSAFE (see Section 21.1.2) */
+}
+
+int
+main(int argc, char *argv[])
+{
+ int j;
+ struct sigaction sa;
+
+ if (argc < 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s {s|p} ...\n", argv[0]);
+
+ setbuf(stdout, NULL); /* Make stdout unbuffered */
+
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sa.sa_handler = handler;
+ if (sigaction(SIGHUP, &sa, NULL) == -1)
+ errExit("sigaction");
+ if (sigaction(SIGCONT, &sa, NULL) == -1)
+ errExit("sigaction");
+
+ printf("parent: PID=%ld, PPID=%ld, PGID=%ld, SID=%ld\n",
+ (long) getpid(), (long) getppid(),
+ (long) getpgrp(), (long) getsid(0));
+
+ /* Create one child for each command-line argument */
+
+ for (j = 1; j < argc; j++) {
+ switch (fork()) {
+ case -1:
+ errExit("fork");
+
+ case 0: /* Child */
+ printf("child: PID=%ld, PPID=%ld, PGID=%ld, SID=%ld\n",
+ (long) getpid(), (long) getppid(),
+ (long) getpgrp(), (long) getsid(0));
+
+ if (argv[j][0] == 's') { /* Stop via signal */
+ printf("PID=%ld stopping\n", (long) getpid());
+ raise(SIGSTOP);
+ } else { /* Wait for signal */
+ alarm(60); /* So we die if not SIGHUPed */
+ printf("PID=%ld pausing\n", (long) getpid());
+ pause();
+ }
+
+ _exit(EXIT_SUCCESS);
+
+ default: /* Parent carries on round loop */
+ break;
+ }
+ }
+
+ /* Parent falls through to here after creating all children */
+
+ sleep(3); /* Give children a chance to start */
+ printf("parent exiting\n");
+ exit(EXIT_SUCCESS); /* And orphan them and their group */
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 34-2 */
+
+/* t_setsid.c
+
+ Demonstrate the use of setsid(2) to start a new session.
+*/
+#if ! defined(_XOPEN_SOURCE) || _XOPEN_SOURCE < 500
+#define _XOPEN_SOURCE 500
+#endif
+#include <unistd.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ if (fork() != 0) /* Exit if parent, or on error */
+ _exit(EXIT_SUCCESS);
+
+ if (setsid() == -1)
+ errExit("setsid");
+
+ printf("PID=%ld, PGID=%ld, SID=%ld\n", (long) getpid(),
+ (long) getpgrp(), (long) getsid(0));
+
+ /* Following should fail, since we don't have a controlling terminal */
+
+ if (open("/dev/tty", O_RDWR) == -1)
+ errExit("open /dev/tty");
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE = change_case fifo_seqnum_client fifo_seqnum_server \
+ pipe_ls_wc pipe_sync popen_glob simple_pipe
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+fifo_seqnum_client.o fifo_seqnum_server.o : fifo_seqnum.h
+
+clean :
+ ${RM} ${EXE} *.o
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 44-1 */
+
+/* change_case.c
+
+ Demonstrate the use of two pipes for bidirectional communication
+ between a parent and child process. The parent reads text from
+ standard input and sends it to the child via one of the pipes.
+ The child reads text from this pipe, converts it to uppercase, and
+ sends it back to the parent using the other pipe. The parent reads
+ the text returned by the child and echoes it on standard output.
+*/
+#include <ctype.h>
+#include "tlpi_hdr.h"
+
+#define BUF_SIZE 100 /* Should be <= PIPE_BUF bytes */
+
+int
+main(int argc, char *argv[])
+{
+ char buf[BUF_SIZE];
+ int outbound[2]; /* Pipe to send data from parent to child */
+ int inbound[2]; /* Pipe to send data from child to parent */
+ int j;
+ ssize_t cnt;
+
+ if (pipe(outbound) == -1)
+ errExit("pipe");
+ if (pipe(inbound) == -1)
+ errExit("pipe");
+
+ switch (fork()) {
+ case -1:
+ errExit("fork");
+
+ case 0: /* Child */
+
+ /* Close unused pipe descriptors */
+
+ if (close(outbound[1]) == -1)
+ errExit("close");
+ if (close(inbound[0]) == -1)
+ errExit("close");
+
+ /* Read data from outbound pipe, convert to uppercase,
+ and send back to parent on inbound pipe */
+
+ while ((cnt = read(outbound[0], buf, BUF_SIZE)) > 0) {
+ for (j = 0; j < cnt; j++)
+ buf[j] = toupper((unsigned char) buf[j]);
+ if (write(inbound[1], buf, cnt) != cnt)
+ fatal("failed/partial write(): inbound pipe");
+ }
+
+ if (cnt == -1)
+ errExit("read");
+ _exit(EXIT_SUCCESS);
+
+ default:
+
+ /* Close unused pipe descriptors */
+
+ if (close(outbound[0]) == -1)
+ errExit("close");
+ if (close(inbound[1]) == -1)
+ errExit("close");
+
+ /* Read data from stdin, send to the child via the
+ outbound pipe, read the results back from the child
+ on the inbound pipe, and print them on stdout */
+
+ while ((cnt = read(STDIN_FILENO, buf, BUF_SIZE)) > 0) {
+ if (write(outbound[1], buf, cnt) != cnt)
+ fatal("failed/partial write(): outbound pipe");
+
+ cnt = read(inbound[0], buf, BUF_SIZE);
+ if (cnt == -1)
+ errExit("read");
+ if (cnt > 0)
+ if (write(STDOUT_FILENO, buf, cnt) != cnt)
+ fatal("failed/partial write(): STDOUT_FILENO");
+ }
+
+ if (cnt == -1)
+ errExit("read");
+
+ /* Exiting will close write end of outbound pipe, so that
+ child see EOF */
+
+ exit(EXIT_SUCCESS);
+ }
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 44-6 */
+
+/* fifo_seqnum.h
+
+ Header file used by fifo_seqnum_server.c and fifo_seqnum_client.c
+
+ These programs create FIFOS in /tmp. This makes it easy to compile and
+ run the programs. However, for a security reasons, a real-world
+ application should never create sensitive files in /tmp. (As a simple of
+ example of the kind of security problems that can result, a malicious
+ user could create a FIFO using the name defined in SERVER_FIFO, and
+ thereby cause a denial of service attack against this application.
+ See Section 38.7 of "The Linux Programming Interface" for more details
+ on this subject.)
+*/
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+#define SERVER_FIFO "/tmp/seqnum_sv"
+ /* Well-known name for server's FIFO */
+#define CLIENT_FIFO_TEMPLATE "/tmp/seqnum_cl.%ld"
+ /* Template for building client FIFO name */
+#define CLIENT_FIFO_NAME_LEN (sizeof(CLIENT_FIFO_TEMPLATE) + 20)
+ /* Space required for client FIFO pathname
+ (+20 as a generous allowance for the PID) */
+
+struct request { /* Request (client --> server) */
+ pid_t pid; /* PID of client */
+ int seqLen; /* Length of desired sequence */
+};
+
+struct response { /* Response (server --> client) */
+ int seqNum; /* Start of sequence */
+};
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 44-8 */
+
+/* fifo_seqnum_client.c
+
+ A simple client that uses a well-known FIFO to request a (trivial)
+ "sequence number service". This client creates its own FIFO (using a
+ convention agreed upon by client and server) which is used to receive a reply
+ from the server. The client then sends a request to the server consisting of
+ its PID and the length of the sequence it wishes to be allocated. The client
+ then reads the server's response and displays it on stdout.
+
+ See fifo_seqnum.h for the format of request and response messages.
+
+ The server is in fifo_seqnum_server.c.
+*/
+#include "fifo_seqnum.h"
+
+static char clientFifo[CLIENT_FIFO_NAME_LEN];
+
+static void /* Invoked on exit to delete client FIFO */
+removeFifo(void)
+{
+ unlink(clientFifo);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int serverFd, clientFd;
+ struct request req;
+ struct response resp;
+
+ if (argc > 1 && strcmp(argv[1], "--help") == 0)
+ usageErr("%s [seq-len...]\n", argv[0]);
+
+ /* Create our FIFO (before sending request, to avoid a race) */
+
+ umask(0); /* So we get the permissions we want */
+ snprintf(clientFifo, CLIENT_FIFO_NAME_LEN, CLIENT_FIFO_TEMPLATE,
+ (long) getpid());
+ if (mkfifo(clientFifo, S_IRUSR | S_IWUSR | S_IWGRP) == -1
+ && errno != EEXIST)
+ errExit("mkfifo %s", clientFifo);
+
+ if (atexit(removeFifo) != 0)
+ errExit("atexit");
+
+ /* Construct request message, open server FIFO, and send message */
+
+ req.pid = getpid();
+ req.seqLen = (argc > 1) ? getInt(argv[1], GN_GT_0, "seq-len") : 1;
+
+ serverFd = open(SERVER_FIFO, O_WRONLY);
+ if (serverFd == -1)
+ errExit("open %s", SERVER_FIFO);
+
+ if (write(serverFd, &req, sizeof(struct request)) !=
+ sizeof(struct request))
+ fatal("Can't write to server");
+
+ /* Open our FIFO, read and display response */
+
+ clientFd = open(clientFifo, O_RDONLY);
+ if (clientFd == -1)
+ errExit("open %s", clientFifo);
+
+ if (read(clientFd, &resp, sizeof(struct response))
+ != sizeof(struct response))
+ fatal("Can't read response from server");
+
+ printf("%d\n", resp.seqNum);
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 44-7 */
+
+/* fifo_seqnum_server.c
+
+ An example of a server using a FIFO to handle client requests.
+ The "service" provided is the allocation of unique sequential
+ numbers. Each client submits a request consisting of its PID, and
+ the length of the sequence it is to be allocated by the server.
+ The PID is used by both the server and the client to construct
+ the name of the FIFO used by the client for receiving responses.
+
+ The server reads each client request, and uses the client's FIFO
+ to send back the starting value of the sequence allocated to that
+ client. The server then increments its counter of used numbers
+ by the length specified in the client request.
+
+ See fifo_seqnum.h for the format of request and response messages.
+
+ The client is in fifo_seqnum_client.c.
+*/
+#include <signal.h>
+#include "fifo_seqnum.h"
+
+int
+main(int argc, char *argv[])
+{
+ int serverFd, dummyFd, clientFd;
+ char clientFifo[CLIENT_FIFO_NAME_LEN];
+ struct request req;
+ struct response resp;
+ int seqNum = 0; /* This is our "service" */
+
+ /* Create well-known FIFO, and open it for reading */
+
+ umask(0); /* So we get the permissions we want */
+ if (mkfifo(SERVER_FIFO, S_IRUSR | S_IWUSR | S_IWGRP) == -1
+ && errno != EEXIST)
+ errExit("mkfifo %s", SERVER_FIFO);
+ serverFd = open(SERVER_FIFO, O_RDONLY);
+ if (serverFd == -1)
+ errExit("open %s", SERVER_FIFO);
+
+ /* Open an extra write descriptor, so that we never see EOF */
+
+ dummyFd = open(SERVER_FIFO, O_WRONLY);
+ if (dummyFd == -1)
+ errExit("open %s", SERVER_FIFO);
+
+ /* Let's find out about broken client pipe via failed write() */
+
+ if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) errExit("signal");
+
+ for (;;) { /* Read requests and send responses */
+ if (read(serverFd, &req, sizeof(struct request))
+ != sizeof(struct request)) {
+ fprintf(stderr, "Error reading request; discarding\n");
+ continue; /* Either partial read or error */
+ }
+
+ /* Open client FIFO (previously created by client) */
+
+ snprintf(clientFifo, CLIENT_FIFO_NAME_LEN, CLIENT_FIFO_TEMPLATE,
+ (long) req.pid);
+ clientFd = open(clientFifo, O_WRONLY);
+ if (clientFd == -1) { /* Open failed, give up on client */
+ errMsg("open %s", clientFifo);
+ continue;
+ }
+
+ /* Send response and close FIFO */
+
+ resp.seqNum = seqNum;
+ if (write(clientFd, &resp, sizeof(struct response))
+ != sizeof(struct response))
+ fprintf(stderr, "Error writing to FIFO %s\n", clientFifo);
+ if (close(clientFd) == -1)
+ errMsg("close");
+
+ seqNum += req.seqLen; /* Update our sequence number */
+ }
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 44-4 */
+
+/* pipe_ls_wc.c
+
+ Demonstrate the use of a pipe to connect two filters. We use fork()
+ to create two children. The first one execs ls(1), which writes to
+ the pipe, the second execs wc(1) to read from the pipe.
+*/
+#include <sys/wait.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int pfd[2]; /* Pipe file descriptors */
+
+ if (pipe(pfd) == -1) /* Create pipe */
+ errExit("pipe");
+
+ switch (fork()) {
+ case -1:
+ errExit("fork");
+
+ case 0: /* First child: exec 'ls' to write to pipe */
+ if (close(pfd[0]) == -1) /* Read end is unused */
+ errExit("close 1");
+
+ /* Duplicate stdout on write end of pipe; close duplicated descriptor */
+
+ if (pfd[1] != STDOUT_FILENO) { /* Defensive check */
+ if (dup2(pfd[1], STDOUT_FILENO) == -1)
+ errExit("dup2 1");
+ if (close(pfd[1]) == -1)
+ errExit("close 2");
+ }
+
+ execlp("ls", "ls", (char *) NULL); /* Writes to pipe */
+ errExit("execlp ls");
+
+ default: /* Parent falls through to create next child */
+ break;
+ }
+
+ switch (fork()) {
+ case -1:
+ errExit("fork");
+
+ case 0: /* Second child: exec 'wc' to read from pipe */
+ if (close(pfd[1]) == -1) /* Write end is unused */
+ errExit("close 3");
+
+ /* Duplicate stdin on read end of pipe; close duplicated descriptor */
+
+ if (pfd[0] != STDIN_FILENO) { /* Defensive check */
+ if (dup2(pfd[0], STDIN_FILENO) == -1)
+ errExit("dup2 2");
+ if (close(pfd[0]) == -1)
+ errExit("close 4");
+ }
+
+ execlp("wc", "wc", "-l", (char *) NULL);
+ errExit("execlp wc");
+
+ default: /* Parent falls through */
+ break;
+ }
+
+ /* Parent closes unused file descriptors for pipe, and waits for children */
+
+ if (close(pfd[0]) == -1)
+ errExit("close 5");
+ if (close(pfd[1]) == -1)
+ errExit("close 6");
+ if (wait(NULL) == -1)
+ errExit("wait 1");
+ if (wait(NULL) == -1)
+ errExit("wait 2");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 44-3 */
+
+/* pipe_sync.c
+
+ Show how pipes can be used for synchronizing the actions of a parent and
+ multiple child processes.
+
+ Usage: pipe_sync sleep-time...
+
+ After creating a pipe, the program creates one child for each command-line
+ argument. Each child simulates doing some work by sleeping for the number of
+ seconds specified in the corresponding command-line argument. When it has
+ finished doing its "work", each child closes its file descriptor for the
+ write end of the pipe; the parent can see that all children have finished
+ their work when it sees end-of-file on the read end of the pipe.
+*/
+#include "curr_time.h" /* Declaration of currTime() */
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int pfd[2]; /* Process synchronization pipe */
+ int j, dummy;
+
+ if (argc < 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s sleep-time...\n", argv[0]);
+
+ setbuf(stdout, NULL); /* Make stdout unbuffered, since we
+ terminate child with _exit() */
+ printf("%s Parent started\n", currTime("%T"));
+
+ if (pipe(pfd) == -1)
+ errExit("pipe");
+
+ for (j = 1; j < argc; j++) {
+ switch (fork()) {
+ case -1:
+ errExit("fork %d", j);
+
+ case 0: /* Child */
+ if (close(pfd[0]) == -1) /* Read end is unused */
+ errExit("close");
+
+ /* Child does some work, and lets parent know it's done */
+
+ sleep(getInt(argv[j], GN_NONNEG, "sleep-time"));
+ /* Simulate processing */
+ printf("%s Child %d (PID=%ld) closing pipe\n",
+ currTime("%T"), j, (long) getpid());
+ if (close(pfd[1]) == -1)
+ errExit("close");
+
+ /* Child now carries on to do other things... */
+
+ _exit(EXIT_SUCCESS);
+
+ default: /* Parent loops to create next child */
+ break;
+ }
+ }
+
+ /* Parent comes here; close write end of pipe so we can see EOF */
+
+ if (close(pfd[1]) == -1) /* Write end is unused */
+ errExit("close");
+
+ /* Parent may do other work, then synchronizes with children */
+
+ if (read(pfd[0], &dummy, 1) != 0)
+ fatal("parent didn't get EOF");
+ printf("%s Parent ready to go\n", currTime("%T"));
+
+ /* Parent can now carry on to do other things... */
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 44-5 */
+
+/* popen_glob.c
+
+ Demonstrate the use of popen() and pclose().
+
+ This program reads filename wildcard patterns from standard input and
+ passes each pattern to a popen() call that returns the output from ls(1)
+ for the wildcard pattern. The program displays the returned output.
+*/
+#include <ctype.h>
+#include <limits.h>
+#include "print_wait_status.h" /* For printWaitStatus() */
+#include "tlpi_hdr.h"
+
+#define POPEN_FMT "/bin/ls -d %s 2> /dev/null"
+#define PAT_SIZE 50
+#define PCMD_BUF_SIZE (sizeof(POPEN_FMT) + PAT_SIZE)
+
+int
+main(int argc, char *argv[])
+{
+ char pat[PAT_SIZE]; /* Pattern for globbing */
+ char popenCmd[PCMD_BUF_SIZE];
+ FILE *fp; /* File stream returned by popen() */
+ Boolean badPattern; /* Invalid characters in 'pat'? */
+ int len, status, fileCnt, j;
+ char pathname[PATH_MAX];
+
+ for (;;) { /* Read pattern, display results of globbing */
+ printf("pattern: ");
+ fflush(stdout);
+ if (fgets(pat, PAT_SIZE, stdin) == NULL)
+ break; /* EOF */
+ len = strlen(pat);
+ if (len <= 1) /* Empty line */
+ continue;
+
+ if (pat[len - 1] == '\n') /* Strip trailing newline */
+ pat[len - 1] = '\0';
+
+ /* Ensure that the pattern contains only valid characters,
+ i.e., letters, digits, underscore, dot, and the shell
+ globbing characters. (Our definition of valid is more
+ restrictive than the shell, which permits other characters
+ to be included in a filename if they are quoted.) */
+
+ for (j = 0, badPattern = FALSE; j < len && !badPattern; j++)
+ if (!isalnum((unsigned char) pat[j]) &&
+ strchr("_*?[^-].", pat[j]) == NULL)
+ badPattern = TRUE;
+
+ if (badPattern) {
+ printf("Bad pattern character: %c\n", pat[j - 1]);
+ continue;
+ }
+
+ /* Build and execute command to glob 'pat' */
+
+ snprintf(popenCmd, PCMD_BUF_SIZE, POPEN_FMT, pat);
+
+ fp = popen(popenCmd, "r");
+ if (fp == NULL) {
+ printf("popen() failed\n");
+ continue;
+ }
+
+ /* Read resulting list of pathnames until EOF */
+
+ fileCnt = 0;
+ while (fgets(pathname, PATH_MAX, fp) != NULL) {
+ printf("%s", pathname);
+ fileCnt++;
+ }
+
+ /* Close pipe, fetch and display termination status */
+
+ status = pclose(fp);
+ printf(" %d matching file%s\n", fileCnt, (fileCnt != 1) ? "s" : "");
+ printf(" pclose() status = %#x\n", (unsigned int) status);
+ if (status != -1)
+ printWaitStatus("\t", status);
+ }
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 44-2 */
+
+/* simple_pipe.c
+
+ Simple demonstration of the use of a pipe to communicate
+ between a parent and a child process.
+
+ Usage: simple_pipe "string"
+
+ The program creates a pipe, and then calls fork() to create a child process.
+ After the fork(), the parent writes the string given on the command line
+ to the pipe, and the child uses a loop to read data from the pipe and
+ print it on standard output.
+*/
+#include <sys/wait.h>
+#include "tlpi_hdr.h"
+
+#define BUF_SIZE 10
+
+int
+main(int argc, char *argv[])
+{
+ int pfd[2]; /* Pipe file descriptors */
+ char buf[BUF_SIZE];
+ ssize_t numRead;
+
+ if (argc != 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s string\n", argv[0]);
+
+ if (pipe(pfd) == -1) /* Create the pipe */
+ errExit("pipe");
+
+ switch (fork()) {
+ case -1:
+ errExit("fork");
+
+ case 0: /* Child - reads from pipe */
+ if (close(pfd[1]) == -1) /* Write end is unused */
+ errExit("close - child");
+
+ for (;;) { /* Read data from pipe, echo on stdout */
+ numRead = read(pfd[0], buf, BUF_SIZE);
+ if (numRead == -1)
+ errExit("read");
+ if (numRead == 0)
+ break; /* End-of-file */
+ if (write(STDOUT_FILENO, buf, numRead) != numRead)
+ fatal("child - partial/failed write");
+ }
+
+ write(STDOUT_FILENO, "\n", 1);
+ if (close(pfd[0]) == -1)
+ errExit("close");
+ _exit(EXIT_SUCCESS);
+
+ default: /* Parent - writes to pipe */
+ if (close(pfd[0]) == -1) /* Read end is unused */
+ errExit("close - parent");
+
+ if (write(pfd[1], argv[1], strlen(argv[1])) != strlen(argv[1]))
+ fatal("parent - partial/failed write");
+
+ if (close(pfd[1]) == -1) /* Child will see EOF */
+ errExit("close");
+ wait(NULL); /* Wait for child to finish */
+ exit(EXIT_SUCCESS);
+ }
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE = mq_notify_sig mq_notify_sigwaitinfo mq_notify_thread \
+ mq_notify_via_signal mq_notify_via_thread \
+ pmsg_create pmsg_getattr pmsg_receive pmsg_send pmsg_unlink
+
+LINUX_EXE =
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+LDLIBS = ${IMPL_LDLIBS} ${LINUX_LIBRT}
+ # All of the programs in this directory need the
+ # realtime library, librt.
+
+clean :
+ ${RM} ${EXE} *.o
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 52-6 */
+
+/* mq_notify_sig.c
+
+ Usage: mq_notify_sig mq-name
+
+ Demonstrate message notification via signals (catching the signals with
+ a signal handler) on a POSIX message queue.
+*/
+#include <signal.h>
+#include <mqueue.h>
+#include <fcntl.h> /* For definition of O_NONBLOCK */
+#include "tlpi_hdr.h"
+
+#define NOTIFY_SIG SIGUSR1
+
+static void
+handler(int sig)
+{
+ /* Just interrupt sigsuspend() */
+}
+
+/* This program does not handle the case where a message already exists on
+ the queue by the time the first attempt is made to register for message
+ notification. In that case, the program would never receive a notification.
+ See mq_notify_via_signal.c for an example of how to deal with that case. */
+
+int
+main(int argc, char *argv[])
+{
+ struct sigevent sev;
+ mqd_t mqd;
+ struct mq_attr attr;
+ void *buffer;
+ ssize_t numRead;
+ sigset_t blockMask, emptyMask;
+ struct sigaction sa;
+
+ if (argc != 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s mq-name\n", argv[0]);
+
+ mqd = mq_open(argv[1], O_RDONLY | O_NONBLOCK);
+ if (mqd == (mqd_t) -1)
+ errExit("mq_open");
+
+ /* Determine mq_msgsize for message queue, and allocate an input buffer
+ of that size */
+
+ if (mq_getattr(mqd, &attr) == -1)
+ errExit("mq_getattr");
+
+ buffer = malloc(attr.mq_msgsize);
+ if (buffer == NULL)
+ errExit("malloc");
+
+ /* Block the notification signal and establish a handler for it */
+
+ sigemptyset(&blockMask);
+ sigaddset(&blockMask, NOTIFY_SIG);
+ if (sigprocmask(SIG_BLOCK, &blockMask, NULL) == -1)
+ errExit("sigprocmask");
+
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sa.sa_handler = handler;
+ if (sigaction(NOTIFY_SIG, &sa, NULL) == -1)
+ errExit("sigaction");
+
+ /* Register for message notification via a signal */
+
+ sev.sigev_notify = SIGEV_SIGNAL;
+ sev.sigev_signo = NOTIFY_SIG;
+ if (mq_notify(mqd, &sev) == -1)
+ errExit("mq_notify");
+
+ sigemptyset(&emptyMask);
+
+ for (;;) {
+ sigsuspend(&emptyMask); /* Wait for notification signal */
+
+ /* Reregister for message notification */
+
+ if (mq_notify(mqd, &sev) == -1)
+ errExit("mq_notify");
+
+ while ((numRead = mq_receive(mqd, buffer, attr.mq_msgsize, NULL)) >= 0)
+ printf("Read %ld bytes\n", (long) numRead);
+
+ if (errno != EAGAIN) /* Unexpected error */
+ errExit("mq_receive");
+ }
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 52-6 */
+
+/* mq_notify_sigwaitinfo.c
+
+ Usage: mq_notify_sigwaitinfo mq-name
+
+ Demonstrate message notification via signals (accepting the signals with
+ sigwaitinfo()) on a POSIX message queue.
+*/
+#define _POSIX_C_SOURCE 199309
+#include <signal.h>
+#include <mqueue.h>
+#include <fcntl.h> /* For definition of O_NONBLOCK */
+#include "tlpi_hdr.h"
+
+#define NOTIFY_SIG SIGRTMIN /* Signal used for message notifications */
+
+/* This program does not handle the case where a message already exists on
+ the queue by the time the first attempt is made to register for message
+ notification. In that case, the program would never receive a notification.
+ Compare mq_notify_sig.c and mq_notify_via_signal.c for some hints on how
+ this program might be modified to handle that case. */
+
+int
+main(int argc, char *argv[])
+{
+ struct sigevent sev;
+ mqd_t mqd;
+ struct mq_attr attr;
+ void *buffer;
+ ssize_t numRead;
+ sigset_t blockMask;
+ siginfo_t si;
+
+ if (argc != 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s mq-name\n", argv[0]);
+
+ mqd = mq_open(argv[1], O_RDONLY | O_NONBLOCK);
+ if (mqd == (mqd_t) -1)
+ errExit("mq_open");
+
+ /* Determine mq_msgsize for message queue, and allocate an input buffer
+ of that size */
+
+ if (mq_getattr(mqd, &attr) == -1)
+ errExit("mq_getattr");
+ buffer = malloc(attr.mq_msgsize);
+ if (buffer == NULL)
+ errExit("malloc");
+
+ /* Block the signal that we'll accept using sigwaitinfo() */
+
+ sigemptyset(&blockMask);
+ sigaddset(&blockMask, NOTIFY_SIG);
+ if (sigprocmask(SIG_BLOCK, &blockMask, NULL) == -1)
+ errExit("sigprocmask");
+
+ /* Set up message notification using the signal NOTIFY_SIG */
+
+ sev.sigev_notify = SIGEV_SIGNAL;
+ sev.sigev_signo = NOTIFY_SIG;
+ sev.sigev_value.sival_ptr = &mqd;
+ /* This allows us to obtain a pointer to 'mqd' in the
+ siginfo_t structure returned by sigwaitinfo() */
+
+ if (mq_notify(mqd, &sev) == -1)
+ errExit("mq_notify");
+
+ for (;;) {
+
+ /* Wait for a signal; when it is received, display associated
+ information */
+
+ if (sigwaitinfo(&blockMask, &si) == -1)
+ errExit("sigwaitinfo");
+
+ printf("Accepted signal:\n");
+ printf(" si_signo = %d\n", si.si_signo);
+ printf(" si_pid = %ld\n", (long) si.si_pid);
+ printf(" si_uid = %ld\n", (long) si.si_uid);
+ printf(" si_code = %d (%s)\n", si.si_code,
+ (si.si_code == SI_MESGQ) ? "SI_MESGQ" : "???");
+ printf(" *sival_ptr = %p\n\n", si.si_value.sival_ptr);
+
+ /* Reestablish message notification */
+
+ if (mq_notify(mqd, &sev) == -1)
+ errExit("mq_notify");
+
+ /* Although only one signal might have been queued (if NOTIFY_SIG
+ is a standard signal) we might have received multiple messages,
+ so use nonblocking mq_receive() calls inside a loop to read
+ as many messages as possible. */
+
+ while ((numRead = mq_receive(mqd, buffer, attr.mq_msgsize, NULL)) >= 0)
+ printf("Read %ld bytes\n", (long) numRead);
+
+ if (errno != EAGAIN) /* Unexpected error */
+ errExit("mq_receive");
+ }
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 52-7 */
+
+/* mq_notify_thread.c
+
+ Demonstrate message notification via threads on a POSIX message queue.
+*/
+#include <pthread.h>
+#include <mqueue.h>
+#include <fcntl.h> /* For definition of O_NONBLOCK */
+#include "tlpi_hdr.h"
+
+/* This program does not handle the case where a message already exists on
+ the queue by the time the first attempt is made to register for message
+ notification. In that case, the program would never receive a notification.
+ See mq_notify_via_thread.c for an example of how to deal with that case. */
+
+static void notifySetup(mqd_t *mqdp);
+
+static void /* Thread notification function */
+threadFunc(union sigval sv)
+{
+ ssize_t numRead;
+ mqd_t *mqdp;
+ void *buffer;
+ struct mq_attr attr;
+
+ mqdp = sv.sival_ptr;
+
+ /* Determine mq_msgsize for message queue, and allocate an input buffer
+ of that size */
+
+ if (mq_getattr(*mqdp, &attr) == -1)
+ errExit("mq_getattr");
+
+ buffer = malloc(attr.mq_msgsize);
+ if (buffer == NULL)
+ errExit("malloc");
+
+ /* Reregister for message notification */
+
+ notifySetup(mqdp);
+
+ while ((numRead = mq_receive(*mqdp, buffer, attr.mq_msgsize, NULL)) >= 0)
+ printf("Read %ld bytes\n", (long) numRead);
+
+ if (errno != EAGAIN) /* Unexpected error */
+ errExit("mq_receive");
+
+ free(buffer);
+}
+
+static void
+notifySetup(mqd_t *mqdp)
+{
+ struct sigevent sev;
+
+ sev.sigev_notify = SIGEV_THREAD; /* Notify via thread */
+ sev.sigev_notify_function = threadFunc;
+ sev.sigev_notify_attributes = NULL;
+ /* Could be pointer to pthread_attr_t structure */
+ sev.sigev_value.sival_ptr = mqdp; /* Argument to threadFunc() */
+
+ if (mq_notify(*mqdp, &sev) == -1)
+ errExit("mq_notify");
+}
+
+int
+main(int argc, char *argv[])
+{
+ mqd_t mqd;
+
+ if (argc != 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s mq-name\n", argv[0]);
+
+ mqd = mq_open(argv[1], O_RDONLY | O_NONBLOCK);
+ if (mqd == (mqd_t) -1)
+ errExit("mq_open");
+
+ notifySetup(&mqd);
+ pause(); /* Wait for notifications via thread function */
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 52 */
+
+/* mq_notify_via_signal.c
+
+ Usage: mq_notify_via_signal /mq-name
+
+ Demonstrate message notification via signals (catching the signals with
+ a signal handler) on a POSIX message queue.
+
+ See also mq_notify_sig.c.
+*/
+#include <signal.h>
+#include <mqueue.h>
+#include <fcntl.h> /* For definition of O_NONBLOCK */
+#include "tlpi_hdr.h"
+
+#define NOTIFY_SIG SIGUSR1
+
+static volatile sig_atomic_t gotSig = 1; /* See comment in main() */
+
+/* Handler for message notification signal */
+
+static void
+handler(int sig)
+{
+ gotSig = 1;
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct sigevent sev;
+ mqd_t mqd;
+ struct sigaction sa;
+ int j;
+ char *msg;
+ ssize_t numRead;
+ struct mq_attr attr;
+
+ if (argc != 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s /mq-name\n", argv[0]);
+
+ /* Open the (existing) queue in nonblocking mode so that we can drain
+ messages from it without blocking once the queue has been emptied */
+
+ mqd = mq_open(argv[1], O_RDONLY | O_NONBLOCK);
+ if (mqd == (mqd_t) -1)
+ errExit("mq_open");
+
+ /* Establish handler for notification signal */
+
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sa.sa_handler = handler;
+ if (sigaction(NOTIFY_SIG, &sa, NULL) == -1)
+ errExit("sigaction");
+
+ /* Determine mq_msgsize for message queue, and allocate an input buffer
+ of that size */
+
+ if (mq_getattr(mqd, &attr) == -1)
+ errExit("mq_getattr");
+
+ msg = malloc(attr.mq_msgsize);
+ if (msg == NULL)
+ errExit("malloc");
+
+ /* Possibly, a message had already been queued by the time we enter
+ the loop below. By initializing 'gotSig' to 1 above, we trigger the
+ program to make the initial registration for notification and force
+ the queue to be drained of any messages on the first loop iteration. */
+
+ for (j = 0; ; j++) {
+ if (gotSig) {
+ gotSig = 0;
+
+ /* Register for message notification */
+
+ sev.sigev_notify = SIGEV_SIGNAL;
+ sev.sigev_signo = NOTIFY_SIG;
+ if (mq_notify(mqd, &sev) == -1)
+ errExit("mq_notify");
+
+ /* Drain all messages from the queue */
+
+ while ((numRead = mq_receive(mqd, msg,
+ attr.mq_msgsize, NULL)) >= 0) {
+ /* Do whatever processing is required for each message */
+
+ printf("Read %ld bytes\n", (long) numRead);
+ }
+ if (errno != EAGAIN) /* Unexpected error */
+ errExit("mq_receive");
+ }
+
+ printf("j = %d\n", j);
+ sleep(5); /* Do some "work" */
+ }
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 52 */
+
+/* mq_notify_via_thread.c
+
+ Demonstrate message notification via threads on a POSIX message queue.
+
+ See also mq_notify_thread.c.
+*/
+#include <pthread.h>
+#include <mqueue.h>
+#include <fcntl.h> /* For definition of O_NONBLOCK */
+#include "tlpi_hdr.h"
+
+static void notifySetup(mqd_t *mqdp);
+
+/* Drain all messages from the queue referred to by 'mqd' */
+
+static void
+drainQueue(mqd_t mqd)
+{
+ ssize_t numRead;
+ char *msg;
+ struct mq_attr attr;
+
+ /* Determine mq_msgsize for message queue, and allocate
+ a buffer of that size */
+
+ if (mq_getattr(mqd, &attr) == -1)
+ errExit("mq_getattr");
+
+ msg = malloc(attr.mq_msgsize);
+ if (msg == NULL)
+ errExit("malloc");
+
+ while ((numRead = mq_receive(mqd, msg, attr.mq_msgsize, NULL)) >= 0) {
+
+ /* Do whatever processing is required for message */
+
+ printf("Read %ld bytes\n", (long) numRead);
+ }
+
+ if (errno != EAGAIN) /* Unexpected error */
+ errExit("mq_receive");
+
+ free(msg);
+}
+
+static void /* Thread notification function */
+threadFunc(union sigval sv)
+{
+ mqd_t *mqdp;
+
+ mqdp = sv.sival_ptr;
+
+ /* Reregister for message notification */
+
+ notifySetup(mqdp);
+ drainQueue(*mqdp);
+}
+
+static void
+notifySetup(mqd_t *mqdp)
+{
+ struct sigevent sev;
+
+ sev.sigev_notify = SIGEV_THREAD; /* Notify via thread */
+ sev.sigev_notify_function = threadFunc;
+ sev.sigev_notify_attributes = NULL;
+ /* Could be pointer to pthread_attr_t structure */
+ sev.sigev_value.sival_ptr = mqdp; /* Argument to threadFunc() */
+
+ if (mq_notify(*mqdp, &sev) == -1)
+ errExit("mq_notify");
+}
+
+int
+main(int argc, char *argv[])
+{
+ mqd_t mqd;
+
+ if (argc != 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s /mq-name\n", argv[0]);
+
+ mqd = mq_open(argv[1], O_RDONLY | O_NONBLOCK);
+ if (mqd == (mqd_t) -1)
+ errExit("mq_open");
+
+ notifySetup(&mqd);
+ drainQueue(mqd); /* Handle possibility that messages were already
+ queued before we established notification */
+
+ pause(); /* Wait for notifications via thread function */
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 52-2 */
+
+/* pmsg_create.c
+
+ Create a POSIX message queue.
+
+ Usage as shown in usageError().
+
+ Linux supports POSIX message queues since kernel 2.6.6.
+*/
+#include <mqueue.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+static void
+usageError(const char *progName)
+{
+ fprintf(stderr, "Usage: %s [-cx] [-m maxmsg] [-s msgsize] mq-name "
+ "[octal-perms]\n", progName);
+ fprintf(stderr, " -c Create queue (O_CREAT)\n");
+ fprintf(stderr, " -m maxmsg Set maximum # of messages\n");
+ fprintf(stderr, " -s msgsize Set maximum message size\n");
+ fprintf(stderr, " -x Create exclusively (O_EXCL)\n");
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int flags, opt;
+ mode_t perms;
+ mqd_t mqd;
+ struct mq_attr attr, *attrp;
+
+ /* If 'attrp' is NULL, mq_open() uses default attributes. If an
+ option specifying a message queue attribute is supplied on the
+ command line, we save the attribute in 'attr' and set 'attrp'
+ pointing to 'attr'. We assign some (arbitrary) default values
+ to the fields of 'attr' in case the user specifies the value
+ for one of the queue attributes, but not the other. */
+
+ attrp = NULL;
+ attr.mq_maxmsg = 10;
+ attr.mq_msgsize = 2048;
+ flags = O_RDWR;
+
+ /* Parse command-line options */
+
+ while ((opt = getopt(argc, argv, "cm:s:x")) != -1) {
+ switch (opt) {
+ case 'c':
+ flags |= O_CREAT;
+ break;
+
+ case 'm':
+ attr.mq_maxmsg = atoi(optarg);
+ attrp = &attr;
+ break;
+
+ case 's':
+ attr.mq_msgsize = atoi(optarg);
+ attrp = &attr;
+ break;
+
+ case 'x':
+ flags |= O_EXCL;
+ break;
+
+ default:
+ usageError(argv[0]);
+ }
+ }
+
+ if (optind >= argc)
+ usageError(argv[0]);
+
+ perms = (argc <= optind + 1) ? (S_IRUSR | S_IWUSR) :
+ getInt(argv[optind + 1], GN_BASE_8, "octal-perms");
+
+ mqd = mq_open(argv[optind], flags, perms, attrp);
+ if (mqd == (mqd_t) -1)
+ errExit("mq_open");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 52-3 */
+
+/* pmsg_getattr.c
+
+ Display attributes of a POSIX message queue.
+
+ Linux supports POSIX message queues since kernel 2.6.6.
+*/
+#include <mqueue.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ mqd_t mqd;
+ struct mq_attr attr;
+
+ if (argc != 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s mq-name\n", argv[0]);
+
+ mqd = mq_open(argv[1], O_RDONLY);
+ if (mqd == (mqd_t) -1)
+ errExit("mq_open");
+
+ if (mq_getattr(mqd, &attr) == -1)
+ errExit("mq_getattr");
+
+ printf("Maximum # of messages on queue: %ld\n", attr.mq_maxmsg);
+ printf("Maximum message size: %ld\n", attr.mq_msgsize);
+ printf("# of messages currently on queue: %ld\n", attr.mq_curmsgs);
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 52-5 */
+
+/* pmsg_receive.c
+
+ Usage as shown in usageError().
+
+ Receive a message from a POSIX message queue, and write it on
+ standard output.
+
+ See also pmsg_send.c.
+
+ Linux supports POSIX message queues since kernel 2.6.6.
+*/
+#include <mqueue.h>
+#include <fcntl.h> /* For definition of O_NONBLOCK */
+#include "tlpi_hdr.h"
+
+static void
+usageError(const char *progName)
+{
+ fprintf(stderr, "Usage: %s [-n] mq-name\n", progName);
+ fprintf(stderr, " -n Use O_NONBLOCK flag\n");
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int flags, opt;
+ mqd_t mqd;
+ unsigned int prio;
+ void *buffer;
+ struct mq_attr attr;
+ ssize_t numRead;
+
+ flags = O_RDONLY;
+ while ((opt = getopt(argc, argv, "n")) != -1) {
+ switch (opt) {
+ case 'n': flags |= O_NONBLOCK; break;
+ default: usageError(argv[0]);
+ }
+ }
+
+ if (optind >= argc)
+ usageError(argv[0]);
+
+ mqd = mq_open(argv[optind], flags);
+ if (mqd == (mqd_t) -1)
+ errExit("mq_open");
+
+ /* We need to know the 'mq_msgsize' attribute of the queue in
+ order to determine the size of the buffer for mq_receive() */
+
+ if (mq_getattr(mqd, &attr) == -1)
+ errExit("mq_getattr");
+
+ buffer = malloc(attr.mq_msgsize);
+ if (buffer == NULL)
+ errExit("malloc");
+
+ numRead = mq_receive(mqd, buffer, attr.mq_msgsize, &prio);
+ if (numRead == -1)
+ errExit("mq_receive");
+
+ printf("Read %ld bytes; priority = %u\n", (long) numRead, prio);
+ if (write(STDOUT_FILENO, buffer, numRead) == -1)
+ errExit("write");
+ write(STDOUT_FILENO, "\n", 1);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 52-4 */
+
+/* pmsg_send.c
+
+ Usage as shown in usageError().
+
+ Send a message (specified as a command line argument) to a
+ POSIX message queue.
+
+ See also pmsg_receive.c.
+
+ Linux supports POSIX message queues since kernel 2.6.6.
+*/
+#include <mqueue.h>
+#include <fcntl.h> /* For definition of O_NONBLOCK */
+#include "tlpi_hdr.h"
+
+static void
+usageError(const char *progName)
+{
+ fprintf(stderr, "Usage: %s [-n] mq-name msg [prio]\n", progName);
+ fprintf(stderr, " -n Use O_NONBLOCK flag\n");
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int flags, opt;
+ mqd_t mqd;
+ unsigned int prio;
+
+ flags = O_WRONLY;
+ while ((opt = getopt(argc, argv, "n")) != -1) {
+ switch (opt) {
+ case 'n': flags |= O_NONBLOCK; break;
+ default: usageError(argv[0]);
+ }
+ }
+
+ if (optind + 1 >= argc)
+ usageError(argv[0]);
+
+ mqd = mq_open(argv[optind], flags);
+ if (mqd == (mqd_t) -1)
+ errExit("mq_open");
+
+ prio = (argc > optind + 2) ? atoi(argv[optind + 2]) : 0;
+
+ if (mq_send(mqd, argv[optind + 1], strlen(argv[optind + 1]), prio) == -1)
+ errExit("mq_send");
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 52-1 */
+
+/* pmsg_unlink.c
+
+ Usage: pmsg_unlink mq-name
+
+ Unlink a POSIX message queue.
+
+ Linux supports POSIX message queues since kernel 2.6.6.
+*/
+#include <mqueue.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ if (argc != 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s mq-name\n", argv[0]);
+
+ if (mq_unlink(argv[1]) == -1)
+ errExit("mq_unlink");
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE = bad_longjmp display_env longjmp \
+ necho setjmp_vars setjmp_vars_opt t_getenv
+
+LINUX_EXE = modify_env
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+clean :
+ ${RM} ${EXE} *.o
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 6-2 */
+
+/* bad_longjmp.c
+
+ Demonstrate the incorrect use of longjmp() to jump into a function
+ that has already returned.
+
+ Usage: bad_longjmp [x]
+
+ The presence or absence of the command-line argument determines
+ whether we will call an intervening recursive function between the
+ function that establishes the jump point, and the one that does
+ the jump. Each case results in a different erroneous behaviour.
+*/
+#include <setjmp.h>
+#include "tlpi_hdr.h"
+
+static jmp_buf env; /* Global buffer for saving environment */
+
+static void
+doJump(void)
+{
+ printf("Entered doJump\n");
+ longjmp(env, 2);
+ printf("Exiting doJump\n");
+}
+
+static void
+setJump2(void)
+{
+ printf("Entered setJump2\n");
+ setjmp(env);
+ printf("Exiting setJump2\n");
+}
+
+static void
+setJump(void)
+{
+
+ printf("Entered setJump\n");
+ setJump2();
+ printf("Exiting setJump\n");
+}
+
+static void
+recur(int n)
+{
+ printf("Entered recur %d\n", n);
+ if (n > 0)
+ recur(n - 1);
+ printf("Exiting recur %d\n", n);
+}
+
+int
+main(int argc, char *argv[])
+{
+ setJump();
+ if (argc > 1)
+ recur(2);
+ doJump();
+ printf("Back at main\n");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 6-3 */
+
+/* display_env.c
+
+ Display the process environment list.
+*/
+#include "tlpi_hdr.h"
+
+extern char **environ;
+ /* Or define _GNU_SOURCE to get it from <unistd.h> */
+
+int
+main(int argc, char *argv[])
+{
+ char **ep;
+
+ for (ep = environ; *ep != NULL; ep++)
+ puts(*ep);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 6-5 */
+
+/* longjmp.c
+
+ Demonstrate the use of setjmp() and longjmp() to perform a nonlocal goto.
+
+ Usage: longjmp [x]
+
+ The presence or absence of a command-line argument determines which of two
+ functions (f1() or f2()) we will longjmp() from.
+*/
+#include <setjmp.h>
+#include "tlpi_hdr.h"
+
+static jmp_buf env;
+
+static void
+f2(void)
+{
+ longjmp(env, 2);
+}
+
+static void
+f1(int argc)
+{
+ if (argc == 1)
+ longjmp(env, 1);
+ f2();
+}
+
+int
+main(int argc, char *argv[])
+{
+ switch (setjmp(env)) {
+ case 0: /* This is the return after the initial setjmp() */
+ printf("Calling f1() after initial setjmp()\n");
+ f1(argc); /* Never returns... */
+ break; /* ... but this is good form */
+
+ case 1:
+ printf("We jumped back from f1()\n");
+ break;
+
+ case 2:
+ printf("We jumped back from f2()\n");
+ break;
+ }
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 6-1 */
+
+/* mem_segments.c
+
+ A program that does nothing in particular, but the comments indicate
+ which memory segments each type of variable is allocated in.
+*/
+#define _BSD_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+
+char globBuf[65536]; /* Uninitialized data segment */
+int primes[] = { 2, 3, 5, 7 }; /* Initialized data segment */
+
+static int
+square(int x) /* Allocated in frame for square() */
+{
+ int result; /* Allocated in frame for square() */
+
+ result = x * x;
+ return result; /* Return value passed via register */
+}
+
+static void
+doCalc(int val) /* Allocated in frame for doCalc() */
+{
+ printf("The square of %d is %d\n", val, square(val));
+
+ if (val < 1000) {
+ int t; /* Allocated in frame for doCalc() */
+
+ t = val * val * val;
+ printf("The cube of %d is %d\n", val, t);
+ }
+}
+
+int
+main(int argc, char *argv[]) /* Allocated in frame for main() */
+{
+ static int key = 9973; /* Initialized data segment */
+ static char mbuf[10240000]; /* Uninitialized data segment */
+ char *p; /* Allocated in frame for main() */
+
+ p = malloc(1024); /* Points to memory in heap segment */
+
+ doCalc(key);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 6-4 */
+
+/* modify_env.c
+
+ Demonstrate modification of the process environment list.
+
+ Usage: modify_env name=value...
+
+ Note: some UNIX implementations do not provide clearenv(), setenv(),
+ and unsetenv().
+*/
+#define _GNU_SOURCE /* Get various declarations from <stdlib.h> */
+#include <stdlib.h>
+#include "tlpi_hdr.h"
+
+extern char **environ;
+
+int
+main(int argc, char *argv[])
+{
+ int j;
+ char **ep;
+
+ clearenv(); /* Erase entire environment */
+
+ /* Add any definitions specified on command line to environment */
+
+ for (j = 1; j < argc; j++)
+ if (putenv(argv[j]) != 0)
+ errExit("putenv: %s", argv[j]);
+
+ /* Add a definition for GREET if one does not already exist */
+
+ if (setenv("GREET", "Hello world", 0) == -1)
+ errExit("setenv");
+
+ /* Remove any existing definition of BYE */
+
+ unsetenv("BYE");
+
+ /* Display current environment */
+
+ for (ep = environ; *ep != NULL; ep++)
+ puts(*ep);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 6-2 */
+
+/* necho.c
+
+ A simple version of echo(1): echo our command-line arguments.
+*/
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int j;
+
+ for (j = 0; j < argc; j++)
+ printf("argv[%d] = %s\n", j, argv[j]);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 6-3 */
+
+/* setenv.c
+
+ An implementation of setenv() and unsetenv() using environ, putenv(),
+ and getenv().
+*/
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+int
+unsetenv(const char *name)
+{
+ extern char **environ;
+ char **ep, **sp;
+ size_t len;
+
+ if (name == NULL || name[0] == '\0' || strchr(name, '=') != NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ len = strlen(name);
+
+ for (ep = environ; *ep != NULL; )
+ if (strncmp(*ep, name, len) == 0 && (*ep)[len] == '=') {
+
+ /* Remove found entry by shifting all successive entries
+ back one element */
+
+ for (sp = ep; *sp != NULL; sp++)
+ *sp = *(sp + 1);
+
+ /* Continue around the loop to further instances of 'name' */
+
+ } else {
+ ep++;
+ }
+
+ return 0;
+}
+
+int
+setenv(const char *name, const char *value, int overwrite)
+{
+ char *es;
+
+ if (name == NULL || name[0] == '\0' || strchr(name, '=') != NULL ||
+ value == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (getenv(name) != NULL && overwrite == 0)
+ return 0;
+
+ unsetenv(name); /* Remove all occurrences */
+
+ es = malloc(strlen(name) + strlen(value) + 2);
+ /* +2 for '=' and null terminator */
+ if (es == NULL)
+ return -1;
+
+ strcpy(es, name);
+ strcat(es, "=");
+ strcat(es, value);
+
+ return (putenv(es) != 0) ? -1 : 0;
+}
+
+#ifdef TEST_IT
+
+int
+main()
+{
+ if (putenv("TT=xxxxx") != 0)
+ perror("putenv");
+
+ system("echo '***** Environment before unsetenv(TT)'; "
+ "printenv | grep ^TT");
+ system("echo 'Total lines from printenv:' `printenv | wc -l`");
+
+ unsetenv("TT");
+
+ system("echo '***** Environment after unsetenv(TT)'; "
+ "printenv | grep ^TT");
+ system("echo 'Total lines from printenv:' `printenv | wc -l`");
+
+ setenv("xyz", "one", 1);
+ setenv("xyz", "two", 0);
+ setenv("xyz2", "222", 0);
+
+ system("echo '***** Environment after setenv() calls'; "
+ "printenv | grep ^x");
+ system("echo 'Total lines from printenv:' `printenv | wc -l`");
+
+ exit(EXIT_SUCCESS);
+}
+
+#endif
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 6-6 */
+
+/* setjmp_vars.c
+
+ Compiling this program with and without optimization yields different
+ results, since the optimizer reorganizes code and variables in a manner
+ that does not take account of the dynamic flow of control established by
+ a long jump.
+
+ Try looking at the assembler source (.s) for the unoptimized (cc -S)
+ and optimized (cc -O -S) versions of this program to see the cause
+ of these differences.
+*/
+#include <stdio.h>
+#include <stdlib.h>
+#include <setjmp.h>
+
+static jmp_buf env;
+
+static void
+doJump(int nvar, int rvar, int vvar)
+{
+ printf("Inside doJump(): nvar=%d rvar=%d vvar=%d\n", nvar, rvar, vvar);
+ longjmp(env, 1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int nvar;
+ register int rvar; /* Allocated in register if possible */
+ volatile int vvar; /* See text */
+
+ nvar = 111;
+ rvar = 222;
+ vvar = 333;
+
+ if (setjmp(env) == 0) { /* Code executed after setjmp() */
+ nvar = 777;
+ rvar = 888;
+ vvar = 999;
+ doJump(nvar, rvar, vvar);
+
+ } else { /* Code executed after longjmp() */
+
+ printf("After longjmp(): nvar=%d rvar=%d vvar=%d\n", nvar, rvar, vvar);
+ }
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 6 */
+
+/* t_getenv.c
+
+ Demonstrate the use of getenv() to retrieve the value of an
+ environment variable.
+*/
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ char *val;
+
+ if (argc != 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s environ-var\n", argv[0]);
+
+ val = getenv(argv[1]);
+ printf("%s\n", (val != NULL) ? val : "No such variable");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE =
+
+LINUX_EXE = idshow
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+clean :
+ ${RM} ${EXE} *.o
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 9-1 */
+
+/* idshow.c
+
+ Display all user and group identifiers associated with a process.
+
+ Note: This program uses Linux-specific calls and the Linux-specific
+ file-system user and group IDs.
+*/
+#define _GNU_SOURCE
+#include <unistd.h>
+#include <sys/fsuid.h>
+#include <limits.h>
+#include "ugid_functions.h" /* userNameFromId() & groupNameFromId() */
+#include "tlpi_hdr.h"
+
+#define SG_SIZE (NGROUPS_MAX + 1)
+
+int
+main(int argc, char *argv[])
+{
+ uid_t ruid, euid, suid, fsuid;
+ gid_t rgid, egid, sgid, fsgid;
+ gid_t suppGroups[SG_SIZE];
+ int numGroups, j;
+ char *p;
+
+ if (getresuid(&ruid, &euid, &suid) == -1)
+ errExit("getresuid");
+ if (getresgid(&rgid, &egid, &sgid) == -1)
+ errExit("getresgid");
+
+ /* Attempts to change the file-system IDs are always ignored
+ for unprivileged processes, but even so, the following
+ calls return the current file-system IDs */
+
+ fsuid = setfsuid(0);
+ fsgid = setfsgid(0);
+
+ printf("UID: ");
+ p = userNameFromId(ruid);
+ printf("real=%s (%ld); ", (p == NULL) ? "???" : p, (long) ruid);
+ p = userNameFromId(euid);
+ printf("eff=%s (%ld); ", (p == NULL) ? "???" : p, (long) euid);
+ p = userNameFromId(suid);
+ printf("saved=%s (%ld); ", (p == NULL) ? "???" : p, (long) suid);
+ p = userNameFromId(fsuid);
+ printf("fs=%s (%ld); ", (p == NULL) ? "???" : p, (long) fsuid);
+ printf("\n");
+
+ printf("GID: ");
+ p = groupNameFromId(rgid);
+ printf("real=%s (%ld); ", (p == NULL) ? "???" : p, (long) rgid);
+ p = groupNameFromId(egid);
+ printf("eff=%s (%ld); ", (p == NULL) ? "???" : p, (long) egid);
+ p = groupNameFromId(sgid);
+ printf("saved=%s (%ld); ", (p == NULL) ? "???" : p, (long) sgid);
+ p = groupNameFromId(fsgid);
+ printf("fs=%s (%ld); ", (p == NULL) ? "???" : p, (long) fsgid);
+ printf("\n");
+
+ numGroups = getgroups(SG_SIZE, suppGroups);
+ if (numGroups == -1)
+ errExit("getgroups");
+
+ printf("Supplementary groups (%d): ", numGroups);
+ for (j = 0; j < numGroups; j++) {
+ p = groupNameFromId(suppGroups[j]);
+ printf("%s (%ld) ", (p == NULL) ? "???" : p, (long) suppGroups[j]);
+ }
+ printf("\n");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE = acct_on acct_view child_status closeonexec envargs exit_handlers \
+ footprint fork_file_sharing fork_sig_sync \
+ fork_stdio_buf fork_whos_on_first \
+ make_zombie multi_SIGCHLD multi_wait necho orphan \
+ t_execl t_execle t_execve t_execlp t_fork t_system \
+ t_vfork vfork_fd_test
+
+LINUX_EXE = demo_clone t_clone acct_v3_view
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+clean :
+ ${RM} ${EXE} *.o
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 28-1 */
+
+/* acct_on.c
+
+ Use acct(2) to enable or disable process accounting.
+*/
+#define _BSD_SOURCE
+#include <unistd.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ if (argc > 2 || (argc > 1 && strcmp(argv[1], "--help") == 0))
+ usageErr("%s [file]\n", argv[0]);
+
+ if (acct(argv[1]) == -1)
+ errExit("acct");
+
+ printf("Process accounting %s\n",
+ (argv[1] == NULL) ? "disabled" : "enabled");
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 28 */
+
+/* acct_v3_view.c
+
+ Display contents of a Linux-specific Version 3 process accounting file.
+
+ This program will produce sensible results only when used to read
+ an account file produced on a Linux 2.6 (or later) system configured
+ with CONFIG_BSD_PROCESS_ACCT_V3.
+
+ See also acct_view.c.
+
+ This program is Linux-specific. The Version 3 accounting file is
+ supported since kernel 2.6.8.
+*/
+#include <fcntl.h>
+#include <time.h>
+#include <sys/stat.h>
+#include <sys/acct.h>
+#include <limits.h>
+#include "ugid_functions.h" /* Declaration of userNameFromId() */
+#include "tlpi_hdr.h"
+
+#define TIME_BUF_SIZE 100
+
+#define GLIBC_DEFINES_ACCT_V3 /* This is true since glibc 2.6 */
+
+#ifndef GLIBC_DEFINES_ACCT_V3
+struct acct_v3
+{
+ char ac_flag;
+ char ac_version;
+ u_int16_t ac_tty;
+ u_int32_t ac_exitcode;
+ u_int32_t ac_uid;
+ u_int32_t ac_gid;
+ u_int32_t ac_pid;
+ u_int32_t ac_ppid;
+ u_int32_t ac_btime;
+ float ac_etime;
+ comp_t ac_utime;
+ comp_t ac_stime;
+ comp_t ac_mem;
+ comp_t ac_io;
+ comp_t ac_rw;
+ comp_t ac_minflt;
+ comp_t ac_majflt;
+ comp_t ac_swaps;
+ char ac_comm[ACCT_COMM];
+};
+
+#endif
+
+static long long /* Convert comp_t value into long long */
+comptToLL(comp_t ct)
+{
+ const int EXP_SIZE = 3; /* 3-bit, base-8 exponent */
+ const int MANTISSA_SIZE = 13; /* Followed by 13-bit mantissa */
+ const int MANTISSA_MASK = (1 << MANTISSA_SIZE) - 1;
+ long long mantissa, exp;
+
+ mantissa = ct & MANTISSA_MASK;
+ exp = (ct >> MANTISSA_SIZE) & ((1 << EXP_SIZE) - 1);
+ return mantissa << (exp * 3); /* Power of 8 = left shift 3 bits */
+}
+
+int
+main(int argc, char *argv[])
+{
+ int acctFile;
+ struct acct_v3 ac;
+ ssize_t numRead;
+ char *s;
+ char timeBuf[TIME_BUF_SIZE];
+ struct tm *loc;
+ time_t t;
+
+ if (argc != 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s file\n", argv[0]);
+
+ acctFile = open(argv[1], O_RDONLY);
+ if (acctFile == -1)
+ errExit("open");
+
+ printf("ver. command flags term. PID PPID user group"
+ " start date+time CPU elapsed\n");
+ printf(" status "
+ " time time\n");
+
+ while ((numRead = read(acctFile, &ac, sizeof(struct acct_v3))) > 0) {
+ if (numRead != sizeof(struct acct_v3))
+ fatal("partial read");
+
+ printf("%1d ", (int) ac.ac_version);
+ printf("%-8.8s ", ac.ac_comm);
+
+ printf("%c", (ac.ac_flag & AFORK) ? 'F' : '-') ;
+ printf("%c", (ac.ac_flag & ASU) ? 'S' : '-') ;
+ printf("%c", (ac.ac_flag & AXSIG) ? 'X' : '-') ;
+ printf("%c", (ac.ac_flag & ACORE) ? 'C' : '-') ;
+
+ printf(" %#6lx ", (unsigned long) ac.ac_exitcode);
+
+ printf(" %5ld %5ld ", (long) ac.ac_pid, (long) ac.ac_ppid);
+
+ s = userNameFromId(ac.ac_uid);
+ printf("%-8.8s ", (s == NULL) ? "???" : s);
+
+ s = groupNameFromId(ac.ac_gid);
+ printf("%-8.8s ", (s == NULL) ? "???" : s);
+
+ t = ac.ac_btime;
+ loc = localtime(&t);
+ if (loc == NULL) {
+ printf("???Unknown time??? ");
+ } else {
+ strftime(timeBuf, TIME_BUF_SIZE, "%Y-%m-%d %T ", loc);
+ printf("%s ", timeBuf);
+ }
+
+ printf("%5.2f %7.2f ", (double) (comptToLL(ac.ac_utime) +
+ comptToLL(ac.ac_stime)) / sysconf(_SC_CLK_TCK),
+ ac.ac_etime / sysconf(_SC_CLK_TCK));
+ printf("\n");
+ }
+
+ if (numRead == -1)
+ errExit("read");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 28-2 */
+
+/* acct_view.c
+
+ Display contents of a process accounting file.
+*/
+#include <fcntl.h>
+#include <time.h>
+#include <sys/stat.h>
+#include <sys/acct.h>
+#include <limits.h>
+#include "ugid_functions.h" /* Declaration of userNameFromId() */
+#include "tlpi_hdr.h"
+
+#define TIME_BUF_SIZE 100
+
+static long long /* Convert comp_t value into long long */
+comptToLL(comp_t ct)
+{
+ const int EXP_SIZE = 3; /* 3-bit, base-8 exponent */
+ const int MANTISSA_SIZE = 13; /* Followed by 13-bit mantissa */
+ const int MANTISSA_MASK = (1 << MANTISSA_SIZE) - 1;
+ long long mantissa, exp;
+
+ mantissa = ct & MANTISSA_MASK;
+ exp = (ct >> MANTISSA_SIZE) & ((1 << EXP_SIZE) - 1);
+ return mantissa << (exp * 3); /* Power of 8 = left shift 3 bits */
+}
+
+int
+main(int argc, char *argv[])
+{
+ int acctFile;
+ struct acct ac;
+ ssize_t numRead;
+ char *s;
+ char timeBuf[TIME_BUF_SIZE];
+ struct tm *loc;
+ time_t t;
+
+ if (argc != 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s file\n", argv[0]);
+
+ acctFile = open(argv[1], O_RDONLY);
+ if (acctFile == -1)
+ errExit("open");
+
+ printf("command flags term. user "
+ "start time CPU elapsed\n");
+ printf(" status "
+ " time time\n");
+
+ while ((numRead = read(acctFile, &ac, sizeof(struct acct))) > 0) {
+ if (numRead != sizeof(struct acct))
+ fatal("partial read");
+
+ printf("%-8.8s ", ac.ac_comm);
+
+ printf("%c", (ac.ac_flag & AFORK) ? 'F' : '-') ;
+ printf("%c", (ac.ac_flag & ASU) ? 'S' : '-') ;
+
+ /* Not all implementations support AXSIG and ACORE */
+
+#ifdef AXSIG
+ printf("%c", (ac.ac_flag & AXSIG) ? 'X' : '-') ;
+#else
+ printf(" ");
+#endif
+#ifdef ACORE
+ printf("%c", (ac.ac_flag & ACORE) ? 'C' : '-') ;
+#else
+ printf(" ");
+#endif
+
+#ifdef __linux__
+ printf(" %#6lx ", (unsigned long) ac.ac_exitcode);
+#else /* Many other implementations provide ac_stat instead */
+ /* But the BSDs don't provide this field either */
+#if ! defined(__FreeBSD__) && ! defined(__NetBSD__) && \
+ ! defined(__APPLE__)
+ printf(" %#6lx ", (unsigned long) ac.ac_stat);
+#else
+ printf(" ");
+#endif
+#endif
+
+ s = userNameFromId(ac.ac_uid);
+ printf("%-8.8s ", (s == NULL) ? "???" : s);
+
+ t = ac.ac_btime;
+ loc = localtime(&t);
+ if (loc == NULL) {
+ printf("???Unknown time??? ");
+ } else {
+ strftime(timeBuf, TIME_BUF_SIZE, "%Y-%m-%d %T ", loc);
+ printf("%s ", timeBuf);
+ }
+
+ printf("%5.2f %7.2f ", (double) (comptToLL(ac.ac_utime) +
+ comptToLL(ac.ac_stime)) / sysconf(_SC_CLK_TCK),
+ (double) comptToLL(ac.ac_etime) / sysconf(_SC_CLK_TCK));
+ printf("\n");
+ }
+
+ if (numRead == -1)
+ errExit("read");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 26-3 */
+
+/* child_status.c
+
+ Demonstrate the use of wait() and the W* macros for analyzing the child
+ status returned by wait()
+
+ Usage: child_status [exit-status]
+
+ If "exit-status" is supplied, then the child immediately exits with this
+ status. If no command-line argument is supplied then the child loops waiting
+ for signals that either cause it to stop or to terminate - both conditions
+ can be detected and differentiated by the parent. The parent process
+ repeatedly waits on the child until it detects that the child either exited
+ normally or was killed by a signal.
+*/
+#include <sys/wait.h>
+#include "print_wait_status.h" /* Declares printWaitStatus() */
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int status;
+ pid_t childPid;
+
+ if (argc > 1 && strcmp(argv[1], "--help") == 0)
+ usageErr("%s [exit-status]\n", argv[0]);
+
+ switch (fork()) {
+ case -1: errExit("fork");
+
+ case 0: /* Child: either exits immediately with given
+ status or loops waiting for signals */
+ printf("Child started with PID = %ld\n", (long) getpid());
+ if (argc > 1) /* Status supplied on command line? */
+ exit(getInt(argv[1], 0, "exit-status"));
+ else /* Otherwise, wait for signals */
+ for (;;)
+ pause();
+ exit(EXIT_FAILURE); /* Not reached, but good practice */
+
+ default: /* Parent: repeatedly wait on child until it
+ either exits or is terminated by a signal */
+ for (;;) {
+ childPid = waitpid(-1, &status, WUNTRACED
+#ifdef WCONTINUED /* Not present on older versions of Linux */
+ | WCONTINUED
+#endif
+ );
+ if (childPid == -1)
+ errExit("waitpid");
+
+ /* Print status in hex, and as separate decimal bytes */
+
+ printf("waitpid() returned: PID=%ld; status=0x%04x (%d,%d)\n",
+ (long) childPid,
+ (unsigned int) status, status >> 8, status & 0xff);
+ printWaitStatus(NULL, status);
+
+ if (WIFEXITED(status) || WIFSIGNALED(status))
+ exit(EXIT_SUCCESS);
+ }
+ }
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 27-6 */
+
+/* closeonexec.c
+
+ Demonstrate retrieving and updating of the file descriptor
+ close-on-exec flag.
+*/
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int flags;
+
+ if (argc > 1) {
+ flags = fcntl(STDOUT_FILENO, F_GETFD); /* Fetch flags */
+ if (flags == -1)
+ errExit("fcntl - F_GETFD");
+
+ flags |= FD_CLOEXEC; /* Turn on FD_CLOEXEC */
+
+ if (fcntl(STDOUT_FILENO, F_SETFD, flags) == -1) /* Update flags */
+ errExit("fcntl - F_SETFD");
+ }
+
+ execlp("ls", "ls", "-l", argv[0], (char *) NULL);
+ errExit("execlp");
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 28 */
+
+/* demo_clone.c
+
+ Demonstrate the use of the Linux-specific clone() system call.
+
+ This program creates a child using clone(). Various flags can be included
+ in the clone() call by specifying option letters in the first command-line
+ argument to the program (see usageError() below for a list of the option
+ letters). Note that not all combinations of flags are valid. See Section
+ 28.2.1 or the clone(2) man page about information about which flag
+ combinations are required or invalid.
+
+ This program is Linux-specific.
+*/
+#define _GNU_SOURCE
+#include <string.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <fcntl.h>
+#include <sched.h>
+#include "print_wait_status.h"
+#include "tlpi_hdr.h"
+
+#ifndef CHILD_SIG
+#define CHILD_SIG SIGUSR1 /* Signal to be generated on termination
+ of cloned child */
+#endif
+
+typedef struct { /* For passing info to child startup function */
+ int fd;
+ mode_t umask;
+ int exitStatus;
+ int signal;
+} ChildParams;
+
+static int /* Startup function for cloned child */
+childFunc(void *arg)
+{
+ ChildParams *cp;
+
+ printf("Child: PID=%ld PPID=%ld\n", (long) getpid(), (long) getppid());
+
+ cp = (ChildParams *) arg; /* Cast arg to true form */
+
+ /* The following changes may affect parent */
+
+ umask(cp->umask);
+ if (close(cp->fd) == -1)
+ errExit("child:close");
+ if (signal(cp->signal, SIG_DFL) == SIG_ERR)
+ errExit("child:signal");
+
+ return cp->exitStatus; /* Child terminates now */
+}
+
+static void /* Handler for child termination signal */
+grimReaper(int sig)
+{
+ int savedErrno;
+
+ /* UNSAFE: This handler uses non-async-signal-safe functions
+ (printf(), strsignal(); see Section 21.1.2) */
+
+ savedErrno = errno; /* In case we change 'errno' here */
+
+ printf("Caught signal %d (%s)\n", sig, strsignal(sig));
+
+ errno = savedErrno;
+}
+
+static void
+usageError(char *progName)
+{
+ fprintf(stderr, "Usage: %s [arg]\n", progName);
+#define fpe(str) fprintf(stderr, " " str)
+ fpe("'arg' can contain the following letters:\n");
+ fpe(" d - share file descriptors (CLONE_FILES)\n");
+ fpe(" f - share file-system information (CLONE_FS)\n");
+ fpe(" s - share signal dispositions (CLONE_SIGHAND)\n");
+ fpe(" v - share virtual memory (CLONE_VM)\n");
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ const int STACK_SIZE = 65536; /* Stack size for cloned child */
+ char *stack; /* Start of stack buffer area */
+ char *stackTop; /* End of stack buffer area */
+ int flags; /* Flags for cloning child */
+ ChildParams cp; /* Passed to child function */
+ const mode_t START_UMASK = S_IWOTH; /* Initial umask setting */
+ struct sigaction sa;
+ char *p;
+ int status;
+ ssize_t s;
+ pid_t pid;
+
+ printf("Parent: PID=%ld PPID=%ld\n", (long) getpid(), (long) getppid());
+
+ /* Set up an argument structure to be passed to cloned child, and
+ set some process attributes that will be modified by child */
+
+ cp.exitStatus = 22; /* Child will exit with this status */
+
+ umask(START_UMASK); /* Initialize umask to some value */
+ cp.umask = S_IWGRP; /* Child sets umask to this value */
+
+ cp.fd = open("/dev/null", O_RDWR); /* Child will close this fd */
+ if (cp.fd == -1)
+ errExit("open");
+
+ cp.signal = SIGTERM; /* Child will change disposition */
+ if (signal(cp.signal, SIG_IGN) == SIG_ERR) errExit("signal");
+
+ /* Initialize clone flags using command-line argument (if supplied) */
+
+ flags = 0;
+ if (argc > 1) {
+ for (p = argv[1]; *p != '\0'; p++) {
+ if (*p == 'd') flags |= CLONE_FILES;
+ else if (*p == 'f') flags |= CLONE_FS;
+ else if (*p == 's') flags |= CLONE_SIGHAND;
+ else if (*p == 'v') flags |= CLONE_VM;
+ else usageError(argv[0]);
+ }
+ }
+
+ /* Allocate stack for child */
+
+ stack = malloc(STACK_SIZE);
+ if (stack == NULL)
+ errExit("malloc");
+ stackTop = stack + STACK_SIZE; /* Assume stack grows downward */
+
+ /* Establish handler to catch child termination signal */
+
+ if (CHILD_SIG != 0) {
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ sa.sa_handler = grimReaper;
+ if (sigaction(CHILD_SIG, &sa, NULL) == -1)
+ errExit("sigaction");
+ }
+
+ /* Create child; child commences execution in childFunc() */
+
+ if (clone(childFunc, stackTop, flags | CHILD_SIG, &cp) == -1)
+ errExit("clone");
+
+ /* Parent falls through to here. Wait for child; __WCLONE option is
+ required for child notifying with signal other than SIGCHLD. */
+
+ pid = waitpid(-1, &status, (CHILD_SIG != SIGCHLD) ? __WCLONE : 0);
+ if (pid == -1)
+ errExit("waitpid");
+
+ printf(" Child PID=%ld\n", (long) pid);
+ printWaitStatus(" Status: ", status);
+
+ /* Check whether changes made by cloned child have affected parent */
+
+ printf("Parent - checking process attributes:\n");
+ if (umask(0) != START_UMASK)
+ printf(" umask has changed\n");
+ else
+ printf(" umask has not changed\n");
+
+ s = write(cp.fd, "Hello world\n", 12);
+ if (s == -1 && errno == EBADF)
+ printf(" file descriptor %d has been closed\n", cp.fd);
+ else if (s == -1)
+ printf(" write() on file descriptor %d failed (%s)\n",
+ cp.fd, strerror(errno));
+ else
+ printf(" write() on file descriptor %d succeeded\n", cp.fd);
+
+ if (sigaction(cp.signal, NULL, &sa) == -1)
+ errExit("sigaction");
+ if (sa.sa_handler != SIG_IGN)
+ printf(" signal disposition has changed\n");
+ else
+ printf(" signal disposition has not changed\n");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 27-2 */
+
+/* envargs.c
+
+ Display argument list and environment.
+*/
+#include "tlpi_hdr.h"
+
+extern char **environ;
+
+int
+main(int argc, char *argv[])
+{
+ int j;
+ char **ep;
+
+ /* Display argument list */
+
+ for (j = 0; j < argc; j++)
+ printf("argv[%d] = %s\n", j, argv[j]);
+
+ /* Display environment list */
+
+ for (ep = environ; *ep != NULL; ep++)
+ printf("environ: %s\n", *ep);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 27-2 */
+
+/* execlp.c
+
+ An implementation of execlp() using execve().
+
+ For the full details, see the SUSv3 specification of the exec functions
+ (XSH:exec). For an alternate (and more complete) implementation, see the
+ posix/exec*.c sources in glibc.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+
+extern char **environ;
+
+#define max(x,y) ((x) > (y) ? (x) : (y))
+#define SHELL_PATH "/bin/sh" /* Pathname for the standard shell */
+
+/* Exec a script file using the standard shell */
+
+static void
+execShScript(int argc, char *argv[], char *envp[])
+{
+ char *shArgv[argc + 1];
+ int j;
+
+ shArgv[0] = SHELL_PATH;
+ for (j = 0; j <= argc; j++)
+ shArgv[j + 1] = argv[j];
+ execve(SHELL_PATH, shArgv, envp);
+
+ /* We only get here if execve() fails, in which case we return
+ to our caller */
+}
+
+int
+execlp(const char *filename, const char *arg, ...)
+{
+ char **argv; /* Argument vector for new program */
+ int argc; /* Number of items used in argv */
+ int argvSize; /* Currently allocated size of argv */
+ va_list argList; /* For variable argument list parsing */
+ char **envp; /* Environment for new program */
+ char *PATH; /* Value of PATH environment variable */
+ char *pathname; /* Path prefix + '/' + filename */
+ char *prStart, *prEnd; /* Start and end of prefix currently
+ being processed from PATH */
+ int savedErrno;
+ int morePrefixes; /* True if there are more prefixes in PATH */
+ char *p;
+ int j;
+ int fndEACCES; /* True if any execve() returned EACCES */
+
+ fndEACCES = 0;
+
+ /***** Build argument vector from variable length argument list *****/
+
+ argvSize = 100;
+ argv = calloc(argvSize, sizeof(void *));
+ if (argv == NULL)
+ return -1;
+
+ argv[0] = (char *) arg;
+ argc = 1;
+
+ /* Walk through variable-length argument list until NULL terminator
+ is found, building argv as we go */
+
+ va_start(argList, arg);
+ while (argv[argc - 1] != NULL) {
+ if (argc + 1 >= argvSize) { /* Resize argv if required */
+ argvSize += 100;
+ argv = realloc(argv, sizeof(void *));
+ if (argv == NULL)
+ return -1;
+ }
+
+ argv[argc] = va_arg(argList, char *);
+ argc++;
+ }
+
+ va_end(argList);
+
+ /***** Use caller's environment to create envp vector *****/
+
+ for (j = 0; environ[j] != NULL; ) /* Calculate size of environ */
+ j++;
+ envp = calloc(sizeof(void *), j + 1);
+ if (envp == NULL)
+ return -1;
+
+ for (j = 0; environ[j] != NULL; j++) /* Duplicate environ in envp */
+ envp[j] = strdup(environ[j]);
+ envp[j] = NULL; /* List must be terminated by NULL pointer */
+
+ /***** Now try to exec filename *****/
+
+ if (strchr(filename, '/') != NULL) {
+
+ /* If file contains a slash, it's a pathname and we don't do
+ a search using PATH */
+
+ pathname = strdup(filename);
+
+ execve(pathname, argv, envp);
+
+ savedErrno = errno; /* So we can return correct errno */
+ if (errno == ENOEXEC)
+ execShScript(argc, argv, envp);
+
+ free(pathname); /* Avoid memory leaks */
+
+ } else { /* Use PATH */
+
+ /* Treat undefined PATH as "." */
+
+ p = getenv("PATH");
+ PATH = (p == NULL || strlen(p) == 0) ? strdup(".") : strdup(p);
+
+ /* For each prefix in PATH, try to exec 'filename' in that
+ directory. The loop will terminate either when we successfully
+ exec or when we run out of path prefixes. */
+
+ prStart = PATH;
+ morePrefixes = 1;
+
+ while (morePrefixes) {
+
+ /* Locate end of prefix */
+
+ prEnd = strchr(prStart, ':');
+ if (prEnd == NULL) /* Handle last prefix */
+ prEnd = prStart + strlen(prStart);
+
+ /* Build complete pathname from path prefix and filename */
+
+ pathname = malloc(max(1, prEnd - prStart) + strlen(filename)
+ + 2);
+ pathname[0] = '\0';
+ if (prEnd == prStart) /* Last prefix */
+ strcat(pathname, ".");
+ else
+ strncat(pathname, prStart, prEnd - prStart);
+ strcat(pathname, "/");
+ strcat(pathname, filename);
+
+ if (*prEnd == '\0') /* No more prefixes */
+ morePrefixes = 0;
+ else
+ prStart = prEnd + 1; /* Move to start of next prefix */
+
+ /* Try to exec pathname; execve() returns only if we failed */
+
+ execve(pathname, argv, envp);
+ savedErrno = errno; /* So we can return correct errno */
+ if (errno == EACCES)
+ fndEACCES = 1;
+ else if (errno == ENOEXEC)
+ execShScript(argc, argv, envp);
+
+ /* Avoid memory leaks, in case no execve() succeeds! */
+
+ free(pathname);
+ }
+
+ free(PATH);
+ }
+
+ /* If we get here, execve() failed; clean up and return -1, ensuring
+ that errno contains the value returned by (the last) execve() */
+
+ free(argv);
+ for (j = 0; envp[j] != NULL; j++)
+ free(envp[j]);
+ free(envp);
+
+ /* SUSv3 says that if any execve() failed with EACCES, then that
+ is the error that should be returned by exec[lv]p() */
+
+ errno = fndEACCES ? EACCES : savedErrno;
+ return -1;
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 25-1 */
+
+/* exit_handlers.c
+
+ Demonstrate the use of atexit(3) and on_exit(3), which can be used to
+ register functions (commonly known as "exit handlers") to be called at
+ normal process exit. (These functions are not called if the process does
+ an _exit(2) or if it is terminated by a signal.)
+*/
+#define _BSD_SOURCE /* Get on_exit() declaration from <stdlib.h> */
+#include <stdlib.h>
+#include "tlpi_hdr.h"
+
+#ifdef __linux__ /* Few UNIX implementations have on_exit() */
+#define HAVE_ON_EXIT
+#endif
+
+static void
+atexitFunc1(void)
+{
+ printf("atexit function 1 called\n");
+}
+
+static void
+atexitFunc2(void)
+{
+ printf("atexit function 2 called\n");
+}
+
+#ifdef HAVE_ON_EXIT
+static void
+onexitFunc(int exitStatus, void *arg)
+{
+ printf("on_exit function called: status=%d, arg=%ld\n",
+ exitStatus, (long) arg);
+}
+#endif
+
+int
+main(int argc, char *argv[])
+{
+#ifdef HAVE_ON_EXIT
+ if (on_exit(onexitFunc, (void *) 10) != 0)
+ fatal("on_exit 1");
+#endif
+ if (atexit(atexitFunc1) != 0)
+ fatal("atexit 1");
+ if (atexit(atexitFunc2) != 0)
+ fatal("atexit 2");
+#ifdef HAVE_ON_EXIT
+ if (on_exit(onexitFunc, (void *) 20) != 0)
+ fatal("on_exit 2");
+#endif
+
+ exit(2);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 24-3 */
+
+/* footprint.c
+
+ Using fork() + wait() to control the memory footprint of an application.
+
+ This program contains a function that (artificially) consumes a large
+ amount of memory. To avoid changing the process's memory footprint, the
+ program creates a child process that calls the function. When the child
+ terminates, all of its memory is freed, and the memory consumption of
+ the parent is left unaffected.
+*/
+#define _BSD_SOURCE /* To get sbrk() declaration from <unistd.h> in case
+ _XOPEN_SOURCE >= 600; defining _SVID_SOURCE or
+ _GNU_SOURCE also suffices */
+#include <sys/wait.h>
+#include "tlpi_hdr.h"
+
+static int
+func(int arg)
+{
+ int j;
+
+ for (j = 0; j < 0x100; j++)
+ if (malloc(0x8000) == NULL)
+ errExit("malloc");
+ printf("Program break in child: %10p\n", sbrk(0));
+
+ return arg;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int arg = (argc > 1) ? getInt(argv[1], 0, "arg") : 0;
+ pid_t childPid;
+ int status;
+
+ setbuf(stdout, NULL); /* Disable buffering of stdout */
+
+ printf("Program break in parent: %10p\n", sbrk(0));
+
+ childPid = fork();
+ if (childPid == -1)
+ errExit("fork");
+
+ if (childPid == 0) /* Child calls func() and */
+ exit(func(arg)); /* uses return value as exit status */
+
+ /* Parent waits for child to terminate. It can determine the
+ result of func() by inspecting 'status' */
+
+ if (wait(&status) == -1)
+ errExit("wait");
+
+ printf("Program break in parent: %10p\n", sbrk(0));
+
+ printf("Status = %d %d\n", status, WEXITSTATUS(status));
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 24-2 */
+
+/* fork_file_sharing.c
+
+ Show that the file descriptors of a forked child refer to the
+ same open file objects as the parent.
+*/
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int fd, flags;
+ char template[] = "/tmp/testXXXXXX";
+
+ setbuf(stdout, NULL); /* Disable buffering of stdout */
+
+ /* Open a temporary file, set its file offset to some arbitrary value,
+ and change the setting of one of the open file status flags. */
+
+ fd = mkstemp(template);
+ if (fd == -1)
+ errExit("mkstemp");
+
+ printf("File offset before fork(): %lld\n",
+ (long long) lseek(fd, 0, SEEK_CUR));
+
+ flags = fcntl(fd, F_GETFL);
+ if (flags == -1)
+ errExit("fcntl - F_GETFL");
+ printf("O_APPEND flag before fork() is: %s\n",
+ (flags & O_APPEND) ? "on" : "off");
+
+ switch (fork()) {
+ case -1:
+ errExit("fork");
+
+ case 0: /* Child: change file offset and status flags */
+ if (lseek(fd, 1000, SEEK_SET) == -1)
+ errExit("lseek");
+
+ flags = fcntl(fd, F_GETFL); /* Fetch current flags */
+ if (flags == -1)
+ errExit("fcntl - F_GETFL");
+ flags |= O_APPEND; /* Turn O_APPEND on */
+ if (fcntl(fd, F_SETFL, flags) == -1)
+ errExit("fcntl - F_SETFL");
+ _exit(EXIT_SUCCESS);
+
+ default: /* Parent: can see file changes made by child */
+ if (wait(NULL) == -1)
+ errExit("wait"); /* Wait for child exit */
+ printf("Child has exited\n");
+
+ printf("File offset in parent: %lld\n",
+ (long long) lseek(fd, 0, SEEK_CUR));
+
+ flags = fcntl(fd, F_GETFL);
+ if (flags == -1)
+ errExit("fcntl - F_GETFL");
+ printf("O_APPEND flag in parent is: %s\n",
+ (flags & O_APPEND) ? "on" : "off");
+ exit(EXIT_SUCCESS);
+ }
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 24-6 */
+
+/* fork_sig_sync.c
+
+ Demonstrate how signals can be used to synchronize the actions
+ of a parent and child process.
+*/
+#include <signal.h>
+#include "curr_time.h" /* Declaration of currTime() */
+#include "tlpi_hdr.h"
+
+#define SYNC_SIG SIGUSR1 /* Synchronization signal */
+
+static void /* Signal handler - does nothing but return */
+handler(int sig)
+{
+}
+
+int
+main(int argc, char *argv[])
+{
+ pid_t childPid;
+ sigset_t blockMask, origMask, emptyMask;
+ struct sigaction sa;
+
+ setbuf(stdout, NULL); /* Disable buffering of stdout */
+
+ sigemptyset(&blockMask);
+ sigaddset(&blockMask, SYNC_SIG); /* Block signal */
+ if (sigprocmask(SIG_BLOCK, &blockMask, &origMask) == -1)
+ errExit("sigprocmask");
+
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ sa.sa_handler = handler;
+ if (sigaction(SYNC_SIG, &sa, NULL) == -1)
+ errExit("sigaction");
+
+ switch (childPid = fork()) {
+ case -1:
+ errExit("fork");
+
+ case 0: /* Child */
+
+ /* Child does some required action here... */
+
+ printf("[%s %ld] Child started - doing some work\n",
+ currTime("%T"), (long) getpid());
+ sleep(2); /* Simulate time spent doing some work */
+
+ /* And then signals parent that it's done */
+
+ printf("[%s %ld] Child about to signal parent\n",
+ currTime("%T"), (long) getpid());
+ if (kill(getppid(), SYNC_SIG) == -1)
+ errExit("kill");
+
+ /* Now child can do other things... */
+
+ _exit(EXIT_SUCCESS);
+
+ default: /* Parent */
+
+ /* Parent may do some work here, and then waits for child to
+ complete the required action */
+
+ printf("[%s %ld] Parent about to wait for signal\n",
+ currTime("%T"), (long) getpid());
+ sigemptyset(&emptyMask);
+ if (sigsuspend(&emptyMask) == -1 && errno != EINTR)
+ errExit("sigsuspend");
+ printf("[%s %ld] Parent got signal\n", currTime("%T"), (long) getpid());
+
+ /* If required, return signal mask to its original state */
+
+ if (sigprocmask(SIG_SETMASK, &origMask, NULL) == -1)
+ errExit("sigprocmask");
+
+ /* Parent carries on to do other things... */
+
+ exit(EXIT_SUCCESS);
+ }
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 25-2 */
+
+/* fork_stdio_buf.c
+
+ Experiment with fork() and stdio buffering.
+*/
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ printf("Hello world\n");
+ write(STDOUT_FILENO, "Ciao\n", 5);
+
+ if (fork() == -1)
+ errExit("fork");
+
+ /* Both child and parent continue execution here */
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 24-5 */
+
+/* fork_whos_on_first.c
+
+ Parent repeatedly creates a child, and then processes both race to be the
+ first to print a message. (Each child terminates after printing its message.)
+ The results of running this program give us an idea of which of the two
+ processes--parent or child--is usually scheduled first after a fork().
+
+ Whether the child or the parent is scheduled first after fork() has
+ changed a number of times across different kernel versions.
+*/
+#include <sys/wait.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int numChildren, j;
+ pid_t childPid;
+
+ if (argc > 1 && strcmp(argv[1], "--help") == 0)
+ usageErr("%s [num-children]\n", argv[0]);
+
+ numChildren = (argc > 1) ? getInt(argv[1], GN_GT_0, "num-children") : 1;
+
+ setbuf(stdout, NULL); /* Make stdout unbuffered */
+
+ for (j = 0; j < numChildren; j++) {
+ switch (childPid = fork()) {
+ case -1:
+ errExit("fork");
+
+ case 0:
+ printf("%d child\n", j);
+ _exit(EXIT_SUCCESS);
+
+ default:
+ printf("%d parent\n", j);
+ wait(NULL); /* Wait for child to terminate */
+ break;
+ }
+ }
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+#!/usr/bin/awk -f
+#
+# fork_whos_on_first.count.awk
+#
+# Copyright, Michael Kerrisk, 2002
+#
+# Read (on stdin) the results of running the fork_whos_on_first.c
+# program to assess the percentage iof cases in which parent or child
+# was first to print a post-fork() message
+#
+BEGIN {
+ last = -1;
+}
+
+{
+ # match pairs of lines by field 1 (loop counter)
+
+ if ($1 != last) {
+ cat[$2]++;
+ tot++;
+ }
+ last = $1;
+}
+
+ # Our input file may be long - periodically print a progress
+ # message with statistics so far
+NR % 200000 == 0 {
+ print "Num children = ", NR / 2;
+ for (k in cat)
+ printf "%-6s %6d %6.2f%%\n", k, cat[k], cat[k] / tot * 100;
+}
+
+END {
+ print "All done";
+ for (k in cat)
+ printf "%-6s %6d %6.2f%%\n", k, cat[k], cat[k] / tot * 100;
+}
--- /dev/null
+#!/usr/bin/awk -f
+length > max { max = length; }
+END { print max; }
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 26-4 */
+
+/* make_zombie.c
+
+ Demonstrate how a child process becomes a zombie in the interval between
+ the time it exits, and the time its parent performs a wait (or exits, at
+ which time it is adopted by init(8), which does a wait, thus releasing
+ the zombie).
+*/
+#include <signal.h>
+#include <libgen.h> /* For basename() declaration */
+#include "tlpi_hdr.h"
+
+#define CMD_SIZE 200
+
+int
+main(int argc, char *argv[])
+{
+ char cmd[CMD_SIZE];
+ pid_t childPid;
+
+ setbuf(stdout, NULL); /* Disable buffering of stdout */
+
+ printf("Parent PID=%ld\n", (long) getpid());
+
+ switch (childPid = fork()) {
+ case -1:
+ errExit("fork");
+
+ case 0: /* Child: immediately exits to become zombie */
+ printf("Child (PID=%ld) exiting\n", (long) getpid());
+ _exit(EXIT_SUCCESS);
+
+ default: /* Parent */
+ sleep(3); /* Give child a chance to start and exit */
+ snprintf(cmd, CMD_SIZE, "ps | grep %s", basename(argv[0]));
+ system(cmd); /* View zombie child */
+
+ /* Now send the "sure kill" signal to the zombie */
+
+ if (kill(childPid, SIGKILL) == -1)
+ errMsg("kill");
+ sleep(3); /* Give child a chance to react to signal */
+ printf("After sending SIGKILL to zombie (PID=%ld):\n", (long) childPid);
+ system(cmd); /* View zombie child again */
+
+ exit(EXIT_SUCCESS);
+ }
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 26-5 */
+
+/* multi_SIGCHLD.c
+
+ Demonstrate the use of a handler for the SIGCHLD signal, and that multiple
+ SIGCHLD signals are not queued while the signal is blocked during the
+ execution of the handler.
+*/
+#include <signal.h>
+#include <sys/wait.h>
+#include "print_wait_status.h"
+#include "curr_time.h"
+#include "tlpi_hdr.h"
+
+static volatile int numLiveChildren = 0;
+ /* Number of children started but not yet waited on */
+
+static void
+sigchldHandler(int sig)
+{
+ int status, savedErrno;
+ pid_t childPid;
+
+ /* UNSAFE: This handler uses non-async-signal-safe functions
+ (printf(), printWaitStatus(), currTime(); see Section 21.1.2) */
+
+ savedErrno = errno; /* In case we modify 'errno' */
+
+ printf("%s handler: Caught SIGCHLD\n", currTime("%T"));
+
+ /* Do nonblocking waits until no more dead children are found */
+
+ while ((childPid = waitpid(-1, &status, WNOHANG)) > 0) {
+ printf("%s handler: Reaped child %ld - ", currTime("%T"),
+ (long) childPid);
+ printWaitStatus(NULL, status);
+ numLiveChildren--;
+ }
+
+ if (childPid == -1 && errno != ECHILD)
+ errMsg("waitpid");
+
+ sleep(5); /* Artificially lengthen execution of handler */
+ printf("%s handler: returning\n", currTime("%T"));
+
+ errno = savedErrno;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int j, sigCnt;
+ sigset_t blockMask, emptyMask;
+ struct sigaction sa;
+
+ if (argc < 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s child-sleep-time...\n", argv[0]);
+
+ setbuf(stdout, NULL); /* Disable buffering of stdout */
+
+ sigCnt = 0;
+ numLiveChildren = argc - 1;
+
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sa.sa_handler = sigchldHandler;
+ if (sigaction(SIGCHLD, &sa, NULL) == -1)
+ errExit("sigaction");
+
+ /* Block SIGCHLD to prevent its delivery if a child terminates
+ before the parent commences the sigsuspend() loop below */
+
+ sigemptyset(&blockMask);
+ sigaddset(&blockMask, SIGCHLD);
+ if (sigprocmask(SIG_SETMASK, &blockMask, NULL) == -1)
+ errExit("sigprocmask");
+
+ /* Create one child process for each command-line argument */
+
+ for (j = 1; j < argc; j++) {
+ switch (fork()) {
+ case -1:
+ errExit("fork");
+
+ case 0: /* Child - sleeps and then exits */
+ sleep(getInt(argv[j], GN_NONNEG, "child-sleep-time"));
+ printf("%s Child %d (PID=%ld) exiting\n", currTime("%T"),
+ j, (long) getpid());
+ _exit(EXIT_SUCCESS);
+
+ default: /* Parent - loops to create next child */
+ break;
+ }
+ }
+
+ /* Parent comes here: wait for SIGCHLD until all children are dead */
+
+ sigemptyset(&emptyMask);
+ while (numLiveChildren > 0) {
+ if (sigsuspend(&emptyMask) == -1 && errno != EINTR)
+ errExit("sigsuspend");
+ sigCnt++;
+ }
+
+ printf("%s All %d children have terminated; SIGCHLD was caught "
+ "%d times\n", currTime("%T"), argc - 1, sigCnt);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 26-1 */
+
+/* multi_wait.c
+
+ Demonstrate the use of wait(2): create multiple children and then wait
+ for them all.
+
+ Usage: multi_wait sleep-time...
+
+ One child process is created for each command-line argument. Each child
+ sleeps for the number of seconds specified in the corresponding command-line
+ argument before exiting. After all children have been created, the parent
+ loops, waiting for terminated children, and displaying their PIDs.
+*/
+#include <sys/wait.h>
+#include <time.h>
+#include "curr_time.h" /* Declaration of currTime() */
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int numDead; /* Number of children so far waited for */
+ pid_t childPid; /* PID of waited for child */
+ int j;
+
+ if (argc < 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s sleep-time...\n", argv[0]);
+
+ setbuf(stdout, NULL); /* Disable buffering of stdout */
+
+ for (j = 1; j < argc; j++) { /* Create one child for each argument */
+ switch (fork()) {
+ case -1:
+ errExit("fork");
+
+ case 0: /* Child sleeps for a while then exits */
+ printf("[%s] child %d started with PID %ld, sleeping %s "
+ "seconds\n", currTime("%T"), j, (long) getpid(),
+ argv[j]);
+ sleep(getInt(argv[j], GN_NONNEG, "sleep-time"));
+ _exit(EXIT_SUCCESS);
+
+ default: /* Parent just continues around loop */
+ break;
+ }
+ }
+
+ numDead = 0;
+ for (;;) { /* Parent waits for each child to exit */
+ childPid = wait(NULL);
+ if (childPid == -1) {
+ if (errno == ECHILD) {
+ printf("No more children - bye!\n");
+ exit(EXIT_SUCCESS);
+ } else { /* Some other (unexpected) error */
+ errExit("wait");
+ }
+ }
+
+ numDead++;
+ printf("[%s] wait() returned child PID %ld (numDead=%d)\n",
+ currTime("%T"), (long) childPid, numDead);
+ }
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 6-2 */
+
+/* necho.c
+
+ A simple version of echo(1): echo our command-line arguments.
+*/
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int j;
+
+ for (j = 0; j < argc; j++)
+ printf("argv[%d] = %s\n", j, argv[j]);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter Z */
+
+/* orphan.c
+
+ Demonstrate how a child becomes orphaned (and adopted by init(8),
+ whose PID is 1) when its parent exits.
+*/
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ pid_t ppid;
+
+ setbuf(stdout, NULL); /* Disable buffering of stdout */
+
+ switch (fork()) {
+ case -1:
+ errExit("fork");
+
+ case 0: /* Child */
+ while ((ppid = getppid()) != 1) { /* Loop until orphaned */
+ printf("Child running (parent PID=%ld)\n", (long) ppid);
+ sleep(1);
+ }
+ printf("Child is orphaned (parent PID=%ld)\n", (long) ppid);
+ _exit(EXIT_SUCCESS);
+
+ default: /* Parent */
+ printf("Parent (PID=%ld) sleeping\n", (long) getpid());
+ sleep(3); /* Give child a chance to start */
+ printf("Parent exiting\n");
+ exit(EXIT_SUCCESS);
+ }
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 26-2 */
+
+/* print_wait_status.c
+
+ Dissect and print the process termination status returned by wait()
+ and related calls.
+*/
+#define _GNU_SOURCE /* Get strsignal() declaration from <string.h> */
+#include <string.h>
+#include <sys/wait.h>
+#include "print_wait_status.h" /* Declaration of printWaitStatus() */
+#include "tlpi_hdr.h"
+
+/* NOTE: The following function employs printf(), which is not
+ async-signal-safe (see Section 21.1.2). As such, this function is
+ also not async-signal-safe (i.e., beware of calling it from a
+ SIGCHLD handler). */
+
+void /* Examine a wait() status using the W* macros */
+printWaitStatus(const char *msg, int status)
+{
+ if (msg != NULL)
+ printf("%s", msg);
+
+ if (WIFEXITED(status)) {
+ printf("child exited, status=%d\n", WEXITSTATUS(status));
+
+ } else if (WIFSIGNALED(status)) {
+ printf("child killed by signal %d (%s)",
+ WTERMSIG(status), strsignal(WTERMSIG(status)));
+#ifdef WCOREDUMP /* Not in SUSv3, may be absent on some systems */
+ if (WCOREDUMP(status))
+ printf(" (core dumped)");
+#endif
+ printf("\n");
+
+ } else if (WIFSTOPPED(status)) {
+ printf("child stopped by signal %d (%s)\n",
+ WSTOPSIG(status), strsignal(WSTOPSIG(status)));
+
+#ifdef WIFCONTINUED /* SUSv3 has this, but older Linux versions and
+ some other UNIX implementations don't */
+ } else if (WIFCONTINUED(status)) {
+ printf("child continued\n");
+#endif
+
+ } else { /* Should never happen */
+ printf("what happened to this child? (status=%x)\n",
+ (unsigned int) status);
+ }
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Header file for Listing 26-2 */
+
+/* print_wait_status.h
+
+ Header file for print_wait_status.c.
+*/
+#ifndef PRINT_WAIT_STATUS_H /* Prevent accidental double inclusion */
+#define PRINT_WAIT_STATUS_H
+
+void printWaitStatus(const char *msg, int status);
+
+#endif
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 27-8 */
+
+/* simple_system.c
+
+ A simple implementation of system(3) that excludes signal manipulation.
+
+ See also system.c.
+*/
+#include <unistd.h>
+#include <sys/wait.h>
+#include <sys/types.h>
+
+int
+system(char *command)
+{
+ int status;
+ pid_t childPid;
+
+ switch (childPid = fork()) {
+ case -1: /* Error */
+ return -1;
+
+ case 0: /* Child */
+ execl("/bin/sh", "sh", "-c", command, (char *) NULL);
+ _exit(127); /* Failed exec */
+
+ default: /* Parent */
+ if (waitpid(childPid, &status, 0) == -1)
+ return -1;
+ else
+ return status;
+ }
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 27-9 */
+
+/* system.c
+
+ An implementation of system(3).
+*/
+#include <unistd.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include <sys/types.h>
+#include <errno.h>
+
+int
+system(const char *command)
+{
+ sigset_t blockMask, origMask;
+ struct sigaction saIgnore, saOrigQuit, saOrigInt, saDefault;
+ pid_t childPid;
+ int status, savedErrno;
+
+ if (command == NULL) /* Is a shell available? */
+ return system(":") == 0;
+
+ /* The parent process (the caller of system()) blocks SIGCHLD
+ and ignore SIGINT and SIGQUIT while the child is executing.
+ We must change the signal settings prior to forking, to avoid
+ possible race conditions. This means that we must undo the
+ effects of the following in the child after fork(). */
+
+ sigemptyset(&blockMask); /* Block SIGCHLD */
+ sigaddset(&blockMask, SIGCHLD);
+ sigprocmask(SIG_BLOCK, &blockMask, &origMask);
+
+ saIgnore.sa_handler = SIG_IGN; /* Ignore SIGINT and SIGQUIT */
+ saIgnore.sa_flags = 0;
+ sigemptyset(&saIgnore.sa_mask);
+ sigaction(SIGINT, &saIgnore, &saOrigInt);
+ sigaction(SIGQUIT, &saIgnore, &saOrigQuit);
+
+ switch (childPid = fork()) {
+ case -1: /* fork() failed */
+ status = -1;
+ break; /* Carry on to reset signal attributes */
+
+ case 0: /* Child: exec command */
+
+ /* We ignore possible error returns because the only specified error
+ is for a failed exec(), and because errors in these calls can't
+ affect the caller of system() (which is a separate process) */
+
+ saDefault.sa_handler = SIG_DFL;
+ saDefault.sa_flags = 0;
+ sigemptyset(&saDefault.sa_mask);
+
+ if (saOrigInt.sa_handler != SIG_IGN)
+ sigaction(SIGINT, &saDefault, NULL);
+ if (saOrigQuit.sa_handler != SIG_IGN)
+ sigaction(SIGQUIT, &saDefault, NULL);
+
+ sigprocmask(SIG_SETMASK, &origMask, NULL);
+
+ execl("/bin/sh", "sh", "-c", command, (char *) NULL);
+ _exit(127); /* We could not exec the shell */
+
+ default: /* Parent: wait for our child to terminate */
+
+ /* We must use waitpid() for this task; using wait() could inadvertently
+ collect the status of one of the caller's other children */
+
+ while (waitpid(childPid, &status, 0) == -1) {
+ if (errno != EINTR) { /* Error other than EINTR */
+ status = -1;
+ break; /* So exit loop */
+ }
+ }
+ break;
+ }
+
+ /* Unblock SIGCHLD, restore dispositions of SIGINT and SIGQUIT */
+
+ savedErrno = errno; /* The following may change 'errno' */
+
+ sigprocmask(SIG_SETMASK, &origMask, NULL);
+ sigaction(SIGINT, &saOrigInt, NULL);
+ sigaction(SIGQUIT, &saOrigQuit, NULL);
+
+ errno = savedErrno;
+
+ return status;
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 28-3 */
+
+/* t_clone.c
+
+ Demonstrate the use of the Linux-specific clone() system call.
+*/
+#define _GNU_SOURCE
+#include <signal.h>
+#include <sys/wait.h>
+#include <fcntl.h>
+#include <sched.h>
+#include "tlpi_hdr.h"
+
+#ifndef CHILD_SIG
+#define CHILD_SIG SIGUSR1 /* Signal to be generated on termination
+ of cloned child */
+#endif
+
+static int /* Startup function for cloned child */
+childFunc(void *arg)
+{
+ if (close(*((int *) arg)) == -1)
+ errExit("close");
+
+ return 0; /* Child terminates now */
+}
+
+int
+main(int argc, char *argv[])
+{
+ const int STACK_SIZE = 65536; /* Stack size for cloned child */
+ char *stack; /* Start of stack buffer */
+ char *stackTop; /* End of stack buffer */
+ int s, fd, flags;
+
+ fd = open("/dev/null", O_RDWR); /* Child will close this fd */
+ if (fd == -1)
+ errExit("open");
+
+ /* If argc > 1, child shares file descriptor table with parent */
+
+ flags = (argc > 1) ? CLONE_FILES : 0;
+
+ /* Allocate stack for child */
+
+ stack = malloc(STACK_SIZE);
+ if (stack == NULL)
+ errExit("malloc");
+ stackTop = stack + STACK_SIZE; /* Assume stack grows downward */
+
+ /* Ignore CHILD_SIG, in case it is a signal whose default is to
+ terminate the process; but don't ignore SIGCHLD (which is ignored
+ by default), since that would prevent the creation of a zombie. */
+
+ if (CHILD_SIG != 0 && CHILD_SIG != SIGCHLD)
+ if (signal(CHILD_SIG, SIG_IGN) == SIG_ERR) errExit("signal");
+
+ /* Create child; child commences execution in childFunc() */
+
+ if (clone(childFunc, stackTop, flags | CHILD_SIG, (void *) &fd) == -1)
+ errExit("clone");
+
+ /* Parent falls through to here. Wait for child; __WCLONE is
+ needed for child notifying with signal other than SIGCHLD. */
+
+ if (waitpid(-1, NULL, (CHILD_SIG != SIGCHLD) ? __WCLONE : 0) == -1)
+ errExit("waitpid");
+ printf("child has terminated\n");
+
+ /* Did close() of file descriptor in child affect parent? */
+
+ s = write(fd, "x", 1);
+ if (s == -1 && errno == EBADF)
+ printf("file descriptor %d has been closed\n", fd);
+ else if (s == -1)
+ printf("write() on file descriptor %d failed "
+ "unexpectedly (%s)\n", fd, strerror(errno));
+ else
+ printf("write() on file descriptor %d succeeded\n", fd);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 27-5 */
+
+/* t_execl.c
+
+ Demonstrate the use of execl() to execute printenv(1).
+*/
+#include <stdlib.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ printf("Initial value of USER: %s\n", getenv("USER"));
+ if (putenv("USER=britta") != 0)
+ errExit("putenv");
+
+ /* exec printenv to display the USER and SHELL environment vars */
+
+ execl("/usr/bin/printenv", "printenv", "USER", "SHELL", (char *) NULL);
+ errExit("execl"); /* If we get here, something went wrong */
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 27-4 */
+
+/* t_execle.c
+
+ Demonstrate the use of execle() to execute a program.
+*/
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ char *envVec[] = { "GREET=salut", "BYE=adieu", NULL };
+ char *filename;
+
+ if (argc != 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s pathname\n", argv[0]);
+
+ /* Execute the program specified in argv[1] */
+
+ filename = strrchr(argv[1], '/'); /* Get basename from argv[1] */
+ if (filename != NULL)
+ filename++;
+ else
+ filename = argv[1];
+
+ execle(argv[1], filename, "hello world", "goodbye", (char *) NULL, envVec);
+ errExit("execle"); /* If we get here, something went wrong */
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 27-3 */
+
+/* t_execlp.c
+
+ Demonstrate the use of execlp() to execute a program.
+*/
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ if (argc != 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s pathname\n", argv[0]);
+
+ /* Execute the program specified in argv[1] */
+
+ execlp(argv[1], argv[1], "hello world", (char *) NULL);
+ errExit("execlp"); /* If we get here, something went wrong */
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 27-1 */
+
+/* t_execve.c
+
+ Demonstrate the use of execve() to execute a program.
+*/
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ char *argVec[10]; /* Larger than required */
+ char *envVec[] = { "GREET=salut", "BYE=adieu", NULL };
+
+ if (argc != 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s pathname\n", argv[0]);
+
+ /* Create an argument list for the new program */
+
+ argVec[0] = strrchr(argv[1], '/'); /* Get basename from argv[1] */
+ if (argVec[0] != NULL)
+ argVec[0]++;
+ else
+ argVec[0] = argv[1];
+ argVec[1] = "hello world";
+ argVec[2] = "goodbye";
+ argVec[3] = NULL; /* List must be NULL-terminated */
+
+ /* Execute the program specified in argv[1] */
+
+ execve(argv[1], argVec, envVec);
+ errExit("execve"); /* If we get here, something went wrong */
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 24-1 */
+
+/* t_fork.c
+
+ Demonstrate the use of fork(), showing that parent and child
+ get separate copies of stack and data segments.
+*/
+#include "tlpi_hdr.h"
+
+static int idata = 111; /* Allocated in data segment */
+
+int
+main(int argc, char *argv[])
+{
+ int istack = 222; /* Allocated in stack segment */
+ pid_t childPid;
+
+ switch (childPid = fork()) {
+ case -1:
+ errExit("fork");
+
+ case 0:
+ idata *= 3;
+ istack *= 3;
+ break;
+
+ default:
+ sleep(3); /* Give child a chance to execute */
+ break;
+ }
+
+ /* Both parent and child come here */
+
+ printf("PID=%ld %s idata=%d istack=%d\n", (long) getpid(),
+ (childPid == 0) ? "(child) " : "(parent)", idata, istack);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 27-7 */
+
+/* t_system.c
+
+ Demonstrate the use of system(3) to execute a shell command.
+*/
+#include <sys/wait.h>
+#include "print_wait_status.h"
+#include "tlpi_hdr.h"
+
+#define MAX_CMD_LEN 200
+
+int
+main(int argc, char *argv[])
+{
+ char str[MAX_CMD_LEN]; /* Command to be executed by system() */
+ int status; /* Status return from system() */
+
+ for (;;) { /* Read and execute a shell command */
+ printf("Command: ");
+ fflush(stdout);
+ if (fgets(str, MAX_CMD_LEN, stdin) == NULL)
+ break; /* end-of-file */
+
+ status = system(str);
+ printf("system() returned: status=0x%04x (%d,%d)\n",
+ (unsigned int) status, status >> 8, status & 0xff);
+
+ if (status == -1) {
+ errExit("system");
+ } else {
+ if (WIFEXITED(status) && WEXITSTATUS(status) == 127)
+ printf("(Probably) could not invoke shell\n");
+ else /* Shell successfully executed command */
+ printWaitStatus(NULL, status);
+ }
+ }
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 24-4 */
+
+/* t_vfork.c
+
+ Demonstrate the use of vfork() to create a child process.
+*/
+#define _BSD_SOURCE /* To get vfork() declaration from <unistd.h>
+ in case _XOPEN_SOURCE >= 700 */
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int istack = 222;
+
+ switch (vfork()) {
+ case -1:
+ errExit("vfork");
+
+ case 0: /* Child executes first, in parent's memory space */
+ sleep(3); /* Even if we sleep for a while,
+ parent still is not scheduled */
+ write(STDOUT_FILENO, "Child executing\n", 16);
+ istack *= 3; /* This change will be seen by parent */
+ _exit(EXIT_SUCCESS);
+
+ default: /* Parent is blocked until child exits */
+ write(STDOUT_FILENO, "Parent executing\n", 17);
+ printf("istack=%d\n", istack);
+ exit(EXIT_SUCCESS);
+ }
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 24-2 */
+
+/* vfork_fd_test.c
+
+ Demonstrate that a vfork()ed child has a separate set of file descriptors
+ from its parent.
+*/
+#define _BSD_SOURCE /* To get vfork() declaration from <unistd.h>
+ in case _XOPEN_SOURCE >= 700 */
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ switch (vfork()) {
+ case -1: errExit("vfork");
+
+ case 0: if (close(STDOUT_FILENO) == -1)
+ errMsg("close - child");
+ _exit(EXIT_SUCCESS);
+
+ default: break;
+ }
+
+ /* Now parent closes STDOUT_FILENO twice: only the second close
+ should fail, indicating that the close(STDOUT_FILENO) by the
+ child did not affect the parent. */
+
+ if (close(STDOUT_FILENO) == -1)
+ errMsg("close");
+ if (close(STDOUT_FILENO) == -1)
+ errMsg("close");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE = sched_set sched_view t_setpriority
+
+LINUX_EXE = demo_sched_fifo t_sched_setaffinity t_sched_getaffinity
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+LDLIBS = ${IMPL_LDLIBS} #CF[ ${LINUX_LIBRT} ]
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+clean :
+ ${RM} ${EXE} *.o
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 35-3 */
+
+/* demo_sched_fifo.c
+
+ This program demonstrates the use of realtime scheduling policies. It creates
+ two processes, each running under the SCHED_FIFO scheduling policy. Each
+ process executes a function that prints a message every quarter of a second
+ of CPU time. After each second of consumed CPU time, the function and calls
+ sched_yield() to yield the CPU to the other process. Once a process has
+ consumed 3 seconds of CPU time, the function terminates.
+
+ This program must be run as superuser, or (on Linux 2.6.12 and later)
+ with a suitable RLIMIT_RTPRIO resource limit.
+*/
+#define _GNU_SOURCE
+#include <sched.h>
+#include <sys/resource.h>
+#include <sys/times.h>
+#include "tlpi_hdr.h"
+
+#define CSEC_STEP 25 /* CPU centiseconds between messages */
+
+static void
+useCPU(char *msg)
+{
+ struct tms tms;
+ int cpuCentisecs, prevStep, prevSec;
+
+ prevStep = 0;
+ prevSec = 0;
+ for (;;) {
+ if (times(&tms) == -1)
+ errExit("times");
+ cpuCentisecs = (tms.tms_utime + tms.tms_stime) * 100 /
+ sysconf(_SC_CLK_TCK);
+
+ if (cpuCentisecs >= prevStep + CSEC_STEP) {
+ prevStep += CSEC_STEP;
+ printf("%s (PID %ld) cpu=%0.2f\n", msg, (long) getpid(),
+ cpuCentisecs / 100.0);
+ }
+
+ if (cpuCentisecs > 300) /* Terminate after 3 seconds */
+ break;
+
+ if (cpuCentisecs >= prevSec + 100) { /* Yield once/second */
+ prevSec = cpuCentisecs;
+ sched_yield();
+ }
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct rlimit rlim;
+ struct sched_param sp;
+ cpu_set_t set;
+
+ setbuf(stdout, NULL); /* Disable buffering of stdout */
+
+ /* Confine all processes to a single CPU, so that the processes
+ won't run in parallel on multi-CPU systems. */
+
+ CPU_ZERO(&set);
+ CPU_SET(1, &set);
+
+ if (sched_setaffinity(getpid(), sizeof(set), &set) == -1)
+ errExit("sched_setaffinity");
+
+ /* Establish a CPU time limit. This demonstrates how we can
+ ensure that a runaway realtime process is terminated if we
+ make a programming error. The resource limit is inherited
+ by the child created using fork().
+
+ An alternative technique would be to make an alarm() call in each
+ process (since interval timers are not inherited across fork()). */
+
+ rlim.rlim_cur = rlim.rlim_max = 50;
+ if (setrlimit(RLIMIT_CPU, &rlim) == -1)
+ errExit("setrlimit");
+
+ /* Run the two processes in the lowest SCHED_FIFO priority */
+
+ sp.sched_priority = sched_get_priority_min(SCHED_FIFO);
+ if (sp.sched_priority == -1)
+ errExit("sched_get_priority_min");
+
+ if (sched_setscheduler(0, SCHED_FIFO, &sp) == -1)
+ errExit("sched_setscheduler");
+
+ switch (fork()) {
+ case -1:
+ errExit("fork");
+
+ case 0:
+ useCPU("child ");
+ exit(EXIT_SUCCESS);
+
+ default:
+ useCPU("parent");
+ exit(EXIT_SUCCESS);
+ }
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 35-2 */
+
+/* sched_set.c
+
+ Usage: sched_set policy priority pid...
+
+ Sets the policy and priority of all process specified by the 'pid' arguments.
+
+ See also sched_view.c.
+
+ The distribution version of this code is slightly different from the code
+ shown in the book in order to better fix a bug that was present in the code
+ as originally shown in the book. See the erratum for page 743
+ (http://man7.org/tlpi/errata/index.html#p_743).
+*/
+#include <sched.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int j, pol;
+ struct sched_param sp;
+
+ if (argc < 3 || strchr("rfo"
+#ifdef SCHED_BATCH /* Linux-specific */
+ "b"
+#endif
+#ifdef SCHED_IDLE /* Linux-specific */
+ "i"
+#endif
+ , argv[1][0]) == NULL)
+ usageErr("%s policy priority [pid...]\n"
+ " policy is 'r' (RR), 'f' (FIFO), "
+#ifdef SCHED_BATCH /* Linux-specific */
+ "'b' (BATCH), "
+#endif
+#ifdef SCHED_IDLE /* Linux-specific */
+ "'i' (IDLE), "
+#endif
+ "or 'o' (OTHER)\n",
+ argv[0]);
+
+ pol = (argv[1][0] == 'r') ? SCHED_RR :
+ (argv[1][0] == 'f') ? SCHED_FIFO :
+#ifdef SCHED_BATCH /* Linux-specific, since kernel 2.6.16 */
+ (argv[1][0] == 'b') ? SCHED_BATCH :
+#endif
+#ifdef SCHED_IDLE /* Linux-specific, since kernel 2.6.23 */
+ (argv[1][0] == 'i') ? SCHED_IDLE :
+#endif
+ SCHED_OTHER;
+
+ sp.sched_priority = getInt(argv[2], 0, "priority");
+
+ for (j = 3; j < argc; j++)
+ if (sched_setscheduler(getLong(argv[j], 0, "pid"), pol, &sp) == -1)
+ errExit("sched_setscheduler");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 35-3 */
+
+/* sched_view.c
+
+ Display the scheduling policy and priority for the processes whose PID
+ are provided on command line.
+
+ See also sched_set.c.
+*/
+#include <sched.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int j, pol;
+ struct sched_param sp;
+
+ for (j = 1; j < argc; j++) {
+ pol = sched_getscheduler(getLong(argv[j], 0, "pid"));
+ if (pol == -1)
+ errExit("sched_getscheduler");
+
+ if (sched_getparam(getLong(argv[j], 0, "pid"), &sp) == -1)
+ errExit("sched_getparam");
+
+ printf("%s: %-5s ", argv[j],
+ (pol == SCHED_OTHER) ? "OTHER" :
+ (pol == SCHED_RR) ? "RR" :
+ (pol == SCHED_FIFO) ? "FIFO" :
+#ifdef SCHED_BATCH /* Linux-specific */
+ (pol == SCHED_BATCH) ? "BATCH" :
+#endif
+#ifdef SCHED_IDLE /* Linux-specific */
+ (pol == SCHED_IDLE) ? "IDLE" :
+#endif
+ "???");
+ printf("%2d\n", sp.sched_priority);
+ }
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 35 */
+
+/* t_sched_getaffinity.c
+
+ Demonstrate the use of the sched_getaffinity() system call to retrieve
+ the CPU affinity of a process.
+
+ Usage: t_sched_getaffinity pid
+
+ See also t_sched_setaffinity.c.
+
+ This program is Linux-specific. The CPU affinity system calls are provided
+ since kernel 2.6.
+*/
+#define _GNU_SOURCE
+#include <sched.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ pid_t pid;
+ cpu_set_t set;
+ size_t s;
+ int cpu;
+
+ if (argc != 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s pid\n", argv[0]);
+
+ pid = getInt(argv[1], GN_NONNEG, "pid");
+
+ s = sched_getaffinity(pid, sizeof(cpu_set_t), &set);
+ if (s == -1)
+ errExit("sched_getaffinity");
+
+ printf("CPUs:");
+ for (cpu = 0; cpu < CPU_SETSIZE; cpu++)
+ if (CPU_ISSET(cpu, &set))
+ printf(" %d", cpu);
+ printf("\n");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 35 */
+
+/* t_sched_setaffinity.c
+
+ Usage: t_sched_setaffinity pid mask
+
+ Set the CPU affinity of the process identified by 'pid' according
+ to the given 'mask'. For example, the following specifies that the
+ given process should run on any but the first CPU of a 4-CPU system:
+
+ t_sched_setaffinity <pid> 0xe
+
+ (0xe = 1110 binary)
+
+ See also t_sched_getaffinity.c.
+
+ This program is Linux-specific. The CPU affinity system calls are provided
+ since kernel 2.6.
+*/
+#define _GNU_SOURCE
+#include <sched.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ pid_t pid;
+ cpu_set_t set;
+ int cpu;
+ unsigned long mask;
+
+ if (argc != 3 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s pid mask\n", argv[0]);
+
+ pid = getInt(argv[1], GN_NONNEG, "pid");
+ mask = getLong(argv[2], GN_ANY_BASE, "octal-mask");
+
+ CPU_ZERO(&set);
+
+ for (cpu = 0; mask > 0; cpu++, mask >>= 1)
+ if (mask & 1)
+ CPU_SET(cpu, &set);
+
+ if (sched_setaffinity(pid, sizeof(set), &set) == -1)
+ errExit("sched_setaffinity");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 35-1 */
+
+/* t_setpriority.c
+
+ Demonstrate the use of setpriority(2) and getpriority(2) to change and
+ retrieve a process's nice value.
+
+ Usage: t_setpriority {p|g|u} id priority
+*/
+#include <sys/time.h>
+#include <sys/resource.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int which, prio;
+ id_t who;
+
+ if (argc != 4 || strchr("pgu", argv[1][0]) == NULL)
+ usageErr("%s {p|g|u} who priority\n"
+ " set priority of: p=process; g=process group; "
+ "u=processes for user\n", argv[0]);
+
+ /* Set nice value according to command-line arguments */
+
+ which = (argv[1][0] == 'p') ? PRIO_PROCESS :
+ (argv[1][0] == 'g') ? PRIO_PGRP : PRIO_USER;
+ who = getLong(argv[2], 0, "who");
+ prio = getInt(argv[3], 0, "prio");
+
+ if (setpriority(which, who, prio) == -1)
+ errExit("setpriority");
+
+ /* Retrieve nice value to check the change */
+
+ errno = 0; /* Because successful call may return -1 */
+ prio = getpriority(which, who);
+ if (prio == -1 && errno != 0)
+ errExit("getpriority");
+
+ printf("Nice value = %d\n", prio);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE = rusage rusage_wait
+
+LINUX_EXE = rlimit_nproc
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+clean :
+ ${RM} ${EXE} *.o
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 36-2 */
+
+/* print_rlimit.c
+
+ Print the soft and hard values of a specified resource limit.
+*/
+#include <sys/resource.h>
+#include "print_rlimit.h" /* Declares function defined here */
+#include "tlpi_hdr.h"
+
+int /* Print 'msg' followed by limits for 'resource' */
+printRlimit(const char *msg, int resource)
+{
+ struct rlimit rlim;
+
+ if (getrlimit(resource, &rlim) == -1)
+ return -1;
+
+ printf("%s soft=", msg);
+ if (rlim.rlim_cur == RLIM_INFINITY)
+ printf("infinite");
+#ifdef RLIM_SAVED_CUR /* Not defined on some implementations */
+ else if (rlim.rlim_cur == RLIM_SAVED_CUR)
+ printf("unrepresentable");
+#endif
+ else
+ printf("%lld", (long long) rlim.rlim_cur);
+
+ printf("; hard=");
+ if (rlim.rlim_max == RLIM_INFINITY)
+ printf("infinite\n");
+#ifdef RLIM_SAVED_MAX /* Not defined on some implementations */
+ else if (rlim.rlim_max == RLIM_SAVED_MAX)
+ printf("unrepresentable");
+#endif
+ else
+ printf("%lld\n", (long long) rlim.rlim_max);
+
+ return 0;
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Header file for Listing 36-2 */
+
+/* print_rlimit.h
+
+ Header file for print_rlimit.c.
+*/
+#ifndef PRINT_RLIMIT_H /* Prevent accidental double inclusion */
+#define PRINT_RLIMIT_H
+
+int printRlimit(const char *msg, int resource);
+
+#endif
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 36-2:b */
+
+/* print_rusage.c
+
+ Print the contents of an 'rusage' (resource usage) structure
+ (returned by a call to getrusage()).
+*/
+#include <sys/resource.h>
+#include "print_rusage.h"
+#include "tlpi_hdr.h"
+
+void
+printRusage(const char *leader, const struct rusage *ru)
+{
+ const char *ldr;
+
+ ldr = (leader == NULL) ? "" : leader;
+
+ printf("%sCPU time (secs): user=%.3f; system=%.3f\n", ldr,
+ ru->ru_utime.tv_sec + ru->ru_utime.tv_usec / 1000000.0,
+ ru->ru_stime.tv_sec + ru->ru_stime.tv_usec / 1000000.0);
+ printf("%sMax resident set size: %ld\n", ldr, ru->ru_maxrss);
+ printf("%sIntegral shared memory: %ld\n", ldr, ru->ru_ixrss);
+ printf("%sIntegral unshared data: %ld\n", ldr, ru->ru_idrss);
+ printf("%sIntegral unshared stack: %ld\n", ldr, ru->ru_isrss);
+ printf("%sPage reclaims: %ld\n", ldr, ru->ru_minflt);
+ printf("%sPage faults: %ld\n", ldr, ru->ru_majflt);
+ printf("%sSwaps: %ld\n", ldr, ru->ru_nswap);
+ printf("%sBlock I/Os: input=%ld; output=%ld\n",
+ ldr, ru->ru_inblock, ru->ru_oublock);
+ printf("%sSignals received: %ld\n", ldr, ru->ru_nsignals);
+ printf("%sIPC messages: sent=%ld; received=%ld\n",
+ ldr, ru->ru_msgsnd, ru->ru_msgrcv);
+ printf("%sContext switches: voluntary=%ld; "
+ "involuntary=%ld\n", ldr, ru->ru_nvcsw, ru->ru_nivcsw);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 36-2:c */
+
+/* print_rusage.h
+
+ Header file for print_rusage.c.
+*/
+#ifndef PRINT_RUSAGE_H /* Prevent accidental double inclusion */
+#define PRINT_RUSAGE_H
+
+#include <sys/resource.h>
+
+void printRusage(const char *leader, const struct rusage *ru);
+
+#endif
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 36-3 */
+
+/* rlimit_nproc.c
+
+ Experiment with maximum processes resource limit.
+
+ Usage: rlimit_nproc hard-limit soft-limit
+
+ NOTE: Only Linux and the BSDs support the RLIMIT_NPROC resource limit.
+*/
+#include <sys/resource.h>
+#include "print_rlimit.h" /* Declaration of printRlimit() */
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ struct rlimit rl;
+ int j;
+ pid_t childPid;
+
+ if (argc < 2 || argc > 3 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s soft-limit [hard-limit]\n", argv[0]);
+
+ printRlimit("Initial maximum process limits: ", RLIMIT_NPROC);
+
+ /* Set new process limits (hard == soft if not specified) */
+
+ rl.rlim_cur = (argv[1][0] == 'i') ? RLIM_INFINITY :
+ getInt(argv[1], 0, "soft-limit");
+ rl.rlim_max = (argc == 2) ? rl.rlim_cur :
+ (argv[2][0] == 'i') ? RLIM_INFINITY :
+ getInt(argv[2], 0, "hard-limit");
+ if (setrlimit(RLIMIT_NPROC, &rl) == -1)
+ errExit("setrlimit");
+
+ printRlimit("New maximum process limits: ", RLIMIT_NPROC);
+
+ /* Create as many children as possible */
+
+ for (j = 1; ; j++) {
+ switch (childPid = fork()) {
+ case -1: errExit("fork");
+
+ case 0: _exit(EXIT_SUCCESS); /* Child */
+
+ default: /* Parent: display message about each new child
+ and let the resulting zombies accumulate */
+ printf("Child %d (PID=%ld) started\n", j, (long) childPid);
+ break;
+ }
+ }
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 36-2:a */
+
+/* rusage.c
+
+ Execute a command and then print a summary of the resources (as retrieved
+ by getrusage()) that it used.
+
+ See also print_rudage.c.
+*/
+#include <sys/resource.h>
+#include <sys/wait.h>
+#include "print_rusage.h"
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ pid_t childPid;
+ struct rusage ru;
+
+ if (argc < 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s command arg...\n", argv[0]);
+
+ switch (childPid = fork()) {
+ case -1:
+ errExit("fork");
+
+ case 0:
+ execvp(argv[1], &argv[1]);
+ errExit("execvp");
+
+ default:
+ printf("Command PID: %ld\n", (long) childPid);
+ if (wait(NULL) == -1)
+ errExit("wait");
+ if (getrusage(RUSAGE_CHILDREN, &ru) == -1)
+ errExit("getrusage");
+
+ printf("\n");
+ printRusage("\t", &ru);
+ exit(EXIT_SUCCESS);
+ }
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 36-1 */
+
+/* rusage_wait.c
+
+ Show that getrusage() RUSAGE_CHILDREN retrieves information
+ only about children that have been waited on.
+*/
+#include <sys/wait.h>
+#include <signal.h>
+#include <time.h>
+#include <sys/resource.h>
+#include "tlpi_hdr.h"
+
+#define NSECS 3 /* Amount of CPU to consume in child */
+
+#define SIG SIGUSR1 /* Child uses this signal to tell parent
+ that it is about to terminate */
+
+static void
+handler(int sig)
+{
+ /* Do nothing: just interrupt sigsuspend() */
+}
+
+static void
+printChildRusage(const char *msg)
+{
+ struct rusage ru;
+
+ printf("%s", msg);
+ if (getrusage(RUSAGE_CHILDREN, &ru) == -1)
+ errExit("getrusage");
+ printf("user CPU=%.2f secs; system CPU=%.2f secs\n",
+ ru.ru_utime.tv_sec + ru.ru_utime.tv_usec / 1000000.0,
+ ru.ru_stime.tv_sec + ru.ru_stime.tv_usec / 1000000.0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ clock_t start;
+ sigset_t mask;
+ struct sigaction sa;
+
+ setbuf(stdout, NULL); /* Disable buffering of stdout */
+
+ sa.sa_handler = handler;
+ sa.sa_flags = 0;
+ sigemptyset(&sa.sa_mask);
+ if (sigaction(SIG, &sa, NULL) == -1)
+ errExit("sigaction");
+
+ /* Child informs parent of impending termination using a signal;
+ block that signal until the parent is ready to catch it. */
+
+ sigemptyset(&mask);
+ sigaddset(&mask, SIG);
+ if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
+ errExit("sigprocmask");
+
+ switch (fork()) {
+ case -1:
+ errExit("fork");
+
+ case 0: /* Child */
+ for (start = clock(); clock() - start < NSECS * CLOCKS_PER_SEC;)
+ continue; /* Burn NSECS seconds of CPU time */
+ printf("Child terminating\n");
+
+ /* Tell parent we're nearly done */
+
+ if (kill(getppid(), SIG) == -1)
+ errExit("kill");
+ _exit(EXIT_SUCCESS);
+
+ default: /* Parent */
+ sigemptyset(&mask);
+ sigsuspend(&mask); /* Wait for signal from child */
+
+ sleep(2); /* Allow child a bit more time to terminate */
+
+ printChildRusage("Before wait: ");
+ if (wait(NULL) == -1)
+ errExit("wait");
+ printChildRusage("After wait: ");
+ exit(EXIT_SUCCESS);
+ }
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE = syscall_speed
+
+LINUX_EXE =
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+clean :
+ ${RM} ${EXE} *.o
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 3 */
+
+/* syscall_speed.c
+
+ By repeatedly invoking a simple system call (getppid()), we can get some
+ idea of the cost of making system calls.
+
+ Usage: time syscall_speed numcalls
+ Def=10000000
+
+ Compiling with -DNOSYSCALL causes a call to a simple function
+ returning an integer, which can be used to compare the overhead
+ of a simple function call against that of a system call.
+*/
+#include "tlpi_hdr.h"
+
+#ifdef NOSYSCALL
+static int myfunc() { return 1; }
+#endif
+
+int
+main(int argc, char *argv[])
+{
+ int numCalls, j;
+
+ numCalls = (argc > 1) ? getInt(argv[1], GN_GT_0, "num-calls") : 10000000;
+
+#ifdef NOSYSCALL
+ printf("Calling normal function\n");
+#else
+ printf("Calling getppid()\n");
+#endif
+
+ for (j = 0; j < numCalls; j++)
+#ifdef NOSYSCALL
+ myfunc();
+#else
+ getppid();
+#endif
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE = psem_getvalue psem_create psem_post psem_unlink \
+ psem_timedwait psem_trywait psem_wait thread_incr_psem
+
+LINUX_EXE =
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+CFLAGS = ${IMPL_CFLAGS} ${IMPL_THREAD_FLAGS}
+LDLIBS = ${IMPL_LDLIBS} ${IMPL_THREAD_FLAGS}
+ # POSIX semaphores need the NPTL thread library on Linux
+
+# psem_timedwait uses clock_gettime() which is in librt
+psem_timedwait: psem_timedwait.o
+ ${CC} -o $@ psem_timedwait.o ${CFLAGS} ${LDLIBS} ${LINUX_LIBRT}
+
+clean :
+ ${RM} ${EXE} *.o
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 53-1 */
+
+/* psem_create.c
+
+ Create a POSIX named semaphore.
+
+ Usage as shown in usageError().
+
+ On Linux, named semaphores are supported with kernel 2.6 or later, and
+ a glibc that provides the NPTL threading implementation.
+*/
+#include <semaphore.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+static void
+usageError(const char *progName)
+{
+ fprintf(stderr, "Usage: %s [-cx] name [octal-perms [value]]\n", progName);
+ fprintf(stderr, " -c Create semaphore (O_CREAT)\n");
+ fprintf(stderr, " -x Create exclusively (O_EXCL)\n");
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int flags, opt;
+ mode_t perms;
+ unsigned int value;
+ sem_t *sem;
+
+ flags = 0;
+ while ((opt = getopt(argc, argv, "cx")) != -1) {
+ switch (opt) {
+ case 'c': flags |= O_CREAT; break;
+ case 'x': flags |= O_EXCL; break;
+ default: usageError(argv[0]);
+ }
+ }
+
+ if (optind >= argc)
+ usageError(argv[0]);
+
+ /* Default permissions are rw-------; default semaphore initialization
+ value is 0 */
+
+ perms = (argc <= optind + 1) ? (S_IRUSR | S_IWUSR) :
+ getInt(argv[optind + 1], GN_BASE_8, "octal-perms");
+ value = (argc <= optind + 2) ? 0 : getInt(argv[optind + 2], 0, "value");
+
+ sem = sem_open(argv[optind], flags, perms, value);
+ if (sem == SEM_FAILED)
+ errExit("sem_open");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 53-5 */
+
+/* psem_getvalue.c
+
+ Obtain the value of a POSIX named semaphore.
+
+ On Linux, named semaphores are supported with kernel 2.6 or later, and
+ a glibc that provides the NPTL threading implementation.
+*/
+#include <semaphore.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int value;
+ sem_t *sem;
+
+ if (argc != 2)
+ usageErr("%s sem-name\n", argv[0]);
+
+ sem = sem_open(argv[1], 0);
+ if (sem == SEM_FAILED)
+ errExit("sem_open");
+
+ if (sem_getvalue(sem, &value) == -1)
+ errExit("sem_getvalue");
+
+ printf("%d\n", value);
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 53-4 */
+
+/* psem_post.c
+
+ Increase the value of a POSIX named semaphore.
+
+ See also psem_wait.c.
+
+ On Linux, named semaphores are supported with kernel 2.6 or later, and
+ a glibc that provides the NPTL threading implementation.
+*/
+#include <semaphore.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ sem_t *sem;
+
+ if (argc != 2)
+ usageErr("%s sem-name\n", argv[0]);
+
+ sem = sem_open(argv[1], 0);
+ if (sem == SEM_FAILED)
+ errExit("sem_open");
+
+ if (sem_post(sem) == -1)
+ errExit("sem_post");
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 53-2 */
+
+/* psem_timedwait.c
+
+ Decrease the value of a POSIX named semaphore using sem_timedwait().
+
+ Usage: psem_timedwait sem-name nsecs
+
+ On Linux, named semaphores are supported with kernel 2.6 or later, and
+ a glibc that provides the NPTL threading implementation.
+*/
+#define _POSIX_C_SOURCE 199309
+#include <semaphore.h>
+#include <time.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ sem_t *sem;
+ struct timespec ts;
+
+ if (argc != 3 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s sem-name num-secs\n", argv[0]);
+
+ sem = sem_open(argv[1], 0);
+ if (sem == SEM_FAILED)
+ errExit("sem_open");
+
+ /* sem_timedwait() expects an absolute time in its second argument.
+ So we take the number of (relative) seconds specified on the
+ command line, and add it to the current system time. */
+
+ if (clock_gettime(CLOCK_REALTIME, &ts) == -1)
+ errExit("clock_gettime-CLOCK_REALTIME");
+
+ ts.tv_sec += atoi(argv[2]);
+
+ if (sem_timedwait(sem, &ts) == -1)
+ errExit("sem_timedwait");
+
+ printf("%ld sem_wait() succeeded\n", (long) getpid());
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 53 */
+
+/* psem_trywait.c
+
+ Try to decrease the value of a POSIX named semaphore using the
+ nonblocking sem_trywait() function.
+
+ On Linux, named semaphores are supported with kernel 2.6 or later, and
+ a glibc that provides the NPTL threading implementation.
+*/
+#include <semaphore.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ sem_t *sem;
+
+ if (argc < 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s sem-name\n", argv[0]);
+
+ sem = sem_open(argv[1], 0);
+ if (sem == SEM_FAILED)
+ errExit("sem_open");
+
+ if (sem_trywait(sem) == -1)
+ errExit("sem_trywait");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 53-2 */
+
+/* psem_unlink.c
+
+ Unlink a POSIX named semaphore.
+
+ On Linux, named semaphores are supported with kernel 2.6 or later, and
+ a glibc that provides the NPTL threading implementation.
+*/
+#include <semaphore.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ if (argc != 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s sem-name\n", argv[0]);
+
+ if (sem_unlink(argv[1]) == -1)
+ errExit("sem_unlink");
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 53-3 */
+
+/* psem_wait.c
+
+ Decrease the value of a POSIX named semaphore.
+
+ See also psem_post.c.
+
+ On Linux, named semaphores are supported with kernel 2.6 or later, and
+ a glibc that provides the NPTL threading implementation.
+*/
+#include <semaphore.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ sem_t *sem;
+
+ if (argc < 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s sem-name\n", argv[0]);
+
+ sem = sem_open(argv[1], 0);
+ if (sem == SEM_FAILED)
+ errExit("sem_open");
+
+ if (sem_wait(sem) == -1)
+ errExit("sem_wait");
+
+ printf("%ld sem_wait() succeeded\n", (long) getpid());
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 53-6 */
+
+/* thread_incr_psem.c
+
+ Use a POSIX unnamed semaphore to synchronize access by two threads to
+ a global variable.
+
+ See also thread_incr.c and thread_incr_mutex.c.
+*/
+#include <semaphore.h>
+#include <pthread.h>
+#include "tlpi_hdr.h"
+
+static int glob = 0;
+static sem_t sem;
+
+static void * /* Loop 'arg' times incrementing 'glob' */
+threadFunc(void *arg)
+{
+ int loops = *((int *) arg);
+ int loc, j;
+
+ for (j = 0; j < loops; j++) {
+ if (sem_wait(&sem) == -1)
+ errExit("sem_wait");
+
+ loc = glob;
+ loc++;
+ glob = loc;
+
+ if (sem_post(&sem) == -1)
+ errExit("sem_post");
+ }
+
+ return NULL;
+}
+
+int
+main(int argc, char *argv[])
+{
+ pthread_t t1, t2;
+ int loops, s;
+
+ loops = (argc > 1) ? getInt(argv[1], GN_GT_0, "num-loops") : 10000000;
+
+ /* Initialize a semaphore with the value 1 */
+
+ if (sem_init(&sem, 0, 1) == -1)
+ errExit("sem_init");
+
+ /* Create two threads that increment 'glob' */
+
+ s = pthread_create(&t1, NULL, threadFunc, &loops);
+ if (s != 0)
+ errExitEN(s, "pthread_create");
+ s = pthread_create(&t2, NULL, threadFunc, &loops);
+ if (s != 0)
+ errExitEN(s, "pthread_create");
+
+ /* Wait for threads to terminate */
+
+ s = pthread_join(t1, NULL);
+ if (s != 0)
+ errExitEN(s, "pthread_join");
+ s = pthread_join(t2, NULL);
+ if (s != 0)
+ errExitEN(s, "pthread_join");
+
+ printf("glob = %d\n", glob);
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE = pshm_create pshm_read pshm_write pshm_unlink
+
+LINUX_EXE =
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+LDLIBS = ${IMPL_LDLIBS} ${LINUX_LIBRT}
+ # All of the programs in this directory need the
+ # realtime library, librt.
+
+
+clean :
+ ${RM} ${EXE} *.o
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 54-1 */
+
+/* pshm_create.c
+
+ Create a POSIX shared memory object with specified size and permissions.
+
+ Usage as shown in usageError().
+*/
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include "tlpi_hdr.h"
+
+static void
+usageError(const char *progName)
+{
+ fprintf(stderr, "Usage: %s [-cx] shm-name size [octal-perms]\n", progName);
+ fprintf(stderr, " -c Create shared memory (O_CREAT)\n");
+ fprintf(stderr, " -x Create exclusively (O_EXCL)\n");
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int flags, opt, fd;
+ mode_t perms;
+ size_t size;
+ void *addr;
+
+ flags = O_RDWR;
+ while ((opt = getopt(argc, argv, "cx")) != -1) {
+ switch (opt) {
+ case 'c': flags |= O_CREAT; break;
+ case 'x': flags |= O_EXCL; break;
+ default: usageError(argv[0]);
+ }
+ }
+
+ if (optind + 1 >= argc)
+ usageError(argv[0]);
+
+ size = getLong(argv[optind + 1], GN_ANY_BASE, "size");
+ perms = (argc <= optind + 2) ? (S_IRUSR | S_IWUSR) :
+ getLong(argv[optind + 2], GN_BASE_8, "octal-perms");
+
+ /* Create shared memory object and set its size */
+
+ fd = shm_open(argv[optind], flags, perms);
+ if (fd == -1)
+ errExit("shm_open");
+
+ if (ftruncate(fd, size) == -1)
+ errExit("ftruncate");
+
+ /* Map shared memory object */
+
+ addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (addr == MAP_FAILED)
+ errExit("mmap");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 54-3 */
+
+/* pshm_read.c
+
+ Usage: pshm_read shm-name
+
+ Copy the contents of the POSIX shared memory object named in
+ 'name' to stdout.
+
+ See also pshm_write.c.
+*/
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int fd;
+ char *addr;
+ struct stat sb;
+
+ if (argc != 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s shm-name\n", argv[0]);
+
+ fd = shm_open(argv[1], O_RDONLY, 0); /* Open existing object */
+ if (fd == -1)
+ errExit("shm_open");
+
+ /* Use shared memory object size as length argument for mmap()
+ and as number of bytes to write() */
+
+ if (fstat(fd, &sb) == -1)
+ errExit("fstat");
+
+ addr = mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, fd, 0);
+ if (addr == MAP_FAILED)
+ errExit("mmap");
+
+ if (close(fd) == -1) /* 'fd' is no longer needed */
+ errExit("close");
+
+ write(STDOUT_FILENO, addr, sb.st_size);
+ printf("\n");
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 54-4 */
+
+/* pshm_unlink.c
+
+ Usage: pshm_unlink shm-name
+
+ Remove the POSIX shared memory object identified by 'name'
+*/
+#include <fcntl.h>
+#include <sys/mman.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ if (argc != 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s shm-name\n", argv[0]);
+
+ if (shm_unlink(argv[1]) == -1)
+ errExit("shm_unlink");
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 54-2 */
+
+/* pshm_write.c
+
+ Usage: pshm_write shm-name string
+
+ Copy 'string' into the POSIX shared memory object named in 'shm-name'.
+
+ See also pshm_read.c.
+*/
+#include <fcntl.h>
+#include <sys/mman.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int fd;
+ size_t len; /* Size of shared memory object */
+ char *addr;
+
+ if (argc != 3 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s shm-name string\n", argv[0]);
+
+ fd = shm_open(argv[1], O_RDWR, 0); /* Open existing object */
+ if (fd == -1)
+ errExit("shm_open");
+
+ len = strlen(argv[2]);
+ if (ftruncate(fd, len) == -1) /* Resize object to hold string */
+ errExit("ftruncate");
+ printf("Resized to %ld bytes\n", (long) len);
+
+ addr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (addr == MAP_FAILED)
+ errExit("mmap");
+
+ if (close(fd) == -1) /* 'fd' is no longer needed */
+ errExit("close");
+
+ printf("copying %ld bytes\n", (long) len);
+ memcpy(addr, argv[2], len); /* Copy string to shared memory */
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE = script unbuffer
+
+LINUX_EXE =
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+clean :
+ ${RM} ${EXE} *.o
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 64-2 */
+
+/* pty_fork.c
+
+ Implements ptyFork(), a function that creates a child process connected to
+ the parent (i.e., the calling process) via a pseudoterminal (pty). The child
+ is placed in a new session, with the pty slave as its controlling terminal,
+ and its standard input, output, and error connected to the pty slave.
+
+ In the parent, 'masterFd' is used to return the file descriptor for the
+ pty master.
+
+ If 'slaveName' is non-NULL, then it is used to return the name of the pty
+ slave. If 'slaveName' is not NULL, then 'snLen' should be set to indicate
+ the size of the buffer pointed to by 'slaveName'.
+
+ If 'slaveTermios' and 'slaveWS' are non-NULL, then they are used respectively
+ to set the terminal attributes and window size of the pty slave.
+
+ Returns:
+ in child: 0
+ in parent: PID of child or -1 on error
+*/
+#include <fcntl.h>
+#include <termios.h>
+#include <sys/ioctl.h>
+#include "pty_master_open.h"
+#include "pty_fork.h" /* Declares ptyFork() */
+#include "tlpi_hdr.h"
+
+#define MAX_SNAME 1000 /* Maximum size for pty slave name */
+
+pid_t
+ptyFork(int *masterFd, char *slaveName, size_t snLen,
+ const struct termios *slaveTermios, const struct winsize *slaveWS)
+{
+ int mfd, slaveFd, savedErrno;
+ pid_t childPid;
+ char slname[MAX_SNAME];
+
+ mfd = ptyMasterOpen(slname, MAX_SNAME);
+ if (mfd == -1)
+ return -1;
+
+ if (slaveName != NULL) { /* Return slave name to caller */
+ if (strlen(slname) < snLen) {
+ strncpy(slaveName, slname, snLen);
+
+ } else { /* 'slaveName' was too small */
+ close(mfd);
+ errno = EOVERFLOW;
+ return -1;
+ }
+ }
+
+ childPid = fork();
+
+ if (childPid == -1) { /* fork() failed */
+ savedErrno = errno; /* close() might change 'errno' */
+ close(mfd); /* Don't leak file descriptors */
+ errno = savedErrno;
+ return -1;
+ }
+
+ if (childPid != 0) { /* Parent */
+ *masterFd = mfd; /* Only parent gets master fd */
+ return childPid; /* Like parent of fork() */
+ }
+
+ /* Child falls through to here */
+
+ if (setsid() == -1) /* Start a new session */
+ err_exit("ptyFork:setsid");
+
+ close(mfd); /* Not needed in child */
+
+ slaveFd = open(slname, O_RDWR); /* Becomes controlling tty */
+ if (slaveFd == -1)
+ err_exit("ptyFork:open-slave");
+
+#ifdef TIOCSCTTY /* Acquire controlling tty on BSD */
+ if (ioctl(slaveFd, TIOCSCTTY, 0) == -1)
+ err_exit("ptyFork:ioctl-TIOCSCTTY");
+#endif
+
+ if (slaveTermios != NULL) /* Set slave tty attributes */
+ if (tcsetattr(slaveFd, TCSANOW, slaveTermios) == -1)
+ err_exit("ptyFork:tcsetattr");
+
+ if (slaveWS != NULL) /* Set slave tty window size */
+ if (ioctl(slaveFd, TIOCSWINSZ, slaveWS) == -1)
+ err_exit("ptyFork:ioctl-TIOCSWINSZ");
+
+ /* Duplicate pty slave to be child's stdin, stdout, and stderr */
+
+ if (dup2(slaveFd, STDIN_FILENO) != STDIN_FILENO)
+ err_exit("ptyFork:dup2-STDIN_FILENO");
+ if (dup2(slaveFd, STDOUT_FILENO) != STDOUT_FILENO)
+ err_exit("ptyFork:dup2-STDOUT_FILENO");
+ if (dup2(slaveFd, STDERR_FILENO) != STDERR_FILENO)
+ err_exit("ptyFork:dup2-STDERR_FILENO");
+
+ if (slaveFd > STDERR_FILENO) /* Safety check */
+ close(slaveFd); /* No longer need this fd */
+
+ return 0; /* Like child of fork() */
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Header file for Listing 64-2 */
+
+/* pty_fork.h
+
+ Header file for pty_fork.c.
+*/
+#ifndef FORK_PTY_H
+#define FORK_PTY_H
+
+#include <termios.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+
+pid_t ptyFork(int *masterFd, char *slaveName, size_t snLen,
+ const struct termios *slaveTermios, const struct winsize *slaveWS);
+
+#endif
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 64-1 */
+
+/* pty_master_open.c
+
+ Implement our ptyMasterOpen() function, based on UNIX 98 pseudoterminals.
+ See comments below.
+
+ See also pty_master_open_bsd.c.
+*/
+#if ! defined(__sun)
+ /* Prevents ptsname() declaration being visible on Solaris 8 */
+#if ! defined(_XOPEN_SOURCE) || _XOPEN_SOURCE < 600
+#define _XOPEN_SOURCE 600
+#endif
+#endif
+#include <stdlib.h>
+#include <fcntl.h>
+#include "pty_master_open.h" /* Declares ptyMasterOpen() */
+#include "tlpi_hdr.h"
+
+/* Some implementations don't have posix_openpt() */
+
+#if defined(__sun) /* Not on Solaris 8 */
+#define NO_POSIX_OPENPT
+#endif
+
+#ifdef NO_POSIX_OPENPT
+
+static int
+posix_openpt(int flags)
+{
+ return open("/dev/ptmx", flags);
+}
+
+#endif
+
+/* Open a pty master, returning file descriptor, or -1 on error.
+ On successful completion, the name of the corresponding pty
+ slave is returned in 'slaveName'. 'snLen' should be set to
+ indicate the size of the buffer pointed to by 'slaveName'. */
+
+int
+ptyMasterOpen(char *slaveName, size_t snLen)
+{
+ int masterFd, savedErrno;
+ char *p;
+
+ masterFd = posix_openpt(O_RDWR | O_NOCTTY); /* Open pty master */
+ if (masterFd == -1)
+ return -1;
+
+ if (grantpt(masterFd) == -1) { /* Grant access to slave pty */
+ savedErrno = errno;
+ close(masterFd); /* Might change 'errno' */
+ errno = savedErrno;
+ return -1;
+ }
+
+ if (unlockpt(masterFd) == -1) { /* Unlock slave pty */
+ savedErrno = errno;
+ close(masterFd); /* Might change 'errno' */
+ errno = savedErrno;
+ return -1;
+ }
+
+ p = ptsname(masterFd); /* Get slave pty name */
+ if (p == NULL) {
+ savedErrno = errno;
+ close(masterFd); /* Might change 'errno' */
+ errno = savedErrno;
+ return -1;
+ }
+
+ if (strlen(p) < snLen) {
+ strncpy(slaveName, p, snLen);
+ } else { /* Return an error if buffer too small */
+ close(masterFd);
+ errno = EOVERFLOW;
+ return -1;
+ }
+
+ return masterFd;
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Header file for Listing 64-1 */
+
+/* pty_open.h
+
+ Header file for pty_open.c (and pty_master_open_bsd.c).
+*/
+#ifndef PTY_MASTER_OPEN_H
+#define PTY_MASTER_OPEN_H
+
+#include <sys/types.h>
+
+int ptyMasterOpen(char *slaveName, size_t snLen);
+
+#endif
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 64-4 */
+
+/* pty_master_open_bsd.c
+
+ Implement our ptyMasterOpen() function, based on BSD pseudoterminals.
+ See comments below.
+
+ Note: BSD pseudoterminals are not specified in SUSv3, and are considered
+ obsolete on Linux.
+
+ See also pty_master_open.c.
+*/
+#include <fcntl.h>
+#include "pty_master_open.h" /* Declares ptyMasterOpen() */
+#include "tlpi_hdr.h"
+
+#define PTYM_PREFIX "/dev/pty"
+#define PTYS_PREFIX "/dev/tty"
+#define PTY_PREFIX_LEN (sizeof(PTYM_PREFIX) - 1)
+#define PTY_NAME_LEN (PTY_PREFIX_LEN + sizeof("XY"))
+#define X_RANGE "pqrstuvwxyzabcde"
+#define Y_RANGE "0123456789abcdef"
+
+/* Open a BSD pty master, returning file descriptor, or -1 on error.
+ On successful completion, the name of the corresponding pty slave
+ is returned in 'slaveName'. 'snLen' should be set to indicate the
+ size of the buffer pointed to by 'slaveName'. */
+
+int
+ptyMasterOpen(char *slaveName, size_t snLen)
+{
+ int masterFd, n;
+ char *x, *y;
+ char masterName[PTY_NAME_LEN];
+
+ if (PTY_NAME_LEN > snLen) {
+ errno = EOVERFLOW;
+ return -1;
+ }
+
+ memset(masterName, 0, PTY_NAME_LEN);
+ strncpy(masterName, PTYM_PREFIX, PTY_PREFIX_LEN);
+
+ /* Scan through all possible BSD pseudoterminal master names until
+ we find one that we can open. */
+
+ for (x = X_RANGE; *x != '\0'; x++) {
+ masterName[PTY_PREFIX_LEN] = *x;
+
+ for (y = Y_RANGE; *y != '\0'; y++) {
+ masterName[PTY_PREFIX_LEN + 1] = *y;
+
+ masterFd = open(masterName, O_RDWR);
+
+ if (masterFd == -1) {
+ if (errno == ENOENT) /* No such file */
+ return -1; /* Probably no more pty devices */
+ else /* Other error (e.g., pty busy) */
+ continue;
+
+ } else { /* Return slave name corresponding to master */
+ n = snprintf(slaveName, snLen, "%s%c%c", PTYS_PREFIX, *x, *y);
+ if (n >= snLen) {
+ errno = EOVERFLOW;
+ return -1;
+ } else if (n == -1) {
+ return -1;
+ }
+
+ return masterFd;
+ }
+ }
+ }
+
+ return -1; /* Tried all ptys without success */
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 64-3 */
+
+/* script.c
+
+ A simple version of script(1).
+*/
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <termios.h>
+#if ! defined(__hpux)
+/* HP-UX 11 doesn't have this header file */
+#include <sys/select.h>
+#endif
+#include "pty_fork.h" /* Declaration of ptyFork() */
+#include "tty_functions.h" /* Declaration of ttySetRaw() */
+#include "tlpi_hdr.h"
+
+#define BUF_SIZE 256
+#define MAX_SNAME 1000
+
+struct termios ttyOrig;
+
+static void /* Reset terminal mode on program exit */
+ttyReset(void)
+{
+ if (tcsetattr(STDIN_FILENO, TCSANOW, &ttyOrig) == -1)
+ errExit("tcsetattr");
+}
+
+int
+main(int argc, char *argv[])
+{
+ char slaveName[MAX_SNAME];
+ char *shell;
+ int masterFd, scriptFd;
+ struct winsize ws;
+ fd_set inFds;
+ char buf[BUF_SIZE];
+ ssize_t numRead;
+ pid_t childPid;
+
+ /* Retrieve the attributes of terminal on which we are started */
+
+ if (tcgetattr(STDIN_FILENO, &ttyOrig) == -1)
+ errExit("tcgetattr");
+ if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) < 0)
+ errExit("ioctl-TIOCGWINSZ");
+
+ /* Create a child process, with parent and child connected via a
+ pty pair. The child is connected to the pty slave and its terminal
+ attributes are set to be the same as those retrieved above. */
+
+ childPid = ptyFork(&masterFd, slaveName, MAX_SNAME, &ttyOrig, &ws);
+ if (childPid == -1)
+ errExit("ptyFork");
+
+ if (childPid == 0) { /* Child: execute a shell on pty slave */
+
+ /* If the SHELL variable is set, use its value to determine
+ the shell execed in child. Otherwise use /bin/sh. */
+
+ shell = getenv("SHELL");
+ if (shell == NULL || *shell == '\0')
+ shell = "/bin/sh";
+
+ execlp(shell, shell, (char *) NULL);
+ errExit("execlp"); /* If we get here, something went wrong */
+ }
+
+ /* Parent: relay data between terminal and pty master */
+
+ scriptFd = open((argc > 1) ? argv[1] : "typescript",
+ O_WRONLY | O_CREAT | O_TRUNC,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP |
+ S_IROTH | S_IWOTH);
+ if (scriptFd == -1)
+ errExit("open typescript");
+
+ /* Place terminal in raw mode so that we can pass all terminal
+ input to the pseudoterminal master untouched */
+
+ ttySetRaw(STDIN_FILENO, &ttyOrig);
+
+ if (atexit(ttyReset) != 0)
+ errExit("atexit");
+
+ /* Loop monitoring terminal and pty master for input. If the
+ terminal is ready for input, then read some bytes and write
+ them to the pty master. If the pty master is ready for input,
+ then read some bytes and write them to the terminal. */
+
+ for (;;) {
+ FD_ZERO(&inFds);
+ FD_SET(STDIN_FILENO, &inFds);
+ FD_SET(masterFd, &inFds);
+
+ if (select(masterFd + 1, &inFds, NULL, NULL, NULL) == -1)
+ errExit("select");
+
+ if (FD_ISSET(STDIN_FILENO, &inFds)) { /* stdin --> pty */
+ numRead = read(STDIN_FILENO, buf, BUF_SIZE);
+ if (numRead <= 0)
+ exit(EXIT_SUCCESS);
+
+ if (write(masterFd, buf, numRead) != numRead)
+ fatal("partial/failed write (masterFd)");
+ }
+
+ if (FD_ISSET(masterFd, &inFds)) { /* pty --> stdout+file */
+ numRead = read(masterFd, buf, BUF_SIZE);
+ if (numRead <= 0)
+ exit(EXIT_SUCCESS);
+
+ if (write(STDOUT_FILENO, buf, numRead) != numRead)
+ fatal("partial/failed write (STDOUT_FILENO)");
+ if (write(scriptFd, buf, numRead) != numRead)
+ fatal("partial/failed write (scriptFd)");
+ }
+ }
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 64-7 */
+
+/* unbuffer.c
+
+ Use a pseudoterminal to circumvent the block buffering performed by the
+ stdio library when standard output is redirected to a file or pipe.
+ When a program is started using 'unbuffer', its output is redirected to
+ a pseudoterminal, and is consequently line-buffered.
+
+ Usage: unbuffer prog [arg ...]
+*/
+#include <termios.h>
+#if ! defined(__hpux)
+/* HP-UX 11 doesn't have this header file */
+#include <sys/select.h>
+#endif
+#include "pty_fork.h" /* Declaration of ptyFork() */
+#include "tty_functions.h" /* Declaration of ttySetRaw() */
+#include "tlpi_hdr.h"
+
+#define BUF_SIZE 256
+#define MAX_SNAME 1000
+
+struct termios ttyOrig;
+
+static void /* Reset terminal mode on program exit */
+ttyReset(void)
+{
+ if (tcsetattr(STDIN_FILENO, TCSANOW, &ttyOrig) == -1)
+ errExit("tcsetattr");
+}
+
+int
+main(int argc, char *argv[])
+{
+ char slaveName[MAX_SNAME];
+ int masterFd;
+ fd_set inFds;
+ char buf[BUF_SIZE];
+ ssize_t numRead;
+ struct winsize ws;
+
+ /* Retrieve the attributes of terminal on which we are started */
+
+ if (tcgetattr(STDIN_FILENO, &ttyOrig) == -1)
+ errExit("tcgetattr");
+ if (ioctl(STDIN_FILENO, TIOCGWINSZ, (char *) &ws) < 0)
+ errExit("TIOCGWINSZ error");
+
+ /* Create a child process, with parent and child connected via a
+ pty pair. The child is connected to the pty slave and its terminal
+ attributes are set to be the same as those retrieved above. */
+
+ switch (ptyFork(&masterFd, slaveName, MAX_SNAME, &ttyOrig, &ws)) {
+ case -1:
+ errExit("ptyFork");
+
+ case 0: /* Child executes command given in argv[1]... */
+ execvp(argv[1], &argv[1]);
+ errExit("execvp");
+
+ default: /* Parent relays data between terminal and pty master */
+
+ /* Place terminal in raw mode so that we can pass all
+ terminal input to the pseudoterminal master untouched */
+
+ ttySetRaw(STDIN_FILENO, &ttyOrig);
+ if (atexit(ttyReset) != 0)
+ errExit("atexit");
+
+ /* Loop monitoring terminal and pty master for input. If the
+ terminal is ready for input, read some bytes and write
+ them to the pty master. If the pty master is ready for
+ input, read some bytes and write them to the terminal. */
+
+ for (;;) {
+ FD_ZERO(&inFds);
+ FD_SET(STDIN_FILENO, &inFds);
+ FD_SET(masterFd, &inFds);
+ if (select(masterFd + 1, &inFds, NULL, NULL, NULL) == -1)
+ errExit("select");
+
+ if (FD_ISSET(STDIN_FILENO, &inFds)) {
+ numRead = read(STDIN_FILENO, buf, BUF_SIZE);
+ if (numRead <= 0)
+ exit(EXIT_SUCCESS);
+
+ if (write(masterFd, buf, numRead) != numRead)
+ fatal("partial/failed write (masterFd)");
+ }
+
+ if (FD_ISSET(masterFd, &inFds)) {
+ numRead = read(masterFd, buf, BUF_SIZE);
+ if (numRead <= 0)
+ exit(EXIT_SUCCESS);
+
+ if (write(STDOUT_FILENO, buf, numRead) != numRead)
+ fatal("partial/failed write (STDOUT_FILENO)");
+ }
+ }
+ }
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE =
+
+LINUX_EXE = libseccomp_demo seccomp_control_open seccomp_deny_open seccomp_perf
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+clean :
+ ${RM} ${EXE} *.o
+
+showall :
+ @ echo ${EXE}
+
+libseccomp_demo : libseccomp_demo.c
+ ${CC} -o $@ libseccomp_demo.c ${CFLAGS} ${LDLIBS} -lseccomp
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter Z */
+
+/* libseccomp_demo.c
+*/
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <seccomp.h>
+#include "tlpi_hdr.h"
+
+int main(int argc, char *argv[])
+{
+ int rc;
+ scmp_filter_ctx ctx;
+
+ /* Create seccomp filter state that allows by default */
+
+ ctx = seccomp_init(SCMP_ACT_ALLOW);
+ if (ctx == NULL)
+ fatal("seccomp_init() failed");
+
+ /* Cause clone() and fork() to fail, each with different errors */
+
+ rc = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(clone), 0);
+ if (rc < 0)
+ errExitEN(-rc, "seccomp_rule_add");
+
+ rc = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(ENOTSUP), SCMP_SYS(fork), 0);
+ if (rc < 0)
+ errExitEN(-rc, "seccomp_rule_add");
+
+ /* Export the pseudofilter code and BPF binary code,
+ each to different file descriptors (if they are open) */
+
+ seccomp_export_pfc(ctx, 5);
+ seccomp_export_bpf(ctx, 6);
+
+ /* Install the seccomp fileter into the kernel */
+
+ rc = seccomp_load(ctx);
+ if (rc < 0)
+ errExitEN(-rc, "seccomp_load");
+
+ /* Free the user-space seccomp filter state */
+
+ seccomp_release(ctx);
+
+ if (fork() != -1)
+ fprintf(stderr, "fork() succeeded?!\n");
+ else
+ perror("fork");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter Z */
+
+/* seccomp_control_open.c
+
+ A simple seccomp filter example. Install a filter that triggers
+ differnt failures in open(), depending on flags argument.
+*/
+#define _GNU_SOURCE
+#include <stddef.h>
+#include <fcntl.h>
+#include <linux/audit.h>
+#include <sys/syscall.h>
+#include <linux/filter.h>
+#include <linux/seccomp.h>
+#include <sys/prctl.h>
+#include "tlpi_hdr.h"
+
+static int
+seccomp(unsigned int operation, unsigned int flags, void *args)
+{
+ return syscall(__NR_seccomp, operation, flags, args);
+}
+
+static void
+install_filter(void)
+{
+ struct sock_filter filter[] = {
+ /* Load architecture */
+
+ BPF_STMT(BPF_LD | BPF_W | BPF_ABS,
+ (offsetof(struct seccomp_data, arch))),
+
+ /* Kill the process if the architecture is not what we expect */
+
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, AUDIT_ARCH_X86_64, 1, 0),
+ BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL),
+
+ /* Load system call number */
+
+ BPF_STMT(BPF_LD | BPF_W | BPF_ABS,
+ (offsetof(struct seccomp_data, nr))),
+
+ /* Allow syscalls other than open() */
+
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_open, 1, 0),
+ BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
+
+ /* Load second argument of open() (flags) into accumulator */
+
+ BPF_STMT(BPF_LD | BPF_W | BPF_ABS,
+ (offsetof(struct seccomp_data, args[1]))),
+
+ /* Kill the process if O_CREAT was specified */
+
+ BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K, O_CREAT, 0, 1),
+ BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL),
+
+ /* Give ENOTSUP error on attempt to open for writing.
+ Relies on the fact that O_RDWR and O_WRONLY are
+ defined as single, nonoverlapping bits */
+
+ BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K, O_WRONLY | O_RDWR, 0, 1),
+ BPF_STMT(BPF_RET | BPF_K,
+ SECCOMP_RET_ERRNO | (ENOTSUP & SECCOMP_RET_DATA)),
+
+ /* Otherwise allow the open() */
+
+ BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW)
+ };
+
+ struct sock_fprog prog = {
+ .len = (unsigned short) (sizeof(filter) / sizeof(filter[0])),
+ .filter = filter,
+ };
+
+ if (seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog))
+ errExit("seccomp");
+ /* On Linux 3.16 and earlier (or if we have old glibc headers that
+ don't define '__NR_seccomp', we must instead use:
+ if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog))
+ errExit("prctl-PR_SET_SECCOMP");
+ */
+}
+
+int
+main(int argc, char **argv)
+{
+ if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0))
+ errExit("prctl");
+
+ install_filter();
+
+ if (open("/tmp/a", O_RDONLY) == -1)
+ perror("open1");
+ if (open("/tmp/a", O_WRONLY) == -1)
+ perror("open2");
+ if (open("/tmp/a", O_RDWR) == -1)
+ perror("open3");
+ if (open("/tmp/a", O_CREAT | O_RDWR, 0600) == -1)
+ perror("open4");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter Z */
+
+/* seccomp_deny.c
+
+ A simple seccomp filter example. Install a filter that kills the process
+ if open() is called.
+*/
+#define _GNU_SOURCE
+#include <stddef.h>
+#include <fcntl.h>
+#include <linux/audit.h>
+#include <sys/syscall.h>
+#include <linux/filter.h>
+#include <linux/seccomp.h>
+#include <sys/prctl.h>
+#include "tlpi_hdr.h"
+
+static int
+seccomp(unsigned int operation, unsigned int flags, void *args)
+{
+ return syscall(__NR_seccomp, operation, flags, args);
+}
+
+static void
+install_filter(void)
+{
+ struct sock_filter filter[] = {
+ /* Load architecture */
+
+ BPF_STMT(BPF_LD | BPF_W | BPF_ABS,
+ (offsetof(struct seccomp_data, arch))),
+
+ /* Kill process if the architecture is not what we expect */
+
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, AUDIT_ARCH_X86_64, 1, 0),
+ BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL),
+
+ /* Load system call number */
+
+ BPF_STMT(BPF_LD | BPF_W | BPF_ABS,
+ (offsetof(struct seccomp_data, nr))),
+
+ /* Allow system calls other than open() */
+
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_open, 1, 0),
+ BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
+
+ /* Kill process on open() */
+
+ BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL)
+ };
+
+ struct sock_fprog prog = {
+ .len = (unsigned short) (sizeof(filter) / sizeof(filter[0])),
+ .filter = filter,
+ };
+
+ if (seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog) == -1)
+ errExit("seccomp");
+ /* On Linux 3.16 and earlier, we must instead use:
+ if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog))
+ errExit("prctl-PR_SET_SECCOMP");
+ */
+}
+
+int
+main(int argc, char **argv)
+{
+ if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0))
+ errExit("prctl");
+
+ install_filter();
+
+ if (open("/tmp/a", O_RDONLY) == -1)
+ errExit("open");
+
+ printf("We shouldn't see this message\n");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter Z */
+
+/* seccomp_perf.c
+
+ How much does a simple seccomp() filter cost vis-a-vis a trivial system
+ call such as getppid()?
+
+ To test with the in-kernel JIT compiler enabled:
+
+ $ sudo sh -c "echo 1 > /proc/sys/net/core/bpf_jit_enable"
+*/
+#define _GNU_SOURCE
+#include <stddef.h>
+#include <fcntl.h>
+#include <linux/audit.h>
+#include <sys/syscall.h>
+#include <linux/filter.h>
+#include <linux/seccomp.h>
+#include <sys/prctl.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \
+ } while (0)
+
+static int
+seccomp(unsigned int operation, unsigned int flags, void *arg)
+{
+ return syscall(__NR_seccomp, operation, flags, arg);
+ // Or: return prctl(PR_SET_SECCOMP, operation, arg);
+}
+
+static void
+install_filter(void)
+{
+ struct sock_filter filter[] = {
+ /* Load architecture */
+
+ BPF_STMT(BPF_LD | BPF_W | BPF_ABS,
+ (offsetof(struct seccomp_data, arch))),
+
+ /* Kill process if the architecture is not what we expect */
+
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, AUDIT_ARCH_X86_64, 1, 0),
+ BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL),
+
+ /* Load system call number */
+
+ BPF_STMT(BPF_LD | BPF_W | BPF_ABS,
+ (offsetof(struct seccomp_data, nr))),
+
+ /* Allow system calls other than open() */
+
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_open, 1, 0),
+ BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
+
+ /* Kill process on open() */
+
+ BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL)
+ };
+
+ struct sock_fprog prog = {
+ .len = (unsigned short) (sizeof(filter) / sizeof(filter[0])),
+ .filter = filter,
+ };
+
+ if (seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog) == -1)
+ errExit("seccomp");
+}
+
+int
+main(int argc, char *argv[])
+{
+ int j, nloops;
+
+ if (argc < 2) {
+ fprintf(stderr, "Usage: %s <num-loops> [x]\n", argv[0]);
+ fprintf(stderr, " (use 'x' to run with BPF filter applied)\n");
+ exit(EXIT_FAILURE);
+ }
+
+ if (argc > 2) {
+ printf("Appling BPF filter\n");
+
+ if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0))
+ errExit("prctl");
+
+ install_filter();
+ }
+
+ nloops = atoi(argv[1]);
+
+ for (j = 0; j < nloops; j++)
+ getppid();
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+#!/bin/sh
+#
+# Build a program without using libraries
+
+cc -c -g prog.c mod1.c mod2.c mod3.c
+cc -g -o prog_nolib prog.o mod1.o mod2.o mod3.o
--- /dev/null
+#!/bin/sh
+#
+# Build a program using a shared library
+#
+# Objects for library must be compiled with PIC (position
+# independent code) generation
+
+gcc -g -c -fPIC -Wall mod1.c mod2.c mod3.c
+
+# Create library with "real name" (libXYZ.so.maj#.min#.rel#)
+# and embed the "soname" (libXYZ.so.maj#)
+
+gcc -g -shared -Wl,-soname,libdemo.so.1 -o libdemo.so.1.0.1 \
+ mod1.o mod2.o mod3.o
+
+# Create "soname" (libXYZ.so.maj# - a symbolic link to real name)
+
+ln -sf libdemo.so.1.0.1 libdemo.so.1
+
+# Create "linker name" (libXYZ.so - a symbolic link to "soname")
+
+ln -sf libdemo.so.1 libdemo.so
+
+# Build program by linking against "linker name"
+
+cc -g -Wall -c prog.c
+cc -g -o prog_so prog.o -L. -ldemo
--- /dev/null
+#!/bin/sh
+# Build a program using a static library
+#
+
+cc -g -c mod1.c mod2.c mod3.c
+ar r libdemo.a mod1.o mod2.o mod3.o
+
+cc -g -o prog_static prog.c libdemo.a
+# Or: cc -g -o prog_static prog.c -L. -ldemo
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE = dynload
+
+LINUX_EXE =
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+dynload : dynload.o
+ ${CC} -o $@ dynload.o ${CFLAGS} ${LDLIBS} ${LINUX_LIBDL}
+
+clean :
+ ${RM} ${EXE} *.o *.so.*
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+#!/bin/sh
+
+set -v
+
+gcc -g -shared -fPIC -o libfoo1.so foo1.c
+gcc -g -Wl,-Bsymbolic -Wl,-dynamic-list=d --shared -fPIC -o libfoo2.so foo2.c
+gcc -g -Wl,-Bsymbolic -Wl,-allow-shlib-undefined \
+ -shared -fPIC -o libfoo3.so foo3.c
+
+gcc -g -o prog prog.c -L. -lfoo1 -lfoo2 -lfoo3
+
+LD_LIBRARY_PATH=. ./prog
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* foo1.c
+
+*/
+#include <stdlib.h>
+#include <stdio.h>
+
+void
+xyz(void)
+{
+ printf(" func1-xyz\n");
+}
+
+void
+abc(void)
+{
+ printf(" func1-abc\n");
+}
+
+void
+func1(int x)
+{
+ printf("Called func1\n");
+ xyz();
+ abc();
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* foo2.c
+
+*/
+#include <stdlib.h>
+#include <stdio.h>
+
+void
+xyz(void)
+{
+ printf(" func2-xyz\n");
+}
+
+void
+abc(void)
+{
+ printf(" func1-abc\n");
+}
+
+void
+func2(int x)
+{
+ printf("Called func2\n");
+ xyz();
+ abc();
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* foo3.c
+
+*/
+#include <stdlib.h>
+#include <stdio.h>
+
+void
+xyz(void)
+{
+ printf(" func3-xyz\n");
+}
+
+void
+func3(int x)
+{
+ printf("Called func3\n");
+ xyz();
+ abc();
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* prog.c
+
+*/
+#include <stdlib.h>
+#include <stdio.h>
+
+void
+xyz(void)
+{
+ printf(" main-xyz\n");
+}
+
+int
+main(int argc, char*argv[])
+{
+ void func1(void), func2(void), func3(void);
+
+ func1();
+ func2();
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 42-1 */
+
+/* dynload.c
+
+ Usage: dynload library-path function-name
+
+ Demonstrate dynamic loading of libraries. The program loads the
+ named library and then executes the named function in that library.
+*/
+#include <dlfcn.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ void *libHandle; /* Handle for shared library */
+ void (*funcp)(void); /* Pointer to function with no arguments */
+ const char *err;
+
+ if (argc != 3 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s lib-path func-name\n", argv[0]);
+
+ /* Load the shared library and get a handle for later use */
+
+ libHandle = dlopen(argv[1], RTLD_LAZY);
+ if (libHandle == NULL)
+ fatal("dlopen: %s", dlerror());
+
+ /* Search library for symbol named in argv[2] */
+
+ (void) dlerror(); /* Clear dlerror() */
+#pragma GCC diagnostic ignored "-Wpedantic"
+ funcp = (void (*)(void)) dlsym(libHandle, argv[2]);
+
+ /* In the book, instead of the preceding line, the code uses a
+ rather clumsy looking cast of the form:
+
+ *(void **) (&funcp) = dlsym(libHandle, argv[2]);
+
+ This was done because the ISO C standard does not require compilers
+ to allow casting of pointers to functions back and forth to 'void *'.
+ (See TLPI pages 863-864.) SUSv3 TC1 and SUSv4 accepted the ISO C
+ requirement and proposed the clumsy cast as the workaround. However,
+ the 2013 Technical Corrigendum to SUSv4 requires implementations
+ to support casts of the more natural form (now) used in the code
+ above. However, various current compilers (e.g., gcc with the
+ '-pedantic' flag) may still complain about such casts. Therefore,
+ we use a gcc pragma to disable the warning (note that this pragma
+ is available only since gcc 4.6, released in 2010). See also the
+ erratum note for page 864 at http://www.man7.org/tlpi/errata/. */
+
+ err = dlerror();
+ if (err != NULL)
+ fatal("dlsym: %s", err);
+
+ /* Try calling the address returned by dlsym() as a function
+ that takes no arguments */
+
+ (*funcp)();
+
+ dlclose(libHandle); /* Close the library */
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* mod1.c */
+
+#include <stdio.h>
+#include <unistd.h>
+int test1[250000] = { 1, 2, 3 };
+
+#ifndef VERSION
+#define VERSION ""
+#endif
+
+void
+x1(void) {
+ printf("Called mod1-x1 " VERSION "\n");
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* mod2.c */
+
+#include <stdio.h>
+#include <unistd.h>
+int test2[100000] = { 1, 2, 3 };
+
+#ifndef VERSION
+#define VERSION ""
+#endif
+
+void
+x2(void) {
+ printf("Called mod2-x2 " VERSION "\n");
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* mod3.c */
+
+#include <stdio.h>
+#include <unistd.h>
+int test3[10000] = { 1, 2, 3 };
+
+#ifndef VERSION
+#define VERSION ""
+#endif
+
+void
+x3(void) {
+ printf("Called mod3-x3 " VERSION "\n");
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* prog.c */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+void x1(void);
+void x2(void);
+
+int
+main(int argc, char *argv[])
+{
+ x1();
+ x2();
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* modx1.c */
+
+void
+x1(void) {
+ extern void x2(void);
+
+ x2();
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* modx2.c */
+
+#include <stdio.h>
+
+void
+x2(void) {
+ printf("Called modx2\n");
+}
--- /dev/null
+#!/bin/sh
+#
+# sv_build.sh
+#
+set -v
+
+# Build version 1 of shared library
+
+gcc -g -c -fPIC -Wall sv_lib_v1.c
+gcc -g -shared -o libsv.so sv_lib_v1.o -Wl,--version-script,sv_v1.map
+
+gcc -g -o p1 sv_prog.c libsv.so
+
+LD_LIBRARY_PATH=. ./p1
+
+# Build version 2 of shared library
+
+gcc -g -c -fPIC -Wall sv_lib_v2.c
+gcc -g -shared -o libsv.so sv_lib_v2.o -Wl,--version-script,sv_v2.map
+
+gcc -g -o p2 sv_prog.c libsv.so
+
+LD_LIBRARY_PATH=. ./p2
+LD_LIBRARY_PATH=. ./p1
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* sv_lib_v1.c
+
+*/
+#include <stdio.h>
+
+void xyz(void) { printf("v1 xyz()\n"); }
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* sv_lib_v2.c
+
+*/
+
+#include <stdio.h>
+
+__asm__(".symver xyz_new,xyz@@VER_2");
+__asm__(".symver xyz_old,xyz@VER_1");
+
+void xyz_old(void) { printf("v1 xyz\n"); }
+
+void xyz_new(void) { printf("v2 xyz\n"); }
+
+void pqr(void) { printf("v2 pqr\n"); }
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* sv_libabc.c
+
+*/
+#include <stdio.h>
+
+void
+abc(void)
+{
+ void xyz(void);
+
+ printf("abc() calling xyz()\n");
+ xyz();
+}
+
+__asm__(".symver xyz,xyz@VER_1");
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* sv_prog.c
+
+*/
+#include <stdlib.h>
+
+int
+main(int argc, char *argv[])
+{
+ void xyz(void);
+
+ xyz();
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* sv_prog_abc.c
+
+*/
+#include <stdio.h>
+#include <stdlib.h>
+
+int
+main(int argc, char *argv[])
+{
+ void xyz(void), abc(void);
+
+ printf("main() calling xyz()\n");
+ xyz();
+
+ abc();
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* sv_prog_complex.c
+
+*/
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+//void xxx(void) { printf("Hi there\n"); }
+
+int main(int argc, char *argv[]) {
+ void xyz(void);
+ void xyz_old(void), xyz_new(void);
+ void abc(void);
+
+ printf("Calling abc()\n");
+ abc();
+
+ printf("Calling xyz()\n");
+ xyz();
+
+ printf("Calling xyz_new()\n");
+ xyz_new();
+
+ printf("Calling xyz_old()\n");
+ xyz_old();
+
+ //xxx();
+
+ exit(0);
+}
+//__asm__(".symver xyz_old,xyz@VER_1");
--- /dev/null
+#!/bin/sh
+#
+# vis_build.sh
+#
+# Show how versions scripts can be used to control symbol visibility.
+#
+set -v
+
+# In the following, vis_comm() is accidentally exported by the
+# shared library
+
+gcc -g -c -fPIC -Wall vis_comm.c vis_f1.c vis_f2.c
+gcc -g -shared -o vis.so vis_comm.o vis_f1.o vis_f2.o
+readelf --syms --use-dynamic vis.so | grep vis_
+
+# Now we use a version script to control exactly which symbols
+# are exported by the library
+
+gcc -g -c -fPIC -Wall vis_comm.c vis_f1.c vis_f2.c
+gcc -g -shared -o vis.so vis_comm.o vis_f1.o vis_f2.o \
+ -Wl,--version-script,vis.map
+readelf --syms --use-dynamic vis.so | grep vis_
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* vis_comm.c
+
+*/
+int /* Function used by vis_f1() and vis_f2() */
+vis_comm(int j)
+{
+ return j * j;
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* vis_f1.c
+
+*/
+int
+vis_f1(int k)
+{
+ int vis_comm(int j);
+
+ return vis_comm(k) / 2;
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* vis_f2.c
+
+*/
+int
+vis_f2(int k)
+{
+ int vis_comm(int j);
+
+ return vis_comm(k) * vis_comm(k);
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE = catch_rtsigs demo_SIGFPE ignore_pending_sig intquit \
+ nonreentrant ouch sig_receiver sig_sender sig_speed_sigsuspend \
+ sigmask_longjmp sigmask_siglongjmp \
+ t_kill t_sigaltstack t_sigsuspend t_sigqueue t_sigwaitinfo
+
+LINUX_EXE = signalfd_sigval
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+#
+# The following example programs make use of signal() (instead of the
+# preferred sigaction()) to establish signal handlers.
+# By default, signal() on Linux follows the BSD semantics, establishing
+# a reliable signal handler. This is because _BSD_SOURCE is enabled
+# bt default in <features.h>. However, in ../Makefile.inc, we define
+# CFLAGS to include _XOPEN_SOURCE, and this has the effect of disabling
+# _BSD_SOURCE. To ensure that we get the default (BSD) semantics
+# we here explicitly define _BSD_SOURCE when compiling these programs.
+#
+
+ouch : ouch.c
+ ${CC} -D_BSD_SOURCE -o $@ ouch.c ${CFLAGS} ${LDLIBS}
+
+intquit : intquit.c
+ ${CC} -D_BSD_SOURCE -o $@ intquit.c ${CFLAGS} ${LDLIBS}
+
+sig_receiver : sig_receiver.c
+ ${CC} -D_BSD_SOURCE -o $@ sig_receiver.c ${CFLAGS} ${LDLIBS}
+
+nonreentrant : nonreentrant.o
+ ${CC} -o $@ nonreentrant.o ${CFLAGS} ${LDLIBS} ${LINUX_LIBCRYPT}
+
+sigmask_siglongjmp.o : sigmask_longjmp.c
+ ${CC} -o $@ -DUSE_SIGSETJMP -c sigmask_longjmp.c ${CFLAGS}
+
+clean :
+ ${RM} ${EXE} *.o
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 22-3 */
+
+/* catch_rtsigs.c
+
+ Usage: catch_rtsigs [block-time [handler-sleep-time]]
+ default=0 default=1
+
+ block-time specifies an amount of time the program should
+ pause after blocking all signals. This allows
+ multiple signals to be sent to the process before
+ it unblocks all the signals.
+
+ handler-sleep-time specifies the amount of time the signal
+ handler should sleep before returning. Using a
+ nonzero value here allows us to slow things down
+ so that we can see what happens when multiple
+ signals are sent.
+
+ After optionally blocking all (possible) signals and sleeping for
+ 'block-time' seconds, loop continuously using pause() to wait for
+ any incoming signals.
+
+ The program can be terminated by typing control-C (which generates
+ SIGINT) or sending it SIGTERM.
+*/
+#define _GNU_SOURCE
+#include <string.h>
+#include <signal.h>
+#include "tlpi_hdr.h"
+
+static volatile int handlerSleepTime;
+static volatile int sigCnt = 0; /* Number of signals received */
+static volatile int allDone = 0;
+
+static void /* Handler for signals established using SA_SIGINFO */
+siginfoHandler(int sig, siginfo_t *si, void *ucontext)
+{
+ /* UNSAFE: This handler uses non-async-signal-safe functions
+ (printf()); see Section 21.1.2) */
+
+ /* SIGINT or SIGTERM can be used to terminate program */
+
+ if (sig == SIGINT || sig == SIGTERM) {
+ allDone = 1;
+ return;
+ }
+
+ sigCnt++;
+ printf("caught signal %d\n", sig);
+
+ printf(" si_signo=%d, si_code=%d (%s), ", si->si_signo, si->si_code,
+ (si->si_code == SI_USER) ? "SI_USER" :
+ (si->si_code == SI_QUEUE) ? "SI_QUEUE" : "other");
+ printf("si_value=%d\n", si->si_value.sival_int);
+ printf(" si_pid=%ld, si_uid=%ld\n",
+ (long) si->si_pid, (long) si->si_uid);
+
+ sleep(handlerSleepTime);
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct sigaction sa;
+ int sig;
+ sigset_t prevMask, blockMask;
+
+ if (argc > 1 && strcmp(argv[1], "--help") == 0)
+ usageErr("%s [block-time [handler-sleep-time]]\n", argv[0]);
+
+ printf("%s: PID is %ld\n", argv[0], (long) getpid());
+
+ handlerSleepTime = (argc > 2) ?
+ getInt(argv[2], GN_NONNEG, "handler-sleep-time") : 1;
+
+ /* Establish handler for most signals. During execution of the handler,
+ mask all other signals to prevent handlers recursively interrupting
+ each other (which would make the output hard to read). */
+
+ sa.sa_sigaction = siginfoHandler;
+ sa.sa_flags = SA_SIGINFO;
+ sigfillset(&sa.sa_mask);
+
+ for (sig = 1; sig < NSIG; sig++)
+ if (sig != SIGTSTP && sig != SIGQUIT)
+ sigaction(sig, &sa, NULL);
+
+ /* Optionally block signals and sleep, allowing signals to be
+ sent to us before they are unblocked and handled */
+
+ if (argc > 1) {
+ sigfillset(&blockMask);
+ sigdelset(&blockMask, SIGINT);
+ sigdelset(&blockMask, SIGTERM);
+
+ if (sigprocmask(SIG_SETMASK, &blockMask, &prevMask) == -1)
+ errExit("sigprocmask");
+
+ printf("%s: signals blocked - sleeping %s seconds\n", argv[0], argv[1]);
+ sleep(getInt(argv[1], GN_GT_0, "block-time"));
+ printf("%s: sleep complete\n", argv[0]);
+
+ if (sigprocmask(SIG_SETMASK, &prevMask, NULL) == -1)
+ errExit("sigprocmask");
+ }
+
+ while (!allDone) /* Wait for incoming signals */
+ pause();
+
+ printf("Caught %d signals\n", sigCnt);
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 22 */
+
+/* demo_SIGFPE.c
+
+ Demonstrate the generation of the SIGFPE signal.
+
+ Usage: demo_SIGFPE [optstr]
+
+ The main program executes code the generates a SIGFPE signal. Before doing
+ so, the program optionally ignores and/or blocks SIGFPE. If 'optstr'
+ contains 'i', then SIGFPE is ignored, otherwise it is caught by a handler.
+ If 'optstr' contains 'b', then SIGFPE is blocked before it is delivered.
+ The behavior that occurs when SIGFPE is generated depends on the kernel
+ version (Linux 2.6 is different from Linux 2.4 and earlier).
+
+ NOTE: Don't compile this program with optimization, as the arithmetic
+ below is likely to be optimized away completely, with the result that
+ we don't get SIGFPE at all.
+*/
+#define _GNU_SOURCE /* Get strsignal() declaration from <string.h> */
+#include <string.h>
+#include <signal.h>
+#include "tlpi_hdr.h"
+
+static void
+sigfpeCatcher(int sig)
+{
+ printf("Caught signal %d (%s)\n", sig, strsignal(sig));
+ /* UNSAFE (see Section 21.1.2) */
+ sleep(1); /* Slow down execution of handler */
+}
+
+int
+main(int argc, char *argv[])
+{
+ int x, y;
+ sigset_t blockSet, prevMask;
+ Boolean blocking;
+ struct sigaction sa;
+
+ /* If no command-line arguments specified, catch SIGFPE, else ignore it */
+
+ if (argc > 1 && strchr(argv[1], 'i') != NULL) {
+ printf("Ignoring SIGFPE\n");
+ if (signal(SIGFPE, SIG_IGN) == SIG_ERR) errExit("signal");
+ } else {
+ printf("Catching SIGFPE\n");
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ sa.sa_handler = sigfpeCatcher;
+ if (sigaction(SIGFPE, &sa, NULL) == -1)
+ errExit("sigaction");
+ }
+
+ blocking = argc > 1 && strchr(argv[1], 'b') != NULL;
+ if (blocking) {
+ printf("Blocking SIGFPE\n");
+ sigemptyset(&blockSet);
+ sigaddset(&blockSet, SIGFPE);
+ if (sigprocmask(SIG_BLOCK, &blockSet, &prevMask) == -1)
+ errExit("sigprocmask");
+ }
+
+ printf("About to generate SIGFPE\n");
+ y = 0;
+ x = 1 / y;
+ y = x; /* Avoid complaints from "gcc -Wunused-but-set-variable" */
+
+ if (blocking) {
+ printf("Sleeping before unblocking\n");
+ sleep(2);
+ printf("Unblocking SIGFPE\n");
+ if (sigprocmask(SIG_SETMASK, &prevMask, NULL) == -1)
+ errExit("sigprocmask");
+ }
+
+ printf("Shouldn't get here!\n");
+ exit(EXIT_FAILURE);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 20-2 */
+
+/* ignore_pending_sig.c
+
+ This program demonstrates what happens if we mark a pending signal
+ (i.e., one that has been sent, but is currently blocked) as ignored.
+
+ Usage: ignore_pending_sig
+
+ Type Control-C (^C) to generate a SIGINT signal after the program prints
+ its "sleeping" message (see below).
+*/
+#define _GNU_SOURCE /* Get strsignal() declaration from <string.h> */
+#include <string.h>
+#include <signal.h>
+#include "signal_functions.h" /* Declaration of printSigset() */
+#include "tlpi_hdr.h"
+
+static void
+handler(int sig)
+{
+ /* UNSAFE: This handler uses non-async-signal-safe functions
+ (printf(), strsignal(), fflush(); see Section 21.1.2) */
+
+ printf("Caught signal %d (%s)\n", sig, strsignal(sig));
+ fflush(NULL);
+}
+
+int
+main(int argc, char *argv[])
+{
+ sigset_t pending, blocked;
+ const int numSecs = 5;
+ struct sigaction sa;
+
+ /* Set up a handler for SIGINT */
+
+ printf("Setting up handler for SIGINT\n");
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sa.sa_handler = handler;
+ if (sigaction(SIGINT, &sa, NULL) == -1)
+ errExit("sigaction");
+
+ /* Block SIGINT for a while */
+
+ sigemptyset(&blocked);
+ sigaddset(&blocked, SIGINT);
+ if (sigprocmask(SIG_SETMASK, &blocked, NULL) == -1)
+ errExit("sigprocmask");
+
+ printf("BLOCKING SIGINT for %d seconds\n", numSecs);
+ sleep(numSecs);
+
+ /* Display mask of pending signals */
+
+ if (sigpending(&pending) == -1)
+ errExit("sigpending");
+ printf("PENDING signals are: \n");
+ printSigset(stdout, "\t\t", &pending);
+
+ /* Now ignore SIGINT */
+
+ sleep(2);
+ printf("Ignoring SIGINT\n");
+ if (signal(SIGINT, SIG_IGN) == SIG_ERR) errExit("signal");
+
+ /* Redisplay mask of pending signals */
+
+ if (sigpending(&pending) == -1)
+ errExit("sigpending");
+ if (sigismember(&pending, SIGINT)) {
+ printf("SIGINT is now pending\n");
+ } else {
+ printf("PENDING signals are: \n");
+ printSigset(stdout, "\t\t", &pending);
+ }
+ sleep(2);
+
+ /* Reestablish SIGINT handler */
+
+ printf("Reestablishing handler for SIGINT\n");
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sa.sa_handler = handler;
+ if (sigaction(SIGINT, &sa, NULL) == -1)
+ errExit("sigaction");
+
+ sleep(2);
+
+ /* And unblock SIGINT */
+
+ printf("UNBLOCKING SIGINT\n");
+ sigemptyset(&blocked);
+ if (sigprocmask(SIG_SETMASK, &blocked, NULL) == -1)
+ errExit("sigprocmask");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 20-2 */
+
+/* intquit.c
+
+ Catch the SIGINT and SIGQUIT signals, which are normally generated
+ by the control-C (^C) and control-\ (^\) keys respectively.
+
+ Note that although we use signal() to establish signal handlers in this
+ program, the use of sigaction() is always preferable for this task.
+*/
+#include <signal.h>
+#include "tlpi_hdr.h"
+
+static void
+sigHandler(int sig)
+{
+ static int count = 0;
+
+ /* UNSAFE: This handler uses non-async-signal-safe functions
+ (printf(), exit(); see Section 21.1.2) */
+
+ if (sig == SIGINT) {
+ count++;
+ printf("Caught SIGINT (%d)\n", count);
+ return; /* Resume execution at point of interruption */
+ }
+
+ /* Must be SIGQUIT - print a message and terminate the process */
+
+ printf("Caught SIGQUIT - that's all folks!\n");
+ exit(EXIT_SUCCESS);
+}
+
+int
+main(int argc, char *argv[])
+{
+ /* Establish same handler for SIGINT and SIGQUIT. Here we use the
+ simpler signal() API to establish a signal handler, but for the
+ reasons described in Section 22.7 of TLPI, sigaction() is the
+ (strongly) preferred API for this task. */
+
+ if (signal(SIGINT, sigHandler) == SIG_ERR)
+ errExit("signal");
+ if (signal(SIGQUIT, sigHandler) == SIG_ERR)
+ errExit("signal");
+
+ for (;;) /* Loop forever, waiting for signals */
+ pause(); /* Block until a signal is caught */
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 21-1 */
+
+/* nonreentrant.c
+
+ Demonstrate the nonreentrant nature of some library functions, in this
+ example, crypt(3).
+*/
+#if ! defined(_XOPEN_SOURCE) || _XOPEN_SOURCE < 600
+#define _XOPEN_SOURCE 600
+#endif
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include "tlpi_hdr.h"
+
+static char *str2; /* Set from argv[2] */
+static int handled = 0; /* Counts number of calls to handler */
+
+static void
+handler(int sig)
+{
+ crypt(str2, "xx");
+ handled++;
+}
+
+int
+main(int argc, char *argv[])
+{
+ char *cr1;
+ int callNum, mismatch;
+ struct sigaction sa;
+
+ if (argc != 3)
+ usageErr("%s str1 str2\n", argv[0]);
+
+ str2 = argv[2]; /* Make argv[2] available to handler */
+ cr1 = strdup(crypt(argv[1], "xx")); /* Copy statically allocated string
+ to another buffer */
+ if (cr1 == NULL)
+ errExit("strdup");
+
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sa.sa_handler = handler;
+ if (sigaction(SIGINT, &sa, NULL) == -1)
+ errExit("sigaction");
+
+ /* Repeatedly call crypt() using argv[1]. If interrupted by a
+ signal handler, then the static storage returned by crypt()
+ will be overwritten by the results of encrypting argv[2], and
+ strcmp() will detect a mismatch with the value in 'cr1'. */
+
+ for (callNum = 1, mismatch = 0; ; callNum++) {
+ if (strcmp(crypt(argv[1], "xx"), cr1) != 0) {
+ mismatch++;
+ printf("Mismatch on call %d (mismatch=%d handled=%d)\n",
+ callNum, mismatch, handled);
+ }
+ }
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 20-1 */
+
+/* ouch.c
+
+ Catch the SIGINT signal, generated by typing control-C (^C).
+
+ Note that although we use signal() to establish the signal handler in this
+ program, the use of sigaction() is always preferable for this task.
+*/
+#include <signal.h>
+#include "tlpi_hdr.h"
+
+static void
+sigHandler(int sig)
+{
+ printf("Ouch!\n"); /* UNSAFE (see Section 21.1.2) */
+}
+
+int
+main(int argc, char *argv[])
+{
+ int j;
+
+ /* Establish handler for SIGINT. Here we use the simpler signal()
+ API to establish a signal handler, but for the reasons described in
+ Section 22.7 of TLPI, sigaction() is the (strongly) preferred API
+ for this task. */
+
+ if (signal(SIGINT, sigHandler) == SIG_ERR)
+ errExit("signal");
+
+ /* Loop continuously waiting for signals to be delivered */
+
+ for (j = 0; ; j++) {
+ printf("%d\n", j);
+ sleep(3); /* Loop slowly... */
+ }
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 20-7 */
+
+/* sig_receiver.c
+
+ Usage: sig_receiver [block-time]
+
+ Catch and report statistics on signals sent by sig_sender.c.
+
+ Note that although we use signal() to establish the signal handler in this
+ program, the use of sigaction() is always preferable for this task.
+*/
+#define _GNU_SOURCE
+#include <signal.h>
+#include "signal_functions.h" /* Declaration of printSigset() */
+#include "tlpi_hdr.h"
+
+static int sigCnt[NSIG]; /* Counts deliveries of each signal */
+static volatile sig_atomic_t gotSigint = 0;
+ /* Set nonzero if SIGINT is delivered */
+
+static void
+handler(int sig)
+{
+ if (sig == SIGINT)
+ gotSigint = 1;
+ else
+ sigCnt[sig]++;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int n, numSecs;
+ sigset_t pendingMask, blockingMask, emptyMask;
+
+ printf("%s: PID is %ld\n", argv[0], (long) getpid());
+
+ /* Here we use the simpler signal() API to establish a signal handler,
+ but for the reasons described in Section 22.7 of TLPI, sigaction()
+ is the (strongly) preferred API for this task. */
+
+ for (n = 1; n < NSIG; n++) /* Same handler for all signals */
+ (void) signal(n, handler); /* Ignore errors */
+
+ /* If a sleep time was specified, temporarily block all signals,
+ sleep (while another process sends us signals), and then
+ display the mask of pending signals and unblock all signals */
+
+ if (argc > 1) {
+ numSecs = getInt(argv[1], GN_GT_0, NULL);
+
+ sigfillset(&blockingMask);
+ if (sigprocmask(SIG_SETMASK, &blockingMask, NULL) == -1)
+ errExit("sigprocmask");
+
+ printf("%s: sleeping for %d seconds\n", argv[0], numSecs);
+ sleep(numSecs);
+
+ if (sigpending(&pendingMask) == -1)
+ errExit("sigpending");
+
+ printf("%s: pending signals are: \n", argv[0]);
+ printSigset(stdout, "\t\t", &pendingMask);
+
+ sigemptyset(&emptyMask); /* Unblock all signals */
+ if (sigprocmask(SIG_SETMASK, &emptyMask, NULL) == -1)
+ errExit("sigprocmask");
+ }
+
+ while (!gotSigint) /* Loop until SIGINT caught */
+ continue;
+
+ for (n = 1; n < NSIG; n++) /* Display number of signals received */
+ if (sigCnt[n] != 0)
+ printf("%s: signal %d caught %d time%s\n", argv[0], n,
+ sigCnt[n], (sigCnt[n] == 1) ? "" : "s");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 20-6 */
+
+/* sig_sender.c
+
+ Usage: sig_sender PID num-sigs sig [sig2]
+
+ Send signals to sig_receiver.c.
+
+ Sends 'num-sigs' signals of type 'sig' to the process with the specified PID.
+ If a fourth command-line argument is supplied, send one instance of that
+ signal, after sending the previous signals.
+*/
+#include <signal.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int numSigs, sig, j;
+ pid_t pid;
+
+ if (argc < 4 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s pid num-sigs sig-num [sig-num-2]\n", argv[0]);
+
+ pid = getLong(argv[1], 0, "PID");
+ numSigs = getInt(argv[2], GN_GT_0, "num-sigs");
+ sig = getInt(argv[3], 0, "sig-num");
+
+ /* Send signals to receiver */
+
+ printf("%s: sending signal %d to process %ld %d times\n",
+ argv[0], sig, (long) pid, numSigs);
+
+ for (j = 0; j < numSigs; j++)
+ if (kill(pid, sig) == -1)
+ errExit("kill");
+
+ /* If a fourth command-line argument was specified, send that signal */
+
+ if (argc > 4)
+ if (kill(pid, getInt(argv[4], 0, "sig-num-2")) == -1)
+ errExit("kill");
+
+ printf("%s: exiting\n", argv[0]);
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 22 */
+
+/* sig_speed_sigsuspend.c
+
+ This program times how fast signals are sent and received.
+
+ The program forks to create a parent and a child process that alternately
+ send signals to each other (the child starts first). Each process catches
+ the signal with a handler, and waits for signals to arrive using
+ sigsuspend().
+
+ Usage: $ time ./sig_speed_sigsuspend num-sigs
+
+ The 'num-sigs' argument specifies how many times the parent and
+ child send signals to each other.
+
+ Child Parent
+
+ for (s = 0; s < numSigs; s++) { for (s = 0; s < numSigs; s++) {
+ send signal to parent wait for signal from child
+ wait for a signal from parent send a signal to child
+ } }
+*/
+#include <signal.h>
+#include "tlpi_hdr.h"
+
+static void
+handler(int sig)
+{
+}
+
+#define TESTSIG SIGUSR1
+
+int
+main(int argc, char *argv[])
+{
+ int numSigs, scnt;
+ pid_t childPid;
+ sigset_t blockedMask, emptyMask;
+ struct sigaction sa;
+
+ if (argc != 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s num-sigs\n", argv[0]);
+
+ numSigs = getInt(argv[1], GN_GT_0, "num-sigs");
+
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sa.sa_handler = handler;
+ if (sigaction(TESTSIG, &sa, NULL) == -1)
+ errExit("sigaction");
+
+ /* Block the signal before fork(), so that the child doesn't manage
+ to send it to the parent before the parent is ready to catch it */
+
+ sigemptyset(&blockedMask);
+ sigaddset(&blockedMask, TESTSIG);
+ if (sigprocmask(SIG_SETMASK, &blockedMask, NULL) == -1)
+ errExit("sigprocmask");
+
+ sigemptyset(&emptyMask);
+
+ switch (childPid = fork()) {
+ case -1: errExit("fork");
+
+ case 0: /* child */
+ for (scnt = 0; scnt < numSigs; scnt++) {
+ if (kill(getppid(), TESTSIG) == -1)
+ errExit("kill");
+ if (sigsuspend(&emptyMask) == -1 && errno != EINTR)
+ errExit("sigsuspend");
+ }
+ exit(EXIT_SUCCESS);
+
+ default: /* parent */
+ for (scnt = 0; scnt < numSigs; scnt++) {
+ if (sigsuspend(&emptyMask) == -1 && errno != EINTR)
+ errExit("sigsuspend");
+ if (kill(childPid, TESTSIG) == -1)
+ errExit("kill");
+ }
+ exit(EXIT_SUCCESS);
+ }
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 20-4 */
+
+/* siginterrupt.c
+
+ An implementation of the siginterrupt(3) library function.
+*/
+#include <stdio.h>
+#include <signal.h>
+
+int
+siginterrupt(int sig, int flag)
+{
+ int status;
+ struct sigaction act;
+
+ status = sigaction(sig, NULL, &act);
+ if (status == -1)
+ return -1;
+
+ if (flag)
+ act.sa_flags &= ~SA_RESTART;
+ else
+ act.sa_flags |= SA_RESTART;
+
+ return sigaction(sig, &act, NULL);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 21-2 */
+
+/* sigmask_longjmp.c
+
+ Demonstrate the different effects of longjmp() and siglongjmp()
+ on the process signal mask.
+
+ By default, this program uses setjmp() + longjmp(). Compile with
+ -DUSE_SIGSETJMP to use sigsetjmp() + siglongjmp().
+*/
+#define _GNU_SOURCE /* Get strsignal() declaration from <string.h> */
+#include <string.h>
+#include <setjmp.h>
+#include <signal.h>
+#include "signal_functions.h" /* Declaration of printSigMask() */
+#include "tlpi_hdr.h"
+
+static volatile sig_atomic_t canJump = 0;
+ /* Set to 1 once "env" buffer has been
+ initialized by [sig]setjmp() */
+#ifdef USE_SIGSETJMP
+static sigjmp_buf senv;
+#else
+static jmp_buf env;
+#endif
+
+static void
+handler(int sig)
+{
+ /* UNSAFE: This handler uses non-async-signal-safe functions
+ (printf(), strsignal(), printSigMask(); see Section 21.1.2) */
+
+ printf("Received signal %d (%s), signal mask is:\n", sig,
+ strsignal(sig));
+ printSigMask(stdout, NULL);
+
+ if (!canJump) {
+ printf("'env' buffer not yet set, doing a simple return\n");
+ return;
+ }
+
+#ifdef USE_SIGSETJMP
+ siglongjmp(senv, 1);
+#else
+ longjmp(env, 1);
+#endif
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct sigaction sa;
+
+ printSigMask(stdout, "Signal mask at startup:\n");
+
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sa.sa_handler = handler;
+ if (sigaction(SIGINT, &sa, NULL) == -1)
+ errExit("sigaction");
+
+#ifdef USE_SIGSETJMP
+ printf("Calling sigsetjmp()\n");
+ if (sigsetjmp(senv, 1) == 0)
+#else
+ printf("Calling setjmp()\n");
+ if (setjmp(env) == 0)
+#endif
+ canJump = 1; /* Executed after [sig]setjmp() */
+
+ else /* Executed after [sig]longjmp() */
+ printSigMask(stdout, "After jump from handler, signal mask is:\n" );
+
+ for (;;) /* Wait for signals until killed */
+ pause();
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 22-1 */
+
+/* signal.c
+
+ An implementation of signal() using sigaction().
+
+ Compiling with "-DOLD_SIGS" provides the older, unreliable signal handler
+ semantics (which are still the default on some System V derivatives).
+*/
+/* Some UNIX implementations follow the BSD signal() semantics, including
+ Linux, Tru64, and of course the BSD derivatives. Others, such as Solaris,
+ follow the System V semantics. We'll conditionally override signal() on
+ platforms following the System V semantics. For the other implementations,
+ we'll provide a dummy source file that doesn't implement signal().
+*/
+#if defined(__sun) || defined(__sgi)
+#include <signal.h>
+
+typedef void (*sighandler_t)(int);
+
+sighandler_t
+signal(int sig, sighandler_t handler)
+{
+ struct sigaction newDisp, prevDisp;
+
+ newDisp.sa_handler = handler;
+ sigemptyset(&newDisp.sa_mask);
+#ifdef OLD_SIGNAL
+ newDisp.sa_flags = SA_RESETHAND | SA_NODEFER;
+#else
+ newDisp.sa_flags = SA_RESTART;
+#endif
+
+ if (sigaction(sig, &newDisp, &prevDisp) == -1)
+ return SIG_ERR;
+ else
+ return prevDisp.sa_handler;
+}
+
+#else
+/* The following declaration is provided purely to avoid gcc's
+ "warning: ISO C forbids an empty source file" warnings. */
+
+extern int signalDummyVar;
+
+#endif
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 20-4 */
+
+/* signal_functions.c
+
+ Various useful functions for working with signals.
+*/
+#define _GNU_SOURCE
+#include <string.h>
+#include <signal.h>
+#include "signal_functions.h" /* Declares functions defined here */
+#include "tlpi_hdr.h"
+
+/* NOTE: All of the following functions employ fprintf(), which
+ is not async-signal-safe (see Section 21.1.2). As such, these
+ functions are also not async-signal-safe (i.e., beware of
+ indiscriminately calling them from signal handlers). */
+
+void /* Print list of signals within a signal set */
+printSigset(FILE *of, const char *prefix, const sigset_t *sigset)
+{
+ int sig, cnt;
+
+ cnt = 0;
+ for (sig = 1; sig < NSIG; sig++) {
+ if (sigismember(sigset, sig)) {
+ cnt++;
+ fprintf(of, "%s%d (%s)\n", prefix, sig, strsignal(sig));
+ }
+ }
+
+ if (cnt == 0)
+ fprintf(of, "%s<empty signal set>\n", prefix);
+}
+
+int /* Print mask of blocked signals for this process */
+printSigMask(FILE *of, const char *msg)
+{
+ sigset_t currMask;
+
+ if (msg != NULL)
+ fprintf(of, "%s", msg);
+
+ if (sigprocmask(SIG_BLOCK, NULL, &currMask) == -1)
+ return -1;
+
+ printSigset(of, "\t\t", &currMask);
+
+ return 0;
+}
+
+int /* Print signals currently pending for this process */
+printPendingSigs(FILE *of, const char *msg)
+{
+ sigset_t pendingSigs;
+
+ if (msg != NULL)
+ fprintf(of, "%s", msg);
+
+ if (sigpending(&pendingSigs) == -1)
+ return -1;
+
+ printSigset(of, "\t\t", &pendingSigs);
+
+ return 0;
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Header file for Listing 20-4 */
+
+/* signal_functions.h
+
+ Header file for signal_functions.c.
+*/
+#ifndef SIGNAL_FUNCTIONS_H
+#define SIGNAL_FUNCTIONS_H
+
+#include <signal.h>
+#include "tlpi_hdr.h"
+
+int printSigMask(FILE *of, const char *msg);
+
+int printPendingSigs(FILE *of, const char *msg);
+
+void printSigset(FILE *of, const char *ldr, const sigset_t *mask);
+
+#endif
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 22-7 */
+
+/* signalfd_sigval.c
+
+ Usage: signalfd_sigval sig-num...
+
+ Demonstrate the use of signalfd() to receive signals via a file descriptor.
+
+ This program is Linux-specific. The signalfd API is supported since kernel
+ 2.6.22.
+*/
+#include <sys/signalfd.h>
+#include <signal.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ sigset_t mask;
+ int sfd, j;
+ struct signalfd_siginfo fdsi;
+ ssize_t s;
+
+ if (argc < 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s sig-num...\n", argv[0]);
+
+ printf("%s: PID = %ld\n", argv[0], (long) getpid());
+
+ sigemptyset(&mask);
+ for (j = 1; j < argc; j++)
+ sigaddset(&mask, atoi(argv[j]));
+
+ if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
+ errExit("sigprocmask");
+
+ sfd = signalfd(-1, &mask, 0);
+ if (sfd == -1)
+ errExit("signalfd");
+
+ for (;;) {
+ s = read(sfd, &fdsi, sizeof(struct signalfd_siginfo));
+ if (s != sizeof(struct signalfd_siginfo))
+ errExit("read");
+
+ printf("%s: got signal %d", argv[0], fdsi.ssi_signo);
+ if (fdsi.ssi_code == SI_QUEUE) {
+ printf("; ssi_pid = %d; ", fdsi.ssi_pid);
+ printf("ssi_int = %d", fdsi.ssi_int);
+ }
+ printf("\n");
+ }
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 20-3 */
+
+/* t_kill.c
+
+ Send a signal using kill(2) and analyze the return status of the call.
+*/
+#include <signal.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int s, sig;
+
+ if (argc != 3 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s pid sig-num\n", argv[0]);
+
+ sig = getInt(argv[2], 0, "sig-num");
+
+ s = kill(getLong(argv[1], 0, "pid"), sig);
+
+ if (sig != 0) {
+ if (s == -1)
+ errExit("kill");
+
+ } else { /* Null signal: process existence check */
+ if (s == 0) {
+ printf("Process exists and we can send it a signal\n");
+ } else {
+ if (errno == EPERM)
+ printf("Process exists, but we don't have "
+ "permission to send it a signal\n");
+ else if (errno == ESRCH)
+ printf("Process does not exist\n");
+ else
+ errExit("kill");
+ }
+ }
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 21-3 */
+
+/* t_sigaltstack.c
+
+ Demonstrate the use of sigaltstack() to handle a signal on an alternate
+ signal stack.
+*/
+#define _GNU_SOURCE /* Get strsignal() declaration from <string.h> */
+#include <string.h>
+#include <signal.h>
+#include "tlpi_hdr.h"
+
+static void
+sigsegvHandler(int sig)
+{
+ int x;
+
+ /* UNSAFE: This handler uses non-async-signal-safe functions
+ (printf(), strsignal(), fflush(); see Section 21.1.2) */
+
+ printf("Caught signal %d (%s)\n", sig, strsignal(sig));
+ printf("Top of handler stack near %10p\n", (void *) &x);
+ fflush(NULL);
+
+ _exit(EXIT_FAILURE); /* Can't return after SIGSEGV */
+}
+
+/* The following stops 'gcc -Wall' complaining that "control reaches
+ end of non-void function" because we don't follow the call to
+ overflowStack() stack in main() with a call to exit(). */
+
+#ifdef __GNUC__
+static void
+overflowStack(int callNum) __attribute__ ((__noreturn__));
+#endif
+
+static void /* A recursive function that overflows the stack */
+overflowStack(int callNum)
+{
+ char a[100000]; /* Make this stack frame large */
+
+ printf("Call %4d - top of stack near %10p\n", callNum, &a[0]);
+ overflowStack(callNum+1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ stack_t sigstack;
+ struct sigaction sa;
+ int j;
+
+ printf("Top of standard stack is near %10p\n", (void *) &j);
+
+ /* Allocate alternate stack and inform kernel of its existence */
+
+ sigstack.ss_sp = malloc(SIGSTKSZ);
+ if (sigstack.ss_sp == NULL)
+ errExit("malloc");
+
+ sigstack.ss_size = SIGSTKSZ;
+ sigstack.ss_flags = 0;
+ if (sigaltstack(&sigstack, NULL) == -1)
+ errExit("sigaltstack");
+ printf("Alternate stack is at %10p-%p\n",
+ sigstack.ss_sp, (char *) sbrk(0) - 1);
+
+ sa.sa_handler = sigsegvHandler; /* Establish handler for SIGSEGV */
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_ONSTACK; /* Handler uses alternate stack */
+ if (sigaction(SIGSEGV, &sa, NULL) == -1)
+ errExit("sigaction");
+
+ overflowStack(1);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 22-2 */
+
+/* t_sigqueue.c
+
+ Demonstrate the use of sigqueue() to send a (realtime) signal.
+
+ Usage: t_sigqueue sig pid data num-sigs
+
+ Send 'num-sigs' instances of the signal 'sig' (specified as an integer), with
+ accompanying data 'data' (an integer), to the process with the PID 'pid'.
+*/
+#define _POSIX_C_SOURCE 199309
+#include <signal.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int sig, numSigs, j, sigData;
+ union sigval sv;
+
+ if (argc < 4 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s pid sig-num data [num-sigs]\n", argv[0]);
+
+ /* Display our PID and UID, so that they can be compared with the
+ corresponding fields of the siginfo_t argument supplied to the
+ handler in the receiving process */
+
+ printf("%s: PID is %ld, UID is %ld\n", argv[0],
+ (long) getpid(), (long) getuid());
+
+ sig = getInt(argv[2], 0, "sig-num");
+ sigData = getInt(argv[3], GN_ANY_BASE, "data");
+ numSigs = (argc > 4) ? getInt(argv[4], GN_GT_0, "num-sigs") : 1;
+
+ for (j = 0; j < numSigs; j++) {
+ sv.sival_int = sigData + j;
+ if (sigqueue(getLong(argv[1], 0, "pid"), sig, sv) == -1)
+ errExit("sigqueue %d", j);
+ }
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 22-5 */
+
+/* t_sigsuspend.c
+
+ A short program to demonstrate why sigsuspend(&mask) is preferable to
+ calling sigprocmask(SIG_SETMASK, &mask, NULL) + pause() separately.
+ (By default this program uses sigsuspend(). To make it use pause(),
+ compile using "cc -DUSE_PAUSE".)
+
+ Usage: t_sigsuspend [sleep-time]
+
+ Send the SIGINT signal to this program by typing control-C (^C).
+ (Terminate the program using SIGQUIT, i.e., type control-\ (^\).)
+
+ This program contains extra code that does not appear in the version shown
+ in the book. By defining USE_PAUSE when compiling, we can replace the use of
+ sigsuspend() by the nonatomic sigprocmask() + pause(). This allows us to
+ show that doing the latter way will cause some signals to be lost.
+*/
+#define _GNU_SOURCE /* Get strsignal() declaration from <string.h> */
+#include <string.h>
+#include <signal.h>
+#include <time.h>
+#include "signal_functions.h" /* Declarations of printSigMask()
+ and printPendingSigs() */
+#include "tlpi_hdr.h"
+
+/* Global variable incremented each time SIGINT is handled */
+
+static volatile int sigintCnt = 0;
+static volatile sig_atomic_t gotSigquit = 0;
+
+static void
+handler(int sig)
+{
+ printf("Caught signal %d (%s)\n", sig, strsignal(sig));
+ /* UNSAFE (see Section 21.1.2) */
+ if (sig == SIGQUIT)
+ gotSigquit = 1;
+ sigintCnt++;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int loopNum;
+#ifdef USE_PAUSE
+ int sleepTime;
+#endif
+ time_t startTime;
+ sigset_t origMask, blockMask;
+ struct sigaction sa;
+
+ printSigMask(stdout, "Initial signal mask is:\n");
+
+ sigemptyset(&blockMask);
+ sigaddset(&blockMask, SIGINT);
+ sigaddset(&blockMask, SIGQUIT);
+
+#ifdef USE_PAUSE
+ sleepTime = (argc > 1) ? getInt(argv[1], GN_NONNEG, NULL) : 0;
+#endif
+
+ /* Block SIGINT and SIGQUIT - at this point we assume that these signals
+ are not already blocked (obviously true in this simple program) so that
+ 'origMask' will not contain either of these signals after the call. */
+
+ if (sigprocmask(SIG_BLOCK, &blockMask, &origMask) == -1)
+ errExit("sigprocmask - SIG_BLOCK");
+
+ /* Set up handlers for SIGINT and SIGQUIT */
+
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sa.sa_handler = handler;
+ if (sigaction(SIGINT, &sa, NULL) == -1)
+ errExit("sigaction");
+ if (sigaction(SIGQUIT, &sa, NULL) == -1)
+ errExit("sigaction");
+
+ /* Loop until SIGQUIT received */
+
+ for (loopNum = 1; !gotSigquit; loopNum++) {
+ printf("=== LOOP %d\n", loopNum);
+
+ /* Simulate a critical section by delaying a few seconds */
+
+ printSigMask(stdout, "Starting critical section, signal mask is:\n");
+ for (startTime = time(NULL); time(NULL) < startTime + 4; )
+ continue; /* Run for a few seconds elapsed time */
+
+#ifndef USE_PAUSE
+ /* The right way: use sigsuspend() to atomically unblock
+ signals and pause waiting for signal */
+
+ printPendingSigs(stdout,
+ "Before sigsuspend() - pending signals:\n");
+ if (sigsuspend(&origMask) == -1 && errno != EINTR)
+ errExit("sigsuspend");
+#else
+
+ /* The wrong way: unblock signal using sigprocmask(),
+ then pause() */
+
+ if (sigprocmask(SIG_SETMASK, &origMask, NULL) == -1)
+ errExit("sigprocmask - SIG_SETMASK");
+
+ /* At this point, if SIGINT arrives, it will be caught and
+ handled before the pause() call and, in consequence,
+ pause() will block. (And thus only another SIGINT signal
+ AFTER the pause call() will actually cause the pause()
+ call to be interrupted.) Here we make the window between
+ the two calls a bit larger so that we have a better
+ chance of sending the signal. */
+
+ if (sleepTime > 0) {
+ printf("Unblocked SIGINT, now waiting for %d seconds\n", sleepTime);
+ for (startTime = time(NULL);
+ time(NULL) < startTime + sleepTime; )
+ continue;
+ printf("Finished waiting - now going to pause()\n");
+ }
+
+ /* And now wait for the signal */
+
+ pause();
+
+ printf("Signal count = %d\n", sigintCnt);
+ sigintCnt = 0;
+#endif
+ }
+
+ /* Restore signal mask so that signals are unblocked */
+
+ if (sigprocmask(SIG_SETMASK, &origMask, NULL) == -1)
+ errExit("sigprocmask - SIG_SETMASK");
+
+ printSigMask(stdout, "=== Exited loop\nRestored signal mask to:\n");
+
+ /* Do other processing... */
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 22-6 */
+
+/* t_sigwaitinfo.c
+
+ Demonstrate the use of sigwaitinfo() to synchronously wait for a signal.
+*/
+#define _GNU_SOURCE
+#include <string.h>
+#include <signal.h>
+#include <time.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int sig;
+ siginfo_t si;
+ sigset_t allSigs;
+
+ if (argc > 1 && strcmp(argv[1], "--help") == 0)
+ usageErr("%s [delay-secs]\n", argv[0]);
+
+ printf("%s: PID is %ld\n", argv[0], (long) getpid());
+
+ /* Block all signals (except SIGKILL and SIGSTOP) */
+
+ sigfillset(&allSigs);
+ if (sigprocmask(SIG_SETMASK, &allSigs, NULL) == -1)
+ errExit("sigprocmask");
+ printf("%s: signals blocked\n", argv[0]);
+
+ if (argc > 1) { /* Delay so that signals can be sent to us */
+ printf("%s: about to delay %s seconds\n", argv[0], argv[1]);
+ sleep(getInt(argv[1], GN_GT_0, "delay-secs"));
+ printf("%s: finished delay\n", argv[0]);
+ }
+
+ for (;;) { /* Fetch signals until SIGINT (^C) or SIGTERM */
+ sig = sigwaitinfo(&allSigs, &si);
+ if (sig == -1)
+ errExit("sigwaitinfo");
+
+ if (sig == SIGINT || sig == SIGTERM)
+ exit(EXIT_SUCCESS);
+
+ printf("got signal: %d (%s)\n", sig, strsignal(sig));
+ printf(" si_signo=%d, si_code=%d (%s), si_value=%d\n",
+ si.si_signo, si.si_code,
+ (si.si_code == SI_USER) ? "SI_USER" :
+ (si.si_code == SI_QUEUE) ? "SI_QUEUE" : "other",
+ si.si_value.sival_int);
+ printf(" si_pid=%ld, si_uid=%ld\n",
+ (long) si.si_pid, (long) si.si_uid);
+ }
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE = i6d_ucase_sv i6d_ucase_cl \
+ id_echo_cl id_echo_sv \
+ is_echo_cl is_echo_sv is_echo_inetd_sv is_echo_v2_sv \
+ is_seqnum_sv is_seqnum_cl is_seqnum_v2_sv is_seqnum_v2_cl \
+ socknames t_gethostbyname t_getservbyname \
+ ud_ucase_sv ud_ucase_cl \
+ us_xfr_cl us_xfr_sv us_xfr_v2_cl us_xfr_v2_sv
+
+LINUX_EXE = list_host_addresses \
+ scm_cred_recv scm_cred_send scm_rights_recv scm_rights_send \
+ us_abstract_bind
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+i6d_ucase_sv.o i6d_ucase_cl.o : i6d_ucase.h
+
+id_echo_cl.o id_echo_sv.o : id_echo.h
+
+is_seqnum_sv.o is_seqnum_cl.o : is_seqnum.h
+
+is_seqnum_v2_sv.o is_seqnum_v2_cl.o : is_seqnum_v2.h
+
+scm_cred_recv.o scm_cred_send.o : scm_cred.h
+
+scm_rights_recv.o scm_rights_send.o : scm_rights.h
+
+us_xfr_sv.o us_xfr_cl.o : us_xfr.h
+
+us_xfr_v2_sv.o us_xfr_v2_cl.o : us_xfr_v2.h
+
+ud_ucase_sv.o ud_ucase_cl.o : ud_ucase.h
+
+clean :
+ ${RM} ${EXE} *.o
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+Many of the programs in this directory are named according to the
+conventions described below.
+
+A prefix identifies the socket domain in which the program operates,
+and the socket type employed. The socket domain is indicated by one
+or two letters:
+
+ u UNIX
+ i Internet (both v4 and v6)
+ i6 Internet v6 only
+
+The socket type is one of the following:
+
+ d datagram
+ s stream
+
+A suffix indicates whether the program is a client or server:
+
+ cl client
+ sv server
+
+Thus, id_echo_sv.c is a server program that uses datagram sockets in
+the Internet domain.
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 59-2 */
+
+/* i6d_ucase.h
+
+ Header file for i6d_ucase_sv.c and i6d_ucase_cl.c.
+*/
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <ctype.h>
+#include "tlpi_hdr.h"
+
+#define BUF_SIZE 10 /* Maximum size of messages exchanged
+ between client and server */
+
+#define PORT_NUM 50002 /* Server port number */
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 59-4 */
+
+/* i6d_ucase_cl.c
+
+ Client for i6d_ucase_sv.c: send each command-line argument as a datagram to
+ the server, and then display the contents of the server's response datagram.
+*/
+#include "i6d_ucase.h"
+
+int
+main(int argc, char *argv[])
+{
+ struct sockaddr_in6 svaddr;
+ int sfd, j;
+ size_t msgLen;
+ ssize_t numBytes;
+ char resp[BUF_SIZE];
+
+ if (argc < 3 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s host-address msg...\n", argv[0]);
+
+ /* Create a datagram socket; send to an address in the IPv6 domain */
+
+ sfd = socket(AF_INET6, SOCK_DGRAM, 0); /* Create client socket */
+ if (sfd == -1)
+ errExit("socket");
+
+ memset(&svaddr, 0, sizeof(struct sockaddr_in6));
+ svaddr.sin6_family = AF_INET6;
+ svaddr.sin6_port = htons(PORT_NUM);
+ if (inet_pton(AF_INET6, argv[1], &svaddr.sin6_addr) <= 0)
+ fatal("inet_pton failed for address '%s'", argv[1]);
+
+ /* Send messages to server; echo responses on stdout */
+
+ for (j = 2; j < argc; j++) {
+ msgLen = strlen(argv[j]);
+ if (sendto(sfd, argv[j], msgLen, 0, (struct sockaddr *) &svaddr,
+ sizeof(struct sockaddr_in6)) != msgLen)
+ fatal("sendto");
+
+ numBytes = recvfrom(sfd, resp, BUF_SIZE, 0, NULL, NULL);
+ if (numBytes == -1)
+ errExit("recvfrom");
+
+ printf("Response %d: %.*s\n", j - 1, (int) numBytes, resp);
+ }
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 59-3 */
+
+/* i6d_ucase_sv.c
+
+ A server that receives datagrams, converts their contents to uppercase, and
+ then returns them to the senders.
+
+ See also i6d_ucase_cl.c.
+*/
+#include "i6d_ucase.h"
+
+int
+main(int argc, char *argv[])
+{
+ struct sockaddr_in6 svaddr, claddr;
+ int sfd, j;
+ ssize_t numBytes;
+ socklen_t len;
+ char buf[BUF_SIZE];
+ char claddrStr[INET6_ADDRSTRLEN];
+
+ /* Create a datagram socket bound to an address in the IPv6 domain */
+
+ sfd = socket(AF_INET6, SOCK_DGRAM, 0);
+ if (sfd == -1)
+ errExit("socket");
+
+ memset(&svaddr, 0, sizeof(struct sockaddr_in6));
+ svaddr.sin6_family = AF_INET6;
+ svaddr.sin6_addr = in6addr_any; /* Wildcard address */
+ svaddr.sin6_port = htons(PORT_NUM);
+
+ if (bind(sfd, (struct sockaddr *) &svaddr,
+ sizeof(struct sockaddr_in6)) == -1)
+ errExit("bind");
+
+ /* Receive messages, convert to uppercase, and return to client */
+
+ for (;;) {
+ len = sizeof(struct sockaddr_in6);
+ numBytes = recvfrom(sfd, buf, BUF_SIZE, 0,
+ (struct sockaddr *) &claddr, &len);
+ if (numBytes == -1)
+ errExit("recvfrom");
+
+ /* Display address of client that sent the message */
+
+ if (inet_ntop(AF_INET6, &claddr.sin6_addr, claddrStr,
+ INET6_ADDRSTRLEN) == NULL)
+ printf("Couldn't convert client address to string\n");
+ else
+ printf("Server received %ld bytes from (%s, %u)\n",
+ (long) numBytes, claddrStr, ntohs(claddr.sin6_port));
+
+ for (j = 0; j < numBytes; j++)
+ buf[j] = toupper((unsigned char) buf[j]);
+
+ if (sendto(sfd, buf, numBytes, 0, (struct sockaddr *) &claddr, len) !=
+ numBytes)
+ fatal("sendto");
+ }
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 60-1 */
+
+/* id_echo.h
+
+ Header file for id_echo_sv.c and id_echo_cl.c.
+*/
+#include "inet_sockets.h" /* Declares our socket functions */
+#include "tlpi_hdr.h"
+
+#define SERVICE "echo" /* Name of UDP service */
+
+#define BUF_SIZE 500 /* Maximum size of datagrams that can
+ be read by client and server */
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 60-3 */
+
+/* id_echo_cl.c
+
+ A client for the UDP "echo" service. This program sends each of its
+ command-line arguments as a datagram to the server and echoes the
+ contents of the datagrams that the server sends in response.
+
+ See also id_echo_sv.c.
+*/
+#include "id_echo.h"
+
+int
+main(int argc, char *argv[])
+{
+ int sfd, j;
+ size_t len;
+ ssize_t numRead;
+ char buf[BUF_SIZE];
+
+ if (argc < 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s host msg...\n", argv[0]);
+
+ /* Construct server address from first command-line argument */
+
+ sfd = inetConnect(argv[1], SERVICE, SOCK_DGRAM);
+ if (sfd == -1)
+ fatal("Could not connect to server socket");
+
+ /* Send remaining command-line arguments to server as separate datagrams */
+
+ for (j = 2; j < argc; j++) {
+ len = strlen(argv[j]);
+ if (write(sfd, argv[j], len) != len)
+ fatal("partial/failed write");
+
+ numRead = read(sfd, buf, BUF_SIZE);
+ if (numRead == -1)
+ errExit("read");
+
+ printf("[%ld bytes] %.*s\n", (long) numRead, (int) numRead, buf);
+ }
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 60-2 */
+
+/* id_echo_sv.c
+
+ This program implements a daemon that provides the UDP "echo" service. It
+ reads datagrams and then sends copies back to the originating address.
+
+ NOTE: this program must be run under a root login, in order to allow the
+ "echo" port (7) to be bound. Alternatively, for test purposes, you can edit
+ id_echo.h and replace the SERVICE name with a suitable unreserved port
+ number (e.g., "51000"), and make a corresponding change in the client.
+
+ See also id_echo_cl.c.
+*/
+#include <syslog.h>
+#include "id_echo.h"
+#include "become_daemon.h"
+
+int
+main(int argc, char *argv[])
+{
+ int sfd;
+ ssize_t numRead;
+ socklen_t len;
+ struct sockaddr_storage claddr;
+ char buf[BUF_SIZE];
+ char addrStr[IS_ADDR_STR_LEN];
+
+ if (becomeDaemon(0) == -1)
+ errExit("becomeDaemon");
+
+ sfd = inetBind(SERVICE, SOCK_DGRAM, NULL);
+ if (sfd == -1) {
+ syslog(LOG_ERR, "Could not create server socket (%s)", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ /* Receive datagrams and return copies to senders */
+
+ for (;;) {
+ len = sizeof(struct sockaddr_storage);
+ numRead = recvfrom(sfd, buf, BUF_SIZE, 0,
+ (struct sockaddr *) &claddr, &len);
+ if (numRead == -1)
+ errExit("recvfrom");
+
+ if (sendto(sfd, buf, numRead, 0, (struct sockaddr *) &claddr, len)
+ != numRead)
+ syslog(LOG_WARNING, "Error echoing response to %s (%s)",
+ inetAddressStr((struct sockaddr *) &claddr, len,
+ addrStr, IS_ADDR_STR_LEN),
+ strerror(errno));
+ }
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 59-9 */
+
+/* inet_sockets.c
+
+ A package of useful routines for Internet domain sockets.
+*/
+#define _BSD_SOURCE /* To get NI_MAXHOST and NI_MAXSERV
+ definitions from <netdb.h> */
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include "inet_sockets.h" /* Declares functions defined here */
+#include "tlpi_hdr.h"
+
+/* The following arguments are common to several of the routines
+ below:
+
+ 'host': NULL for loopback IP address, or
+ a host name or numeric IP address
+ 'service': either a name or a port number
+ 'type': either SOCK_STREAM or SOCK_DGRAM
+*/
+
+/* Create socket and connect it to the address specified by
+ 'host' + 'service'/'type'. Return socket descriptor on success,
+ or -1 on error */
+
+int
+inetConnect(const char *host, const char *service, int type)
+{
+ struct addrinfo hints;
+ struct addrinfo *result, *rp;
+ int sfd, s;
+
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_canonname = NULL;
+ hints.ai_addr = NULL;
+ hints.ai_next = NULL;
+ hints.ai_family = AF_UNSPEC; /* Allows IPv4 or IPv6 */
+ hints.ai_socktype = type;
+
+ s = getaddrinfo(host, service, &hints, &result);
+ if (s != 0) {
+ errno = ENOSYS;
+ return -1;
+ }
+
+ /* Walk through returned list until we find an address structure
+ that can be used to successfully connect a socket */
+
+ for (rp = result; rp != NULL; rp = rp->ai_next) {
+ sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+ if (sfd == -1)
+ continue; /* On error, try next address */
+
+ if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1)
+ break; /* Success */
+
+ /* Connect failed: close this socket and try next address */
+
+ close(sfd);
+ }
+
+ freeaddrinfo(result);
+
+ return (rp == NULL) ? -1 : sfd;
+}
+
+/* Create an Internet domain socket and bind it to the address
+ { wildcard-IP-address + 'service'/'type' }.
+ If 'doListen' is TRUE, then make this a listening socket (by
+ calling listen() with 'backlog'), with the SO_REUSEADDR option set.
+ If 'addrLen' is not NULL, then use it to return the size of the
+ address structure for the address family for this socket.
+ Return the socket descriptor on success, or -1 on error. */
+
+static int /* Public interfaces: inetBind() and inetListen() */
+inetPassiveSocket(const char *service, int type, socklen_t *addrlen,
+ Boolean doListen, int backlog)
+{
+ struct addrinfo hints;
+ struct addrinfo *result, *rp;
+ int sfd, optval, s;
+
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_canonname = NULL;
+ hints.ai_addr = NULL;
+ hints.ai_next = NULL;
+ hints.ai_socktype = type;
+ hints.ai_family = AF_UNSPEC; /* Allows IPv4 or IPv6 */
+ hints.ai_flags = AI_PASSIVE; /* Use wildcard IP address */
+
+ s = getaddrinfo(NULL, service, &hints, &result);
+ if (s != 0)
+ return -1;
+
+ /* Walk through returned list until we find an address structure
+ that can be used to successfully create and bind a socket */
+
+ optval = 1;
+ for (rp = result; rp != NULL; rp = rp->ai_next) {
+ sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+ if (sfd == -1)
+ continue; /* On error, try next address */
+
+ if (doListen) {
+ if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &optval,
+ sizeof(optval)) == -1) {
+ close(sfd);
+ freeaddrinfo(result);
+ return -1;
+ }
+ }
+
+ if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0)
+ break; /* Success */
+
+ /* bind() failed: close this socket and try next address */
+
+ close(sfd);
+ }
+
+ if (rp != NULL && doListen) {
+ if (listen(sfd, backlog) == -1) {
+ freeaddrinfo(result);
+ return -1;
+ }
+ }
+
+ if (rp != NULL && addrlen != NULL)
+ *addrlen = rp->ai_addrlen; /* Return address structure size */
+
+ freeaddrinfo(result);
+
+ return (rp == NULL) ? -1 : sfd;
+}
+
+/* Create stream socket, bound to wildcard IP address + port given in
+ 'service'. Make the socket a listening socket, with the specified
+ 'backlog'. Return socket descriptor on success, or -1 on error. */
+
+int
+inetListen(const char *service, int backlog, socklen_t *addrlen)
+{
+ return inetPassiveSocket(service, SOCK_STREAM, addrlen, TRUE, backlog);
+}
+
+/* Create socket bound to wildcard IP address + port given in
+ 'service'. Return socket descriptor on success, or -1 on error. */
+
+int
+inetBind(const char *service, int type, socklen_t *addrlen)
+{
+ return inetPassiveSocket(service, type, addrlen, FALSE, 0);
+}
+
+/* Given a socket address in 'addr', whose length is specified in
+ 'addrlen', return a null-terminated string containing the host and
+ service names in the form "(hostname, port#)". The string is
+ returned in the buffer pointed to by 'addrStr', and this value is
+ also returned as the function result. The caller must specify the
+ size of the 'addrStr' buffer in 'addrStrLen'. */
+
+char *
+inetAddressStr(const struct sockaddr *addr, socklen_t addrlen,
+ char *addrStr, int addrStrLen)
+{
+ char host[NI_MAXHOST], service[NI_MAXSERV];
+
+ if (getnameinfo(addr, addrlen, host, NI_MAXHOST,
+ service, NI_MAXSERV, NI_NUMERICSERV) == 0)
+ snprintf(addrStr, addrStrLen, "(%s, %s)", host, service);
+ else
+ snprintf(addrStr, addrStrLen, "(?UNKNOWN?)");
+
+ return addrStr;
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 59-8 */
+
+/* inet_sockets.h
+
+ Header file for inet_sockets.c.
+*/
+#ifndef INET_SOCKETS_H
+#define INET_SOCKETS_H /* Prevent accidental double inclusion */
+
+#include <sys/socket.h>
+#include <netdb.h>
+
+int inetConnect(const char *host, const char *service, int type);
+
+int inetListen(const char *service, int backlog, socklen_t *addrlen);
+
+int inetBind(const char *service, int type, socklen_t *addrlen);
+
+char *inetAddressStr(const struct sockaddr *addr, socklen_t addrlen,
+ char *addrStr, int addrStrLen);
+
+#define IS_ADDR_STR_LEN 4096
+ /* Suggested length for string buffer that caller
+ should pass to inetAddressStr(). Must be greater
+ than (NI_MAXHOST + NI_MAXSERV + 4) */
+#endif
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 61-2 */
+
+/* is_echo_cl.c
+
+ A simple client to communicate with the standard "echo" server.
+
+ In many Linux distributions, the "echo" server is not started by default.
+ Normally it is implemented internally by inetd(8), and to enable we must
+ do the following *as root*:
+
+ 1. Edit the file /etc/inetd.conf, and uncomment the two lines
+ for the the "echo" service by removing the '#' character at the
+ beginning of the line. The lines typically look like this:
+
+ # echo stream tcp nowait root internal
+ # echo dgram udp wait root internal
+
+ and we must change them to this:
+
+ echo stream tcp nowait root internal
+ echo dgram udp wait root internal
+
+ 2. Inform inetd(8) of the change using the following command:
+
+ bash# killall -HUP inetd
+
+ If your system uses xinetd(8) instead of inetd(8), then read the
+ xinetd.conf(5) manual page. (You may also find that your system has a GUI
+ admin tool that allows you to easily enable/disable the "echo" service.)
+
+ See also is_echo_sv.c.
+*/
+#include "inet_sockets.h"
+#include "tlpi_hdr.h"
+
+#define BUF_SIZE 100
+
+int
+main(int argc, char *argv[])
+{
+ int sfd;
+ ssize_t numRead;
+ char buf[BUF_SIZE];
+
+ if (argc != 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s host\n", argv[0]);
+
+ sfd = inetConnect(argv[1], "echo", SOCK_STREAM);
+ if (sfd == -1)
+ errExit("inetConnect");
+
+ switch (fork()) {
+ case -1:
+ errExit("fork");
+
+ case 0: /* Child: read server's response, echo on stdout */
+ for (;;) {
+ numRead = read(sfd, buf, BUF_SIZE);
+ if (numRead <= 0) /* Exit on EOF or error */
+ break;
+ printf("%.*s", (int) numRead, buf);
+ }
+ exit(EXIT_SUCCESS);
+
+ default: /* Parent: write contents of stdin to socket */
+ for (;;) {
+ numRead = read(STDIN_FILENO, buf, BUF_SIZE);
+ if (numRead <= 0) /* Exit loop on EOF or error */
+ break;
+ if (write(sfd, buf, numRead) != numRead)
+ fatal("write() failed");
+ }
+
+ /* Close writing channel, so server sees EOF */
+
+ if (shutdown(sfd, SHUT_WR) == -1)
+ errExit("shutdown");
+ exit(EXIT_SUCCESS);
+ }
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 60-6 */
+
+/* is_echo_inetd_sv.c
+
+ An inetd-invoked implementation of the TCP "echo" service.
+
+ Compare this program with is_echo_sv.c.
+*/
+#include <syslog.h>
+#include "tlpi_hdr.h"
+
+#define BUF_SIZE 4096
+
+int
+main(int argc, char *argv[])
+{
+ char buf[BUF_SIZE];
+ ssize_t numRead;
+
+ while ((numRead = read(STDIN_FILENO, buf, BUF_SIZE)) > 0) {
+ if (write(STDOUT_FILENO, buf, numRead) != numRead) {
+ syslog(LOG_ERR, "write() failed: %s", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ if (numRead == -1) {
+ syslog(LOG_ERR, "Error from read(): %s", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 60-4 */
+
+/* is_echo_sv.c
+
+ An implementation of the TCP "echo" service.
+
+ NOTE: this program must be run under a root login, in order to allow the
+ "echo" port (7) to be bound. Alternatively, for test purposes, you can
+ replace the SERVICE name below with a suitable unreserved port number
+ (e.g., "51000"), and make a corresponding change in the client.
+
+ See also is_echo_cl.c.
+*/
+#include <signal.h>
+#include <syslog.h>
+#include <sys/wait.h>
+#include "become_daemon.h"
+#include "inet_sockets.h" /* Declarations of inet*() socket functions */
+#include "tlpi_hdr.h"
+
+#define SERVICE "echo" /* Name of TCP service */
+#define BUF_SIZE 4096
+
+static void /* SIGCHLD handler to reap dead child processes */
+grimReaper(int sig)
+{
+ int savedErrno; /* Save 'errno' in case changed here */
+
+ savedErrno = errno;
+ while (waitpid(-1, NULL, WNOHANG) > 0)
+ continue;
+ errno = savedErrno;
+}
+
+/* Handle a client request: copy socket input back to socket */
+
+static void
+handleRequest(int cfd)
+{
+ char buf[BUF_SIZE];
+ ssize_t numRead;
+
+ while ((numRead = read(cfd, buf, BUF_SIZE)) > 0) {
+ if (write(cfd, buf, numRead) != numRead) {
+ syslog(LOG_ERR, "write() failed: %s", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ if (numRead == -1) {
+ syslog(LOG_ERR, "Error from read(): %s", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ int lfd, cfd; /* Listening and connected sockets */
+ struct sigaction sa;
+
+ if (becomeDaemon(0) == -1)
+ errExit("becomeDaemon");
+
+ /* Establish SIGCHLD handler to reap terminated child processes */
+
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ sa.sa_handler = grimReaper;
+ if (sigaction(SIGCHLD, &sa, NULL) == -1) {
+ syslog(LOG_ERR, "Error from sigaction(): %s", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ lfd = inetListen(SERVICE, 10, NULL);
+ if (lfd == -1) {
+ syslog(LOG_ERR, "Could not create server socket (%s)", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ for (;;) {
+ cfd = accept(lfd, NULL, NULL); /* Wait for connection */
+ if (cfd == -1) {
+ syslog(LOG_ERR, "Failure in accept(): %s", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ /* Handle each client request in a new child process */
+
+ switch (fork()) {
+ case -1:
+ syslog(LOG_ERR, "Can't create child (%s)", strerror(errno));
+ close(cfd); /* Give up on this client */
+ break; /* May be temporary; try next client */
+
+ case 0: /* Child */
+ close(lfd); /* Unneeded copy of listening socket */
+ handleRequest(cfd);
+ _exit(EXIT_SUCCESS);
+
+ default: /* Parent */
+ close(cfd); /* Unneeded copy of connected socket */
+ break; /* Loop to accept next connection */
+ }
+ }
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 60-2 */
+
+/* is_echo_v2_sv.c
+
+ An implementation of the TCP "echo" service that can either be invoked from
+ the command line, or via inetd(8) if we specify the "-i" command-line option.
+
+ If run from the command line, this program must be run under a root login,
+ in order to allow the "echo" port (7) to be bound. Alternatively, for test
+ purposes, you can replace the SERVICE name below with a suitable unreserved
+ port number (e.g., "51000"), and make a corresponding change in the client.
+
+ If run from inetd(8), place a line similar to the following in
+ /etc/inetd.conf (you will need to modify <some-path> as required):
+
+ echo stream tcp nowait root /some-path/is_echo_v2_sv is_echo_v2_sv -i
+
+ Note that this program is very similar to is_echo_sv.c: all we've
+ done is add a few extra lines of code to handle the "-i" option.
+
+ See also is_echo_sv.c.
+*/
+#include <syslog.h>
+#include <signal.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include "become_daemon.h"
+#include "inet_sockets.h" /* Declares our socket functions */
+#include "tlpi_hdr.h"
+
+#define SERVICE "echo" /* Name of TCP service */
+#define BUF_SIZE 4096
+
+static void /* SIGCHLD handler to reap dead child processes */
+grimReaper(int sig)
+{
+ int savedErrno; /* Save 'errno' in case changed here */
+
+ savedErrno = errno;
+ while (waitpid(-1, NULL, WNOHANG) > 0)
+ continue;
+ errno = savedErrno;
+}
+
+static void /* Handle client: copy socket input back to socket */
+handleRequest(int cfd)
+{
+ ssize_t numRead;
+ char buf[BUF_SIZE];
+
+ while ((numRead = read(cfd, buf, BUF_SIZE)) > 0) {
+ if (write(cfd, buf, numRead) != numRead) {
+ syslog(LOG_ERR, "write() failed: %s", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ if (numRead == -1) {
+ syslog(LOG_ERR, "Error from read(): %s", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ int lfd, cfd; /* Listening and connected sockets */
+ struct sigaction sa;
+
+ /* The "-i" option means we were invoked from inetd(8), so that
+ all we need to do is handle the connection on STDIN_FILENO */
+
+ if (argc > 1 && strcmp(argv[1], "-i") == 0) {
+ handleRequest(STDIN_FILENO);
+ exit(EXIT_SUCCESS);
+ }
+
+ if (becomeDaemon(0) == -1)
+ errExit("becomeDaemon");
+
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ sa.sa_handler = grimReaper;
+ if (sigaction(SIGCHLD, &sa, NULL) == -1) {
+ syslog(LOG_ERR, "Error from sigaction(): %s", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ lfd = inetListen(SERVICE, 10, NULL);
+ if (lfd == -1) {
+ syslog(LOG_ERR, "Could not create server socket (%s)", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ for (;;) {
+ cfd = accept(lfd, NULL, NULL); /* Wait for connection */
+ if (cfd == -1) {
+ syslog(LOG_ERR, "Failure in accept(): %s",
+ strerror(errno));
+ continue; /* Try next */
+ }
+
+ switch (fork()) { /* Create child for each client */
+ case -1:
+ syslog(LOG_ERR, "Can't create child (%s)",
+ strerror(errno));
+ close(cfd); /* Give up on this client */
+ break; /* May be temporary; try next client */
+
+ case 0: /* Child */
+ close(lfd); /* Don't need copy of listening socket */
+ handleRequest(cfd);
+ _exit(EXIT_SUCCESS);
+
+ default: /* Parent */
+ close(cfd); /* Don't need copy of connected socket */
+ break; /* Loop to accept next connection */
+ }
+ }
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 59-5 */
+
+/* is_seqnum.h
+
+ Header file for is_seqnum_sv.c and is_seqnum_cl.c.
+*/
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <signal.h>
+#include "read_line.h" /* Declaration of readLine() */
+#include "tlpi_hdr.h"
+
+#define PORT_NUM "50000" /* Port number for server */
+
+#define INT_LEN 30 /* Size of string able to hold largest
+ integer (including terminating '\n') */
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 59-7 */
+
+/* is_seqnum_cl.c
+
+ A simple Internet stream socket client. This client requests a sequence
+ number from the server.
+
+ See also is_seqnum_sv.c.
+*/
+#include <netdb.h>
+#include "is_seqnum.h"
+
+int
+main(int argc, char *argv[])
+{
+ char *reqLenStr; /* Requested length of sequence */
+ char seqNumStr[INT_LEN]; /* Start of granted sequence */
+ int cfd;
+ ssize_t numRead;
+ struct addrinfo hints;
+ struct addrinfo *result, *rp;
+
+ if (argc < 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s server-host [sequence-len]\n", argv[0]);
+
+ /* Call getaddrinfo() to obtain a list of addresses that
+ we can try connecting to */
+
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_canonname = NULL;
+ hints.ai_addr = NULL;
+ hints.ai_next = NULL;
+ hints.ai_family = AF_UNSPEC; /* Allows IPv4 or IPv6 */
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_NUMERICSERV;
+
+ if (getaddrinfo(argv[1], PORT_NUM, &hints, &result) != 0)
+ errExit("getaddrinfo");
+
+ /* Walk through returned list until we find an address structure
+ that can be used to successfully connect a socket */
+
+ for (rp = result; rp != NULL; rp = rp->ai_next) {
+ cfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+ if (cfd == -1)
+ continue; /* On error, try next address */
+
+ if (connect(cfd, rp->ai_addr, rp->ai_addrlen) != -1)
+ break; /* Success */
+
+ /* Connect failed: close this socket and try next address */
+
+ close(cfd);
+ }
+
+ if (rp == NULL)
+ fatal("Could not connect socket to any address");
+
+ freeaddrinfo(result);
+
+ /* Send requested sequence length, with terminating newline */
+
+ reqLenStr = (argc > 2) ? argv[2] : "1";
+ if (write(cfd, reqLenStr, strlen(reqLenStr)) != strlen(reqLenStr))
+ fatal("Partial/failed write (reqLenStr)");
+ if (write(cfd, "\n", 1) != 1)
+ fatal("Partial/failed write (newline)");
+
+ /* Read and display sequence number returned by server */
+
+ numRead = readLine(cfd, seqNumStr, INT_LEN);
+ if (numRead == -1)
+ errExit("readLine");
+ if (numRead == 0)
+ fatal("Unexpected EOF from server");
+
+ printf("Sequence number: %s", seqNumStr); /* Includes '\n' */
+
+ exit(EXIT_SUCCESS); /* Closes 'cfd' */
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 59-6 */
+
+/* is_seqnum_sv.c
+
+ A simple Internet stream socket server. Our service is to provide
+ unique sequence numbers to clients.
+
+ Usage: is_seqnum_sv [init-seq-num]
+ (default = 0)
+
+ See also is_seqnum_cl.c.
+*/
+#define _BSD_SOURCE /* To get definitions of NI_MAXHOST and
+ NI_MAXSERV from <netdb.h> */
+#include <netdb.h>
+#include "is_seqnum.h"
+
+#define BACKLOG 50
+
+int
+main(int argc, char *argv[])
+{
+ uint32_t seqNum;
+ char reqLenStr[INT_LEN]; /* Length of requested sequence */
+ char seqNumStr[INT_LEN]; /* Start of granted sequence */
+ struct sockaddr_storage claddr;
+ int lfd, cfd, optval, reqLen;
+ socklen_t addrlen;
+ struct addrinfo hints;
+ struct addrinfo *result, *rp;
+#define ADDRSTRLEN (NI_MAXHOST + NI_MAXSERV + 10)
+ char addrStr[ADDRSTRLEN];
+ char host[NI_MAXHOST];
+ char service[NI_MAXSERV];
+
+ if (argc > 1 && strcmp(argv[1], "--help") == 0)
+ usageErr("%s [init-seq-num]\n", argv[0]);
+
+ seqNum = (argc > 1) ? getInt(argv[1], 0, "init-seq-num") : 0;
+
+ /* Ignore the SIGPIPE signal, so that we find out about broken connection
+ errors via a failure from write(). */
+
+ if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) errExit("signal");
+
+ /* Call getaddrinfo() to obtain a list of addresses that
+ we can try binding to */
+
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_canonname = NULL;
+ hints.ai_addr = NULL;
+ hints.ai_next = NULL;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_family = AF_UNSPEC; /* Allows IPv4 or IPv6 */
+ hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV;
+ /* Wildcard IP address; service name is numeric */
+
+ if (getaddrinfo(NULL, PORT_NUM, &hints, &result) != 0)
+ errExit("getaddrinfo");
+
+ /* Walk through returned list until we find an address structure
+ that can be used to successfully create and bind a socket */
+
+ optval = 1;
+ for (rp = result; rp != NULL; rp = rp->ai_next) {
+ lfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+ if (lfd == -1)
+ continue; /* On error, try next address */
+
+ if (setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval))
+ == -1)
+ errExit("setsockopt");
+
+ if (bind(lfd, rp->ai_addr, rp->ai_addrlen) == 0)
+ break; /* Success */
+
+ /* bind() failed: close this socket and try next address */
+
+ close(lfd);
+ }
+
+ if (rp == NULL)
+ fatal("Could not bind socket to any address");
+
+ if (listen(lfd, BACKLOG) == -1)
+ errExit("listen");
+
+ freeaddrinfo(result);
+
+ for (;;) { /* Handle clients iteratively */
+
+ /* Accept a client connection, obtaining client's address */
+
+ addrlen = sizeof(struct sockaddr_storage);
+ cfd = accept(lfd, (struct sockaddr *) &claddr, &addrlen);
+ if (cfd == -1) {
+ errMsg("accept");
+ continue;
+ }
+
+ if (getnameinfo((struct sockaddr *) &claddr, addrlen,
+ host, NI_MAXHOST, service, NI_MAXSERV, 0) == 0)
+ snprintf(addrStr, ADDRSTRLEN, "(%s, %s)", host, service);
+ else
+ snprintf(addrStr, ADDRSTRLEN, "(?UNKNOWN?)");
+ printf("Connection from %s\n", addrStr);
+
+ /* Read client request, send sequence number back */
+
+ if (readLine(cfd, reqLenStr, INT_LEN) <= 0) {
+ close(cfd);
+ continue; /* Failed read; skip request */
+ }
+
+ reqLen = atoi(reqLenStr);
+ if (reqLen <= 0) { /* Watch for misbehaving clients */
+ close(cfd);
+ continue; /* Bad request; skip it */
+ }
+
+ snprintf(seqNumStr, INT_LEN, "%d\n", seqNum);
+ if (write(cfd, seqNumStr, strlen(seqNumStr)) != strlen(seqNumStr))
+ fprintf(stderr, "Error on write");
+
+ seqNum += reqLen; /* Update sequence number */
+
+ if (close(cfd) == -1) /* Close connection */
+ errMsg("close");
+ }
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 59-2:a */
+
+/* is_seqnum_v2.h
+
+ Header file for is_seqnum_v2_sv.c and is_seqnum_v2_cl.c.
+*/
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <signal.h>
+#include "inet_sockets.h" /* Declares our socket functions */
+#include "read_line.h" /* Declaration of readLine() */
+#include "tlpi_hdr.h"
+
+#define PORT_NUM_STR "50000" /* Port number for server */
+
+#define INT_LEN 30 /* Size of string able to hold largest
+ integer (including terminating '\n') */
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 59-2:c */
+
+/* is_seqnum_v2_cl.c
+
+ A simple Internet stream socket client. This server obtains a sequence
+ number from the server.
+
+ The program is the same as is_seqnum_cl.c, except that it uses the
+ functions in our inet_sockets.c library to simplify the creation of a
+ socket that connects to the server's socket.
+
+ See also is_seqnum_v2_sv.c.
+*/
+#include "is_seqnum_v2.h"
+
+int
+main(int argc, char *argv[])
+{
+ char *reqLenStr; /* Requested length of sequence */
+ char seqNumStr[INT_LEN]; /* Start of granted sequence */
+ int cfd;
+ ssize_t numRead;
+
+ if (argc < 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s server-host [sequence-len]\n", argv[0]);
+
+ cfd = inetConnect(argv[1], PORT_NUM_STR, SOCK_STREAM);
+ if (cfd == -1)
+ fatal("inetConnect() failed");
+
+ reqLenStr = (argc > 2) ? argv[2] : "1";
+ if (write(cfd, reqLenStr, strlen(reqLenStr)) != strlen(reqLenStr))
+ fatal("Partial/failed write (reqLenStr)");
+ if (write(cfd, "\n", 1) != 1)
+ fatal("Partial/failed write (newline)");
+
+ numRead = readLine(cfd, seqNumStr, INT_LEN);
+ if (numRead == -1)
+ errExit("readLine");
+ if (numRead == 0)
+ fatal("Unexpected EOF from server");
+
+ printf("Sequence number: %s", seqNumStr); /* Includes '\n' */
+
+ exit(EXIT_SUCCESS); /* Closes 'cfd' */
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 59-2:b */
+
+/* is_seqnum_v2_sv.c
+
+ A simple Internet stream socket server. Our service is to provide unique
+ sequence numbers to the client.
+
+ This program is the same as is_seqnum_cl.c, except that it uses the functions
+ in our inet_sockets.c library to simplify set up of the server's socket.
+
+ Usage: is_seqnum_sv [init-seq-num] (default = 0)
+
+ See also is_seqnum_v2_cl.c.
+*/
+#include "is_seqnum_v2.h"
+
+int
+main(int argc, char *argv[])
+{
+ uint32_t seqNum;
+ char reqLenStr[INT_LEN]; /* Length of requested sequence */
+ char seqNumStr[INT_LEN]; /* Start of granted sequence */
+ struct sockaddr *claddr;
+ int lfd, cfd, reqLen;
+ socklen_t addrlen, alen;
+ char addrStr[IS_ADDR_STR_LEN];
+
+ if (argc > 1 && strcmp(argv[1], "--help") == 0)
+ usageErr("%s [init-seq-num]\n", argv[0]);
+
+ seqNum = (argc > 1) ? getInt(argv[1], 0, "init-seq-num") : 0;
+
+ /* Ignore the SIGPIPE signal, so that we find out about broken connection
+ errors via a failure from write(). */
+
+ if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) errExit("signal");
+
+ lfd = inetListen(PORT_NUM_STR, 5, &addrlen);
+ if (lfd == -1)
+ fatal("inetListen() failed");
+
+ /* Allocate a buffer large enough to hold the client's socket address */
+
+ claddr = malloc(addrlen);
+ if (claddr == NULL)
+ errExit("malloc");
+
+ for (;;) { /* Handle clients iteratively */
+
+ /* Accept a client connection, obtaining client's address */
+
+ alen = addrlen;
+ cfd = accept(lfd, (struct sockaddr *) claddr, &alen);
+ if (cfd == -1) {
+ errMsg("accept");
+ continue;
+ }
+
+ printf("Connection from %s\n", inetAddressStr(claddr, alen,
+ addrStr, IS_ADDR_STR_LEN));
+
+ /* Read client request, send sequence number back */
+
+ if (readLine(cfd, reqLenStr, INT_LEN) <= 0) {
+ close(cfd);
+ continue; /* Failed read; skip request */
+ }
+
+ reqLen = atoi(reqLenStr);
+ if (reqLen <= 0) { /* Watch for misbehaving clients */
+ close(cfd);
+ continue; /* Bad request; skip it */
+ }
+
+ snprintf(seqNumStr, INT_LEN, "%d\n", seqNum);
+ if (write(cfd, seqNumStr, strlen(seqNumStr)) != strlen(seqNumStr))
+ fprintf(stderr, "Error on write");
+
+ seqNum += reqLen; /* Update sequence number */
+
+ if (close(cfd) == -1) /* Close connection */
+ errMsg("close");
+ }
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 61 */
+
+/* list_host_addresses.c
+
+ List host's network interfaces and IP addresses.
+*/
+#define _GNU_SOURCE /* To get definition of NI_MAXHOST */
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <ifaddrs.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <linux/if_link.h>
+
+int
+main(int argc, char *argv[])
+{
+ struct ifaddrs *ifaddr;
+ int family, s;
+ char host[NI_MAXHOST];
+
+ if (getifaddrs(&ifaddr) == -1) {
+ perror("getifaddrs");
+ exit(EXIT_FAILURE);
+ }
+
+ /* Walk through linked list, ignoring loopback interface and
+ non-AF_INET* addresses */
+
+ for (; ifaddr != NULL; ifaddr = ifaddr->ifa_next) {
+
+ if (ifaddr->ifa_addr == NULL || strcmp(ifaddr->ifa_name, "lo") == 0)
+ continue;
+
+ family = ifaddr->ifa_addr->sa_family;
+
+ if (family != AF_INET && family != AF_INET6)
+ continue;
+
+ /* Display interface name and address */
+
+ s = getnameinfo(ifaddr->ifa_addr,
+ (family == AF_INET) ? sizeof(struct sockaddr_in) :
+ sizeof(struct sockaddr_in6),
+ host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
+ if (s != 0) {
+ printf("getnameinfo() failed: %s\n", gai_strerror(s));
+ exit(EXIT_FAILURE);
+ }
+
+ printf("%-16s %s\n", ifaddr->ifa_name, host);
+ }
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 61-1 */
+
+/* rdwrn.c
+
+ Implementations of readn() and writen().
+*/
+#include <unistd.h>
+#include <errno.h>
+#include "rdwrn.h" /* Declares readn() and writen() */
+
+/* Read 'n' bytes from 'fd' into 'buf', restarting after partial
+ reads or interruptions by a signal handlers */
+
+ssize_t
+readn(int fd, void *buffer, size_t n)
+{
+ ssize_t numRead; /* # of bytes fetched by last read() */
+ size_t totRead; /* Total # of bytes read so far */
+ char *buf;
+
+ buf = buffer; /* No pointer arithmetic on "void *" */
+ for (totRead = 0; totRead < n; ) {
+ numRead = read(fd, buf, n - totRead);
+
+ if (numRead == 0) /* EOF */
+ return totRead; /* May be 0 if this is first read() */
+ if (numRead == -1) {
+ if (errno == EINTR)
+ continue; /* Interrupted --> restart read() */
+ else
+ return -1; /* Some other error */
+ }
+ totRead += numRead;
+ buf += numRead;
+ }
+ return totRead; /* Must be 'n' bytes if we get here */
+}
+
+/* Write 'n' bytes to 'fd' from 'buf', restarting after partial
+ write or interruptions by a signal handlers */
+
+ssize_t
+writen(int fd, const void *buffer, size_t n)
+{
+ ssize_t numWritten; /* # of bytes written by last write() */
+ size_t totWritten; /* Total # of bytes written so far */
+ const char *buf;
+
+ buf = buffer; /* No pointer arithmetic on "void *" */
+ for (totWritten = 0; totWritten < n; ) {
+ numWritten = write(fd, buf, n - totWritten);
+
+ /* The "write() returns 0" case should never happen, but the
+ following ensures that we don't loop forever if it does */
+
+ if (numWritten <= 0) {
+ if (numWritten == -1 && errno == EINTR)
+ continue; /* Interrupted --> restart write() */
+ else
+ return -1; /* Some other error */
+ }
+ totWritten += numWritten;
+ buf += numWritten;
+ }
+ return totWritten; /* Must be 'n' bytes if we get here */
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Header file for Listing 61-1 */
+
+/* rdwrn.h
+
+ Header file for rdwrn.c.
+*/
+#ifndef RDWRN_H
+#define RDWRN_H
+
+#include <sys/types.h>
+
+ssize_t readn(int fd, void *buf, size_t len);
+
+ssize_t writen(int fd, const void *buf, size_t len);
+
+#endif
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 59-1 */
+
+/* read_line.c
+
+ Implementation of readLine().
+*/
+#include <unistd.h>
+#include <errno.h>
+#include "read_line.h" /* Declaration of readLine() */
+
+/* Read characters from 'fd' until a newline is encountered. If a newline
+ character is not encountered in the first (n - 1) bytes, then the excess
+ characters are discarded. The returned string placed in 'buf' is
+ null-terminated and includes the newline character if it was read in the
+ first (n - 1) bytes. The function return value is the number of bytes
+ placed in buffer (which includes the newline character if encountered,
+ but excludes the terminating null byte). */
+
+ssize_t
+readLine(int fd, void *buffer, size_t n)
+{
+ ssize_t numRead; /* # of bytes fetched by last read() */
+ size_t totRead; /* Total bytes read so far */
+ char *buf;
+ char ch;
+
+ if (n <= 0 || buffer == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ buf = buffer; /* No pointer arithmetic on "void *" */
+
+ totRead = 0;
+ for (;;) {
+ numRead = read(fd, &ch, 1);
+
+ if (numRead == -1) {
+ if (errno == EINTR) /* Interrupted --> restart read() */
+ continue;
+ else
+ return -1; /* Some other error */
+
+ } else if (numRead == 0) { /* EOF */
+ if (totRead == 0) /* No bytes read; return 0 */
+ return 0;
+ else /* Some bytes read; add '\0' */
+ break;
+
+ } else { /* 'numRead' must be 1 if we get here */
+ if (totRead < n - 1) { /* Discard > (n - 1) bytes */
+ totRead++;
+ *buf++ = ch;
+ }
+
+ if (ch == '\n')
+ break;
+ }
+ }
+
+ *buf = '\0';
+ return totRead;
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Header file for Listing 59-1 */
+
+/* read_line.h
+
+ Header file for read_line.c.
+*/
+#ifndef READ_LINE_H
+#define READ_LINE_H
+
+#include <sys/types.h>
+
+ssize_t readLine(int fd, void *buffer, size_t n);
+
+#endif
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 59-1:a */
+
+/* read_line_buf.c
+
+ Implementation of readLineBuf(), a version of readLine() that is more
+ efficient because it reads blocks of characters at a time.
+*/
+#include <unistd.h>
+#include <errno.h>
+#include "read_line_buf.h"
+
+void /* Initialize a ReadLineBuf structure */
+readLineBufInit(int fd, struct ReadLineBuf *rlbuf)
+{
+ rlbuf->fd = fd;
+ rlbuf->len = 0;
+ rlbuf->next = 0;
+}
+
+/* Return a line of input from the buffer 'rlbuf', placing the characters in
+ 'buffer'. The 'n' argument specifies the size of 'buffer'. If the line of
+ input is larger than this, then the excess characters are discarded. */
+
+ssize_t
+readLineBuf(struct ReadLineBuf *rlbuf, char *buffer, size_t n)
+{
+ size_t cnt;
+ char c;
+
+ if (n <= 0 || buffer == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ cnt = 0;
+
+ /* Fetch characters from rlbuf->buf, up to the next new line. */
+
+ for (;;) {
+
+ /* If there are insufficient characters in 'tlbuf', then obtain
+ further input from the associated file descriptor. */
+
+ if (rlbuf->next >= rlbuf->len) {
+ rlbuf->len = read(rlbuf->fd, rlbuf->buf, RL_MAX_BUF);
+ if (rlbuf->len == -1)
+ return -1;
+
+ if (rlbuf->len == 0) /* End of file */
+ break;
+
+ rlbuf->next = 0;
+ }
+
+ c = rlbuf->buf[rlbuf->next];
+ rlbuf->next++;
+
+ if (cnt < n)
+ buffer[cnt++] = c;
+
+ if (c == '\n')
+ break;
+ }
+
+ return cnt;
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 59-1:b */
+
+/* read_line_buf.h
+
+ Header file for read_line_buf.c (implementation of readLineBuf()).
+*/
+#ifndef READ_LINE_BUF_H /* Prevent accidental double inclusion */
+#define READ_LINE_BUF_H
+
+#include <unistd.h>
+#include <pthread.h>
+#include <errno.h>
+
+#define RL_MAX_BUF 10
+
+struct ReadLineBuf {
+ int fd; /* File descriptor from which to read */
+ char buf[RL_MAX_BUF]; /* Current buffer from file */
+ int next; /* Index of next unread character in 'buf' */
+ ssize_t len; /* Number of characters in 'buf' */
+};
+
+void readLineBufInit(int fd, struct ReadLineBuf *rlbuf);
+
+ssize_t readLineBuf(struct ReadLineBuf *rlbuf, char *buffer, size_t n);
+
+#endif
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 61 */
+
+/* scm_cred.h
+
+ Header file used by scm_cred_send.c and scm_cred_recv.c.
+*/
+#define _GNU_SOURCE /* To get SCM_CREDENTIALS definition from
+ <sys/sockets.h> */
+#include <sys/socket.h>
+#include <sys/un.h>
+#include "unix_sockets.h" /* Declares our socket functions */
+#include "tlpi_hdr.h"
+
+#define SOCK_PATH "scm_cred"
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 61 */
+
+/* scm_cred_recv.c
+
+ Used in conjunction with scm_cred_send.c to demonstrate passing of
+ process credentials via a UNIX domain socket.
+
+ This program receives credentials sent to a UNIX domain socket.
+
+ Usage is as shown in the usageErr() call below.
+
+ Credentials can exchanged over stream or datagram sockets. This program
+ uses stream sockets by default; the "-d" command-line option specifies
+ that datagram sockets should be used instead.
+
+ This program is Linux-specific.
+*/
+#include "scm_cred.h"
+
+int
+main(int argc, char *argv[])
+{
+ struct msghdr msgh;
+ struct iovec iov;
+ struct ucred *ucredp, ucred;
+ int data, lfd, sfd, optval, opt;
+ ssize_t nr;
+ Boolean useDatagramSocket;
+
+ /* Allocate a char array of suitable size to hold the ancillary data.
+ However, since this buffer is in reality a 'struct cmsghdr', use a
+ union to ensure that it is aligned as required for that structure. */
+ union {
+ struct cmsghdr cmh;
+ char control[CMSG_SPACE(sizeof(struct ucred))];
+ /* Space large enough to hold a ucred structure */
+ } control_un;
+ struct cmsghdr *cmhp;
+ socklen_t len;
+
+ /* Parse command-line arguments */
+
+ useDatagramSocket = FALSE;
+
+ while ((opt = getopt(argc, argv, "d")) != -1) {
+ switch (opt) {
+ case 'd':
+ useDatagramSocket = TRUE;
+ break;
+
+ default:
+ usageErr("%s [-d]\n"
+ " -d use datagram socket\n", argv[0]);
+ }
+ }
+
+ /* Create socket bound to well-known address */
+
+ if (remove(SOCK_PATH) == -1 && errno != ENOENT)
+ errExit("remove-%s", SOCK_PATH);
+
+ if (useDatagramSocket) {
+ printf("Receiving via datagram socket\n");
+ sfd = unixBind(SOCK_PATH, SOCK_DGRAM);
+ if (sfd == -1)
+ errExit("unixBind");
+
+ } else {
+ printf("Receiving via stream socket\n");
+ lfd = unixListen(SOCK_PATH, 5);
+ if (lfd == -1)
+ errExit("unixListen");
+
+ sfd = accept(lfd, NULL, NULL);
+ if (sfd == -1)
+ errExit("accept");
+ }
+
+ /* We must set the SO_PASSCRED socket option in order to receive
+ credentials */
+
+ optval = 1;
+ if (setsockopt(sfd, SOL_SOCKET, SO_PASSCRED, &optval, sizeof(optval)) == -1)
+ errExit("setsockopt");
+
+ /* Set 'control_un' to describe ancillary data that we want to receive */
+
+ control_un.cmh.cmsg_len = CMSG_LEN(sizeof(struct ucred));
+ control_un.cmh.cmsg_level = SOL_SOCKET;
+ control_un.cmh.cmsg_type = SCM_CREDENTIALS;
+
+ /* Set 'msgh' fields to describe 'control_un' */
+
+ msgh.msg_control = control_un.control;
+ msgh.msg_controllen = sizeof(control_un.control);
+
+ /* Set fields of 'msgh' to point to buffer used to receive (real)
+ data read by recvmsg() */
+
+ msgh.msg_iov = &iov;
+ msgh.msg_iovlen = 1;
+ iov.iov_base = &data;
+ iov.iov_len = sizeof(int);
+
+ msgh.msg_name = NULL; /* We don't need address of peer */
+ msgh.msg_namelen = 0;
+
+ /* Receive real plus ancillary data */
+
+ nr = recvmsg(sfd, &msgh, 0);
+ if (nr == -1)
+ errExit("recvmsg");
+ printf("recvmsg() returned %ld\n", (long) nr);
+
+ if (nr > 0)
+ printf("Received data = %d\n", data);
+
+ /* Extract credentials information from received ancillary data */
+
+ cmhp = CMSG_FIRSTHDR(&msgh);
+ if (cmhp == NULL || cmhp->cmsg_len != CMSG_LEN(sizeof(struct ucred)))
+ fatal("bad cmsg header / message length");
+ if (cmhp->cmsg_level != SOL_SOCKET)
+ fatal("cmsg_level != SOL_SOCKET");
+ if (cmhp->cmsg_type != SCM_CREDENTIALS)
+ fatal("cmsg_type != SCM_CREDENTIALS");
+
+ ucredp = (struct ucred *) CMSG_DATA(cmhp);
+ printf("Received credentials pid=%ld, uid=%ld, gid=%ld\n",
+ (long) ucredp->pid, (long) ucredp->uid, (long) ucredp->gid);
+
+ /* The Linux-specific, read-only SO_PEERCRED socket option returns
+ credential information about the peer, as described in socket(7) */
+
+ len = sizeof(struct ucred);
+ if (getsockopt(sfd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == -1)
+ errExit("getsockopt");
+
+ printf("Credentials from SO_PEERCRED: pid=%ld, euid=%ld, egid=%ld\n",
+ (long) ucred.pid, (long) ucred.uid, (long) ucred.gid);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 61 */
+
+/* scm_cred_send.c
+
+ Used in conjunction with scm_cred_recv.c to demonstrate passing of
+ process credentials via a UNIX domain socket.
+
+ This program sends credentials to a UNIX domain socket.
+
+ Usage is as shown in the usageErr() call below.
+
+ Credentials can exchanged over stream or datagram sockets. This program
+ uses stream sockets by default; the "-d" command-line option specifies
+ that datagram sockets should be used instead.
+
+ This program is Linux-specific.
+*/
+#include "scm_cred.h"
+
+int
+main(int argc, char *argv[])
+{
+ struct msghdr msgh;
+ struct iovec iov;
+ int data, sfd, opt;
+ ssize_t ns;
+ Boolean useDatagramSocket, noExplicit;
+
+ /* Allocate a char array of suitable size to hold the ancillary data.
+ However, since this buffer is in reality a 'struct cmsghdr', use a
+ union to ensure that it is aligned as required for that structure. */
+ union {
+ struct cmsghdr cmh;
+ char control[CMSG_SPACE(sizeof(struct ucred))];
+ /* Space large enough to hold a ucred structure */
+ } control_un;
+ struct cmsghdr *cmhp;
+
+ /* Parse command-line arguments */
+
+ useDatagramSocket = FALSE;
+ noExplicit = FALSE;
+
+ while ((opt = getopt(argc, argv, "dn")) != -1) {
+ switch (opt) {
+ case 'd':
+ useDatagramSocket = TRUE;
+ break;
+
+ case 'n':
+ noExplicit = TRUE;
+ break;
+
+ default:
+ usageErr("%s [-d] [-n] [data [PID [UID [GID]]]]\n"
+ " -d use datagram socket\n"
+ " -n don't construct explicit "
+ "credentials structure\n", argv[0]);
+ }
+ }
+
+ /* On Linux, we must transmit at least 1 byte of real data in
+ order to send ancillary data */
+
+ msgh.msg_iov = &iov;
+ msgh.msg_iovlen = 1;
+ iov.iov_base = &data;
+ iov.iov_len = sizeof(int);
+
+ /* Data is optionally taken from command line */
+
+ data = (argc > optind) ? atoi(argv[optind]) : 12345;
+
+ /* Don't need to specify destination address, because we use
+ connect() below */
+
+ msgh.msg_name = NULL;
+ msgh.msg_namelen = 0;
+
+ if (noExplicit) {
+
+ /* Don't construct an explicit credentials structure. (It
+ is not necessary to do so, if we just want the receiver to
+ receive our real credentials.) */
+
+ printf("Not explicitly sending a credentials structure\n");
+ msgh.msg_control = NULL;
+ msgh.msg_controllen = 0;
+
+ } else {
+ struct ucred *ucp;
+
+ /* Set 'msgh' fields to describe 'control_un' */
+
+ msgh.msg_control = control_un.control;
+ msgh.msg_controllen = sizeof(control_un.control);
+
+ /* Set message header to describe ancillary data that we want to send */
+
+ cmhp = CMSG_FIRSTHDR(&msgh);
+ cmhp->cmsg_len = CMSG_LEN(sizeof(struct ucred));
+ cmhp->cmsg_level = SOL_SOCKET;
+ cmhp->cmsg_type = SCM_CREDENTIALS;
+ ucp = (struct ucred *) CMSG_DATA(cmhp);
+
+ /*
+ We could rewrite the preceding as:
+
+ control_un.cmh.cmsg_len = CMSG_LEN(sizeof(struct ucred));
+ control_un.cmh.cmsg_level = SOL_SOCKET;
+ control_un.cmh.cmsg_type = SCM_CREDENTIALS;
+ ucp = (struct ucred *) CMSG_DATA(CMSG_FIRSTHDR(&msgh));
+ */
+
+ /* Use sender's own PID, real UID, and real GID, unless
+ alternate values were supplied on the command line */
+
+ ucp->pid = (argc > optind + 1 && strcmp(argv[optind + 1], "-") != 0) ?
+ atoi(argv[optind + 1]) : getpid();
+ ucp->uid = (argc > optind + 2 && strcmp(argv[optind + 2], "-") != 0) ?
+ atoi(argv[optind + 2]) : getuid();
+ ucp->gid = (argc > optind + 3 && strcmp(argv[optind + 3], "-") != 0) ?
+ atoi(argv[optind + 3]) : getgid();
+
+ printf("Send credentials pid=%ld, uid=%ld, gid=%ld\n",
+ (long) ucp->pid, (long) ucp->uid, (long) ucp->gid);
+ }
+
+ sfd = unixConnect(SOCK_PATH, useDatagramSocket ? SOCK_DGRAM : SOCK_STREAM);
+ if (sfd == -1)
+ errExit("unixConnect");
+
+ ns = sendmsg(sfd, &msgh, 0);
+ if (ns == -1)
+ errExit("sendmsg");
+ printf("sendmsg() returned %ld\n", (long) ns);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 61 */
+
+/* scm_rights.h
+
+ Header file used by scm_rights_send.c and scm_rights_recv.c.
+*/
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <fcntl.h>
+#include "unix_sockets.h" /* Declares our unix*() socket functions */
+#include "tlpi_hdr.h"
+
+#define SOCK_PATH "scm_rights"
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 61 */
+
+/* scm_rights_recv.c
+
+ Used in conjunction with scm_rights_send.c to demonstrate passing of
+ file descriptors via a UNIX domain socket.
+
+ This program receives a file descriptor sent to a UNIX domain socket.
+
+ Usage is as shown in the usageErr() call below.
+
+ File descriptors can exchanged over stream or datagram sockets. This
+ program uses stream sockets by default; the "-d" command-line option
+ specifies that datagram sockets should be used instead.
+
+ This program is Linux-specific.
+*/
+#include "scm_rights.h"
+
+#define BUF_SIZE 100
+
+int
+main(int argc, char *argv[])
+{
+ struct msghdr msgh;
+ struct iovec iov;
+ int data, lfd, sfd, fd, opt;
+ ssize_t nr;
+ Boolean useDatagramSocket;
+
+ /* Allocate a char array of suitable size to hold the ancillary data.
+ However, since this buffer is in reality a 'struct cmsghdr', use a
+ union to ensure that it is aligned as required for that structure. */
+ union {
+ struct cmsghdr cmh;
+ char control[CMSG_SPACE(sizeof(int))];
+ /* Space large enough to hold an 'int' */
+ } control_un;
+ struct cmsghdr *cmhp;
+
+ /* Parse command-line arguments */
+
+ useDatagramSocket = FALSE;
+
+ while ((opt = getopt(argc, argv, "d")) != -1) {
+ switch (opt) {
+ case 'd':
+ useDatagramSocket = TRUE;
+ break;
+
+ default:
+ usageErr("%s [-d]\n"
+ " -d use datagram socket\n", argv[0]);
+ }
+ }
+
+ /* Create socket bound to well-known address */
+
+ if (remove(SOCK_PATH) == -1 && errno != ENOENT)
+ errExit("remove-%s", SOCK_PATH);
+
+ if (useDatagramSocket) {
+ fprintf(stderr, "Receiving via datagram socket\n");
+ sfd = unixBind(SOCK_PATH, SOCK_DGRAM);
+ if (sfd == -1)
+ errExit("unixBind");
+
+ } else {
+ fprintf(stderr, "Receiving via stream socket\n");
+ lfd = unixListen(SOCK_PATH, 5);
+ if (lfd == -1)
+ errExit("unixListen");
+
+ sfd = accept(lfd, NULL, NULL);
+ if (sfd == -1)
+ errExit("accept");
+ }
+
+ /* Set 'control_un' to describe ancillary data that we want to receive */
+
+ control_un.cmh.cmsg_len = CMSG_LEN(sizeof(int));
+ control_un.cmh.cmsg_level = SOL_SOCKET;
+ control_un.cmh.cmsg_type = SCM_RIGHTS;
+
+ /* Set 'msgh' fields to describe 'control_un' */
+
+ msgh.msg_control = control_un.control;
+ msgh.msg_controllen = sizeof(control_un.control);
+
+ /* Set fields of 'msgh' to point to buffer used to receive (real)
+ data read by recvmsg() */
+
+ msgh.msg_iov = &iov;
+ msgh.msg_iovlen = 1;
+ iov.iov_base = &data;
+ iov.iov_len = sizeof(int);
+
+ msgh.msg_name = NULL; /* We don't need address of peer */
+ msgh.msg_namelen = 0;
+
+ /* Receive real plus ancillary data */
+
+ nr = recvmsg(sfd, &msgh, 0);
+ if (nr == -1)
+ errExit("recvmsg");
+ fprintf(stderr, "recvmsg() returned %ld\n", (long) nr);
+
+ if (nr > 0)
+ fprintf(stderr, "Received data = %d\n", data);
+
+ /* Get the received file descriptor (which is typically a different
+ file descriptor number than was used in the sending process) */
+
+ cmhp = CMSG_FIRSTHDR(&msgh);
+ if (cmhp == NULL || cmhp->cmsg_len != CMSG_LEN(sizeof(int)))
+ fatal("bad cmsg header / message length");
+ if (cmhp->cmsg_level != SOL_SOCKET)
+ fatal("cmsg_level != SOL_SOCKET");
+ if (cmhp->cmsg_type != SCM_RIGHTS)
+ fatal("cmsg_type != SCM_RIGHTS");
+
+ fd = *((int *) CMSG_DATA(cmhp));
+ fprintf(stderr, "Received fd=%d\n", fd);
+
+ /* Having obtained the file descriptor, read the file's contents and
+ print them on standard output */
+
+ for (;;) {
+ char buf[BUF_SIZE];
+ ssize_t numRead;
+
+ numRead = read(fd, buf, BUF_SIZE);
+ if (numRead == -1)
+ errExit("read");
+
+ if (numRead == 0)
+ break;
+
+ write(STDOUT_FILENO, buf, numRead);
+ }
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 61 */
+
+/* scm_rights_send.c
+
+ Used in conjunction with scm_rights_recv.c to demonstrate passing of
+ file descriptors via a UNIX domain socket.
+
+ This program sends a file descriptor to a UNIX domain socket.
+
+ Usage is as shown in the usageErr() call below.
+
+ File descriptors can exchanged over stream or datagram sockets. This
+ program uses stream sockets by default; the "-d" command-line option
+ specifies that datagram sockets should be used instead.
+
+ This program is Linux-specific.
+*/
+#include "scm_rights.h"
+
+int
+main(int argc, char *argv[])
+{
+ struct msghdr msgh;
+ struct iovec iov;
+ int data, sfd, opt, fd;
+ ssize_t ns;
+ Boolean useDatagramSocket;
+
+ /* Allocate a char array of suitable size to hold the ancillary data.
+ However, since this buffer is in reality a 'struct cmsghdr', use a
+ union to ensure that it is aligned as required for that structure. */
+ union {
+ struct cmsghdr cmh;
+ char control[CMSG_SPACE(sizeof(int))];
+ /* Space large enough to hold an 'int' */
+ } control_un;
+ struct cmsghdr *cmhp;
+
+ /* Parse command-line arguments */
+
+ useDatagramSocket = FALSE;
+
+ while ((opt = getopt(argc, argv, "d")) != -1) {
+ switch (opt) {
+ case 'd':
+ useDatagramSocket = TRUE;
+ break;
+
+ default:
+ usageErr("%s [-d] file\n"
+ " -d use datagram socket\n", argv[0]);
+ }
+ }
+
+ if (argc != optind + 1)
+ usageErr("%s [-d] file\n", argv[0]);
+
+ /* Open the file named on the command line */
+
+ fd = open(argv[optind], O_RDONLY);
+ if (fd == -1)
+ errExit("open");
+
+ /* On Linux, we must transmit at least 1 byte of real data in
+ order to send ancillary data */
+
+ msgh.msg_iov = &iov;
+ msgh.msg_iovlen = 1;
+ iov.iov_base = &data;
+ iov.iov_len = sizeof(int);
+ data = 12345;
+
+ /* We don't need to specify destination address, because we use
+ connect() below */
+
+ msgh.msg_name = NULL;
+ msgh.msg_namelen = 0;
+
+ msgh.msg_control = control_un.control;
+ msgh.msg_controllen = sizeof(control_un.control);
+
+ fprintf(stderr, "Sending fd %d\n", fd);
+
+ /* Set message header to describe ancillary data that we want to send */
+
+ cmhp = CMSG_FIRSTHDR(&msgh);
+ cmhp->cmsg_len = CMSG_LEN(sizeof(int));
+ cmhp->cmsg_level = SOL_SOCKET;
+ cmhp->cmsg_type = SCM_RIGHTS;
+ *((int *) CMSG_DATA(cmhp)) = fd;
+
+ /*
+ We could rewrite the preceding lines as:
+
+ control_un.cmh.cmsg_len = CMSG_LEN(sizeof(int));
+ control_un.cmh.cmsg_level = SOL_SOCKET;
+ control_un.cmh.cmsg_type = SCM_RIGHTS;
+ *((int *) CMSG_DATA(CMSG_FIRSTHDR(&msgh))) = fd;
+ */
+
+ /* Do the actual send */
+
+ sfd = unixConnect(SOCK_PATH, useDatagramSocket ? SOCK_DGRAM : SOCK_STREAM);
+ if (sfd == -1)
+ errExit("unixConnect");
+
+ ns = sendmsg(sfd, &msgh, 0);
+ if (ns == -1)
+ errExit("sendmsg");
+
+ fprintf(stderr, "sendmsg() returned %ld\n", (long) ns);
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 61-3 */
+
+/* sendfile.c
+
+ Implement sendfile() in terms of read(), write(), and lseek().
+*/
+#include "tlpi_hdr.h"
+#define BUF_SIZE 8192
+
+ssize_t
+sendfile(int out_fd, int in_fd, off_t *offset, size_t count)
+{
+ off_t orig;
+ char buf[BUF_SIZE];
+ size_t toRead, numRead, numSent, totSent;
+
+ if (offset != NULL) {
+
+ /* Save current file offset and set offset to value in '*offset' */
+
+ orig = lseek(in_fd, 0, SEEK_CUR);
+ if (orig == -1)
+ return -1;
+ if (lseek(in_fd, *offset, SEEK_SET) == -1)
+ return -1;
+ }
+
+ totSent = 0;
+
+ while (count > 0) {
+ toRead = min(BUF_SIZE, count);
+
+ numRead = read(in_fd, buf, toRead);
+ if (numRead == -1)
+ return -1;
+ if (numRead == 0)
+ break; /* EOF */
+
+ numSent = write(out_fd, buf, numRead);
+ if (numSent == -1)
+ return -1;
+ if (numSent == 0) /* Should never happen */
+ fatal("sendfile: write() transferred 0 bytes");
+
+ count -= numSent;
+ totSent += numSent;
+ }
+
+ if (offset != NULL) {
+
+ /* Return updated file offset in '*offset', and reset the file offset
+ to the value it had when we were called. */
+
+ *offset = lseek(in_fd, 0, SEEK_CUR);
+ if (*offset == -1)
+ return -1;
+ if (lseek(in_fd, orig, SEEK_SET) == -1)
+ return -1;
+ }
+
+ return totSent;
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 61-3 */
+
+/* socknames.c
+
+ Demonstrate the use of getsockname() and getpeername() to retrieve the local
+ and peer socket addresses.
+*/
+#include "inet_sockets.h" /* Declares our socket functions */
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int listenFd, acceptFd, connFd;
+ socklen_t len; /* Size of socket address buffer */
+ void *addr; /* Buffer for socket address */
+ char addrStr[IS_ADDR_STR_LEN];
+
+ if (argc != 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s service\n", argv[0]);
+
+ /* Create listening socket, obtain size of address structure */
+
+ listenFd = inetListen(argv[1], 5, &len);
+ if (listenFd == -1)
+ errExit("inetListen");
+
+ connFd = inetConnect(NULL, argv[1], SOCK_STREAM);
+ if (connFd == -1)
+ errExit("inetConnect");
+
+ acceptFd = accept(listenFd, NULL, NULL);
+ if (acceptFd == -1)
+ errExit("accept");
+
+ addr = malloc(len);
+ if (addr == NULL)
+ errExit("malloc");
+
+ if (getsockname(connFd, addr, &len) == -1)
+ errExit("getsockname");
+ printf("getsockname(connFd): %s\n",
+ inetAddressStr(addr, len, addrStr, IS_ADDR_STR_LEN));
+
+ if (getsockname(acceptFd, addr, &len) == -1)
+ errExit("getsockname");
+ printf("getsockname(acceptFd): %s\n",
+ inetAddressStr(addr, len, addrStr, IS_ADDR_STR_LEN));
+
+ if (getpeername(connFd, addr, &len) == -1)
+ errExit("getpeername");
+ printf("getpeername(connFd): %s\n",
+ inetAddressStr(addr, len, addrStr, IS_ADDR_STR_LEN));
+
+ if (getpeername(acceptFd, addr, &len) == -1)
+ errExit("getpeername");
+ printf("getpeername(acceptFd): %s\n",
+ inetAddressStr(addr, len, addrStr, IS_ADDR_STR_LEN));
+
+ sleep(30); /* Give us time to run netstat(8) */
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 59-10 */
+
+/* t_gethostbyname.c
+
+ Demonstrate the use of gethostbyname() to lookup of the address for a
+ given host name. Note that gethostbyname() is now obsolete; new programs
+ should use getaddrinfo().
+*/
+#if defined(_AIX) /* AIX 5.1 needs this to get hstrerror() declaration */
+#define _USE_IRS
+#endif
+#define _BSD_SOURCE /* To get hstrerror() declaration from <netdb.h> */
+#include <netdb.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ struct hostent *h;
+ char **pp;
+ char str[INET6_ADDRSTRLEN];
+
+ for (argv++; *argv != NULL; argv++) {
+ h = gethostbyname(*argv);
+ if (h == NULL) {
+ fprintf(stderr, "gethostbyname() failed for '%s': %s\n",
+ *argv, hstrerror(h_errno));
+ continue;
+ }
+
+ printf("Canonical name: %s\n", h->h_name);
+
+ printf(" alias(es): ");
+ for (pp = h->h_aliases; *pp != NULL; pp++)
+ printf(" %s", *pp);
+ printf("\n");
+
+ printf(" address type: %s\n",
+ (h->h_addrtype == AF_INET) ? "AF_INET" :
+ (h->h_addrtype == AF_INET6) ? "AF_INET6" : "???");
+
+ if (h->h_addrtype == AF_INET || h->h_addrtype == AF_INET6) {
+ printf(" address(es): ");
+ for (pp = h->h_addr_list; *pp != NULL; pp++)
+ printf(" %s", inet_ntop(h->h_addrtype, *pp,
+ str, INET6_ADDRSTRLEN));
+ printf("\n");
+ }
+ }
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 59 */
+
+/* t_getservbyname.c
+
+ Demonstrate the use of getservbyname() to look up the port number
+ corresponding to a given service name. Note that getservbyname() is
+ now obsolete; new programs should use getaddrinfo().
+*/
+#include <netdb.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ struct servent *s;
+ char **pp;
+
+ if (argc < 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s service [protocol]\n", argv[0]);
+
+ s = getservbyname(argv[1], argv[2]);
+ if (s == NULL)
+ fatal("getservbyname() could not find a matching record");
+
+ printf("Official name: %s\n", s->s_name);
+ printf("Aliases: ");
+ for (pp = s->s_aliases; *pp != NULL; pp++)
+ printf(" %s", *pp);
+ printf("\n");
+ printf("Port: %u\n", ntohs(s->s_port));
+ printf("Protocol: %s\n", s->s_proto);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 57-5 */
+
+/* ud_ucase.h
+
+ Header file for ud_ucase_sv.c and ud_ucase_cl.c.
+
+ These programs employ sockets in /tmp. This makes it easy to compile
+ and run the programs. However, for a security reasons, a real-world
+ application should never create sensitive files in /tmp. (As a simple of
+ example of the kind of security problems that can result, a malicious
+ user could create a file using the name defined in SV_SOCK_PATH, and
+ thereby cause a denial of service attack against this application.
+ See Section 38.7 of "The Linux Programming Interface" for more details
+ on this subject.)
+*/
+#include <sys/un.h>
+#include <sys/socket.h>
+#include <ctype.h>
+#include "tlpi_hdr.h"
+
+#define BUF_SIZE 10 /* Maximum size of messages exchanged
+ between client and server */
+
+#define SV_SOCK_PATH "/tmp/ud_ucase"
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 57-7 */
+
+/* ud_ucase_cl.c
+
+ A UNIX domain client that communicates with the server in ud_ucase_sv.c.
+ This client sends each command-line argument as a datagram to the server,
+ and then displays the contents of the server's response datagram.
+*/
+#include "ud_ucase.h"
+
+int
+main(int argc, char *argv[])
+{
+ struct sockaddr_un svaddr, claddr;
+ int sfd, j;
+ size_t msgLen;
+ ssize_t numBytes;
+ char resp[BUF_SIZE];
+
+ if (argc < 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s msg...\n", argv[0]);
+
+ /* Create client socket; bind to unique pathname (based on PID) */
+
+ sfd = socket(AF_UNIX, SOCK_DGRAM, 0);
+ if (sfd == -1)
+ errExit("socket");
+
+ memset(&claddr, 0, sizeof(struct sockaddr_un));
+ claddr.sun_family = AF_UNIX;
+ snprintf(claddr.sun_path, sizeof(claddr.sun_path),
+ "/tmp/ud_ucase_cl.%ld", (long) getpid());
+
+ if (bind(sfd, (struct sockaddr *) &claddr, sizeof(struct sockaddr_un)) == -1)
+ errExit("bind");
+
+ /* Construct address of server */
+
+ memset(&svaddr, 0, sizeof(struct sockaddr_un));
+ svaddr.sun_family = AF_UNIX;
+ strncpy(svaddr.sun_path, SV_SOCK_PATH, sizeof(svaddr.sun_path) - 1);
+
+ /* Send messages to server; echo responses on stdout */
+
+ for (j = 1; j < argc; j++) {
+ msgLen = strlen(argv[j]); /* May be longer than BUF_SIZE */
+ if (sendto(sfd, argv[j], msgLen, 0, (struct sockaddr *) &svaddr,
+ sizeof(struct sockaddr_un)) != msgLen)
+ fatal("sendto");
+
+ numBytes = recvfrom(sfd, resp, BUF_SIZE, 0, NULL, NULL);
+ /* Or equivalently: numBytes = recv(sfd, resp, BUF_SIZE, 0);
+ or: numBytes = read(sfd, resp, BUF_SIZE); */
+ if (numBytes == -1)
+ errExit("recvfrom");
+ printf("Response %d: %.*s\n", j, (int) numBytes, resp);
+ }
+
+ remove(claddr.sun_path); /* Remove client socket pathname */
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 57-6 */
+
+/* ud_ucase_sv.c
+
+ A server that uses a UNIX domain datagram socket to receive datagrams,
+ convert their contents to uppercase, and then return them to the senders.
+
+ See also ud_ucase_cl.c.
+*/
+#include "ud_ucase.h"
+
+int
+main(int argc, char *argv[])
+{
+ struct sockaddr_un svaddr, claddr;
+ int sfd, j;
+ ssize_t numBytes;
+ socklen_t len;
+ char buf[BUF_SIZE];
+
+ sfd = socket(AF_UNIX, SOCK_DGRAM, 0); /* Create server socket */
+ if (sfd == -1)
+ errExit("socket");
+
+ /* Construct well-known address and bind server socket to it */
+
+ /* For an explanation of the following check, see the erratum note for
+ page 1168 at http://www.man7.org/tlpi/errata/. */
+
+ if (strlen(SV_SOCK_PATH) > sizeof(svaddr.sun_path) - 1)
+ fatal("Server socket path too long: %s", SV_SOCK_PATH);
+
+ if (remove(SV_SOCK_PATH) == -1 && errno != ENOENT)
+ errExit("remove-%s", SV_SOCK_PATH);
+
+ memset(&svaddr, 0, sizeof(struct sockaddr_un));
+ svaddr.sun_family = AF_UNIX;
+ strncpy(svaddr.sun_path, SV_SOCK_PATH, sizeof(svaddr.sun_path) - 1);
+
+ if (bind(sfd, (struct sockaddr *) &svaddr, sizeof(struct sockaddr_un)) == -1)
+ errExit("bind");
+
+ /* Receive messages, convert to uppercase, and return to client */
+
+ for (;;) {
+ len = sizeof(struct sockaddr_un);
+ numBytes = recvfrom(sfd, buf, BUF_SIZE, 0,
+ (struct sockaddr *) &claddr, &len);
+ if (numBytes == -1)
+ errExit("recvfrom");
+
+ printf("Server received %ld bytes from %s\n", (long) numBytes,
+ claddr.sun_path);
+
+ for (j = 0; j < numBytes; j++)
+ buf[j] = toupper((unsigned char) buf[j]);
+
+ if (sendto(sfd, buf, numBytes, 0, (struct sockaddr *) &claddr, len) !=
+ numBytes)
+ fatal("sendto");
+ }
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 59-3:b */
+
+/* unix_sockets.c
+
+ A package of useful routines for UNIX domain sockets.
+*/
+#include "unix_sockets.h" /* Declares functions defined here */
+#include "tlpi_hdr.h"
+
+/* Build a UNIX domain socket address structure for 'path', returning
+ it in 'addr'. Returns -1 on success, or 0 on error. */
+
+int
+unixBuildAddress(const char *path, struct sockaddr_un *addr)
+{
+ if (addr == NULL || path == NULL ||
+ strlen(path) >= sizeof(addr->sun_path) - 1) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ memset(addr, 0, sizeof(struct sockaddr_un));
+ addr->sun_family = AF_UNIX;
+ if (strlen(path) < sizeof(addr->sun_path)) {
+ strncpy(addr->sun_path, path, sizeof(addr->sun_path) - 1);
+ return 0;
+ } else {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+}
+
+/* Create a UNIX domain socket of type 'type' and connect it
+ to the remote address specified by the 'path'.
+ Return the socket descriptor on success, or -1 on error */
+
+int
+unixConnect(const char *path, int type)
+{
+ int sd, savedErrno;
+ struct sockaddr_un addr;
+
+ if (unixBuildAddress(path, &addr) == -1)
+ return -1;
+
+ sd = socket(AF_UNIX, type, 0);
+ if (sd == -1)
+ return -1;
+
+ if (connect(sd, (struct sockaddr *) &addr,
+ sizeof(struct sockaddr_un)) == -1) {
+ savedErrno = errno;
+ close(sd); /* Might change 'errno' */
+ errno = savedErrno;
+ return -1;
+ }
+
+ return sd;
+}
+
+/* Create a UNIX domain socket and bind it to 'path'. If 'doListen'
+ is true, then call listen() with specified 'backlog'.
+ Return the socket descriptor on success, or -1 on error. */
+
+static int /* Public interfaces: unixBind() and unixListen() */
+unixPassiveSocket(const char *path, int type, Boolean doListen, int backlog)
+{
+ int sd, savedErrno;
+ struct sockaddr_un addr;
+
+ if (unixBuildAddress(path, &addr) == -1)
+ return -1;
+
+ sd = socket(AF_UNIX, type, 0);
+ if (sd == -1)
+ return -1;
+
+ if (bind(sd, (struct sockaddr *) &addr, sizeof(struct sockaddr_un)) == -1) {
+ savedErrno = errno;
+ close(sd); /* Might change 'errno' */
+ errno = savedErrno;
+ return -1;
+ }
+
+ if (doListen) {
+ if (listen(sd, backlog) == -1) {
+ savedErrno = errno;
+ close(sd); /* Might change 'errno' */
+ errno = savedErrno;
+ return -1;
+ }
+ }
+
+ return sd;
+}
+
+/* Create stream socket, bound to 'path'. Make the socket a listening
+ socket, with the specified 'backlog'. Return socket descriptor on
+ success, or -1 on error. */
+
+int
+unixListen(const char *path, int backlog)
+{
+ return unixPassiveSocket(path, SOCK_STREAM, TRUE, backlog);
+}
+
+/* Create socket of type 'type' bound to 'path'.
+ Return socket descriptor on success, or -1 on error. */
+
+int
+unixBind(const char *path, int type)
+{
+ return unixPassiveSocket(path, type, FALSE, 0);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 59-3:a */
+
+/* unix_sockets.h
+
+ Header file for unix_sockets.c.
+*/
+#ifndef UNIX_SOCKETS_H
+#define UNIX_SOCKETS_H /* Prevent accidental double inclusion */
+
+#include <sys/socket.h>
+#include <sys/un.h>
+
+int unixBuildAddress(const char *path, struct sockaddr_un *addr);
+
+int unixConnect(const char *path, int type);
+
+int unixListen(const char *path, int backlog);
+
+int unixBind(const char *path, int type);
+
+#endif
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 57-8 */
+
+/* us_abstract_bind.c
+
+ Demonstrate how to bind a UNIX domain socket to a name in the
+ Linux-specific abstract namespace.
+
+ This program is Linux-specific.
+
+ The first printing of the book used slightly different code. The code was
+ correct, but could have been better (to understand why, see the errata
+ for page 1176 of the book). The old code is shown in comments below.
+*/
+#include <sys/un.h>
+#include <sys/socket.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int sockfd;
+ struct sockaddr_un addr;
+ char *str;
+
+ memset(&addr, 0, sizeof(struct sockaddr_un)); /* Clear address structure */
+ addr.sun_family = AF_UNIX; /* UNIX domain address */
+
+ /* addr.sun_path[0] has already been set to 0 by memset() */
+
+ str = "xyz"; /* Abstract name is "\0xyz" */
+ strncpy(&addr.sun_path[1], str, strlen(str));
+
+ // In early printings of the book, the above two lines were instead:
+ //
+ // strncpy(&addr.sun_path[1], "xyz", sizeof(addr.sun_path) - 2);
+ // /* Abstract name is "xyz" followed by null bytes */
+
+ sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (sockfd == -1)
+ errExit("socket");
+
+ if (bind(sockfd, (struct sockaddr *) &addr,
+ sizeof(sa_family_t) + strlen(str) + 1) == -1)
+ errExit("bind");
+
+ // In early printings of the book, the final part of the bind() call
+ // above was instead:
+ // sizeof(struct sockaddr_un)) == -1)
+
+ sleep(60);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 57-2 */
+
+/* us_xfr.h
+
+ Header file for us_xfr_sv.c and us_xfr_cl.c.
+
+ These programs employ a socket in /tmp. This makes it easy to compile
+ and run the programs. However, for a security reasons, a real-world
+ application should never create sensitive files in /tmp. (As a simple of
+ example of the kind of security problems that can result, a malicious
+ user could create a file using the name defined in SV_SOCK_PATH, and
+ thereby cause a denial of service attack against this application.
+ See Section 38.7 of "The Linux Programming Interface" for more details
+ on this subject.)
+*/
+#include <sys/un.h>
+#include <sys/socket.h>
+#include "tlpi_hdr.h"
+
+#define SV_SOCK_PATH "/tmp/us_xfr"
+
+#define BUF_SIZE 100
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 57-4 */
+
+/* us_xfr_cl.c
+
+ An example UNIX domain stream socket client. This client transmits contents
+ of stdin to a server socket.
+
+ See also us_xfr_sv.c.
+*/
+#include "us_xfr.h"
+
+int
+main(int argc, char *argv[])
+{
+ struct sockaddr_un addr;
+ int sfd;
+ ssize_t numRead;
+ char buf[BUF_SIZE];
+
+ sfd = socket(AF_UNIX, SOCK_STREAM, 0); /* Create client socket */
+ if (sfd == -1)
+ errExit("socket");
+
+ /* Construct server address, and make the connection */
+
+ memset(&addr, 0, sizeof(struct sockaddr_un));
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, SV_SOCK_PATH, sizeof(addr.sun_path) - 1);
+
+ if (connect(sfd, (struct sockaddr *) &addr,
+ sizeof(struct sockaddr_un)) == -1)
+ errExit("connect");
+
+ /* Copy stdin to socket */
+
+ while ((numRead = read(STDIN_FILENO, buf, BUF_SIZE)) > 0)
+ if (write(sfd, buf, numRead) != numRead)
+ fatal("partial/failed write");
+
+ if (numRead == -1)
+ errExit("read");
+
+ exit(EXIT_SUCCESS); /* Closes our socket; server sees EOF */
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 57-3 */
+
+/* us_xfr_sv.c
+
+ An example UNIX stream socket server. Accepts incoming connections
+ and copies data sent from clients to stdout.
+
+ See also us_xfr_cl.c.
+*/
+#include "us_xfr.h"
+#define BACKLOG 5
+
+int
+main(int argc, char *argv[])
+{
+ struct sockaddr_un addr;
+ int sfd, cfd;
+ ssize_t numRead;
+ char buf[BUF_SIZE];
+
+ sfd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (sfd == -1)
+ errExit("socket");
+
+ /* Construct server socket address, bind socket to it,
+ and make this a listening socket */
+
+ /* For an explanation of the following check, see the errata notes for
+ pages 1168 and 1172 at http://www.man7.org/tlpi/errata/. */
+
+ if (strlen(SV_SOCK_PATH) > sizeof(addr.sun_path) - 1)
+ fatal("Server socket path too long: %s", SV_SOCK_PATH);
+
+ if (remove(SV_SOCK_PATH) == -1 && errno != ENOENT)
+ errExit("remove-%s", SV_SOCK_PATH);
+
+ memset(&addr, 0, sizeof(struct sockaddr_un));
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, SV_SOCK_PATH, sizeof(addr.sun_path) - 1);
+
+ if (bind(sfd, (struct sockaddr *) &addr, sizeof(struct sockaddr_un)) == -1)
+ errExit("bind");
+
+ if (listen(sfd, BACKLOG) == -1)
+ errExit("listen");
+
+ for (;;) { /* Handle client connections iteratively */
+
+ /* Accept a connection. The connection is returned on a new
+ socket, 'cfd'; the listening socket ('sfd') remains open
+ and can be used to accept further connections. */
+
+ cfd = accept(sfd, NULL, NULL);
+ if (cfd == -1)
+ errExit("accept");
+
+ /* Transfer data from connected socket to stdout until EOF */
+
+ while ((numRead = read(cfd, buf, BUF_SIZE)) > 0)
+ if (write(STDOUT_FILENO, buf, numRead) != numRead)
+ fatal("partial/failed write");
+
+ if (numRead == -1)
+ errExit("read");
+
+ if (close(cfd) == -1)
+ errMsg("close");
+ }
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 59-3:c */
+
+/* us_xfr_v2.h
+
+ Header file for us_xfr_sv.c and us_xfr_cl.c.
+*/
+#include "unix_sockets.h" /* Declares our socket functions */
+#include "tlpi_hdr.h"
+
+#define SV_SOCK_PATH "us_xfr_v2"
+
+#define BUF_SIZE 100
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 59-3:e */
+
+/* us_xfr_v2_cl.c
+
+ An example UNIX domain stream socket client. This client transmits contents
+ of stdin to a server socket. This program is similar to us_xfr_cl.c, except
+ that it uses the functions in unix_sockets.c to simplify working with UNIX
+ domain sockets.
+
+ See also us_xfr_v2_sv.c.
+*/
+#include "us_xfr_v2.h"
+
+int
+main(int argc, char *argv[])
+{
+ int sfd;
+ ssize_t numRead;
+ char buf[BUF_SIZE];
+
+ sfd = unixConnect(SV_SOCK_PATH, SOCK_STREAM);
+ if (sfd == -1)
+ errExit("unixConnect");
+
+ /* Copy stdin to socket */
+
+ while ((numRead = read(STDIN_FILENO, buf, BUF_SIZE)) > 0)
+ if (write(sfd, buf, numRead) != numRead)
+ fatal("partial/failed write");
+
+ if (numRead == -1)
+ errExit("read");
+ exit(EXIT_SUCCESS); /* Closes our socket; server sees EOF */
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 59-3:d */
+
+/* us_xfr_v2_sv.c
+
+ An example UNIX stream socket server. Accepts incoming connections and
+ copies data sent from clients to stdout. This program is similar to
+ us_xfr_sv.c, except that it uses the functions in unix_sockets.c to
+ simplify working with UNIX domain sockets.
+
+ See also us_xfr_v2_cl.c.
+*/
+#include "us_xfr_v2.h"
+
+int
+main(int argc, char *argv[])
+{
+ int sfd, cfd;
+ ssize_t numRead;
+ char buf[BUF_SIZE];
+
+ sfd = unixListen(SV_SOCK_PATH, 5);
+ if (sfd == -1)
+ errExit("unixListen");
+
+ for (;;) { /* Handle client connections iteratively */
+ cfd = accept(sfd, NULL, NULL);
+ if (cfd == -1)
+ errExit("accept");
+
+ /* Transfer data from connected socket to stdout until EOF */
+
+ while ((numRead = read(cfd, buf, BUF_SIZE)) > 0)
+ if (write(STDOUT_FILENO, buf, numRead) != numRead)
+ fatal("partial/failed write");
+
+ if (numRead == -1)
+ errExit("read");
+ if (close(cfd) == -1)
+ errMsg("close");
+ }
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE = svmsg_demo_server t_ftok
+
+LINUX_EXE =
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+clean :
+ ${RM} ${EXE} *.o
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 45-1 */
+
+/* svmsg_demo_server.c
+
+ Demonstration System V message queue-based server.
+*/
+#include <sys/types.h>
+#include <sys/ipc.h>
+#include <sys/msg.h>
+#include <sys/stat.h>
+#include "tlpi_hdr.h"
+
+#define KEY_FILE "/some-path/some-file"
+ /* Should be an existing file or one
+ that this program creates */
+
+int
+main(int argc, char *argv[])
+{
+ int msqid;
+ key_t key;
+ const int MQ_PERMS = S_IRUSR | S_IWUSR | S_IWGRP; /* rw--w---- */
+
+ /* Optional code here to check if another server process is
+ already running */
+
+ /* Generate the key for the message queue */
+
+ key = ftok(KEY_FILE, 1);
+ if (key == -1)
+ errExit("ftok");
+
+ /* While msgget() fails, try creating the queue exclusively */
+
+ while ((msqid = msgget(key, IPC_CREAT | IPC_EXCL | MQ_PERMS)) ==
+ -1) {
+ if (errno == EEXIST) { /* MQ with the same key already
+ exists - remove it and try again */
+ msqid = msgget(key, 0);
+ if (msqid == -1)
+ errExit("msgget() failed to retrieve old queue ID");
+ if (msgctl(msqid, IPC_RMID, NULL) == -1)
+ errExit("msgget() failed to delete old queue");
+ printf("Removed old message queue (id=%d)\n", msqid);
+
+ } else { /* Some other error --> give up */
+ errExit("msgget() failed");
+ }
+ }
+
+ /* Upon loop exit, we've successfully created the message queue,
+ and we can then carry on to do other work... */
+
+ printf("Queue created with id %d\n", msqid);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 45-2 */
+
+/* t_ftok.c
+
+ Test the key values returned by ftok(3).
+
+ Usage: t_ftok key-file key-char
+
+ Simply calls ftok(), using the values supplied in the command-line arguments,
+ and displays the resulting key.
+*/
+#include <sys/ipc.h>
+#include <sys/stat.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ key_t key;
+ struct stat sb;
+
+ if (argc != 3 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s file-name keychar\n", argv[0]);
+
+ printf("Size of key_t = %ld bytes\n", (long) sizeof(key_t));
+
+ if (stat(argv[1], &sb) == -1)
+ errExit("stat");
+
+ key = ftok(argv[1], argv[2][0]);
+ if (key == -1)
+ errExit("ftok");
+
+ printf("Key = %lx i-node = %lx st_dev = %lx proj = %x\n",
+ (unsigned long) key, (unsigned long) sb.st_ino,
+ (unsigned long) sb.st_dev, (unsigned int) argv[2][0]);
+ if (key == -1)
+ printf("File does not exist\n");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE = svmsg_chqbytes svmsg_file_client svmsg_file_server \
+ svmsg_create svmsg_receive svmsg_rm svmsg_send
+
+LINUX_EXE = svmsg_info svmsg_ls
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+clean :
+ ${RM} ${EXE} *.o
+
+svmsg_file_client.o svmsg_file_server.o : svmsg_file.h
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 46-5 */
+
+/* svmsg_chqbytes.c
+
+ Usage: svmsg_chqbytes msqid max-bytes
+
+ Change the 'msg_qbytes' setting of the System V message queue identified
+ by 'msqid'.
+*/
+#include <sys/types.h>
+#include <sys/msg.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ struct msqid_ds ds;
+ int msqid;
+
+ if (argc != 3 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s msqid max-bytes\n", argv[0]);
+
+ /* Retrieve copy of associated data structure from kernel */
+
+ msqid = getInt(argv[1], 0, "msqid");
+ if (msgctl(msqid, IPC_STAT, &ds) == -1)
+ errExit("msgctl");
+
+ ds.msg_qbytes = getInt(argv[2], 0, "max-bytes");
+
+ /* Update associated data structure in kernel */
+
+ if (msgctl(msqid, IPC_SET, &ds) == -1)
+ errExit("msgctl");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 46-1 */
+
+/* svmsg_create.c
+
+ Experiment with the use of msgget() to create a System V message queue.
+*/
+#include <sys/types.h>
+#include <sys/ipc.h>
+#include <sys/msg.h>
+#include <sys/stat.h>
+#include "tlpi_hdr.h"
+
+static void /* Print usage info, then exit */
+usageError(const char *progName, const char *msg)
+{
+ if (msg != NULL)
+ fprintf(stderr, "%s", msg);
+ fprintf(stderr, "Usage: %s [-cx] {-f pathname | -k key | -p} "
+ "[octal-perms]\n", progName);
+ fprintf(stderr, " -c Use IPC_CREAT flag\n");
+ fprintf(stderr, " -x Use IPC_EXCL flag\n");
+ fprintf(stderr, " -f pathname Generate key using ftok()\n");
+ fprintf(stderr, " -k key Use 'key' as key\n");
+ fprintf(stderr, " -p Use IPC_PRIVATE key\n");
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int numKeyFlags; /* Counts -f, -k, and -p options */
+ int flags, msqid, opt;
+ unsigned int perms;
+ long lkey;
+ key_t key;
+
+ /* Parse command-line options and arguments */
+
+ numKeyFlags = 0;
+ flags = 0;
+
+ while ((opt = getopt(argc, argv, "cf:k:px")) != -1) {
+ switch (opt) {
+ case 'c':
+ flags |= IPC_CREAT;
+ break;
+
+ case 'f': /* -f pathname */
+ key = ftok(optarg, 1);
+ if (key == -1)
+ errExit("ftok");
+ numKeyFlags++;
+ break;
+
+ case 'k': /* -k key (octal, decimal or hexadecimal) */
+ if (sscanf(optarg, "%li", &lkey) != 1)
+ cmdLineErr("-k option requires a numeric argument\n");
+ key = lkey;
+ numKeyFlags++;
+ break;
+
+ case 'p':
+ key = IPC_PRIVATE;
+ numKeyFlags++;
+ break;
+
+ case 'x':
+ flags |= IPC_EXCL;
+ break;
+
+ default:
+ usageError(argv[0], "Bad option\n");
+ }
+ }
+
+ if (numKeyFlags != 1)
+ usageError(argv[0], "Exactly one of the options -f, -k, "
+ "or -p must be supplied\n");
+
+ perms = (optind == argc) ? (S_IRUSR | S_IWUSR) :
+ getInt(argv[optind], GN_BASE_8, "octal-perms");
+
+ msqid = msgget(key, flags | perms);
+ if (msqid == -1)
+ errExit("msgget");
+
+ printf("%d\n", msqid);
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 46-7 */
+
+/* svmsg_file.h
+
+ Header file for svmsg_file_server.c and svmsg_file_client.c.
+*/
+#include <sys/types.h>
+#include <sys/msg.h>
+#include <sys/stat.h>
+#include <stddef.h> /* For definition of offsetof() */
+#include <limits.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include "tlpi_hdr.h"
+
+#define SERVER_KEY 0x1aaaaaa1 /* Key for server's message queue */
+
+struct requestMsg { /* Requests (client to server) */
+ long mtype; /* Unused */
+ int clientId; /* ID of client's message queue */
+ char pathname[PATH_MAX]; /* File to be returned */
+};
+
+/* REQ_MSG_SIZE computes size of 'mtext' part of 'requestMsg' structure.
+ We use offsetof() to handle the possibility that there are padding
+ bytes between the 'clientId' and 'pathname' fields. */
+
+#define REQ_MSG_SIZE (offsetof(struct requestMsg, pathname) - \
+ offsetof(struct requestMsg, clientId) + PATH_MAX)
+
+#define RESP_MSG_SIZE 8192
+
+struct responseMsg { /* Responses (server to client) */
+ long mtype; /* One of RESP_MT_* values below */
+ char data[RESP_MSG_SIZE]; /* File content / response message */
+};
+
+/* Types for response messages sent from server to client */
+
+#define RESP_MT_FAILURE 1 /* File couldn't be opened */
+#define RESP_MT_DATA 2 /* Message contains file data */
+#define RESP_MT_END 3 /* File data complete */
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 46-9 */
+
+/* svmsg_file_client.c
+
+ Send a message to the server svmsg_file_server.c requesting the
+ contents of the file named on the command line, and then receive the
+ file contents via a series of messages sent back by the server. Display
+ the total number of bytes and messages received. The server and client
+ communicate using System V message queues.
+*/
+#include "svmsg_file.h"
+
+static int clientId;
+
+static void
+removeQueue(void)
+{
+ if (msgctl(clientId, IPC_RMID, NULL) == -1)
+ errExit("msgctl");
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct requestMsg req;
+ struct responseMsg resp;
+ int serverId, numMsgs;
+ ssize_t msgLen, totBytes;
+
+ if (argc != 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s pathname\n", argv[0]);
+
+ if (strlen(argv[1]) > sizeof(req.pathname) - 1)
+ cmdLineErr("pathname too long (max: %ld bytes)\n",
+ (long) sizeof(req.pathname) - 1);
+
+ /* Get server's queue identifier; create queue for response */
+
+ serverId = msgget(SERVER_KEY, S_IWUSR);
+ if (serverId == -1)
+ errExit("msgget - server message queue");
+
+ clientId = msgget(IPC_PRIVATE, S_IRUSR | S_IWUSR | S_IWGRP);
+ if (clientId == -1)
+ errExit("msgget - client message queue");
+
+ if (atexit(removeQueue) != 0)
+ errExit("atexit");
+
+ /* Send message asking for file named in argv[1] */
+
+ req.mtype = 1; /* Any type will do */
+ req.clientId = clientId;
+ strncpy(req.pathname, argv[1], sizeof(req.pathname) - 1);
+ req.pathname[sizeof(req.pathname) - 1] = '\0';
+ /* Ensure string is terminated */
+
+ if (msgsnd(serverId, &req, REQ_MSG_SIZE, 0) == -1)
+ errExit("msgsnd");
+
+ /* Get first response, which may be failure notification */
+
+ msgLen = msgrcv(clientId, &resp, RESP_MSG_SIZE, 0, 0);
+ if (msgLen == -1)
+ errExit("msgrcv");
+
+ if (resp.mtype == RESP_MT_FAILURE) {
+ printf("%s\n", resp.data); /* Display msg from server */
+ exit(EXIT_FAILURE);
+ }
+
+ /* File was opened successfully by server; process messages
+ (including the one already received) containing file data */
+
+ totBytes = msgLen; /* Count first message */
+ for (numMsgs = 1; resp.mtype == RESP_MT_DATA; numMsgs++) {
+ msgLen = msgrcv(clientId, &resp, RESP_MSG_SIZE, 0, 0);
+ if (msgLen == -1)
+ errExit("msgrcv");
+
+ totBytes += msgLen;
+ }
+
+ printf("Received %ld bytes (%d messages)\n", (long) totBytes, numMsgs);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 46-8 */
+
+/* svmsg_file_server.c
+
+ A file server that uses System V message queues to handle client requests
+ (see svmsg_file_client.c). The client sends an initial request containing
+ the name of the desired file, and the identifier of the message queue to be
+ used to send the file contents back to the child. The server attempts to
+ open the desired file. If the file cannot be opened, a failure response is
+ sent to the client, otherwise the contents of the requested file are sent
+ in a series of messages.
+
+ This application makes use of multiple message queues. The server maintains
+ a queue (with a well-known key) dedicated to incoming client requests. Each
+ client creates its own private queue, which is used to pass response
+ messages from the server back to the client.
+
+ This program operates as a concurrent server, forking a new child process to
+ handle each client request while the parent waits for further client requests.
+*/
+#include "svmsg_file.h"
+
+static void /* SIGCHLD handler */
+grimReaper(int sig)
+{
+ int savedErrno;
+
+ savedErrno = errno; /* waitpid() might change 'errno' */
+ while (waitpid(-1, NULL, WNOHANG) > 0)
+ continue;
+ errno = savedErrno;
+}
+
+static void /* Executed in child process: serve a single client */
+serveRequest(const struct requestMsg *req)
+{
+ int fd;
+ ssize_t numRead;
+ struct responseMsg resp;
+
+ fd = open(req->pathname, O_RDONLY);
+ if (fd == -1) { /* Open failed: send error text */
+ resp.mtype = RESP_MT_FAILURE;
+ snprintf(resp.data, sizeof(resp.data), "%s", "Couldn't open");
+ msgsnd(req->clientId, &resp, strlen(resp.data) + 1, 0);
+ exit(EXIT_FAILURE); /* and terminate */
+ }
+
+ /* Transmit file contents in messages with type RESP_MT_DATA. We don't
+ diagnose read() and msgsnd() errors since we can't notify client. */
+
+ resp.mtype = RESP_MT_DATA;
+ while ((numRead = read(fd, resp.data, RESP_MSG_SIZE)) > 0)
+ if (msgsnd(req->clientId, &resp, numRead, 0) == -1)
+ break;
+
+ /* Send a message of type RESP_MT_END to signify end-of-file */
+
+ resp.mtype = RESP_MT_END;
+ msgsnd(req->clientId, &resp, 0, 0); /* Zero-length mtext */
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct requestMsg req;
+ pid_t pid;
+ ssize_t msgLen;
+ int serverId;
+ struct sigaction sa;
+
+ /* Create server message queue */
+
+ serverId = msgget(SERVER_KEY, IPC_CREAT | IPC_EXCL |
+ S_IRUSR | S_IWUSR | S_IWGRP);
+ if (serverId == -1)
+ errExit("msgget");
+
+ /* Establish SIGCHLD handler to reap terminated children */
+
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ sa.sa_handler = grimReaper;
+ if (sigaction(SIGCHLD, &sa, NULL) == -1)
+ errExit("sigaction");
+
+ /* Read requests, handle each in a separate child process */
+
+ for (;;) {
+ msgLen = msgrcv(serverId, &req, REQ_MSG_SIZE, 0, 0);
+ if (msgLen == -1) {
+ if (errno == EINTR) /* Interrupted by SIGCHLD handler? */
+ continue; /* ... then restart msgrcv() */
+ errMsg("msgrcv"); /* Some other error */
+ break; /* ... so terminate loop */
+ }
+
+ pid = fork(); /* Create child process */
+ if (pid == -1) {
+ errMsg("fork");
+ break;
+ }
+
+ if (pid == 0) { /* Child handles request */
+ serveRequest(&req);
+ _exit(EXIT_SUCCESS);
+ }
+
+ /* Parent loops to receive next client request */
+ }
+
+ /* If msgrcv() or fork() fails, remove server MQ and exit */
+
+ if (msgctl(serverId, IPC_RMID, NULL) == -1)
+ errExit("msgctl");
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 46 */
+
+/* svmsg_info.c
+
+ Demonstrate the use of the MSG_INFO operation to retrieve a 'msginfo'
+ structure containing the current usage of System V message queue resources.
+
+ This program is Linux-specific.
+*/
+#define _GNU_SOURCE
+#include <sys/msg.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ struct msginfo info;
+ int s;
+
+ s = msgctl(0, MSG_INFO, (struct msqid_ds *) &info);
+ if (s == -1)
+ errExit("msgctl");
+
+ printf("Maximum ID index = %d\n", s);
+ printf("queues in_use = %ld\n", (long) info.msgpool);
+ printf("msg_hdrs = %ld\n", (long) info.msgmap);
+ printf("msg_bytes = %ld\n", (long) info.msgmax);
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 46-6 */
+
+/* svmsg_ls.c
+
+ Display a list of all System V message queues on the system.
+
+ This program is Linux-specific.
+*/
+#define _GNU_SOURCE
+#include <sys/types.h>
+#include <sys/msg.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int maxind, ind, msqid;
+ struct msqid_ds ds;
+ struct msginfo msginfo;
+
+ /* Obtain size of kernel 'entries' array */
+
+ maxind = msgctl(0, MSG_INFO, (struct msqid_ds *) &msginfo);
+ if (maxind == -1)
+ errExit("msgctl-MSG_INFO");
+
+ printf("maxind: %d\n\n", maxind);
+ printf("index id key messages\n");
+
+ /* Retrieve and display information from each element of 'entries' array */
+
+ for (ind = 0; ind <= maxind; ind++) {
+ msqid = msgctl(ind, MSG_STAT, &ds);
+ if (msqid == -1) {
+ if (errno != EINVAL && errno != EACCES)
+ errMsg("msgctl-MSG_STAT"); /* Unexpected error */
+ continue; /* Ignore this item */
+ }
+
+ printf("%4d %8d 0x%08lx %7ld\n", ind, msqid,
+ (unsigned long) ds.msg_perm.__key, (long) ds.msg_qnum);
+ }
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 46-3 */
+
+/* svmsg_receive.c
+
+ Usage: svmsg_receive [-nex] [-t msg-type] msqid [max-bytes]
+
+ Experiment with the msgrcv() system call to receive messages from a
+ System V message queue.
+
+ See also svmsg_send.c.
+*/
+#define _GNU_SOURCE /* Get definition of MSG_EXCEPT */
+#include <sys/types.h>
+#include <sys/msg.h>
+#include "tlpi_hdr.h"
+
+#define MAX_MTEXT 1024
+
+struct mbuf {
+ long mtype; /* Message type */
+ char mtext[MAX_MTEXT]; /* Message body */
+};
+
+static void
+usageError(const char *progName, const char *msg)
+{
+ if (msg != NULL)
+ fprintf(stderr, "%s", msg);
+ fprintf(stderr, "Usage: %s [options] msqid [max-bytes]\n", progName);
+ fprintf(stderr, "Permitted options are:\n");
+ fprintf(stderr, " -e Use MSG_NOERROR flag\n");
+ fprintf(stderr, " -t type Select message of given type\n");
+ fprintf(stderr, " -n Use IPC_NOWAIT flag\n");
+#ifdef MSG_EXCEPT
+ fprintf(stderr, " -x Use MSG_EXCEPT flag\n");
+#endif
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int msqid, flags, type;
+ ssize_t msgLen;
+ size_t maxBytes;
+ struct mbuf msg; /* Message buffer for msgrcv() */
+ int opt; /* Option character from getopt() */
+
+ /* Parse command-line options and arguments */
+
+ flags = 0;
+ type = 0;
+ while ((opt = getopt(argc, argv, "ent:x")) != -1) {
+ switch (opt) {
+ case 'e': flags |= MSG_NOERROR; break;
+ case 'n': flags |= IPC_NOWAIT; break;
+ case 't': type = atoi(optarg); break;
+#ifdef MSG_EXCEPT
+ case 'x': flags |= MSG_EXCEPT; break;
+#endif
+ default: usageError(argv[0], NULL);
+ }
+ }
+
+ if (argc < optind + 1 || argc > optind + 2)
+ usageError(argv[0], "Wrong number of arguments\n");
+
+ msqid = getInt(argv[optind], 0, "msqid");
+ maxBytes = (argc > optind + 1) ?
+ getInt(argv[optind + 1], 0, "max-bytes") : MAX_MTEXT;
+
+ /* Get message and display on stdout */
+
+ msgLen = msgrcv(msqid, &msg, maxBytes, type, flags);
+ if (msgLen == -1)
+ errExit("msgrcv");
+
+ printf("Received: type=%ld; length=%ld", msg.mtype, (long) msgLen);
+ if (msgLen > 0)
+ printf("; body=%s", msg.mtext);
+ printf("\n");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 46-4 */
+
+/* svmsg_rm.c
+
+ Remove the System V message queues identified by the command-line arguments.
+*/
+#include <sys/types.h>
+#include <sys/msg.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int j;
+
+ if (argc > 1 && strcmp(argv[1], "--help") == 0)
+ usageErr("%s [msqid...]\n", argv[0]);
+
+ for (j = 1; j < argc; j++)
+ if (msgctl(getInt(argv[j], 0, "msqid"), IPC_RMID, NULL) == -1)
+ errExit("msgctl %s", argv[j]);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 46-2 */
+
+/* svmsg_send.c
+
+ Usage: svmsg_send [-n] msqid msg-type [msg-text]
+
+ Experiment with the msgsnd() system call to send messages to a
+ System V message queue.
+
+ See also svmsg_receive.c.
+*/
+#include <sys/types.h>
+#include <sys/msg.h>
+#include "tlpi_hdr.h"
+
+#define MAX_MTEXT 1024
+
+struct mbuf {
+ long mtype; /* Message type */
+ char mtext[MAX_MTEXT]; /* Message body */
+};
+
+static void /* Print (optional) message, then usage description */
+usageError(const char *progName, const char *msg)
+{
+ if (msg != NULL)
+ fprintf(stderr, "%s", msg);
+ fprintf(stderr, "Usage: %s [-n] msqid msg-type [msg-text]\n", progName);
+ fprintf(stderr, " -n Use IPC_NOWAIT flag\n");
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int msqid, flags, msgLen;
+ struct mbuf msg; /* Message buffer for msgsnd() */
+ int opt; /* Option character from getopt() */
+
+ /* Parse command-line options and arguments */
+
+ flags = 0;
+ while ((opt = getopt(argc, argv, "n")) != -1) {
+ if (opt == 'n')
+ flags |= IPC_NOWAIT;
+ else
+ usageError(argv[0], NULL);
+ }
+
+ if (argc < optind + 2 || argc > optind + 3)
+ usageError(argv[0], "Wrong number of arguments\n");
+
+ msqid = getInt(argv[optind], 0, "msqid");
+ msg.mtype = getInt(argv[optind + 1], 0, "msg-type");
+
+ if (argc > optind + 2) { /* 'msg-text' was supplied */
+ msgLen = strlen(argv[optind + 2]) + 1;
+ if (msgLen > MAX_MTEXT)
+ cmdLineErr("msg-text too long (max: %d characters)\n", MAX_MTEXT);
+
+ memcpy(msg.mtext, argv[optind + 2], msgLen);
+
+ } else { /* No 'msg-text' ==> zero-length msg */
+ msgLen = 0;
+ }
+
+ /* Send message */
+
+ if (msgsnd(msqid, &msg, msgLen, flags) == -1)
+ errExit("msgsnd");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE = svsem_create svsem_demo svsem_mon svsem_op svsem_rm svsem_setall
+
+LINUX_EXE = svsem_info
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+clean :
+ ${RM} ${EXE} *.o
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 47-10 */
+
+/* binary_sems.c
+
+ Implement a binary semaphore protocol using System V semaphores.
+*/
+#include <sys/types.h>
+#include <sys/sem.h>
+#include "semun.h" /* Definition of semun union */
+#include "binary_sems.h"
+
+Boolean bsUseSemUndo = FALSE;
+Boolean bsRetryOnEintr = TRUE;
+
+int /* Initialize semaphore to 1 (i.e., "available") */
+initSemAvailable(int semId, int semNum)
+{
+ union semun arg;
+
+ arg.val = 1;
+ return semctl(semId, semNum, SETVAL, arg);
+}
+
+int /* Initialize semaphore to 0 (i.e., "in use") */
+initSemInUse(int semId, int semNum)
+{
+ union semun arg;
+
+ arg.val = 0;
+ return semctl(semId, semNum, SETVAL, arg);
+}
+
+/* Reserve semaphore (blocking), return 0 on success, or -1 with 'errno'
+ set to EINTR if operation was interrupted by a signal handler */
+
+int /* Reserve semaphore - decrement it by 1 */
+reserveSem(int semId, int semNum)
+{
+ struct sembuf sops;
+
+ sops.sem_num = semNum;
+ sops.sem_op = -1;
+ sops.sem_flg = bsUseSemUndo ? SEM_UNDO : 0;
+
+ while (semop(semId, &sops, 1) == -1)
+ if (errno != EINTR || !bsRetryOnEintr)
+ return -1;
+
+ return 0;
+}
+
+int /* Release semaphore - increment it by 1 */
+releaseSem(int semId, int semNum)
+{
+ struct sembuf sops;
+
+ sops.sem_num = semNum;
+ sops.sem_op = 1;
+ sops.sem_flg = bsUseSemUndo ? SEM_UNDO : 0;
+
+ return semop(semId, &sops, 1);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 47-9 */
+
+/* binary_sems.h
+
+ Header file for binary_sems.c.
+*/
+#ifndef BINARY_SEMS_H /* Prevent accidental double inclusion */
+#define BINARY_SEMS_H
+
+#include "tlpi_hdr.h"
+
+/* Variables controlling operation of functions below */
+
+extern Boolean bsUseSemUndo; /* Use SEM_UNDO during semop()? */
+extern Boolean bsRetryOnEintr; /* Retry if semop() interrupted by
+ signal handler? */
+
+int initSemAvailable(int semId, int semNum);
+
+int initSemInUse(int semId, int semNum);
+
+int reserveSem(int semId, int semNum);
+
+int releaseSem(int semId, int semNum);
+
+#endif
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 47-5:a */
+
+/* event_flags.c
+
+ Implement an event flags protocol using System V semaphores.
+
+ See event_flags.h for a summary of the interface.
+*/
+#include <sys/types.h>
+#include <sys/sem.h>
+#include "semun.h" /* Definition of semun union */
+#include "event_flags.h"
+#include "tlpi_hdr.h"
+
+/* Wait for the specified flag to become "set" (0) */
+
+int
+waitForEventFlag(int semId, int semNum)
+{
+ struct sembuf sops;
+
+ sops.sem_num = semNum;
+ sops.sem_op = 0; /* Wait for semaphore to equal 0 */
+ sops.sem_flg = 0;
+
+ /* Waiting for a semaphore to become zero may block, so we
+ program to retry if interrupted by a signal handler */
+
+ while (semop(semId, &sops, 1) == -1)
+ if (errno != EINTR)
+ return -1;
+ return 0;
+}
+
+/* "Clear" the event flag (give it the value 1) */
+
+int
+clearEventFlag(int semId, int semNum)
+{
+ union semun arg;
+
+ arg.val = 1;
+ return semctl(semId, semNum, SETVAL, arg);
+}
+
+/* "Set" the event flag (give it the value 0) */
+
+int
+setEventFlag(int semId, int semNum)
+{
+ union semun arg;
+
+ arg.val = 0;
+ return semctl(semId, semNum, SETVAL, arg);
+}
+
+/* Get current state of event flag */
+
+int
+getFlagState(int semId, int semNum, Boolean *isSet)
+{
+ int sem_val;
+ union semun dummy;
+
+ sem_val = semctl(semId, semNum, GETVAL, dummy);
+ if (sem_val == -1)
+ return -1;
+
+ *isSet = (sem_val == 0) ? TRUE : FALSE;
+ return 0;
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 47-5:b */
+
+/* event_flags.h
+
+ Header file for event_flags.c.
+
+ The event flags operations are:
+
+ set a flag: setEventFlag(semId, semNum)
+ clear a flag: clearEventFlag(semId, semNum)
+ wait for flag to be set: waitForEventFlag(semId, semNum)
+ read a flag's value: getFlagState(semId, semNum, &isSet)
+
+ NB: The semantics of System V semaphores require that the "set"
+ value for a flag is 0 and the "clear" value is 1.
+*/
+#ifndef EVENT_FLAGS_H
+#define EVENT_FLAGS_H /* Prevent accidental double inclusion */
+
+#include "tlpi_hdr.h"
+
+int waitForEventFlag(int semId, int semNum);
+
+int clearEventFlag(int semId, int semNum);
+
+int setEventFlag(int semId, int semNum);
+
+int getFlagState(int semId, int semNum, Boolean *isSet);
+
+#endif
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 47-2 */
+
+/* semun.h
+
+ Definition of the semun union used by the System V semaphore semop()
+ system call.
+*/
+#ifndef SEMUN_H
+#define SEMUN_H /* Prevent accidental double inclusion */
+
+#include <sys/types.h> /* For portability */
+#include <sys/sem.h>
+
+#if ! defined(__FreeBSD__) && ! defined(__OpenBSD__) && \
+ ! defined(__sgi) && ! defined(__APPLE__)
+ /* Some implementations already declare this union */
+
+union semun { /* Used in calls to semctl() */
+ int val;
+ struct semid_ds * buf;
+ unsigned short * array;
+#if defined(__linux__)
+ struct seminfo * __buf;
+#endif
+};
+
+#endif
+
+#endif
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 47-5 */
+
+/* svsem_bad_init.c
+
+ A demonstration of the wrong way to initialize a system V semaphore.
+
+ Compare this program with svsem_good_init.c.
+*/
+#include <sys/types.h>
+#include <sys/sem.h>
+#include <sys/stat.h>
+#include "semun.h" /* Definition of semun union */
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int semid, key, perms;
+ struct sembuf sops[2];
+
+ key = 12345;
+ perms = S_IRUSR | S_IWUSR;
+ semid = semget(key, 1, IPC_CREAT | IPC_EXCL | perms);
+
+ if (semid != -1) { /* Successfully created the semaphore */
+ union semun arg;
+
+ /* XXXX */
+
+ arg.val = 0; /* So initialize it */
+ if (semctl(semid, 0, SETVAL, arg) == -1)
+ errExit("semctl");
+
+ } else { /* We didn't create semaphore set */
+ if (errno != EEXIST) { /* Unexpected error from semget() */
+ errExit("semget 1");
+ } else { /* Someone else already created it */
+ semid = semget(key, 1, perms); /* So just get ID */
+ if (semid == -1)
+ errExit("semget 2");
+ }
+ }
+
+ /* Now perform some operation on the semaphore */
+
+ sops[0].sem_op = 1; /* Add 1 */
+ sops[0].sem_num = 0; /* ... to semaphore 0 */
+ sops[0].sem_flg = 0;
+ if (semop(semid, sops, 1) == -1)
+ errExit("semop");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 47 */
+
+/* svsem_create.c
+
+ Experiment with the use of semget() to create a System V semaphore set.
+
+ Usage as shown in usageError().
+*/
+#include <sys/types.h>
+#include <sys/ipc.h>
+#include <sys/sem.h>
+#include <sys/stat.h>
+#include "tlpi_hdr.h"
+
+static void
+usageError(const char *progName, const char *msg)
+{
+ if (msg != NULL)
+ fprintf(stderr, "%s", msg);
+ fprintf(stderr, "Usage: %s [-cx] {-f pathname | -k key | -p} "
+ "num-sems [octal-perms]\n", progName);
+ fprintf(stderr, " -c Use IPC_CREAT flag\n");
+ fprintf(stderr, " -x Use IPC_EXCL flag\n");
+ fprintf(stderr, " -f pathname Generate key using ftok()\n");
+ fprintf(stderr, " -k key Use 'key' as key\n");
+ fprintf(stderr, " -p Use IPC_PRIVATE key\n");
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int numKeyFlags; /* Counts -f, -k, and -p options */
+ int flags, semid, numSems, opt;
+ unsigned int perms;
+ long lkey;
+ key_t key;
+
+ /* Parse command-line options and arguments */
+
+ numKeyFlags = 0;
+ flags = 0;
+
+ while ((opt = getopt(argc, argv, "cf:k:px")) != -1) {
+ switch (opt) {
+ case 'c':
+ flags |= IPC_CREAT;
+ break;
+
+ case 'f': /* -f pathname */
+ key = ftok(optarg, 1);
+ if (key == -1)
+ errExit("ftok");
+ numKeyFlags++;
+ break;
+
+ case 'k': /* -k key (octal, decimal or hexadecimal) */
+ if (sscanf(optarg, "%li", &lkey) != 1)
+ cmdLineErr("-k option requires a numeric argument\n");
+ key = lkey;
+ numKeyFlags++;
+ break;
+
+ case 'p':
+ key = IPC_PRIVATE;
+ numKeyFlags++;
+ break;
+
+ case 'x':
+ flags |= IPC_EXCL;
+ break;
+
+ default:
+ usageError(argv[0], NULL);
+ }
+ }
+
+ if (numKeyFlags != 1)
+ usageError(argv[0], "Exactly one of the options -f, -k, "
+ "or -p must be supplied\n");
+
+ if (optind >= argc)
+ usageError(argv[0], "Must specify number of semaphores\n");
+
+ numSems = getInt(argv[optind], 0, "num-sems");
+
+ perms = (argc <= optind + 1) ? (S_IRUSR | S_IWUSR) :
+ getInt(argv[optind + 1], GN_BASE_8, "octal-perms");
+
+ semid = semget(key, numSems, flags | perms);
+ if (semid == -1)
+ errExit("semget");
+
+ printf("%d\n", semid); /* On success, display semaphore set id */
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 47-1 */
+
+/* svsem_demo.c
+
+ A simple demonstration of System V semaphores.
+*/
+#include <sys/types.h>
+#include <sys/sem.h>
+#include <sys/stat.h>
+#include "curr_time.h" /* Declaration of currTime() */
+#include "semun.h" /* Definition of semun union */
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int semid;
+
+ if (argc < 2 || argc > 3 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s init-value\n"
+ " or: %s semid operation\n", argv[0], argv[0]);
+
+ if (argc == 2) { /* Create and initialize semaphore */
+ union semun arg;
+
+ semid = semget(IPC_PRIVATE, 1, S_IRUSR | S_IWUSR);
+ if (semid == -1)
+ errExit("semid");
+
+ arg.val = getInt(argv[1], 0, "init-value");
+ if (semctl(semid, /* semnum= */ 0, SETVAL, arg) == -1)
+ errExit("semctl");
+
+ printf("Semaphore ID = %d\n", semid);
+
+ } else { /* Perform an operation on first semaphore */
+
+ struct sembuf sop; /* Structure defining operation */
+
+ semid = getInt(argv[1], 0, "semid");
+
+ sop.sem_num = 0; /* Specifies first semaphore in set */
+ sop.sem_op = getInt(argv[2], 0, "operation");
+ /* Add, subtract, or wait for 0 */
+ sop.sem_flg = 0; /* No special options for operation */
+
+ printf("%ld: about to semop at %s\n", (long) getpid(), currTime("%T"));
+ if (semop(semid, &sop, 1) == -1)
+ errExit("semop");
+
+ printf("%ld: semop completed at %s\n", (long) getpid(), currTime("%T"));
+ }
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 47-6 */
+
+/* svsem_good_init.c
+
+ Show how to initialize System V semaphores in a manner that avoids
+ race conditions. (Compare with svsem_bad_init.c.)
+*/
+#include <sys/types.h>
+#include <sys/sem.h>
+#include <sys/stat.h>
+#include "semun.h" /* Definition of semun union */
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int semid, key, perms;
+ struct sembuf sops[2];
+
+ if (argc != 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s sem-op\n", argv[0]);
+
+ key = 12345;
+ perms = S_IRUSR | S_IWUSR;
+
+ semid = semget(key, 1, IPC_CREAT | IPC_EXCL | perms);
+
+ if (semid != -1) { /* Successfully created the semaphore */
+ union semun arg;
+ struct sembuf sop;
+
+ sleep(5);
+ printf("%ld: created semaphore\n", (long) getpid());
+
+ arg.val = 0; /* So initialize it to 0 */
+ if (semctl(semid, 0, SETVAL, arg) == -1)
+ errExit("semctl 1");
+ printf("%ld: initialized semaphore\n", (long) getpid());
+
+ /* Perform a "no-op" semaphore operation - changes sem_otime
+ so other processes can see we've initialized the set. */
+
+ sop.sem_num = 0; /* Operate on semaphore 0 */
+ sop.sem_op = 0; /* Wait for value to equal 0 */
+ sop.sem_flg = 0;
+ if (semop(semid, &sop, 1) == -1)
+ errExit("semop");
+ printf("%ld: completed dummy semop()\n", (long) getpid());
+
+ } else { /* We didn't create the semaphore set */
+
+ if (errno != EEXIST) { /* Unexpected error from semget() */
+ errExit("semget 1");
+
+ } else { /* Someone else already created it */
+ const int MAX_TRIES = 10;
+ int j;
+ union semun arg;
+ struct semid_ds ds;
+
+ semid = semget(key, 1, perms); /* So just get ID */
+ if (semid == -1)
+ errExit("semget 2");
+
+ printf("%ld: got semaphore key\n", (long) getpid());
+ /* Wait until another process has called semop() */
+
+ arg.buf = &ds;
+ for (j = 0; j < MAX_TRIES; j++) {
+ printf("Try %d\n", j);
+ if (semctl(semid, 0, IPC_STAT, arg) == -1)
+ errExit("semctl 2");
+
+ if (ds.sem_otime != 0) /* Semop() performed? */
+ break; /* Yes, quit loop */
+ sleep(1); /* If not, wait and retry */
+ }
+
+ if (ds.sem_otime == 0) /* Loop ran to completion! */
+ fatal("Existing semaphore not initialized");
+ }
+ }
+
+ /* Now perform some operation on the semaphore */
+
+ sops[0].sem_num = 0; /* Operate on semaphore 0... */
+ sops[0].sem_op = getInt(argv[1], 0, "sem-op");
+ sops[0].sem_flg = 0;
+ if (semop(semid, sops, 1) == -1)
+ errExit("semop");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 47 */
+
+/* svsem_info.c
+
+ Demonstrate the use of the SEM_INFO operation to retrieve a 'seminfo'
+ structure containing the current usage of System V semaphore resources.
+
+ This program is Linux-specific.
+*/
+#define _GNU_SOURCE
+#include <sys/sem.h>
+#include "semun.h" /* Definition of semun union */
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ struct seminfo info;
+ union semun arg;
+ int s;
+
+ arg.__buf = &info;
+
+ s = semctl(0, 0, SEM_INFO, arg);
+ if (s == -1)
+ errExit("semctl");
+
+ printf("Maximum ID index = %d\n", s);
+ printf("sets in_use = %ld\n", (long) info.semusz);
+ printf("used_sems = %ld\n", (long) info.semaem);
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 47-3 */
+
+/* svsem_mon.c
+
+ Display various information about the semaphores in a System V semaphore set.
+
+ Since the information obtained by this program is not obtained atomically,
+ it may not be consistent if another process makes changes to the semaphore
+ at the moment this program is running.
+*/
+#include <sys/types.h>
+#include <sys/sem.h>
+#include <time.h>
+#include "semun.h" /* Definition of semun union */
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ struct semid_ds ds;
+ union semun arg, dummy; /* Fourth argument for semctl() */
+ int semid, j;
+
+ if (argc != 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s semid\n", argv[0]);
+
+ semid = getInt(argv[1], 0, "semid");
+
+ arg.buf = &ds;
+ if (semctl(semid, 0, IPC_STAT, arg) == -1)
+ errExit("semctl");
+
+ printf("Semaphore changed: %s", ctime(&ds.sem_ctime));
+ printf("Last semop(): %s", ctime(&ds.sem_otime));
+
+ /* Display per-semaphore information */
+
+ arg.array = calloc(ds.sem_nsems, sizeof(arg.array[0]));
+ if (arg.array == NULL)
+ errExit("calloc");
+ if (semctl(semid, 0, GETALL, arg) == -1)
+ errExit("semctl-GETALL");
+
+ printf("Sem # Value SEMPID SEMNCNT SEMZCNT\n");
+
+ for (j = 0; j < ds.sem_nsems; j++)
+ printf("%3d %5d %5d %5d %5d\n", j, arg.array[j],
+ semctl(semid, j, GETPID, dummy),
+ semctl(semid, j, GETNCNT, dummy),
+ semctl(semid, j, GETZCNT, dummy));
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 47-8 */
+
+/* svsem_op.c
+
+ Perform groups of operations on a System V semaphore set.
+
+ Usage as shown in usageError().
+*/
+#include <sys/types.h>
+#include <sys/sem.h>
+#include <ctype.h>
+#include "curr_time.h" /* Declaration of currTime() */
+#include "tlpi_hdr.h"
+
+#define MAX_SEMOPS 1000 /* Maximum operations that we permit for
+ a single semop() */
+
+static void
+usageError(const char *progName)
+{
+ fprintf(stderr, "Usage: %s semid op[,op...] ...\n\n", progName);
+ fprintf(stderr, "'op' is either: <sem#>{+|-}<value>[n][u]\n");
+ fprintf(stderr, " or: <sem#>=0[n]\n");
+ fprintf(stderr, " \"n\" means include IPC_NOWAIT in 'op'\n");
+ fprintf(stderr, " \"u\" means include SEM_UNDO in 'op'\n\n");
+ fprintf(stderr, "The operations in each argument are "
+ "performed in a single semop() call\n\n");
+ fprintf(stderr, "e.g.: %s 12345 0+1,1-2un\n", progName);
+ fprintf(stderr, " %s 12345 0=0n 1+1,2-1u 1=0\n", progName);
+ exit(EXIT_FAILURE);
+}
+
+/* Parse comma-delimited operations in 'arg', returning them in the
+ array 'sops'. Return number of operations as function result. */
+
+static int
+parseOps(char *arg, struct sembuf sops[])
+{
+ char *comma, *sign, *remaining, *flags;
+ int numOps; /* Number of operations in 'arg' */
+
+ for (numOps = 0, remaining = arg; ; numOps++) {
+ if (numOps >= MAX_SEMOPS)
+ cmdLineErr("Too many operations (maximum=%d): \"%s\"\n",
+ MAX_SEMOPS, arg);
+
+ if (*remaining == '\0')
+ fatal("Trailing comma or empty argument: \"%s\"", arg);
+ if (!isdigit((unsigned char) *remaining))
+ cmdLineErr("Expected initial digit: \"%s\"\n", arg);
+
+ sops[numOps].sem_num = strtol(remaining, &sign, 10);
+
+ if (*sign == '\0' || strchr("+-=", *sign) == NULL)
+ cmdLineErr("Expected '+', '-', or '=' in \"%s\"\n", arg);
+ if (!isdigit((unsigned char) *(sign + 1)))
+ cmdLineErr("Expected digit after '%c' in \"%s\"\n", *sign, arg);
+
+ sops[numOps].sem_op = strtol(sign + 1, &flags, 10);
+
+ if (*sign == '-') /* Reverse sign of operation */
+ sops[numOps].sem_op = - sops[numOps].sem_op;
+ else if (*sign == '=') /* Should be '=0' */
+ if (sops[numOps].sem_op != 0)
+ cmdLineErr("Expected \"=0\" in \"%s\"\n", arg);
+
+ sops[numOps].sem_flg = 0;
+ for (;; flags++) {
+ if (*flags == 'n')
+ sops[numOps].sem_flg |= IPC_NOWAIT;
+ else if (*flags == 'u')
+ sops[numOps].sem_flg |= SEM_UNDO;
+ else
+ break;
+ }
+
+ if (*flags != ',' && *flags != '\0')
+ cmdLineErr("Bad trailing character (%c) in \"%s\"\n", *flags, arg);
+
+ comma = strchr(remaining, ',');
+ if (comma == NULL)
+ break; /* No comma --> no more ops */
+ else
+ remaining = comma + 1;
+ }
+
+ return numOps + 1;
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct sembuf sops[MAX_SEMOPS];
+ int ind, nsops;
+
+ if (argc < 2 || strcmp(argv[1], "--help") == 0)
+ usageError(argv[0]);
+
+ for (ind = 2; argv[ind] != NULL; ind++) {
+ nsops = parseOps(argv[ind], sops);
+
+ printf("%5ld, %s: about to semop() [%s]\n", (long) getpid(),
+ currTime("%T"), argv[ind]);
+
+ if (semop(getInt(argv[1], 0, "semid"), sops, nsops) == -1)
+ errExit("semop (PID=%ld)", (long) getpid());
+
+ printf("%5ld, %s: semop() completed [%s]\n", (long) getpid(),
+ currTime("%T"), argv[ind]);
+ }
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 47 */
+
+/* svsem_rm.c
+
+ Remove the System V semaphore sets whose IDs are specified by the
+ command-line arguments.
+*/
+#include <sys/types.h>
+#include <sys/sem.h>
+#include "semun.h" /* Definition of semun union */
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int j;
+ union semun dummy;
+
+ if (argc > 1 && strcmp(argv[1], "--help") == 0)
+ usageErr("%s [semid...]\n", argv[0]);
+
+ for (j = 1; j < argc; j++)
+ if (semctl(getInt(argv[j], 0, "semid"), 0, IPC_RMID, dummy) == -1)
+ errExit("semctl %s", argv[j]);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 47-4 */
+
+/* svsem_setall.c
+
+ Usage: svsem_setall semid value...
+
+ Set the values of all of the members of a System V semaphore set according
+ to the values supplied on the command line.
+*/
+#include <sys/types.h>
+#include <sys/sem.h>
+#include "semun.h" /* Definition of semun union */
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ struct semid_ds ds;
+ union semun arg; /* Fourth argument for semctl() */
+ int j, semid;
+
+ if (argc < 3 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s semid val...\n", argv[0]);
+
+ semid = getInt(argv[1], 0, "semid");
+
+ /* Obtain size of semaphore set */
+
+ arg.buf = &ds;
+ if (semctl(semid, 0, IPC_STAT, arg) == -1)
+ errExit("semctl");
+
+ /* The number of values supplied on the command line must match the
+ number of semaphores in the set */
+
+ if (ds.sem_nsems != argc - 2)
+ cmdLineErr("Set contains %ld semaphores, but %d values were supplied\n",
+ (long) ds.sem_nsems, argc - 2);
+
+ /* Set up array of values; perform semaphore initialization */
+
+ arg.array = calloc(ds.sem_nsems, sizeof(arg.array[0]));
+ if (arg.array == NULL)
+ errExit("calloc");
+
+ for (j = 2; j < argc; j++)
+ arg.array[j - 2] = getInt(argv[j], 0, "val");
+
+ if (semctl(semid, 0, SETALL, arg) == -1)
+ errExit("semctl-SETALL");
+ printf("Semaphore values changed (PID=%ld)\n", (long) getpid());
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE = svshm_attach svshm_create svshm_mon svshm_rm \
+ svshm_xfr_reader svshm_xfr_writer
+
+LINUX_EXE = svshm_info svshm_lock svshm_unlock
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+clean :
+ ${RM} ${EXE} *.o
+
+svshm_xfr_reader.o svshm_xfr_writer.o: svshm_xfr.h
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 48 */
+
+/* svshm_attach.c
+
+ Experiment with the use of shmat() to attach previously created
+ System V shared memory segments.
+
+ Usage: svshm_attach [shmid:addr[rR]]...
+
+ r = attach with SHM_RND flag
+ R = attach with SHM_RDONLY flag
+*/
+#include <sys/types.h>
+#include <sys/shm.h>
+#include "tlpi_hdr.h"
+
+static void
+usageError(char *progName)
+{
+ fprintf(stderr, "Usage: %s [shmid:address[rR]]...\n", progName);
+ fprintf(stderr, " r=SHM_RND; R=SHM_RDONLY\n");
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ void *addr;
+ char *retAddr, *p;
+ int j, flags, shmid;
+
+ printf("SHMLBA = %ld (%#lx), PID = %ld\n",
+ (long) SHMLBA, (unsigned long) SHMLBA, (long) getpid());
+
+ for (j = 1; j < argc; j++) {
+ shmid = strtol(argv[j], &p, 0);
+ if (*p != ':')
+ usageError(argv[0]);
+
+ addr = (void *) strtol(p + 1, NULL, 0);
+ flags = (strchr(p + 1, 'r') != NULL) ? SHM_RND : 0;
+ if (strchr(p + 1, 'R') != NULL)
+ flags |= SHM_RDONLY;
+
+ retAddr = shmat(shmid, addr, flags);
+ if (retAddr == (void *) -1)
+ errExit("shmat: %s", argv[j]);
+
+ printf("%d: %s ==> %p\n", j, argv[j], retAddr);
+ }
+
+ printf("Sleeping 5 seconds\n");
+ sleep(5);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 48 */
+
+/* svshm_create.c
+
+ Experiment with the use of shmget() to create a System V shared memory
+ segment.
+
+ Usage as shown in usageError().
+*/
+#include <sys/types.h>
+#include <sys/ipc.h>
+#include <sys/shm.h>
+#include <sys/stat.h>
+#include "tlpi_hdr.h"
+
+static void
+usageError(const char *progName, const char *msg)
+{
+ if (msg != NULL)
+ fprintf(stderr, "%s", msg);
+ fprintf(stderr, "Usage: %s [-cx] {-f pathname | -k key | -p} "
+ "seg-size [octal-perms]\n", progName);
+ fprintf(stderr, " -c Use IPC_CREAT flag\n");
+ fprintf(stderr, " -x Use IPC_EXCL flag\n");
+ fprintf(stderr, " -f pathname Generate key using ftok()\n");
+ fprintf(stderr, " -k key Use 'key' as key\n");
+ fprintf(stderr, " -p Use IPC_PRIVATE key\n");
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int numKeyFlags; /* Counts -f, -k, and -p options */
+ int flags, shmid, segSize;
+ unsigned int perms;
+ long lkey;
+ key_t key;
+ int opt; /* Option character from getopt() */
+
+ /* Parse command-line options and arguments */
+
+ numKeyFlags = 0;
+ flags = 0;
+
+ while ((opt = getopt(argc, argv, "cf:k:px")) != -1) {
+ switch (opt) {
+ case 'c':
+ flags |= IPC_CREAT;
+ break;
+
+ case 'f': /* -f pathname */
+ key = ftok(optarg, 1);
+ if (key == -1)
+ errExit("ftok");
+ numKeyFlags++;
+ break;
+
+ case 'k': /* -k key (octal, decimal or hexadecimal) */
+ if (sscanf(optarg, "%li", &lkey) != 1)
+ cmdLineErr("-k option requires a numeric argument\n");
+ key = lkey;
+ numKeyFlags++;
+ break;
+
+ case 'p':
+ key = IPC_PRIVATE;
+ numKeyFlags++;
+ break;
+
+ case 'x':
+ flags |= IPC_EXCL;
+ break;
+
+ default:
+ usageError(argv[0], NULL);
+ }
+ }
+
+ if (numKeyFlags != 1)
+ usageError(argv[0], "Exactly one of the options -f, -k, "
+ "or -p must be supplied\n");
+
+ if (optind >= argc)
+ usageError(argv[0], "Size of segment must be specified\n");
+
+ segSize = getLong(argv[optind], GN_ANY_BASE, "seg-size");
+
+ perms = (argc <= optind + 1) ? (S_IRUSR | S_IWUSR) :
+ getInt(argv[optind + 1], GN_BASE_8, "octal-perms");
+
+ shmid = shmget(key, segSize, flags | perms);
+ if (shmid == -1)
+ errExit("shmget");
+
+ printf("%d\n", shmid); /* On success, display shared mem. id */
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 48 */
+
+/* svshm_info.c
+
+ Demonstrate the use of the SHM_INFO operation to retrieve a 'shminfo'
+ structure containing the current usage of System V shared memory resources.
+
+ This program is Linux-specific.
+*/
+#define _GNU_SOURCE
+#include <sys/shm.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ struct shm_info info;
+ int s;
+
+ s = shmctl(0, SHM_INFO, (struct shmid_ds *) &info);
+ if (s == -1)
+ errExit("shmctl");
+
+ printf("Maximum ID index = %d\n", s);
+ printf("shm_tot = %ld\n", (long) info.shm_tot);
+ printf("shm_rss = %ld\n", (long) info.shm_rss);
+ printf("shm_swp = %ld\n", (long) info.shm_swp);
+ printf("swap_attempts = %ld\n", (long) info.swap_attempts);
+ printf("swap_successes = %ld\n", (long) info.swap_successes);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 48 */
+
+/* svshm_lock.c
+
+ Lock the System V shared memory segments identified by the
+ command-line arguments.
+
+ See also svshm_unlock.c.
+*/
+#include <sys/types.h>
+#include <sys/shm.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int j;
+
+ for (j = 1; j < argc; j++)
+ if (shmctl(getInt(argv[j], 0, "shmid"), SHM_LOCK, NULL) == -1)
+ errExit("shmctl");
+
+ sleep(5);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 48-4 */
+
+/* svshm_mon.c
+
+ Display information from the associated data structure for the
+ System V shared memory segment identified on the command line.
+*/
+#include <sys/types.h>
+#include <sys/shm.h>
+#include <time.h>
+#include "tlpi_hdr.h"
+
+static void
+printShmDS(const struct shmid_ds *ds)
+{
+ printf("Size: %ld\n", (long) ds->shm_segsz);
+ printf("# of attached processes: %ld\n", (long) ds->shm_nattch);
+
+ printf("Mode: %lo",
+ (unsigned long) ds->shm_perm.mode);
+#ifdef SHM_DEST
+ printf("%s", (ds->shm_perm.mode & SHM_DEST) ? " [DEST]" : "");
+#endif
+#ifdef SHM_LOCKED
+ printf("%s", (ds->shm_perm.mode & SHM_LOCKED) ? " [LOCKED]" : "");
+#endif
+ printf("\n");
+
+ printf("Last shmat(): %s", ctime(&ds->shm_atime));
+ printf("Last shmdt(): %s", ctime(&ds->shm_dtime));
+ printf("Last change: %s", ctime(&ds->shm_ctime));
+
+ printf("Creator PID: %ld\n", (long) ds->shm_cpid);
+ printf("PID of last attach/detach: %ld\n", (long) ds->shm_lpid);
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct shmid_ds ds;
+
+ if (argc != 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s shmid\n", argv[0]);
+
+ if (shmctl(getInt(argv[1], 0, "shmid"), IPC_STAT, &ds) == -1)
+ errExit("shmctl");
+
+ printShmDS(&ds);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 48 */
+
+/* svshm_rm.c
+
+ Remove the System V shared memory segments identified by the
+ command-line arguments
+*/
+#include <sys/types.h>
+#include <sys/shm.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int j;
+
+ if (argc > 1 && strcmp(argv[1], "--help") == 0)
+ usageErr("%s [shmid...]\n", argv[0]);
+
+ for (j = 1; j < argc; j++)
+ if (shmctl(getInt(argv[j], 0, "shmid"), IPC_RMID, NULL) == -1)
+ errExit("shmctl %s", argv[j]);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 48 */
+
+/* svshm_unlock.c
+
+ Unlock the System V shared memory segments identified by the
+ command-line arguments.
+
+ See also svshm_lock.c.
+*/
+#include <sys/types.h>
+#include <sys/shm.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int j;
+
+ for (j = 1; j < argc; j++)
+ if (shmctl(getInt(argv[j], 0, "shmid"), SHM_UNLOCK, NULL) == -1)
+ errExit("shmctl");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 48-1 */
+
+/* svshm_xfr.h
+
+ Header file used by the svshm_xfr_reader.c and svshm_xfr_writer.c programs.
+*/
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/sem.h>
+#include <sys/shm.h>
+#include "binary_sems.h" /* Declares our binary semaphore functions */
+#include "tlpi_hdr.h"
+
+/* Hard-coded keys for IPC objects */
+
+#define SHM_KEY 0x1234 /* Key for shared memory segment */
+#define SEM_KEY 0x5678 /* Key for semaphore set */
+
+#define OBJ_PERMS (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)
+ /* Permissions for our IPC objects */
+
+/* Two semaphores are used to ensure exclusive, alternating access
+ to the shared memory segment */
+
+#define WRITE_SEM 0 /* Writer has access to shared memory */
+#define READ_SEM 1 /* Reader has access to shared memory */
+
+#ifndef BUF_SIZE /* Allow "cc -D" to override definition */
+#define BUF_SIZE 1024 /* Size of transfer buffer */
+#endif
+
+struct shmseg { /* Defines structure of shared memory segment */
+ int cnt; /* Number of bytes used in 'buf' */
+ char buf[BUF_SIZE]; /* Data being transferred */
+};
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 48-3 */
+
+/* svshm_xfr_reader.c
+
+ Read data from a System V shared memory using a binary semaphore lock-step
+ protocol; see svshm_xfr_writer.c
+*/
+#include "svshm_xfr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int semid, shmid, xfrs, bytes;
+ struct shmseg *shmp;
+
+ /* Get IDs for semaphore set and shared memory created by writer */
+
+ semid = semget(SEM_KEY, 0, 0);
+ if (semid == -1)
+ errExit("semget");
+
+ shmid = shmget(SHM_KEY, 0, 0);
+ if (shmid == -1)
+ errExit("shmget");
+
+ /* Attach shared memory read-only, as we will only read */
+
+ shmp = shmat(shmid, NULL, SHM_RDONLY);
+ if (shmp == (void *) -1)
+ errExit("shmat");
+
+ /* Transfer blocks of data from shared memory to stdout */
+
+ for (xfrs = 0, bytes = 0; ; xfrs++) {
+ if (reserveSem(semid, READ_SEM) == -1) /* Wait for our turn */
+ errExit("reserveSem");
+
+ if (shmp->cnt == 0) /* Writer encountered EOF */
+ break;
+ bytes += shmp->cnt;
+
+ if (write(STDOUT_FILENO, shmp->buf, shmp->cnt) != shmp->cnt)
+ fatal("partial/failed write");
+
+ if (releaseSem(semid, WRITE_SEM) == -1) /* Give writer a turn */
+ errExit("releaseSem");
+ }
+
+ if (shmdt(shmp) == -1)
+ errExit("shmdt");
+
+ /* Give writer one more turn, so it can clean up */
+
+ if (releaseSem(semid, WRITE_SEM) == -1)
+ errExit("releaseSem");
+
+ fprintf(stderr, "Received %d bytes (%d xfrs)\n", bytes, xfrs);
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 48-2 */
+
+/* svshm_xfr_writer.c
+
+ Read buffers of data data from standard input into a System V shared memory
+ segment from which it is copied by svshm_xfr_reader.c
+
+ We use a pair of binary semaphores to ensure that the writer and reader have
+ exclusive, alternating access to the shared memory. (I.e., the writer writes
+ a block of text, then the reader reads, then the writer writes etc). This
+ ensures that each block of data is processed in turn by the writer and
+ reader.
+
+ This program needs to be started before the reader process as it creates the
+ shared memory and semaphores used by both processes.
+
+ Together, these two programs can be used to transfer a stream of data through
+ shared memory as follows:
+
+ $ svshm_xfr_writer < infile &
+ $ svshm_xfr_reader > out_file
+*/
+#include "semun.h" /* Definition of semun union */
+#include "svshm_xfr.h"
+
+int
+main(int argc, char *argv[])
+{
+ int semid, shmid, bytes, xfrs;
+ struct shmseg *shmp;
+ union semun dummy;
+
+ /* Create set containing two semaphores; initialize so that
+ writer has first access to shared memory. */
+
+ semid = semget(SEM_KEY, 2, IPC_CREAT | OBJ_PERMS);
+ if (semid == -1)
+ errExit("semget");
+
+ if (initSemAvailable(semid, WRITE_SEM) == -1)
+ errExit("initSemAvailable");
+ if (initSemInUse(semid, READ_SEM) == -1)
+ errExit("initSemInUse");
+
+ /* Create shared memory; attach at address chosen by system */
+
+ shmid = shmget(SHM_KEY, sizeof(struct shmseg), IPC_CREAT | OBJ_PERMS);
+ if (shmid == -1)
+ errExit("shmget");
+
+ shmp = shmat(shmid, NULL, 0);
+ if (shmp == (void *) -1)
+ errExit("shmat");
+
+ /* Transfer blocks of data from stdin to shared memory */
+
+ for (xfrs = 0, bytes = 0; ; xfrs++, bytes += shmp->cnt) {
+ if (reserveSem(semid, WRITE_SEM) == -1) /* Wait for our turn */
+ errExit("reserveSem");
+
+ shmp->cnt = read(STDIN_FILENO, shmp->buf, BUF_SIZE);
+ if (shmp->cnt == -1)
+ errExit("read");
+
+ if (releaseSem(semid, READ_SEM) == -1) /* Give reader a turn */
+ errExit("releaseSem");
+
+ /* Have we reached EOF? We test this after giving the reader
+ a turn so that it can see the 0 value in shmp->cnt. */
+
+ if (shmp->cnt == 0)
+ break;
+ }
+
+ /* Wait until reader has let us have one more turn. We then know
+ reader has finished, and so we can delete the IPC objects. */
+
+ if (reserveSem(semid, WRITE_SEM) == -1)
+ errExit("reserveSem");
+
+ if (semctl(semid, 0, IPC_RMID, dummy) == -1)
+ errExit("semctl");
+ if (shmdt(shmp) == -1)
+ errExit("shmdt");
+ if (shmctl(shmid, IPC_RMID, 0) == -1)
+ errExit("shmctl");
+
+ fprintf(stderr, "Sent %d bytes (%d xfrs)\n", bytes, xfrs);
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE = t_uname
+
+LINUX_EXE = procfs_pidmax procfs_user_exe
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+clean :
+ ${RM} ${EXE} *.o
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 12-1 */
+
+/* procfs_pidmax.c
+
+ This program demonstrates how to access a file in the /proc file system. It
+ can be used to read and modify the /proc/sys/kernel/pid_max file (which is
+ available only in Linux 2.6 and later).
+
+ Usage: procfs_pidmax [new-pidmax]
+
+ Displays the current maximum PID, and, if a command line
+ argument is supplied, sets the maximum PID to that value.
+
+ Note: privilege is required to change the maximum PID value.
+
+ This program is Linux-specific.
+*/
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+#define MAX_LINE 100
+
+int
+main(int argc, char *argv[])
+{
+ int fd;
+ char line[MAX_LINE];
+ ssize_t n;
+
+ fd = open("/proc/sys/kernel/pid_max", (argc > 1) ? O_RDWR : O_RDONLY);
+ if (fd == -1)
+ errExit("open");
+
+ n = read(fd, line, MAX_LINE);
+ if (n == -1)
+ errExit("read");
+
+ if (argc > 1)
+ printf("Old value: ");
+ printf("%.*s", (int) n, line);
+
+ if (argc > 1) {
+ if (lseek(fd, 0, SEEK_SET) == -1)
+ errExit("lseek");
+
+ if (write(fd, argv[1], strlen(argv[1])) != strlen(argv[1]))
+ fatal("write() failed");
+
+ system("echo /proc/sys/kernel/pid_max now contains "
+ "`cat /proc/sys/kernel/pid_max`");
+ }
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 12-1 */
+
+/* procfs_user_exe.c
+
+ Demonstrate the use of /proc/PID files to obtain information about
+ processes on a Linux system. In this case, given a username, this
+ program scans all /proc/PID/status files to produce a list of all
+ processes running under a particular real user ID; for each process,
+ the process ID and command that it executes are shown.
+
+ This program is Linux-specific.
+*/
+#define _GNU_SOURCE
+#include <limits.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <ctype.h>
+#include "ugid_functions.h"
+#include "tlpi_hdr.h"
+
+#define MAX_LINE 1000
+
+int
+main(int argc, char *argv[])
+{
+ DIR *dirp;
+ struct dirent *dp;
+ char path[PATH_MAX];
+ char line[MAX_LINE], cmd[MAX_LINE];
+ FILE *fp;
+ char *p;
+ uid_t uid, checkedUid;
+ Boolean gotName, gotUid;
+
+ if (argc < 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s username\n", argv[0]);
+
+ checkedUid = userIdFromName(argv[1]);
+ if (checkedUid == -1)
+ cmdLineErr("Bad username: %s\n", argv[1]);
+
+ dirp = opendir("/proc");
+ if (dirp == NULL)
+ errExit("opendir");
+
+ /* Scan entries under /proc directory */
+
+ for (;;) {
+ errno = 0; /* To distinguish error from end-of-directory */
+ dp = readdir(dirp);
+ if (dp == NULL) {
+ if (errno != 0)
+ errExit("readdir");
+ else
+ break;
+ }
+
+ /* Since we are looking for /proc/PID directories, skip entries
+ that are not directories, or don't begin with a digit. */
+
+ if (dp->d_type != DT_DIR || !isdigit((unsigned char) dp->d_name[0]))
+ continue;
+
+ snprintf(path, PATH_MAX, "/proc/%s/status", dp->d_name);
+
+ fp = fopen(path, "r");
+ if (fp == NULL)
+ continue; /* Ignore errors: fopen() might fail if
+ process has just terminated */
+
+ gotName = FALSE;
+ gotUid = FALSE;
+ while (!gotName || !gotUid) {
+ if (fgets(line, MAX_LINE, fp) == NULL)
+ break;
+
+ /* The "Name:" line contains the name of the command that
+ this process is running */
+
+ if (strncmp(line, "Name:", 5) == 0) {
+ for (p = line + 5; *p != '\0' && isspace((unsigned char) *p); )
+ p++;
+ strncpy(cmd, p, MAX_LINE - 1);
+ cmd[MAX_LINE -1] = '\0'; /* Ensure null-terminated */
+
+ gotName = TRUE;
+ }
+
+ /* The "Uid:" line contains the real, effective, saved set-,
+ and file-system user IDs */
+
+ if (strncmp(line, "Uid:", 4) == 0) {
+ uid = strtol(line + 4, NULL, 10);
+ gotUid = TRUE;
+ }
+ }
+
+ fclose(fp);
+
+ /* If we found a username and a UID, and the UID matches,
+ then display the PID and command name */
+
+ if (gotName && gotUid && uid == checkedUid)
+ printf("%5s %s", dp->d_name, cmd);
+ }
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 12-2 */
+
+/* t_uname.c
+
+ Demonstrate the use of the uname() system call, which returns various
+ identifying information about the system.
+*/
+#ifdef __linux__
+#define _GNU_SOURCE
+#endif
+#include <sys/utsname.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ struct utsname uts;
+
+ if (uname(&uts) == -1)
+ errExit("uname");
+
+ printf("Node name: %s\n", uts.nodename);
+ printf("System name: %s\n", uts.sysname);
+ printf("Release: %s\n", uts.release);
+ printf("Version: %s\n", uts.version);
+ printf("Machine: %s\n", uts.machine);
+#ifdef _GNU_SOURCE
+ printf("Domain name: %s\n", uts.domainname);
+#endif
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE = t_fpathconf t_sysconf
+
+LINUX_EXE =
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+clean :
+ ${RM} ${EXE} *.o
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 11-2 */
+
+/* t_fpathconf.c
+
+ Demonstrate the use of fpathconf() to retrieve the values of
+ pathname-related limits.
+*/
+#include "tlpi_hdr.h"
+
+static void /* Print 'msg' plus value of fpathconf(fd, name) */
+fpathconfPrint(const char *msg, int fd, int name)
+{
+ long lim;
+
+ errno = 0;
+ lim = fpathconf(fd, name);
+ if (lim != -1) { /* Call succeeded, limit determinate */
+ printf("%s %ld\n", msg, lim);
+ } else {
+ if (errno == 0) /* Call succeeded, limit indeterminate */
+ printf("%s (indeterminate)\n", msg);
+ else /* Call failed */
+ errExit("fpathconf %s", msg);
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ fpathconfPrint("_PC_NAME_MAX: ", STDIN_FILENO, _PC_NAME_MAX);
+ fpathconfPrint("_PC_PATH_MAX: ", STDIN_FILENO, _PC_PATH_MAX);
+ fpathconfPrint("_PC_PIPE_BUF: ", STDIN_FILENO, _PC_PIPE_BUF);
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 11-1 */
+
+/* t_sysconf.c
+
+ Demonstrate the use of sysconf() to retrieve system limits.
+*/
+#include "tlpi_hdr.h"
+
+static void /* Print 'msg' plus sysconf() value for 'name' */
+sysconfPrint(const char *msg, int name)
+{
+ long lim;
+
+ errno = 0;
+ lim = sysconf(name);
+ if (lim != -1) { /* Call succeeded, limit determinate */
+ printf("%s %ld\n", msg, lim);
+ } else {
+ if (errno == 0) /* Call succeeded, limit indeterminate */
+ printf("%s (indeterminate)\n", msg);
+ else /* Call failed */
+ errExit("sysconf %s", msg);
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ sysconfPrint("_SC_ARG_MAX: ", _SC_ARG_MAX);
+ sysconfPrint("_SC_LOGIN_NAME_MAX: ", _SC_LOGIN_NAME_MAX);
+ sysconfPrint("_SC_OPEN_MAX: ", _SC_OPEN_MAX);
+ sysconfPrint("_SC_NGROUPS_MAX: ", _SC_NGROUPS_MAX);
+ sysconfPrint("_SC_PAGESIZE: ", _SC_PAGESIZE);
+ sysconfPrint("_SC_RTSIG_MAX: ", _SC_RTSIG_MAX);
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE = detached_attrib one_time_init prod_condvar prod_no_condvar \
+ pthread_barrier_demo \
+ simple_thread strerror_test strerror_test_tsd \
+ thread_cancel thread_cleanup thread_incr thread_incr_mutex \
+ thread_incr_rwlock thread_incr_spinlock \
+ thread_lock_speed \
+ thread_multijoin
+
+LINUX_EXE = strerror_test_tls
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+CFLAGS = ${IMPL_CFLAGS} ${IMPL_THREAD_FLAGS}
+LDLIBS = ${IMPL_LDLIBS} ${IMPL_THREAD_FLAGS}
+
+strerror_test: strerror_test.o strerror.o
+ ${CC} -o $@ strerror_test.o strerror.o \
+ ${CFLAGS} ${LDLIBS}
+
+strerror_test_tsd: strerror_test.o strerror_tsd.o
+ ${CC} -o $@ strerror_test.o strerror_tsd.o \
+ ${CFLAGS} ${LDLIBS}
+
+strerror_test_tls: strerror_test.o strerror_tls.o
+ ${CC} -o $@ strerror_test.o strerror_tls.o \
+ ${CFLAGS} ${LDLIBS}
+
+clean :
+ ${RM} ${EXE} *.o
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 29-2 */
+
+/* detached_attrib.c
+
+ An example of the use of POSIX thread attributes (pthread_attr_t):
+ creating a detached thread.
+*/
+#include <pthread.h>
+#include "tlpi_hdr.h"
+
+static void *
+threadFunc(void *x)
+{
+ return x;
+}
+
+int
+main(int argc, char *argv[])
+{
+ pthread_t thr;
+ pthread_attr_t attr;
+ int s;
+
+ s = pthread_attr_init(&attr); /* Assigns default values */
+ if (s != 0)
+ errExitEN(s, "pthread_attr_init");
+
+ s = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ if (s != 0)
+ errExitEN(s, "pthread_attr_setdetachstate");
+
+ s = pthread_create(&thr, &attr, threadFunc, (void *) 1);
+ if (s != 0)
+ errExitEN(s, "pthread_create");
+
+ s = pthread_attr_destroy(&attr); /* No longer needed */
+ if (s != 0)
+ errExitEN(s, "pthread_attr_destroy");
+
+ s = pthread_join(thr, NULL);
+ if (s != 0)
+ errExitEN(s, "pthread_join failed as expected");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 31-1 */
+
+/* one_time_init.c
+
+ The one_time_init() function implemented here performs the same task as
+ the POSIX threads pthread_once() library function.
+*/
+#include <pthread.h>
+#include "tlpi_hdr.h"
+
+struct once_struct { /* Our equivalent of pthread_once_t */
+ pthread_mutex_t mtx;
+ int called;
+};
+
+#define ONCE_INITIALISER { PTHREAD_MUTEX_INITIALIZER, 0 }
+
+struct once_struct once = ONCE_INITIALISER;
+
+static int
+one_time_init(struct once_struct *once_control, void (*init)(void))
+{
+ int s;
+
+ s = pthread_mutex_lock(&(once_control->mtx));
+ if (s == -1)
+ errExitEN(s, "pthread_mutex_lock");
+
+ if (!once_control->called) {
+ (*init)();
+ once_control->called = 1;
+ }
+
+ s = pthread_mutex_unlock(&(once_control->mtx));
+ if (s == -1)
+ errExitEN(s, "pthread_mutex_unlock");
+
+ return 0;
+}
+
+/* Remaining code is for testing one_time_init() */
+
+static void
+init_func()
+{
+ /* We should see this message only once, no matter how many
+ times one_time_init() is called */
+
+ printf("Called init_func()\n");
+}
+
+static void *
+threadFunc(void *arg)
+{
+ /* The following allows us to verify that even if a single thread calls
+ one_time_init() multiple times, init_func() is called only once */
+
+ one_time_init(&once, init_func);
+ one_time_init(&once, init_func);
+ return NULL;
+}
+
+int
+main(int argc, char *argv[])
+{
+ pthread_t t1, t2;
+ int s;
+
+ /* Create two threads, both of which will call one_time_init() */
+
+ s = pthread_create(&t1, NULL, threadFunc, (void *) 1);
+ if (s != 0)
+ errExitEN(s, "pthread_create");
+
+ s = pthread_create(&t2, NULL, threadFunc, (void *) 2);
+ if (s != 0)
+ errExitEN(s, "pthread_create");
+
+ s = pthread_join(t1, NULL);
+ if (s != 0)
+ errExitEN(s, "pthread_join");
+ printf("First thread returned\n");
+
+ s = pthread_join(t2, NULL);
+ if (s != 0)
+ errExitEN(s, "pthread_join");
+ printf("Second thread returned\n");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 30 */
+
+/* prod_condvar.c
+
+ A simple POSIX threads producer-consumer example using a condition variable.
+*/
+#include <time.h>
+#include <pthread.h>
+#include "tlpi_hdr.h"
+
+static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
+
+static int avail = 0;
+
+static void *
+threadFunc(void *arg)
+{
+ int cnt = atoi((char *) arg);
+ int s, j;
+
+ for (j = 0; j < cnt; j++) {
+ sleep(1);
+
+ /* Code to produce a unit omitted */
+
+ s = pthread_mutex_lock(&mtx);
+ if (s != 0)
+ errExitEN(s, "pthread_mutex_lock");
+
+ avail++; /* Let consumer know another unit is available */
+
+ s = pthread_mutex_unlock(&mtx);
+ if (s != 0)
+ errExitEN(s, "pthread_mutex_unlock");
+
+ s = pthread_cond_signal(&cond); /* Wake sleeping consumer */
+ if (s != 0)
+ errExitEN(s, "pthread_cond_signal");
+ }
+
+ return NULL;
+}
+
+int
+main(int argc, char *argv[])
+{
+ pthread_t tid;
+ int s, j;
+ int totRequired; /* Total number of units that all threads
+ will produce */
+ int numConsumed; /* Total units so far consumed */
+ Boolean done;
+ time_t t;
+
+ t = time(NULL);
+
+ /* Create all threads */
+
+ totRequired = 0;
+ for (j = 1; j < argc; j++) {
+ totRequired += atoi(argv[j]);
+
+ s = pthread_create(&tid, NULL, threadFunc, argv[j]);
+ if (s != 0)
+ errExitEN(s, "pthread_create");
+ }
+
+ /* Loop to consume available units */
+
+ numConsumed = 0;
+ done = FALSE;
+
+ for (;;) {
+ s = pthread_mutex_lock(&mtx);
+ if (s != 0)
+ errExitEN(s, "pthread_mutex_lock");
+
+ while (avail == 0) { /* Wait for something to consume */
+ s = pthread_cond_wait(&cond, &mtx);
+ if (s != 0)
+ errExitEN(s, "pthread_cond_wait");
+ }
+
+ /* At this point, 'mtx' is locked... */
+
+ while (avail > 0) { /* Consume all available units */
+
+ /* Do something with produced unit */
+
+ numConsumed ++;
+ avail--;
+ printf("T=%ld: numConsumed=%d\n", (long) (time(NULL) - t),
+ numConsumed);
+
+ done = numConsumed >= totRequired;
+ }
+
+ s = pthread_mutex_unlock(&mtx);
+ if (s != 0)
+ errExitEN(s, "pthread_mutex_unlock");
+
+ if (done)
+ break;
+
+ /* Perhaps do other work here that does not require mutex lock */
+
+ }
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 30 */
+
+/* prod_no_condvar.c
+
+ A simple POSIX threads producer-consumer example that doesn't use
+ a condition variable.
+
+ See also prod_condvar.c.
+*/
+#include <time.h>
+#include <pthread.h>
+#include "tlpi_hdr.h"
+
+static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
+
+static int avail = 0;
+
+static void *
+threadFunc(void *arg)
+{
+ int cnt = atoi((char *) arg);
+ int s, j;
+
+ for (j = 0; j < cnt; j++) {
+ sleep(1);
+
+ /* Code to produce a unit omitted */
+
+ s = pthread_mutex_lock(&mtx);
+ if (s != 0)
+ errExitEN(s, "pthread_mutex_lock");
+
+ avail++; /* Let consumer know another unit is available */
+
+ s = pthread_mutex_unlock(&mtx);
+ if (s != 0)
+ errExitEN(s, "pthread_mutex_unlock");
+ }
+
+ return NULL;
+}
+
+int
+main(int argc, char *argv[])
+{
+ pthread_t tid;
+ int s, j;
+ int totRequired; /* Total number of units that all
+ threads will produce */
+ int numConsumed; /* Total units so far consumed */
+ Boolean done;
+ time_t t;
+
+ t = time(NULL);
+
+ /* Create all threads */
+
+ totRequired = 0;
+ for (j = 1; j < argc; j++) {
+ totRequired += atoi(argv[j]);
+
+ s = pthread_create(&tid, NULL, threadFunc, argv[j]);
+ if (s != 0)
+ errExitEN(s, "pthread_create");
+ }
+
+ /* Use a polling loop to check for available units */
+
+ numConsumed = 0;
+ done = FALSE;
+
+ for (;;) {
+ s = pthread_mutex_lock(&mtx);
+ if (s != 0)
+ errExitEN(s, "pthread_mutex_lock");
+
+ while (avail > 0) { /* Consume all available units */
+
+ /* Do something with produced unit */
+
+ numConsumed ++;
+ avail--;
+ printf("T=%ld: numConsumed=%d\n", (long) (time(NULL) - t),
+ numConsumed);
+
+ done = numConsumed >= totRequired;
+ }
+
+ s = pthread_mutex_unlock(&mtx);
+ if (s != 0)
+ errExitEN(s, "pthread_mutex_unlock");
+
+ if (done)
+ break;
+
+ /* Perhaps do other work here that does not require mutex lock */
+
+ }
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 30 */
+
+/* pthread_barrier_demo.c
+
+ A demonstration of the use of the pthreads barrier API.
+
+ Usage: pthread_barrier_demo num-barriers num-threads
+
+ The program creates 'num-threads' threads, each of which loop
+ 'num-threads' times, waiting on the same barrier.
+*/
+#include <pthread.h>
+#include "tlpi_hdr.h"
+
+static pthread_barrier_t barrier;
+ /* Barrier waited on by all threads */
+
+static int numBarriers; /* Number of times the threads will
+ pass the barrier */
+
+static void *
+threadFunc(void *arg)
+{
+ int s, j, nsecs;
+ long threadNum = (long) arg;
+
+ printf("Thread %ld started\n", threadNum);
+
+ /* Seed the random number generator based on the current time
+ (so that we get different seeds on each run) plus thread
+ number (so that each thread gets a unique seed). */
+
+ srandom(time(NULL) + threadNum);
+
+ /* Each thread loops, sleeping for a few seconds and then waiting
+ on the barrier. The loop terminates when each thread has passed
+ the barrier 'numBarriers' times. */
+
+ for (j = 0; j < numBarriers; j++) {
+
+ nsecs = random() % 5 + 1; /* Sleep for 1 to 5 seconds */
+ sleep(nsecs);
+
+ /* Calling pthread_barrier_wait() causes each thread to block
+ until the call has been made by number of threads specified
+ in the pthread_barrier_init() call. */
+
+ printf("Thread %ld about to wait on barrier %d "
+ "after sleeping %d seconds\n", threadNum, j, nsecs);
+ s = pthread_barrier_wait(&barrier);
+
+ /* After the required number of threads have called
+ pthread_barrier_wait(), all of the threads unblock, and
+ the barrier is reset to the state it had after the call to
+ pthread_barrier_init(). In other words, the barrier can be
+ once again used by the threads as a synchronization point.
+
+ On success, pthread_barrier_wait() returns the special value
+ PTHREAD_BARRIER_SERIAL_THREAD in exactly one of the waiting
+ threads, and 0 in all of the other threads. This permits
+ the program to ensure that some action is performed exactly
+ once each time a barrier is passed. */
+
+ if (s == 0) {
+ printf("Thread %ld passed barrier %d: return value was 0\n",
+ threadNum, j);
+
+ } else if (s == PTHREAD_BARRIER_SERIAL_THREAD) {
+ printf("Thread %ld passed barrier %d: return value was "
+ "PTHREAD_BARRIER_SERIAL_THREAD\n", threadNum, j);
+
+ /* In the thread that gets the PTHREAD_BARRIER_SERIAL_THREAD
+ return value, we briefly delay, and then print a newline
+ character. This should give all of the threads a chance
+ to print the message saying they have passed the barrier,
+ and then provide a newline that separates those messages
+ from subsequent output. (The only purpose of this step
+ is to make the program output a little easier to read.) */
+
+ usleep(100000);
+ printf("\n");
+
+ } else { /* Error */
+ errExitEN(s, "pthread_barrier_wait (%ld)", threadNum);
+ }
+ }
+
+ /* Print out thread termination message after a briefly delaying,
+ so that the other threads have a chance to display the return
+ value they received from pthread_barrier_wait(). (This simply
+ makes the program output a little easier to read.)*/
+
+ usleep(200000);
+ printf("Thread %ld terminating\n", threadNum);
+
+ return NULL;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int s, numThreads;
+ long threadNum;
+ pthread_t *tid;
+
+ if (argc != 3 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s num-barriers num-threads\n", argv[0]);
+
+ numBarriers = atoi(argv[1]);
+ numThreads = atoi(argv[2]);
+
+ /* Allocate array to hold thread IDs */
+
+ tid = calloc(sizeof(pthread_t), numThreads);
+ if (tid == NULL)
+ errExit("calloc");
+
+ /* Initialize the barrier. The final argument specifies the
+ number of threads that must call pthread_barrier_wait()
+ before any thread will unblock from that call. */
+
+ s = pthread_barrier_init(&barrier, NULL, numThreads);
+ if (s != 0)
+ errExitEN(s, "pthread_barrier_init");
+
+ /* Create 'numThreads' threads */
+
+ for (threadNum = 0; threadNum < numThreads; threadNum++) {
+ s = pthread_create(&tid[threadNum], NULL, threadFunc,
+ (void *) threadNum);
+ if (s != 0)
+ errExitEN(s, "pthread_create");
+ }
+
+ /* Each thread prints a start-up message. We briefly delay,
+ and then print a newline character so that an empty line
+ appears after the start-up messages. */
+
+ usleep(100000);
+ printf("\n");
+
+ /* Wait for all of the threads to terminate */
+
+ for (threadNum = 0; threadNum < numThreads; threadNum++) {
+ s = pthread_join(tid[threadNum], NULL);
+ if (s != 0)
+ errExitEN(s, "pthread_join");
+ }
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 29-1 */
+
+/* simple_thread.c
+
+ A simple POSIX threads example: create a thread, and then join with it.
+*/
+#include <pthread.h>
+#include "tlpi_hdr.h"
+
+static void *
+threadFunc(void *arg)
+{
+ char *s = (char *) arg;
+
+ printf("%s", s);
+
+ return (void *) strlen(s);
+}
+
+int
+main(int argc, char *argv[])
+{
+ pthread_t t1;
+ void *res;
+ int s;
+
+ s = pthread_create(&t1, NULL, threadFunc, "Hello world\n");
+ if (s != 0)
+ errExitEN(s, "pthread_create");
+
+ printf("Message from main()\n");
+ s = pthread_join(t1, &res);
+ if (s != 0)
+ errExitEN(s, "pthread_join");
+
+ printf("Thread returned %ld\n", (long) res);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 31-1 */
+
+/* strerror.c
+
+ An implementation of strerror() that is not thread-safe.
+*/
+#define _GNU_SOURCE /* Get '_sys_nerr' and '_sys_errlist'
+ declarations from <stdio.h> */
+#include <stdio.h>
+#include <string.h> /* Get declaration of strerror() */
+
+#define MAX_ERROR_LEN 256 /* Maximum length of string
+ returned by strerror() */
+
+static char buf[MAX_ERROR_LEN]; /* Statically allocated return buffer */
+
+char *
+strerror(int err)
+{
+ if (err < 0 || err >= _sys_nerr || _sys_errlist[err] == NULL) {
+ snprintf(buf, MAX_ERROR_LEN, "Unknown error %d", err);
+ } else {
+ strncpy(buf, _sys_errlist[err], MAX_ERROR_LEN - 1);
+ buf[MAX_ERROR_LEN - 1] = '\0'; /* Ensure null termination */
+ }
+
+ return buf;
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 31-2 */
+
+/* strerror_test.c
+
+ A program to test whether the implementation of strerror() thread-safe.
+*/
+#include <stdio.h>
+#include <string.h> /* Get declaration of strerror() */
+#include <pthread.h>
+#include "tlpi_hdr.h"
+
+static void *
+threadFunc(void *arg)
+{
+ char *str;
+
+ printf("Other thread about to call strerror()\n");
+ str = strerror(EPERM);
+ printf("Other thread: str (%p) = %s\n", str, str);
+
+ return NULL;
+}
+
+int
+main(int argc, char *argv[])
+{
+ pthread_t t;
+ int s;
+ char *str;
+
+ str = strerror(EINVAL);
+ printf("Main thread has called strerror()\n");
+
+ s = pthread_create(&t, NULL, threadFunc, NULL);
+ if (s != 0)
+ errExitEN(s, "pthread_create");
+
+ s = pthread_join(t, NULL);
+ if (s != 0)
+ errExitEN(s, "pthread_join");
+
+ /* If strerror() is not thread-safe, then the output of this printf() be
+ the same as that produced by the analogous printf() in threadFunc() */
+
+ printf("Main thread: str (%p) = %s\n", str, str);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 31-4 */
+
+/* strerror_tls.c
+
+ An implementation of strerror() that is made thread-safe through
+ the use of thread-local storage.
+
+ See also strerror_tsd.c.
+
+ Thread-local storage requires: Linux 2.6 or later, NPTL, and
+ gcc 3.3 or later.
+*/
+#define _GNU_SOURCE /* Get '_sys_nerr' and '_sys_errlist'
+ declarations from <stdio.h> */
+#include <stdio.h>
+#include <string.h> /* Get declaration of strerror() */
+#include <pthread.h>
+
+#define MAX_ERROR_LEN 256 /* Maximum length of string in per-thread
+ buffer returned by strerror() */
+
+static __thread char buf[MAX_ERROR_LEN];
+ /* Thread-local return buffer */
+
+char *
+strerror(int err)
+{
+ if (err < 0 || err >= _sys_nerr || _sys_errlist[err] == NULL) {
+ snprintf(buf, MAX_ERROR_LEN, "Unknown error %d", err);
+ } else {
+ strncpy(buf, _sys_errlist[err], MAX_ERROR_LEN - 1);
+ buf[MAX_ERROR_LEN - 1] = '\0'; /* Ensure null termination */
+ }
+
+ return buf;
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 31-3 */
+
+/* strerror_tsd.c
+
+ An implementation of strerror() that is made thread-safe through
+ the use of thread-specific data.
+
+ See also strerror_tls.c.
+*/
+#define _GNU_SOURCE /* Get '_sys_nerr' and '_sys_errlist'
+ declarations from <stdio.h> */
+#include <stdio.h>
+#include <string.h> /* Get declaration of strerror() */
+#include <pthread.h>
+#include "tlpi_hdr.h"
+
+static pthread_once_t once = PTHREAD_ONCE_INIT;
+static pthread_key_t strerrorKey;
+
+#define MAX_ERROR_LEN 256 /* Maximum length of string in per-thread
+ buffer returned by strerror() */
+
+static void /* Free thread-specific data buffer */
+destructor(void *buf)
+{
+ free(buf);
+}
+
+static void /* One-time key creation function */
+createKey(void)
+{
+ int s;
+
+ /* Allocate a unique thread-specific data key and save the address
+ of the destructor for thread-specific data buffers */
+
+ s = pthread_key_create(&strerrorKey, destructor);
+ if (s != 0)
+ errExitEN(s, "pthread_key_create");
+}
+
+char *
+strerror(int err)
+{
+ int s;
+ char *buf;
+
+ /* Make first caller allocate key for thread-specific data */
+
+ s = pthread_once(&once, createKey);
+ if (s != 0)
+ errExitEN(s, "pthread_once");
+
+ buf = pthread_getspecific(strerrorKey);
+ if (buf == NULL) { /* If first call from this thread, allocate
+ buffer for thread, and save its location */
+ buf = malloc(MAX_ERROR_LEN);
+ if (buf == NULL)
+ errExit("malloc");
+
+ s = pthread_setspecific(strerrorKey, buf);
+ if (s != 0)
+ errExitEN(s, "pthread_setspecific");
+ }
+
+ if (err < 0 || err >= _sys_nerr || _sys_errlist[err] == NULL) {
+ snprintf(buf, MAX_ERROR_LEN, "Unknown error %d", err);
+ } else {
+ strncpy(buf, _sys_errlist[err], MAX_ERROR_LEN - 1);
+ buf[MAX_ERROR_LEN - 1] = '\0'; /* Ensure null termination */
+ }
+
+ return buf;
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 32-1 */
+
+/* thread_cancel.c
+
+ Demonstrate the use of pthread_cancel() to cancel a POSIX thread.
+*/
+#include <pthread.h>
+#include "tlpi_hdr.h"
+
+static void *
+threadFunc(void *arg)
+{
+ int j;
+
+ printf("New thread started\n"); /* May be a cancellation point */
+ for (j = 1; ; j++) {
+ printf("Loop %d\n", j); /* May be a cancellation point */
+ sleep(1); /* A cancellation point */
+ }
+
+ /* NOTREACHED */
+ return NULL;
+}
+
+int
+main(int argc, char *argv[])
+{
+ pthread_t thr;
+ int s;
+ void *res;
+
+ s = pthread_create(&thr, NULL, threadFunc, NULL);
+ if (s != 0)
+ errExitEN(s, "pthread_create");
+
+ sleep(3); /* Allow new thread to run a while */
+
+ s = pthread_cancel(thr);
+ if (s != 0)
+ errExitEN(s, "pthread_cancel");
+
+ s = pthread_join(thr, &res);
+ if (s != 0)
+ errExitEN(s, "pthread_join");
+
+ if (res == PTHREAD_CANCELED)
+ printf("Thread was canceled\n");
+ else
+ printf("Thread was not canceled (should not happen!)\n");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 32-2 */
+
+/* thread_cleanup.c
+
+ An example of thread cancellation using the POSIX threads API:
+ demonstrate the use of pthread_cancel() and cleanup handlers.
+*/
+#include <pthread.h>
+#include "tlpi_hdr.h"
+
+static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
+static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
+static int glob = 0; /* Predicate variable */
+
+static void /* Free memory pointed to by 'arg' and unlock mutex */
+cleanupHandler(void *arg)
+{
+ int s;
+
+ printf("cleanup: freeing block at %p\n", arg);
+ free(arg);
+
+ printf("cleanup: unlocking mutex\n");
+ s = pthread_mutex_unlock(&mtx);
+ if (s != 0)
+ errExitEN(s, "pthread_mutex_unlock");
+}
+
+static void *
+threadFunc(void *arg)
+{
+ int s;
+ void *buf = NULL; /* Buffer allocated by thread */
+
+ buf = malloc(0x10000); /* Not a cancellation point */
+ printf("thread: allocated memory at %p\n", buf);
+
+ s = pthread_mutex_lock(&mtx); /* Not a cancellation point */
+ if (s != 0)
+ errExitEN(s, "pthread_mutex_lock");
+
+ pthread_cleanup_push(cleanupHandler, buf);
+
+ while (glob == 0) {
+ s = pthread_cond_wait(&cond, &mtx); /* A cancellation point */
+ if (s != 0)
+ errExitEN(s, "pthread_cond_wait");
+ }
+
+ printf("thread: condition wait loop completed\n");
+ pthread_cleanup_pop(1); /* Executes cleanup handler */
+ return NULL;
+}
+
+int
+main(int argc, char *argv[])
+{
+ pthread_t thr;
+ void *res;
+ int s;
+
+ s = pthread_create(&thr, NULL, threadFunc, NULL);
+ if (s != 0)
+ errExitEN(s, "pthread_create");
+
+ sleep(2); /* Give thread a chance to get started */
+
+ if (argc == 1) { /* Cancel thread */
+ printf("main: about to cancel thread\n");
+ s = pthread_cancel(thr);
+ if (s != 0)
+ errExitEN(s, "pthread_cancel");
+
+ } else { /* Signal condition variable */
+ printf("main: about to signal condition variable\n");
+
+ s = pthread_mutex_lock(&mtx); /* See the TLPI page 679 erratum */
+ if (s != 0)
+ errExitEN(s, "pthread_mutex_lock");
+
+ glob = 1;
+
+ s = pthread_mutex_unlock(&mtx); /* See the TLPI page 679 erratum */
+ if (s != 0)
+ errExitEN(s, "pthread_mutex_unlock");
+
+ s = pthread_cond_signal(&cond);
+ if (s != 0)
+ errExitEN(s, "pthread_cond_signal");
+ }
+
+ s = pthread_join(thr, &res);
+ if (s != 0)
+ errExitEN(s, "pthread_join");
+ if (res == PTHREAD_CANCELED)
+ printf("main: thread was canceled\n");
+ else
+ printf("main: thread terminated normally\n");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 30-1 */
+
+/* thread_incr.c
+
+ This program employs two POSIX threads that increment the same global
+ variable, without using any synchronization method. As a consequence,
+ updates are sometimes lost.
+
+ See also thread_incr_mutex.c.
+*/
+#include <pthread.h>
+#include "tlpi_hdr.h"
+
+static volatile int glob = 0; /* "volatile" prevents compiler optimizations
+ of arithmetic operations on 'glob' */
+static void * /* Loop 'arg' times incrementing 'glob' */
+threadFunc(void *arg)
+{
+ int loops = *((int *) arg);
+ int loc, j;
+
+ for (j = 0; j < loops; j++) {
+ loc = glob;
+ loc++;
+ glob = loc;
+ }
+
+ return NULL;
+}
+
+int
+main(int argc, char *argv[])
+{
+ pthread_t t1, t2;
+ int loops, s;
+
+ loops = (argc > 1) ? getInt(argv[1], GN_GT_0, "num-loops") : 10000000;
+
+ s = pthread_create(&t1, NULL, threadFunc, &loops);
+ if (s != 0)
+ errExitEN(s, "pthread_create");
+ s = pthread_create(&t2, NULL, threadFunc, &loops);
+ if (s != 0)
+ errExitEN(s, "pthread_create");
+
+ s = pthread_join(t1, NULL);
+ if (s != 0)
+ errExitEN(s, "pthread_join");
+ s = pthread_join(t2, NULL);
+ if (s != 0)
+ errExitEN(s, "pthread_join");
+
+ printf("glob = %d\n", glob);
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 30-2 */
+
+/* thread_incr_mutex.c
+
+ This program employs two POSIX threads that increment the same global
+ variable, synchronizing their access using a mutex. As a consequence,
+ updates are not lost. Compare with thread_incr.c, thread_incr_spinlock.c,
+ and thread_incr_rwlock.c.
+*/
+#include <pthread.h>
+#include "tlpi_hdr.h"
+
+static volatile int glob = 0;
+static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
+
+static void * /* Loop 'arg' times incrementing 'glob' */
+threadFunc(void *arg)
+{
+ int loops = *((int *) arg);
+ int loc, j, s;
+
+ for (j = 0; j < loops; j++) {
+ s = pthread_mutex_lock(&mtx);
+ if (s != 0)
+ errExitEN(s, "pthread_mutex_lock");
+
+ loc = glob;
+ loc++;
+ glob = loc;
+
+ s = pthread_mutex_unlock(&mtx);
+ if (s != 0)
+ errExitEN(s, "pthread_mutex_unlock");
+ }
+
+ return NULL;
+}
+
+int
+main(int argc, char *argv[])
+{
+ pthread_t t1, t2;
+ int loops, s;
+
+ loops = (argc > 1) ? getInt(argv[1], GN_GT_0, "num-loops") : 10000000;
+
+ s = pthread_create(&t1, NULL, threadFunc, &loops);
+ if (s != 0)
+ errExitEN(s, "pthread_create");
+ s = pthread_create(&t2, NULL, threadFunc, &loops);
+ if (s != 0)
+ errExitEN(s, "pthread_create");
+
+ s = pthread_join(t1, NULL);
+ if (s != 0)
+ errExitEN(s, "pthread_join");
+ s = pthread_join(t2, NULL);
+ if (s != 0)
+ errExitEN(s, "pthread_join");
+
+ printf("glob = %d\n", glob);
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 33 */
+
+/* thread_incr_rwlock.c
+
+ This program employs two POSIX threads that increment the same global
+ variable, synchronizing their access using a read/write lock. As a
+ consequence, updates are not lost. Compare with thread_incr.c,
+ thread_incr_mutex.c, and thread_incr_spinlock.c.
+*/
+#include <pthread.h>
+#include "tlpi_hdr.h"
+
+static volatile int glob = 0;
+static pthread_rwlock_t rwlock;
+
+static void * /* Loop 'arg' times incrementing 'glob' */
+threadFunc(void *arg)
+{
+ int loops = *((int *) arg);
+ int loc, j, s;
+
+ for (j = 0; j < loops; j++) {
+ s = pthread_rwlock_wrlock(&rwlock);
+ if (s != 0)
+ errExitEN(s, "pthread_rwlock_wrlock");
+
+ loc = glob;
+ loc++;
+ glob = loc;
+
+ s = pthread_rwlock_unlock(&rwlock);
+ if (s != 0)
+ errExitEN(s, "pthread_rwlock_unlock");
+ }
+
+ return NULL;
+}
+
+int
+main(int argc, char *argv[])
+{
+ pthread_t t1, t2;
+ int loops, s;
+
+ loops = (argc > 1) ? getInt(argv[1], GN_GT_0, "num-loops") : 10000000;
+
+ s = pthread_rwlock_init(&rwlock, 0);
+ if (s != 0)
+ errExitEN(s, "pthread_rwlock_init");
+
+ s = pthread_create(&t1, NULL, threadFunc, &loops);
+ if (s != 0)
+ errExitEN(s, "pthread_create");
+ s = pthread_create(&t2, NULL, threadFunc, &loops);
+ if (s != 0)
+ errExitEN(s, "pthread_create");
+
+ s = pthread_join(t1, NULL);
+ if (s != 0)
+ errExitEN(s, "pthread_join");
+ s = pthread_join(t2, NULL);
+ if (s != 0)
+ errExitEN(s, "pthread_join");
+
+ printf("glob = %d\n", glob);
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 33 */
+
+/* thread_incr_spinlock.c
+
+ This program employs two POSIX threads that increment the same global
+ variable, synchronizing their access using a spinlock. As a consequence,
+ updates are not lost. Compare with thread_incr.c, thread_incr_mutex.c,
+ and thread_incr_rwlock.c
+*/
+#include <pthread.h>
+#include "tlpi_hdr.h"
+
+static volatile int glob = 0;
+static pthread_spinlock_t splock;
+
+static void * /* Loop 'arg' times incrementing 'glob' */
+threadFunc(void *arg)
+{
+ int loops = *((int *) arg);
+ int loc, j, s;
+
+ for (j = 0; j < loops; j++) {
+ s = pthread_spin_lock(&splock);
+ if (s != 0)
+ errExitEN(s, "pthread_spin_lock");
+
+ loc = glob;
+ loc++;
+ glob = loc;
+
+ s = pthread_spin_unlock(&splock);
+ if (s != 0)
+ errExitEN(s, "pthread_spin_unlock");
+ }
+
+ return NULL;
+}
+
+int
+main(int argc, char *argv[])
+{
+ pthread_t t1, t2;
+ int loops, s;
+
+ loops = (argc > 1) ? getInt(argv[1], GN_GT_0, "num-loops") : 10000000;
+
+ s = pthread_spin_init(&splock, 0);
+ if (s != 0)
+ errExitEN(s, "pthread_spin_init");
+
+ s = pthread_create(&t1, NULL, threadFunc, &loops);
+ if (s != 0)
+ errExitEN(s, "pthread_create");
+ s = pthread_create(&t2, NULL, threadFunc, &loops);
+ if (s != 0)
+ errExitEN(s, "pthread_create");
+
+ s = pthread_join(t1, NULL);
+ if (s != 0)
+ errExitEN(s, "pthread_join");
+ s = pthread_join(t2, NULL);
+ if (s != 0)
+ errExitEN(s, "pthread_join");
+
+ printf("glob = %d\n", glob);
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 33 */
+
+/* thread_lock_speed.c
+
+ This program employs POSIX threads that increment the same global
+ variable, synchronizing their access using either a mutex or a spinlock.
+ Command-line arguments allow the user to specify:
+
+ * The number of threads that will increment the global variable.
+ * The number of "outer loops" that each thread will execute using
+ the lock/unlock APIs (either mutexes or spin locks).
+ * The number "inner loops" executed for each "outer loop" step.
+ Each inner loop iteration increments the global variable by 1.
+
+ By default, the threads use mutexes to synchronize their access to
+ the global variable. Specifying the "-s" option causes spin locks
+ to be employed instead.
+
+ The idea is to vary the number of threads and number of inner loops
+ while using time(1) to measure the real and CPU time consumed by the
+ program. In some scenarios (e.g., many threads, large "inner loop"
+ values), mutexes will perform better, while in others (few threads,
+ small "inner loop" value), spin locks are likely to be better.
+*/
+#include <pthread.h>
+#include "tlpi_hdr.h"
+
+static volatile int glob = 0;
+static pthread_spinlock_t splock;
+static pthread_mutex_t mtx;
+static int useMutex = 0;
+static int numOuterLoops;
+static int numInnerLoops;
+
+static void *
+threadFunc(void *arg)
+{
+ int j, k, s;
+
+ for (j = 0; j < numOuterLoops; j++) {
+ if (useMutex) {
+ s = pthread_mutex_lock(&mtx);
+ if (s != 0)
+ errExitEN(s, "pthread_mutex_lock");
+ } else {
+ s = pthread_spin_lock(&splock);
+ if (s != 0)
+ errExitEN(s, "pthread_spin_lock");
+ }
+
+ for (k = 0; k < numInnerLoops; k++)
+ glob++;
+
+ if (useMutex) {
+ s = pthread_mutex_unlock(&mtx);
+ if (s != 0)
+ errExitEN(s, "pthread_mutex_unlock");
+ } else {
+ s = pthread_spin_unlock(&splock);
+ if (s != 0)
+ errExitEN(s, "pthread_spin_unlock");
+ }
+ }
+
+ return NULL;
+}
+
+static void
+usageError(char *pname)
+{
+ fprintf(stderr,
+ "Usage: %s [-s] num-threads "
+ "[num-inner-loops [num-outer-loops]]\n", pname);
+ fprintf(stderr,
+ " -q Don't print verbose messages\n");
+ fprintf(stderr,
+ " -s Use spin locks (instead of the default mutexes)\n");
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int opt, s, j;
+ int numThreads;
+ pthread_t *thread;
+ int verbose;
+
+ /* Prevent runaway/forgotten process from burning up CPU time forever */
+
+ alarm(120); /* Unhandled SIGALRM will kill process */
+
+ useMutex = 1;
+ verbose = 1;
+ while ((opt = getopt(argc, argv, "qs")) != -1) {
+ switch (opt) {
+ case 'q':
+ verbose = 0;
+ break;
+ case 's':
+ useMutex = 0;
+ break;
+ default:
+ usageError(argv[0]);
+ }
+ }
+
+ if (optind >= argc)
+ usageError(argv[0]);
+
+ numThreads = atoi(argv[optind]);
+ numInnerLoops = (optind + 1 < argc) ? atoi(argv[optind + 1]) : 1;
+ numOuterLoops = (optind + 2 < argc) ? atoi(argv[optind + 2]) : 10000000;
+
+ if (verbose) {
+ printf("Using %s\n", useMutex ? "mutexes" : "spin locks");
+ printf("\tthreads: %d; outer loops: %d; inner loops: %d\n",
+ numThreads, numOuterLoops, numInnerLoops);
+ }
+
+ thread = calloc(numThreads, sizeof(pthread_t));
+ if (thread == NULL)
+ errExit("calloc");
+
+ if (useMutex) {
+ s = pthread_mutex_init(&mtx, NULL);
+ if (s != 0)
+ errExitEN(s, "pthread_mutex_init");
+ } else {
+ s = pthread_spin_init(&splock, 0);
+ if (s != 0)
+ errExitEN(s, "pthread_spin_init");
+ }
+
+ for (j = 0; j < numThreads; j++) {
+ s = pthread_create(&thread[j], NULL, threadFunc, NULL);
+ if (s != 0)
+ errExitEN(s, "pthread_create");
+ }
+
+ for (j = 0; j < numThreads; j++) {
+ s = pthread_join(thread[j], NULL);
+ if (s != 0)
+ errExitEN(s, "pthread_join");
+ }
+
+ if (verbose)
+ printf("glob = %d\n", glob);
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 30-4 */
+
+/* thread_multijoin.c
+
+ This program creates one thread for each of its command-line arguments.
+ Each thread sleeps for the number of seconds specified in the corresponding
+ command-line argument, and then terminates. This sleep interval simulates
+ "work" done by the thread.
+
+ The program maintains a global array (pointed to by 'thread') recording
+ information about all threads that have been created. The items of this
+ array record the thread ID ('tid') and current state ('state', of type
+ 'enum tstate') of each thread.
+
+ As each thread terminates, it sets its 'state' to TS_TERMINATED and
+ signals the 'threadDied' condition variable. The main thread continuously
+ waits on this condition variable, and is thus informed when any of the
+ threads that it created has terminated. When 'numLive', which records
+ the number of live threads, falls to 0, the main thread terminates.
+*/
+#include <pthread.h>
+#include "tlpi_hdr.h"
+
+static pthread_cond_t threadDied = PTHREAD_COND_INITIALIZER;
+static pthread_mutex_t threadMutex = PTHREAD_MUTEX_INITIALIZER;
+ /* Protects all of the following global variables */
+
+static int totThreads = 0; /* Total number of threads created */
+static int numLive = 0; /* Total number of threads still alive or
+ terminated but not yet joined */
+static int numUnjoined = 0; /* Number of terminated threads that
+ have not yet been joined */
+enum tstate { /* Thread states */
+ TS_ALIVE, /* Thread is alive */
+ TS_TERMINATED, /* Thread terminated, not yet joined */
+ TS_JOINED /* Thread terminated, and joined */
+};
+
+static struct { /* Info about each thread */
+ pthread_t tid; /* ID of this thread */
+ enum tstate state; /* Thread state (TS_* constants above) */
+ int sleepTime; /* Number seconds to live before terminating */
+} *thread;
+
+static void * /* Start function for thread */
+threadFunc(void *arg)
+{
+ int idx = (int) arg;
+ int s;
+
+ sleep(thread[idx].sleepTime); /* Simulate doing some work */
+ printf("Thread %d terminating\n", idx);
+
+ s = pthread_mutex_lock(&threadMutex);
+ if (s != 0)
+ errExitEN(s, "pthread_mutex_lock");
+
+ numUnjoined++;
+ thread[idx].state = TS_TERMINATED;
+
+ s = pthread_mutex_unlock(&threadMutex);
+ if (s != 0)
+ errExitEN(s, "pthread_mutex_unlock");
+ s = pthread_cond_signal(&threadDied);
+ if (s != 0)
+ errExitEN(s, "pthread_cond_signal");
+
+ return NULL;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int s, idx;
+
+ if (argc < 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s num-secs...\n", argv[0]);
+
+ thread = calloc(argc - 1, sizeof(*thread));
+ if (thread == NULL)
+ errExit("calloc");
+
+ /* Create all threads */
+
+ for (idx = 0; idx < argc - 1; idx++) {
+ thread[idx].sleepTime = getInt(argv[idx + 1], GN_NONNEG, NULL);
+ thread[idx].state = TS_ALIVE;
+ s = pthread_create(&thread[idx].tid, NULL, threadFunc, (void *) idx);
+ if (s != 0)
+ errExitEN(s, "pthread_create");
+ }
+
+ totThreads = argc - 1;
+ numLive = totThreads;
+
+ /* Join with terminated threads */
+
+ while (numLive > 0) {
+ s = pthread_mutex_lock(&threadMutex);
+ if (s != 0)
+ errExitEN(s, "pthread_mutex_lock");
+
+ while (numUnjoined == 0) {
+ s = pthread_cond_wait(&threadDied, &threadMutex);
+ if (s != 0)
+ errExitEN(s, "pthread_cond_wait");
+ }
+
+ for (idx = 0; idx < totThreads; idx++) {
+ if (thread[idx].state == TS_TERMINATED) {
+ s = pthread_join(thread[idx].tid, NULL);
+ if (s != 0)
+ errExitEN(s, "pthread_join");
+
+ thread[idx].state = TS_JOINED;
+ numLive--;
+ numUnjoined--;
+
+ printf("Reaped thread %d (numLive=%d)\n", idx, numLive);
+ }
+ }
+
+ s = pthread_mutex_unlock(&threadMutex);
+ if (s != 0)
+ errExitEN(s, "pthread_mutex_unlock");
+ }
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE = calendar_time show_time process_time strtime t_stime
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+clean :
+ ${RM} ${EXE} *.o
+
+showall :
+ @ echo ${EXE}
+
+cal_time: cal_time.o
+ ${CC} -o $@ cal_time.o ${CFLAGS} ${LDLIBS} ${LINUX_LIBRT}
+
+process_time_test: process_time_test.o
+ ${CC} -o $@ process_time_test.o ${CFLAGS} ${LDLIBS} ${LINUX_LIBRT}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 10-1 */
+
+/* calendar_time.c
+
+ Demonstrate the use of functions for working with calendar time.
+
+ This program retrieves the current time and displays it in various forms.
+*/
+#include <locale.h>
+#include <time.h>
+#include <sys/time.h>
+#include "tlpi_hdr.h"
+
+#define SECONDS_IN_TROPICAL_YEAR (365.24219 * 24 * 60 * 60)
+
+int
+main(int argc, char *argv[])
+{
+ time_t t;
+ struct tm *gmp, *locp;
+ struct tm gm, loc;
+ struct timeval tv;
+
+ /* Retrieve time, convert and display it in various forms */
+
+ t = time(NULL);
+ printf("Seconds since the Epoch (1 Jan 1970): %ld", (long) t);
+ printf(" (about %6.3f years)\n", t / SECONDS_IN_TROPICAL_YEAR);
+
+ if (gettimeofday(&tv, NULL) == -1)
+ errExit("gettimeofday");
+ printf(" gettimeofday() returned %ld secs, %ld microsecs\n",
+ (long) tv.tv_sec, (long) tv.tv_usec);
+
+ gmp = gmtime(&t);
+ if (gmp == NULL)
+ errExit("gmtime");
+
+ gm = *gmp; /* Save local copy, since *gmp may be modified
+ by asctime() or gmtime() */
+ printf("Broken down by gmtime():\n");
+ printf(" year=%d mon=%d mday=%d hour=%d min=%d sec=%d ", gm.tm_year,
+ gm.tm_mon, gm.tm_mday, gm.tm_hour, gm.tm_min, gm.tm_sec);
+ printf("wday=%d yday=%d isdst=%d\n", gm.tm_wday, gm.tm_yday, gm.tm_isdst);
+
+ /* The TZ environment variable will affect localtime().
+ Try, for example:
+
+ TZ=Pacific/Auckland calendar_time
+ */
+
+ locp = localtime(&t);
+ if (locp == NULL)
+ errExit("localtime");
+
+ loc = *locp; /* Save local copy */
+
+ printf("Broken down by localtime():\n");
+ printf(" year=%d mon=%d mday=%d hour=%d min=%d sec=%d ",
+ loc.tm_year, loc.tm_mon, loc.tm_mday,
+ loc.tm_hour, loc.tm_min, loc.tm_sec);
+ printf("wday=%d yday=%d isdst=%d\n\n",
+ loc.tm_wday, loc.tm_yday, loc.tm_isdst);
+
+ printf("asctime() formats the gmtime() value as: %s", asctime(&gm));
+ printf("ctime() formats the time() value as: %s", ctime(&t));
+
+ printf("mktime() of gmtime() value: %ld secs\n", (long) mktime(&gm));
+ printf("mktime() of localtime() value: %ld secs\n", (long) mktime(&loc));
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 10-2 */
+
+/* curr_time.c
+
+ Implement our currTime() function.
+*/
+#include <time.h>
+#include "curr_time.h" /* Declares function defined here */
+
+#define BUF_SIZE 1000
+
+/* Return a string containing the current time formatted according to
+ the specification in 'format' (see strftime(3) for specifiers).
+ If 'format' is NULL, we use "%c" as a specifier (which gives the'
+ date and time as for ctime(3), but without the trailing newline).
+ Returns NULL on error. */
+
+char *
+currTime(const char *format)
+{
+ static char buf[BUF_SIZE]; /* Nonreentrant */
+ time_t t;
+ size_t s;
+ struct tm *tm;
+
+ t = time(NULL);
+ tm = localtime(&t);
+ if (tm == NULL)
+ return NULL;
+
+ s = strftime(buf, BUF_SIZE, (format != NULL) ? format : "%c", tm);
+
+ return (s == 0) ? NULL : buf;
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Header file for Listing 10-2 */
+
+/* curr_time.h
+
+ Header file for curr_time.c.
+*/
+#ifndef CURR_TIME_H
+#define CURR_TIME_H /* Prevent accidental double inclusion */
+
+char *currTime(const char *fmt);
+
+#endif
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 10-5 */
+
+/* process_time.c
+
+ Demonstrate usage of clock(3) and times(2) to retrieve process virtual times.
+
+ Usage: process_time [num-calls]
+
+ Make 'num-calls' calls to getppid(), and then display process times.
+*/
+#include <sys/times.h>
+#include <time.h>
+#include "tlpi_hdr.h"
+
+static void /* Display 'msg' and process times */
+displayProcessTimes(const char *msg)
+{
+ struct tms t;
+ clock_t clockTime;
+ static long clockTicks = 0;
+
+ if (msg != NULL)
+ printf("%s", msg);
+
+ if (clockTicks == 0) { /* Fetch clock ticks on first call */
+ clockTicks = sysconf(_SC_CLK_TCK);
+ if (clockTicks == -1)
+ errExit("sysconf");
+ }
+
+ clockTime = clock();
+ if (clockTime == -1)
+ errExit("clock");
+
+ printf(" clock() returns: %ld clocks-per-sec (%.2f secs)\n",
+ (long) clockTime, (double) clockTime / CLOCKS_PER_SEC);
+
+ if (times(&t) == -1)
+ errExit("times");
+ printf(" times() yields: user CPU=%.2f; system CPU: %.2f\n",
+ (double) t.tms_utime / clockTicks,
+ (double) t.tms_stime / clockTicks);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int numCalls, j;
+
+ printf("CLOCKS_PER_SEC=%ld sysconf(_SC_CLK_TCK)=%ld\n\n",
+ (long) CLOCKS_PER_SEC, sysconf(_SC_CLK_TCK));
+
+ displayProcessTimes("At program start:\n");
+
+ /* Call getppid() a large number of times, so that
+ some user and system CPU time are consumed */
+
+ numCalls = (argc > 1) ? getInt(argv[1], GN_GT_0, "num-calls") : 100000000;
+ for (j = 0; j < numCalls; j++)
+ (void) getppid();
+
+ displayProcessTimes("After getppid() loop:\n");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 10-4 */
+
+/* show_time.c
+
+ A short program that allows us to see the effects of locale and timezone
+ on some of the functions that deal with time.
+
+ Try running this program with command lines such as the following:
+
+ ./show_time
+ TZ=":Pacific/Auckland" ./show_time
+ TZ=":US/Central" ./show_time
+ TZ=":CET" ./show_time
+*/
+#include <time.h>
+#include <locale.h>
+#include "tlpi_hdr.h"
+
+#define BUF_SIZE 200
+
+int
+main(int argc, char *argv[])
+{
+ time_t t;
+ struct tm *loc;
+ char buf[BUF_SIZE];
+
+ if (setlocale(LC_ALL, "") == NULL)
+ errExit("setlocale"); /* Use locale settings in conversions */
+
+ t = time(NULL);
+
+ printf("ctime() of time() value is: %s", ctime(&t));
+
+ loc = localtime(&t);
+ if (loc == NULL)
+ errExit("localtime");
+
+ printf("asctime() of local time is: %s", asctime(loc));
+
+ if (strftime(buf, BUF_SIZE, "%A, %d %B %Y, %H:%M:%S %Z", loc) == 0)
+ fatal("strftime returned 0");
+ printf("strftime() of local time is: %s\n", buf);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 10-3 */
+
+/* strtime.c
+
+ Demonstrate the use of strptime() and strftime().
+
+ Calls strptime() using the given "format" to process the "input-date+time".
+ The conversion is then reversed by calling strftime() with the given
+ "out-format" (or a default format if this argument is omitted).
+*/
+#if ! defined(__sun)
+#ifndef _XOPEN_SOURCE
+#define _XOPEN_SOURCE
+#endif
+#endif
+#include <time.h>
+#include <locale.h>
+#include "tlpi_hdr.h"
+
+#define SBUF_SIZE 1000
+
+int
+main(int argc, char *argv[])
+{
+ struct tm tm;
+ char sbuf[SBUF_SIZE];
+ char *ofmt;
+
+ if (argc < 3 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s input-date-time in-format [out-format]\n", argv[0]);
+
+ if (setlocale(LC_ALL, "") == NULL)
+ errExit("setlocale"); /* Use locale settings in conversions */
+
+ memset(&tm, 0, sizeof(struct tm)); /* Initialize 'tm' */
+ if (strptime(argv[1], argv[2], &tm) == NULL)
+ fatal("strptime");
+
+ tm.tm_isdst = -1; /* Not set by strptime(); tells mktime()
+ to determine if DST is in effect */
+ printf("calendar time (seconds since Epoch): %ld\n", (long) mktime(&tm));
+
+ ofmt = (argc > 3) ? argv[3] : "%H:%M:%S %A, %d %B %Y %Z";
+ if (strftime(sbuf, SBUF_SIZE, ofmt, &tm) == 0)
+ fatal("strftime returned 0");
+ printf("strftime() yields: %s\n", sbuf);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 10 */
+
+/* t_stime.c
+
+ Demonstrate the use of stime() to set the system time.
+
+ Requires superuser privileges.
+*/
+#define _SVID_SOURCE /* For stime() */
+#if ! defined(__sun)
+#ifndef _XOPEN_SOURCE
+#define _XOPEN_SOURCE /* For strptime() */
+#endif
+#endif
+#include <time.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ struct tm tm;
+ time_t t;
+
+ if (argc != 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s \"DD MMM YYYY HH:MM:SS\"\n", argv[0]);
+
+ if (strptime(argv[1], "%d %b %Y %H:%M:%S", &tm) == NULL)
+ fatal("strptime failed");
+
+ t = mktime(&tm);
+ if (stime(&t) == -1)
+ errExit("stime");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE = ptmr_null_evp ptmr_sigev_signal ptmr_sigev_thread \
+ real_timer t_nanosleep timed_read
+
+LINUX_EXE = demo_timerfd t_clock_nanosleep
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+LDLIBS = ${IMPL_LDLIBS} ${LINUX_LIBRT}
+ # Many of the programs in this directory need the
+ # realtime library, librt; to keep this Makefile simple,
+ # we link *all* of the programs against that library.
+
+clean :
+ ${RM} ${EXE} *.o
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 23-8 */
+
+/* demo_timerfd.c
+
+ Demonstrate the use of the timerfd API, which creates timers whose
+ expirations can be read via a file descriptor.
+
+ This program is Linux-specific. The timerfd API is supported since kernel
+ 2.6.25. Library support is provided since glibc 2.8.
+*/
+#include <sys/timerfd.h>
+#include <time.h>
+#include <stdint.h> /* Definition of uint64_t */
+#include "itimerspec_from_str.h" /* Declares itimerspecFromStr() */
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ struct itimerspec ts;
+ struct timespec start, now;
+ int maxExp, fd, secs, nanosecs;
+ uint64_t numExp, totalExp;
+ ssize_t s;
+
+ if (argc < 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s secs[/nsecs][:int-secs[/int-nsecs]] [max-exp]\n", argv[0]);
+
+ itimerspecFromStr(argv[1], &ts);
+ maxExp = (argc > 2) ? getInt(argv[2], GN_GT_0, "max-exp") : 1;
+
+ fd = timerfd_create(CLOCK_REALTIME, 0);
+ if (fd == -1)
+ errExit("timerfd_create");
+
+ if (timerfd_settime(fd, 0, &ts, NULL) == -1)
+ errExit("timerfd_settime");
+
+ if (clock_gettime(CLOCK_MONOTONIC, &start) == -1)
+ errExit("clock_gettime");
+
+ for (totalExp = 0; totalExp < maxExp;) {
+
+ /* Read number of expirations on the timer, and then display
+ time elapsed since timer was started, followed by number
+ of expirations read and total expirations so far. */
+
+ s = read(fd, &numExp, sizeof(uint64_t));
+ if (s != sizeof(uint64_t))
+ errExit("read");
+
+ totalExp += numExp;
+
+ if (clock_gettime(CLOCK_MONOTONIC, &now) == -1)
+ errExit("clock_gettime");
+
+ secs = now.tv_sec - start.tv_sec;
+ nanosecs = now.tv_nsec - start.tv_nsec;
+ if (nanosecs < 0) {
+ secs--;
+ nanosecs += 1000000000;
+ }
+
+ printf("%d.%03d: expirations read: %llu; total=%llu\n",
+ secs, (nanosecs + 500000) / 1000000,
+ (unsigned long long) numExp, (unsigned long long) totalExp);
+ }
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 23-6 */
+
+/* itimerspec_from_str.c
+
+ Implement our itimerspecFromStr() function.
+*/
+#ifndef __APPLE__ /* Mac OS X doesn't define the 'itimerspec' structure
+ (or the POSIX timer functions (timer_*()) */
+#include <string.h>
+#include <stdlib.h>
+#include "itimerspec_from_str.h" /* Declares function defined here */
+
+/* Convert a string of the following form to an itimerspec structure:
+ "value.sec[/value.nanosec][:interval.sec[/interval.nanosec]]".
+ Optional components that are omitted cause 0 to be assigned to the
+ corresponding structure fields. */
+
+void
+itimerspecFromStr(char *str, struct itimerspec *tsp)
+{
+ char *dupstr ,*cptr, *sptr;
+
+ dupstr = strdup(str);
+
+ cptr = strchr(dupstr, ':');
+ if (cptr != NULL)
+ *cptr = '\0';
+
+ sptr = strchr(dupstr, '/');
+ if (sptr != NULL)
+ *sptr = '\0';
+
+ tsp->it_value.tv_sec = atoi(dupstr);
+ tsp->it_value.tv_nsec = (sptr != NULL) ? atoi(sptr + 1) : 0;
+
+ if (cptr == NULL) {
+ tsp->it_interval.tv_sec = 0;
+ tsp->it_interval.tv_nsec = 0;
+ } else {
+ sptr = strchr(cptr + 1, '/');
+ if (sptr != NULL)
+ *sptr = '\0';
+ tsp->it_interval.tv_sec = atoi(cptr + 1);
+ tsp->it_interval.tv_nsec = (sptr != NULL) ? atoi(sptr + 1) : 0;
+ }
+ free(dupstr);
+}
+#endif
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Header file for Listing 23-6 */
+
+/* itimerspec_from_str.h
+
+ Header file for itimerspec_from_str.c.
+*/
+#ifndef ITIMERSPEC_FROM_STR_H
+#define ITIMERSPEC_FROM_STR_H
+
+#include <time.h>
+
+void itimerspecFromStr(char *str, struct itimerspec *tsp);
+
+#endif
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 23-3 */
+
+/* ptmr_null_evp.c
+
+ A program to demonstrate POSIX timer defaults when the 'evp' argument
+ of timer_create() is specified as NULL.
+
+ Kernel support for Linux timers is provided since Linux 2.6. On older
+ systems, an incomplete user-space implementation of POSIX timers
+ was provided in glibc.
+*/
+#define _POSIX_C_SOURCE 199309
+#include <signal.h>
+#include <time.h>
+#include "curr_time.h" /* Declaration of currTime() */
+#include "tlpi_hdr.h"
+
+static void
+handler(int sig, siginfo_t *si, void *uc)
+{
+ printf("[%s] Got signal %d\n", currTime("%T"), sig);
+ printf(" sival_int = %d\n", si->si_value.sival_int);
+#ifdef __linux__
+ printf(" si_overrun = %d\n", si->si_overrun);
+#endif
+ printf(" timer_getoverrun() = %d\n",
+ timer_getoverrun((timer_t) si->si_value.sival_ptr));
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct itimerspec ts;
+ timer_t tid;
+ int j;
+ struct sigaction sa;
+
+ if (argc < 2)
+ usageErr("%s secs [nsecs [int-secs [int-nsecs]]]\n", argv[0]);
+
+ sa.sa_flags = SA_SIGINFO;
+ sa.sa_sigaction = handler;
+ sigemptyset(&sa.sa_mask);
+ if (sigaction(SIGALRM, &sa, NULL) == -1)
+ errExit("sigaction");
+
+ if (timer_create(CLOCK_REALTIME, NULL, &tid) == -1)
+ errExit("timer_create");
+ printf("timer ID = %ld\n", (long) tid);
+
+ ts.it_value.tv_sec = atoi(argv[1]);
+ ts.it_value.tv_nsec = (argc > 2) ? atoi(argv[2]) : 0;
+ ts.it_interval.tv_sec = (argc > 3) ? atoi(argv[3]) : 0;
+ ts.it_interval.tv_nsec = (argc > 4) ? atoi(argv[4]) : 0;
+ if (timer_settime(tid, 0, &ts, NULL) == -1)
+ errExit("timer_settime");
+
+ for (j = 0; ; j++)
+ pause();
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 23-5 */
+
+/* ptmr_sigev_signal.c
+
+ This program demonstrates the use of signals as the notification mechanism
+ for expirations of a POSIX timer. Each of the program's command-line
+ arguments specifies the initial value and interval for a POSIX timer. The
+ format of these arguments is defined by the function itimerspecFromStr().
+
+ The program establishes a handler for the timer notification signal, creates
+ and arms one timer for each command-line argument, and then pauses. Each
+ timer expiration causes the generation of a signal, and, when invoked, the
+ signal handler displays information about the timer expiration.
+
+ Kernel support for Linux timers is provided since Linux 2.6. On older
+ systems, an incomplete user-space implementation of POSIX timers
+ was provided in glibc.
+*/
+#define _POSIX_C_SOURCE 199309
+#include <signal.h>
+#include <time.h>
+#include "curr_time.h" /* Declares currTime() */
+#include "itimerspec_from_str.h" /* Declares itimerspecFromStr() */
+#include "tlpi_hdr.h"
+
+#define TIMER_SIG SIGRTMAX /* Our timer notification signal */
+
+static void
+handler(int sig, siginfo_t *si, void *uc)
+{
+ timer_t *tidptr;
+
+ tidptr = si->si_value.sival_ptr;
+
+ /* UNSAFE: This handler uses non-async-signal-safe functions
+ (printf(); see Section 21.1.2) */
+
+ printf("[%s] Got signal %d\n", currTime("%T"), sig);
+ printf(" *sival_ptr = %ld\n", (long) *tidptr);
+ printf(" timer_getoverrun() = %d\n", timer_getoverrun(*tidptr));
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct itimerspec ts;
+ struct sigaction sa;
+ struct sigevent sev;
+ timer_t *tidlist;
+ int j;
+
+ if (argc < 2)
+ usageErr("%s secs[/nsecs][:int-secs[/int-nsecs]]...\n", argv[0]);
+
+ tidlist = calloc(argc - 1, sizeof(timer_t));
+ if (tidlist == NULL)
+ errExit("malloc");
+
+ /* Establish handler for notification signal */
+
+ sa.sa_flags = SA_SIGINFO;
+ sa.sa_sigaction = handler;
+ sigemptyset(&sa.sa_mask);
+ if (sigaction(TIMER_SIG, &sa, NULL) == -1)
+ errExit("sigaction");
+
+ /* Create and start one timer for each command-line argument */
+
+ sev.sigev_notify = SIGEV_SIGNAL; /* Notify via signal */
+ sev.sigev_signo = TIMER_SIG; /* Notify using this signal */
+
+ for (j = 0; j < argc - 1; j++) {
+ itimerspecFromStr(argv[j + 1], &ts);
+
+ sev.sigev_value.sival_ptr = &tidlist[j];
+ /* Allows handler to get ID of this timer */
+
+ if (timer_create(CLOCK_REALTIME, &sev, &tidlist[j]) == -1)
+ errExit("timer_create");
+ printf("Timer ID: %ld (%s)\n", (long) tidlist[j], argv[j + 1]);
+
+ if (timer_settime(tidlist[j], 0, &ts, NULL) == -1)
+ errExit("timer_settime");
+ }
+
+ for (;;) /* Wait for incoming timer signals */
+ pause();
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 23-7 */
+
+/* ptmr_sigev_thread.c
+
+ This program demonstrates the use of threads as the notification mechanism
+ for expirations of a POSIX timer. Each of the program's command-line
+ arguments specifies the initial value and interval for a POSIX timer. The
+ format of these arguments is defined by the function itimerspecFromStr().
+
+ The program creates and arms one timer for each command-line argument.
+ The timer notification method is specified as SIGEV_THREAD, causing the
+ timer notifications to be delivered via a thread that invokes threadFunc()
+ as its start function. The threadFunc() function displays information
+ about the timer expiration, increments a global counter of timer expirations,
+ and signals a condition variable to indicate that the counter has changed.
+ In the main thread, a loop waits on the condition variable, and each time
+ the condition variable is signaled, the main thread prints the value of the
+ global variable that counts timer expirations.
+
+ Kernel support for Linux timers is provided since Linux 2.6. On older
+ systems, an incomplete user-space implementation of POSIX timers
+ was provided in glibc.
+*/
+#include <signal.h>
+#include <time.h>
+#include <pthread.h>
+#include "curr_time.h" /* Declares currTime() */
+#include "tlpi_hdr.h"
+#include "itimerspec_from_str.h" /* Declares itimerspecFromStr() */
+
+static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
+
+static int expireCnt = 0; /* Number of expirations of all timers */
+
+static void /* Thread notification function */
+threadFunc(union sigval sv)
+{
+ timer_t *tidptr;
+ int s;
+
+ tidptr = sv.sival_ptr;
+
+ printf("[%s] Thread notify\n", currTime("%T"));
+ printf(" timer ID=%ld\n", (long) *tidptr);
+ printf(" timer_getoverrun()=%d\n", timer_getoverrun(*tidptr));
+
+ /* Increment counter variable shared with main thread and signal
+ condition variable to notify main thread of the change. */
+
+ s = pthread_mutex_lock(&mtx);
+ if (s != 0)
+ errExitEN(s, "pthread_mutex_lock");
+
+ expireCnt += 1 + timer_getoverrun(*tidptr);
+
+ s = pthread_mutex_unlock(&mtx);
+ if (s != 0)
+ errExitEN(s, "pthread_mutex_unlock");
+
+ s = pthread_cond_signal(&cond);
+ if (s != 0)
+ errExitEN(s, "pthread_cond_signal");
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct sigevent sev;
+ struct itimerspec ts;
+ timer_t *tidlist;
+ int s, j;
+
+ if (argc < 2)
+ usageErr("%s secs[/nsecs][:int-secs[/int-nsecs]]...\n", argv[0]);
+
+ tidlist = calloc(argc - 1, sizeof(timer_t));
+ if (tidlist == NULL)
+ errExit("malloc");
+
+ sev.sigev_notify = SIGEV_THREAD; /* Notify via thread */
+ sev.sigev_notify_function = threadFunc; /* Thread start function */
+ sev.sigev_notify_attributes = NULL;
+ /* Could be pointer to pthread_attr_t structure */
+
+ /* Create and start one timer for each command-line argument */
+
+ for (j = 0; j < argc - 1; j++) {
+ itimerspecFromStr(argv[j + 1], &ts);
+
+ sev.sigev_value.sival_ptr = &tidlist[j];
+ /* Passed as argument to threadFunc() */
+
+ if (timer_create(CLOCK_REALTIME, &sev, &tidlist[j]) == -1)
+ errExit("timer_create");
+ printf("Timer ID: %ld (%s)\n", (long) tidlist[j], argv[j + 1]);
+
+ if (timer_settime(tidlist[j], 0, &ts, NULL) == -1)
+ errExit("timer_settime");
+ }
+
+ /* The main thread waits on a condition variable that is signaled
+ on each invocation of the thread notification function. We
+ print a message so that the user can see that this occurred. */
+
+ s = pthread_mutex_lock(&mtx);
+ if (s != 0)
+ errExitEN(s, "pthread_mutex_lock");
+
+ for (;;) {
+ s = pthread_cond_wait(&cond, &mtx);
+ if (s != 0)
+ errExitEN(s, "pthread_cond_wait");
+ printf("main(): expireCnt = %d\n", expireCnt);
+ }
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 23-1 */
+
+/* real_timer.c
+
+ A demonstration of the use of (real-time) timers created using setitimer().
+
+ Usage: real_timer [secs [usecs [int-secs [int-usecs]]]]
+ Defaults: 2 0 0 0
+
+ The command-line arguments are the second and microsecond settings for
+ the timer's initial value and interval.
+
+ The version of this code shown in the first print of the book contained
+ an error in the main() function whereby 'maxSigs' was initialized before
+ 'itv', even though the initialization of the former variable depends on the
+ initialization of the latter variable. See the erratum for page 983 to 984.
+*/
+#include <signal.h>
+#include <sys/time.h>
+#include <time.h>
+#include "tlpi_hdr.h"
+
+static volatile sig_atomic_t gotAlarm = 0;
+ /* Set nonzero on receipt of SIGALRM */
+
+/* Retrieve and display the real time, and (if 'includeTimer' is
+ TRUE) the current value and interval for the ITIMER_REAL timer */
+
+static void
+displayTimes(const char *msg, Boolean includeTimer)
+{
+ struct itimerval itv;
+ static struct timeval start;
+ struct timeval curr;
+ static int callNum = 0; /* Number of calls to this function */
+
+ if (callNum == 0) /* Initialize elapsed time meter */
+ if (gettimeofday(&start, NULL) == -1)
+ errExit("gettimeofday");
+
+ if (callNum % 20 == 0) /* Print header every 20 lines */
+ printf(" Elapsed Value Interval\n");
+
+ if (gettimeofday(&curr, NULL) == -1)
+ errExit("gettimeofday");
+ printf("%-7s %6.2f", msg, curr.tv_sec - start.tv_sec +
+ (curr.tv_usec - start.tv_usec) / 1000000.0);
+
+ if (includeTimer) {
+ if (getitimer(ITIMER_REAL, &itv) == -1)
+ errExit("getitimer");
+ printf(" %6.2f %6.2f",
+ itv.it_value.tv_sec + itv.it_value.tv_usec / 1000000.0,
+ itv.it_interval.tv_sec + itv.it_interval.tv_usec / 1000000.0);
+ }
+
+ printf("\n");
+ callNum++;
+}
+
+static void
+sigalrmHandler(int sig)
+{
+ gotAlarm = 1;
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct itimerval itv;
+ clock_t prevClock;
+ int maxSigs; /* Number of signals to catch before exiting */
+ int sigCnt; /* Number of signals so far caught */
+ struct sigaction sa;
+
+ if (argc > 1 && strcmp(argv[1], "--help") == 0)
+ usageErr("%s [secs [usecs [int-secs [int-usecs]]]]\n", argv[0]);
+
+ sigCnt = 0;
+
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sa.sa_handler = sigalrmHandler;
+ if (sigaction(SIGALRM, &sa, NULL) == -1)
+ errExit("sigaction");
+
+ /* Set timer from the command-line arguments */
+
+ itv.it_value.tv_sec = (argc > 1) ? getLong(argv[1], 0, "secs") : 2;
+ itv.it_value.tv_usec = (argc > 2) ? getLong(argv[2], 0, "usecs") : 0;
+ itv.it_interval.tv_sec = (argc > 3) ? getLong(argv[3], 0, "int-secs") : 0;
+ itv.it_interval.tv_usec = (argc > 4) ? getLong(argv[4], 0, "int-usecs") : 0;
+
+ /* Exit after 3 signals, or on first signal if interval is 0 */
+
+ maxSigs = (itv.it_interval.tv_sec == 0 &&
+ itv.it_interval.tv_usec == 0) ? 1 : 3;
+
+ displayTimes("START:", FALSE);
+ if (setitimer(ITIMER_REAL, &itv, NULL) == -1)
+ errExit("setitimer");
+
+ prevClock = clock();
+ sigCnt = 0;
+
+ for (;;) {
+
+ /* Inner loop consumes at least 0.5 seconds CPU time */
+
+ while (((clock() - prevClock) * 10 / CLOCKS_PER_SEC) < 5) {
+ if (gotAlarm) { /* Did we get a signal? */
+ gotAlarm = 0;
+ displayTimes("ALARM:", TRUE);
+
+ sigCnt++;
+ if (sigCnt >= maxSigs) {
+ printf("That's all folks\n");
+ exit(EXIT_SUCCESS);
+ }
+ }
+ }
+
+ prevClock = clock();
+ displayTimes("Main: ", TRUE);
+ }
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 23-2 */
+
+/* t_clock_nanosleep.c
+
+ Demonstrate the use of clock_nanosleep() to sleep for an interval
+ specified in nanoseconds.
+
+ See also t_nanosleep.c.
+
+ Linux supports clock_nanosleep() since kernel 2.6.
+*/
+#if ! defined(_XOPEN_SOURCE) || _XOPEN_SOURCE < 600
+#define _XOPEN_SOURCE 600
+#endif
+#include <sys/time.h>
+#include <time.h>
+#include <signal.h>
+#include "tlpi_hdr.h"
+
+static void
+sigintHandler(int sig)
+{
+ return; /* Just interrupt clock_nanosleep() */
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct timeval start, finish;
+ struct timespec request, remain;
+ struct sigaction sa;
+ int s, flags;
+
+ if (argc < 3 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s secs nanosecs [a]\n", argv[0]);
+
+ /* Allow SIGINT handler to interrupt clock_nanosleep() */
+
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sa.sa_handler = sigintHandler;
+ if (sigaction(SIGINT, &sa, NULL) == -1)
+ errExit("sigaction");
+
+ /* If more than three command-line arguments, use TIMER_ABSTIME flag */
+
+ flags = (argc > 3) ? TIMER_ABSTIME : 0;
+
+ if (flags == TIMER_ABSTIME) {
+ if (clock_gettime(CLOCK_REALTIME, &request) == -1)
+ errExit("clock_gettime");
+ printf("Initial CLOCK_REALTIME value: %ld.%09ld\n",
+ (long) request.tv_sec, (long) request.tv_nsec);
+
+ request.tv_sec += getLong(argv[1], 0, "secs");
+ request.tv_nsec += getLong(argv[2], 0, "nanosecs");
+ if (request.tv_nsec >= 1000000000) {
+ request.tv_sec += request.tv_nsec / 1000000000;
+ request.tv_nsec %= 1000000000;
+ }
+
+ } else { /* Relative sleep */
+ request.tv_sec = getLong(argv[1], 0, "secs");
+ request.tv_nsec = getLong(argv[2], 0, "nanosecs");
+ }
+
+ if (gettimeofday(&start, NULL) == -1)
+ errExit("gettimeofday");
+
+ for (;;) {
+ s = clock_nanosleep(CLOCK_REALTIME, flags, &request, &remain);
+ if (s != 0 && s != EINTR)
+ errExitEN(s, "clock_nanosleep");
+
+ if (s == EINTR)
+ printf("Interrupted... ");
+
+ if (gettimeofday(&finish, NULL) == -1)
+ errExit("gettimeofday");
+ printf("Slept: %.6f secs", finish.tv_sec - start.tv_sec +
+ (finish.tv_usec - start.tv_usec) / 1000000.0);
+
+ if (s == 0)
+ break; /* sleep completed */
+
+ if (flags != TIMER_ABSTIME) {
+ printf("... Remaining: %ld.%09ld",
+ (long) remain.tv_sec, remain.tv_nsec);
+
+ request = remain;
+ }
+
+ printf("... Restarting\n");
+ }
+
+ printf("\nSleep complete\n");
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 23-3 */
+
+/* t_nanosleep.c
+
+ Demonstrate the use of nanosleep() to sleep for an interval
+ specified in nanoseconds.
+
+ See also t_clock_nanosleep.c.
+*/
+#define _POSIX_C_SOURCE 199309
+#include <sys/time.h>
+#include <time.h>
+#include <signal.h>
+#include "tlpi_hdr.h"
+
+static void
+sigintHandler(int sig)
+{
+ return; /* Just interrupt nanosleep() */
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct timeval start, finish;
+ struct timespec request, remain;
+ struct sigaction sa;
+ int s;
+
+ if (argc != 3 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s secs nanosecs\n", argv[0]);
+
+ request.tv_sec = getLong(argv[1], 0, "secs");
+ request.tv_nsec = getLong(argv[2], 0, "nanosecs");
+
+ /* Allow SIGINT handler to interrupt nanosleep() */
+
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sa.sa_handler = sigintHandler;
+ if (sigaction(SIGINT, &sa, NULL) == -1)
+ errExit("sigaction");
+
+ if (gettimeofday(&start, NULL) == -1)
+ errExit("gettimeofday");
+
+ for (;;) {
+ s = nanosleep(&request, &remain);
+ if (s == -1 && errno != EINTR)
+ errExit("nanosleep");
+
+ if (gettimeofday(&finish, NULL) == -1)
+ errExit("gettimeofday");
+ printf("Slept for: %9.6f secs\n", finish.tv_sec - start.tv_sec +
+ (finish.tv_usec - start.tv_usec) / 1000000.0);
+
+ if (s == 0)
+ break; /* nanosleep() completed */
+
+ printf("Remaining: %2ld.%09ld\n", (long) remain.tv_sec, remain.tv_nsec);
+ request = remain; /* Next sleep is with remaining time */
+ }
+
+ printf("Sleep complete\n");
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 23-2 */
+
+/* timed_read.c
+
+ Demonstrate the use of a timer to place a timeout on a blocking system call
+ (read(2) in this case).
+*/
+#include <signal.h>
+#include "tlpi_hdr.h"
+
+#define BUF_SIZE 200
+
+static void /* SIGALRM handler: interrupts blocked system call */
+handler(int sig)
+{
+ printf("Caught signal\n"); /* UNSAFE (see Section 21.1.2) */
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct sigaction sa;
+ char buf[BUF_SIZE];
+ ssize_t numRead;
+ int savedErrno;
+
+ if (argc > 1 && strcmp(argv[1], "--help") == 0)
+ usageErr("%s [num-secs [restart-flag]]\n", argv[0]);
+
+ /* Set up handler for SIGALRM. Allow system calls to be interrupted,
+ unless second command-line argument was supplied. */
+
+ sa.sa_flags = (argc > 2) ? SA_RESTART : 0;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_handler = handler;
+ if (sigaction(SIGALRM, &sa, NULL) == -1)
+ errExit("sigaction");
+
+ alarm((argc > 1) ? getInt(argv[1], GN_NONNEG, "num-secs") : 10);
+
+ numRead = read(STDIN_FILENO, buf, BUF_SIZE);
+
+ savedErrno = errno; /* In case alarm() changes it */
+ alarm(0); /* Ensure timer is turned off */
+ errno = savedErrno;
+
+ /* Determine result of read() */
+
+ if (numRead == -1) {
+ if (errno == EINTR)
+ printf("Read timed out\n");
+ else
+ errMsg("read");
+ } else {
+ printf("Successful read (%ld bytes): %.*s",
+ (long) numRead, (int) numRead, buf);
+ }
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE = demo_SIGWINCH new_intr no_echo test_tty_functions
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+clean :
+ ${RM} ${EXE} *.o
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 62-5 */
+
+/* demo_SIGWINCH.c
+
+ Demonstrate the generation of the SIGWINCH signal. A handler for SIGWINCH
+ allows the program to discover terminal window size changes; when that
+ signal is generated, the program displays the new terminal window size.
+*/
+#include <signal.h>
+#include <termios.h>
+#include <sys/ioctl.h>
+#include "tlpi_hdr.h"
+
+static void
+sigwinchHandler(int sig)
+{
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct winsize ws;
+ struct sigaction sa;
+
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sa.sa_handler = sigwinchHandler;
+ if (sigaction(SIGWINCH, &sa, NULL) == -1)
+ errExit("sigaction");
+
+ for (;;) {
+ pause(); /* Wait for SIGWINCH signal */
+
+ if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == -1)
+ errExit("ioctl");
+ printf("Caught SIGWINCH, new window size: "
+ "%d rows * %d columns\n", ws.ws_row, ws.ws_col);
+ }
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 62-1 */
+
+/* new_intr.c
+
+ Demonstrate the use of tcgetattr() and tcsetattr() to change the
+ terminal INTR (interrupt) character.
+*/
+#include <termios.h>
+#include <ctype.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ struct termios tp;
+ int intrChar;
+
+ if (argc > 1 && strcmp(argv[1], "--help") == 0)
+ usageErr("%s [intr-char]\n", argv[0]);
+
+ /* Determine new INTR setting from command line */
+
+ if (argc == 1) { /* Disable */
+ intrChar = fpathconf(STDIN_FILENO, _PC_VDISABLE);
+ if (intrChar == -1)
+ errExit("Couldn't determine VDISABLE");
+ } else if (isdigit((unsigned char) argv[1][0])) {
+ intrChar = strtoul(argv[1], NULL, 0); /* Allows hex, octal */
+ } else { /* Literal character */
+ intrChar = argv[1][0];
+ }
+
+ /* Fetch current terminal settings, modify INTR character, and
+ push changes back to the terminal driver */
+
+ if (tcgetattr(STDIN_FILENO, &tp) == -1)
+ errExit("tcgetattr");
+ tp.c_cc[VINTR] = intrChar;
+ if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &tp) == -1)
+ errExit("tcsetattr");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 62-2 */
+
+/* no_echo.c
+
+ Demonstrate the use of tcgetattr() and tcsetattr() to change terminal
+ attributes. In this case, we disable echoing of terminal input.
+*/
+#include <termios.h>
+#include "tlpi_hdr.h"
+
+#define BUF_SIZE 100
+
+int
+main(int argc, char *argv[])
+{
+ struct termios tp, save;
+ char buf[BUF_SIZE];
+
+ /* Retrieve current terminal settings, turn echoing off */
+
+ if (tcgetattr(STDIN_FILENO, &tp) == -1)
+ errExit("tcgetattr");
+ save = tp; /* So we can restore settings later */
+ tp.c_lflag &= ~ECHO; /* ECHO off, other bits unchanged */
+ if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &tp) == -1)
+ errExit("tcsetattr");
+
+ /* Read some input and then display it back to the user */
+
+ printf("Enter text: ");
+ fflush(stdout);
+ if (fgets(buf, BUF_SIZE, stdin) == NULL)
+ printf("Got end-of-file/error on fgets()\n");
+ else
+ printf("\nRead: %s", buf);
+
+ /* Restore original terminal settings */
+
+ if (tcsetattr(STDIN_FILENO, TCSANOW, &save) == -1)
+ errExit("tcsetattr");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 62-4 */
+
+/* test_tty_functions.c
+
+ Put the terminal in raw mode (or cbreak if a command-line argument is
+ supplied), allowing the program to read input a character at a time, and
+ read and echo characters.
+
+ Usage: test_tty_functions [x]
+
+ See also tty_functions.c.
+*/
+
+#include <termios.h>
+#include <signal.h>
+#include <ctype.h>
+#include "tty_functions.h" /* Declarations of ttySetCbreak()
+ and ttySetRaw() */
+#include "tlpi_hdr.h"
+
+static struct termios userTermios;
+ /* Terminal settings as defined by user */
+
+static void /* General handler: restore tty settings and exit */
+handler(int sig)
+{
+ if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &userTermios) == -1)
+ errExit("tcsetattr");
+ _exit(EXIT_SUCCESS);
+}
+
+static void /* Handler for SIGTSTP */
+tstpHandler(int sig)
+{
+ struct termios ourTermios; /* To save our tty settings */
+ sigset_t tstpMask, prevMask;
+ struct sigaction sa;
+ int savedErrno;
+
+ savedErrno = errno; /* We might change 'errno' here */
+
+ /* Save current terminal settings, restore terminal to
+ state at time of program startup */
+
+ if (tcgetattr(STDIN_FILENO, &ourTermios) == -1)
+ errExit("tcgetattr");
+ if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &userTermios) == -1)
+ errExit("tcsetattr");
+
+ /* Set the disposition of SIGTSTP to the default, raise the signal
+ once more, and then unblock it so that we actually stop */
+
+ if (signal(SIGTSTP, SIG_DFL) == SIG_ERR)
+ errExit("signal");
+ raise(SIGTSTP);
+
+ sigemptyset(&tstpMask);
+ sigaddset(&tstpMask, SIGTSTP);
+ if (sigprocmask(SIG_UNBLOCK, &tstpMask, &prevMask) == -1)
+ errExit("sigprocmask");
+
+ /* Execution resumes here after SIGCONT */
+
+ if (sigprocmask(SIG_SETMASK, &prevMask, NULL) == -1)
+ errExit("sigprocmask"); /* Reblock SIGTSTP */
+
+ sigemptyset(&sa.sa_mask); /* Reestablish handler */
+ sa.sa_flags = SA_RESTART;
+ sa.sa_handler = tstpHandler;
+ if (sigaction(SIGTSTP, &sa, NULL) == -1)
+ errExit("sigaction");
+
+ /* The user may have changed the terminal settings while we were
+ stopped; save the settings so we can restore them later */
+
+ if (tcgetattr(STDIN_FILENO, &userTermios) == -1)
+ errExit("tcgetattr");
+
+ /* Restore our terminal settings */
+
+ if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &ourTermios) == -1)
+ errExit("tcsetattr");
+
+ errno = savedErrno;
+}
+
+int
+main(int argc, char *argv[])
+{
+ char ch;
+ struct sigaction sa, prev;
+ ssize_t n;
+
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+
+ if (argc > 1) { /* Use cbreak mode */
+ if (ttySetCbreak(STDIN_FILENO, &userTermios) == -1)
+ errExit("ttySetCbreak");
+
+ /* Terminal special characters can generate signals in cbreak
+ mode. Catch them so that we can adjust the terminal mode.
+ We establish handlers only if the signals are not being ignored. */
+
+ sa.sa_handler = handler;
+
+ if (sigaction(SIGQUIT, NULL, &prev) == -1)
+ errExit("sigaction");
+ if (prev.sa_handler != SIG_IGN)
+ if (sigaction(SIGQUIT, &sa, NULL) == -1)
+ errExit("sigaction");
+
+ if (sigaction(SIGINT, NULL, &prev) == -1)
+ errExit("sigaction");
+ if (prev.sa_handler != SIG_IGN)
+ if (sigaction(SIGINT, &sa, NULL) == -1)
+ errExit("sigaction");
+
+ sa.sa_handler = tstpHandler;
+
+ if (sigaction(SIGTSTP, NULL, &prev) == -1)
+ errExit("sigaction");
+ if (prev.sa_handler != SIG_IGN)
+ if (sigaction(SIGTSTP, &sa, NULL) == -1)
+ errExit("sigaction");
+ } else { /* Use raw mode */
+ if (ttySetRaw(STDIN_FILENO, &userTermios) == -1)
+ errExit("ttySetRaw");
+ }
+
+ sa.sa_handler = handler;
+ if (sigaction(SIGTERM, &sa, NULL) == -1)
+ errExit("sigaction");
+
+ setbuf(stdout, NULL); /* Disable stdout buffering */
+
+ for (;;) { /* Read and echo stdin */
+ n = read(STDIN_FILENO, &ch, 1);
+ if (n == -1) {
+ errMsg("read");
+ break;
+ }
+
+ if (n == 0) /* Can occur after terminal disconnect */
+ break;
+
+ if (isalpha((unsigned char) ch)) /* Letters --> lowercase */
+ putchar(tolower((unsigned char) ch));
+ else if (ch == '\n' || ch == '\r')
+ putchar(ch);
+ else if (iscntrl((unsigned char) ch))
+ printf("^%c", ch ^ 64); /* Echo Control-A as ^A, etc. */
+ else
+ putchar('*'); /* All other chars as '*' */
+
+ if (ch == 'q') /* Quit loop */
+ break;
+ }
+
+ if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &userTermios) == -1)
+ errExit("tcsetattr");
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 62-3 */
+
+/* tty_functions.c
+
+ Implement ttySetCbreak() and ttySetRaw().
+*/
+#include <termios.h>
+#include <unistd.h>
+#include "tty_functions.h" /* Declares functions defined here */
+
+/* Place terminal referred to by 'fd' in cbreak mode (noncanonical mode
+ with echoing turned off). This function assumes that the terminal is
+ currently in cooked mode (i.e., we shouldn't call it if the terminal
+ is currently in raw mode, since it does not undo all of the changes
+ made by the ttySetRaw() function below). Return 0 on success, or -1
+ on error. If 'prevTermios' is non-NULL, then use the buffer to which
+ it points to return the previous terminal settings. */
+
+int
+ttySetCbreak(int fd, struct termios *prevTermios)
+{
+ struct termios t;
+
+ if (tcgetattr(fd, &t) == -1)
+ return -1;
+
+ if (prevTermios != NULL)
+ *prevTermios = t;
+
+ t.c_lflag &= ~(ICANON | ECHO);
+ t.c_lflag |= ISIG;
+
+ t.c_iflag &= ~ICRNL;
+
+ t.c_cc[VMIN] = 1; /* Character-at-a-time input */
+ t.c_cc[VTIME] = 0; /* with blocking */
+
+ if (tcsetattr(fd, TCSAFLUSH, &t) == -1)
+ return -1;
+
+ return 0;
+}
+
+/* Place terminal referred to by 'fd' in raw mode (noncanonical mode
+ with all input and output processing disabled). Return 0 on success,
+ or -1 on error. If 'prevTermios' is non-NULL, then use the buffer to
+ which it points to return the previous terminal settings. */
+
+int
+ttySetRaw(int fd, struct termios *prevTermios)
+{
+ struct termios t;
+
+ if (tcgetattr(fd, &t) == -1)
+ return -1;
+
+ if (prevTermios != NULL)
+ *prevTermios = t;
+
+ t.c_lflag &= ~(ICANON | ISIG | IEXTEN | ECHO);
+ /* Noncanonical mode, disable signals, extended
+ input processing, and echoing */
+
+ t.c_iflag &= ~(BRKINT | ICRNL | IGNBRK | IGNCR | INLCR |
+ INPCK | ISTRIP | IXON | PARMRK);
+ /* Disable special handling of CR, NL, and BREAK.
+ No 8th-bit stripping or parity error handling.
+ Disable START/STOP output flow control. */
+
+ t.c_oflag &= ~OPOST; /* Disable all output processing */
+
+ t.c_cc[VMIN] = 1; /* Character-at-a-time input */
+ t.c_cc[VTIME] = 0; /* with blocking */
+
+ if (tcsetattr(fd, TCSAFLUSH, &t) == -1)
+ return -1;
+
+ return 0;
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Header file for Listing 62-3 */
+
+/* tty_functions.h
+
+ Header file for tty_functions.c.
+*/
+#ifndef TTY_FUNCTIONS_H
+#define TTY_FUNCTIONS_H
+
+#include <termios.h>
+
+int ttySetCbreak(int fd, struct termios *prevTermios);
+
+int ttySetRaw(int fd, struct termios *prevTermios);
+
+#endif
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 62-2 */
+
+/* ttyname.c
+
+ An implementation of ttyname(3).
+*/
+#include <dirent.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+/* Helper function for ttyname(). We do most of the real work here.
+ Look in 'devDir' for the terminal device name corresponding to the
+ stat structure given in 'fdStat' (which the caller must ensure
+ derives from a terminal device).
+
+ Return the device name (as a statically-allocated) string if
+ found, or NULL if the device is not found or an error occurs */
+
+static char *
+ttynameCheckDir(const struct stat *fdStat, const char *devDir)
+{
+ DIR *dirh;
+ struct dirent *dent;
+ static char *ttyPath; /* Currently checked entry; also used
+ to return tty name, if found */
+ static int ttyLen = 0; /* Length of ttyPath */
+ struct stat devStat; /* stat entry for ttyPath */
+ int found; /* True if we find device entry */
+ int requiredLen;
+
+ if (ttyLen == 0) { /* First call - allocate ttyPath */
+ ttyPath = malloc(50);
+ if (ttyPath == NULL)
+ return NULL;
+ ttyLen = 50;
+ }
+
+ dirh = opendir(devDir);
+ if (dirh == NULL)
+ return NULL;
+
+ /* We walk through each file in /dev looking for an entry whose
+ device ID (st_rdev) matches the device ID corresponding to 'fd'.
+ To do this, we construct a pathname for each entry in /dev, and
+ then call stat() on that pathname. This is somewhat expensive.
+ The glibc implementation of ttyname() performs an optimization:
+ it performs a first pass of the entries in /dev, performing
+ a stat() call only if fdStat->st_ino == dent->d_ino. This speeds
+ the search (since many calls to stat() are avoided), but is not
+ guaranteed to work in every case (e.g., if there are symbolic
+ links in /dev). Therefore, if the first pass fails to find a
+ matching device, glibc's ttyname() performs a second pass
+ without the st_ino check (i.e., like we do below). */
+
+ found = 0;
+ while ((dent = readdir(dirh)) != NULL) {
+ requiredLen = strlen(devDir) + 1 + strlen(dent->d_name) + 1;
+
+ if (requiredLen > ttyLen) { /* Resize ttyPath if required */
+ ttyPath = realloc(ttyPath, requiredLen);
+ if (ttyPath == NULL)
+ break;
+ ttyLen = requiredLen;
+ }
+
+ snprintf(ttyPath, ttyLen, "%s/%s", devDir, dent->d_name);
+
+ if (stat(ttyPath, &devStat) == -1)
+ continue; /* Ignore unstat-able entries */
+
+ if (S_ISCHR(devStat.st_mode) &&
+ fdStat->st_rdev == devStat.st_rdev) {
+ found = 1;
+ break;
+ }
+ }
+
+ closedir(dirh);
+
+ return found ? ttyPath : NULL;
+}
+
+/* Return the name of the terminal associated with 'fd' (in a
+ statically-allocated string that is overwritten on subsequent
+ calls), or NULL if it is not a terminal or an error occurs */
+
+char *
+ttyname(int fd)
+{
+ char *d;
+ struct stat fdStat; /* stat entry for fd */
+
+ if (!isatty(fd)) /* Is fd even a terminal? */
+ return NULL;
+
+ if (fstat(fd, &fdStat) == -1)
+ return NULL;
+
+ if (!S_ISCHR(fdStat.st_mode)) /* Is fd even a character device? */
+ return NULL;
+
+ /* First check for a pseudoterminal entry in the /dev/pts
+ directory. If that fails, try looking in /dev.
+ (We check the directories in this order for efficiency:
+ /dev/pts is small, and will contain the required device if
+ fd refers to an X-terminal or similar.) */
+
+ d = ttynameCheckDir(&fdStat, "/dev/pts");
+ return (d != NULL) ? d : ttynameCheckDir(&fdStat, "/dev");
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE = t_getpwent t_getpwnam_r
+
+LINUX_EXE = check_password idshow
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+clean :
+ ${RM} ${EXE} *.o
+
+check_password : check_password.o
+ ${CC} -o $@ check_password.o ${LDFLAGS} ${LDLIBS} ${LINUX_LIBCRYPT}
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 8-2 */
+
+/* check_password.c
+
+ Read a user name and password and check if they are valid.
+
+ This program uses the shadow password file. Some UNIX implementations
+ don't support this feature.
+*/
+/* Compile with -lcrypt */
+#if ! defined(__sun)
+#define _BSD_SOURCE /* Get getpass() declaration from <unistd.h> */
+#ifndef _XOPEN_SOURCE
+#define _XOPEN_SOURCE /* Get crypt() declaration from <unistd.h> */
+#endif
+#endif
+#include <unistd.h>
+#include <limits.h>
+#include <pwd.h>
+#include <shadow.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ char *username, *password, *encrypted, *p;
+ struct passwd *pwd;
+ struct spwd *spwd;
+ Boolean authOk;
+ size_t len;
+ long lnmax;
+
+ /* Determine size of buffer required for a username, and allocate it */
+
+ lnmax = sysconf(_SC_LOGIN_NAME_MAX);
+ if (lnmax == -1) /* If limit is indeterminate */
+ lnmax = 256; /* make a guess */
+
+ username = malloc(lnmax);
+ if (username == NULL)
+ errExit("malloc");
+
+ printf("Username: ");
+ fflush(stdout);
+ if (fgets(username, lnmax, stdin) == NULL)
+ exit(EXIT_FAILURE); /* Exit on EOF */
+
+ len = strlen(username);
+ if (username[len - 1] == '\n')
+ username[len - 1] = '\0'; /* Remove trailing '\n' */
+
+ /* Look up password and shadow password records for username */
+
+ pwd = getpwnam(username);
+ if (pwd == NULL)
+ fatal("couldn't get password record");
+ spwd = getspnam(username);
+ if (spwd == NULL && errno == EACCES)
+ fatal("no permission to read shadow password file");
+
+ if (spwd != NULL) /* If there is a shadow password record */
+ pwd->pw_passwd = spwd->sp_pwdp; /* Use the shadow password */
+
+ password = getpass("Password: ");
+
+ /* Encrypt password and erase cleartext version immediately */
+
+ encrypted = crypt(password, pwd->pw_passwd);
+ for (p = password; *p != '\0'; )
+ *p++ = '\0';
+
+ if (encrypted == NULL)
+ errExit("crypt");
+
+ authOk = strcmp(encrypted, pwd->pw_passwd) == 0;
+ if (!authOk) {
+ printf("Incorrect password\n");
+ exit(EXIT_FAILURE);
+ }
+
+ printf("Successfully authenticated: UID=%ld\n", (long) pwd->pw_uid);
+
+ /* Now do authenticated work... */
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 8 */
+
+/* t_getpwent.c
+
+ Demonstrate the use of getpwent() to retrieve records from the system
+ password file.
+*/
+#include <pwd.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ struct passwd *pwd;
+
+ while ((pwd = getpwent()) != NULL)
+ printf("%-8s %5ld\n", pwd->pw_name, (long) pwd->pw_uid);
+ endpwent();
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 8 */
+
+/* t_getpwnam_r.c
+
+ Demonstrate the use of getpwnam_r() to retrieve the password record for
+ a named user from the system password file.
+*/
+#include <pwd.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ struct passwd pwd;
+ struct passwd *result;
+ char *buf;
+ size_t bufSize;
+ int s;
+
+ if (argc != 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s username\n", argv[0]);
+
+ bufSize = sysconf(_SC_GETPW_R_SIZE_MAX);
+ buf = malloc(bufSize);
+ if (buf == NULL)
+ errExit("malloc %d", bufSize);
+
+ s = getpwnam_r(argv[1], &pwd, buf, bufSize, &result);
+ if (s != 0)
+ errExitEN(s, "getpwnam_r");
+
+ if (result != NULL)
+ printf("Name: %s\n", pwd.pw_gecos);
+ else
+ printf("Not found\n");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 8-1 */
+
+/* ugid_functions.c
+
+ Implements a set of functions that convert user/group names to user/group IDs
+ and vice versa.
+*/
+#include <pwd.h>
+#include <grp.h>
+#include <ctype.h>
+#include "ugid_functions.h" /* Declares functions defined here */
+
+char * /* Return name corresponding to 'uid', or NULL on error */
+userNameFromId(uid_t uid)
+{
+ struct passwd *pwd;
+
+ pwd = getpwuid(uid);
+ return (pwd == NULL) ? NULL : pwd->pw_name;
+}
+
+uid_t /* Return UID corresponding to 'name', or -1 on error */
+userIdFromName(const char *name)
+{
+ struct passwd *pwd;
+ uid_t u;
+ char *endptr;
+
+ if (name == NULL || *name == '\0') /* On NULL or empty string */
+ return -1; /* return an error */
+
+ u = strtol(name, &endptr, 10); /* As a convenience to caller */
+ if (*endptr == '\0') /* allow a numeric string */
+ return u;
+
+ pwd = getpwnam(name);
+ if (pwd == NULL)
+ return -1;
+
+ return pwd->pw_uid;
+}
+
+char * /* Return name corresponding to 'gid', or NULL on error */
+groupNameFromId(gid_t gid)
+{
+ struct group *grp;
+
+ grp = getgrgid(gid);
+ return (grp == NULL) ? NULL : grp->gr_name;
+}
+
+gid_t /* Return GID corresponding to 'name', or -1 on error */
+groupIdFromName(const char *name)
+{
+ struct group *grp;
+ gid_t g;
+ char *endptr;
+
+ if (name == NULL || *name == '\0') /* On NULL or empty string */
+ return -1; /* return an error */
+
+ g = strtol(name, &endptr, 10); /* As a convenience to caller */
+ if (*endptr == '\0') /* allow a numeric string */
+ return g;
+
+ grp = getgrnam(name);
+ if (grp == NULL)
+ return -1;
+
+ return grp->gr_gid;
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU Lesser General Public License as published *
+* by the Free Software Foundation, either version 3 or (at your option) *
+* any later version. This program is distributed without any warranty. *
+* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Header file for Listing 8-1 */
+
+/* ugid_functions.h
+
+ Header file for ugid_functions.c.
+*/
+#ifndef UGID_FUNCTIONS_H
+#define UGID_FUNCTIONS_H
+
+#include "tlpi_hdr.h"
+
+char *userNameFromId(uid_t uid);
+
+uid_t userIdFromName(const char *name);
+
+char *groupNameFromId(gid_t gid);
+
+gid_t groupIdFromName(const char *name);
+
+#endif
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE = memlock madvise_dontneed
+
+LINUX_EXE = t_mprotect
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+clean :
+ ${RM} ${EXE} *.o
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Solution for Exercise 50-2 */
+
+/* madvise_dontneed.c
+
+ Demonstrate the "destructive" semantics of the madvise() MADV_DONTNEED
+ operation. On Linux, when MAD_DONTNEED is applied to a MAP_PRIVATE mapping,
+ the pages (and thus any modifications to the pages) are discarded; when
+ next accessed, the pages are reinitialized from the underlying file.
+
+ NOTE: MADV_DONTNEED is not destructive on some UNIX implementations.
+
+ The mincore() system call is supported on Linux since kernel 2.4.
+*/
+#ifdef __linux__
+#define _BSD_SOURCE
+#endif
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+#define MAP_SIZE 4096
+#define WRITE_SIZE 10
+
+int
+main(int argc, char *argv[])
+{
+ char *addr;
+ int fd, j;
+
+ setbuf(stdout, NULL);
+
+ if (argc != 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s file\n", argv[0]);
+
+ unlink(argv[1]);
+ fd = open(argv[1], O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
+ if (fd == -1)
+ errExit("open");
+
+ for (j = 0; j < MAP_SIZE; j++)
+ write(fd, "a", 1);
+ if (fsync(fd) == -1)
+ errExit("fsync");
+ close(fd);
+
+ fd = open(argv[1], O_RDWR);
+ if (fd == -1)
+ errExit("open");
+
+ addr = mmap(NULL, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
+ if (addr == MAP_FAILED)
+ errExit("mmap");
+
+ printf("After mmap: ");
+ write(STDOUT_FILENO, addr, WRITE_SIZE);
+ printf("\n");
+
+ /* Copy-on-write semantics mean that the following modification
+ will create private copies of the pages for this process */
+
+ for (j = 0; j < MAP_SIZE; j++)
+ addr[j]++;
+
+ printf("After modification: ");
+ write(STDOUT_FILENO, addr, WRITE_SIZE);
+ printf("\n");
+
+ /* After the following, the mapping contents revert to the original file
+ contents (if MADV_DONTNEED has destructive semantics, as on Linux) */
+
+ if (madvise(addr, MAP_SIZE, MADV_DONTNEED) == -1)
+ errExit("madvise");
+
+ printf("After MADV_DONTNEED: ");
+ write(STDOUT_FILENO, addr, WRITE_SIZE);
+ printf("\n");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 50-2 */
+
+/* memlock.c
+
+ Demonstrate the use of mlock(), to place memory locks, and mincore(), to
+ retrieve memory-residence information about the calling processes virtual
+ memory pages.
+
+ Note: some UNIX implementations do not provide mincore().
+
+ The madvise() system call is upported on Linux since kernel 2.4.
+*/
+#define _BSD_SOURCE /* Get mincore() declaration and MAP_ANONYMOUS
+ definition from <sys/mman.h> */
+#include <sys/mman.h>
+#include "tlpi_hdr.h"
+
+/* Display residency of pages in range [addr .. (addr + length - 1)] */
+
+static void
+displayMincore(char *addr, size_t length)
+{
+ unsigned char *vec;
+ long pageSize, numPages, j;
+
+#ifndef _SC_PAGESIZE
+ pageSize = getpagesize(); /* Some systems don't have _SC_PAGESIZE */
+#else
+ pageSize = sysconf(_SC_PAGESIZE);
+#endif
+
+ numPages = (length + pageSize - 1) / pageSize;
+ vec = malloc(numPages);
+ if (vec == NULL)
+ errExit("malloc");
+
+ if (mincore(addr, length, vec) == -1)
+ errExit("mincore");
+
+ for (j = 0; j < numPages; j++) {
+ if (j % 64 == 0)
+ printf("%s%10p: ", (j == 0) ? "" : "\n", addr + (j * pageSize));
+ printf("%c", (vec[j] & 1) ? '*' : '.');
+ }
+ printf("\n");
+
+ free(vec);
+}
+
+int
+main(int argc, char *argv[])
+{
+ char *addr;
+ size_t len, lockLen;
+ long pageSize, stepSize, j;
+
+ if (argc != 4 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s num-pages lock-page-step lock-page-len\n", argv[0]);
+
+#ifndef _SC_PAGESIZE
+ pageSize = getpagesize();
+ if (pageSize == -1)
+ errExit("getpagesize");
+#else
+ pageSize = sysconf(_SC_PAGESIZE);
+ if (pageSize == -1)
+ errExit("sysconf(_SC_PAGESIZE)");
+#endif
+
+ len = getInt(argv[1], GN_GT_0, "num-pages") * pageSize;
+ stepSize = getInt(argv[2], GN_GT_0, "lock-page-step") * pageSize;
+ lockLen = getInt(argv[3], GN_GT_0, "lock-page-len") * pageSize;
+
+ addr = mmap(NULL, len, PROT_READ, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
+ if (addr == MAP_FAILED)
+ errExit("mmap");
+
+ printf("Allocated %ld (%#lx) bytes starting at %p\n",
+ (long) len, (unsigned long) len, addr);
+
+ printf("Before mlock:\n");
+ displayMincore(addr, len);
+
+ /* Lock pages specified by command-line arguments into memory */
+
+ for (j = 0; j + lockLen <= len; j += stepSize)
+ if (mlock(addr + j, lockLen) == -1)
+ errExit("mlock");
+
+ printf("After mlock:\n");
+ displayMincore(addr, len);
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 50-1 */
+
+/* t_mprotect.c
+
+ Demonstrate the use of mprotect() to change the protection on
+ a region of memory.
+
+ This program is Linux-specific.
+*/
+#define _BSD_SOURCE /* Get MAP_ANONYMOUS definition from <sys/mman.h> */
+#include <sys/mman.h>
+#include "tlpi_hdr.h"
+
+#define LEN (1024 * 1024)
+
+#define SHELL_FMT "cat /proc/%ld/maps | grep zero"
+#define CMD_SIZE (sizeof(SHELL_FMT) + 20)
+ /* Allow extra space for integer string */
+
+int
+main(int argc, char *argv[])
+{
+ char cmd[CMD_SIZE];
+ char *addr;
+
+ /* Create an anonymous mapping with all access denied */
+
+ addr = mmap(NULL, LEN, PROT_NONE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
+ if (addr == MAP_FAILED)
+ errExit("mmap");
+
+ /* Display line from /proc/self/maps corresponding to mapping */
+
+ printf("Before mprotect()\n");
+ snprintf(cmd, CMD_SIZE, SHELL_FMT, (long) getpid());
+ system(cmd);
+
+ /* Change protection on memory to allow read and write access */
+
+ if (mprotect(addr, LEN, PROT_READ | PROT_WRITE) == -1)
+ errExit("mprotect");
+
+ printf("After mprotect()\n");
+ system(cmd); /* Review protection via /proc/self/maps */
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+include ../Makefile.inc
+
+GEN_EXE =
+
+LINUX_EXE = t_setxattr xattr_view
+
+EXE = ${GEN_EXE} ${LINUX_EXE}
+
+all : ${EXE}
+
+allgen : ${GEN_EXE}
+
+clean :
+ ${RM} ${EXE} *.o
+
+showall :
+ @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB} # True as a rough approximation
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Supplementary program for Chapter 16 */
+
+/* t_setxattr.c
+
+ Demonstrate the use of setxattr() to set a file extended attribute.
+
+ This program is Linux (2.6 and later) specific.
+
+ See also view_xattr.c.
+*/
+#include <sys/xattr.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+ char *value;
+
+ if (argc < 2 || strcmp(argv[1], "--help") == 0)
+ usageErr("%s file\n", argv[0]);
+
+ value = "The past is not dead.";
+ if (setxattr(argv[1], "user.x", value, strlen(value), 0) == -1)
+ errExit("setxattr");
+
+ value = "In fact, it's not even past.";
+ if (setxattr(argv[1], "user.y", value, strlen(value), 0) == -1)
+ errExit("setxattr");
+
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*************************************************************************\
+* Copyright (C) Michael Kerrisk, 2017. *
+* *
+* This program is free software. You may use, modify, and redistribute it *
+* under the terms of the GNU General Public License as published by the *
+* Free Software Foundation, either version 3 or (at your option) any *
+* later version. This program is distributed without any warranty. See *
+* the file COPYING.gpl-v3 for details. *
+\*************************************************************************/
+
+/* Listing 16-1 */
+
+/* view_xattr.c
+
+ Display the extended attributes of a file.
+
+ This program is Linux (2.6 and later) specific.
+
+ See also t_setxattr.c.
+*/
+#include <sys/xattr.h>
+#include "tlpi_hdr.h"
+
+#define XATTR_SIZE 10000
+
+static void
+usageError(char *progName)
+{
+ fprintf(stderr, "Usage: %s [-x] file...\n", progName);
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ char list[XATTR_SIZE], value[XATTR_SIZE];
+ ssize_t listLen, valueLen;
+ int ns, j, k, opt;
+ Boolean hexDisplay;
+
+ hexDisplay = 0;
+ while ((opt = getopt(argc, argv, "x")) != -1) {
+ switch (opt) {
+ case 'x': hexDisplay = 1; break;
+ case '?': usageError(argv[0]);
+ }
+ }
+
+ if (optind >= argc)
+ usageError(argv[0]);
+
+ for (j = optind; j < argc; j++) {
+ listLen = listxattr(argv[j], list, XATTR_SIZE);
+ if (listLen == -1)
+ errExit("listxattr");
+
+ printf("%s:\n", argv[j]);
+
+ /* Loop through all EA names, displaying name + value */
+
+ for (ns = 0; ns < listLen; ns += strlen(&list[ns]) + 1) {
+ printf(" name=%s; ", &list[ns]);
+
+ valueLen = getxattr(argv[j], &list[ns], value, XATTR_SIZE);
+ if (valueLen == -1) {
+ printf("couldn't get value");
+ } else if (!hexDisplay) {
+ printf("value=%.*s", (int) valueLen, value);
+ } else {
+ printf("value=");
+ for (k = 0; k < valueLen; k++)
+ printf("%02x ", (unsigned int) value[k]);
+ }
+
+ printf("\n");
+ }
+
+ printf("\n");
+ }
+
+ exit(EXIT_SUCCESS);
+}