From: mcc Date: Thu, 19 Oct 2017 16:25:04 +0000 (+0000) Subject: My initial commit message X-Git-Url: https://git.halfball.org/?a=commitdiff_plain;h=290fc52c9ef8c995059923451587880de0a46de6;p=tlpi.git My initial commit message --- 290fc52c9ef8c995059923451587880de0a46de6 diff --git a/BUILDING b/BUILDING new file mode 100644 index 0000000..848275a --- /dev/null +++ b/BUILDING @@ -0,0 +1,146 @@ +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. diff --git a/CHANGES b/CHANGES new file mode 100644 index 0000000..20c0c77 --- /dev/null +++ b/CHANGES @@ -0,0 +1,888 @@ +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 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 + usageErr(). + 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 instead of + 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. diff --git a/COPYING.agpl-v3 b/COPYING.agpl-v3 new file mode 100644 index 0000000..dba13ed --- /dev/null +++ b/COPYING.agpl-v3 @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + 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. + + + Copyright (C) + + 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 . + +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 +. diff --git a/COPYING.gpl-v3 b/COPYING.gpl-v3 new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/COPYING.gpl-v3 @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + 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. + + + Copyright (C) + + 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 . + +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: + + Copyright (C) + 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 +. + + 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 +. diff --git a/COPYING.lgpl-v3 b/COPYING.lgpl-v3 new file mode 100644 index 0000000..65c5ca8 --- /dev/null +++ b/COPYING.lgpl-v3 @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + 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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..df80ca8 --- /dev/null +++ b/Makefile @@ -0,0 +1,47 @@ +# 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 diff --git a/Makefile.inc b/Makefile.inc new file mode 100644 index 0000000..70e8614 --- /dev/null +++ b/Makefile.inc @@ -0,0 +1,41 @@ +# 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 diff --git a/Makefile.inc.FreeBSD b/Makefile.inc.FreeBSD new file mode 100644 index 0000000..bd0aafc --- /dev/null +++ b/Makefile.inc.FreeBSD @@ -0,0 +1,15 @@ +# 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 diff --git a/Makefile.inc.HP-UX b/Makefile.inc.HP-UX new file mode 100644 index 0000000..f3a1c12 --- /dev/null +++ b/Makefile.inc.HP-UX @@ -0,0 +1,22 @@ +# 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 diff --git a/Makefile.inc.MacOSX b/Makefile.inc.MacOSX new file mode 100644 index 0000000..4f8a3c3 --- /dev/null +++ b/Makefile.inc.MacOSX @@ -0,0 +1,15 @@ +# 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 diff --git a/Makefile.inc.Solaris b/Makefile.inc.Solaris new file mode 100644 index 0000000..ad11885 --- /dev/null +++ b/Makefile.inc.Solaris @@ -0,0 +1,15 @@ +# 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 diff --git a/Makefile.inc.Tru64 b/Makefile.inc.Tru64 new file mode 100644 index 0000000..1348fa8 --- /dev/null +++ b/Makefile.inc.Tru64 @@ -0,0 +1,16 @@ +# 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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..c10d11f --- /dev/null +++ b/README.md @@ -0,0 +1,172 @@ +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/ diff --git a/acl/Makefile b/acl/Makefile new file mode 100644 index 0000000..ef35fae --- /dev/null +++ b/acl/Makefile @@ -0,0 +1,23 @@ +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 diff --git a/acl/acl_update.c b/acl/acl_update.c new file mode 100644 index 0000000..3ce667b --- /dev/null +++ b/acl/acl_update.c @@ -0,0 +1,453 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/acl/acl_view.c b/acl/acl_view.c new file mode 100644 index 0000000..85d90ee --- /dev/null +++ b/acl/acl_view.c @@ -0,0 +1,144 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/altio/Makefile b/altio/Makefile new file mode 100644 index 0000000..83bdd37 --- /dev/null +++ b/altio/Makefile @@ -0,0 +1,19 @@ +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 diff --git a/altio/demo_sigio.c b/altio/demo_sigio.c new file mode 100644 index 0000000..27a7fa3 --- /dev/null +++ b/altio/demo_sigio.c @@ -0,0 +1,90 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include +#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); +} diff --git a/altio/epoll_input.c b/altio/epoll_input.c new file mode 100644 index 0000000..4aeb6f6 --- /dev/null +++ b/altio/epoll_input.c @@ -0,0 +1,113 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/altio/poll_pipes.c b/altio/poll_pipes.c new file mode 100644 index 0000000..3b7ae34 --- /dev/null +++ b/altio/poll_pipes.c @@ -0,0 +1,91 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/altio/select_mq.c b/altio/select_mq.c new file mode 100644 index 0000000..ceb7dd1 --- /dev/null +++ b/altio/select_mq.c @@ -0,0 +1,160 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#if ! defined(__hpux) +/* HP-UX 11 doesn't have this header file */ +#include +#endif +#include +#include +#include +#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); + } + } +} diff --git a/altio/self_pipe.c b/altio/self_pipe.c new file mode 100644 index 0000000..781fe30 --- /dev/null +++ b/altio/self_pipe.c @@ -0,0 +1,153 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#if ! defined(__hpux) /* HP-UX 11 doesn't have this header file */ +#include +#endif +#include +#include +#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); +} diff --git a/altio/t_select.c b/altio/t_select.c new file mode 100644 index 0000000..db16a6f --- /dev/null +++ b/altio/t_select.c @@ -0,0 +1,99 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#if ! defined(__hpux) +/* HP-UX 11 doesn't have this header file */ +#include +#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); +} diff --git a/cap/Makefile b/cap/Makefile new file mode 100644 index 0000000..7615286 --- /dev/null +++ b/cap/Makefile @@ -0,0 +1,22 @@ +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 diff --git a/cap/check_password_caps.c b/cap/check_password_caps.c new file mode 100644 index 0000000..ab5a61a --- /dev/null +++ b/cap/check_password_caps.c @@ -0,0 +1,184 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 */ +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE /* Get crypt() declaration from */ +#endif +#include +#include +#include +#include +#include +#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); +} diff --git a/daemons/Makefile b/daemons/Makefile new file mode 100644 index 0000000..bdf9747 --- /dev/null +++ b/daemons/Makefile @@ -0,0 +1,17 @@ +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 diff --git a/daemons/become_daemon.c b/daemons/become_daemon.c new file mode 100644 index 0000000..986fc9e --- /dev/null +++ b/daemons/become_daemon.c @@ -0,0 +1,71 @@ +/*************************************************************************\ +* 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 +#include +#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; +} diff --git a/daemons/become_daemon.h b/daemons/become_daemon.h new file mode 100644 index 0000000..096bd52 --- /dev/null +++ b/daemons/become_daemon.h @@ -0,0 +1,33 @@ +/*************************************************************************\ +* 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 diff --git a/daemons/daemon_SIGHUP.c b/daemons/daemon_SIGHUP.c new file mode 100644 index 0000000..a63383f --- /dev/null +++ b/daemons/daemon_SIGHUP.c @@ -0,0 +1,163 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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 +#include + +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 */ + } + } +} diff --git a/daemons/t_syslog.c b/daemons/t_syslog.c new file mode 100644 index 0000000..93ff56e --- /dev/null +++ b/daemons/t_syslog.c @@ -0,0 +1,82 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/daemons/test_become_daemon.c b/daemons/test_become_daemon.c new file mode 100644 index 0000000..df4650c --- /dev/null +++ b/daemons/test_become_daemon.c @@ -0,0 +1,30 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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); +} diff --git a/dirs_links/Makefile b/dirs_links/Makefile new file mode 100644 index 0000000..8a880b5 --- /dev/null +++ b/dirs_links/Makefile @@ -0,0 +1,20 @@ +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 diff --git a/dirs_links/bad_symlink.c b/dirs_links/bad_symlink.c new file mode 100644 index 0000000..79b57a6 --- /dev/null +++ b/dirs_links/bad_symlink.c @@ -0,0 +1,44 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/dirs_links/file_type_stats.c b/dirs_links/file_type_stats.c new file mode 100644 index 0000000..50fd3d4 --- /dev/null +++ b/dirs_links/file_type_stats.c @@ -0,0 +1,91 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/dirs_links/list_files.c b/dirs_links/list_files.c new file mode 100644 index 0000000..5a6f967 --- /dev/null +++ b/dirs_links/list_files.c @@ -0,0 +1,81 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 */ +#include +#endif +#include +#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); +} diff --git a/dirs_links/list_files_readdir_r.c b/dirs_links/list_files_readdir_r.c new file mode 100644 index 0000000..72f7856 --- /dev/null +++ b/dirs_links/list_files_readdir_r.c @@ -0,0 +1,99 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 */ +#include +#endif +#include +#include +#include +#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 . 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); +} diff --git a/dirs_links/nftw_dir_tree.c b/dirs_links/nftw_dir_tree.c new file mode 100644 index 0000000..484d141 --- /dev/null +++ b/dirs_links/nftw_dir_tree.c @@ -0,0 +1,101 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/dirs_links/t_dirbasename.c b/dirs_links/t_dirbasename.c new file mode 100644 index 0000000..90c7d2b --- /dev/null +++ b/dirs_links/t_dirbasename.c @@ -0,0 +1,47 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/dirs_links/t_unlink.c b/dirs_links/t_unlink.c new file mode 100644 index 0000000..d71cc72 --- /dev/null +++ b/dirs_links/t_unlink.c @@ -0,0 +1,73 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/dirs_links/view_symlink.c b/dirs_links/view_symlink.c new file mode 100644 index 0000000..c347920 --- /dev/null +++ b/dirs_links/view_symlink.c @@ -0,0 +1,55 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include /* 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); +} diff --git a/filebuff/Makefile b/filebuff/Makefile new file mode 100644 index 0000000..c444579 --- /dev/null +++ b/filebuff/Makefile @@ -0,0 +1,32 @@ +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 diff --git a/filebuff/copy.c b/filebuff/copy.c new file mode 100644 index 0000000..b407ef0 --- /dev/null +++ b/filebuff/copy.c @@ -0,0 +1,63 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/filebuff/direct_read.c b/filebuff/direct_read.c new file mode 100644 index 0000000..e512f91 --- /dev/null +++ b/filebuff/direct_read.c @@ -0,0 +1,70 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 */ +#include +#include +#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); +} diff --git a/filebuff/mix23_linebuff.c b/filebuff/mix23_linebuff.c new file mode 100644 index 0000000..37ad820 --- /dev/null +++ b/filebuff/mix23_linebuff.c @@ -0,0 +1,31 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include + +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); +} diff --git a/filebuff/mix23io.c b/filebuff/mix23io.c new file mode 100644 index 0000000..0168678 --- /dev/null +++ b/filebuff/mix23io.c @@ -0,0 +1,35 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include + +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); +} diff --git a/filebuff/write_bytes.c b/filebuff/write_bytes.c new file mode 100644 index 0000000..3f1d113 --- /dev/null +++ b/filebuff/write_bytes.c @@ -0,0 +1,82 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/fileio/Makefile b/fileio/Makefile new file mode 100644 index 0000000..7fc92aa --- /dev/null +++ b/fileio/Makefile @@ -0,0 +1,21 @@ +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 diff --git a/fileio/atomic_append.c b/fileio/atomic_append.c new file mode 100644 index 0000000..ec92046 --- /dev/null +++ b/fileio/atomic_append.c @@ -0,0 +1,66 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/fileio/bad_exclusive_open.c b/fileio/bad_exclusive_open.c new file mode 100644 index 0000000..34c8e9e --- /dev/null +++ b/fileio/bad_exclusive_open.c @@ -0,0 +1,64 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/fileio/copy.c b/fileio/copy.c new file mode 100644 index 0000000..b407ef0 --- /dev/null +++ b/fileio/copy.c @@ -0,0 +1,63 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/fileio/large_file.c b/fileio/large_file.c new file mode 100644 index 0000000..d6eaa5b --- /dev/null +++ b/fileio/large_file.c @@ -0,0 +1,44 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/fileio/multi_descriptors.c b/fileio/multi_descriptors.c new file mode 100644 index 0000000..bad3f34 --- /dev/null +++ b/fileio/multi_descriptors.c @@ -0,0 +1,65 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/fileio/seek_io.c b/fileio/seek_io.c new file mode 100644 index 0000000..c57c386 --- /dev/null +++ b/fileio/seek_io.c @@ -0,0 +1,114 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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|R|w|s}... + + 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 Read 'length' bytes from the file at current + file offset, displaying them as text. + + R Read 'length' bytes from the file at current + file offset, displaying them in hex. + + w Write 'string' at current file offset. + + s Set the file offset to 'offset'. + + Example: + + seek_io myfile wxyz s1 r2 +*/ +#include +#include +#include +#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|R|w|s}...\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); +} diff --git a/fileio/t_readv.c b/fileio/t_readv.c new file mode 100644 index 0000000..278bd01 --- /dev/null +++ b/fileio/t_readv.c @@ -0,0 +1,68 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#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); +} diff --git a/fileio/t_truncate.c b/fileio/t_truncate.c new file mode 100644 index 0000000..a5ebe81 --- /dev/null +++ b/fileio/t_truncate.c @@ -0,0 +1,30 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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); +} diff --git a/filelock/Makefile b/filelock/Makefile new file mode 100644 index 0000000..2a56a21 --- /dev/null +++ b/filelock/Makefile @@ -0,0 +1,17 @@ +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 diff --git a/filelock/create_pid_file.c b/filelock/create_pid_file.c new file mode 100644 index 0000000..3c322b4 --- /dev/null +++ b/filelock/create_pid_file.c @@ -0,0 +1,81 @@ +/*************************************************************************\ +* 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 +#include +#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; +} diff --git a/filelock/create_pid_file.h b/filelock/create_pid_file.h new file mode 100644 index 0000000..030910d --- /dev/null +++ b/filelock/create_pid_file.h @@ -0,0 +1,24 @@ +/*************************************************************************\ +* 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 diff --git a/filelock/i_fcntl_locking.c b/filelock/i_fcntl_locking.c new file mode 100644 index 0000000..ea496f4 --- /dev/null +++ b/filelock/i_fcntl_locking.c @@ -0,0 +1,219 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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"); + } + } +} diff --git a/filelock/region_locking.c b/filelock/region_locking.c new file mode 100644 index 0000000..d53e3de --- /dev/null +++ b/filelock/region_locking.c @@ -0,0 +1,64 @@ +/*************************************************************************\ +* 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 +#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; +} diff --git a/filelock/region_locking.h b/filelock/region_locking.h new file mode 100644 index 0000000..e406b2e --- /dev/null +++ b/filelock/region_locking.h @@ -0,0 +1,28 @@ +/*************************************************************************\ +* 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 + +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 diff --git a/filelock/t_flock.c b/filelock/t_flock.c new file mode 100644 index 0000000..ccbaf06 --- /dev/null +++ b/filelock/t_flock.c @@ -0,0 +1,66 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/files/Makefile b/files/Makefile new file mode 100644 index 0000000..95e030c --- /dev/null +++ b/files/Makefile @@ -0,0 +1,19 @@ +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 diff --git a/files/chiflag.c b/files/chiflag.c new file mode 100644 index 0000000..cc772ce --- /dev/null +++ b/files/chiflag.c @@ -0,0 +1,111 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 from + a kernel earlier than 2.6.19. You'll instead need to do something + like including and replacing all of the FS_* names + below by EXT2_*. + + This program is Linux-specific. +*/ +#define _GNU_SOURCE +#include +#include +#include +#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); +} diff --git a/files/file_perms.c b/files/file_perms.c new file mode 100644 index 0000000..76e436a --- /dev/null +++ b/files/file_perms.c @@ -0,0 +1,49 @@ +/*************************************************************************\ +* 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 +#include +#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; +} diff --git a/files/file_perms.h b/files/file_perms.h new file mode 100644 index 0000000..c0d8993 --- /dev/null +++ b/files/file_perms.h @@ -0,0 +1,27 @@ +/*************************************************************************\ +* 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 + +#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 diff --git a/files/t_chown.c b/files/t_chown.c new file mode 100644 index 0000000..31405a5 --- /dev/null +++ b/files/t_chown.c @@ -0,0 +1,69 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/files/t_stat.c b/files/t_stat.c new file mode 100644 index 0000000..88b0348 --- /dev/null +++ b/files/t_stat.c @@ -0,0 +1,111 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#if defined(_AIX) +#define _BSD +#endif +#if defined(__sgi) || defined(__sun) /* Some systems need this */ +#include /* To get major() and minor() */ +#endif +#if defined(__hpux) /* Other systems need this */ +#include +#endif +#include +#include +#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); +} diff --git a/files/t_umask.c b/files/t_umask.c new file mode 100644 index 0000000..7e0642b --- /dev/null +++ b/files/t_umask.c @@ -0,0 +1,62 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/files/t_utime.c b/files/t_utime.c new file mode 100644 index 0000000..07fd9a1 --- /dev/null +++ b/files/t_utime.c @@ -0,0 +1,47 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/files/t_utimes.c b/files/t_utimes.c new file mode 100644 index 0000000..7c57769 --- /dev/null +++ b/files/t_utimes.c @@ -0,0 +1,48 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/filesys/Makefile b/filesys/Makefile new file mode 100644 index 0000000..153bd57 --- /dev/null +++ b/filesys/Makefile @@ -0,0 +1,19 @@ +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 diff --git a/filesys/t_mount.c b/filesys/t_mount.c new file mode 100644 index 0000000..de0dbbe --- /dev/null +++ b/filesys/t_mount.c @@ -0,0 +1,166 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/filesys/t_statfs.c b/filesys/t_statfs.c new file mode 100644 index 0000000..4d547b2 --- /dev/null +++ b/filesys/t_statfs.c @@ -0,0 +1,56 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/filesys/t_statvfs.c b/filesys/t_statvfs.c new file mode 100644 index 0000000..413e1a2 --- /dev/null +++ b/filesys/t_statvfs.c @@ -0,0 +1,53 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/filesys/t_umount.c b/filesys/t_umount.c new file mode 100644 index 0000000..2437378 --- /dev/null +++ b/filesys/t_umount.c @@ -0,0 +1,34 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/getopt/Makefile b/getopt/Makefile new file mode 100644 index 0000000..8017c22 --- /dev/null +++ b/getopt/Makefile @@ -0,0 +1,19 @@ +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 diff --git a/getopt/t_getopt.c b/getopt/t_getopt.c new file mode 100644 index 0000000..e38b885 --- /dev/null +++ b/getopt/t_getopt.c @@ -0,0 +1,68 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/inotify/Makefile b/inotify/Makefile new file mode 100644 index 0000000..ba92cd0 --- /dev/null +++ b/inotify/Makefile @@ -0,0 +1,21 @@ +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 diff --git a/inotify/demo_inotify.c b/inotify/demo_inotify.c new file mode 100644 index 0000000..86f7931 --- /dev/null +++ b/inotify/demo_inotify.c @@ -0,0 +1,108 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/inotify/dnotify.c b/inotify/dnotify.c new file mode 100644 index 0000000..7bc9c13 --- /dev/null +++ b/inotify/dnotify.c @@ -0,0 +1,116 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 */ +#include +#include +#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 */ +} diff --git a/inotify/inotify_dtree.c b/inotify/inotify_dtree.c new file mode 100644 index 0000000..552167a --- /dev/null +++ b/inotify/inotify_dtree.c @@ -0,0 +1,1487 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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); +} diff --git a/inotify/rand_dtree.c b/inotify/rand_dtree.c new file mode 100644 index 0000000..cfcda18 --- /dev/null +++ b/inotify/rand_dtree.c @@ -0,0 +1,270 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#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); +} diff --git a/lib/Build_ename.sh b/lib/Build_ename.sh new file mode 100644 index 0000000..d2eb014 --- /dev/null +++ b/lib/Build_ename.sh @@ -0,0 +1,53 @@ +#!/bin/sh +# +# Create a new version of the file ename.c.inc by parsing symbolic +# error names defined in errno.h +# +echo '#include ' | 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; +} +' + diff --git a/lib/Makefile b/lib/Makefile new file mode 100644 index 0000000..63abd13 --- /dev/null +++ b/lib/Makefile @@ -0,0 +1,27 @@ +# 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} + diff --git a/lib/README b/lib/README new file mode 100644 index 0000000..1815ba2 --- /dev/null +++ b/lib/README @@ -0,0 +1,7 @@ +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. diff --git a/lib/alt_functions.c b/lib/alt_functions.c new file mode 100644 index 0000000..9cca72c --- /dev/null +++ b/lib/alt_functions.c @@ -0,0 +1,57 @@ +/*************************************************************************\ +* 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 +#include +#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); +} diff --git a/lib/alt_functions.h b/lib/alt_functions.h new file mode 100644 index 0000000..84e33e8 --- /dev/null +++ b/lib/alt_functions.h @@ -0,0 +1,36 @@ +/*************************************************************************\ +* 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 diff --git a/lib/become_daemon.c b/lib/become_daemon.c new file mode 120000 index 0000000..55c4906 --- /dev/null +++ b/lib/become_daemon.c @@ -0,0 +1 @@ +../daemons/become_daemon.c \ No newline at end of file diff --git a/lib/become_daemon.h b/lib/become_daemon.h new file mode 120000 index 0000000..9dd5369 --- /dev/null +++ b/lib/become_daemon.h @@ -0,0 +1 @@ +../daemons/become_daemon.h \ No newline at end of file diff --git a/lib/binary_sems.c b/lib/binary_sems.c new file mode 120000 index 0000000..85ed9d0 --- /dev/null +++ b/lib/binary_sems.c @@ -0,0 +1 @@ +../svsem/binary_sems.c \ No newline at end of file diff --git a/lib/binary_sems.h b/lib/binary_sems.h new file mode 120000 index 0000000..3fdebc9 --- /dev/null +++ b/lib/binary_sems.h @@ -0,0 +1 @@ +../svsem/binary_sems.h \ No newline at end of file diff --git a/lib/create_pid_file.c b/lib/create_pid_file.c new file mode 120000 index 0000000..e5c27e0 --- /dev/null +++ b/lib/create_pid_file.c @@ -0,0 +1 @@ +../filelock/create_pid_file.c \ No newline at end of file diff --git a/lib/create_pid_file.h b/lib/create_pid_file.h new file mode 120000 index 0000000..2af3faf --- /dev/null +++ b/lib/create_pid_file.h @@ -0,0 +1 @@ +../filelock/create_pid_file.h \ No newline at end of file diff --git a/lib/curr_time.c b/lib/curr_time.c new file mode 120000 index 0000000..de4facc --- /dev/null +++ b/lib/curr_time.c @@ -0,0 +1 @@ +../time/curr_time.c \ No newline at end of file diff --git a/lib/curr_time.h b/lib/curr_time.h new file mode 120000 index 0000000..d690613 --- /dev/null +++ b/lib/curr_time.h @@ -0,0 +1 @@ +../time/curr_time.h \ No newline at end of file diff --git a/lib/error_functions.c b/lib/error_functions.c new file mode 100644 index 0000000..a9eb2eb --- /dev/null +++ b/lib/error_functions.c @@ -0,0 +1,201 @@ +/*************************************************************************\ +* 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 +#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); +} diff --git a/lib/error_functions.h b/lib/error_functions.h new file mode 100644 index 0000000..af01bb7 --- /dev/null +++ b/lib/error_functions.h @@ -0,0 +1,47 @@ +/*************************************************************************\ +* 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 diff --git a/lib/event_flags.c b/lib/event_flags.c new file mode 120000 index 0000000..98d2c03 --- /dev/null +++ b/lib/event_flags.c @@ -0,0 +1 @@ +../svsem/event_flags.c \ No newline at end of file diff --git a/lib/event_flags.h b/lib/event_flags.h new file mode 120000 index 0000000..43daafb --- /dev/null +++ b/lib/event_flags.h @@ -0,0 +1 @@ +../svsem/event_flags.h \ No newline at end of file diff --git a/lib/file_perms.c b/lib/file_perms.c new file mode 120000 index 0000000..92cd6d1 --- /dev/null +++ b/lib/file_perms.c @@ -0,0 +1 @@ +../files/file_perms.c \ No newline at end of file diff --git a/lib/file_perms.h b/lib/file_perms.h new file mode 120000 index 0000000..66e8f1b --- /dev/null +++ b/lib/file_perms.h @@ -0,0 +1 @@ +../files/file_perms.h \ No newline at end of file diff --git a/lib/get_num.c b/lib/get_num.c new file mode 100644 index 0000000..60c2586 --- /dev/null +++ b/lib/get_num.c @@ -0,0 +1,103 @@ +/*************************************************************************\ +* 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 +#include +#include +#include +#include +#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; +} diff --git a/lib/get_num.h b/lib/get_num.h new file mode 100644 index 0000000..8a3e403 --- /dev/null +++ b/lib/get_num.h @@ -0,0 +1,32 @@ +/*************************************************************************\ +* 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 diff --git a/lib/inet_sockets.c b/lib/inet_sockets.c new file mode 120000 index 0000000..34c76ad --- /dev/null +++ b/lib/inet_sockets.c @@ -0,0 +1 @@ +../sockets/inet_sockets.c \ No newline at end of file diff --git a/lib/inet_sockets.h b/lib/inet_sockets.h new file mode 120000 index 0000000..2b549c9 --- /dev/null +++ b/lib/inet_sockets.h @@ -0,0 +1 @@ +../sockets/inet_sockets.h \ No newline at end of file diff --git a/lib/itimerspec_from_str.c b/lib/itimerspec_from_str.c new file mode 120000 index 0000000..ff6c4d8 --- /dev/null +++ b/lib/itimerspec_from_str.c @@ -0,0 +1 @@ +../timers/itimerspec_from_str.c \ No newline at end of file diff --git a/lib/itimerspec_from_str.h b/lib/itimerspec_from_str.h new file mode 120000 index 0000000..3db4bf8 --- /dev/null +++ b/lib/itimerspec_from_str.h @@ -0,0 +1 @@ +../timers/itimerspec_from_str.h \ No newline at end of file diff --git a/lib/print_rlimit.c b/lib/print_rlimit.c new file mode 120000 index 0000000..4b71968 --- /dev/null +++ b/lib/print_rlimit.c @@ -0,0 +1 @@ +../procres/print_rlimit.c \ No newline at end of file diff --git a/lib/print_rlimit.h b/lib/print_rlimit.h new file mode 120000 index 0000000..d33bdc6 --- /dev/null +++ b/lib/print_rlimit.h @@ -0,0 +1 @@ +../procres/print_rlimit.h \ No newline at end of file diff --git a/lib/print_rusage.c b/lib/print_rusage.c new file mode 120000 index 0000000..95c7a66 --- /dev/null +++ b/lib/print_rusage.c @@ -0,0 +1 @@ +../procres/print_rusage.c \ No newline at end of file diff --git a/lib/print_rusage.h b/lib/print_rusage.h new file mode 120000 index 0000000..b862ed5 --- /dev/null +++ b/lib/print_rusage.h @@ -0,0 +1 @@ +../procres/print_rusage.h \ No newline at end of file diff --git a/lib/print_wait_status.c b/lib/print_wait_status.c new file mode 120000 index 0000000..d2b2cb7 --- /dev/null +++ b/lib/print_wait_status.c @@ -0,0 +1 @@ +../procexec/print_wait_status.c \ No newline at end of file diff --git a/lib/print_wait_status.h b/lib/print_wait_status.h new file mode 120000 index 0000000..50a5c48 --- /dev/null +++ b/lib/print_wait_status.h @@ -0,0 +1 @@ +../procexec/print_wait_status.h \ No newline at end of file diff --git a/lib/pty_fork.c b/lib/pty_fork.c new file mode 120000 index 0000000..7b17eb2 --- /dev/null +++ b/lib/pty_fork.c @@ -0,0 +1 @@ +../pty/pty_fork.c \ No newline at end of file diff --git a/lib/pty_fork.h b/lib/pty_fork.h new file mode 120000 index 0000000..37e65c5 --- /dev/null +++ b/lib/pty_fork.h @@ -0,0 +1 @@ +../pty/pty_fork.h \ No newline at end of file diff --git a/lib/pty_master_open.c b/lib/pty_master_open.c new file mode 120000 index 0000000..4b8ea49 --- /dev/null +++ b/lib/pty_master_open.c @@ -0,0 +1 @@ +../pty/pty_master_open.c \ No newline at end of file diff --git a/lib/pty_master_open.h b/lib/pty_master_open.h new file mode 120000 index 0000000..265c5e0 --- /dev/null +++ b/lib/pty_master_open.h @@ -0,0 +1 @@ +../pty/pty_master_open.h \ No newline at end of file diff --git a/lib/rdwrn.c b/lib/rdwrn.c new file mode 120000 index 0000000..5f7da7f --- /dev/null +++ b/lib/rdwrn.c @@ -0,0 +1 @@ +../sockets/rdwrn.c \ No newline at end of file diff --git a/lib/rdwrn.h b/lib/rdwrn.h new file mode 120000 index 0000000..fabebea --- /dev/null +++ b/lib/rdwrn.h @@ -0,0 +1 @@ +../sockets/rdwrn.h \ No newline at end of file diff --git a/lib/read_line.c b/lib/read_line.c new file mode 120000 index 0000000..5f672cd --- /dev/null +++ b/lib/read_line.c @@ -0,0 +1 @@ +../sockets/read_line.c \ No newline at end of file diff --git a/lib/read_line.h b/lib/read_line.h new file mode 120000 index 0000000..5b48b9c --- /dev/null +++ b/lib/read_line.h @@ -0,0 +1 @@ +../sockets/read_line.h \ No newline at end of file diff --git a/lib/read_line_buf.c b/lib/read_line_buf.c new file mode 120000 index 0000000..f29efc5 --- /dev/null +++ b/lib/read_line_buf.c @@ -0,0 +1 @@ +../sockets/read_line_buf.c \ No newline at end of file diff --git a/lib/read_line_buf.h b/lib/read_line_buf.h new file mode 120000 index 0000000..3d1f3a9 --- /dev/null +++ b/lib/read_line_buf.h @@ -0,0 +1 @@ +../sockets/read_line_buf.h \ No newline at end of file diff --git a/lib/region_locking.c b/lib/region_locking.c new file mode 120000 index 0000000..3bcd617 --- /dev/null +++ b/lib/region_locking.c @@ -0,0 +1 @@ +../filelock/region_locking.c \ No newline at end of file diff --git a/lib/region_locking.h b/lib/region_locking.h new file mode 120000 index 0000000..c2c93bc --- /dev/null +++ b/lib/region_locking.h @@ -0,0 +1 @@ +../filelock/region_locking.h \ No newline at end of file diff --git a/lib/semun.h b/lib/semun.h new file mode 120000 index 0000000..4a321dd --- /dev/null +++ b/lib/semun.h @@ -0,0 +1 @@ +../svsem/semun.h \ No newline at end of file diff --git a/lib/signal.c b/lib/signal.c new file mode 120000 index 0000000..9e80e9d --- /dev/null +++ b/lib/signal.c @@ -0,0 +1 @@ +../signals/signal.c \ No newline at end of file diff --git a/lib/signal_functions.c b/lib/signal_functions.c new file mode 120000 index 0000000..b11cb69 --- /dev/null +++ b/lib/signal_functions.c @@ -0,0 +1 @@ +../signals/signal_functions.c \ No newline at end of file diff --git a/lib/signal_functions.h b/lib/signal_functions.h new file mode 120000 index 0000000..b388134 --- /dev/null +++ b/lib/signal_functions.h @@ -0,0 +1 @@ +../signals/signal_functions.h \ No newline at end of file diff --git a/lib/tlpi_hdr.h b/lib/tlpi_hdr.h new file mode 100644 index 0000000..db27371 --- /dev/null +++ b/lib/tlpi_hdr.h @@ -0,0 +1,83 @@ +/*************************************************************************\ +* 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 /* Type definitions used by many programs */ +#include /* Standard I/O functions */ +#include /* Prototypes of commonly used library functions, + plus EXIT_SUCCESS and EXIT_FAILURE constants */ +#include /* Prototypes for many system calls */ +#include /* Declares errno and defines error constants */ +#include /* 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 /* 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 diff --git a/lib/tty_functions.c b/lib/tty_functions.c new file mode 120000 index 0000000..4aaadb2 --- /dev/null +++ b/lib/tty_functions.c @@ -0,0 +1 @@ +../tty/tty_functions.c \ No newline at end of file diff --git a/lib/tty_functions.h b/lib/tty_functions.h new file mode 120000 index 0000000..cda31e3 --- /dev/null +++ b/lib/tty_functions.h @@ -0,0 +1 @@ +../tty/tty_functions.h \ No newline at end of file diff --git a/lib/ugid_functions.c b/lib/ugid_functions.c new file mode 120000 index 0000000..84ef2f6 --- /dev/null +++ b/lib/ugid_functions.c @@ -0,0 +1 @@ +../users_groups/ugid_functions.c \ No newline at end of file diff --git a/lib/ugid_functions.h b/lib/ugid_functions.h new file mode 120000 index 0000000..e2a2227 --- /dev/null +++ b/lib/ugid_functions.h @@ -0,0 +1 @@ +../users_groups/ugid_functions.h \ No newline at end of file diff --git a/lib/unix_sockets.c b/lib/unix_sockets.c new file mode 120000 index 0000000..20cd954 --- /dev/null +++ b/lib/unix_sockets.c @@ -0,0 +1 @@ +../sockets/unix_sockets.c \ No newline at end of file diff --git a/lib/unix_sockets.h b/lib/unix_sockets.h new file mode 120000 index 0000000..6dd44a9 --- /dev/null +++ b/lib/unix_sockets.h @@ -0,0 +1 @@ +../sockets/unix_sockets.h \ No newline at end of file diff --git a/loginacct/Makefile b/loginacct/Makefile new file mode 100644 index 0000000..3f9b820 --- /dev/null +++ b/loginacct/Makefile @@ -0,0 +1,19 @@ +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 diff --git a/loginacct/dump_utmpx.c b/loginacct/dump_utmpx.c new file mode 100644 index 0000000..6358fdc --- /dev/null +++ b/loginacct/dump_utmpx.c @@ -0,0 +1,76 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include +#include +#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); +} diff --git a/loginacct/utmpx_login.c b/loginacct/utmpx_login.c new file mode 100644 index 0000000..375637d --- /dev/null +++ b/loginacct/utmpx_login.c @@ -0,0 +1,90 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include /* 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); +} diff --git a/loginacct/view_lastlog.c b/loginacct/view_lastlog.c new file mode 100644 index 0000000..cecfd9b --- /dev/null +++ b/loginacct/view_lastlog.c @@ -0,0 +1,62 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include /* Definition of _PATH_LASTLOG */ +#include +#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); +} diff --git a/memalloc/Makefile b/memalloc/Makefile new file mode 100644 index 0000000..8b10bee --- /dev/null +++ b/memalloc/Makefile @@ -0,0 +1,19 @@ +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 diff --git a/memalloc/free_and_sbrk.c b/memalloc/free_and_sbrk.c new file mode 100644 index 0000000..a1c7c46 --- /dev/null +++ b/memalloc/free_and_sbrk.c @@ -0,0 +1,73 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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); +} diff --git a/mmap/Makefile b/mmap/Makefile new file mode 100644 index 0000000..ff17e0e --- /dev/null +++ b/mmap/Makefile @@ -0,0 +1,19 @@ +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 diff --git a/mmap/anon_mmap.c b/mmap/anon_mmap.c new file mode 100644 index 0000000..7c6ec8c --- /dev/null +++ b/mmap/anon_mmap.c @@ -0,0 +1,76 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#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); + } +} diff --git a/mmap/mmcat.c b/mmap/mmcat.c new file mode 100644 index 0000000..23e8000 --- /dev/null +++ b/mmap/mmcat.c @@ -0,0 +1,56 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#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); +} diff --git a/mmap/mmcopy.c b/mmap/mmcopy.c new file mode 100644 index 0000000..b9eeb2e --- /dev/null +++ b/mmap/mmcopy.c @@ -0,0 +1,70 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#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); +} diff --git a/mmap/t_mmap.c b/mmap/t_mmap.c new file mode 100644 index 0000000..5169eb0 --- /dev/null +++ b/mmap/t_mmap.c @@ -0,0 +1,59 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/mmap/t_remap_file_pages.c b/mmap/t_remap_file_pages.c new file mode 100644 index 0000000..e5af21d --- /dev/null +++ b/mmap/t_remap_file_pages.c @@ -0,0 +1,74 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 */ +#include +#include +#include +#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); +} diff --git a/namespaces/Makefile b/namespaces/Makefile new file mode 100644 index 0000000..30aa024 --- /dev/null +++ b/namespaces/Makefile @@ -0,0 +1,47 @@ +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 diff --git a/namespaces/demo_userns.c b/namespaces/demo_userns.c new file mode 100644 index 0000000..436bd33 --- /dev/null +++ b/namespaces/demo_userns.c @@ -0,0 +1,82 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include +#include +#include + +#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); +} diff --git a/namespaces/demo_uts_namespaces.c b/namespaces/demo_uts_namespaces.c new file mode 100644 index 0000000..cd81140 --- /dev/null +++ b/namespaces/demo_uts_namespaces.c @@ -0,0 +1,104 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include +#include +#include +#include + +/* 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 \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); +} diff --git a/namespaces/hostname.c b/namespaces/hostname.c new file mode 100644 index 0000000..a1b1fef --- /dev/null +++ b/namespaces/hostname.c @@ -0,0 +1,48 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include +#include +#include + +#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); +} diff --git a/namespaces/multi_pidns.c b/namespaces/multi_pidns.c new file mode 100644 index 0000000..18af453 --- /dev/null +++ b/namespaces/multi_pidns.c @@ -0,0 +1,115 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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); +} diff --git a/namespaces/ns_child_exec.c b/namespaces/ns_child_exec.c new file mode 100644 index 0000000..245b19c --- /dev/null +++ b/namespaces/ns_child_exec.c @@ -0,0 +1,124 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include +#include +#include + +#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); +} diff --git a/namespaces/ns_exec.c b/namespaces/ns_exec.c new file mode 100644 index 0000000..4f12765 --- /dev/null +++ b/namespaces/ns_exec.c @@ -0,0 +1,56 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include +#include + +#include + +/* 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"); +} diff --git a/namespaces/ns_run.c b/namespaces/ns_run.c new file mode 100644 index 0000000..0bb26d5 --- /dev/null +++ b/namespaces/ns_run.c @@ -0,0 +1,108 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include +#include +#include + +/* 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"); +} diff --git a/namespaces/orphan.c b/namespaces/orphan.c new file mode 100644 index 0000000..e151a87 --- /dev/null +++ b/namespaces/orphan.c @@ -0,0 +1,59 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include + +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); +} diff --git a/namespaces/pidns_init_sleep.c b/namespaces/pidns_init_sleep.c new file mode 100644 index 0000000..3732fe0 --- /dev/null +++ b/namespaces/pidns_init_sleep.c @@ -0,0 +1,82 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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); +} diff --git a/namespaces/simple_init.c b/namespaces/simple_init.c new file mode 100644 index 0000000..29a0494 --- /dev/null +++ b/namespaces/simple_init.c @@ -0,0 +1,261 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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); +} diff --git a/namespaces/t_setns_userns.c b/namespaces/t_setns_userns.c new file mode 100644 index 0000000..91b8dc8 --- /dev/null +++ b/namespaces/t_setns_userns.c @@ -0,0 +1,63 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include +#include +#include + +#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); +} diff --git a/namespaces/unshare.c b/namespaces/unshare.c new file mode 100644 index 0000000..e55fb13 --- /dev/null +++ b/namespaces/unshare.c @@ -0,0 +1,99 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include +#include + +#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"); +} diff --git a/namespaces/userns_child_exec.c b/namespaces/userns_child_exec.c new file mode 100644 index 0000000..16cc50b --- /dev/null +++ b/namespaces/userns_child_exec.c @@ -0,0 +1,339 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 1' -G '0 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); +} diff --git a/namespaces/userns_overview.go b/namespaces/userns_overview.go new file mode 100644 index 0000000..24f84b7 --- /dev/null +++ b/namespaces/userns_overview.go @@ -0,0 +1,241 @@ +/* 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) +} diff --git a/namespaces/userns_setns_test.c b/namespaces/userns_setns_test.c new file mode 100644 index 0000000..e6ed206 --- /dev/null +++ b/namespaces/userns_setns_test.c @@ -0,0 +1,136 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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); +} diff --git a/pgsjc/Makefile b/pgsjc/Makefile new file mode 100644 index 0000000..ee389c7 --- /dev/null +++ b/pgsjc/Makefile @@ -0,0 +1,18 @@ +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 diff --git a/pgsjc/catch_SIGHUP.c b/pgsjc/catch_SIGHUP.c new file mode 100644 index 0000000..62e2041 --- /dev/null +++ b/pgsjc/catch_SIGHUP.c @@ -0,0 +1,62 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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()); + } +} diff --git a/pgsjc/disc_SIGHUP.c b/pgsjc/disc_SIGHUP.c new file mode 100644 index 0000000..e10f3de --- /dev/null +++ b/pgsjc/disc_SIGHUP.c @@ -0,0 +1,82 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 */ +#include +#include +#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 */ +} diff --git a/pgsjc/handling_SIGTSTP.c b/pgsjc/handling_SIGTSTP.c new file mode 100644 index 0000000..c1d8079 --- /dev/null +++ b/pgsjc/handling_SIGTSTP.c @@ -0,0 +1,82 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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"); + } +} diff --git a/pgsjc/job_mon.c b/pgsjc/job_mon.c new file mode 100644 index 0000000..9a089f7 --- /dev/null +++ b/pgsjc/job_mon.c @@ -0,0 +1,107 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 */ +#include +#include +#include +#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(); +} diff --git a/pgsjc/orphaned_pgrp_SIGHUP.c b/pgsjc/orphaned_pgrp_SIGHUP.c new file mode 100644 index 0000000..e5a054a --- /dev/null +++ b/pgsjc/orphaned_pgrp_SIGHUP.c @@ -0,0 +1,102 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 */ +#include +#include +#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 */ +} diff --git a/pgsjc/t_setsid.c b/pgsjc/t_setsid.c new file mode 100644 index 0000000..0a5a9c6 --- /dev/null +++ b/pgsjc/t_setsid.c @@ -0,0 +1,41 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/pipes/Makefile b/pipes/Makefile new file mode 100644 index 0000000..be7463d --- /dev/null +++ b/pipes/Makefile @@ -0,0 +1,20 @@ +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 diff --git a/pipes/change_case.c b/pipes/change_case.c new file mode 100644 index 0000000..848bbd9 --- /dev/null +++ b/pipes/change_case.c @@ -0,0 +1,101 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); + } +} diff --git a/pipes/fifo_seqnum.h b/pipes/fifo_seqnum.h new file mode 100644 index 0000000..ebd1a78 --- /dev/null +++ b/pipes/fifo_seqnum.h @@ -0,0 +1,46 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#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 */ +}; diff --git a/pipes/fifo_seqnum_client.c b/pipes/fifo_seqnum_client.c new file mode 100644 index 0000000..d481cd5 --- /dev/null +++ b/pipes/fifo_seqnum_client.c @@ -0,0 +1,83 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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); +} diff --git a/pipes/fifo_seqnum_server.c b/pipes/fifo_seqnum_server.c new file mode 100644 index 0000000..9c80f3c --- /dev/null +++ b/pipes/fifo_seqnum_server.c @@ -0,0 +1,91 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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 */ + } +} diff --git a/pipes/pipe_ls_wc.c b/pipes/pipe_ls_wc.c new file mode 100644 index 0000000..2ff5e16 --- /dev/null +++ b/pipes/pipe_ls_wc.c @@ -0,0 +1,90 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/pipes/pipe_sync.c b/pipes/pipe_sync.c new file mode 100644 index 0000000..7f638e6 --- /dev/null +++ b/pipes/pipe_sync.c @@ -0,0 +1,87 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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); +} diff --git a/pipes/popen_glob.c b/pipes/popen_glob.c new file mode 100644 index 0000000..6eb2339 --- /dev/null +++ b/pipes/popen_glob.c @@ -0,0 +1,96 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/pipes/simple_pipe.c b/pipes/simple_pipe.c new file mode 100644 index 0000000..25f1ac1 --- /dev/null +++ b/pipes/simple_pipe.c @@ -0,0 +1,78 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); + } +} diff --git a/pmsg/Makefile b/pmsg/Makefile new file mode 100644 index 0000000..1a82fd9 --- /dev/null +++ b/pmsg/Makefile @@ -0,0 +1,25 @@ +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 diff --git a/pmsg/mq_notify_sig.c b/pmsg/mq_notify_sig.c new file mode 100644 index 0000000..ea1254f --- /dev/null +++ b/pmsg/mq_notify_sig.c @@ -0,0 +1,102 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include /* 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"); + } +} diff --git a/pmsg/mq_notify_sigwaitinfo.c b/pmsg/mq_notify_sigwaitinfo.c new file mode 100644 index 0000000..d706002 --- /dev/null +++ b/pmsg/mq_notify_sigwaitinfo.c @@ -0,0 +1,111 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include /* 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"); + } +} diff --git a/pmsg/mq_notify_thread.c b/pmsg/mq_notify_thread.c new file mode 100644 index 0000000..1788b26 --- /dev/null +++ b/pmsg/mq_notify_thread.c @@ -0,0 +1,91 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include /* 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 */ +} diff --git a/pmsg/mq_notify_via_signal.c b/pmsg/mq_notify_via_signal.c new file mode 100644 index 0000000..39cb3f3 --- /dev/null +++ b/pmsg/mq_notify_via_signal.c @@ -0,0 +1,109 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include /* 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" */ + } +} diff --git a/pmsg/mq_notify_via_thread.c b/pmsg/mq_notify_via_thread.c new file mode 100644 index 0000000..dfe9f54 --- /dev/null +++ b/pmsg/mq_notify_via_thread.c @@ -0,0 +1,103 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include /* 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 */ +} diff --git a/pmsg/pmsg_create.c b/pmsg/pmsg_create.c new file mode 100644 index 0000000..50b3f24 --- /dev/null +++ b/pmsg/pmsg_create.c @@ -0,0 +1,96 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#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); +} diff --git a/pmsg/pmsg_getattr.c b/pmsg/pmsg_getattr.c new file mode 100644 index 0000000..e4ea667 --- /dev/null +++ b/pmsg/pmsg_getattr.c @@ -0,0 +1,42 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/pmsg/pmsg_receive.c b/pmsg/pmsg_receive.c new file mode 100644 index 0000000..ec7f0af --- /dev/null +++ b/pmsg/pmsg_receive.c @@ -0,0 +1,81 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include /* 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); +} diff --git a/pmsg/pmsg_send.c b/pmsg/pmsg_send.c new file mode 100644 index 0000000..4cda32d --- /dev/null +++ b/pmsg/pmsg_send.c @@ -0,0 +1,63 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include /* 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); +} diff --git a/pmsg/pmsg_unlink.c b/pmsg/pmsg_unlink.c new file mode 100644 index 0000000..ebbd36d --- /dev/null +++ b/pmsg/pmsg_unlink.c @@ -0,0 +1,33 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/proc/Makefile b/proc/Makefile new file mode 100644 index 0000000..fcd5da6 --- /dev/null +++ b/proc/Makefile @@ -0,0 +1,20 @@ +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 diff --git a/proc/bad_longjmp.c b/proc/bad_longjmp.c new file mode 100644 index 0000000..b4fc154 --- /dev/null +++ b/proc/bad_longjmp.c @@ -0,0 +1,74 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/proc/display_env.c b/proc/display_env.c new file mode 100644 index 0000000..23dc8a4 --- /dev/null +++ b/proc/display_env.c @@ -0,0 +1,31 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 */ + +int +main(int argc, char *argv[]) +{ + char **ep; + + for (ep = environ; *ep != NULL; ep++) + puts(*ep); + + exit(EXIT_SUCCESS); +} diff --git a/proc/longjmp.c b/proc/longjmp.c new file mode 100644 index 0000000..c8eaa02 --- /dev/null +++ b/proc/longjmp.c @@ -0,0 +1,60 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/proc/mem_segments.c b/proc/mem_segments.c new file mode 100644 index 0000000..7521364 --- /dev/null +++ b/proc/mem_segments.c @@ -0,0 +1,59 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include + +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); +} diff --git a/proc/modify_env.c b/proc/modify_env.c new file mode 100644 index 0000000..62ce0f0 --- /dev/null +++ b/proc/modify_env.c @@ -0,0 +1,57 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 */ +#include +#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); +} diff --git a/proc/necho.c b/proc/necho.c new file mode 100644 index 0000000..f4f4b19 --- /dev/null +++ b/proc/necho.c @@ -0,0 +1,28 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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); +} diff --git a/proc/setenv.c b/proc/setenv.c new file mode 100644 index 0000000..3404e10 --- /dev/null +++ b/proc/setenv.c @@ -0,0 +1,112 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include + +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 diff --git a/proc/setjmp_vars.c b/proc/setjmp_vars.c new file mode 100644 index 0000000..c797725 --- /dev/null +++ b/proc/setjmp_vars.c @@ -0,0 +1,60 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include + +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); +} diff --git a/proc/t_getenv.c b/proc/t_getenv.c new file mode 100644 index 0000000..8bed084 --- /dev/null +++ b/proc/t_getenv.c @@ -0,0 +1,32 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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); +} diff --git a/proccred/Makefile b/proccred/Makefile new file mode 100644 index 0000000..9adfef8 --- /dev/null +++ b/proccred/Makefile @@ -0,0 +1,19 @@ +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 diff --git a/proccred/idshow.c b/proccred/idshow.c new file mode 100644 index 0000000..0b6a4e8 --- /dev/null +++ b/proccred/idshow.c @@ -0,0 +1,84 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#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); +} diff --git a/procexec/Makefile b/procexec/Makefile new file mode 100644 index 0000000..767b4d9 --- /dev/null +++ b/procexec/Makefile @@ -0,0 +1,24 @@ +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 diff --git a/procexec/acct_on.c b/procexec/acct_on.c new file mode 100644 index 0000000..2b58487 --- /dev/null +++ b/procexec/acct_on.c @@ -0,0 +1,33 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/procexec/acct_v3_view.c b/procexec/acct_v3_view.c new file mode 100644 index 0000000..bca5ced --- /dev/null +++ b/procexec/acct_v3_view.c @@ -0,0 +1,141 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include +#include +#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); +} diff --git a/procexec/acct_view.c b/procexec/acct_view.c new file mode 100644 index 0000000..ba2acce --- /dev/null +++ b/procexec/acct_view.c @@ -0,0 +1,119 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include +#include +#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); +} diff --git a/procexec/child_status.c b/procexec/child_status.c new file mode 100644 index 0000000..249a62f --- /dev/null +++ b/procexec/child_status.c @@ -0,0 +1,75 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); + } + } +} diff --git a/procexec/closeonexec.c b/procexec/closeonexec.c new file mode 100644 index 0000000..b1b6eec --- /dev/null +++ b/procexec/closeonexec.c @@ -0,0 +1,39 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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"); +} diff --git a/procexec/demo_clone.c b/procexec/demo_clone.c new file mode 100644 index 0000000..7d31c89 --- /dev/null +++ b/procexec/demo_clone.c @@ -0,0 +1,198 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include +#include +#include +#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); +} diff --git a/procexec/envargs.c b/procexec/envargs.c new file mode 100644 index 0000000..4461477 --- /dev/null +++ b/procexec/envargs.c @@ -0,0 +1,38 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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); +} diff --git a/procexec/execlp.c b/procexec/execlp.c new file mode 100644 index 0000000..e641062 --- /dev/null +++ b/procexec/execlp.c @@ -0,0 +1,197 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include +#include +#include + +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; +} diff --git a/procexec/exit_handlers.c b/procexec/exit_handlers.c new file mode 100644 index 0000000..9126a4e --- /dev/null +++ b/procexec/exit_handlers.c @@ -0,0 +1,66 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 */ +#include +#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); +} diff --git a/procexec/footprint.c b/procexec/footprint.c new file mode 100644 index 0000000..d9bf43b --- /dev/null +++ b/procexec/footprint.c @@ -0,0 +1,71 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 in case + _XOPEN_SOURCE >= 600; defining _SVID_SOURCE or + _GNU_SOURCE also suffices */ +#include +#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); +} diff --git a/procexec/fork_file_sharing.c b/procexec/fork_file_sharing.c new file mode 100644 index 0000000..634117a --- /dev/null +++ b/procexec/fork_file_sharing.c @@ -0,0 +1,78 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#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); + } +} diff --git a/procexec/fork_sig_sync.c b/procexec/fork_sig_sync.c new file mode 100644 index 0000000..d6557b3 --- /dev/null +++ b/procexec/fork_sig_sync.c @@ -0,0 +1,93 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); + } +} diff --git a/procexec/fork_stdio_buf.c b/procexec/fork_stdio_buf.c new file mode 100644 index 0000000..ed9740a --- /dev/null +++ b/procexec/fork_stdio_buf.c @@ -0,0 +1,31 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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); +} diff --git a/procexec/fork_whos_on_first.c b/procexec/fork_whos_on_first.c new file mode 100644 index 0000000..dc1bed8 --- /dev/null +++ b/procexec/fork_whos_on_first.c @@ -0,0 +1,56 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/procexec/fork_whos_on_first.count.awk b/procexec/fork_whos_on_first.count.awk new file mode 100755 index 0000000..34a20d3 --- /dev/null +++ b/procexec/fork_whos_on_first.count.awk @@ -0,0 +1,37 @@ +#!/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; +} diff --git a/procexec/longest_line.awk b/procexec/longest_line.awk new file mode 100755 index 0000000..5cf0246 --- /dev/null +++ b/procexec/longest_line.awk @@ -0,0 +1,3 @@ +#!/usr/bin/awk -f +length > max { max = length; } +END { print max; } diff --git a/procexec/make_zombie.c b/procexec/make_zombie.c new file mode 100644 index 0000000..5594e03 --- /dev/null +++ b/procexec/make_zombie.c @@ -0,0 +1,59 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include /* 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); + } +} diff --git a/procexec/multi_SIGCHLD.c b/procexec/multi_SIGCHLD.c new file mode 100644 index 0000000..5313e4b --- /dev/null +++ b/procexec/multi_SIGCHLD.c @@ -0,0 +1,119 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/procexec/multi_wait.c b/procexec/multi_wait.c new file mode 100644 index 0000000..ee5e5d2 --- /dev/null +++ b/procexec/multi_wait.c @@ -0,0 +1,75 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); + } +} diff --git a/procexec/necho.c b/procexec/necho.c new file mode 100644 index 0000000..f4f4b19 --- /dev/null +++ b/procexec/necho.c @@ -0,0 +1,28 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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); +} diff --git a/procexec/orphan.c b/procexec/orphan.c new file mode 100644 index 0000000..34847b5 --- /dev/null +++ b/procexec/orphan.c @@ -0,0 +1,45 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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); + } +} diff --git a/procexec/print_wait_status.c b/procexec/print_wait_status.c new file mode 100644 index 0000000..fd8e00e --- /dev/null +++ b/procexec/print_wait_status.c @@ -0,0 +1,61 @@ +/*************************************************************************\ +* 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 */ +#include +#include +#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); + } +} diff --git a/procexec/print_wait_status.h b/procexec/print_wait_status.h new file mode 100644 index 0000000..f89ea75 --- /dev/null +++ b/procexec/print_wait_status.h @@ -0,0 +1,22 @@ +/*************************************************************************\ +* 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 diff --git a/procexec/simple_system.c b/procexec/simple_system.c new file mode 100644 index 0000000..e9d98db --- /dev/null +++ b/procexec/simple_system.c @@ -0,0 +1,43 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include + +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; + } +} diff --git a/procexec/system.c b/procexec/system.c new file mode 100644 index 0000000..b5c9968 --- /dev/null +++ b/procexec/system.c @@ -0,0 +1,100 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include +#include + +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; +} diff --git a/procexec/t_clone.c b/procexec/t_clone.c new file mode 100644 index 0000000..03c8971 --- /dev/null +++ b/procexec/t_clone.c @@ -0,0 +1,92 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include +#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); +} diff --git a/procexec/t_execl.c b/procexec/t_execl.c new file mode 100644 index 0000000..68f71c3 --- /dev/null +++ b/procexec/t_execl.c @@ -0,0 +1,31 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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 */ +} diff --git a/procexec/t_execle.c b/procexec/t_execle.c new file mode 100644 index 0000000..5e32529 --- /dev/null +++ b/procexec/t_execle.c @@ -0,0 +1,38 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 */ +} diff --git a/procexec/t_execlp.c b/procexec/t_execlp.c new file mode 100644 index 0000000..d304052 --- /dev/null +++ b/procexec/t_execlp.c @@ -0,0 +1,29 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 */ +} diff --git a/procexec/t_execve.c b/procexec/t_execve.c new file mode 100644 index 0000000..a6caf1c --- /dev/null +++ b/procexec/t_execve.c @@ -0,0 +1,43 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 */ +} diff --git a/procexec/t_fork.c b/procexec/t_fork.c new file mode 100644 index 0000000..b41ef9e --- /dev/null +++ b/procexec/t_fork.c @@ -0,0 +1,48 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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); +} diff --git a/procexec/t_system.c b/procexec/t_system.c new file mode 100644 index 0000000..868250a --- /dev/null +++ b/procexec/t_system.c @@ -0,0 +1,50 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/procexec/t_vfork.c b/procexec/t_vfork.c new file mode 100644 index 0000000..73583b4 --- /dev/null +++ b/procexec/t_vfork.c @@ -0,0 +1,42 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 + 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); + } +} diff --git a/procexec/vfork_fd_test.c b/procexec/vfork_fd_test.c new file mode 100644 index 0000000..d5b769d --- /dev/null +++ b/procexec/vfork_fd_test.c @@ -0,0 +1,45 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 + 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); +} diff --git a/procpri/Makefile b/procpri/Makefile new file mode 100644 index 0000000..750769b --- /dev/null +++ b/procpri/Makefile @@ -0,0 +1,21 @@ +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 diff --git a/procpri/demo_sched_fifo.c b/procpri/demo_sched_fifo.c new file mode 100644 index 0000000..dd0ae77 --- /dev/null +++ b/procpri/demo_sched_fifo.c @@ -0,0 +1,114 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#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); + } +} diff --git a/procpri/sched_set.c b/procpri/sched_set.c new file mode 100644 index 0000000..37a157e --- /dev/null +++ b/procpri/sched_set.c @@ -0,0 +1,71 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/procpri/sched_view.c b/procpri/sched_view.c new file mode 100644 index 0000000..8765600 --- /dev/null +++ b/procpri/sched_view.c @@ -0,0 +1,52 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/procpri/t_sched_getaffinity.c b/procpri/t_sched_getaffinity.c new file mode 100644 index 0000000..fc6fdf8 --- /dev/null +++ b/procpri/t_sched_getaffinity.c @@ -0,0 +1,53 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/procpri/t_sched_setaffinity.c b/procpri/t_sched_setaffinity.c new file mode 100644 index 0000000..8258e52 --- /dev/null +++ b/procpri/t_sched_setaffinity.c @@ -0,0 +1,58 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 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 +#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); +} diff --git a/procpri/t_setpriority.c b/procpri/t_setpriority.c new file mode 100644 index 0000000..ff2b288 --- /dev/null +++ b/procpri/t_setpriority.c @@ -0,0 +1,55 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/procres/Makefile b/procres/Makefile new file mode 100644 index 0000000..333f5f7 --- /dev/null +++ b/procres/Makefile @@ -0,0 +1,19 @@ +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 diff --git a/procres/print_rlimit.c b/procres/print_rlimit.c new file mode 100644 index 0000000..43c9538 --- /dev/null +++ b/procres/print_rlimit.c @@ -0,0 +1,50 @@ +/*************************************************************************\ +* 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 +#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; +} diff --git a/procres/print_rlimit.h b/procres/print_rlimit.h new file mode 100644 index 0000000..703f4c8 --- /dev/null +++ b/procres/print_rlimit.h @@ -0,0 +1,22 @@ +/*************************************************************************\ +* 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 diff --git a/procres/print_rusage.c b/procres/print_rusage.c new file mode 100644 index 0000000..1910d24 --- /dev/null +++ b/procres/print_rusage.c @@ -0,0 +1,46 @@ +/*************************************************************************\ +* 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 +#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); +} diff --git a/procres/print_rusage.h b/procres/print_rusage.h new file mode 100644 index 0000000..8a742ea --- /dev/null +++ b/procres/print_rusage.h @@ -0,0 +1,24 @@ +/*************************************************************************\ +* 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 + +void printRusage(const char *leader, const struct rusage *ru); + +#endif diff --git a/procres/rlimit_nproc.c b/procres/rlimit_nproc.c new file mode 100644 index 0000000..85a1a1f --- /dev/null +++ b/procres/rlimit_nproc.c @@ -0,0 +1,63 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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; + } + } +} diff --git a/procres/rusage.c b/procres/rusage.c new file mode 100644 index 0000000..98245bb --- /dev/null +++ b/procres/rusage.c @@ -0,0 +1,53 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); + } +} diff --git a/procres/rusage_wait.c b/procres/rusage_wait.c new file mode 100644 index 0000000..0fde0d2 --- /dev/null +++ b/procres/rusage_wait.c @@ -0,0 +1,98 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include +#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); + } +} diff --git a/progconc/Makefile b/progconc/Makefile new file mode 100644 index 0000000..8641861 --- /dev/null +++ b/progconc/Makefile @@ -0,0 +1,19 @@ +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 diff --git a/progconc/syscall_speed.c b/progconc/syscall_speed.c new file mode 100644 index 0000000..1ebc85f --- /dev/null +++ b/progconc/syscall_speed.c @@ -0,0 +1,52 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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); +} diff --git a/psem/Makefile b/psem/Makefile new file mode 100644 index 0000000..e2119cd --- /dev/null +++ b/psem/Makefile @@ -0,0 +1,28 @@ +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 diff --git a/psem/psem_create.c b/psem/psem_create.c new file mode 100644 index 0000000..f7c1e79 --- /dev/null +++ b/psem/psem_create.c @@ -0,0 +1,68 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#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); +} diff --git a/psem/psem_getvalue.c b/psem/psem_getvalue.c new file mode 100644 index 0000000..d02e1e6 --- /dev/null +++ b/psem/psem_getvalue.c @@ -0,0 +1,41 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/psem/psem_post.c b/psem/psem_post.c new file mode 100644 index 0000000..724b00b --- /dev/null +++ b/psem/psem_post.c @@ -0,0 +1,40 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/psem/psem_timedwait.c b/psem/psem_timedwait.c new file mode 100644 index 0000000..4cbde9a --- /dev/null +++ b/psem/psem_timedwait.c @@ -0,0 +1,54 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/psem/psem_trywait.c b/psem/psem_trywait.c new file mode 100644 index 0000000..b8bf27b --- /dev/null +++ b/psem/psem_trywait.c @@ -0,0 +1,40 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/psem/psem_unlink.c b/psem/psem_unlink.c new file mode 100644 index 0000000..242d0f5 --- /dev/null +++ b/psem/psem_unlink.c @@ -0,0 +1,32 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/psem/psem_wait.c b/psem/psem_wait.c new file mode 100644 index 0000000..eed8f83 --- /dev/null +++ b/psem/psem_wait.c @@ -0,0 +1,42 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/psem/thread_incr_psem.c b/psem/thread_incr_psem.c new file mode 100644 index 0000000..9c1136d --- /dev/null +++ b/psem/thread_incr_psem.c @@ -0,0 +1,81 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/pshm/Makefile b/pshm/Makefile new file mode 100644 index 0000000..3c6959b --- /dev/null +++ b/pshm/Makefile @@ -0,0 +1,24 @@ +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 diff --git a/pshm/pshm_create.c b/pshm/pshm_create.c new file mode 100644 index 0000000..820d723 --- /dev/null +++ b/pshm/pshm_create.c @@ -0,0 +1,73 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#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); +} diff --git a/pshm/pshm_read.c b/pshm/pshm_read.c new file mode 100644 index 0000000..89f662e --- /dev/null +++ b/pshm/pshm_read.c @@ -0,0 +1,57 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#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); +} diff --git a/pshm/pshm_unlink.c b/pshm/pshm_unlink.c new file mode 100644 index 0000000..0a0f0e3 --- /dev/null +++ b/pshm/pshm_unlink.c @@ -0,0 +1,32 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/pshm/pshm_write.c b/pshm/pshm_write.c new file mode 100644 index 0000000..0a9c1c3 --- /dev/null +++ b/pshm/pshm_write.c @@ -0,0 +1,54 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/pty/Makefile b/pty/Makefile new file mode 100644 index 0000000..977dad4 --- /dev/null +++ b/pty/Makefile @@ -0,0 +1,19 @@ +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 diff --git a/pty/pty_fork.c b/pty/pty_fork.c new file mode 100644 index 0000000..0cbe4e5 --- /dev/null +++ b/pty/pty_fork.c @@ -0,0 +1,117 @@ +/*************************************************************************\ +* 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 +#include +#include +#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() */ +} diff --git a/pty/pty_fork.h b/pty/pty_fork.h new file mode 100644 index 0000000..99c5e80 --- /dev/null +++ b/pty/pty_fork.h @@ -0,0 +1,27 @@ +/*************************************************************************\ +* 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 +#include +#include + +pid_t ptyFork(int *masterFd, char *slaveName, size_t snLen, + const struct termios *slaveTermios, const struct winsize *slaveWS); + +#endif diff --git a/pty/pty_master_open.c b/pty/pty_master_open.c new file mode 100644 index 0000000..b8e1b6e --- /dev/null +++ b/pty/pty_master_open.c @@ -0,0 +1,93 @@ +/*************************************************************************\ +* 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 +#include +#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; +} diff --git a/pty/pty_master_open.h b/pty/pty_master_open.h new file mode 100644 index 0000000..715fb23 --- /dev/null +++ b/pty/pty_master_open.h @@ -0,0 +1,24 @@ +/*************************************************************************\ +* 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 + +int ptyMasterOpen(char *slaveName, size_t snLen); + +#endif diff --git a/pty/pty_master_open_bsd.c b/pty/pty_master_open_bsd.c new file mode 100644 index 0000000..0bef872 --- /dev/null +++ b/pty/pty_master_open_bsd.c @@ -0,0 +1,86 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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 */ +} diff --git a/pty/script.c b/pty/script.c new file mode 100644 index 0000000..e062bf4 --- /dev/null +++ b/pty/script.c @@ -0,0 +1,131 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include +#if ! defined(__hpux) +/* HP-UX 11 doesn't have this header file */ +#include +#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)"); + } + } +} diff --git a/pty/unbuffer.c b/pty/unbuffer.c new file mode 100644 index 0000000..8301550 --- /dev/null +++ b/pty/unbuffer.c @@ -0,0 +1,112 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#if ! defined(__hpux) +/* HP-UX 11 doesn't have this header file */ +#include +#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)"); + } + } + } +} diff --git a/seccomp/Makefile b/seccomp/Makefile new file mode 100644 index 0000000..bc9229b --- /dev/null +++ b/seccomp/Makefile @@ -0,0 +1,22 @@ +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 diff --git a/seccomp/libseccomp_demo.c b/seccomp/libseccomp_demo.c new file mode 100644 index 0000000..9bd55aa --- /dev/null +++ b/seccomp/libseccomp_demo.c @@ -0,0 +1,64 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include +#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); +} diff --git a/seccomp/seccomp_control_open.c b/seccomp/seccomp_control_open.c new file mode 100644 index 0000000..4f73205 --- /dev/null +++ b/seccomp/seccomp_control_open.c @@ -0,0 +1,113 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include +#include +#include +#include +#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); +} diff --git a/seccomp/seccomp_deny_open.c b/seccomp/seccomp_deny_open.c new file mode 100644 index 0000000..d1977dc --- /dev/null +++ b/seccomp/seccomp_deny_open.c @@ -0,0 +1,90 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include +#include +#include +#include +#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); +} diff --git a/seccomp/seccomp_perf.c b/seccomp/seccomp_perf.c new file mode 100644 index 0000000..28c11ae --- /dev/null +++ b/seccomp/seccomp_perf.c @@ -0,0 +1,109 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 [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); +} diff --git a/shlibs/Demo_no_lib.sh b/shlibs/Demo_no_lib.sh new file mode 100644 index 0000000..c896a60 --- /dev/null +++ b/shlibs/Demo_no_lib.sh @@ -0,0 +1,6 @@ +#!/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 diff --git a/shlibs/Demo_shared_lib.sh b/shlibs/Demo_shared_lib.sh new file mode 100644 index 0000000..aec6de7 --- /dev/null +++ b/shlibs/Demo_shared_lib.sh @@ -0,0 +1,27 @@ +#!/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 diff --git a/shlibs/Demo_static_lib.sh b/shlibs/Demo_static_lib.sh new file mode 100644 index 0000000..5847c72 --- /dev/null +++ b/shlibs/Demo_static_lib.sh @@ -0,0 +1,9 @@ +#!/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 diff --git a/shlibs/Makefile b/shlibs/Makefile new file mode 100644 index 0000000..6006c03 --- /dev/null +++ b/shlibs/Makefile @@ -0,0 +1,19 @@ +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 diff --git a/shlibs/demo_Bsymbolic/build.sh b/shlibs/demo_Bsymbolic/build.sh new file mode 100755 index 0000000..b0f860e --- /dev/null +++ b/shlibs/demo_Bsymbolic/build.sh @@ -0,0 +1,12 @@ +#!/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 diff --git a/shlibs/demo_Bsymbolic/foo1.c b/shlibs/demo_Bsymbolic/foo1.c new file mode 100644 index 0000000..48ef9a8 --- /dev/null +++ b/shlibs/demo_Bsymbolic/foo1.c @@ -0,0 +1,35 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include + +void +xyz(void) +{ + printf(" func1-xyz\n"); +} + +void +abc(void) +{ + printf(" func1-abc\n"); +} + +void +func1(int x) +{ + printf("Called func1\n"); + xyz(); + abc(); +} diff --git a/shlibs/demo_Bsymbolic/foo2.c b/shlibs/demo_Bsymbolic/foo2.c new file mode 100644 index 0000000..fa62958 --- /dev/null +++ b/shlibs/demo_Bsymbolic/foo2.c @@ -0,0 +1,35 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include + +void +xyz(void) +{ + printf(" func2-xyz\n"); +} + +void +abc(void) +{ + printf(" func1-abc\n"); +} + +void +func2(int x) +{ + printf("Called func2\n"); + xyz(); + abc(); +} diff --git a/shlibs/demo_Bsymbolic/foo3.c b/shlibs/demo_Bsymbolic/foo3.c new file mode 100644 index 0000000..fe6dc0f --- /dev/null +++ b/shlibs/demo_Bsymbolic/foo3.c @@ -0,0 +1,29 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include + +void +xyz(void) +{ + printf(" func3-xyz\n"); +} + +void +func3(int x) +{ + printf("Called func3\n"); + xyz(); + abc(); +} diff --git a/shlibs/demo_Bsymbolic/prog.c b/shlibs/demo_Bsymbolic/prog.c new file mode 100644 index 0000000..c645661 --- /dev/null +++ b/shlibs/demo_Bsymbolic/prog.c @@ -0,0 +1,32 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include + +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); +} diff --git a/shlibs/dynload.c b/shlibs/dynload.c new file mode 100644 index 0000000..ce1c9f7 --- /dev/null +++ b/shlibs/dynload.c @@ -0,0 +1,74 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/shlibs/mod1.c b/shlibs/mod1.c new file mode 100644 index 0000000..30e09c0 --- /dev/null +++ b/shlibs/mod1.c @@ -0,0 +1,24 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +int test1[250000] = { 1, 2, 3 }; + +#ifndef VERSION +#define VERSION "" +#endif + +void +x1(void) { + printf("Called mod1-x1 " VERSION "\n"); +} diff --git a/shlibs/mod2.c b/shlibs/mod2.c new file mode 100644 index 0000000..2aa42c1 --- /dev/null +++ b/shlibs/mod2.c @@ -0,0 +1,24 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +int test2[100000] = { 1, 2, 3 }; + +#ifndef VERSION +#define VERSION "" +#endif + +void +x2(void) { + printf("Called mod2-x2 " VERSION "\n"); +} diff --git a/shlibs/mod3.c b/shlibs/mod3.c new file mode 100644 index 0000000..33fceb7 --- /dev/null +++ b/shlibs/mod3.c @@ -0,0 +1,24 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +int test3[10000] = { 1, 2, 3 }; + +#ifndef VERSION +#define VERSION "" +#endif + +void +x3(void) { + printf("Called mod3-x3 " VERSION "\n"); +} diff --git a/shlibs/prog.c b/shlibs/prog.c new file mode 100644 index 0000000..098e91a --- /dev/null +++ b/shlibs/prog.c @@ -0,0 +1,25 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include + +void x1(void); +void x2(void); + +int +main(int argc, char *argv[]) +{ + x1(); + x2(); + exit(EXIT_SUCCESS); +} diff --git a/shlibs/rpath_demo/d1/modx1.c b/shlibs/rpath_demo/d1/modx1.c new file mode 100644 index 0000000..dbc7a0b --- /dev/null +++ b/shlibs/rpath_demo/d1/modx1.c @@ -0,0 +1,18 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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(); +} diff --git a/shlibs/rpath_demo/d2/modx2.c b/shlibs/rpath_demo/d2/modx2.c new file mode 100644 index 0000000..4c445e0 --- /dev/null +++ b/shlibs/rpath_demo/d2/modx2.c @@ -0,0 +1,18 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 + +void +x2(void) { + printf("Called modx2\n"); +} diff --git a/shlibs/version_scripts/sv_build.sh b/shlibs/version_scripts/sv_build.sh new file mode 100644 index 0000000..a863fee --- /dev/null +++ b/shlibs/version_scripts/sv_build.sh @@ -0,0 +1,24 @@ +#!/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 diff --git a/shlibs/version_scripts/sv_lib_v1.c b/shlibs/version_scripts/sv_lib_v1.c new file mode 100644 index 0000000..82b70d1 --- /dev/null +++ b/shlibs/version_scripts/sv_lib_v1.c @@ -0,0 +1,16 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 + +void xyz(void) { printf("v1 xyz()\n"); } diff --git a/shlibs/version_scripts/sv_lib_v2.c b/shlibs/version_scripts/sv_lib_v2.c new file mode 100644 index 0000000..735f603 --- /dev/null +++ b/shlibs/version_scripts/sv_lib_v2.c @@ -0,0 +1,24 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 + +__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"); } diff --git a/shlibs/version_scripts/sv_libabc.c b/shlibs/version_scripts/sv_libabc.c new file mode 100644 index 0000000..554c8e4 --- /dev/null +++ b/shlibs/version_scripts/sv_libabc.c @@ -0,0 +1,25 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 + +void +abc(void) +{ + void xyz(void); + + printf("abc() calling xyz()\n"); + xyz(); +} + +__asm__(".symver xyz,xyz@VER_1"); diff --git a/shlibs/version_scripts/sv_prog.c b/shlibs/version_scripts/sv_prog.c new file mode 100644 index 0000000..c258963 --- /dev/null +++ b/shlibs/version_scripts/sv_prog.c @@ -0,0 +1,24 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 + +int +main(int argc, char *argv[]) +{ + void xyz(void); + + xyz(); + + exit(EXIT_SUCCESS); +} diff --git a/shlibs/version_scripts/sv_prog_abc.c b/shlibs/version_scripts/sv_prog_abc.c new file mode 100644 index 0000000..daab364 --- /dev/null +++ b/shlibs/version_scripts/sv_prog_abc.c @@ -0,0 +1,28 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include + +int +main(int argc, char *argv[]) +{ + void xyz(void), abc(void); + + printf("main() calling xyz()\n"); + xyz(); + + abc(); + + exit(EXIT_SUCCESS); +} diff --git a/shlibs/version_scripts/sv_prog_complex.c b/shlibs/version_scripts/sv_prog_complex.c new file mode 100644 index 0000000..44aa2c5 --- /dev/null +++ b/shlibs/version_scripts/sv_prog_complex.c @@ -0,0 +1,41 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include + +//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"); diff --git a/shlibs/version_scripts/vis_build.sh b/shlibs/version_scripts/vis_build.sh new file mode 100644 index 0000000..9343714 --- /dev/null +++ b/shlibs/version_scripts/vis_build.sh @@ -0,0 +1,22 @@ +#!/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_ diff --git a/shlibs/version_scripts/vis_comm.c b/shlibs/version_scripts/vis_comm.c new file mode 100644 index 0000000..cb4552e --- /dev/null +++ b/shlibs/version_scripts/vis_comm.c @@ -0,0 +1,18 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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; +} diff --git a/shlibs/version_scripts/vis_f1.c b/shlibs/version_scripts/vis_f1.c new file mode 100644 index 0000000..5093422 --- /dev/null +++ b/shlibs/version_scripts/vis_f1.c @@ -0,0 +1,20 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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; +} diff --git a/shlibs/version_scripts/vis_f2.c b/shlibs/version_scripts/vis_f2.c new file mode 100644 index 0000000..10da590 --- /dev/null +++ b/shlibs/version_scripts/vis_f2.c @@ -0,0 +1,20 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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); +} diff --git a/signals/Makefile b/signals/Makefile new file mode 100644 index 0000000..a7d96b2 --- /dev/null +++ b/signals/Makefile @@ -0,0 +1,48 @@ +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 . 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 diff --git a/signals/catch_rtsigs.c b/signals/catch_rtsigs.c new file mode 100644 index 0000000..de94013 --- /dev/null +++ b/signals/catch_rtsigs.c @@ -0,0 +1,122 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/signals/demo_SIGFPE.c b/signals/demo_SIGFPE.c new file mode 100644 index 0000000..098d863 --- /dev/null +++ b/signals/demo_SIGFPE.c @@ -0,0 +1,89 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 */ +#include +#include +#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); +} diff --git a/signals/ignore_pending_sig.c b/signals/ignore_pending_sig.c new file mode 100644 index 0000000..b4ac8bd --- /dev/null +++ b/signals/ignore_pending_sig.c @@ -0,0 +1,109 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 */ +#include +#include +#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); +} diff --git a/signals/intquit.c b/signals/intquit.c new file mode 100644 index 0000000..1769368 --- /dev/null +++ b/signals/intquit.c @@ -0,0 +1,59 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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 */ +} diff --git a/signals/nonreentrant.c b/signals/nonreentrant.c new file mode 100644 index 0000000..42884d0 --- /dev/null +++ b/signals/nonreentrant.c @@ -0,0 +1,70 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#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); + } + } +} diff --git a/signals/ouch.c b/signals/ouch.c new file mode 100644 index 0000000..da17a66 --- /dev/null +++ b/signals/ouch.c @@ -0,0 +1,48 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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... */ + } +} diff --git a/signals/sig_receiver.c b/signals/sig_receiver.c new file mode 100644 index 0000000..d59b15a --- /dev/null +++ b/signals/sig_receiver.c @@ -0,0 +1,89 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/signals/sig_sender.c b/signals/sig_sender.c new file mode 100644 index 0000000..36204be --- /dev/null +++ b/signals/sig_sender.c @@ -0,0 +1,56 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/signals/sig_speed_sigsuspend.c b/signals/sig_speed_sigsuspend.c new file mode 100644 index 0000000..7801349 --- /dev/null +++ b/signals/sig_speed_sigsuspend.c @@ -0,0 +1,94 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); + } +} diff --git a/signals/siginterrupt.c b/signals/siginterrupt.c new file mode 100644 index 0000000..0bf6339 --- /dev/null +++ b/signals/siginterrupt.c @@ -0,0 +1,36 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include + +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); +} diff --git a/signals/sigmask_longjmp.c b/signals/sigmask_longjmp.c new file mode 100644 index 0000000..939792c --- /dev/null +++ b/signals/sigmask_longjmp.c @@ -0,0 +1,86 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 */ +#include +#include +#include +#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(); +} diff --git a/signals/signal.c b/signals/signal.c new file mode 100644 index 0000000..ac617f9 --- /dev/null +++ b/signals/signal.c @@ -0,0 +1,56 @@ +/*************************************************************************\ +* 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 + +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 diff --git a/signals/signal_functions.c b/signals/signal_functions.c new file mode 100644 index 0000000..6d402b7 --- /dev/null +++ b/signals/signal_functions.c @@ -0,0 +1,75 @@ +/*************************************************************************\ +* 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 +#include +#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\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; +} diff --git a/signals/signal_functions.h b/signals/signal_functions.h new file mode 100644 index 0000000..9e7651e --- /dev/null +++ b/signals/signal_functions.h @@ -0,0 +1,29 @@ +/*************************************************************************\ +* 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 +#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 diff --git a/signals/signalfd_sigval.c b/signals/signalfd_sigval.c new file mode 100644 index 0000000..c3de973 --- /dev/null +++ b/signals/signalfd_sigval.c @@ -0,0 +1,62 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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"); + } +} diff --git a/signals/t_kill.c b/signals/t_kill.c new file mode 100644 index 0000000..b8867e1 --- /dev/null +++ b/signals/t_kill.c @@ -0,0 +1,51 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/signals/t_sigaltstack.c b/signals/t_sigaltstack.c new file mode 100644 index 0000000..32ea8c0 --- /dev/null +++ b/signals/t_sigaltstack.c @@ -0,0 +1,85 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 */ +#include +#include +#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); +} diff --git a/signals/t_sigqueue.c b/signals/t_sigqueue.c new file mode 100644 index 0000000..6a2e62d --- /dev/null +++ b/signals/t_sigqueue.c @@ -0,0 +1,53 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/signals/t_sigsuspend.c b/signals/t_sigsuspend.c new file mode 100644 index 0000000..3769c1d --- /dev/null +++ b/signals/t_sigsuspend.c @@ -0,0 +1,153 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 */ +#include +#include +#include +#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); +} diff --git a/signals/t_sigwaitinfo.c b/signals/t_sigwaitinfo.c new file mode 100644 index 0000000..5f5fa6e --- /dev/null +++ b/signals/t_sigwaitinfo.c @@ -0,0 +1,65 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#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); + } +} diff --git a/sockets/Makefile b/sockets/Makefile new file mode 100644 index 0000000..9d2c22a --- /dev/null +++ b/sockets/Makefile @@ -0,0 +1,45 @@ +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 diff --git a/sockets/README b/sockets/README new file mode 100644 index 0000000..34f0749 --- /dev/null +++ b/sockets/README @@ -0,0 +1,23 @@ +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. diff --git a/sockets/i6d_ucase.h b/sockets/i6d_ucase.h new file mode 100644 index 0000000..0c67398 --- /dev/null +++ b/sockets/i6d_ucase.h @@ -0,0 +1,26 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include +#include "tlpi_hdr.h" + +#define BUF_SIZE 10 /* Maximum size of messages exchanged + between client and server */ + +#define PORT_NUM 50002 /* Server port number */ diff --git a/sockets/i6d_ucase_cl.c b/sockets/i6d_ucase_cl.c new file mode 100644 index 0000000..0584bcc --- /dev/null +++ b/sockets/i6d_ucase_cl.c @@ -0,0 +1,60 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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); +} diff --git a/sockets/i6d_ucase_sv.c b/sockets/i6d_ucase_sv.c new file mode 100644 index 0000000..8790582 --- /dev/null +++ b/sockets/i6d_ucase_sv.c @@ -0,0 +1,72 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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"); + } +} diff --git a/sockets/id_echo.h b/sockets/id_echo.h new file mode 100644 index 0000000..6cad12d --- /dev/null +++ b/sockets/id_echo.h @@ -0,0 +1,23 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 */ diff --git a/sockets/id_echo_cl.c b/sockets/id_echo_cl.c new file mode 100644 index 0000000..9aa193f --- /dev/null +++ b/sockets/id_echo_cl.c @@ -0,0 +1,55 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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); +} diff --git a/sockets/id_echo_sv.c b/sockets/id_echo_sv.c new file mode 100644 index 0000000..552121d --- /dev/null +++ b/sockets/id_echo_sv.c @@ -0,0 +1,64 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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)); + } +} diff --git a/sockets/inet_sockets.c b/sockets/inet_sockets.c new file mode 100644 index 0000000..cb09282 --- /dev/null +++ b/sockets/inet_sockets.c @@ -0,0 +1,188 @@ +/*************************************************************************\ +* 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 */ +#include +#include +#include +#include +#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; +} diff --git a/sockets/inet_sockets.h b/sockets/inet_sockets.h new file mode 100644 index 0000000..621713e --- /dev/null +++ b/sockets/inet_sockets.h @@ -0,0 +1,36 @@ +/*************************************************************************\ +* 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 +#include + +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 diff --git a/sockets/is_echo_cl.c b/sockets/is_echo_cl.c new file mode 100644 index 0000000..c361ad2 --- /dev/null +++ b/sockets/is_echo_cl.c @@ -0,0 +1,90 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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); + } +} diff --git a/sockets/is_echo_inetd_sv.c b/sockets/is_echo_inetd_sv.c new file mode 100644 index 0000000..9887ae9 --- /dev/null +++ b/sockets/is_echo_inetd_sv.c @@ -0,0 +1,43 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/sockets/is_echo_sv.c b/sockets/is_echo_sv.c new file mode 100644 index 0000000..7024545 --- /dev/null +++ b/sockets/is_echo_sv.c @@ -0,0 +1,116 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#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 */ + } + } +} diff --git a/sockets/is_echo_v2_sv.c b/sockets/is_echo_v2_sv.c new file mode 100644 index 0000000..0d07a6f --- /dev/null +++ b/sockets/is_echo_v2_sv.c @@ -0,0 +1,130 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 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 +#include +#include +#include +#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 */ + } + } +} diff --git a/sockets/is_seqnum.h b/sockets/is_seqnum.h new file mode 100644 index 0000000..c838008 --- /dev/null +++ b/sockets/is_seqnum.h @@ -0,0 +1,26 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#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') */ diff --git a/sockets/is_seqnum_cl.c b/sockets/is_seqnum_cl.c new file mode 100644 index 0000000..ae90a86 --- /dev/null +++ b/sockets/is_seqnum_cl.c @@ -0,0 +1,90 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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' */ +} diff --git a/sockets/is_seqnum_sv.c b/sockets/is_seqnum_sv.c new file mode 100644 index 0000000..9855570 --- /dev/null +++ b/sockets/is_seqnum_sv.c @@ -0,0 +1,140 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 */ +#include +#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"); + } +} diff --git a/sockets/is_seqnum_v2.h b/sockets/is_seqnum_v2.h new file mode 100644 index 0000000..50e0fe9 --- /dev/null +++ b/sockets/is_seqnum_v2.h @@ -0,0 +1,27 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#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') */ diff --git a/sockets/is_seqnum_v2_cl.c b/sockets/is_seqnum_v2_cl.c new file mode 100644 index 0000000..e68a6d9 --- /dev/null +++ b/sockets/is_seqnum_v2_cl.c @@ -0,0 +1,56 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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' */ +} diff --git a/sockets/is_seqnum_v2_sv.c b/sockets/is_seqnum_v2_sv.c new file mode 100644 index 0000000..051c851 --- /dev/null +++ b/sockets/is_seqnum_v2_sv.c @@ -0,0 +1,94 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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"); + } +} diff --git a/sockets/list_host_addresses.c b/sockets/list_host_addresses.c new file mode 100644 index 0000000..2be8fea --- /dev/null +++ b/sockets/list_host_addresses.c @@ -0,0 +1,68 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include +#include +#include +#include +#include +#include + +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); +} diff --git a/sockets/rdwrn.c b/sockets/rdwrn.c new file mode 100644 index 0000000..ccbe244 --- /dev/null +++ b/sockets/rdwrn.c @@ -0,0 +1,76 @@ +/*************************************************************************\ +* 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 +#include +#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 */ +} diff --git a/sockets/rdwrn.h b/sockets/rdwrn.h new file mode 100644 index 0000000..bb55f8b --- /dev/null +++ b/sockets/rdwrn.h @@ -0,0 +1,26 @@ +/*************************************************************************\ +* 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 + +ssize_t readn(int fd, void *buf, size_t len); + +ssize_t writen(int fd, const void *buf, size_t len); + +#endif diff --git a/sockets/read_line.c b/sockets/read_line.c new file mode 100644 index 0000000..0b4aaff --- /dev/null +++ b/sockets/read_line.c @@ -0,0 +1,73 @@ +/*************************************************************************\ +* 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 +#include +#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; +} diff --git a/sockets/read_line.h b/sockets/read_line.h new file mode 100644 index 0000000..b03a1e8 --- /dev/null +++ b/sockets/read_line.h @@ -0,0 +1,24 @@ +/*************************************************************************\ +* 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 + +ssize_t readLine(int fd, void *buffer, size_t n); + +#endif diff --git a/sockets/read_line_buf.c b/sockets/read_line_buf.c new file mode 100644 index 0000000..1240a44 --- /dev/null +++ b/sockets/read_line_buf.c @@ -0,0 +1,76 @@ +/*************************************************************************\ +* 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 +#include +#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; +} diff --git a/sockets/read_line_buf.h b/sockets/read_line_buf.h new file mode 100644 index 0000000..7955e39 --- /dev/null +++ b/sockets/read_line_buf.h @@ -0,0 +1,37 @@ +/*************************************************************************\ +* 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 +#include +#include + +#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 diff --git a/sockets/scm_cred.h b/sockets/scm_cred.h new file mode 100644 index 0000000..eae4b1c --- /dev/null +++ b/sockets/scm_cred.h @@ -0,0 +1,24 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 + */ +#include +#include +#include "unix_sockets.h" /* Declares our socket functions */ +#include "tlpi_hdr.h" + +#define SOCK_PATH "scm_cred" diff --git a/sockets/scm_cred_recv.c b/sockets/scm_cred_recv.c new file mode 100644 index 0000000..5b2a974 --- /dev/null +++ b/sockets/scm_cred_recv.c @@ -0,0 +1,153 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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); +} diff --git a/sockets/scm_cred_send.c b/sockets/scm_cred_send.c new file mode 100644 index 0000000..fe20708 --- /dev/null +++ b/sockets/scm_cred_send.c @@ -0,0 +1,149 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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); +} diff --git a/sockets/scm_rights.h b/sockets/scm_rights.h new file mode 100644 index 0000000..9780b0a --- /dev/null +++ b/sockets/scm_rights.h @@ -0,0 +1,23 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include "unix_sockets.h" /* Declares our unix*() socket functions */ +#include "tlpi_hdr.h" + +#define SOCK_PATH "scm_rights" diff --git a/sockets/scm_rights_recv.c b/sockets/scm_rights_recv.c new file mode 100644 index 0000000..acb78bb --- /dev/null +++ b/sockets/scm_rights_recv.c @@ -0,0 +1,153 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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); +} diff --git a/sockets/scm_rights_send.c b/sockets/scm_rights_send.c new file mode 100644 index 0000000..8d12dbb --- /dev/null +++ b/sockets/scm_rights_send.c @@ -0,0 +1,123 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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); +} diff --git a/sockets/sendfile.c b/sockets/sendfile.c new file mode 100644 index 0000000..8368203 --- /dev/null +++ b/sockets/sendfile.c @@ -0,0 +1,72 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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; +} diff --git a/sockets/socknames.c b/sockets/socknames.c new file mode 100644 index 0000000..b738f78 --- /dev/null +++ b/sockets/socknames.c @@ -0,0 +1,72 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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); +} diff --git a/sockets/t_gethostbyname.c b/sockets/t_gethostbyname.c new file mode 100644 index 0000000..19c86c7 --- /dev/null +++ b/sockets/t_gethostbyname.c @@ -0,0 +1,64 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 */ +#include +#include +#include +#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); +} diff --git a/sockets/t_getservbyname.c b/sockets/t_getservbyname.c new file mode 100644 index 0000000..c665892 --- /dev/null +++ b/sockets/t_getservbyname.c @@ -0,0 +1,44 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/sockets/ud_ucase.h b/sockets/ud_ucase.h new file mode 100644 index 0000000..ab57b35 --- /dev/null +++ b/sockets/ud_ucase.h @@ -0,0 +1,34 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include "tlpi_hdr.h" + +#define BUF_SIZE 10 /* Maximum size of messages exchanged + between client and server */ + +#define SV_SOCK_PATH "/tmp/ud_ucase" diff --git a/sockets/ud_ucase_cl.c b/sockets/ud_ucase_cl.c new file mode 100644 index 0000000..4ae21a0 --- /dev/null +++ b/sockets/ud_ucase_cl.c @@ -0,0 +1,71 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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); +} diff --git a/sockets/ud_ucase_sv.c b/sockets/ud_ucase_sv.c new file mode 100644 index 0000000..535daad --- /dev/null +++ b/sockets/ud_ucase_sv.c @@ -0,0 +1,72 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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"); + } +} diff --git a/sockets/unix_sockets.c b/sockets/unix_sockets.c new file mode 100644 index 0000000..5e5108f --- /dev/null +++ b/sockets/unix_sockets.c @@ -0,0 +1,124 @@ +/*************************************************************************\ +* 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); +} diff --git a/sockets/unix_sockets.h b/sockets/unix_sockets.h new file mode 100644 index 0000000..bfe294a --- /dev/null +++ b/sockets/unix_sockets.h @@ -0,0 +1,31 @@ +/*************************************************************************\ +* 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 +#include + +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 diff --git a/sockets/us_abstract_bind.c b/sockets/us_abstract_bind.c new file mode 100644 index 0000000..6faa770 --- /dev/null +++ b/sockets/us_abstract_bind.c @@ -0,0 +1,63 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/sockets/us_xfr.h b/sockets/us_xfr.h new file mode 100644 index 0000000..3e8a3c4 --- /dev/null +++ b/sockets/us_xfr.h @@ -0,0 +1,32 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include "tlpi_hdr.h" + +#define SV_SOCK_PATH "/tmp/us_xfr" + +#define BUF_SIZE 100 diff --git a/sockets/us_xfr_cl.c b/sockets/us_xfr_cl.c new file mode 100644 index 0000000..b8823f1 --- /dev/null +++ b/sockets/us_xfr_cl.c @@ -0,0 +1,54 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 */ +} diff --git a/sockets/us_xfr_sv.c b/sockets/us_xfr_sv.c new file mode 100644 index 0000000..00e4f41 --- /dev/null +++ b/sockets/us_xfr_sv.c @@ -0,0 +1,79 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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"); + } +} diff --git a/sockets/us_xfr_v2.h b/sockets/us_xfr_v2.h new file mode 100644 index 0000000..fdd0e99 --- /dev/null +++ b/sockets/us_xfr_v2.h @@ -0,0 +1,22 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 diff --git a/sockets/us_xfr_v2_cl.c b/sockets/us_xfr_v2_cl.c new file mode 100644 index 0000000..dfce5b2 --- /dev/null +++ b/sockets/us_xfr_v2_cl.c @@ -0,0 +1,44 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 */ +} diff --git a/sockets/us_xfr_v2_sv.c b/sockets/us_xfr_v2_sv.c new file mode 100644 index 0000000..c669f7a --- /dev/null +++ b/sockets/us_xfr_v2_sv.c @@ -0,0 +1,51 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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"); + } +} diff --git a/svipc/Makefile b/svipc/Makefile new file mode 100644 index 0000000..7778395 --- /dev/null +++ b/svipc/Makefile @@ -0,0 +1,19 @@ +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 diff --git a/svipc/svmsg_demo_server.c b/svipc/svmsg_demo_server.c new file mode 100644 index 0000000..bbb99b8 --- /dev/null +++ b/svipc/svmsg_demo_server.c @@ -0,0 +1,67 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include +#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); +} diff --git a/svipc/t_ftok.c b/svipc/t_ftok.c new file mode 100644 index 0000000..58baed0 --- /dev/null +++ b/svipc/t_ftok.c @@ -0,0 +1,51 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/svmsg/Makefile b/svmsg/Makefile new file mode 100644 index 0000000..7f0b322 --- /dev/null +++ b/svmsg/Makefile @@ -0,0 +1,22 @@ +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 diff --git a/svmsg/svmsg_chqbytes.c b/svmsg/svmsg_chqbytes.c new file mode 100644 index 0000000..589b94c --- /dev/null +++ b/svmsg/svmsg_chqbytes.c @@ -0,0 +1,47 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/svmsg/svmsg_create.c b/svmsg/svmsg_create.c new file mode 100644 index 0000000..e107ef5 --- /dev/null +++ b/svmsg/svmsg_create.c @@ -0,0 +1,99 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include +#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); +} diff --git a/svmsg/svmsg_file.h b/svmsg/svmsg_file.h new file mode 100644 index 0000000..eb515f8 --- /dev/null +++ b/svmsg/svmsg_file.h @@ -0,0 +1,53 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include /* For definition of offsetof() */ +#include +#include +#include +#include +#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 */ diff --git a/svmsg/svmsg_file_client.c b/svmsg/svmsg_file_client.c new file mode 100644 index 0000000..0dd9588 --- /dev/null +++ b/svmsg/svmsg_file_client.c @@ -0,0 +1,97 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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); +} diff --git a/svmsg/svmsg_file_server.c b/svmsg/svmsg_file_server.c new file mode 100644 index 0000000..7972c7d --- /dev/null +++ b/svmsg/svmsg_file_server.c @@ -0,0 +1,127 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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); +} diff --git a/svmsg/svmsg_info.c b/svmsg/svmsg_info.c new file mode 100644 index 0000000..6f7bf75 --- /dev/null +++ b/svmsg/svmsg_info.c @@ -0,0 +1,39 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/svmsg/svmsg_ls.c b/svmsg/svmsg_ls.c new file mode 100644 index 0000000..eb0d462 --- /dev/null +++ b/svmsg/svmsg_ls.c @@ -0,0 +1,55 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/svmsg/svmsg_receive.c b/svmsg/svmsg_receive.c new file mode 100644 index 0000000..8ce8fa9 --- /dev/null +++ b/svmsg/svmsg_receive.c @@ -0,0 +1,94 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/svmsg/svmsg_rm.c b/svmsg/svmsg_rm.c new file mode 100644 index 0000000..60fd8e4 --- /dev/null +++ b/svmsg/svmsg_rm.c @@ -0,0 +1,34 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/svmsg/svmsg_send.c b/svmsg/svmsg_send.c new file mode 100644 index 0000000..1fa36d1 --- /dev/null +++ b/svmsg/svmsg_send.c @@ -0,0 +1,83 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/svsem/Makefile b/svsem/Makefile new file mode 100644 index 0000000..4ab71a1 --- /dev/null +++ b/svsem/Makefile @@ -0,0 +1,19 @@ +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 diff --git a/svsem/binary_sems.c b/svsem/binary_sems.c new file mode 100644 index 0000000..c85b80c --- /dev/null +++ b/svsem/binary_sems.c @@ -0,0 +1,72 @@ +/*************************************************************************\ +* 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 +#include +#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); +} diff --git a/svsem/binary_sems.h b/svsem/binary_sems.h new file mode 100644 index 0000000..ca75784 --- /dev/null +++ b/svsem/binary_sems.h @@ -0,0 +1,36 @@ +/*************************************************************************\ +* 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 diff --git a/svsem/event_flags.c b/svsem/event_flags.c new file mode 100644 index 0000000..226147b --- /dev/null +++ b/svsem/event_flags.c @@ -0,0 +1,81 @@ +/*************************************************************************\ +* 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 +#include +#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; +} diff --git a/svsem/event_flags.h b/svsem/event_flags.h new file mode 100644 index 0000000..14ac62c --- /dev/null +++ b/svsem/event_flags.h @@ -0,0 +1,40 @@ +/*************************************************************************\ +* 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 diff --git a/svsem/semun.h b/svsem/semun.h new file mode 100644 index 0000000..5562576 --- /dev/null +++ b/svsem/semun.h @@ -0,0 +1,39 @@ +/*************************************************************************\ +* 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 /* For portability */ +#include + +#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 diff --git a/svsem/svsem_bad_init.c b/svsem/svsem_bad_init.c new file mode 100644 index 0000000..de41b81 --- /dev/null +++ b/svsem/svsem_bad_init.c @@ -0,0 +1,63 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#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); +} diff --git a/svsem/svsem_create.c b/svsem/svsem_create.c new file mode 100644 index 0000000..c421e48 --- /dev/null +++ b/svsem/svsem_create.c @@ -0,0 +1,106 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include +#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); +} diff --git a/svsem/svsem_demo.c b/svsem/svsem_demo.c new file mode 100644 index 0000000..cff0fdb --- /dev/null +++ b/svsem/svsem_demo.c @@ -0,0 +1,65 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#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); +} diff --git a/svsem/svsem_good_init.c b/svsem/svsem_good_init.c new file mode 100644 index 0000000..ee38511 --- /dev/null +++ b/svsem/svsem_good_init.c @@ -0,0 +1,103 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#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); +} diff --git a/svsem/svsem_info.c b/svsem/svsem_info.c new file mode 100644 index 0000000..4744d6e --- /dev/null +++ b/svsem/svsem_info.c @@ -0,0 +1,42 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/svsem/svsem_mon.c b/svsem/svsem_mon.c new file mode 100644 index 0000000..0dc2c9e --- /dev/null +++ b/svsem/svsem_mon.c @@ -0,0 +1,63 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#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); +} diff --git a/svsem/svsem_op.c b/svsem/svsem_op.c new file mode 100644 index 0000000..00c2f5f --- /dev/null +++ b/svsem/svsem_op.c @@ -0,0 +1,123 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#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: {+|-}[n][u]\n"); + fprintf(stderr, " or: =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); +} diff --git a/svsem/svsem_rm.c b/svsem/svsem_rm.c new file mode 100644 index 0000000..51f03c6 --- /dev/null +++ b/svsem/svsem_rm.c @@ -0,0 +1,37 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/svsem/svsem_setall.c b/svsem/svsem_setall.c new file mode 100644 index 0000000..252d528 --- /dev/null +++ b/svsem/svsem_setall.c @@ -0,0 +1,64 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/svshm/Makefile b/svshm/Makefile new file mode 100644 index 0000000..9951b46 --- /dev/null +++ b/svshm/Makefile @@ -0,0 +1,22 @@ +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 diff --git a/svshm/svshm_attach.c b/svshm/svshm_attach.c new file mode 100644 index 0000000..87444a3 --- /dev/null +++ b/svshm/svshm_attach.c @@ -0,0 +1,66 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/svshm/svshm_create.c b/svshm/svshm_create.c new file mode 100644 index 0000000..cd928bf --- /dev/null +++ b/svshm/svshm_create.c @@ -0,0 +1,108 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include +#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); +} diff --git a/svshm/svshm_info.c b/svshm/svshm_info.c new file mode 100644 index 0000000..a461af3 --- /dev/null +++ b/svshm/svshm_info.c @@ -0,0 +1,42 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/svshm/svshm_lock.c b/svshm/svshm_lock.c new file mode 100644 index 0000000..067eadd --- /dev/null +++ b/svshm/svshm_lock.c @@ -0,0 +1,36 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/svshm/svshm_mon.c b/svshm/svshm_mon.c new file mode 100644 index 0000000..08ec311 --- /dev/null +++ b/svshm/svshm_mon.c @@ -0,0 +1,61 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#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); +} diff --git a/svshm/svshm_rm.c b/svshm/svshm_rm.c new file mode 100644 index 0000000..de71612 --- /dev/null +++ b/svshm/svshm_rm.c @@ -0,0 +1,35 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/svshm/svshm_unlock.c b/svshm/svshm_unlock.c new file mode 100644 index 0000000..46be092 --- /dev/null +++ b/svshm/svshm_unlock.c @@ -0,0 +1,34 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/svshm/svshm_xfr.h b/svshm/svshm_xfr.h new file mode 100644 index 0000000..3b2279c --- /dev/null +++ b/svshm/svshm_xfr.h @@ -0,0 +1,45 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include +#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 */ +}; diff --git a/svshm/svshm_xfr_reader.c b/svshm/svshm_xfr_reader.c new file mode 100644 index 0000000..a700187 --- /dev/null +++ b/svshm/svshm_xfr_reader.c @@ -0,0 +1,69 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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); +} diff --git a/svshm/svshm_xfr_writer.c b/svshm/svshm_xfr_writer.c new file mode 100644 index 0000000..1e5cb34 --- /dev/null +++ b/svshm/svshm_xfr_writer.c @@ -0,0 +1,100 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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); +} diff --git a/sysinfo/Makefile b/sysinfo/Makefile new file mode 100644 index 0000000..e7744da --- /dev/null +++ b/sysinfo/Makefile @@ -0,0 +1,19 @@ +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 diff --git a/sysinfo/procfs_pidmax.c b/sysinfo/procfs_pidmax.c new file mode 100644 index 0000000..bd34249 --- /dev/null +++ b/sysinfo/procfs_pidmax.c @@ -0,0 +1,64 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/sysinfo/procfs_user_exe.c b/sysinfo/procfs_user_exe.c new file mode 100644 index 0000000..913258d --- /dev/null +++ b/sysinfo/procfs_user_exe.c @@ -0,0 +1,118 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include +#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); +} diff --git a/sysinfo/t_uname.c b/sysinfo/t_uname.c new file mode 100644 index 0000000..c6946d1 --- /dev/null +++ b/sysinfo/t_uname.c @@ -0,0 +1,41 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/syslim/Makefile b/syslim/Makefile new file mode 100644 index 0000000..31b4fb6 --- /dev/null +++ b/syslim/Makefile @@ -0,0 +1,19 @@ +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 diff --git a/syslim/t_fpathconf.c b/syslim/t_fpathconf.c new file mode 100644 index 0000000..6f1432d --- /dev/null +++ b/syslim/t_fpathconf.c @@ -0,0 +1,44 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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); +} diff --git a/syslim/t_sysconf.c b/syslim/t_sysconf.c new file mode 100644 index 0000000..3de13d4 --- /dev/null +++ b/syslim/t_sysconf.c @@ -0,0 +1,46 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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); +} diff --git a/threads/Makefile b/threads/Makefile new file mode 100644 index 0000000..3b15009 --- /dev/null +++ b/threads/Makefile @@ -0,0 +1,40 @@ +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 diff --git a/threads/detached_attrib.c b/threads/detached_attrib.c new file mode 100644 index 0000000..486a5f5 --- /dev/null +++ b/threads/detached_attrib.c @@ -0,0 +1,55 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/threads/one_time_init.c b/threads/one_time_init.c new file mode 100644 index 0000000..fe25988 --- /dev/null +++ b/threads/one_time_init.c @@ -0,0 +1,100 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/threads/prod_condvar.c b/threads/prod_condvar.c new file mode 100644 index 0000000..bacbddb --- /dev/null +++ b/threads/prod_condvar.c @@ -0,0 +1,121 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/threads/prod_no_condvar.c b/threads/prod_no_condvar.c new file mode 100644 index 0000000..e98aa35 --- /dev/null +++ b/threads/prod_no_condvar.c @@ -0,0 +1,111 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/threads/pthread_barrier_demo.c b/threads/pthread_barrier_demo.c new file mode 100644 index 0000000..f0844f9 --- /dev/null +++ b/threads/pthread_barrier_demo.c @@ -0,0 +1,161 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/threads/simple_thread.c b/threads/simple_thread.c new file mode 100644 index 0000000..3473573 --- /dev/null +++ b/threads/simple_thread.c @@ -0,0 +1,49 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/threads/strerror.c b/threads/strerror.c new file mode 100644 index 0000000..cef2337 --- /dev/null +++ b/threads/strerror.c @@ -0,0 +1,38 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 */ +#include +#include /* 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; +} diff --git a/threads/strerror_test.c b/threads/strerror_test.c new file mode 100644 index 0000000..aed59e7 --- /dev/null +++ b/threads/strerror_test.c @@ -0,0 +1,58 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include /* Get declaration of strerror() */ +#include +#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); +} diff --git a/threads/strerror_tls.c b/threads/strerror_tls.c new file mode 100644 index 0000000..dc7d2ac --- /dev/null +++ b/threads/strerror_tls.c @@ -0,0 +1,46 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 */ +#include +#include /* Get declaration of strerror() */ +#include + +#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; +} diff --git a/threads/strerror_tsd.c b/threads/strerror_tsd.c new file mode 100644 index 0000000..c82d1d1 --- /dev/null +++ b/threads/strerror_tsd.c @@ -0,0 +1,84 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 */ +#include +#include /* Get declaration of strerror() */ +#include +#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; +} diff --git a/threads/thread_cancel.c b/threads/thread_cancel.c new file mode 100644 index 0000000..9605e2b --- /dev/null +++ b/threads/thread_cancel.c @@ -0,0 +1,62 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/threads/thread_cleanup.c b/threads/thread_cleanup.c new file mode 100644 index 0000000..71ae7d5 --- /dev/null +++ b/threads/thread_cleanup.c @@ -0,0 +1,111 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/threads/thread_incr.c b/threads/thread_incr.c new file mode 100644 index 0000000..9322435 --- /dev/null +++ b/threads/thread_incr.c @@ -0,0 +1,65 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/threads/thread_incr_mutex.c b/threads/thread_incr_mutex.c new file mode 100644 index 0000000..598dbe0 --- /dev/null +++ b/threads/thread_incr_mutex.c @@ -0,0 +1,73 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/threads/thread_incr_rwlock.c b/threads/thread_incr_rwlock.c new file mode 100644 index 0000000..b6942c3 --- /dev/null +++ b/threads/thread_incr_rwlock.c @@ -0,0 +1,77 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/threads/thread_incr_spinlock.c b/threads/thread_incr_spinlock.c new file mode 100644 index 0000000..59c1ede --- /dev/null +++ b/threads/thread_incr_spinlock.c @@ -0,0 +1,77 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/threads/thread_lock_speed.c b/threads/thread_lock_speed.c new file mode 100644 index 0000000..7b8e216 --- /dev/null +++ b/threads/thread_lock_speed.c @@ -0,0 +1,160 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/threads/thread_multijoin.c b/threads/thread_multijoin.c new file mode 100644 index 0000000..2421a69 --- /dev/null +++ b/threads/thread_multijoin.c @@ -0,0 +1,139 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/time/Makefile b/time/Makefile new file mode 100644 index 0000000..4f92fad --- /dev/null +++ b/time/Makefile @@ -0,0 +1,23 @@ +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 diff --git a/time/calendar_time.c b/time/calendar_time.c new file mode 100644 index 0000000..2d6e9a5 --- /dev/null +++ b/time/calendar_time.c @@ -0,0 +1,82 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#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); +} diff --git a/time/curr_time.c b/time/curr_time.c new file mode 100644 index 0000000..40a0133 --- /dev/null +++ b/time/curr_time.c @@ -0,0 +1,44 @@ +/*************************************************************************\ +* 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 +#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; +} diff --git a/time/curr_time.h b/time/curr_time.h new file mode 100644 index 0000000..3a34f12 --- /dev/null +++ b/time/curr_time.h @@ -0,0 +1,22 @@ +/*************************************************************************\ +* 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 diff --git a/time/process_time.c b/time/process_time.c new file mode 100644 index 0000000..04eba51 --- /dev/null +++ b/time/process_time.c @@ -0,0 +1,75 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/time/show_time.c b/time/show_time.c new file mode 100644 index 0000000..3cd622a --- /dev/null +++ b/time/show_time.c @@ -0,0 +1,56 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/time/strtime.c b/time/strtime.c new file mode 100644 index 0000000..8ddf220 --- /dev/null +++ b/time/strtime.c @@ -0,0 +1,59 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/time/t_stime.c b/time/t_stime.c new file mode 100644 index 0000000..f60fe4c --- /dev/null +++ b/time/t_stime.c @@ -0,0 +1,45 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/timers/Makefile b/timers/Makefile new file mode 100644 index 0000000..dc3477e --- /dev/null +++ b/timers/Makefile @@ -0,0 +1,25 @@ +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 diff --git a/timers/demo_timerfd.c b/timers/demo_timerfd.c new file mode 100644 index 0000000..e70b4ab --- /dev/null +++ b/timers/demo_timerfd.c @@ -0,0 +1,80 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include /* 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); +} diff --git a/timers/itimerspec_from_str.c b/timers/itimerspec_from_str.c new file mode 100644 index 0000000..d8af189 --- /dev/null +++ b/timers/itimerspec_from_str.c @@ -0,0 +1,58 @@ +/*************************************************************************\ +* 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 +#include +#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 diff --git a/timers/itimerspec_from_str.h b/timers/itimerspec_from_str.h new file mode 100644 index 0000000..fd451c8 --- /dev/null +++ b/timers/itimerspec_from_str.h @@ -0,0 +1,24 @@ +/*************************************************************************\ +* 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 + +void itimerspecFromStr(char *str, struct itimerspec *tsp); + +#endif diff --git a/timers/ptmr_null_evp.c b/timers/ptmr_null_evp.c new file mode 100644 index 0000000..55af1a5 --- /dev/null +++ b/timers/ptmr_null_evp.c @@ -0,0 +1,70 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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(); +} diff --git a/timers/ptmr_sigev_signal.c b/timers/ptmr_sigev_signal.c new file mode 100644 index 0000000..5c90706 --- /dev/null +++ b/timers/ptmr_sigev_signal.c @@ -0,0 +1,98 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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(); +} diff --git a/timers/ptmr_sigev_thread.c b/timers/ptmr_sigev_thread.c new file mode 100644 index 0000000..f13ee51 --- /dev/null +++ b/timers/ptmr_sigev_thread.c @@ -0,0 +1,126 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#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); + } +} diff --git a/timers/real_timer.c b/timers/real_timer.c new file mode 100644 index 0000000..d1739b7 --- /dev/null +++ b/timers/real_timer.c @@ -0,0 +1,136 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#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); + } +} diff --git a/timers/t_clock_nanosleep.c b/timers/t_clock_nanosleep.c new file mode 100644 index 0000000..12d376c --- /dev/null +++ b/timers/t_clock_nanosleep.c @@ -0,0 +1,108 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#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); +} diff --git a/timers/t_nanosleep.c b/timers/t_nanosleep.c new file mode 100644 index 0000000..a609405 --- /dev/null +++ b/timers/t_nanosleep.c @@ -0,0 +1,76 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#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); +} diff --git a/timers/timed_read.c b/timers/timed_read.c new file mode 100644 index 0000000..59c1d2d --- /dev/null +++ b/timers/timed_read.c @@ -0,0 +1,70 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/tty/Makefile b/tty/Makefile new file mode 100644 index 0000000..25f0477 --- /dev/null +++ b/tty/Makefile @@ -0,0 +1,17 @@ +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 diff --git a/tty/demo_SIGWINCH.c b/tty/demo_SIGWINCH.c new file mode 100644 index 0000000..5a2245e --- /dev/null +++ b/tty/demo_SIGWINCH.c @@ -0,0 +1,49 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#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); + } +} diff --git a/tty/new_intr.c b/tty/new_intr.c new file mode 100644 index 0000000..7b1159b --- /dev/null +++ b/tty/new_intr.c @@ -0,0 +1,53 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#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); +} diff --git a/tty/no_echo.c b/tty/no_echo.c new file mode 100644 index 0000000..cc864c7 --- /dev/null +++ b/tty/no_echo.c @@ -0,0 +1,53 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/tty/test_tty_functions.c b/tty/test_tty_functions.c new file mode 100644 index 0000000..012c1a1 --- /dev/null +++ b/tty/test_tty_functions.c @@ -0,0 +1,173 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#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); +} diff --git a/tty/tty_functions.c b/tty/tty_functions.c new file mode 100644 index 0000000..726cc8a --- /dev/null +++ b/tty/tty_functions.c @@ -0,0 +1,89 @@ +/*************************************************************************\ +* 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 +#include +#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; +} diff --git a/tty/tty_functions.h b/tty/tty_functions.h new file mode 100644 index 0000000..905496b --- /dev/null +++ b/tty/tty_functions.h @@ -0,0 +1,26 @@ +/*************************************************************************\ +* 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 + +int ttySetCbreak(int fd, struct termios *prevTermios); + +int ttySetRaw(int fd, struct termios *prevTermios); + +#endif diff --git a/tty/ttyname.c b/tty/ttyname.c new file mode 100644 index 0000000..cc77b37 --- /dev/null +++ b/tty/ttyname.c @@ -0,0 +1,123 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#include +#include +#include + +/* 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"); +} diff --git a/users_groups/Makefile b/users_groups/Makefile new file mode 100644 index 0000000..879664b --- /dev/null +++ b/users_groups/Makefile @@ -0,0 +1,22 @@ +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 diff --git a/users_groups/check_password.c b/users_groups/check_password.c new file mode 100644 index 0000000..5807e84 --- /dev/null +++ b/users_groups/check_password.c @@ -0,0 +1,96 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 */ +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE /* Get crypt() declaration from */ +#endif +#endif +#include +#include +#include +#include +#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); +} diff --git a/users_groups/t_getpwent.c b/users_groups/t_getpwent.c new file mode 100644 index 0000000..387800c --- /dev/null +++ b/users_groups/t_getpwent.c @@ -0,0 +1,30 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/users_groups/t_getpwnam_r.c b/users_groups/t_getpwnam_r.c new file mode 100644 index 0000000..4a9d686 --- /dev/null +++ b/users_groups/t_getpwnam_r.c @@ -0,0 +1,48 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/users_groups/ugid_functions.c b/users_groups/ugid_functions.c new file mode 100644 index 0000000..fe11872 --- /dev/null +++ b/users_groups/ugid_functions.c @@ -0,0 +1,81 @@ +/*************************************************************************\ +* 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 +#include +#include +#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; +} diff --git a/users_groups/ugid_functions.h b/users_groups/ugid_functions.h new file mode 100644 index 0000000..9ff36e3 --- /dev/null +++ b/users_groups/ugid_functions.h @@ -0,0 +1,30 @@ +/*************************************************************************\ +* 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 diff --git a/vmem/Makefile b/vmem/Makefile new file mode 100644 index 0000000..4ce0ef7 --- /dev/null +++ b/vmem/Makefile @@ -0,0 +1,19 @@ +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 diff --git a/vmem/madvise_dontneed.c b/vmem/madvise_dontneed.c new file mode 100644 index 0000000..94635cc --- /dev/null +++ b/vmem/madvise_dontneed.c @@ -0,0 +1,90 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#include +#include +#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); +} diff --git a/vmem/memlock.c b/vmem/memlock.c new file mode 100644 index 0000000..6042828 --- /dev/null +++ b/vmem/memlock.c @@ -0,0 +1,104 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 */ +#include +#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); +} diff --git a/vmem/t_mprotect.c b/vmem/t_mprotect.c new file mode 100644 index 0000000..9c20896 --- /dev/null +++ b/vmem/t_mprotect.c @@ -0,0 +1,57 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 */ +#include +#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); +} diff --git a/xattr/Makefile b/xattr/Makefile new file mode 100644 index 0000000..095f9a4 --- /dev/null +++ b/xattr/Makefile @@ -0,0 +1,19 @@ +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 diff --git a/xattr/t_setxattr.c b/xattr/t_setxattr.c new file mode 100644 index 0000000..60fa296 --- /dev/null +++ b/xattr/t_setxattr.c @@ -0,0 +1,41 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +} diff --git a/xattr/xattr_view.c b/xattr/xattr_view.c new file mode 100644 index 0000000..69554d1 --- /dev/null +++ b/xattr/xattr_view.c @@ -0,0 +1,82 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2017. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU General Public License as published by the * +* Free Software Foundation, either 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 +#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); +}