### -*-makefile-*-
###
-### Build script for nsict-utils
+### Build script for distorted-utils
###
### (c) 2008 Mark Wooding
###
space_SOURCES = space.c
dist_man_MANS += space.1
-## not
-bin_PROGRAMS += not
-not_SOURCES = not.c
-dist_man_MANS += not.1
-
-## if-mtu
-bin_PROGRAMS += if-mtu
-if_mtu_SOURCES = if-mtu.c
-dist_man_MANS += if-mtu.1
+## cdb tools
+if HAVE_LIBCDB
+bin_PROGRAMS += cdb-probe cdb-check-domain
+cdb_probe_SOURCES = cdb-probe.c
+cdb_probe_LDADD = -lcdb
+cdb_check_domain_SOURCES = cdb-check-domain.c
+cdb_check_domain_LDADD = -lcdb
+dist_man_MANS += cdb-probe.1 cdb-check-domain.1
+endif
## getpass
bin_PROGRAMS += getpass
getpass_SOURCES = getpass.c
dist_man_MANS += getpass.1
-## xtitle
-bin_PROGRAMS += xtitle
-xtitle_SOURCES = xtitle.c
-## !UNDOCUMENTED
+## gorp
+if HAVE_CATACOMB
+bin_PROGRAMS += gorp
+gorp_SOURCES = gorp.c
+gorp_LDADD = $(catacomb_LIBS)
+dist_man_MANS += gorp.1
+endif
-## pause
+## if-mtu
+bin_PROGRAMS += if-mtu
+if_mtu_SOURCES = if-mtu.c
+dist_man_MANS += if-mtu.1
+
+## locking
if HAVE_MLIB
-bin_PROGRAMS += pause
-pause_SOURCES = pause.c
-pause_LDADD = $(mLib_LIBS) $(MATH_LIBS)
-dist_man_MANS += pause.1
+bin_PROGRAMS += locking
+locking_SOURCES = locking.c
+locking_LDADD = $(mLib_LIBS)
+dist_man_MANS += locking.1
endif
## mtimeout
dist_man_MANS += mtimeout.1
endif
-## stamp
-if HAVE_MLIB
-bin_PROGRAMS += stamp
-stamp_SOURCES = stamp.c
-stamp_LDADD = $(mLib_LIBS)
-dist_man_MANS += stamp.1
-endif
+## not
+bin_PROGRAMS += not
+not_SOURCES = not.c
+dist_man_MANS += not.1
-## locking
+## pause
if HAVE_MLIB
-bin_PROGRAMS += locking
-locking_SOURCES = locking.c
-locking_LDADD = $(mLib_LIBS)
-dist_man_MANS += locking.1
+bin_PROGRAMS += pause
+pause_SOURCES = pause.c
+pause_LDADD = $(mLib_LIBS) $(MATH_LIBS)
+dist_man_MANS += pause.1
endif
## prlimit
endif
endif
-## gorp
-if HAVE_CATACOMB
-bin_PROGRAMS += gorp
-gorp_SOURCES = gorp.c
-gorp_LDADD = $(catacomb_LIBS)
-dist_man_MANS += gorp.1
+## sema
+if HAVE_MLIB
+bin_PROGRAMS += sema
+sema_SOURCES = sema.c
+sema_SOURCES += fence.c fence.h
+sema_SOURCES += timemax.cc timemax.h
+sema_LDADD = $(mLib_LIBS)
+sema_LINK = $(LINK) # don't need C++ libraries here
+dist_man_MANS += sema.1
endif
-## cdb tools
-if HAVE_LIBCDB
-bin_PROGRAMS += cdb-probe cdb-check-domain
-cdb_probe_SOURCES = cdb-probe.c
-cdb_probe_LDADD = -lcdb
-cdb_check_domain_SOURCES = cdb-check-domain.c
-cdb_check_domain_LDADD = -lcdb
-dist_man_MANS += cdb-probe.1 cdb-check-domain.1
+## stamp
+if HAVE_MLIB
+bin_PROGRAMS += stamp
+stamp_SOURCES = stamp.c
+stamp_LDADD = $(mLib_LIBS)
+dist_man_MANS += stamp.1
endif
## x86 model identification
endif
endif
+## xtitle
+bin_PROGRAMS += xtitle
+xtitle_SOURCES = xtitle.c
+dist_man_MANS += xtitle.1
+
###--------------------------------------------------------------------------
### Tools in scripts.
EXTRA_DIST += config/confsubst
## Shell scripts.
-dist_bin_SCRIPTS += check-sender
-dist_man_MANS += check-sender.1
-
dist_bin_SCRIPTS += buf
dist_man_MANS += buf.1
+dist_bin_SCRIPTS += check-sender
+dist_man_MANS += check-sender.1
+
dist_bin_SCRIPTS += create
dist_man_MANS += create.1
-dist_bin_SCRIPTS += z
-dist_man_MANS += z.1
-
bin_SCRIPTS += hush
man_MANS += hush.1
CLEANFILES += hush hush.1
$(SUBST) $(srcdir)/hush.1.in >$@.new $(SUBSTITUTIONS) && \
mv $@.new $@
+dist_bin_SCRIPTS += sshsvc-mkauthkeys
+dist_man_MANS += sshsvc-mkauthkeys.1
+
dist_bin_SCRIPTS += with-umask
dist_man_MANS += with-umask.1
-dist_bin_SCRIPTS += sshsvc-mkauthkeys
-dist_man_MANS += sshsvc-mkauthkeys.1
+dist_bin_SCRIPTS += z
+dist_man_MANS += z.1
## bash scripts.
if HAVE_BASH
$(AM_V_GEN)pod2man --section 8 $(srcdir)/shadowfix.in >$@.new && \
mv $@.new $@
-bin_SCRIPTS += unfwd
-CLEANFILES += unfwd
-EXTRA_DIST += unfwd.in
+dist_bin_SCRIPTS += unfwd
dist_man_MANS += unfwd.1
-unfwd: unfwd.in Makefile
- $(SUBST) $(srcdir)/unfwd.in >$@.new $(SUBSTITUTIONS) && \
- chmod +x $@.new && mv $@.new $@
-
endif
## Perl modules.
EXTRA_DIST += debian/changelog
EXTRA_DIST += debian/control
EXTRA_DIST += debian/compat
+EXTRA_DIST += debian/source/format
## What to install where.
EXTRA_DIST += debian/inst
dnl -*-autoconf-*-
dnl
-dnl Configuration script for nsict-utils
+dnl Configuration script for distorted-utils
dnl
dnl (c) 2008 Mark Wooding
dnl
## Compiler.
AC_PROG_CC
AX_CFLAGS_WARN_ALL
+AC_PROG_CXX
+AX_CXXFLAGS_WARN_ALL
## Libraries.
OLIBS=$LIBS
Section: utils
Priority: extra
Maintainer: Mark Wooding <mdw@distorted.org.uk>
-Build-Depends: tinycdb, debhelper (>= 9), python, python-cdb,
- catacomb-dev (>= 2.1.1), mlib-dev (>= 2.0.4), libcdb-dev
+Build-Depends: debhelper (>= 9), pkg-config,
+ libcdb-dev, mlib-dev (>= 2.0.4), catacomb-dev (>= 2.1.1),
+ python, python-cdb,
+ tclsh
Standards-Version: 3.1.1
Package: distorted-utils
Replaces: nsict-utils (<< 1.4.0~)
Breaks: nsict-utils (<< 1.4.0~)
Depends:
- mdwopt-perl,
+ buf,
+ create,
distorted-cdb,
- locking,
distorted-mail,
- if-mtu,
- shadowfix,
- zz,
+ getpass,
gorp,
+ hush,
+ if-mtu,
+ inplace,
+ locking,
+ mdwopt-perl,
mtimeout,
- splitconf,
- xtitle,
pause,
- buf,
- create,
- inplace,
- stamp,
+ shadowfix,
space,
- getpass,
- hush,
+ splitconf,
+ sshsvc-mkauthkeys,
+ stamp,
with-umask,
- sshsvc-mkauthkeys
+ xtitle,
+ zz
Description: Dummy package for convenience.
-Package: nsict-utils
+Package: buf
Architecture: all
-Section: oldlibs
-Depends: distorted-utils
-Description: Dummy package for transition.
- This is a transitional dummy package. It can safely be removed.
+Section: utils
+Description: Captures stdin in a temporary file and runs a command.
-Package: mdwopt-perl
+Package: create
Architecture: all
-Section: perl
-Depends: perl5
-Description: Options parser library for perl.
+Section: utils
+Description: Simple script encapsulating atomic file update.
Package: distorted-cdb
Architecture: any
Depends: ${shlibs:Depends}, python (>= 2.4), python-cdb
Description: Simple utilities for messing with CDB files.
-Package: nsict-cdb
+Package: distorted-mail
Architecture: all
-Section: oldlibs
-Depends: distorted-cdb
-Description: Dummy package for transition.
- This is a transitional dummy package. It can safely be removed.
+Section: mail
+Depends: ${shlibs:Depends}, qmail, distorted-cdb, perl5, libmime-perl
+Description: Some simple scripts for mail handling.
-Package: mtimeout
+Package: getpass
Architecture: any
-Section: utils
Depends: ${shlibs:Depends}
-Description: Run a program for at most a given amount of time.
-
-Package: prlimit
-Architecture: linux-any
Section: utils
-Depends: ${shlibs:Depends}
-Description: Run a program for at most a given amount of time.
+Description: Read a password without echoing; write it to stdout.
-Package: locking
+Package: gorp
Architecture: any
-Section: utils
Depends: ${shlibs:Depends}
-Description: Run a program while holding a file lock.
-
-Package: distorted-mail
-Architecture: all
-Section: mail
-Depends: ${shlibs:Depends}, qmail, nsict-cdb, perl5, libmime-perl
-Description: Some simple scripts for mail handling.
+Section: utils
+Description: Print a random base64 string.
-Package: nsict-mail
+Package: hush
Architecture: all
-Section: oldlibs
-Depends: distorted-mail
-Description: Dummy package for transition.
- This is a transitional dummy package. It can safely be removed.
+Section: utils
+Description: Run a command, hiding its output in a logfile unless it fails
+ The hush program is useful for running noisy programs from cron or similar,
+ where you get spammed with uninteresting success reports. hush runs a
+ command, logging its output, but, unless the command actually fails, it
+ produces no output of its own.
Package: if-mtu
Architecture: any
Depends: ${shlibs:Depends}
Description: Report the MTU of a network interface.
-Package: shadowfix
-Architecture: all
-Section: utils
-Depends: perl5, mdwopt-perl
-Description: Check and fix shadow password and group files.
-
-Package: zz
+Package: inplace
Architecture: all
Section: utils
-Description: Run a program, automatically decompressing its argument files.
+Description: Update files in place safely.
-Package: xtitle
+Package: locking
Architecture: any
-Depends: ${shlibs:Depends}
-Recommends: x-terminal-emulator
Section: utils
-Description: Simple program for messing with xterm (or compatible) title bars.
+Depends: ${shlibs:Depends}
+Description: Run a program while holding a file lock.
-Package: splitconf
+Package: mdwopt-perl
Architecture: all
-Depends: tclsh
-Section: utils
-Description: Gather little config files into one big one.
+Section: perl
+Depends: perl5
+Description: Options parser library for perl.
-Package: gorp
+Package: mtimeout
Architecture: any
-Depends: ${shlibs:Depends}
Section: utils
-Description: Print a random base64 string.
+Depends: ${shlibs:Depends}
+Description: Run a program for at most a given amount of time.
Package: pause
Architecture: any
Section: utils
Description: Wait for a given time, or until a key is pressed.
-Package: buf
-Architecture: all
+Package: prlimit
+Architecture: linux-any
Section: utils
-Description: Captures stdin in a temporary file and runs a command.
+Depends: ${shlibs:Depends}
+Description: Run a program for at most a given amount of time.
-Package: with-umask
+Package: sema
+Architecture: any
+Depends: ${shlibs:Depends}
+Section: utils
+Description: Perform simple operations on SysV semaphores.
+ The sema program performs simple operations on (System V-style) semaphores.
+ It's not intended to be a utility for general-purpose hacking on existing
+ semaphores, but rather a tool for doing synchronization between shell
+ scripts or other simple programs.
+
+Package: shadowfix
Architecture: all
Section: utils
-Description: Runs a command with a given umask.
+Depends: perl5, mdwopt-perl
+Description: Check and fix shadow password and group files.
-Package: create
+Package: space
+Architecture: any
+Depends: ${shlibs:Depends}
+Section: utils
+Description: Identify and fix problematic whitespace in text files.
+ The space program won't send you to the moon, but it will identify
+ and fix problems such as trailing whitespace and spaces before tabs.
+ It can safely update files in place, and could therefore be used as part
+ of a commit hook.
+
+Package: splitconf
Architecture: all
+Depends: tclsh
Section: utils
-Description: Simple script encapsulating atomic file update.
+Description: Gather little config files into one big one.
-Package: inplace
+Package: sshsvc-mkauthkeys
Architecture: all
Section: utils
-Description: Update files in place safely.
+Description: Construct .ssh/authorized_keys files for SSH services.
+ SSH is a fine way to provide services to external users. It conveniently
+ takes care of details like authenticating users and encrypting
+ communications. Unfortunately, managing the key files is rather painful.
+ This simple script does a lot of the heavy lifting.
Package: stamp
Architecture: any
Section: utils
Description: Like cat, but prefixing each line with a datestamp.
-Package: getpass
-Architecture: any
-Depends: ${shlibs:Depends}
-Section: utils
-Description: Read a password without echoing; write it to stdout.
-
-Package: space
-Architecture: any
-Depends: ${shlibs:Depends}
+Package: with-umask
+Architecture: all
Section: utils
-Description: Identify and fix problematic whitespace in text files.
- The space program won't send you to the moon, but it will identify
- and fix problems such as trailing whitespace and spaces before tabs.
- It can safely update files in place, and could therefore be used as part
- of a commit hook.
+Description: Runs a command with a given umask.
Package: x86-model
Architecture: any-i386 any-amd64
Description: Shows basic model information about x86 processors.
The cpuid program is probably better for most people.
-Package: hush
-Architecture: all
+Package: xtitle
+Architecture: any
+Depends: ${shlibs:Depends}
+Recommends: x-terminal-emulator
Section: utils
-Description: Run a command, hiding its output in a logfile unless it fails
- The hush program is useful for running noisy programs from cron or similar,
- where you get spammed with uninteresting success reports. hush runs a
- command, logging its output, but, unless the command actually fails, it
- produces no output of its own.
+Description: Simple program for messing with xterm (or compatible) title bars.
-Package: sshsvc-mkauthkeys
+Package: zz
Architecture: all
Section: utils
-Description: Construct .ssh/authorized_keys files for SSH services.
- SSH is a fine way to provide services to external users. It conveniently
- takes care of details like authenticating users and encrypting
- communications. Unfortunately, managing the key files is rather painful.
- This simple script does a lot of the heavy lifting.
+Description: Run a program, automatically decompressing its argument files.
+
+Package: nsict-cdb
+Architecture: all
+Section: oldlibs
+Depends: distorted-cdb
+Description: Dummy package for transition.
+ This is a transitional dummy package. It can safely be removed.
+
+Package: nsict-mail
+Architecture: all
+Section: oldlibs
+Depends: distorted-mail
+Description: Dummy package for transition.
+ This is a transitional dummy package. It can safely be removed.
+
+Package: nsict-utils
+Architecture: all
+Section: oldlibs
+Depends: distorted-utils
+Description: Dummy package for transition.
+ This is a transitional dummy package. It can safely be removed.
-nsict-utils is copyright (c) 2003 Mark Wooding
+distorted-utils is copyright (c) 2003 Mark Wooding
-nsict-utils is free software; you can redistribute it and/or modify
+distorted-utils 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 2 of the License, or
(at your option) any later version.
-nsict-utils is distributed in the hope that it will be useful,
+distorted-utils 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.
-MdwOpt.pm mdwopt-perl /usr/share/perl5
buf buf /usr/bin
buf.1 buf /usr/share/man/man1
cdb-assign distorted-cdb /usr/bin
cdb-assign.1 distorted-cdb /usr/share/man/man1
-cdb-map distorted-cdb /usr/bin
-cdb-map.1 distorted-cdb /usr/share/man/man1
cdb-check-domain distorted-cdb /usr/bin
cdb-check-domain.1 distorted-cdb /usr/share/man/man1
cdb-list distorted-cdb /usr/bin
cdb-list.1 distorted-cdb /usr/share/man/man1
+cdb-map distorted-cdb /usr/bin
+cdb-map.1 distorted-cdb /usr/share/man/man1
cdb-probe distorted-cdb /usr/bin
cdb-probe.1 distorted-cdb /usr/share/man/man1
check-sender distorted-mail /usr/bin
getpass.1 getpass /usr/share/man/man1
gorp gorp /usr/bin
gorp.1 gorp /usr/share/man/man1
+hush hush /usr/bin
+hush.1 hush /usr/share/man/man1
if-mtu if-mtu /usr/bin
if-mtu.1 if-mtu /usr/share/man/man1
inplace inplace /usr/bin
inplace.1 inplace /usr/share/man/man1
-mtimeout mtimeout /usr/bin
-mtimeout.1 mtimeout /usr/share/man/man1
locking locking /usr/bin
locking.1 locking /usr/share/man/man1
+MdwOpt.pm mdwopt-perl /usr/share/perl5
+mtimeout mtimeout /usr/bin
+mtimeout.1 mtimeout /usr/share/man/man1
not distorted-mail /usr/bin
not.1 distorted-mail /usr/share/man/man1
pause pause /usr/bin
pause.1 pause /usr/share/man/man1
prlimit prlimit /usr/bin
prlimit.1 prlimit /usr/share/man/man1
+sema sema /usr/bin
+sema.1 sema /usr/share/man/man1
shadowfix shadowfix /usr/sbin
shadowfix.8 shadowfix /usr/share/man/man8
space space /usr/bin
space.1 space /usr/share/man/man1
splitconf splitconf /usr/bin
splitconf.1 splitconf /usr/share/man/man1
+sshsvc-mkauthkeys sshsvc-mkauthkeys /usr/bin
+sshsvc-mkauthkeys.1 sshsvc-mkauthkeys /usr/share/man/man1
stamp stamp /usr/bin
stamp.1 stamp /usr/share/man/man1
unfwd distorted-mail /usr/bin
unfwd.1 distorted-mail /usr/share/man/man1
+with-umask with-umask /usr/bin
+with-umask.1 with-umask /usr/share/man/man1
+x86-model x86-model /usr/bin
+x86-model.1 x86-model /usr/share/man/man1
xtitle xtitle /usr/bin
+xtitle.1 xtitle /usr/share/man/man1
z zz /usr/bin
z.1 zz /usr/share/man/man1
-x86-model x86-model /usr/bin
-x86-model.1 x86-model /usr/share/man/man1
-hush hush /usr/bin
-hush.1 hush /usr/share/man/man1
-with-umask with-umask /usr/bin
-with-umask.1 with-umask /usr/share/man/man1
-sshsvc-mkauthkeys sshsvc-mkauthkeys /usr/bin
-sshsvc-mkauthkeys.1 sshsvc-mkauthkeys /usr/share/man/man1
--- /dev/null
+/* -*-c-*-
+ *
+ * Bodge to control memory access ordering
+ *
+ * (c) 2016 Mark Wooding
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * 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 2 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, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "fence.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @compiler_fence@ --- *
+ *
+ * Arguments: some pointers to things
+ *
+ * Returns: Nothing.
+ *
+ * Use: Actually doesn't do anything at all, but in a separate
+ * translation unit so that the compiler can't tell that. So it
+ * won't be able to move memory accesses to the addressed
+ * objects across the call.
+ */
+
+void compiler_fence(void *p, ...) { }
+
+/*----- That's all, folks -------------------------------------------------*/
--- /dev/null
+/* -*-c-*-
+ *
+ * Bodge to control memory access ordering
+ *
+ * (c) 2016 Mark Wooding
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * 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 2 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, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef FENCE_H
+#define FENCE_H
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <mLib/macros.h>
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @compiler_fence@ --- *
+ *
+ * Arguments: some pointers to things
+ *
+ * Returns: Nothing.
+ *
+ * Use: Actually doesn't do anything at all, but in a separate
+ * translation unit so that the compiler can't tell that. So it
+ * won't be able to move memory accesses to the addressed
+ * objects across the call.
+ */
+
+#define FENCE_END ((void *)0)
+EXECL_LIKE(0) void compiler_fence(void */*p*/, ...);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+ }
+#endif
+
+#endif
/* -*-c-*-
- *
- * $Id$
*
* Pause for a while
*
--- /dev/null
+.TH "sema" 1 "6 March 2016" "Mark Wooding" "Toys"
+.SH NAME
+sema \- operations on (SysV-style) semaphores
+.
+.SH SYNOPSIS
+.B sema
+.RB [ \-w
+.IR when ]
+.I subcmd
+.RI [ args ...]
+.PP
+Subcommands:
+'RS
+.br
+.B mkfile
+.RB [ \-x ]
+.RB [ \-m
+.IR mode ]
+.I name
+.br
+.B new
+.RB [ \-x ]
+.RB [ \-m
+.IR mode ]
+.I name
+.I value
+.br
+.B rm
+.RB [ \-f ]
+.I name
+.br
+.B get
+.I name
+.br
+.B set
+.I name
+.I value
+.br
+.B post
+.RB [ \-n
+.IR count ]
+.I name
+.br
+.B wait
+.RB [ \-n
+.IR count ]
+.I name
+.RI [ cmd
+.IR args ...]
+.RE
+.
+.SH DESCRIPTION
+The
+.B sema
+program performs simple operations
+on (System V-style) semaphores.
+It's not intended to be a utility for
+general-purpose hacking on
+existing semaphores,
+but rather a tool for doing synchronization
+between shell scripts or other simple programs.
+The two biggest limitations of
+.B sema
+are that it only ever acts on the first semaphore of a set,
+and it has no way to change the project-id
+(see
+.BR ftok (3)).
+.SS Common features
+Semaphores are identified by a
+.IR name .
+Currently, this is always a pathname,
+though additional ways of designating semaphore sets
+may be added in later versions,
+so an open-ended syntax is used:
+a
+.I name
+has the form
+.IP
+.RI [ type \c
+.BR : ] \c
+.I value
+.PP
+Currently defined
+.IR type s
+and the required
+.IR value s
+are as follows.
+.TP
+.B file
+The
+.I value
+is a pathname to a file in the filesystem.
+The file's inode number and other information
+are converted to an IPC key using
+.BR ftok (3)
+which is used to fetch a semaphore id using
+.BR semget (2).
+.PP
+The
+.I type
+may be omitted if it is
+.B file
+and the
+.I value
+does not contain a
+.RB ` : '
+before its first
+.RB ` / '
+(if any);
+as a result,
+most plain pathnames can be used directly.
+.PP
+The command-line options accepted before the subcommand name
+are as follows.
+.TP
+.B "\-h, \-\-help"
+Write a help message to standard output
+and exit successfully.
+.TP
+.B "\-v, \-\-version"
+Write a version number to standard output
+and exit successfully.
+.TP
+.BI "\-w, \-\-wait " duration
+Nearly all operations on SysV semaphores may need to block
+for an extended period of time.
+This is obvious for waiting, but, alas,
+semaphore initialization is poorly designed,
+and
+.I every
+operation which expects a properly initialized semaphore
+has little choice but to wait until the semaphore has been set up.
+.IP
+If
+.I duration
+is
+.B forever
+(the default)
+then
+.B sema
+will wait indefinitely until the thing it's waiting for happens.
+If
+.I duration
+is
+.B none
+or
+.B never
+then
+.B sema
+will exit immediately,
+with status 251,
+instead of waiting.
+Otherwise,
+.I
+duration
+should be a
+(possibly fractional \(en i.e., with a decimal point)
+number followed by an optional unit suffix:
+.RB ` s '
+for seconds (the default),
+.RB ` m '
+for minutes,
+.RB ` h '
+for hours, and
+.RB ` d '
+for days.
+.SS Exit status
+.IP 0
+Success.
+.IP 251
+The semaphore did not become available
+within the period
+.B sema
+was told to wait.
+.IP 252
+The semaphore was not properly initialized
+within the period
+.B sema
+was told to wait.
+.IP 253
+An error was detected in
+.BR sema 's
+command-line arguments.
+.IP 254
+A system error occurred.
+.IP Other
+Exit status from the program executed by the
+.B wait
+subcommand.
+.PP
+If
+.B sema
+detects an error,
+it writes a human-readable description of the problem
+to standard error and exits with one of the above status codes.
+The codes have been chosen so as not to conflict
+with those commonly used by other programs,
+so that errors from
+.B sema
+itself can be distinguished from problems encountered
+by the program executed by the
+.B wait
+subcommand,
+but the available space for error codes is small
+and conflicts are inevitable.
+.
+.SH SUBCOMMANDS
+.SS mkfile
+Create the file designated by the semaphore
+.I name
+argument.
+(An error is reported if the
+.I name
+does not designate a pathname.)
+.PP
+Options are as follows.
+.TP
+.BI "\-m, \-\-mode " mode
+Set the permissions for the new file.
+The
+.I mode
+should be a numeric file permission specification, in octal,
+as for
+.BR chmod (1).
+The default is to allow read and write according to the process umask.
+.TP
+.B "\-x, \-\-exclusive"
+Fail if the file already exists.
+.SS new
+Create a new semaphore set containing a single semaphore
+with the given
+.I name
+and initial
+.IR value .
+Options are as follows.
+.TP
+.BI "\-m, \-\-mode " mode
+Set the permissions for the new file.
+The
+.I mode
+should be a numeric file permission specification, in octal;
+the read and write bits determine whether the owner, group and others
+can read and change the semaphore;
+the execute bits are ignored.
+The default is to allow read and write according to the process umask.
+.TP
+.B "\-x, \-\-exclusive"
+Fail if a semaphore set with the given
+.I name
+already exists.
+.SS rm
+Delete the semaphore set with the given
+.IR name .
+.PP
+Options are as follows.
+.TP
+.B "\-f, \-\-force"
+Don't report an error if no semaphore set exists with the given name.
+.SS get
+Fetch the current value of the semaphore with the given
+.I name
+and
+write it, in decimal, to standard outout, followed by a newline.
+.PP
+Because this captures a snapshot of the state
+of an asynchronously changing value,
+this command is only really useful for diagnostic purposes
+or when the system is known to be quiescent.
+.PP
+There are no options.
+.SS set
+Set the value of the semaphore with the given
+.I name
+to
+.IR value .
+.PP
+Because this modifies a the state
+of an asynchronously changing value,
+this command is only really useful
+when the system is known to be quiescent.
+.PP
+There are no options.
+.SS post
+Atomically increment the value of the semaphore with the given
+.IR name .
+This operation can't block
+(though it may still be necessary to wait
+until the semaphore set is initialized).
+.PP
+Options are as follows.
+.TP
+.BI "\-n, \-\-count " count
+Adjust the semaphore value by
+.IR count ,
+which must be a positive integer,
+rather than 1.
+.SS wait
+Wait until the value of the semaphore with the given
+.I name
+is positive and then atomically decrement it.
+.PP
+If a
+.I command
+is provided
+(possibly with some
+.IR arguments )
+then execute it and then increment the semaphore value again
+when it finishes.
+The program is executed directly by
+.BR execvp (3).
+Restoring the semaphore value is reliable:
+it is done by the kernel,
+so there's no risk of some program crashing and
+leaving the semaphore in an inconsistent state;
+though obviously if the program gets stuck
+it will continue to hold the semaphore until it's killed.
+The semaphore is released as soon as the
+.I command
+exits;
+if it forked child processed,
+the semaphore will be released
+and the children will continue to run.
+.PP
+Options are as follows.
+.TP
+.BI "\-n, \-\-count " count
+Adjust the semaphore value by
+.IR count ,
+which must be a positive integer,
+rather than 1.
+.B sema
+will wait until semaphore value is at least
+.IR count ,
+and atomically decrease it by
+.IR count .
+If there is a
+.I command
+then
+.B sema
+arranges for the semaphore value to be increased by
+.I count
+when it exits.
+.
+.SH BUGS
+System V semaphores are remarkably awful.
+POSIX semaphores are superficially much better,
+but actually deficient in a number of ways.
+Most significantly for our purposes,
+there's no analogue of the
+.B SEM_UNDO
+feature,
+so to implement the feature of
+.B wait
+which holds the semaphore during the execution of a command
+.B sema
+would have to wait for it to finish;
+and if
+.B sema
+is killed in the meantime then nobody will fix the semaphore.
+Another important deficiency is that
+POSIX semaphores can only be adjusted a single step at a time,
+so the
+.B \-n
+feature of the
+.B wait
+and
+.B post
+commands can't be implemented satisfactorily.
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>
--- /dev/null
+/* -*-c-*-
+ *
+ * Operations on SysV semaphores
+ *
+ * (c) 2016 Mark Wooding
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * 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 2 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, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <float.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <sys/types.h>
+#include <sys/ipc.h>
+#include <sys/sem.h>
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <mLib/mdwopt.h>
+#include <mLib/quis.h>
+#include <mLib/report.h>
+#include <mLib/tv.h>
+
+#include "fence.h"
+#include "timemax.h"
+
+/* Oh, for pity's sake why did nobody do this for us? */
+union semun {
+ int val;
+ struct semid_ds *buf;
+ unsigned short *array;
+};
+
+/*----- Random utilities --------------------------------------------------*/
+
+static int parse_int(const char *what, const char *p,
+ int radix, int min, int max)
+{
+ char *q;
+ int oerr;
+ long i = -1;
+
+ oerr = errno;
+ if (isspace((unsigned char)*p)) errno = EINVAL;
+ else i = strtol(p, &q, radix);
+ if (*q || errno || !(min <= i && i <= max))
+ die(253, "invalid %s `%s'", what, p);
+ errno = oerr;
+ return ((int)i);
+}
+
+/*----- Timeout handling --------------------------------------------------*/
+
+enum { NEVER, UNTIL, FOREVER };
+
+struct timeout {
+ int wait;
+ struct timeval tv;
+};
+
+static struct timeval now;
+
+static void update_now(void) { int rc = gettimeofday(&now, 0); assert(!rc); }
+
+static void f2tv(double t, struct timeval *tv)
+ { tv->tv_sec = t; tv->tv_usec = 1e6*(t - tv->tv_sec); }
+
+static void parse_timeout(const char *p, struct timeout *t)
+{
+ double x = 0;
+ char *q;
+ int oerr;
+ time_t t_max = timemax();
+ struct timeval tv;
+
+ if (strcmp(p, "forever") == 0)
+ t->wait = FOREVER;
+ else if (strcmp(p, "none") == 0 || strcmp(p, "never") == 0)
+ t->wait = NEVER;
+ else {
+ oerr = errno; errno = 0;
+ if (isspace((unsigned char)*p)) errno = EINVAL;
+ else x = strtod(p, &q);
+ if (x < 0) errno = ERANGE;
+ if (!errno) {
+ switch (tolower((unsigned char )*q)) {
+ case 'd': x *= 24;
+ case 'h': x *= 60;
+ case 'm': x *= 60;
+ case 's': q++; break;
+ }
+ if (*q) errno = EINVAL;
+ }
+ if (errno) die(253, "invalid timeout specification `%s'", p);
+ if (x >= t_max - now.tv_sec) t->wait = FOREVER;
+ else if (!x) t->wait = NEVER;
+ else { t->wait = UNTIL; f2tv(x, &tv); TV_ADD(&t->tv, &now, &tv); }
+ errno = oerr;
+ }
+}
+
+/*----- Semaphore operations ----------------------------------------------*/
+
+#define SEMA_PROJ 0xd1 /* fair die roll */
+
+enum { NTY_PATH };
+struct nameinfo {
+ unsigned ty;
+ union {
+ const char *path;
+ } u;
+};
+
+static void parse_name(const char *name, struct nameinfo *ni)
+{
+ size_t plen = strcspn(name, ":/");
+ switch (name[plen]) {
+ case 0: case '/': ni->ty = NTY_PATH; ni->u.path = name; break;
+ case ':':
+ if (strncmp(name, "file:", 5) == 0)
+ { ni->ty = NTY_PATH; ni->u.path = name + plen + 1; }
+ else
+ die(253, "unknown name type `%.*s'", (int)plen, name);
+ break;
+ }
+}
+
+static int sema_initialized_p(int semid)
+{
+ union semun u;
+ struct semid_ds sem;
+
+ u.buf = &sem;
+ if (semctl(semid, 0, IPC_STAT, u) < 0) {
+ die(254, "failed to check that semaphore set is initialized: %s",
+ strerror(errno));
+ }
+ return (!!sem.sem_otime);
+}
+
+static void make_sema_file(const char *name, int of, mode_t mode)
+{
+ int fd;
+ struct nameinfo ni;
+
+ parse_name(name, &ni);
+ if (ni.ty != NTY_PATH)
+ die(253, "semaphore name `%s' doesn't designate a path", name);
+
+ if ((fd = open(ni.u.path, O_CREAT | of, mode)) < 0) {
+ die(254, "failed to create semaphore file `%s': %s",
+ ni.u.path, strerror(errno));
+ }
+ close(fd);
+}
+
+#define OF_PROBE 1u
+#define OF_UNINIT 2u
+
+static int open_sema(const char *name, unsigned f, int of,
+ mode_t mode, int ival,
+ const struct timeout *t)
+{
+ int ff = mode & 0777, rc, nrace = 5;
+ int semid;
+ union semun u;
+ struct sembuf buf[1];
+ double w, ww;
+ struct nameinfo ni;
+ struct timeval tv, ttv;
+ key_t k;
+
+ /* Turn the name into an IPC key. */
+ parse_name(name, &ni);
+ assert(ni.ty == NTY_PATH);
+ if ((k = ftok(ni.u.path, SEMA_PROJ)) == (key_t)-1) {
+ die(254, "failed to get key from semaphore file `%s': %s",
+ ni.u.path, strerror(errno));
+ }
+
+ for (;;) {
+ /* Oh, horrors. A newly created semaphore set is uninitialized. But if
+ * we set IPC_CREAT without IPC_EXCL then we don't have any idea whether
+ * the semaphore set was created or not. So we have this little dance to
+ * do.
+ */
+ if ((of & (O_CREAT | O_EXCL)) != (O_CREAT | O_EXCL)) {
+ semid = semget(k, 1, ff);
+ if (semid >= 0) goto await;
+ else if (errno != ENOENT) goto fail;
+ else if (ff & O_CREAT) /* try to create -- below */;
+ else if (f & OF_PROBE) return (-1);
+ else goto fail;
+ }
+
+ /* So, here, we have O_CREAT set, and either the semaphore set doesn't
+ * seem to exist yet, or O_EXCL is set and we didn't bother checking yet.
+ * So now we try to create the set. If that fails with something other
+ * than EEXIST, or we were trying with O_EXCL, then we're done.
+ * Otherwise it's just appeared out of nowhere, so let's briefly try
+ * racing with whoever else it is, but give up if it doesn't look like
+ * we're likely to win.
+ */
+ if ((semid = semget(k, 1, ff | IPC_CREAT | IPC_EXCL)) >= 0) break;
+ else if ((ff & O_EXCL) || errno != EEXIST || nrace--) goto fail;
+ }
+
+ /* Right, we just created the semaphore set. Now we have to initialize it,
+ * because nobody did that for us. Set the initial value through semop to
+ * set the sem_otime stamp as an indicator that the set is properly
+ * initialized.
+ */
+ u.val = ival ? 0 : 1;
+ buf[0].sem_num = 0;
+ buf[0].sem_op = ival ? ival : -1;
+ buf[0].sem_flg = IPC_NOWAIT;
+ if (semctl(semid, 0, SETVAL, u) < 0 ||
+ semop(semid, buf, 1) < 0)
+ die(254, "failed to initialize semaphore set: %s", strerror(errno));
+
+ /* Whew. */
+ return (semid);
+
+ /* Make sure that the semaphore set is actually initialized before we
+ * continue.
+ */
+await:
+ if ((f & OF_UNINIT) || sema_initialized_p(semid)) return (semid);
+ if (t->wait == NEVER) goto notready;
+ w = 1e-4;
+ update_now();
+ for (;;) {
+ ww = w/2 + w*rand()/RAND_MAX; f2tv(ww, &tv); TV_ADD(&tv, &tv, &now);
+ if (t->wait == UNTIL) {
+ if (TV_CMP(&now, >=, &t->tv)) goto notready;
+ if (TV_CMP(&tv, >, &t->tv)) tv = t->tv;
+ }
+ for (;;) {
+ TV_SUB(&ttv, &tv, &now);
+ rc = select(0, 0, 0, 0, &ttv);
+ update_now();
+ if (!rc) break;
+ else if (errno != EINTR)
+ die(254, "unexpected error from select: %s", strerror(errno));
+ }
+ if (sema_initialized_p(semid)) return (0);
+ w *= 2;
+ if (w >= 10) w = 10;
+ }
+
+ /* Done. */
+ return (semid);
+
+ /* Report a get failure. */
+fail:
+ die(254, "failed to open semaphore set: %s", strerror(errno));
+
+notready:
+ die(252, "semaphore set not ready");
+}
+
+static void rm_sema(int semid)
+{
+ if (semctl(semid, 0, IPC_RMID))
+ die(254, "failed to remove semaphore set: %s", strerror(errno));
+}
+
+static unsigned read_sema(int semid)
+{
+ int val;
+
+ if ((val = semctl(semid, 0, GETVAL)) < 0)
+ die(254, "failed to read semaphore value: %s", strerror(errno));
+ return (val);
+}
+
+static void set_sema(int semid, unsigned val)
+{
+ union semun u;
+
+ u.val = val;
+ if (semctl(semid, 0, SETVAL, u) < 0)
+ die(254, "failed to set semaphore value: %s", strerror(errno));
+}
+
+static void post_sema(int semid, int by)
+{
+ struct sembuf buf[1];
+
+ buf[0].sem_num = 0;
+ buf[0].sem_op = by;
+ buf[0].sem_flg = 0;
+ if (semop(semid, buf, 1))
+ die(254, "failed to post semaphore: %s", strerror(errno));
+}
+
+static struct sembuf *sigsembuf;
+static int sigflag;
+
+static void wait_alarm(int hunoz)
+ { sigflag = 1; if (sigsembuf) sigsembuf->sem_flg |= IPC_NOWAIT; }
+
+static int wait_sema(int semid, int by, int flg, const struct timeout *t)
+{
+ struct sembuf buf[1];
+ struct sigaction sa, osa;
+ sigset_t ss, oss, ps;
+ struct timeval tv;
+ struct itimerval it, oit;
+ int rc, sig;
+ unsigned f = 0;
+#define f_alarm 1u
+
+ /* Set up the command buffer. */
+ buf[0].sem_num = 0;
+ buf[0].sem_op = -by;
+ buf[0].sem_flg = flg;
+
+ /* Modify it according to the timeout settings. */
+ switch (t->wait) {
+
+ case NEVER:
+ /* Don't wait. */
+ buf[0].sem_flg |= IPC_NOWAIT;
+ break;
+
+ case FOREVER:
+ /* Wait until it's ready. */
+ break;
+
+ case UNTIL:
+ /* Wait until the specified time. This is fiddly, and will likely
+ * require setting an alarm.
+ */
+
+ /* If the timeout is already in the past, then don't bother with the
+ * alarm, and simply don't wait.
+ */
+ if (TV_CMP(&t->tv, <=, &now)) {
+ buf[0].sem_flg |= IPC_NOWAIT;
+ break;
+ }
+
+ /* Find the current alarm setting. */
+ if (getitimer(ITIMER_REAL, &oit)) {
+ die(254, "failed to read current alarm setting: %s",
+ strerror(errno));
+ }
+
+ /* If that's not set, or it's going to go off after our timeout, then
+ * we should override it temporarily. Otherwise, just let it do its
+ * (likely lethal) thing.
+ */
+ if (!oit.it_value.tv_sec && !oit.it_value.tv_usec)
+ f |= f_alarm;
+ else {
+ TV_ADD(&oit.it_value, &oit.it_value, &now);
+ if (TV_CMP(&t->tv, <, &oit.it_value)) f |= f_alarm;
+ }
+
+ /* If we are setting an alarm, then now is the time to mess about with
+ * signals and timers.
+ */
+ if (f & f_alarm) {
+
+ /* Mask out the signal while we fiddle with the settings. */
+ sigemptyset(&ss);
+ sigaddset(&ss, SIGALRM);
+ if (sigprocmask(SIG_BLOCK, &ss, &oss))
+ die(254, "failed to block alarm signal: %s", strerror(errno));
+
+ /* Establish our new signal handler. */
+ sigsembuf = &buf[0];
+ sigflag = 0;
+ sa.sa_handler = wait_alarm;
+ sa.sa_mask = ss;
+ sa.sa_flags = 0;
+ if (sigaction(SIGALRM, &sa, &osa))
+ die(254, "failed to set alarm handler: %s", strerror(errno));
+
+ /* Set up the timer. */
+ TV_SUB(&it.it_value, &t->tv, &now);
+ it.it_interval.tv_sec = 0; it.it_interval.tv_usec = 0;
+ if (setitimer(ITIMER_REAL, &it, 0))
+ die(254, "failed to set alarm: %s", strerror(errno));
+
+ /* This bit's quite scummy. There isn't a POSIX-standard way to wait
+ * for a finite time before giving up, so we use alarms. But there
+ * also isn't a standard way to avoid a race between the alarm going
+ * off and us waiting for the semaphore, because semop doesn't have a
+ * signal set argument. So instead we have the signal handler frob
+ * the semop parameter block as well as setting a flag. Assuming
+ * that semop is actually a system call, then one of the following
+ * will happen: the signal will happen before the call, in which case
+ * it will set IPC_NOWAIT and semop will either succeed or fail
+ * immediately; the signal will happen during the call, while we're
+ * waiting for the semaphore, so semop will return with EINTR; or the
+ * signal will happen afterwards, by which point it's too late for us
+ * to care.
+ */
+ compiler_fence(buf, &sigflag, FENCE_END);
+ if (sigprocmask(SIG_UNBLOCK, &ss, 0))
+ die(254, "failed to unblock alarm signal: %s", strerror(errno));
+ }
+ break;
+ }
+
+ /* Wait for the semaphore. */
+ if ((rc = semop(semid, buf, 1)) < 0 &&
+ errno != EAGAIN &&
+ !(errno == EINTR && sigflag))
+ die(254, "failed to post semaphore: %s", strerror(errno));
+
+ /* Now we clean up again afterwards. */
+ update_now();
+
+ /* If we set an alarm, we must dismantle it and restore the previous
+ * situation.
+ */
+ if (f & f_alarm) {
+
+ /* We're messing with the alarm again, so mask it out temporarily. */
+ if (sigprocmask(SIG_BLOCK, &ss, &oss))
+ die(254, "failed to block alarm signal: %s", strerror(errno));
+
+ /* Turn off the alarm timer. We don't need it any more. */
+ it.it_value.tv_sec = 0; it.it_value.tv_usec = 0;
+ if (setitimer(ITIMER_REAL, &it, 0))
+ die(254, "failed to disarm alarm timer: %s", strerror(errno));
+
+ /* At this point, if there's an alarm signal pending, it must be from the
+ * timer we set, and it's too late to do any good. So just clear it and
+ * move on. After this point, alarms will be from the previously
+ * established timer, and we should let them happen.
+ */
+ if (sigpending(&ps))
+ die(254, "failed to read pending signals: %s", strerror(errno));
+ if (sigismember(&ps, SIGALRM) && sigwait(&ss, &sig))
+ die(254, "failed to clear pending alarm: %s", strerror(errno));
+
+ /* Figure out how to restore the old timer. */
+ if (oit.it_value.tv_sec || oit.it_value.tv_usec) {
+ if (TV_CMP(&oit.it_value, >, &now))
+ TV_SUB(&oit.it_value, &oit.it_value, &now);
+ else {
+ /* We should have had an alarm by now. Schedule one for when we
+ * unblock the signal again.
+ */
+ raise(SIGALRM);
+
+ /* Sort out the timer again. */
+ if (!oit.it_interval.tv_sec && !oit.it_interval.tv_usec)
+ oit.it_value.tv_sec = 0, oit.it_value.tv_usec = 0;
+ else {
+ TV_SUB(&tv, &now, &oit.it_value);
+ if (TV_CMP(&tv, <, &oit.it_interval))
+ TV_SUB(&oit.it_value, &oit.it_interval, &tv);
+ else {
+ /* We've overshot the previous deadline, and missed at least one
+ * repetition. If this is bad, then we've already failed quite
+ * miserably.
+ */
+ oit.it_value = oit.it_interval;
+ }
+ }
+ }
+ }
+
+ /* We've now recovered the right settings, even if that's just to turn
+ * the alarm off, so go do that.
+ */
+ if ((oit.it_value.tv_sec || oit.it_value.tv_usec) &&
+ setitimer(ITIMER_REAL, &oit, 0))
+ die(254, "failed to restore old alarm settings: %s", strerror(errno));
+
+ /* Restore the old signal handler. */
+ if (sigaction(SIGALRM, &osa, 0))
+ die(254, "failed to restore old alarm handler: %s", strerror(errno));
+
+ /* And finally we can restore the previous signal mask. */
+ if (sigprocmask(SIG_SETMASK, &oss, 00))
+ die(254, "failed to restore old signal mask: %s", strerror(errno));
+ }
+
+ return (rc ? -1 : 0);
+}
+
+/*----- Main code ---------------------------------------------------------*/
+
+static struct timeout timeout = { FOREVER };
+
+static int cmd_mkfile(int argc, char *argv[])
+{
+ mode_t mode = 0666, mask = 0;
+ const char *name = 0;
+ int of = 0;
+ unsigned f = 0;
+#define f_bogus 1u
+#define f_mode 2u
+
+ for (;;) {
+ static const struct option opts[] = {
+ { "mode", OPTF_ARGREQ, 0, 'm' },
+ { "exclusive", 0, 0, 'x' },
+ { 0, 0, 0, 0 }
+ };
+ int o = mdwopt(argc, argv, "m:x", opts, 0, 0, OPTF_NOPROGNAME);
+
+ if (o < 0) break;
+ switch (o) {
+ case 'm':
+ mode = parse_int("mode", optarg, 8, 0, 07777);
+ f |= f_mode;
+ break;
+ case 'x': of |= O_EXCL; break;
+ default: f |= f_bogus; break;
+ }
+ }
+ argc -= optind; argv += optind;
+ if (argc < 1) f |= f_bogus;
+ else { name = argv[0]; argc--; argv++; }
+ if (argc) f |= f_bogus;
+ if (f & f_bogus) return (-1);
+
+ if (f & f_mode) mask = umask(0);
+ make_sema_file(name, of, mode);
+ if (f & f_mode) umask(mask);
+ return (0);
+
+#undef f_bogus
+#undef f_mode
+}
+
+static int cmd_new(int argc, char *argv[])
+{
+ mode_t mode = 0, mask;
+ const char *name = 0;
+ unsigned init = 0;
+ int of = O_CREAT;
+ unsigned f = 0;
+#define f_bogus 1u
+#define f_mode 2u
+
+ for (;;) {
+ static const struct option opts[] = {
+ { "mode", OPTF_ARGREQ, 0, 'm' },
+ { "exclusive", 0, 0, 'x' },
+ { 0, 0, 0, 0 }
+ };
+ int o = mdwopt(argc, argv, "m:x", opts, 0, 0, OPTF_NOPROGNAME);
+
+ if (o < 0) break;
+ switch (o) {
+ case 'm':
+ mode = parse_int("mode", optarg, 8, 0, 0777);
+ f |= f_mode;
+ break;
+ case 'x': of |= O_EXCL; break;
+ default: f |= f_bogus; break;
+ }
+ }
+ argc -= optind; argv += optind;
+ if (argc < 2) f |= f_bogus;
+ else {
+ name = argv[0];
+ init = parse_int("initial semaphore value", argv[1],
+ 0, 0, SEM_VALUE_MAX);
+ argc -= 2; argv += 2;
+ }
+ if (argc) f |= f_bogus;
+ if (f & f_bogus) return (-1);
+
+ if (!(f & f_mode)) {
+ mask = umask(0); umask(mask);
+ mode = ~mask & 0777;
+ }
+ open_sema(name, 0, of, mode, init, &timeout);
+ return (0);
+
+#undef f_bogus
+#undef f_mode
+}
+
+static int cmd_rm(int argc, char *argv[])
+{
+ const char *name = 0;
+ unsigned f = 0, ff = 0;
+ int semid;
+#define f_bogus 1u
+
+ for (;;) {
+ static const struct option opts[] = {
+ { "force", 0, 0, 'f' },
+ { 0, 0, 0, 0 }
+ };
+ int o = mdwopt(argc, argv, "f", opts, 0, 0, OPTF_NOPROGNAME);
+
+ if (o < 0) break;
+ switch (o) {
+ case 'f': ff |= OF_PROBE; break;
+ default: f |= f_bogus; break;
+ }
+ }
+ argc -= optind; argv += optind;
+ if (!argc) f |= f_bogus;
+ else { name = argv[0]; argc--; argv++; }
+ if (argc) f |= f_bogus;
+ if (f & f_bogus) return (-1);
+
+ semid = open_sema(name, ff | OF_UNINIT, 0, 0, 0, &timeout);
+ if (semid >= 0) rm_sema(semid);
+
+ return (0);
+
+#undef f_bogus
+}
+
+static int cmd_set(int argc, char *argv[])
+{
+ const char *name = 0;
+ int semid;
+ unsigned n = 0;
+ unsigned f = 0;
+#define f_bogus 1u
+
+ for (;;) {
+ static const struct option opts[] = {
+ { 0, 0, 0, 0 }
+ };
+ int o = mdwopt(argc, argv, "", opts, 0, 0, OPTF_NOPROGNAME);
+
+ if (o < 0) break;
+ switch (o) {
+ default: f |= f_bogus; break;
+ }
+ }
+ argc -= optind; argv += optind;
+ if (argc < 2) f |= f_bogus;
+ else {
+ name = argv[0];
+ n = parse_int("count", argv[1], 0, 0, SEM_VALUE_MAX);
+ argc -= 2; argv += 2;
+ }
+ if (argc) f |= f_bogus;
+ if (f & f_bogus) return (-1);
+
+ semid = open_sema(name, 0, 0, 0, 0, &timeout);
+ set_sema(semid, n);
+ return (0);
+
+#undef f_bogus
+}
+
+static int cmd_get(int argc, char *argv[])
+{
+ const char *name = 0;
+ int semid;
+ unsigned f = 0;
+#define f_bogus 1u
+
+ for (;;) {
+ static const struct option opts[] = {
+ { 0, 0, 0, 0 }
+ };
+ int o = mdwopt(argc, argv, "", opts, 0, 0, OPTF_NOPROGNAME);
+
+ if (o < 0) break;
+ switch (o) {
+ default: f |= f_bogus; break;
+ }
+ }
+ argc -= optind; argv += optind;
+ if (!argc) f |= f_bogus;
+ else { name = argv[0]; argc--; argv++; }
+ if (argc) f |= f_bogus;
+ if (f & f_bogus) return (-1);
+
+ semid = open_sema(name, 0, 0, 0, 0, &timeout);
+ printf("%u\n", read_sema(semid));
+ return (0);
+
+#undef f_bogus
+}
+
+static int cmd_post(int argc, char *argv[])
+{
+ const char *name = 0;
+ int semid, n = 1;
+ unsigned f = 0;
+#define f_bogus 1u
+
+ for (;;) {
+ static const struct option opts[] = {
+ { "count", OPTF_ARGREQ, 0, 'n' },
+ { 0, 0, 0, 0 }
+ };
+ int o = mdwopt(argc, argv, "n:", opts, 0, 0, OPTF_NOPROGNAME);
+
+ if (o < 0) break;
+ switch (o) {
+ case 'n': n = parse_int("count", optarg, 0, 1, SEM_VALUE_MAX); break;
+ default: f |= f_bogus; break;
+ }
+ }
+ argc -= optind; argv += optind;
+ if (!argc) f |= f_bogus;
+ else { name = argv[0]; argc--; argv++; }
+ if (argc) f |= f_bogus;
+ if (f & f_bogus) return (-1);
+
+ semid = open_sema(name, 0, 0, 0, 0, &timeout);
+ post_sema(semid, n);
+ return (0);
+
+#undef f_bogus
+}
+
+static int cmd_wait(int argc, char *argv[])
+{
+ const char *name = 0;
+ int semid, n = 1;
+ int flg = 0;
+ unsigned f = 0;
+#define f_bogus 1u
+
+ for (;;) {
+ static const struct option opts[] = {
+ { "count", OPTF_ARGREQ, 0, 'n' },
+ { 0, 0, 0, 0 }
+ };
+ int o = mdwopt(argc, argv, "-n:", opts, 0, 0, OPTF_NOPROGNAME);
+
+ if (o < 0) break;
+ switch (o) {
+ case 'n': n = parse_int("count", optarg, 0, 1, SEM_VALUE_MAX); break;
+ case 0:
+ if (!name) name = optarg;
+ else { optind--; goto done_opts; }
+ break;
+ default: f |= f_bogus; break;
+ }
+ }
+done_opts:
+ argc -= optind; argv += optind;
+ if (!name) {
+ if (!argc) f |= f_bogus;
+ else { name = argv[0]; argc--; argv++; }
+ }
+ if (argc) flg |= SEM_UNDO;
+ if (f & f_bogus) return (-1);
+
+ semid = open_sema(name, 0, 0, 0, 0, &timeout);
+ if (wait_sema(semid, n, flg, &timeout))
+ die(251, "semaphore busy");
+
+ if (argc) {
+ execvp(argv[0], argv);
+ die(254, "failed to exec `%s': %s", argv[0], strerror(errno));
+ }
+ return (0);
+
+#undef f_bogus
+}
+
+static const struct subcmd {
+ const char *name;
+ int (*func)(int, char *[]);
+ const char *usage;
+} subcmds[] = {
+ { "mkfile", cmd_mkfile, "[-x] [-m MODE] NAME" },
+ { "new", cmd_new, "[-x] [-m MODE] NAME VALUE" },
+ { "rm", cmd_rm, "[-f] NAME" },
+ { "get", cmd_get, "NAME" },
+ { "set", cmd_set, "NAME VALUE" },
+ { "post", cmd_post, "[-n COUNT] NAME" },
+ { "wait", cmd_wait, "[-n COUNT] NAME [COMMAND ARGUMENTS ...]" },
+ { 0, 0, 0 }
+};
+
+static void version(FILE *fp)
+ { pquis(fp, "$, version " VERSION "\n"); }
+
+static void usage(FILE *fp)
+{
+ const struct subcmd *c;
+
+ fprintf(fp, "usage:\n");
+ for (c = subcmds; c->name; c++)
+ fprintf(fp, "\t%s [-OPTIONS] %s %s\n", QUIS, c->name, c->usage);
+}
+
+static void help(FILE *fp)
+{
+ version(fp); putc('\n', fp);
+ usage(fp);
+ fprintf(fp, "\n\
+Top-level options:\n\
+\n\
+ -w, --wait=WHEN How long to wait (`forever', `never', or\n\
+ nonnegative number followed by `s', `m',\n\
+ `h', `d')\n");
+}
+
+int main(int argc, char *argv[])
+{
+ const struct subcmd *c;
+ int rc;
+ unsigned f = 0;
+#define f_bogus 1u
+
+ ego(argv[0]);
+ update_now();
+ for (;;) {
+ static const struct option opts[] = {
+ { "help", 0, 0, 'h' },
+ { "version", 0, 0, 'v' },
+ { "wait", OPTF_ARGREQ, 0, 'w' },
+ { 0, 0, 0, 0 }
+ };
+ int o = mdwopt(argc, argv, "+hvw:", opts, 0, 0, 0);
+
+ if (o < 0) break;
+ switch (o) {
+ case 'h': help(stdout); exit(0); break;
+ case 'v': version(stdout); exit(0); break;
+ case 'w': parse_timeout(optarg, &timeout); break;
+ default: f |= f_bogus; break;
+ }
+ }
+ argc -= optind; argv += optind;
+ if (!argc) f |= f_bogus;
+ if (f & f_bogus) { usage(stderr); exit(253); }
+
+ optind = 0;
+ for (c = subcmds; c->name; c++)
+ if (strcmp(argv[0], c->name) == 0) goto found;
+ die(253, "unknown command `%s'", argv[0]);
+found:
+ argc--; argv++;
+ rc = c->func(argc, argv);
+ if (rc < 0) {
+ fprintf(stderr, "usage: %s %s %s\n", QUIS, c->name, c->usage);
+ exit(253);
+ }
+ return (rc);
+
+#undef f_bogus
+}
+
+/*----- That's all, folks -------------------------------------------------*/
=head1 AUTHOR
-Mark Wooding, <mdw@nsict.org>
+Mark Wooding, <mdw@distorted.org.uk>
=cut
--- /dev/null
+/* -*-c++-*-
+ *
+ * Return the largest allowable time_t value
+ *
+ * (c) 2016 Mark Wooding
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * 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 2 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, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <ctime>
+#include <limits>
+
+#include "timemax.h"
+
+using namespace std;
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @timemax@ --- *
+ *
+ * Arguments: ---
+ *
+ * Returns: The largest value repesentable in an object of type @time_t@.
+ *
+ * Use: For some stupid reason there isn't a @TIME_MAX@ anywhere, and
+ * it's really hard to discover the right value in C. But it's
+ * quite easy in C++, so that's what we do.
+ */
+
+time_t timemax() { return numeric_limits<time_t>::max(); }
+
+/*----- That's all, folks -------------------------------------------------*/
--- /dev/null
+/* -*-c-*-
+ *
+ * Return the largest allowable @time_t@ value
+ *
+ * (c) 2016 Mark Wooding
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * 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 2 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, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef TIMEMAX_H
+#define TIMEMAX_H
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <time.h>
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @timemax@ --- *
+ *
+ * Arguments: ---
+ *
+ * Returns: The largest value repesentable in an object of type @time_t@.
+ *
+ * Use: For some stupid reason there isn't a @TIME_MAX@ anywhere, and
+ * it's really hard to discover the right value in C. But it's
+ * quite easy in C++, so that's what we do.
+ */
+
+time_t timemax(void);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+ }
+#endif
+
+#endif
--- /dev/null
+.TH xtitle 1 "29 March 2012" "Mark Wooding" "Toys"
+.SH NAME
+xtitle \- set or read an xterm title string
+.SH SYNOPSIS
+.B xtitle
+.RB [ \-q ]
+.br
+.B xtitle
+.I string
+\&...
+.SH DESCRIPTION
+The
+.B xtitle
+program sets or reads the title string of an
+.BR xterm (1)
+or compatible terminal emulator.
+.PP
+Without
+.BR \-q ,
+concatenate the
+.IR string s
+provided on the command line
+with spaces between them,
+and set the result as the title string.
+.PP
+With
+.BR \-q ,
+read the current title and
+write it to standard output
+followed by a newline.
+.PP
+If standard input is a terminal then
+.B xtitle
+will use it
+(writing to file descriptor 0!);
+otherwise it will open
+.B /dev/tty
+and use that.
+.SH BUGS
+.B xtitle
+makes no attempt to determine whether its terminal is in fact
+.B xterm
+or something compatible.
+.PP
+In
+.B \-q
+mode,
+if the terminal doesn't understand the necessary control sequence,
+or the feature has been disabled for (quite sensible) security reasons,
+then
+.B xtitle
+will wait forever for the title string to appear.
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>