From: Ian Jackson Date: Fri, 29 Nov 2013 20:04:22 +0000 (+0000) Subject: Merge remote-tracking branch 'remotes/dgit/dgit/sid' X-Git-Tag: debian/4.3.0~11 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?p=chiark-utils.git;a=commitdiff_plain;h=c46b09429cb3309bfb32fd57c294c9dc4d5cf6e9;hp=dcb3df3fda85c612a887127d5357806238c72f0b Merge remote-tracking branch 'remotes/dgit/dgit/sid' Done with merge -s ours. We are a descendant of this, logically speaking, although the archive version is missing .gitignore. --- diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d7146d6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +*~ +*.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 diff --git a/cprogs/Makefile b/cprogs/Makefile index d328bf5..5a349c1 100644 --- a/cprogs/Makefile +++ b/cprogs/Makefile @@ -28,10 +28,11 @@ include ../settings.make 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) @@ -48,8 +49,9 @@ really: really.o myopt.o 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 @@ -79,7 +81,7 @@ install-docs: watershed.txt install-examples: clean: - rm -f *~ ./#*# *.o + rm -f *~ ./#*# *.o $(PROGRAMS) distclean realclean: clean rm -f $(TARGETS) diff --git a/cprogs/really.8 b/cprogs/really.8 index 45bca20..2344e14 100644 --- a/cprogs/really.8 +++ b/cprogs/really.8 @@ -8,11 +8,11 @@ really \- gain privilege or run commands a different user .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 @@ -20,11 +20,14 @@ will run .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 @@ -68,6 +71,17 @@ relative position of .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. @@ -126,7 +140,7 @@ This version of .B really was written by Ian Jackson . .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 . .PP .B really diff --git a/cprogs/really.c b/cprogs/really.c index 46db574..ef2fb64 100644 --- a/cprogs/really.c +++ b/cprogs/really.c @@ -44,7 +44,7 @@ void usagemessage(void) { " -G|--gid } the group list\n" "other really-options:\n" " -h|--help display this message\n" - " -R|--chroot chroot (but *not* chdir)\n", + " -R|--chroot chroot (but *not* chdir - danger!)\n", stderr) == EOF) { perror("write usage"); exit(-1); } } diff --git a/cprogs/watershed.c b/cprogs/watershed.c index 3bf9b07..580d220 100644 --- a/cprogs/watershed.c +++ b/cprogs/watershed.c @@ -270,6 +270,7 @@ static const struct option os[]= { { "--state-dir", 1,0,'d' }, { "--command-id",1,0,'i' }, + { "--help", 0,0,'h' }, { 0 } }; @@ -294,11 +295,17 @@ static int cohort_fd, lock_fd; }while(0) -static void badusage(void) { +static void printusage(FILE *f) { fputs(_("usage: watershed [] ...\n" - "options: -d|--state-dir -i|--command-id \n" + "options:\n" + " -d|--state-dir \n" + " -i|--command-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) { @@ -330,11 +337,12 @@ static char *m_asprintf(const char *fmt, ...) { 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(); } } diff --git a/cprogs/xbatmon-simple.c b/cprogs/xbatmon-simple.c index 6ca3294..5d64ff4 100644 --- a/cprogs/xbatmon-simple.c +++ b/cprogs/xbatmon-simple.c @@ -326,6 +326,16 @@ ALL_VARS(V_NOTFOUND) 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; diff --git a/cprogs/xduplic-copier.1 b/cprogs/xduplic-copier.1 new file mode 100644 index 0000000..2e4977f --- /dev/null +++ b/cprogs/xduplic-copier.1 @@ -0,0 +1,79 @@ +.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 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. diff --git a/cprogs/xduplic-copier.c b/cprogs/xduplic-copier.c new file mode 100644 index 0000000..80374f9 --- /dev/null +++ b/cprogs/xduplic-copier.c @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2002,2013 Ian Jackson + * + * 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 . + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +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; + } + } +} diff --git a/debian/changelog b/debian/changelog index 02b4972..cdee25f 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,51 @@ +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 Mon, 11 Nov 2013 13:43:47 +0000 + +chiark-utils (4.2.1~~iwj2) unstable; urgency=low + + New utility: + * git-cache-proxy + + -- Ian Jackson Mon, 11 Nov 2013 13:14:35 +0000 + +chiark-utils (4.2.1~~iwj1) unstable; urgency=low + + New features: + * random-word: -F option for using only first lines of source file. + + Code cleanups: + * random-word: Some perl-mode emacs formatting glitch workarounds. + * random-word: use strict; -w. + + -- Ian Jackson 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 Sat, 29 Sep 2012 13:47:21 +0100 + chiark-utils (4.2.0) unstable; urgency=low * Rename `xacpi-simple' to `xbatmon-simple': diff --git a/debian/control b/debian/control index f99a656..4c20e5a 100644 --- a/debian/control +++ b/debian/control @@ -2,7 +2,7 @@ Source: chiark-utils Section: admin Priority: extra Maintainer: Ian Jackson -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 @@ -96,6 +96,9 @@ Description: chiark system administration utilities 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 diff --git a/debian/copyright b/debian/copyright index 2d0cbc9..7ad9628 100644 --- a/debian/copyright +++ b/debian/copyright @@ -32,6 +32,12 @@ watershed, a tool for optimising runs of idempotent commands, is xbatmon-simple, a simple X client for displaying ACPI battery status Copyright 2004,2012 Ian Jackson +xduplic-copier, a simple X client for typing into multiple windows + Copyright 2002,2013 Ian Jackson + +git-cache-proxy, a rather shoddy daemon for speeding up git clone + Copyright 2013 Ian Jackson + summer, a tool for reporting complete details about a filesystem tree Copyright 2003-2007 Ian Jackson manpage Copyright 2006 Peter Maydell diff --git a/debian/rules b/debian/rules index 62d84cf..d1ec815 100755 --- a/debian/rules +++ b/debian/rules @@ -123,7 +123,8 @@ binary-arch: checkroot build binary-prep $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; \ diff --git a/scripts/Makefile b/scripts/Makefile index 94458b6..5a8d028 100644 --- a/scripts/Makefile +++ b/scripts/Makefile @@ -24,7 +24,8 @@ include ../settings.make 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 diff --git a/scripts/git-cache-proxy b/scripts/git-cache-proxy new file mode 100755 index 0000000..d12bdb3 --- /dev/null +++ b/scripts/git-cache-proxy @@ -0,0 +1,478 @@ +#!/usr/bin/perl -w +# +# git caching proxy + +# usage: run it on some port, and then clone or fetch +# "git://:/[ ]" +# where is http:///... or git:///... +# and is zero or more (whitespace-separated) of +# [] will be ignored if not recognised +# {} 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= 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 and subsequently +# heavily modified by Ian Jackson +# 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 () { + 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->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(); +} diff --git a/scripts/random-word b/scripts/random-word index 66d09a2..649af15 100755 --- a/scripts/random-word +++ b/scripts/random-word @@ -1,4 +1,4 @@ -#!/usr/bin/perl +#!/usr/bin/perl -w # Copyright 2004 Ian Jackson # @@ -16,27 +16,32 @@ # 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 { @@ -49,6 +54,9 @@ sub debug ($) { or fail("write debug: $!"); } +our $randfile; +our $r; + for $randfile (@randfile) { $r= new IO::File "$randfile", 'r'; debug("open $randfile ".($r ? "ok" : "failed $!")); @@ -58,17 +66,28 @@ for $randfile (@randfile) { $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];