My initial commit message
authormcc <mcc@duce.noddleisland.org>
Thu, 19 Oct 2017 16:25:04 +0000 (16:25 +0000)
committermcc <mcc@duce.noddleisland.org>
Thu, 19 Oct 2017 16:25:04 +0000 (16:25 +0000)
486 files changed:
BUILDING [new file with mode: 0644]
CHANGES [new file with mode: 0644]
COPYING.agpl-v3 [new file with mode: 0644]
COPYING.gpl-v3 [new file with mode: 0644]
COPYING.lgpl-v3 [new file with mode: 0644]
Makefile [new file with mode: 0644]
Makefile.inc [new file with mode: 0644]
Makefile.inc.FreeBSD [new file with mode: 0644]
Makefile.inc.HP-UX [new file with mode: 0644]
Makefile.inc.MacOSX [new file with mode: 0644]
Makefile.inc.Solaris [new file with mode: 0644]
Makefile.inc.Tru64 [new file with mode: 0644]
README.md [new file with mode: 0644]
acl/Makefile [new file with mode: 0644]
acl/acl_update.c [new file with mode: 0644]
acl/acl_view.c [new file with mode: 0644]
altio/Makefile [new file with mode: 0644]
altio/demo_sigio.c [new file with mode: 0644]
altio/epoll_input.c [new file with mode: 0644]
altio/poll_pipes.c [new file with mode: 0644]
altio/select_mq.c [new file with mode: 0644]
altio/self_pipe.c [new file with mode: 0644]
altio/t_select.c [new file with mode: 0644]
cap/Makefile [new file with mode: 0644]
cap/check_password_caps.c [new file with mode: 0644]
daemons/Makefile [new file with mode: 0644]
daemons/become_daemon.c [new file with mode: 0644]
daemons/become_daemon.h [new file with mode: 0644]
daemons/daemon_SIGHUP.c [new file with mode: 0644]
daemons/t_syslog.c [new file with mode: 0644]
daemons/test_become_daemon.c [new file with mode: 0644]
dirs_links/Makefile [new file with mode: 0644]
dirs_links/bad_symlink.c [new file with mode: 0644]
dirs_links/file_type_stats.c [new file with mode: 0644]
dirs_links/list_files.c [new file with mode: 0644]
dirs_links/list_files_readdir_r.c [new file with mode: 0644]
dirs_links/nftw_dir_tree.c [new file with mode: 0644]
dirs_links/t_dirbasename.c [new file with mode: 0644]
dirs_links/t_unlink.c [new file with mode: 0644]
dirs_links/view_symlink.c [new file with mode: 0644]
filebuff/Makefile [new file with mode: 0644]
filebuff/copy.c [new file with mode: 0644]
filebuff/direct_read.c [new file with mode: 0644]
filebuff/mix23_linebuff.c [new file with mode: 0644]
filebuff/mix23io.c [new file with mode: 0644]
filebuff/write_bytes.c [new file with mode: 0644]
fileio/Makefile [new file with mode: 0644]
fileio/atomic_append.c [new file with mode: 0644]
fileio/bad_exclusive_open.c [new file with mode: 0644]
fileio/copy.c [new file with mode: 0644]
fileio/large_file.c [new file with mode: 0644]
fileio/multi_descriptors.c [new file with mode: 0644]
fileio/seek_io.c [new file with mode: 0644]
fileio/t_readv.c [new file with mode: 0644]
fileio/t_truncate.c [new file with mode: 0644]
filelock/Makefile [new file with mode: 0644]
filelock/create_pid_file.c [new file with mode: 0644]
filelock/create_pid_file.h [new file with mode: 0644]
filelock/i_fcntl_locking.c [new file with mode: 0644]
filelock/region_locking.c [new file with mode: 0644]
filelock/region_locking.h [new file with mode: 0644]
filelock/t_flock.c [new file with mode: 0644]
files/Makefile [new file with mode: 0644]
files/chiflag.c [new file with mode: 0644]
files/file_perms.c [new file with mode: 0644]
files/file_perms.h [new file with mode: 0644]
files/t_chown.c [new file with mode: 0644]
files/t_stat.c [new file with mode: 0644]
files/t_umask.c [new file with mode: 0644]
files/t_utime.c [new file with mode: 0644]
files/t_utimes.c [new file with mode: 0644]
filesys/Makefile [new file with mode: 0644]
filesys/t_mount.c [new file with mode: 0644]
filesys/t_statfs.c [new file with mode: 0644]
filesys/t_statvfs.c [new file with mode: 0644]
filesys/t_umount.c [new file with mode: 0644]
getopt/Makefile [new file with mode: 0644]
getopt/t_getopt.c [new file with mode: 0644]
inotify/Makefile [new file with mode: 0644]
inotify/demo_inotify.c [new file with mode: 0644]
inotify/dnotify.c [new file with mode: 0644]
inotify/inotify_dtree.c [new file with mode: 0644]
inotify/rand_dtree.c [new file with mode: 0644]
lib/Build_ename.sh [new file with mode: 0644]
lib/Makefile [new file with mode: 0644]
lib/README [new file with mode: 0644]
lib/alt_functions.c [new file with mode: 0644]
lib/alt_functions.h [new file with mode: 0644]
lib/become_daemon.c [new symlink]
lib/become_daemon.h [new symlink]
lib/binary_sems.c [new symlink]
lib/binary_sems.h [new symlink]
lib/create_pid_file.c [new symlink]
lib/create_pid_file.h [new symlink]
lib/curr_time.c [new symlink]
lib/curr_time.h [new symlink]
lib/error_functions.c [new file with mode: 0644]
lib/error_functions.h [new file with mode: 0644]
lib/event_flags.c [new symlink]
lib/event_flags.h [new symlink]
lib/file_perms.c [new symlink]
lib/file_perms.h [new symlink]
lib/get_num.c [new file with mode: 0644]
lib/get_num.h [new file with mode: 0644]
lib/inet_sockets.c [new symlink]
lib/inet_sockets.h [new symlink]
lib/itimerspec_from_str.c [new symlink]
lib/itimerspec_from_str.h [new symlink]
lib/print_rlimit.c [new symlink]
lib/print_rlimit.h [new symlink]
lib/print_rusage.c [new symlink]
lib/print_rusage.h [new symlink]
lib/print_wait_status.c [new symlink]
lib/print_wait_status.h [new symlink]
lib/pty_fork.c [new symlink]
lib/pty_fork.h [new symlink]
lib/pty_master_open.c [new symlink]
lib/pty_master_open.h [new symlink]
lib/rdwrn.c [new symlink]
lib/rdwrn.h [new symlink]
lib/read_line.c [new symlink]
lib/read_line.h [new symlink]
lib/read_line_buf.c [new symlink]
lib/read_line_buf.h [new symlink]
lib/region_locking.c [new symlink]
lib/region_locking.h [new symlink]
lib/semun.h [new symlink]
lib/signal.c [new symlink]
lib/signal_functions.c [new symlink]
lib/signal_functions.h [new symlink]
lib/tlpi_hdr.h [new file with mode: 0644]
lib/tty_functions.c [new symlink]
lib/tty_functions.h [new symlink]
lib/ugid_functions.c [new symlink]
lib/ugid_functions.h [new symlink]
lib/unix_sockets.c [new symlink]
lib/unix_sockets.h [new symlink]
loginacct/Makefile [new file with mode: 0644]
loginacct/dump_utmpx.c [new file with mode: 0644]
loginacct/utmpx_login.c [new file with mode: 0644]
loginacct/view_lastlog.c [new file with mode: 0644]
memalloc/Makefile [new file with mode: 0644]
memalloc/free_and_sbrk.c [new file with mode: 0644]
mmap/Makefile [new file with mode: 0644]
mmap/anon_mmap.c [new file with mode: 0644]
mmap/mmcat.c [new file with mode: 0644]
mmap/mmcopy.c [new file with mode: 0644]
mmap/t_mmap.c [new file with mode: 0644]
mmap/t_remap_file_pages.c [new file with mode: 0644]
namespaces/Makefile [new file with mode: 0644]
namespaces/demo_userns.c [new file with mode: 0644]
namespaces/demo_uts_namespaces.c [new file with mode: 0644]
namespaces/hostname.c [new file with mode: 0644]
namespaces/multi_pidns.c [new file with mode: 0644]
namespaces/ns_child_exec.c [new file with mode: 0644]
namespaces/ns_exec.c [new file with mode: 0644]
namespaces/ns_run.c [new file with mode: 0644]
namespaces/orphan.c [new file with mode: 0644]
namespaces/pidns_init_sleep.c [new file with mode: 0644]
namespaces/simple_init.c [new file with mode: 0644]
namespaces/t_setns_userns.c [new file with mode: 0644]
namespaces/unshare.c [new file with mode: 0644]
namespaces/userns_child_exec.c [new file with mode: 0644]
namespaces/userns_overview.go [new file with mode: 0644]
namespaces/userns_setns_test.c [new file with mode: 0644]
pgsjc/Makefile [new file with mode: 0644]
pgsjc/catch_SIGHUP.c [new file with mode: 0644]
pgsjc/disc_SIGHUP.c [new file with mode: 0644]
pgsjc/handling_SIGTSTP.c [new file with mode: 0644]
pgsjc/job_mon.c [new file with mode: 0644]
pgsjc/orphaned_pgrp_SIGHUP.c [new file with mode: 0644]
pgsjc/t_setsid.c [new file with mode: 0644]
pipes/Makefile [new file with mode: 0644]
pipes/change_case.c [new file with mode: 0644]
pipes/fifo_seqnum.h [new file with mode: 0644]
pipes/fifo_seqnum_client.c [new file with mode: 0644]
pipes/fifo_seqnum_server.c [new file with mode: 0644]
pipes/pipe_ls_wc.c [new file with mode: 0644]
pipes/pipe_sync.c [new file with mode: 0644]
pipes/popen_glob.c [new file with mode: 0644]
pipes/simple_pipe.c [new file with mode: 0644]
pmsg/Makefile [new file with mode: 0644]
pmsg/mq_notify_sig.c [new file with mode: 0644]
pmsg/mq_notify_sigwaitinfo.c [new file with mode: 0644]
pmsg/mq_notify_thread.c [new file with mode: 0644]
pmsg/mq_notify_via_signal.c [new file with mode: 0644]
pmsg/mq_notify_via_thread.c [new file with mode: 0644]
pmsg/pmsg_create.c [new file with mode: 0644]
pmsg/pmsg_getattr.c [new file with mode: 0644]
pmsg/pmsg_receive.c [new file with mode: 0644]
pmsg/pmsg_send.c [new file with mode: 0644]
pmsg/pmsg_unlink.c [new file with mode: 0644]
proc/Makefile [new file with mode: 0644]
proc/bad_longjmp.c [new file with mode: 0644]
proc/display_env.c [new file with mode: 0644]
proc/longjmp.c [new file with mode: 0644]
proc/mem_segments.c [new file with mode: 0644]
proc/modify_env.c [new file with mode: 0644]
proc/necho.c [new file with mode: 0644]
proc/setenv.c [new file with mode: 0644]
proc/setjmp_vars.c [new file with mode: 0644]
proc/t_getenv.c [new file with mode: 0644]
proccred/Makefile [new file with mode: 0644]
proccred/idshow.c [new file with mode: 0644]
procexec/Makefile [new file with mode: 0644]
procexec/acct_on.c [new file with mode: 0644]
procexec/acct_v3_view.c [new file with mode: 0644]
procexec/acct_view.c [new file with mode: 0644]
procexec/child_status.c [new file with mode: 0644]
procexec/closeonexec.c [new file with mode: 0644]
procexec/demo_clone.c [new file with mode: 0644]
procexec/envargs.c [new file with mode: 0644]
procexec/execlp.c [new file with mode: 0644]
procexec/exit_handlers.c [new file with mode: 0644]
procexec/footprint.c [new file with mode: 0644]
procexec/fork_file_sharing.c [new file with mode: 0644]
procexec/fork_sig_sync.c [new file with mode: 0644]
procexec/fork_stdio_buf.c [new file with mode: 0644]
procexec/fork_whos_on_first.c [new file with mode: 0644]
procexec/fork_whos_on_first.count.awk [new file with mode: 0755]
procexec/longest_line.awk [new file with mode: 0755]
procexec/make_zombie.c [new file with mode: 0644]
procexec/multi_SIGCHLD.c [new file with mode: 0644]
procexec/multi_wait.c [new file with mode: 0644]
procexec/necho.c [new file with mode: 0644]
procexec/orphan.c [new file with mode: 0644]
procexec/print_wait_status.c [new file with mode: 0644]
procexec/print_wait_status.h [new file with mode: 0644]
procexec/simple_system.c [new file with mode: 0644]
procexec/system.c [new file with mode: 0644]
procexec/t_clone.c [new file with mode: 0644]
procexec/t_execl.c [new file with mode: 0644]
procexec/t_execle.c [new file with mode: 0644]
procexec/t_execlp.c [new file with mode: 0644]
procexec/t_execve.c [new file with mode: 0644]
procexec/t_fork.c [new file with mode: 0644]
procexec/t_system.c [new file with mode: 0644]
procexec/t_vfork.c [new file with mode: 0644]
procexec/vfork_fd_test.c [new file with mode: 0644]
procpri/Makefile [new file with mode: 0644]
procpri/demo_sched_fifo.c [new file with mode: 0644]
procpri/sched_set.c [new file with mode: 0644]
procpri/sched_view.c [new file with mode: 0644]
procpri/t_sched_getaffinity.c [new file with mode: 0644]
procpri/t_sched_setaffinity.c [new file with mode: 0644]
procpri/t_setpriority.c [new file with mode: 0644]
procres/Makefile [new file with mode: 0644]
procres/print_rlimit.c [new file with mode: 0644]
procres/print_rlimit.h [new file with mode: 0644]
procres/print_rusage.c [new file with mode: 0644]
procres/print_rusage.h [new file with mode: 0644]
procres/rlimit_nproc.c [new file with mode: 0644]
procres/rusage.c [new file with mode: 0644]
procres/rusage_wait.c [new file with mode: 0644]
progconc/Makefile [new file with mode: 0644]
progconc/syscall_speed.c [new file with mode: 0644]
psem/Makefile [new file with mode: 0644]
psem/psem_create.c [new file with mode: 0644]
psem/psem_getvalue.c [new file with mode: 0644]
psem/psem_post.c [new file with mode: 0644]
psem/psem_timedwait.c [new file with mode: 0644]
psem/psem_trywait.c [new file with mode: 0644]
psem/psem_unlink.c [new file with mode: 0644]
psem/psem_wait.c [new file with mode: 0644]
psem/thread_incr_psem.c [new file with mode: 0644]
pshm/Makefile [new file with mode: 0644]
pshm/pshm_create.c [new file with mode: 0644]
pshm/pshm_read.c [new file with mode: 0644]
pshm/pshm_unlink.c [new file with mode: 0644]
pshm/pshm_write.c [new file with mode: 0644]
pty/Makefile [new file with mode: 0644]
pty/pty_fork.c [new file with mode: 0644]
pty/pty_fork.h [new file with mode: 0644]
pty/pty_master_open.c [new file with mode: 0644]
pty/pty_master_open.h [new file with mode: 0644]
pty/pty_master_open_bsd.c [new file with mode: 0644]
pty/script.c [new file with mode: 0644]
pty/unbuffer.c [new file with mode: 0644]
seccomp/Makefile [new file with mode: 0644]
seccomp/libseccomp_demo.c [new file with mode: 0644]
seccomp/seccomp_control_open.c [new file with mode: 0644]
seccomp/seccomp_deny_open.c [new file with mode: 0644]
seccomp/seccomp_perf.c [new file with mode: 0644]
shlibs/Demo_no_lib.sh [new file with mode: 0644]
shlibs/Demo_shared_lib.sh [new file with mode: 0644]
shlibs/Demo_static_lib.sh [new file with mode: 0644]
shlibs/Makefile [new file with mode: 0644]
shlibs/demo_Bsymbolic/build.sh [new file with mode: 0755]
shlibs/demo_Bsymbolic/foo1.c [new file with mode: 0644]
shlibs/demo_Bsymbolic/foo2.c [new file with mode: 0644]
shlibs/demo_Bsymbolic/foo3.c [new file with mode: 0644]
shlibs/demo_Bsymbolic/prog.c [new file with mode: 0644]
shlibs/dynload.c [new file with mode: 0644]
shlibs/mod1.c [new file with mode: 0644]
shlibs/mod2.c [new file with mode: 0644]
shlibs/mod3.c [new file with mode: 0644]
shlibs/prog.c [new file with mode: 0644]
shlibs/rpath_demo/d1/modx1.c [new file with mode: 0644]
shlibs/rpath_demo/d2/modx2.c [new file with mode: 0644]
shlibs/version_scripts/sv_build.sh [new file with mode: 0644]
shlibs/version_scripts/sv_lib_v1.c [new file with mode: 0644]
shlibs/version_scripts/sv_lib_v2.c [new file with mode: 0644]
shlibs/version_scripts/sv_libabc.c [new file with mode: 0644]
shlibs/version_scripts/sv_prog.c [new file with mode: 0644]
shlibs/version_scripts/sv_prog_abc.c [new file with mode: 0644]
shlibs/version_scripts/sv_prog_complex.c [new file with mode: 0644]
shlibs/version_scripts/vis_build.sh [new file with mode: 0644]
shlibs/version_scripts/vis_comm.c [new file with mode: 0644]
shlibs/version_scripts/vis_f1.c [new file with mode: 0644]
shlibs/version_scripts/vis_f2.c [new file with mode: 0644]
signals/Makefile [new file with mode: 0644]
signals/catch_rtsigs.c [new file with mode: 0644]
signals/demo_SIGFPE.c [new file with mode: 0644]
signals/ignore_pending_sig.c [new file with mode: 0644]
signals/intquit.c [new file with mode: 0644]
signals/nonreentrant.c [new file with mode: 0644]
signals/ouch.c [new file with mode: 0644]
signals/sig_receiver.c [new file with mode: 0644]
signals/sig_sender.c [new file with mode: 0644]
signals/sig_speed_sigsuspend.c [new file with mode: 0644]
signals/siginterrupt.c [new file with mode: 0644]
signals/sigmask_longjmp.c [new file with mode: 0644]
signals/signal.c [new file with mode: 0644]
signals/signal_functions.c [new file with mode: 0644]
signals/signal_functions.h [new file with mode: 0644]
signals/signalfd_sigval.c [new file with mode: 0644]
signals/t_kill.c [new file with mode: 0644]
signals/t_sigaltstack.c [new file with mode: 0644]
signals/t_sigqueue.c [new file with mode: 0644]
signals/t_sigsuspend.c [new file with mode: 0644]
signals/t_sigwaitinfo.c [new file with mode: 0644]
sockets/Makefile [new file with mode: 0644]
sockets/README [new file with mode: 0644]
sockets/i6d_ucase.h [new file with mode: 0644]
sockets/i6d_ucase_cl.c [new file with mode: 0644]
sockets/i6d_ucase_sv.c [new file with mode: 0644]
sockets/id_echo.h [new file with mode: 0644]
sockets/id_echo_cl.c [new file with mode: 0644]
sockets/id_echo_sv.c [new file with mode: 0644]
sockets/inet_sockets.c [new file with mode: 0644]
sockets/inet_sockets.h [new file with mode: 0644]
sockets/is_echo_cl.c [new file with mode: 0644]
sockets/is_echo_inetd_sv.c [new file with mode: 0644]
sockets/is_echo_sv.c [new file with mode: 0644]
sockets/is_echo_v2_sv.c [new file with mode: 0644]
sockets/is_seqnum.h [new file with mode: 0644]
sockets/is_seqnum_cl.c [new file with mode: 0644]
sockets/is_seqnum_sv.c [new file with mode: 0644]
sockets/is_seqnum_v2.h [new file with mode: 0644]
sockets/is_seqnum_v2_cl.c [new file with mode: 0644]
sockets/is_seqnum_v2_sv.c [new file with mode: 0644]
sockets/list_host_addresses.c [new file with mode: 0644]
sockets/rdwrn.c [new file with mode: 0644]
sockets/rdwrn.h [new file with mode: 0644]
sockets/read_line.c [new file with mode: 0644]
sockets/read_line.h [new file with mode: 0644]
sockets/read_line_buf.c [new file with mode: 0644]
sockets/read_line_buf.h [new file with mode: 0644]
sockets/scm_cred.h [new file with mode: 0644]
sockets/scm_cred_recv.c [new file with mode: 0644]
sockets/scm_cred_send.c [new file with mode: 0644]
sockets/scm_rights.h [new file with mode: 0644]
sockets/scm_rights_recv.c [new file with mode: 0644]
sockets/scm_rights_send.c [new file with mode: 0644]
sockets/sendfile.c [new file with mode: 0644]
sockets/socknames.c [new file with mode: 0644]
sockets/t_gethostbyname.c [new file with mode: 0644]
sockets/t_getservbyname.c [new file with mode: 0644]
sockets/ud_ucase.h [new file with mode: 0644]
sockets/ud_ucase_cl.c [new file with mode: 0644]
sockets/ud_ucase_sv.c [new file with mode: 0644]
sockets/unix_sockets.c [new file with mode: 0644]
sockets/unix_sockets.h [new file with mode: 0644]
sockets/us_abstract_bind.c [new file with mode: 0644]
sockets/us_xfr.h [new file with mode: 0644]
sockets/us_xfr_cl.c [new file with mode: 0644]
sockets/us_xfr_sv.c [new file with mode: 0644]
sockets/us_xfr_v2.h [new file with mode: 0644]
sockets/us_xfr_v2_cl.c [new file with mode: 0644]
sockets/us_xfr_v2_sv.c [new file with mode: 0644]
svipc/Makefile [new file with mode: 0644]
svipc/svmsg_demo_server.c [new file with mode: 0644]
svipc/t_ftok.c [new file with mode: 0644]
svmsg/Makefile [new file with mode: 0644]
svmsg/svmsg_chqbytes.c [new file with mode: 0644]
svmsg/svmsg_create.c [new file with mode: 0644]
svmsg/svmsg_file.h [new file with mode: 0644]
svmsg/svmsg_file_client.c [new file with mode: 0644]
svmsg/svmsg_file_server.c [new file with mode: 0644]
svmsg/svmsg_info.c [new file with mode: 0644]
svmsg/svmsg_ls.c [new file with mode: 0644]
svmsg/svmsg_receive.c [new file with mode: 0644]
svmsg/svmsg_rm.c [new file with mode: 0644]
svmsg/svmsg_send.c [new file with mode: 0644]
svsem/Makefile [new file with mode: 0644]
svsem/binary_sems.c [new file with mode: 0644]
svsem/binary_sems.h [new file with mode: 0644]
svsem/event_flags.c [new file with mode: 0644]
svsem/event_flags.h [new file with mode: 0644]
svsem/semun.h [new file with mode: 0644]
svsem/svsem_bad_init.c [new file with mode: 0644]
svsem/svsem_create.c [new file with mode: 0644]
svsem/svsem_demo.c [new file with mode: 0644]
svsem/svsem_good_init.c [new file with mode: 0644]
svsem/svsem_info.c [new file with mode: 0644]
svsem/svsem_mon.c [new file with mode: 0644]
svsem/svsem_op.c [new file with mode: 0644]
svsem/svsem_rm.c [new file with mode: 0644]
svsem/svsem_setall.c [new file with mode: 0644]
svshm/Makefile [new file with mode: 0644]
svshm/svshm_attach.c [new file with mode: 0644]
svshm/svshm_create.c [new file with mode: 0644]
svshm/svshm_info.c [new file with mode: 0644]
svshm/svshm_lock.c [new file with mode: 0644]
svshm/svshm_mon.c [new file with mode: 0644]
svshm/svshm_rm.c [new file with mode: 0644]
svshm/svshm_unlock.c [new file with mode: 0644]
svshm/svshm_xfr.h [new file with mode: 0644]
svshm/svshm_xfr_reader.c [new file with mode: 0644]
svshm/svshm_xfr_writer.c [new file with mode: 0644]
sysinfo/Makefile [new file with mode: 0644]
sysinfo/procfs_pidmax.c [new file with mode: 0644]
sysinfo/procfs_user_exe.c [new file with mode: 0644]
sysinfo/t_uname.c [new file with mode: 0644]
syslim/Makefile [new file with mode: 0644]
syslim/t_fpathconf.c [new file with mode: 0644]
syslim/t_sysconf.c [new file with mode: 0644]
threads/Makefile [new file with mode: 0644]
threads/detached_attrib.c [new file with mode: 0644]
threads/one_time_init.c [new file with mode: 0644]
threads/prod_condvar.c [new file with mode: 0644]
threads/prod_no_condvar.c [new file with mode: 0644]
threads/pthread_barrier_demo.c [new file with mode: 0644]
threads/simple_thread.c [new file with mode: 0644]
threads/strerror.c [new file with mode: 0644]
threads/strerror_test.c [new file with mode: 0644]
threads/strerror_tls.c [new file with mode: 0644]
threads/strerror_tsd.c [new file with mode: 0644]
threads/thread_cancel.c [new file with mode: 0644]
threads/thread_cleanup.c [new file with mode: 0644]
threads/thread_incr.c [new file with mode: 0644]
threads/thread_incr_mutex.c [new file with mode: 0644]
threads/thread_incr_rwlock.c [new file with mode: 0644]
threads/thread_incr_spinlock.c [new file with mode: 0644]
threads/thread_lock_speed.c [new file with mode: 0644]
threads/thread_multijoin.c [new file with mode: 0644]
time/Makefile [new file with mode: 0644]
time/calendar_time.c [new file with mode: 0644]
time/curr_time.c [new file with mode: 0644]
time/curr_time.h [new file with mode: 0644]
time/process_time.c [new file with mode: 0644]
time/show_time.c [new file with mode: 0644]
time/strtime.c [new file with mode: 0644]
time/t_stime.c [new file with mode: 0644]
timers/Makefile [new file with mode: 0644]
timers/demo_timerfd.c [new file with mode: 0644]
timers/itimerspec_from_str.c [new file with mode: 0644]
timers/itimerspec_from_str.h [new file with mode: 0644]
timers/ptmr_null_evp.c [new file with mode: 0644]
timers/ptmr_sigev_signal.c [new file with mode: 0644]
timers/ptmr_sigev_thread.c [new file with mode: 0644]
timers/real_timer.c [new file with mode: 0644]
timers/t_clock_nanosleep.c [new file with mode: 0644]
timers/t_nanosleep.c [new file with mode: 0644]
timers/timed_read.c [new file with mode: 0644]
tty/Makefile [new file with mode: 0644]
tty/demo_SIGWINCH.c [new file with mode: 0644]
tty/new_intr.c [new file with mode: 0644]
tty/no_echo.c [new file with mode: 0644]
tty/test_tty_functions.c [new file with mode: 0644]
tty/tty_functions.c [new file with mode: 0644]
tty/tty_functions.h [new file with mode: 0644]
tty/ttyname.c [new file with mode: 0644]
users_groups/Makefile [new file with mode: 0644]
users_groups/check_password.c [new file with mode: 0644]
users_groups/t_getpwent.c [new file with mode: 0644]
users_groups/t_getpwnam_r.c [new file with mode: 0644]
users_groups/ugid_functions.c [new file with mode: 0644]
users_groups/ugid_functions.h [new file with mode: 0644]
vmem/Makefile [new file with mode: 0644]
vmem/madvise_dontneed.c [new file with mode: 0644]
vmem/memlock.c [new file with mode: 0644]
vmem/t_mprotect.c [new file with mode: 0644]
xattr/Makefile [new file with mode: 0644]
xattr/t_setxattr.c [new file with mode: 0644]
xattr/xattr_view.c [new file with mode: 0644]

diff --git a/BUILDING b/BUILDING
new file mode 100644 (file)
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 (file)
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 <sys/ipc.h> header file.
+                (Thanks to Robert P. J. Day.)
+
+2012-06-05
+        COPYING
+                Renamed to COPYING.agpl-v3
+        Added new files:
+                COPYING.gpl-v3
+                        Copy of the GNU General Public License, version 3
+                COPYING.lgpl-v3
+                        Copy of the GNU Lesser General Public License, version 3
+        lib/*
+                Changed the license of the library functions in the /lib
+                directory to GNU Lesser General Public License, version 3.
+        sockets/read_line_buf.c
+        sockets/read_line_buf.h
+                Created links for the sockets/read_line_buf.{c,h} files in the
+                lib/ directory, so that these files are licensed LGPLv3.
+
+        README
+                Updated to note the licensing of the library functions.
+
+2012-07-05
+        filesys/t_statfs.c
+        filesys/t_statvfs.c
+                Changed several printf() statements to use unsigned types for
+                various fields, since (in the case of the 'statvfs' structure at
+                least) these fields are specified as unsigned in SUSv[34].
+                (The 'statfs' structure isn't covered by SUSv[34], but using 
+                unsigned types seems reasonable and safe.)
+
+2012-07-22
+        filelock/create_pid_file.c
+                Small wording fix in a comment.
+                (Only in "dist" version of code.)
+        procexec/execlp.c
+                Small wording fix in a comment.
+        svshm/svshm_xfr_reader.c
+                Small wording fix in a comment.
+                (Only in "dist" version of code.)
+        sysinfo/procfs_pidmax.c
+                Small wording fix in a comment.
+                (Only in "dist" version of code.)
+        threads/one_time_init.c
+                Small wording fix in a comment.
+
+2012-07-26
+        filebuff/direct_read.c
+                Fix an error in comment describing program arguments.
+                (Thanks to Jason Orendorff.)
+                (Only in "dist" version of code.)
+
+2012-08-03
+        loginacct/dump_utmpx.c
+        loginacct/utmpx_login.c
+        loginacct/view_lastlog.c
+                Remove unneeded casts.
+                See the errata for pages 824, 829, and 831.
+
+2012-09-10
+        loginacct/dump_utmpx.c
+                Fix error introduced in 2012-08-03 changes.
+        altio/epoll_input.c
+                Improve a comment.
+                See the erratum for page 1363.
+
+2012-09-27
+        svsem/Makefile
+                Fixed a bug in the Makefile that causes svsem_demo.c
+                not to be built.
+                (Thanks to Jinnan Wang.)
+
+2012-09-30
+        timers/real_timer.c
+                Use NULL instead of 0 for last argument of setitimer() call.
+                See the erratum for page 484.
+                (Thanks to Trevor Woerner.)
+
+2012-10-02
+        Makefile.inc
+                Add "-Wno-unused-but-set-variable" to compiler flags
+                (the IMPL_CFLAGS macro), to prevent compilation warnings
+                in three of the example programs.  (Those warnings do not
+                correspond to real problems in the code.) The
+                "-Wunused-but-set-variable" flag was added (and turned on
+                by default) in GCC 4.6, which was released on 2011-03-35
+                (i.e., after TLPI was written).
+
+2012-10-15
+        loginacct/dump_utmpx.c
+        loginacct/utmpx_login.c
+        loginacct/view_lastlog.c
+                Revert the changes of 2012-08-03. These were made overlooking
+                the fact that in the 'utmpx' structure, 'tv_sec' is defined as
+                being of type 'int32_t', not 'time_t', so that the 2012-08-03
+                changes in fact cause warnings on 64-bit systems.
+                As a consequence, three (as yet unapplied) errata
+                for pages 824, 829, and 831 are removed.
+
+2012-10-18
+        procexec/demo_clone.c
+                Minor fix to a comment (s/will affect/may affect/).
+                Declare the variable that holds return status of write()
+                as 'ssize_t'.
+                (Thanks to Trevor Woerner.)
+
+2012-12-17
+        sockets/i6d_ucase_sv.c
+                Fix typo in a comment.
+                (Only in "dist" version of code.)
+                (Thanks to Kanak Kshetri.)
+        sockets/i6d_ucase_cl.c
+                Fix typo in a comment.
+                (Only in "dist" version of code.)
+
+2012-12-24
+        procexec/demo_clone.c
+                Add a comment at the top of the program clarifying that the
+                user must select a valid set of flags for the clone() call.
+                (Thanks to Jeffrey Thompson.)
+
+2013-01-02
+        altio/poll_pipes.c
+                Fix typos in two errExit() string arguments.
+                See the erratum for page 1340.
+
+2013-02-11
+        sockets/id_echo_sv.c
+                Remove unneeded use of the variable 'addrlen'.
+                See the erratum for page 1241.
+
+2013-03-09
+        procpri/sched_set.c
+                Fix a bug in the handling of the command-line arguments.
+                See the erratum for page 743.
+
+2013-03-10
+        procpri/Makefile
+                Move demo_sched_fifo from GEN_EXE to LINUX_EXE.
+                (Thanks to Antonio Jose Rodrigues.)
+        timers/Makefile
+                Remove demo_timerfd from GEN_EXE target.
+                (Thanks to Antonio Jose Rodrigues.)
+
+2013-03-11
+        sysinfo/t_uname.c
+                Make definition of _GNU_SOURCE conditional on __linux__.
+                See the erratum for page 230.
+
+2013-03-18
+        threads/detached_attrib.c
+                Fix a typo in a string:
+                        errExitEN(s, "pthread_attr_getdetachstate");
+                    ==>
+                        errExitEN(s, "pthread_attr_setdetachstate");
+
+                Interestingly, this error was not present in the printed
+                version of the code (page 628 of TLPI).
+                (Thanks to Kanak Kshetri.)
+
+2013-04-05
+        svmsg/Makefile
+                Fix a typo that meant that 'svmsg_send' was not compiled.
+                (Thanks to George Yoshida.)
+
+2013-06-05
+        xattr/xattr_view.c
+                Fix error-handling code for usage diagnostic.
+                See the erratum for page 317.
+
+2013-07-11
+        timers/itimerspec_from_str.c
+                Change itimerspecFromStr() so that it does not modify
+                its string argument.
+                See the errata for pages 502 and 503.
+
+2013-09-13
+        procexec/acct_on.c
+                Add missing argument to the call to usageErr();
+                See the erratum for page 592.
+                (Thanks to Liu Jiaming.)
+
+2013-09-17
+        mmap/mmcopy.c
+                Correct argument pasted to msync() (s/src/dst/).
+                (Thanks to Robert P. J. Day.)
+
+2013-09-18
+        filebuff/write_bytes.c
+                Minor to changes to comments at top of the program.
+        filebuff/Makefile
+                Add entries to produce the non-vanilla flavors of the
+                write_bytes program:
+                    write_bytes_fdatasync  (do fdatasync() after each write)
+                    write_bytes_fsync      (do fsync() after each write)
+                    write_bytes_o_fsync    (open the file with O_SYNC)
+
+2013-09-22
+        procpri/t_setpriority.c
+                Fix typo in a diagnostic message.
+                See the erratum for page 737.
+
+2013-09-23
+        procpri/demo_sched_fifo.c
+                s/useCPU("child ")/useCPU("parent")/ near the end of
+                the program.
+                (Thanks to Liu Jiaming.)
+
+2013-09-23
+        README
+                Fixed a few typos.
+                (Thanks to Robert P. J. Day.)
+
+2013-10-18
+        svmsg/svmsg_file_client.c
+                Fix typo in a comment.
+        svmsg/svmsg_file_client.c
+                Remove a redundant msgctl(IPC_RMID) operation.
+                See the erratum for page 961.
+                (Thanks to Liu Jiaming.)
+
+2013-10-22
+        pipes/popen_glob.c
+                s/==/=/ inside a printf() string.
+                See the erratum for page 905.
+                (Thanks to Liu Jiaming.)
+
+2013-10-22
+        mmap/mmcat.c
+        mmap/mmcopy.c
+                Add code to check for a zero-length input file.
+                See the erratum for page 1023.
+                (Thanks to Liu Jiaming.)
+
+2013-10-25
+        filelock/t_flock.c
+                Fix an error in the message printed by
+                <span class="func">usageErr()</span>.
+                See the erratum for page 1121.
+                (Thanks to Liu Jiaming.)
+
+2013-10-30
+        mmap/mmcat.c
+        mmap/mmcopy.c
+                Fix a typo syntax error introduced in 2013-10-22 edits.
+                (Thanks to Yongzhi Pan.)
+
+2013-10-30
+        pipes/pipe_ls_wc.c
+                Fix a typo in comment at top of the program.
+                (Only in "dist" version of code.)
+
+2013-11-01
+        sockets/ud_ucase.h
+                Fix a wordo in comment.
+                See the erratum for page 1171.
+                (Thanks to Liu Jiaming.)
+
+2013-11-07
+        sockets/id_echo_cl.c
+                Fix a typo in usageErr() error message.
+                See the erratum for page 1242.
+                (Thanks to Liu Jiaming.)
+
+2013-11-11
+        altio/poll_pipes.c
+                Fix a bogus comment, and a glitch in a printf() call.
+                See the erratum for page 1341.
+                (Thanks to Liu Jiaming.)
+        altio/self_pipe.c
+                Fix a division error in the last printf() call
+                (divide by 1000, not 10000).
+                (Thanks to Liu Jiaming.)
+        altio/t_select.c
+                Fix a division error in the last printf() call
+                (divide by 1000, not 10000).
+                See the erratum for page 1336.
+                (Thanks to Liu Jiaming.)
+2013-11-5
+        dirs_links/nftw_dir_tree.c
+                Fix wordo in a comment
+                (Only in "dist" version of code.)
+        dirs_links/view_symlink.c
+                Added a comment explaining use of lstat().
+                (Only in "dist" version of code.)
+
+2013-11-25
+        pmsg/mq_notify_thread.c
+                Remove an unnecessary call to pthread_exit().
+                See the erratum for page 1082.
+
+2013-12-04
+        threads/thread_incr_mutex,c
+                Add "volatile" qualifier to declaration of 'glob'
+                See the erratum for page 636.
+                (Thanks to Arnaud Frugier.)
+
+2013-12-05
+        sockets/is_echo_sv.c
+        sockets/is_echo_v2_sv.c
+        sockets/scm_cred_recv.c
+        sockets/scm_rights_recv.c
+        sockets/socknames.c
+        sockets/us_xfr_sv.c
+        sockets/us_xfr_v2_sv.c
+                Change last argument of accept() from 'NULL' to '0'.
+
+2013-12-05
+        sockets/i6d_ucase_cl.c
+        sockets/ud_ucase_cl.c
+                Change last argument of recvfrom() from 'NULL' to '0'.
+
+2014-01-02
+        shlibs/dynload.c
+                Add a comment explaining SUSv4 TC1 changes that permit more
+                natural casts of function pointers returned by dlsym().
+                See the "update" erratum for page 864.
+                (Only in "dist" version of code.)
+
+2014-03-12
+        Makefile.inc
+                Add "-D_DEFAULT_SOURCE" to IMPL_CFLAGS. This avoids the
+
+                        # warning "_BSD_SOURCE and _SVID_SOURCE are
+                        deprecated, use _DEFAULT_SOURCE"
+
+                warnings that are produced when compiling code that 
+                defines _SVID_SOURCE or _BSD_SOURCE against glibc headers
+                from version 2.20 onward.
+        sysinfo/procfs_user_exe.c
+                Move inclusion of our header files (ugid_functions.h and 
+                tlpi_hdr.h) to follow other #include lines (as is done in
+                all other code).
+        vmem/madvise_dontneed.c
+                Fix a typo in a comment.
+        threads/thread_cleanup.c
+                Add mutex locking around assignment to 'glob'; see the
+                erratum for page 679.
+                (Only in "dist" version of code.)
+                (Thanks to Jingtian Zhang.)
+
+2014-05-21
+        inotify/demo_inotify.c
+                Properly align 'buf' on an 8-byte boundary.
+                See the erratum for page 383.
+                (Thanks to Matt Wojciak and Heinrich Schuchardt.)
+
+2014-05-31
+        Makefile
+                Add 'filebuff' and 'syslim' directories
+
+2014-06-20
+        timers/timed_read.c
+                Fix an off-by-one error in the read() call.
+
+2014-07-09
+        inotify/inotify_dtree.c
+        inotify/rand_dtree.c
+                New files: an application that provides a thorough-going
+                demonstration of the use of inotify for monitoring directory
+                subtrees, and an associated test program.
+
+2014-07-10
+        pmsg/Makefile
+        timers/Makefile
+                Minor fixes
+
+2014-07-24
+        sockets/i6d_ucase_cl.c
+        sockets/ud_ucase_cl.c
+                Change last argument of recvfrom() from "0" to "NULL".
+                (With this change, the code now matches that shown in the book.)
+        sockets/is_echo_sv.c
+        sockets/socknames.c
+        sockets/us_xfr_sv.c
+                Change last argument of accept() from "0" to "NULL".
+                (With this change, the code now matches that shown in the book.)
+        sockets/is_echo_v2_sv.c
+        sockets/scm_cred_recv.c
+        sockets/scm_rights_recv.c
+        sockets/us_xfr_v2_sv.c
+                Change last argument of accept() from "0" to "NULL".
+
+2014-11-05
+        pmsg/pmsg_create.c
+                Change the default value assigned to 'attr.mq_msgsize'.
+                See the erratum for page 1069.
+
+2014-11-12
+        acl/Makefile
+                Simplify Makefile by including 'libacl' in LDLIBS.
+        cap/Makefile
+                Remove crufty, unneeded rule; and simplify the Makefile
+                by including 'libcrypt' in LDLIBS.
+        pmsg/Makefile
+                Remove unneeded rule for building 'mq_notify_thread'.
+        psem/Makefile
+                Remove unneeded rule
+        progconc/Makefile
+                Remove unneeded rule for building 'syscall_speed'.
+        timers/Makefile
+                Most of the programs in this directory must be linked
+                against the realtime library, librt; simplify the Makefile
+                by linking all of the programs against that library.
+
+2014-11-14
+        Makefile.inc
+                Remove "-Wno-unused-but-set-variable" from IMPL_CFLAGS.
+                Remove redundant "-Wpointer-arith", which is anyway enabled
+                by -pedantic.
+                Remove unneeded "-Wno-format-y2k".
+                Remove unneeded "-Wno-unused-parameter".
+        progconc/syscall_speed.c
+                Minor change so that -Wunused-but-set-variable does not
+                give a warning.
+        signals/demo_SIGFPE.c
+                Minor change so that -Wunused-but-set-variable does not
+                give a warning.
+        signals/t_sigsuspend.c
+                Minor change so that -Wunused-but-set-variable does not
+                give a warning.
+                (Only in "dist" version of code.)
+
+2014-11-15
+        lib/*
+                Various files in this directory that were hard links
+                to files that were also linked in other directories
+                are now symbolic links.
+
+2014-11-22
+        threads/pthread_barrier_demo.c
+                New program demonstrating use of POSIX threads barriers API.
+        threads/thread_incr_rwlock.c
+                New program demonstrating use of POSIX threads rwlocks.
+        threads/thread_incr_spinlock.c
+                New program demonstrating use of POSIX threads spinlocks.
+        threads/thread_incr_mutex.c
+                Update header comments to refer to new spinlocks and
+                rwlocks programs.
+        threads/Makefile
+                Update to include targets for new program.
+
+2014-11-29
+        altio/poll_pipes.c
+                Change the 'timeout' argument in the poll() call from -1 to 0.
+                (See the erratum for page 1341.)
+
+2014-11-30
+        procexec/execlp.c
+                Minor comment and whitespace fixes.
+
+2014-12-01
+        pmsg/mq_notify_sigwaitinfo.c
+                Minor layout fix and removal of a redundant comment.
+        pmsg/mq_notify_sig.c
+        pmsg/mq_notify_thread.c
+                Remove a redundant comment.
+                (Only in "dist" version of code.)
+        pmsg/mq_notify_via_signal.c
+        pmsg/mq_notify_via_thread.c
+                New files demonstrating message queue notification via
+                signals and via threads.
+        pmsg/mq_notify_sigwaitinfo.c
+        pmsg/mq_notify_sig.c
+        pmsg/mq_notify_thread.c
+                Add comments noting that these programs do not handle the case
+                where a message is already on the queue by the time the first
+                attempt is made to register for message notification, along
+                with reference to code examples that addess this point.
+
+2014-12-03
+        sockets/list_host_addresses.c
+                A new small program demonstrating the use of getifaddrs(3).
+        socket/Makefile
+                Add list_host_addresses.c.
+
+2014-12-09
+        threads/thread_lock_speed.c
+                A new program allowing the performance of mutexes and
+                spin locks to be compared in a few different scenarios.
+
+2014-12-12
+        dirs_links/file_type_stats.c
+                Handle non-stat()-able files correctly.
+        dirs_links
+                Rework so that 'all' target includes examples using nftw()
+
+2014-12-17
+        README
+                The source code licensing for the "main" program examples
+                has changed from GNU Affero GPLv3 or later to plain
+                GNU GPLv3 or later.
+
+2015-01-26
+       filelock/i_fcntl_locking.c
+                Added support for the OFD locking commands (F_OFD_SETLK,
+                F_OFD_SETLKW, F_OFD_GETLKW) that were added to Linux in
+                version 3.15.
+                (Only in "dist" version of code.)
+
+2015-01-28
+       cap/check_password_caps.c
+                Fix a typo in a comment.
+                (Only in "dist" version of code.)
+
+2015-01-30
+       threads/thread_multijoin.c
+                Fix minor whitespace error.
+                (See the erratum for page 651.)
+
+2015-03-13
+        shlibs/dynload.c
+                Switch to using the more natural cast permitted by
+                POSIX.1-2008 TC1 (2013). See also the erratum for page 864.
+                (Only in "dist" version of code.)
+
+2015-03-13
+        shlibs/dynload.c
+                Tweak comment on dlsym() cast permitted by
+                POSIX.1-2008 TC1 (2013).
+                (Only in "dist" version of code.)
+
+2015-03-25
+        namespaces/Makefile
+        namespaces/demo_userns.c
+        namespaces/demo_uts_namespaces.c
+        namespaces/hostname.c
+        namespaces/multi_pidns.c
+        namespaces/ns_child_exec.c
+        namespaces/ns_exec.c
+        namespaces/ns_run.c
+        namespaces/orphan.c
+        namespaces/pidns_init_sleep.c
+        namespaces/simple_init.c
+        namespaces/t_setns_userns.c
+        namespaces/unshare.c
+        namespaces/userns_child_exec.c
+        namespaces/userns_setns_test.c
+                Added various namespaces-related code, covered in my article
+                series on LWN.net, start at https://lwn.net/Articles/531114/
+
+2015-03-25
+        Makefile
+        README
+                Adjusted for addition of "namespaces" subdirectory
+
+2015-04-21
+        inotify/rand_dtree.c
+                Add "#define _XOPEN_SOURCE 500" so that the program
+                builds cleanly from the command line. (It already built
+                fine using the Makefile.)
+        namespaces/README
+                Add a README explaining the origin of these programs.
+        sockets/scm_cred_recv.c
+        sockets/scm_cred_send.c
+        sockets/scm_rights_recv.c
+        sockets/scm_rights_send.c
+                Add a comment explaining use of a union to force alignment.
+
+2015-05-09
+        sysinfo/procfs_pidmax.c
+                lseek() to start of /proc/sys/kernel/pidmax.c file before
+                writing to it. (See the erratum for page 228.)
+
+2015-09-09
+        seccomp/libseccomp_demo.c
+        seccomp/seccomp_control_open.c
+        seccomp/seccomp_deny_open.c
+        seccomp/seccomp_perf.c
+                Added some seccomp examples. For more information see my
+                slides on seccomp at http://man7.org/conf and Jake Edge's
+                LWN.net write-up of one of my seccomp presentations at
+                http://lwn.net/Articles/656307/.
+
+2015-10-28
+        pmsg/mq_notify_via_thread.c
+                Rework code to remove a race condition.
+
+2015-11-05
+        signal/intquit.c
+        signal/ouch.c
+        signal/sig_receiver.c:
+                Add a comment explaining that sigaction() is preferred over
+                signal() when establishing signal handlers.
+                (Only in "dist" version of code.)
+
+2016-04-01
+        procexec/t_execle.c
+                Add an argument to the execle() call.
+
+2016-04-14
+        sockets/is_seqnum_sv.c
+        sockets/is_seqnum_v2_sv.c
+                Remove unneeded '&' in argument to write().
+
+2016-04-20
+        files/t_stat.c
+                Use <sys/sysmacros.h> instead of <sys/types.h>
+                to get the definitions of major() and minor().
+
+2016-04-29
+        */Makefile
+                Fix misnamed macro (LPLIB --> TLPI_LIB).
+                (Thanks to Ivo Tisch.)
+        lib/Makefile
+                Better dependency checking
+
+2016-05-05
+        psem/Makefile
+                Fix the target logic for psem_timedwait, so that it builds
+                correctly on systems where the vDSO doesn't export
+                clock_gettime(). (the x86 vDSO does export clock_gettime(),
+                which hid the error when building there.)
+
+2016-05-11
+        namespaces/userns_child_exec.c
+                Check that a command line argument is supplied.
+
+2016-05-13
+        shlibs/dynload.c
+                Remove unneeded check for NULL symbol value.
+                (See the erratum for page 865.)
+
+2016-06-05
+        filesys/t_mount.c
+                Support MS_LAZYTIME and MS_RELATIME flags
+                (Only in "dist" version of code.)
+
+2016-06-28
+        namespaces/unshare.c
+                Fix typo in usage() message.
+
+2016-07-01
+        psem/psem_timedwait.c
+        threads/thread_multijoin.c
+                Use slightly better text ('num-secs' vs 'nsecs') in
+                "usage" message.
+
+2016-07-25
+       sockets/ud_ucase_sv.c
+       sockets/us_xfr_sv.c
+                Add a length check for the server socket pathname.
+                (Only in "dist" version of code.)
+                (See the errata for pages 1168 and 1172.)
+
+2016-12-14
+        namespaces/userns_overview.go
+                Add a Go program that introspects the user namespace
+                hierarchy of the system.
+
+2017-04-19
+        sockets/us_abstract_bind.c
+                Fix a wording error in a comment (\0abc ==> /0xyz).
+
+2017-05-03
+        namespaces/userns_child_exec.c
+                Close unneeded file descriptor before doing execve().
+
+2017-06-09
+        loginacct/dump_utmpx.c
+        loginacct/view_lastlog.c
+                Fix handling of 'time_t' fields for cases where
+                fields may actually be smaller than 'time_t'.
+                (See the errata for pages 825 and 831.)
+
+2017-06-12
+        threads/thread_lock_speed.c
+                Add '-q' command-line option to cause the program to operate
+                quietly.
+
+2017-07-14
+        namespaces/ns_child_exec.c
+                Instead of '&argv[0]', use the simpler equivalent 'argv'
+
+2017-08-15
+        getopt/t_getopt.c
+                Add '__attribute__ ((__noreturn__))' to declaration of
+                usageError() to prevent "this statement may fall through"
+                warnings from "gcc -Wimplicit-fallthrough" in switch()
+                statement in main().
+                (Only in "dist" version of code.)
+
+2017-08-15
+        inotify/inotify_dtree.c
+                Eliminate "’snprintf’ output may be truncated before the last
+                format character" warning from "gcc -Wformat-truncation".
+
+2017-08-15
+        inotify/rand_dtree.c
+                Eliminate compiler warning about redefinition of
+                _XOPEN_SOURCE.
+
+2017-10-01
+        threads/thread_lock_speed.c
+                Add a alarm timer to prevent runaway/forgotten process
+                from burning CPU time forever
+
+2017-10-02
+        inotify/inotify_dtree.c
+                Remove some superfluous signal-related code.
+                (Thanks to Mirko Parthey.)
+
+2017-10-16
+        namespaces/multi_pidns.c
+                Allocate stacks for the child processes on the heap rather
+                than in static memory. Marcos Paulo de Souza pointed out
+                that the children were being killed by SIGSEGV after they
+                had completed the sleep() calls. (Some further investigation
+                showed that all children except the *last* are killed with
+                SIGSEGV.) It appears that they are killed after the child
+                start function returns.  The problem goes away if the
+                children are allocated stacks in separate memory areas by
+                calling malloc() (which is the change made in this patch)
+                or in separate statically allocated buffers.
+
+                The reason that the children were killed is based on (my
+                misunderstanding of) the subtleties of the magic done
+                in the glibc clone() wrapper function. (See, for example,
+                the x86-64 implementation in the glibc source file
+                sysdeps/unix/sysv/linux/x86_64/clone.S).  The
+                previous code was relying on the fact that the parent's
+                memory was duplicated in the child during the clone() system
+                call, and the assumption that that duplicated memory could be
+                used in the child.  However, before executing the clone()
+                system call, the clone() wrapper function saves some
+                information (that will be used by the child) onto the stack.
+                This happens in the address space of the parent, before the
+                memory is duplicated in the system call.  Since the previous
+                code was making use of the same statically allocated buffer
+                (i.e., the same address as was used for the parent's stack)
+                for the child stack, the consequence was that the steps in
+                the clone() wrapper function were corrupting the stack of the
+                *parent* process, which ultimately resulted in (all but the
+                last of) the child processes crashing.
+
+2017-10-17
+        namespaces/demo_userns.c
+        namespaces/demo_uts_namespaces.c
+        namespaces/ns_child_exec.c
+        namespaces/pidns_init_sleep.c
+        namespaces/userns_cap_sig_expt.c
+        namespaces/userns_child_exec.c
+        namespaces/userns_setns_test.c
+                Allocate child stack on heap, rather than statically.
+                Although this is not strictly necessary in these programs
+                (since only one clone() child is created in each of the
+                programs), using dynamically allocated memory is good
+                pedagogical practice, to prevent problems such as were
+                revealed in the namespaces/multi_pidns.c program.
diff --git a/COPYING.agpl-v3 b/COPYING.agpl-v3
new file mode 100644 (file)
index 0000000..dba13ed
--- /dev/null
@@ -0,0 +1,661 @@
+                    GNU AFFERO GENERAL PUBLIC LICENSE
+                       Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+  A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate.  Many developers of free software are heartened and
+encouraged by the resulting cooperation.  However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+  The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community.  It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server.  Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+  An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals.  This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU Affero General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Remote Network Interaction; Use with the GNU General Public License.
+
+  Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software.  This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time.  Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source.  For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code.  There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<http://www.gnu.org/licenses/>.
diff --git a/COPYING.gpl-v3 b/COPYING.gpl-v3
new file mode 100644 (file)
index 0000000..94a9ed0
--- /dev/null
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/COPYING.lgpl-v3 b/COPYING.lgpl-v3
new file mode 100644 (file)
index 0000000..65c5ca8
--- /dev/null
@@ -0,0 +1,165 @@
+                   GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+  This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+  0. Additional Definitions.
+
+  As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+  "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+  An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+  A "Combined Work" is a work produced by combining or linking an
+Application with the Library.  The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+  The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+  The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+  1. Exception to Section 3 of the GNU GPL.
+
+  You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+  2. Conveying Modified Versions.
+
+  If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+   a) under this License, provided that you make a good faith effort to
+   ensure that, in the event an Application does not supply the
+   function or data, the facility still operates, and performs
+   whatever part of its purpose remains meaningful, or
+
+   b) under the GNU GPL, with none of the additional permissions of
+   this License applicable to that copy.
+
+  3. Object Code Incorporating Material from Library Header Files.
+
+  The object code form of an Application may incorporate material from
+a header file that is part of the Library.  You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+   a) Give prominent notice with each copy of the object code that the
+   Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the object code with a copy of the GNU GPL and this license
+   document.
+
+  4. Combined Works.
+
+  You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+   a) Give prominent notice with each copy of the Combined Work that
+   the Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the Combined Work with a copy of the GNU GPL and this license
+   document.
+
+   c) For a Combined Work that displays copyright notices during
+   execution, include the copyright notice for the Library among
+   these notices, as well as a reference directing the user to the
+   copies of the GNU GPL and this license document.
+
+   d) Do one of the following:
+
+       0) Convey the Minimal Corresponding Source under the terms of this
+       License, and the Corresponding Application Code in a form
+       suitable for, and under terms that permit, the user to
+       recombine or relink the Application with a modified version of
+       the Linked Version to produce a modified Combined Work, in the
+       manner specified by section 6 of the GNU GPL for conveying
+       Corresponding Source.
+
+       1) Use a suitable shared library mechanism for linking with the
+       Library.  A suitable mechanism is one that (a) uses at run time
+       a copy of the Library already present on the user's computer
+       system, and (b) will operate properly with a modified version
+       of the Library that is interface-compatible with the Linked
+       Version.
+
+   e) Provide Installation Information, but only if you would otherwise
+   be required to provide such information under section 6 of the
+   GNU GPL, and only to the extent that such information is
+   necessary to install and execute a modified version of the
+   Combined Work produced by recombining or relinking the
+   Application with a modified version of the Linked Version. (If
+   you use option 4d0, the Installation Information must accompany
+   the Minimal Corresponding Source and Corresponding Application
+   Code. If you use option 4d1, you must provide the Installation
+   Information in the manner specified by section 6 of the GNU GPL
+   for conveying Corresponding Source.)
+
+  5. Combined Libraries.
+
+  You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+   a) Accompany the combined library with a copy of the same work based
+   on the Library, uncombined with any other library facilities,
+   conveyed under the terms of this License.
+
+   b) Give prominent notice with the combined library that part of it
+   is a work based on the Library, and explaining where to find the
+   accompanying uncombined form of the same work.
+
+  6. Revised Versions of the GNU Lesser General Public License.
+
+  The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+  Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+  If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
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 (file)
index 0000000..70e8614
--- /dev/null
@@ -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 (file)
index 0000000..bd0aafc
--- /dev/null
@@ -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 (file)
index 0000000..f3a1c12
--- /dev/null
@@ -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 (file)
index 0000000..4f8a3c3
--- /dev/null
@@ -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 (file)
index 0000000..ad11885
--- /dev/null
@@ -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 (file)
index 0000000..1348fa8
--- /dev/null
@@ -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 (file)
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 (file)
index 0000000..ef35fae
--- /dev/null
@@ -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 (file)
index 0000000..3ce667b
--- /dev/null
@@ -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 <sys/acl.h>
+#include "ugid_functions.h"
+#include "tlpi_hdr.h"
+
+#define MAX_ENTRIES 10000   /* Maximum entries that we can handle in an ACL */
+
+struct AccessControlEntry {     /* Represent a single ACL entry */
+    acl_tag_t   tag;            /* Tag type */
+    id_t        qual;           /* Optional tag qualifier (UID or GID) */
+    int         perms;          /* Permissions bit mask */
+};
+
+static void
+usageError(char *progName, char *msg, Boolean shortUsage)
+{
+    if (msg != NULL)
+        fprintf(stderr, "%s\n", msg);
+
+    if (shortUsage) {
+        fprintf(stderr, "Type '%s --help' for usage information\n", progName);
+        exit(EXIT_FAILURE);
+    }
+
+    fprintf(stderr, "Usage: %s -m acl [-d] [-n] file...\n", progName);
+    fprintf(stderr, "   or: %s -x acl [-d] [-n] file...\n", progName);
+    fprintf(stderr, "   or: %s -k file...\n\n", progName);
+    fprintf(stderr, "   or: %s -V acl\n\n", progName);
+#define fpe(msg) fprintf(stderr, "      " msg);
+    fpe("-m  Modify/create ACL entries\n");
+    fpe("-x  Remove ACL entries\n");
+    fpe("-k  Remove default ACL\n\n");
+    fpe("-V  Check validity of 'acl'\n\n");
+    fpe("'acl' consists of one or more comma-separated entries of the form:\n");
+    fpe("\n            tag:[qualifier][:[perms]]\n\n");
+    fpe("      'perms' are specified only for -m\n\n");
+    fpe("-d  Apply operation to default ACL\n");
+    fpe("-n  Don't recalculate mask entry\n");
+    fpe("      NOTE: if you specify this option and have specified\n");
+    fpe("      the -m option, then you may encounter errors if the\n");
+    fpe("      file does not already have a mask entry.\n");
+    exit(EXIT_FAILURE);
+}
+
+/* Parse an ACL entry specification (the null-terminated string
+   provided in 'entryStr') of the form:
+
+        tag:[qualifier][:[permissions]]
+
+   returning the parsed information in the structure 'ace'.
+
+   If 'permsReqd' is set, then the ACL entries must contain
+   permission specifications (i.e., a colon followed by at least
+   one of [-rwx], otherwise they must not.
+
+   Return TRUE if the specification parsed okay, or FALSE otherwise. */
+
+static Boolean
+parseEntrySpec(char *entryStr, struct AccessControlEntry *ace,
+               Boolean permsReqd)
+{
+    char *colon1, *colon2;
+    Boolean hasQual;            /* Is optional qualifier present? */
+    Boolean hasPerms;           /* Are permissions specified? */
+
+    colon1 = strchr(entryStr, ':');
+    if (colon1 == NULL) {
+        fprintf(stderr, "Missing initial colon in ACL entry: %s\n", entryStr);
+        return FALSE;
+    }
+
+    hasQual = *(colon1 + 1) != '\0' && *(colon1 + 1) != ':';
+
+    *colon1 = '\0';     /* Add terminator to tag type */
+
+    colon2 = strchr(colon1 + 1, ':');
+    hasPerms = colon2 != NULL && *(colon2 + 1) != '\0';
+
+    if (hasPerms && !permsReqd) {
+        fprintf(stderr, "Cannot specify permissions here\n");
+        return FALSE;
+    }
+
+    if (!hasPerms && permsReqd) {
+        fprintf(stderr, "Must specify permissions\n");
+        return FALSE;
+    }
+
+    /* Determine tag type, depending on tag string and presence
+       of qualifier after the first ':' */
+
+    if (strcmp(entryStr, "u") == 0 || strcmp(entryStr, "user") == 0)
+        ace->tag = hasQual ? ACL_USER : ACL_USER_OBJ;
+    else if (strcmp(entryStr, "g") == 0 || strcmp(entryStr, "group") == 0)
+        ace->tag = hasQual ? ACL_GROUP : ACL_GROUP_OBJ;
+    else if (strcmp(entryStr, "o") == 0 || strcmp(entryStr, "other") == 0)
+        ace->tag = ACL_OTHER;
+    else if (strcmp(entryStr, "m") == 0 || strcmp(entryStr, "mask") == 0)
+        ace->tag = ACL_MASK;
+    else {
+        fprintf(stderr, "Bad tag: %s\n", entryStr);
+        return FALSE;
+    }
+
+    /* For ACL_USER and ACL_GROUP tags, extract a UID / GID from qualifier */
+
+    if (colon2 != NULL)
+        *colon2 = '\0';         /* Add terminator to qualifier */
+
+    ace->qual = 0;
+
+    if (ace->tag == ACL_USER) {
+        ace->qual = userIdFromName(colon1 + 1);
+        if (ace->qual == -1) {
+            fprintf(stderr, "Bad user ID: %s\n", colon1 + 1);
+            return FALSE;
+        }
+    } else if (ace->tag == ACL_GROUP) {
+        ace->qual = groupIdFromName(colon1 + 1);
+        if (ace->qual == -1) {
+            fprintf(stderr, "Bad group ID: %s\n", colon1 + 1);
+            return FALSE;
+        }
+    }
+
+    /* If a permissions string was present, return it as a bit mask */
+
+    if (hasPerms) {
+        char *p;
+
+        ace->perms = 0;
+
+        /* We're not too thorough here -- we don't check for multiple
+           instances of [-rwx], or check if the permissions string is
+           longer than three characters... */
+
+        for (p = colon2 + 1; *p != '\0'; p++) {
+            if (*p == 'r')
+                ace->perms |= ACL_READ;
+            else if (*p == 'w')
+                ace->perms |= ACL_WRITE;
+            else if (*p == 'x')
+                ace->perms |= ACL_EXECUTE;
+            else if (*p != '-') {
+                fprintf(stderr, "Bad character in permissions "
+                        "string: %c\n", *p);
+                return FALSE;
+            }
+        }
+    }
+
+    return TRUE;
+}
+
+/* Parse a text form ACL, returning information about the entries in
+   'aclist'. On success, return the number of ACL entries found; on
+   error return -1. */
+
+static int
+parseACL(char *aclStr, struct AccessControlEntry aclist[],
+        Boolean permsReqd)
+{
+    char *nextEntry, *comma;
+    int n;
+
+    n = 0;
+    for (nextEntry = aclStr; ; nextEntry = comma + 1) {
+
+        if (n >= MAX_ENTRIES) {
+            fprintf(stderr, "Too many entries in ACL\n");
+            return -1;
+        }
+
+        comma = strchr(nextEntry, ',');
+        if (comma != NULL)
+            *comma = '\0';
+
+        if (!parseEntrySpec(nextEntry, &aclist[n], permsReqd))
+            return -1;
+
+        if (comma == NULL)              /* This was the last entry */
+            break;
+
+        n++;
+    }
+
+    return n + 1;
+}
+
+/* Find the the ACL entry in 'acl' corresponding to the tag type and
+   qualifier in 'tag' and 'id'. Return the matching entry, or NULL
+   if no entry was found. */
+
+static acl_entry_t
+findEntry(acl_t acl, acl_tag_t tag, id_t qaul)
+{
+    acl_entry_t entry;
+    acl_tag_t entryTag;
+    uid_t *uidp;
+    gid_t *gidp;
+    int ent, s;
+
+    for (ent = ACL_FIRST_ENTRY; ; ent = ACL_NEXT_ENTRY) {
+        s = acl_get_entry(acl, ent, &entry);
+        if (s == -1)
+            errExit("acl_get_entry");
+
+        if (s == 0)
+            return NULL;
+
+        if (acl_get_tag_type(entry, &entryTag) == -1)
+            errExit("acl_get_tag_type");
+
+        if (tag == entryTag) {
+            if (tag == ACL_USER) {
+                uidp = acl_get_qualifier(entry);
+                if (uidp == NULL)
+                    errExit("acl_get_qualifier");
+
+                if (qaul == *uidp) {
+                    if (acl_free(uidp) == -1)
+                        errExit("acl_free");
+                    return entry;
+                } else {
+                    if (acl_free(uidp) == -1)
+                        errExit("acl_free");
+                }
+
+            } else if (tag == ACL_GROUP) {
+                gidp = acl_get_qualifier(entry);
+                if (gidp == NULL)
+                    errExit("acl_get_qualifier");
+
+                if (qaul == *gidp) {
+                    if (acl_free(gidp) == -1)
+                        errExit("acl_free");
+                    return entry;
+                } else {
+                    if (acl_free(gidp) == -1)
+                        errExit("acl_free");
+                }
+
+            } else {
+                return entry;
+            }
+        }
+    }
+}
+
+/* Set the permissions in 'perms' in the ACL entry 'entry' */
+
+static void
+setPerms(acl_entry_t entry, int perms)
+{
+    acl_permset_t permset;
+
+    if (acl_get_permset(entry, &permset) == -1)
+        errExit("acl_get_permset");
+
+    if (acl_clear_perms(permset) == -1)
+        errExit("acl_clear_perms");
+
+    if (perms & ACL_READ)
+        if (acl_add_perm(permset, ACL_READ) == -1)
+            errExit("acl_add_perm");
+    if (perms & ACL_WRITE)
+        if (acl_add_perm(permset, ACL_WRITE) == -1)
+            errExit("acl_add_perm");
+    if (perms & ACL_EXECUTE)
+        if (acl_add_perm(permset, ACL_EXECUTE) == -1)
+            errExit("acl_add_perm");
+
+    if (acl_set_permset(entry, permset) == -1)
+        errExit("acl_set_permset");
+}
+
+int
+main(int argc, char *argv[])
+{
+    Boolean recalcMask, useDefaultACL;
+    Boolean modifyACL, removeACL, removeDefaultACL, checkValidity;
+    int optCnt, j, opt, numEntries, en;
+    acl_type_t type;
+    char *aclSpec;
+    acl_t acl;
+    acl_entry_t entry;
+    struct AccessControlEntry aclist[MAX_ENTRIES];
+
+    if (argc < 2 || strcmp(argv[1], "--help") == 0)
+        usageError(argv[0], NULL, FALSE);
+
+    /* Parse command-line options */
+
+    recalcMask = TRUE;
+    useDefaultACL = FALSE;
+    modifyACL = FALSE;
+    removeACL = FALSE;
+    checkValidity = FALSE;
+    removeDefaultACL = FALSE;
+    optCnt = 0;
+
+    while ((opt = getopt(argc, argv, "m:x:kdnV:")) != -1) {
+        switch (opt) {
+        case 'm':
+            modifyACL = TRUE;
+            aclSpec = optarg;
+            optCnt++;
+            break;
+
+        case 'x':
+            removeACL = TRUE;
+            aclSpec = optarg;
+            optCnt++;
+            break;
+
+        case 'k':
+            removeDefaultACL = TRUE;
+            optCnt++;
+            break;
+
+        case 'V':
+            checkValidity = TRUE;
+            aclSpec = optarg;
+            optCnt++;
+            break;
+
+        case 'd':
+            useDefaultACL = TRUE;
+            break;
+
+        case 'n':
+            recalcMask = FALSE;
+            break;
+
+        default:
+            usageError(argv[0], "Bad option\n", TRUE);
+            break;
+        }
+    }
+
+    if (optCnt != 1)
+        usageError(argv[0], "Specify exactly one of -m, -x, -k, or -V\n", TRUE);
+
+    if (checkValidity && useDefaultACL)
+        usageError(argv[0], "Can't specify -d with -V\n", TRUE);
+
+    if (checkValidity) {
+        if (parseACL(aclSpec, aclist, TRUE) == -1) {
+            fatal("Bad ACL entry specification");
+        } else {
+            printf("ACL is valid\n");
+            exit(EXIT_SUCCESS);
+        }
+    }
+
+    if (modifyACL || removeACL) {
+        numEntries = parseACL(aclSpec, aclist, modifyACL);
+        if (numEntries == -1)
+            usageError(argv[0], "Bad ACL specification\n", TRUE);
+    }
+
+    type = useDefaultACL ? ACL_TYPE_DEFAULT : ACL_TYPE_ACCESS;
+
+    /* Perform the operation on each file argument */
+
+    for (j = optind; j < argc; j++) {
+        if (removeDefaultACL) {
+            if (acl_delete_def_file(argv[j]) == -1)
+                errExit("acl_delete_def_file: %s", argv[j]);
+
+        } else if (modifyACL || removeACL) {
+
+            acl = acl_get_file(argv[j], type);
+            if (acl == NULL)
+                errExit("acl_get_file");
+
+            /* Apply each of the entries in 'aclist' to the
+               current file */
+
+            for (en = 0; en < numEntries; en++) {
+                entry = findEntry(acl, aclist[en].tag, aclist[en].qual);
+
+                if (removeACL) {
+                    if (entry != NULL)
+                        if (acl_delete_entry(acl, entry) == -1)
+                            errExit("acl_delete_entry");
+
+                } else {        /* modifyACL */
+
+                    if (entry == NULL) {
+
+                        /* Entry didn't exist in ACL -- create a new
+                           entry with required tag and qualifier */
+
+                        if (acl_create_entry(&acl, &entry) == -1)
+                            errExit("acl_create_entry");
+                        if (acl_set_tag_type(entry, aclist[en].tag) == -1)
+                            errExit("acl_set_tag_type");
+                        if (aclist[en].tag == ACL_USER ||
+                                aclist[en].tag == ACL_GROUP)
+                            if (acl_set_qualifier(entry,
+                                        &aclist[en].qual) == -1)
+                                errExit("acl_set_qualifier");
+                    }
+
+                    setPerms(entry, aclist[en].perms);
+                }
+
+                /* Recalculate the mask entry if requested */
+
+                if (recalcMask)
+                    if (acl_calc_mask(&acl) == -1)
+                        errExit("acl_calc_mask");
+
+                /* Update the file ACL */
+
+                if (acl_valid(acl) == -1)
+                    errExit("acl_valid");
+
+                if (acl_set_file(argv[j], type, acl) == -1)
+                    errExit("acl_set_file");
+            }
+
+            if (acl_free(acl) == -1)
+                errExit("acl_free");
+        } else {
+            fatal("Bad logic!");
+        }
+    }
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/acl/acl_view.c b/acl/acl_view.c
new file mode 100644 (file)
index 0000000..85d90ee
--- /dev/null
@@ -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 <acl/libacl.h>
+#include <sys/acl.h>
+#include "ugid_functions.h"
+#include "tlpi_hdr.h"
+
+static void
+usageError(char *progName)
+{
+    fprintf(stderr, "Usage: %s [-d] filename\n", progName);
+    exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+    acl_t acl;
+    acl_type_t type;
+    acl_entry_t entry;
+    acl_tag_t tag;
+    uid_t *uidp;
+    gid_t *gidp;
+    acl_permset_t permset;
+    char *name;
+    int entryId, permVal, opt;
+
+    type = ACL_TYPE_ACCESS;
+    while ((opt = getopt(argc, argv, "d")) != -1) {
+        switch (opt) {
+        case 'd': type = ACL_TYPE_DEFAULT;      break;
+        case '?': usageError(argv[0]);
+        }
+    }
+
+    if (optind + 1 != argc)
+        usageError(argv[0]);
+
+    acl = acl_get_file(argv[optind], type);
+    if (acl == NULL)
+        errExit("acl_get_file");
+
+    /* Walk through each entry in this ACL */
+
+    for (entryId = ACL_FIRST_ENTRY; ; entryId = ACL_NEXT_ENTRY) {
+
+        if (acl_get_entry(acl, entryId, &entry) != 1)
+            break;                      /* Exit on error or no more entries */
+
+        /* Retrieve and display tag type */
+
+        if (acl_get_tag_type(entry, &tag) == -1)
+            errExit("acl_get_tag_type");
+
+        printf("%-12s", (tag == ACL_USER_OBJ) ?  "user_obj" :
+                        (tag == ACL_USER) ?      "user" :
+                        (tag == ACL_GROUP_OBJ) ? "group_obj" :
+                        (tag == ACL_GROUP) ?     "group" :
+                        (tag == ACL_MASK) ?      "mask" :
+                        (tag == ACL_OTHER) ?     "other" : "???");
+
+        /* Retrieve and display optional tag qualifier */
+
+        if (tag == ACL_USER) {
+            uidp = acl_get_qualifier(entry);
+            if (uidp == NULL)
+                errExit("acl_get_qualifier");
+
+            name = userNameFromId(*uidp);
+            if (name == NULL)
+                printf("%-8d ", *uidp);
+            else
+                printf("%-8s ", name);
+
+            if (acl_free(uidp) == -1)
+                errExit("acl_free");
+
+        } else if (tag == ACL_GROUP) {
+            gidp = acl_get_qualifier(entry);
+            if (gidp == NULL)
+                errExit("acl_get_qualifier");
+
+            name = groupNameFromId(*gidp);
+            if (name == NULL)
+                printf("%-8d ", *gidp);
+            else
+                printf("%-8s ", name);
+
+            if (acl_free(gidp) == -1)
+                errExit("acl_free");
+
+        } else {
+            printf("         ");
+        }
+
+        /* Retrieve and display permissions */
+
+        if (acl_get_permset(entry, &permset) == -1)
+            errExit("acl_get_permset");
+
+        permVal = acl_get_perm(permset, ACL_READ);
+        if (permVal == -1)
+            errExit("acl_get_perm - ACL_READ");
+        printf("%c", (permVal == 1) ? 'r' : '-');
+        permVal = acl_get_perm(permset, ACL_WRITE);
+        if (permVal == -1)
+            errExit("acl_get_perm - ACL_WRITE");
+        printf("%c", (permVal == 1) ? 'w' : '-');
+        permVal = acl_get_perm(permset, ACL_EXECUTE);
+        if (permVal == -1)
+            errExit("acl_get_perm - ACL_EXECUTE");
+        printf("%c", (permVal == 1) ? 'x' : '-');
+
+        printf("\n");
+    }
+
+    if (acl_free(acl) == -1)
+        errExit("acl_free");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/altio/Makefile b/altio/Makefile
new file mode 100644 (file)
index 0000000..83bdd37
--- /dev/null
@@ -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 (file)
index 0000000..27a7fa3
--- /dev/null
@@ -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 <signal.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <termios.h>
+#include "tty_functions.h"      /* Declaration of ttySetCbreak() */
+#include "tlpi_hdr.h"
+
+static volatile sig_atomic_t gotSigio = 0;
+                                /* Set nonzero on receipt of SIGIO */
+
+static void
+sigioHandler(int sig)
+{
+    gotSigio = 1;
+}
+
+int
+main(int argc, char *argv[])
+{
+    int flags, j, cnt;
+    struct termios origTermios;
+    char ch;
+    struct sigaction sa;
+    Boolean done;
+
+    /* Establish handler for "I/O possible" signal */
+
+    sigemptyset(&sa.sa_mask);
+    sa.sa_flags = SA_RESTART;
+    sa.sa_handler = sigioHandler;
+    if (sigaction(SIGIO, &sa, NULL) == -1)
+        errExit("sigaction");
+
+    /* Set owner process that is to receive "I/O possible" signal */
+
+    if (fcntl(STDIN_FILENO, F_SETOWN, getpid()) == -1)
+        errExit("fcntl(F_SETOWN)");
+
+    /* Enable "I/O possible" signaling and make I/O nonblocking
+       for file descriptor */
+
+    flags = fcntl(STDIN_FILENO, F_GETFL);
+    if (fcntl(STDIN_FILENO, F_SETFL, flags | O_ASYNC | O_NONBLOCK) == -1)
+        errExit("fcntl(F_SETFL)");
+
+    /* Place terminal in cbreak mode */
+
+    if (ttySetCbreak(STDIN_FILENO, &origTermios) == -1)
+        errExit("ttySetCbreak");
+
+    for (done = FALSE, cnt = 0; !done ; cnt++) {
+        for (j = 0; j < 100000000; j++)
+            continue;                   /* Slow main loop down a little */
+
+        if (gotSigio) {                 /* Is input available? */
+            gotSigio = 0;
+
+            /* Read all available input until error (probably EAGAIN)
+               or EOF (not actually possible in cbreak mode) or a
+               hash (#) character is read */
+
+            while (read(STDIN_FILENO, &ch, 1) > 0 && !done) {
+                printf("cnt=%d; read %c\n", cnt, ch);
+                done = ch == '#';
+            }
+        }
+    }
+
+    /* Restore original terminal settings */
+
+    if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &origTermios) == -1)
+        errExit("tcsetattr");
+    exit(EXIT_SUCCESS);
+}
diff --git a/altio/epoll_input.c b/altio/epoll_input.c
new file mode 100644 (file)
index 0000000..4aeb6f6
--- /dev/null
@@ -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 <sys/epoll.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+#define MAX_BUF     1000        /* Maximum bytes fetched by a single read() */
+#define MAX_EVENTS     5        /* Maximum number of events to be returned from
+                                   a single epoll_wait() call */
+
+int
+main(int argc, char *argv[])
+{
+    int epfd, ready, fd, s, j, numOpenFds;
+    struct epoll_event ev;
+    struct epoll_event evlist[MAX_EVENTS];
+    char buf[MAX_BUF];
+
+    if (argc < 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s file...\n", argv[0]);
+
+    epfd = epoll_create(argc - 1);
+    if (epfd == -1)
+        errExit("epoll_create");
+
+    /* Open each file on command line, and add it to the "interest
+       list" for the epoll instance */
+
+    for (j = 1; j < argc; j++) {
+        fd = open(argv[j], O_RDONLY);
+        if (fd == -1)
+            errExit("open");
+        printf("Opened \"%s\" on fd %d\n", argv[j], fd);
+
+        ev.events = EPOLLIN;            /* Only interested in input events */
+        ev.data.fd = fd;
+        if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev) == -1)
+            errExit("epoll_ctl");
+    }
+
+    numOpenFds = argc - 1;
+
+    while (numOpenFds > 0) {
+
+        /* Fetch up to MAX_EVENTS items from the ready list of the
+           epoll instance */
+
+        printf("About to epoll_wait()\n");
+        ready = epoll_wait(epfd, evlist, MAX_EVENTS, -1);
+        if (ready == -1) {
+            if (errno == EINTR)
+                continue;               /* Restart if interrupted by signal */
+            else
+                errExit("epoll_wait");
+        }
+
+        printf("Ready: %d\n", ready);
+
+        /* Deal with returned list of events */
+
+        for (j = 0; j < ready; j++) {
+            printf("  fd=%d; events: %s%s%s\n", evlist[j].data.fd,
+                    (evlist[j].events & EPOLLIN)  ? "EPOLLIN "  : "",
+                    (evlist[j].events & EPOLLHUP) ? "EPOLLHUP " : "",
+                    (evlist[j].events & EPOLLERR) ? "EPOLLERR " : "");
+
+            if (evlist[j].events & EPOLLIN) {
+                s = read(evlist[j].data.fd, buf, MAX_BUF);
+                if (s == -1)
+                    errExit("read");
+                printf("    read %d bytes: %.*s\n", s, s, buf);
+
+            } else if (evlist[j].events & (EPOLLHUP | EPOLLERR)) {
+
+                /* After the epoll_wait(), EPOLLIN and EPOLLHUP may both have
+                   been set. But we'll only get here, and thus close the file
+                   descriptor, if EPOLLIN was not set. This ensures that all
+                   outstanding input (possibly more than MAX_BUF bytes) is
+                   consumed (by further loop iterations) before the file
+                   descriptor is closed. */
+
+                printf("    closing fd %d\n", evlist[j].data.fd);
+                if (close(evlist[j].data.fd) == -1)
+                    errExit("close");
+                numOpenFds--;
+            }
+        }
+    }
+
+    printf("All file descriptors closed; bye\n");
+    exit(EXIT_SUCCESS);
+}
diff --git a/altio/poll_pipes.c b/altio/poll_pipes.c
new file mode 100644 (file)
index 0000000..3b7ae34
--- /dev/null
@@ -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 <time.h>
+#include <poll.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    int numPipes, j, ready, randPipe, numWrites;
+    int (*pfds)[2];                     /* File descriptors for all pipes */
+    struct pollfd *pollFd;
+
+    if (argc < 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s num-pipes [num-writes]\n", argv[0]);
+
+    /* Allocate the arrays that we use. The arrays are sized according
+       to the number of pipes specified on command line */
+
+    numPipes = getInt(argv[1], GN_GT_0, "num-pipes");
+
+    pfds = calloc(numPipes, sizeof(int [2]));
+    if (pfds == NULL)
+        errExit("calloc");
+    pollFd = calloc(numPipes, sizeof(struct pollfd));
+    if (pollFd == NULL)
+        errExit("calloc");
+
+    /* Create the number of pipes specified on command line */
+
+    for (j = 0; j < numPipes; j++)
+        if (pipe(pfds[j]) == -1)
+            errExit("pipe %d", j);
+
+    /* Perform specified number of writes to random pipes */
+
+    numWrites = (argc > 2) ? getInt(argv[2], GN_GT_0, "num-writes") : 1;
+
+    srandom((int) time(NULL));
+    for (j = 0; j < numWrites; j++) {
+        randPipe = random() % numPipes;
+        printf("Writing to fd: %3d (read fd: %3d)\n",
+                pfds[randPipe][1], pfds[randPipe][0]);
+        if (write(pfds[randPipe][1], "a", 1) == -1)
+            errExit("write %d", pfds[randPipe][1]);
+    }
+
+    /* Build the file descriptor list to be supplied to poll(). This list
+       is set to contain the file descriptors for the read ends of all of
+       the pipes. */
+
+    for (j = 0; j < numPipes; j++) {
+        pollFd[j].fd = pfds[j][0];
+        pollFd[j].events = POLLIN;
+    }
+
+    ready = poll(pollFd, numPipes, 0);
+    if (ready == -1)
+        errExit("poll");
+
+    printf("poll() returned: %d\n", ready);
+
+    /* Check which pipes have data available for reading */
+
+    for (j = 0; j < numPipes; j++)
+        if (pollFd[j].revents & POLLIN)
+            printf("Readable: %3d\n", pollFd[j].fd);
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/altio/select_mq.c b/altio/select_mq.c
new file mode 100644 (file)
index 0000000..ceb7dd1
--- /dev/null
@@ -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 <sys/time.h>
+#if ! defined(__hpux)
+/* HP-UX 11 doesn't have this header file */
+#include <sys/select.h>
+#endif
+#include <sys/msg.h>
+#include <signal.h>
+#include <stddef.h>
+#include "tlpi_hdr.h"
+
+#define BUF_SIZE 200
+
+/* Total size of the 'pbuf' struct must not exceed PIPE_BUF, otherwise
+   writes by multiple children may not be atomic, with the result that
+   messages are broken up and interleaved. */
+
+#define MAX_MTEXT 400
+
+struct pbuf {
+    int msqid;                  /* Origin of message */
+    int len;                    /* Number of bytes used in mtext */
+    long mtype;                 /* Message type */
+    char mtext[MAX_MTEXT];      /* Message body */
+};
+
+/* Function called by child: monitors message queue identified by
+   'msqid', copying every message to the pipe identified by 'fd'. */
+
+static void
+childMon(int msqid, int fd)
+{
+    struct pbuf pmsg;
+    ssize_t msgLen;
+    size_t wlen;
+
+    for (;;) {
+        msgLen = msgrcv(msqid, &pmsg.mtype, MAX_MTEXT, 0, 0);
+        if (msgLen == -1)
+            errExit("msgrcv");
+
+        /* We add some info to the message read by msgrcv() before
+           writing to the pipe. */
+
+        pmsg.msqid = msqid;
+        pmsg.len = msgLen;      /* So parent knows how much to read from pipe */
+
+        wlen = offsetof(struct pbuf, mtext) + msgLen;
+        /* Or: wlen = &pmsg.mtext - &pmsg + msgLen */
+
+        if (write(fd, &pmsg, wlen) != wlen)
+            fatal("partial/failed write to pipe");
+    }
+}
+
+int
+main(int argc, char *argv[])
+{
+    fd_set readfds;
+    int ready, nfds, j;
+    int pfd[2];                 /* Pipe used to transfer messages from
+                                   children to parent */
+    ssize_t numRead;
+    char buf[BUF_SIZE];
+    struct pbuf pmsg;
+
+    if (argc < 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s msqid...\n", argv[0]);
+
+    if (pipe(pfd) == -1)
+        errExit("pipe");
+
+    /* Create one child for each message queue being monitored */
+
+    for (j = 1; j < argc; j++) {
+        switch (fork()) {
+        case -1:
+            errMsg("fork");
+            killpg(0, SIGTERM);
+            _exit(EXIT_FAILURE);        /* NOTREACHED */
+
+        case 0:
+            childMon(getInt(argv[j], 0, "msqid"), pfd[1]);
+            _exit(EXIT_FAILURE);        /* NOTREACHED */
+
+        default:
+            break;
+        }
+    }
+
+    /* Parent falls through to here */
+
+    for (;;) {
+        FD_ZERO(&readfds);
+        FD_SET(STDIN_FILENO, &readfds);
+        FD_SET(pfd[0], &readfds);
+        nfds = max(STDIN_FILENO, pfd[0]) + 1;
+
+        ready = select(nfds, &readfds, NULL, NULL, NULL);
+        if (ready == -1)
+            errExit("select");
+
+        /* Check if terminal fd is ready */
+
+        if (FD_ISSET(STDIN_FILENO, &readfds)) {
+            numRead = read(STDIN_FILENO, buf, BUF_SIZE - 1);
+            if (numRead == -1)
+                errExit("read stdin");
+
+            buf[numRead] = '\0';
+            printf("Read from terminal: %s", buf);
+            if (numRead > 0 && buf[numRead - 1] != '\n')
+                printf("\n");
+        }
+
+        /* Check if pipe fd is ready */
+
+        if (FD_ISSET(pfd[0], &readfds)) {
+            numRead = read(pfd[0], &pmsg, offsetof(struct pbuf, mtext));
+            if (numRead == -1)
+                errExit("read pipe");
+            if (numRead == 0)
+                fatal("EOF on pipe");
+
+            numRead = read(pfd[0], &pmsg.mtext, pmsg.len);
+            if (numRead == -1)
+                errExit("read pipe");
+            if (numRead == 0)
+                fatal("EOF on pipe");
+
+            printf("MQ %d: type=%ld length=%d <%.*s>\n", pmsg.msqid,
+                    pmsg.mtype, pmsg.len, pmsg.len, pmsg.mtext);
+        }
+    }
+}
diff --git a/altio/self_pipe.c b/altio/self_pipe.c
new file mode 100644 (file)
index 0000000..781fe30
--- /dev/null
@@ -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 <sys/time.h>
+#if ! defined(__hpux)   /* HP-UX 11 doesn't have this header file */
+#include <sys/select.h>
+#endif
+#include <fcntl.h>
+#include <signal.h>
+#include "tlpi_hdr.h"
+
+static int pfd[2];                      /* File descriptors for pipe */
+
+static void
+handler(int sig)
+{
+    int savedErrno;                     /* In case we change 'errno' */
+
+    savedErrno = errno;
+    if (write(pfd[1], "x", 1) == -1 && errno != EAGAIN)
+        errExit("write");
+    errno = savedErrno;
+}
+
+int
+main(int argc, char *argv[])
+{
+    fd_set readfds;
+    int ready, nfds, flags;
+    struct timeval timeout;
+    struct timeval *pto;
+    struct sigaction sa;
+    char ch;
+    int fd, j;
+
+    if (argc < 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s {timeout|-} fd...\n"
+                "\t\t('-' means infinite timeout)\n", argv[0]);
+
+    /* Initialize 'timeout', 'readfds', and 'nfds' for select() */
+
+    if (strcmp(argv[1], "-") == 0) {
+        pto = NULL;                     /* Infinite timeout */
+    } else {
+        pto = &timeout;
+        timeout.tv_sec = getLong(argv[1], 0, "timeout");
+        timeout.tv_usec = 0;            /* No microseconds */
+    }
+
+    nfds = 0;
+
+    /* Build the 'readfds' from the fd numbers given in command line */
+
+    FD_ZERO(&readfds);
+    for (j = 2; j < argc; j++) {
+        fd = getInt(argv[j], 0, "fd");
+        if (fd >= FD_SETSIZE)
+            cmdLineErr("file descriptor exceeds limit (%d)\n", FD_SETSIZE);
+
+        if (fd >= nfds)
+            nfds = fd + 1;              /* Record maximum fd + 1 */
+        FD_SET(fd, &readfds);
+    }
+
+    /* Create pipe before establishing signal handler to prevent race */
+
+    if (pipe(pfd) == -1)
+        errExit("pipe");
+
+    FD_SET(pfd[0], &readfds);           /* Add read end of pipe to 'readfds' */
+    nfds = max(nfds, pfd[0] + 1);       /* And adjust 'nfds' if required */
+
+    /* Make read and write ends of pipe nonblocking */
+
+    flags = fcntl(pfd[0], F_GETFL);
+    if (flags == -1)
+        errExit("fcntl-F_GETFL");
+    flags |= O_NONBLOCK;                /* Make read end nonblocking */
+    if (fcntl(pfd[0], F_SETFL, flags) == -1)
+        errExit("fcntl-F_SETFL");
+
+    flags = fcntl(pfd[1], F_GETFL);
+    if (flags == -1)
+        errExit("fcntl-F_GETFL");
+    flags |= O_NONBLOCK;                /* Make write end nonblocking */
+    if (fcntl(pfd[1], F_SETFL, flags) == -1)
+        errExit("fcntl-F_SETFL");
+
+    sigemptyset(&sa.sa_mask);
+    sa.sa_flags = SA_RESTART;           /* Restart interrupted reads()s */
+    sa.sa_handler = handler;
+    if (sigaction(SIGINT, &sa, NULL) == -1)
+        errExit("sigaction");
+
+    while ((ready = select(nfds, &readfds, NULL, NULL, pto)) == -1 &&
+            errno == EINTR)
+        continue;                       /* Restart if interrupted by signal */
+    if (ready == -1)                    /* Unexpected error */
+        errExit("select");
+
+    if (FD_ISSET(pfd[0], &readfds)) {   /* Handler was called */
+        printf("A signal was caught\n");
+
+        for (;;) {                      /* Consume bytes from pipe */
+            if (read(pfd[0], &ch, 1) == -1) {
+                if (errno == EAGAIN)
+                    break;              /* No more bytes */
+                else
+                    errExit("read");    /* Some other error */
+            }
+
+            /* Perform any actions that should be taken in response to signal */
+        }
+    }
+
+    /* Examine file descriptor sets returned by select() to see
+       which other file descriptors are ready */
+
+    printf("ready = %d\n", ready);
+    for (j = 2; j < argc; j++) {
+        fd = getInt(argv[j], 0, "fd");
+        printf("%d: %s\n", fd, FD_ISSET(fd, &readfds) ? "r" : "");
+    }
+
+    /* And check if read end of pipe is ready */
+
+    printf("%d: %s   (read end of pipe)\n", pfd[0],
+            FD_ISSET(pfd[0], &readfds) ? "r" : "");
+
+    if (pto != NULL)
+        printf("timeout after select(): %ld.%03ld\n",
+               (long) timeout.tv_sec, (long) timeout.tv_usec / 1000);
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/altio/t_select.c b/altio/t_select.c
new file mode 100644 (file)
index 0000000..db16a6f
--- /dev/null
@@ -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 <sys/time.h>
+#if ! defined(__hpux)
+/* HP-UX 11 doesn't have this header file */
+#include <sys/select.h>
+#endif
+#include "tlpi_hdr.h"
+
+static void
+usageError(const char *progName)
+{
+    fprintf(stderr, "Usage: %s {timeout|-} fd-num[rw]...\n", progName);
+    fprintf(stderr, "    - means infinite timeout; \n");
+    fprintf(stderr, "    r = monitor for read\n");
+    fprintf(stderr, "    w = monitor for write\n\n");
+    fprintf(stderr, "    e.g.: %s - 0rw 1w\n", progName);
+    exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+    fd_set readfds, writefds;
+    int ready, nfds, fd, numRead, j;
+    struct timeval timeout;
+    struct timeval *pto;
+    char buf[10];                       /* Large enough to hold "rw\0" */
+
+    if (argc < 2 || strcmp(argv[1], "--help") == 0)
+        usageError(argv[0]);
+
+    /* Timeout for select() is specified in argv[1] */
+
+    if (strcmp(argv[1], "-") == 0) {
+        pto = NULL;                     /* Infinite timeout */
+    } else {
+        pto = &timeout;
+        timeout.tv_sec = getLong(argv[1], 0, "timeout");
+        timeout.tv_usec = 0;            /* No microseconds */
+    }
+
+    /* Process remaining arguments to build file descriptor sets */
+
+    nfds = 0;
+    FD_ZERO(&readfds);
+    FD_ZERO(&writefds);
+
+    for (j = 2; j < argc; j++) {
+        numRead = sscanf(argv[j], "%d%2[rw]", &fd, buf);
+        if (numRead != 2)
+            usageError(argv[0]);
+        if (fd >= FD_SETSIZE)
+            cmdLineErr("file descriptor exceeds limit (%d)\n", FD_SETSIZE);
+
+        if (fd >= nfds)
+            nfds = fd + 1;              /* Record maximum fd + 1 */
+        if (strchr(buf, 'r') != NULL)
+            FD_SET(fd, &readfds);
+        if (strchr(buf, 'w') != NULL)
+            FD_SET(fd, &writefds);
+    }
+
+    /* We've built all of the arguments; now call select() */
+
+    ready = select(nfds, &readfds, &writefds, NULL, pto);
+                                        /* Ignore exceptional events */
+    if (ready == -1)
+        errExit("select");
+
+    /* Display results of select() */
+
+    printf("ready = %d\n", ready);
+    for (fd = 0; fd < nfds; fd++)
+        printf("%d: %s%s\n", fd, FD_ISSET(fd, &readfds) ? "r" : "",
+                FD_ISSET(fd, &writefds) ? "w" : "");
+
+    if (pto != NULL)
+        printf("timeout after select(): %ld.%03ld\n",
+               (long) timeout.tv_sec, (long) timeout.tv_usec / 1000);
+    exit(EXIT_SUCCESS);
+}
diff --git a/cap/Makefile b/cap/Makefile
new file mode 100644 (file)
index 0000000..7615286
--- /dev/null
@@ -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 (file)
index 0000000..ab5a61a
--- /dev/null
@@ -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 <unistd.h> */
+#ifndef _XOPEN_SOURCE
+#define _XOPEN_SOURCE           /* Get crypt() declaration from <unistd.h> */
+#endif
+#include <sys/capability.h>
+#include <unistd.h>
+#include <limits.h>
+#include <pwd.h>
+#include <shadow.h>
+#include "tlpi_hdr.h"
+
+/* Change setting of capability in caller's effective capabilities */
+
+static int
+modifyCap(int capability, int setting)
+{
+    cap_t caps;
+    cap_value_t capList[1];
+
+    /* Retrieve caller's current capabilities */
+
+    caps = cap_get_proc();
+    if (caps == NULL)
+        return -1;
+
+    /* Change setting of 'capability' in the effective set of 'caps'. The
+       third argument, 1, is the number of items in the array 'capList'. */
+
+    capList[0] = capability;
+    if (cap_set_flag(caps, CAP_EFFECTIVE, 1, capList, setting) == -1) {
+        cap_free(caps);
+        return -1;
+    }
+
+    /* Push modified capability sets back to kernel, to change
+       caller's capabilities */
+
+    if (cap_set_proc(caps) == -1) {
+        cap_free(caps);
+        return -1;
+    }
+
+    /* Free the structure that was allocated by libcap */
+
+    if (cap_free(caps) == -1)
+        return -1;
+
+    return 0;
+}
+
+static int              /* Raise capability in caller's effective set */
+raiseCap(int capability)
+{
+    return modifyCap(capability, CAP_SET);
+}
+
+/* An analogous dropCap() (unneeded in this program), could be
+   defined as: modifyCap(capability, CAP_CLEAR); */
+
+static int              /* Drop all capabilities from all sets */
+dropAllCaps(void)
+{
+    cap_t empty;
+    int s;
+
+    empty = cap_init();
+    if (empty == NULL)
+        return -1;
+
+    s = cap_set_proc(empty);
+
+    if (cap_free(empty) == -1)
+        return -1;
+
+    return s;
+}
+
+int
+main(int argc, char *argv[])
+{
+    char *username, *password, *encrypted, *p;
+    struct passwd *pwd;
+    struct spwd *spwd;
+    Boolean authOk;
+    size_t len;
+    long lnmax;
+
+    /* Determine size of buffer required for a username, and allocate it */
+
+    lnmax = sysconf(_SC_LOGIN_NAME_MAX);
+    if (lnmax == -1)                        /* If limit is indeterminate */
+        lnmax = 256;                        /* make a guess */
+
+    username = malloc(lnmax);
+    if (username == NULL)
+        errExit("malloc");
+
+    printf("Username: ");
+    fflush(stdout);
+    if (fgets(username, lnmax, stdin) == NULL)
+        exit(EXIT_FAILURE);                 /* Exit on EOF */
+
+    len = strlen(username);
+    if (username[len - 1] == '\n')
+        username[len - 1] = '\0';           /* Remove trailing '\n' */
+
+    /* Look up password record for username */
+
+    pwd = getpwnam(username);
+    if (pwd == NULL)
+        fatal("couldn't get password record");
+
+    /* Only raise CAP_DAC_READ_SEARCH for as long as we need it */
+
+    if (raiseCap(CAP_DAC_READ_SEARCH) == -1)
+        fatal("raiseCap() failed");
+
+    /* Look up shadow password record for username */
+
+    spwd = getspnam(username);
+    if (spwd == NULL && errno == EACCES)
+        fatal("no permission to read shadow password file");
+
+    /* At this point, we won't need any more capabilities,
+       so drop all capabilities from all sets */
+
+    if (dropAllCaps() == -1)
+        fatal("dropAllCaps() failed");
+
+    if (spwd != NULL)           /* If there is a shadow password record */
+        pwd->pw_passwd = spwd->sp_pwdp;     /* Use the shadow password */
+
+    password = getpass("Password: ");
+
+    /* Encrypt password and erase cleartext version immediately */
+
+    encrypted = crypt(password, pwd->pw_passwd);
+    for (p = password; *p != '\0'; )
+        *p++ = '\0';
+
+    if (encrypted == NULL)
+        errExit("crypt");
+
+    authOk = strcmp(encrypted, pwd->pw_passwd) == 0;
+    if (!authOk) {
+        printf("Incorrect password\n");
+        exit(EXIT_FAILURE);
+    }
+
+    printf("Successfully authenticated: UID=%ld\n", (long) pwd->pw_uid);
+
+    /* Now do authenticated work... */
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/daemons/Makefile b/daemons/Makefile
new file mode 100644 (file)
index 0000000..bdf9747
--- /dev/null
@@ -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 (file)
index 0000000..986fc9e
--- /dev/null
@@ -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 <sys/stat.h>
+#include <fcntl.h>
+#include "become_daemon.h"
+#include "tlpi_hdr.h"
+
+int                                     /* Returns 0 on success, -1 on error */
+becomeDaemon(int flags)
+{
+    int maxfd, fd;
+
+    switch (fork()) {                   /* Become background process */
+    case -1: return -1;
+    case 0:  break;                     /* Child falls through... */
+    default: _exit(EXIT_SUCCESS);       /* while parent terminates */
+    }
+
+    if (setsid() == -1)                 /* Become leader of new session */
+        return -1;
+
+    switch (fork()) {                   /* Ensure we are not session leader */
+    case -1: return -1;
+    case 0:  break;
+    default: _exit(EXIT_SUCCESS);
+    }
+
+    if (!(flags & BD_NO_UMASK0))
+        umask(0);                       /* Clear file mode creation mask */
+
+    if (!(flags & BD_NO_CHDIR))
+        chdir("/");                     /* Change to root directory */
+
+    if (!(flags & BD_NO_CLOSE_FILES)) { /* Close all open files */
+        maxfd = sysconf(_SC_OPEN_MAX);
+        if (maxfd == -1)                /* Limit is indeterminate... */
+            maxfd = BD_MAX_CLOSE;       /* so take a guess */
+
+        for (fd = 0; fd < maxfd; fd++)
+            close(fd);
+    }
+
+    if (!(flags & BD_NO_REOPEN_STD_FDS)) {
+        close(STDIN_FILENO);            /* Reopen standard fd's to /dev/null */
+
+        fd = open("/dev/null", O_RDWR);
+
+        if (fd != STDIN_FILENO)         /* 'fd' should be 0 */
+            return -1;
+        if (dup2(STDIN_FILENO, STDOUT_FILENO) != STDOUT_FILENO)
+            return -1;
+        if (dup2(STDIN_FILENO, STDERR_FILENO) != STDERR_FILENO)
+            return -1;
+    }
+
+    return 0;
+}
diff --git a/daemons/become_daemon.h b/daemons/become_daemon.h
new file mode 100644 (file)
index 0000000..096bd52
--- /dev/null
@@ -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 (file)
index 0000000..a63383f
--- /dev/null
@@ -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 <sys/stat.h>
+#include <signal.h>
+#include "become_daemon.h"
+#include "tlpi_hdr.h"
+
+static const char *LOG_FILE = "/tmp/ds.log";
+static const char *CONFIG_FILE = "/tmp/ds.conf";
+
+#include <time.h>
+#include <stdarg.h>
+
+static FILE *logfp;                 /* Log file stream */
+
+/* Write a message to the log file. Handle variable length argument
+   lists, with an initial format string (like printf(3), but without
+   a trailing newline). Precede each message with a timestamp. */
+
+static void
+logMessage(const char *format, ...)
+{
+    va_list argList;
+    const char *TIMESTAMP_FMT = "%F %X";        /* = YYYY-MM-DD HH:MM:SS */
+#define TS_BUF_SIZE sizeof("YYYY-MM-DD HH:MM:SS")       /* Includes '\0' */
+    char timestamp[TS_BUF_SIZE];
+    time_t t;
+    struct tm *loc;
+
+    t = time(NULL);
+    loc = localtime(&t);
+    if (loc == NULL ||
+           strftime(timestamp, TS_BUF_SIZE, TIMESTAMP_FMT, loc) == 0)
+        fprintf(logfp, "???Unknown time????: ");
+    else
+        fprintf(logfp, "%s: ", timestamp);
+
+    va_start(argList, format);
+    vfprintf(logfp, format, argList);
+    fprintf(logfp, "\n");
+    va_end(argList);
+}
+
+/* Open the log file 'logFilename' */
+
+static void
+logOpen(const char *logFilename)
+{
+    mode_t m;
+
+    m = umask(077);
+    logfp = fopen(logFilename, "a");
+    umask(m);
+
+    /* If opening the log fails we can't display a message... */
+
+    if (logfp == NULL)
+        exit(EXIT_FAILURE);
+
+    setbuf(logfp, NULL);                    /* Disable stdio buffering */
+
+    logMessage("Opened log file");
+}
+
+/* Close the log file */
+
+static void
+logClose(void)
+{
+    logMessage("Closing log file");
+    fclose(logfp);
+}
+
+/* (Re)initialize from configuration file. In a real application
+   we would of course have some daemon initialization parameters in
+   this file. In this dummy version, we simply read a single line
+   from the file and write it to the log. */
+
+static void
+readConfigFile(const char *configFilename)
+{
+    FILE *configfp;
+#define SBUF_SIZE 100
+    char str[SBUF_SIZE];
+
+    configfp = fopen(configFilename, "r");
+    if (configfp != NULL) {                 /* Ignore nonexistent file */
+        if (fgets(str, SBUF_SIZE, configfp) == NULL)
+            str[0] = '\0';
+        else
+            str[strlen(str) - 1] = '\0';    /* Strip trailing '\n' */
+        logMessage("Read config file: %s", str);
+        fclose(configfp);
+    }
+}
+
+static volatile sig_atomic_t hupReceived = 0;
+                        /* Set nonzero on receipt of SIGHUP */
+
+static void
+sighupHandler(int sig)
+{
+    hupReceived = 1;
+}
+
+int
+main(int argc, char *argv[])
+{
+    const int SLEEP_TIME = 15;      /* Time to sleep between messages */
+    int count = 0;                  /* Number of completed SLEEP_TIME intervals */
+    int unslept;                    /* Time remaining in sleep interval */
+    struct sigaction sa;
+
+    sigemptyset(&sa.sa_mask);
+    sa.sa_flags = SA_RESTART;
+    sa.sa_handler = sighupHandler;
+    if (sigaction(SIGHUP, &sa, NULL) == -1)
+        errExit("sigaction");
+
+    if (becomeDaemon(0) == -1)
+        errExit("becomeDaemon");
+
+    logOpen(LOG_FILE);
+    readConfigFile(CONFIG_FILE);
+
+    unslept = SLEEP_TIME;
+
+    for (;;) {
+        unslept = sleep(unslept);       /* Returns > 0 if interrupted */
+
+        if (hupReceived) {              /* If we got SIGHUP... */
+            hupReceived = 0;            /* Get ready for next SIGHUP */
+            logClose();
+            logOpen(LOG_FILE);
+            readConfigFile(CONFIG_FILE);
+        }
+
+        if (unslept == 0) {             /* On completed interval */
+            count++;
+            logMessage("Main: %d", count);
+            unslept = SLEEP_TIME;       /* Reset interval */
+        }
+    }
+}
diff --git a/daemons/t_syslog.c b/daemons/t_syslog.c
new file mode 100644 (file)
index 0000000..93ff56e
--- /dev/null
@@ -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 <syslog.h>
+#include "tlpi_hdr.h"
+
+static void
+usageError(const char *progName)
+{
+    fprintf(stderr, "Usage: %s [-p] [-e] [-l level] \"message\"\n", progName);
+    fprintf(stderr, "    -p   log PID\n");
+    fprintf(stderr, "    -e   log to stderr also\n");
+    fprintf(stderr, "    -l   level (g=EMERG; a=ALERT; c=CRIT; e=ERR\n");
+    fprintf(stderr, "                w=WARNING; n=NOTICE; i=INFO; d=DEBUG)\n");
+    exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+    int level, options, opt;
+
+    options = 0;
+    level = LOG_INFO;
+
+    while ((opt = getopt(argc, argv, "l:pe")) != -1) {
+        switch (opt) {
+        case 'l':
+            switch (optarg[0]) {
+            case 'a': level = LOG_ALERT;        break;
+            case 'c': level = LOG_CRIT;         break;
+            case 'e': level = LOG_ERR;          break;
+            case 'w': level = LOG_WARNING;      break;
+            case 'n': level = LOG_NOTICE;       break;
+            case 'i': level = LOG_INFO;         break;
+            case 'd': level = LOG_DEBUG;        break;
+            default:  cmdLineErr("Bad facility: %c\n", optarg[0]);
+            }
+            break;
+
+        case 'p':
+            options |= LOG_PID;
+            break;
+
+#if ! defined(__hpux) && ! defined(__sun)
+
+        /* Not on HP-UX 11 or Solaris 8 */
+
+        case 'e':
+            options |= LOG_PERROR;
+            break;
+#endif
+
+        default:
+            fprintf(stderr, "Bad option\n");
+            usageError(argv[0]);
+        }
+    }
+
+    if (argc != optind + 1)
+        usageError(argv[0]);
+
+    openlog(argv[0], options, LOG_USER);
+    syslog(LOG_USER | level, "%s", argv[optind]);
+    closelog();
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/daemons/test_become_daemon.c b/daemons/test_become_daemon.c
new file mode 100644 (file)
index 0000000..df4650c
--- /dev/null
@@ -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 (file)
index 0000000..8a880b5
--- /dev/null
@@ -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 (file)
index 0000000..79b57a6
--- /dev/null
@@ -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 <sys/stat.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    int fd;
+
+    if (mkdir("test", S_IRUSR | S_IWUSR | S_IXUSR) == -1)
+        errExit("mkdir");
+    if (chdir("test") == -1)
+        errExit("chdir");
+    fd = open("myfile", O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
+    if (fd == -1)
+        errExit("open");
+    if (close(fd) == -1)
+        errExit("close");
+    if (symlink("myfile", "../mylink") == -1)
+        errExit("symlink");
+    if (chmod("../mylink", S_IRUSR) == -1)
+        errExit("chmod");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/dirs_links/file_type_stats.c b/dirs_links/file_type_stats.c
new file mode 100644 (file)
index 0000000..50fd3d4
--- /dev/null
@@ -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 <ftw.h>
+#include "tlpi_hdr.h"
+
+static int numReg = 0, numDir = 0, numSymLk = 0, numSocket = 0,
+           numFifo = 0, numChar = 0, numBlock = 0,
+           numNonstatable = 0;
+
+static int
+countFile(const char *path, const struct stat *sb, int flag, struct FTW *ftwb)
+
+{
+    if (flag == FTW_NS) {
+        numNonstatable++;
+        return 0;
+    }
+
+    switch (sb->st_mode & S_IFMT) {
+    case S_IFREG:  numReg++;    break;
+    case S_IFDIR:  numDir++;    break;
+    case S_IFCHR:  numChar++;   break;
+    case S_IFBLK:  numBlock++;  break;
+    case S_IFLNK:  numSymLk++;  break;
+    case S_IFIFO:  numFifo++;   break;
+    case S_IFSOCK: numSocket++; break;
+    }
+    return 0;           /* Always tell nftw() to continue */
+}
+
+static void
+printStats(const char *msg, int num, int numFiles)
+{
+    printf("%-15s   %6d %6.1f%%\n", msg, num, num * 100.0 / numFiles);
+}
+
+int
+main(int argc, char *argv[])
+{
+    int numFiles;       /* Total number of files */
+
+    if (argc != 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s dir-path\n", argv[0]);
+
+    /* Traverse directory tree counting files; don't follow symbolic links */
+
+    if (nftw(argv[1], &countFile, 20, FTW_PHYS) == -1) {
+        perror("nftw");
+        exit(EXIT_FAILURE);
+    }
+
+    numFiles = numReg + numDir + numSymLk + numSocket +
+                numFifo + numChar + numBlock + numNonstatable;
+
+    if (numFiles == 0) {
+        printf("No files found\n");
+    } else {
+        printf("Total files:      %6d\n", numFiles);
+        printStats("Regular:", numReg, numFiles);
+        printStats("Directory:", numDir, numFiles);
+        printStats("Char device:", numChar, numFiles);
+        printStats("Block device:", numBlock, numFiles);
+        printStats("Symbolic link:", numSymLk, numFiles);
+        printStats("FIFO:", numFifo, numFiles);
+        printStats("Socket:", numSocket, numFiles);
+        printStats("Non-statable:", numNonstatable, numFiles);
+    }
+    exit(EXIT_SUCCESS);
+}
diff --git a/dirs_links/list_files.c b/dirs_links/list_files.c
new file mode 100644 (file)
index 0000000..5a6f967
--- /dev/null
@@ -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 <dirent.h> */
+#include <sys/types.h>
+#endif
+#include <dirent.h>
+#include "tlpi_hdr.h"
+
+static void             /* List all files in directory 'dirpath' */
+listFiles(const char *dirpath)
+{
+    DIR *dirp;
+    struct dirent *dp;
+    Boolean isCurrent;          /* True if 'dirpath' is "." */
+
+    isCurrent = strcmp(dirpath, ".") == 0;
+
+    dirp = opendir(dirpath);
+    if (dirp  == NULL) {
+        errMsg("opendir failed on '%s'", dirpath);
+        return;
+    }
+
+    /* For each entry in this directory, print directory + filename */
+
+    for (;;) {
+        errno = 0;              /* To distinguish error from end-of-directory */
+        dp = readdir(dirp);
+        if (dp == NULL)
+            break;
+
+        if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0)
+            continue;           /* Skip . and .. */
+
+        if (!isCurrent)
+            printf("%s/", dirpath);
+        printf("%s\n", dp->d_name);
+    }
+
+    if (errno != 0)
+        errExit("readdir");
+
+    if (closedir(dirp) == -1)
+        errMsg("closedir");
+}
+
+int
+main(int argc, char *argv[])
+{
+    if (argc > 1 && strcmp(argv[1], "--help") == 0)
+        usageErr("%s [dir-path...]\n", argv[0]);
+
+    if (argc == 1)              /* No arguments - use current directory */
+        listFiles(".");
+    else
+        for (argv++; *argv; argv++)
+            listFiles(*argv);
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/dirs_links/list_files_readdir_r.c b/dirs_links/list_files_readdir_r.c
new file mode 100644 (file)
index 0000000..72f7856
--- /dev/null
@@ -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 <dirent.h> */
+#include <sys/types.h>
+#endif
+#include <limits.h>
+#include <dirent.h>
+#include <stddef.h>
+#include "tlpi_hdr.h"
+
+static void             /* List all files in directory 'dirpath' */
+listFiles(const char *dirpath)
+{
+    DIR *dirp;
+    Boolean isCurrent;          /* True if 'dirpath' is "." */
+    struct dirent *result, *entryp;
+    int nameMax;
+
+    isCurrent = strcmp(dirpath, ".") == 0;
+
+    /* On Linux, NAME_MAX is defined in <limits.h>. However, this limit
+       may vary across file systems, so we really should use pathconf()
+       to find the true limit for this file system. */
+
+    nameMax = pathconf(dirpath, _PC_NAME_MAX);
+    if (nameMax == -1)          /* Indeterminate or error */
+        nameMax = 255;          /* So take a guess */
+
+    entryp = malloc(offsetof(struct dirent, d_name) + nameMax + 1);
+    if (entryp == NULL)
+        errExit("malloc");
+
+    /* Open the directory - on failure print an error and return */
+
+    dirp = opendir(dirpath);
+    if (dirp == NULL) {
+        errMsg("opendir failed on '%s'", dirpath);
+        return;
+    }
+
+    /* Look at each of the entries in this directory */
+
+    for (;;) {
+        errno = readdir_r(dirp, entryp, &result);
+        if (errno != 0)
+            errExit("readdir_r");
+
+        if (result == NULL)     /* End of stream */
+            break;
+
+        /* Skip . and .. */
+
+        if (strcmp(entryp->d_name, ".") == 0 ||
+                strcmp(entryp->d_name, "..") == 0)
+            continue;
+
+        /* Print directory + filename */
+
+        if (!isCurrent) printf("%s/", dirpath);
+        printf("%s\n", entryp->d_name);
+    }
+
+    if (closedir(dirp) == -1)
+        errMsg("closedir");
+}
+
+int
+main(int argc, char *argv[])
+{
+    if (argc == 1)              /* No arguments - use current directory */
+        listFiles(".");
+    else
+        for (argv++; *argv; argv++)
+            listFiles(*argv);
+    exit(EXIT_SUCCESS);
+}
diff --git a/dirs_links/nftw_dir_tree.c b/dirs_links/nftw_dir_tree.c
new file mode 100644 (file)
index 0000000..484d141
--- /dev/null
@@ -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 <ftw.h>
+#include "tlpi_hdr.h"
+
+static void
+usageError(const char *progName, const char *msg)
+{
+    if (msg != NULL)
+        fprintf(stderr, "%s\n", msg);
+    fprintf(stderr, "Usage: %s [-d] [-m] [-p] [directory-path]\n", progName);
+    fprintf(stderr, "\t-d Use FTW_DEPTH flag\n");
+    fprintf(stderr, "\t-m Use FTW_MOUNT flag\n");
+    fprintf(stderr, "\t-p Use FTW_PHYS flag\n");
+    exit(EXIT_FAILURE);
+}
+
+static int                      /* Function called by nftw() */
+dirTree(const char *pathname, const struct stat *sbuf, int type,
+        struct FTW *ftwb)
+{
+    switch (sbuf->st_mode & S_IFMT) {       /* Print file type */
+    case S_IFREG:  printf("-"); break;
+    case S_IFDIR:  printf("d"); break;
+    case S_IFCHR:  printf("c"); break;
+    case S_IFBLK:  printf("b"); break;
+    case S_IFLNK:  printf("l"); break;
+    case S_IFIFO:  printf("p"); break;
+    case S_IFSOCK: printf("s"); break;
+    default:       printf("?"); break;      /* Should never happen (on Linux) */
+    }
+
+    printf(" %s  ",
+            (type == FTW_D)  ? "D  " : (type == FTW_DNR) ? "DNR" :
+            (type == FTW_DP) ? "DP " : (type == FTW_F)   ? "F  " :
+            (type == FTW_SL) ? "SL " : (type == FTW_SLN) ? "SLN" :
+            (type == FTW_NS) ? "NS " : "  ");
+
+    if (type != FTW_NS)
+        printf("%7ld ", (long) sbuf->st_ino);
+    else
+        printf("        ");
+
+    printf(" %*s", 4 * ftwb->level, "");        /* Indent suitably */
+    printf("%s\n",  &pathname[ftwb->base]);     /* Print basename */
+    return 0;                                   /* Tell nftw() to continue */
+}
+
+int
+main(int argc, char *argv[])
+{
+    int flags, opt;
+
+    flags = 0;
+    while ((opt = getopt(argc, argv, "dmp")) != -1) {
+        switch (opt) {
+        case 'd': flags |= FTW_DEPTH;   break;
+        case 'm': flags |= FTW_MOUNT;   break;
+        case 'p': flags |= FTW_PHYS;    break;
+        default:  usageError(argv[0], NULL);
+        }
+    }
+
+    if (argc > optind + 1)
+        usageError(argv[0], NULL);
+
+    if (nftw((argc > optind) ? argv[optind] : ".", dirTree, 10, flags) == -1) {
+        perror("nftw");
+        exit(EXIT_FAILURE);
+    }
+    exit(EXIT_SUCCESS);
+}
diff --git a/dirs_links/t_dirbasename.c b/dirs_links/t_dirbasename.c
new file mode 100644 (file)
index 0000000..90c7d2b
--- /dev/null
@@ -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 <libgen.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    char *t1, *t2;
+    int j;
+
+    for (j = 1; j < argc; j++)  {
+        t1 = strdup(argv[j]);
+        if (t1 == NULL)
+            errExit("strdup");
+        t2 = strdup(argv[j]);
+        if (t2 == NULL)
+            errExit("strdup");
+
+        printf("%s ==> %s + %s\n", argv[j], dirname(t1), basename(t2));
+
+        free(t1);
+        free(t2);
+    }
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/dirs_links/t_unlink.c b/dirs_links/t_unlink.c
new file mode 100644 (file)
index 0000000..d71cc72
--- /dev/null
@@ -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 <sys/stat.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+#define CMD_SIZE 200
+#define BUF_SIZE 1024
+
+int
+main(int argc, char *argv[])
+{
+    int fd, j, numBlocks;
+    char shellCmd[CMD_SIZE];            /* Command to be passed to system() */
+    char buf[BUF_SIZE];                 /* Random bytes to write to file */
+
+    if (argc < 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s temp-file [num-1kB-blocks] \n", argv[0]);
+
+    numBlocks = (argc > 2) ? getInt(argv[2], GN_GT_0, "num-1kB-blocks")
+                           : 100000;
+
+    /* O_EXCL so that we ensure we create a new file */
+
+    fd = open(argv[1], O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
+    if (fd == -1)
+        errExit("open");
+
+    if (unlink(argv[1]) == -1)          /* Remove filename */
+        errExit("unlink");
+
+    for (j = 0; j < numBlocks; j++)     /* Write lots of junk to file */
+        if (write(fd, buf, BUF_SIZE) != BUF_SIZE)
+            fatal("partial/failed write");
+
+    snprintf(shellCmd, CMD_SIZE, "df -k `dirname %s`", argv[1]);
+    system(shellCmd);                   /* View space used in file system */
+
+    if (close(fd) == -1)                /* File is now destroyed */
+        errExit("close");
+    printf("********** Closed file descriptor\n");
+
+    /* See the erratum for page 348 at http://man7.org/tlpi/errata/.
+       Depending on factors such as random scheduler decisions and the
+       size of the file created, the 'df' command executed by the second
+       system() call below does may not show a change in the amount
+       of disk space consumed, because the blocks of the closed file
+       have not yet been freed by the kernel. If this is the case,
+       then inserting a sleep(1) call here should be sufficient to
+       ensure that the the file blocks have been freed by the time
+       of the second 'df' command.
+    */
+
+    system(shellCmd);                   /* Review space used in file system */
+    exit(EXIT_SUCCESS);
+}
diff --git a/dirs_links/view_symlink.c b/dirs_links/view_symlink.c
new file mode 100644 (file)
index 0000000..c347920
--- /dev/null
@@ -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 <sys/stat.h>
+#include <limits.h>             /* For definition of PATH_MAX */
+#include "tlpi_hdr.h"
+
+#define BUF_SIZE PATH_MAX
+
+int
+main(int argc, char *argv[])
+{
+    struct stat statbuf;
+    char buf[BUF_SIZE];
+    ssize_t numBytes;
+
+    if (argc != 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s pathname\n", argv[0]);
+
+    /* User lstat() to check whether the supplied pathname is
+       a symbolic link. Alternatively, we could have checked to
+       whether readlink() failed with EINVAL. */
+
+    if (lstat(argv[1], &statbuf) == -1)
+        errExit("lstat");
+
+    if (!S_ISLNK(statbuf.st_mode))
+        fatal("%s is not a symbolic link", argv[1]);
+
+    numBytes = readlink(argv[1], buf, BUF_SIZE - 1);
+    if (numBytes == -1)
+        errExit("readlink");
+    buf[numBytes] = '\0';                       /* Add terminating null byte */
+    printf("readlink: %s --> %s\n", argv[1], buf);
+
+    if (realpath(argv[1], buf) == NULL)
+        errExit("realpath");
+    printf("realpath: %s --> %s\n", argv[1], buf);
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/filebuff/Makefile b/filebuff/Makefile
new file mode 100644 (file)
index 0000000..c444579
--- /dev/null
@@ -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 (file)
index 0000000..b407ef0
--- /dev/null
@@ -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 <sys/stat.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+#ifndef BUF_SIZE        /* Allow "cc -D" to override definition */
+#define BUF_SIZE 1024
+#endif
+
+int
+main(int argc, char *argv[])
+{
+    int inputFd, outputFd, openFlags;
+    mode_t filePerms;
+    ssize_t numRead;
+    char buf[BUF_SIZE];
+
+    if (argc != 3 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s old-file new-file\n", argv[0]);
+
+    /* Open input and output files */
+
+    inputFd = open(argv[1], O_RDONLY);
+    if (inputFd == -1)
+        errExit("opening file %s", argv[1]);
+
+    openFlags = O_CREAT | O_WRONLY | O_TRUNC;
+    filePerms = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP |
+                S_IROTH | S_IWOTH;      /* rw-rw-rw- */
+    outputFd = open(argv[2], openFlags, filePerms);
+    if (outputFd == -1)
+        errExit("opening file %s", argv[2]);
+
+    /* Transfer data until we encounter end of input or an error */
+
+    while ((numRead = read(inputFd, buf, BUF_SIZE)) > 0)
+        if (write(outputFd, buf, numRead) != numRead)
+            fatal("couldn't write whole buffer");
+    if (numRead == -1)
+        errExit("read");
+
+    if (close(inputFd) == -1)
+        errExit("close input");
+    if (close(outputFd) == -1)
+        errExit("close output");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/filebuff/direct_read.c b/filebuff/direct_read.c
new file mode 100644 (file)
index 0000000..e512f91
--- /dev/null
@@ -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 <fcntl.h> */
+#include <fcntl.h>
+#include <malloc.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    int fd;
+    ssize_t numRead;
+    size_t length, alignment;
+    off_t offset;
+    char *buf;
+
+    if (argc < 3 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s file length [offset [alignment]]\n", argv[0]);
+
+    length = getLong(argv[2], GN_ANY_BASE, "length");
+    offset = (argc > 3) ? getLong(argv[3], GN_ANY_BASE, "offset") : 0;
+    alignment = (argc > 4) ? getLong(argv[4], GN_ANY_BASE, "alignment") : 4096;
+
+    fd = open(argv[1], O_RDONLY | O_DIRECT);
+    if (fd == -1)
+        errExit("open");
+
+    /* memalign() allocates a block of memory aligned on an address that
+       is a multiple of its first argument. By specifying this argument as
+       2 * 'alignment' and then adding 'alignment' to the returned pointer,
+       we ensure that 'buf' is aligned on a non-power-of-two multiple of
+       'alignment'. We do this to ensure that if, for example, we ask
+       for a 256-byte aligned buffer, we don't accidentally get
+       a buffer that is also aligned on a 512-byte boundary. */
+
+    buf = memalign(alignment * 2, length + alignment);
+    if (buf == NULL)
+        errExit("memalign");
+
+    buf += alignment;
+
+    if (lseek(fd, offset, SEEK_SET) == -1)
+        errExit("lseek");
+
+    numRead = read(fd, buf, length);
+    if (numRead == -1)
+        errExit("read");
+    printf("Read %ld bytes\n", (long) numRead);
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/filebuff/mix23_linebuff.c b/filebuff/mix23_linebuff.c
new file mode 100644 (file)
index 0000000..37ad820
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+int
+main(int argc, char *argv[])
+{
+    printf("If I had more time, \n");
+    write(STDOUT_FILENO, "I would have written you a shorter letter.\n", 43);
+    exit(EXIT_SUCCESS);
+}
diff --git a/filebuff/mix23io.c b/filebuff/mix23io.c
new file mode 100644 (file)
index 0000000..0168678
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+int
+main(int argc, char *argv[])
+{
+    printf("To man the world is twofold, ");
+    if (argc > 1)
+        printf("\n");
+    write(STDOUT_FILENO, "in accordance with his twofold attitude.\n", 41);
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/filebuff/write_bytes.c b/filebuff/write_bytes.c
new file mode 100644 (file)
index 0000000..3f1d113
--- /dev/null
@@ -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 <sys/stat.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    size_t bufSize, numBytes, thisWrite, totWritten;
+    char *buf;
+    int fd, openFlags;
+
+    if (argc != 4 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s file num-bytes buf-size\n", argv[0]);
+
+    numBytes = getLong(argv[2], GN_GT_0, "num-bytes");
+    bufSize = getLong(argv[3], GN_GT_0, "buf-size");
+
+    buf = malloc(bufSize);
+    if (buf == NULL)
+        errExit("malloc");
+
+    openFlags = O_CREAT | O_WRONLY;
+
+#if defined(USE_O_SYNC) && defined(O_SYNC)
+    openFlags |= O_SYNC;
+#endif
+
+    fd = open(argv[1], openFlags, S_IRUSR | S_IWUSR);
+    if (fd == -1)
+        errExit("open");
+
+    for (totWritten = 0; totWritten < numBytes;
+            totWritten += thisWrite) {
+        thisWrite = min(bufSize, numBytes - totWritten);
+
+        if (write(fd, buf, thisWrite) != thisWrite)
+            fatal("partial/failed write");
+
+#ifdef USE_FSYNC
+        if (fsync(fd))
+            errExit("fsync");
+#endif
+#ifdef USE_FDATASYNC
+        if (fdatasync(fd))
+            errExit("fdatasync");
+#endif
+    }
+
+    if (close(fd) == -1)
+        errExit("close");
+    exit(EXIT_SUCCESS);
+}
diff --git a/fileio/Makefile b/fileio/Makefile
new file mode 100644 (file)
index 0000000..7fc92aa
--- /dev/null
@@ -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 (file)
index 0000000..ec92046
--- /dev/null
@@ -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 <sys/stat.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    int numBytes, j, flags, fd;
+    Boolean useLseek;
+
+    if (argc < 3 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s file num-bytes [x]\n"
+                 "        'x' means use lseek() instead of O_APPEND\n",
+                 argv[0]);
+
+    useLseek = argc > 3;
+    flags = useLseek ? 0 : O_APPEND;
+    numBytes = getInt(argv[2], 0, "num-bytes");
+
+    fd = open(argv[1], O_RDWR | O_CREAT | flags, S_IRUSR | S_IWUSR);
+    if (fd == -1)
+        errExit("open");
+
+    for (j = 0; j < numBytes; j++) {
+        if (useLseek)
+            if (lseek(fd, 0, SEEK_END) == -1)
+                errExit("lseek");
+        if (write(fd, "x", 1) != 1)
+            fatal("write() failed");
+    }
+
+    printf("%ld done\n", (long) getpid());
+    exit(EXIT_SUCCESS);
+}
diff --git a/fileio/bad_exclusive_open.c b/fileio/bad_exclusive_open.c
new file mode 100644 (file)
index 0000000..34c8e9e
--- /dev/null
@@ -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 <sys/stat.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    int fd;
+
+    if (argc < 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s file\n", argv[0]);
+
+    fd = open(argv[1], O_WRONLY);       /* Open 1: check if file exists */
+    if (fd != -1) {                     /* Open succeeded */
+        printf("[PID %ld] File \"%s\" already exists\n",
+                (long) getpid(), argv[1]);
+        close(fd);
+    } else {
+        if (errno != ENOENT) {          /* Failed for unexpected reason */
+            errExit("open");
+        } else {
+            printf("[PID %ld] File \"%s\" doesn't exist yet\n",
+                    (long) getpid(), argv[1]);
+            if (argc > 2) {             /* Delay between check and create */
+                sleep(5);               /* Suspend execution for 5 seconds */
+                printf("[PID %ld] Done sleeping\n", (long) getpid());
+            }
+            fd = open(argv[1], O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
+            if (fd == -1)
+                errExit("open");
+
+            printf("[PID %ld] Created file \"%s\" exclusively\n",
+                    (long) getpid(), argv[1]);          /* MAY NOT BE TRUE! */
+        }
+    }
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/fileio/copy.c b/fileio/copy.c
new file mode 100644 (file)
index 0000000..b407ef0
--- /dev/null
@@ -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 <sys/stat.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+#ifndef BUF_SIZE        /* Allow "cc -D" to override definition */
+#define BUF_SIZE 1024
+#endif
+
+int
+main(int argc, char *argv[])
+{
+    int inputFd, outputFd, openFlags;
+    mode_t filePerms;
+    ssize_t numRead;
+    char buf[BUF_SIZE];
+
+    if (argc != 3 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s old-file new-file\n", argv[0]);
+
+    /* Open input and output files */
+
+    inputFd = open(argv[1], O_RDONLY);
+    if (inputFd == -1)
+        errExit("opening file %s", argv[1]);
+
+    openFlags = O_CREAT | O_WRONLY | O_TRUNC;
+    filePerms = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP |
+                S_IROTH | S_IWOTH;      /* rw-rw-rw- */
+    outputFd = open(argv[2], openFlags, filePerms);
+    if (outputFd == -1)
+        errExit("opening file %s", argv[2]);
+
+    /* Transfer data until we encounter end of input or an error */
+
+    while ((numRead = read(inputFd, buf, BUF_SIZE)) > 0)
+        if (write(outputFd, buf, numRead) != numRead)
+            fatal("couldn't write whole buffer");
+    if (numRead == -1)
+        errExit("read");
+
+    if (close(inputFd) == -1)
+        errExit("close input");
+    if (close(outputFd) == -1)
+        errExit("close output");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/fileio/large_file.c b/fileio/large_file.c
new file mode 100644 (file)
index 0000000..d6eaa5b
--- /dev/null
@@ -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 <sys/stat.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    int fd;
+    off64_t off;
+
+    if (argc != 3 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s pathname offset\n", argv[0]);
+
+    fd = open64(argv[1], O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
+    if (fd == -1)
+        errExit("open64");
+
+    off = atoll(argv[2]);
+    if (lseek64(fd, off, SEEK_SET) == -1)
+        errExit("lseek64");
+
+    if (write(fd, "test", 4) == -1)
+        errExit("write");
+    exit(EXIT_SUCCESS);
+}
diff --git a/fileio/multi_descriptors.c b/fileio/multi_descriptors.c
new file mode 100644 (file)
index 0000000..bad3f34
--- /dev/null
@@ -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 <sys/stat.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    int fd1, fd2, fd3;
+#define file "a"
+    char cmd[] = "cat " file "; echo";
+
+    fd1 = open(file, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+    if (fd1 == -1)
+        errExit("open fd1");
+    fd2 = dup(fd1);
+    if (fd2 == -1)
+        errExit("dup");
+    fd3 = open(file, O_RDWR);
+    if (fd3 == -1)
+        errExit("open fd3");
+
+    /* 'fd1' and 'fd2' share same open file table entry (and thus file
+       offset). 'fd3' has its own open file table entry, and thus a
+       separate file offset. */
+
+    if (write(fd1, "Hello,", 6) == -1)
+        errExit("write1");
+    system(cmd);
+    if (write(fd2, " world", 6) == -1)
+        errExit("write2");
+    system(cmd);
+    if (lseek(fd2, 0, SEEK_SET) == -1)
+        errExit("lseek");
+    if (write(fd1, "HELLO,", 6) == -1)
+        errExit("write3");
+    system(cmd);
+    if (write(fd3, "Gidday", 6) == -1)
+        errExit("write4");
+    system(cmd);
+
+    if (close(fd1) == -1)
+        errExit("close output");
+    if (close(fd2) == -1)
+        errExit("close output");
+    if (close(fd3) == -1)
+        errExit("close output");
+    exit(EXIT_SUCCESS);
+}
diff --git a/fileio/seek_io.c b/fileio/seek_io.c
new file mode 100644 (file)
index 0000000..c57c386
--- /dev/null
@@ -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<length>|R<length>|w<string>|s<offset>}...
+
+   This program opens the file named on its command line, and then performs
+   the file I/O operations specified by its remaining command-line arguments:
+
+           r<length>    Read 'length' bytes from the file at current
+                        file offset, displaying them as text.
+
+           R<length>    Read 'length' bytes from the file at current
+                        file offset, displaying them in hex.
+
+           w<string>    Write 'string' at current file offset.
+
+           s<offset>    Set the file offset to 'offset'.
+
+   Example:
+
+        seek_io myfile wxyz s1 r2
+*/
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    size_t len;
+    off_t offset;
+    int fd, ap, j;
+    char *buf;
+    ssize_t numRead, numWritten;
+
+    if (argc < 3 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s file {r<length>|R<length>|w<string>|s<offset>}...\n",
+                 argv[0]);
+
+    fd = open(argv[1], O_RDWR | O_CREAT,
+                S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP |
+                S_IROTH | S_IWOTH);                     /* rw-rw-rw- */
+    if (fd == -1)
+        errExit("open");
+
+    for (ap = 2; ap < argc; ap++) {
+        switch (argv[ap][0]) {
+        case 'r':   /* Display bytes at current offset, as text */
+        case 'R':   /* Display bytes at current offset, in hex */
+            len = getLong(&argv[ap][1], GN_ANY_BASE, argv[ap]);
+
+            buf = malloc(len);
+            if (buf == NULL)
+                errExit("malloc");
+
+            numRead = read(fd, buf, len);
+            if (numRead == -1)
+                errExit("read");
+
+            if (numRead == 0) {
+                printf("%s: end-of-file\n", argv[ap]);
+            } else {
+                printf("%s: ", argv[ap]);
+                for (j = 0; j < numRead; j++) {
+                    if (argv[ap][0] == 'r')
+                        printf("%c", isprint((unsigned char) buf[j]) ?
+                                                buf[j] : '?');
+                    else
+                        printf("%02x ", (unsigned int) buf[j]);
+                }
+                printf("\n");
+            }
+
+            free(buf);
+            break;
+
+        case 'w':   /* Write string at current offset */
+            numWritten = write(fd, &argv[ap][1], strlen(&argv[ap][1]));
+            if (numWritten == -1)
+                errExit("write");
+            printf("%s: wrote %ld bytes\n", argv[ap], (long) numWritten);
+            break;
+
+        case 's':   /* Change file offset */
+            offset = getLong(&argv[ap][1], GN_ANY_BASE, argv[ap]);
+            if (lseek(fd, offset, SEEK_SET) == -1)
+                errExit("lseek");
+            printf("%s: seek succeeded\n", argv[ap]);
+            break;
+
+        default:
+            cmdLineErr("Argument must start with [rRws]: %s\n", argv[ap]);
+        }
+    }
+
+    if (close(fd) == -1)
+        errExit("close");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/fileio/t_readv.c b/fileio/t_readv.c
new file mode 100644 (file)
index 0000000..278bd01
--- /dev/null
@@ -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 <sys/stat.h>
+#include <sys/uio.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    int fd;
+    struct iovec iov[3];
+    struct stat myStruct;       /* First buffer */
+    int x;                      /* Second buffer */
+#define STR_SIZE 100
+    char str[STR_SIZE];         /* Third buffer */
+    ssize_t numRead, totRequired;
+
+    if (argc != 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s file\n", argv[0]);
+
+    fd = open(argv[1], O_RDONLY);
+    if (fd == -1)
+        errExit("open");
+
+    totRequired = 0;
+
+    iov[0].iov_base = &myStruct;
+    iov[0].iov_len = sizeof(struct stat);
+    totRequired += iov[0].iov_len;
+
+    iov[1].iov_base = &x;
+    iov[1].iov_len = sizeof(x);
+    totRequired += iov[1].iov_len;
+
+    iov[2].iov_base = str;
+    iov[2].iov_len = STR_SIZE;
+    totRequired += iov[2].iov_len;
+
+    numRead = readv(fd, iov, 3);
+    if (numRead == -1)
+        errExit("readv");
+
+    if (numRead < totRequired)
+        printf("Read fewer bytes than requested\n");
+
+    printf("total bytes requested: %ld; bytes read: %ld\n",
+            (long) totRequired, (long) numRead);
+    exit(EXIT_SUCCESS);
+}
diff --git a/fileio/t_truncate.c b/fileio/t_truncate.c
new file mode 100644 (file)
index 0000000..a5ebe81
--- /dev/null
@@ -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 (file)
index 0000000..2a56a21
--- /dev/null
@@ -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 (file)
index 0000000..3c322b4
--- /dev/null
@@ -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 <sys/stat.h>
+#include <fcntl.h>
+#include "region_locking.h"             /* For lockRegion() */
+#include "create_pid_file.h"            /* Declares createPidFile() and
+                                           defines CPF_CLOEXEC */
+#include "tlpi_hdr.h"
+
+#define BUF_SIZE 100            /* Large enough to hold maximum PID as string */
+
+/* Open/create the file named in 'pidFile', lock it, optionally set the
+   close-on-exec flag for the file descriptor, write our PID into the file,
+   and (in case the caller is interested) return the file descriptor
+   referring to the locked file. The caller is responsible for deleting
+   'pidFile' file (just) before process termination. 'progName' should be the
+   name of the calling program (i.e., argv[0] or similar), and is used only for
+   diagnostic messages. If we can't open 'pidFile', or we encounter some other
+   error, then we print an appropriate diagnostic and terminate. */
+
+int
+createPidFile(const char *progName, const char *pidFile, int flags)
+{
+    int fd;
+    char buf[BUF_SIZE];
+
+    fd = open(pidFile, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
+    if (fd == -1)
+        errExit("Could not open PID file %s", pidFile);
+
+    if (flags & CPF_CLOEXEC) {
+
+        /* Set the close-on-exec file descriptor flag */
+
+        /* Instead of the following steps, we could (on Linux) have opened the
+           file with O_CLOEXEC flag. However, not all systems support open()
+           O_CLOEXEC (which was standardized only in SUSv4), so instead we use
+           fcntl() to set the close-on-exec flag after opening the file */
+
+        flags = fcntl(fd, F_GETFD);                     /* Fetch flags */
+        if (flags == -1)
+            errExit("Could not get flags for PID file %s", pidFile);
+
+        flags |= FD_CLOEXEC;                            /* Turn on FD_CLOEXEC */
+
+        if (fcntl(fd, F_SETFD, flags) == -1)            /* Update flags */
+            errExit("Could not set flags for PID file %s", pidFile);
+    }
+
+    if (lockRegion(fd, F_WRLCK, SEEK_SET, 0, 0) == -1) {
+        if (errno  == EAGAIN || errno == EACCES)
+            fatal("PID file '%s' is locked; probably "
+                     "'%s' is already running", pidFile, progName);
+        else
+            errExit("Unable to lock PID file '%s'", pidFile);
+    }
+
+    if (ftruncate(fd, 0) == -1)
+        errExit("Could not truncate PID file '%s'", pidFile);
+
+    snprintf(buf, BUF_SIZE, "%ld\n", (long) getpid());
+    if (write(fd, buf, strlen(buf)) != strlen(buf))
+        fatal("Writing to PID file '%s'", pidFile);
+
+    return fd;
+}
diff --git a/filelock/create_pid_file.h b/filelock/create_pid_file.h
new file mode 100644 (file)
index 0000000..030910d
--- /dev/null
@@ -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 (file)
index 0000000..ea496f4
--- /dev/null
@@ -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 <sys/stat.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+#define MAX_LINE 100
+
+#ifdef __linux__
+#ifndef F_OFD_GETLK     /* In case we are on a system with glibc version
+                           earlier than 2.20 */
+#define F_OFD_GETLK     36
+#define F_OFD_SETLK     37
+#define F_OFD_SETLKW    38
+#endif
+#endif
+
+/* Return TRUE if mandatory locking is enabled for fd. */
+
+static Boolean
+mandLockingEnabled(int fd)
+{
+    /* Mandatory locking is enabled for a file if the set-group-ID bit is on
+       but group-execute permission is off (a combination that under earlier
+       versions of UNIX had no useful meaning). If mandatory locking is enabled
+       for a file, then attempts to perform write(2) or read(2) calls on locked
+       regions of files will block until the lock is removed (or if the I/O
+       call is nonblocking it will return immediately with an error). */
+
+    struct stat sb;
+
+    if (fstat(fd, &sb) == -1)
+        errExit("stat");
+    return (sb.st_mode & S_ISGID) != 0 && (sb.st_mode & S_IXGRP) == 0;
+}
+
+static void
+displayCmdFmt(int argc, char *argv[], const int fdList[])
+{
+    int j;
+
+    if (argc == 2) {            /* Only a single filename argument */
+        printf("\nFormat: cmd lock start length [whence]\n\n");
+    } else {
+        printf("\nFormat: %scmd lock start length [whence]\n\n",
+                (argc > 2) ? "file-num " : "");
+        printf("    file-num is a number from the following list\n");
+        for (j = 1; j < argc; j++)
+            printf("        %2d  %-10s [%s locking]\n", j, argv[j],
+                mandLockingEnabled(fdList[j]) ? "mandatory" :
+                                                "advisory");
+    }
+    printf("    'cmd' is 'g' (GETLK), 's' (SETLK), or 'w' (SETLKW)\n");
+#ifdef __linux__
+    printf("        or for OFD locks: 'G' (OFD_GETLK), 'S' (OFD_SETLK), or "
+            "'W' (OFD_SETLKW)\n");
+#endif
+    printf("    'lock' is 'r' (READ), 'w' (WRITE), or 'u' (UNLOCK)\n");
+    printf("    'start' and 'length' specify byte range to lock\n");
+    printf("    'whence' is 's' (SEEK_SET, default), 'c' (SEEK_CUR), "
+           "or 'e' (SEEK_END)\n\n");
+                /* Of course, SEEK_CUR is redundant since we can't
+                   change the file offset in this program... */
+}
+
+int
+main(int argc, char *argv[])
+{
+    int fd, numRead, cmd, status;
+    char lock, cmdCh, whence, line[MAX_LINE];
+    struct flock fl;
+    long long len, st;
+    int *fdList;
+    int fileNum, j;
+
+    if (argc < 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s file...\n", argv[0]);
+
+    fdList = calloc(argc, sizeof(int));
+    if (fdList == NULL)
+        errExit("calloc");
+
+    for (j = 1; j < argc; j++) {
+        fdList[j] = open(argv[j], O_RDWR);
+        if (fdList[j] == -1)
+            errExit("open (%s)", argv[j]);
+    }
+
+    /* Inform user what type of locking is in effect for each file. */
+
+    printf("File       Locking\n");
+    printf("----       -------\n");
+
+    for (j = 1; j < argc; j++)
+        printf("%-10s %s\n", argv[j], mandLockingEnabled(fdList[j]) ?
+                "mandatory" : "advisory");
+    printf("\n");
+
+    printf("Enter ? for help\n");
+
+    for (;;) {          /* Prompt for locking command and carry it out */
+        printf("PID=%ld> ", (long) getpid());
+        fflush(stdout);
+
+        if (fgets(line, MAX_LINE, stdin) == NULL)       /* EOF */
+            exit(EXIT_SUCCESS);
+        line[strlen(line) - 1] = '\0';          /* Remove trailing '\n' */
+
+        if (*line == '\0')
+            continue;                           /* Skip blank lines */
+
+        if (line[0] == '?') {
+            displayCmdFmt(argc, argv, fdList);
+            continue;
+        }
+
+        whence = 's';                   /* In case not otherwise filled in */
+
+        if (argc == 2) {                /* Just 1 file arg on command line? */
+            fileNum = 1;                /* Then no need to read a file number */
+            numRead = sscanf(line, " %c %c %lld %lld %c",
+                    &cmdCh, &lock, &st, &len, &whence);
+        } else {
+            numRead = sscanf(line, "%d %c %c %lld %lld %c",
+                    &fileNum, &cmdCh, &lock, &st, &len, &whence);
+        }
+
+        fl.l_start = st;
+        fl.l_len = len;
+
+        if (fileNum < 1 || fileNum >= argc) {
+            printf("File number must be in range 1 to %d\n", argc-1);
+            continue;
+        }
+
+        fd = fdList[fileNum];
+
+        if (!((numRead >= 4 && argc == 2) || (numRead >= 5 && argc > 2)) ||
+                strchr("gswGSW", cmdCh) == NULL ||
+                strchr("rwu", lock) == NULL || strchr("sce", whence) == NULL) {
+            printf("Invalid command!\n");
+            continue;
+        }
+
+        cmd =
+#ifdef __linux__
+              (cmdCh == 'G') ? F_OFD_GETLK : (cmdCh == 'S') ? F_OFD_SETLK :
+              (cmdCh == 'W') ? F_OFD_SETLKW :
+#endif
+              (cmdCh == 'g') ? F_GETLK : (cmdCh == 's') ? F_SETLK : F_SETLKW;
+#ifdef __linux__
+        fl.l_pid = 0;   /* Required for 'OFD' locking commands */
+#endif
+        fl.l_type = (lock == 'r') ? F_RDLCK : (lock == 'w') ? F_WRLCK : F_UNLCK;
+        fl.l_whence = (whence == 'c') ? SEEK_CUR :
+                      (whence == 'e') ? SEEK_END : SEEK_SET;
+
+        status = fcntl(fd, cmd, &fl);           /* Perform request... */
+
+        if (cmd == F_GETLK
+#ifdef __linux__
+                || cmd == F_OFD_GETLK
+#endif
+                ) {
+            if (status == -1) {
+                errMsg("fcntl");
+            } else {
+                if (fl.l_type == F_UNLCK)
+                    printf("[PID=%ld] Lock can be placed\n", (long) getpid());
+                else                            /* Locked out by someone else */
+                    printf("[PID=%ld] Denied by %s lock on %lld:%lld "
+                            "(held by PID %ld)\n", (long) getpid(),
+                            (fl.l_type == F_RDLCK) ? "READ" : "WRITE",
+                            (long long) fl.l_start,
+                            (long long) fl.l_len, (long) fl.l_pid);
+            }
+        } else {
+            if (status == 0)
+                printf("[PID=%ld] %s\n", (long) getpid(),
+                        (lock == 'u') ? "unlocked" : "got lock");
+            else if (errno == EAGAIN || errno == EACCES)
+                printf("[PID=%ld] failed (incompatible lock)\n",
+                        (long) getpid());
+            else if (errno == EDEADLK)                          /* F_SETLKW */
+                printf("[PID=%ld] failed (deadlock)\n", (long) getpid());
+            else
+                errMsg("fcntl");
+        }
+    }
+}
diff --git a/filelock/region_locking.c b/filelock/region_locking.c
new file mode 100644 (file)
index 0000000..d53e3de
--- /dev/null
@@ -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 <fcntl.h>
+#include "region_locking.h"             /* Declares functions defined here */
+
+/* Lock a file region (private; public interfaces below) */
+
+static int
+lockReg(int fd, int cmd, int type, int whence, int start, off_t len)
+{
+    struct flock fl;
+
+    fl.l_type = type;
+    fl.l_whence = whence;
+    fl.l_start = start;
+    fl.l_len = len;
+
+    return fcntl(fd, cmd, &fl);
+}
+
+int                     /* Lock a file region using nonblocking F_SETLK */
+lockRegion(int fd, int type, int whence, int start, int len)
+{
+    return lockReg(fd, F_SETLK, type, whence, start, len);
+}
+
+int                     /* Lock a file region using blocking F_SETLKW */
+lockRegionWait(int fd, int type, int whence, int start, int len)
+{
+    return lockReg(fd, F_SETLKW, type, whence, start, len);
+}
+
+/* Test if a file region is lockable. Return 0 if lockable, or
+   PID of process holding incompatible lock, or -1 on error. */
+
+pid_t
+regionIsLocked(int fd, int type, int whence, int start, int len)
+{
+    struct flock fl;
+
+    fl.l_type = type;
+    fl.l_whence = whence;
+    fl.l_start = start;
+    fl.l_len = len;
+
+    if (fcntl(fd, F_GETLK, &fl) == -1)
+        return -1;
+
+    return (fl.l_type == F_UNLCK) ? 0 : fl.l_pid;
+}
diff --git a/filelock/region_locking.h b/filelock/region_locking.h
new file mode 100644 (file)
index 0000000..e406b2e
--- /dev/null
@@ -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 <sys/types.h>
+
+int lockRegion(int fd, int type, int whence, int start, int len);
+
+int lockRegionWait(int fd, int type, int whence, int start, int len);
+
+pid_t regionIsLocked(int fd, int type, int whence, int start, int len);
+
+#endif
diff --git a/filelock/t_flock.c b/filelock/t_flock.c
new file mode 100644 (file)
index 0000000..ccbaf06
--- /dev/null
@@ -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 <sys/file.h>
+#include <fcntl.h>
+#include "curr_time.h"                  /* Declaration of currTime() */
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    int fd, lock;
+    const char *lname;
+
+    if (argc < 3 || strcmp(argv[1], "--help") == 0 ||
+            strchr("sx", argv[2][0]) == NULL)
+        usageErr("%s file lock [sleep-time]\n"
+                 "    'lock' is 's' (shared) or 'x' (exclusive)\n"
+                 "        optionally followed by 'n' (nonblocking)\n"
+                 "    'sleep-time' specifies time to hold lock\n", argv[0]);
+
+    lock = (argv[2][0] == 's') ? LOCK_SH : LOCK_EX;
+    if (argv[2][1] == 'n')
+        lock |= LOCK_NB;
+
+    fd = open(argv[1], O_RDONLY);               /* Open file to be locked */
+    if (fd == -1)
+        errExit("open");
+
+    lname = (lock & LOCK_SH) ? "LOCK_SH" : "LOCK_EX";
+
+    printf("PID %ld: requesting %s at %s\n", (long) getpid(), lname,
+            currTime("%T"));
+
+    if (flock(fd, lock) == -1) {
+        if (errno == EWOULDBLOCK)
+            fatal("PID %ld: already locked - bye!", (long) getpid());
+        else
+            errExit("flock (PID=%ld)", (long) getpid());
+    }
+
+    printf("PID %ld: granted    %s at %s\n", (long) getpid(), lname,
+            currTime("%T"));
+
+    sleep((argc > 3) ? getInt(argv[3], GN_NONNEG, "sleep-time") : 10);
+
+    printf("PID %ld: releasing  %s at %s\n", (long) getpid(), lname,
+            currTime("%T"));
+    if (flock(fd, LOCK_UN) == -1)
+        errExit("flock");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/files/Makefile b/files/Makefile
new file mode 100644 (file)
index 0000000..95e030c
--- /dev/null
@@ -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 (file)
index 0000000..cc772ce
--- /dev/null
@@ -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 <linux/fs.h> from
+   a kernel earlier than 2.6.19. You'll instead need to do something
+   like including <linux/ext2_fs.h> and replacing all of the FS_* names
+   below by EXT2_*.
+
+   This program is Linux-specific.
+*/
+#define _GNU_SOURCE
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <linux/fs.h>
+#include "tlpi_hdr.h"
+
+static void
+usageError(const char *progName)
+{
+    fprintf(stderr, "Usage: %s {+-=}{attrib-chars} file...\n\n", progName);
+#define fpe(str) fprintf(stderr, "    " str)            /* Shorter! */
+    fpe("+ add attribute; - remove attribute; "
+                        "= set attributes absolutely\n\n");
+    fpe("'attrib-chars' contains one or more of:\n");
+    fpe("    a   Force open() to include O_APPEND "
+                        "(privilege required)\n");
+    fpe("    A   Do not update last access time\n");
+    fpe("    c   Compress (requires e2compr package)\n");
+    fpe("    d   Do not backup with dump(8)\n");
+    fpe("    D   Synchronous directory updates\n");
+    fpe("    i   Immutable (privilege required)\n");
+    fpe("    j   Enable ext3/ext4 data journaling\n");
+    fpe("    s   Secure deletion (not implemented)\n");
+    fpe("    S   Synchronous file updates\n");
+    fpe("    t   Disable tail-packing (Reiserfs only)\n");
+    fpe("    T   Mark as top-level directory for Orlov algorithm\n");
+    fpe("    u   Undelete (not implemented)\n");
+    exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+    int attr, oldAttr, j, fd;
+    char *p;
+
+    if (argc < 3 || strchr("+-=", argv[1][0]) == NULL ||
+            strcmp(argv[1], "--help") == 0)
+        usageError(argv[0]);
+
+    /* Build bit mask based on attribute string in argv[1] */
+
+    attr = 0;
+    for (p = &argv[1][1]; *p != '\0'; p++) {
+        switch (*p) {
+        case 'a': attr |= FS_APPEND_FL;         break;
+        case 'A': attr |= FS_NOATIME_FL;        break;
+        case 'c': attr |= FS_COMPR_FL;          break;
+        case 'd': attr |= FS_NODUMP_FL;         break;
+        case 'D': attr |= FS_DIRSYNC_FL;        break;
+        case 'i': attr |= FS_IMMUTABLE_FL;      break;
+        case 'j': attr |= FS_JOURNAL_DATA_FL;   break;
+        case 's': attr |= FS_SECRM_FL;          break;
+        case 'S': attr |= FS_SYNC_FL;           break;
+        case 't': attr |= FS_NOTAIL_FL;         break;
+        case 'T': attr |= FS_TOPDIR_FL;         break;
+        case 'u': attr |= FS_UNRM_FL;           break;
+        default:  usageError(argv[0]);
+        }
+    }
+
+    /* Open each filename in turn, and change its attributes */
+
+    for (j = 2; j < argc; j++) {
+        fd = open(argv[j], O_RDONLY);
+        if (fd == -1) {         /* Most likely error is nonexistent file */
+            errMsg("open: %s", argv[j]);
+            continue;
+        }
+
+        /* If argv[1] starts with + or -, then retrieve existing
+           attribute bit mask and modify as required */
+
+        if (argv[1][0] == '+' || argv[1][0] == '-') {
+            if (ioctl(fd, FS_IOC_GETFLAGS, &oldAttr) == -1)
+                errExit("ioctl1: %s", argv[j]);
+            attr = (*argv[1] == '-') ? (oldAttr & ~attr) : (oldAttr | attr);
+        }
+
+        if (ioctl(fd, FS_IOC_SETFLAGS, &attr) == -1)
+            errExit("ioctl2: %s", argv[j]);
+        if (close(fd) == -1)
+            errExit("close");
+    }
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/files/file_perms.c b/files/file_perms.c
new file mode 100644 (file)
index 0000000..76e436a
--- /dev/null
@@ -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 <sys/stat.h>
+#include <stdio.h>
+#include "file_perms.h"                 /* Interface for this implementation */
+
+#define STR_SIZE sizeof("rwxrwxrwx")
+
+char *          /* Return ls(1)-style string for file permissions mask */
+filePermStr(mode_t perm, int flags)
+{
+    static char str[STR_SIZE];
+
+    /* If FP_SPECIAL was specified, we emulate the trickery of ls(1) in
+       returning set-user-ID, set-group-ID, and sticky bit information in
+       the user/group/other execute fields. This is made more complex by
+       the fact that the case of the character displayed for this bits
+       depends on whether the corresponding execute bit is on or off. */
+
+    snprintf(str, STR_SIZE, "%c%c%c%c%c%c%c%c%c",
+        (perm & S_IRUSR) ? 'r' : '-', (perm & S_IWUSR) ? 'w' : '-',
+        (perm & S_IXUSR) ?
+            (((perm & S_ISUID) && (flags & FP_SPECIAL)) ? 's' : 'x') :
+            (((perm & S_ISUID) && (flags & FP_SPECIAL)) ? 'S' : '-'),
+        (perm & S_IRGRP) ? 'r' : '-', (perm & S_IWGRP) ? 'w' : '-',
+        (perm & S_IXGRP) ?
+            (((perm & S_ISGID) && (flags & FP_SPECIAL)) ? 's' : 'x') :
+            (((perm & S_ISGID) && (flags & FP_SPECIAL)) ? 'S' : '-'),
+        (perm & S_IROTH) ? 'r' : '-', (perm & S_IWOTH) ? 'w' : '-',
+        (perm & S_IXOTH) ?
+            (((perm & S_ISVTX) && (flags & FP_SPECIAL)) ? 't' : 'x') :
+            (((perm & S_ISVTX) && (flags & FP_SPECIAL)) ? 'T' : '-'));
+
+    return str;
+}
diff --git a/files/file_perms.h b/files/file_perms.h
new file mode 100644 (file)
index 0000000..c0d8993
--- /dev/null
@@ -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 <sys/types.h>
+
+#define FP_SPECIAL 1            /* Include set-user-ID, set-group-ID, and sticky
+                                   bit information in returned string */
+
+char *filePermStr(mode_t perm, int flags);
+
+#endif
diff --git a/files/t_chown.c b/files/t_chown.c
new file mode 100644 (file)
index 0000000..31405a5
--- /dev/null
@@ -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 <pwd.h>
+#include <grp.h>
+#include "ugid_functions.h"             /* Declarations of userIdFromName()
+                                           and groupIdFromName() */
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    uid_t uid;
+    gid_t gid;
+    int j;
+    Boolean errFnd;
+
+    if (argc < 3 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s owner group [file...]\n"
+                "        owner or group can be '-', "
+                "meaning leave unchanged\n", argv[0]);
+
+    if (strcmp(argv[1], "-") == 0) {            /* "-" ==> don't change owner */
+        uid = -1;
+    } else {                                    /* Turn user name into UID */
+        uid = userIdFromName(argv[1]);
+        if (uid == -1)
+            fatal("No such user (%s)", argv[1]);
+    }
+
+    if (strcmp(argv[2], "-") == 0) {            /* "-" ==> don't change group */
+        gid = -1;
+    } else {                                    /* Turn group name into GID */
+        gid = groupIdFromName(argv[2]);
+        if (gid == -1)
+            fatal("No group user (%s)", argv[1]);
+    }
+
+    /* Change ownership of all files named in remaining arguments */
+
+    errFnd = FALSE;
+    for (j = 3; j < argc; j++) {
+        if (chown(argv[j], uid, gid) == -1) {
+            errMsg("chown: %s", argv[j]);
+            errFnd = TRUE;
+        }
+    }
+
+    exit(errFnd ? EXIT_FAILURE : EXIT_SUCCESS);
+}
diff --git a/files/t_stat.c b/files/t_stat.c
new file mode 100644 (file)
index 0000000..88b0348
--- /dev/null
@@ -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 <sys/sysmacros.h>
+#if defined(_AIX)
+#define _BSD
+#endif
+#if defined(__sgi) || defined(__sun)            /* Some systems need this */
+#include <sys/mkdev.h>                          /* To get major() and minor() */
+#endif
+#if defined(__hpux)                             /* Other systems need this */
+#include <sys/mknod.h>
+#endif
+#include <sys/stat.h>
+#include <time.h>
+#include "file_perms.h"
+#include "tlpi_hdr.h"
+
+static void
+displayStatInfo(const struct stat *sb)
+{
+    printf("File type:                ");
+
+    switch (sb->st_mode & S_IFMT) {
+    case S_IFREG:  printf("regular file\n");            break;
+    case S_IFDIR:  printf("directory\n");               break;
+    case S_IFCHR:  printf("character device\n");        break;
+    case S_IFBLK:  printf("block device\n");            break;
+    case S_IFLNK:  printf("symbolic (soft) link\n");    break;
+    case S_IFIFO:  printf("FIFO or pipe\n");            break;
+    case S_IFSOCK: printf("socket\n");                  break;
+    default:       printf("unknown file type?\n");      break;
+    }
+
+    printf("Device containing i-node: major=%ld   minor=%ld\n",
+                (long) major(sb->st_dev), (long) minor(sb->st_dev));
+
+    printf("I-node number:            %ld\n", (long) sb->st_ino);
+
+    printf("Mode:                     %lo (%s)\n",
+            (unsigned long) sb->st_mode, filePermStr(sb->st_mode, 0));
+
+    if (sb->st_mode & (S_ISUID | S_ISGID | S_ISVTX))
+        printf("    special bits set:     %s%s%s\n",
+                (sb->st_mode & S_ISUID) ? "set-UID " : "",
+                (sb->st_mode & S_ISGID) ? "set-GID " : "",
+                (sb->st_mode & S_ISVTX) ? "sticky " : "");
+
+    printf("Number of (hard) links:   %ld\n", (long) sb->st_nlink);
+
+    printf("Ownership:                UID=%ld   GID=%ld\n",
+            (long) sb->st_uid, (long) sb->st_gid);
+
+    if (S_ISCHR(sb->st_mode) || S_ISBLK(sb->st_mode))
+        printf("Device number (st_rdev):  major=%ld; minor=%ld\n",
+                (long) major(sb->st_rdev), (long) minor(sb->st_rdev));
+
+    printf("File size:                %lld bytes\n", (long long) sb->st_size);
+    printf("Optimal I/O block size:   %ld bytes\n", (long) sb->st_blksize);
+    printf("512B blocks allocated:    %lld\n", (long long) sb->st_blocks);
+
+    printf("Last file access:         %s", ctime(&sb->st_atime));
+    printf("Last file modification:   %s", ctime(&sb->st_mtime));
+    printf("Last status change:       %s", ctime(&sb->st_ctime));
+}
+
+int
+main(int argc, char *argv[])
+{
+    struct stat sb;
+    Boolean statLink;           /* True if "-l" specified (i.e., use lstat) */
+    int fname;                  /* Location of filename argument in argv[] */
+
+    statLink = (argc > 1) && strcmp(argv[1], "-l") == 0;
+                                /* Simple parsing for "-l" */
+    fname = statLink ? 2 : 1;
+
+    if (fname >= argc || (argc > 1 && strcmp(argv[1], "--help") == 0))
+        usageErr("%s [-l] file\n"
+                "        -l = use lstat() instead of stat()\n", argv[0]);
+
+    if (statLink) {
+        if (lstat(argv[fname], &sb) == -1)
+            errExit("lstat");
+    } else {
+        if (stat(argv[fname], &sb) == -1)
+            errExit("stat");
+    }
+
+    displayStatInfo(&sb);
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/files/t_umask.c b/files/t_umask.c
new file mode 100644 (file)
index 0000000..7e0642b
--- /dev/null
@@ -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 <sys/stat.h>
+#include <fcntl.h>
+#include "file_perms.h"
+#include "tlpi_hdr.h"
+
+#define MYFILE "myfile"
+#define MYDIR  "mydir"
+#define FILE_PERMS    (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)
+#define DIR_PERMS     (S_IRWXU | S_IRWXG | S_IRWXO)
+#define UMASK_SETTING (S_IWGRP | S_IXGRP | S_IWOTH | S_IXOTH)
+
+int
+main(int argc, char *argv[])
+{
+    int fd;
+    struct stat sb;
+    mode_t u;
+
+    umask(UMASK_SETTING);
+
+    fd = open(MYFILE, O_RDWR | O_CREAT | O_EXCL, FILE_PERMS);
+    if (fd == -1)
+        errExit("open-%s", MYFILE);
+    if (mkdir(MYDIR, DIR_PERMS) == -1)
+        errExit("mkdir-%s", MYDIR);
+
+    u = umask(0);               /* Retrieves (and clears) umask value */
+
+    if (stat(MYFILE, &sb) == -1)
+        errExit("stat-%s", MYFILE);
+    printf("Requested file perms: %s\n", filePermStr(FILE_PERMS, 0));
+    printf("Process umask:        %s\n", filePermStr(u, 0));
+    printf("Actual file perms:    %s\n\n", filePermStr(sb.st_mode, 0));
+
+    if (stat(MYDIR, &sb) == -1)
+        errExit("stat-%s", MYDIR);
+    printf("Requested dir. perms: %s\n", filePermStr(DIR_PERMS, 0));
+    printf("Process umask:        %s\n", filePermStr(u, 0));
+    printf("Actual dir. perms:    %s\n", filePermStr(sb.st_mode, 0));
+
+    if (unlink(MYFILE) == -1)
+        errMsg("unlink-%s", MYFILE);
+    if (rmdir(MYDIR) == -1)
+        errMsg("rmdir-%s", MYDIR);
+    exit(EXIT_SUCCESS);
+}
diff --git a/files/t_utime.c b/files/t_utime.c
new file mode 100644 (file)
index 0000000..07fd9a1
--- /dev/null
@@ -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 <sys/stat.h>
+#include <utime.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    char *pathname;
+    struct stat sb;
+    struct utimbuf utb;
+
+    if (argc != 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s file\n", argv[0]);
+
+    pathname = argv[1];
+
+    if (stat(pathname, &sb) == -1)    /* Retrieve current file times */
+        errExit("stat");
+
+    utb.actime = sb.st_atime;         /* Leave access time unchanged */
+    utb.modtime = sb.st_atime;        /* Make modify time same as access time */
+    if (utime(pathname, &utb) == -1)  /* Update file times */
+        errExit("utime");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/files/t_utimes.c b/files/t_utimes.c
new file mode 100644 (file)
index 0000000..7c57769
--- /dev/null
@@ -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 <sys/stat.h>
+#include <sys/time.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    struct stat sb;
+    struct timeval tv[2];
+
+    if (argc != 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s file\n", argv[0]);
+
+    if (stat(argv[1], &sb) == -1)       /* Retrieve current file times */
+        errExit("stat");
+
+    tv[0].tv_sec = sb.st_atime;         /* Leave atime seconds unchanged */
+    tv[0].tv_usec = 223344;             /* Change microseconds for atime */
+    tv[1].tv_sec = sb.st_atime;         /* mtime seconds == atime seconds */
+    tv[1].tv_usec = 667788;             /* mtime microseconds */
+
+    if (utimes(argv[1], tv) == -1)
+        errExit("utimes");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/filesys/Makefile b/filesys/Makefile
new file mode 100644 (file)
index 0000000..153bd57
--- /dev/null
@@ -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 (file)
index 0000000..de0dbbe
--- /dev/null
@@ -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 <sys/mount.h>
+#include "tlpi_hdr.h"
+
+#ifndef MS_DIRSYNC      /* May not be defined in older glibc headers */
+#define MS_DIRSYNC 128
+#endif
+
+#ifndef MS_BIND         /* May not be defined in older glibc headers */
+#define MS_BIND 4096
+#endif
+
+#ifndef MS_MOVE         /* May not be defined in older glibc headers */
+#define MS_MOVE 8192
+#endif
+
+#ifndef MS_REC          /* May not be defined in older glibc headers */
+#define MS_REC 16384
+#endif
+
+#ifndef MS_UNBINDABLE   /* May not be defined in older glibc headers */
+#define MS_UNBINDABLE (1<<17) /* change to unbindable */
+#endif
+
+#ifndef MS_PRIVATE      /* May not be defined in older glibc headers */
+#define MS_PRIVATE (1<<18) /* change to private */
+#endif
+
+#ifndef MS_SLAVE        /* May not be defined in older glibc headers */
+#define MS_SLAVE (1<<19) /* change to slave */
+#endif
+
+#ifndef MS_SHARED       /* May not be defined in older glibc headers */
+#define MS_SHARED (1<<20) /* change to shared */
+#endif
+
+#ifndef MS_LAZYTIME
+#define MS_LAZYTIME     (1<<25)
+#endif
+
+static void
+usageError(const char *progName, const char *msg)
+{
+    if (msg != NULL)
+        fprintf(stderr, "%s", msg);
+
+    fprintf(stderr, "Usage: %s [options] source target\n\n", progName);
+    fprintf(stderr, "Available options:\n");
+#define fpe(str) fprintf(stderr, "    " str)    /* Shorter! */
+    fpe("-t fstype        [e.g., 'ext2' or 'reiserfs']\n");
+    fpe("-o data          [file system-dependent options,\n");
+    fpe("                 e.g., 'bsdgroups' for ext2]\n");
+    fpe("-f mountflags    can include any of:\n");
+#define fpe2(str) fprintf(stderr, "            " str)
+    fpe2("b - MS_BIND         create a bind mount\n");
+    fpe2("d - MS_DIRSYNC      synchronous directory updates\n");
+    fpe2("l - MS_MANDLOCK     permit mandatory locking\n");
+    fpe2("L - MS_LAZYATIME    lazy atime updates\n");
+    fpe2("m - MS_MOVE         atomically move subtree\n");
+    fpe2("A - MS_NOATIME      don't update atime (last access time)\n");
+    fpe2("V - MS_NODEV        don't permit device access\n");
+    fpe2("D - MS_NODIRATIME   don't update atime on directories\n");
+    fpe2("E - MS_NOEXEC       don't allow executables\n");
+    fpe2("S - MS_NOSUID       disable set-user/group-ID programs\n");
+    fpe2("p - MS_PRIVATE      mark as private\n");
+    fpe2("r - MS_RDONLY       read-only mount\n");
+    fpe2("c - MS_REC          recursive mount\n");
+    fpe2("T - MS_RELATIME     relative atime updates\n");
+    fpe2("R - MS_REMOUNT      remount\n");
+    fpe2("h - MS_SHARED       mark as shared\n");
+    fpe2("v - MS_SLAVE        mark as slave\n");
+    fpe2("s - MS_SYNCHRONOUS  make writes synchronous\n");
+    fpe2("u - MS_UNBINDABLE   mark as unbindable\n");
+    exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+    unsigned long flags;
+    char *data, *fstype;
+    int j, opt;
+
+    flags = 0;
+    data = NULL;
+    fstype = NULL;
+
+    while ((opt = getopt(argc, argv, "o:t:f:")) != -1) {
+        switch (opt) {
+        case 'o':
+            data = optarg;
+            break;
+
+        case 't':
+            fstype = optarg;
+            break;
+
+        case 'f':
+            for (j = 0; j < strlen(optarg); j++) {
+
+                /* In this version of the program we support more flags than
+                   in the version of the program shown in the book */
+
+                switch (optarg[j]) {
+                case 'b': flags |= MS_BIND;             break;
+                case 'd': flags |= MS_DIRSYNC;          break;
+                case 'l': flags |= MS_MANDLOCK;         break;
+                case 'm': flags |= MS_MOVE;             break;
+                case 'A': flags |= MS_NOATIME;          break;
+                case 'V': flags |= MS_NODEV;            break;
+                case 'D': flags |= MS_NODIRATIME;       break;
+                case 'E': flags |= MS_NOEXEC;           break;
+                case 'S': flags |= MS_NOSUID;           break;
+                case 'p': flags |= MS_PRIVATE;          break;
+                case 'r': flags |= MS_RDONLY;           break;
+                case 'c': flags |= MS_REC;              break;
+                case 'T': flags |= MS_RELATIME;         break;
+                case 'R': flags |= MS_REMOUNT;          break;
+                case 'h': flags |= MS_SHARED;           break;
+                case 'v': flags |= MS_SLAVE;            break;
+                case 's': flags |= MS_SYNCHRONOUS;      break;
+                case 'u': flags |= MS_UNBINDABLE;       break;
+                default:  usageError(argv[0], NULL);
+                }
+            }
+            break;
+
+        default:
+            usageError(argv[0], NULL);
+        }
+    }
+
+    if (argc != optind + 2)
+        usageError(argv[0], "Wrong number of arguments\n");
+
+    if (mount(argv[optind], argv[optind + 1], fstype, flags, data) == -1)
+        errExit("mount");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/filesys/t_statfs.c b/filesys/t_statfs.c
new file mode 100644 (file)
index 0000000..4d547b2
--- /dev/null
@@ -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 <sys/statfs.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    struct statfs sfs;
+
+    if (argc != 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s path\n", argv[0]);
+
+    if (statfs(argv[1], &sfs) == -1)
+        errExit("statfs");
+
+    printf("File system type:              %#lx\n",
+            (unsigned long) sfs.f_type);
+    printf("Optimal I/O block size:        %lu\n",
+            (unsigned long) sfs.f_bsize);
+    printf("Total data blocks:             %lu\n",
+            (unsigned long) sfs.f_blocks);
+    printf("Free data blocks:              %lu\n",
+            (unsigned long) sfs.f_bfree);
+    printf("Free blocks for nonsuperuser:  %lu\n",
+            (unsigned long) sfs.f_bavail);
+    printf("Total i-nodes:                 %lu\n",
+            (unsigned long) sfs.f_files);
+    printf("File system ID:                %#x, %#x\n",
+            (unsigned) sfs.f_fsid.__val[0], (unsigned) sfs.f_fsid.__val[1]);
+    printf("Free i-nodes:                  %lu\n",
+            (unsigned long) sfs.f_ffree);
+    printf("Maximum file name length:      %lu\n",
+            (unsigned long) sfs.f_namelen);
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/filesys/t_statvfs.c b/filesys/t_statvfs.c
new file mode 100644 (file)
index 0000000..413e1a2
--- /dev/null
@@ -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 <sys/statvfs.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    struct statvfs sb;
+
+    if (argc != 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s path\n", argv[0]);
+
+    if (statvfs(argv[1], &sb) == -1)
+        errExit("statvfs");
+
+    printf("Block size                       %lu\n", sb.f_bsize);
+    printf("Fundamental block size           %lu\n", sb.f_frsize);
+    printf("Total blocks (in above units)    %lu\n",
+            (unsigned long) sb.f_blocks);
+    printf("Free blocks for priv. proc.      %lu\n",
+            (unsigned long) sb.f_bfree);
+    printf("Free blocks for unpriv. proc.    %lu\n",
+            (unsigned long) sb.f_bavail);
+    printf("Total number of i-nodes          %lu\n",
+            (unsigned long) sb.f_files);
+    printf("Free i-nodes for priv. proc.     %lu\n",
+            (unsigned long) sb.f_ffree);
+    printf("Free i-nodes for nonpriv. proc.  %lu\n",
+            (unsigned long) sb.f_favail);
+    printf("File system ID                   %#lx\n", sb.f_fsid);
+    printf("Flags                            %#lx\n", sb.f_flag);
+    printf("Maximum filename length          %lu\n", sb.f_namemax);
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/filesys/t_umount.c b/filesys/t_umount.c
new file mode 100644 (file)
index 0000000..2437378
--- /dev/null
@@ -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 <sys/mount.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    if (argc != 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s mount-point\n", argv[0]);
+
+    if (umount(argv[1]) == -1)
+        errExit("umount");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/getopt/Makefile b/getopt/Makefile
new file mode 100644 (file)
index 0000000..8017c22
--- /dev/null
@@ -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 (file)
index 0000000..e38b885
--- /dev/null
@@ -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 <ctype.h>
+#include "tlpi_hdr.h"
+
+#define printable(ch) (isprint((unsigned char) ch) ? ch : '#')
+
+#ifdef __GNUC__
+__attribute__((noreturn))       /* Prevent "this statement may fall through"
+                                   warnings from "gcc -Wimplicit-fallthrough"
+                                   in switch() statement in main(). */
+#endif
+static void             /* Print "usage" message and exit */
+usageError(char *progName, char *msg, int opt)
+{
+    if (msg != NULL && opt != 0)
+        fprintf(stderr, "%s (-%c)\n", msg, printable(opt));
+    fprintf(stderr, "Usage: %s [-p arg] [-x]\n", progName);
+    exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+    int opt, xfnd;
+    char *pstr;
+
+    xfnd = 0;
+    pstr = NULL;
+
+    while ((opt = getopt(argc, argv, ":p:x")) != -1) {
+        printf("opt =%3d (%c); optind = %d", opt, printable(opt), optind);
+        if (opt == '?' || opt == ':')
+            printf("; optopt =%3d (%c)", optopt, printable(optopt));
+        printf("\n");
+
+        switch (opt) {
+        case 'p': pstr = optarg;        break;
+        case 'x': xfnd++;               break;
+        case ':': usageError(argv[0], "Missing argument", optopt);
+        case '?': usageError(argv[0], "Unrecognized option", optopt);
+        default:  fatal("Unexpected case in switch()");
+        }
+    }
+
+    if (xfnd != 0)
+        printf("-x was specified (count=%d)\n", xfnd);
+    if (pstr != NULL)
+        printf("-p was specified with the value \"%s\"\n", pstr);
+    if (optind < argc)
+        printf("First nonoption argument is \"%s\" at argv[%d]\n",
+                argv[optind], optind);
+    exit(EXIT_SUCCESS);
+}
diff --git a/inotify/Makefile b/inotify/Makefile
new file mode 100644 (file)
index 0000000..ba92cd0
--- /dev/null
@@ -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 (file)
index 0000000..86f7931
--- /dev/null
@@ -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 <sys/inotify.h>
+#include <limits.h>
+#include "tlpi_hdr.h"
+
+static void             /* Display information from inotify_event structure */
+displayInotifyEvent(struct inotify_event *i)
+{
+    printf("    wd =%2d; ", i->wd);
+    if (i->cookie > 0)
+        printf("cookie =%4d; ", i->cookie);
+
+    printf("mask = ");
+    if (i->mask & IN_ACCESS)        printf("IN_ACCESS ");
+    if (i->mask & IN_ATTRIB)        printf("IN_ATTRIB ");
+    if (i->mask & IN_CLOSE_NOWRITE) printf("IN_CLOSE_NOWRITE ");
+    if (i->mask & IN_CLOSE_WRITE)   printf("IN_CLOSE_WRITE ");
+    if (i->mask & IN_CREATE)        printf("IN_CREATE ");
+    if (i->mask & IN_DELETE)        printf("IN_DELETE ");
+    if (i->mask & IN_DELETE_SELF)   printf("IN_DELETE_SELF ");
+    if (i->mask & IN_IGNORED)       printf("IN_IGNORED ");
+    if (i->mask & IN_ISDIR)         printf("IN_ISDIR ");
+    if (i->mask & IN_MODIFY)        printf("IN_MODIFY ");
+    if (i->mask & IN_MOVE_SELF)     printf("IN_MOVE_SELF ");
+    if (i->mask & IN_MOVED_FROM)    printf("IN_MOVED_FROM ");
+    if (i->mask & IN_MOVED_TO)      printf("IN_MOVED_TO ");
+    if (i->mask & IN_OPEN)          printf("IN_OPEN ");
+    if (i->mask & IN_Q_OVERFLOW)    printf("IN_Q_OVERFLOW ");
+    if (i->mask & IN_UNMOUNT)       printf("IN_UNMOUNT ");
+    printf("\n");
+
+    if (i->len > 0)
+        printf("        name = %s\n", i->name);
+}
+
+#define BUF_LEN (10 * (sizeof(struct inotify_event) + NAME_MAX + 1))
+
+int
+main(int argc, char *argv[])
+{
+    int inotifyFd, wd, j;
+    char buf[BUF_LEN] __attribute__ ((aligned(8)));
+    ssize_t numRead;
+    char *p;
+    struct inotify_event *event;
+
+    if (argc < 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s pathname...\n", argv[0]);
+
+    inotifyFd = inotify_init();                 /* Create inotify instance */
+    if (inotifyFd == -1)
+        errExit("inotify_init");
+
+    /* For each command-line argument, add a watch for all events */
+
+    for (j = 1; j < argc; j++) {
+        wd = inotify_add_watch(inotifyFd, argv[j], IN_ALL_EVENTS);
+        if (wd == -1)
+            errExit("inotify_add_watch");
+
+        printf("Watching %s using wd %d\n", argv[j], wd);
+    }
+
+    for (;;) {                                  /* Read events forever */
+        numRead = read(inotifyFd, buf, BUF_LEN);
+        if (numRead == 0)
+            fatal("read() from inotify fd returned 0!");
+
+        if (numRead == -1)
+            errExit("read");
+
+        printf("Read %ld bytes from inotify fd\n", (long) numRead);
+
+        /* Process all of the events in buffer returned by read() */
+
+        for (p = buf; p < buf + numRead; ) {
+            event = (struct inotify_event *) p;
+            displayInotifyEvent(event);
+
+            p += sizeof(struct inotify_event) + event->len;
+        }
+    }
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/inotify/dnotify.c b/inotify/dnotify.c
new file mode 100644 (file)
index 0000000..7bc9c13
--- /dev/null
@@ -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 <fcntl.h> */
+#include <fcntl.h>
+#include <signal.h>
+#include "tlpi_hdr.h"
+
+static void             /* Print (optional) message and usage info, then exit */
+usageError(const char *progName, const char *msg)
+{
+    if (msg != NULL)
+        fprintf(stderr, "%s", msg);
+
+    fprintf(stderr, "Usage: %s directory:[events]...\n", progName);
+    fprintf(stderr, "    Events are:\n"
+        "        a - access; A - attrib; c - create; d - delete\n"
+        "        m - modify; r - rename; M - multishot\n"
+        "        (default is all events)\n");
+    exit(EXIT_FAILURE);
+}
+
+static void
+handler(int sig, siginfo_t *si, void *ucontext)
+{
+    printf("Got event on descriptor %d\n", si->si_fd);
+                        /* UNSAFE (see Section 21.1.2) */
+}
+
+int
+main(int argc, char *argv[])
+{
+    struct sigaction sa;
+    int fd, events, fnum;
+    const int NOTIFY_SIG = SIGRTMIN;
+    char *p;
+
+    if (argc < 2 || strcmp(argv[1], "--help") == 0)
+        usageError(argv[0], NULL);
+
+    /* Establish handler for notification signal */
+
+    sa.sa_sigaction = handler;
+    sigemptyset(&sa.sa_mask);
+    sa.sa_flags = SA_SIGINFO;           /* So handler gets siginfo_t arg. */
+    if (sigaction(NOTIFY_SIG, &sa, NULL) == -1)
+        errExit("sigaction");
+
+    for (fnum = 1; fnum < argc; fnum++) {
+        p = strchr(argv[fnum], ':');    /* Look for optional ':' */
+
+        if (p == NULL) {                /* Default is all events + multishot */
+            events = DN_ACCESS | DN_ATTRIB | DN_CREATE | DN_DELETE |
+                     DN_MODIFY | DN_RENAME | DN_MULTISHOT;
+        } else {                        /* ':' present, parse event chars */
+            *p = '\0';                  /* Terminates directory component */
+            events = 0;
+            for (p++; *p != '\0'; p++) {
+                switch (*p) {
+                case 'a': events |= DN_ACCESS;          break;
+                case 'A': events |= DN_ATTRIB;          break;
+                case 'c': events |= DN_CREATE;          break;
+                case 'd': events |= DN_DELETE;          break;
+                case 'm': events |= DN_MODIFY;          break;
+                case 'r': events |= DN_RENAME;          break;
+                case 'M': events |= DN_MULTISHOT;       break;
+                default:  usageError(argv[0], "Bad event character\n");
+                }
+            }
+        }
+
+        /* Obtain a file descriptor for the directory to be monitored */
+
+        fd = open(argv[fnum], O_RDONLY);
+        if (fd == -1)
+            errExit("open");
+        printf("opened '%s' as file descriptor %d\n", argv[fnum], fd);
+
+        /* Use alternate signal instead of SIGIO for dnotify events */
+
+        if (fcntl(fd, F_SETSIG, NOTIFY_SIG) == -1)
+            errExit("fcntl - F_SETSIG");
+
+        /* Enable directory change notifications */
+
+        if (fcntl(fd, F_NOTIFY, events) == -1)
+            errExit("fcntl-F_NOTIFY");
+        printf("events: %o\n", (unsigned int) events);
+    }
+
+    for (;;)
+        pause();                        /* Wait for events */
+}
diff --git a/inotify/inotify_dtree.c b/inotify/inotify_dtree.c
new file mode 100644 (file)
index 0000000..552167a
--- /dev/null
@@ -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 <sys/select.h>
+#include <sys/stat.h>
+#include <limits.h>
+#include <sys/select.h>
+#include <sys/inotify.h>
+#include <fcntl.h>
+#include <ftw.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
+                        } while (0)
+
+/* logMessage() flags */
+
+#define VB_BASIC 1      /* Basic messages */
+#define VB_NOISY 2      /* Verbose messages */
+
+static int verboseMask;
+static int checkCache;
+static int dumpCache;
+static int readBufferSize = 0;
+static char *stopFile;
+static int abortOnCacheProblem;
+
+static FILE *logfp = NULL;
+
+static int inotifyReadCnt = 0;          /* Counts number of read()s from
+                                           inotify file descriptor */
+
+static const int INOTIFY_READ_BUF_LEN =
+        (100 * (sizeof(struct inotify_event) + NAME_MAX + 1));
+
+static void dumpCacheToLog(void);
+
+/* Something went badly wrong. Create a 'stop' file to signal the
+   'rand_dtree' processes to stop, dump a copy of the cache to the
+   log file, and abort. */
+
+__attribute__ ((__noreturn__))
+static void
+createStopFileAndAbort(void)
+{
+    open(stopFile, O_CREAT | O_RDWR, 0600);
+    dumpCacheToLog();
+    abort();
+}
+
+/* Write a log message. The message is sent to none, either, or both of
+   stderr and the log file, depending on 'vb_mask' and whether a log file
+   has been specified via command-line options . */
+
+static void
+logMessage(int vb_mask, const char *format, ...)
+{
+    va_list argList;
+
+    /* Write message to stderr if 'vb_mask' is zero, or matches one
+       of the bits in 'verboseMask' */
+
+    if ((vb_mask == 0) || (vb_mask & verboseMask)) {
+        va_start(argList, format);
+        vfprintf(stderr, format, argList);
+        va_end(argList);
+    }
+
+    /* If we have a log file, write the message there */
+
+    if (logfp != NULL) {
+        va_start(argList, format);
+        vfprintf(logfp, format, argList);
+        va_end(argList);
+    }
+}
+
+/***********************************************************************/
+
+/* Display some information about an inotify event. (Used when
+   when we are doing verbose logging.) */
+
+static void
+displayInotifyEvent(struct inotify_event *ev)
+{
+    logMessage(VB_NOISY, "==> wd = %d; ", ev->wd);
+    if (ev->cookie > 0)
+        logMessage(VB_NOISY, "cookie = %4d; ", ev->cookie);
+
+    logMessage(VB_NOISY, "mask = ");
+
+    if (ev->mask & IN_ISDIR)
+        logMessage(VB_NOISY, "IN_ISDIR ");
+
+    if (ev->mask & IN_CREATE)
+        logMessage(VB_NOISY, "IN_CREATE ");
+
+    if (ev->mask & IN_DELETE_SELF)
+        logMessage(VB_NOISY, "IN_DELETE_SELF ");
+
+    if (ev->mask & IN_MOVE_SELF)
+        logMessage(VB_NOISY, "IN_MOVE_SELF ");
+    if (ev->mask & IN_MOVED_FROM)
+        logMessage(VB_NOISY, "IN_MOVED_FROM ");
+    if (ev->mask & IN_MOVED_TO)
+        logMessage(VB_NOISY, "IN_MOVED_TO ");
+
+    if (ev->mask & IN_IGNORED)
+        logMessage(VB_NOISY, "IN_IGNORED ");
+    if (ev->mask & IN_Q_OVERFLOW)
+        logMessage(VB_NOISY, "IN_Q_OVERFLOW ");
+    if (ev->mask & IN_UNMOUNT)
+        logMessage(VB_NOISY, "IN_UNMOUNT ");
+
+    logMessage(VB_NOISY, "\n");
+
+    if (ev->len > 0)
+        logMessage(VB_NOISY, "        name = %s\n", ev->name);
+}
+
+/***********************************************************************/
+
+/* Data structures and functions for the watch list cache */
+
+/* We use a very simple data structure for caching watched directory
+   paths: a dynamically sized array that is searched linearly. Not
+   efficient, but our main goal is to demonstrate the use of inotify. */
+
+struct watch {
+    int wd;                     /* Watch descriptor (-1 if slot unused) */
+    char path[PATH_MAX];        /* Cached pathname */
+};
+
+struct watch *wlCache = NULL;   /* Array of cached items */
+
+static int cacheSize = 0;       /* Current size of the array */
+
+/* Deallocate the watch cache */
+
+static void
+freeCache(void)
+{
+    free(wlCache);
+    cacheSize = 0;
+    wlCache = NULL;
+}
+
+/* Check that all pathnames in the cache are valid, and refer
+   to directories */
+
+static void
+checkCacheConsistency(void)
+{
+    int failures, j;
+    struct stat sb;
+
+    failures = 0;
+    for (j = 0; j < cacheSize; j++) {
+        if (wlCache[j].wd >= 0) {
+            if (lstat(wlCache[j].path, &sb) == -1) {
+                logMessage(0,
+                        "checkCacheConsistency: stat: "
+                        "[slot = %d; wd = %d] %s: %s\n",
+                        j, wlCache[j].wd, wlCache[j].path, strerror(errno));
+                failures++;
+        } else if (!S_ISDIR(sb.st_mode)) {
+            logMessage(0, "checkCacheConsistency: %s is not a directory\n",
+                                wlCache[j].path);
+                    exit(EXIT_FAILURE);
+            }
+        }
+    }
+
+    if (failures > 0)
+        logMessage(VB_NOISY, "checkCacheConsistency: %d failures\n",
+                   failures);
+}
+
+/* Check whether the cache contains the watch descriptor 'wd'.
+   If found, return the slot number, otherwise return -1. */
+
+static int
+findWatch(int wd)
+{
+    int j;
+
+    for (j = 0; j < cacheSize; j++)
+        if (wlCache[j].wd == wd)
+            return j;
+
+    return -1;
+}
+
+/* Find and return the cache slot for the watch descriptor 'wd'.
+   The caller expects this watch descriptor to exist.  If it does not,
+   there is a problem, which is signaled by the -1 return. */
+
+static int
+findWatchChecked(int wd)
+{
+    int slot;
+
+    slot = findWatch(wd);
+
+    if (slot >= 0)
+        return slot;
+
+    logMessage(0, "Could not find watch %d\n", wd);
+
+    /* With multiple renamers there are still rare cases where
+       the cache is missing entries after a 'Could not find watch'
+       event. It looks as though this is because of races with nftw(),
+       since the cache is (occasionally) re-created with fewer
+       entries than there are objects in the tree(s). Returning
+       -1 to our caller identifies that there's a problem, and the
+       caller should probably trigger a cache rebuild. */
+
+    if (abortOnCacheProblem) {
+        createStopFileAndAbort();
+    } else {
+        return -1;
+    }
+}
+
+/* Mark a cache entry as unused */
+
+static void
+markCacheSlotEmpty(int slot)
+{
+    logMessage(VB_NOISY,
+            "        markCacheSlotEmpty: slot = %d;  wd = %d; path = %s\n",
+            slot, wlCache[slot].wd, wlCache[slot].path);
+
+    wlCache[slot].wd = -1;
+    wlCache[slot].path[0] = '\0';
+}
+
+/* Find a free slot in the cache */
+
+static int
+findEmptyCacheSlot(void)
+{
+    int j;
+    const int ALLOC_INCR = 200;
+
+    for (j = 0; j < cacheSize; j++)
+        if (wlCache[j].wd == -1)
+            return j;
+
+    /* No free slot found; resize cache */
+
+    cacheSize += ALLOC_INCR;
+
+    wlCache = realloc(wlCache, cacheSize * sizeof(struct watch));
+    if (wlCache == NULL)
+        errExit("realloc");
+
+    for (j = cacheSize - ALLOC_INCR; j < cacheSize; j++)
+        markCacheSlotEmpty(j);
+
+    return cacheSize - ALLOC_INCR;      /* Return first slot in
+                                           newly allocated space */
+}
+
+/* Add an item to the cache */
+
+static int
+addWatchToCache(int wd, const char *pathname)
+{
+    int slot;
+
+    slot = findEmptyCacheSlot();
+
+    wlCache[slot].wd = wd;
+    strncpy(wlCache[slot].path, pathname, PATH_MAX);
+
+    return slot;
+}
+
+/* Return the cache slot that corresponds to a particular pathname,
+   or -1 if the pathname is not in the cache */
+
+static int
+pathnameToCacheSlot(const char *pathname)
+{
+    int j;
+
+    for (j = 0; j < cacheSize; j++)
+        if (wlCache[j].wd >= 0 && strcmp(wlCache[j].path, pathname) == 0)
+            return j;
+
+    return -1;
+}
+
+/* Is 'pathname' in the watch cache? */
+
+static int
+pathnameInCache(const char *pathname)
+{
+    return pathnameToCacheSlot(pathname) >= 0;
+}
+
+/* Dump contents of watch cache to the log file */
+
+static void
+dumpCacheToLog(void)
+{
+    int cnt, j;
+
+    cnt = 0;
+
+    for (j = 0; j < cacheSize; j++) {
+        if (wlCache[j].wd >= 0) {
+            fprintf(logfp, "%d: wd = %d; %s\n", j,
+                    wlCache[j].wd, wlCache[j].path);
+            cnt++;
+        }
+    }
+
+    fprintf(logfp, "Total entries: %d\n", cnt);
+}
+
+/***********************************************************************/
+
+/* Data structures and functions for dealing with the directory pathnames
+   provided as command-line arguments. These directories form the roots of
+   the trees that we will monitor */
+
+static char **rootDirPaths; /* List of pathnames supplied on command line */
+static int numRootDirs;     /* Number of pathnames supplied on command line */
+static int ignoreRootDirs;  /* Number of command-line pathnames that
+                               we've ceased to monitor */
+static struct stat *rootDirStat;
+                            /* stat(2) structures for croot directories */
+
+/* Duplicate the pathnames supplied on the command line, perform
+   some sanity checking along the way */
+
+static void
+copyRootDirPaths(char *argv[])
+{
+    char **p;
+    int j, k;
+    struct stat sb;
+
+    p = argv;
+    numRootDirs = 0;
+
+    /* Count the number of root paths, and check that the paths are valid */
+
+    for (p = argv; *p != NULL; p++) {
+
+        /* Check that command-line arguments are directories */
+
+        if (lstat(*p, &sb) == -1) {
+            fprintf(stderr, "lstat() failed on '%s'\n", *p);
+            exit(EXIT_FAILURE);
+        }
+
+        if (! S_ISDIR(sb.st_mode)) {
+            fprintf(stderr, "'%s' is not a directory\n", *p);
+            exit(EXIT_FAILURE);
+        }
+
+        numRootDirs++;
+    }
+
+    /* Create a copy of the root directory pathnames */
+
+    rootDirPaths = calloc(numRootDirs, sizeof(char *));
+    if (rootDirPaths == NULL)
+        errExit("calloc");
+
+    rootDirStat = calloc(numRootDirs, sizeof(struct stat));
+    if (rootDirPaths == NULL)
+        errExit("calloc");
+
+    for (j = 0; j < numRootDirs; j++) {
+        rootDirPaths[j] = strdup(argv[j]);
+        if (rootDirPaths[j] == NULL)
+            errExit("strdup");
+
+        /* If the same filesystem object appears more than once in the
+           command line, this will cause confusion if we later try to zap
+           an object from the set of root paths. So, reject such
+           duplicates now. Note that we can't just do simple string
+           comparisons of the arguments, since different pathname strings
+           may refer to the same filesystem object (e.g., "mydir" and
+           "./mydir"). So, we use stat() to compare i-node numbers and
+           containing device IDs. */
+
+        if (lstat(argv[j], &rootDirStat[j]) == -1)
+            errExit("lstat");
+
+        for (k = 0; k < j; k++) {
+            if ((rootDirStat[j].st_ino == rootDirStat[k].st_ino) &&
+                (rootDirStat[j].st_dev == rootDirStat[k].st_dev)) {
+
+                fprintf(stderr, "Duplicate filesystem objects: %s, %s\n",
+                        argv[j], argv[k]);
+                exit(EXIT_FAILURE);
+            }
+        }
+    }
+
+    ignoreRootDirs = 0;
+}
+
+/* Return the address of the element in 'rootDirPaths' that points
+   to a string matching 'path', or NULL if there is no match */
+
+static char **
+findRootDirPath(const char *path)
+{
+    int j;
+
+    for (j = 0; j < numRootDirs; j++)
+        if (rootDirPaths[j] != NULL && strcmp(path, rootDirPaths[j]) == 0)
+            return &rootDirPaths[j];
+
+    return NULL;
+}
+
+/* Is 'path' one of the pathnames that was listed on the command line? */
+
+static int
+isRootDirPath(const char *path)
+{
+    return findRootDirPath(path) != NULL;
+}
+
+/* We've ceased to monitor a root directory pathname (probably because it
+   was renamed), so zap this pathname from the root path list */
+
+static void
+zapRootDirPath(const char *path)
+{
+    char **p;
+
+    printf("zapRootDirPath: %s\n", path);
+
+    p = findRootDirPath(path);
+    if (p == NULL) {
+        fprintf(stderr, "zapRootDirPath(): path not found!\n");
+        exit(EXIT_FAILURE);
+    }
+
+    *p = NULL;
+    ignoreRootDirs++;
+    if (ignoreRootDirs == numRootDirs) {
+        fprintf(stderr, "No more root paths left to monitor; bye!\n");
+        exit(EXIT_SUCCESS);
+    }
+}
+
+/***********************************************************************/
+
+/* Below is a function called by nftw() to traverse a directory tree.
+   The function adds a watch for each directory in the tree. Each
+   successful call to this function should return 0 to indicate to
+   nftw() that the tree traversal should continue. */
+
+/* The usual hack for nftw()...  We can't pass arguments to the
+   function invoked by nftw(), so we use these global variables to
+   exchange information with the function. */
+
+static int dirCnt;      /* Count of directories added to watch list */
+static int ifd;         /* Inotify file descriptor */
+
+static int
+traverseTree(const char *pathname, const struct stat *sb, int tflag,
+             struct FTW *ftwbuf)
+{
+    int wd, slot, flags;
+
+    if (! S_ISDIR(sb->st_mode))
+        return 0;               /* Ignore nondirectory files */
+
+    /* Create a watch for this directory */
+
+    flags = IN_CREATE | IN_MOVED_FROM | IN_MOVED_TO | IN_DELETE_SELF;
+
+    if (isRootDirPath(pathname))
+        flags |= IN_MOVE_SELF;
+
+    wd = inotify_add_watch(ifd, pathname, flags | IN_ONLYDIR);
+    if (wd == -1) {
+
+        /* By the time we come to create a watch, the directory might
+           already have been deleted or renamed, in which case we'll get
+           an ENOENT error. In that case, we log the error, but
+           carry on execution. Other errors are unexpected, and if we
+           hit them, we give up. */
+
+        logMessage(VB_BASIC, "inotify_add_watch: %s: %s\n",
+                pathname, strerror(errno));
+        if (errno == ENOENT)
+            return 0;
+        else
+            exit(EXIT_FAILURE);
+    }
+
+    if (findWatch(wd) >= 0) {
+
+        /* This watch descriptor is already in the cache;
+           nothing more to do. */
+
+        logMessage(VB_BASIC, "WD %d already in cache (%s)\n", wd, pathname);
+        return 0;
+    }
+
+    dirCnt++;
+
+    /* Cache information about the watch */
+
+    slot = addWatchToCache(wd, pathname);
+
+    /* Print the name of the current directory */
+
+    logMessage(VB_NOISY, "    watchDir: wd = %d [cache slot: %d]; %s\n",
+                wd, slot, pathname);
+
+    return 0;
+}
+
+/* Add the directory in 'pathname' to the watch list of the inotify
+   file descriptor 'inotifyFd'. The process is recursive: watch items
+   are also created for all of the subdirectories of 'pathname'.
+   Returns number of watches/cache entries added for this subtree. */
+
+static int
+watchDir(int inotifyFd, const char *pathname)
+{
+    dirCnt = 0;
+    ifd = inotifyFd;
+
+    /* Use FTW_PHYS to avoid following soft links to directories (which
+       could lead us in circles) */
+
+    /* By the time we come to process 'pathname', it may already have
+       been deleted, so we log errors from nftw(), but keep on going */
+
+    if (nftw(pathname, traverseTree, 20, FTW_PHYS) == -1)
+        logMessage(VB_BASIC,
+                "nftw: %s: %s (directory probably deleted before we "
+                "could watch)\n", pathname, strerror(errno));
+
+    return dirCnt;
+}
+
+/* Add watches and cache entries for a subtree, logging a message
+   noting the number entries added. */
+
+static void
+watchSubtree(int inotifyFd, char *path)
+{
+    int cnt;
+
+    cnt = watchDir(inotifyFd, path);
+
+    logMessage(VB_BASIC, "    watchSubtree: %s: %d entries added\n",
+            path, cnt);
+}
+
+/***********************************************************************/
+
+/* The directory oldPathPrefix/oldName was renamed to
+   newPathPrefix/newName. Fix up cache entries for
+   oldPathPrefix/oldName and all of its subdirectories
+   to reflect the change. */
+
+static void
+rewriteCachedPaths(const char *oldPathPrefix, const char *oldName,
+                   const char *newPathPrefix, const char *newName)
+{
+    char fullPath[PATH_MAX], newPrefix[PATH_MAX];
+    char newPath[PATH_MAX];
+    size_t len;
+    int j;
+
+    snprintf(fullPath, sizeof(fullPath), "%s/%s", oldPathPrefix, oldName);
+    snprintf(newPrefix, sizeof(newPrefix), "%s/%s", newPathPrefix, newName);
+    len = strlen(fullPath);
+
+    logMessage(VB_BASIC, "Rename: %s ==> %s\n", fullPath, newPrefix);
+
+    for (j = 0; j < cacheSize; j++) {
+        if (strncmp(fullPath, wlCache[j].path, len) == 0 &&
+                    (wlCache[j].path[len] == '/' ||
+                     wlCache[j].path[len] == '\0')) {
+            snprintf(newPath, sizeof(newPath), "%s%s", newPrefix,
+                    &wlCache[j].path[len]);
+
+            strncpy(wlCache[j].path, newPath, PATH_MAX);
+
+            logMessage(VB_NOISY, "    wd %d [cache slot %d] ==> %s\n",
+                    wlCache[j].wd, j, newPath);
+        }
+    }
+}
+
+/* Zap watches and cache entries for directory 'path' and all of its
+   subdirectories. Returns number of entries that we (tried to) zap,
+   or -1 if an inotify_rm_watch() call failed. */
+
+static int
+zapSubtree(int inotifyFd, char *path)
+{
+    size_t len;
+    int j;
+    int cnt;
+    char *pn;
+
+    logMessage(VB_NOISY, "Zapping subtree: %s\n", path);
+
+    len = strlen(path);
+
+    /* The argument we receive might be a pointer to a pathname string
+       that is actually stored in the cache.  If we zap that pathname
+       part way through scanning the whole cache, then chaos results.
+       So, create a temporary copy. */
+
+    pn = strdup(path);
+
+    cnt = 0;
+
+    for (j = 0; j < cacheSize; j++) {
+        if (wlCache[j].wd >= 0) {
+            if (strncmp(pn, wlCache[j].path, len) == 0 &&
+                    (wlCache[j].path[len] == '/' ||
+                     wlCache[j].path[len] == '\0')) {
+
+                logMessage(VB_NOISY,
+                           "    removing watch: wd = %d (%s)\n",
+                           wlCache[j].wd, wlCache[j].path);
+
+                if (inotify_rm_watch(inotifyFd, wlCache[j].wd) == -1) {
+                    logMessage(0, "inotify_rm_watch wd = %d (%s): %s\n",
+                            wlCache[j].wd, wlCache[j].path, strerror(errno));
+
+                    /* When we have multiple renamers, sometimes
+                       inotify_rm_watch() fails. In this case, we force a
+                       cache rebuild by returning -1.
+                       (TODO: Is there a better solution?) */
+
+                    cnt = -1;
+                    break;
+                }
+
+                markCacheSlotEmpty(j);
+                cnt++;
+            }
+        }
+    }
+
+    free(pn);
+    return cnt;
+}
+
+/* When the cache is in an unrecoverable state, we discard the current
+   inotify file descriptor ('oldInotifyFd') and create a new one (returned
+   as the function result), and zap and rebuild the cache.
+
+   If 'oldInotifyFd' is -1, this is the initial build of the cache, or an
+   explicitly requested cache rebuild, so we are a little less verbose,
+   and we reset 'reinitCnt'.  */
+
+static int
+reinitialize(int oldInotifyFd)
+{
+    int inotifyFd;
+    static int reinitCnt;
+    int cnt, j;
+
+    if (oldInotifyFd >= 0) {
+        close(oldInotifyFd);
+
+        reinitCnt++;
+        logMessage(0, "Reinitializing cache and inotify FD (reinitCnt = %d)\n",
+                reinitCnt);
+
+    } else {
+        logMessage(0, "Initializing cache\n");
+        reinitCnt = 0;
+    }
+
+    inotifyFd = inotify_init();
+    if (inotifyFd == -1)
+        errExit("inotify_init");
+
+    logMessage(VB_BASIC, "    new inotifyFd = %d\n", inotifyFd);
+
+    freeCache();
+
+    for (j = 0; j < numRootDirs; j++)
+        if (rootDirPaths[j] != NULL)
+            watchSubtree(inotifyFd, rootDirPaths[j]);
+
+    cnt = 0;
+    for (j = 0; j < cacheSize; j++)
+        if (wlCache[j].wd >= 0)
+            cnt++;
+
+    if (oldInotifyFd >= 0)
+        logMessage(0, "Rebuilt cache with %d entries\n", cnt);
+
+    return inotifyFd;
+}
+
+/* Process the next inotify event in the buffer specified by 'buf'
+   and 'bufSize'. In most cases, a single event is consumed, but
+   if there is an IN_MOVED_FROM+IN_MOVED_TO pair that share a cookie
+   value, both events are consumed.
+
+   Returns the number of bytes in the event(s) consumed from 'buf'.  */
+
+static size_t
+processNextInotifyEvent(int *inotifyFd, char *buf, int bufSize, int firstTry)
+{
+    char fullPath[PATH_MAX + NAME_MAX];
+    struct inotify_event *ev;
+    size_t evLen;
+    int evCacheSlot;
+
+    ev = (struct inotify_event *) buf;
+
+    displayInotifyEvent(ev);
+
+    if (ev->wd != -1 && !(ev->mask & IN_IGNORED)) {
+
+                /* IN_Q_OVERFLOW has (ev->wd == -1) */
+                /* Skip IN_IGNORED, since it will come after an event
+                   that has already zapped the corresponding cache entry */
+
+        /* Cache consistency check; see the discussion
+           of "intra-tree" rename() events */
+
+        evCacheSlot = findWatchChecked(ev->wd);
+        if (evCacheSlot == -1) {
+
+            /* Cache reached an inconsistent state */
+
+            *inotifyFd = reinitialize(*inotifyFd);
+
+            /* Discard all remaining events in current read() buffer */
+
+            return INOTIFY_READ_BUF_LEN;
+        }
+    }
+
+    evLen = sizeof(struct inotify_event) + ev->len;
+
+    if ((ev->mask & IN_ISDIR) &&
+            (ev->mask & (IN_CREATE | IN_MOVED_TO))) {
+
+        /* A new subdirectory was created, or a subdirectory was
+           renamed into the tree; create watches for it, and all
+           of its subdirectories. */
+
+        snprintf(fullPath, sizeof(fullPath), "%s/%s",
+                 wlCache[evCacheSlot].path, ev->name);
+
+        logMessage(VB_BASIC, "Directory creation on wd %d: %s\n",
+                ev->wd, fullPath);
+
+        /* We only watch the new subtree if it has not already been cached.
+
+           This deals with a race condition:
+           * On the one hand, the following steps might occur:
+               1. The "child" directory is created.
+               2. The "grandchild" directory is created
+               3. We receive an IN_CREATE event for the creation of the
+                  "child" and create a watch and a cache entry for it.
+               4. To handle the possibility that step 2 came before
+                  step 3, we recursively walk through the descendants of
+                  the "child" directory, adding any subdirectories to
+                  the cache.
+           * On the other hand, the following steps might occur:
+               1. The "child" directory is created.
+               3. We receive an IN_CREATE event for the creation of the
+                  "child" and create a watch and a cache entry for it.
+               3. The "grandchild" directory is created
+               4. During the recursive walk through the descendants of
+                  the "child" directory, we cache the "grandchild" and
+                  add a watch for it.
+               5. We receive the IN_CREATE event for the creation of
+                  the "grandchild". At this point, we should NOT create
+                  a cache entry and watch for the "grandchild" because
+                  they already exist. (Creating the watch for the
+                  second time is harmless, but adding a second cache
+                  for the grandchild would leave the cache in a confused
+                  state.) */
+
+        if (!pathnameInCache(fullPath))
+            watchSubtree(*inotifyFd, fullPath);
+
+    } else if (ev->mask & IN_DELETE_SELF) {
+
+        /* A directory was deleted. Remove the corresponding item from
+           the cache. */
+
+        logMessage(VB_BASIC, "Clearing watchlist item %d (%s)\n",
+                   ev->wd, wlCache[evCacheSlot].path);
+
+        if (isRootDirPath(wlCache[evCacheSlot].path))
+            zapRootDirPath(wlCache[evCacheSlot].path);
+
+        markCacheSlotEmpty(evCacheSlot);
+            /* No need to remove the watch; that happens automatically */
+
+    } else if ((ev->mask & (IN_MOVED_FROM | IN_ISDIR)) ==
+               (IN_MOVED_FROM | IN_ISDIR)) {
+
+        /* We have a "moved from" event. To know how to deal with it, we
+           need to determine whether there is a following "moved to"
+           event with a matching cookie value (i.e., an "intra-tree"
+           rename() where the source and destination are inside our
+           monitored trees).  If there is not, then we are dealing
+           with a rename() out of our monitored tree(s).
+
+           We assume that if this is an "intra-tree" rename() event, then
+           the "moved to" event is the next event in the buffer returned
+           by the current read(). (If we are already at the last event in
+           this buffer, then we ask our caller to read a bit more, in
+           the hope of getting the following IN_MOVED_TO event in the
+           next read().)
+
+           In most cases, the assumption holds. However, where multiple
+           processes are manipulating the tree, we can can get event
+           sequences such as the following:
+
+                 IN_MOVED_FROM (rename(x) by process A)
+                         IN_MOVED_FROM (rename(y) by process B)
+                         IN_MOVED_TO   (rename(y) by process B)
+                 IN_MOVED_TO   (rename(x) by process A)
+
+           In principle, there may be arbitrarily complex variations on
+           the above theme. Our assumption that related IN_MOVED_FROM
+           and IN_MOVED_TO events are consecutive is broken by such
+           scenarios.
+
+           We could try to resolve this issue by extending the window
+           we use to search for IN_MOVED_TO events beyond the next item
+           in the queue. But this must be done heuristically (e.g.,
+           limiting the window to N events or to events read within
+           X milliseconds), because sometimes we will have an unmatched
+           IN_MOVED_FROM events that result from out-of-tree renames.
+           The heuristic approach is therefore unavoidably racy: there
+           is always a chance that we will fail to match up an
+           IN_MOVED_FROM+IN_MOVED_TO event pair.
+
+           So, this program takes the simple approach of assuming
+           that an IN_MOVED_FROM+IN_MOVED_TO pair occupy consecutive
+           events in the buffer returned by read().
+
+           When that assumption is wrong (and we therefore fail
+           to recognize an intra-tree rename() event), then
+           the rename will be treated as separate "moved from" and
+           "moved to" events, with the result that some watch items
+           and cache entries are zapped and re-created. This causes
+           the watch descriptors in our cache to become inconsistent
+           with the watch descriptors in as yet unread events,
+           because the watches are re-created with different watch
+           descriptor numbers.
+
+           Once such an inconsistency occurs, then, at some later point,
+           we will do a lookup for a watch descriptor returned by
+           inotify, and find that it is not in our cache. When that
+           happens, we reinitialize our cache with a fresh set of watch
+           descriptors and re-create the inotify file descriptor, in
+           order to bring our cache back into consistency with the
+           filesystem. An alternative would be to cache the cookies of
+           the (recent) IN_MOVED_FROM events for which which we did not
+           find a matching IN_MOVED_TO event, and rebuild our watch
+           cache when we find an IN_MOVED_TO event whose cookie matches
+           one of the cached cookies. Yet another approach when we
+           detect an out-of-tree rename would be to reinitialize the
+           cache and create a new inotify file descriptor.
+           (TODO: consider the fact that for a rename event, there
+           won't be other events for the object between IN_MOVED_FROM
+           and IN_MOVED_TO.)
+
+           Rebuilding the watch cache is expensive if the monitored
+           tree is large. So, there is a trade-off between how much
+           effort we want to go to to avoid cache rebuilds versus
+           how much effort we want to devote to matching up
+           IN_MOVED_FROM+IN_MOVED_TO event pairs. At the one extreme
+           we would do no search ahead for IN_MOVED_TO, with the result
+           that every rename() potentially could trigger a cache
+           rebuild. Limiting the search window to just the following
+           event is a compromise that catches the vast majority of
+           intra-tree renames and triggers relatively few cache rebuilds.
+         */
+
+        struct inotify_event *nextEv;
+
+        nextEv = (struct inotify_event *) (buf + evLen);
+
+        if (((char *) nextEv < buf + bufSize) &&
+                (nextEv->mask & IN_MOVED_TO) &&
+                (nextEv->cookie == ev->cookie)) {
+
+            int nextEvCacheSlot;
+
+            /* We have a rename() event. We need to fix up the
+               cached pathnames for the corresponding directory
+               and all of its subdirectories. */
+
+            nextEvCacheSlot = findWatchChecked(nextEv->wd);
+
+            if (nextEvCacheSlot == -1) {
+
+                /* Cache reached an inconsistent state */
+
+                *inotifyFd = reinitialize(*inotifyFd);
+
+                /* Discard all remaining events in current read() buffer */
+
+                return INOTIFY_READ_BUF_LEN;
+            }
+
+            rewriteCachedPaths(wlCache[evCacheSlot].path, ev->name,
+                               wlCache[nextEvCacheSlot].path, nextEv->name);
+
+            /* We have also processed the next (IN_MOVED_TO) event,
+               so skip over it */
+
+            evLen += sizeof(struct inotify_event) + nextEv->len;
+
+        } else if (((char *) nextEv < buf + bufSize) || !firstTry) {
+
+            /* We got a "moved from" event without an accompanying
+               "moved to" event. The directory has been moved
+               outside the tree we are monitoring. We need to
+               remove the watches and zap the cache entries for
+               the moved directory and all of its subdirectories. */
+
+            logMessage(VB_NOISY, "MOVED_OUT: %p %p\n",
+                    wlCache[evCacheSlot].path, ev->name);
+            logMessage(VB_NOISY, "firstTry = %d; remaining bytes = %d\n",
+                    firstTry, buf + bufSize - (char *) nextEv);
+            snprintf(fullPath, sizeof(fullPath), "%s/%s",
+                     wlCache[evCacheSlot].path, ev->name);
+
+            if (zapSubtree(*inotifyFd, fullPath) == -1) {
+
+                /* Cache reached an inconsistent state */
+
+                *inotifyFd = reinitialize(*inotifyFd);
+
+                /* Discard all remaining events in current read() buffer */
+
+                return INOTIFY_READ_BUF_LEN;
+            }
+
+        } else {
+            logMessage(VB_NOISY, "HANGING IN_MOVED_FROM\n");
+
+            return -1;  /* Tell our caller to do another read() */
+        }
+
+    } else if (ev->mask & IN_Q_OVERFLOW) {
+
+        static int overflowCnt = 0;
+
+        overflowCnt++;
+
+        logMessage(0, "Queue overflow (%d) (inotifyReadCnt = %d)\n",
+                    overflowCnt, inotifyReadCnt);
+
+        /* When the queue overflows, some events are lost, at which
+           point we've lost any chance of keeping our cache consistent
+           with the state of the filesystem. So, discard this inotify
+           file descriptor and create a new one, and zap and rebuild
+           the cache. */
+
+        *inotifyFd = reinitialize(*inotifyFd);
+
+        /* Discard all remaining events in current read() buffer */
+
+        evLen = INOTIFY_READ_BUF_LEN;
+
+    } else if (ev->mask & IN_UNMOUNT) {
+
+        /* When a filesystem is unmounted, each of the watches on the
+           is dropped, and an unmount and an ignore event are generated.
+           There's nothing left for us to monitor, so we just zap the
+           corresponding cache entry. */
+
+        logMessage(0, "Filesystem unmounted: %s\n",
+                wlCache[evCacheSlot].path);
+
+        markCacheSlotEmpty(evCacheSlot);
+            /* No need to remove the watch; that happens automatically */
+
+    } else if (ev->mask & IN_MOVE_SELF &&
+            isRootDirPath(wlCache[evCacheSlot].path)) {
+
+        /* If the root path moves to a new location in the same
+           filesystem, then all cached pathnames become invalid, and we
+           have no direct way of knowing the new name of the root path.
+           We could in theory find the new name by caching the i-node of
+           the root path on start-up and then trying to find a pathname
+           that corresponds to that i-node. Instead, we'll keep things
+           simple, and just cease monitoring it. */
+
+        logMessage(0, "Root path moved: %s\n",
+                    wlCache[evCacheSlot].path);
+
+        zapRootDirPath(wlCache[evCacheSlot].path);
+
+        if (zapSubtree(*inotifyFd, wlCache[evCacheSlot].path) == -1) {
+
+            /* Cache reached an inconsistent state */
+
+            *inotifyFd = reinitialize(*inotifyFd);
+
+            /* Discard all remaining events in current read() buffer */
+
+            return INOTIFY_READ_BUF_LEN;
+        }
+    }
+
+    if (checkCache)
+        checkCacheConsistency();
+
+    if (dumpCache)
+        dumpCacheToLog();
+
+    return evLen;
+}
+
+static void
+alarmHandler(int sig)
+{
+    return;             /* Just interrupt read() */
+}
+
+/* Read a block of events from the inotify file descriptor, 'inotifyFd'.
+   Process the events relating to directories in the subtree we are
+   monitoring, in order to keep our cached view of the subtree in sync
+   with the filesystem. */
+
+static void
+processInotifyEvents(int *inotifyFd)
+{
+    char buf[INOTIFY_READ_BUF_LEN]
+        __attribute__ ((aligned(__alignof__(struct inotify_event))));
+    ssize_t numRead, nr;
+    char *evp;
+    size_t cnt;
+    int evLen;
+    int firstTry;
+    int j;
+    struct sigaction sa;
+
+    /* SIGALRM handler is designed simply to interrupt read() */
+
+    sigemptyset(&sa.sa_mask);
+    sa.sa_handler = alarmHandler;
+    sa.sa_flags = 0;
+    if (sigaction(SIGALRM, &sa, NULL) == -1)
+        errExit("sigaction");
+
+    firstTry = 1;
+
+    /* Read some events from inotify file descriptor */
+
+    cnt = (readBufferSize > 0) ? readBufferSize : INOTIFY_READ_BUF_LEN;
+    numRead = read(*inotifyFd, buf, cnt);
+    if (numRead == -1)
+        errExit("read");
+    if (numRead == 0) {
+        fprintf(stderr, "read() from inotify fd returned 0!");
+        exit(EXIT_FAILURE);
+    }
+
+    inotifyReadCnt++;
+
+    logMessage(VB_NOISY,
+               "\n==========> Read %d: got %zd bytes\n",
+               inotifyReadCnt, numRead);
+
+    /* Process each event in the buffer returned by read() */
+
+    for (evp = buf; evp < buf + numRead; ) {
+        evLen = processNextInotifyEvent(inotifyFd, evp,
+                                 buf + numRead - evp, firstTry);
+
+        if (evLen > 0) {
+            evp += evLen;
+            firstTry = 1;
+        } else {
+
+            /* We got here because an IN_MOVED_FROM event was found at
+               the end of a previously read buffer and that event may be
+               part of an "intra-tree" rename(), meaning that we should
+               check if there is a subsequent IN_MOVED_TO event with the
+               same cookie value. We left that event unprocessed and we
+               will now try to read some more events, delaying for a
+               short time, to give the associated IN_MOVED_IN event (if
+               there is one) a chance to arrive. However, we only want
+               to do this once: if the read() below fails to gather
+               further events, then when we reprocess the IN_MOVED_FROM
+               we should treat it as though this is an out-of-tree
+               rename(). Thus, we set 'firstTry' to 0 for the next
+               processNextInotifyEvent() call. */
+
+            int savedErrno;
+
+            firstTry = 0;
+
+            numRead = buf + numRead - evp;
+
+            /* Shuffle remaining bytes to start of buffer */
+
+            for (j = 0; j < numRead; j++)
+                buf[j] = evp[j];
+
+            /* Set a timeout for read(). Some rough testing suggests
+               that a 2-millisecond timeout is sufficient to ensure
+               that, in around 99.8% of cases, we get the IN_MOVED_TO
+               event (if there is one) that matched an IN_MOVED_FROM
+               event, even in a highly dynamic directory tree. This
+               number may, of course, warrant tuning on different
+               hardware and in environments with different filesystem
+               activity levels. */
+
+            ualarm(2000, 0);
+
+            nr = read(*inotifyFd, buf + numRead,
+                      INOTIFY_READ_BUF_LEN - numRead);
+
+            savedErrno = errno; /* In case ualarm() should change errno */
+            ualarm(0, 0);       /* Cancel alarm */
+            errno = savedErrno;
+
+            if (nr == -1 && errno != EINTR)
+                errExit("read");
+            if (nr == 0) {
+                fprintf(stderr, "read() from inotify fd returned 0!");
+                exit(EXIT_FAILURE);
+            }
+
+            if (errno != -1) {
+                numRead += nr;
+                inotifyReadCnt++;
+
+                logMessage(VB_NOISY,
+                       "\n==========> SECONDARY Read %d: got %zd bytes\n",
+                       inotifyReadCnt, nr);
+
+            } else {                    /* EINTR */
+                logMessage(VB_NOISY,
+                       "\n==========> SECONDARY Read got nothing\n");
+            }
+
+            evp = buf;          /* Start again at beginning of buffer */
+        }
+    }
+}
+
+/***********************************************************************/
+
+/* We allow some simple interactive commands, mainly to check the
+   operation of the program */
+
+static void
+executeCommand(int *inotifyFd)
+{
+    const int MAX_LINE = 100;
+    ssize_t numRead;
+    char line[MAX_LINE], arg[MAX_LINE];
+    char cmd;
+    int j, cnt, ns, failures;
+    struct stat sb;
+    FILE *fp;
+
+    numRead = read(STDIN_FILENO, line, MAX_LINE);
+    if (numRead <= 0) {
+        printf("bye!\n");
+        exit(EXIT_FAILURE);
+    }
+
+    line[numRead - 1] = '\0';
+
+    if (strlen(line) == 0)
+        return;
+
+    ns = sscanf(line, "%c %s\n", &cmd, arg);
+
+    switch (cmd) {
+
+    case 'a':   /* Add/refresh a subtree */
+
+        cnt = zapSubtree(*inotifyFd, arg);
+        if (cnt == 0) {
+            logMessage(VB_BASIC, "Adding new subtree: %s\n", arg);
+        } else {
+            logMessage(VB_BASIC, "Zapped: %s, %d entries\n", arg, cnt);
+        }
+
+        watchSubtree(*inotifyFd, arg);
+        break;
+
+    case 'c':   /* Check that all cached pathnames exist */
+    case 'C':
+
+        cnt = 0;
+        failures = 0;
+        for (j = 0; j < cacheSize; j++) {
+            if (wlCache[j].wd >= 0) {
+                if (lstat(wlCache[j].path, &sb) == -1) {
+                    if (cmd == 'c')
+                        logMessage(VB_BASIC,
+                                "stat: [slot = %d; wd = %d] %s: %s\n",
+                                j, wlCache[j].wd, wlCache[j].path,
+                                strerror(errno));
+                    failures++;
+                } else if (!S_ISDIR(sb.st_mode)) {
+                    if (cmd == 'c')
+                        logMessage(0, "%s is not a directory\n",
+                                wlCache[j].path);
+                    exit(EXIT_FAILURE);
+                } else {
+                    if (cmd == 'c')
+                        logMessage(VB_NOISY,
+                                "OK: [slot = %d; wd = %d] %s\n",
+                                j, wlCache[j].wd, wlCache[j].path);
+                    cnt++;
+                }
+            }
+        }
+
+        logMessage(0, "Successfully verified %d entries\n", cnt);
+        logMessage(0, "Failures: %d\n", failures);
+        break;
+
+    case 'l':   /* List entries in the cache */
+
+        cnt = 0;
+
+        for (j = 0; j < cacheSize; j++) {
+            if (wlCache[j].wd >= 0) {
+                logMessage(0, "%d: %d %s\n", j, wlCache[j].wd,
+                           wlCache[j].path);
+                cnt++;
+            }
+        }
+
+        logMessage(VB_BASIC, "Total entries: %d\n", cnt);
+        break;
+
+    case 'q':   /* Quit */
+
+        exit(EXIT_SUCCESS);
+
+    case 'v':   /* Set log verbosity level */
+
+        if (ns == 2)
+            verboseMask = atoi(arg);
+        else {
+            verboseMask = !verboseMask;
+            printf("%s\n", verboseMask ? "on" : "off");
+        }
+        break;
+
+    case 'd':   /* Toggle cache dumping */
+
+        dumpCache = !dumpCache;
+        printf("%s\n", dumpCache ? "on" : "off");
+        break;
+
+    case 'x':   /* Set toggle checking */
+
+        checkCache = !checkCache;
+        printf("%s\n", checkCache ? "on" : "off");
+        break;
+
+    case 'w':   /* Write directory list to file */
+
+        /* We can compare the output from the below against the output
+           from "find DIR -type d" to check whether the contents of the
+           cache are consistent with the state of the filesystem */
+
+        fp = fopen(arg, "w+");
+        if (fp == NULL)
+            perror("fopen");
+
+        for (j = 0; j < cacheSize; j++)
+            if (wlCache[j].wd >= 0)
+                fprintf(fp, "%s\n", wlCache[j].path);
+
+        fclose(fp);
+        break;
+
+    case 'z':   /* Stop watching a subtree, and zap its cache entries */
+
+        cnt = zapSubtree(*inotifyFd, arg);
+        logMessage(VB_BASIC, "Zapped: %s, %d entries\n", arg, cnt);
+        break;
+
+    case '0':   /* Rebuild cache */
+        close(*inotifyFd);
+        *inotifyFd = reinitialize(-1);
+        break;
+
+    default:
+        printf("Unrecognized command: %c\n", cmd);
+        printf("Commands:\n");
+        printf("0        Rebuild cache\n");
+        printf("a path   Add/refresh pathname watches and cache\n");
+        printf("c        Verify cached pathnames\n");
+        printf("d        Toggle cache dumping\n");
+        printf("l        List cached pathnames\n");
+        printf("q        Quit\n");
+        printf("v [n]    Toggle/set verbose level for messages to stderr\n");
+        printf("             0 = no messages\n");
+        printf("             1 = basic messages\n");
+        printf("             2 = verbose messages\n");
+        printf("             3 = basic and verbose messages\n");
+        printf("w file   Write directory list to file\n");
+        printf("x        Toggle cache checking\n");
+        printf("z path   Zap pathname and watches from cache\n");
+        break;
+    }
+}
+
+/***********************************************************************/
+
+static void
+usageError(const char *pname)
+{
+    fprintf(stderr, "Usage: %s [options] directory-path\n\n",
+            pname);
+    fprintf(stderr, "    -v lvl   Display logging information\n");
+    fprintf(stderr, "    -l file  Send logging information to a file\n");
+    fprintf(stderr, "    -x       Check cache consistency after each "
+                                  "operation\n");
+    fprintf(stderr, "    -d       Dump cache to log after every operation\n");
+    fprintf(stderr, "    -b size  Set buffer size for read() from "
+                                  "inotify FD\n");
+    fprintf(stderr, "    -a file  Abort when cache inconsistency detected, "
+            "and create 'stop' file\n");
+
+    exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+    fd_set rfds;
+    int opt;
+    int inotifyFd;
+
+    /* Parse command-line options */
+
+    verboseMask = 0;
+    checkCache = 0;
+    dumpCache = 0;
+    stopFile = NULL;
+    abortOnCacheProblem = 0;
+
+    while ((opt = getopt(argc, argv, "a:dxl:v:b:")) != -1) {
+        switch (opt) {
+
+        case 'a':
+            abortOnCacheProblem = 1;
+            stopFile = optarg;
+            break;
+
+        case 'x':
+            checkCache = 1;
+            break;
+
+        case 'd':
+            dumpCache = 1;
+            break;
+
+        case 'v':
+            verboseMask = atoi(optarg);
+            break;
+
+        case 'b':
+            readBufferSize = atoi(optarg);
+            break;
+
+        case 'l':
+            logfp = fopen(optarg, "w+");
+            if (logfp == NULL)
+                errExit("fopen");
+            setbuf(logfp, NULL);
+            break;
+
+        default:
+            usageError(argv[0]);
+        }
+    }
+
+    if (optind >= argc)
+        usageError(argv[0]);
+
+    /* Save a copy of the directories on the command line */
+
+    copyRootDirPaths(&argv[optind]);
+
+    /* Create an inotify instance and populate it with entries for
+       directory named on command line */
+
+    inotifyFd = reinitialize(-1);
+
+    /* Loop to handle inotify events and keyboard commands */
+
+    printf("%s> ", argv[0]);
+    fflush(stdout);
+
+    for (;;) {
+        FD_ZERO(&rfds);
+        FD_SET(STDIN_FILENO, &rfds);
+        FD_SET(inotifyFd, &rfds);
+        if (select(inotifyFd + 1, &rfds, NULL, NULL, NULL) == -1)
+            errExit("select");
+
+        if (FD_ISSET(STDIN_FILENO, &rfds)) {
+            executeCommand(&inotifyFd);
+
+            printf("%s> ", argv[0]);
+            fflush(stdout);
+        }
+
+        if (FD_ISSET(inotifyFd, &rfds))
+            processInotifyEvents(&inotifyFd);
+    }
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/inotify/rand_dtree.c b/inotify/rand_dtree.c
new file mode 100644 (file)
index 0000000..cfcda18
--- /dev/null
@@ -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 <stdarg.h>
+#include <limits.h>
+#include <ftw.h>
+#include "tlpi_hdr.h"
+
+#define DLIM 60
+
+/* Hack! We can't pass arguments to the function invoked by nftw(),
+   so we use these global variables to exchange information with
+   the function */
+
+static int dcnt;
+static char **dirList = NULL;
+static int dlSize = 0;
+static const int D_INCR = 1000;
+
+static int
+traverseTree(const char *pathname, const struct stat *sb, int tflag,
+             struct FTW *ftwbuf)
+{
+    if (! S_ISDIR(sb->st_mode))
+        return 0;
+
+    //printf("%s\n", pathname);
+
+    if (dcnt >= dlSize) {
+        dlSize += D_INCR;
+        dirList = realloc(dirList, dlSize * sizeof(char *));
+        if (dirList == NULL)
+            errExit("realloc");
+    }
+
+    dirList[dcnt] = strdup(pathname);
+
+    dcnt++;
+
+    return 0;
+}
+
+static int
+getDirList(const char *pathname)
+{
+    dcnt = 0;
+
+    if (nftw(pathname, traverseTree, 20, FTW_PHYS) == -1)
+        errMsg("nftw: %s", pathname);
+
+    return dcnt;
+}
+
+static FILE *logfp = NULL;
+
+static void
+logMessage(const char *format, ...)
+{
+    va_list argList;
+
+    va_start(argList, format);
+
+    if (logfp != NULL)
+        vfprintf(logfp, format, argList);
+
+    va_end(argList);
+}
+
+static void
+usageError(char *pname)
+{
+    fprintf(stderr, "Usage: %s [options] dirpath {c|d|m}\n\n", pname);
+    fprintf(stderr, "Perform random operations in the "
+            "directory tree 'dirpath'\n");
+    fprintf(stderr, "    c == create directories\n");
+    fprintf(stderr, "    d == delete directories\n");
+    fprintf(stderr, "    m == rename directories\n\n");
+    fprintf(stderr, "Options:\n");
+    fprintf(stderr, "    -l logfile     Record activity in log file\n");
+    fprintf(stderr, "    -m maxops      Do at most 'maxops' operations "
+            "(default is unlimited)\n");
+    fprintf(stderr, "    -s usecs       Sleep 'usecs' microseconds "
+            "between each operation\n");
+    fprintf(stderr, "    -z stopfile    Immediately stop when the file "
+            "'stopfile' is created\n");
+    exit(EXIT_FAILURE);
+}
+
+#define MARKER_STRING "--"
+
+int
+main(int argc, char *argv[])
+{
+    int j;
+    char *to_move;
+    int opcnt, maxops;
+    char path[PATH_MAX];
+    char tfile[PATH_MAX];
+    char target[PATH_MAX];
+    char spath[PATH_MAX];
+    char *p;
+    int nslashes;
+    int opt;
+    int usecs;
+    char *stopFile;
+    int scnt;
+
+    opcnt = 0;
+
+    srandom(0);
+
+    stopFile = NULL;
+    maxops = 0;
+    usecs = 1;
+    while ((opt = getopt(argc, argv, "l:m:s:z:")) != -1) {
+        switch (opt) {
+        case 's':
+            usecs = atoi(optarg);
+            break;
+
+        case 'z':
+            stopFile = optarg;
+            break;
+
+        case 'm':
+            maxops = atoi(optarg);
+            break;
+
+        case 'l':
+            logfp = fopen(optarg, "w+");
+            if (logfp == NULL)
+                errExit("fopen");
+            setbuf(logfp, NULL);
+            break;
+
+        default:
+            usageError(argv[0]);
+        }
+    }
+
+    if (optind + 1 >= argc)
+        usageError(argv[0]);
+
+    opcnt = 0;
+
+    for (;;) {
+
+        getDirList(argv[optind]);
+
+        switch (argv[optind + 1][0]) {
+        case 'c':
+            snprintf(path, sizeof(path), "%s/%ld%s%s_%d",
+                    dirList[random() % dcnt],
+                    (long) getpid() % 100, MARKER_STRING, "cr", opcnt);
+            if (strlen(path) > DLIM)
+                continue;
+            nslashes = 0;
+            for (p = path; *p; p++)
+                if (*p == '/')
+                    nslashes++;
+
+            if (nslashes > 1)
+                if (random() % nslashes > 0)
+                    continue;
+
+            if (mkdir(path, 0700) == 0)
+                logMessage("mkdir: %s\n", path);
+
+            scnt = 1;
+            while ((random() % 3) < 2) {
+
+                snprintf(spath, sizeof(path), "%s/%ld%s%s%d_%d", path,
+                        (long) getpid() % 100,
+                        MARKER_STRING, "scr", scnt, opcnt);
+                if (strlen(spath) > DLIM)
+                    break;
+
+                if (mkdir(spath, 0700) == 0)
+                    logMessage("mkdir: %s\n", spath);
+
+                strncpy(path, spath, PATH_MAX);
+                path[PATH_MAX - 1] = '\0';
+                scnt++;
+            }
+            break;
+
+        case 'd':
+            if (dcnt == 0)
+                continue;
+
+            snprintf(path, sizeof(path), "%s", dirList[random() % dcnt]);
+            while (strstr(path, MARKER_STRING) != NULL) {
+
+                if (rmdir(path) == -1)
+                    break;
+                logMessage("rmdir: %s\n", path);
+
+                p = strrchr(path, '/');
+                if (p == NULL)
+                    break;
+
+                *p = '\0';
+            }
+            break;
+
+        case 'm':
+            if (dcnt < 3)
+                continue;
+
+            to_move = dirList[random() % dcnt];
+
+            if (strstr(to_move, MARKER_STRING) != NULL) {
+                p = strrchr(to_move, '/');
+                snprintf(tfile, sizeof(tfile), "%s", p + 1);
+                p = strstr(tfile, "__ren");
+                if (p != NULL)
+                    *p = '\0';
+                snprintf(target, sizeof(target), "%s/%s__ren%04d-%ld",
+                        dirList[random() % dcnt],
+                        tfile, opcnt, (long) getpid());
+
+                if (strlen(target) > DLIM)
+                    break;
+
+                if (rename(to_move, target) == 0)
+                    logMessage("rename: %s ==> %s\n", to_move, target);
+            }
+
+            break;
+        }
+
+        for (j = 0; j < dcnt; j++) {
+            free(dirList[j]);
+            dirList[j] = NULL;
+        }
+
+        opcnt++;
+
+        usleep(usecs);
+
+        if (maxops > 0 && opcnt >= maxops)
+            break;
+
+        if (access(stopFile, F_OK) == 0)
+            break;
+    }
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/lib/Build_ename.sh b/lib/Build_ename.sh
new file mode 100644 (file)
index 0000000..d2eb014
--- /dev/null
@@ -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 <errno.h>' | cpp -dM | 
+sed -n -e '/#define  *E/s/#define  *//p' |sort -k2n |
+awk '
+BEGIN {
+        entries_per_line = 4
+        line_len = 68;
+        last = 0;
+        varname ="    enames";
+        print "static char *ename[] = {";
+        line =  "    /*   0 */ \"\"";
+}
+{
+    if ($2 ~ /^E[A-Z0-9]*$/) {      # These entries are sorted at top
+        synonym[$1] = $2;
+    } else {
+        while (last + 1 < $2) {
+            last++;
+            line = line ", ";
+            if (length(line ename) > line_len || last == 1) {
+                print line;
+                line = "    /* " last " */ ";
+                line = sprintf("    /* %3d */ ", last);
+            }
+            line = line "\"" "\"" ;
+        }
+        last = $2;
+        ename = $1;
+        for (k in synonym)
+            if (synonym[k] == $1) ename = ename "/" k;
+            line = line ", ";
+            if (length(line ename) > line_len || last == 1) {
+                print line;
+                line = "    /* " last " */ ";
+                line = sprintf("    /* %3d */ ", last);;
+            }
+            line = line "\"" ename "\"" ;
+    }
+}
+END {
+    print  line;
+    print "};"
+    print "";
+    print "#define MAX_ENAME " last;
+}
+'
diff --git a/lib/Makefile b/lib/Makefile
new file mode 100644 (file)
index 0000000..63abd13
--- /dev/null
@@ -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 (file)
index 0000000..1815ba2
--- /dev/null
@@ -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 (file)
index 0000000..9cca72c
--- /dev/null
@@ -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 <stdio.h>
+#include <fcntl.h>
+#include "alt_functions.h"
+
+/* A very minimal implementation of strsignal()... */
+
+#define BUF_SIZE 100
+
+char *
+ALT_strsignal(int sig)
+{
+    static char buf[BUF_SIZE];          /* Not thread-safe */
+
+    snprintf(buf, BUF_SIZE, "SIG-%d", sig);
+    return buf;
+}
+
+/* A very minimal implementation of hstrerror()... */
+
+char *
+ALT_hstrerror(int err)
+{
+    static char buf[BUF_SIZE];          /* Not thread-safe */
+
+    snprintf(buf, BUF_SIZE, "hstrerror-%d", err);
+    return buf;
+}
+
+/* posix_openpt() is simple to implement */
+
+int
+ALT_posix_openpt(int flags)
+{
+    return open("/dev/ptmx", flags);
+}
diff --git a/lib/alt_functions.h b/lib/alt_functions.h
new file mode 100644 (file)
index 0000000..84e33e8
--- /dev/null
@@ -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 (symlink)
index 0000000..55c4906
--- /dev/null
@@ -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 (symlink)
index 0000000..9dd5369
--- /dev/null
@@ -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 (symlink)
index 0000000..85ed9d0
--- /dev/null
@@ -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 (symlink)
index 0000000..3fdebc9
--- /dev/null
@@ -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 (symlink)
index 0000000..e5c27e0
--- /dev/null
@@ -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 (symlink)
index 0000000..2af3faf
--- /dev/null
@@ -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 (symlink)
index 0000000..de4facc
--- /dev/null
@@ -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 (symlink)
index 0000000..d690613
--- /dev/null
@@ -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 (file)
index 0000000..a9eb2eb
--- /dev/null
@@ -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 <stdarg.h>
+#include "error_functions.h"
+#include "tlpi_hdr.h"
+#include "ename.c.inc"          /* Defines ename and MAX_ENAME */
+
+#ifdef __GNUC__                 /* Prevent 'gcc -Wall' complaining  */
+__attribute__ ((__noreturn__))  /* if we call this function as last */
+#endif                          /* statement in a non-void function */
+static void
+terminate(Boolean useExit3)
+{
+    char *s;
+
+    /* Dump core if EF_DUMPCORE environment variable is defined and
+       is a nonempty string; otherwise call exit(3) or _exit(2),
+       depending on the value of 'useExit3'. */
+
+    s = getenv("EF_DUMPCORE");
+
+    if (s != NULL && *s != '\0')
+        abort();
+    else if (useExit3)
+        exit(EXIT_FAILURE);
+    else
+        _exit(EXIT_FAILURE);
+}
+
+/* Diagnose 'errno' error by:
+
+      * outputting a string containing the error name (if available
+        in 'ename' array) corresponding to the value in 'err', along
+        with the corresponding error message from strerror(), and
+
+      * outputting the caller-supplied error message specified in
+        'format' and 'ap'. */
+
+static void
+outputError(Boolean useErr, int err, Boolean flushStdout,
+        const char *format, va_list ap)
+{
+#define BUF_SIZE 500
+    char buf[BUF_SIZE], userMsg[BUF_SIZE], errText[BUF_SIZE];
+
+    vsnprintf(userMsg, BUF_SIZE, format, ap);
+
+    if (useErr)
+        snprintf(errText, BUF_SIZE, " [%s %s]",
+                (err > 0 && err <= MAX_ENAME) ?
+                ename[err] : "?UNKNOWN?", strerror(err));
+    else
+        snprintf(errText, BUF_SIZE, ":");
+
+    snprintf(buf, BUF_SIZE, "ERROR%s %s\n", errText, userMsg);
+
+    if (flushStdout)
+        fflush(stdout);       /* Flush any pending stdout */
+    fputs(buf, stderr);
+    fflush(stderr);           /* In case stderr is not line-buffered */
+}
+
+/* Display error message including 'errno' diagnostic, and
+   return to caller */
+
+void
+errMsg(const char *format, ...)
+{
+    va_list argList;
+    int savedErrno;
+
+    savedErrno = errno;       /* In case we change it here */
+
+    va_start(argList, format);
+    outputError(TRUE, errno, TRUE, format, argList);
+    va_end(argList);
+
+    errno = savedErrno;
+}
+
+/* Display error message including 'errno' diagnostic, and
+   terminate the process */
+
+void
+errExit(const char *format, ...)
+{
+    va_list argList;
+
+    va_start(argList, format);
+    outputError(TRUE, errno, TRUE, format, argList);
+    va_end(argList);
+
+    terminate(TRUE);
+}
+
+/* Display error message including 'errno' diagnostic, and
+   terminate the process by calling _exit().
+
+   The relationship between this function and errExit() is analogous
+   to that between _exit(2) and exit(3): unlike errExit(), this
+   function does not flush stdout and calls _exit(2) to terminate the
+   process (rather than exit(3), which would cause exit handlers to be
+   invoked).
+
+   These differences make this function especially useful in a library
+   function that creates a child process that must then terminate
+   because of an error: the child must terminate without flushing
+   stdio buffers that were partially filled by the caller and without
+   invoking exit handlers that were established by the caller. */
+
+void
+err_exit(const char *format, ...)
+{
+    va_list argList;
+
+    va_start(argList, format);
+    outputError(TRUE, errno, FALSE, format, argList);
+    va_end(argList);
+
+    terminate(FALSE);
+}
+
+/* The following function does the same as errExit(), but expects
+   the error number in 'errnum' */
+
+void
+errExitEN(int errnum, const char *format, ...)
+{
+    va_list argList;
+
+    va_start(argList, format);
+    outputError(TRUE, errnum, TRUE, format, argList);
+    va_end(argList);
+
+    terminate(TRUE);
+}
+
+/* Print an error message (without an 'errno' diagnostic) */
+
+void
+fatal(const char *format, ...)
+{
+    va_list argList;
+
+    va_start(argList, format);
+    outputError(FALSE, 0, TRUE, format, argList);
+    va_end(argList);
+
+    terminate(TRUE);
+}
+
+/* Print a command usage error message and terminate the process */
+
+void
+usageErr(const char *format, ...)
+{
+    va_list argList;
+
+    fflush(stdout);           /* Flush any pending stdout */
+
+    fprintf(stderr, "Usage: ");
+    va_start(argList, format);
+    vfprintf(stderr, format, argList);
+    va_end(argList);
+
+    fflush(stderr);           /* In case stderr is not line-buffered */
+    exit(EXIT_FAILURE);
+}
+
+/* Diagnose an error in command-line arguments and
+   terminate the process */
+
+void
+cmdLineErr(const char *format, ...)
+{
+    va_list argList;
+
+    fflush(stdout);           /* Flush any pending stdout */
+
+    fprintf(stderr, "Command-line usage error: ");
+    va_start(argList, format);
+    vfprintf(stderr, format, argList);
+    va_end(argList);
+
+    fflush(stderr);           /* In case stderr is not line-buffered */
+    exit(EXIT_FAILURE);
+}
diff --git a/lib/error_functions.h b/lib/error_functions.h
new file mode 100644 (file)
index 0000000..af01bb7
--- /dev/null
@@ -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 (symlink)
index 0000000..98d2c03
--- /dev/null
@@ -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 (symlink)
index 0000000..43daafb
--- /dev/null
@@ -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 (symlink)
index 0000000..92cd6d1
--- /dev/null
@@ -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 (symlink)
index 0000000..66e8f1b
--- /dev/null
@@ -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 (file)
index 0000000..60c2586
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <errno.h>
+#include "get_num.h"
+
+/* Print a diagnostic message that contains a function name ('fname'),
+   the value of a command-line argument ('arg'), the name of that
+   command-line argument ('name'), and a diagnostic error message ('msg'). */
+
+static void
+gnFail(const char *fname, const char *msg, const char *arg, const char *name)
+{
+    fprintf(stderr, "%s error", fname);
+    if (name != NULL)
+        fprintf(stderr, " (in %s)", name);
+    fprintf(stderr, ": %s\n", msg);
+    if (arg != NULL && *arg != '\0')
+        fprintf(stderr, "        offending text: %s\n", arg);
+
+    exit(EXIT_FAILURE);
+}
+
+/* Convert a numeric command-line argument ('arg') into a long integer,
+   returned as the function result. 'flags' is a bit mask of flags controlling
+   how the conversion is done and what diagnostic checks are performed on the
+   numeric result; see get_num.h for details.
+
+   'fname' is the name of our caller, and 'name' is the name associated with
+   the command-line argument 'arg'. 'fname' and 'name' are used to print a
+   diagnostic message in case an error is detected when processing 'arg'. */
+
+static long
+getNum(const char *fname, const char *arg, int flags, const char *name)
+{
+    long res;
+    char *endptr;
+    int base;
+
+    if (arg == NULL || *arg == '\0')
+        gnFail(fname, "null or empty string", arg, name);
+
+    base = (flags & GN_ANY_BASE) ? 0 : (flags & GN_BASE_8) ? 8 :
+                        (flags & GN_BASE_16) ? 16 : 10;
+
+    errno = 0;
+    res = strtol(arg, &endptr, base);
+    if (errno != 0)
+        gnFail(fname, "strtol() failed", arg, name);
+
+    if (*endptr != '\0')
+        gnFail(fname, "nonnumeric characters", arg, name);
+
+    if ((flags & GN_NONNEG) && res < 0)
+        gnFail(fname, "negative value not allowed", arg, name);
+
+    if ((flags & GN_GT_0) && res <= 0)
+        gnFail(fname, "value must be > 0", arg, name);
+
+    return res;
+}
+
+/* Convert a numeric command-line argument string to a long integer. See the
+   comments for getNum() for a description of the arguments to this function. */
+
+long
+getLong(const char *arg, int flags, const char *name)
+{
+    return getNum("getLong", arg, flags, name);
+}
+
+/* Convert a numeric command-line argument string to an integer. See the
+   comments for getNum() for a description of the arguments to this function. */
+
+int
+getInt(const char *arg, int flags, const char *name)
+{
+    long res;
+
+    res = getNum("getInt", arg, flags, name);
+
+    if (res > INT_MAX || res < INT_MIN)
+        gnFail("getInt", "integer out of range", arg, name);
+
+    return (int) res;
+}
diff --git a/lib/get_num.h b/lib/get_num.h
new file mode 100644 (file)
index 0000000..8a3e403
--- /dev/null
@@ -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 (symlink)
index 0000000..34c76ad
--- /dev/null
@@ -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 (symlink)
index 0000000..2b549c9
--- /dev/null
@@ -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 (symlink)
index 0000000..ff6c4d8
--- /dev/null
@@ -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 (symlink)
index 0000000..3db4bf8
--- /dev/null
@@ -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 (symlink)
index 0000000..4b71968
--- /dev/null
@@ -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 (symlink)
index 0000000..d33bdc6
--- /dev/null
@@ -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 (symlink)
index 0000000..95c7a66
--- /dev/null
@@ -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 (symlink)
index 0000000..b862ed5
--- /dev/null
@@ -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 (symlink)
index 0000000..d2b2cb7
--- /dev/null
@@ -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 (symlink)
index 0000000..50a5c48
--- /dev/null
@@ -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 (symlink)
index 0000000..7b17eb2
--- /dev/null
@@ -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 (symlink)
index 0000000..37e65c5
--- /dev/null
@@ -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 (symlink)
index 0000000..4b8ea49
--- /dev/null
@@ -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 (symlink)
index 0000000..265c5e0
--- /dev/null
@@ -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 (symlink)
index 0000000..5f7da7f
--- /dev/null
@@ -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 (symlink)
index 0000000..fabebea
--- /dev/null
@@ -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 (symlink)
index 0000000..5f672cd
--- /dev/null
@@ -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 (symlink)
index 0000000..5b48b9c
--- /dev/null
@@ -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 (symlink)
index 0000000..f29efc5
--- /dev/null
@@ -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 (symlink)
index 0000000..3d1f3a9
--- /dev/null
@@ -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 (symlink)
index 0000000..3bcd617
--- /dev/null
@@ -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 (symlink)
index 0000000..c2c93bc
--- /dev/null
@@ -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 (symlink)
index 0000000..4a321dd
--- /dev/null
@@ -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 (symlink)
index 0000000..9e80e9d
--- /dev/null
@@ -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 (symlink)
index 0000000..b11cb69
--- /dev/null
@@ -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 (symlink)
index 0000000..b388134
--- /dev/null
@@ -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 (file)
index 0000000..db27371
--- /dev/null
@@ -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 <sys/types.h>  /* Type definitions used by many programs */
+#include <stdio.h>      /* Standard I/O functions */
+#include <stdlib.h>     /* Prototypes of commonly used library functions,
+                           plus EXIT_SUCCESS and EXIT_FAILURE constants */
+#include <unistd.h>     /* Prototypes for many system calls */
+#include <errno.h>      /* Declares errno and defines error constants */
+#include <string.h>     /* Commonly used string-handling functions */
+
+#include "get_num.h"    /* Declares our functions for handling numeric
+                           arguments (getInt(), getLong()) */
+
+#include "error_functions.h"  /* Declares our error-handling functions */
+
+/* Unfortunately some UNIX implementations define FALSE and TRUE -
+   here we'll undefine them */
+
+#ifdef TRUE
+#undef TRUE
+#endif
+
+#ifdef FALSE
+#undef FALSE
+#endif
+
+typedef enum { FALSE, TRUE } Boolean;
+
+#define min(m,n) ((m) < (n) ? (m) : (n))
+#define max(m,n) ((m) > (n) ? (m) : (n))
+
+/* Some systems don't define 'socklen_t' */
+
+#if defined(__sgi)
+typedef int socklen_t;
+#endif
+
+#if defined(__sun)
+#include <sys/file.h>           /* Has definition of FASYNC */
+#endif
+
+#if ! defined(O_ASYNC) && defined(FASYNC)
+/* Some systems define FASYNC instead of O_ASYNC */
+#define O_ASYNC FASYNC
+#endif
+
+#if defined(MAP_ANON) && ! defined(MAP_ANONYMOUS)
+/* BSD derivatives usually have MAP_ANON, not MAP_ANONYMOUS */
+#define MAP_ANONYMOUS MAP_ANON
+
+#endif
+
+#if ! defined(O_SYNC) && defined(O_FSYNC)
+/* Some implementations have O_FSYNC instead of O_SYNC */
+#define O_SYNC O_FSYNC
+#endif
+
+#if defined(__FreeBSD__)
+
+/* FreeBSD uses these alternate names for fields in the sigval structure */
+
+#define sival_int sigval_int
+#define sival_ptr sigval_ptr
+#endif
+
+#endif
diff --git a/lib/tty_functions.c b/lib/tty_functions.c
new file mode 120000 (symlink)
index 0000000..4aaadb2
--- /dev/null
@@ -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 (symlink)
index 0000000..cda31e3
--- /dev/null
@@ -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 (symlink)
index 0000000..84ef2f6
--- /dev/null
@@ -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 (symlink)
index 0000000..e2a2227
--- /dev/null
@@ -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 (symlink)
index 0000000..20cd954
--- /dev/null
@@ -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 (symlink)
index 0000000..6dd44a9
--- /dev/null
@@ -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 (file)
index 0000000..3f9b820
--- /dev/null
@@ -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 (file)
index 0000000..6358fdc
--- /dev/null
@@ -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 <time.h>
+#include <utmpx.h>
+#include <paths.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    struct utmpx *ut;
+    struct in_addr in;
+
+    if (argc > 1 && strcmp(argv[1], "--help") == 0)
+        usageErr("%s [utmp-pathname]\n", argv[0]);
+
+    if (argc > 1)               /* Use alternate file if supplied */
+        if (utmpxname(argv[1]) == -1)
+            errExit("utmpxname");
+
+    setutxent();
+
+    printf("user     type            PID line   id  host     ");
+    printf("term exit session  address         date/time\n");
+
+    while ((ut = getutxent()) != NULL) {        /* Sequential scan to EOF */
+        printf("%-8s ", ut->ut_user);
+        printf("%-9.9s ",
+                (ut->ut_type == EMPTY) ?         "EMPTY" :
+                (ut->ut_type == RUN_LVL) ?       "RUN_LVL" :
+                (ut->ut_type == BOOT_TIME) ?     "BOOT_TIME" :
+                (ut->ut_type == NEW_TIME) ?      "NEW_TIME" :
+                (ut->ut_type == OLD_TIME) ?      "OLD_TIME" :
+                (ut->ut_type == INIT_PROCESS) ?  "INIT_PR" :
+                (ut->ut_type == LOGIN_PROCESS) ? "LOGIN_PR" :
+                (ut->ut_type == USER_PROCESS) ?  "USER_PR" :
+                (ut->ut_type == DEAD_PROCESS) ?  "DEAD_PR" : "???");
+        printf("(%1d) ", ut->ut_type);
+        printf("%5ld %-6.6s %-3.5s %-9.9s ", (long) ut->ut_pid,
+                ut->ut_line, ut->ut_id, ut->ut_host);
+        printf("%3d %3d ", ut->ut_exit.e_termination, ut->ut_exit.e_exit);
+        printf("%8ld ", (long) ut->ut_session);
+
+        /* Display IPv4 address */
+
+        in.s_addr = ut->ut_addr_v6[0];
+        printf(" %-15.15s ", inet_ntoa(in));
+        time_t tv_sec = ut->ut_tv.tv_sec;
+        printf("%s", ctime((time_t *) &tv_sec));
+    }
+
+    endutxent();
+    exit(EXIT_SUCCESS);
+}
diff --git a/loginacct/utmpx_login.c b/loginacct/utmpx_login.c
new file mode 100644 (file)
index 0000000..375637d
--- /dev/null
@@ -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 <time.h>
+#include <utmpx.h>
+#include <paths.h>              /* Definitions of _PATH_UTMP and _PATH_WTMP */
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    struct utmpx ut;
+    char *devName;
+
+    if (argc < 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s username [sleep-time]\n", argv[0]);
+
+    /* Initialize login record for utmp and wtmp files */
+
+    memset(&ut, 0, sizeof(struct utmpx));
+    ut.ut_type = USER_PROCESS;          /* This is a user login */
+    strncpy(ut.ut_user, argv[1], sizeof(ut.ut_user));
+    if (time((time_t *) &ut.ut_tv.tv_sec) == -1)
+        errExit("time");                /* Stamp with current time */
+    ut.ut_pid = getpid();
+
+    /* Set ut_line and ut_id based on the terminal associated with
+       'stdin'. This code assumes terminals named "/dev/[pt]t[sy]*".
+       The "/dev/" dirname is 5 characters; the "[pt]t[sy]" filename
+       prefix is 3 characters (making 8 characters in all). */
+
+    devName = ttyname(STDIN_FILENO);
+    if (devName == NULL)
+        errExit("ttyname");
+    if (strlen(devName) <= 8)           /* Should never happen */
+        fatal("Terminal name is too short: %s", devName);
+
+    strncpy(ut.ut_line, devName + 5, sizeof(ut.ut_line));
+    strncpy(ut.ut_id, devName + 8, sizeof(ut.ut_id));
+
+    printf("Creating login entries in utmp and wtmp\n");
+    printf("        using pid %ld, line %.*s, id %.*s\n",
+            (long) ut.ut_pid, (int) sizeof(ut.ut_line), ut.ut_line,
+            (int) sizeof(ut.ut_id), ut.ut_id);
+
+    setutxent();                        /* Rewind to start of utmp file */
+    if (pututxline(&ut) == NULL)        /* Write login record to utmp */
+        errExit("pututxline");
+    updwtmpx(_PATH_WTMP, &ut);          /* Append login record to wtmp */
+
+    /* Sleep a while, so we can examine utmp and wtmp files */
+
+    sleep((argc > 2) ? getInt(argv[2], GN_NONNEG, "sleep-time") : 15);
+
+    /* Now do a "logout"; use values from previously initialized 'ut',
+       except for changes below */
+
+    ut.ut_type = DEAD_PROCESS;          /* Required for logout record */
+    time((time_t *) &ut.ut_tv.tv_sec);  /* Stamp with logout time */
+    memset(&ut.ut_user, 0, sizeof(ut.ut_user));
+                                        /* Logout record has null username */
+
+    printf("Creating logout entries in utmp and wtmp\n");
+    setutxent();                        /* Rewind to start of utmp file */
+    if (pututxline(&ut) == NULL)        /* Overwrite previous utmp record */
+        errExit("pututxline");
+    updwtmpx(_PATH_WTMP, &ut);          /* Append logout record to wtmp */
+
+    endutxent();
+    exit(EXIT_SUCCESS);
+}
diff --git a/loginacct/view_lastlog.c b/loginacct/view_lastlog.c
new file mode 100644 (file)
index 0000000..cecfd9b
--- /dev/null
@@ -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 <time.h>
+#include <lastlog.h>
+#include <paths.h>                      /* Definition of _PATH_LASTLOG */
+#include <fcntl.h>
+#include "ugid_functions.h"             /* Declaration of userIdFromName() */
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    struct lastlog llog;
+    int fd, j;
+    uid_t uid;
+
+    if (argc > 1 && strcmp(argv[1], "--help") == 0)
+        usageErr("%s [username...]\n", argv[0]);
+
+    fd = open(_PATH_LASTLOG, O_RDONLY);
+    if (fd == -1)
+        errExit("open");
+
+    for (j = 1; j < argc; j++) {
+        uid = userIdFromName(argv[j]);
+        if (uid == -1) {
+            printf("No such user: %s\n", argv[j]);
+            continue;
+        }
+
+        if (lseek(fd, uid * sizeof(struct lastlog), SEEK_SET) == -1)
+            errExit("lseek");
+
+        if (read(fd, &llog, sizeof(struct lastlog)) <= 0) {
+            printf("read failed for %s\n", argv[j]);    /* EOF or error */
+            continue;
+        }
+
+        time_t ll_time = llog.ll_time;
+        printf("%-8.8s %-6.6s %-20.20s %s", argv[j], llog.ll_line,
+                llog.ll_host, ctime((time_t *) &ll_time));
+    }
+
+    close(fd);
+    exit(EXIT_SUCCESS);
+}
diff --git a/memalloc/Makefile b/memalloc/Makefile
new file mode 100644 (file)
index 0000000..8b10bee
--- /dev/null
@@ -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 (file)
index 0000000..a1c7c46
--- /dev/null
@@ -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 (file)
index 0000000..ff17e0e
--- /dev/null
@@ -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 (file)
index 0000000..7c6ec8c
--- /dev/null
@@ -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 <sys/wait.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    int *addr;                  /* Pointer to shared memory region */
+
+    /* Parent creates mapped region prior to calling fork() */
+
+#ifdef USE_MAP_ANON             /* Use MAP_ANONYMOUS */
+    addr = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE,
+                MAP_SHARED | MAP_ANONYMOUS, -1, 0);
+    if (addr == MAP_FAILED)
+        errExit("mmap");
+
+#else                           /* Map /dev/zero */
+    int fd;
+
+    fd = open("/dev/zero", O_RDWR);
+    if (fd == -1)
+        errExit("open");
+
+    addr = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+    if (addr == MAP_FAILED)
+        errExit("mmap");
+
+    if (close(fd) == -1)        /* No longer needed */
+        errExit("close");
+#endif
+
+    *addr = 1;                  /* Initialize integer in mapped region */
+
+    switch (fork()) {           /* Parent and child share mapping */
+    case -1:
+        errExit("fork");
+
+    case 0:                     /* Child: increment shared integer and exit */
+        printf("Child started, value = %d\n", *addr);
+        (*addr)++;
+        if (munmap(addr, sizeof(int)) == -1)
+            errExit("munmap");
+        exit(EXIT_SUCCESS);
+
+    default:                    /* Parent: wait for child to terminate */
+        if (wait(NULL) == -1)
+            errExit("wait");
+        printf("In parent, value = %d\n", *addr);
+        if (munmap(addr, sizeof(int)) == -1)
+            errExit("munmap");
+        exit(EXIT_SUCCESS);
+    }
+}
diff --git a/mmap/mmcat.c b/mmap/mmcat.c
new file mode 100644 (file)
index 0000000..23e8000
--- /dev/null
@@ -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 <sys/mman.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    char *addr;
+    int fd;
+    struct stat sb;
+
+    if (argc != 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s file\n", argv[0]);
+
+    fd = open(argv[1], O_RDONLY);
+    if (fd == -1)
+        errExit("open");
+
+    /* Obtain the size of the file and use it to specify the size of
+       the mapping and the size of the buffer to be written */
+
+    if (fstat(fd, &sb) == -1)
+        errExit("fstat");
+
+    /* Handle zero-length file specially, since specifying a size of
+       zero to mmap() will fail with the error EINVAL */
+
+    if (sb.st_size == 0)
+        exit(EXIT_SUCCESS);
+
+    addr = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+    if (addr == MAP_FAILED)
+        errExit("mmap");
+
+    if (write(STDOUT_FILENO, addr, sb.st_size) != sb.st_size)
+        fatal("partial/failed write");
+    exit(EXIT_SUCCESS);
+}
diff --git a/mmap/mmcopy.c b/mmap/mmcopy.c
new file mode 100644 (file)
index 0000000..b9eeb2e
--- /dev/null
@@ -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 <sys/mman.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    char *src, *dst;
+    int fdSrc, fdDst;
+    struct stat sb;
+
+    if (argc != 3)
+        usageErr("%s source-file dest-file\n", argv[0]);
+
+    fdSrc = open(argv[1], O_RDONLY);
+    if (fdSrc == -1)
+        errExit("open");
+
+    /* Use fstat() to obtain size of file: we use this to specify the
+       size of the two mappings */
+
+    if (fstat(fdSrc, &sb) == -1)
+        errExit("fstat");
+
+    /* Handle zero-length file specially, since specifying a size of
+       zero to mmap() will fail with the error EINVAL */
+
+    if (sb.st_size == 0)
+        exit(EXIT_SUCCESS);
+
+    src = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fdSrc, 0);
+    if (src == MAP_FAILED)
+        errExit("mmap");
+
+    fdDst = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+    if (fdDst == -1)
+        errExit("open");
+
+    if (ftruncate(fdDst, sb.st_size) == -1)
+        errExit("ftruncate");
+
+    dst = mmap(NULL, sb.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fdDst, 0);
+    if (dst == MAP_FAILED)
+        errExit("mmap");
+
+    memcpy(dst, src, sb.st_size);       /* Copy bytes between mappings */
+    if (msync(dst, sb.st_size, MS_SYNC) == -1)
+        errExit("msync");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/mmap/t_mmap.c b/mmap/t_mmap.c
new file mode 100644 (file)
index 0000000..5169eb0
--- /dev/null
@@ -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 <sys/mman.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+#define MEM_SIZE 10
+
+int
+main(int argc, char *argv[])
+{
+    char *addr;
+    int fd;
+
+    if (argc < 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s file [new-value]\n", argv[0]);
+
+    fd = open(argv[1], O_RDWR);
+    if (fd == -1)
+        errExit("open");
+
+    addr = mmap(NULL, MEM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+    if (addr == MAP_FAILED)
+        errExit("mmap");
+
+    if (close(fd) == -1)                /* No longer need 'fd' */
+        errExit("close");
+
+    printf("Current string=%.*s\n", MEM_SIZE, addr);
+                        /* Secure practice: output at most MEM_SIZE bytes */
+
+    if (argc > 2) {                     /* Update contents of region */
+        if (strlen(argv[2]) >= MEM_SIZE)
+            cmdLineErr("'new-value' too large\n");
+
+        memset(addr, 0, MEM_SIZE);      /* Zero out region */
+        strncpy(addr, argv[2], MEM_SIZE - 1);
+        if (msync(addr, MEM_SIZE, MS_SYNC) == -1)
+            errExit("msync");
+
+        printf("Copied \"%s\" to shared memory\n", argv[2]);
+    }
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/mmap/t_remap_file_pages.c b/mmap/t_remap_file_pages.c
new file mode 100644 (file)
index 0000000..e5af21d
--- /dev/null
@@ -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 <sys/mman.h> */
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    int fd, j;
+    char ch;
+    long pageSize;
+    char *addr;
+
+    fd = open("/tmp/tfile", O_CREAT | O_TRUNC | O_RDWR, S_IRUSR | S_IWUSR);
+    if (fd == -1)
+        errExit("open");
+
+    pageSize = sysconf(_SC_PAGESIZE);
+    if (pageSize == -1)
+        fatal("Couldn't determine page size");
+
+    for (ch = 'a'; ch < 'd'; ch++)
+        for (j = 0; j < pageSize; j++)
+            write(fd, &ch, 1);
+
+    system("od -a /tmp/tfile");
+
+    addr = mmap(0, 3 * pageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+    if (addr == MAP_FAILED)
+        errExit("mmap");
+
+    printf("Mapped at address %p\n", addr);
+
+    /* The three pages of the file -- 0 1 2 -- are currently mapped
+       linearly. Now we rearrange the mapping to 2 1 0. */
+
+    if (remap_file_pages(addr, pageSize, 0, 2, 0) == -1)
+        errExit("remap_file_pages");
+    if (remap_file_pages(addr + 2 * pageSize, pageSize, 0, 0, 0) == -1)
+        errExit("remap_file_pages");
+
+    /* Now we modify the contents of the mapping */
+
+    for (j = 0; j < 0x100; j++)         /* Modifies page 2 of file */
+        *(addr + j) = '0';
+    for (j = 0; j < 0x100; j++)         /* Modifies page 0 of file */
+        *(addr + 2 * pageSize + j) = '2';
+
+    system("od -a /tmp/tfile");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/namespaces/Makefile b/namespaces/Makefile
new file mode 100644 (file)
index 0000000..30aa024
--- /dev/null
@@ -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 (file)
index 0000000..436bd33
--- /dev/null
@@ -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 <sys/capability.h>
+#include <sys/wait.h>
+#include <sched.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
+                        } while (0)
+
+static int                      /* Startup function for cloned child */
+childFunc(void *arg)
+{
+    cap_t caps;
+
+    for (;;) {
+        printf("eUID = %ld; eGID = %ld; ",
+                (long) geteuid(), (long) getegid());
+
+        caps = cap_get_proc();
+        printf("capabilities: %s\n", cap_to_text(caps, NULL));
+
+        if (arg == NULL)
+            break;
+
+        sleep(5);
+    }
+
+    return 0;
+}
+
+#define STACK_SIZE (1024 * 1024)
+
+int
+main(int argc, char *argv[])
+{
+    pid_t pid;
+    char *child_stack;
+
+    child_stack = malloc(STACK_SIZE);
+    if (child_stack == NULL)
+        errExit("malloc");
+
+    /* Create child; child commences execution in childFunc() */
+
+    pid = clone(childFunc, child_stack + STACK_SIZE,    /* Assume stack
+                                                           grows downward */
+                CLONE_NEWUSER | SIGCHLD, argv[1]);
+    if (pid == -1)
+        errExit("clone");
+
+    printf("PID of child: %ld\n", (long) pid);
+
+    /* Parent falls through to here.  Wait for child. */
+
+    if (waitpid(pid, NULL, 0) == -1)
+        errExit("waitpid");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/namespaces/demo_uts_namespaces.c b/namespaces/demo_uts_namespaces.c
new file mode 100644 (file)
index 0000000..cd81140
--- /dev/null
@@ -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 <sys/wait.h>
+#include <sys/utsname.h>
+#include <sched.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+/* A simple error-handling function: print an error message based
+   on the value in 'errno' and terminate the calling process */
+
+#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
+                        } while (0)
+
+static int              /* Start function for cloned child */
+childFunc(void *arg)
+{
+    struct utsname uts;
+
+    /* Change hostname in UTS namespace of child */
+
+    if (sethostname(arg, strlen(arg)) == -1)
+        errExit("sethostname");
+
+    /* Retrieve and display hostname */
+
+    if (uname(&uts) == -1)
+        errExit("uname");
+    printf("uts.nodename in child:  %s\n", uts.nodename);
+
+    /* Keep the namespace open for a while, by sleeping.
+       This allows some experimentation--for example, another
+       process might join the namespace. */
+
+    sleep(1000);
+
+    return 0;           /* Terminates child */
+}
+
+#define STACK_SIZE (1024 * 1024)    /* Stack size for cloned child */
+
+int
+main(int argc, char *argv[])
+{
+    pid_t child_pid;
+    struct utsname uts;
+    char *child_stack;
+
+    if (argc < 2) {
+        fprintf(stderr, "Usage: %s <child-hostname>\n", argv[0]);
+        exit(EXIT_SUCCESS);
+    }
+
+    child_stack = malloc(STACK_SIZE);
+    if (child_stack == NULL)
+        errExit("malloc");
+
+    /* Create a child that has its own UTS namespace;
+       the child commences execution in childFunc() */
+
+    child_pid = clone(childFunc,
+                    child_stack + STACK_SIZE,   /* Points to start of
+                                                   downwardly growing stack */
+                    CLONE_NEWUTS | SIGCHLD, argv[1]);
+    if (child_pid == -1)
+        errExit("clone");
+    printf("PID of child created by clone() is %ld\n", (long) child_pid);
+
+    /* Parent falls through to here */
+
+    sleep(1);           /* Give child time to change its hostname */
+
+    /* Display the hostname in parent's UTS namespace. This will be
+       different from the hostname in child's UTS namespace. */
+
+    if (uname(&uts) == -1)
+        errExit("uname");
+    printf("uts.nodename in parent: %s\n", uts.nodename);
+
+    if (waitpid(child_pid, NULL, 0) == -1)      /* Wait for child */
+        errExit("waitpid");
+    printf("child has terminated\n");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/namespaces/hostname.c b/namespaces/hostname.c
new file mode 100644 (file)
index 0000000..a1b1fef
--- /dev/null
@@ -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 <sys/param.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#define BUF_SIZE (MAXHOSTNAMELEN + 1)
+
+int
+main(int argc, char *argv[])
+{
+    char buf[BUF_SIZE];
+
+    if (argc > 1) {
+        if (sethostname(argv[1], strlen(argv[1])) == -1) {
+            perror("sethostname");
+            exit(EXIT_FAILURE);
+        }
+    } else {
+        if (gethostname(buf, BUF_SIZE) == -1) {
+            perror("gethostname");
+            exit(EXIT_FAILURE);
+        }
+        printf("%s\n", buf);
+    }
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/namespaces/multi_pidns.c b/namespaces/multi_pidns.c
new file mode 100644 (file)
index 0000000..18af453
--- /dev/null
@@ -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 <sched.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <signal.h>
+#include <stdio.h>
+#include <limits.h>
+#include <sys/mount.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+/* A simple error-handling function: print an error message based
+   on the value in 'errno' and terminate the calling process */
+
+#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
+                        } while (0)
+
+#define STACK_SIZE (1024 * 1024)
+
+/* Recursively create a series of child process in nested PID namespaces.
+   'arg' is an integer that counts down to 0 during the recursion.
+   When the counter reaches 0, recursion stops and the tail child
+   executes the sleep(1) program. */
+
+static int
+childFunc(void *arg)
+{
+    static int first_call = 1;
+    long level = (long) arg;
+    char *child_stack;
+
+    if (!first_call) {
+
+        /* Unless this is the first recursive call to childFunc()
+           (i.e., we were invoked from main()), mount a procfs
+           for the current PID namespace */
+
+        char mount_point[PATH_MAX];
+
+        snprintf(mount_point, PATH_MAX, "/proc%c", (char) ('0' + level));
+
+        mkdir(mount_point, 0555);       /* Create directory for mount point */
+        if (mount("proc", mount_point, "proc", 0, NULL) == -1)
+            errExit("mount");
+        printf("Mounting procfs at %s\n", mount_point);
+    }
+
+    first_call = 0;
+
+    if (level > 0) {
+
+        /* Recursively invoke childFunc() to create another child in a
+           nested PID namespace */
+
+        level--;
+        pid_t child_pid;
+
+        child_stack = malloc(STACK_SIZE);
+        if (child_stack == NULL)
+            errExit("malloc");
+
+        child_pid = clone(childFunc,
+                    child_stack + STACK_SIZE,   /* Points to start of
+                                                   downwardly growing stack */
+                    CLONE_NEWPID | SIGCHLD, (void *) level);
+
+        if (child_pid == -1)
+            errExit("clone");
+
+        if (waitpid(child_pid, NULL, 0) == -1)  /* Wait for child */
+            errExit("waitpid");
+
+        free(child_stack);
+    } else {
+
+        /* Tail end of recursion: execute sleep(1) */
+
+        printf("Final child sleeping\n");
+        execlp("sleep", "sleep", "1000", (char *) NULL);
+        errExit("execlp");
+    }
+
+    return 0;
+}
+
+int
+main(int argc, char *argv[])
+{
+    long levels;
+
+    levels = (argc > 1) ? atoi(argv[1]) : 5;
+    childFunc((void *) levels);
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/namespaces/ns_child_exec.c b/namespaces/ns_child_exec.c
new file mode 100644 (file)
index 0000000..245b19c
--- /dev/null
@@ -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 <sched.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <stdio.h>
+
+#ifndef CLONE_NEWCGROUP         /* Added in Linux 4.6 */
+#define CLONE_NEWCGROUP         0x02000000
+#endif
+
+/* A simple error-handling function: print an error message based
+   on the value in 'errno' and terminate the calling process */
+
+#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
+                        } while (0)
+
+static void
+usage(char *pname)
+{
+    fprintf(stderr, "Usage: %s [options] cmd [arg...]\n", pname);
+    fprintf(stderr, "Options can be:\n");
+    fprintf(stderr, "    -C   new cgroup namespace\n");
+    fprintf(stderr, "    -i   new IPC namespace\n");
+    fprintf(stderr, "    -m   new mount namespace\n");
+    fprintf(stderr, "    -n   new network namespace\n");
+    fprintf(stderr, "    -p   new PID namespace\n");
+    fprintf(stderr, "    -u   new UTS namespace\n");
+    fprintf(stderr, "    -U   new user namespace\n");
+    fprintf(stderr, "    -v   Display verbose messages\n");
+    exit(EXIT_FAILURE);
+}
+
+static int              /* Start function for cloned child */
+childFunc(void *arg)
+{
+    char **argv = arg;
+
+    execvp(argv[0], argv);
+    errExit("execvp");
+}
+
+#define STACK_SIZE (1024 * 1024)
+
+int
+main(int argc, char *argv[])
+{
+    int flags, opt, verbose;
+    pid_t child_pid;
+    char *child_stack;
+
+    flags = 0;
+    verbose = 0;
+
+    /* Parse command-line options. The initial '+' character in
+       the final getopt() argument prevents GNU-style permutation
+       of command-line options. That's useful, since sometimes
+       the 'command' to be executed by this program itself
+       has command-line options. We don't want getopt() to treat
+       those as options to this program. */
+
+    while ((opt = getopt(argc, argv, "+CimnpuUv")) != -1) {
+        switch (opt) {
+        case 'C': flags |= CLONE_NEWCGROUP;     break;
+        case 'i': flags |= CLONE_NEWIPC;        break;
+        case 'm': flags |= CLONE_NEWNS;         break;
+        case 'n': flags |= CLONE_NEWNET;        break;
+        case 'p': flags |= CLONE_NEWPID;        break;
+        case 'u': flags |= CLONE_NEWUTS;        break;
+        case 'U': flags |= CLONE_NEWUSER;       break;
+        case 'v': verbose = 1;                  break;
+        default:  usage(argv[0]);
+        }
+    }
+
+    if (optind >= argc)
+        usage(argv[0]);
+
+    child_stack = malloc(STACK_SIZE);
+    if (child_stack == NULL)
+        errExit("malloc");
+
+    child_pid = clone(childFunc,
+                    child_stack + STACK_SIZE,
+                    flags | SIGCHLD, &argv[optind]);
+    if (child_pid == -1)
+        errExit("clone");
+
+    if (verbose)
+        printf("%s: PID of child created by clone() is %ld\n",
+                argv[0], (long) child_pid);
+
+    /* Parent falls through to here */
+
+    if (waitpid(child_pid, NULL, 0) == -1)      /* Wait for child */
+        errExit("waitpid");
+
+    if (verbose)
+        printf("%s: terminating\n", argv[0]);
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/namespaces/ns_exec.c b/namespaces/ns_exec.c
new file mode 100644 (file)
index 0000000..4f12765
--- /dev/null
@@ -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 <fcntl.h>
+#include <sched.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <sys/wait.h>
+
+/* A simple error-handling function: print an error message based
+   on the value in 'errno' and terminate the calling process */
+
+#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
+                        } while (0)
+
+int
+main(int argc, char *argv[])
+{
+    int fd;
+
+    if (argc < 3) {
+        fprintf(stderr, "%s /proc/PID/ns/FILE cmd [arg...]\n", argv[0]);
+        exit(EXIT_FAILURE);
+    }
+
+    fd = open(argv[1], O_RDONLY);   /* Get descriptor for namespace */
+    if (fd == -1)
+        errExit("open");
+
+    if (setns(fd, 0) == -1)         /* Join that namespace */
+        errExit("setns");
+
+    execvp(argv[2], &argv[2]);      /* Execute a command in namespace */
+    errExit("execvp");
+}
diff --git a/namespaces/ns_run.c b/namespaces/ns_run.c
new file mode 100644 (file)
index 0000000..0bb26d5
--- /dev/null
@@ -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 <fcntl.h>
+#include <sched.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/wait.h>
+
+/* A simple error-handling function: print an error message based
+   on the value in 'errno' and terminate the calling process */
+
+#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
+                        } while (0)
+
+static void
+usage(char *pname)
+{
+    fprintf(stderr, "Usage: %s [-f] [-n /proc/PID/ns/FILE] cmd [arg...]\n",
+            pname);
+    fprintf(stderr, "\t-f     Execute command in child process\n");
+    fprintf(stderr, "\t-n     Join specified namespace\n");
+
+    exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+    int fd, opt, do_fork;
+    pid_t pid;
+
+    /* Parse command-line options. The initial '+' character in
+       the final getopt() argument prevents GNU-style permutation
+       of command-line options. That's useful, since sometimes
+       the 'command' to be executed by this program itself
+       has command-line options. We don't want getopt() to treat
+       those as options to this program. */
+
+    do_fork = 0;
+    while ((opt = getopt(argc, argv, "+fn:")) != -1) {
+        switch (opt) {
+
+        case 'n':       /* Join a namespace */
+            fd = open(optarg, O_RDONLY); /* Get descriptor for namespace */
+            if (fd == -1)
+                errExit("open");
+
+            if (setns(fd, 0) == -1)      /* Join that namespace */
+                errExit("setns");
+            break;
+
+        case 'f':
+            do_fork = 1;
+            break;
+
+        default:
+            usage(argv[0]);
+        }
+    }
+
+    if (argc <= optind)
+        usage(argv[0]);
+
+    /* If the "-f" option was specified, execute the supplied command
+       in a child process. This is mainly useful when working with PID
+       namespaces, since setns() to a PID namespace only places
+       (subsequently created) child processes in the names, and
+       does not affect the PID namespace membership of the caller. */
+
+    if (do_fork) {
+        pid = fork();
+        if (pid == -1)
+            errExit("fork");
+
+        if (pid != 0) {                 /* Parent */
+            if (waitpid(-1, NULL, 0) == -1)     /* Wait for child */
+                errExit("waitpid");
+            exit(EXIT_SUCCESS);
+        }
+
+        /* Child falls through to code below */
+    }
+
+    execvp(argv[optind], &argv[optind]);
+    errExit("execvp");
+}
diff --git a/namespaces/orphan.c b/namespaces/orphan.c
new file mode 100644 (file)
index 0000000..e151a87
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+int
+main(int argc, char *argv[])
+{
+    pid_t pid;
+
+    pid = fork();
+    if (pid == -1) {
+        perror("fork");
+        exit(EXIT_FAILURE);
+    }
+
+    if (pid != 0) {             /* Parent */
+        printf("Parent (PID=%ld) created child with PID %ld\n",
+                (long) getpid(), (long) pid);
+        printf("Parent (PID=%ld; PPID=%ld) terminating\n",
+                (long) getpid(), (long) getppid());
+        exit(EXIT_SUCCESS);
+    }
+
+    /* Child falls through to here */
+
+    do {
+        usleep(100000);
+    } while (getppid() != 1);           /* Am I an orphan yet? */
+
+    printf("\nChild  (PID=%ld) now an orphan (parent PID=%ld)\n",
+            (long) getpid(), (long) getppid());
+
+    sleep(1);
+
+    printf("Child  (PID=%ld) terminating\n", (long) getpid());
+    _exit(EXIT_SUCCESS);
+}
diff --git a/namespaces/pidns_init_sleep.c b/namespaces/pidns_init_sleep.c
new file mode 100644 (file)
index 0000000..3732fe0
--- /dev/null
@@ -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 <sched.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+#include <sys/mount.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <signal.h>
+#include <stdio.h>
+
+/* A simple error-handling function: print an error message based
+   on the value in 'errno' and terminate the calling process */
+
+#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
+                        } while (0)
+
+static int              /* Start function for cloned child */
+childFunc(void *arg)
+{
+    printf("childFunc(): PID  = %ld\n", (long) getpid());
+    printf("childFunc(): PPID = %ld\n", (long) getppid());
+
+    char *mount_point = arg;
+
+    if (mount_point != NULL) {
+        mkdir(mount_point, 0555);       /* Create directory for mount point */
+        if (mount("proc", mount_point, "proc", 0, NULL) == -1)
+            errExit("mount");
+        printf("Mounting procfs at %s\n", mount_point);
+    }
+
+    execlp("sleep", "sleep", "600", (char *) NULL);
+    errExit("execlp");  /* Only reached if execlp() fails */
+}
+
+#define STACK_SIZE (1024 * 1024)
+
+int
+main(int argc, char *argv[])
+{
+    pid_t child_pid;
+    char *child_stack;
+
+    child_stack = malloc(STACK_SIZE);
+    if (child_stack == NULL)
+        errExit("malloc");
+
+    child_pid = clone(childFunc,
+                    child_stack + STACK_SIZE,   /* Points to start of
+                                                   downwardly growing stack */
+                    CLONE_NEWPID | SIGCHLD, argv[1]);
+
+    if (child_pid == -1)
+        errExit("clone");
+
+    printf("PID returned by clone(): %ld\n", (long) child_pid);
+
+    if (waitpid(child_pid, NULL, 0) == -1)      /* Wait for child */
+        errExit("waitpid");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/namespaces/simple_init.c b/namespaces/simple_init.c
new file mode 100644 (file)
index 0000000..29a0494
--- /dev/null
@@ -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 <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <wordexp.h>
+#include <errno.h>
+#include <sys/wait.h>
+#include <sys/mount.h>
+
+#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
+                        } while (0)
+
+static int verbose = 0;
+
+/* Display wait status (from waitpid() or similar) given in 'wstatus' */
+
+/* SIGCHLD handler: reap child processes as they change state */
+
+static void
+child_handler(int sig)
+{
+    pid_t pid;
+    int wstatus;
+
+    /* WUNTRACED and WCONTINUED allow waitpid() to catch stopped and
+       continued children (in addition to terminated children) */
+
+    while ((pid = waitpid(-1, &wstatus,
+                          WNOHANG | WUNTRACED | WCONTINUED)) != 0) {
+        if (pid == -1) {
+            if (errno == ECHILD)        /* No more children */
+                break;
+            else
+                perror("waitpid");      /* Unexpected error */
+        }
+
+        if (verbose)
+            printf("\tinit: SIGCHLD handler: PID %ld terminated\n",
+                    (long) pid);
+    }
+}
+
+/* Perform word expansion on string in 'cmd', allocating and
+   returning a vector of words on success or NULL on failure */
+
+static char **
+expand_words(char *cmd)
+{
+    char **arg_vec;
+    int s;
+    wordexp_t pwordexp;
+
+    s = wordexp(cmd, &pwordexp, 0);
+    if (s != 0) {
+        fprintf(stderr, "Word expansion failed.\n"
+                        "\tNote that only simple "
+                        "commands plus arguments are supported\n"
+                        "\t(no pipelines, I/O redirection, and so on)\n");
+        return NULL;
+    }
+
+    arg_vec = calloc(pwordexp.we_wordc + 1, sizeof(char *));
+    if (arg_vec == NULL)
+        errExit("calloc");
+
+    for (s = 0; s < pwordexp.we_wordc; s++)
+        arg_vec[s] = pwordexp.we_wordv[s];
+
+    arg_vec[pwordexp.we_wordc] = NULL;
+
+    return arg_vec;
+}
+
+static void
+usage(char *pname)
+{
+    fprintf(stderr, "Usage: %s [-v] [-p proc-mount]\n", pname);
+    fprintf(stderr, "\t-v              Provide verbose logging\n");
+    fprintf(stderr, "\t-p proc-mount   Mount a procfs at specified path\n");
+
+    exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+    struct sigaction sa;
+#define CMD_SIZE 10000
+    char cmd[CMD_SIZE];
+    pid_t pid;
+    int opt;
+    char *proc_path;
+
+    proc_path = NULL;
+    while ((opt = getopt(argc, argv, "p:v")) != -1) {
+        switch (opt) {
+        case 'p': proc_path = optarg;   break;
+        case 'v': verbose = 1;          break;
+        default:  usage(argv[0]);
+        }
+    }
+
+    sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
+    sigemptyset(&sa.sa_mask);
+    sa.sa_handler = child_handler;
+    if (sigaction(SIGCHLD, &sa, NULL) == -1)
+        errExit("sigaction");
+
+    if (verbose)
+        printf("\tinit: my PID is %ld\n", (long) getpid());
+
+    /* Performing terminal operations while not being the foreground
+       process group for the terminal generates a SIGTTOU that stops the
+       process.  However our init "shell" needs to be able to perform
+       such operations (just like a normal shell), so we ignore that
+       signal, which allows the operations to proceed successfully. */
+
+    signal(SIGTTOU, SIG_IGN);
+    /* Become leader of a new process group and make that process
+       group the foreground process group for the terminal */
+
+    if (setpgid(0, 0) == -1)
+        errExit("setpgid");;
+    if (tcsetpgrp(STDIN_FILENO, getpgrp()) == -1)
+        errExit("tcsetpgrp-child");
+
+    /* If the user asked to mount a procfs, mount it at the specified path */
+
+    if (proc_path != NULL) {
+
+        /* Some distributions enable mount propagation (mount --make-shared)
+           by default. This would cause the mount that we create here to
+           propagate to other namespaces. If we were mounting the
+           procfs for this new PID namespace at "/proc" (which is typical),
+           then this would hide the original "/proc" mount point in the
+           intial namespace, which we probably don't want, since it will
+           confuse a lot of system tools. To prevent propagation from
+           occurring, we need to mark the mount point either as a slave
+           mount or as a private mount.
+
+           For further information on this topic, see the kernel source
+           file Documentation/filesystems/sharedsubtree.txt and the
+           mount(8) man page */
+
+        if (verbose)
+            printf("Making %s a private mount\n", proc_path);
+
+        /* EINVAL is the case that occurs if 'proc_path' exists but is
+           not (yet) a mount point */
+
+        if (mount("none", proc_path, NULL, MS_SLAVE, NULL) == -1 &&
+                errno != EINVAL)
+            perror("mount-make-slave-/");
+
+        if (verbose)
+            printf("Mounting procfs at %s\n", proc_path);
+
+        if (mount("proc", proc_path, "proc", 0, NULL) == -1)
+            errExit("mount-procfs");
+    }
+
+    /* Loop executing "shell" commands. Note that our shell facility is
+       very simple: it handles simple commands with arguments, and
+       performs wordexp() expansions (globbing, variable and command
+       substitution, tilde expansion, and quote removal). Complex
+       commands (pipelines, ||, &&) and I/O redirections, and
+       standard shell features are not supported. */
+
+    while (1) {
+
+        /* Read a shell command; exit on end of file */
+
+        printf("init$ ");
+        if (fgets(cmd, CMD_SIZE, stdin) == NULL) {
+            if (verbose)
+                printf("\tinit: exiting");
+            printf("\n");
+            break;
+        }
+
+        if (cmd[strlen(cmd) - 1] == '\n')
+            cmd[strlen(cmd) - 1] = '\0';        /* Strip trailing '\n' */
+
+        if (strlen(cmd) == 0)
+            continue;           /* Ignore empty commands */
+
+        pid = fork();           /* Create child process */
+        if (pid == -1) {
+            perror("fork");
+            break;
+        }
+
+        if (pid == 0) {         /* Child */
+            char **arg_vec;
+
+            arg_vec = expand_words(cmd);
+            if (arg_vec == NULL)        /* Word expansion failed */
+                exit(EXIT_FAILURE);
+
+            /* Make child the leader of a new process group and
+               make that process group the foreground process
+               group for the terminal */
+
+            if (setpgid(0, 0) == -1)
+                errExit("setpgid");;
+            if (tcsetpgrp(STDIN_FILENO, getpgrp()) == -1)
+                errExit("tcsetpgrp-child");
+
+            /* Child executes shell command and terminates */
+
+            execvp(arg_vec[0], arg_vec);
+            errExit("execvp");          /* Only reached if execvp() fails */
+        }
+
+        /* Parent falls through to here */
+
+        if (verbose)
+            printf("\tinit: created child %ld\n", (long) pid);
+
+        pause();                /* Will be interrupted by signal handler */
+
+        /* After child changes state, ensure that the 'init' program
+           is the foreground process group for the terminal */
+
+        if (tcsetpgrp(STDIN_FILENO, getpgrp()) == -1)
+            errExit("tcsetpgrp-parent");
+    }
+
+    /* If we mounted a procfs earlier, unmount it before terminating */
+
+    if (proc_path != NULL) {
+        if (verbose)
+            printf("Unmounting procfs at %s\n", proc_path);
+        if (umount(proc_path) == -1)
+            errExit("umount-procfs");
+    }
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/namespaces/t_setns_userns.c b/namespaces/t_setns_userns.c
new file mode 100644 (file)
index 0000000..91b8dc8
--- /dev/null
@@ -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 <fcntl.h>
+#include <sched.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/capability.h>
+
+#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
+                        } while (0)
+
+static void
+display_creds_and_caps(char *msg)
+{
+    cap_t caps;
+
+    printf("%seUID = %ld;  eGID = %ld;  ", msg,
+            (long) geteuid(), (long) getegid());
+
+    caps = cap_get_proc();
+    printf("capabilities: %s\n", cap_to_text(caps, NULL));
+}
+
+int
+main(int argc, char *argv[])
+{
+    int fd;
+
+    if (argc < 2) {
+        fprintf(stderr, "Usage: %s /proc/PID/ns/FILE\n", argv[0]);
+        exit(EXIT_FAILURE);
+    }
+
+    display_creds_and_caps("Initial:\n");
+    printf("\n");
+
+    fd = open(argv[1], O_RDONLY); /* Get descriptor for namespace */
+    if (fd == -1)
+        errExit("open");
+
+    if (setns(fd, CLONE_NEWUSER) == -1)  /* Join that namespace */
+        errExit("setns-1");
+
+    display_creds_and_caps("After setns():\n");
+    exit(EXIT_SUCCESS);
+}
diff --git a/namespaces/unshare.c b/namespaces/unshare.c
new file mode 100644 (file)
index 0000000..e55fb13
--- /dev/null
@@ -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 <sched.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/wait.h>
+
+#ifndef CLONE_NEWCGROUP         /* Added in Linux 4.6 */
+#define CLONE_NEWCGROUP         0x02000000
+#endif
+
+/* A simple error-handling function: print an error message based
+   on the value in 'errno' and terminate the calling process */
+
+#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
+                        } while (0)
+
+static void
+usage(char *pname)
+{
+    fprintf(stderr, "Usage: %s [options] cmd [arg...]\n", pname);
+    fprintf(stderr, "Options can be:\n");
+    fprintf(stderr, "    -f   fork() before executing cmd "
+            "(useful when unsharing PID namespace)\n");
+    fprintf(stderr, "    -C   unshare cgroup namespace\n");
+    fprintf(stderr, "    -i   unshare IPC namespace\n");
+    fprintf(stderr, "    -m   unshare mount namespace\n");
+    fprintf(stderr, "    -n   unshare network namespace\n");
+    fprintf(stderr, "    -p   unshare PID namespace\n");
+    fprintf(stderr, "    -u   unshare UTS namespace\n");
+    fprintf(stderr, "    -U   unshare user namespace\n");
+    exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+    int flags, do_fork, opt;
+
+    flags = 0;
+    do_fork = 0;
+    while ((opt = getopt(argc, argv, "CfimnpuU")) != -1) {
+        switch (opt) {
+        case 'f': do_fork = 1;                  break;
+        case 'C': flags |= CLONE_NEWCGROUP;     break;
+        case 'i': flags |= CLONE_NEWIPC;        break;
+        case 'm': flags |= CLONE_NEWNS;         break;
+        case 'n': flags |= CLONE_NEWNET;        break;
+        case 'p': flags |= CLONE_NEWPID;        break;
+        case 'u': flags |= CLONE_NEWUTS;        break;
+        case 'U': flags |= CLONE_NEWUSER;       break;
+        default:  usage(argv[0]);
+        }
+    }
+
+    if (optind >= argc)
+        usage(argv[0]);
+
+    if (unshare(flags) == -1)
+        errExit("unshare");
+
+    /* If we are unsharing the PID namespace, then the caller is *not*
+       moved into the new namespace. Instead, only the children are moved
+       into the namespace. Therefore, we support an option that causes
+       the program to call fork() before executing the specified program,
+       in order to create a new child that will be created in a new PID
+       namespace. */
+
+    if (do_fork) {
+        if (fork()) {
+            wait(NULL);         /* Parent waits for child to complete */
+            exit(EXIT_SUCCESS);
+        }
+
+        /* Child falls through to execute command */
+    }
+
+    execvp(argv[optind], &argv[optind]);
+    errExit("execvp");
+}
diff --git a/namespaces/userns_child_exec.c b/namespaces/userns_child_exec.c
new file mode 100644 (file)
index 0000000..16cc50b
--- /dev/null
@@ -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 <sched.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <limits.h>
+#include <errno.h>
+
+#ifndef CLONE_NEWCGROUP         /* Added in Linux 4.6 */
+#define CLONE_NEWCGROUP         0x02000000
+#endif
+
+/* A simple error-handling function: print an error message based
+   on the value in 'errno' and terminate the calling process */
+
+#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
+                        } while (0)
+
+struct child_args {
+    char **argv;        /* Command to be executed by child, with args */
+    int    pipe_fd[2];  /* Pipe used to synchronize parent and child */
+};
+
+static int verbose;
+
+static void
+usage(char *pname)
+{
+    fprintf(stderr, "Usage: %s [options] cmd [arg...]\n\n", pname);
+    fprintf(stderr, "Create a child process that executes a shell "
+            "command in a new user namespace,\n"
+            "and possibly also other new namespace(s).\n\n");
+    fprintf(stderr, "Options can be:\n\n");
+#define fpe(str) fprintf(stderr, "    %s", str);
+    fpe("-C          New cgroup namespace\n");
+    fpe("-i          New IPC namespace\n");
+    fpe("-m          New mount namespace\n");
+    fpe("-n          New network namespace\n");
+    fpe("-p          New PID namespace\n");
+    fpe("-u          New UTS namespace\n");
+    fpe("-U          New user namespace\n");
+    fpe("-M uid_map  Specify UID map for user namespace\n");
+    fpe("-G gid_map  Specify GID map for user namespace\n");
+    fpe("-D          Do not write \"deny\" to /proc/PID/setgroups before\n");
+    fpe("            updating GID map\n");
+    fpe("-z          Map user's UID and GID to 0 in user namespace\n");
+    fpe("            (equivalent to: -M '0 <uid> 1' -G '0 <gid> 1')\n");
+    fpe("-v          Display verbose messages\n");
+    fpe("\n");
+    fpe("If -z, -M, or -G is specified, -U is required.\n");
+    fpe("It is not permitted to specify both -z and either -M or -G.\n");
+    fpe("\n");
+    fpe("Map strings for -M and -G consist of records of the form:\n");
+    fpe("\n");
+    fpe("    ID-inside-ns   ID-outside-ns   len\n");
+    fpe("\n");
+    fpe("A map string can contain multiple records, separated"
+        " by commas;\n");
+    fpe("the commas are replaced by newlines before writing"
+        " to map files.\n");
+
+    exit(EXIT_FAILURE);
+}
+
+/* Update the mapping file 'map_file', with the value provided in
+   'mapping', a string that defines a UID or GID mapping. A UID or
+   GID mapping consists of one or more newline-delimited records
+   of the form:
+
+       ID_inside-ns    ID-outside-ns   length
+
+   Requiring the user to supply a string that contains newlines is
+   of course inconvenient for command-line use. Thus, we permit the
+   use of commas to delimit records in this string, and replace them
+   with newlines before writing the string to the file. */
+
+static void
+update_map(char *mapping, char *map_file)
+{
+    int fd, j;
+    size_t map_len;     /* Length of 'mapping' */
+
+    /* Replace commas in mapping string with newlines */
+
+    map_len = strlen(mapping);
+    for (j = 0; j < map_len; j++)
+        if (mapping[j] == ',')
+            mapping[j] = '\n';
+
+    fd = open(map_file, O_RDWR);
+    if (fd == -1) {
+        fprintf(stderr, "ERROR: open %s: %s\n", map_file, strerror(errno));
+        return;
+        //exit(EXIT_FAILURE);
+    }
+
+    if (write(fd, mapping, map_len) != map_len) {
+        fprintf(stderr, "ERROR: write %s: %s\n", map_file, strerror(errno));
+        //exit(EXIT_FAILURE);
+    }
+
+    close(fd);
+}
+
+/* Linux 3.19 made a change in the handling of setgroups(2) and the
+   'gid_map' file to address a security issue. The issue allowed
+   *unprivileged* users to employ user namespaces in order to drop
+   groups from their supplementary group list using setgroups(2).
+   (Formerly, this possibility was available only to privileged
+   processes.) The effect was to create possibilities for unprivileged
+   process to access files for which they would not otherwise have had
+   permission. (For further details, see the user_namespaces(7) man
+   page.)
+
+   The upshot of the 3.19 changes is that in order for a process lacking
+   suitable privileges (i.e., one that lacks the CAP_SETGID capability
+   in the parent user namespace) to update the 'gid_maps' file, use of the
+   setgroups() system call in this user namespace must first be
+   disabled by writing "deny" to one of the /proc/PID/setgroups files
+   for this namespace.  That is the purpose of the following function. */
+
+static void
+proc_setgroups_write(pid_t child_pid, char *str)
+{
+    char setgroups_path[PATH_MAX];
+    int fd;
+
+    snprintf(setgroups_path, PATH_MAX, "/proc/%ld/setgroups",
+            (long) child_pid);
+
+    fd = open(setgroups_path, O_RDWR);
+    if (fd == -1) {
+
+        /* We may be on a system that doesn't support
+           /proc/PID/setgroups. In that case, the file won't exist,
+           and the system won't impose the restrictions that Linux 3.19
+           added. That's fine: we don't need to do anything in order
+           to permit 'gid_map' to be updated.
+
+           However, if the error from open() was something other than
+           the ENOENT error that is expected for that case, let the
+           user know. */
+
+        if (errno != ENOENT)
+            fprintf(stderr, "ERROR: open %s: %s\n", setgroups_path,
+                strerror(errno));
+        return;
+    }
+
+    if (verbose)
+        printf("Writing to \"%s\" to %s\n", str, setgroups_path);
+
+    if (write(fd, str, strlen(str)) == -1)
+        fprintf(stderr, "ERROR: write %s: %s\n", setgroups_path,
+            strerror(errno));
+
+    close(fd);
+}
+
+static int              /* Start function for cloned child */
+childFunc(void *arg)
+{
+    struct child_args *args = (struct child_args *) arg;
+    char ch;
+
+    /* Wait until the parent has updated the UID and GID mappings.
+       See the comment in main(). We wait for end of file on a
+       pipe that will be closed by the parent process once it has
+       updated the mappings. */
+
+    close(args->pipe_fd[1]);    /* Close our descriptor for the write
+                                   end of the pipe so that we see EOF
+                                   when parent closes its descriptor */
+    if (read(args->pipe_fd[0], &ch, 1) != 0) {
+        fprintf(stderr,
+                "Failure in child: read from pipe returned != 0\n");
+        exit(EXIT_FAILURE);
+    }
+
+    close(args->pipe_fd[0]);    /* We no longer need the pipe */
+
+    /* Execute a shell command */
+
+    if (verbose)
+        printf("About to exec: %s\n", args->argv[0]);
+
+    execvp(args->argv[0], args->argv);
+    errExit("execvp");
+}
+
+#define STACK_SIZE (1024 * 1024)
+
+int
+main(int argc, char *argv[])
+{
+    int flags, opt, map_zero, deny_setgroups;
+    pid_t child_pid;
+    struct child_args args;
+    char *uid_map, *gid_map;
+    const int MAP_BUF_SIZE = 100;
+    char map_buf[MAP_BUF_SIZE];
+    char map_path[PATH_MAX];
+    char *child_stack;
+
+    /* Parse command-line options. The initial '+' character in
+       the final getopt(3) argument prevents GNU-style permutation
+       of command-line options. Preventing that is useful, since
+       sometimes the 'command' to be executed by this program itself
+       has command-line options. We don't want getopt() to treat
+       those as options to this program. */
+
+    flags = 0;
+    verbose = 0;
+    gid_map = NULL;
+    uid_map = NULL;
+    map_zero = 0;
+    deny_setgroups = 1;
+    while ((opt = getopt(argc, argv, "+CimnpuvzM:G:DU")) != -1) {
+        switch (opt) {
+        case 'C': flags |= CLONE_NEWCGROUP;     break;
+        case 'i': flags |= CLONE_NEWIPC;        break;
+        case 'm': flags |= CLONE_NEWNS;         break;
+        case 'n': flags |= CLONE_NEWNET;        break;
+        case 'p': flags |= CLONE_NEWPID;        break;
+        case 'u': flags |= CLONE_NEWUTS;        break;
+        case 'v': verbose = 1;                  break;
+        case 'z': map_zero = 1;                 break;
+        case 'M': uid_map = optarg;             break;
+        case 'G': gid_map = optarg;             break;
+        case 'D': deny_setgroups = 0;           break;
+        case 'U': flags |= CLONE_NEWUSER;       break;
+        default:  usage(argv[0]);
+        }
+    }
+
+    /* -M or -G without -U is nonsensical */
+
+    if (((uid_map != NULL || gid_map != NULL || map_zero) &&
+                !(flags & CLONE_NEWUSER)) ||
+            (map_zero && (uid_map != NULL || gid_map != NULL)))
+        usage(argv[0]);
+
+    if (optind >= argc)
+        usage(argv[0]);
+
+    args.argv = &argv[optind];
+
+    /* We use a pipe to synchronize the parent and child, in order to
+       ensure that the parent sets the UID and GID maps before the child
+       calls execve(). This ensures that the child maintains its
+       capabilities during the execve() in the common case where we
+       want to map the child's effective user ID to 0 in the new user
+       namespace. Without this synchronization, the child would lose
+       its capabilities if it performed an execve() with nonzero
+       user IDs (see the capabilities(7) man page for details of the
+       transformation of a process's capabilities during execve()). */
+
+    if (pipe(args.pipe_fd) == -1)
+        errExit("pipe");
+
+    /* Create the child in new namespace(s) */
+
+    child_stack = malloc(STACK_SIZE);
+    if (child_stack == NULL)
+        errExit("malloc");
+
+    child_pid = clone(childFunc, child_stack + STACK_SIZE,
+                      flags | SIGCHLD, &args);
+    if (child_pid == -1)
+        errExit("clone");
+
+    /* Parent falls through to here */
+
+    if (verbose)
+        printf("%s: PID of child created by clone() is %ld\n",
+                argv[0], (long) child_pid);
+
+    /* Update the UID and GID maps in the child */
+
+    if (uid_map != NULL || map_zero) {
+        snprintf(map_path, PATH_MAX, "/proc/%ld/uid_map",
+                (long) child_pid);
+        if (map_zero) {
+            snprintf(map_buf, MAP_BUF_SIZE, "0 %ld 1", (long) getuid());
+            uid_map = map_buf;
+        }
+        update_map(uid_map, map_path);
+    }
+
+    if (gid_map != NULL || map_zero) {
+        if (deny_setgroups)
+            proc_setgroups_write(child_pid, "deny");
+
+        snprintf(map_path, PATH_MAX, "/proc/%ld/gid_map",
+                (long) child_pid);
+        if (map_zero) {
+            snprintf(map_buf, MAP_BUF_SIZE, "0 %ld 1", (long) getgid());
+            gid_map = map_buf;
+        }
+        update_map(gid_map, map_path);
+    }
+
+    /* Close the write end of the pipe, to signal to the child that we
+       have updated the UID and GID maps */
+
+    close(args.pipe_fd[1]);
+
+    if (waitpid(child_pid, NULL, 0) == -1)      /* Wait for child */
+        errExit("waitpid");
+
+    if (verbose)
+        printf("%s: terminating\n", argv[0]);
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/namespaces/userns_overview.go b/namespaces/userns_overview.go
new file mode 100644 (file)
index 0000000..24f84b7
--- /dev/null
@@ -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 (file)
index 0000000..e6ed206
--- /dev/null
@@ -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 <fcntl.h>
+#include <sched.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/wait.h>
+#include <sys/capability.h>
+
+/* A simple error-handling function: print an error message based
+   on the value in 'errno' and terminate the calling process */
+
+#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
+                        } while (0)
+
+static void
+display_creds_and_caps(char *msg)
+{
+    cap_t caps;
+
+    printf("%s eUID = %ld;  eGID = %ld;  ", msg,
+            (long) geteuid(), (long) getegid());
+
+    caps = cap_get_proc();
+    printf("capabilities: %s\n", cap_to_text(caps, NULL));
+}
+
+/* Try to join the user namespace identified by the file
+   descriptor 'fd'. 'pname' is a per-process string that
+   the caller can use to distinguish information messages
+   displayed by this function */
+
+static void
+test_setns(char *pname, int fd)
+{
+    char path[PATH_MAX];
+    ssize_t s;
+
+    /* Display caller's user namespace ID */
+
+    s = readlink("/proc/self/ns/user", path, PATH_MAX);
+    if (s == -1)
+        errExit("readlink");
+
+    printf("%s readlink(\"/proc/self/ns/user\") ==> %s\n", pname, path);
+
+    /* Attempt to join the user namespace specified by 'fd' */
+
+    if (setns(fd, CLONE_NEWUSER) == -1)
+        printf("%s setns() failed: %s\n", pname, strerror(errno));
+    else {
+        printf("%s setns() succeeded\n", pname);
+        display_creds_and_caps(pname);
+    }
+}
+
+static int              /* Start function for cloned child */
+childFunc(void *arg)
+{
+    long fd = (long) arg;
+
+    usleep(100000);     /* Avoid intermingling with parent's output */
+
+    /* Test whether setns() is possible from the child user namespace */
+
+    test_setns("child: ", fd);
+
+    return 0;
+}
+
+#define STACK_SIZE (1024 * 1024)
+
+int
+main(int argc, char *argv[])
+{
+    pid_t child_pid;
+    long fd;
+    char *child_stack;
+
+    if (argc != 2) {
+        fprintf(stderr, "Usage: %s /proc/PID/ns/user]\n", argv[0]);
+        exit(EXIT_FAILURE);
+    }
+
+    /* Open user namespace file specified on command line */
+
+    fd = open(argv[1], O_RDONLY);
+    if (fd == -1)
+        errExit("open");
+
+    /* Create child process in new user namespace */
+
+    child_stack = malloc(STACK_SIZE);
+    if (child_stack == NULL)
+        errExit("malloc");
+
+    child_pid = clone(childFunc, child_stack + STACK_SIZE,
+                      CLONE_NEWUSER | SIGCHLD, (void *) fd);
+    if (child_pid == -1)
+        errExit("clone");
+
+    /* Test whether setns() is possible from the parent user namespace */
+
+    test_setns("parent:", fd);
+    printf("\n");
+
+    if (waitpid(child_pid, NULL, 0) == -1)      /* Wait for child */
+        errExit("waitpid");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/pgsjc/Makefile b/pgsjc/Makefile
new file mode 100644 (file)
index 0000000..ee389c7
--- /dev/null
@@ -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 (file)
index 0000000..62e2041
--- /dev/null
@@ -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 <unistd.h>
+#include <signal.h>
+#include "tlpi_hdr.h"
+
+static void
+handler(int sig)
+{
+}
+
+int
+main(int argc, char *argv[])
+{
+    pid_t childPid;
+    struct sigaction sa;
+
+    setbuf(stdout, NULL);       /* Make stdout unbuffered */
+
+    sigemptyset(&sa.sa_mask);
+    sa.sa_flags = 0;
+    sa.sa_handler = handler;
+    if (sigaction(SIGHUP, &sa, NULL) == -1)
+        errExit("sigaction");
+
+    childPid = fork();
+    if (childPid == -1)
+        errExit("fork");
+
+    if (childPid == 0 && argc > 1)
+        if (setpgid(0, 0) == -1)        /* Move to new process group */
+            errExit("setpgid");
+
+    printf("PID=%ld; PPID=%ld; PGID=%ld; SID=%ld\n", (long) getpid(),
+            (long) getppid(), (long) getpgrp(), (long) getsid(0));
+
+    alarm(60);                  /* An unhandled SIGALRM ensures this process
+                                   will die if nothing else terminates it */
+    for(;;) {                   /* Wait for signals */
+        pause();
+        printf("%ld: caught SIGHUP\n", (long) getpid());
+    }
+}
diff --git a/pgsjc/disc_SIGHUP.c b/pgsjc/disc_SIGHUP.c
new file mode 100644 (file)
index 0000000..e10f3de
--- /dev/null
@@ -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 <string.h> */
+#include <string.h>
+#include <signal.h>
+#include "tlpi_hdr.h"
+
+static void             /* Handler for SIGHUP */
+handler(int sig)
+{
+    printf("PID %ld: caught signal %2d (%s)\n", (long) getpid(),
+            sig, strsignal(sig));
+                        /* UNSAFE (see Section 21.1.2) */
+}
+
+int
+main(int argc, char *argv[])
+{
+    pid_t parentPid, childPid;
+    int j;
+    struct sigaction sa;
+
+    if (argc < 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s {d|s}... [ > sig.log 2>&1 ]\n", argv[0]);
+
+    setbuf(stdout, NULL);               /* Make stdout unbuffered */
+
+    parentPid = getpid();
+    printf("PID of parent process is:       %ld\n", (long) parentPid);
+    printf("Foreground process group ID is: %ld\n",
+            (long) tcgetpgrp(STDIN_FILENO));
+
+    for (j = 1; j < argc; j++) {        /* Create child processes */
+        childPid = fork();
+        if (childPid == -1)
+            errExit("fork");
+
+        if (childPid == 0) {            /* If child... */
+            if (argv[j][0] == 'd')      /* 'd' --> to different pgrp */
+                if (setpgid(0, 0) == -1)
+                    errExit("setpgid");
+
+            sigemptyset(&sa.sa_mask);
+            sa.sa_flags = 0;
+            sa.sa_handler = handler;
+            if (sigaction(SIGHUP, &sa, NULL) == -1)
+                errExit("sigaction");
+            break;                      /* Child exits loop */
+        }
+    }
+
+    /* All processes fall through to here */
+
+    alarm(60);      /* Ensure each process eventually terminates */
+
+    printf("PID=%ld PGID=%ld\n", (long) getpid(), (long) getpgrp());
+    for (;;)
+        pause();        /* Wait for signals */
+}
diff --git a/pgsjc/handling_SIGTSTP.c b/pgsjc/handling_SIGTSTP.c
new file mode 100644 (file)
index 0000000..c1d8079
--- /dev/null
@@ -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 <signal.h>
+#include "tlpi_hdr.h"
+
+static void                             /* Handler for SIGTSTP */
+tstpHandler(int sig)
+{
+    sigset_t tstpMask, prevMask;
+    int savedErrno;
+    struct sigaction sa;
+
+    savedErrno = errno;                 /* In case we change 'errno' here */
+
+    printf("Caught SIGTSTP\n");         /* UNSAFE (see Section 21.1.2) */
+
+    if (signal(SIGTSTP, SIG_DFL) == SIG_ERR)
+        errExit("signal");              /* Set handling to default */
+
+    raise(SIGTSTP);                     /* Generate a further SIGTSTP */
+
+    /* Unblock SIGTSTP; the pending SIGTSTP immediately suspends the program */
+
+    sigemptyset(&tstpMask);
+    sigaddset(&tstpMask, SIGTSTP);
+    if (sigprocmask(SIG_UNBLOCK, &tstpMask, &prevMask) == -1)
+        errExit("sigprocmask");
+
+    /* Execution resumes here after SIGCONT */
+
+    if (sigprocmask(SIG_SETMASK, &prevMask, NULL) == -1)
+        errExit("sigprocmask");         /* Reblock SIGTSTP */
+
+    sigemptyset(&sa.sa_mask);           /* Reestablish handler */
+    sa.sa_flags = SA_RESTART;
+    sa.sa_handler = tstpHandler;
+    if (sigaction(SIGTSTP, &sa, NULL) == -1)
+        errExit("sigaction");
+
+    printf("Exiting SIGTSTP handler\n");
+    errno = savedErrno;
+}
+
+int
+main(int argc, char *argv[])
+{
+    struct sigaction sa;
+
+    /* Only establish handler for SIGTSTP if it is not being ignored */
+
+    if (sigaction(SIGTSTP, NULL, &sa) == -1)
+        errExit("sigaction");
+
+    if (sa.sa_handler != SIG_IGN) {
+        sigemptyset(&sa.sa_mask);
+        sa.sa_flags = SA_RESTART;
+        sa.sa_handler = tstpHandler;
+        if (sigaction(SIGTSTP, &sa, NULL) == -1)
+            errExit("sigaction");
+    }
+
+    for (;;) {                          /* Wait for signals */
+        pause();
+        printf("Main\n");
+    }
+}
diff --git a/pgsjc/job_mon.c b/pgsjc/job_mon.c
new file mode 100644 (file)
index 0000000..9a089f7
--- /dev/null
@@ -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 <string.h> */
+#include <string.h>
+#include <signal.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+static int cmdNum;      /* Our position in pipeline */
+
+static void             /* Handler for various signals */
+handler(int sig)
+{
+    /* UNSAFE: This handler uses non-async-signal-safe functions
+       (fprintf(), strsignal(); see Section 21.1.2) */
+
+    if (getpid() == getpgrp())          /* If process group leader */
+        fprintf(stderr, "Terminal FG process group: %ld\n",
+                (long) tcgetpgrp(STDERR_FILENO));
+    fprintf(stderr, "Process %ld (%d) received signal %d (%s)\n",
+                (long) getpid(), cmdNum, sig, strsignal(sig));
+
+    /* If we catch SIGTSTP, it won't actually stop us. Therefore we
+       raise SIGSTOP so we actually get stopped. */
+
+    if (sig == SIGTSTP)
+        raise(SIGSTOP);
+}
+
+int
+main(int argc, char *argv[])
+{
+    struct sigaction sa;
+
+    sigemptyset(&sa.sa_mask);
+    sa.sa_flags = SA_RESTART;
+    sa.sa_handler = handler;
+    if (sigaction(SIGINT, &sa, NULL) == -1)
+        errExit("sigaction");
+    if (sigaction(SIGTSTP, &sa, NULL) == -1)
+        errExit("sigaction");
+    if (sigaction(SIGCONT, &sa, NULL) == -1)
+        errExit("sigaction");
+
+    /* If stdin is a terminal, this is the first process in pipeline:
+       print a heading and initialize message to be sent down pipe */
+
+    if (isatty(STDIN_FILENO)) {
+        fprintf(stderr, "Terminal FG process group: %ld\n",
+                (long) tcgetpgrp(STDIN_FILENO));
+        fprintf(stderr, "Command   PID  PPID  PGRP   SID\n");
+        cmdNum = 0;
+
+    } else {            /* Not first in pipeline, so read message from pipe */
+        if (read(STDIN_FILENO, &cmdNum, sizeof(cmdNum)) <= 0)
+            fatal("read got EOF or error");
+    }
+
+    cmdNum++;
+    fprintf(stderr, "%4d    %5ld %5ld %5ld %5ld\n", cmdNum,
+            (long) getpid(), (long) getppid(),
+            (long) getpgrp(), (long) getsid(0));
+
+    /* If not the last process, pass a message to the next process */
+
+    if (!isatty(STDOUT_FILENO))   /* If not tty, then should be pipe */
+        if (write(STDOUT_FILENO, &cmdNum, sizeof(cmdNum)) == -1)
+            errMsg("write");
+
+    for(;;)             /* Wait for signals */
+        pause();
+}
diff --git a/pgsjc/orphaned_pgrp_SIGHUP.c b/pgsjc/orphaned_pgrp_SIGHUP.c
new file mode 100644 (file)
index 0000000..e5a054a
--- /dev/null
@@ -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 <string.h> */
+#include <string.h>
+#include <signal.h>
+#include "tlpi_hdr.h"
+
+static void             /* Signal handler */
+handler(int sig)
+{
+    printf("PID=%ld: caught signal %d (%s)\n", (long) getpid(),
+            sig, strsignal(sig));       /* UNSAFE (see Section 21.1.2) */
+}
+
+int
+main(int argc, char *argv[])
+{
+    int j;
+    struct sigaction sa;
+
+    if (argc < 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s {s|p} ...\n", argv[0]);
+
+    setbuf(stdout, NULL);               /* Make stdout unbuffered */
+
+    sigemptyset(&sa.sa_mask);
+    sa.sa_flags = 0;
+    sa.sa_handler = handler;
+    if (sigaction(SIGHUP, &sa, NULL) == -1)
+        errExit("sigaction");
+    if (sigaction(SIGCONT, &sa, NULL) == -1)
+        errExit("sigaction");
+
+    printf("parent: PID=%ld, PPID=%ld, PGID=%ld, SID=%ld\n",
+            (long) getpid(), (long) getppid(),
+            (long) getpgrp(), (long) getsid(0));
+
+    /* Create one child for each command-line argument */
+
+    for (j = 1; j < argc; j++) {
+        switch (fork()) {
+        case -1:
+            errExit("fork");
+
+        case 0:         /* Child */
+            printf("child:  PID=%ld, PPID=%ld, PGID=%ld, SID=%ld\n",
+                    (long) getpid(), (long) getppid(),
+                    (long) getpgrp(), (long) getsid(0));
+
+            if (argv[j][0] == 's') {    /* Stop via signal */
+                printf("PID=%ld stopping\n", (long) getpid());
+                raise(SIGSTOP);
+            } else {                    /* Wait for signal */
+                alarm(60);              /* So we die if not SIGHUPed */
+                printf("PID=%ld pausing\n", (long) getpid());
+                pause();
+            }
+
+            _exit(EXIT_SUCCESS);
+
+        default:        /* Parent carries on round loop */
+            break;
+        }
+    }
+
+    /* Parent falls through to here after creating all children */
+
+    sleep(3);                           /* Give children a chance to start */
+    printf("parent exiting\n");
+    exit(EXIT_SUCCESS);                 /* And orphan them and their group */
+}
diff --git a/pgsjc/t_setsid.c b/pgsjc/t_setsid.c
new file mode 100644 (file)
index 0000000..0a5a9c6
--- /dev/null
@@ -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 <unistd.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    if (fork() != 0)            /* Exit if parent, or on error */
+        _exit(EXIT_SUCCESS);
+
+    if (setsid() == -1)
+        errExit("setsid");
+
+    printf("PID=%ld, PGID=%ld, SID=%ld\n", (long) getpid(),
+            (long) getpgrp(), (long) getsid(0));
+
+    /* Following should fail, since we don't have a controlling terminal */
+
+    if (open("/dev/tty", O_RDWR) == -1)
+        errExit("open /dev/tty");
+    exit(EXIT_SUCCESS);
+}
diff --git a/pipes/Makefile b/pipes/Makefile
new file mode 100644 (file)
index 0000000..be7463d
--- /dev/null
@@ -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 (file)
index 0000000..848bbd9
--- /dev/null
@@ -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 <ctype.h>
+#include "tlpi_hdr.h"
+
+#define BUF_SIZE 100    /* Should be <= PIPE_BUF bytes */
+
+int
+main(int argc, char *argv[])
+{
+    char buf[BUF_SIZE];
+    int outbound[2];            /* Pipe to send data from parent to child */
+    int inbound[2];             /* Pipe to send data from child to parent */
+    int j;
+    ssize_t cnt;
+
+    if (pipe(outbound) == -1)
+        errExit("pipe");
+    if (pipe(inbound) == -1)
+        errExit("pipe");
+
+    switch (fork()) {
+    case -1:
+        errExit("fork");
+
+    case 0: /* Child */
+
+        /* Close unused pipe descriptors */
+
+        if (close(outbound[1]) == -1)
+            errExit("close");
+        if (close(inbound[0]) == -1)
+            errExit("close");
+
+        /* Read data from outbound pipe, convert to uppercase,
+           and send back to parent on inbound pipe */
+
+        while ((cnt = read(outbound[0], buf, BUF_SIZE)) > 0) {
+            for (j = 0; j < cnt; j++)
+                buf[j] = toupper((unsigned char) buf[j]);
+            if (write(inbound[1], buf, cnt) != cnt)
+                fatal("failed/partial write(): inbound pipe");
+        }
+
+        if (cnt == -1)
+            errExit("read");
+        _exit(EXIT_SUCCESS);
+
+    default:
+
+        /* Close unused pipe descriptors */
+
+        if (close(outbound[0]) == -1)
+            errExit("close");
+        if (close(inbound[1]) == -1)
+            errExit("close");
+
+        /* Read data from stdin, send to the child via the
+           outbound pipe, read the results back from the child
+           on the inbound pipe, and print them on stdout */
+
+        while ((cnt = read(STDIN_FILENO, buf, BUF_SIZE)) > 0) {
+            if (write(outbound[1], buf, cnt) != cnt)
+                fatal("failed/partial write(): outbound pipe");
+
+            cnt = read(inbound[0], buf, BUF_SIZE);
+            if (cnt == -1)
+                errExit("read");
+            if (cnt > 0)
+                if (write(STDOUT_FILENO, buf, cnt) != cnt)
+                    fatal("failed/partial write(): STDOUT_FILENO");
+        }
+
+        if (cnt == -1)
+            errExit("read");
+
+        /* Exiting will close write end of outbound pipe, so that
+           child see EOF */
+
+        exit(EXIT_SUCCESS);
+    }
+}
diff --git a/pipes/fifo_seqnum.h b/pipes/fifo_seqnum.h
new file mode 100644 (file)
index 0000000..ebd1a78
--- /dev/null
@@ -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 <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+#define SERVER_FIFO "/tmp/seqnum_sv"
+                                /* Well-known name for server's FIFO */
+#define CLIENT_FIFO_TEMPLATE "/tmp/seqnum_cl.%ld"
+                                /* Template for building client FIFO name */
+#define CLIENT_FIFO_NAME_LEN (sizeof(CLIENT_FIFO_TEMPLATE) + 20)
+                                /* Space required for client FIFO pathname
+                                  (+20 as a generous allowance for the PID) */
+
+struct request {                /* Request (client --> server) */
+    pid_t pid;                  /* PID of client */
+    int seqLen;                 /* Length of desired sequence */
+};
+
+struct response {               /* Response (server --> client) */
+    int seqNum;                 /* Start of sequence */
+};
diff --git a/pipes/fifo_seqnum_client.c b/pipes/fifo_seqnum_client.c
new file mode 100644 (file)
index 0000000..d481cd5
--- /dev/null
@@ -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 (file)
index 0000000..9c80f3c
--- /dev/null
@@ -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 <signal.h>
+#include "fifo_seqnum.h"
+
+int
+main(int argc, char *argv[])
+{
+    int serverFd, dummyFd, clientFd;
+    char clientFifo[CLIENT_FIFO_NAME_LEN];
+    struct request req;
+    struct response resp;
+    int seqNum = 0;                     /* This is our "service" */
+
+    /* Create well-known FIFO, and open it for reading */
+
+    umask(0);                           /* So we get the permissions we want */
+    if (mkfifo(SERVER_FIFO, S_IRUSR | S_IWUSR | S_IWGRP) == -1
+            && errno != EEXIST)
+        errExit("mkfifo %s", SERVER_FIFO);
+    serverFd = open(SERVER_FIFO, O_RDONLY);
+    if (serverFd == -1)
+        errExit("open %s", SERVER_FIFO);
+
+    /* Open an extra write descriptor, so that we never see EOF */
+
+    dummyFd = open(SERVER_FIFO, O_WRONLY);
+    if (dummyFd == -1)
+        errExit("open %s", SERVER_FIFO);
+
+    /* Let's find out about broken client pipe via failed write() */
+
+    if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)    errExit("signal");
+
+    for (;;) {                          /* Read requests and send responses */
+        if (read(serverFd, &req, sizeof(struct request))
+                != sizeof(struct request)) {
+            fprintf(stderr, "Error reading request; discarding\n");
+            continue;                   /* Either partial read or error */
+        }
+
+        /* Open client FIFO (previously created by client) */
+
+        snprintf(clientFifo, CLIENT_FIFO_NAME_LEN, CLIENT_FIFO_TEMPLATE,
+                (long) req.pid);
+        clientFd = open(clientFifo, O_WRONLY);
+        if (clientFd == -1) {           /* Open failed, give up on client */
+            errMsg("open %s", clientFifo);
+            continue;
+        }
+
+        /* Send response and close FIFO */
+
+        resp.seqNum = seqNum;
+        if (write(clientFd, &resp, sizeof(struct response))
+                != sizeof(struct response))
+            fprintf(stderr, "Error writing to FIFO %s\n", clientFifo);
+        if (close(clientFd) == -1)
+            errMsg("close");
+
+        seqNum += req.seqLen;           /* Update our sequence number */
+    }
+}
diff --git a/pipes/pipe_ls_wc.c b/pipes/pipe_ls_wc.c
new file mode 100644 (file)
index 0000000..2ff5e16
--- /dev/null
@@ -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 <sys/wait.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    int pfd[2];                                     /* Pipe file descriptors */
+
+    if (pipe(pfd) == -1)                            /* Create pipe */
+        errExit("pipe");
+
+    switch (fork()) {
+    case -1:
+        errExit("fork");
+
+    case 0:             /* First child: exec 'ls' to write to pipe */
+        if (close(pfd[0]) == -1)                    /* Read end is unused */
+            errExit("close 1");
+
+        /* Duplicate stdout on write end of pipe; close duplicated descriptor */
+
+        if (pfd[1] != STDOUT_FILENO) {              /* Defensive check */
+            if (dup2(pfd[1], STDOUT_FILENO) == -1)
+                errExit("dup2 1");
+            if (close(pfd[1]) == -1)
+                errExit("close 2");
+        }
+
+        execlp("ls", "ls", (char *) NULL);          /* Writes to pipe */
+        errExit("execlp ls");
+
+    default:            /* Parent falls through to create next child */
+        break;
+    }
+
+    switch (fork()) {
+    case -1:
+        errExit("fork");
+
+    case 0:             /* Second child: exec 'wc' to read from pipe */
+        if (close(pfd[1]) == -1)                    /* Write end is unused */
+            errExit("close 3");
+
+        /* Duplicate stdin on read end of pipe; close duplicated descriptor */
+
+        if (pfd[0] != STDIN_FILENO) {               /* Defensive check */
+            if (dup2(pfd[0], STDIN_FILENO) == -1)
+                errExit("dup2 2");
+            if (close(pfd[0]) == -1)
+                errExit("close 4");
+        }
+
+        execlp("wc", "wc", "-l", (char *) NULL);
+        errExit("execlp wc");
+
+    default: /* Parent falls through */
+        break;
+    }
+
+    /* Parent closes unused file descriptors for pipe, and waits for children */
+
+    if (close(pfd[0]) == -1)
+        errExit("close 5");
+    if (close(pfd[1]) == -1)
+        errExit("close 6");
+    if (wait(NULL) == -1)
+        errExit("wait 1");
+    if (wait(NULL) == -1)
+        errExit("wait 2");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/pipes/pipe_sync.c b/pipes/pipe_sync.c
new file mode 100644 (file)
index 0000000..7f638e6
--- /dev/null
@@ -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 (file)
index 0000000..6eb2339
--- /dev/null
@@ -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 <ctype.h>
+#include <limits.h>
+#include "print_wait_status.h"          /* For printWaitStatus() */
+#include "tlpi_hdr.h"
+
+#define POPEN_FMT "/bin/ls -d %s 2> /dev/null"
+#define PAT_SIZE 50
+#define PCMD_BUF_SIZE (sizeof(POPEN_FMT) + PAT_SIZE)
+
+int
+main(int argc, char *argv[])
+{
+    char pat[PAT_SIZE];                 /* Pattern for globbing */
+    char popenCmd[PCMD_BUF_SIZE];
+    FILE *fp;                           /* File stream returned by popen() */
+    Boolean badPattern;                 /* Invalid characters in 'pat'? */
+    int len, status, fileCnt, j;
+    char pathname[PATH_MAX];
+
+    for (;;) {                  /* Read pattern, display results of globbing */
+        printf("pattern: ");
+        fflush(stdout);
+        if (fgets(pat, PAT_SIZE, stdin) == NULL)
+            break;                      /* EOF */
+        len = strlen(pat);
+        if (len <= 1)                   /* Empty line */
+            continue;
+
+        if (pat[len - 1] == '\n')       /* Strip trailing newline */
+            pat[len - 1] = '\0';
+
+        /* Ensure that the pattern contains only valid characters,
+           i.e., letters, digits, underscore, dot, and the shell
+           globbing characters. (Our definition of valid is more
+           restrictive than the shell, which permits other characters
+           to be included in a filename if they are quoted.) */
+
+        for (j = 0, badPattern = FALSE; j < len && !badPattern; j++)
+            if (!isalnum((unsigned char) pat[j]) &&
+                    strchr("_*?[^-].", pat[j]) == NULL)
+                badPattern = TRUE;
+
+        if (badPattern) {
+            printf("Bad pattern character: %c\n", pat[j - 1]);
+            continue;
+        }
+
+        /* Build and execute command to glob 'pat' */
+
+        snprintf(popenCmd, PCMD_BUF_SIZE, POPEN_FMT, pat);
+
+        fp = popen(popenCmd, "r");
+        if (fp == NULL) {
+            printf("popen() failed\n");
+            continue;
+        }
+
+        /* Read resulting list of pathnames until EOF */
+
+        fileCnt = 0;
+        while (fgets(pathname, PATH_MAX, fp) != NULL) {
+            printf("%s", pathname);
+            fileCnt++;
+        }
+
+        /* Close pipe, fetch and display termination status */
+
+        status = pclose(fp);
+        printf("    %d matching file%s\n", fileCnt, (fileCnt != 1) ? "s" : "");
+        printf("    pclose() status = %#x\n", (unsigned int) status);
+        if (status != -1)
+            printWaitStatus("\t", status);
+    }
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/pipes/simple_pipe.c b/pipes/simple_pipe.c
new file mode 100644 (file)
index 0000000..25f1ac1
--- /dev/null
@@ -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 <sys/wait.h>
+#include "tlpi_hdr.h"
+
+#define BUF_SIZE 10
+
+int
+main(int argc, char *argv[])
+{
+    int pfd[2];                             /* Pipe file descriptors */
+    char buf[BUF_SIZE];
+    ssize_t numRead;
+
+    if (argc != 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s string\n", argv[0]);
+
+    if (pipe(pfd) == -1)                    /* Create the pipe */
+        errExit("pipe");
+
+    switch (fork()) {
+    case -1:
+        errExit("fork");
+
+    case 0:             /* Child  - reads from pipe */
+        if (close(pfd[1]) == -1)            /* Write end is unused */
+            errExit("close - child");
+
+        for (;;) {              /* Read data from pipe, echo on stdout */
+            numRead = read(pfd[0], buf, BUF_SIZE);
+            if (numRead == -1)
+                errExit("read");
+            if (numRead == 0)
+                break;                      /* End-of-file */
+            if (write(STDOUT_FILENO, buf, numRead) != numRead)
+                fatal("child - partial/failed write");
+        }
+
+        write(STDOUT_FILENO, "\n", 1);
+        if (close(pfd[0]) == -1)
+            errExit("close");
+        _exit(EXIT_SUCCESS);
+
+    default:            /* Parent - writes to pipe */
+        if (close(pfd[0]) == -1)            /* Read end is unused */
+            errExit("close - parent");
+
+        if (write(pfd[1], argv[1], strlen(argv[1])) != strlen(argv[1]))
+            fatal("parent - partial/failed write");
+
+        if (close(pfd[1]) == -1)            /* Child will see EOF */
+            errExit("close");
+        wait(NULL);                         /* Wait for child to finish */
+        exit(EXIT_SUCCESS);
+    }
+}
diff --git a/pmsg/Makefile b/pmsg/Makefile
new file mode 100644 (file)
index 0000000..1a82fd9
--- /dev/null
@@ -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 (file)
index 0000000..ea1254f
--- /dev/null
@@ -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 <signal.h>
+#include <mqueue.h>
+#include <fcntl.h>              /* For definition of O_NONBLOCK */
+#include "tlpi_hdr.h"
+
+#define NOTIFY_SIG SIGUSR1
+
+static void
+handler(int sig)
+{
+    /* Just interrupt sigsuspend() */
+}
+
+/* This program does not handle the case where a message already exists on
+   the queue by the time the first attempt is made to register for message
+   notification. In that case, the program would never receive a notification.
+   See mq_notify_via_signal.c for an example of how to deal with that case. */
+
+int
+main(int argc, char *argv[])
+{
+    struct sigevent sev;
+    mqd_t mqd;
+    struct mq_attr attr;
+    void *buffer;
+    ssize_t numRead;
+    sigset_t blockMask, emptyMask;
+    struct sigaction sa;
+
+    if (argc != 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s mq-name\n", argv[0]);
+
+    mqd = mq_open(argv[1], O_RDONLY | O_NONBLOCK);
+    if (mqd == (mqd_t) -1)
+        errExit("mq_open");
+
+    /* Determine mq_msgsize for message queue, and allocate an input buffer
+       of that size */
+
+    if (mq_getattr(mqd, &attr) == -1)
+        errExit("mq_getattr");
+
+    buffer = malloc(attr.mq_msgsize);
+    if (buffer == NULL)
+        errExit("malloc");
+
+    /* Block the notification signal and establish a handler for it */
+
+    sigemptyset(&blockMask);
+    sigaddset(&blockMask, NOTIFY_SIG);
+    if (sigprocmask(SIG_BLOCK, &blockMask, NULL) == -1)
+        errExit("sigprocmask");
+
+    sigemptyset(&sa.sa_mask);
+    sa.sa_flags = 0;
+    sa.sa_handler = handler;
+    if (sigaction(NOTIFY_SIG, &sa, NULL) == -1)
+        errExit("sigaction");
+
+    /* Register for message notification via a signal */
+
+    sev.sigev_notify = SIGEV_SIGNAL;
+    sev.sigev_signo = NOTIFY_SIG;
+    if (mq_notify(mqd, &sev) == -1)
+        errExit("mq_notify");
+
+    sigemptyset(&emptyMask);
+
+    for (;;) {
+        sigsuspend(&emptyMask);         /* Wait for notification signal */
+
+        /* Reregister for message notification */
+
+        if (mq_notify(mqd, &sev) == -1)
+            errExit("mq_notify");
+
+        while ((numRead = mq_receive(mqd, buffer, attr.mq_msgsize, NULL)) >= 0)
+            printf("Read %ld bytes\n", (long) numRead);
+
+        if (errno != EAGAIN)            /* Unexpected error */
+            errExit("mq_receive");
+    }
+}
diff --git a/pmsg/mq_notify_sigwaitinfo.c b/pmsg/mq_notify_sigwaitinfo.c
new file mode 100644 (file)
index 0000000..d706002
--- /dev/null
@@ -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 <signal.h>
+#include <mqueue.h>
+#include <fcntl.h>              /* For definition of O_NONBLOCK */
+#include "tlpi_hdr.h"
+
+#define NOTIFY_SIG SIGRTMIN     /* Signal used for message notifications */
+
+/* This program does not handle the case where a message already exists on
+   the queue by the time the first attempt is made to register for message
+   notification. In that case, the program would never receive a notification.
+   Compare mq_notify_sig.c and mq_notify_via_signal.c for some hints on how
+   this program might be modified to handle that case. */
+
+int
+main(int argc, char *argv[])
+{
+    struct sigevent sev;
+    mqd_t mqd;
+    struct mq_attr attr;
+    void *buffer;
+    ssize_t numRead;
+    sigset_t blockMask;
+    siginfo_t si;
+
+    if (argc != 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s mq-name\n", argv[0]);
+
+    mqd = mq_open(argv[1], O_RDONLY | O_NONBLOCK);
+    if (mqd == (mqd_t) -1)
+        errExit("mq_open");
+
+    /* Determine mq_msgsize for message queue, and allocate an input buffer
+       of that size */
+
+    if (mq_getattr(mqd, &attr) == -1)
+        errExit("mq_getattr");
+    buffer = malloc(attr.mq_msgsize);
+    if (buffer == NULL)
+        errExit("malloc");
+
+    /* Block the signal that we'll accept using sigwaitinfo() */
+
+    sigemptyset(&blockMask);
+    sigaddset(&blockMask, NOTIFY_SIG);
+    if (sigprocmask(SIG_BLOCK, &blockMask, NULL) == -1)
+        errExit("sigprocmask");
+
+    /* Set up message notification using the signal NOTIFY_SIG */
+
+    sev.sigev_notify = SIGEV_SIGNAL;
+    sev.sigev_signo = NOTIFY_SIG;
+    sev.sigev_value.sival_ptr = &mqd;
+                /* This allows us to obtain a pointer to 'mqd' in the
+                   siginfo_t structure returned by sigwaitinfo() */
+
+    if (mq_notify(mqd, &sev) == -1)
+        errExit("mq_notify");
+
+    for (;;) {
+
+        /* Wait for a signal; when it is received, display associated
+           information */
+
+        if (sigwaitinfo(&blockMask, &si) == -1)
+            errExit("sigwaitinfo");
+
+        printf("Accepted signal:\n");
+        printf("        si_signo   = %d\n", si.si_signo);
+        printf("        si_pid     = %ld\n", (long) si.si_pid);
+        printf("        si_uid     = %ld\n", (long) si.si_uid);
+        printf("        si_code    = %d (%s)\n", si.si_code,
+                (si.si_code == SI_MESGQ) ? "SI_MESGQ" : "???");
+        printf("        *sival_ptr = %p\n\n", si.si_value.sival_ptr);
+
+        /* Reestablish message notification */
+
+        if (mq_notify(mqd, &sev) == -1)
+            errExit("mq_notify");
+
+        /* Although only one signal might have been queued (if NOTIFY_SIG
+           is a standard signal) we might have received multiple messages,
+           so use nonblocking mq_receive() calls inside a loop to read
+           as many messages as possible. */
+
+        while ((numRead = mq_receive(mqd, buffer, attr.mq_msgsize, NULL)) >= 0)
+            printf("Read %ld bytes\n", (long) numRead);
+
+        if (errno != EAGAIN)            /* Unexpected error */
+            errExit("mq_receive");
+    }
+}
diff --git a/pmsg/mq_notify_thread.c b/pmsg/mq_notify_thread.c
new file mode 100644 (file)
index 0000000..1788b26
--- /dev/null
@@ -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 <pthread.h>
+#include <mqueue.h>
+#include <fcntl.h>              /* For definition of O_NONBLOCK */
+#include "tlpi_hdr.h"
+
+/* This program does not handle the case where a message already exists on
+   the queue by the time the first attempt is made to register for message
+   notification. In that case, the program would never receive a notification.
+   See mq_notify_via_thread.c for an example of how to deal with that case. */
+
+static void notifySetup(mqd_t *mqdp);
+
+static void                     /* Thread notification function */
+threadFunc(union sigval sv)
+{
+    ssize_t numRead;
+    mqd_t *mqdp;
+    void *buffer;
+    struct mq_attr attr;
+
+    mqdp = sv.sival_ptr;
+
+    /* Determine mq_msgsize for message queue, and allocate an input buffer
+       of that size */
+
+    if (mq_getattr(*mqdp, &attr) == -1)
+        errExit("mq_getattr");
+
+    buffer = malloc(attr.mq_msgsize);
+    if (buffer == NULL)
+        errExit("malloc");
+
+    /* Reregister for message notification */
+
+    notifySetup(mqdp);
+
+    while ((numRead = mq_receive(*mqdp, buffer, attr.mq_msgsize, NULL)) >= 0)
+        printf("Read %ld bytes\n", (long) numRead);
+
+    if (errno != EAGAIN)                        /* Unexpected error */
+        errExit("mq_receive");
+
+    free(buffer);
+}
+
+static void
+notifySetup(mqd_t *mqdp)
+{
+    struct sigevent sev;
+
+    sev.sigev_notify = SIGEV_THREAD;            /* Notify via thread */
+    sev.sigev_notify_function = threadFunc;
+    sev.sigev_notify_attributes = NULL;
+            /* Could be pointer to pthread_attr_t structure */
+    sev.sigev_value.sival_ptr = mqdp;           /* Argument to threadFunc() */
+
+    if (mq_notify(*mqdp, &sev) == -1)
+        errExit("mq_notify");
+}
+
+int
+main(int argc, char *argv[])
+{
+    mqd_t mqd;
+
+    if (argc != 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s mq-name\n", argv[0]);
+
+    mqd = mq_open(argv[1], O_RDONLY | O_NONBLOCK);
+    if (mqd == (mqd_t) -1)
+        errExit("mq_open");
+
+    notifySetup(&mqd);
+    pause();                    /* Wait for notifications via thread function */
+}
diff --git a/pmsg/mq_notify_via_signal.c b/pmsg/mq_notify_via_signal.c
new file mode 100644 (file)
index 0000000..39cb3f3
--- /dev/null
@@ -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 <signal.h>
+#include <mqueue.h>
+#include <fcntl.h>              /* For definition of O_NONBLOCK */
+#include "tlpi_hdr.h"
+
+#define NOTIFY_SIG SIGUSR1
+
+static volatile sig_atomic_t gotSig = 1;        /* See comment in main() */
+
+/* Handler for message notification signal */
+
+static void
+handler(int sig)
+{
+    gotSig = 1;
+}
+
+int
+main(int argc, char *argv[])
+{
+    struct sigevent sev;
+    mqd_t mqd;
+    struct sigaction sa;
+    int j;
+    char *msg;
+    ssize_t numRead;
+    struct mq_attr attr;
+
+    if (argc != 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s /mq-name\n", argv[0]);
+
+    /* Open the (existing) queue in nonblocking mode so that we can drain
+       messages from it without blocking once the queue has been emptied */
+
+    mqd = mq_open(argv[1], O_RDONLY | O_NONBLOCK);
+    if (mqd == (mqd_t) -1)
+        errExit("mq_open");
+
+    /* Establish handler for notification signal */
+
+    sigemptyset(&sa.sa_mask);
+    sa.sa_flags = 0;
+    sa.sa_handler = handler;
+    if (sigaction(NOTIFY_SIG, &sa, NULL) == -1)
+        errExit("sigaction");
+
+    /* Determine mq_msgsize for message queue, and allocate an input buffer
+       of that size */
+
+    if (mq_getattr(mqd, &attr) == -1)
+        errExit("mq_getattr");
+
+    msg = malloc(attr.mq_msgsize);
+    if (msg == NULL)
+        errExit("malloc");
+
+    /* Possibly, a message had already been queued by the time we enter
+       the loop below. By initializing 'gotSig' to 1 above, we trigger the
+       program to make the initial registration for notification and force
+       the queue to be drained of any messages on the first loop iteration. */
+
+    for (j = 0; ; j++) {
+        if (gotSig) {
+            gotSig = 0;
+
+            /* Register for message notification */
+
+            sev.sigev_notify = SIGEV_SIGNAL;
+            sev.sigev_signo = NOTIFY_SIG;
+            if (mq_notify(mqd, &sev) == -1)
+                errExit("mq_notify");
+
+            /* Drain all messages from the queue */
+
+            while ((numRead = mq_receive(mqd, msg,
+                                attr.mq_msgsize, NULL)) >= 0) {
+                /* Do whatever processing is required for each message */
+
+                printf("Read %ld bytes\n", (long) numRead);
+            }
+            if (errno != EAGAIN)        /* Unexpected error */
+                errExit("mq_receive");
+        }
+
+        printf("j = %d\n", j);
+        sleep(5);               /* Do some "work" */
+    }
+}
diff --git a/pmsg/mq_notify_via_thread.c b/pmsg/mq_notify_via_thread.c
new file mode 100644 (file)
index 0000000..dfe9f54
--- /dev/null
@@ -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 <pthread.h>
+#include <mqueue.h>
+#include <fcntl.h>              /* For definition of O_NONBLOCK */
+#include "tlpi_hdr.h"
+
+static void notifySetup(mqd_t *mqdp);
+
+/* Drain all messages from the queue referred to by 'mqd' */
+
+static void
+drainQueue(mqd_t mqd)
+{
+    ssize_t numRead;
+    char *msg;
+    struct mq_attr attr;
+
+    /* Determine mq_msgsize for message queue, and allocate
+       a buffer of that size */
+
+    if (mq_getattr(mqd, &attr) == -1)
+        errExit("mq_getattr");
+
+    msg = malloc(attr.mq_msgsize);
+    if (msg == NULL)
+        errExit("malloc");
+
+    while ((numRead = mq_receive(mqd, msg, attr.mq_msgsize, NULL)) >= 0) {
+
+        /* Do whatever processing is required for message */
+
+        printf("Read %ld bytes\n", (long) numRead);
+    }
+
+    if (errno != EAGAIN)                /* Unexpected error */
+        errExit("mq_receive");
+
+    free(msg);
+}
+
+static void                     /* Thread notification function */
+threadFunc(union sigval sv)
+{
+    mqd_t *mqdp;
+
+    mqdp = sv.sival_ptr;
+
+    /* Reregister for message notification */
+
+    notifySetup(mqdp);
+    drainQueue(*mqdp);
+}
+
+static void
+notifySetup(mqd_t *mqdp)
+{
+    struct sigevent sev;
+
+    sev.sigev_notify = SIGEV_THREAD;            /* Notify via thread */
+    sev.sigev_notify_function = threadFunc;
+    sev.sigev_notify_attributes = NULL;
+            /* Could be pointer to pthread_attr_t structure */
+    sev.sigev_value.sival_ptr = mqdp;           /* Argument to threadFunc() */
+
+    if (mq_notify(*mqdp, &sev) == -1)
+        errExit("mq_notify");
+}
+
+int
+main(int argc, char *argv[])
+{
+    mqd_t mqd;
+
+    if (argc != 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s /mq-name\n", argv[0]);
+
+    mqd = mq_open(argv[1], O_RDONLY | O_NONBLOCK);
+    if (mqd == (mqd_t) -1)
+        errExit("mq_open");
+
+    notifySetup(&mqd);
+    drainQueue(mqd);    /* Handle possibility that messages were already
+                           queued before we established notification */
+
+    pause();            /* Wait for notifications via thread function */
+}
diff --git a/pmsg/pmsg_create.c b/pmsg/pmsg_create.c
new file mode 100644 (file)
index 0000000..50b3f24
--- /dev/null
@@ -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 <mqueue.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+static void
+usageError(const char *progName)
+{
+    fprintf(stderr, "Usage: %s [-cx] [-m maxmsg] [-s msgsize] mq-name "
+            "[octal-perms]\n", progName);
+    fprintf(stderr, "    -c          Create queue (O_CREAT)\n");
+    fprintf(stderr, "    -m maxmsg   Set maximum # of messages\n");
+    fprintf(stderr, "    -s msgsize  Set maximum message size\n");
+    fprintf(stderr, "    -x          Create exclusively (O_EXCL)\n");
+    exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+    int flags, opt;
+    mode_t perms;
+    mqd_t mqd;
+    struct mq_attr attr, *attrp;
+
+    /* If 'attrp' is NULL, mq_open() uses default attributes. If an
+       option specifying a message queue attribute is supplied on the
+       command line, we save the attribute in 'attr' and set 'attrp'
+       pointing to 'attr'. We assign some (arbitrary) default values
+       to the fields of 'attr' in case the user specifies the value
+       for one of the queue attributes, but not the other. */
+
+    attrp = NULL;
+    attr.mq_maxmsg = 10;
+    attr.mq_msgsize = 2048;
+    flags = O_RDWR;
+
+    /* Parse command-line options */
+
+    while ((opt = getopt(argc, argv, "cm:s:x")) != -1) {
+        switch (opt) {
+        case 'c':
+            flags |= O_CREAT;
+            break;
+
+        case 'm':
+            attr.mq_maxmsg = atoi(optarg);
+            attrp = &attr;
+            break;
+
+        case 's':
+            attr.mq_msgsize = atoi(optarg);
+            attrp = &attr;
+            break;
+
+        case 'x':
+            flags |= O_EXCL;
+            break;
+
+        default:
+            usageError(argv[0]);
+        }
+    }
+
+    if (optind >= argc)
+        usageError(argv[0]);
+
+    perms = (argc <= optind + 1) ? (S_IRUSR | S_IWUSR) :
+                getInt(argv[optind + 1], GN_BASE_8, "octal-perms");
+
+    mqd = mq_open(argv[optind], flags, perms, attrp);
+    if (mqd == (mqd_t) -1)
+        errExit("mq_open");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/pmsg/pmsg_getattr.c b/pmsg/pmsg_getattr.c
new file mode 100644 (file)
index 0000000..e4ea667
--- /dev/null
@@ -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 <mqueue.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    mqd_t mqd;
+    struct mq_attr attr;
+
+    if (argc != 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s mq-name\n", argv[0]);
+
+    mqd = mq_open(argv[1], O_RDONLY);
+    if (mqd == (mqd_t) -1)
+        errExit("mq_open");
+
+    if (mq_getattr(mqd, &attr) == -1)
+        errExit("mq_getattr");
+
+    printf("Maximum # of messages on queue:   %ld\n", attr.mq_maxmsg);
+    printf("Maximum message size:             %ld\n", attr.mq_msgsize);
+    printf("# of messages currently on queue: %ld\n", attr.mq_curmsgs);
+    exit(EXIT_SUCCESS);
+}
diff --git a/pmsg/pmsg_receive.c b/pmsg/pmsg_receive.c
new file mode 100644 (file)
index 0000000..ec7f0af
--- /dev/null
@@ -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 <mqueue.h>
+#include <fcntl.h>              /* For definition of O_NONBLOCK */
+#include "tlpi_hdr.h"
+
+static void
+usageError(const char *progName)
+{
+    fprintf(stderr, "Usage: %s [-n] mq-name\n", progName);
+    fprintf(stderr, "    -n           Use O_NONBLOCK flag\n");
+    exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+    int flags, opt;
+    mqd_t mqd;
+    unsigned int prio;
+    void *buffer;
+    struct mq_attr attr;
+    ssize_t numRead;
+
+    flags = O_RDONLY;
+    while ((opt = getopt(argc, argv, "n")) != -1) {
+        switch (opt) {
+        case 'n':   flags |= O_NONBLOCK;        break;
+        default:    usageError(argv[0]);
+        }
+    }
+
+    if (optind >= argc)
+        usageError(argv[0]);
+
+    mqd = mq_open(argv[optind], flags);
+    if (mqd == (mqd_t) -1)
+        errExit("mq_open");
+
+    /* We need to know the 'mq_msgsize' attribute of the queue in
+       order to determine the size of the buffer for mq_receive() */
+
+    if (mq_getattr(mqd, &attr) == -1)
+        errExit("mq_getattr");
+
+    buffer = malloc(attr.mq_msgsize);
+    if (buffer == NULL)
+        errExit("malloc");
+
+    numRead = mq_receive(mqd, buffer, attr.mq_msgsize, &prio);
+    if (numRead == -1)
+        errExit("mq_receive");
+
+    printf("Read %ld bytes; priority = %u\n", (long) numRead, prio);
+    if (write(STDOUT_FILENO, buffer, numRead) == -1)
+        errExit("write");
+    write(STDOUT_FILENO, "\n", 1);
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/pmsg/pmsg_send.c b/pmsg/pmsg_send.c
new file mode 100644 (file)
index 0000000..4cda32d
--- /dev/null
@@ -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 <mqueue.h>
+#include <fcntl.h>              /* For definition of O_NONBLOCK */
+#include "tlpi_hdr.h"
+
+static void
+usageError(const char *progName)
+{
+    fprintf(stderr, "Usage: %s [-n] mq-name msg [prio]\n", progName);
+    fprintf(stderr, "    -n           Use O_NONBLOCK flag\n");
+    exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+    int flags, opt;
+    mqd_t mqd;
+    unsigned int prio;
+
+    flags = O_WRONLY;
+    while ((opt = getopt(argc, argv, "n")) != -1) {
+        switch (opt) {
+        case 'n':   flags |= O_NONBLOCK;        break;
+        default:    usageError(argv[0]);
+        }
+    }
+
+    if (optind + 1 >= argc)
+        usageError(argv[0]);
+
+    mqd = mq_open(argv[optind], flags);
+    if (mqd == (mqd_t) -1)
+        errExit("mq_open");
+
+    prio = (argc > optind + 2) ? atoi(argv[optind + 2]) : 0;
+
+    if (mq_send(mqd, argv[optind + 1], strlen(argv[optind + 1]), prio) == -1)
+        errExit("mq_send");
+    exit(EXIT_SUCCESS);
+}
diff --git a/pmsg/pmsg_unlink.c b/pmsg/pmsg_unlink.c
new file mode 100644 (file)
index 0000000..ebbd36d
--- /dev/null
@@ -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 <mqueue.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    if (argc != 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s mq-name\n", argv[0]);
+
+    if (mq_unlink(argv[1]) == -1)
+        errExit("mq_unlink");
+    exit(EXIT_SUCCESS);
+}
diff --git a/proc/Makefile b/proc/Makefile
new file mode 100644 (file)
index 0000000..fcd5da6
--- /dev/null
@@ -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 (file)
index 0000000..b4fc154
--- /dev/null
@@ -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 <setjmp.h>
+#include "tlpi_hdr.h"
+
+static jmp_buf env;     /* Global buffer for saving environment */
+
+static void
+doJump(void)
+{
+    printf("Entered doJump\n");
+    longjmp(env, 2);
+    printf("Exiting doJump\n");
+}
+
+static void
+setJump2(void)
+{
+    printf("Entered setJump2\n");
+    setjmp(env);
+    printf("Exiting setJump2\n");
+}
+
+static void
+setJump(void)
+{
+
+    printf("Entered setJump\n");
+    setJump2();
+    printf("Exiting setJump\n");
+}
+
+static void
+recur(int n)
+{
+    printf("Entered recur %d\n", n);
+    if (n > 0)
+        recur(n - 1);
+    printf("Exiting recur %d\n", n);
+}
+
+int
+main(int argc, char *argv[])
+{
+    setJump();
+    if (argc > 1)
+        recur(2);
+    doJump();
+    printf("Back at main\n");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/proc/display_env.c b/proc/display_env.c
new file mode 100644 (file)
index 0000000..23dc8a4
--- /dev/null
@@ -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 <unistd.h> */
+
+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 (file)
index 0000000..c8eaa02
--- /dev/null
@@ -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 <setjmp.h>
+#include "tlpi_hdr.h"
+
+static jmp_buf env;
+
+static void
+f2(void)
+{
+    longjmp(env, 2);
+}
+
+static void
+f1(int argc)
+{
+    if (argc == 1)
+        longjmp(env, 1);
+    f2();
+}
+
+int
+main(int argc, char *argv[])
+{
+    switch (setjmp(env)) {
+    case 0:     /* This is the return after the initial setjmp() */
+        printf("Calling f1() after initial setjmp()\n");
+        f1(argc);               /* Never returns... */
+        break;                  /* ... but this is good form */
+
+    case 1:
+        printf("We jumped back from f1()\n");
+        break;
+
+    case 2:
+        printf("We jumped back from f2()\n");
+        break;
+    }
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/proc/mem_segments.c b/proc/mem_segments.c
new file mode 100644 (file)
index 0000000..7521364
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+
+char globBuf[65536];            /* Uninitialized data segment */
+int primes[] = { 2, 3, 5, 7 };  /* Initialized data segment */
+
+static int
+square(int x)                   /* Allocated in frame for square() */
+{
+    int result;                 /* Allocated in frame for square() */
+
+    result = x * x;
+    return result;              /* Return value passed via register */
+}
+
+static void
+doCalc(int val)                 /* Allocated in frame for doCalc() */
+{
+    printf("The square of %d is %d\n", val, square(val));
+
+    if (val < 1000) {
+        int t;                  /* Allocated in frame for doCalc() */
+
+        t = val * val * val;
+        printf("The cube of %d is %d\n", val, t);
+    }
+}
+
+int
+main(int argc, char *argv[])    /* Allocated in frame for main() */
+{
+    static int key = 9973;      /* Initialized data segment */
+    static char mbuf[10240000]; /* Uninitialized data segment */
+    char *p;                    /* Allocated in frame for main() */
+
+    p = malloc(1024);           /* Points to memory in heap segment */
+
+    doCalc(key);
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/proc/modify_env.c b/proc/modify_env.c
new file mode 100644 (file)
index 0000000..62ce0f0
--- /dev/null
@@ -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 <stdlib.h> */
+#include <stdlib.h>
+#include "tlpi_hdr.h"
+
+extern char **environ;
+
+int
+main(int argc, char *argv[])
+{
+    int j;
+    char **ep;
+
+    clearenv();         /* Erase entire environment */
+
+    /* Add any definitions specified on command line to environment */
+
+    for (j = 1; j < argc; j++)
+        if (putenv(argv[j]) != 0)
+            errExit("putenv: %s", argv[j]);
+
+    /* Add a definition for GREET if one does not already exist */
+
+    if (setenv("GREET", "Hello world", 0) == -1)
+        errExit("setenv");
+
+    /* Remove any existing definition of BYE */
+
+    unsetenv("BYE");
+
+    /* Display current environment */
+
+    for (ep = environ; *ep != NULL; ep++)
+        puts(*ep);
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/proc/necho.c b/proc/necho.c
new file mode 100644 (file)
index 0000000..f4f4b19
--- /dev/null
@@ -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 (file)
index 0000000..3404e10
--- /dev/null
@@ -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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+int
+unsetenv(const char *name)
+{
+    extern char **environ;
+    char **ep, **sp;
+    size_t len;
+
+    if (name == NULL || name[0] == '\0' || strchr(name, '=') != NULL) {
+        errno = EINVAL;
+        return -1;
+    }
+
+    len = strlen(name);
+
+    for (ep = environ; *ep != NULL; )
+        if (strncmp(*ep, name, len) == 0 && (*ep)[len] == '=') {
+
+            /* Remove found entry by shifting all successive entries
+               back one element */
+
+            for (sp = ep; *sp != NULL; sp++)
+                *sp = *(sp + 1);
+
+            /* Continue around the loop to further instances of 'name' */
+
+        } else {
+            ep++;
+        }
+
+    return 0;
+}
+
+int
+setenv(const char *name, const char *value, int overwrite)
+{
+    char *es;
+
+    if (name == NULL || name[0] == '\0' || strchr(name, '=') != NULL ||
+            value == NULL) {
+        errno = EINVAL;
+        return -1;
+    }
+
+    if (getenv(name) != NULL && overwrite == 0)
+        return 0;
+
+    unsetenv(name);             /* Remove all occurrences */
+
+    es = malloc(strlen(name) + strlen(value) + 2);
+                                /* +2 for '=' and null terminator */
+    if (es == NULL)
+        return -1;
+
+    strcpy(es, name);
+    strcat(es, "=");
+    strcat(es, value);
+
+    return (putenv(es) != 0) ? -1 : 0;
+}
+
+#ifdef TEST_IT
+
+int
+main()
+{
+    if (putenv("TT=xxxxx") != 0)
+        perror("putenv");
+
+    system("echo '***** Environment before unsetenv(TT)'; "
+            "printenv | grep ^TT");
+    system("echo 'Total lines from printenv:' `printenv | wc -l`");
+
+    unsetenv("TT");
+
+    system("echo '***** Environment after unsetenv(TT)'; "
+            "printenv | grep ^TT");
+    system("echo 'Total lines from printenv:' `printenv | wc -l`");
+
+    setenv("xyz", "one", 1);
+    setenv("xyz", "two", 0);
+    setenv("xyz2", "222", 0);
+
+    system("echo '***** Environment after setenv() calls'; "
+            "printenv | grep ^x");
+    system("echo 'Total lines from printenv:' `printenv | wc -l`");
+
+    exit(EXIT_SUCCESS);
+}
+
+#endif
diff --git a/proc/setjmp_vars.c b/proc/setjmp_vars.c
new file mode 100644 (file)
index 0000000..c797725
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <setjmp.h>
+
+static jmp_buf env;
+
+static void
+doJump(int nvar, int rvar, int vvar)
+{
+    printf("Inside doJump(): nvar=%d rvar=%d vvar=%d\n", nvar, rvar, vvar);
+    longjmp(env, 1);
+}
+
+int
+main(int argc, char *argv[])
+{
+    int nvar;
+    register int rvar;          /* Allocated in register if possible */
+    volatile int vvar;          /* See text */
+
+    nvar = 111;
+    rvar = 222;
+    vvar = 333;
+
+    if (setjmp(env) == 0) {     /* Code executed after setjmp() */
+        nvar = 777;
+        rvar = 888;
+        vvar = 999;
+        doJump(nvar, rvar, vvar);
+
+    } else {                    /* Code executed after longjmp() */
+
+        printf("After longjmp(): nvar=%d rvar=%d vvar=%d\n", nvar, rvar, vvar);
+    }
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/proc/t_getenv.c b/proc/t_getenv.c
new file mode 100644 (file)
index 0000000..8bed084
--- /dev/null
@@ -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 (file)
index 0000000..9adfef8
--- /dev/null
@@ -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 (file)
index 0000000..0b6a4e8
--- /dev/null
@@ -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 <unistd.h>
+#include <sys/fsuid.h>
+#include <limits.h>
+#include "ugid_functions.h"   /* userNameFromId() & groupNameFromId() */
+#include "tlpi_hdr.h"
+
+#define SG_SIZE (NGROUPS_MAX + 1)
+
+int
+main(int argc, char *argv[])
+{
+    uid_t ruid, euid, suid, fsuid;
+    gid_t rgid, egid, sgid, fsgid;
+    gid_t suppGroups[SG_SIZE];
+    int numGroups, j;
+    char *p;
+
+    if (getresuid(&ruid, &euid, &suid) == -1)
+        errExit("getresuid");
+    if (getresgid(&rgid, &egid, &sgid) == -1)
+        errExit("getresgid");
+
+    /* Attempts to change the file-system IDs are always ignored
+       for unprivileged processes, but even so, the following
+       calls return the current file-system IDs */
+
+    fsuid = setfsuid(0);
+    fsgid = setfsgid(0);
+
+    printf("UID: ");
+    p = userNameFromId(ruid);
+    printf("real=%s (%ld); ", (p == NULL) ? "???" : p, (long) ruid);
+    p = userNameFromId(euid);
+    printf("eff=%s (%ld); ", (p == NULL) ? "???" : p, (long) euid);
+    p = userNameFromId(suid);
+    printf("saved=%s (%ld); ", (p == NULL) ? "???" : p, (long) suid);
+    p = userNameFromId(fsuid);
+    printf("fs=%s (%ld); ", (p == NULL) ? "???" : p, (long) fsuid);
+    printf("\n");
+
+    printf("GID: ");
+    p = groupNameFromId(rgid);
+    printf("real=%s (%ld); ", (p == NULL) ? "???" : p, (long) rgid);
+    p = groupNameFromId(egid);
+    printf("eff=%s (%ld); ", (p == NULL) ? "???" : p, (long) egid);
+    p = groupNameFromId(sgid);
+    printf("saved=%s (%ld); ", (p == NULL) ? "???" : p, (long) sgid);
+    p = groupNameFromId(fsgid);
+    printf("fs=%s (%ld); ", (p == NULL) ? "???" : p, (long) fsgid);
+    printf("\n");
+
+    numGroups = getgroups(SG_SIZE, suppGroups);
+    if (numGroups == -1)
+        errExit("getgroups");
+
+    printf("Supplementary groups (%d): ", numGroups);
+    for (j = 0; j < numGroups; j++) {
+        p = groupNameFromId(suppGroups[j]);
+        printf("%s (%ld) ", (p == NULL) ? "???" : p, (long) suppGroups[j]);
+    }
+    printf("\n");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/procexec/Makefile b/procexec/Makefile
new file mode 100644 (file)
index 0000000..767b4d9
--- /dev/null
@@ -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 (file)
index 0000000..2b58487
--- /dev/null
@@ -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 <unistd.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    if (argc > 2 || (argc > 1 && strcmp(argv[1], "--help") == 0))
+        usageErr("%s [file]\n", argv[0]);
+
+    if (acct(argv[1]) == -1)
+        errExit("acct");
+
+    printf("Process accounting %s\n",
+            (argv[1] == NULL) ? "disabled" : "enabled");
+    exit(EXIT_SUCCESS);
+}
diff --git a/procexec/acct_v3_view.c b/procexec/acct_v3_view.c
new file mode 100644 (file)
index 0000000..bca5ced
--- /dev/null
@@ -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 <fcntl.h>
+#include <time.h>
+#include <sys/stat.h>
+#include <sys/acct.h>
+#include <limits.h>
+#include "ugid_functions.h"             /* Declaration of userNameFromId() */
+#include "tlpi_hdr.h"
+
+#define TIME_BUF_SIZE 100
+
+#define GLIBC_DEFINES_ACCT_V3           /* This is true since glibc 2.6 */
+
+#ifndef GLIBC_DEFINES_ACCT_V3
+struct acct_v3
+{
+    char        ac_flag;
+    char        ac_version;
+    u_int16_t   ac_tty;
+    u_int32_t   ac_exitcode;
+    u_int32_t   ac_uid;
+    u_int32_t   ac_gid;
+    u_int32_t   ac_pid;
+    u_int32_t   ac_ppid;
+    u_int32_t   ac_btime;
+    float       ac_etime;
+    comp_t      ac_utime;
+    comp_t      ac_stime;
+    comp_t      ac_mem;
+    comp_t      ac_io;
+    comp_t      ac_rw;
+    comp_t      ac_minflt;
+    comp_t      ac_majflt;
+    comp_t      ac_swaps;
+    char        ac_comm[ACCT_COMM];
+};
+
+#endif
+
+static long long                /* Convert comp_t value into long long */
+comptToLL(comp_t ct)
+{
+    const int EXP_SIZE = 3;             /* 3-bit, base-8 exponent */
+    const int MANTISSA_SIZE = 13;       /* Followed by 13-bit mantissa */
+    const int MANTISSA_MASK = (1 << MANTISSA_SIZE) - 1;
+    long long mantissa, exp;
+
+    mantissa = ct & MANTISSA_MASK;
+    exp = (ct >> MANTISSA_SIZE) & ((1 << EXP_SIZE) - 1);
+    return mantissa << (exp * 3);       /* Power of 8 = left shift 3 bits */
+}
+
+int
+main(int argc, char *argv[])
+{
+    int acctFile;
+    struct acct_v3 ac;
+    ssize_t numRead;
+    char *s;
+    char timeBuf[TIME_BUF_SIZE];
+    struct tm *loc;
+    time_t t;
+
+    if (argc != 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s file\n", argv[0]);
+
+    acctFile = open(argv[1], O_RDONLY);
+    if (acctFile == -1)
+        errExit("open");
+
+    printf("ver. command    flags   term.   PID   PPID  user     group"
+           "      start date+time     CPU   elapsed\n");
+    printf("                       status                             "
+           "                          time    time\n");
+
+    while ((numRead = read(acctFile, &ac, sizeof(struct acct_v3))) > 0) {
+        if (numRead != sizeof(struct acct_v3))
+            fatal("partial read");
+
+        printf("%1d    ", (int) ac.ac_version);
+        printf("%-8.8s   ", ac.ac_comm);
+
+        printf("%c", (ac.ac_flag & AFORK) ? 'F' : '-') ;
+        printf("%c", (ac.ac_flag & ASU)   ? 'S' : '-') ;
+        printf("%c", (ac.ac_flag & AXSIG) ? 'X' : '-') ;
+        printf("%c", (ac.ac_flag & ACORE) ? 'C' : '-') ;
+
+        printf(" %#6lx   ", (unsigned long) ac.ac_exitcode);
+
+        printf(" %5ld %5ld  ", (long) ac.ac_pid, (long) ac.ac_ppid);
+
+        s = userNameFromId(ac.ac_uid);
+        printf("%-8.8s ", (s == NULL) ? "???" : s);
+
+        s = groupNameFromId(ac.ac_gid);
+        printf("%-8.8s ", (s == NULL) ? "???" : s);
+
+        t = ac.ac_btime;
+        loc = localtime(&t);
+        if (loc == NULL) {
+            printf("???Unknown time???  ");
+        } else {
+            strftime(timeBuf, TIME_BUF_SIZE, "%Y-%m-%d %T ", loc);
+            printf("%s ", timeBuf);
+        }
+
+        printf("%5.2f %7.2f ", (double) (comptToLL(ac.ac_utime) +
+                    comptToLL(ac.ac_stime)) / sysconf(_SC_CLK_TCK),
+                ac.ac_etime / sysconf(_SC_CLK_TCK));
+        printf("\n");
+    }
+
+    if (numRead == -1)
+        errExit("read");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/procexec/acct_view.c b/procexec/acct_view.c
new file mode 100644 (file)
index 0000000..ba2acce
--- /dev/null
@@ -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 <fcntl.h>
+#include <time.h>
+#include <sys/stat.h>
+#include <sys/acct.h>
+#include <limits.h>
+#include "ugid_functions.h"             /* Declaration of userNameFromId() */
+#include "tlpi_hdr.h"
+
+#define TIME_BUF_SIZE 100
+
+static long long                /* Convert comp_t value into long long */
+comptToLL(comp_t ct)
+{
+    const int EXP_SIZE = 3;             /* 3-bit, base-8 exponent */
+    const int MANTISSA_SIZE = 13;       /* Followed by 13-bit mantissa */
+    const int MANTISSA_MASK = (1 << MANTISSA_SIZE) - 1;
+    long long mantissa, exp;
+
+    mantissa = ct & MANTISSA_MASK;
+    exp = (ct >> MANTISSA_SIZE) & ((1 << EXP_SIZE) - 1);
+    return mantissa << (exp * 3);       /* Power of 8 = left shift 3 bits */
+}
+
+int
+main(int argc, char *argv[])
+{
+    int acctFile;
+    struct acct ac;
+    ssize_t numRead;
+    char *s;
+    char timeBuf[TIME_BUF_SIZE];
+    struct tm *loc;
+    time_t t;
+
+    if (argc != 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s file\n", argv[0]);
+
+    acctFile = open(argv[1], O_RDONLY);
+    if (acctFile == -1)
+        errExit("open");
+
+    printf("command  flags   term.  user     "
+            "start time            CPU   elapsed\n");
+    printf("                status           "
+            "                      time    time\n");
+
+    while ((numRead = read(acctFile, &ac, sizeof(struct acct))) > 0) {
+        if (numRead != sizeof(struct acct))
+            fatal("partial read");
+
+        printf("%-8.8s  ", ac.ac_comm);
+
+        printf("%c", (ac.ac_flag & AFORK) ? 'F' : '-') ;
+        printf("%c", (ac.ac_flag & ASU)   ? 'S' : '-') ;
+
+        /* Not all implementations support AXSIG and ACORE */
+
+#ifdef AXSIG
+        printf("%c", (ac.ac_flag & AXSIG) ? 'X' : '-') ;
+#else
+        printf(" ");
+#endif
+#ifdef ACORE
+        printf("%c", (ac.ac_flag & ACORE) ? 'C' : '-') ;
+#else
+        printf(" ");
+#endif
+
+#ifdef __linux__
+        printf(" %#6lx   ", (unsigned long) ac.ac_exitcode);
+#else   /* Many other implementations provide ac_stat instead */
+        /* But the BSDs don't provide this field either */
+#if ! defined(__FreeBSD__) && ! defined(__NetBSD__) && \
+        ! defined(__APPLE__)
+        printf(" %#6lx   ", (unsigned long) ac.ac_stat);
+#else
+        printf("          ");
+#endif
+#endif
+
+        s = userNameFromId(ac.ac_uid);
+        printf("%-8.8s ", (s == NULL) ? "???" : s);
+
+        t = ac.ac_btime;
+        loc = localtime(&t);
+        if (loc == NULL) {
+            printf("???Unknown time???  ");
+        } else {
+            strftime(timeBuf, TIME_BUF_SIZE, "%Y-%m-%d %T ", loc);
+            printf("%s ", timeBuf);
+        }
+
+        printf("%5.2f %7.2f ", (double) (comptToLL(ac.ac_utime) +
+                    comptToLL(ac.ac_stime)) / sysconf(_SC_CLK_TCK),
+                (double) comptToLL(ac.ac_etime) / sysconf(_SC_CLK_TCK));
+        printf("\n");
+    }
+
+    if (numRead == -1)
+        errExit("read");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/procexec/child_status.c b/procexec/child_status.c
new file mode 100644 (file)
index 0000000..249a62f
--- /dev/null
@@ -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 <sys/wait.h>
+#include "print_wait_status.h"          /* Declares printWaitStatus() */
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    int status;
+    pid_t childPid;
+
+    if (argc > 1 && strcmp(argv[1], "--help") == 0)
+        usageErr("%s [exit-status]\n", argv[0]);
+
+    switch (fork()) {
+    case -1: errExit("fork");
+
+    case 0:             /* Child: either exits immediately with given
+                           status or loops waiting for signals */
+        printf("Child started with PID = %ld\n", (long) getpid());
+        if (argc > 1)                   /* Status supplied on command line? */
+            exit(getInt(argv[1], 0, "exit-status"));
+        else                            /* Otherwise, wait for signals */
+            for (;;)
+                pause();
+        exit(EXIT_FAILURE);             /* Not reached, but good practice */
+
+    default:            /* Parent: repeatedly wait on child until it
+                           either exits or is terminated by a signal */
+        for (;;) {
+            childPid = waitpid(-1, &status, WUNTRACED
+#ifdef WCONTINUED       /* Not present on older versions of Linux */
+                                                | WCONTINUED
+#endif
+                    );
+            if (childPid == -1)
+                errExit("waitpid");
+
+            /* Print status in hex, and as separate decimal bytes */
+
+            printf("waitpid() returned: PID=%ld; status=0x%04x (%d,%d)\n",
+                    (long) childPid,
+                    (unsigned int) status, status >> 8, status & 0xff);
+            printWaitStatus(NULL, status);
+
+            if (WIFEXITED(status) || WIFSIGNALED(status))
+                exit(EXIT_SUCCESS);
+        }
+    }
+}
diff --git a/procexec/closeonexec.c b/procexec/closeonexec.c
new file mode 100644 (file)
index 0000000..b1b6eec
--- /dev/null
@@ -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 <fcntl.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    int flags;
+
+    if (argc > 1) {
+        flags = fcntl(STDOUT_FILENO, F_GETFD);              /* Fetch flags */
+        if (flags == -1)
+            errExit("fcntl - F_GETFD");
+
+        flags |= FD_CLOEXEC;                    /* Turn on FD_CLOEXEC */
+
+        if (fcntl(STDOUT_FILENO, F_SETFD, flags) == -1)     /* Update flags */
+            errExit("fcntl - F_SETFD");
+    }
+
+    execlp("ls", "ls", "-l", argv[0], (char *) NULL);
+    errExit("execlp");
+}
diff --git a/procexec/demo_clone.c b/procexec/demo_clone.c
new file mode 100644 (file)
index 0000000..7d31c89
--- /dev/null
@@ -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 <string.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <fcntl.h>
+#include <sched.h>
+#include "print_wait_status.h"
+#include "tlpi_hdr.h"
+
+#ifndef CHILD_SIG
+#define CHILD_SIG SIGUSR1       /* Signal to be generated on termination
+                                   of cloned child */
+#endif
+
+typedef struct {        /* For passing info to child startup function */
+    int    fd;
+    mode_t umask;
+    int    exitStatus;
+    int    signal;
+} ChildParams;
+
+static int              /* Startup function for cloned child */
+childFunc(void *arg)
+{
+    ChildParams *cp;
+
+    printf("Child:  PID=%ld PPID=%ld\n", (long) getpid(), (long) getppid());
+
+    cp = (ChildParams *) arg;   /* Cast arg to true form */
+
+    /* The following changes may affect parent */
+
+    umask(cp->umask);
+    if (close(cp->fd) == -1)
+        errExit("child:close");
+    if (signal(cp->signal, SIG_DFL) == SIG_ERR)
+        errExit("child:signal");
+
+    return cp->exitStatus;      /* Child terminates now */
+}
+
+static void             /* Handler for child termination signal */
+grimReaper(int sig)
+{
+    int savedErrno;
+
+    /* UNSAFE: This handler uses non-async-signal-safe functions
+       (printf(), strsignal(); see Section 21.1.2) */
+
+    savedErrno = errno;         /* In case we change 'errno' here */
+
+    printf("Caught signal %d (%s)\n", sig, strsignal(sig));
+
+    errno = savedErrno;
+}
+
+static void
+usageError(char *progName)
+{
+    fprintf(stderr, "Usage: %s [arg]\n", progName);
+#define fpe(str) fprintf(stderr, "        " str)
+    fpe("'arg' can contain the following letters:\n");
+    fpe("    d - share file descriptors (CLONE_FILES)\n");
+    fpe("    f - share file-system information (CLONE_FS)\n");
+    fpe("    s - share signal dispositions (CLONE_SIGHAND)\n");
+    fpe("    v - share virtual memory (CLONE_VM)\n");
+    exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+    const int STACK_SIZE = 65536;       /* Stack size for cloned child */
+    char *stack;                        /* Start of stack buffer area */
+    char *stackTop;                     /* End of stack buffer area */
+    int flags;                          /* Flags for cloning child */
+    ChildParams cp;                     /* Passed to child function */
+    const mode_t START_UMASK = S_IWOTH; /* Initial umask setting */
+    struct sigaction sa;
+    char *p;
+    int status;
+    ssize_t s;
+    pid_t pid;
+
+    printf("Parent: PID=%ld PPID=%ld\n", (long) getpid(), (long) getppid());
+
+    /* Set up an argument structure to be passed to cloned child, and
+       set some process attributes that will be modified by child */
+
+    cp.exitStatus = 22;                 /* Child will exit with this status */
+
+    umask(START_UMASK);                 /* Initialize umask to some value */
+    cp.umask = S_IWGRP;                 /* Child sets umask to this value */
+
+    cp.fd = open("/dev/null", O_RDWR);  /* Child will close this fd */
+    if (cp.fd == -1)
+        errExit("open");
+
+    cp.signal = SIGTERM;                /* Child will change disposition */
+    if (signal(cp.signal, SIG_IGN) == SIG_ERR)  errExit("signal");
+
+    /* Initialize clone flags using command-line argument (if supplied) */
+
+    flags = 0;
+    if (argc > 1) {
+        for (p = argv[1]; *p != '\0'; p++) {
+            if      (*p == 'd') flags |= CLONE_FILES;
+            else if (*p == 'f') flags |= CLONE_FS;
+            else if (*p == 's') flags |= CLONE_SIGHAND;
+            else if (*p == 'v') flags |= CLONE_VM;
+            else usageError(argv[0]);
+        }
+    }
+
+    /* Allocate stack for child */
+
+    stack = malloc(STACK_SIZE);
+    if (stack == NULL)
+        errExit("malloc");
+    stackTop = stack + STACK_SIZE;  /* Assume stack grows downward */
+
+    /* Establish handler to catch child termination signal */
+
+    if (CHILD_SIG != 0) {
+        sigemptyset(&sa.sa_mask);
+        sa.sa_flags = SA_RESTART;
+        sa.sa_handler = grimReaper;
+        if (sigaction(CHILD_SIG, &sa, NULL) == -1)
+            errExit("sigaction");
+    }
+
+    /* Create child; child commences execution in childFunc() */
+
+    if (clone(childFunc, stackTop, flags | CHILD_SIG, &cp) == -1)
+        errExit("clone");
+
+    /* Parent falls through to here. Wait for child; __WCLONE option is
+       required for child notifying with signal other than SIGCHLD. */
+
+    pid = waitpid(-1, &status, (CHILD_SIG != SIGCHLD) ? __WCLONE : 0);
+    if (pid == -1)
+        errExit("waitpid");
+
+    printf("    Child PID=%ld\n", (long) pid);
+    printWaitStatus("    Status: ", status);
+
+    /* Check whether changes made by cloned child have affected parent */
+
+    printf("Parent - checking process attributes:\n");
+    if (umask(0) != START_UMASK)
+        printf("    umask has changed\n");
+    else
+        printf("    umask has not changed\n");
+
+    s = write(cp.fd, "Hello world\n", 12);
+    if (s == -1 && errno == EBADF)
+        printf("    file descriptor %d has been closed\n", cp.fd);
+    else if (s == -1)
+        printf("    write() on file descriptor %d failed (%s)\n",
+                cp.fd, strerror(errno));
+    else
+        printf("    write() on file descriptor %d succeeded\n", cp.fd);
+
+    if (sigaction(cp.signal, NULL, &sa) == -1)
+        errExit("sigaction");
+    if (sa.sa_handler != SIG_IGN)
+        printf("    signal disposition has changed\n");
+    else
+        printf("    signal disposition has not changed\n");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/procexec/envargs.c b/procexec/envargs.c
new file mode 100644 (file)
index 0000000..4461477
--- /dev/null
@@ -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 (file)
index 0000000..e641062
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+
+extern char **environ;
+
+#define max(x,y) ((x) > (y) ? (x) : (y))
+#define SHELL_PATH "/bin/sh"            /* Pathname for the standard shell */
+
+/* Exec a script file using the standard shell */
+
+static void
+execShScript(int argc, char *argv[], char *envp[])
+{
+    char *shArgv[argc + 1];
+    int j;
+
+    shArgv[0] = SHELL_PATH;
+    for (j = 0; j <= argc; j++)
+        shArgv[j + 1] = argv[j];
+    execve(SHELL_PATH, shArgv, envp);
+
+    /* We only get here if execve() fails, in which case we return
+       to our caller */
+}
+
+int
+execlp(const char *filename, const char *arg, ...)
+{
+    char **argv;                /* Argument vector for new program */
+    int argc;                   /* Number of items used in argv */
+    int argvSize;               /* Currently allocated size of argv */
+    va_list argList;            /* For variable argument list parsing */
+    char **envp;                /* Environment for new program */
+    char *PATH;                 /* Value of PATH environment variable */
+    char *pathname;             /* Path prefix + '/' + filename */
+    char *prStart, *prEnd;      /* Start and end of prefix currently
+                                   being processed from PATH */
+    int savedErrno;
+    int morePrefixes;           /* True if there are more prefixes in PATH */
+    char *p;
+    int j;
+    int fndEACCES;              /* True if any execve() returned EACCES */
+
+    fndEACCES = 0;
+
+    /***** Build argument vector from variable length argument list *****/
+
+    argvSize = 100;
+    argv = calloc(argvSize, sizeof(void *));
+    if (argv == NULL)
+        return -1;
+
+    argv[0] = (char *) arg;
+    argc = 1;
+
+    /* Walk through variable-length argument list until NULL terminator
+       is found, building argv as we go */
+
+    va_start(argList, arg);
+    while (argv[argc - 1] != NULL) {
+        if (argc + 1 >= argvSize) {     /* Resize argv if required */
+            argvSize += 100;
+            argv = realloc(argv, sizeof(void *));
+            if (argv == NULL)
+                return -1;
+        }
+
+        argv[argc] = va_arg(argList, char *);
+        argc++;
+    }
+
+    va_end(argList);
+
+    /***** Use caller's environment to create envp vector *****/
+
+    for (j = 0; environ[j] != NULL; )   /* Calculate size of environ */
+        j++;
+    envp = calloc(sizeof(void *), j + 1);
+    if (envp == NULL)
+        return -1;
+
+    for (j = 0; environ[j] != NULL; j++)    /* Duplicate environ in envp */
+        envp[j] = strdup(environ[j]);
+    envp[j] = NULL;             /* List must be terminated by NULL pointer */
+
+    /***** Now try to exec filename *****/
+
+    if (strchr(filename, '/') != NULL) {
+
+         /* If file contains a slash, it's a pathname and we don't do
+            a search using PATH */
+
+        pathname = strdup(filename);
+
+        execve(pathname, argv, envp);
+
+        savedErrno = errno;             /* So we can return correct errno */
+        if (errno == ENOEXEC)
+            execShScript(argc, argv, envp);
+
+        free(pathname);                 /* Avoid memory leaks */
+
+    } else {            /* Use PATH */
+
+        /* Treat undefined PATH as "." */
+
+        p = getenv("PATH");
+        PATH = (p == NULL || strlen(p) == 0) ? strdup(".") : strdup(p);
+
+        /* For each prefix in PATH, try to exec 'filename' in that
+           directory. The loop will terminate either when we successfully
+           exec or when we run out of path prefixes. */
+
+        prStart = PATH;
+        morePrefixes = 1;
+
+        while (morePrefixes) {
+
+            /* Locate end of prefix */
+
+            prEnd = strchr(prStart, ':');
+            if (prEnd == NULL)          /* Handle last prefix */
+                prEnd = prStart + strlen(prStart);
+
+            /* Build complete pathname from path prefix and filename */
+
+            pathname = malloc(max(1, prEnd - prStart) + strlen(filename)
+                                + 2);
+            pathname[0] = '\0';
+            if (prEnd == prStart)       /* Last prefix */
+                strcat(pathname, ".");
+            else
+                strncat(pathname, prStart, prEnd - prStart);
+            strcat(pathname, "/");
+            strcat(pathname, filename);
+
+            if (*prEnd == '\0')         /* No more prefixes */
+                morePrefixes = 0;
+            else
+                prStart = prEnd + 1;    /* Move to start of next prefix */
+
+            /* Try to exec pathname; execve() returns only if we failed */
+
+            execve(pathname, argv, envp);
+            savedErrno = errno;         /* So we can return correct errno */
+            if (errno == EACCES)
+                fndEACCES = 1;
+            else if (errno == ENOEXEC)
+                execShScript(argc, argv, envp);
+
+            /* Avoid memory leaks, in case no execve() succeeds! */
+
+            free(pathname);
+        }
+
+        free(PATH);
+    }
+
+    /* If we get here, execve() failed; clean up and return -1, ensuring
+       that errno contains the value returned by (the last) execve() */
+
+    free(argv);
+    for (j = 0; envp[j] != NULL; j++)
+        free(envp[j]);
+    free(envp);
+
+    /* SUSv3 says that if any execve() failed with EACCES, then that
+       is the error that should be returned by exec[lv]p() */
+
+    errno = fndEACCES ? EACCES : savedErrno;
+    return -1;
+}
diff --git a/procexec/exit_handlers.c b/procexec/exit_handlers.c
new file mode 100644 (file)
index 0000000..9126a4e
--- /dev/null
@@ -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 <stdlib.h> */
+#include <stdlib.h>
+#include "tlpi_hdr.h"
+
+#ifdef __linux__        /* Few UNIX implementations have on_exit() */
+#define HAVE_ON_EXIT
+#endif
+
+static void
+atexitFunc1(void)
+{
+    printf("atexit function 1 called\n");
+}
+
+static void
+atexitFunc2(void)
+{
+    printf("atexit function 2 called\n");
+}
+
+#ifdef HAVE_ON_EXIT
+static void
+onexitFunc(int exitStatus, void *arg)
+{
+    printf("on_exit function called: status=%d, arg=%ld\n",
+                exitStatus, (long) arg);
+}
+#endif
+
+int
+main(int argc, char *argv[])
+{
+#ifdef HAVE_ON_EXIT
+    if (on_exit(onexitFunc, (void *) 10) != 0)
+        fatal("on_exit 1");
+#endif
+    if (atexit(atexitFunc1) != 0)
+        fatal("atexit 1");
+    if (atexit(atexitFunc2) != 0)
+        fatal("atexit 2");
+#ifdef HAVE_ON_EXIT
+    if (on_exit(onexitFunc, (void *) 20) != 0)
+        fatal("on_exit 2");
+#endif
+
+    exit(2);
+}
diff --git a/procexec/footprint.c b/procexec/footprint.c
new file mode 100644 (file)
index 0000000..d9bf43b
--- /dev/null
@@ -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 <unistd.h> in case
+                           _XOPEN_SOURCE >= 600; defining _SVID_SOURCE or
+                           _GNU_SOURCE also suffices */
+#include <sys/wait.h>
+#include "tlpi_hdr.h"
+
+static int
+func(int arg)
+{
+    int j;
+
+    for (j = 0; j < 0x100; j++)
+        if (malloc(0x8000) == NULL)
+            errExit("malloc");
+    printf("Program break in child:  %10p\n", sbrk(0));
+
+    return arg;
+}
+
+int
+main(int argc, char *argv[])
+{
+    int arg = (argc > 1) ? getInt(argv[1], 0, "arg") : 0;
+    pid_t childPid;
+    int status;
+
+    setbuf(stdout, NULL);           /* Disable buffering of stdout */
+
+    printf("Program break in parent: %10p\n", sbrk(0));
+
+    childPid = fork();
+    if (childPid == -1)
+        errExit("fork");
+
+    if (childPid == 0)              /* Child calls func() and */
+        exit(func(arg));            /* uses return value as exit status */
+
+    /* Parent waits for child to terminate. It can determine the
+       result of func() by inspecting 'status' */
+
+    if (wait(&status) == -1)
+        errExit("wait");
+
+    printf("Program break in parent: %10p\n", sbrk(0));
+
+    printf("Status = %d %d\n", status, WEXITSTATUS(status));
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/procexec/fork_file_sharing.c b/procexec/fork_file_sharing.c
new file mode 100644 (file)
index 0000000..634117a
--- /dev/null
@@ -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 <sys/stat.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    int fd, flags;
+    char template[] = "/tmp/testXXXXXX";
+
+    setbuf(stdout, NULL);                   /* Disable buffering of stdout */
+
+    /* Open a temporary file, set its file offset to some arbitrary value,
+       and change the setting of one of the open file status flags. */
+
+    fd = mkstemp(template);
+    if (fd == -1)
+        errExit("mkstemp");
+
+    printf("File offset before fork(): %lld\n",
+            (long long) lseek(fd, 0, SEEK_CUR));
+
+    flags = fcntl(fd, F_GETFL);
+    if (flags == -1)
+        errExit("fcntl - F_GETFL");
+    printf("O_APPEND flag before fork() is: %s\n",
+            (flags & O_APPEND) ? "on" : "off");
+
+    switch (fork()) {
+    case -1:
+        errExit("fork");
+
+    case 0:     /* Child: change file offset and status flags */
+        if (lseek(fd, 1000, SEEK_SET) == -1)
+            errExit("lseek");
+
+        flags = fcntl(fd, F_GETFL);         /* Fetch current flags */
+        if (flags == -1)
+            errExit("fcntl - F_GETFL");
+        flags |= O_APPEND;                  /* Turn O_APPEND on */
+        if (fcntl(fd, F_SETFL, flags) == -1)
+            errExit("fcntl - F_SETFL");
+        _exit(EXIT_SUCCESS);
+
+    default:    /* Parent: can see file changes made by child */
+        if (wait(NULL) == -1)
+            errExit("wait");                /* Wait for child exit */
+        printf("Child has exited\n");
+
+        printf("File offset in parent: %lld\n",
+                (long long) lseek(fd, 0, SEEK_CUR));
+
+        flags = fcntl(fd, F_GETFL);
+        if (flags == -1)
+            errExit("fcntl - F_GETFL");
+        printf("O_APPEND flag in parent is: %s\n",
+                (flags & O_APPEND) ? "on" : "off");
+        exit(EXIT_SUCCESS);
+    }
+}
diff --git a/procexec/fork_sig_sync.c b/procexec/fork_sig_sync.c
new file mode 100644 (file)
index 0000000..d6557b3
--- /dev/null
@@ -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 <signal.h>
+#include "curr_time.h"                  /* Declaration of currTime() */
+#include "tlpi_hdr.h"
+
+#define SYNC_SIG SIGUSR1                /* Synchronization signal */
+
+static void             /* Signal handler - does nothing but return */
+handler(int sig)
+{
+}
+
+int
+main(int argc, char *argv[])
+{
+    pid_t childPid;
+    sigset_t blockMask, origMask, emptyMask;
+    struct sigaction sa;
+
+    setbuf(stdout, NULL);               /* Disable buffering of stdout */
+
+    sigemptyset(&blockMask);
+    sigaddset(&blockMask, SYNC_SIG);    /* Block signal */
+    if (sigprocmask(SIG_BLOCK, &blockMask, &origMask) == -1)
+        errExit("sigprocmask");
+
+    sigemptyset(&sa.sa_mask);
+    sa.sa_flags = SA_RESTART;
+    sa.sa_handler = handler;
+    if (sigaction(SYNC_SIG, &sa, NULL) == -1)
+        errExit("sigaction");
+
+    switch (childPid = fork()) {
+    case -1:
+        errExit("fork");
+
+    case 0: /* Child */
+
+        /* Child does some required action here... */
+
+        printf("[%s %ld] Child started - doing some work\n",
+                currTime("%T"), (long) getpid());
+        sleep(2);               /* Simulate time spent doing some work */
+
+        /* And then signals parent that it's done */
+
+        printf("[%s %ld] Child about to signal parent\n",
+                currTime("%T"), (long) getpid());
+        if (kill(getppid(), SYNC_SIG) == -1)
+            errExit("kill");
+
+        /* Now child can do other things... */
+
+        _exit(EXIT_SUCCESS);
+
+    default: /* Parent */
+
+        /* Parent may do some work here, and then waits for child to
+           complete the required action */
+
+        printf("[%s %ld] Parent about to wait for signal\n",
+                currTime("%T"), (long) getpid());
+        sigemptyset(&emptyMask);
+        if (sigsuspend(&emptyMask) == -1 && errno != EINTR)
+            errExit("sigsuspend");
+        printf("[%s %ld] Parent got signal\n", currTime("%T"), (long) getpid());
+
+        /* If required, return signal mask to its original state */
+
+        if (sigprocmask(SIG_SETMASK, &origMask, NULL) == -1)
+            errExit("sigprocmask");
+
+        /* Parent carries on to do other things... */
+
+        exit(EXIT_SUCCESS);
+    }
+}
diff --git a/procexec/fork_stdio_buf.c b/procexec/fork_stdio_buf.c
new file mode 100644 (file)
index 0000000..ed9740a
--- /dev/null
@@ -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 (file)
index 0000000..dc1bed8
--- /dev/null
@@ -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 <sys/wait.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    int numChildren, j;
+    pid_t childPid;
+
+    if (argc > 1 && strcmp(argv[1], "--help") == 0)
+        usageErr("%s [num-children]\n", argv[0]);
+
+    numChildren = (argc > 1) ? getInt(argv[1], GN_GT_0, "num-children") : 1;
+
+    setbuf(stdout, NULL);               /* Make stdout unbuffered */
+
+    for (j = 0; j < numChildren; j++) {
+        switch (childPid = fork()) {
+        case -1:
+            errExit("fork");
+
+        case 0:
+            printf("%d child\n", j);
+            _exit(EXIT_SUCCESS);
+
+        default:
+            printf("%d parent\n", j);
+            wait(NULL);                 /* Wait for child to terminate */
+            break;
+        }
+    }
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/procexec/fork_whos_on_first.count.awk b/procexec/fork_whos_on_first.count.awk
new file mode 100755 (executable)
index 0000000..34a20d3
--- /dev/null
@@ -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 (executable)
index 0000000..5cf0246
--- /dev/null
@@ -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 (file)
index 0000000..5594e03
--- /dev/null
@@ -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 <signal.h>
+#include <libgen.h>             /* For basename() declaration */
+#include "tlpi_hdr.h"
+
+#define CMD_SIZE 200
+
+int
+main(int argc, char *argv[])
+{
+    char cmd[CMD_SIZE];
+    pid_t childPid;
+
+    setbuf(stdout, NULL);       /* Disable buffering of stdout */
+
+    printf("Parent PID=%ld\n", (long) getpid());
+
+    switch (childPid = fork()) {
+    case -1:
+        errExit("fork");
+
+    case 0:     /* Child: immediately exits to become zombie */
+        printf("Child (PID=%ld) exiting\n", (long) getpid());
+        _exit(EXIT_SUCCESS);
+
+    default:    /* Parent */
+        sleep(3);               /* Give child a chance to start and exit */
+        snprintf(cmd, CMD_SIZE, "ps | grep %s", basename(argv[0]));
+        system(cmd);            /* View zombie child */
+
+        /* Now send the "sure kill" signal to the zombie */
+
+        if (kill(childPid, SIGKILL) == -1)
+            errMsg("kill");
+        sleep(3);               /* Give child a chance to react to signal */
+        printf("After sending SIGKILL to zombie (PID=%ld):\n", (long) childPid);
+        system(cmd);            /* View zombie child again */
+
+        exit(EXIT_SUCCESS);
+    }
+}
diff --git a/procexec/multi_SIGCHLD.c b/procexec/multi_SIGCHLD.c
new file mode 100644 (file)
index 0000000..5313e4b
--- /dev/null
@@ -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 <signal.h>
+#include <sys/wait.h>
+#include "print_wait_status.h"
+#include "curr_time.h"
+#include "tlpi_hdr.h"
+
+static volatile int numLiveChildren = 0;
+                /* Number of children started but not yet waited on */
+
+static void
+sigchldHandler(int sig)
+{
+    int status, savedErrno;
+    pid_t childPid;
+
+    /* UNSAFE: This handler uses non-async-signal-safe functions
+       (printf(), printWaitStatus(), currTime(); see Section 21.1.2) */
+
+    savedErrno = errno;         /* In case we modify 'errno' */
+
+    printf("%s handler: Caught SIGCHLD\n", currTime("%T"));
+
+    /* Do nonblocking waits until no more dead children are found */
+
+    while ((childPid = waitpid(-1, &status, WNOHANG)) > 0) {
+        printf("%s handler: Reaped child %ld - ", currTime("%T"),
+                (long) childPid);
+        printWaitStatus(NULL, status);
+        numLiveChildren--;
+    }
+
+    if (childPid == -1 && errno != ECHILD)
+        errMsg("waitpid");
+
+    sleep(5);           /* Artificially lengthen execution of handler */
+    printf("%s handler: returning\n", currTime("%T"));
+
+    errno = savedErrno;
+}
+
+int
+main(int argc, char *argv[])
+{
+    int j, sigCnt;
+    sigset_t blockMask, emptyMask;
+    struct sigaction sa;
+
+    if (argc < 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s child-sleep-time...\n", argv[0]);
+
+    setbuf(stdout, NULL);       /* Disable buffering of stdout */
+
+    sigCnt = 0;
+    numLiveChildren = argc - 1;
+
+    sigemptyset(&sa.sa_mask);
+    sa.sa_flags = 0;
+    sa.sa_handler = sigchldHandler;
+    if (sigaction(SIGCHLD, &sa, NULL) == -1)
+        errExit("sigaction");
+
+    /* Block SIGCHLD to prevent its delivery if a child terminates
+       before the parent commences the sigsuspend() loop below */
+
+    sigemptyset(&blockMask);
+    sigaddset(&blockMask, SIGCHLD);
+    if (sigprocmask(SIG_SETMASK, &blockMask, NULL) == -1)
+        errExit("sigprocmask");
+
+    /* Create one child process for each command-line argument */
+
+    for (j = 1; j < argc; j++) {
+        switch (fork()) {
+        case -1:
+            errExit("fork");
+
+        case 0:         /* Child - sleeps and then exits */
+            sleep(getInt(argv[j], GN_NONNEG, "child-sleep-time"));
+            printf("%s Child %d (PID=%ld) exiting\n", currTime("%T"),
+                    j, (long) getpid());
+            _exit(EXIT_SUCCESS);
+
+        default:        /* Parent - loops to create next child */
+            break;
+        }
+    }
+
+    /* Parent comes here: wait for SIGCHLD until all children are dead */
+
+    sigemptyset(&emptyMask);
+    while (numLiveChildren > 0) {
+        if (sigsuspend(&emptyMask) == -1 && errno != EINTR)
+            errExit("sigsuspend");
+        sigCnt++;
+    }
+
+    printf("%s All %d children have terminated; SIGCHLD was caught "
+            "%d times\n", currTime("%T"), argc - 1, sigCnt);
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/procexec/multi_wait.c b/procexec/multi_wait.c
new file mode 100644 (file)
index 0000000..ee5e5d2
--- /dev/null
@@ -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 <sys/wait.h>
+#include <time.h>
+#include "curr_time.h"              /* Declaration of currTime() */
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    int numDead;       /* Number of children so far waited for */
+    pid_t childPid;    /* PID of waited for child */
+    int j;
+
+    if (argc < 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s sleep-time...\n", argv[0]);
+
+    setbuf(stdout, NULL);           /* Disable buffering of stdout */
+
+    for (j = 1; j < argc; j++) {    /* Create one child for each argument */
+        switch (fork()) {
+        case -1:
+            errExit("fork");
+
+        case 0:                     /* Child sleeps for a while then exits */
+            printf("[%s] child %d started with PID %ld, sleeping %s "
+                    "seconds\n", currTime("%T"), j, (long) getpid(),
+                    argv[j]);
+            sleep(getInt(argv[j], GN_NONNEG, "sleep-time"));
+            _exit(EXIT_SUCCESS);
+
+        default:                    /* Parent just continues around loop */
+            break;
+        }
+    }
+
+    numDead = 0;
+    for (;;) {                      /* Parent waits for each child to exit */
+        childPid = wait(NULL);
+        if (childPid == -1) {
+            if (errno == ECHILD) {
+                printf("No more children - bye!\n");
+                exit(EXIT_SUCCESS);
+            } else {                /* Some other (unexpected) error */
+                errExit("wait");
+            }
+        }
+
+        numDead++;
+        printf("[%s] wait() returned child PID %ld (numDead=%d)\n",
+                currTime("%T"), (long) childPid, numDead);
+    }
+}
diff --git a/procexec/necho.c b/procexec/necho.c
new file mode 100644 (file)
index 0000000..f4f4b19
--- /dev/null
@@ -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 (file)
index 0000000..34847b5
--- /dev/null
@@ -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 (file)
index 0000000..fd8e00e
--- /dev/null
@@ -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 <string.h> */
+#include <string.h>
+#include <sys/wait.h>
+#include "print_wait_status.h"  /* Declaration of printWaitStatus() */
+#include "tlpi_hdr.h"
+
+/* NOTE: The following function employs printf(), which is not
+   async-signal-safe (see Section 21.1.2). As such, this function is
+   also not async-signal-safe (i.e., beware of calling it from a
+   SIGCHLD handler). */
+
+void                    /* Examine a wait() status using the W* macros */
+printWaitStatus(const char *msg, int status)
+{
+    if (msg != NULL)
+        printf("%s", msg);
+
+    if (WIFEXITED(status)) {
+        printf("child exited, status=%d\n", WEXITSTATUS(status));
+
+    } else if (WIFSIGNALED(status)) {
+        printf("child killed by signal %d (%s)",
+                WTERMSIG(status), strsignal(WTERMSIG(status)));
+#ifdef WCOREDUMP        /* Not in SUSv3, may be absent on some systems */
+        if (WCOREDUMP(status))
+            printf(" (core dumped)");
+#endif
+        printf("\n");
+
+    } else if (WIFSTOPPED(status)) {
+        printf("child stopped by signal %d (%s)\n",
+                WSTOPSIG(status), strsignal(WSTOPSIG(status)));
+
+#ifdef WIFCONTINUED     /* SUSv3 has this, but older Linux versions and
+                           some other UNIX implementations don't */
+    } else if (WIFCONTINUED(status)) {
+        printf("child continued\n");
+#endif
+
+    } else {            /* Should never happen */
+        printf("what happened to this child? (status=%x)\n",
+                (unsigned int) status);
+    }
+}
diff --git a/procexec/print_wait_status.h b/procexec/print_wait_status.h
new file mode 100644 (file)
index 0000000..f89ea75
--- /dev/null
@@ -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 (file)
index 0000000..e9d98db
--- /dev/null
@@ -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 <unistd.h>
+#include <sys/wait.h>
+#include <sys/types.h>
+
+int
+system(char *command)
+{
+    int status;
+    pid_t childPid;
+
+    switch (childPid = fork()) {
+    case -1: /* Error */
+        return -1;
+
+    case 0: /* Child */
+        execl("/bin/sh", "sh", "-c", command, (char *) NULL);
+        _exit(127);                     /* Failed exec */
+
+    default: /* Parent */
+        if (waitpid(childPid, &status, 0) == -1)
+            return -1;
+        else
+            return status;
+    }
+}
diff --git a/procexec/system.c b/procexec/system.c
new file mode 100644 (file)
index 0000000..b5c9968
--- /dev/null
@@ -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 <unistd.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include <sys/types.h>
+#include <errno.h>
+
+int
+system(const char *command)
+{
+    sigset_t blockMask, origMask;
+    struct sigaction saIgnore, saOrigQuit, saOrigInt, saDefault;
+    pid_t childPid;
+    int status, savedErrno;
+
+    if (command == NULL)                /* Is a shell available? */
+        return system(":") == 0;
+
+    /* The parent process (the caller of system()) blocks SIGCHLD
+       and ignore SIGINT and SIGQUIT while the child is executing.
+       We must change the signal settings prior to forking, to avoid
+       possible race conditions. This means that we must undo the
+       effects of the following in the child after fork(). */
+
+    sigemptyset(&blockMask);            /* Block SIGCHLD */
+    sigaddset(&blockMask, SIGCHLD);
+    sigprocmask(SIG_BLOCK, &blockMask, &origMask);
+
+    saIgnore.sa_handler = SIG_IGN;      /* Ignore SIGINT and SIGQUIT */
+    saIgnore.sa_flags = 0;
+    sigemptyset(&saIgnore.sa_mask);
+    sigaction(SIGINT, &saIgnore, &saOrigInt);
+    sigaction(SIGQUIT, &saIgnore, &saOrigQuit);
+
+    switch (childPid = fork()) {
+    case -1: /* fork() failed */
+        status = -1;
+        break;          /* Carry on to reset signal attributes */
+
+    case 0: /* Child: exec command */
+
+        /* We ignore possible error returns because the only specified error
+           is for a failed exec(), and because errors in these calls can't
+           affect the caller of system() (which is a separate process) */
+
+        saDefault.sa_handler = SIG_DFL;
+        saDefault.sa_flags = 0;
+        sigemptyset(&saDefault.sa_mask);
+
+        if (saOrigInt.sa_handler != SIG_IGN)
+            sigaction(SIGINT, &saDefault, NULL);
+        if (saOrigQuit.sa_handler != SIG_IGN)
+            sigaction(SIGQUIT, &saDefault, NULL);
+
+        sigprocmask(SIG_SETMASK, &origMask, NULL);
+
+        execl("/bin/sh", "sh", "-c", command, (char *) NULL);
+        _exit(127);                     /* We could not exec the shell */
+
+    default: /* Parent: wait for our child to terminate */
+
+        /* We must use waitpid() for this task; using wait() could inadvertently
+           collect the status of one of the caller's other children */
+
+        while (waitpid(childPid, &status, 0) == -1) {
+            if (errno != EINTR) {       /* Error other than EINTR */
+                status = -1;
+                break;                  /* So exit loop */
+            }
+        }
+        break;
+    }
+
+    /* Unblock SIGCHLD, restore dispositions of SIGINT and SIGQUIT */
+
+    savedErrno = errno;                 /* The following may change 'errno' */
+
+    sigprocmask(SIG_SETMASK, &origMask, NULL);
+    sigaction(SIGINT, &saOrigInt, NULL);
+    sigaction(SIGQUIT, &saOrigQuit, NULL);
+
+    errno = savedErrno;
+
+    return status;
+}
diff --git a/procexec/t_clone.c b/procexec/t_clone.c
new file mode 100644 (file)
index 0000000..03c8971
--- /dev/null
@@ -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 <signal.h>
+#include <sys/wait.h>
+#include <fcntl.h>
+#include <sched.h>
+#include "tlpi_hdr.h"
+
+#ifndef CHILD_SIG
+#define CHILD_SIG SIGUSR1       /* Signal to be generated on termination
+                                   of cloned child */
+#endif
+
+static int                      /* Startup function for cloned child */
+childFunc(void *arg)
+{
+    if (close(*((int *) arg)) == -1)
+        errExit("close");
+
+    return 0;                           /* Child terminates now */
+}
+
+int
+main(int argc, char *argv[])
+{
+    const int STACK_SIZE = 65536;       /* Stack size for cloned child */
+    char *stack;                        /* Start of stack buffer */
+    char *stackTop;                     /* End of stack buffer */
+    int s, fd, flags;
+
+    fd = open("/dev/null", O_RDWR);     /* Child will close this fd */
+    if (fd == -1)
+        errExit("open");
+
+    /* If argc > 1, child shares file descriptor table with parent */
+
+    flags = (argc > 1) ? CLONE_FILES : 0;
+
+    /* Allocate stack for child */
+
+    stack = malloc(STACK_SIZE);
+    if (stack == NULL)
+        errExit("malloc");
+    stackTop = stack + STACK_SIZE;      /* Assume stack grows downward */
+
+    /* Ignore CHILD_SIG, in case it is a signal whose default is to
+       terminate the process; but don't ignore SIGCHLD (which is ignored
+       by default), since that would prevent the creation of a zombie. */
+
+    if (CHILD_SIG != 0 && CHILD_SIG != SIGCHLD)
+        if (signal(CHILD_SIG, SIG_IGN) == SIG_ERR)          errExit("signal");
+
+    /* Create child; child commences execution in childFunc() */
+
+    if (clone(childFunc, stackTop, flags | CHILD_SIG, (void *) &fd) == -1)
+        errExit("clone");
+
+    /* Parent falls through to here. Wait for child; __WCLONE is
+       needed for child notifying with signal other than SIGCHLD. */
+
+    if (waitpid(-1, NULL, (CHILD_SIG != SIGCHLD) ? __WCLONE : 0) == -1)
+        errExit("waitpid");
+    printf("child has terminated\n");
+
+    /* Did close() of file descriptor in child affect parent? */
+
+    s = write(fd, "x", 1);
+    if (s == -1 && errno == EBADF)
+        printf("file descriptor %d has been closed\n", fd);
+    else if (s == -1)
+        printf("write() on file descriptor %d failed "
+                "unexpectedly (%s)\n", fd, strerror(errno));
+    else
+        printf("write() on file descriptor %d succeeded\n", fd);
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/procexec/t_execl.c b/procexec/t_execl.c
new file mode 100644 (file)
index 0000000..68f71c3
--- /dev/null
@@ -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 <stdlib.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    printf("Initial value of USER: %s\n", getenv("USER"));
+    if (putenv("USER=britta") != 0)
+        errExit("putenv");
+
+    /* exec printenv to display the USER and SHELL environment vars */
+
+    execl("/usr/bin/printenv", "printenv", "USER", "SHELL", (char *) NULL);
+    errExit("execl");           /* If we get here, something went wrong */
+}
diff --git a/procexec/t_execle.c b/procexec/t_execle.c
new file mode 100644 (file)
index 0000000..5e32529
--- /dev/null
@@ -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 (file)
index 0000000..d304052
--- /dev/null
@@ -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 (file)
index 0000000..a6caf1c
--- /dev/null
@@ -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 (file)
index 0000000..b41ef9e
--- /dev/null
@@ -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 (file)
index 0000000..868250a
--- /dev/null
@@ -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 <sys/wait.h>
+#include "print_wait_status.h"
+#include "tlpi_hdr.h"
+
+#define MAX_CMD_LEN 200
+
+int
+main(int argc, char *argv[])
+{
+    char str[MAX_CMD_LEN];      /* Command to be executed by system() */
+    int status;                 /* Status return from system() */
+
+    for (;;) {                  /* Read and execute a shell command */
+        printf("Command: ");
+        fflush(stdout);
+        if (fgets(str, MAX_CMD_LEN, stdin) == NULL)
+            break;              /* end-of-file */
+
+        status = system(str);
+        printf("system() returned: status=0x%04x (%d,%d)\n",
+                (unsigned int) status, status >> 8, status & 0xff);
+
+        if (status == -1) {
+            errExit("system");
+        } else {
+            if (WIFEXITED(status) && WEXITSTATUS(status) == 127)
+                printf("(Probably) could not invoke shell\n");
+            else                /* Shell successfully executed command */
+                printWaitStatus(NULL, status);
+        }
+    }
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/procexec/t_vfork.c b/procexec/t_vfork.c
new file mode 100644 (file)
index 0000000..73583b4
--- /dev/null
@@ -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 <unistd.h>
+                           in case _XOPEN_SOURCE >= 700 */
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    int istack = 222;
+
+    switch (vfork()) {
+    case -1:
+        errExit("vfork");
+
+    case 0:             /* Child executes first, in parent's memory space */
+        sleep(3);                   /* Even if we sleep for a while,
+                                       parent still is not scheduled */
+        write(STDOUT_FILENO, "Child executing\n", 16);
+        istack *= 3;                /* This change will be seen by parent */
+        _exit(EXIT_SUCCESS);
+
+    default:            /* Parent is blocked until child exits */
+        write(STDOUT_FILENO, "Parent executing\n", 17);
+        printf("istack=%d\n", istack);
+        exit(EXIT_SUCCESS);
+    }
+}
diff --git a/procexec/vfork_fd_test.c b/procexec/vfork_fd_test.c
new file mode 100644 (file)
index 0000000..d5b769d
--- /dev/null
@@ -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 <unistd.h>
+                           in case _XOPEN_SOURCE >= 700 */
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    switch (vfork()) {
+    case -1: errExit("vfork");
+
+    case 0: if (close(STDOUT_FILENO) == -1)
+                errMsg("close - child");
+            _exit(EXIT_SUCCESS);
+
+    default: break;
+    }
+
+    /* Now parent closes STDOUT_FILENO twice: only the second close
+       should fail, indicating that the close(STDOUT_FILENO) by the
+       child did not affect the parent. */
+
+    if (close(STDOUT_FILENO) == -1)
+        errMsg("close");
+    if (close(STDOUT_FILENO) == -1)
+        errMsg("close");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/procpri/Makefile b/procpri/Makefile
new file mode 100644 (file)
index 0000000..750769b
--- /dev/null
@@ -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 (file)
index 0000000..dd0ae77
--- /dev/null
@@ -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 <sched.h>
+#include <sys/resource.h>
+#include <sys/times.h>
+#include "tlpi_hdr.h"
+
+#define CSEC_STEP 25            /* CPU centiseconds between messages */
+
+static void
+useCPU(char *msg)
+{
+    struct tms tms;
+    int cpuCentisecs, prevStep, prevSec;
+
+    prevStep = 0;
+    prevSec = 0;
+    for (;;) {
+        if (times(&tms) == -1)
+            errExit("times");
+        cpuCentisecs = (tms.tms_utime + tms.tms_stime) * 100 /
+                        sysconf(_SC_CLK_TCK);
+
+        if (cpuCentisecs >= prevStep + CSEC_STEP) {
+            prevStep += CSEC_STEP;
+            printf("%s (PID %ld) cpu=%0.2f\n", msg, (long) getpid(),
+                    cpuCentisecs / 100.0);
+        }
+
+        if (cpuCentisecs > 300)         /* Terminate after 3 seconds */
+            break;
+
+        if (cpuCentisecs >= prevSec + 100) {    /* Yield once/second */
+            prevSec = cpuCentisecs;
+            sched_yield();
+        }
+    }
+}
+
+int
+main(int argc, char *argv[])
+{
+    struct rlimit rlim;
+    struct sched_param sp;
+    cpu_set_t set;
+
+    setbuf(stdout, NULL);               /* Disable buffering of stdout */
+
+    /* Confine all processes to a single CPU, so that the processes
+       won't run in parallel on multi-CPU systems. */
+
+    CPU_ZERO(&set);
+    CPU_SET(1, &set);
+
+    if (sched_setaffinity(getpid(), sizeof(set), &set) == -1)
+        errExit("sched_setaffinity");
+
+    /* Establish a CPU time limit. This demonstrates how we can
+       ensure that a runaway realtime process is terminated if we
+       make a programming error. The resource limit is inherited
+       by the child created using fork().
+
+       An alternative technique would be to make an alarm() call in each
+       process (since interval timers are not inherited across fork()). */
+
+    rlim.rlim_cur = rlim.rlim_max = 50;
+    if (setrlimit(RLIMIT_CPU, &rlim) == -1)
+        errExit("setrlimit");
+
+    /* Run the two processes in the lowest SCHED_FIFO priority */
+
+    sp.sched_priority = sched_get_priority_min(SCHED_FIFO);
+    if (sp.sched_priority == -1)
+        errExit("sched_get_priority_min");
+
+    if (sched_setscheduler(0, SCHED_FIFO, &sp) == -1)
+        errExit("sched_setscheduler");
+
+    switch (fork()) {
+    case -1:
+        errExit("fork");
+
+    case 0:
+        useCPU("child ");
+        exit(EXIT_SUCCESS);
+
+    default:
+        useCPU("parent");
+        exit(EXIT_SUCCESS);
+    }
+}
diff --git a/procpri/sched_set.c b/procpri/sched_set.c
new file mode 100644 (file)
index 0000000..37a157e
--- /dev/null
@@ -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 <sched.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    int j, pol;
+    struct sched_param sp;
+
+    if (argc < 3 || strchr("rfo"
+#ifdef SCHED_BATCH              /* Linux-specific */
+                "b"
+#endif
+#ifdef SCHED_IDLE               /* Linux-specific */
+                "i"
+#endif
+                , argv[1][0]) == NULL)
+        usageErr("%s policy priority [pid...]\n"
+                "    policy is 'r' (RR), 'f' (FIFO), "
+#ifdef SCHED_BATCH              /* Linux-specific */
+                "'b' (BATCH), "
+#endif
+#ifdef SCHED_IDLE               /* Linux-specific */
+                "'i' (IDLE), "
+#endif
+                "or 'o' (OTHER)\n",
+                argv[0]);
+
+    pol = (argv[1][0] == 'r') ? SCHED_RR :
+                (argv[1][0] == 'f') ? SCHED_FIFO :
+#ifdef SCHED_BATCH              /* Linux-specific, since kernel 2.6.16 */
+                (argv[1][0] == 'b') ? SCHED_BATCH :
+#endif
+#ifdef SCHED_IDLE               /* Linux-specific, since kernel 2.6.23 */
+                (argv[1][0] == 'i') ? SCHED_IDLE :
+#endif
+                SCHED_OTHER;
+
+    sp.sched_priority = getInt(argv[2], 0, "priority");
+
+    for (j = 3; j < argc; j++)
+        if (sched_setscheduler(getLong(argv[j], 0, "pid"), pol, &sp) == -1)
+            errExit("sched_setscheduler");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/procpri/sched_view.c b/procpri/sched_view.c
new file mode 100644 (file)
index 0000000..8765600
--- /dev/null
@@ -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 <sched.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    int j, pol;
+    struct sched_param sp;
+
+    for (j = 1; j < argc; j++) {
+        pol = sched_getscheduler(getLong(argv[j], 0, "pid"));
+        if (pol == -1)
+            errExit("sched_getscheduler");
+
+        if (sched_getparam(getLong(argv[j], 0, "pid"), &sp) == -1)
+            errExit("sched_getparam");
+
+        printf("%s: %-5s ", argv[j],
+                (pol == SCHED_OTHER) ? "OTHER" :
+                (pol == SCHED_RR) ? "RR" :
+                (pol == SCHED_FIFO) ? "FIFO" :
+#ifdef SCHED_BATCH              /* Linux-specific */
+                (pol == SCHED_BATCH) ? "BATCH" :
+#endif
+#ifdef SCHED_IDLE               /* Linux-specific */
+                (pol == SCHED_IDLE) ? "IDLE" :
+#endif
+                "???");
+        printf("%2d\n", sp.sched_priority);
+    }
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/procpri/t_sched_getaffinity.c b/procpri/t_sched_getaffinity.c
new file mode 100644 (file)
index 0000000..fc6fdf8
--- /dev/null
@@ -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 <sched.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    pid_t pid;
+    cpu_set_t set;
+    size_t s;
+    int cpu;
+
+    if (argc != 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s pid\n", argv[0]);
+
+    pid = getInt(argv[1], GN_NONNEG, "pid");
+
+    s = sched_getaffinity(pid, sizeof(cpu_set_t), &set);
+    if (s == -1)
+        errExit("sched_getaffinity");
+
+    printf("CPUs:");
+    for (cpu = 0; cpu < CPU_SETSIZE; cpu++)
+        if (CPU_ISSET(cpu, &set))
+            printf(" %d", cpu);
+    printf("\n");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/procpri/t_sched_setaffinity.c b/procpri/t_sched_setaffinity.c
new file mode 100644 (file)
index 0000000..8258e52
--- /dev/null
@@ -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 <pid> 0xe
+
+                (0xe = 1110 binary)
+
+   See also t_sched_getaffinity.c.
+
+   This program is Linux-specific. The CPU affinity system calls are provided
+   since kernel 2.6.
+*/
+#define _GNU_SOURCE
+#include <sched.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    pid_t pid;
+    cpu_set_t set;
+    int cpu;
+    unsigned long mask;
+
+    if (argc != 3 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s pid mask\n", argv[0]);
+
+    pid = getInt(argv[1], GN_NONNEG, "pid");
+    mask = getLong(argv[2], GN_ANY_BASE, "octal-mask");
+
+    CPU_ZERO(&set);
+
+    for (cpu = 0; mask > 0; cpu++, mask >>= 1)
+        if (mask & 1)
+            CPU_SET(cpu, &set);
+
+    if (sched_setaffinity(pid, sizeof(set), &set) == -1)
+        errExit("sched_setaffinity");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/procpri/t_setpriority.c b/procpri/t_setpriority.c
new file mode 100644 (file)
index 0000000..ff2b288
--- /dev/null
@@ -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 <sys/time.h>
+#include <sys/resource.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    int which, prio;
+    id_t who;
+
+    if (argc != 4 || strchr("pgu", argv[1][0]) == NULL)
+        usageErr("%s {p|g|u} who priority\n"
+                "    set priority of: p=process; g=process group; "
+                "u=processes for user\n", argv[0]);
+
+    /* Set nice value according to command-line arguments */
+
+    which = (argv[1][0] == 'p') ? PRIO_PROCESS :
+                (argv[1][0] == 'g') ? PRIO_PGRP : PRIO_USER;
+    who = getLong(argv[2], 0, "who");
+    prio = getInt(argv[3], 0, "prio");
+
+    if (setpriority(which, who, prio) == -1)
+        errExit("setpriority");
+
+    /* Retrieve nice value to check the change */
+
+    errno = 0;                  /* Because successful call may return -1 */
+    prio = getpriority(which, who);
+    if (prio == -1 && errno != 0)
+        errExit("getpriority");
+
+    printf("Nice value = %d\n", prio);
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/procres/Makefile b/procres/Makefile
new file mode 100644 (file)
index 0000000..333f5f7
--- /dev/null
@@ -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 (file)
index 0000000..43c9538
--- /dev/null
@@ -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 <sys/resource.h>
+#include "print_rlimit.h"       /* Declares function defined here */
+#include "tlpi_hdr.h"
+
+int                     /* Print 'msg' followed by limits for 'resource' */
+printRlimit(const char *msg, int resource)
+{
+    struct rlimit rlim;
+
+    if (getrlimit(resource, &rlim) == -1)
+        return -1;
+
+    printf("%s soft=", msg);
+    if (rlim.rlim_cur == RLIM_INFINITY)
+        printf("infinite");
+#ifdef RLIM_SAVED_CUR           /* Not defined on some implementations */
+    else if (rlim.rlim_cur == RLIM_SAVED_CUR)
+        printf("unrepresentable");
+#endif
+    else
+        printf("%lld", (long long) rlim.rlim_cur);
+
+    printf("; hard=");
+    if (rlim.rlim_max == RLIM_INFINITY)
+        printf("infinite\n");
+#ifdef RLIM_SAVED_MAX           /* Not defined on some implementations */
+    else if (rlim.rlim_max == RLIM_SAVED_MAX)
+        printf("unrepresentable");
+#endif
+    else
+        printf("%lld\n", (long long) rlim.rlim_max);
+
+    return 0;
+}
diff --git a/procres/print_rlimit.h b/procres/print_rlimit.h
new file mode 100644 (file)
index 0000000..703f4c8
--- /dev/null
@@ -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 (file)
index 0000000..1910d24
--- /dev/null
@@ -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 <sys/resource.h>
+#include "print_rusage.h"
+#include "tlpi_hdr.h"
+
+void
+printRusage(const char *leader, const struct rusage *ru)
+{
+    const char *ldr;
+
+    ldr = (leader == NULL) ? "" : leader;
+
+    printf("%sCPU time (secs):         user=%.3f; system=%.3f\n", ldr,
+            ru->ru_utime.tv_sec + ru->ru_utime.tv_usec / 1000000.0,
+            ru->ru_stime.tv_sec + ru->ru_stime.tv_usec / 1000000.0);
+    printf("%sMax resident set size:   %ld\n", ldr, ru->ru_maxrss);
+    printf("%sIntegral shared memory:  %ld\n", ldr, ru->ru_ixrss);
+    printf("%sIntegral unshared data:  %ld\n", ldr, ru->ru_idrss);
+    printf("%sIntegral unshared stack: %ld\n", ldr, ru->ru_isrss);
+    printf("%sPage reclaims:           %ld\n", ldr, ru->ru_minflt);
+    printf("%sPage faults:             %ld\n", ldr, ru->ru_majflt);
+    printf("%sSwaps:                   %ld\n", ldr, ru->ru_nswap);
+    printf("%sBlock I/Os:              input=%ld; output=%ld\n",
+            ldr, ru->ru_inblock, ru->ru_oublock);
+    printf("%sSignals received:        %ld\n", ldr, ru->ru_nsignals);
+    printf("%sIPC messages:            sent=%ld; received=%ld\n",
+            ldr, ru->ru_msgsnd, ru->ru_msgrcv);
+    printf("%sContext switches:        voluntary=%ld; "
+            "involuntary=%ld\n", ldr, ru->ru_nvcsw, ru->ru_nivcsw);
+}
diff --git a/procres/print_rusage.h b/procres/print_rusage.h
new file mode 100644 (file)
index 0000000..8a742ea
--- /dev/null
@@ -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 <sys/resource.h>
+
+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 (file)
index 0000000..85a1a1f
--- /dev/null
@@ -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 <sys/resource.h>
+#include "print_rlimit.h"               /* Declaration of printRlimit() */
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    struct rlimit rl;
+    int j;
+    pid_t childPid;
+
+    if (argc < 2 || argc > 3 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s soft-limit [hard-limit]\n", argv[0]);
+
+    printRlimit("Initial maximum process limits: ", RLIMIT_NPROC);
+
+    /* Set new process limits (hard == soft if not specified) */
+
+    rl.rlim_cur = (argv[1][0] == 'i') ? RLIM_INFINITY :
+                                getInt(argv[1], 0, "soft-limit");
+    rl.rlim_max = (argc == 2) ? rl.rlim_cur :
+                (argv[2][0] == 'i') ? RLIM_INFINITY :
+                                getInt(argv[2], 0, "hard-limit");
+    if (setrlimit(RLIMIT_NPROC, &rl) == -1)
+        errExit("setrlimit");
+
+    printRlimit("New maximum process limits:     ", RLIMIT_NPROC);
+
+    /* Create as many children as possible */
+
+    for (j = 1; ; j++) {
+        switch (childPid = fork()) {
+        case -1: errExit("fork");
+
+        case 0: _exit(EXIT_SUCCESS);            /* Child */
+
+        default:        /* Parent: display message about each new child
+                           and let the resulting zombies accumulate */
+            printf("Child %d (PID=%ld) started\n", j, (long) childPid);
+            break;
+        }
+    }
+}
diff --git a/procres/rusage.c b/procres/rusage.c
new file mode 100644 (file)
index 0000000..98245bb
--- /dev/null
@@ -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 <sys/resource.h>
+#include <sys/wait.h>
+#include "print_rusage.h"
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    pid_t childPid;
+    struct rusage ru;
+
+    if (argc < 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s command arg...\n", argv[0]);
+
+    switch (childPid = fork()) {
+    case -1:
+        errExit("fork");
+
+    case 0:
+        execvp(argv[1], &argv[1]);
+        errExit("execvp");
+
+    default:
+        printf("Command PID: %ld\n", (long) childPid);
+        if (wait(NULL) == -1)
+            errExit("wait");
+        if (getrusage(RUSAGE_CHILDREN, &ru) == -1)
+            errExit("getrusage");
+
+        printf("\n");
+        printRusage("\t", &ru);
+        exit(EXIT_SUCCESS);
+    }
+}
diff --git a/procres/rusage_wait.c b/procres/rusage_wait.c
new file mode 100644 (file)
index 0000000..0fde0d2
--- /dev/null
@@ -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 <sys/wait.h>
+#include <signal.h>
+#include <time.h>
+#include <sys/resource.h>
+#include "tlpi_hdr.h"
+
+#define NSECS 3         /* Amount of CPU to consume in child */
+
+#define SIG SIGUSR1     /* Child uses this signal to tell parent
+                           that it is about to terminate */
+
+static void
+handler(int sig)
+{
+    /* Do nothing: just interrupt sigsuspend() */
+}
+
+static void
+printChildRusage(const char *msg)
+{
+    struct rusage ru;
+
+    printf("%s", msg);
+    if (getrusage(RUSAGE_CHILDREN, &ru) == -1)
+        errExit("getrusage");
+    printf("user CPU=%.2f secs; system CPU=%.2f secs\n",
+            ru.ru_utime.tv_sec + ru.ru_utime.tv_usec / 1000000.0,
+            ru.ru_stime.tv_sec + ru.ru_stime.tv_usec / 1000000.0);
+}
+
+int
+main(int argc, char *argv[])
+{
+    clock_t start;
+    sigset_t mask;
+    struct sigaction sa;
+
+    setbuf(stdout, NULL);       /* Disable buffering of stdout */
+
+    sa.sa_handler = handler;
+    sa.sa_flags = 0;
+    sigemptyset(&sa.sa_mask);
+    if (sigaction(SIG, &sa, NULL) == -1)
+        errExit("sigaction");
+
+    /* Child informs parent of impending termination using a signal;
+       block that signal until the parent is ready to catch it. */
+
+    sigemptyset(&mask);
+    sigaddset(&mask, SIG);
+    if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
+        errExit("sigprocmask");
+
+    switch (fork()) {
+    case -1:
+        errExit("fork");
+
+    case 0:             /* Child */
+        for (start = clock(); clock() - start < NSECS * CLOCKS_PER_SEC;)
+            continue;           /* Burn NSECS seconds of CPU time */
+        printf("Child terminating\n");
+
+        /* Tell parent we're nearly done */
+
+        if (kill(getppid(), SIG) == -1)
+            errExit("kill");
+        _exit(EXIT_SUCCESS);
+
+    default:    /* Parent */
+        sigemptyset(&mask);
+        sigsuspend(&mask);      /* Wait for signal from child */
+
+        sleep(2);               /* Allow child a bit more time to terminate */
+
+        printChildRusage("Before wait: ");
+        if (wait(NULL) == -1)
+            errExit("wait");
+        printChildRusage("After wait:  ");
+        exit(EXIT_SUCCESS);
+    }
+}
diff --git a/progconc/Makefile b/progconc/Makefile
new file mode 100644 (file)
index 0000000..8641861
--- /dev/null
@@ -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 (file)
index 0000000..1ebc85f
--- /dev/null
@@ -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 (file)
index 0000000..e2119cd
--- /dev/null
@@ -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 (file)
index 0000000..f7c1e79
--- /dev/null
@@ -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 <semaphore.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+static void
+usageError(const char *progName)
+{
+    fprintf(stderr, "Usage: %s [-cx] name [octal-perms [value]]\n", progName);
+    fprintf(stderr, "    -c   Create semaphore (O_CREAT)\n");
+    fprintf(stderr, "    -x   Create exclusively (O_EXCL)\n");
+    exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+    int flags, opt;
+    mode_t perms;
+    unsigned int value;
+    sem_t *sem;
+
+    flags = 0;
+    while ((opt = getopt(argc, argv, "cx")) != -1) {
+        switch (opt) {
+        case 'c':   flags |= O_CREAT;           break;
+        case 'x':   flags |= O_EXCL;            break;
+        default:    usageError(argv[0]);
+        }
+    }
+
+    if (optind >= argc)
+        usageError(argv[0]);
+
+    /* Default permissions are rw-------; default semaphore initialization
+       value is 0 */
+
+    perms = (argc <= optind + 1) ? (S_IRUSR | S_IWUSR) :
+                getInt(argv[optind + 1], GN_BASE_8, "octal-perms");
+    value = (argc <= optind + 2) ? 0 : getInt(argv[optind + 2], 0, "value");
+
+    sem = sem_open(argv[optind], flags, perms, value);
+    if (sem == SEM_FAILED)
+        errExit("sem_open");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/psem/psem_getvalue.c b/psem/psem_getvalue.c
new file mode 100644 (file)
index 0000000..d02e1e6
--- /dev/null
@@ -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 <semaphore.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    int value;
+    sem_t *sem;
+
+    if (argc != 2)
+        usageErr("%s sem-name\n", argv[0]);
+
+    sem = sem_open(argv[1], 0);
+    if (sem == SEM_FAILED)
+        errExit("sem_open");
+
+    if (sem_getvalue(sem, &value) == -1)
+        errExit("sem_getvalue");
+
+    printf("%d\n", value);
+    exit(EXIT_SUCCESS);
+}
diff --git a/psem/psem_post.c b/psem/psem_post.c
new file mode 100644 (file)
index 0000000..724b00b
--- /dev/null
@@ -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 <semaphore.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    sem_t *sem;
+
+    if (argc != 2)
+        usageErr("%s sem-name\n", argv[0]);
+
+    sem = sem_open(argv[1], 0);
+    if (sem == SEM_FAILED)
+        errExit("sem_open");
+
+    if (sem_post(sem) == -1)
+        errExit("sem_post");
+    exit(EXIT_SUCCESS);
+}
diff --git a/psem/psem_timedwait.c b/psem/psem_timedwait.c
new file mode 100644 (file)
index 0000000..4cbde9a
--- /dev/null
@@ -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 <semaphore.h>
+#include <time.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    sem_t *sem;
+    struct timespec ts;
+
+    if (argc != 3 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s sem-name num-secs\n", argv[0]);
+
+    sem = sem_open(argv[1], 0);
+    if (sem == SEM_FAILED)
+        errExit("sem_open");
+
+    /* sem_timedwait() expects an absolute time in its second argument.
+       So we take the number of (relative) seconds specified on the
+       command line, and add it to the current system time. */
+
+    if (clock_gettime(CLOCK_REALTIME, &ts) == -1)
+        errExit("clock_gettime-CLOCK_REALTIME");
+
+    ts.tv_sec += atoi(argv[2]);
+
+    if (sem_timedwait(sem, &ts) == -1)
+        errExit("sem_timedwait");
+
+    printf("%ld sem_wait() succeeded\n", (long) getpid());
+    exit(EXIT_SUCCESS);
+}
diff --git a/psem/psem_trywait.c b/psem/psem_trywait.c
new file mode 100644 (file)
index 0000000..b8bf27b
--- /dev/null
@@ -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 <semaphore.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    sem_t *sem;
+
+    if (argc < 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s sem-name\n", argv[0]);
+
+    sem = sem_open(argv[1], 0);
+    if (sem == SEM_FAILED)
+        errExit("sem_open");
+
+    if (sem_trywait(sem) == -1)
+        errExit("sem_trywait");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/psem/psem_unlink.c b/psem/psem_unlink.c
new file mode 100644 (file)
index 0000000..242d0f5
--- /dev/null
@@ -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 <semaphore.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    if (argc != 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s sem-name\n", argv[0]);
+
+    if (sem_unlink(argv[1]) == -1)
+        errExit("sem_unlink");
+    exit(EXIT_SUCCESS);
+}
diff --git a/psem/psem_wait.c b/psem/psem_wait.c
new file mode 100644 (file)
index 0000000..eed8f83
--- /dev/null
@@ -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 <semaphore.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    sem_t *sem;
+
+    if (argc < 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s sem-name\n", argv[0]);
+
+    sem = sem_open(argv[1], 0);
+    if (sem == SEM_FAILED)
+        errExit("sem_open");
+
+    if (sem_wait(sem) == -1)
+        errExit("sem_wait");
+
+    printf("%ld sem_wait() succeeded\n", (long) getpid());
+    exit(EXIT_SUCCESS);
+}
diff --git a/psem/thread_incr_psem.c b/psem/thread_incr_psem.c
new file mode 100644 (file)
index 0000000..9c1136d
--- /dev/null
@@ -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 <semaphore.h>
+#include <pthread.h>
+#include "tlpi_hdr.h"
+
+static int glob = 0;
+static sem_t sem;
+
+static void *                   /* Loop 'arg' times incrementing 'glob' */
+threadFunc(void *arg)
+{
+    int loops = *((int *) arg);
+    int loc, j;
+
+    for (j = 0; j < loops; j++) {
+        if (sem_wait(&sem) == -1)
+            errExit("sem_wait");
+
+        loc = glob;
+        loc++;
+        glob = loc;
+
+        if (sem_post(&sem) == -1)
+            errExit("sem_post");
+    }
+
+    return NULL;
+}
+
+int
+main(int argc, char *argv[])
+{
+    pthread_t t1, t2;
+    int loops, s;
+
+    loops = (argc > 1) ? getInt(argv[1], GN_GT_0, "num-loops") : 10000000;
+
+    /* Initialize a semaphore with the value 1 */
+
+    if (sem_init(&sem, 0, 1) == -1)
+        errExit("sem_init");
+
+    /* Create two threads that increment 'glob' */
+
+    s = pthread_create(&t1, NULL, threadFunc, &loops);
+    if (s != 0)
+        errExitEN(s, "pthread_create");
+    s = pthread_create(&t2, NULL, threadFunc, &loops);
+    if (s != 0)
+        errExitEN(s, "pthread_create");
+
+    /* Wait for threads to terminate */
+
+    s = pthread_join(t1, NULL);
+    if (s != 0)
+        errExitEN(s, "pthread_join");
+    s = pthread_join(t2, NULL);
+    if (s != 0)
+        errExitEN(s, "pthread_join");
+
+    printf("glob = %d\n", glob);
+    exit(EXIT_SUCCESS);
+}
diff --git a/pshm/Makefile b/pshm/Makefile
new file mode 100644 (file)
index 0000000..3c6959b
--- /dev/null
@@ -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 (file)
index 0000000..820d723
--- /dev/null
@@ -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 <sys/stat.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include "tlpi_hdr.h"
+
+static void
+usageError(const char *progName)
+{
+    fprintf(stderr, "Usage: %s [-cx] shm-name size [octal-perms]\n", progName);
+    fprintf(stderr, "    -c   Create shared memory (O_CREAT)\n");
+    fprintf(stderr, "    -x   Create exclusively (O_EXCL)\n");
+    exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+    int flags, opt, fd;
+    mode_t perms;
+    size_t size;
+    void *addr;
+
+    flags = O_RDWR;
+    while ((opt = getopt(argc, argv, "cx")) != -1) {
+        switch (opt) {
+        case 'c':   flags |= O_CREAT;           break;
+        case 'x':   flags |= O_EXCL;            break;
+        default:    usageError(argv[0]);
+        }
+    }
+
+    if (optind + 1 >= argc)
+        usageError(argv[0]);
+
+    size = getLong(argv[optind + 1], GN_ANY_BASE, "size");
+    perms = (argc <= optind + 2) ? (S_IRUSR | S_IWUSR) :
+                getLong(argv[optind + 2], GN_BASE_8, "octal-perms");
+
+    /* Create shared memory object and set its size */
+
+    fd = shm_open(argv[optind], flags, perms);
+    if (fd == -1)
+        errExit("shm_open");
+
+    if (ftruncate(fd, size) == -1)
+        errExit("ftruncate");
+
+    /* Map shared memory object */
+
+    addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+    if (addr == MAP_FAILED)
+        errExit("mmap");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/pshm/pshm_read.c b/pshm/pshm_read.c
new file mode 100644 (file)
index 0000000..89f662e
--- /dev/null
@@ -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 <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    int fd;
+    char *addr;
+    struct stat sb;
+
+    if (argc != 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s shm-name\n", argv[0]);
+
+    fd = shm_open(argv[1], O_RDONLY, 0);    /* Open existing object */
+    if (fd == -1)
+        errExit("shm_open");
+
+    /* Use shared memory object size as length argument for mmap()
+       and as number of bytes to write() */
+
+    if (fstat(fd, &sb) == -1)
+        errExit("fstat");
+
+    addr = mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, fd, 0);
+    if (addr == MAP_FAILED)
+        errExit("mmap");
+
+    if (close(fd) == -1)                    /* 'fd' is no longer needed */
+        errExit("close");
+
+    write(STDOUT_FILENO, addr, sb.st_size);
+    printf("\n");
+    exit(EXIT_SUCCESS);
+}
diff --git a/pshm/pshm_unlink.c b/pshm/pshm_unlink.c
new file mode 100644 (file)
index 0000000..0a0f0e3
--- /dev/null
@@ -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 <fcntl.h>
+#include <sys/mman.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    if (argc != 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s shm-name\n", argv[0]);
+
+    if (shm_unlink(argv[1]) == -1)
+        errExit("shm_unlink");
+    exit(EXIT_SUCCESS);
+}
diff --git a/pshm/pshm_write.c b/pshm/pshm_write.c
new file mode 100644 (file)
index 0000000..0a9c1c3
--- /dev/null
@@ -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 <fcntl.h>
+#include <sys/mman.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    int fd;
+    size_t len;                 /* Size of shared memory object */
+    char *addr;
+
+    if (argc != 3 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s shm-name string\n", argv[0]);
+
+    fd = shm_open(argv[1], O_RDWR, 0);      /* Open existing object */
+    if (fd == -1)
+        errExit("shm_open");
+
+    len = strlen(argv[2]);
+    if (ftruncate(fd, len) == -1)           /* Resize object to hold string */
+        errExit("ftruncate");
+    printf("Resized to %ld bytes\n", (long) len);
+
+    addr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+    if (addr == MAP_FAILED)
+        errExit("mmap");
+
+    if (close(fd) == -1)                    /* 'fd' is no longer needed */
+        errExit("close");
+
+    printf("copying %ld bytes\n", (long) len);
+    memcpy(addr, argv[2], len);             /* Copy string to shared memory */
+    exit(EXIT_SUCCESS);
+}
diff --git a/pty/Makefile b/pty/Makefile
new file mode 100644 (file)
index 0000000..977dad4
--- /dev/null
@@ -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 (file)
index 0000000..0cbe4e5
--- /dev/null
@@ -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 <fcntl.h>
+#include <termios.h>
+#include <sys/ioctl.h>
+#include "pty_master_open.h"
+#include "pty_fork.h"                   /* Declares ptyFork() */
+#include "tlpi_hdr.h"
+
+#define MAX_SNAME 1000                  /* Maximum size for pty slave name */
+
+pid_t
+ptyFork(int *masterFd, char *slaveName, size_t snLen,
+        const struct termios *slaveTermios, const struct winsize *slaveWS)
+{
+    int mfd, slaveFd, savedErrno;
+    pid_t childPid;
+    char slname[MAX_SNAME];
+
+    mfd = ptyMasterOpen(slname, MAX_SNAME);
+    if (mfd == -1)
+        return -1;
+
+    if (slaveName != NULL) {            /* Return slave name to caller */
+        if (strlen(slname) < snLen) {
+            strncpy(slaveName, slname, snLen);
+
+        } else {                        /* 'slaveName' was too small */
+            close(mfd);
+            errno = EOVERFLOW;
+            return -1;
+        }
+    }
+
+    childPid = fork();
+
+    if (childPid == -1) {               /* fork() failed */
+        savedErrno = errno;             /* close() might change 'errno' */
+        close(mfd);                     /* Don't leak file descriptors */
+        errno = savedErrno;
+        return -1;
+    }
+
+    if (childPid != 0) {                /* Parent */
+        *masterFd = mfd;                /* Only parent gets master fd */
+        return childPid;                /* Like parent of fork() */
+    }
+
+    /* Child falls through to here */
+
+    if (setsid() == -1)                 /* Start a new session */
+        err_exit("ptyFork:setsid");
+
+    close(mfd);                         /* Not needed in child */
+
+    slaveFd = open(slname, O_RDWR);     /* Becomes controlling tty */
+    if (slaveFd == -1)
+        err_exit("ptyFork:open-slave");
+
+#ifdef TIOCSCTTY                        /* Acquire controlling tty on BSD */
+    if (ioctl(slaveFd, TIOCSCTTY, 0) == -1)
+        err_exit("ptyFork:ioctl-TIOCSCTTY");
+#endif
+
+    if (slaveTermios != NULL)           /* Set slave tty attributes */
+        if (tcsetattr(slaveFd, TCSANOW, slaveTermios) == -1)
+            err_exit("ptyFork:tcsetattr");
+
+    if (slaveWS != NULL)                /* Set slave tty window size */
+        if (ioctl(slaveFd, TIOCSWINSZ, slaveWS) == -1)
+            err_exit("ptyFork:ioctl-TIOCSWINSZ");
+
+    /* Duplicate pty slave to be child's stdin, stdout, and stderr */
+
+    if (dup2(slaveFd, STDIN_FILENO) != STDIN_FILENO)
+        err_exit("ptyFork:dup2-STDIN_FILENO");
+    if (dup2(slaveFd, STDOUT_FILENO) != STDOUT_FILENO)
+        err_exit("ptyFork:dup2-STDOUT_FILENO");
+    if (dup2(slaveFd, STDERR_FILENO) != STDERR_FILENO)
+        err_exit("ptyFork:dup2-STDERR_FILENO");
+
+    if (slaveFd > STDERR_FILENO)        /* Safety check */
+        close(slaveFd);                 /* No longer need this fd */
+
+    return 0;                           /* Like child of fork() */
+}
diff --git a/pty/pty_fork.h b/pty/pty_fork.h
new file mode 100644 (file)
index 0000000..99c5e80
--- /dev/null
@@ -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 <termios.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+
+pid_t ptyFork(int *masterFd, char *slaveName, size_t snLen,
+        const struct termios *slaveTermios, const struct winsize *slaveWS);
+
+#endif
diff --git a/pty/pty_master_open.c b/pty/pty_master_open.c
new file mode 100644 (file)
index 0000000..b8e1b6e
--- /dev/null
@@ -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 <stdlib.h>
+#include <fcntl.h>
+#include "pty_master_open.h"            /* Declares ptyMasterOpen() */
+#include "tlpi_hdr.h"
+
+/* Some implementations don't have posix_openpt() */
+
+#if defined(__sun)                      /* Not on Solaris 8 */
+#define NO_POSIX_OPENPT
+#endif
+
+#ifdef NO_POSIX_OPENPT
+
+static int
+posix_openpt(int flags)
+{
+    return open("/dev/ptmx", flags);
+}
+
+#endif
+
+/* Open a pty master, returning file descriptor, or -1 on error.
+   On successful completion, the name of the corresponding pty
+   slave is returned in 'slaveName'. 'snLen' should be set to
+   indicate the size of the buffer pointed to by 'slaveName'. */
+
+int
+ptyMasterOpen(char *slaveName, size_t snLen)
+{
+    int masterFd, savedErrno;
+    char *p;
+
+    masterFd = posix_openpt(O_RDWR | O_NOCTTY);         /* Open pty master */
+    if (masterFd == -1)
+        return -1;
+
+    if (grantpt(masterFd) == -1) {              /* Grant access to slave pty */
+        savedErrno = errno;
+        close(masterFd);                        /* Might change 'errno' */
+        errno = savedErrno;
+        return -1;
+    }
+
+    if (unlockpt(masterFd) == -1) {             /* Unlock slave pty */
+        savedErrno = errno;
+        close(masterFd);                        /* Might change 'errno' */
+        errno = savedErrno;
+        return -1;
+    }
+
+    p = ptsname(masterFd);                      /* Get slave pty name */
+    if (p == NULL) {
+        savedErrno = errno;
+        close(masterFd);                        /* Might change 'errno' */
+        errno = savedErrno;
+        return -1;
+    }
+
+    if (strlen(p) < snLen) {
+        strncpy(slaveName, p, snLen);
+    } else {                    /* Return an error if buffer too small */
+        close(masterFd);
+        errno = EOVERFLOW;
+        return -1;
+    }
+
+    return masterFd;
+}
diff --git a/pty/pty_master_open.h b/pty/pty_master_open.h
new file mode 100644 (file)
index 0000000..715fb23
--- /dev/null
@@ -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 <sys/types.h>
+
+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 (file)
index 0000000..0bef872
--- /dev/null
@@ -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 <fcntl.h>
+#include "pty_master_open.h"            /* Declares ptyMasterOpen() */
+#include "tlpi_hdr.h"
+
+#define PTYM_PREFIX     "/dev/pty"
+#define PTYS_PREFIX     "/dev/tty"
+#define PTY_PREFIX_LEN  (sizeof(PTYM_PREFIX) - 1)
+#define PTY_NAME_LEN    (PTY_PREFIX_LEN + sizeof("XY"))
+#define X_RANGE         "pqrstuvwxyzabcde"
+#define Y_RANGE         "0123456789abcdef"
+
+/* Open a BSD pty master, returning file descriptor, or -1 on error.
+   On successful completion, the name of the corresponding pty slave
+   is returned in 'slaveName'. 'snLen' should be set to indicate the
+   size of the buffer pointed to by 'slaveName'. */
+
+int
+ptyMasterOpen(char *slaveName, size_t snLen)
+{
+    int masterFd, n;
+    char *x, *y;
+    char masterName[PTY_NAME_LEN];
+
+    if (PTY_NAME_LEN > snLen) {
+        errno = EOVERFLOW;
+        return -1;
+    }
+
+    memset(masterName, 0, PTY_NAME_LEN);
+    strncpy(masterName, PTYM_PREFIX, PTY_PREFIX_LEN);
+
+    /* Scan through all possible BSD pseudoterminal master names until
+       we find one that we can open. */
+
+    for (x = X_RANGE; *x != '\0'; x++) {
+        masterName[PTY_PREFIX_LEN] = *x;
+
+        for (y = Y_RANGE; *y != '\0'; y++) {
+            masterName[PTY_PREFIX_LEN + 1] = *y;
+
+            masterFd = open(masterName, O_RDWR);
+
+            if (masterFd == -1) {
+                if (errno == ENOENT)    /* No such file */
+                    return -1;          /* Probably no more pty devices */
+                else                    /* Other error (e.g., pty busy) */
+                    continue;
+
+            } else {            /* Return slave name corresponding to master */
+                n = snprintf(slaveName, snLen, "%s%c%c", PTYS_PREFIX, *x, *y);
+                if (n >= snLen) {
+                    errno = EOVERFLOW;
+                    return -1;
+                } else if (n == -1) {
+                    return -1;
+                }
+
+                return masterFd;
+            }
+        }
+    }
+
+    return -1;                  /* Tried all ptys without success */
+}
diff --git a/pty/script.c b/pty/script.c
new file mode 100644 (file)
index 0000000..e062bf4
--- /dev/null
@@ -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 <sys/stat.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <termios.h>
+#if ! defined(__hpux)
+/* HP-UX 11 doesn't have this header file */
+#include <sys/select.h>
+#endif
+#include "pty_fork.h"           /* Declaration of ptyFork() */
+#include "tty_functions.h"      /* Declaration of ttySetRaw() */
+#include "tlpi_hdr.h"
+
+#define BUF_SIZE 256
+#define MAX_SNAME 1000
+
+struct termios ttyOrig;
+
+static void             /* Reset terminal mode on program exit */
+ttyReset(void)
+{
+    if (tcsetattr(STDIN_FILENO, TCSANOW, &ttyOrig) == -1)
+        errExit("tcsetattr");
+}
+
+int
+main(int argc, char *argv[])
+{
+    char slaveName[MAX_SNAME];
+    char *shell;
+    int masterFd, scriptFd;
+    struct winsize ws;
+    fd_set inFds;
+    char buf[BUF_SIZE];
+    ssize_t numRead;
+    pid_t childPid;
+
+    /* Retrieve the attributes of terminal on which we are started */
+
+    if (tcgetattr(STDIN_FILENO, &ttyOrig) == -1)
+        errExit("tcgetattr");
+    if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) < 0)
+        errExit("ioctl-TIOCGWINSZ");
+
+    /* Create a child process, with parent and child connected via a
+       pty pair. The child is connected to the pty slave and its terminal
+       attributes are set to be the same as those retrieved above. */
+
+    childPid = ptyFork(&masterFd, slaveName, MAX_SNAME, &ttyOrig, &ws);
+    if (childPid == -1)
+        errExit("ptyFork");
+
+    if (childPid == 0) {        /* Child: execute a shell on pty slave */
+
+        /* If the SHELL variable is set, use its value to determine
+           the shell execed in child. Otherwise use /bin/sh. */
+
+        shell = getenv("SHELL");
+        if (shell == NULL || *shell == '\0')
+            shell = "/bin/sh";
+
+        execlp(shell, shell, (char *) NULL);
+        errExit("execlp");      /* If we get here, something went wrong */
+    }
+
+    /* Parent: relay data between terminal and pty master */
+
+    scriptFd = open((argc > 1) ? argv[1] : "typescript",
+                        O_WRONLY | O_CREAT | O_TRUNC,
+                        S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP |
+                                S_IROTH | S_IWOTH);
+    if (scriptFd == -1)
+        errExit("open typescript");
+
+    /* Place terminal in raw mode so that we can pass all terminal
+     input to the pseudoterminal master untouched */
+
+    ttySetRaw(STDIN_FILENO, &ttyOrig);
+
+    if (atexit(ttyReset) != 0)
+        errExit("atexit");
+
+    /* Loop monitoring terminal and pty master for input. If the
+       terminal is ready for input, then read some bytes and write
+       them to the pty master. If the pty master is ready for input,
+       then read some bytes and write them to the terminal. */
+
+    for (;;) {
+        FD_ZERO(&inFds);
+        FD_SET(STDIN_FILENO, &inFds);
+        FD_SET(masterFd, &inFds);
+
+        if (select(masterFd + 1, &inFds, NULL, NULL, NULL) == -1)
+            errExit("select");
+
+        if (FD_ISSET(STDIN_FILENO, &inFds)) {   /* stdin --> pty */
+            numRead = read(STDIN_FILENO, buf, BUF_SIZE);
+            if (numRead <= 0)
+                exit(EXIT_SUCCESS);
+
+            if (write(masterFd, buf, numRead) != numRead)
+                fatal("partial/failed write (masterFd)");
+        }
+
+        if (FD_ISSET(masterFd, &inFds)) {      /* pty --> stdout+file */
+            numRead = read(masterFd, buf, BUF_SIZE);
+            if (numRead <= 0)
+                exit(EXIT_SUCCESS);
+
+            if (write(STDOUT_FILENO, buf, numRead) != numRead)
+                fatal("partial/failed write (STDOUT_FILENO)");
+            if (write(scriptFd, buf, numRead) != numRead)
+                fatal("partial/failed write (scriptFd)");
+        }
+    }
+}
diff --git a/pty/unbuffer.c b/pty/unbuffer.c
new file mode 100644 (file)
index 0000000..8301550
--- /dev/null
@@ -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 <termios.h>
+#if ! defined(__hpux)
+/* HP-UX 11 doesn't have this header file */
+#include <sys/select.h>
+#endif
+#include "pty_fork.h"           /* Declaration of ptyFork() */
+#include "tty_functions.h"      /* Declaration of ttySetRaw() */
+#include "tlpi_hdr.h"
+
+#define BUF_SIZE 256
+#define MAX_SNAME 1000
+
+struct termios ttyOrig;
+
+static void             /* Reset terminal mode on program exit */
+ttyReset(void)
+{
+    if (tcsetattr(STDIN_FILENO, TCSANOW, &ttyOrig) == -1)
+        errExit("tcsetattr");
+}
+
+int
+main(int argc, char *argv[])
+{
+    char slaveName[MAX_SNAME];
+    int masterFd;
+    fd_set inFds;
+    char buf[BUF_SIZE];
+    ssize_t numRead;
+    struct winsize ws;
+
+    /* Retrieve the attributes of terminal on which we are started */
+
+    if (tcgetattr(STDIN_FILENO, &ttyOrig) == -1)
+        errExit("tcgetattr");
+    if (ioctl(STDIN_FILENO, TIOCGWINSZ, (char *) &ws) < 0)
+        errExit("TIOCGWINSZ error");
+
+    /* Create a child process, with parent and child connected via a
+       pty pair. The child is connected to the pty slave and its terminal
+       attributes are set to be the same as those retrieved above. */
+
+    switch (ptyFork(&masterFd, slaveName, MAX_SNAME, &ttyOrig, &ws)) {
+    case -1:
+        errExit("ptyFork");
+
+    case 0:     /* Child executes command given in argv[1]... */
+        execvp(argv[1], &argv[1]);
+        errExit("execvp");
+
+    default:    /* Parent relays data between terminal and pty master */
+
+        /* Place terminal in raw mode so that we can pass all
+           terminal input to the pseudoterminal master untouched */
+
+        ttySetRaw(STDIN_FILENO, &ttyOrig);
+        if (atexit(ttyReset) != 0)
+            errExit("atexit");
+
+        /* Loop monitoring terminal and pty master for input. If the
+           terminal is ready for input, read some bytes and write
+           them to the pty master. If the pty master is ready for
+           input, read some bytes and write them to the terminal. */
+
+        for (;;) {
+            FD_ZERO(&inFds);
+            FD_SET(STDIN_FILENO, &inFds);
+            FD_SET(masterFd, &inFds);
+            if (select(masterFd + 1, &inFds, NULL, NULL, NULL) == -1)
+                errExit("select");
+
+            if (FD_ISSET(STDIN_FILENO, &inFds)) {
+                numRead = read(STDIN_FILENO, buf, BUF_SIZE);
+                if (numRead <= 0)
+                    exit(EXIT_SUCCESS);
+
+                if (write(masterFd, buf, numRead) != numRead)
+                    fatal("partial/failed write (masterFd)");
+            }
+
+            if (FD_ISSET(masterFd, &inFds)) {
+                numRead = read(masterFd, buf, BUF_SIZE);
+                if (numRead <= 0)
+                    exit(EXIT_SUCCESS);
+
+                if (write(STDOUT_FILENO, buf, numRead) != numRead)
+                    fatal("partial/failed write (STDOUT_FILENO)");
+            }
+        }
+    }
+}
diff --git a/seccomp/Makefile b/seccomp/Makefile
new file mode 100644 (file)
index 0000000..bc9229b
--- /dev/null
@@ -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 (file)
index 0000000..9bd55aa
--- /dev/null
@@ -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 <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <seccomp.h>
+#include "tlpi_hdr.h"
+
+int main(int argc, char *argv[])
+{
+    int rc;
+    scmp_filter_ctx ctx;
+
+    /* Create seccomp filter state that allows by default */
+
+    ctx = seccomp_init(SCMP_ACT_ALLOW);
+    if (ctx == NULL)
+        fatal("seccomp_init() failed");
+
+    /* Cause clone() and fork() to fail, each with different errors */
+
+    rc = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(clone), 0);
+    if (rc < 0)
+        errExitEN(-rc, "seccomp_rule_add");
+
+    rc = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(ENOTSUP), SCMP_SYS(fork), 0);
+    if (rc < 0)
+        errExitEN(-rc, "seccomp_rule_add");
+
+    /* Export the pseudofilter code and BPF binary code,
+       each to different file descriptors (if they are open) */
+
+    seccomp_export_pfc(ctx, 5);
+    seccomp_export_bpf(ctx, 6);
+
+    /* Install the seccomp fileter into the kernel */
+
+    rc = seccomp_load(ctx);
+    if (rc < 0)
+        errExitEN(-rc, "seccomp_load");
+
+    /* Free the user-space seccomp filter state */
+
+    seccomp_release(ctx);
+
+    if (fork() != -1)
+        fprintf(stderr, "fork() succeeded?!\n");
+    else
+        perror("fork");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/seccomp/seccomp_control_open.c b/seccomp/seccomp_control_open.c
new file mode 100644 (file)
index 0000000..4f73205
--- /dev/null
@@ -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 <stddef.h>
+#include <fcntl.h>
+#include <linux/audit.h>
+#include <sys/syscall.h>
+#include <linux/filter.h>
+#include <linux/seccomp.h>
+#include <sys/prctl.h>
+#include "tlpi_hdr.h"
+
+static int
+seccomp(unsigned int operation, unsigned int flags, void *args)
+{
+    return syscall(__NR_seccomp, operation, flags, args);
+}
+
+static void
+install_filter(void)
+{
+    struct sock_filter filter[] = {
+        /* Load architecture */
+
+        BPF_STMT(BPF_LD | BPF_W | BPF_ABS,
+                (offsetof(struct seccomp_data, arch))),
+
+        /* Kill the process if the architecture is not what we expect */
+
+        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, AUDIT_ARCH_X86_64, 1, 0),
+        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL),
+
+        /* Load system call number */
+
+        BPF_STMT(BPF_LD | BPF_W | BPF_ABS,
+                 (offsetof(struct seccomp_data, nr))),
+
+        /* Allow syscalls other than open() */
+
+        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_open, 1, 0),
+        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
+
+        /* Load second argument of open() (flags) into accumulator */
+
+        BPF_STMT(BPF_LD | BPF_W | BPF_ABS,
+                 (offsetof(struct seccomp_data, args[1]))),
+
+        /* Kill the process if O_CREAT was specified */
+
+        BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K, O_CREAT, 0, 1),
+        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL),
+
+        /* Give ENOTSUP error on attempt to open for writing.
+           Relies on the fact that O_RDWR and O_WRONLY are
+           defined as single, nonoverlapping bits  */
+
+        BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K, O_WRONLY | O_RDWR, 0, 1),
+        BPF_STMT(BPF_RET | BPF_K,
+                 SECCOMP_RET_ERRNO | (ENOTSUP & SECCOMP_RET_DATA)),
+
+        /* Otherwise allow the open() */
+
+        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW)
+    };
+
+    struct sock_fprog prog = {
+        .len = (unsigned short) (sizeof(filter) / sizeof(filter[0])),
+        .filter = filter,
+    };
+
+    if (seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog))
+        errExit("seccomp");
+    /* On Linux 3.16 and earlier (or if we have old glibc headers that
+       don't define '__NR_seccomp', we must instead use:
+            if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog))
+                errExit("prctl-PR_SET_SECCOMP");
+    */
+}
+
+int
+main(int argc, char **argv)
+{
+    if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0))
+        errExit("prctl");
+
+    install_filter();
+
+    if (open("/tmp/a", O_RDONLY) == -1)
+        perror("open1");
+    if (open("/tmp/a", O_WRONLY) == -1)
+        perror("open2");
+    if (open("/tmp/a", O_RDWR) == -1)
+        perror("open3");
+    if (open("/tmp/a", O_CREAT | O_RDWR, 0600) == -1)
+        perror("open4");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/seccomp/seccomp_deny_open.c b/seccomp/seccomp_deny_open.c
new file mode 100644 (file)
index 0000000..d1977dc
--- /dev/null
@@ -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 <stddef.h>
+#include <fcntl.h>
+#include <linux/audit.h>
+#include <sys/syscall.h>
+#include <linux/filter.h>
+#include <linux/seccomp.h>
+#include <sys/prctl.h>
+#include "tlpi_hdr.h"
+
+static int
+seccomp(unsigned int operation, unsigned int flags, void *args)
+{
+    return syscall(__NR_seccomp, operation, flags, args);
+}
+
+static void
+install_filter(void)
+{
+    struct sock_filter filter[] = {
+        /* Load architecture */
+
+        BPF_STMT(BPF_LD | BPF_W | BPF_ABS,
+                (offsetof(struct seccomp_data, arch))),
+
+        /* Kill process if the architecture is not what we expect */
+
+        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, AUDIT_ARCH_X86_64, 1, 0),
+        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL),
+
+        /* Load system call number */
+
+        BPF_STMT(BPF_LD | BPF_W | BPF_ABS,
+                 (offsetof(struct seccomp_data, nr))),
+
+        /* Allow system calls other than open() */
+
+        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_open, 1, 0),
+        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
+
+        /* Kill process on open() */
+
+        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL)
+    };
+
+    struct sock_fprog prog = {
+        .len = (unsigned short) (sizeof(filter) / sizeof(filter[0])),
+        .filter = filter,
+    };
+
+    if (seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog) == -1)
+        errExit("seccomp");
+    /* On Linux 3.16 and earlier, we must instead use:
+            if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog))
+                errExit("prctl-PR_SET_SECCOMP");
+    */
+}
+
+int
+main(int argc, char **argv)
+{
+    if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0))
+        errExit("prctl");
+
+    install_filter();
+
+    if (open("/tmp/a", O_RDONLY) == -1)
+        errExit("open");
+
+    printf("We shouldn't see this message\n");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/seccomp/seccomp_perf.c b/seccomp/seccomp_perf.c
new file mode 100644 (file)
index 0000000..28c11ae
--- /dev/null
@@ -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 <stddef.h>
+#include <fcntl.h>
+#include <linux/audit.h>
+#include <sys/syscall.h>
+#include <linux/filter.h>
+#include <linux/seccomp.h>
+#include <sys/prctl.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
+                        } while (0)
+
+static int
+seccomp(unsigned int operation, unsigned int flags, void *arg)
+{
+    return syscall(__NR_seccomp, operation, flags, arg);
+    // Or: return prctl(PR_SET_SECCOMP, operation, arg);
+}
+
+static void
+install_filter(void)
+{
+    struct sock_filter filter[] = {
+        /* Load architecture */
+
+        BPF_STMT(BPF_LD | BPF_W | BPF_ABS,
+                (offsetof(struct seccomp_data, arch))),
+
+        /* Kill process if the architecture is not what we expect */
+
+        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, AUDIT_ARCH_X86_64, 1, 0),
+        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL),
+
+        /* Load system call number */
+
+        BPF_STMT(BPF_LD | BPF_W | BPF_ABS,
+                 (offsetof(struct seccomp_data, nr))),
+
+        /* Allow system calls other than open() */
+
+        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_open, 1, 0),
+        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
+
+        /* Kill process on open() */
+
+        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL)
+    };
+
+    struct sock_fprog prog = {
+        .len = (unsigned short) (sizeof(filter) / sizeof(filter[0])),
+        .filter = filter,
+    };
+
+    if (seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog) == -1)
+        errExit("seccomp");
+}
+
+int
+main(int argc, char *argv[])
+{
+    int j, nloops;
+
+    if (argc < 2) {
+        fprintf(stderr, "Usage: %s <num-loops> [x]\n", argv[0]);
+        fprintf(stderr, "       (use 'x' to run with BPF filter applied)\n");
+        exit(EXIT_FAILURE);
+    }
+
+    if (argc > 2) {
+        printf("Appling BPF filter\n");
+
+        if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0))
+            errExit("prctl");
+
+        install_filter();
+    }
+
+    nloops = atoi(argv[1]);
+
+    for (j = 0; j < nloops; j++)
+        getppid();
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/shlibs/Demo_no_lib.sh b/shlibs/Demo_no_lib.sh
new file mode 100644 (file)
index 0000000..c896a60
--- /dev/null
@@ -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 (file)
index 0000000..aec6de7
--- /dev/null
@@ -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 (file)
index 0000000..5847c72
--- /dev/null
@@ -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 (file)
index 0000000..6006c03
--- /dev/null
@@ -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 (executable)
index 0000000..b0f860e
--- /dev/null
@@ -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 (file)
index 0000000..48ef9a8
--- /dev/null
@@ -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 <stdlib.h>
+#include <stdio.h>
+
+void
+xyz(void)
+{
+    printf("        func1-xyz\n");
+}
+
+void
+abc(void)
+{
+    printf("        func1-abc\n");
+}
+
+void
+func1(int x)
+{
+    printf("Called func1\n");
+    xyz();
+    abc();
+}
diff --git a/shlibs/demo_Bsymbolic/foo2.c b/shlibs/demo_Bsymbolic/foo2.c
new file mode 100644 (file)
index 0000000..fa62958
--- /dev/null
@@ -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 <stdlib.h>
+#include <stdio.h>
+
+void
+xyz(void)
+{
+    printf("        func2-xyz\n");
+}
+
+void
+abc(void)
+{
+    printf("        func1-abc\n");
+}
+
+void
+func2(int x)
+{
+    printf("Called func2\n");
+    xyz();
+    abc();
+}
diff --git a/shlibs/demo_Bsymbolic/foo3.c b/shlibs/demo_Bsymbolic/foo3.c
new file mode 100644 (file)
index 0000000..fe6dc0f
--- /dev/null
@@ -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 <stdlib.h>
+#include <stdio.h>
+
+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 (file)
index 0000000..c645661
--- /dev/null
@@ -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 <stdlib.h>
+#include <stdio.h>
+
+void
+xyz(void)
+{
+    printf("        main-xyz\n");
+}
+
+int
+main(int argc, char*argv[])
+{
+    void func1(void), func2(void), func3(void);
+
+    func1();
+    func2();
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/shlibs/dynload.c b/shlibs/dynload.c
new file mode 100644 (file)
index 0000000..ce1c9f7
--- /dev/null
@@ -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 <dlfcn.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    void *libHandle;            /* Handle for shared library */
+    void (*funcp)(void);        /* Pointer to function with no arguments */
+    const char *err;
+
+    if (argc != 3 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s lib-path func-name\n", argv[0]);
+
+    /* Load the shared library and get a handle for later use */
+
+    libHandle = dlopen(argv[1], RTLD_LAZY);
+    if (libHandle == NULL)
+        fatal("dlopen: %s", dlerror());
+
+    /* Search library for symbol named in argv[2] */
+
+    (void) dlerror();                           /* Clear dlerror() */
+#pragma GCC diagnostic ignored "-Wpedantic"
+    funcp = (void (*)(void)) dlsym(libHandle, argv[2]);
+
+    /* In the book, instead of the preceding line, the code uses a
+       rather clumsy looking cast of the form:
+
+           *(void **) (&funcp) = dlsym(libHandle, argv[2]);
+
+       This was done because the ISO C standard does not require compilers
+       to allow casting of pointers to functions back and forth to 'void *'.
+       (See TLPI pages 863-864.) SUSv3 TC1 and SUSv4 accepted the ISO C
+       requirement and proposed the clumsy cast as the workaround. However,
+       the 2013 Technical Corrigendum to SUSv4 requires implementations
+       to support casts of the more natural form (now) used in the code
+       above. However, various current compilers (e.g., gcc with the
+       '-pedantic' flag) may still complain about such casts. Therefore,
+       we use a gcc pragma to disable the warning (note that this pragma
+       is available only since gcc 4.6, released in 2010). See also the
+       erratum note for page 864 at http://www.man7.org/tlpi/errata/. */
+
+    err = dlerror();
+    if (err != NULL)
+        fatal("dlsym: %s", err);
+
+    /* Try calling the address returned by dlsym() as a function
+       that takes no arguments */
+
+    (*funcp)();
+
+    dlclose(libHandle);                         /* Close the library */
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/shlibs/mod1.c b/shlibs/mod1.c
new file mode 100644 (file)
index 0000000..30e09c0
--- /dev/null
@@ -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 <stdio.h>
+#include <unistd.h>
+int test1[250000] = { 1, 2, 3 };
+
+#ifndef VERSION
+#define VERSION ""
+#endif
+
+void
+x1(void) {
+    printf("Called mod1-x1 " VERSION "\n");
+}
diff --git a/shlibs/mod2.c b/shlibs/mod2.c
new file mode 100644 (file)
index 0000000..2aa42c1
--- /dev/null
@@ -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 <stdio.h>
+#include <unistd.h>
+int test2[100000] = { 1, 2, 3 };
+
+#ifndef VERSION
+#define VERSION ""
+#endif
+
+void
+x2(void) {
+    printf("Called mod2-x2 " VERSION "\n");
+}
diff --git a/shlibs/mod3.c b/shlibs/mod3.c
new file mode 100644 (file)
index 0000000..33fceb7
--- /dev/null
@@ -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 <stdio.h>
+#include <unistd.h>
+int test3[10000] = { 1, 2, 3 };
+
+#ifndef VERSION
+#define VERSION ""
+#endif
+
+void
+x3(void) {
+    printf("Called mod3-x3 " VERSION "\n");
+}
diff --git a/shlibs/prog.c b/shlibs/prog.c
new file mode 100644 (file)
index 0000000..098e91a
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+
+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 (file)
index 0000000..dbc7a0b
--- /dev/null
@@ -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 (file)
index 0000000..4c445e0
--- /dev/null
@@ -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 <stdio.h>
+
+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 (file)
index 0000000..a863fee
--- /dev/null
@@ -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 (file)
index 0000000..82b70d1
--- /dev/null
@@ -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 <stdio.h>
+
+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 (file)
index 0000000..735f603
--- /dev/null
@@ -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 <stdio.h>
+
+__asm__(".symver xyz_new,xyz@@VER_2");
+__asm__(".symver xyz_old,xyz@VER_1");
+
+void xyz_old(void) { printf("v1 xyz\n"); }
+
+void xyz_new(void) { printf("v2 xyz\n"); }
+
+void pqr(void) { printf("v2 pqr\n"); }
diff --git a/shlibs/version_scripts/sv_libabc.c b/shlibs/version_scripts/sv_libabc.c
new file mode 100644 (file)
index 0000000..554c8e4
--- /dev/null
@@ -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 <stdio.h>
+
+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 (file)
index 0000000..c258963
--- /dev/null
@@ -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 <stdlib.h>
+
+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 (file)
index 0000000..daab364
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+
+int
+main(int argc, char *argv[])
+{
+    void xyz(void), abc(void);
+
+    printf("main() calling xyz()\n");
+    xyz();
+
+    abc();
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/shlibs/version_scripts/sv_prog_complex.c b/shlibs/version_scripts/sv_prog_complex.c
new file mode 100644 (file)
index 0000000..44aa2c5
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+//void xxx(void) { printf("Hi there\n"); }
+
+int main(int argc, char *argv[]) {
+    void xyz(void);
+    void xyz_old(void), xyz_new(void);
+    void abc(void);
+
+    printf("Calling abc()\n");
+    abc();
+
+    printf("Calling xyz()\n");
+    xyz();
+
+    printf("Calling xyz_new()\n");
+    xyz_new();
+
+    printf("Calling xyz_old()\n");
+    xyz_old();
+
+    //xxx();
+
+    exit(0);
+}
+//__asm__(".symver xyz_old,xyz@VER_1");
diff --git a/shlibs/version_scripts/vis_build.sh b/shlibs/version_scripts/vis_build.sh
new file mode 100644 (file)
index 0000000..9343714
--- /dev/null
@@ -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 (file)
index 0000000..cb4552e
--- /dev/null
@@ -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 (file)
index 0000000..5093422
--- /dev/null
@@ -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 (file)
index 0000000..10da590
--- /dev/null
@@ -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 (file)
index 0000000..a7d96b2
--- /dev/null
@@ -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 <features.h>. However, in ../Makefile.inc, we define 
+# CFLAGS to include _XOPEN_SOURCE, and this has the effect of disabling
+# _BSD_SOURCE. To ensure that we get the default (BSD) semantics
+# we here explicitly define _BSD_SOURCE when compiling these programs.
+#
+
+ouch : ouch.c
+       ${CC} -D_BSD_SOURCE -o $@ ouch.c ${CFLAGS} ${LDLIBS}
+
+intquit : intquit.c
+       ${CC} -D_BSD_SOURCE -o $@ intquit.c ${CFLAGS} ${LDLIBS}
+
+sig_receiver : sig_receiver.c
+       ${CC} -D_BSD_SOURCE -o $@ sig_receiver.c ${CFLAGS} ${LDLIBS}
+
+nonreentrant : nonreentrant.o
+       ${CC} -o $@ nonreentrant.o ${CFLAGS} ${LDLIBS} ${LINUX_LIBCRYPT}
+
+sigmask_siglongjmp.o : sigmask_longjmp.c
+       ${CC} -o $@ -DUSE_SIGSETJMP -c sigmask_longjmp.c ${CFLAGS}
+
+clean : 
+       ${RM} ${EXE} *.o
+
+showall :
+       @ echo ${EXE}
+
+${EXE} : ${TLPI_LIB}           # True as a rough approximation
diff --git a/signals/catch_rtsigs.c b/signals/catch_rtsigs.c
new file mode 100644 (file)
index 0000000..de94013
--- /dev/null
@@ -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 <string.h>
+#include <signal.h>
+#include "tlpi_hdr.h"
+
+static volatile int handlerSleepTime;
+static volatile int sigCnt = 0;         /* Number of signals received */
+static volatile int allDone = 0;
+
+static void             /* Handler for signals established using SA_SIGINFO */
+siginfoHandler(int sig, siginfo_t *si, void *ucontext)
+{
+    /* UNSAFE: This handler uses non-async-signal-safe functions
+       (printf()); see Section 21.1.2) */
+
+    /* SIGINT or SIGTERM can be used to terminate program */
+
+    if (sig == SIGINT || sig == SIGTERM) {
+        allDone = 1;
+        return;
+    }
+
+    sigCnt++;
+    printf("caught signal %d\n", sig);
+
+    printf("    si_signo=%d, si_code=%d (%s), ", si->si_signo, si->si_code,
+            (si->si_code == SI_USER) ? "SI_USER" :
+            (si->si_code == SI_QUEUE) ? "SI_QUEUE" : "other");
+    printf("si_value=%d\n", si->si_value.sival_int);
+    printf("    si_pid=%ld, si_uid=%ld\n",
+            (long) si->si_pid, (long) si->si_uid);
+
+    sleep(handlerSleepTime);
+}
+
+int
+main(int argc, char *argv[])
+{
+    struct sigaction sa;
+    int sig;
+    sigset_t prevMask, blockMask;
+
+    if (argc > 1 && strcmp(argv[1], "--help") == 0)
+        usageErr("%s [block-time [handler-sleep-time]]\n", argv[0]);
+
+    printf("%s: PID is %ld\n", argv[0], (long) getpid());
+
+    handlerSleepTime = (argc > 2) ?
+                getInt(argv[2], GN_NONNEG, "handler-sleep-time") : 1;
+
+    /* Establish handler for most signals. During execution of the handler,
+       mask all other signals to prevent handlers recursively interrupting
+       each other (which would make the output hard to read). */
+
+    sa.sa_sigaction = siginfoHandler;
+    sa.sa_flags = SA_SIGINFO;
+    sigfillset(&sa.sa_mask);
+
+    for (sig = 1; sig < NSIG; sig++)
+        if (sig != SIGTSTP && sig != SIGQUIT)
+            sigaction(sig, &sa, NULL);
+
+    /* Optionally block signals and sleep, allowing signals to be
+       sent to us before they are unblocked and handled */
+
+    if (argc > 1) {
+        sigfillset(&blockMask);
+        sigdelset(&blockMask, SIGINT);
+        sigdelset(&blockMask, SIGTERM);
+
+        if (sigprocmask(SIG_SETMASK, &blockMask, &prevMask) == -1)
+            errExit("sigprocmask");
+
+        printf("%s: signals blocked - sleeping %s seconds\n", argv[0], argv[1]);
+        sleep(getInt(argv[1], GN_GT_0, "block-time"));
+        printf("%s: sleep complete\n", argv[0]);
+
+        if (sigprocmask(SIG_SETMASK, &prevMask, NULL) == -1)
+            errExit("sigprocmask");
+    }
+
+    while (!allDone)                    /* Wait for incoming signals */
+        pause();
+
+    printf("Caught %d signals\n", sigCnt);
+    exit(EXIT_SUCCESS);
+}
diff --git a/signals/demo_SIGFPE.c b/signals/demo_SIGFPE.c
new file mode 100644 (file)
index 0000000..098d863
--- /dev/null
@@ -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 <string.h> */
+#include <string.h>
+#include <signal.h>
+#include "tlpi_hdr.h"
+
+static void
+sigfpeCatcher(int sig)
+{
+    printf("Caught signal %d (%s)\n", sig, strsignal(sig));
+                                /* UNSAFE (see Section 21.1.2) */
+    sleep(1);                   /* Slow down execution of handler */
+}
+
+int
+main(int argc, char *argv[])
+{
+    int x, y;
+    sigset_t blockSet, prevMask;
+    Boolean blocking;
+    struct sigaction sa;
+
+    /* If no command-line arguments specified, catch SIGFPE, else ignore it */
+
+    if (argc > 1 && strchr(argv[1], 'i') != NULL) {
+        printf("Ignoring SIGFPE\n");
+        if (signal(SIGFPE, SIG_IGN) == SIG_ERR)     errExit("signal");
+    } else {
+        printf("Catching SIGFPE\n");
+        sigemptyset(&sa.sa_mask);
+        sa.sa_flags = SA_RESTART;
+        sa.sa_handler = sigfpeCatcher;
+        if (sigaction(SIGFPE, &sa, NULL) == -1)
+            errExit("sigaction");
+    }
+
+    blocking = argc > 1 && strchr(argv[1], 'b') != NULL;
+    if (blocking) {
+        printf("Blocking SIGFPE\n");
+        sigemptyset(&blockSet);
+        sigaddset(&blockSet, SIGFPE);
+        if (sigprocmask(SIG_BLOCK, &blockSet, &prevMask) == -1)
+            errExit("sigprocmask");
+    }
+
+    printf("About to generate SIGFPE\n");
+    y = 0;
+    x = 1 / y;
+    y = x;      /* Avoid complaints from "gcc -Wunused-but-set-variable" */
+
+    if (blocking) {
+        printf("Sleeping before unblocking\n");
+        sleep(2);
+        printf("Unblocking SIGFPE\n");
+        if (sigprocmask(SIG_SETMASK, &prevMask, NULL) == -1)
+            errExit("sigprocmask");
+    }
+
+    printf("Shouldn't get here!\n");
+    exit(EXIT_FAILURE);
+}
diff --git a/signals/ignore_pending_sig.c b/signals/ignore_pending_sig.c
new file mode 100644 (file)
index 0000000..b4ac8bd
--- /dev/null
@@ -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 <string.h> */
+#include <string.h>
+#include <signal.h>
+#include "signal_functions.h"   /* Declaration of printSigset() */
+#include "tlpi_hdr.h"
+
+static void
+handler(int sig)
+{
+    /* UNSAFE: This handler uses non-async-signal-safe functions
+       (printf(), strsignal(), fflush(); see Section 21.1.2) */
+
+    printf("Caught signal %d (%s)\n", sig, strsignal(sig));
+    fflush(NULL);
+}
+
+int
+main(int argc, char *argv[])
+{
+    sigset_t pending, blocked;
+    const int numSecs = 5;
+    struct sigaction sa;
+
+    /* Set up a handler for SIGINT */
+
+    printf("Setting up handler for SIGINT\n");
+    sigemptyset(&sa.sa_mask);
+    sa.sa_flags = 0;
+    sa.sa_handler = handler;
+    if (sigaction(SIGINT, &sa, NULL) == -1)
+        errExit("sigaction");
+
+    /* Block SIGINT for a while */
+
+    sigemptyset(&blocked);
+    sigaddset(&blocked, SIGINT);
+    if (sigprocmask(SIG_SETMASK, &blocked, NULL) == -1)
+        errExit("sigprocmask");
+
+    printf("BLOCKING SIGINT for %d seconds\n", numSecs);
+    sleep(numSecs);
+
+    /* Display mask of pending signals */
+
+    if (sigpending(&pending) == -1)
+        errExit("sigpending");
+    printf("PENDING signals are: \n");
+    printSigset(stdout, "\t\t", &pending);
+
+    /* Now ignore SIGINT */
+
+    sleep(2);
+    printf("Ignoring SIGINT\n");
+    if (signal(SIGINT, SIG_IGN) == SIG_ERR)     errExit("signal");
+
+    /* Redisplay mask of pending signals */
+
+    if (sigpending(&pending) == -1)
+        errExit("sigpending");
+    if (sigismember(&pending, SIGINT)) {
+        printf("SIGINT is now pending\n");
+    } else {
+        printf("PENDING signals are: \n");
+        printSigset(stdout, "\t\t", &pending);
+    }
+    sleep(2);
+
+    /* Reestablish SIGINT handler */
+
+    printf("Reestablishing handler for SIGINT\n");
+    sigemptyset(&sa.sa_mask);
+    sa.sa_flags = 0;
+    sa.sa_handler = handler;
+    if (sigaction(SIGINT, &sa, NULL) == -1)
+        errExit("sigaction");
+
+    sleep(2);
+
+    /* And unblock SIGINT */
+
+    printf("UNBLOCKING SIGINT\n");
+    sigemptyset(&blocked);
+    if (sigprocmask(SIG_SETMASK, &blocked, NULL) == -1)
+        errExit("sigprocmask");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/signals/intquit.c b/signals/intquit.c
new file mode 100644 (file)
index 0000000..1769368
--- /dev/null
@@ -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 <signal.h>
+#include "tlpi_hdr.h"
+
+static void
+sigHandler(int sig)
+{
+    static int count = 0;
+
+    /* UNSAFE: This handler uses non-async-signal-safe functions
+       (printf(), exit(); see Section 21.1.2) */
+
+    if (sig == SIGINT) {
+        count++;
+        printf("Caught SIGINT (%d)\n", count);
+        return;                 /* Resume execution at point of interruption */
+    }
+
+    /* Must be SIGQUIT - print a message and terminate the process */
+
+    printf("Caught SIGQUIT - that's all folks!\n");
+    exit(EXIT_SUCCESS);
+}
+
+int
+main(int argc, char *argv[])
+{
+    /* Establish same handler for SIGINT and SIGQUIT. Here we use the
+       simpler signal() API to establish a signal handler, but for the
+       reasons described in Section 22.7 of TLPI, sigaction() is the
+       (strongly) preferred API for this task. */
+
+    if (signal(SIGINT, sigHandler) == SIG_ERR)
+        errExit("signal");
+    if (signal(SIGQUIT, sigHandler) == SIG_ERR)
+        errExit("signal");
+
+    for (;;)                    /* Loop forever, waiting for signals */
+        pause();                /* Block until a signal is caught */
+}
diff --git a/signals/nonreentrant.c b/signals/nonreentrant.c
new file mode 100644 (file)
index 0000000..42884d0
--- /dev/null
@@ -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 <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include "tlpi_hdr.h"
+
+static char *str2;              /* Set from argv[2] */
+static int handled = 0;         /* Counts number of calls to handler */
+
+static void
+handler(int sig)
+{
+    crypt(str2, "xx");
+    handled++;
+}
+
+int
+main(int argc, char *argv[])
+{
+    char *cr1;
+    int callNum, mismatch;
+    struct sigaction sa;
+
+    if (argc != 3)
+        usageErr("%s str1 str2\n", argv[0]);
+
+    str2 = argv[2];                      /* Make argv[2] available to handler */
+    cr1 = strdup(crypt(argv[1], "xx"));  /* Copy statically allocated string
+                                            to another buffer */
+    if (cr1 == NULL)
+        errExit("strdup");
+
+    sigemptyset(&sa.sa_mask);
+    sa.sa_flags = 0;
+    sa.sa_handler = handler;
+    if (sigaction(SIGINT, &sa, NULL) == -1)
+        errExit("sigaction");
+
+    /* Repeatedly call crypt() using argv[1]. If interrupted by a
+       signal handler, then the static storage returned by crypt()
+       will be overwritten by the results of encrypting argv[2], and
+       strcmp() will detect a mismatch with the value in 'cr1'. */
+
+    for (callNum = 1, mismatch = 0; ; callNum++) {
+        if (strcmp(crypt(argv[1], "xx"), cr1) != 0) {
+            mismatch++;
+            printf("Mismatch on call %d (mismatch=%d handled=%d)\n",
+                    callNum, mismatch, handled);
+        }
+    }
+}
diff --git a/signals/ouch.c b/signals/ouch.c
new file mode 100644 (file)
index 0000000..da17a66
--- /dev/null
@@ -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 <signal.h>
+#include "tlpi_hdr.h"
+
+static void
+sigHandler(int sig)
+{
+    printf("Ouch!\n");                  /* UNSAFE (see Section 21.1.2) */
+}
+
+int
+main(int argc, char *argv[])
+{
+    int j;
+
+    /* Establish handler for SIGINT. Here we use the simpler signal()
+       API to establish a signal handler, but for the reasons described in
+       Section 22.7 of TLPI, sigaction() is the (strongly) preferred API
+       for this task. */
+
+    if (signal(SIGINT, sigHandler) == SIG_ERR)
+        errExit("signal");
+
+    /* Loop continuously waiting for signals to be delivered */
+
+    for (j = 0; ; j++) {
+        printf("%d\n", j);
+        sleep(3);                       /* Loop slowly... */
+    }
+}
diff --git a/signals/sig_receiver.c b/signals/sig_receiver.c
new file mode 100644 (file)
index 0000000..d59b15a
--- /dev/null
@@ -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 <signal.h>
+#include "signal_functions.h"           /* Declaration of printSigset() */
+#include "tlpi_hdr.h"
+
+static int sigCnt[NSIG];                /* Counts deliveries of each signal */
+static volatile sig_atomic_t gotSigint = 0;
+                                        /* Set nonzero if SIGINT is delivered */
+
+static void
+handler(int sig)
+{
+    if (sig == SIGINT)
+        gotSigint = 1;
+    else
+        sigCnt[sig]++;
+}
+
+int
+main(int argc, char *argv[])
+{
+    int n, numSecs;
+    sigset_t pendingMask, blockingMask, emptyMask;
+
+    printf("%s: PID is %ld\n", argv[0], (long) getpid());
+
+    /* Here we use the simpler signal() API to establish a signal handler,
+       but for the reasons described in Section 22.7 of TLPI, sigaction()
+       is the (strongly) preferred API for this task. */
+
+    for (n = 1; n < NSIG; n++)          /* Same handler for all signals */
+        (void) signal(n, handler);      /* Ignore errors */
+
+    /* If a sleep time was specified, temporarily block all signals,
+       sleep (while another process sends us signals), and then
+       display the mask of pending signals and unblock all signals */
+
+    if (argc > 1) {
+        numSecs = getInt(argv[1], GN_GT_0, NULL);
+
+        sigfillset(&blockingMask);
+        if (sigprocmask(SIG_SETMASK, &blockingMask, NULL) == -1)
+            errExit("sigprocmask");
+
+        printf("%s: sleeping for %d seconds\n", argv[0], numSecs);
+        sleep(numSecs);
+
+        if (sigpending(&pendingMask) == -1)
+            errExit("sigpending");
+
+        printf("%s: pending signals are: \n", argv[0]);
+        printSigset(stdout, "\t\t", &pendingMask);
+
+        sigemptyset(&emptyMask);        /* Unblock all signals */
+        if (sigprocmask(SIG_SETMASK, &emptyMask, NULL) == -1)
+            errExit("sigprocmask");
+    }
+
+    while (!gotSigint)                  /* Loop until SIGINT caught */
+        continue;
+
+    for (n = 1; n < NSIG; n++)          /* Display number of signals received */
+        if (sigCnt[n] != 0)
+            printf("%s: signal %d caught %d time%s\n", argv[0], n,
+                    sigCnt[n], (sigCnt[n] == 1) ? "" : "s");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/signals/sig_sender.c b/signals/sig_sender.c
new file mode 100644 (file)
index 0000000..36204be
--- /dev/null
@@ -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 <signal.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    int numSigs, sig, j;
+    pid_t pid;
+
+    if (argc < 4 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s pid num-sigs sig-num [sig-num-2]\n", argv[0]);
+
+    pid = getLong(argv[1], 0, "PID");
+    numSigs = getInt(argv[2], GN_GT_0, "num-sigs");
+    sig = getInt(argv[3], 0, "sig-num");
+
+    /* Send signals to receiver */
+
+    printf("%s: sending signal %d to process %ld %d times\n",
+            argv[0], sig, (long) pid, numSigs);
+
+    for (j = 0; j < numSigs; j++)
+        if (kill(pid, sig) == -1)
+            errExit("kill");
+
+    /* If a fourth command-line argument was specified, send that signal */
+
+    if (argc > 4)
+        if (kill(pid, getInt(argv[4], 0, "sig-num-2")) == -1)
+            errExit("kill");
+
+    printf("%s: exiting\n", argv[0]);
+    exit(EXIT_SUCCESS);
+}
diff --git a/signals/sig_speed_sigsuspend.c b/signals/sig_speed_sigsuspend.c
new file mode 100644 (file)
index 0000000..7801349
--- /dev/null
@@ -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 <signal.h>
+#include "tlpi_hdr.h"
+
+static void
+handler(int sig)
+{
+}
+
+#define TESTSIG SIGUSR1
+
+int
+main(int argc, char *argv[])
+{
+    int numSigs, scnt;
+    pid_t childPid;
+    sigset_t blockedMask, emptyMask;
+    struct sigaction sa;
+
+    if (argc != 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s num-sigs\n", argv[0]);
+
+    numSigs = getInt(argv[1], GN_GT_0, "num-sigs");
+
+    sigemptyset(&sa.sa_mask);
+    sa.sa_flags = 0;
+    sa.sa_handler = handler;
+    if (sigaction(TESTSIG, &sa, NULL) == -1)
+        errExit("sigaction");
+
+    /* Block the signal before fork(), so that the child doesn't manage
+       to send it to the parent before the parent is ready to catch it */
+
+    sigemptyset(&blockedMask);
+    sigaddset(&blockedMask, TESTSIG);
+    if (sigprocmask(SIG_SETMASK, &blockedMask, NULL) == -1)
+        errExit("sigprocmask");
+
+    sigemptyset(&emptyMask);
+
+    switch (childPid = fork()) {
+    case -1: errExit("fork");
+
+    case 0:     /* child */
+        for (scnt = 0; scnt < numSigs; scnt++) {
+            if (kill(getppid(), TESTSIG) == -1)
+                errExit("kill");
+            if (sigsuspend(&emptyMask) == -1 && errno != EINTR)
+                    errExit("sigsuspend");
+        }
+        exit(EXIT_SUCCESS);
+
+    default: /* parent */
+        for (scnt = 0; scnt < numSigs; scnt++) {
+            if (sigsuspend(&emptyMask) == -1 && errno != EINTR)
+                    errExit("sigsuspend");
+            if (kill(childPid, TESTSIG) == -1)
+                errExit("kill");
+        }
+        exit(EXIT_SUCCESS);
+    }
+}
diff --git a/signals/siginterrupt.c b/signals/siginterrupt.c
new file mode 100644 (file)
index 0000000..0bf6339
--- /dev/null
@@ -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 <stdio.h>
+#include <signal.h>
+
+int
+siginterrupt(int sig, int flag)
+{
+    int status;
+    struct sigaction act;
+
+    status = sigaction(sig, NULL, &act);
+    if (status == -1)
+        return -1;
+
+    if (flag)
+        act.sa_flags &= ~SA_RESTART;
+    else
+        act.sa_flags |= SA_RESTART;
+
+    return sigaction(sig, &act, NULL);
+}
diff --git a/signals/sigmask_longjmp.c b/signals/sigmask_longjmp.c
new file mode 100644 (file)
index 0000000..939792c
--- /dev/null
@@ -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 <string.h> */
+#include <string.h>
+#include <setjmp.h>
+#include <signal.h>
+#include "signal_functions.h"           /* Declaration of printSigMask() */
+#include "tlpi_hdr.h"
+
+static volatile sig_atomic_t canJump = 0;
+                        /* Set to 1 once "env" buffer has been
+                           initialized by [sig]setjmp() */
+#ifdef USE_SIGSETJMP
+static sigjmp_buf senv;
+#else
+static jmp_buf env;
+#endif
+
+static void
+handler(int sig)
+{
+    /* UNSAFE: This handler uses non-async-signal-safe functions
+       (printf(), strsignal(), printSigMask(); see Section 21.1.2) */
+
+    printf("Received signal %d (%s), signal mask is:\n", sig,
+            strsignal(sig));
+    printSigMask(stdout, NULL);
+
+    if (!canJump) {
+        printf("'env' buffer not yet set, doing a simple return\n");
+        return;
+    }
+
+#ifdef USE_SIGSETJMP
+    siglongjmp(senv, 1);
+#else
+    longjmp(env, 1);
+#endif
+}
+
+int
+main(int argc, char *argv[])
+{
+    struct sigaction sa;
+
+    printSigMask(stdout, "Signal mask at startup:\n");
+
+    sigemptyset(&sa.sa_mask);
+    sa.sa_flags = 0;
+    sa.sa_handler = handler;
+    if (sigaction(SIGINT, &sa, NULL) == -1)
+        errExit("sigaction");
+
+#ifdef USE_SIGSETJMP
+    printf("Calling sigsetjmp()\n");
+    if (sigsetjmp(senv, 1) == 0)
+#else
+    printf("Calling setjmp()\n");
+    if (setjmp(env) == 0)
+#endif
+        canJump = 1;                    /* Executed after [sig]setjmp() */
+
+    else                                /* Executed after [sig]longjmp() */
+        printSigMask(stdout, "After jump from handler, signal mask is:\n" );
+
+    for (;;)                            /* Wait for signals until killed */
+        pause();
+}
diff --git a/signals/signal.c b/signals/signal.c
new file mode 100644 (file)
index 0000000..ac617f9
--- /dev/null
@@ -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 <signal.h>
+
+typedef void (*sighandler_t)(int);
+
+sighandler_t
+signal(int sig, sighandler_t handler)
+{
+    struct sigaction newDisp, prevDisp;
+
+    newDisp.sa_handler = handler;
+    sigemptyset(&newDisp.sa_mask);
+#ifdef OLD_SIGNAL
+    newDisp.sa_flags = SA_RESETHAND | SA_NODEFER;
+#else
+    newDisp.sa_flags = SA_RESTART;
+#endif
+
+    if (sigaction(sig, &newDisp, &prevDisp) == -1)
+        return SIG_ERR;
+    else
+        return prevDisp.sa_handler;
+}
+
+#else
+/* The following declaration is provided purely to avoid gcc's
+   "warning: ISO C forbids an empty source file" warnings. */
+
+extern int signalDummyVar;
+
+#endif
diff --git a/signals/signal_functions.c b/signals/signal_functions.c
new file mode 100644 (file)
index 0000000..6d402b7
--- /dev/null
@@ -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 <string.h>
+#include <signal.h>
+#include "signal_functions.h"           /* Declares functions defined here */
+#include "tlpi_hdr.h"
+
+/* NOTE: All of the following functions employ fprintf(), which
+   is not async-signal-safe (see Section 21.1.2). As such, these
+   functions are also not async-signal-safe (i.e., beware of
+   indiscriminately calling them from signal handlers). */
+
+void                    /* Print list of signals within a signal set */
+printSigset(FILE *of, const char *prefix, const sigset_t *sigset)
+{
+    int sig, cnt;
+
+    cnt = 0;
+    for (sig = 1; sig < NSIG; sig++) {
+        if (sigismember(sigset, sig)) {
+            cnt++;
+            fprintf(of, "%s%d (%s)\n", prefix, sig, strsignal(sig));
+        }
+    }
+
+    if (cnt == 0)
+        fprintf(of, "%s<empty signal set>\n", prefix);
+}
+
+int                     /* Print mask of blocked signals for this process */
+printSigMask(FILE *of, const char *msg)
+{
+    sigset_t currMask;
+
+    if (msg != NULL)
+        fprintf(of, "%s", msg);
+
+    if (sigprocmask(SIG_BLOCK, NULL, &currMask) == -1)
+        return -1;
+
+    printSigset(of, "\t\t", &currMask);
+
+    return 0;
+}
+
+int                     /* Print signals currently pending for this process */
+printPendingSigs(FILE *of, const char *msg)
+{
+    sigset_t pendingSigs;
+
+    if (msg != NULL)
+        fprintf(of, "%s", msg);
+
+    if (sigpending(&pendingSigs) == -1)
+        return -1;
+
+    printSigset(of, "\t\t", &pendingSigs);
+
+    return 0;
+}
diff --git a/signals/signal_functions.h b/signals/signal_functions.h
new file mode 100644 (file)
index 0000000..9e7651e
--- /dev/null
@@ -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 <signal.h>
+#include "tlpi_hdr.h"
+
+int printSigMask(FILE *of, const char *msg);
+
+int printPendingSigs(FILE *of, const char *msg);
+
+void printSigset(FILE *of, const char *ldr, const sigset_t *mask);
+
+#endif
diff --git a/signals/signalfd_sigval.c b/signals/signalfd_sigval.c
new file mode 100644 (file)
index 0000000..c3de973
--- /dev/null
@@ -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 <sys/signalfd.h>
+#include <signal.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    sigset_t mask;
+    int sfd, j;
+    struct signalfd_siginfo fdsi;
+    ssize_t s;
+
+    if (argc < 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s sig-num...\n", argv[0]);
+
+    printf("%s: PID = %ld\n", argv[0], (long) getpid());
+
+    sigemptyset(&mask);
+    for (j = 1; j < argc; j++)
+        sigaddset(&mask, atoi(argv[j]));
+
+    if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
+        errExit("sigprocmask");
+
+    sfd = signalfd(-1, &mask, 0);
+    if (sfd == -1)
+        errExit("signalfd");
+
+    for (;;) {
+        s = read(sfd, &fdsi, sizeof(struct signalfd_siginfo));
+        if (s != sizeof(struct signalfd_siginfo))
+            errExit("read");
+
+        printf("%s: got signal %d", argv[0], fdsi.ssi_signo);
+        if (fdsi.ssi_code == SI_QUEUE) {
+            printf("; ssi_pid = %d; ", fdsi.ssi_pid);
+            printf("ssi_int = %d", fdsi.ssi_int);
+        }
+        printf("\n");
+    }
+}
diff --git a/signals/t_kill.c b/signals/t_kill.c
new file mode 100644 (file)
index 0000000..b8867e1
--- /dev/null
@@ -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 <signal.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    int s, sig;
+
+    if (argc != 3 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s pid sig-num\n", argv[0]);
+
+    sig = getInt(argv[2], 0, "sig-num");
+
+    s = kill(getLong(argv[1], 0, "pid"), sig);
+
+    if (sig != 0) {
+        if (s == -1)
+            errExit("kill");
+
+    } else {                    /* Null signal: process existence check */
+        if (s == 0) {
+            printf("Process exists and we can send it a signal\n");
+        } else {
+            if (errno == EPERM)
+                printf("Process exists, but we don't have "
+                       "permission to send it a signal\n");
+            else if (errno == ESRCH)
+                printf("Process does not exist\n");
+            else
+                errExit("kill");
+        }
+    }
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/signals/t_sigaltstack.c b/signals/t_sigaltstack.c
new file mode 100644 (file)
index 0000000..32ea8c0
--- /dev/null
@@ -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 <string.h> */
+#include <string.h>
+#include <signal.h>
+#include "tlpi_hdr.h"
+
+static void
+sigsegvHandler(int sig)
+{
+    int x;
+
+    /* UNSAFE: This handler uses non-async-signal-safe functions
+       (printf(), strsignal(), fflush(); see Section 21.1.2) */
+
+    printf("Caught signal %d (%s)\n", sig, strsignal(sig));
+    printf("Top of handler stack near     %10p\n", (void *) &x);
+    fflush(NULL);
+
+    _exit(EXIT_FAILURE);                /* Can't return after SIGSEGV */
+}
+
+/* The following stops 'gcc -Wall' complaining that "control reaches
+   end of non-void function" because we don't follow the call to
+   overflowStack() stack in main() with a call to exit(). */
+
+#ifdef __GNUC__
+static void
+overflowStack(int callNum) __attribute__ ((__noreturn__));
+#endif
+
+static void             /* A recursive function that overflows the stack */
+overflowStack(int callNum)
+{
+    char a[100000];                     /* Make this stack frame large */
+
+    printf("Call %4d - top of stack near %10p\n", callNum, &a[0]);
+    overflowStack(callNum+1);
+}
+
+int
+main(int argc, char *argv[])
+{
+    stack_t sigstack;
+    struct sigaction sa;
+    int j;
+
+    printf("Top of standard stack is near %10p\n", (void *) &j);
+
+    /* Allocate alternate stack and inform kernel of its existence */
+
+    sigstack.ss_sp = malloc(SIGSTKSZ);
+    if (sigstack.ss_sp == NULL)
+        errExit("malloc");
+
+    sigstack.ss_size = SIGSTKSZ;
+    sigstack.ss_flags = 0;
+    if (sigaltstack(&sigstack, NULL) == -1)
+        errExit("sigaltstack");
+    printf("Alternate stack is at         %10p-%p\n",
+            sigstack.ss_sp, (char *) sbrk(0) - 1);
+
+    sa.sa_handler = sigsegvHandler;     /* Establish handler for SIGSEGV */
+    sigemptyset(&sa.sa_mask);
+    sa.sa_flags = SA_ONSTACK;           /* Handler uses alternate stack */
+    if (sigaction(SIGSEGV, &sa, NULL) == -1)
+        errExit("sigaction");
+
+    overflowStack(1);
+}
diff --git a/signals/t_sigqueue.c b/signals/t_sigqueue.c
new file mode 100644 (file)
index 0000000..6a2e62d
--- /dev/null
@@ -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 <signal.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    int sig, numSigs, j, sigData;
+    union sigval sv;
+
+    if (argc < 4 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s pid sig-num data [num-sigs]\n", argv[0]);
+
+    /* Display our PID and UID, so that they can be compared with the
+       corresponding fields of the siginfo_t argument supplied to the
+       handler in the receiving process */
+
+    printf("%s: PID is %ld, UID is %ld\n", argv[0],
+            (long) getpid(), (long) getuid());
+
+    sig = getInt(argv[2], 0, "sig-num");
+    sigData = getInt(argv[3], GN_ANY_BASE, "data");
+    numSigs = (argc > 4) ? getInt(argv[4], GN_GT_0, "num-sigs") : 1;
+
+    for (j = 0; j < numSigs; j++) {
+        sv.sival_int = sigData + j;
+        if (sigqueue(getLong(argv[1], 0, "pid"), sig, sv) == -1)
+            errExit("sigqueue %d", j);
+    }
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/signals/t_sigsuspend.c b/signals/t_sigsuspend.c
new file mode 100644 (file)
index 0000000..3769c1d
--- /dev/null
@@ -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 <string.h> */
+#include <string.h>
+#include <signal.h>
+#include <time.h>
+#include "signal_functions.h"           /* Declarations of printSigMask()
+                                           and printPendingSigs() */
+#include "tlpi_hdr.h"
+
+/* Global variable incremented each time SIGINT is handled */
+
+static volatile int sigintCnt = 0;
+static volatile sig_atomic_t gotSigquit = 0;
+
+static void
+handler(int sig)
+{
+    printf("Caught signal %d (%s)\n", sig, strsignal(sig));
+                                        /* UNSAFE (see Section 21.1.2) */
+    if (sig == SIGQUIT)
+        gotSigquit = 1;
+    sigintCnt++;
+}
+
+int
+main(int argc, char *argv[])
+{
+    int loopNum;
+#ifdef USE_PAUSE
+    int sleepTime;
+#endif
+    time_t startTime;
+    sigset_t origMask, blockMask;
+    struct sigaction sa;
+
+    printSigMask(stdout, "Initial signal mask is:\n");
+
+    sigemptyset(&blockMask);
+    sigaddset(&blockMask, SIGINT);
+    sigaddset(&blockMask, SIGQUIT);
+
+#ifdef USE_PAUSE
+    sleepTime = (argc > 1) ? getInt(argv[1], GN_NONNEG, NULL) : 0;
+#endif
+
+    /* Block SIGINT and SIGQUIT - at this point we assume that these signals
+      are not already blocked (obviously true in this simple program) so that
+      'origMask' will not contain either of these signals after the call. */
+
+    if (sigprocmask(SIG_BLOCK, &blockMask, &origMask) == -1)
+        errExit("sigprocmask - SIG_BLOCK");
+
+    /* Set up handlers for SIGINT and SIGQUIT */
+
+    sigemptyset(&sa.sa_mask);
+    sa.sa_flags = 0;
+    sa.sa_handler = handler;
+    if (sigaction(SIGINT, &sa, NULL) == -1)
+        errExit("sigaction");
+    if (sigaction(SIGQUIT, &sa, NULL) == -1)
+        errExit("sigaction");
+
+    /* Loop until SIGQUIT received */
+
+    for (loopNum = 1; !gotSigquit; loopNum++) {
+        printf("=== LOOP %d\n", loopNum);
+
+        /* Simulate a critical section by delaying a few seconds */
+
+        printSigMask(stdout, "Starting critical section, signal mask is:\n");
+        for (startTime = time(NULL); time(NULL) < startTime + 4; )
+            continue;                   /* Run for a few seconds elapsed time */
+
+#ifndef USE_PAUSE
+        /* The right way: use sigsuspend() to atomically unblock
+           signals and pause waiting for signal */
+
+        printPendingSigs(stdout,
+                "Before sigsuspend() - pending signals:\n");
+        if (sigsuspend(&origMask) == -1 && errno != EINTR)
+            errExit("sigsuspend");
+#else
+
+        /* The wrong way: unblock signal using sigprocmask(),
+           then pause() */
+
+        if (sigprocmask(SIG_SETMASK, &origMask, NULL) == -1)
+            errExit("sigprocmask - SIG_SETMASK");
+
+        /* At this point, if SIGINT arrives, it will be caught and
+           handled before the pause() call and, in consequence,
+           pause() will block. (And thus only another SIGINT signal
+           AFTER the pause call() will actually cause the pause()
+           call to be interrupted.)  Here we make the window between
+           the two calls a bit larger so that we have a better
+           chance of sending the signal. */
+
+        if (sleepTime > 0) {
+            printf("Unblocked SIGINT, now waiting for %d seconds\n", sleepTime);
+            for (startTime = time(NULL);
+                    time(NULL) < startTime + sleepTime; )
+                continue;
+            printf("Finished waiting - now going to pause()\n");
+        }
+
+        /* And now wait for the signal */
+
+        pause();
+
+        printf("Signal count = %d\n", sigintCnt);
+        sigintCnt = 0;
+#endif
+    }
+
+    /* Restore signal mask so that signals are unblocked */
+
+    if (sigprocmask(SIG_SETMASK, &origMask, NULL) == -1)
+        errExit("sigprocmask - SIG_SETMASK");
+
+    printSigMask(stdout, "=== Exited loop\nRestored signal mask to:\n");
+
+    /* Do other processing... */
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/signals/t_sigwaitinfo.c b/signals/t_sigwaitinfo.c
new file mode 100644 (file)
index 0000000..5f5fa6e
--- /dev/null
@@ -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 <string.h>
+#include <signal.h>
+#include <time.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    int sig;
+    siginfo_t si;
+    sigset_t allSigs;
+
+    if (argc > 1 && strcmp(argv[1], "--help") == 0)
+        usageErr("%s [delay-secs]\n", argv[0]);
+
+    printf("%s: PID is %ld\n", argv[0], (long) getpid());
+
+    /* Block all signals (except SIGKILL and SIGSTOP) */
+
+    sigfillset(&allSigs);
+    if (sigprocmask(SIG_SETMASK, &allSigs, NULL) == -1)
+        errExit("sigprocmask");
+    printf("%s: signals blocked\n", argv[0]);
+
+    if (argc > 1) {             /* Delay so that signals can be sent to us */
+        printf("%s: about to delay %s seconds\n", argv[0], argv[1]);
+        sleep(getInt(argv[1], GN_GT_0, "delay-secs"));
+        printf("%s: finished delay\n", argv[0]);
+    }
+
+    for (;;) {                  /* Fetch signals until SIGINT (^C) or SIGTERM */
+        sig = sigwaitinfo(&allSigs, &si);
+        if (sig == -1)
+            errExit("sigwaitinfo");
+
+        if (sig == SIGINT || sig == SIGTERM)
+            exit(EXIT_SUCCESS);
+
+        printf("got signal: %d (%s)\n", sig, strsignal(sig));
+        printf("    si_signo=%d, si_code=%d (%s), si_value=%d\n",
+                si.si_signo, si.si_code,
+                (si.si_code == SI_USER) ? "SI_USER" :
+                    (si.si_code == SI_QUEUE) ? "SI_QUEUE" : "other",
+                si.si_value.sival_int);
+        printf("    si_pid=%ld, si_uid=%ld\n",
+                (long) si.si_pid, (long) si.si_uid);
+    }
+}
diff --git a/sockets/Makefile b/sockets/Makefile
new file mode 100644 (file)
index 0000000..9d2c22a
--- /dev/null
@@ -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 (file)
index 0000000..34f0749
--- /dev/null
@@ -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 (file)
index 0000000..0c67398
--- /dev/null
@@ -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 <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <ctype.h>
+#include "tlpi_hdr.h"
+
+#define BUF_SIZE 10                     /* Maximum size of messages exchanged
+                                           between client and server */
+
+#define PORT_NUM 50002                  /* Server port number */
diff --git a/sockets/i6d_ucase_cl.c b/sockets/i6d_ucase_cl.c
new file mode 100644 (file)
index 0000000..0584bcc
--- /dev/null
@@ -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 (file)
index 0000000..8790582
--- /dev/null
@@ -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 (file)
index 0000000..6cad12d
--- /dev/null
@@ -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 (file)
index 0000000..9aa193f
--- /dev/null
@@ -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 (file)
index 0000000..552121d
--- /dev/null
@@ -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 <syslog.h>
+#include "id_echo.h"
+#include "become_daemon.h"
+
+int
+main(int argc, char *argv[])
+{
+    int sfd;
+    ssize_t numRead;
+    socklen_t len;
+    struct sockaddr_storage claddr;
+    char buf[BUF_SIZE];
+    char addrStr[IS_ADDR_STR_LEN];
+
+    if (becomeDaemon(0) == -1)
+        errExit("becomeDaemon");
+
+    sfd = inetBind(SERVICE, SOCK_DGRAM, NULL);
+    if (sfd == -1) {
+        syslog(LOG_ERR, "Could not create server socket (%s)", strerror(errno));
+        exit(EXIT_FAILURE);
+    }
+
+    /* Receive datagrams and return copies to senders */
+
+    for (;;) {
+        len = sizeof(struct sockaddr_storage);
+        numRead = recvfrom(sfd, buf, BUF_SIZE, 0,
+                           (struct sockaddr *) &claddr, &len);
+        if (numRead == -1)
+            errExit("recvfrom");
+
+        if (sendto(sfd, buf, numRead, 0, (struct sockaddr *) &claddr, len)
+                        != numRead)
+            syslog(LOG_WARNING, "Error echoing response to %s (%s)",
+                    inetAddressStr((struct sockaddr *) &claddr, len,
+                                   addrStr, IS_ADDR_STR_LEN),
+                    strerror(errno));
+    }
+}
diff --git a/sockets/inet_sockets.c b/sockets/inet_sockets.c
new file mode 100644 (file)
index 0000000..cb09282
--- /dev/null
@@ -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 <netdb.h> */
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include "inet_sockets.h"       /* Declares functions defined here */
+#include "tlpi_hdr.h"
+
+/* The following arguments are common to several of the routines
+   below:
+
+        'host':         NULL for loopback IP address, or
+                        a host name or numeric IP address
+        'service':      either a name or a port number
+        'type':         either SOCK_STREAM or SOCK_DGRAM
+*/
+
+/* Create socket and connect it to the address specified by
+  'host' + 'service'/'type'. Return socket descriptor on success,
+  or -1 on error */
+
+int
+inetConnect(const char *host, const char *service, int type)
+{
+    struct addrinfo hints;
+    struct addrinfo *result, *rp;
+    int sfd, s;
+
+    memset(&hints, 0, sizeof(struct addrinfo));
+    hints.ai_canonname = NULL;
+    hints.ai_addr = NULL;
+    hints.ai_next = NULL;
+    hints.ai_family = AF_UNSPEC;        /* Allows IPv4 or IPv6 */
+    hints.ai_socktype = type;
+
+    s = getaddrinfo(host, service, &hints, &result);
+    if (s != 0) {
+        errno = ENOSYS;
+        return -1;
+    }
+
+    /* Walk through returned list until we find an address structure
+       that can be used to successfully connect a socket */
+
+    for (rp = result; rp != NULL; rp = rp->ai_next) {
+        sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+        if (sfd == -1)
+            continue;                   /* On error, try next address */
+
+        if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1)
+            break;                      /* Success */
+
+        /* Connect failed: close this socket and try next address */
+
+        close(sfd);
+    }
+
+    freeaddrinfo(result);
+
+    return (rp == NULL) ? -1 : sfd;
+}
+
+/* Create an Internet domain socket and bind it to the address
+   { wildcard-IP-address + 'service'/'type' }.
+   If 'doListen' is TRUE, then make this a listening socket (by
+   calling listen() with 'backlog'), with the SO_REUSEADDR option set.
+   If 'addrLen' is not NULL, then use it to return the size of the
+   address structure for the address family for this socket.
+   Return the socket descriptor on success, or -1 on error. */
+
+static int              /* Public interfaces: inetBind() and inetListen() */
+inetPassiveSocket(const char *service, int type, socklen_t *addrlen,
+                  Boolean doListen, int backlog)
+{
+    struct addrinfo hints;
+    struct addrinfo *result, *rp;
+    int sfd, optval, s;
+
+    memset(&hints, 0, sizeof(struct addrinfo));
+    hints.ai_canonname = NULL;
+    hints.ai_addr = NULL;
+    hints.ai_next = NULL;
+    hints.ai_socktype = type;
+    hints.ai_family = AF_UNSPEC;        /* Allows IPv4 or IPv6 */
+    hints.ai_flags = AI_PASSIVE;        /* Use wildcard IP address */
+
+    s = getaddrinfo(NULL, service, &hints, &result);
+    if (s != 0)
+        return -1;
+
+    /* Walk through returned list until we find an address structure
+       that can be used to successfully create and bind a socket */
+
+    optval = 1;
+    for (rp = result; rp != NULL; rp = rp->ai_next) {
+        sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+        if (sfd == -1)
+            continue;                   /* On error, try next address */
+
+        if (doListen) {
+            if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &optval,
+                    sizeof(optval)) == -1) {
+                close(sfd);
+                freeaddrinfo(result);
+                return -1;
+            }
+        }
+
+        if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0)
+            break;                      /* Success */
+
+        /* bind() failed: close this socket and try next address */
+
+        close(sfd);
+    }
+
+    if (rp != NULL && doListen) {
+        if (listen(sfd, backlog) == -1) {
+            freeaddrinfo(result);
+            return -1;
+        }
+    }
+
+    if (rp != NULL && addrlen != NULL)
+        *addrlen = rp->ai_addrlen;      /* Return address structure size */
+
+    freeaddrinfo(result);
+
+    return (rp == NULL) ? -1 : sfd;
+}
+
+/* Create stream socket, bound to wildcard IP address + port given in
+  'service'. Make the socket a listening socket, with the specified
+  'backlog'. Return socket descriptor on success, or -1 on error. */
+
+int
+inetListen(const char *service, int backlog, socklen_t *addrlen)
+{
+    return inetPassiveSocket(service, SOCK_STREAM, addrlen, TRUE, backlog);
+}
+
+/* Create socket bound to wildcard IP address + port given in
+   'service'. Return socket descriptor on success, or -1 on error. */
+
+int
+inetBind(const char *service, int type, socklen_t *addrlen)
+{
+    return inetPassiveSocket(service, type, addrlen, FALSE, 0);
+}
+
+/* Given a socket address in 'addr', whose length is specified in
+   'addrlen', return a null-terminated string containing the host and
+   service names in the form "(hostname, port#)". The string is
+   returned in the buffer pointed to by 'addrStr', and this value is
+   also returned as the function result. The caller must specify the
+   size of the 'addrStr' buffer in 'addrStrLen'. */
+
+char *
+inetAddressStr(const struct sockaddr *addr, socklen_t addrlen,
+               char *addrStr, int addrStrLen)
+{
+    char host[NI_MAXHOST], service[NI_MAXSERV];
+
+    if (getnameinfo(addr, addrlen, host, NI_MAXHOST,
+                    service, NI_MAXSERV, NI_NUMERICSERV) == 0)
+        snprintf(addrStr, addrStrLen, "(%s, %s)", host, service);
+    else
+        snprintf(addrStr, addrStrLen, "(?UNKNOWN?)");
+
+    return addrStr;
+}
diff --git a/sockets/inet_sockets.h b/sockets/inet_sockets.h
new file mode 100644 (file)
index 0000000..621713e
--- /dev/null
@@ -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 <sys/socket.h>
+#include <netdb.h>
+
+int inetConnect(const char *host, const char *service, int type);
+
+int inetListen(const char *service, int backlog, socklen_t *addrlen);
+
+int inetBind(const char *service, int type, socklen_t *addrlen);
+
+char *inetAddressStr(const struct sockaddr *addr, socklen_t addrlen,
+                char *addrStr, int addrStrLen);
+
+#define IS_ADDR_STR_LEN 4096
+                        /* Suggested length for string buffer that caller
+                           should pass to inetAddressStr(). Must be greater
+                           than (NI_MAXHOST + NI_MAXSERV + 4) */
+#endif
diff --git a/sockets/is_echo_cl.c b/sockets/is_echo_cl.c
new file mode 100644 (file)
index 0000000..c361ad2
--- /dev/null
@@ -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 (file)
index 0000000..9887ae9
--- /dev/null
@@ -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 <syslog.h>
+#include "tlpi_hdr.h"
+
+#define BUF_SIZE 4096
+
+int
+main(int argc, char *argv[])
+{
+    char buf[BUF_SIZE];
+    ssize_t numRead;
+
+    while ((numRead = read(STDIN_FILENO, buf, BUF_SIZE)) > 0) {
+        if (write(STDOUT_FILENO, buf, numRead) != numRead) {
+            syslog(LOG_ERR, "write() failed: %s", strerror(errno));
+            exit(EXIT_FAILURE);
+        }
+    }
+
+    if (numRead == -1) {
+        syslog(LOG_ERR, "Error from read(): %s", strerror(errno));
+        exit(EXIT_FAILURE);
+    }
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/sockets/is_echo_sv.c b/sockets/is_echo_sv.c
new file mode 100644 (file)
index 0000000..7024545
--- /dev/null
@@ -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 <signal.h>
+#include <syslog.h>
+#include <sys/wait.h>
+#include "become_daemon.h"
+#include "inet_sockets.h"       /* Declarations of inet*() socket functions */
+#include "tlpi_hdr.h"
+
+#define SERVICE "echo"          /* Name of TCP service */
+#define BUF_SIZE 4096
+
+static void             /* SIGCHLD handler to reap dead child processes */
+grimReaper(int sig)
+{
+    int savedErrno;             /* Save 'errno' in case changed here */
+
+    savedErrno = errno;
+    while (waitpid(-1, NULL, WNOHANG) > 0)
+        continue;
+    errno = savedErrno;
+}
+
+/* Handle a client request: copy socket input back to socket */
+
+static void
+handleRequest(int cfd)
+{
+    char buf[BUF_SIZE];
+    ssize_t numRead;
+
+    while ((numRead = read(cfd, buf, BUF_SIZE)) > 0) {
+        if (write(cfd, buf, numRead) != numRead) {
+            syslog(LOG_ERR, "write() failed: %s", strerror(errno));
+            exit(EXIT_FAILURE);
+        }
+    }
+
+    if (numRead == -1) {
+        syslog(LOG_ERR, "Error from read(): %s", strerror(errno));
+        exit(EXIT_FAILURE);
+    }
+}
+
+int
+main(int argc, char *argv[])
+{
+    int lfd, cfd;               /* Listening and connected sockets */
+    struct sigaction sa;
+
+    if (becomeDaemon(0) == -1)
+        errExit("becomeDaemon");
+
+    /* Establish SIGCHLD handler to reap terminated child processes */
+
+    sigemptyset(&sa.sa_mask);
+    sa.sa_flags = SA_RESTART;
+    sa.sa_handler = grimReaper;
+    if (sigaction(SIGCHLD, &sa, NULL) == -1) {
+        syslog(LOG_ERR, "Error from sigaction(): %s", strerror(errno));
+        exit(EXIT_FAILURE);
+    }
+
+    lfd = inetListen(SERVICE, 10, NULL);
+    if (lfd == -1) {
+        syslog(LOG_ERR, "Could not create server socket (%s)", strerror(errno));
+        exit(EXIT_FAILURE);
+    }
+
+    for (;;) {
+        cfd = accept(lfd, NULL, NULL);  /* Wait for connection */
+        if (cfd == -1) {
+            syslog(LOG_ERR, "Failure in accept(): %s", strerror(errno));
+            exit(EXIT_FAILURE);
+        }
+
+        /* Handle each client request in a new child process */
+
+        switch (fork()) {
+        case -1:
+            syslog(LOG_ERR, "Can't create child (%s)", strerror(errno));
+            close(cfd);                 /* Give up on this client */
+            break;                      /* May be temporary; try next client */
+
+        case 0:                         /* Child */
+            close(lfd);                 /* Unneeded copy of listening socket */
+            handleRequest(cfd);
+            _exit(EXIT_SUCCESS);
+
+        default:                        /* Parent */
+            close(cfd);                 /* Unneeded copy of connected socket */
+            break;                      /* Loop to accept next connection */
+        }
+    }
+}
diff --git a/sockets/is_echo_v2_sv.c b/sockets/is_echo_v2_sv.c
new file mode 100644 (file)
index 0000000..0d07a6f
--- /dev/null
@@ -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 <some-path> as required):
+
+        echo stream tcp nowait root /some-path/is_echo_v2_sv is_echo_v2_sv -i
+
+   Note that this program is very similar to is_echo_sv.c: all we've
+   done is add a few extra lines of code to handle the "-i" option.
+
+   See also is_echo_sv.c.
+*/
+#include <syslog.h>
+#include <signal.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include "become_daemon.h"
+#include "inet_sockets.h"       /* Declares our socket functions */
+#include "tlpi_hdr.h"
+
+#define SERVICE "echo"          /* Name of TCP service */
+#define BUF_SIZE 4096
+
+static void             /* SIGCHLD handler to reap dead child processes */
+grimReaper(int sig)
+{
+    int savedErrno;             /* Save 'errno' in case changed here */
+
+    savedErrno = errno;
+    while (waitpid(-1, NULL, WNOHANG) > 0)
+        continue;
+    errno = savedErrno;
+}
+
+static void             /* Handle client: copy socket input back to socket */
+handleRequest(int cfd)
+{
+    ssize_t numRead;
+    char buf[BUF_SIZE];
+
+    while ((numRead = read(cfd, buf, BUF_SIZE)) > 0) {
+        if (write(cfd, buf, numRead) != numRead) {
+            syslog(LOG_ERR, "write() failed: %s", strerror(errno));
+            exit(EXIT_FAILURE);
+        }
+    }
+
+    if (numRead == -1) {
+        syslog(LOG_ERR, "Error from read(): %s", strerror(errno));
+        exit(EXIT_FAILURE);
+    }
+}
+
+int
+main(int argc, char *argv[])
+{
+    int lfd, cfd;               /* Listening and connected sockets */
+    struct sigaction sa;
+
+    /* The "-i" option means we were invoked from inetd(8), so that
+       all we need to do is handle the connection on STDIN_FILENO */
+
+    if (argc > 1 && strcmp(argv[1], "-i") == 0) {
+        handleRequest(STDIN_FILENO);
+        exit(EXIT_SUCCESS);
+    }
+
+    if (becomeDaemon(0) == -1)
+        errExit("becomeDaemon");
+
+    sigemptyset(&sa.sa_mask);
+    sa.sa_flags = SA_RESTART;
+    sa.sa_handler = grimReaper;
+    if (sigaction(SIGCHLD, &sa, NULL) == -1) {
+        syslog(LOG_ERR, "Error from sigaction(): %s", strerror(errno));
+        exit(EXIT_FAILURE);
+    }
+
+    lfd = inetListen(SERVICE, 10, NULL);
+    if (lfd == -1) {
+        syslog(LOG_ERR, "Could not create server socket (%s)", strerror(errno));
+        exit(EXIT_FAILURE);
+    }
+
+    for (;;) {
+        cfd = accept(lfd, NULL, NULL);  /* Wait for connection */
+        if (cfd == -1) {
+            syslog(LOG_ERR, "Failure in accept(): %s",
+                    strerror(errno));
+            continue;           /* Try next */
+        }
+
+        switch (fork()) {       /* Create child for each client */
+        case -1:
+            syslog(LOG_ERR, "Can't create child (%s)",
+                    strerror(errno));
+            close(cfd);         /* Give up on this client */
+            break;              /* May be temporary; try next client */
+
+        case 0:                 /* Child */
+            close(lfd);         /* Don't need copy of listening socket */
+            handleRequest(cfd);
+            _exit(EXIT_SUCCESS);
+
+        default:                /* Parent */
+            close(cfd);         /* Don't need copy of connected socket */
+            break;              /* Loop to accept next connection */
+        }
+    }
+}
diff --git a/sockets/is_seqnum.h b/sockets/is_seqnum.h
new file mode 100644 (file)
index 0000000..c838008
--- /dev/null
@@ -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 <netinet/in.h>
+#include <sys/socket.h>
+#include <signal.h>
+#include "read_line.h"          /* Declaration of readLine() */
+#include "tlpi_hdr.h"
+
+#define PORT_NUM "50000"        /* Port number for server */
+
+#define INT_LEN 30              /* Size of string able to hold largest
+                                   integer (including terminating '\n') */
diff --git a/sockets/is_seqnum_cl.c b/sockets/is_seqnum_cl.c
new file mode 100644 (file)
index 0000000..ae90a86
--- /dev/null
@@ -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 <netdb.h>
+#include "is_seqnum.h"
+
+int
+main(int argc, char *argv[])
+{
+    char *reqLenStr;                    /* Requested length of sequence */
+    char seqNumStr[INT_LEN];            /* Start of granted sequence */
+    int cfd;
+    ssize_t numRead;
+    struct addrinfo hints;
+    struct addrinfo *result, *rp;
+
+    if (argc < 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s server-host [sequence-len]\n", argv[0]);
+
+    /* Call getaddrinfo() to obtain a list of addresses that
+       we can try connecting to */
+
+    memset(&hints, 0, sizeof(struct addrinfo));
+    hints.ai_canonname = NULL;
+    hints.ai_addr = NULL;
+    hints.ai_next = NULL;
+    hints.ai_family = AF_UNSPEC;                /* Allows IPv4 or IPv6 */
+    hints.ai_socktype = SOCK_STREAM;
+    hints.ai_flags = AI_NUMERICSERV;
+
+    if (getaddrinfo(argv[1], PORT_NUM, &hints, &result) != 0)
+        errExit("getaddrinfo");
+
+    /* Walk through returned list until we find an address structure
+       that can be used to successfully connect a socket */
+
+    for (rp = result; rp != NULL; rp = rp->ai_next) {
+        cfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+        if (cfd == -1)
+            continue;                           /* On error, try next address */
+
+        if (connect(cfd, rp->ai_addr, rp->ai_addrlen) != -1)
+            break;                              /* Success */
+
+        /* Connect failed: close this socket and try next address */
+
+        close(cfd);
+    }
+
+    if (rp == NULL)
+        fatal("Could not connect socket to any address");
+
+    freeaddrinfo(result);
+
+    /* Send requested sequence length, with terminating newline */
+
+    reqLenStr = (argc > 2) ? argv[2] : "1";
+    if (write(cfd, reqLenStr, strlen(reqLenStr)) !=  strlen(reqLenStr))
+        fatal("Partial/failed write (reqLenStr)");
+    if (write(cfd, "\n", 1) != 1)
+        fatal("Partial/failed write (newline)");
+
+    /* Read and display sequence number returned by server */
+
+    numRead = readLine(cfd, seqNumStr, INT_LEN);
+    if (numRead == -1)
+        errExit("readLine");
+    if (numRead == 0)
+        fatal("Unexpected EOF from server");
+
+    printf("Sequence number: %s", seqNumStr);   /* Includes '\n' */
+
+    exit(EXIT_SUCCESS);                         /* Closes 'cfd' */
+}
diff --git a/sockets/is_seqnum_sv.c b/sockets/is_seqnum_sv.c
new file mode 100644 (file)
index 0000000..9855570
--- /dev/null
@@ -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 <netdb.h> */
+#include <netdb.h>
+#include "is_seqnum.h"
+
+#define BACKLOG 50
+
+int
+main(int argc, char *argv[])
+{
+    uint32_t seqNum;
+    char reqLenStr[INT_LEN];            /* Length of requested sequence */
+    char seqNumStr[INT_LEN];            /* Start of granted sequence */
+    struct sockaddr_storage claddr;
+    int lfd, cfd, optval, reqLen;
+    socklen_t addrlen;
+    struct addrinfo hints;
+    struct addrinfo *result, *rp;
+#define ADDRSTRLEN (NI_MAXHOST + NI_MAXSERV + 10)
+    char addrStr[ADDRSTRLEN];
+    char host[NI_MAXHOST];
+    char service[NI_MAXSERV];
+
+    if (argc > 1 && strcmp(argv[1], "--help") == 0)
+        usageErr("%s [init-seq-num]\n", argv[0]);
+
+    seqNum = (argc > 1) ? getInt(argv[1], 0, "init-seq-num") : 0;
+
+    /* Ignore the SIGPIPE signal, so that we find out about broken connection
+       errors via a failure from write(). */
+
+    if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)    errExit("signal");
+
+    /* Call getaddrinfo() to obtain a list of addresses that
+       we can try binding to */
+
+    memset(&hints, 0, sizeof(struct addrinfo));
+    hints.ai_canonname = NULL;
+    hints.ai_addr = NULL;
+    hints.ai_next = NULL;
+    hints.ai_socktype = SOCK_STREAM;
+    hints.ai_family = AF_UNSPEC;        /* Allows IPv4 or IPv6 */
+    hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV;
+                        /* Wildcard IP address; service name is numeric */
+
+    if (getaddrinfo(NULL, PORT_NUM, &hints, &result) != 0)
+        errExit("getaddrinfo");
+
+    /* Walk through returned list until we find an address structure
+       that can be used to successfully create and bind a socket */
+
+    optval = 1;
+    for (rp = result; rp != NULL; rp = rp->ai_next) {
+        lfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+        if (lfd == -1)
+            continue;                   /* On error, try next address */
+
+        if (setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval))
+                == -1)
+             errExit("setsockopt");
+
+        if (bind(lfd, rp->ai_addr, rp->ai_addrlen) == 0)
+            break;                      /* Success */
+
+        /* bind() failed: close this socket and try next address */
+
+        close(lfd);
+    }
+
+    if (rp == NULL)
+        fatal("Could not bind socket to any address");
+
+    if (listen(lfd, BACKLOG) == -1)
+        errExit("listen");
+
+    freeaddrinfo(result);
+
+    for (;;) {                  /* Handle clients iteratively */
+
+        /* Accept a client connection, obtaining client's address */
+
+        addrlen = sizeof(struct sockaddr_storage);
+        cfd = accept(lfd, (struct sockaddr *) &claddr, &addrlen);
+        if (cfd == -1) {
+            errMsg("accept");
+            continue;
+        }
+
+        if (getnameinfo((struct sockaddr *) &claddr, addrlen,
+                    host, NI_MAXHOST, service, NI_MAXSERV, 0) == 0)
+            snprintf(addrStr, ADDRSTRLEN, "(%s, %s)", host, service);
+        else
+            snprintf(addrStr, ADDRSTRLEN, "(?UNKNOWN?)");
+        printf("Connection from %s\n", addrStr);
+
+        /* Read client request, send sequence number back */
+
+        if (readLine(cfd, reqLenStr, INT_LEN) <= 0) {
+            close(cfd);
+            continue;                   /* Failed read; skip request */
+        }
+
+        reqLen = atoi(reqLenStr);
+        if (reqLen <= 0) {              /* Watch for misbehaving clients */
+            close(cfd);
+            continue;                   /* Bad request; skip it */
+        }
+
+        snprintf(seqNumStr, INT_LEN, "%d\n", seqNum);
+        if (write(cfd, seqNumStr, strlen(seqNumStr)) != strlen(seqNumStr))
+            fprintf(stderr, "Error on write");
+
+        seqNum += reqLen;               /* Update sequence number */
+
+        if (close(cfd) == -1)           /* Close connection */
+            errMsg("close");
+    }
+}
diff --git a/sockets/is_seqnum_v2.h b/sockets/is_seqnum_v2.h
new file mode 100644 (file)
index 0000000..50e0fe9
--- /dev/null
@@ -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 <netinet/in.h>
+#include <sys/socket.h>
+#include <signal.h>
+#include "inet_sockets.h"       /* Declares our socket functions */
+#include "read_line.h"          /* Declaration of readLine() */
+#include "tlpi_hdr.h"
+
+#define PORT_NUM_STR "50000"    /* Port number for server */
+
+#define INT_LEN 30              /* Size of string able to hold largest
+                                   integer (including terminating '\n') */
diff --git a/sockets/is_seqnum_v2_cl.c b/sockets/is_seqnum_v2_cl.c
new file mode 100644 (file)
index 0000000..e68a6d9
--- /dev/null
@@ -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 (file)
index 0000000..051c851
--- /dev/null
@@ -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 (file)
index 0000000..2be8fea
--- /dev/null
@@ -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 <arpa/inet.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <ifaddrs.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <linux/if_link.h>
+
+int
+main(int argc, char *argv[])
+{
+    struct ifaddrs *ifaddr;
+    int family, s;
+    char host[NI_MAXHOST];
+
+    if (getifaddrs(&ifaddr) == -1) {
+        perror("getifaddrs");
+        exit(EXIT_FAILURE);
+    }
+
+    /* Walk through linked list, ignoring loopback interface and
+       non-AF_INET* addresses */
+
+    for (; ifaddr != NULL; ifaddr = ifaddr->ifa_next) {
+
+        if (ifaddr->ifa_addr == NULL || strcmp(ifaddr->ifa_name, "lo") == 0)
+            continue;
+
+        family = ifaddr->ifa_addr->sa_family;
+
+        if (family != AF_INET && family != AF_INET6)
+            continue;
+
+        /* Display interface name and address */
+
+        s = getnameinfo(ifaddr->ifa_addr,
+                    (family == AF_INET) ? sizeof(struct sockaddr_in) :
+                                          sizeof(struct sockaddr_in6),
+                    host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
+        if (s != 0) {
+            printf("getnameinfo() failed: %s\n", gai_strerror(s));
+            exit(EXIT_FAILURE);
+        }
+
+        printf("%-16s %s\n", ifaddr->ifa_name, host);
+    }
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/sockets/rdwrn.c b/sockets/rdwrn.c
new file mode 100644 (file)
index 0000000..ccbe244
--- /dev/null
@@ -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 <unistd.h>
+#include <errno.h>
+#include "rdwrn.h"                      /* Declares readn() and writen() */
+
+/* Read 'n' bytes from 'fd' into 'buf', restarting after partial
+   reads or interruptions by a signal handlers */
+
+ssize_t
+readn(int fd, void *buffer, size_t n)
+{
+    ssize_t numRead;                    /* # of bytes fetched by last read() */
+    size_t totRead;                     /* Total # of bytes read so far */
+    char *buf;
+
+    buf = buffer;                       /* No pointer arithmetic on "void *" */
+    for (totRead = 0; totRead < n; ) {
+        numRead = read(fd, buf, n - totRead);
+
+        if (numRead == 0)               /* EOF */
+            return totRead;             /* May be 0 if this is first read() */
+        if (numRead == -1) {
+            if (errno == EINTR)
+                continue;               /* Interrupted --> restart read() */
+            else
+                return -1;              /* Some other error */
+        }
+        totRead += numRead;
+        buf += numRead;
+    }
+    return totRead;                     /* Must be 'n' bytes if we get here */
+}
+
+/* Write 'n' bytes to 'fd' from 'buf', restarting after partial
+   write or interruptions by a signal handlers */
+
+ssize_t
+writen(int fd, const void *buffer, size_t n)
+{
+    ssize_t numWritten;                 /* # of bytes written by last write() */
+    size_t totWritten;                  /* Total # of bytes written so far */
+    const char *buf;
+
+    buf = buffer;                       /* No pointer arithmetic on "void *" */
+    for (totWritten = 0; totWritten < n; ) {
+        numWritten = write(fd, buf, n - totWritten);
+
+        /* The "write() returns 0" case should never happen, but the
+           following ensures that we don't loop forever if it does */
+
+        if (numWritten <= 0) {
+            if (numWritten == -1 && errno == EINTR)
+                continue;               /* Interrupted --> restart write() */
+            else
+                return -1;              /* Some other error */
+        }
+        totWritten += numWritten;
+        buf += numWritten;
+    }
+    return totWritten;                  /* Must be 'n' bytes if we get here */
+}
diff --git a/sockets/rdwrn.h b/sockets/rdwrn.h
new file mode 100644 (file)
index 0000000..bb55f8b
--- /dev/null
@@ -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 <sys/types.h>
+
+ssize_t readn(int fd, void *buf, size_t len);
+
+ssize_t writen(int fd, const void *buf, size_t len);
+
+#endif
diff --git a/sockets/read_line.c b/sockets/read_line.c
new file mode 100644 (file)
index 0000000..0b4aaff
--- /dev/null
@@ -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 <unistd.h>
+#include <errno.h>
+#include "read_line.h"                  /* Declaration of readLine() */
+
+/* Read characters from 'fd' until a newline is encountered. If a newline
+  character is not encountered in the first (n - 1) bytes, then the excess
+  characters are discarded. The returned string placed in 'buf' is
+  null-terminated and includes the newline character if it was read in the
+  first (n - 1) bytes. The function return value is the number of bytes
+  placed in buffer (which includes the newline character if encountered,
+  but excludes the terminating null byte). */
+
+ssize_t
+readLine(int fd, void *buffer, size_t n)
+{
+    ssize_t numRead;                    /* # of bytes fetched by last read() */
+    size_t totRead;                     /* Total bytes read so far */
+    char *buf;
+    char ch;
+
+    if (n <= 0 || buffer == NULL) {
+        errno = EINVAL;
+        return -1;
+    }
+
+    buf = buffer;                       /* No pointer arithmetic on "void *" */
+
+    totRead = 0;
+    for (;;) {
+        numRead = read(fd, &ch, 1);
+
+        if (numRead == -1) {
+            if (errno == EINTR)         /* Interrupted --> restart read() */
+                continue;
+            else
+                return -1;              /* Some other error */
+
+        } else if (numRead == 0) {      /* EOF */
+            if (totRead == 0)           /* No bytes read; return 0 */
+                return 0;
+            else                        /* Some bytes read; add '\0' */
+                break;
+
+        } else {                        /* 'numRead' must be 1 if we get here */
+            if (totRead < n - 1) {      /* Discard > (n - 1) bytes */
+                totRead++;
+                *buf++ = ch;
+            }
+
+            if (ch == '\n')
+                break;
+        }
+    }
+
+    *buf = '\0';
+    return totRead;
+}
diff --git a/sockets/read_line.h b/sockets/read_line.h
new file mode 100644 (file)
index 0000000..b03a1e8
--- /dev/null
@@ -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 <sys/types.h>
+
+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 (file)
index 0000000..1240a44
--- /dev/null
@@ -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 <unistd.h>
+#include <errno.h>
+#include "read_line_buf.h"
+
+void                    /* Initialize a ReadLineBuf structure */
+readLineBufInit(int fd, struct ReadLineBuf *rlbuf)
+{
+    rlbuf->fd = fd;
+    rlbuf->len = 0;
+    rlbuf->next = 0;
+}
+
+/* Return a line of input from the buffer 'rlbuf', placing the characters in
+   'buffer'. The 'n' argument specifies the size of 'buffer'. If the line of
+   input is larger than this, then the excess characters are discarded. */
+
+ssize_t
+readLineBuf(struct ReadLineBuf *rlbuf, char *buffer, size_t n)
+{
+    size_t cnt;
+    char c;
+
+    if (n <= 0 || buffer == NULL) {
+        errno = EINVAL;
+        return -1;
+    }
+
+    cnt = 0;
+
+    /* Fetch characters from rlbuf->buf, up to the next new line. */
+
+    for (;;) {
+
+        /* If there are insufficient characters in 'tlbuf', then obtain
+           further input from the associated file descriptor. */
+
+        if (rlbuf->next >= rlbuf->len) {
+            rlbuf->len = read(rlbuf->fd, rlbuf->buf, RL_MAX_BUF);
+            if (rlbuf->len == -1)
+                return -1;
+
+            if (rlbuf->len == 0)        /* End of file */
+                break;
+
+            rlbuf->next = 0;
+        }
+
+        c = rlbuf->buf[rlbuf->next];
+        rlbuf->next++;
+
+        if (cnt < n)
+            buffer[cnt++] = c;
+
+        if (c == '\n')
+            break;
+    }
+
+    return cnt;
+}
diff --git a/sockets/read_line_buf.h b/sockets/read_line_buf.h
new file mode 100644 (file)
index 0000000..7955e39
--- /dev/null
@@ -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 <unistd.h>
+#include <pthread.h>
+#include <errno.h>
+
+#define RL_MAX_BUF 10
+
+struct ReadLineBuf {
+    int     fd;                 /* File descriptor from which to read */
+    char    buf[RL_MAX_BUF];    /* Current buffer from file */
+    int     next;               /* Index of next unread character in 'buf' */
+    ssize_t len;                /* Number of characters in 'buf' */
+};
+
+void readLineBufInit(int fd, struct ReadLineBuf *rlbuf);
+
+ssize_t readLineBuf(struct ReadLineBuf *rlbuf, char *buffer, size_t n);
+
+#endif
diff --git a/sockets/scm_cred.h b/sockets/scm_cred.h
new file mode 100644 (file)
index 0000000..eae4b1c
--- /dev/null
@@ -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
+                                   <sys/sockets.h> */
+#include <sys/socket.h>
+#include <sys/un.h>
+#include "unix_sockets.h"       /* Declares our socket functions */
+#include "tlpi_hdr.h"
+
+#define SOCK_PATH "scm_cred"
diff --git a/sockets/scm_cred_recv.c b/sockets/scm_cred_recv.c
new file mode 100644 (file)
index 0000000..5b2a974
--- /dev/null
@@ -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 (file)
index 0000000..fe20708
--- /dev/null
@@ -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 (file)
index 0000000..9780b0a
--- /dev/null
@@ -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 <sys/socket.h>
+#include <sys/un.h>
+#include <fcntl.h>
+#include "unix_sockets.h"       /* Declares our unix*() socket functions */
+#include "tlpi_hdr.h"
+
+#define SOCK_PATH "scm_rights"
diff --git a/sockets/scm_rights_recv.c b/sockets/scm_rights_recv.c
new file mode 100644 (file)
index 0000000..acb78bb
--- /dev/null
@@ -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 (file)
index 0000000..8d12dbb
--- /dev/null
@@ -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 (file)
index 0000000..8368203
--- /dev/null
@@ -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 (file)
index 0000000..b738f78
--- /dev/null
@@ -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 (file)
index 0000000..19c86c7
--- /dev/null
@@ -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 <netdb.h> */
+#include <netdb.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    struct hostent *h;
+    char **pp;
+    char str[INET6_ADDRSTRLEN];
+
+    for (argv++; *argv != NULL; argv++) {
+        h = gethostbyname(*argv);
+        if (h == NULL) {
+            fprintf(stderr, "gethostbyname() failed for '%s': %s\n",
+                    *argv, hstrerror(h_errno));
+            continue;
+        }
+
+        printf("Canonical name: %s\n", h->h_name);
+
+        printf("        alias(es):     ");
+        for (pp = h->h_aliases; *pp != NULL; pp++)
+            printf(" %s", *pp);
+        printf("\n");
+
+        printf("        address type:   %s\n",
+                (h->h_addrtype == AF_INET) ? "AF_INET" :
+                (h->h_addrtype == AF_INET6) ? "AF_INET6" : "???");
+
+        if (h->h_addrtype == AF_INET || h->h_addrtype == AF_INET6) {
+            printf("        address(es):   ");
+            for (pp = h->h_addr_list; *pp != NULL; pp++)
+                printf(" %s", inet_ntop(h->h_addrtype, *pp,
+                                        str, INET6_ADDRSTRLEN));
+            printf("\n");
+        }
+    }
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/sockets/t_getservbyname.c b/sockets/t_getservbyname.c
new file mode 100644 (file)
index 0000000..c665892
--- /dev/null
@@ -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 <netdb.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    struct servent *s;
+    char **pp;
+
+    if (argc < 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s service [protocol]\n", argv[0]);
+
+    s = getservbyname(argv[1], argv[2]);
+    if (s == NULL)
+        fatal("getservbyname() could not find a matching record");
+
+    printf("Official name: %s\n", s->s_name);
+    printf("Aliases:      ");
+    for (pp = s->s_aliases; *pp != NULL; pp++)
+        printf(" %s", *pp);
+    printf("\n");
+    printf("Port:          %u\n", ntohs(s->s_port));
+    printf("Protocol:      %s\n", s->s_proto);
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/sockets/ud_ucase.h b/sockets/ud_ucase.h
new file mode 100644 (file)
index 0000000..ab57b35
--- /dev/null
@@ -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 <sys/un.h>
+#include <sys/socket.h>
+#include <ctype.h>
+#include "tlpi_hdr.h"
+
+#define BUF_SIZE 10             /* Maximum size of messages exchanged
+                                   between client and server */
+
+#define SV_SOCK_PATH "/tmp/ud_ucase"
diff --git a/sockets/ud_ucase_cl.c b/sockets/ud_ucase_cl.c
new file mode 100644 (file)
index 0000000..4ae21a0
--- /dev/null
@@ -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 (file)
index 0000000..535daad
--- /dev/null
@@ -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 (file)
index 0000000..5e5108f
--- /dev/null
@@ -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 (file)
index 0000000..bfe294a
--- /dev/null
@@ -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 <sys/socket.h>
+#include <sys/un.h>
+
+int unixBuildAddress(const char *path, struct sockaddr_un *addr);
+
+int unixConnect(const char *path, int type);
+
+int unixListen(const char *path, int backlog);
+
+int unixBind(const char *path, int type);
+
+#endif
diff --git a/sockets/us_abstract_bind.c b/sockets/us_abstract_bind.c
new file mode 100644 (file)
index 0000000..6faa770
--- /dev/null
@@ -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 <sys/un.h>
+#include <sys/socket.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    int sockfd;
+    struct sockaddr_un addr;
+    char *str;
+
+    memset(&addr, 0, sizeof(struct sockaddr_un));  /* Clear address structure */
+    addr.sun_family = AF_UNIX;                     /* UNIX domain address */
+
+    /* addr.sun_path[0] has already been set to 0 by memset() */
+
+    str = "xyz";        /* Abstract name is "\0xyz" */
+    strncpy(&addr.sun_path[1], str, strlen(str));
+
+    // In early printings of the book, the above two lines were instead:
+    //
+    // strncpy(&addr.sun_path[1], "xyz", sizeof(addr.sun_path) - 2);
+    //            /* Abstract name is "xyz" followed by null bytes */
+
+    sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
+    if (sockfd == -1)
+        errExit("socket");
+
+    if (bind(sockfd, (struct sockaddr *) &addr,
+            sizeof(sa_family_t) + strlen(str) + 1) == -1)
+        errExit("bind");
+
+    // In early printings of the book, the final part of the bind() call
+    // above was instead:
+    //        sizeof(struct sockaddr_un)) == -1)
+
+    sleep(60);
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/sockets/us_xfr.h b/sockets/us_xfr.h
new file mode 100644 (file)
index 0000000..3e8a3c4
--- /dev/null
@@ -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 <sys/un.h>
+#include <sys/socket.h>
+#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 (file)
index 0000000..b8823f1
--- /dev/null
@@ -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 (file)
index 0000000..00e4f41
--- /dev/null
@@ -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 (file)
index 0000000..fdd0e99
--- /dev/null
@@ -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 (file)
index 0000000..dfce5b2
--- /dev/null
@@ -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 (file)
index 0000000..c669f7a
--- /dev/null
@@ -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 (file)
index 0000000..7778395
--- /dev/null
@@ -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 (file)
index 0000000..bbb99b8
--- /dev/null
@@ -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 <sys/types.h>
+#include <sys/ipc.h>
+#include <sys/msg.h>
+#include <sys/stat.h>
+#include "tlpi_hdr.h"
+
+#define KEY_FILE "/some-path/some-file"
+                                /* Should be an existing file or one
+                                   that this program creates */
+
+int
+main(int argc, char *argv[])
+{
+    int msqid;
+    key_t key;
+    const int MQ_PERMS = S_IRUSR | S_IWUSR | S_IWGRP;   /* rw--w---- */
+
+    /* Optional code here to check if another server process is
+       already running */
+
+    /* Generate the key for the message queue */
+
+    key = ftok(KEY_FILE, 1);
+    if (key == -1)
+        errExit("ftok");
+
+    /* While msgget() fails, try creating the queue exclusively */
+
+    while ((msqid = msgget(key, IPC_CREAT | IPC_EXCL | MQ_PERMS)) ==
+            -1) {
+        if (errno == EEXIST) {          /* MQ with the same key already
+                                           exists - remove it and try again */
+            msqid = msgget(key, 0);
+            if (msqid == -1)
+                errExit("msgget() failed to retrieve old queue ID");
+            if (msgctl(msqid, IPC_RMID, NULL) == -1)
+                errExit("msgget() failed to delete old queue");
+            printf("Removed old message queue (id=%d)\n", msqid);
+
+        } else {                        /* Some other error --> give up */
+            errExit("msgget() failed");
+        }
+    }
+
+    /* Upon loop exit, we've successfully created the message queue,
+       and we can then carry on to do other work... */
+
+    printf("Queue created with id %d\n", msqid);
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/svipc/t_ftok.c b/svipc/t_ftok.c
new file mode 100644 (file)
index 0000000..58baed0
--- /dev/null
@@ -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 <sys/ipc.h>
+#include <sys/stat.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    key_t key;
+    struct stat sb;
+
+    if (argc != 3 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s file-name keychar\n", argv[0]);
+
+    printf("Size of key_t = %ld bytes\n", (long) sizeof(key_t));
+
+    if (stat(argv[1], &sb) == -1)
+        errExit("stat");
+
+    key = ftok(argv[1], argv[2][0]);
+    if (key == -1)
+        errExit("ftok");
+
+    printf("Key = %lx i-node = %lx st_dev = %lx proj = %x\n",
+          (unsigned long) key, (unsigned long) sb.st_ino,
+          (unsigned long) sb.st_dev, (unsigned int) argv[2][0]);
+    if (key == -1)
+        printf("File does not exist\n");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/svmsg/Makefile b/svmsg/Makefile
new file mode 100644 (file)
index 0000000..7f0b322
--- /dev/null
@@ -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 (file)
index 0000000..589b94c
--- /dev/null
@@ -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 <sys/types.h>
+#include <sys/msg.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    struct msqid_ds ds;
+    int msqid;
+
+    if (argc != 3 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s msqid max-bytes\n", argv[0]);
+
+    /* Retrieve copy of associated data structure from kernel */
+
+    msqid = getInt(argv[1], 0, "msqid");
+    if (msgctl(msqid, IPC_STAT, &ds) == -1)
+        errExit("msgctl");
+
+    ds.msg_qbytes = getInt(argv[2], 0, "max-bytes");
+
+    /* Update associated data structure in kernel */
+
+    if (msgctl(msqid, IPC_SET, &ds) == -1)
+        errExit("msgctl");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/svmsg/svmsg_create.c b/svmsg/svmsg_create.c
new file mode 100644 (file)
index 0000000..e107ef5
--- /dev/null
@@ -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 <sys/types.h>
+#include <sys/ipc.h>
+#include <sys/msg.h>
+#include <sys/stat.h>
+#include "tlpi_hdr.h"
+
+static void             /* Print usage info, then exit */
+usageError(const char *progName, const char *msg)
+{
+    if (msg != NULL)
+        fprintf(stderr, "%s", msg);
+    fprintf(stderr, "Usage: %s [-cx] {-f pathname | -k key | -p} "
+                            "[octal-perms]\n", progName);
+    fprintf(stderr, "    -c           Use IPC_CREAT flag\n");
+    fprintf(stderr, "    -x           Use IPC_EXCL flag\n");
+    fprintf(stderr, "    -f pathname  Generate key using ftok()\n");
+    fprintf(stderr, "    -k key       Use 'key' as key\n");
+    fprintf(stderr, "    -p           Use IPC_PRIVATE key\n");
+    exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+    int numKeyFlags;            /* Counts -f, -k, and -p options */
+    int flags, msqid, opt;
+    unsigned int perms;
+    long lkey;
+    key_t key;
+
+    /* Parse command-line options and arguments */
+
+    numKeyFlags = 0;
+    flags = 0;
+
+    while ((opt = getopt(argc, argv, "cf:k:px")) != -1) {
+        switch (opt) {
+        case 'c':
+            flags |= IPC_CREAT;
+            break;
+
+        case 'f':               /* -f pathname */
+            key = ftok(optarg, 1);
+            if (key == -1)
+                errExit("ftok");
+            numKeyFlags++;
+            break;
+
+        case 'k':               /* -k key (octal, decimal or hexadecimal) */
+            if (sscanf(optarg, "%li", &lkey) != 1)
+                cmdLineErr("-k option requires a numeric argument\n");
+            key = lkey;
+            numKeyFlags++;
+            break;
+
+        case 'p':
+            key = IPC_PRIVATE;
+            numKeyFlags++;
+            break;
+
+        case 'x':
+            flags |= IPC_EXCL;
+            break;
+
+        default:
+            usageError(argv[0], "Bad option\n");
+        }
+    }
+
+    if (numKeyFlags != 1)
+        usageError(argv[0], "Exactly one of the options -f, -k, "
+                            "or -p must be supplied\n");
+
+    perms = (optind == argc) ? (S_IRUSR | S_IWUSR) :
+                getInt(argv[optind], GN_BASE_8, "octal-perms");
+
+    msqid = msgget(key, flags | perms);
+    if (msqid == -1)
+        errExit("msgget");
+
+    printf("%d\n", msqid);
+    exit(EXIT_SUCCESS);
+}
diff --git a/svmsg/svmsg_file.h b/svmsg/svmsg_file.h
new file mode 100644 (file)
index 0000000..eb515f8
--- /dev/null
@@ -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 <sys/types.h>
+#include <sys/msg.h>
+#include <sys/stat.h>
+#include <stddef.h>                     /* For definition of offsetof() */
+#include <limits.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include "tlpi_hdr.h"
+
+#define SERVER_KEY 0x1aaaaaa1           /* Key for server's message queue */
+
+struct requestMsg {                     /* Requests (client to server) */
+    long mtype;                         /* Unused */
+    int  clientId;                      /* ID of client's message queue */
+    char pathname[PATH_MAX];            /* File to be returned */
+};
+
+/* REQ_MSG_SIZE computes size of 'mtext' part of 'requestMsg' structure.
+   We use offsetof() to handle the possibility that there are padding
+   bytes between the 'clientId' and 'pathname' fields. */
+
+#define REQ_MSG_SIZE (offsetof(struct requestMsg, pathname) - \
+                      offsetof(struct requestMsg, clientId) + PATH_MAX)
+
+#define RESP_MSG_SIZE 8192
+
+struct responseMsg {                    /* Responses (server to client) */
+    long mtype;                         /* One of RESP_MT_* values below */
+    char data[RESP_MSG_SIZE];           /* File content / response message */
+};
+
+/* Types for response messages sent from server to client */
+
+#define RESP_MT_FAILURE 1               /* File couldn't be opened */
+#define RESP_MT_DATA    2               /* Message contains file data */
+#define RESP_MT_END     3               /* File data complete */
diff --git a/svmsg/svmsg_file_client.c b/svmsg/svmsg_file_client.c
new file mode 100644 (file)
index 0000000..0dd9588
--- /dev/null
@@ -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 (file)
index 0000000..7972c7d
--- /dev/null
@@ -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 (file)
index 0000000..6f7bf75
--- /dev/null
@@ -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 <sys/msg.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    struct msginfo info;
+    int s;
+
+    s = msgctl(0, MSG_INFO, (struct msqid_ds *) &info);
+    if (s == -1)
+        errExit("msgctl");
+
+    printf("Maximum ID index = %d\n", s);
+    printf("queues in_use    = %ld\n", (long) info.msgpool);
+    printf("msg_hdrs         = %ld\n", (long) info.msgmap);
+    printf("msg_bytes        = %ld\n", (long) info.msgmax);
+    exit(EXIT_SUCCESS);
+}
diff --git a/svmsg/svmsg_ls.c b/svmsg/svmsg_ls.c
new file mode 100644 (file)
index 0000000..eb0d462
--- /dev/null
@@ -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 <sys/types.h>
+#include <sys/msg.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    int maxind, ind, msqid;
+    struct msqid_ds ds;
+    struct msginfo msginfo;
+
+    /* Obtain size of kernel 'entries' array */
+
+    maxind = msgctl(0, MSG_INFO, (struct msqid_ds *) &msginfo);
+    if (maxind == -1)
+        errExit("msgctl-MSG_INFO");
+
+    printf("maxind: %d\n\n", maxind);
+    printf("index     id       key      messages\n");
+
+    /* Retrieve and display information from each element of 'entries' array */
+
+    for (ind = 0; ind <= maxind; ind++) {
+        msqid = msgctl(ind, MSG_STAT, &ds);
+        if (msqid == -1) {
+            if (errno != EINVAL && errno != EACCES)
+                errMsg("msgctl-MSG_STAT");              /* Unexpected error */
+            continue;                                   /* Ignore this item */
+        }
+
+        printf("%4d %8d  0x%08lx %7ld\n", ind, msqid,
+                (unsigned long) ds.msg_perm.__key, (long) ds.msg_qnum);
+    }
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/svmsg/svmsg_receive.c b/svmsg/svmsg_receive.c
new file mode 100644 (file)
index 0000000..8ce8fa9
--- /dev/null
@@ -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 <sys/types.h>
+#include <sys/msg.h>
+#include "tlpi_hdr.h"
+
+#define MAX_MTEXT 1024
+
+struct mbuf {
+    long mtype;                 /* Message type */
+    char mtext[MAX_MTEXT];      /* Message body */
+};
+
+static void
+usageError(const char *progName, const char *msg)
+{
+    if (msg != NULL)
+        fprintf(stderr, "%s", msg);
+    fprintf(stderr, "Usage: %s [options] msqid [max-bytes]\n", progName);
+    fprintf(stderr, "Permitted options are:\n");
+    fprintf(stderr, "    -e       Use MSG_NOERROR flag\n");
+    fprintf(stderr, "    -t type  Select message of given type\n");
+    fprintf(stderr, "    -n       Use IPC_NOWAIT flag\n");
+#ifdef MSG_EXCEPT
+    fprintf(stderr, "    -x       Use MSG_EXCEPT flag\n");
+#endif
+    exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+    int msqid, flags, type;
+    ssize_t msgLen;
+    size_t maxBytes;
+    struct mbuf msg;            /* Message buffer for msgrcv() */
+    int opt;                    /* Option character from getopt() */
+
+    /* Parse command-line options and arguments */
+
+    flags = 0;
+    type = 0;
+    while ((opt = getopt(argc, argv, "ent:x")) != -1) {
+        switch (opt) {
+        case 'e':       flags |= MSG_NOERROR;   break;
+        case 'n':       flags |= IPC_NOWAIT;    break;
+        case 't':       type = atoi(optarg);    break;
+#ifdef MSG_EXCEPT
+        case 'x':       flags |= MSG_EXCEPT;    break;
+#endif
+        default:        usageError(argv[0], NULL);
+        }
+    }
+
+    if (argc < optind + 1 || argc > optind + 2)
+        usageError(argv[0], "Wrong number of arguments\n");
+
+    msqid = getInt(argv[optind], 0, "msqid");
+    maxBytes = (argc > optind + 1) ?
+                getInt(argv[optind + 1], 0, "max-bytes") : MAX_MTEXT;
+
+    /* Get message and display on stdout */
+
+    msgLen = msgrcv(msqid, &msg, maxBytes, type, flags);
+    if (msgLen == -1)
+        errExit("msgrcv");
+
+    printf("Received: type=%ld; length=%ld", msg.mtype, (long) msgLen);
+    if (msgLen > 0)
+        printf("; body=%s", msg.mtext);
+    printf("\n");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/svmsg/svmsg_rm.c b/svmsg/svmsg_rm.c
new file mode 100644 (file)
index 0000000..60fd8e4
--- /dev/null
@@ -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 <sys/types.h>
+#include <sys/msg.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    int j;
+
+    if (argc > 1 && strcmp(argv[1], "--help") == 0)
+        usageErr("%s [msqid...]\n", argv[0]);
+
+    for (j = 1; j < argc; j++)
+        if (msgctl(getInt(argv[j], 0, "msqid"), IPC_RMID, NULL) == -1)
+            errExit("msgctl %s", argv[j]);
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/svmsg/svmsg_send.c b/svmsg/svmsg_send.c
new file mode 100644 (file)
index 0000000..1fa36d1
--- /dev/null
@@ -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 <sys/types.h>
+#include <sys/msg.h>
+#include "tlpi_hdr.h"
+
+#define MAX_MTEXT 1024
+
+struct mbuf {
+    long mtype;                         /* Message type */
+    char mtext[MAX_MTEXT];              /* Message body */
+};
+
+static void             /* Print (optional) message, then usage description */
+usageError(const char *progName, const char *msg)
+{
+    if (msg != NULL)
+        fprintf(stderr, "%s", msg);
+    fprintf(stderr, "Usage: %s [-n] msqid msg-type [msg-text]\n", progName);
+    fprintf(stderr, "    -n       Use IPC_NOWAIT flag\n");
+    exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+    int msqid, flags, msgLen;
+    struct mbuf msg;                    /* Message buffer for msgsnd() */
+    int opt;                            /* Option character from getopt() */
+
+    /* Parse command-line options and arguments */
+
+    flags = 0;
+    while ((opt = getopt(argc, argv, "n")) != -1) {
+        if (opt == 'n')
+            flags |= IPC_NOWAIT;
+        else
+            usageError(argv[0], NULL);
+    }
+
+    if (argc < optind + 2 || argc > optind + 3)
+        usageError(argv[0], "Wrong number of arguments\n");
+
+    msqid = getInt(argv[optind], 0, "msqid");
+    msg.mtype = getInt(argv[optind + 1], 0, "msg-type");
+
+    if (argc > optind + 2) {            /* 'msg-text' was supplied */
+        msgLen = strlen(argv[optind + 2]) + 1;
+        if (msgLen > MAX_MTEXT)
+            cmdLineErr("msg-text too long (max: %d characters)\n", MAX_MTEXT);
+
+        memcpy(msg.mtext, argv[optind + 2], msgLen);
+
+    } else {                            /* No 'msg-text' ==> zero-length msg */
+        msgLen = 0;
+    }
+
+    /* Send message */
+
+    if (msgsnd(msqid, &msg, msgLen, flags) == -1)
+        errExit("msgsnd");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/svsem/Makefile b/svsem/Makefile
new file mode 100644 (file)
index 0000000..4ab71a1
--- /dev/null
@@ -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 (file)
index 0000000..c85b80c
--- /dev/null
@@ -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 <sys/types.h>
+#include <sys/sem.h>
+#include "semun.h"                      /* Definition of semun union */
+#include "binary_sems.h"
+
+Boolean bsUseSemUndo = FALSE;
+Boolean bsRetryOnEintr = TRUE;
+
+int                     /* Initialize semaphore to 1 (i.e., "available") */
+initSemAvailable(int semId, int semNum)
+{
+    union semun arg;
+
+    arg.val = 1;
+    return semctl(semId, semNum, SETVAL, arg);
+}
+
+int                     /* Initialize semaphore to 0 (i.e., "in use") */
+initSemInUse(int semId, int semNum)
+{
+    union semun arg;
+
+    arg.val = 0;
+    return semctl(semId, semNum, SETVAL, arg);
+}
+
+/* Reserve semaphore (blocking), return 0 on success, or -1 with 'errno'
+   set to EINTR if operation was interrupted by a signal handler */
+
+int                     /* Reserve semaphore - decrement it by 1 */
+reserveSem(int semId, int semNum)
+{
+    struct sembuf sops;
+
+    sops.sem_num = semNum;
+    sops.sem_op = -1;
+    sops.sem_flg = bsUseSemUndo ? SEM_UNDO : 0;
+
+    while (semop(semId, &sops, 1) == -1)
+        if (errno != EINTR || !bsRetryOnEintr)
+            return -1;
+
+    return 0;
+}
+
+int                     /* Release semaphore - increment it by 1 */
+releaseSem(int semId, int semNum)
+{
+    struct sembuf sops;
+
+    sops.sem_num = semNum;
+    sops.sem_op = 1;
+    sops.sem_flg = bsUseSemUndo ? SEM_UNDO : 0;
+
+    return semop(semId, &sops, 1);
+}
diff --git a/svsem/binary_sems.h b/svsem/binary_sems.h
new file mode 100644 (file)
index 0000000..ca75784
--- /dev/null
@@ -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 (file)
index 0000000..226147b
--- /dev/null
@@ -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 <sys/types.h>
+#include <sys/sem.h>
+#include "semun.h"              /* Definition of semun union */
+#include "event_flags.h"
+#include "tlpi_hdr.h"
+
+/* Wait for the specified flag to become "set" (0) */
+
+int
+waitForEventFlag(int semId, int semNum)
+{
+    struct sembuf sops;
+
+    sops.sem_num = semNum;
+    sops.sem_op = 0;                    /* Wait for semaphore to equal 0 */
+    sops.sem_flg = 0;
+
+    /* Waiting for a semaphore to become zero may block, so we
+       program to retry if interrupted by a signal handler */
+
+    while (semop(semId, &sops, 1) == -1)
+        if (errno != EINTR)
+            return -1;
+    return 0;
+}
+
+/* "Clear" the event flag (give it the value 1) */
+
+int
+clearEventFlag(int semId, int semNum)
+{
+    union semun arg;
+
+    arg.val = 1;
+    return semctl(semId, semNum, SETVAL, arg);
+}
+
+/* "Set" the event flag (give it the value 0) */
+
+int
+setEventFlag(int semId, int semNum)
+{
+    union semun arg;
+
+    arg.val = 0;
+    return semctl(semId, semNum, SETVAL, arg);
+}
+
+/* Get current state of event flag */
+
+int
+getFlagState(int semId, int semNum, Boolean *isSet)
+{
+    int sem_val;
+    union semun dummy;
+
+    sem_val = semctl(semId, semNum, GETVAL, dummy);
+    if (sem_val == -1)
+        return -1;
+
+    *isSet = (sem_val == 0) ? TRUE : FALSE;
+    return 0;
+}
diff --git a/svsem/event_flags.h b/svsem/event_flags.h
new file mode 100644 (file)
index 0000000..14ac62c
--- /dev/null
@@ -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 (file)
index 0000000..5562576
--- /dev/null
@@ -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 <sys/types.h>          /* For portability */
+#include <sys/sem.h>
+
+#if ! defined(__FreeBSD__) && ! defined(__OpenBSD__) && \
+                ! defined(__sgi) && ! defined(__APPLE__)
+                /* Some implementations already declare this union */
+
+union semun {                   /* Used in calls to semctl() */
+    int                 val;
+    struct semid_ds *   buf;
+    unsigned short *    array;
+#if defined(__linux__)
+    struct seminfo *    __buf;
+#endif
+};
+
+#endif
+
+#endif
diff --git a/svsem/svsem_bad_init.c b/svsem/svsem_bad_init.c
new file mode 100644 (file)
index 0000000..de41b81
--- /dev/null
@@ -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 <sys/types.h>
+#include <sys/sem.h>
+#include <sys/stat.h>
+#include "semun.h"                      /* Definition of semun union */
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    int semid, key, perms;
+    struct sembuf sops[2];
+
+    key = 12345;
+    perms = S_IRUSR | S_IWUSR;
+    semid = semget(key, 1, IPC_CREAT | IPC_EXCL | perms);
+
+    if (semid != -1) {                  /* Successfully created the semaphore */
+        union semun arg;
+
+        /* XXXX */
+
+        arg.val = 0;                    /* So initialize it */
+        if (semctl(semid, 0, SETVAL, arg) == -1)
+            errExit("semctl");
+
+    } else {                            /* We didn't create semaphore set */
+        if (errno != EEXIST) {          /* Unexpected error from semget() */
+            errExit("semget 1");
+        } else {                        /* Someone else already created it */
+            semid = semget(key, 1, perms);      /* So just get ID */
+            if (semid == -1)
+                errExit("semget 2");
+        }
+    }
+
+    /* Now perform some operation on the semaphore */
+
+    sops[0].sem_op = 1;         /* Add 1 */
+    sops[0].sem_num = 0;        /* ... to semaphore 0 */
+    sops[0].sem_flg = 0;
+    if (semop(semid, sops, 1) == -1)
+        errExit("semop");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/svsem/svsem_create.c b/svsem/svsem_create.c
new file mode 100644 (file)
index 0000000..c421e48
--- /dev/null
@@ -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 <sys/types.h>
+#include <sys/ipc.h>
+#include <sys/sem.h>
+#include <sys/stat.h>
+#include "tlpi_hdr.h"
+
+static void
+usageError(const char *progName, const char *msg)
+{
+    if (msg != NULL)
+        fprintf(stderr, "%s", msg);
+    fprintf(stderr, "Usage: %s [-cx] {-f pathname | -k key | -p} "
+                            "num-sems [octal-perms]\n", progName);
+    fprintf(stderr, "    -c           Use IPC_CREAT flag\n");
+    fprintf(stderr, "    -x           Use IPC_EXCL flag\n");
+    fprintf(stderr, "    -f pathname  Generate key using ftok()\n");
+    fprintf(stderr, "    -k key       Use 'key' as key\n");
+    fprintf(stderr, "    -p           Use IPC_PRIVATE key\n");
+    exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+    int numKeyFlags;            /* Counts -f, -k, and -p options */
+    int flags, semid, numSems, opt;
+    unsigned int perms;
+    long lkey;
+    key_t key;
+
+    /* Parse command-line options and arguments */
+
+    numKeyFlags = 0;
+    flags = 0;
+
+    while ((opt = getopt(argc, argv, "cf:k:px")) != -1) {
+        switch (opt) {
+        case 'c':
+            flags |= IPC_CREAT;
+            break;
+
+        case 'f':               /* -f pathname */
+            key = ftok(optarg, 1);
+            if (key == -1)
+                errExit("ftok");
+            numKeyFlags++;
+            break;
+
+        case 'k':               /* -k key (octal, decimal or hexadecimal) */
+            if (sscanf(optarg, "%li", &lkey) != 1)
+                cmdLineErr("-k option requires a numeric argument\n");
+            key = lkey;
+            numKeyFlags++;
+            break;
+
+        case 'p':
+            key = IPC_PRIVATE;
+            numKeyFlags++;
+            break;
+
+        case 'x':
+            flags |= IPC_EXCL;
+            break;
+
+        default:
+            usageError(argv[0], NULL);
+        }
+    }
+
+    if (numKeyFlags != 1)
+        usageError(argv[0], "Exactly one of the options -f, -k, "
+                            "or -p must be supplied\n");
+
+    if (optind >= argc)
+        usageError(argv[0], "Must specify number of semaphores\n");
+
+    numSems = getInt(argv[optind], 0, "num-sems");
+
+    perms = (argc <= optind + 1) ? (S_IRUSR | S_IWUSR) :
+                getInt(argv[optind + 1], GN_BASE_8, "octal-perms");
+
+    semid = semget(key, numSems, flags | perms);
+    if (semid == -1)
+        errExit("semget");
+
+    printf("%d\n", semid);      /* On success, display semaphore set id */
+    exit(EXIT_SUCCESS);
+}
diff --git a/svsem/svsem_demo.c b/svsem/svsem_demo.c
new file mode 100644 (file)
index 0000000..cff0fdb
--- /dev/null
@@ -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 <sys/types.h>
+#include <sys/sem.h>
+#include <sys/stat.h>
+#include "curr_time.h"                  /* Declaration of currTime() */
+#include "semun.h"                      /* Definition of semun union */
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    int semid;
+
+    if (argc < 2 || argc > 3 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s init-value\n"
+                 "   or: %s semid operation\n", argv[0], argv[0]);
+
+    if (argc == 2) {            /* Create and initialize semaphore */
+        union semun arg;
+
+        semid = semget(IPC_PRIVATE, 1, S_IRUSR | S_IWUSR);
+        if (semid == -1)
+            errExit("semid");
+
+        arg.val = getInt(argv[1], 0, "init-value");
+        if (semctl(semid, /* semnum= */ 0, SETVAL, arg) == -1)
+            errExit("semctl");
+
+        printf("Semaphore ID = %d\n", semid);
+
+    } else {                    /* Perform an operation on first semaphore */
+
+        struct sembuf sop;              /* Structure defining operation */
+
+        semid = getInt(argv[1], 0, "semid");
+
+        sop.sem_num = 0;                /* Specifies first semaphore in set */
+        sop.sem_op = getInt(argv[2], 0, "operation");
+                                        /* Add, subtract, or wait for 0 */
+        sop.sem_flg = 0;                /* No special options for operation */
+
+        printf("%ld: about to semop at  %s\n", (long) getpid(), currTime("%T"));
+        if (semop(semid, &sop, 1) == -1)
+            errExit("semop");
+
+        printf("%ld: semop completed at %s\n", (long) getpid(), currTime("%T"));
+    }
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/svsem/svsem_good_init.c b/svsem/svsem_good_init.c
new file mode 100644 (file)
index 0000000..ee38511
--- /dev/null
@@ -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 <sys/types.h>
+#include <sys/sem.h>
+#include <sys/stat.h>
+#include "semun.h"                      /* Definition of semun union */
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    int semid, key, perms;
+    struct sembuf sops[2];
+
+    if (argc != 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s sem-op\n", argv[0]);
+
+    key = 12345;
+    perms = S_IRUSR | S_IWUSR;
+
+    semid = semget(key, 1, IPC_CREAT | IPC_EXCL | perms);
+
+    if (semid != -1) {                  /* Successfully created the semaphore */
+        union semun arg;
+        struct sembuf sop;
+
+        sleep(5);
+        printf("%ld: created semaphore\n", (long) getpid());
+
+        arg.val = 0;                    /* So initialize it to 0 */
+        if (semctl(semid, 0, SETVAL, arg) == -1)
+            errExit("semctl 1");
+        printf("%ld: initialized semaphore\n", (long) getpid());
+
+        /* Perform a "no-op" semaphore operation - changes sem_otime
+           so other processes can see we've initialized the set. */
+
+        sop.sem_num = 0;                /* Operate on semaphore 0 */
+        sop.sem_op = 0;                 /* Wait for value to equal 0 */
+        sop.sem_flg = 0;
+        if (semop(semid, &sop, 1) == -1)
+            errExit("semop");
+        printf("%ld: completed dummy semop()\n", (long) getpid());
+
+    } else {                            /* We didn't create the semaphore set */
+
+        if (errno != EEXIST) {          /* Unexpected error from semget() */
+            errExit("semget 1");
+
+        } else {                        /* Someone else already created it */
+            const int MAX_TRIES = 10;
+            int j;
+            union semun arg;
+            struct semid_ds ds;
+
+            semid = semget(key, 1, perms);      /* So just get ID */
+            if (semid == -1)
+                errExit("semget 2");
+
+            printf("%ld: got semaphore key\n", (long) getpid());
+            /* Wait until another process has called semop() */
+
+            arg.buf = &ds;
+            for (j = 0; j < MAX_TRIES; j++) {
+                printf("Try %d\n", j);
+                if (semctl(semid, 0, IPC_STAT, arg) == -1)
+                    errExit("semctl 2");
+
+                if (ds.sem_otime != 0)          /* Semop() performed? */
+                    break;                      /* Yes, quit loop */
+                sleep(1);                       /* If not, wait and retry */
+            }
+
+            if (ds.sem_otime == 0)              /* Loop ran to completion! */
+                fatal("Existing semaphore not initialized");
+        }
+    }
+
+    /* Now perform some operation on the semaphore */
+
+    sops[0].sem_num = 0;                        /* Operate on semaphore 0... */
+    sops[0].sem_op = getInt(argv[1], 0, "sem-op");
+    sops[0].sem_flg = 0;
+    if (semop(semid, sops, 1) == -1)
+        errExit("semop");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/svsem/svsem_info.c b/svsem/svsem_info.c
new file mode 100644 (file)
index 0000000..4744d6e
--- /dev/null
@@ -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 <sys/sem.h>
+#include "semun.h"              /* Definition of semun union */
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    struct seminfo info;
+    union semun arg;
+    int s;
+
+    arg.__buf = &info;
+
+    s = semctl(0, 0, SEM_INFO, arg);
+    if (s == -1)
+        errExit("semctl");
+
+    printf("Maximum ID index = %d\n", s);
+    printf("sets in_use      = %ld\n", (long) info.semusz);
+    printf("used_sems        = %ld\n", (long) info.semaem);
+    exit(EXIT_SUCCESS);
+}
diff --git a/svsem/svsem_mon.c b/svsem/svsem_mon.c
new file mode 100644 (file)
index 0000000..0dc2c9e
--- /dev/null
@@ -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 <sys/types.h>
+#include <sys/sem.h>
+#include <time.h>
+#include "semun.h"                      /* Definition of semun union */
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    struct semid_ds ds;
+    union semun arg, dummy;             /* Fourth argument for semctl() */
+    int semid, j;
+
+    if (argc != 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s semid\n", argv[0]);
+
+    semid = getInt(argv[1], 0, "semid");
+
+    arg.buf = &ds;
+    if (semctl(semid, 0, IPC_STAT, arg) == -1)
+        errExit("semctl");
+
+    printf("Semaphore changed: %s", ctime(&ds.sem_ctime));
+    printf("Last semop():      %s", ctime(&ds.sem_otime));
+
+    /* Display per-semaphore information */
+
+    arg.array = calloc(ds.sem_nsems, sizeof(arg.array[0]));
+    if (arg.array == NULL)
+        errExit("calloc");
+    if (semctl(semid, 0, GETALL, arg) == -1)
+        errExit("semctl-GETALL");
+
+    printf("Sem #  Value  SEMPID  SEMNCNT  SEMZCNT\n");
+
+    for (j = 0; j < ds.sem_nsems; j++)
+        printf("%3d   %5d   %5d  %5d    %5d\n", j, arg.array[j],
+                semctl(semid, j, GETPID, dummy),
+                semctl(semid, j, GETNCNT, dummy),
+                semctl(semid, j, GETZCNT, dummy));
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/svsem/svsem_op.c b/svsem/svsem_op.c
new file mode 100644 (file)
index 0000000..00c2f5f
--- /dev/null
@@ -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 <sys/types.h>
+#include <sys/sem.h>
+#include <ctype.h>
+#include "curr_time.h"          /* Declaration of currTime() */
+#include "tlpi_hdr.h"
+
+#define MAX_SEMOPS 1000         /* Maximum operations that we permit for
+                                   a single semop() */
+
+static void
+usageError(const char *progName)
+{
+    fprintf(stderr, "Usage: %s semid op[,op...] ...\n\n", progName);
+    fprintf(stderr, "'op' is either: <sem#>{+|-}<value>[n][u]\n");
+    fprintf(stderr, "            or: <sem#>=0[n]\n");
+    fprintf(stderr, "       \"n\" means include IPC_NOWAIT in 'op'\n");
+    fprintf(stderr, "       \"u\" means include SEM_UNDO in 'op'\n\n");
+    fprintf(stderr, "The operations in each argument are "
+                    "performed in a single semop() call\n\n");
+    fprintf(stderr, "e.g.: %s 12345 0+1,1-2un\n", progName);
+    fprintf(stderr, "      %s 12345 0=0n 1+1,2-1u 1=0\n", progName);
+    exit(EXIT_FAILURE);
+}
+
+/* Parse comma-delimited operations in 'arg', returning them in the
+   array 'sops'. Return number of operations as function result. */
+
+static int
+parseOps(char *arg, struct sembuf sops[])
+{
+    char *comma, *sign, *remaining, *flags;
+    int numOps;                 /* Number of operations in 'arg' */
+
+    for (numOps = 0, remaining = arg; ; numOps++) {
+        if (numOps >= MAX_SEMOPS)
+            cmdLineErr("Too many operations (maximum=%d): \"%s\"\n",
+                        MAX_SEMOPS, arg);
+
+        if (*remaining == '\0')
+            fatal("Trailing comma or empty argument: \"%s\"", arg);
+        if (!isdigit((unsigned char) *remaining))
+            cmdLineErr("Expected initial digit: \"%s\"\n", arg);
+
+        sops[numOps].sem_num = strtol(remaining, &sign, 10);
+
+        if (*sign == '\0' || strchr("+-=", *sign) == NULL)
+            cmdLineErr("Expected '+', '-', or '=' in \"%s\"\n", arg);
+        if (!isdigit((unsigned char) *(sign + 1)))
+            cmdLineErr("Expected digit after '%c' in \"%s\"\n", *sign, arg);
+
+        sops[numOps].sem_op = strtol(sign + 1, &flags, 10);
+
+        if (*sign == '-')                       /* Reverse sign of operation */
+            sops[numOps].sem_op = - sops[numOps].sem_op;
+        else if (*sign == '=')                  /* Should be '=0' */
+            if (sops[numOps].sem_op != 0)
+                cmdLineErr("Expected \"=0\" in \"%s\"\n", arg);
+
+        sops[numOps].sem_flg = 0;
+        for (;; flags++) {
+            if (*flags == 'n')
+                sops[numOps].sem_flg |= IPC_NOWAIT;
+            else if (*flags == 'u')
+                sops[numOps].sem_flg |= SEM_UNDO;
+            else
+                break;
+        }
+
+        if (*flags != ',' && *flags != '\0')
+            cmdLineErr("Bad trailing character (%c) in \"%s\"\n", *flags, arg);
+
+        comma = strchr(remaining, ',');
+        if (comma == NULL)
+            break;                              /* No comma --> no more ops */
+        else
+            remaining = comma + 1;
+    }
+
+    return numOps + 1;
+}
+
+int
+main(int argc, char *argv[])
+{
+    struct sembuf sops[MAX_SEMOPS];
+    int ind, nsops;
+
+    if (argc < 2 || strcmp(argv[1], "--help") == 0)
+        usageError(argv[0]);
+
+    for (ind = 2; argv[ind] != NULL; ind++) {
+        nsops = parseOps(argv[ind], sops);
+
+        printf("%5ld, %s: about to semop()  [%s]\n", (long) getpid(),
+                currTime("%T"), argv[ind]);
+
+        if (semop(getInt(argv[1], 0, "semid"), sops, nsops) == -1)
+            errExit("semop (PID=%ld)", (long) getpid());
+
+        printf("%5ld, %s: semop() completed [%s]\n", (long) getpid(),
+                currTime("%T"), argv[ind]);
+    }
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/svsem/svsem_rm.c b/svsem/svsem_rm.c
new file mode 100644 (file)
index 0000000..51f03c6
--- /dev/null
@@ -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 <sys/types.h>
+#include <sys/sem.h>
+#include "semun.h"              /* Definition of semun union */
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    int j;
+    union semun dummy;
+
+    if (argc > 1 && strcmp(argv[1], "--help") == 0)
+        usageErr("%s [semid...]\n", argv[0]);
+
+    for (j = 1; j < argc; j++)
+        if (semctl(getInt(argv[j], 0, "semid"), 0, IPC_RMID, dummy) == -1)
+            errExit("semctl %s", argv[j]);
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/svsem/svsem_setall.c b/svsem/svsem_setall.c
new file mode 100644 (file)
index 0000000..252d528
--- /dev/null
@@ -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 <sys/types.h>
+#include <sys/sem.h>
+#include "semun.h"                      /* Definition of semun union */
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    struct semid_ds ds;
+    union semun arg;                    /* Fourth argument for semctl() */
+    int j, semid;
+
+    if (argc < 3 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s semid val...\n", argv[0]);
+
+    semid = getInt(argv[1], 0, "semid");
+
+    /* Obtain size of semaphore set */
+
+    arg.buf = &ds;
+    if (semctl(semid, 0, IPC_STAT, arg) == -1)
+        errExit("semctl");
+
+    /* The number of values supplied on the command line must match the
+       number of semaphores in the set */
+
+    if (ds.sem_nsems != argc - 2)
+        cmdLineErr("Set contains %ld semaphores, but %d values were supplied\n",
+                (long) ds.sem_nsems, argc - 2);
+
+    /* Set up array of values; perform semaphore initialization */
+
+    arg.array = calloc(ds.sem_nsems, sizeof(arg.array[0]));
+    if (arg.array == NULL)
+        errExit("calloc");
+
+    for (j = 2; j < argc; j++)
+        arg.array[j - 2] = getInt(argv[j], 0, "val");
+
+    if (semctl(semid, 0, SETALL, arg) == -1)
+        errExit("semctl-SETALL");
+    printf("Semaphore values changed (PID=%ld)\n", (long) getpid());
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/svshm/Makefile b/svshm/Makefile
new file mode 100644 (file)
index 0000000..9951b46
--- /dev/null
@@ -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 (file)
index 0000000..87444a3
--- /dev/null
@@ -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 <sys/types.h>
+#include <sys/shm.h>
+#include "tlpi_hdr.h"
+
+static void
+usageError(char *progName)
+{
+    fprintf(stderr, "Usage: %s [shmid:address[rR]]...\n", progName);
+    fprintf(stderr, "            r=SHM_RND; R=SHM_RDONLY\n");
+    exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+    void *addr;
+    char *retAddr, *p;
+    int j, flags, shmid;
+
+    printf("SHMLBA = %ld (%#lx), PID = %ld\n",
+            (long) SHMLBA, (unsigned long) SHMLBA, (long) getpid());
+
+    for (j = 1; j < argc; j++) {
+        shmid = strtol(argv[j], &p, 0);
+        if (*p != ':')
+            usageError(argv[0]);
+
+        addr = (void *) strtol(p + 1, NULL, 0);
+        flags = (strchr(p + 1, 'r') != NULL) ? SHM_RND : 0;
+        if (strchr(p + 1, 'R') != NULL)
+            flags |= SHM_RDONLY;
+
+        retAddr = shmat(shmid, addr, flags);
+        if (retAddr == (void *) -1)
+            errExit("shmat: %s", argv[j]);
+
+        printf("%d: %s ==> %p\n", j, argv[j], retAddr);
+    }
+
+    printf("Sleeping 5 seconds\n");
+    sleep(5);
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/svshm/svshm_create.c b/svshm/svshm_create.c
new file mode 100644 (file)
index 0000000..cd928bf
--- /dev/null
@@ -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 <sys/types.h>
+#include <sys/ipc.h>
+#include <sys/shm.h>
+#include <sys/stat.h>
+#include "tlpi_hdr.h"
+
+static void
+usageError(const char *progName, const char *msg)
+{
+    if (msg != NULL)
+        fprintf(stderr, "%s", msg);
+    fprintf(stderr, "Usage: %s [-cx] {-f pathname | -k key | -p} "
+                            "seg-size [octal-perms]\n", progName);
+    fprintf(stderr, "    -c           Use IPC_CREAT flag\n");
+    fprintf(stderr, "    -x           Use IPC_EXCL flag\n");
+    fprintf(stderr, "    -f pathname  Generate key using ftok()\n");
+    fprintf(stderr, "    -k key       Use 'key' as key\n");
+    fprintf(stderr, "    -p           Use IPC_PRIVATE key\n");
+    exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+    int numKeyFlags;            /* Counts -f, -k, and -p options */
+    int flags, shmid, segSize;
+    unsigned int perms;
+    long lkey;
+    key_t key;
+    int opt;                    /* Option character from getopt() */
+
+    /* Parse command-line options and arguments */
+
+    numKeyFlags = 0;
+    flags = 0;
+
+    while ((opt = getopt(argc, argv, "cf:k:px")) != -1) {
+        switch (opt) {
+        case 'c':
+            flags |= IPC_CREAT;
+            break;
+
+        case 'f':               /* -f pathname */
+            key = ftok(optarg, 1);
+            if (key == -1)
+                errExit("ftok");
+            numKeyFlags++;
+            break;
+
+        case 'k':               /* -k key (octal, decimal or hexadecimal) */
+            if (sscanf(optarg, "%li", &lkey) != 1)
+                cmdLineErr("-k option requires a numeric argument\n");
+            key = lkey;
+            numKeyFlags++;
+            break;
+
+        case 'p':
+            key = IPC_PRIVATE;
+            numKeyFlags++;
+            break;
+
+        case 'x':
+            flags |= IPC_EXCL;
+            break;
+
+        default:
+            usageError(argv[0], NULL);
+        }
+    }
+
+    if (numKeyFlags != 1)
+        usageError(argv[0], "Exactly one of the options -f, -k, "
+                            "or -p must be supplied\n");
+
+    if (optind >= argc)
+        usageError(argv[0], "Size of segment must be specified\n");
+
+    segSize = getLong(argv[optind], GN_ANY_BASE, "seg-size");
+
+    perms = (argc <= optind + 1) ? (S_IRUSR | S_IWUSR) :
+                getInt(argv[optind + 1], GN_BASE_8, "octal-perms");
+
+    shmid = shmget(key, segSize, flags | perms);
+    if (shmid == -1)
+        errExit("shmget");
+
+    printf("%d\n", shmid);      /* On success, display shared mem. id */
+    exit(EXIT_SUCCESS);
+}
diff --git a/svshm/svshm_info.c b/svshm/svshm_info.c
new file mode 100644 (file)
index 0000000..a461af3
--- /dev/null
@@ -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 <sys/shm.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    struct shm_info info;
+    int s;
+
+    s = shmctl(0, SHM_INFO, (struct shmid_ds *) &info);
+    if (s == -1)
+        errExit("shmctl");
+
+    printf("Maximum ID index = %d\n", s);
+    printf("shm_tot          = %ld\n", (long) info.shm_tot);
+    printf("shm_rss          = %ld\n", (long) info.shm_rss);
+    printf("shm_swp          = %ld\n", (long) info.shm_swp);
+    printf("swap_attempts    = %ld\n", (long) info.swap_attempts);
+    printf("swap_successes   = %ld\n", (long) info.swap_successes);
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/svshm/svshm_lock.c b/svshm/svshm_lock.c
new file mode 100644 (file)
index 0000000..067eadd
--- /dev/null
@@ -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 <sys/types.h>
+#include <sys/shm.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    int j;
+
+    for (j = 1; j < argc; j++)
+        if (shmctl(getInt(argv[j], 0, "shmid"), SHM_LOCK, NULL) == -1)
+            errExit("shmctl");
+
+    sleep(5);
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/svshm/svshm_mon.c b/svshm/svshm_mon.c
new file mode 100644 (file)
index 0000000..08ec311
--- /dev/null
@@ -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 <sys/types.h>
+#include <sys/shm.h>
+#include <time.h>
+#include "tlpi_hdr.h"
+
+static void
+printShmDS(const struct shmid_ds *ds)
+{
+    printf("Size:                      %ld\n", (long) ds->shm_segsz);
+    printf("# of attached processes:   %ld\n", (long) ds->shm_nattch);
+
+    printf("Mode:                      %lo",
+            (unsigned long) ds->shm_perm.mode);
+#ifdef SHM_DEST
+    printf("%s", (ds->shm_perm.mode & SHM_DEST) ? " [DEST]" : "");
+#endif
+#ifdef SHM_LOCKED
+    printf("%s", (ds->shm_perm.mode & SHM_LOCKED) ? " [LOCKED]" : "");
+#endif
+    printf("\n");
+
+    printf("Last shmat():              %s", ctime(&ds->shm_atime));
+    printf("Last shmdt():              %s", ctime(&ds->shm_dtime));
+    printf("Last change:               %s", ctime(&ds->shm_ctime));
+
+    printf("Creator PID:               %ld\n", (long) ds->shm_cpid);
+    printf("PID of last attach/detach: %ld\n", (long) ds->shm_lpid);
+}
+
+int
+main(int argc, char *argv[])
+{
+    struct shmid_ds ds;
+
+    if (argc != 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s shmid\n", argv[0]);
+
+    if (shmctl(getInt(argv[1], 0, "shmid"), IPC_STAT, &ds) == -1)
+        errExit("shmctl");
+
+    printShmDS(&ds);
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/svshm/svshm_rm.c b/svshm/svshm_rm.c
new file mode 100644 (file)
index 0000000..de71612
--- /dev/null
@@ -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 <sys/types.h>
+#include <sys/shm.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    int j;
+
+    if (argc > 1 && strcmp(argv[1], "--help") == 0)
+        usageErr("%s [shmid...]\n", argv[0]);
+
+    for (j = 1; j < argc; j++)
+        if (shmctl(getInt(argv[j], 0, "shmid"), IPC_RMID, NULL) == -1)
+            errExit("shmctl %s", argv[j]);
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/svshm/svshm_unlock.c b/svshm/svshm_unlock.c
new file mode 100644 (file)
index 0000000..46be092
--- /dev/null
@@ -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 <sys/types.h>
+#include <sys/shm.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    int j;
+
+    for (j = 1; j < argc; j++)
+        if (shmctl(getInt(argv[j], 0, "shmid"), SHM_UNLOCK, NULL) == -1)
+            errExit("shmctl");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/svshm/svshm_xfr.h b/svshm/svshm_xfr.h
new file mode 100644 (file)
index 0000000..3b2279c
--- /dev/null
@@ -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 <sys/types.h>
+#include <sys/stat.h>
+#include <sys/sem.h>
+#include <sys/shm.h>
+#include "binary_sems.h"        /* Declares our binary semaphore functions */
+#include "tlpi_hdr.h"
+
+/* Hard-coded keys for IPC objects */
+
+#define SHM_KEY 0x1234          /* Key for shared memory segment */
+#define SEM_KEY 0x5678          /* Key for semaphore set */
+
+#define OBJ_PERMS (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)
+                                /* Permissions for our IPC objects */
+
+/* Two semaphores are used to ensure exclusive, alternating access
+   to the shared memory segment */
+
+#define WRITE_SEM 0             /* Writer has access to shared memory */
+#define READ_SEM 1              /* Reader has access to shared memory */
+
+#ifndef BUF_SIZE                /* Allow "cc -D" to override definition */
+#define BUF_SIZE 1024           /* Size of transfer buffer */
+#endif
+
+struct shmseg {                 /* Defines structure of shared memory segment */
+    int cnt;                    /* Number of bytes used in 'buf' */
+    char buf[BUF_SIZE];         /* Data being transferred */
+};
diff --git a/svshm/svshm_xfr_reader.c b/svshm/svshm_xfr_reader.c
new file mode 100644 (file)
index 0000000..a700187
--- /dev/null
@@ -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 (file)
index 0000000..1e5cb34
--- /dev/null
@@ -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 (file)
index 0000000..e7744da
--- /dev/null
@@ -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 (file)
index 0000000..bd34249
--- /dev/null
@@ -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 <fcntl.h>
+#include "tlpi_hdr.h"
+
+#define MAX_LINE 100
+
+int
+main(int argc, char *argv[])
+{
+    int fd;
+    char line[MAX_LINE];
+    ssize_t n;
+
+    fd = open("/proc/sys/kernel/pid_max", (argc > 1) ? O_RDWR : O_RDONLY);
+    if (fd == -1)
+        errExit("open");
+
+    n = read(fd, line, MAX_LINE);
+    if (n == -1)
+        errExit("read");
+
+    if (argc > 1)
+        printf("Old value: ");
+    printf("%.*s", (int) n, line);
+
+    if (argc > 1) {
+        if (lseek(fd, 0, SEEK_SET) == -1)
+            errExit("lseek");
+
+        if (write(fd, argv[1], strlen(argv[1])) != strlen(argv[1]))
+            fatal("write() failed");
+
+        system("echo /proc/sys/kernel/pid_max now contains "
+               "`cat /proc/sys/kernel/pid_max`");
+    }
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/sysinfo/procfs_user_exe.c b/sysinfo/procfs_user_exe.c
new file mode 100644 (file)
index 0000000..913258d
--- /dev/null
@@ -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 <limits.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <ctype.h>
+#include "ugid_functions.h"
+#include "tlpi_hdr.h"
+
+#define MAX_LINE 1000
+
+int
+main(int argc, char *argv[])
+{
+    DIR *dirp;
+    struct dirent *dp;
+    char path[PATH_MAX];
+    char line[MAX_LINE], cmd[MAX_LINE];
+    FILE *fp;
+    char *p;
+    uid_t uid, checkedUid;
+    Boolean gotName, gotUid;
+
+    if (argc < 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s username\n", argv[0]);
+
+    checkedUid = userIdFromName(argv[1]);
+    if (checkedUid == -1)
+        cmdLineErr("Bad username: %s\n", argv[1]);
+
+    dirp = opendir("/proc");
+    if (dirp == NULL)
+        errExit("opendir");
+
+    /* Scan entries under /proc directory */
+
+    for (;;) {
+        errno = 0;              /* To distinguish error from end-of-directory */
+        dp = readdir(dirp);
+        if (dp == NULL) {
+            if (errno != 0)
+                errExit("readdir");
+            else
+                break;
+        }
+
+        /* Since we are looking for /proc/PID directories, skip entries
+           that are not directories, or don't begin with a digit. */
+
+        if (dp->d_type != DT_DIR || !isdigit((unsigned char) dp->d_name[0]))
+            continue;
+
+        snprintf(path, PATH_MAX, "/proc/%s/status", dp->d_name);
+
+        fp = fopen(path, "r");
+        if (fp == NULL)
+            continue;           /* Ignore errors: fopen() might fail if
+                                   process has just terminated */
+
+        gotName = FALSE;
+        gotUid = FALSE;
+        while (!gotName || !gotUid) {
+            if (fgets(line, MAX_LINE, fp) == NULL)
+                break;
+
+            /* The "Name:" line contains the name of the command that
+               this process is running */
+
+            if (strncmp(line, "Name:", 5) == 0) {
+                for (p = line + 5; *p != '\0' && isspace((unsigned char) *p); )
+                    p++;
+                strncpy(cmd, p, MAX_LINE - 1);
+                cmd[MAX_LINE -1] = '\0';        /* Ensure null-terminated */
+
+                gotName = TRUE;
+            }
+
+            /* The "Uid:" line contains the real, effective, saved set-,
+               and file-system user IDs */
+
+            if (strncmp(line, "Uid:", 4) == 0) {
+                uid = strtol(line + 4, NULL, 10);
+                gotUid = TRUE;
+            }
+        }
+
+        fclose(fp);
+
+        /* If we found a username and a UID, and the UID matches,
+           then display the PID and command name */
+
+        if (gotName && gotUid && uid == checkedUid)
+            printf("%5s %s", dp->d_name, cmd);
+    }
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/sysinfo/t_uname.c b/sysinfo/t_uname.c
new file mode 100644 (file)
index 0000000..c6946d1
--- /dev/null
@@ -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 <sys/utsname.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    struct utsname uts;
+
+    if (uname(&uts) == -1)
+        errExit("uname");
+
+    printf("Node name:   %s\n", uts.nodename);
+    printf("System name: %s\n", uts.sysname);
+    printf("Release:     %s\n", uts.release);
+    printf("Version:     %s\n", uts.version);
+    printf("Machine:     %s\n", uts.machine);
+#ifdef _GNU_SOURCE
+    printf("Domain name: %s\n", uts.domainname);
+#endif
+    exit(EXIT_SUCCESS);
+}
diff --git a/syslim/Makefile b/syslim/Makefile
new file mode 100644 (file)
index 0000000..31b4fb6
--- /dev/null
@@ -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 (file)
index 0000000..6f1432d
--- /dev/null
@@ -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 (file)
index 0000000..3de13d4
--- /dev/null
@@ -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 (file)
index 0000000..3b15009
--- /dev/null
@@ -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 (file)
index 0000000..486a5f5
--- /dev/null
@@ -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 <pthread.h>
+#include "tlpi_hdr.h"
+
+static void *
+threadFunc(void *x)
+{
+    return x;
+}
+
+int
+main(int argc, char *argv[])
+{
+    pthread_t thr;
+    pthread_attr_t attr;
+    int s;
+
+    s = pthread_attr_init(&attr);       /* Assigns default values */
+    if (s != 0)
+        errExitEN(s, "pthread_attr_init");
+
+    s = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+    if (s != 0)
+        errExitEN(s, "pthread_attr_setdetachstate");
+
+    s = pthread_create(&thr, &attr, threadFunc, (void *) 1);
+    if (s != 0)
+        errExitEN(s, "pthread_create");
+
+    s = pthread_attr_destroy(&attr);    /* No longer needed */
+    if (s != 0)
+        errExitEN(s, "pthread_attr_destroy");
+
+    s = pthread_join(thr, NULL);
+    if (s != 0)
+        errExitEN(s, "pthread_join failed as expected");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/threads/one_time_init.c b/threads/one_time_init.c
new file mode 100644 (file)
index 0000000..fe25988
--- /dev/null
@@ -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 <pthread.h>
+#include "tlpi_hdr.h"
+
+struct once_struct {            /* Our equivalent of pthread_once_t */
+    pthread_mutex_t mtx;
+    int called;
+};
+
+#define ONCE_INITIALISER { PTHREAD_MUTEX_INITIALIZER, 0 }
+
+struct once_struct once = ONCE_INITIALISER;
+
+static int
+one_time_init(struct once_struct *once_control, void (*init)(void))
+{
+    int s;
+
+    s = pthread_mutex_lock(&(once_control->mtx));
+    if (s == -1)
+        errExitEN(s, "pthread_mutex_lock");
+
+    if (!once_control->called) {
+        (*init)();
+        once_control->called = 1;
+    }
+
+    s = pthread_mutex_unlock(&(once_control->mtx));
+    if (s == -1)
+        errExitEN(s, "pthread_mutex_unlock");
+
+    return 0;
+}
+
+/* Remaining code is for testing one_time_init() */
+
+static void
+init_func()
+{
+    /* We should see this message only once, no matter how many
+       times one_time_init() is called */
+
+    printf("Called init_func()\n");
+}
+
+static void *
+threadFunc(void *arg)
+{
+    /* The following allows us to verify that even if a single thread calls
+       one_time_init() multiple times, init_func() is called only once */
+
+    one_time_init(&once, init_func);
+    one_time_init(&once, init_func);
+    return NULL;
+}
+
+int
+main(int argc, char *argv[])
+{
+    pthread_t t1, t2;
+    int s;
+
+    /* Create two threads, both of which will call one_time_init() */
+
+    s = pthread_create(&t1, NULL, threadFunc, (void *) 1);
+    if (s != 0)
+        errExitEN(s, "pthread_create");
+
+    s = pthread_create(&t2, NULL, threadFunc, (void *) 2);
+    if (s != 0)
+        errExitEN(s, "pthread_create");
+
+    s = pthread_join(t1, NULL);
+    if (s != 0)
+        errExitEN(s, "pthread_join");
+    printf("First thread returned\n");
+
+    s = pthread_join(t2, NULL);
+    if (s != 0)
+        errExitEN(s, "pthread_join");
+    printf("Second thread returned\n");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/threads/prod_condvar.c b/threads/prod_condvar.c
new file mode 100644 (file)
index 0000000..bacbddb
--- /dev/null
@@ -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 <time.h>
+#include <pthread.h>
+#include "tlpi_hdr.h"
+
+static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
+
+static int avail = 0;
+
+static void *
+threadFunc(void *arg)
+{
+    int cnt = atoi((char *) arg);
+    int s, j;
+
+    for (j = 0; j < cnt; j++) {
+        sleep(1);
+
+        /* Code to produce a unit omitted */
+
+        s = pthread_mutex_lock(&mtx);
+        if (s != 0)
+            errExitEN(s, "pthread_mutex_lock");
+
+        avail++;        /* Let consumer know another unit is available */
+
+        s = pthread_mutex_unlock(&mtx);
+        if (s != 0)
+            errExitEN(s, "pthread_mutex_unlock");
+
+        s = pthread_cond_signal(&cond);         /* Wake sleeping consumer */
+        if (s != 0)
+            errExitEN(s, "pthread_cond_signal");
+    }
+
+    return NULL;
+}
+
+int
+main(int argc, char *argv[])
+{
+    pthread_t tid;
+    int s, j;
+    int totRequired;            /* Total number of units that all threads
+                                   will produce */
+    int numConsumed;            /* Total units so far consumed */
+    Boolean done;
+    time_t t;
+
+    t = time(NULL);
+
+    /* Create all threads */
+
+    totRequired = 0;
+    for (j = 1; j < argc; j++) {
+        totRequired += atoi(argv[j]);
+
+        s = pthread_create(&tid, NULL, threadFunc, argv[j]);
+        if (s != 0)
+            errExitEN(s, "pthread_create");
+    }
+
+    /* Loop to consume available units */
+
+    numConsumed = 0;
+    done = FALSE;
+
+    for (;;) {
+        s = pthread_mutex_lock(&mtx);
+        if (s != 0)
+            errExitEN(s, "pthread_mutex_lock");
+
+        while (avail == 0) {            /* Wait for something to consume */
+            s = pthread_cond_wait(&cond, &mtx);
+            if (s != 0)
+                errExitEN(s, "pthread_cond_wait");
+        }
+
+        /* At this point, 'mtx' is locked... */
+
+        while (avail > 0) {             /* Consume all available units */
+
+            /* Do something with produced unit */
+
+            numConsumed ++;
+            avail--;
+            printf("T=%ld: numConsumed=%d\n", (long) (time(NULL) - t),
+                    numConsumed);
+
+            done = numConsumed >= totRequired;
+        }
+
+        s = pthread_mutex_unlock(&mtx);
+        if (s != 0)
+            errExitEN(s, "pthread_mutex_unlock");
+
+        if (done)
+            break;
+
+        /* Perhaps do other work here that does not require mutex lock */
+
+    }
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/threads/prod_no_condvar.c b/threads/prod_no_condvar.c
new file mode 100644 (file)
index 0000000..e98aa35
--- /dev/null
@@ -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 <time.h>
+#include <pthread.h>
+#include "tlpi_hdr.h"
+
+static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
+
+static int avail = 0;
+
+static void *
+threadFunc(void *arg)
+{
+    int cnt = atoi((char *) arg);
+    int s, j;
+
+    for (j = 0; j < cnt; j++) {
+        sleep(1);
+
+        /* Code to produce a unit omitted */
+
+        s = pthread_mutex_lock(&mtx);
+        if (s != 0)
+            errExitEN(s, "pthread_mutex_lock");
+
+        avail++;        /* Let consumer know another unit is available */
+
+        s = pthread_mutex_unlock(&mtx);
+        if (s != 0)
+            errExitEN(s, "pthread_mutex_unlock");
+    }
+
+    return NULL;
+}
+
+int
+main(int argc, char *argv[])
+{
+    pthread_t tid;
+    int s, j;
+    int totRequired;            /* Total number of units that all
+                                   threads will produce */
+    int numConsumed;            /* Total units so far consumed */
+    Boolean done;
+    time_t t;
+
+    t = time(NULL);
+
+    /* Create all threads */
+
+    totRequired = 0;
+    for (j = 1; j < argc; j++) {
+        totRequired += atoi(argv[j]);
+
+        s = pthread_create(&tid, NULL, threadFunc, argv[j]);
+        if (s != 0)
+            errExitEN(s, "pthread_create");
+    }
+
+    /* Use a polling loop to check for available units */
+
+    numConsumed = 0;
+    done = FALSE;
+
+    for (;;) {
+        s = pthread_mutex_lock(&mtx);
+        if (s != 0)
+            errExitEN(s, "pthread_mutex_lock");
+
+        while (avail > 0) {             /* Consume all available units */
+
+            /* Do something with produced unit */
+
+            numConsumed ++;
+            avail--;
+            printf("T=%ld: numConsumed=%d\n", (long) (time(NULL) - t),
+                    numConsumed);
+
+            done = numConsumed >= totRequired;
+        }
+
+        s = pthread_mutex_unlock(&mtx);
+        if (s != 0)
+            errExitEN(s, "pthread_mutex_unlock");
+
+        if (done)
+            break;
+
+        /* Perhaps do other work here that does not require mutex lock */
+
+    }
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/threads/pthread_barrier_demo.c b/threads/pthread_barrier_demo.c
new file mode 100644 (file)
index 0000000..f0844f9
--- /dev/null
@@ -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 <pthread.h>
+#include "tlpi_hdr.h"
+
+static pthread_barrier_t barrier;
+                                /* Barrier waited on by all threads */
+
+static int numBarriers;         /* Number of times the threads will
+                                   pass the barrier */
+
+static void *
+threadFunc(void *arg)
+{
+    int s, j, nsecs;
+    long threadNum = (long) arg;
+
+    printf("Thread %ld started\n", threadNum);
+
+    /* Seed the random number generator based on the current time
+       (so that we get different seeds on each run) plus thread
+       number (so that each thread gets a unique seed). */
+
+    srandom(time(NULL) + threadNum);
+
+    /* Each thread loops, sleeping for a few seconds and then waiting
+       on the barrier. The loop terminates when each thread has passed
+       the barrier 'numBarriers' times. */
+
+    for (j = 0; j < numBarriers; j++) {
+
+        nsecs = random() % 5 + 1;       /* Sleep for 1 to 5 seconds */
+        sleep(nsecs);
+
+        /* Calling pthread_barrier_wait() causes each thread to block
+           until the call has been made by number of threads specified
+           in the pthread_barrier_init() call. */
+
+        printf("Thread %ld about to wait on barrier %d "
+                "after sleeping %d seconds\n", threadNum, j, nsecs);
+        s = pthread_barrier_wait(&barrier);
+
+        /* After the required number of threads have called
+           pthread_barrier_wait(), all of the threads unblock, and
+           the barrier is reset to the state it had after the call to
+           pthread_barrier_init(). In other words, the barrier can be
+           once again used by the threads as a synchronization point.
+
+           On success, pthread_barrier_wait() returns the special value
+           PTHREAD_BARRIER_SERIAL_THREAD in exactly one of the waiting
+           threads, and 0 in all of the other threads. This permits
+           the program to ensure that some action is performed exactly
+           once each time a barrier is passed. */
+
+        if (s == 0) {
+            printf("Thread %ld passed barrier %d: return value was 0\n",
+                    threadNum, j);
+
+        } else if (s == PTHREAD_BARRIER_SERIAL_THREAD) {
+            printf("Thread %ld passed barrier %d: return value was "
+                    "PTHREAD_BARRIER_SERIAL_THREAD\n", threadNum, j);
+
+            /* In the thread that gets the PTHREAD_BARRIER_SERIAL_THREAD
+               return value, we briefly delay, and then print a newline
+               character. This should give all of the threads a chance
+               to print the message saying they have passed the barrier,
+               and then provide a newline that separates those messages
+               from subsequent output. (The only purpose of this step
+               is to make the program output a little easier to read.) */
+
+            usleep(100000);
+            printf("\n");
+
+        } else {        /* Error */
+            errExitEN(s, "pthread_barrier_wait (%ld)", threadNum);
+        }
+    }
+
+    /* Print out thread termination message after a briefly delaying,
+       so that the other threads have a chance to display the return
+       value they received from pthread_barrier_wait(). (This simply
+       makes the program output a little easier to read.)*/
+
+    usleep(200000);
+    printf("Thread %ld terminating\n", threadNum);
+
+    return NULL;
+}
+
+int
+main(int argc, char *argv[])
+{
+    int s, numThreads;
+    long threadNum;
+    pthread_t *tid;
+
+    if (argc != 3 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s num-barriers num-threads\n", argv[0]);
+
+    numBarriers = atoi(argv[1]);
+    numThreads = atoi(argv[2]);
+
+    /* Allocate array to hold thread IDs */
+
+    tid = calloc(sizeof(pthread_t), numThreads);
+    if (tid == NULL)
+        errExit("calloc");
+
+    /* Initialize the barrier. The final argument specifies the
+       number of threads that must call pthread_barrier_wait()
+       before any thread will unblock from that call. */
+
+    s = pthread_barrier_init(&barrier, NULL, numThreads);
+    if (s != 0)
+        errExitEN(s, "pthread_barrier_init");
+
+    /* Create 'numThreads' threads */
+
+    for (threadNum = 0; threadNum < numThreads; threadNum++) {
+        s = pthread_create(&tid[threadNum], NULL, threadFunc,
+                (void *) threadNum);
+        if (s != 0)
+            errExitEN(s, "pthread_create");
+    }
+
+    /* Each thread prints a start-up message. We briefly delay,
+       and then print a newline character so that an empty line
+       appears after the start-up messages. */
+
+    usleep(100000);
+    printf("\n");
+
+    /* Wait for all of the threads to terminate */
+
+    for (threadNum = 0; threadNum < numThreads; threadNum++) {
+        s = pthread_join(tid[threadNum], NULL);
+        if (s != 0)
+            errExitEN(s, "pthread_join");
+    }
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/threads/simple_thread.c b/threads/simple_thread.c
new file mode 100644 (file)
index 0000000..3473573
--- /dev/null
@@ -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 <pthread.h>
+#include "tlpi_hdr.h"
+
+static void *
+threadFunc(void *arg)
+{
+    char *s = (char *) arg;
+
+    printf("%s", s);
+
+    return (void *) strlen(s);
+}
+
+int
+main(int argc, char *argv[])
+{
+    pthread_t t1;
+    void *res;
+    int s;
+
+    s = pthread_create(&t1, NULL, threadFunc, "Hello world\n");
+    if (s != 0)
+        errExitEN(s, "pthread_create");
+
+    printf("Message from main()\n");
+    s = pthread_join(t1, &res);
+    if (s != 0)
+        errExitEN(s, "pthread_join");
+
+    printf("Thread returned %ld\n", (long) res);
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/threads/strerror.c b/threads/strerror.c
new file mode 100644 (file)
index 0000000..cef2337
--- /dev/null
@@ -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 <stdio.h> */
+#include <stdio.h>
+#include <string.h>                 /* Get declaration of strerror() */
+
+#define MAX_ERROR_LEN 256           /* Maximum length of string
+                                       returned by strerror() */
+
+static char buf[MAX_ERROR_LEN];     /* Statically allocated return buffer */
+
+char *
+strerror(int err)
+{
+    if (err < 0 || err >= _sys_nerr || _sys_errlist[err] == NULL) {
+        snprintf(buf, MAX_ERROR_LEN, "Unknown error %d", err);
+    } else {
+        strncpy(buf, _sys_errlist[err], MAX_ERROR_LEN - 1);
+        buf[MAX_ERROR_LEN - 1] = '\0';          /* Ensure null termination */
+    }
+
+    return buf;
+}
diff --git a/threads/strerror_test.c b/threads/strerror_test.c
new file mode 100644 (file)
index 0000000..aed59e7
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>                 /* Get declaration of strerror() */
+#include <pthread.h>
+#include "tlpi_hdr.h"
+
+static void *
+threadFunc(void *arg)
+{
+    char *str;
+
+    printf("Other thread about to call strerror()\n");
+    str = strerror(EPERM);
+    printf("Other thread: str (%p) = %s\n", str, str);
+
+    return NULL;
+}
+
+int
+main(int argc, char *argv[])
+{
+    pthread_t t;
+    int s;
+    char *str;
+
+    str = strerror(EINVAL);
+    printf("Main thread has called strerror()\n");
+
+    s = pthread_create(&t, NULL, threadFunc, NULL);
+    if (s != 0)
+        errExitEN(s, "pthread_create");
+
+    s = pthread_join(t, NULL);
+    if (s != 0)
+        errExitEN(s, "pthread_join");
+
+    /* If strerror() is not thread-safe, then the output of this printf() be
+       the same as that produced by the analogous printf() in threadFunc() */
+
+    printf("Main thread:  str (%p) = %s\n", str, str);
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/threads/strerror_tls.c b/threads/strerror_tls.c
new file mode 100644 (file)
index 0000000..dc7d2ac
--- /dev/null
@@ -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 <stdio.h> */
+#include <stdio.h>
+#include <string.h>                 /* Get declaration of strerror() */
+#include <pthread.h>
+
+#define MAX_ERROR_LEN 256           /* Maximum length of string in per-thread
+                                       buffer returned by strerror() */
+
+static __thread char buf[MAX_ERROR_LEN];
+                                    /* Thread-local return buffer */
+
+char *
+strerror(int err)
+{
+    if (err < 0 || err >= _sys_nerr || _sys_errlist[err] == NULL) {
+        snprintf(buf, MAX_ERROR_LEN, "Unknown error %d", err);
+    } else {
+        strncpy(buf, _sys_errlist[err], MAX_ERROR_LEN - 1);
+        buf[MAX_ERROR_LEN - 1] = '\0';          /* Ensure null termination */
+    }
+
+    return buf;
+}
diff --git a/threads/strerror_tsd.c b/threads/strerror_tsd.c
new file mode 100644 (file)
index 0000000..c82d1d1
--- /dev/null
@@ -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 <stdio.h> */
+#include <stdio.h>
+#include <string.h>                 /* Get declaration of strerror() */
+#include <pthread.h>
+#include "tlpi_hdr.h"
+
+static pthread_once_t once = PTHREAD_ONCE_INIT;
+static pthread_key_t strerrorKey;
+
+#define MAX_ERROR_LEN 256           /* Maximum length of string in per-thread
+                                       buffer returned by strerror() */
+
+static void                         /* Free thread-specific data buffer */
+destructor(void *buf)
+{
+    free(buf);
+}
+
+static void                         /* One-time key creation function */
+createKey(void)
+{
+    int s;
+
+    /* Allocate a unique thread-specific data key and save the address
+       of the destructor for thread-specific data buffers */
+
+    s = pthread_key_create(&strerrorKey, destructor);
+    if (s != 0)
+        errExitEN(s, "pthread_key_create");
+}
+
+char *
+strerror(int err)
+{
+    int s;
+    char *buf;
+
+    /* Make first caller allocate key for thread-specific data */
+
+    s = pthread_once(&once, createKey);
+    if (s != 0)
+        errExitEN(s, "pthread_once");
+
+    buf = pthread_getspecific(strerrorKey);
+    if (buf == NULL) {          /* If first call from this thread, allocate
+                                   buffer for thread, and save its location */
+        buf = malloc(MAX_ERROR_LEN);
+        if (buf == NULL)
+            errExit("malloc");
+
+        s = pthread_setspecific(strerrorKey, buf);
+        if (s != 0)
+            errExitEN(s, "pthread_setspecific");
+    }
+
+    if (err < 0 || err >= _sys_nerr || _sys_errlist[err] == NULL) {
+        snprintf(buf, MAX_ERROR_LEN, "Unknown error %d", err);
+    } else {
+        strncpy(buf, _sys_errlist[err], MAX_ERROR_LEN - 1);
+        buf[MAX_ERROR_LEN - 1] = '\0';          /* Ensure null termination */
+    }
+
+    return buf;
+}
diff --git a/threads/thread_cancel.c b/threads/thread_cancel.c
new file mode 100644 (file)
index 0000000..9605e2b
--- /dev/null
@@ -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 <pthread.h>
+#include "tlpi_hdr.h"
+
+static void *
+threadFunc(void *arg)
+{
+    int j;
+
+    printf("New thread started\n");     /* May be a cancellation point */
+    for (j = 1; ; j++) {
+        printf("Loop %d\n", j);         /* May be a cancellation point */
+        sleep(1);                       /* A cancellation point */
+    }
+
+    /* NOTREACHED */
+    return NULL;
+}
+
+int
+main(int argc, char *argv[])
+{
+    pthread_t thr;
+    int s;
+    void *res;
+
+    s = pthread_create(&thr, NULL, threadFunc, NULL);
+    if (s != 0)
+        errExitEN(s, "pthread_create");
+
+    sleep(3);                           /* Allow new thread to run a while */
+
+    s = pthread_cancel(thr);
+    if (s != 0)
+        errExitEN(s, "pthread_cancel");
+
+    s = pthread_join(thr, &res);
+    if (s != 0)
+        errExitEN(s, "pthread_join");
+
+    if (res == PTHREAD_CANCELED)
+        printf("Thread was canceled\n");
+    else
+        printf("Thread was not canceled (should not happen!)\n");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/threads/thread_cleanup.c b/threads/thread_cleanup.c
new file mode 100644 (file)
index 0000000..71ae7d5
--- /dev/null
@@ -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 <pthread.h>
+#include "tlpi_hdr.h"
+
+static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
+static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
+static int glob = 0;                    /* Predicate variable */
+
+static void     /* Free memory pointed to by 'arg' and unlock mutex */
+cleanupHandler(void *arg)
+{
+    int s;
+
+    printf("cleanup: freeing block at %p\n", arg);
+    free(arg);
+
+    printf("cleanup: unlocking mutex\n");
+    s = pthread_mutex_unlock(&mtx);
+    if (s != 0)
+        errExitEN(s, "pthread_mutex_unlock");
+}
+
+static void *
+threadFunc(void *arg)
+{
+    int s;
+    void *buf = NULL;                   /* Buffer allocated by thread */
+
+    buf = malloc(0x10000);              /* Not a cancellation point */
+    printf("thread:  allocated memory at %p\n", buf);
+
+    s = pthread_mutex_lock(&mtx);       /* Not a cancellation point */
+    if (s != 0)
+        errExitEN(s, "pthread_mutex_lock");
+
+    pthread_cleanup_push(cleanupHandler, buf);
+
+    while (glob == 0) {
+        s = pthread_cond_wait(&cond, &mtx);     /* A cancellation point */
+        if (s != 0)
+            errExitEN(s, "pthread_cond_wait");
+    }
+
+    printf("thread:  condition wait loop completed\n");
+    pthread_cleanup_pop(1);             /* Executes cleanup handler */
+    return NULL;
+}
+
+int
+main(int argc, char *argv[])
+{
+    pthread_t thr;
+    void *res;
+    int s;
+
+    s = pthread_create(&thr, NULL, threadFunc, NULL);
+    if (s != 0)
+        errExitEN(s, "pthread_create");
+
+    sleep(2);                   /* Give thread a chance to get started */
+
+    if (argc == 1) {            /* Cancel thread */
+        printf("main:    about to cancel thread\n");
+        s = pthread_cancel(thr);
+        if (s != 0)
+            errExitEN(s, "pthread_cancel");
+
+    } else {                    /* Signal condition variable */
+        printf("main:    about to signal condition variable\n");
+
+        s = pthread_mutex_lock(&mtx);   /* See the TLPI page 679 erratum */
+        if (s != 0)
+            errExitEN(s, "pthread_mutex_lock");
+
+        glob = 1;
+
+        s = pthread_mutex_unlock(&mtx); /* See the TLPI page 679 erratum */
+        if (s != 0)
+            errExitEN(s, "pthread_mutex_unlock");
+
+        s = pthread_cond_signal(&cond);
+        if (s != 0)
+            errExitEN(s, "pthread_cond_signal");
+    }
+
+    s = pthread_join(thr, &res);
+    if (s != 0)
+        errExitEN(s, "pthread_join");
+    if (res == PTHREAD_CANCELED)
+        printf("main:    thread was canceled\n");
+    else
+        printf("main:    thread terminated normally\n");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/threads/thread_incr.c b/threads/thread_incr.c
new file mode 100644 (file)
index 0000000..9322435
--- /dev/null
@@ -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 <pthread.h>
+#include "tlpi_hdr.h"
+
+static volatile int glob = 0;   /* "volatile" prevents compiler optimizations
+                                   of arithmetic operations on 'glob' */
+static void *                   /* Loop 'arg' times incrementing 'glob' */
+threadFunc(void *arg)
+{
+    int loops = *((int *) arg);
+    int loc, j;
+
+    for (j = 0; j < loops; j++) {
+        loc = glob;
+        loc++;
+        glob = loc;
+    }
+
+    return NULL;
+}
+
+int
+main(int argc, char *argv[])
+{
+    pthread_t t1, t2;
+    int loops, s;
+
+    loops = (argc > 1) ? getInt(argv[1], GN_GT_0, "num-loops") : 10000000;
+
+    s = pthread_create(&t1, NULL, threadFunc, &loops);
+    if (s != 0)
+        errExitEN(s, "pthread_create");
+    s = pthread_create(&t2, NULL, threadFunc, &loops);
+    if (s != 0)
+        errExitEN(s, "pthread_create");
+
+    s = pthread_join(t1, NULL);
+    if (s != 0)
+        errExitEN(s, "pthread_join");
+    s = pthread_join(t2, NULL);
+    if (s != 0)
+        errExitEN(s, "pthread_join");
+
+    printf("glob = %d\n", glob);
+    exit(EXIT_SUCCESS);
+}
diff --git a/threads/thread_incr_mutex.c b/threads/thread_incr_mutex.c
new file mode 100644 (file)
index 0000000..598dbe0
--- /dev/null
@@ -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 <pthread.h>
+#include "tlpi_hdr.h"
+
+static volatile int glob = 0;
+static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
+
+static void *                   /* Loop 'arg' times incrementing 'glob' */
+threadFunc(void *arg)
+{
+    int loops = *((int *) arg);
+    int loc, j, s;
+
+    for (j = 0; j < loops; j++) {
+        s = pthread_mutex_lock(&mtx);
+        if (s != 0)
+            errExitEN(s, "pthread_mutex_lock");
+
+        loc = glob;
+        loc++;
+        glob = loc;
+
+        s = pthread_mutex_unlock(&mtx);
+        if (s != 0)
+            errExitEN(s, "pthread_mutex_unlock");
+    }
+
+    return NULL;
+}
+
+int
+main(int argc, char *argv[])
+{
+    pthread_t t1, t2;
+    int loops, s;
+
+    loops = (argc > 1) ? getInt(argv[1], GN_GT_0, "num-loops") : 10000000;
+
+    s = pthread_create(&t1, NULL, threadFunc, &loops);
+    if (s != 0)
+        errExitEN(s, "pthread_create");
+    s = pthread_create(&t2, NULL, threadFunc, &loops);
+    if (s != 0)
+        errExitEN(s, "pthread_create");
+
+    s = pthread_join(t1, NULL);
+    if (s != 0)
+        errExitEN(s, "pthread_join");
+    s = pthread_join(t2, NULL);
+    if (s != 0)
+        errExitEN(s, "pthread_join");
+
+    printf("glob = %d\n", glob);
+    exit(EXIT_SUCCESS);
+}
diff --git a/threads/thread_incr_rwlock.c b/threads/thread_incr_rwlock.c
new file mode 100644 (file)
index 0000000..b6942c3
--- /dev/null
@@ -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 <pthread.h>
+#include "tlpi_hdr.h"
+
+static volatile int glob = 0;
+static pthread_rwlock_t rwlock;
+
+static void *                   /* Loop 'arg' times incrementing 'glob' */
+threadFunc(void *arg)
+{
+    int loops = *((int *) arg);
+    int loc, j, s;
+
+    for (j = 0; j < loops; j++) {
+        s = pthread_rwlock_wrlock(&rwlock);
+        if (s != 0)
+            errExitEN(s, "pthread_rwlock_wrlock");
+
+        loc = glob;
+        loc++;
+        glob = loc;
+
+        s = pthread_rwlock_unlock(&rwlock);
+        if (s != 0)
+            errExitEN(s, "pthread_rwlock_unlock");
+    }
+
+    return NULL;
+}
+
+int
+main(int argc, char *argv[])
+{
+    pthread_t t1, t2;
+    int loops, s;
+
+    loops = (argc > 1) ? getInt(argv[1], GN_GT_0, "num-loops") : 10000000;
+
+    s = pthread_rwlock_init(&rwlock, 0);
+    if (s != 0)
+        errExitEN(s, "pthread_rwlock_init");
+
+    s = pthread_create(&t1, NULL, threadFunc, &loops);
+    if (s != 0)
+        errExitEN(s, "pthread_create");
+    s = pthread_create(&t2, NULL, threadFunc, &loops);
+    if (s != 0)
+        errExitEN(s, "pthread_create");
+
+    s = pthread_join(t1, NULL);
+    if (s != 0)
+        errExitEN(s, "pthread_join");
+    s = pthread_join(t2, NULL);
+    if (s != 0)
+        errExitEN(s, "pthread_join");
+
+    printf("glob = %d\n", glob);
+    exit(EXIT_SUCCESS);
+}
diff --git a/threads/thread_incr_spinlock.c b/threads/thread_incr_spinlock.c
new file mode 100644 (file)
index 0000000..59c1ede
--- /dev/null
@@ -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 <pthread.h>
+#include "tlpi_hdr.h"
+
+static volatile int glob = 0;
+static pthread_spinlock_t splock;
+
+static void *                   /* Loop 'arg' times incrementing 'glob' */
+threadFunc(void *arg)
+{
+    int loops = *((int *) arg);
+    int loc, j, s;
+
+    for (j = 0; j < loops; j++) {
+        s = pthread_spin_lock(&splock);
+        if (s != 0)
+            errExitEN(s, "pthread_spin_lock");
+
+        loc = glob;
+        loc++;
+        glob = loc;
+
+        s = pthread_spin_unlock(&splock);
+        if (s != 0)
+            errExitEN(s, "pthread_spin_unlock");
+    }
+
+    return NULL;
+}
+
+int
+main(int argc, char *argv[])
+{
+    pthread_t t1, t2;
+    int loops, s;
+
+    loops = (argc > 1) ? getInt(argv[1], GN_GT_0, "num-loops") : 10000000;
+
+    s = pthread_spin_init(&splock, 0);
+    if (s != 0)
+        errExitEN(s, "pthread_spin_init");
+
+    s = pthread_create(&t1, NULL, threadFunc, &loops);
+    if (s != 0)
+        errExitEN(s, "pthread_create");
+    s = pthread_create(&t2, NULL, threadFunc, &loops);
+    if (s != 0)
+        errExitEN(s, "pthread_create");
+
+    s = pthread_join(t1, NULL);
+    if (s != 0)
+        errExitEN(s, "pthread_join");
+    s = pthread_join(t2, NULL);
+    if (s != 0)
+        errExitEN(s, "pthread_join");
+
+    printf("glob = %d\n", glob);
+    exit(EXIT_SUCCESS);
+}
diff --git a/threads/thread_lock_speed.c b/threads/thread_lock_speed.c
new file mode 100644 (file)
index 0000000..7b8e216
--- /dev/null
@@ -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 <pthread.h>
+#include "tlpi_hdr.h"
+
+static volatile int glob = 0;
+static pthread_spinlock_t splock;
+static pthread_mutex_t mtx;
+static int useMutex = 0;
+static int numOuterLoops;
+static int numInnerLoops;
+
+static void *
+threadFunc(void *arg)
+{
+    int j, k, s;
+
+    for (j = 0; j < numOuterLoops; j++) {
+        if (useMutex) {
+            s = pthread_mutex_lock(&mtx);
+            if (s != 0)
+                errExitEN(s, "pthread_mutex_lock");
+        } else {
+            s = pthread_spin_lock(&splock);
+            if (s != 0)
+                errExitEN(s, "pthread_spin_lock");
+        }
+
+        for (k = 0; k < numInnerLoops; k++)
+            glob++;
+
+        if (useMutex) {
+            s = pthread_mutex_unlock(&mtx);
+            if (s != 0)
+                errExitEN(s, "pthread_mutex_unlock");
+        } else {
+            s = pthread_spin_unlock(&splock);
+            if (s != 0)
+                errExitEN(s, "pthread_spin_unlock");
+        }
+    }
+
+    return NULL;
+}
+
+static void
+usageError(char *pname)
+{
+    fprintf(stderr,
+            "Usage: %s [-s] num-threads "
+            "[num-inner-loops [num-outer-loops]]\n", pname);
+    fprintf(stderr,
+            "    -q   Don't print verbose messages\n");
+    fprintf(stderr,
+            "    -s   Use spin locks (instead of the default mutexes)\n");
+    exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+    int opt, s, j;
+    int numThreads;
+    pthread_t *thread;
+    int verbose;
+
+    /* Prevent runaway/forgotten process from burning up CPU time forever */
+
+    alarm(120);         /* Unhandled SIGALRM will kill process */
+
+    useMutex = 1;
+    verbose = 1;
+    while ((opt = getopt(argc, argv, "qs")) != -1) {
+        switch (opt) {
+        case 'q':
+            verbose = 0;
+            break;
+        case 's':
+            useMutex = 0;
+            break;
+        default:
+            usageError(argv[0]);
+        }
+    }
+
+    if (optind >= argc)
+        usageError(argv[0]);
+
+    numThreads = atoi(argv[optind]);
+    numInnerLoops = (optind + 1 < argc) ? atoi(argv[optind + 1]) : 1;
+    numOuterLoops = (optind + 2 < argc) ? atoi(argv[optind + 2]) : 10000000;
+
+    if (verbose) {
+        printf("Using %s\n", useMutex ? "mutexes" : "spin locks");
+        printf("\tthreads: %d; outer loops: %d; inner loops: %d\n",
+                numThreads, numOuterLoops, numInnerLoops);
+    }
+
+    thread = calloc(numThreads, sizeof(pthread_t));
+    if (thread == NULL)
+        errExit("calloc");
+
+    if (useMutex) {
+        s = pthread_mutex_init(&mtx, NULL);
+        if (s != 0)
+            errExitEN(s, "pthread_mutex_init");
+    } else {
+        s = pthread_spin_init(&splock, 0);
+        if (s != 0)
+            errExitEN(s, "pthread_spin_init");
+    }
+
+    for (j = 0; j < numThreads; j++) {
+        s = pthread_create(&thread[j], NULL, threadFunc, NULL);
+        if (s != 0)
+            errExitEN(s, "pthread_create");
+    }
+
+    for (j = 0; j < numThreads; j++) {
+        s = pthread_join(thread[j], NULL);
+        if (s != 0)
+            errExitEN(s, "pthread_join");
+    }
+
+    if (verbose)
+        printf("glob = %d\n", glob);
+    exit(EXIT_SUCCESS);
+}
diff --git a/threads/thread_multijoin.c b/threads/thread_multijoin.c
new file mode 100644 (file)
index 0000000..2421a69
--- /dev/null
@@ -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 <pthread.h>
+#include "tlpi_hdr.h"
+
+static pthread_cond_t threadDied = PTHREAD_COND_INITIALIZER;
+static pthread_mutex_t threadMutex = PTHREAD_MUTEX_INITIALIZER;
+                /* Protects all of the following global variables */
+
+static int totThreads = 0;      /* Total number of threads created */
+static int numLive = 0;         /* Total number of threads still alive or
+                                   terminated but not yet joined */
+static int numUnjoined = 0;     /* Number of terminated threads that
+                                   have not yet been joined */
+enum tstate {                   /* Thread states */
+    TS_ALIVE,                   /* Thread is alive */
+    TS_TERMINATED,              /* Thread terminated, not yet joined */
+    TS_JOINED                   /* Thread terminated, and joined */
+};
+
+static struct {                 /* Info about each thread */
+    pthread_t tid;              /* ID of this thread */
+    enum tstate state;          /* Thread state (TS_* constants above) */
+    int sleepTime;              /* Number seconds to live before terminating */
+} *thread;
+
+static void *                   /* Start function for thread */
+threadFunc(void *arg)
+{
+    int idx = (int) arg;
+    int s;
+
+    sleep(thread[idx].sleepTime);       /* Simulate doing some work */
+    printf("Thread %d terminating\n", idx);
+
+    s = pthread_mutex_lock(&threadMutex);
+    if (s != 0)
+        errExitEN(s, "pthread_mutex_lock");
+
+    numUnjoined++;
+    thread[idx].state = TS_TERMINATED;
+
+    s = pthread_mutex_unlock(&threadMutex);
+    if (s != 0)
+        errExitEN(s, "pthread_mutex_unlock");
+    s = pthread_cond_signal(&threadDied);
+    if (s != 0)
+        errExitEN(s, "pthread_cond_signal");
+
+    return NULL;
+}
+
+int
+main(int argc, char *argv[])
+{
+    int s, idx;
+
+    if (argc < 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s num-secs...\n", argv[0]);
+
+    thread = calloc(argc - 1, sizeof(*thread));
+    if (thread == NULL)
+        errExit("calloc");
+
+    /* Create all threads */
+
+    for (idx = 0; idx < argc - 1; idx++) {
+        thread[idx].sleepTime = getInt(argv[idx + 1], GN_NONNEG, NULL);
+        thread[idx].state = TS_ALIVE;
+        s = pthread_create(&thread[idx].tid, NULL, threadFunc, (void *) idx);
+        if (s != 0)
+            errExitEN(s, "pthread_create");
+    }
+
+    totThreads = argc - 1;
+    numLive = totThreads;
+
+    /* Join with terminated threads */
+
+    while (numLive > 0) {
+        s = pthread_mutex_lock(&threadMutex);
+        if (s != 0)
+            errExitEN(s, "pthread_mutex_lock");
+
+        while (numUnjoined == 0) {
+            s = pthread_cond_wait(&threadDied, &threadMutex);
+            if (s != 0)
+                errExitEN(s, "pthread_cond_wait");
+        }
+
+        for (idx = 0; idx < totThreads; idx++) {
+            if (thread[idx].state == TS_TERMINATED) {
+                s = pthread_join(thread[idx].tid, NULL);
+                if (s != 0)
+                    errExitEN(s, "pthread_join");
+
+                thread[idx].state = TS_JOINED;
+                numLive--;
+                numUnjoined--;
+
+                printf("Reaped thread %d (numLive=%d)\n", idx, numLive);
+            }
+        }
+
+        s = pthread_mutex_unlock(&threadMutex);
+        if (s != 0)
+            errExitEN(s, "pthread_mutex_unlock");
+    }
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/time/Makefile b/time/Makefile
new file mode 100644 (file)
index 0000000..4f92fad
--- /dev/null
@@ -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 (file)
index 0000000..2d6e9a5
--- /dev/null
@@ -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 <locale.h>
+#include <time.h>
+#include <sys/time.h>
+#include "tlpi_hdr.h"
+
+#define SECONDS_IN_TROPICAL_YEAR (365.24219 * 24 * 60 * 60)
+
+int
+main(int argc, char *argv[])
+{
+    time_t t;
+    struct tm *gmp, *locp;
+    struct tm gm, loc;
+    struct timeval tv;
+
+    /* Retrieve time, convert and display it in various forms */
+
+    t = time(NULL);
+    printf("Seconds since the Epoch (1 Jan 1970): %ld", (long) t);
+    printf(" (about %6.3f years)\n", t / SECONDS_IN_TROPICAL_YEAR);
+
+    if (gettimeofday(&tv, NULL) == -1)
+        errExit("gettimeofday");
+    printf("  gettimeofday() returned %ld secs, %ld microsecs\n",
+            (long) tv.tv_sec, (long) tv.tv_usec);
+
+    gmp = gmtime(&t);
+    if (gmp == NULL)
+        errExit("gmtime");
+
+    gm = *gmp;          /* Save local copy, since *gmp may be modified
+                           by asctime() or gmtime() */
+    printf("Broken down by gmtime():\n");
+    printf("  year=%d mon=%d mday=%d hour=%d min=%d sec=%d ", gm.tm_year,
+            gm.tm_mon, gm.tm_mday, gm.tm_hour, gm.tm_min, gm.tm_sec);
+    printf("wday=%d yday=%d isdst=%d\n", gm.tm_wday, gm.tm_yday, gm.tm_isdst);
+
+    /* The TZ environment variable will affect localtime().
+       Try, for example:
+
+                TZ=Pacific/Auckland calendar_time
+    */
+
+    locp = localtime(&t);
+    if (locp == NULL)
+        errExit("localtime");
+
+    loc = *locp;        /* Save local copy */
+
+    printf("Broken down by localtime():\n");
+    printf("  year=%d mon=%d mday=%d hour=%d min=%d sec=%d ",
+            loc.tm_year, loc.tm_mon, loc.tm_mday,
+            loc.tm_hour, loc.tm_min, loc.tm_sec);
+    printf("wday=%d yday=%d isdst=%d\n\n",
+            loc.tm_wday, loc.tm_yday, loc.tm_isdst);
+
+    printf("asctime() formats the gmtime() value as: %s", asctime(&gm));
+    printf("ctime() formats the time() value as:     %s", ctime(&t));
+
+    printf("mktime() of gmtime() value:    %ld secs\n", (long) mktime(&gm));
+    printf("mktime() of localtime() value: %ld secs\n", (long) mktime(&loc));
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/time/curr_time.c b/time/curr_time.c
new file mode 100644 (file)
index 0000000..40a0133
--- /dev/null
@@ -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 <time.h>
+#include "curr_time.h"          /* Declares function defined here */
+
+#define BUF_SIZE 1000
+
+/* Return a string containing the current time formatted according to
+   the specification in 'format' (see strftime(3) for specifiers).
+   If 'format' is NULL, we use "%c" as a specifier (which gives the'
+   date and time as for ctime(3), but without the trailing newline).
+   Returns NULL on error. */
+
+char *
+currTime(const char *format)
+{
+    static char buf[BUF_SIZE];  /* Nonreentrant */
+    time_t t;
+    size_t s;
+    struct tm *tm;
+
+    t = time(NULL);
+    tm = localtime(&t);
+    if (tm == NULL)
+        return NULL;
+
+    s = strftime(buf, BUF_SIZE, (format != NULL) ? format : "%c", tm);
+
+    return (s == 0) ? NULL : buf;
+}
diff --git a/time/curr_time.h b/time/curr_time.h
new file mode 100644 (file)
index 0000000..3a34f12
--- /dev/null
@@ -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 (file)
index 0000000..04eba51
--- /dev/null
@@ -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 <sys/times.h>
+#include <time.h>
+#include "tlpi_hdr.h"
+
+static void             /* Display 'msg' and process times */
+displayProcessTimes(const char *msg)
+{
+    struct tms t;
+    clock_t clockTime;
+    static long clockTicks = 0;
+
+    if (msg != NULL)
+        printf("%s", msg);
+
+    if (clockTicks == 0) {      /* Fetch clock ticks on first call */
+        clockTicks = sysconf(_SC_CLK_TCK);
+        if (clockTicks == -1)
+            errExit("sysconf");
+    }
+
+    clockTime = clock();
+    if (clockTime == -1)
+        errExit("clock");
+
+    printf("        clock() returns: %ld clocks-per-sec (%.2f secs)\n",
+            (long) clockTime, (double) clockTime / CLOCKS_PER_SEC);
+
+    if (times(&t) == -1)
+        errExit("times");
+    printf("        times() yields: user CPU=%.2f; system CPU: %.2f\n",
+            (double) t.tms_utime / clockTicks,
+            (double) t.tms_stime / clockTicks);
+}
+
+int
+main(int argc, char *argv[])
+{
+    int numCalls, j;
+
+    printf("CLOCKS_PER_SEC=%ld  sysconf(_SC_CLK_TCK)=%ld\n\n",
+            (long) CLOCKS_PER_SEC, sysconf(_SC_CLK_TCK));
+
+    displayProcessTimes("At program start:\n");
+
+    /* Call getppid() a large number of times, so that
+       some user and system CPU time are consumed */
+
+    numCalls = (argc > 1) ? getInt(argv[1], GN_GT_0, "num-calls") : 100000000;
+    for (j = 0; j < numCalls; j++)
+        (void) getppid();
+
+    displayProcessTimes("After getppid() loop:\n");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/time/show_time.c b/time/show_time.c
new file mode 100644 (file)
index 0000000..3cd622a
--- /dev/null
@@ -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 <time.h>
+#include <locale.h>
+#include "tlpi_hdr.h"
+
+#define BUF_SIZE 200
+
+int
+main(int argc, char *argv[])
+{
+    time_t t;
+    struct tm *loc;
+    char buf[BUF_SIZE];
+
+    if (setlocale(LC_ALL, "") == NULL)
+        errExit("setlocale");   /* Use locale settings in conversions */
+
+    t = time(NULL);
+
+    printf("ctime() of time() value is:  %s", ctime(&t));
+
+    loc = localtime(&t);
+    if (loc == NULL)
+        errExit("localtime");
+
+    printf("asctime() of local time is:  %s", asctime(loc));
+
+    if (strftime(buf, BUF_SIZE, "%A, %d %B %Y, %H:%M:%S %Z", loc) == 0)
+        fatal("strftime returned 0");
+    printf("strftime() of local time is: %s\n", buf);
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/time/strtime.c b/time/strtime.c
new file mode 100644 (file)
index 0000000..8ddf220
--- /dev/null
@@ -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 <time.h>
+#include <locale.h>
+#include "tlpi_hdr.h"
+
+#define SBUF_SIZE 1000
+
+int
+main(int argc, char *argv[])
+{
+    struct tm tm;
+    char sbuf[SBUF_SIZE];
+    char *ofmt;
+
+    if (argc < 3 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s input-date-time in-format [out-format]\n", argv[0]);
+
+    if (setlocale(LC_ALL, "") == NULL)
+        errExit("setlocale");   /* Use locale settings in conversions */
+
+    memset(&tm, 0, sizeof(struct tm));          /* Initialize 'tm' */
+    if (strptime(argv[1], argv[2], &tm) == NULL)
+        fatal("strptime");
+
+    tm.tm_isdst = -1;           /* Not set by strptime(); tells mktime()
+                                   to determine if DST is in effect */
+    printf("calendar time (seconds since Epoch): %ld\n", (long) mktime(&tm));
+
+    ofmt = (argc > 3) ? argv[3] : "%H:%M:%S %A, %d %B %Y %Z";
+    if (strftime(sbuf, SBUF_SIZE, ofmt, &tm) == 0)
+        fatal("strftime returned 0");
+    printf("strftime() yields: %s\n", sbuf);
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/time/t_stime.c b/time/t_stime.c
new file mode 100644 (file)
index 0000000..f60fe4c
--- /dev/null
@@ -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 <time.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    struct tm tm;
+    time_t t;
+
+    if (argc != 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s \"DD MMM YYYY HH:MM:SS\"\n", argv[0]);
+
+    if (strptime(argv[1], "%d %b %Y %H:%M:%S", &tm) == NULL)
+        fatal("strptime failed");
+
+    t = mktime(&tm);
+    if (stime(&t) == -1)
+        errExit("stime");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/timers/Makefile b/timers/Makefile
new file mode 100644 (file)
index 0000000..dc3477e
--- /dev/null
@@ -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 (file)
index 0000000..e70b4ab
--- /dev/null
@@ -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 <sys/timerfd.h>
+#include <time.h>
+#include <stdint.h>                     /* Definition of uint64_t */
+#include "itimerspec_from_str.h"        /* Declares itimerspecFromStr() */
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    struct itimerspec ts;
+    struct timespec start, now;
+    int maxExp, fd, secs, nanosecs;
+    uint64_t numExp, totalExp;
+    ssize_t s;
+
+    if (argc < 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s secs[/nsecs][:int-secs[/int-nsecs]] [max-exp]\n", argv[0]);
+
+    itimerspecFromStr(argv[1], &ts);
+    maxExp = (argc > 2) ? getInt(argv[2], GN_GT_0, "max-exp") : 1;
+
+    fd = timerfd_create(CLOCK_REALTIME, 0);
+    if (fd == -1)
+        errExit("timerfd_create");
+
+    if (timerfd_settime(fd, 0, &ts, NULL) == -1)
+        errExit("timerfd_settime");
+
+    if (clock_gettime(CLOCK_MONOTONIC, &start) == -1)
+        errExit("clock_gettime");
+
+    for (totalExp = 0; totalExp < maxExp;) {
+
+        /* Read number of expirations on the timer, and then display
+           time elapsed since timer was started, followed by number
+           of expirations read and total expirations so far. */
+
+        s = read(fd, &numExp, sizeof(uint64_t));
+        if (s != sizeof(uint64_t))
+            errExit("read");
+
+        totalExp += numExp;
+
+        if (clock_gettime(CLOCK_MONOTONIC, &now) == -1)
+            errExit("clock_gettime");
+
+        secs = now.tv_sec - start.tv_sec;
+        nanosecs = now.tv_nsec - start.tv_nsec;
+        if (nanosecs < 0) {
+            secs--;
+            nanosecs += 1000000000;
+        }
+
+        printf("%d.%03d: expirations read: %llu; total=%llu\n",
+                secs, (nanosecs + 500000) / 1000000,
+                (unsigned long long) numExp, (unsigned long long) totalExp);
+    }
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/timers/itimerspec_from_str.c b/timers/itimerspec_from_str.c
new file mode 100644 (file)
index 0000000..d8af189
--- /dev/null
@@ -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 <string.h>
+#include <stdlib.h>
+#include "itimerspec_from_str.h"        /* Declares function defined here */
+
+/* Convert a string of the following form to an itimerspec structure:
+   "value.sec[/value.nanosec][:interval.sec[/interval.nanosec]]".
+   Optional components that are omitted cause 0 to be assigned to the
+   corresponding structure fields. */
+
+void
+itimerspecFromStr(char *str, struct itimerspec *tsp)
+{
+    char *dupstr ,*cptr, *sptr;
+
+    dupstr = strdup(str);
+
+    cptr = strchr(dupstr, ':');
+    if (cptr != NULL)
+        *cptr = '\0';
+
+    sptr = strchr(dupstr, '/');
+    if (sptr != NULL)
+        *sptr = '\0';
+
+    tsp->it_value.tv_sec = atoi(dupstr);
+    tsp->it_value.tv_nsec = (sptr != NULL) ? atoi(sptr + 1) : 0;
+
+    if (cptr == NULL) {
+        tsp->it_interval.tv_sec = 0;
+        tsp->it_interval.tv_nsec = 0;
+    } else {
+        sptr = strchr(cptr + 1, '/');
+        if (sptr != NULL)
+            *sptr = '\0';
+        tsp->it_interval.tv_sec = atoi(cptr + 1);
+        tsp->it_interval.tv_nsec = (sptr != NULL) ? atoi(sptr + 1) : 0;
+    }
+    free(dupstr);
+}
+#endif
diff --git a/timers/itimerspec_from_str.h b/timers/itimerspec_from_str.h
new file mode 100644 (file)
index 0000000..fd451c8
--- /dev/null
@@ -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 <time.h>
+
+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 (file)
index 0000000..55af1a5
--- /dev/null
@@ -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 <signal.h>
+#include <time.h>
+#include "curr_time.h"          /* Declaration of currTime() */
+#include "tlpi_hdr.h"
+
+static void
+handler(int sig, siginfo_t *si, void *uc)
+{
+    printf("[%s] Got signal %d\n", currTime("%T"), sig);
+    printf("    sival_int          = %d\n", si->si_value.sival_int);
+#ifdef __linux__
+    printf("    si_overrun         = %d\n", si->si_overrun);
+#endif
+    printf("    timer_getoverrun() = %d\n",
+            timer_getoverrun((timer_t) si->si_value.sival_ptr));
+}
+
+int
+main(int argc, char *argv[])
+{
+    struct itimerspec ts;
+    timer_t tid;
+    int j;
+    struct sigaction sa;
+
+    if (argc < 2)
+        usageErr("%s secs [nsecs [int-secs [int-nsecs]]]\n", argv[0]);
+
+    sa.sa_flags = SA_SIGINFO;
+    sa.sa_sigaction = handler;
+    sigemptyset(&sa.sa_mask);
+    if (sigaction(SIGALRM, &sa, NULL) == -1)
+        errExit("sigaction");
+
+    if (timer_create(CLOCK_REALTIME, NULL, &tid) == -1)
+        errExit("timer_create");
+    printf("timer ID = %ld\n", (long) tid);
+
+    ts.it_value.tv_sec = atoi(argv[1]);
+    ts.it_value.tv_nsec = (argc > 2) ? atoi(argv[2]) : 0;
+    ts.it_interval.tv_sec = (argc > 3) ? atoi(argv[3]) : 0;
+    ts.it_interval.tv_nsec = (argc > 4) ? atoi(argv[4]) : 0;
+    if (timer_settime(tid, 0, &ts, NULL) == -1)
+        errExit("timer_settime");
+
+    for (j = 0; ; j++)
+        pause();
+}
diff --git a/timers/ptmr_sigev_signal.c b/timers/ptmr_sigev_signal.c
new file mode 100644 (file)
index 0000000..5c90706
--- /dev/null
@@ -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 <signal.h>
+#include <time.h>
+#include "curr_time.h"                  /* Declares currTime() */
+#include "itimerspec_from_str.h"        /* Declares itimerspecFromStr() */
+#include "tlpi_hdr.h"
+
+#define TIMER_SIG SIGRTMAX              /* Our timer notification signal */
+
+static void
+handler(int sig, siginfo_t *si, void *uc)
+{
+    timer_t *tidptr;
+
+    tidptr = si->si_value.sival_ptr;
+
+    /* UNSAFE: This handler uses non-async-signal-safe functions
+       (printf(); see Section 21.1.2) */
+
+    printf("[%s] Got signal %d\n", currTime("%T"), sig);
+    printf("    *sival_ptr         = %ld\n", (long) *tidptr);
+    printf("    timer_getoverrun() = %d\n", timer_getoverrun(*tidptr));
+}
+
+int
+main(int argc, char *argv[])
+{
+    struct itimerspec ts;
+    struct sigaction  sa;
+    struct sigevent   sev;
+    timer_t *tidlist;
+    int j;
+
+    if (argc < 2)
+        usageErr("%s secs[/nsecs][:int-secs[/int-nsecs]]...\n", argv[0]);
+
+    tidlist = calloc(argc - 1, sizeof(timer_t));
+    if (tidlist == NULL)
+        errExit("malloc");
+
+    /* Establish handler for notification signal */
+
+    sa.sa_flags = SA_SIGINFO;
+    sa.sa_sigaction = handler;
+    sigemptyset(&sa.sa_mask);
+    if (sigaction(TIMER_SIG, &sa, NULL) == -1)
+        errExit("sigaction");
+
+    /* Create and start one timer for each command-line argument */
+
+    sev.sigev_notify = SIGEV_SIGNAL;    /* Notify via signal */
+    sev.sigev_signo = TIMER_SIG;        /* Notify using this signal */
+
+    for (j = 0; j < argc - 1; j++) {
+        itimerspecFromStr(argv[j + 1], &ts);
+
+        sev.sigev_value.sival_ptr = &tidlist[j];
+                /* Allows handler to get ID of this timer */
+
+        if (timer_create(CLOCK_REALTIME, &sev, &tidlist[j]) == -1)
+            errExit("timer_create");
+        printf("Timer ID: %ld (%s)\n", (long) tidlist[j], argv[j + 1]);
+
+        if (timer_settime(tidlist[j], 0, &ts, NULL) == -1)
+            errExit("timer_settime");
+    }
+
+    for (;;)                            /* Wait for incoming timer signals */
+        pause();
+}
diff --git a/timers/ptmr_sigev_thread.c b/timers/ptmr_sigev_thread.c
new file mode 100644 (file)
index 0000000..f13ee51
--- /dev/null
@@ -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 <signal.h>
+#include <time.h>
+#include <pthread.h>
+#include "curr_time.h"              /* Declares currTime() */
+#include "tlpi_hdr.h"
+#include "itimerspec_from_str.h"    /* Declares itimerspecFromStr() */
+
+static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
+
+static int expireCnt = 0;           /* Number of expirations of all timers */
+
+static void                         /* Thread notification function */
+threadFunc(union sigval sv)
+{
+    timer_t *tidptr;
+    int s;
+
+    tidptr = sv.sival_ptr;
+
+    printf("[%s] Thread notify\n", currTime("%T"));
+    printf("    timer ID=%ld\n", (long) *tidptr);
+    printf("    timer_getoverrun()=%d\n", timer_getoverrun(*tidptr));
+
+    /* Increment counter variable shared with main thread and signal
+       condition variable to notify main thread of the change. */
+
+    s = pthread_mutex_lock(&mtx);
+    if (s != 0)
+        errExitEN(s, "pthread_mutex_lock");
+
+    expireCnt += 1 + timer_getoverrun(*tidptr);
+
+    s = pthread_mutex_unlock(&mtx);
+    if (s != 0)
+        errExitEN(s, "pthread_mutex_unlock");
+
+    s = pthread_cond_signal(&cond);
+    if (s != 0)
+        errExitEN(s, "pthread_cond_signal");
+}
+
+int
+main(int argc, char *argv[])
+{
+    struct sigevent sev;
+    struct itimerspec ts;
+    timer_t *tidlist;
+    int s, j;
+
+    if (argc < 2)
+        usageErr("%s secs[/nsecs][:int-secs[/int-nsecs]]...\n", argv[0]);
+
+    tidlist = calloc(argc - 1, sizeof(timer_t));
+    if (tidlist == NULL)
+        errExit("malloc");
+
+    sev.sigev_notify = SIGEV_THREAD;            /* Notify via thread */
+    sev.sigev_notify_function = threadFunc;     /* Thread start function */
+    sev.sigev_notify_attributes = NULL;
+            /* Could be pointer to pthread_attr_t structure */
+
+    /* Create and start one timer for each command-line argument */
+
+    for (j = 0; j < argc - 1; j++) {
+        itimerspecFromStr(argv[j + 1], &ts);
+
+        sev.sigev_value.sival_ptr = &tidlist[j];
+                /* Passed as argument to threadFunc() */
+
+        if (timer_create(CLOCK_REALTIME, &sev, &tidlist[j]) == -1)
+            errExit("timer_create");
+        printf("Timer ID: %ld (%s)\n", (long) tidlist[j], argv[j + 1]);
+
+        if (timer_settime(tidlist[j], 0, &ts, NULL) == -1)
+            errExit("timer_settime");
+    }
+
+    /* The main thread waits on a condition variable that is signaled
+       on each invocation of the thread notification function. We
+       print a message so that the user can see that this occurred. */
+
+    s = pthread_mutex_lock(&mtx);
+    if (s != 0)
+        errExitEN(s, "pthread_mutex_lock");
+
+    for (;;) {
+        s = pthread_cond_wait(&cond, &mtx);
+        if (s != 0)
+            errExitEN(s, "pthread_cond_wait");
+        printf("main(): expireCnt = %d\n", expireCnt);
+    }
+}
diff --git a/timers/real_timer.c b/timers/real_timer.c
new file mode 100644 (file)
index 0000000..d1739b7
--- /dev/null
@@ -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 <signal.h>
+#include <sys/time.h>
+#include <time.h>
+#include "tlpi_hdr.h"
+
+static volatile sig_atomic_t gotAlarm = 0;
+                        /* Set nonzero on receipt of SIGALRM */
+
+/* Retrieve and display the real time, and (if 'includeTimer' is
+   TRUE) the current value and interval for the ITIMER_REAL timer */
+
+static void
+displayTimes(const char *msg, Boolean includeTimer)
+{
+    struct itimerval itv;
+    static struct timeval start;
+    struct timeval curr;
+    static int callNum = 0;             /* Number of calls to this function */
+
+    if (callNum == 0)                   /* Initialize elapsed time meter */
+        if (gettimeofday(&start, NULL) == -1)
+            errExit("gettimeofday");
+
+    if (callNum % 20 == 0)              /* Print header every 20 lines */
+        printf("       Elapsed   Value Interval\n");
+
+    if (gettimeofday(&curr, NULL) == -1)
+        errExit("gettimeofday");
+    printf("%-7s %6.2f", msg, curr.tv_sec - start.tv_sec +
+                        (curr.tv_usec - start.tv_usec) / 1000000.0);
+
+    if (includeTimer) {
+        if (getitimer(ITIMER_REAL, &itv) == -1)
+            errExit("getitimer");
+        printf("  %6.2f  %6.2f",
+                itv.it_value.tv_sec + itv.it_value.tv_usec / 1000000.0,
+                itv.it_interval.tv_sec + itv.it_interval.tv_usec / 1000000.0);
+    }
+
+    printf("\n");
+    callNum++;
+}
+
+static void
+sigalrmHandler(int sig)
+{
+    gotAlarm = 1;
+}
+
+int
+main(int argc, char *argv[])
+{
+    struct itimerval itv;
+    clock_t prevClock;
+    int maxSigs;                /* Number of signals to catch before exiting */
+    int sigCnt;                 /* Number of signals so far caught */
+    struct sigaction sa;
+
+    if (argc > 1 && strcmp(argv[1], "--help") == 0)
+        usageErr("%s [secs [usecs [int-secs [int-usecs]]]]\n", argv[0]);
+
+    sigCnt = 0;
+
+    sigemptyset(&sa.sa_mask);
+    sa.sa_flags = 0;
+    sa.sa_handler = sigalrmHandler;
+    if (sigaction(SIGALRM, &sa, NULL) == -1)
+        errExit("sigaction");
+
+    /* Set timer from the command-line arguments */
+
+    itv.it_value.tv_sec = (argc > 1) ? getLong(argv[1], 0, "secs") : 2;
+    itv.it_value.tv_usec = (argc > 2) ? getLong(argv[2], 0, "usecs") : 0;
+    itv.it_interval.tv_sec = (argc > 3) ? getLong(argv[3], 0, "int-secs") : 0;
+    itv.it_interval.tv_usec = (argc > 4) ? getLong(argv[4], 0, "int-usecs") : 0;
+
+    /* Exit after 3 signals, or on first signal if interval is 0 */
+
+    maxSigs = (itv.it_interval.tv_sec == 0 &&
+                itv.it_interval.tv_usec == 0) ? 1 : 3;
+
+    displayTimes("START:", FALSE);
+    if (setitimer(ITIMER_REAL, &itv, NULL) == -1)
+        errExit("setitimer");
+
+    prevClock = clock();
+    sigCnt = 0;
+
+    for (;;) {
+
+        /* Inner loop consumes at least 0.5 seconds CPU time */
+
+        while (((clock() - prevClock) * 10 / CLOCKS_PER_SEC) < 5) {
+            if (gotAlarm) {                     /* Did we get a signal? */
+                gotAlarm = 0;
+                displayTimes("ALARM:", TRUE);
+
+                sigCnt++;
+                if (sigCnt >= maxSigs) {
+                    printf("That's all folks\n");
+                    exit(EXIT_SUCCESS);
+                }
+            }
+        }
+
+        prevClock = clock();
+        displayTimes("Main: ", TRUE);
+    }
+}
diff --git a/timers/t_clock_nanosleep.c b/timers/t_clock_nanosleep.c
new file mode 100644 (file)
index 0000000..12d376c
--- /dev/null
@@ -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 <sys/time.h>
+#include <time.h>
+#include <signal.h>
+#include "tlpi_hdr.h"
+
+static void
+sigintHandler(int sig)
+{
+    return;                             /* Just interrupt clock_nanosleep() */
+}
+
+int
+main(int argc, char *argv[])
+{
+    struct timeval start, finish;
+    struct timespec request, remain;
+    struct sigaction sa;
+    int s, flags;
+
+    if (argc < 3 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s secs nanosecs [a]\n", argv[0]);
+
+    /* Allow SIGINT handler to interrupt clock_nanosleep() */
+
+    sigemptyset(&sa.sa_mask);
+    sa.sa_flags = 0;
+    sa.sa_handler = sigintHandler;
+    if (sigaction(SIGINT, &sa, NULL) == -1)
+        errExit("sigaction");
+
+    /* If more than three command-line arguments, use TIMER_ABSTIME flag */
+
+    flags = (argc > 3) ? TIMER_ABSTIME : 0;
+
+    if (flags == TIMER_ABSTIME) {
+        if (clock_gettime(CLOCK_REALTIME, &request) == -1)
+            errExit("clock_gettime");
+        printf("Initial CLOCK_REALTIME value: %ld.%09ld\n",
+                (long) request.tv_sec, (long) request.tv_nsec);
+
+        request.tv_sec  += getLong(argv[1], 0, "secs");
+        request.tv_nsec += getLong(argv[2], 0, "nanosecs");
+        if (request.tv_nsec >= 1000000000) {
+            request.tv_sec += request.tv_nsec / 1000000000;
+            request.tv_nsec %= 1000000000;
+        }
+
+    } else {                    /* Relative sleep */
+        request.tv_sec  = getLong(argv[1], 0, "secs");
+        request.tv_nsec = getLong(argv[2], 0, "nanosecs");
+    }
+
+    if (gettimeofday(&start, NULL) == -1)
+        errExit("gettimeofday");
+
+    for (;;) {
+        s = clock_nanosleep(CLOCK_REALTIME, flags, &request, &remain);
+        if (s != 0 && s != EINTR)
+            errExitEN(s, "clock_nanosleep");
+
+        if (s == EINTR)
+            printf("Interrupted... ");
+
+        if (gettimeofday(&finish, NULL) == -1)
+            errExit("gettimeofday");
+        printf("Slept: %.6f secs", finish.tv_sec - start.tv_sec +
+                        (finish.tv_usec - start.tv_usec) / 1000000.0);
+
+        if (s == 0)
+            break;                      /* sleep completed */
+
+        if (flags != TIMER_ABSTIME) {
+            printf("... Remaining: %ld.%09ld",
+                    (long) remain.tv_sec, remain.tv_nsec);
+
+            request = remain;
+        }
+
+        printf("... Restarting\n");
+    }
+
+    printf("\nSleep complete\n");
+    exit(EXIT_SUCCESS);
+}
diff --git a/timers/t_nanosleep.c b/timers/t_nanosleep.c
new file mode 100644 (file)
index 0000000..a609405
--- /dev/null
@@ -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 <sys/time.h>
+#include <time.h>
+#include <signal.h>
+#include "tlpi_hdr.h"
+
+static void
+sigintHandler(int sig)
+{
+    return;                     /* Just interrupt nanosleep() */
+}
+
+int
+main(int argc, char *argv[])
+{
+    struct timeval start, finish;
+    struct timespec request, remain;
+    struct sigaction sa;
+    int s;
+
+    if (argc != 3 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s secs nanosecs\n", argv[0]);
+
+    request.tv_sec = getLong(argv[1], 0, "secs");
+    request.tv_nsec = getLong(argv[2], 0, "nanosecs");
+
+    /* Allow SIGINT handler to interrupt nanosleep() */
+
+    sigemptyset(&sa.sa_mask);
+    sa.sa_flags = 0;
+    sa.sa_handler = sigintHandler;
+    if (sigaction(SIGINT, &sa, NULL) == -1)
+        errExit("sigaction");
+
+    if (gettimeofday(&start, NULL) == -1)
+        errExit("gettimeofday");
+
+    for (;;) {
+        s = nanosleep(&request, &remain);
+        if (s == -1 && errno != EINTR)
+            errExit("nanosleep");
+
+        if (gettimeofday(&finish, NULL) == -1)
+            errExit("gettimeofday");
+        printf("Slept for: %9.6f secs\n", finish.tv_sec - start.tv_sec +
+                        (finish.tv_usec - start.tv_usec) / 1000000.0);
+
+        if (s == 0)
+            break;                      /* nanosleep() completed */
+
+        printf("Remaining: %2ld.%09ld\n", (long) remain.tv_sec, remain.tv_nsec);
+        request = remain;               /* Next sleep is with remaining time */
+    }
+
+    printf("Sleep complete\n");
+    exit(EXIT_SUCCESS);
+}
diff --git a/timers/timed_read.c b/timers/timed_read.c
new file mode 100644 (file)
index 0000000..59c1d2d
--- /dev/null
@@ -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 <signal.h>
+#include "tlpi_hdr.h"
+
+#define BUF_SIZE 200
+
+static void     /* SIGALRM handler: interrupts blocked system call */
+handler(int sig)
+{
+    printf("Caught signal\n");          /* UNSAFE (see Section 21.1.2) */
+}
+
+int
+main(int argc, char *argv[])
+{
+    struct sigaction sa;
+    char buf[BUF_SIZE];
+    ssize_t numRead;
+    int savedErrno;
+
+    if (argc > 1 && strcmp(argv[1], "--help") == 0)
+        usageErr("%s [num-secs [restart-flag]]\n", argv[0]);
+
+    /* Set up handler for SIGALRM. Allow system calls to be interrupted,
+       unless second command-line argument was supplied. */
+
+    sa.sa_flags = (argc > 2) ? SA_RESTART : 0;
+    sigemptyset(&sa.sa_mask);
+    sa.sa_handler = handler;
+    if (sigaction(SIGALRM, &sa, NULL) == -1)
+        errExit("sigaction");
+
+    alarm((argc > 1) ? getInt(argv[1], GN_NONNEG, "num-secs") : 10);
+
+    numRead = read(STDIN_FILENO, buf, BUF_SIZE);
+
+    savedErrno = errno;                 /* In case alarm() changes it */
+    alarm(0);                           /* Ensure timer is turned off */
+    errno = savedErrno;
+
+    /* Determine result of read() */
+
+    if (numRead == -1) {
+        if (errno == EINTR)
+            printf("Read timed out\n");
+        else
+            errMsg("read");
+    } else {
+        printf("Successful read (%ld bytes): %.*s",
+                (long) numRead, (int) numRead, buf);
+    }
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/tty/Makefile b/tty/Makefile
new file mode 100644 (file)
index 0000000..25f0477
--- /dev/null
@@ -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 (file)
index 0000000..5a2245e
--- /dev/null
@@ -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 <signal.h>
+#include <termios.h>
+#include <sys/ioctl.h>
+#include "tlpi_hdr.h"
+
+static void
+sigwinchHandler(int sig)
+{
+}
+
+int
+main(int argc, char *argv[])
+{
+    struct winsize ws;
+    struct sigaction sa;
+
+    sigemptyset(&sa.sa_mask);
+    sa.sa_flags = 0;
+    sa.sa_handler = sigwinchHandler;
+    if (sigaction(SIGWINCH, &sa, NULL) == -1)
+        errExit("sigaction");
+
+    for (;;) {
+        pause();                        /* Wait for SIGWINCH signal */
+
+        if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == -1)
+            errExit("ioctl");
+        printf("Caught SIGWINCH, new window size: "
+                "%d rows * %d columns\n", ws.ws_row, ws.ws_col);
+    }
+}
diff --git a/tty/new_intr.c b/tty/new_intr.c
new file mode 100644 (file)
index 0000000..7b1159b
--- /dev/null
@@ -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 <termios.h>
+#include <ctype.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    struct termios tp;
+    int intrChar;
+
+    if (argc > 1 && strcmp(argv[1], "--help") == 0)
+        usageErr("%s [intr-char]\n", argv[0]);
+
+    /* Determine new INTR setting from command line */
+
+    if (argc == 1) {                                    /* Disable */
+        intrChar = fpathconf(STDIN_FILENO, _PC_VDISABLE);
+        if (intrChar == -1)
+            errExit("Couldn't determine VDISABLE");
+    } else if (isdigit((unsigned char) argv[1][0])) {
+        intrChar = strtoul(argv[1], NULL, 0);           /* Allows hex, octal */
+    } else {                                            /* Literal character */
+        intrChar = argv[1][0];
+    }
+
+    /* Fetch current terminal settings, modify INTR character, and
+       push changes back to the terminal driver */
+
+    if (tcgetattr(STDIN_FILENO, &tp) == -1)
+        errExit("tcgetattr");
+    tp.c_cc[VINTR] = intrChar;
+    if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &tp) == -1)
+        errExit("tcsetattr");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/tty/no_echo.c b/tty/no_echo.c
new file mode 100644 (file)
index 0000000..cc864c7
--- /dev/null
@@ -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 <termios.h>
+#include "tlpi_hdr.h"
+
+#define BUF_SIZE 100
+
+int
+main(int argc, char *argv[])
+{
+    struct termios tp, save;
+    char buf[BUF_SIZE];
+
+    /* Retrieve current terminal settings, turn echoing off */
+
+    if (tcgetattr(STDIN_FILENO, &tp) == -1)
+        errExit("tcgetattr");
+    save = tp;                          /* So we can restore settings later */
+    tp.c_lflag &= ~ECHO;                /* ECHO off, other bits unchanged */
+    if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &tp) == -1)
+        errExit("tcsetattr");
+
+    /* Read some input and then display it back to the user */
+
+    printf("Enter text: ");
+    fflush(stdout);
+    if (fgets(buf, BUF_SIZE, stdin) == NULL)
+        printf("Got end-of-file/error on fgets()\n");
+    else
+        printf("\nRead: %s", buf);
+
+    /* Restore original terminal settings */
+
+    if (tcsetattr(STDIN_FILENO, TCSANOW, &save) == -1)
+        errExit("tcsetattr");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/tty/test_tty_functions.c b/tty/test_tty_functions.c
new file mode 100644 (file)
index 0000000..012c1a1
--- /dev/null
@@ -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 <termios.h>
+#include <signal.h>
+#include <ctype.h>
+#include "tty_functions.h"              /* Declarations of ttySetCbreak()
+                                           and ttySetRaw() */
+#include "tlpi_hdr.h"
+
+static struct termios userTermios;
+                        /* Terminal settings as defined by user */
+
+static void             /* General handler: restore tty settings and exit */
+handler(int sig)
+{
+    if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &userTermios) == -1)
+        errExit("tcsetattr");
+    _exit(EXIT_SUCCESS);
+}
+
+static void             /* Handler for SIGTSTP */
+tstpHandler(int sig)
+{
+    struct termios ourTermios;          /* To save our tty settings */
+    sigset_t tstpMask, prevMask;
+    struct sigaction sa;
+    int savedErrno;
+
+    savedErrno = errno;                 /* We might change 'errno' here */
+
+    /* Save current terminal settings, restore terminal to
+       state at time of program startup */
+
+    if (tcgetattr(STDIN_FILENO, &ourTermios) == -1)
+        errExit("tcgetattr");
+    if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &userTermios) == -1)
+        errExit("tcsetattr");
+
+    /* Set the disposition of SIGTSTP to the default, raise the signal
+       once more, and then unblock it so that we actually stop */
+
+    if (signal(SIGTSTP, SIG_DFL) == SIG_ERR)
+        errExit("signal");
+    raise(SIGTSTP);
+
+    sigemptyset(&tstpMask);
+    sigaddset(&tstpMask, SIGTSTP);
+    if (sigprocmask(SIG_UNBLOCK, &tstpMask, &prevMask) == -1)
+        errExit("sigprocmask");
+
+    /* Execution resumes here after SIGCONT */
+
+    if (sigprocmask(SIG_SETMASK, &prevMask, NULL) == -1)
+        errExit("sigprocmask");         /* Reblock SIGTSTP */
+
+    sigemptyset(&sa.sa_mask);           /* Reestablish handler */
+    sa.sa_flags = SA_RESTART;
+    sa.sa_handler = tstpHandler;
+    if (sigaction(SIGTSTP, &sa, NULL) == -1)
+        errExit("sigaction");
+
+    /* The user may have changed the terminal settings while we were
+       stopped; save the settings so we can restore them later */
+
+    if (tcgetattr(STDIN_FILENO, &userTermios) == -1)
+        errExit("tcgetattr");
+
+    /* Restore our terminal settings */
+
+    if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &ourTermios) == -1)
+        errExit("tcsetattr");
+
+    errno = savedErrno;
+}
+
+int
+main(int argc, char *argv[])
+{
+    char ch;
+    struct sigaction sa, prev;
+    ssize_t n;
+
+    sigemptyset(&sa.sa_mask);
+    sa.sa_flags = SA_RESTART;
+
+    if (argc > 1) {                     /* Use cbreak mode */
+        if (ttySetCbreak(STDIN_FILENO, &userTermios) == -1)
+            errExit("ttySetCbreak");
+
+        /* Terminal special characters can generate signals in cbreak
+           mode. Catch them so that we can adjust the terminal mode.
+           We establish handlers only if the signals are not being ignored. */
+
+        sa.sa_handler = handler;
+
+        if (sigaction(SIGQUIT, NULL, &prev) == -1)
+            errExit("sigaction");
+        if (prev.sa_handler != SIG_IGN)
+            if (sigaction(SIGQUIT, &sa, NULL) == -1)
+                errExit("sigaction");
+
+        if (sigaction(SIGINT, NULL, &prev) == -1)
+            errExit("sigaction");
+        if (prev.sa_handler != SIG_IGN)
+            if (sigaction(SIGINT, &sa, NULL) == -1)
+                errExit("sigaction");
+
+        sa.sa_handler = tstpHandler;
+
+        if (sigaction(SIGTSTP, NULL, &prev) == -1)
+            errExit("sigaction");
+        if (prev.sa_handler != SIG_IGN)
+            if (sigaction(SIGTSTP, &sa, NULL) == -1)
+                errExit("sigaction");
+    } else {                            /* Use raw mode */
+        if (ttySetRaw(STDIN_FILENO, &userTermios) == -1)
+            errExit("ttySetRaw");
+    }
+
+    sa.sa_handler = handler;
+    if (sigaction(SIGTERM, &sa, NULL) == -1)
+        errExit("sigaction");
+
+    setbuf(stdout, NULL);               /* Disable stdout buffering */
+
+    for (;;) {                          /* Read and echo stdin */
+        n = read(STDIN_FILENO, &ch, 1);
+        if (n == -1) {
+            errMsg("read");
+            break;
+        }
+
+        if (n == 0)                     /* Can occur after terminal disconnect */
+            break;
+
+        if (isalpha((unsigned char) ch))        /* Letters --> lowercase */
+            putchar(tolower((unsigned char) ch));
+        else if (ch == '\n' || ch == '\r')
+            putchar(ch);
+        else if (iscntrl((unsigned char) ch))
+            printf("^%c", ch ^ 64);     /* Echo Control-A as ^A, etc. */
+        else
+            putchar('*');               /* All other chars as '*' */
+
+        if (ch == 'q')                  /* Quit loop */
+            break;
+    }
+
+    if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &userTermios) == -1)
+        errExit("tcsetattr");
+    exit(EXIT_SUCCESS);
+}
diff --git a/tty/tty_functions.c b/tty/tty_functions.c
new file mode 100644 (file)
index 0000000..726cc8a
--- /dev/null
@@ -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 <termios.h>
+#include <unistd.h>
+#include "tty_functions.h"              /* Declares functions defined here */
+
+/* Place terminal referred to by 'fd' in cbreak mode (noncanonical mode
+   with echoing turned off). This function assumes that the terminal is
+   currently in cooked mode (i.e., we shouldn't call it if the terminal
+   is currently in raw mode, since it does not undo all of the changes
+   made by the ttySetRaw() function below). Return 0 on success, or -1
+   on error. If 'prevTermios' is non-NULL, then use the buffer to which
+   it points to return the previous terminal settings. */
+
+int
+ttySetCbreak(int fd, struct termios *prevTermios)
+{
+    struct termios t;
+
+    if (tcgetattr(fd, &t) == -1)
+        return -1;
+
+    if (prevTermios != NULL)
+        *prevTermios = t;
+
+    t.c_lflag &= ~(ICANON | ECHO);
+    t.c_lflag |= ISIG;
+
+    t.c_iflag &= ~ICRNL;
+
+    t.c_cc[VMIN] = 1;                   /* Character-at-a-time input */
+    t.c_cc[VTIME] = 0;                  /* with blocking */
+
+    if (tcsetattr(fd, TCSAFLUSH, &t) == -1)
+        return -1;
+
+    return 0;
+}
+
+/* Place terminal referred to by 'fd' in raw mode (noncanonical mode
+   with all input and output processing disabled). Return 0 on success,
+   or -1 on error. If 'prevTermios' is non-NULL, then use the buffer to
+   which it points to return the previous terminal settings. */
+
+int
+ttySetRaw(int fd, struct termios *prevTermios)
+{
+    struct termios t;
+
+    if (tcgetattr(fd, &t) == -1)
+        return -1;
+
+    if (prevTermios != NULL)
+        *prevTermios = t;
+
+    t.c_lflag &= ~(ICANON | ISIG | IEXTEN | ECHO);
+                        /* Noncanonical mode, disable signals, extended
+                           input processing, and echoing */
+
+    t.c_iflag &= ~(BRKINT | ICRNL | IGNBRK | IGNCR | INLCR |
+                      INPCK | ISTRIP | IXON | PARMRK);
+                        /* Disable special handling of CR, NL, and BREAK.
+                           No 8th-bit stripping or parity error handling.
+                           Disable START/STOP output flow control. */
+
+    t.c_oflag &= ~OPOST;                /* Disable all output processing */
+
+    t.c_cc[VMIN] = 1;                   /* Character-at-a-time input */
+    t.c_cc[VTIME] = 0;                  /* with blocking */
+
+    if (tcsetattr(fd, TCSAFLUSH, &t) == -1)
+        return -1;
+
+    return 0;
+}
diff --git a/tty/tty_functions.h b/tty/tty_functions.h
new file mode 100644 (file)
index 0000000..905496b
--- /dev/null
@@ -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 <termios.h>
+
+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 (file)
index 0000000..cc77b37
--- /dev/null
@@ -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 <dirent.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+/* Helper function for ttyname(). We do most of the real work here.
+   Look in 'devDir' for the terminal device name corresponding to the
+   stat structure given in 'fdStat' (which the caller must ensure
+   derives from a terminal device).
+
+   Return the device name (as a statically-allocated) string if
+   found, or NULL if the device is not found or an error occurs */
+
+static char *
+ttynameCheckDir(const struct stat *fdStat, const char *devDir)
+{
+    DIR *dirh;
+    struct dirent *dent;
+    static char *ttyPath;               /* Currently checked entry; also used
+                                           to return tty name, if found */
+    static int ttyLen = 0;              /* Length of ttyPath */
+    struct stat devStat;                /* stat entry for ttyPath */
+    int found;                          /* True if we find device entry */
+    int requiredLen;
+
+    if (ttyLen == 0) {                  /* First call - allocate ttyPath */
+        ttyPath = malloc(50);
+        if (ttyPath == NULL)
+            return NULL;
+        ttyLen = 50;
+    }
+
+    dirh = opendir(devDir);
+    if (dirh == NULL)
+        return NULL;
+
+    /* We walk through each file in /dev looking for an entry whose
+       device ID (st_rdev) matches the device ID corresponding to 'fd'.
+       To do this, we construct a pathname for each entry in /dev, and
+       then call stat() on that pathname. This is somewhat expensive.
+       The glibc implementation of ttyname() performs an optimization:
+       it performs a first pass of the entries in /dev, performing
+       a stat() call only if fdStat->st_ino == dent->d_ino. This speeds
+       the search (since many calls to stat() are avoided), but is not
+       guaranteed to work in every case (e.g., if there are symbolic
+       links in /dev). Therefore, if the first pass fails to find a
+       matching device, glibc's ttyname() performs a second pass
+       without the st_ino check (i.e., like we do below). */
+
+    found = 0;
+    while ((dent = readdir(dirh)) != NULL) {
+        requiredLen = strlen(devDir) + 1 + strlen(dent->d_name) + 1;
+
+        if (requiredLen > ttyLen) {     /* Resize ttyPath if required */
+            ttyPath = realloc(ttyPath, requiredLen);
+            if (ttyPath == NULL)
+                break;
+            ttyLen = requiredLen;
+        }
+
+        snprintf(ttyPath, ttyLen, "%s/%s", devDir, dent->d_name);
+
+        if (stat(ttyPath, &devStat) == -1)
+            continue;                   /* Ignore unstat-able entries */
+
+        if (S_ISCHR(devStat.st_mode) &&
+                fdStat->st_rdev == devStat.st_rdev) {
+            found = 1;
+            break;
+        }
+    }
+
+    closedir(dirh);
+
+    return found ? ttyPath : NULL;
+}
+
+/* Return the name of the terminal associated with 'fd' (in a
+   statically-allocated string that is overwritten on subsequent
+   calls), or NULL if it is not a terminal or an error occurs */
+
+char *
+ttyname(int fd)
+{
+    char *d;
+    struct stat fdStat;                 /* stat entry for fd */
+
+    if (!isatty(fd))                    /* Is fd even a terminal? */
+        return NULL;
+
+    if (fstat(fd, &fdStat) == -1)
+        return NULL;
+
+    if (!S_ISCHR(fdStat.st_mode))       /* Is fd even a character device? */
+        return NULL;
+
+    /* First check for a pseudoterminal entry in the /dev/pts
+       directory. If that fails, try looking in /dev.
+       (We check the directories in this order for efficiency:
+       /dev/pts is small, and will contain the required device if
+       fd refers to an X-terminal or similar.)  */
+
+    d = ttynameCheckDir(&fdStat, "/dev/pts");
+    return (d != NULL) ? d : ttynameCheckDir(&fdStat, "/dev");
+}
diff --git a/users_groups/Makefile b/users_groups/Makefile
new file mode 100644 (file)
index 0000000..879664b
--- /dev/null
@@ -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 (file)
index 0000000..5807e84
--- /dev/null
@@ -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 <unistd.h> */
+#ifndef _XOPEN_SOURCE
+#define _XOPEN_SOURCE   /* Get crypt() declaration from <unistd.h> */
+#endif
+#endif
+#include <unistd.h>
+#include <limits.h>
+#include <pwd.h>
+#include <shadow.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    char *username, *password, *encrypted, *p;
+    struct passwd *pwd;
+    struct spwd *spwd;
+    Boolean authOk;
+    size_t len;
+    long lnmax;
+
+    /* Determine size of buffer required for a username, and allocate it */
+
+    lnmax = sysconf(_SC_LOGIN_NAME_MAX);
+    if (lnmax == -1)                    /* If limit is indeterminate */
+        lnmax = 256;                    /* make a guess */
+
+    username = malloc(lnmax);
+    if (username == NULL)
+        errExit("malloc");
+
+    printf("Username: ");
+    fflush(stdout);
+    if (fgets(username, lnmax, stdin) == NULL)
+        exit(EXIT_FAILURE);             /* Exit on EOF */
+
+    len = strlen(username);
+    if (username[len - 1] == '\n')
+        username[len - 1] = '\0';       /* Remove trailing '\n' */
+
+    /* Look up password and shadow password records for username */
+
+    pwd = getpwnam(username);
+    if (pwd == NULL)
+        fatal("couldn't get password record");
+    spwd = getspnam(username);
+    if (spwd == NULL && errno == EACCES)
+        fatal("no permission to read shadow password file");
+
+    if (spwd != NULL)           /* If there is a shadow password record */
+        pwd->pw_passwd = spwd->sp_pwdp;     /* Use the shadow password */
+
+    password = getpass("Password: ");
+
+    /* Encrypt password and erase cleartext version immediately */
+
+    encrypted = crypt(password, pwd->pw_passwd);
+    for (p = password; *p != '\0'; )
+        *p++ = '\0';
+
+    if (encrypted == NULL)
+        errExit("crypt");
+
+    authOk = strcmp(encrypted, pwd->pw_passwd) == 0;
+    if (!authOk) {
+        printf("Incorrect password\n");
+        exit(EXIT_FAILURE);
+    }
+
+    printf("Successfully authenticated: UID=%ld\n", (long) pwd->pw_uid);
+
+    /* Now do authenticated work... */
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/users_groups/t_getpwent.c b/users_groups/t_getpwent.c
new file mode 100644 (file)
index 0000000..387800c
--- /dev/null
@@ -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 <pwd.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    struct passwd *pwd;
+
+    while ((pwd = getpwent()) != NULL)
+        printf("%-8s %5ld\n", pwd->pw_name, (long) pwd->pw_uid);
+    endpwent();
+    exit(EXIT_SUCCESS);
+}
diff --git a/users_groups/t_getpwnam_r.c b/users_groups/t_getpwnam_r.c
new file mode 100644 (file)
index 0000000..4a9d686
--- /dev/null
@@ -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 <pwd.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    struct passwd pwd;
+    struct passwd *result;
+    char *buf;
+    size_t bufSize;
+    int s;
+
+    if (argc != 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s username\n", argv[0]);
+
+    bufSize = sysconf(_SC_GETPW_R_SIZE_MAX);
+    buf = malloc(bufSize);
+    if (buf == NULL)
+        errExit("malloc %d", bufSize);
+
+    s = getpwnam_r(argv[1], &pwd, buf, bufSize, &result);
+    if (s != 0)
+        errExitEN(s, "getpwnam_r");
+
+    if (result != NULL)
+        printf("Name: %s\n", pwd.pw_gecos);
+    else
+        printf("Not found\n");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/users_groups/ugid_functions.c b/users_groups/ugid_functions.c
new file mode 100644 (file)
index 0000000..fe11872
--- /dev/null
@@ -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 <pwd.h>
+#include <grp.h>
+#include <ctype.h>
+#include "ugid_functions.h"     /* Declares functions defined here */
+
+char *          /* Return name corresponding to 'uid', or NULL on error */
+userNameFromId(uid_t uid)
+{
+    struct passwd *pwd;
+
+    pwd = getpwuid(uid);
+    return (pwd == NULL) ? NULL : pwd->pw_name;
+}
+
+uid_t           /* Return UID corresponding to 'name', or -1 on error */
+userIdFromName(const char *name)
+{
+    struct passwd *pwd;
+    uid_t u;
+    char *endptr;
+
+    if (name == NULL || *name == '\0')  /* On NULL or empty string */
+        return -1;                      /* return an error */
+
+    u = strtol(name, &endptr, 10);      /* As a convenience to caller */
+    if (*endptr == '\0')                /* allow a numeric string */
+        return u;
+
+    pwd = getpwnam(name);
+    if (pwd == NULL)
+        return -1;
+
+    return pwd->pw_uid;
+}
+
+char *          /* Return name corresponding to 'gid', or NULL on error */
+groupNameFromId(gid_t gid)
+{
+    struct group *grp;
+
+    grp = getgrgid(gid);
+    return (grp == NULL) ? NULL : grp->gr_name;
+}
+
+gid_t           /* Return GID corresponding to 'name', or -1 on error */
+groupIdFromName(const char *name)
+{
+    struct group *grp;
+    gid_t g;
+    char *endptr;
+
+    if (name == NULL || *name == '\0')  /* On NULL or empty string */
+        return -1;                      /* return an error */
+
+    g = strtol(name, &endptr, 10);      /* As a convenience to caller */
+    if (*endptr == '\0')                /* allow a numeric string */
+        return g;
+
+    grp = getgrnam(name);
+    if (grp == NULL)
+        return -1;
+
+    return grp->gr_gid;
+}
diff --git a/users_groups/ugid_functions.h b/users_groups/ugid_functions.h
new file mode 100644 (file)
index 0000000..9ff36e3
--- /dev/null
@@ -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 (file)
index 0000000..4ce0ef7
--- /dev/null
@@ -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 (file)
index 0000000..94635cc
--- /dev/null
@@ -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 <sys/mman.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "tlpi_hdr.h"
+
+#define MAP_SIZE 4096
+#define WRITE_SIZE 10
+
+int
+main(int argc, char *argv[])
+{
+    char *addr;
+    int fd, j;
+
+    setbuf(stdout, NULL);
+
+    if (argc != 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s file\n", argv[0]);
+
+    unlink(argv[1]);
+    fd = open(argv[1], O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
+    if (fd == -1)
+        errExit("open");
+
+    for (j = 0; j < MAP_SIZE; j++)
+        write(fd, "a", 1);
+    if (fsync(fd) == -1)
+        errExit("fsync");
+    close(fd);
+
+    fd = open(argv[1], O_RDWR);
+    if (fd == -1)
+        errExit("open");
+
+    addr = mmap(NULL, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
+    if (addr == MAP_FAILED)
+        errExit("mmap");
+
+    printf("After mmap:          ");
+    write(STDOUT_FILENO, addr, WRITE_SIZE);
+    printf("\n");
+
+    /* Copy-on-write semantics mean that the following modification
+       will create private copies of the pages for this process */
+
+    for (j = 0; j < MAP_SIZE; j++)
+        addr[j]++;
+
+    printf("After modification:  ");
+    write(STDOUT_FILENO, addr, WRITE_SIZE);
+    printf("\n");
+
+    /* After the following, the mapping contents revert to the original file
+       contents (if MADV_DONTNEED has destructive semantics, as on Linux) */
+
+    if (madvise(addr, MAP_SIZE, MADV_DONTNEED) == -1)
+        errExit("madvise");
+
+    printf("After MADV_DONTNEED: ");
+    write(STDOUT_FILENO, addr, WRITE_SIZE);
+    printf("\n");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/vmem/memlock.c b/vmem/memlock.c
new file mode 100644 (file)
index 0000000..6042828
--- /dev/null
@@ -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 <sys/mman.h> */
+#include <sys/mman.h>
+#include "tlpi_hdr.h"
+
+/* Display residency of pages in range [addr .. (addr + length - 1)] */
+
+static void
+displayMincore(char *addr, size_t length)
+{
+    unsigned char *vec;
+    long pageSize, numPages, j;
+
+#ifndef _SC_PAGESIZE
+    pageSize = getpagesize();   /* Some systems don't have _SC_PAGESIZE */
+#else
+    pageSize = sysconf(_SC_PAGESIZE);
+#endif
+
+    numPages = (length + pageSize - 1) / pageSize;
+    vec = malloc(numPages);
+    if (vec == NULL)
+        errExit("malloc");
+
+    if (mincore(addr, length, vec) == -1)
+        errExit("mincore");
+
+    for (j = 0; j < numPages; j++) {
+        if (j % 64 == 0)
+            printf("%s%10p: ", (j == 0) ? "" : "\n", addr + (j * pageSize));
+        printf("%c", (vec[j] & 1) ? '*' : '.');
+    }
+    printf("\n");
+
+    free(vec);
+}
+
+int
+main(int argc, char *argv[])
+{
+    char *addr;
+    size_t len, lockLen;
+    long pageSize, stepSize, j;
+
+    if (argc != 4 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s num-pages lock-page-step lock-page-len\n", argv[0]);
+
+#ifndef _SC_PAGESIZE
+    pageSize = getpagesize();
+    if (pageSize == -1)
+        errExit("getpagesize");
+#else
+    pageSize = sysconf(_SC_PAGESIZE);
+    if (pageSize == -1)
+        errExit("sysconf(_SC_PAGESIZE)");
+#endif
+
+    len =      getInt(argv[1], GN_GT_0, "num-pages") * pageSize;
+    stepSize = getInt(argv[2], GN_GT_0, "lock-page-step") * pageSize;
+    lockLen =  getInt(argv[3], GN_GT_0, "lock-page-len") * pageSize;
+
+    addr = mmap(NULL, len, PROT_READ, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
+    if (addr == MAP_FAILED)
+        errExit("mmap");
+
+    printf("Allocated %ld (%#lx) bytes starting at %p\n",
+            (long) len, (unsigned long) len, addr);
+
+    printf("Before mlock:\n");
+    displayMincore(addr, len);
+
+    /* Lock pages specified by command-line arguments into memory */
+
+    for (j = 0; j + lockLen <= len; j += stepSize)
+        if (mlock(addr + j, lockLen) == -1)
+            errExit("mlock");
+
+    printf("After mlock:\n");
+    displayMincore(addr, len);
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/vmem/t_mprotect.c b/vmem/t_mprotect.c
new file mode 100644 (file)
index 0000000..9c20896
--- /dev/null
@@ -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 <sys/mman.h> */
+#include <sys/mman.h>
+#include "tlpi_hdr.h"
+
+#define LEN (1024 * 1024)
+
+#define SHELL_FMT "cat /proc/%ld/maps | grep zero"
+#define CMD_SIZE (sizeof(SHELL_FMT) + 20)
+                            /* Allow extra space for integer string */
+
+int
+main(int argc, char *argv[])
+{
+    char cmd[CMD_SIZE];
+    char *addr;
+
+    /* Create an anonymous mapping with all access denied */
+
+    addr = mmap(NULL, LEN, PROT_NONE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
+    if (addr == MAP_FAILED)
+        errExit("mmap");
+
+    /* Display line from /proc/self/maps corresponding to mapping */
+
+    printf("Before mprotect()\n");
+    snprintf(cmd, CMD_SIZE, SHELL_FMT, (long) getpid());
+    system(cmd);
+
+    /* Change protection on memory to allow read and write access */
+
+    if (mprotect(addr, LEN, PROT_READ | PROT_WRITE) == -1)
+        errExit("mprotect");
+
+    printf("After mprotect()\n");
+    system(cmd);                /* Review protection via /proc/self/maps */
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/xattr/Makefile b/xattr/Makefile
new file mode 100644 (file)
index 0000000..095f9a4
--- /dev/null
@@ -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 (file)
index 0000000..60fa296
--- /dev/null
@@ -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 <sys/xattr.h>
+#include "tlpi_hdr.h"
+
+int
+main(int argc, char *argv[])
+{
+    char *value;
+
+    if (argc < 2 || strcmp(argv[1], "--help") == 0)
+        usageErr("%s file\n", argv[0]);
+
+    value = "The past is not dead.";
+    if (setxattr(argv[1], "user.x", value, strlen(value), 0) == -1)
+        errExit("setxattr");
+
+    value = "In fact, it's not even past.";
+    if (setxattr(argv[1], "user.y", value, strlen(value), 0) == -1)
+        errExit("setxattr");
+
+    exit(EXIT_SUCCESS);
+}
diff --git a/xattr/xattr_view.c b/xattr/xattr_view.c
new file mode 100644 (file)
index 0000000..69554d1
--- /dev/null
@@ -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 <sys/xattr.h>
+#include "tlpi_hdr.h"
+
+#define XATTR_SIZE 10000
+
+static void
+usageError(char *progName)
+{
+    fprintf(stderr, "Usage: %s [-x] file...\n", progName);
+    exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+    char list[XATTR_SIZE], value[XATTR_SIZE];
+    ssize_t listLen, valueLen;
+    int ns, j, k, opt;
+    Boolean hexDisplay;
+
+    hexDisplay = 0;
+    while ((opt = getopt(argc, argv, "x")) != -1) {
+        switch (opt) {
+        case 'x': hexDisplay = 1;       break;
+        case '?': usageError(argv[0]);
+        }
+    }
+
+    if (optind >= argc)
+        usageError(argv[0]);
+
+    for (j = optind; j < argc; j++) {
+        listLen = listxattr(argv[j], list, XATTR_SIZE);
+        if (listLen == -1)
+            errExit("listxattr");
+
+        printf("%s:\n", argv[j]);
+
+        /* Loop through all EA names, displaying name + value */
+
+        for (ns = 0; ns < listLen; ns += strlen(&list[ns]) + 1) {
+            printf("        name=%s; ", &list[ns]);
+
+            valueLen = getxattr(argv[j], &list[ns], value, XATTR_SIZE);
+            if (valueLen == -1) {
+                printf("couldn't get value");
+            } else if (!hexDisplay) {
+                printf("value=%.*s", (int) valueLen, value);
+            } else {
+                printf("value=");
+                for (k = 0; k < valueLen; k++)
+                    printf("%02x ", (unsigned int) value[k]);
+            }
+
+            printf("\n");
+        }
+
+        printf("\n");
+    }
+
+    exit(EXIT_SUCCESS);
+}