--- /dev/null
+*~
+*.o
+
+build
+
+cprogs/readbuffer
+cprogs/writebuffer
+cprogs/trivsoundd
+cprogs/really
+cprogs/with-lock-ex
+cprogs/xbatmon-simple
+cprogs/xduplic-copier
+cprogs/mcastsoundd
+cprogs/summer
+cprogs/watershed
+cprogs/watershed.txt
+cprogs/rcopy-repeatedly
+
+debian/tmp
+debian/sv-*
+debian/files
+debian/*.debhelper.log
RWBUFFER_SIZE_MB=16
PROGRAMS= readbuffer writebuffer with-lock-ex xbatmon-simple \
- summer watershed rcopy-repeatedly
+ summer watershed rcopy-repeatedly xduplic-copier
SUIDSBINPROGRAMS= really
DAEMONS= trivsoundd
-MAN1PAGES= readbuffer.1 writebuffer.1 with-lock-ex.1
+MAN1PAGES= readbuffer.1 writebuffer.1 with-lock-ex.1 \
+ xduplic-copier.1
MAN8PAGES= trivsoundd.8 really.8
BUILTTXTDOCS= watershed.txt
TXTDOCS= $(BUILTTXTDOCS)
really.o myopt.o rcopy-repeatedly.o: myopt.h
readbuffer.o writebuffer.o rwbuffer.o wrbufcore.o trivsoundd.o: rwbuffer.h
-xbatmon-simple: xbatmon-simple.o
- $(CC) -o $@ $< -L/usr/X11R6/lib -lX11 -lm
+xbatmon-simple: LDLIBS += -lX11 -lm
+
+xduplic-copier: LDLIBS += -lXmu
summer: summer.o
$(CC) -o $@ $< -lnettle -lgmp
install-examples:
clean:
- rm -f *~ ./#*# *.o
+ rm -f *~ ./#*# *.o $(PROGRAMS)
distclean realclean: clean
rm -f $(TARGETS)
.SH DESCRIPTION
.B really
checks whether the caller is allowed, and if it is it changes its uids
-and gids according to the command line options and executes the
-specified command.
+and gids (and perhaps root directory) according to the command line
+options and executes the specified command.
.PP
If no options are specified, the uid will be set to 0 and the gids
-will be left unchanged.
+and root directory will be left unchanged.
.PP
If no command is specified,
.B really
.BR "$SHELL -i" .
.PP
A caller is allowed if it has write access to
-.BR /etc/inittab .
-This is most easily achieved by creating or using a suitable group,
-containing all the appropriate users, and making
+.BR /etc/inittab
+and is also member of the group
+.BR root .
+This is most easily achieved by making inittab group-writeable by some
+suitable group containing all the appropriate users, and making
.B /etc/inittab
-group-owned by that group and group-writeable.
+group-owned by that group and group-writeable. The root group is
+perhaps a good choice if it isn't being used for anything else.
.SH OPTIONS
.TP
\fB-u\fR \fIusername\fR | \fB--user\fR \fIusername\fR
.B -z
in the argument list is not relevant.
.TP
+\fB-R\fR \fIroot-dir\fR | \fB--chroot\fR \fIroot-dir\fR
+The program will have its root directory set to
+.IR root-dir .
+
+.BR "Do not use this option unless you know what you are doing" :
+Unlike chroot(8), the current working directory will remain unchanged.
+This means that if the current directory isn't underneath the
+specified new root, the program will still be able to access files
+outside the new root by using relative pathnames. If this isn't
+what you want, please use the chroot utility instead.
+.TP
.B \-\-
Indicates the end of the options. The next argument (if present) will
be interpreted as the command name, even if it starts with a hyphen.
.B really
was written by Ian Jackson <ian@chiark.greenend.org.uk>.
.PP
-It and this manpage are Copyright (C) 1992-5,2003 Ian Jackson
+It and this manpage are Copyright (C) 1992-5,2004,2013 Ian Jackson
<ian@chiark.greenend.org.uk>.
.PP
.B really
" -G|--gid <gid> } the group list\n"
"other really-options:\n"
" -h|--help display this message\n"
- " -R|--chroot <dir> chroot (but *not* chdir)\n",
+ " -R|--chroot <dir> chroot (but *not* chdir - danger!)\n",
stderr) == EOF) { perror("write usage"); exit(-1); }
}
static const struct option os[]= {
{ "--state-dir", 1,0,'d' },
{ "--command-id",1,0,'i' },
+ { "--help", 0,0,'h' },
{ 0 }
};
}while(0)
-static void badusage(void) {
+static void printusage(FILE *f) {
fputs(_("usage: watershed [<options>] <command>...\n"
- "options: -d|--state-dir <directory> -i|--command-id <id>\n"
+ "options:\n"
+ " -d|--state-dir <directory>\n"
+ " -i|--command-id <id>\n"
+ " -h|--help\n"
"see /usr/share/doc/chiark-utils-bin/watershed.txt\n"),
- stderr);
+ f);
+}
+static void badusage(void) {
+ printusage(stderr);
exit(127);
}
static void die(const char *m) {
static void parse_args(int argc, char *const *argv) {
int o;
for (;;) {
- o= getopt_long(argc, argv, "+d:i:", os,0);
+ o= getopt_long(argc, argv, "+d:i:h", os,0);
if (o==-1) break;
switch (o) {
case 'd': state_dir= optarg; break;
case 'i': command_id= optarg; break;
+ case 'h': printusage(stdout); exit(0); break;
default: badusage();
}
}
ALL_DIRECT_VARS(V_PRINT)
}
+ if (this_type == -1) {
+ /* some kernels don't seem to provide TYPE in the uevent
+ * guess the type from whether we see "present" or "online" */
+ if (this_online >= 0 && this_present == -1) this_type = TYPE_MAINS;
+ if (this_online == -1 && this_present >= 0) this_type = TYPE_BATTERY;
+ if (debug)
+ printf(" type absent from uevent %6s guessed %12s %"PRId64"\n",
+ "", "", this_type);
+ }
+
int needsfields_MAINS = this_type == TYPE_MAINS;
int needsfields_BATTERY = this_type == TYPE_BATTERY;
int needsfields_BOTH = 1;
--- /dev/null
+.TH WITH-LOCK-EX "1" "July 2003" "Debian" "Chiark-utils-bin"
+.SH NAME
+xduplic-copier \- type into multiple X windows at once
+.SH SYNOPSIS
+.B xduplic-copier
+.SH DESCRIPTION
+xduplic-copier lets you type into multiple X windows at once.
+
+It has a very basic user interface for selecting which windows to type
+into.
+.SH OPTIONS
+xduplic-copier ignores its command-line arguments.
+.SH MODES
+xduplic-copier puts up a small window with some text in it. It has
+two modes:
+.TP
+Idle: "\fBi 0\fR" (red); Typing "\fBT\fR \fIcount\fR" (green)
+If you type keystrokes into xduplic-copier, they will be replicated to
+all the selected windows (if there are any). (You can also type into
+the selected windows individually in the normal way.)
+count is the number of selected windows.
+The starting mode is Idle; you should left-click to start selecting.
+.TP
+Selecting: "\fBS\fR \fIcount\fR" (white)
+In this mode xduplic-copier has grabbed the mouse pointer and you
+can indicate which windows you are going to want to type into.
+.SH MOUSE ACTIONS
+.TP
+Left-click in xduplic-copier
+Switch between typing and selecting modes.
+.TP
+Right-click in xduplic-copier
+Quit.
+.TP
+Left-click in another window while selecting
+Select this window. (Beeps if already selected.)
+.TP
+Right-click in another window while selecting
+Deselect this window. (Beeps if not selected.)
+.TP
+Right-click in root window (ie, desktop background) while selecting
+Deselect all windows.
+.TP
+Q key while selecting
+Quits.
+.SH XTERM AND ALLOW SENDEVENTS
+xduplic-copier does its work by generating synthetic events for the
+selected windows, using XSendEvent. Unfortunately the xterm authors
+think that allowing XSendEvent is a security problem; they are wrong:
+any untrusted person can already take over your xterms anyway. But
+the xterm authors have configured xterm to discard synthetic events by
+default.
+
+You can solve this at runtime by bringing up the ctrl-leftbutton menu in
+each xterm, and ticking the option "Allow SendEvents". Or you can set
+the allowSendEvents in your xterm X resources.
+
+Neither of these significantly reduce your security. Indeed, xterm
+itself allows these properties to be set via the X toolkit system's
+remote widget property setting arrangements - so it would be possible
+for xduplic-copier to set this property itself on any xterms it
+encountered. Unfortunately doing so would be a lot of tedious
+programming.
+.SH BUGS
+If one of the windows you have selected is closed, and you try typing,
+xduplic-copier will crash due to an unhandled X11 error. If you
+notice that you have got into this state, you cannot retain your set
+of selected windows because there is no way to click on the
+now-destroyed window to deselect it. You can keep xduplic-copier from
+crashing by right-clicking on the background in selecting mode, and
+then reselecting all your windows, which may be marginally more
+convenient than restarting it.
+
+The UI is perhaps excessively sparse.
+.SH AUTHOR AND COPYRIGHT
+Ian Jackson <ijackson@chiark.greenend.org.uk> wrote xduplic-copier
+some time in 2002, and updated it in 2013. The manpage is from 2013.
+
+xduplic-copier is govered by the GNU GPL, v3 or later.
--- /dev/null
+/*
+ * Copyright (C) 2002,2013 Ian Jackson <ian@chiark.greenend.org.uk>
+ *
+ * This 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,
+ * or (at your option) any later version.
+ *
+ * This 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/>.
+ */
+
+#include <X11/Xlib.h>
+#include <X11/keysym.h>
+#include <X11/cursorfont.h>
+#include <X11/cursorfont.h>
+#include <X11/Xmu/WinUtil.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+#include <unistd.h>
+
+static Display *display;
+static int selecting, l1_x, l1_y;
+static char stringbuf[20];
+static unsigned long c_black, c_white, c_red, c_yellow;
+static Window w, root;
+static Cursor cursor;
+static GC gc;
+static Colormap cmap;
+
+struct wnode {
+ struct wnode *next;
+ Window w;
+} *headwn;
+
+static unsigned long getcolour(const char *name, int def) {
+ Status st;
+ XColor screen_def, exact_def;
+ st= XAllocNamedColor(display,cmap,name,&screen_def,&exact_def);
+ fprintf(stdout,"name %s pixel %lu\n",name,screen_def.pixel);
+ return st ? screen_def.pixel : def;
+}
+
+static void beep(void) { XBell(display,100); }
+
+/*
+ * While selecting:
+ * Left Right
+ * application select deselect
+ * root start typing deselect all
+ * xduplic start typing quit
+ *
+ * While typing:
+ * Left Right
+ * xduplic start selecting quit
+ *
+ * Colours:
+ *
+ * While typing: yellow on black
+ * While selecting: white on black
+ * Idle (typing into nowhere): red on black
+ */
+
+static void redisplay(void) {
+ XClearWindow(display,w);
+ XDrawString(display,w,gc, l1_x,l1_y, stringbuf,strlen(stringbuf));
+}
+
+static void restatus(void) {
+ XGCValues v;
+ int count;
+ struct wnode *own;
+
+ v.foreground= (selecting ? c_white :
+ headwn ? c_yellow :
+ c_red);
+
+ XChangeGC(display,gc,
+ GCForeground,
+ &v);
+ XClearWindow(display,w);
+
+ for (count=0, own=headwn; own; own=own->next) count++;
+ snprintf(stringbuf,sizeof(stringbuf),
+ "%c %d",
+ selecting ? 'S' :
+ headwn ? 'T' : 'i',
+ count);
+ redisplay();
+}
+
+static void stopselecting(void) {
+ XUngrabPointer(display,CurrentTime);
+ selecting= 0;
+ restatus();
+}
+
+static void startselecting(void) {
+ Status st;
+ st= XGrabPointer(display,root,True,
+ ButtonPressMask,GrabModeAsync,
+ GrabModeAsync,None,cursor,CurrentTime);
+ if (st != Success) beep();
+ else selecting= 1;
+ restatus();
+}
+
+static void buttonpress(XButtonEvent *e) {
+ struct wnode *own, **ownp, *ownn;
+ int rightbutton;
+ Window sw;
+
+ switch (e->button) {
+ case Button1: rightbutton=0; break;
+ case Button3: rightbutton=1; break;
+ default: return;
+ }
+
+ fprintf(stdout,"button right=%d in=%lx sub=%lx (w=%lx root=%lx)\n",
+ rightbutton, (unsigned long)e->window, (unsigned long)e->subwindow,
+ (unsigned long)w, (unsigned long)e->root);
+
+ if (e->window == w) {
+ if (rightbutton) _exit(0);
+ if (selecting) {
+ stopselecting();
+ /* move pointer to where it already is, just in case wm is confused */
+ XWarpPointer(display,None,root, 0,0,0,0, e->x_root,e->y_root);
+ } else {
+ startselecting();
+ }
+ return;
+ }
+
+ if (!selecting) return;
+
+ if (e->window != e->root) return;
+
+ if (!e->subwindow) {
+ if (!rightbutton) {
+ stopselecting();
+ } else {
+ if (!headwn) { beep(); return; }
+ for (own=headwn; own; own=ownn) {
+ ownn= own->next;
+ free(own);
+ }
+ headwn= 0;
+ restatus();
+ }
+ return;
+ }
+
+ sw= XmuClientWindow(display, e->subwindow);
+
+ if (sw == w) { beep(); return; }
+
+ for (ownp=&headwn;
+ (own=(*ownp)) && own->w != sw;
+ ownp= &(*ownp)->next);
+
+ if (!rightbutton) {
+
+ if (own) { beep(); return; }
+ own= malloc(sizeof(*own)); if (!own) { perror("malloc"); exit(-1); }
+ own->w= sw;
+ own->next= headwn;
+ headwn= own;
+
+ } else {
+
+ if (!own) { beep(); return; }
+ *ownp= own->next;
+ free(own);
+
+ }
+
+ restatus();
+}
+
+static void keypress(XKeyEvent *e) {
+ Status st;
+ struct wnode *own;
+ unsigned long mask;
+
+ if (selecting) {
+ fprintf(stdout,"key type %d serial %lu (send %d) "
+ "window %lx root %lx sub %lx time %lx @%dx%d (%dx%dabs) "
+ "state %x keycode %u same %d\n",
+ e->type, e->serial, (int)e->send_event,
+ (unsigned long)e->window,
+ (unsigned long)e->root,
+ (unsigned long)e->subwindow,
+ (unsigned long)e->time,
+ e->x,e->y, e->x_root,e->y_root,
+ e->state, e->keycode, (int)e->same_screen);
+ if (XKeycodeToKeysym(display, e->keycode, 0) == XK_q) _exit(1);
+ beep(); return;
+ }
+ for (own=headwn; own; own=own->next) {
+ mask= (e->type == KeyPress ? KeyPressMask :
+ e->type == KeyRelease ? KeyReleaseMask :
+ KeyPressMask|KeyReleaseMask);
+ e->window= own->w;
+ e->subwindow= None;
+ e->send_event= True;
+ st= XSendEvent(display,own->w,True,mask,(XEvent*)e);
+ if (st != Success) {
+ fprintf(stdout,"sendevent to %lx %d mask %lx\n",
+ (unsigned long)own->w, st, mask);
+ }
+ }
+}
+
+static void expose(XExposeEvent *e) {
+ if (e->count) return;
+ redisplay();
+}
+
+int main(int argc, const char **argv) {
+ XEvent e;
+ XGCValues gcv;
+ XSetWindowAttributes wv;
+ int screen, direction, ascent, descent, l1_width, l1_height;
+ XCharStruct overall;
+ Font font;
+
+ display= XOpenDisplay(0);
+ screen= DefaultScreen(display);
+ cmap= DefaultColormap(display,screen);
+ root= DefaultRootWindow(display);
+
+ c_black= getcolour("black", 0);
+ c_white= getcolour("white", 1);
+ c_yellow= getcolour("yellow", c_white);
+ c_red= getcolour("red", c_white);
+
+ cursor= XCreateFontCursor(display,XC_crosshair);
+
+ wv.event_mask= KeyPressMask|KeyReleaseMask|ButtonPressMask|ExposureMask;
+ w= XCreateWindow(display, root,
+ 0,0, 50,21, 0,DefaultDepth(display,screen),
+ InputOutput, DefaultVisual(display,screen),
+ CWEventMask, &wv);
+
+ font= XLoadFont(display,"fixed");
+
+ gcv.background= c_black;
+ gcv.font= font;
+ gc= XCreateGC(display,w,GCBackground|GCFont,&gcv);
+
+ XQueryTextExtents(display,font, "SIT 0689", 8,
+ &direction,&ascent,&descent,&overall);
+ l1_width= overall.lbearing + overall.rbearing;
+ l1_x= overall.lbearing;
+ l1_y= ascent;
+ l1_height= descent+ascent;
+
+ XResizeWindow(display,w, l1_width,l1_height);
+ XSetWindowBackground(display,w,c_black);
+
+ XMapWindow(display,w);
+ restatus();
+
+ for (;;) {
+ XNextEvent(display,&e);
+ fprintf(stdout,"selecting = %d; event type = %lu\n",
+ selecting, (unsigned long)e.type);
+ switch (e.type) {
+ case Expose: expose(&e.xexpose); break;
+ case ButtonPress: buttonpress(&e.xbutton); break;
+ case KeyPress: case KeyRelease: keypress(&e.xkey); break;
+ }
+ }
+}
+chiark-utils (4.2.1~~iwj4) unstable; urgency=low
+
+ * xduplic-copier: New utility. Closes:#727223.
+ * really: Add "danger!" warning to usage message description of -R.
+ * really: Document -R option in the manpage. Closes:#693354.
+ * really: Document need to be in the "root" group as well. (This is
+ better than removing the restriction, because it would be dangerous to
+ relax this security barrier in existing deployments.) Closes:#693356.
+ * watershed; Provide -h and --help options. Closes:#659989.
+ * xbatmon-simple: Saner build rune.
+ * cprogs/Makefile: clean deletes $PROGRAMS
+
+ --
+
+chiark-utils (4.2.1~~iwj3) unstable; urgency=low
+
+ * git-cache-proxy: postpone chdir so Housekeeping lock and stamp
+ and up in the right directory.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk> Mon, 11 Nov 2013 13:43:47 +0000
+
+chiark-utils (4.2.1~~iwj2) unstable; urgency=low
+
+ New utility:
+ * git-cache-proxy
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk> Mon, 11 Nov 2013 13:14:35 +0000
+
+chiark-utils (4.2.1~~iwj1) unstable; urgency=low
+
+ New features:
+ * random-word: -F<n> option for using only first <n> lines of source file.
+
+ Code cleanups:
+ * random-word: Some perl-mode emacs formatting glitch workarounds.
+ * random-word: use strict; -w.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk> Thu, 05 Sep 2013 00:32:39 +0100
+
+chiark-utils (4.2.1~~iwj) unstable; urgency=low
+
+ * Make xbatmon-simple tolerate the lack of "type" in power supply uevent
+ fields, by guessing the type from the presence or absence of "present"
+ (which appears for batteries) and "online" (which appears for mains).
+ Closes: #689134.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk> Sat, 29 Sep 2012 13:47:21 +0100
+
chiark-utils (4.2.0) unstable; urgency=low
* Rename `xacpi-simple' to `xbatmon-simple':
Section: admin
Priority: extra
Maintainer: Ian Jackson <ijackson@chiark.greenend.org.uk>
-Build-Depends: libx11-dev, nettle-dev, debhelper (>= 5)
+Build-Depends: libx11-dev, libxmu-dev, nettle-dev, debhelper (>= 5)
Standards-Version: 3.9.1
Package: chiark-backup
xbatmon-simple: a very simple X client for displaying battery
charge status.
.
+ xduplic-copier: a very simple X client for typing into multiple windows
+ at once.
+ .
watershed: a utility for saving on superfluous executions of an
idempotent command. (This is the same utility as shipped separately
in Ubuntu's udev, but with slightly different defaults and a
xbatmon-simple, a simple X client for displaying ACPI battery status
Copyright 2004,2012 Ian Jackson <ian@chiark.greenend.org.uk>
+xduplic-copier, a simple X client for typing into multiple windows
+ Copyright 2002,2013 Ian Jackson <ian@chiark.greenend.org.uk>
+
+git-cache-proxy, a rather shoddy daemon for speeding up git clone
+ Copyright 2013 Ian Jackson <ian@chiark.greenend.org.uk>
+
summer, a tool for reporting complete details about a filesystem tree
Copyright 2003-2007 Ian Jackson <ian@chiark.greenend.org.uk>
manpage Copyright 2006 Peter Maydell <pmaydell@chiark.greenend.org.uk>
$t/chiark-really/usr/sbin/*
set -e; for f in $t/chiark-utils-bin/usr/bin/*; do \
case "$$f" in \
- */xbatmon-simple) d=Suggests ;; \
+ */xbatmon-simple|*/xduplic-copier) \
+ d=Suggests ;; \
*/watershed|*/summer) d=Recommends ;; \
*) d=Depends ;; \
esac; \
SCRIPTS= palm-datebook-reminders random-word expire-iso8601 \
genspic2gnuplot gnucap2genspic ngspice2genspic \
cvs-repomove cvs-adjustroot remountresizereiserfs \
- hexterm summarise-mailbox-preserving-privacy
+ hexterm summarise-mailbox-preserving-privacy \
+ git-cache-proxy
MANPAGES1= palm-datebook-reminders
CSCRIPTS= named-conf
--- /dev/null
+#!/usr/bin/perl -w
+#
+# git caching proxy
+
+# usage: run it on some port, and then clone or fetch
+# "git://<realhost>:<realport>/<real-git-url>[ <options>]"
+# where <real-git-url> is http://<host>/... or git://<host>/...
+# and <options> is zero or more (whitespace-separated) of
+# [<some-option>] will be ignored if not recognised
+# {<some-option>} error if not recognised
+# options currently known:
+# fetch=must fail if the fetch/clone from upstream fails
+# fetch=no just use what is in the cache
+# fetch=try use what is in the cache if the fetch/clone fails
+# timeout=<seconds> length of time to allow for fetch/clone
+
+# example inetd.conf line:
+# 9419 stream tcp nowait git-cache /usr/bin/git-cache-proxy git-cache-proxy
+# you'll need to
+# adduser git-cache
+# mkdir /var/cache/git-cache-proxy
+# chown git-cache /var/cache/git-cache-proxy
+
+# git-cache-proxy
+# Copyright 2010 Tony Finch
+# Copyright 2013 Ian Jackson
+#
+# git-cache-proxy is free software; you can redistribute it and/or
+# modify them under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 3, or (at
+# your option) any later version.
+#
+# git-cache-proxy 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, consult the Free Software Foundation's
+# website at www.fsf.org, or the GNU Project website at www.gnu.org.
+#
+# (Some code taken from userv-utils's git-daemon.in and git-service.in
+# which were written by Tony Finch <dot@dotat.at> and subsequently
+# heavily modified by Ian Jackson <ijackson@chiark.greenend.org.uk>
+# and were released under CC0 1.0. The whole program is now GPLv3+.)
+
+use strict;
+use warnings;
+
+use POSIX;
+use Socket;
+use Sys::Syslog;
+use Fcntl qw(:flock SEEK_SET);
+use File::Path qw(remove_tree);
+
+our $us = 'git-cache-proxy';
+
+our $debug = 0;
+our $housekeepingeverydays = 1;
+our $treeexpiredays = 21;
+our $fetchtimeout = 1800;
+our $maxfetchtimeout = 3600;
+our $cachedir = '/var/cache/git-cache-proxy';
+our $housekeepingonly = 0;
+
+#---------- error handling and logging ----------
+
+# This is a bit fiddly, because we want to catch errors sent to stderr
+# and dump them to syslog if we can, but only if we are running as an
+# inetd service.
+
+our $log; # filehandle (ref), or "1" meaning syslog
+
+sub ntoa {
+ my $sockaddr = shift;
+ return ('(local)') unless defined $sockaddr;
+ my ($port,$addr) = sockaddr_in $sockaddr;
+ $addr = inet_ntoa $addr;
+ return ("[$addr]:$port",$addr,$port);
+}
+
+our ($client) = ntoa getpeername STDIN;
+our ($server) = ntoa getsockname STDIN;
+
+sub ensurelog () {
+ return if $log;
+ openlog $us, qw(pid), 'daemon';
+ $log = 1;
+}
+
+sub logm ($$) {
+ my ($pri, $msg) = @_;
+ return if $pri eq 'debug' && !$debug;
+ if ($client eq '(local)') {
+ print STDERR "$us: $pri: $msg\n" or die $!;
+ return;
+ }
+ ensurelog();
+ my $mainmsg = sprintf "%s-%s: %s", $server, $client, $msg;
+ if (ref $log) {
+ my $wholemsg = sprintf("%s [%d] %s: %s\n",
+ strftime("%Y-%m-%d %H:%M:%S Z", gmtime),
+ $$,
+ $pri eq 'err' ? 'error' : $pri,
+ $mainmsg);
+ print $log $wholemsg;
+ } else {
+ syslog $pri, "%s", "$pri $mainmsg";
+ }
+}
+
+if ($client ne '(local)') {
+ open STDERR, ">/dev/null" or exit 255;
+ open TEMPERR, "+>", undef or exit 255;
+ open STDERR, ">&TEMPERR" or exit 255;
+}
+
+END {
+ if ($client ne '(local)') {
+ if ($?) { logm 'crit', "crashing ($?)"; }
+ seek TEMPERR, 0, SEEK_SET;
+ while (<TEMPERR>) {
+ chomp;
+ logm 'crit', $_;
+ }
+ }
+ exit $?;
+}
+
+sub fail ($) {
+ my ($msg) = @_;
+ logm 'err', $msg;
+ exit 0;
+}
+
+sub gitfail ($) {
+ my ($msg) = @_;
+ close LOCK;
+ alarm 60;
+ logm 'notice', $msg;
+ my $gitmsg = "ERR $us: $msg";
+ $gitmsg = substr($gitmsg,0,65535); # just in case
+ printf "%04x%s", length($gitmsg)+4, $gitmsg;
+ flush STDOUT;
+ exit 0;
+}
+
+#---------- argument parsing ----------
+
+for (;;) {
+ last unless @ARGV;
+ last unless $ARGV[0] =~ m/^-/;
+ $_ = shift @ARGV;
+ for (;;) {
+ last unless m/^-./;
+ if (s/^-H/-/) {
+ $housekeepingonly++;
+ } elsif (s/^-D/-/) {
+ $debug++;
+ } elsif (s/^-L(.*)$//) {
+ my $logfile = $_;
+ open STDERR, ">>", $logfile or fail "open $logfile: $!";
+ $log = \*STDERR;
+ } elsif (s/^-d(.*)$//) {
+ $cachedir = $1;
+ } elsif (s/^--( max-fetch-timeout
+ | fetch-timeout
+ | tree-expire-days
+ | housekeeping-interval-days
+ )=(\d+)$//x) {
+ my $vn = $1;
+ $vn =~ y/-//d;
+ die $vn unless defined ${ $::{$vn} };
+ ${ $::{$vn} } = $2;
+ } else {
+ fail "bad usage: unknown option `$_'";
+ }
+ }
+}
+
+!@ARGV or fail "bad usage: no non-option arguments permitted";
+
+#---------- utility functions ----------
+
+sub lockfile ($$$) {
+ my ($fh, $fn, $flockmode) = @_;
+ my $what = $fn.(($flockmode & ~LOCK_NB) == LOCK_SH ? " (shared)" : "");
+ for (;;) {
+ close $fh;
+ open $fh, '+>', $fn or fail "open/create $fn for lock: $!";
+ logm 'debug', "lock $what: acquiring";
+ if (!flock $fh, $flockmode) {
+ if ($flockmode & LOCK_NB && $! == EWOULDBLOCK) {
+ return 0; # ok then
+ }
+ fail "lock $what: $!";
+ }
+ stat $fh or fail "stat opened $fn: $!";
+ my $fh_ino = ((stat _)[1]);
+ if (!stat $fn) {
+ $! == ENOENT or fail "stat $fn: $!";
+ next;
+ }
+ my $fn_ino = ((stat _)[1]);
+ if ($fn_ino == $fh_ino) {
+ logm 'debug', "lock $what: acquired";
+ return 1;
+ }
+ logm 'debug', "lock $what: deleted, need to loop again";
+ # oh dear
+ }
+}
+
+sub xread {
+ my $length = shift;
+ my $buffer = "";
+ while ($length > length $buffer) {
+ my $ret = sysread STDIN, $buffer, $length, length $buffer;
+ fail "expected $length bytes, got ".length $buffer
+ if defined $ret and $ret == 0;
+ fail "read: $!" if not defined $ret and $! != EINTR and $! != EAGAIN;
+ }
+ return $buffer;
+}
+
+#---------- main program ----------
+
+chdir $cachedir or fail "chdir $cachedir: $!";
+
+our ($service,$specpath,$spechost,$subdir);
+our ($tmpd,$gitd,$lock);
+our ($fetch,$url);
+
+sub servinfo ($) {
+ my ($msg) = @_;
+ logm 'info', "service `$specpath': $msg";
+}
+
+sub readcommand () {
+ $SIG{ALRM} = sub { fail "timeout" };
+ alarm 30;
+
+ my $hex_len = xread 4;
+ fail "Bad hex in packet length" unless $hex_len =~ m|^[0-9a-fA-F]{4}$|;
+ my $line = xread -4 + hex $hex_len;
+ unless (($service,$specpath,$spechost) = $line =~
+ m|^(git-[a-z-]+) /*([!-~ ]+)\0host=([!-~]+)\0$|) {
+ $line =~ s|[^ -~]+| |g;
+ gitfail "unknown/unsupported instruction `$line'"
+ }
+
+ alarm 0;
+
+ $service eq 'git-upload-pack'
+ or gitfail "unknown/unsupported service `$service'";
+
+ $fetch = 2; # 0:don't; 1:try; 2:force
+ $url = $specpath;
+
+ while ($url =~ s#\s+(\[)([^][{}]+)\]$## ||
+ $url =~ s#\s+(\{)([^][{}]+)\}$##) {
+ $_ = $2;
+ my $must = $1 eq '{';
+ if (m/^fetch=try$/) {
+ $fetch = 1;
+ } elsif (m/^fetch=no$/) {
+ $fetch = 0;
+ } elsif (m/^fetch=must$/) {
+ $fetch = 2; # the default
+ } elsif (m/^timeout=(\d+)$/ && $1 >= 1) {
+ $fetchtimeout = $1 <= $maxfetchtimeout ? $1 : $maxfetchtimeout;
+ } elsif ($must) {
+ gitfail "unknown/unsupported option `$_'";
+ }
+ }
+
+ $url =~ m{^(?:https?|git)://[-.0-9a-z]+/}
+ or gitfail "unknown/unsupported url scheme or format `$url'";
+
+ $subdir = $url;
+ $subdir =~ s|\\|\\\\|g;
+ $subdir =~ s|,|\\,|g;
+ $subdir =~ s|/|,|g;
+
+ $tmpd= "$subdir\\.tmp";
+ $gitd= "$subdir\\.git";
+ $lock = "$subdir\\.lock";
+
+ servinfo "locking";
+}
+
+sub clonefetch () {
+ lockfile \*LOCK, $lock, LOCK_EX;
+
+ my $exists = lstat $gitd;
+ $exists or $!==ENOENT or fail "lstat $gitd: $!";
+
+ our $fetchfail = '';
+
+ if ($fetch) {
+
+ our @cmd;
+
+ if (!$exists) {
+ system qw(rm -rf --), $tmpd;
+ @cmd = (qw(git clone -q --mirror), $url, $tmpd);
+ servinfo "cloning";
+ } else {
+ @cmd = (qw(git remote update --prune));
+ servinfo "fetching";
+ }
+ my $cmd = "@cmd[0..1]";
+
+ my $child = open FETCHERR, "-|";
+ defined $child or fail "fork: $!";
+ if (!$child) {
+ if ($exists) {
+ chdir $gitd or fail "chdir $gitd: $!";
+ }
+ setpgrp or fail "setpgrp: $!";
+ open STDERR, ">&STDOUT" or fail "redirect stderr: $!";
+ exec @cmd or fail "exec $cmd[0]: $!";
+ }
+
+ my $fetcherr = '';
+ my $timedout = 0;
+ {
+ local $SIG{ALRM} = sub {
+ servinfo "fetch/clone timeout";
+ $timedout=1; kill 9, -$child;
+ };
+ alarm($fetchtimeout);
+ $!=0; { local $/=undef; $fetcherr = <FETCHERR>; }
+ !FETCHERR->error or fail "read pipe from fetch/clone: $!";
+ alarm(10);
+ }
+
+ kill -9, $child or fail "kill fetch/clone: $!";
+ $!=0; $?=0; if (!close FETCHERR) {
+ fail "reap fetch/clone: $!" if $!;
+ my $fetchfail =
+ !($? & 255) ? "$cmd died with error exit code ".($? >> 8) :
+ $? != 9 ? "$cmd died due to fatal signa, status $?" :
+ $timedout ? "$cmd timed out (${fetchtimeout}s)" :
+ "$cmd died due to unexpected SIGKILL";
+ if (length $fetcherr) {
+ $fetchfail .= "\n$fetcherr";
+ $fetchfail =~ s/\n$//;
+ $fetchfail =~ s{\n}{ // }g;
+ }
+ if ($fetch >= 2) {
+ gitfail $fetchfail;
+ } else {
+ servinfo "fetch/clone failed: $fetchfail";
+ }
+ }
+
+ if (!$exists) {
+ rename $tmpd, $gitd or fail "rename fresh $tmpd to $gitd: $!";
+ $exists = 1;
+ }
+ } else {
+ $fetchfail = 'not attempted';
+ }
+
+ if (!$exists) {
+ gitfail "no cached data, and not cloned: $fetchfail";
+ }
+
+ servinfo "sharing";
+ lockfile \*LOCK, $lock, LOCK_SH; # NB releases and relocks
+
+ if (stat $gitd) {
+ return 1;
+ }
+ $!==ENOENT or fail "stat $gitd: $!";
+
+ # Well, err, someone must have taken the lock in between
+ # and garbage collected it. How annoying.
+ return 0;
+}
+
+sub hkfail ($) { my ($msg) = @_; fail "housekeeping: $msg"; }
+
+sub housekeeping () {
+ logm 'info', "housekeeping started";
+ foreach $lock (<[a-z]*\\.lock>) {
+ my $subdir = $lock; $subdir =~ s/\\.lock$//;
+ if (!lstat $lock) {
+ $! == ENOENT or hkfail "$lock: lstat: $!";
+ next;
+ }
+ if (-M _ <= $treeexpiredays) {
+ logm 'debug', "housekeeping: subdirs $subdir: touched recently";
+ next;
+ }
+ if (!lockfile \*LOCK, $lock, LOCK_EX|LOCK_NB) {
+ logm 'info', "housekeeping: subdirs $subdir: lock busy, skipping";
+ next;
+ }
+ logm 'info', "housekeeping: subdirs $subdir: cleaning";
+ my $ok = 1;
+ foreach my $suffix (qw(tmp git)) {
+ my $dir = "${subdir}\\.$suffix";
+ my $errs;
+ remove_tree($dir, { safe=>1, error=>\$errs });
+ if (stat $dir) {
+ $ok = 0;
+ logm 'warning', "housekeeping: $dir: problems with".
+ "deletion prevent cleanup:";
+ foreach my $err (@$errs) {
+ logm 'info', "problem deleting: $err->[0]: $err->[1]";
+ }
+ }
+ }
+ if ($ok) {
+ unlink $lock or hkfail "remove $lock: $!";
+ }
+ }
+ open HS, ">", "Housekeeping.stamp" or hkfail "touch Housekeeping.stamp: $!";
+ close HS or hkfail "close Housekeeping.stamp: $!";
+ logm 'info', "housekeeping finished";
+}
+
+sub housekeepingcheck ($$) {
+ my ($dofork, $force) = @_;
+ if (!$force) {
+ if (!lockfile \*HLOCK, "Housekeeping.lock", LOCK_EX|LOCK_NB) {
+ logm 'debug', "housekeeping lock taken, not running";
+ close HLOCK;
+ return 0;
+ }
+ }
+ if ($force) {
+ logm 'info', "housekeeping forced";
+ } elsif (!lstat "Housekeeping.stamp") {
+ $! == ENOENT or fail "lstat Housekeeping.stamp: $!";
+ logm 'info', "housekeeping not done yet, will run";
+ } elsif (-M _ <= $housekeepingeverydays) {
+ logm 'debug', "housekeeping done recently";
+ close HLOCK;
+ return 0;
+ }
+ if ($dofork) {
+ my $child = fork;
+ defined $child or hkfail "fork: $!";
+ if (!$child) {
+ housekeeping();
+ exit 0;
+ }
+ } else {
+ housekeeping();
+ }
+ close HLOCK;
+ return 1;
+}
+
+sub runcommand () {
+ servinfo "serving";
+
+ chdir $gitd or fail "chdir $gitd: $!";
+
+ exec qw(git-upload-pack --strict --timeout=1000 .)
+ or fail "exec git-upload-pack: $!";
+}
+
+sub daemonservice () {
+ readcommand();
+ while (!clonefetch()) { }
+ housekeepingcheck(1,0);
+ runcommand();
+}
+
+if ($housekeepingonly) {
+ housekeepingcheck(0, $housekeepingonly>=2);
+} else {
+ daemonservice();
+}
-#!/usr/bin/perl
+#!/usr/bin/perl -w
# Copyright 2004 Ian Jackson <ian@chiark.greenend.org.uk>
#
# with this program; if not, consult the Free Software Foundation's
# website at www.fsf.org, or the GNU Project website at www.gnu.org.
+use strict;
+
use IO::Handle;
use IO::File;
use POSIX;
-$want= 1;
-$filename= "/usr/share/dict/words";
-@randfile= ("/dev/urandom", "/dev/random");
+our $want= 1;
+our $filename= "/usr/share/dict/words";
+our @randfile= ("/dev/urandom", "/dev/random");
+our $filemaxlen;
sub fail ($) { die "random-word: $_[0]\n"; }
open D, ">/dev/null" or fail("open /dev/null: $!");
-while ($ARGV[0] =~ m/^\-/) {
+while (@ARGV && $ARGV[0] =~ m/^\-/) {
$_= shift @ARGV;
if (m/^\-\-?$/) {
last;
} elsif (m/^\-n(\d+)$/) {
$want= $1;
} elsif (m/^\-f/ && length > 2) {
- $filename= $';
+ $filename= $'; #';
+ } elsif (m/^\-F(\d+)$/) {
+ $filemaxlen= $1;
} elsif (m/^\-r/ && length > 2) {
- @randfile= ($');
+ @randfile= ($'); #');
} elsif (m/^\-D$/) {
open D, ">&STDERR" or fail("dup stderr for debug: $!");
} else {
or fail("write debug: $!");
}
+our $randfile;
+our $r;
+
for $randfile (@randfile) {
$r= new IO::File "$randfile", 'r';
debug("open $randfile ".($r ? "ok" : "failed $!"));
$r or fail("could not open any random device: $!\n (tried @randfile)");
$r->autoflush(0);
-$w= new IO::File $filename, 'r';
+our $w= new IO::File $filename, 'r';
$w or fail("cannot open $filename: $!");
debug("open $filename ok");
-@words= <$w>;
+our @words;
+if (defined $filemaxlen) {
+ while (@words < $filemaxlen) {
+ my $l = <$w>;
+ last unless defined $l;
+ push @words, $l;
+ }
+} else {
+ @words= <$w>;
+}
$w->error and fail("cannot read $filename: $!");
debug("read $filename ok");
+our @out;
while (@out < $want) {
+ my $rbytes;
$!=0; read $r,$rbytes,4;
length $rbytes==4 or fail("cannot read $randfile: $!");
- $wordno= unpack 'L',$rbytes;
+ my $wordno= unpack 'L',$rbytes;
$wordno &= ~0x80000000;
$wordno %= @words;
$_= $words[$wordno];