chiark / gitweb /
Merge remote-tracking branch 'remotes/dgit/dgit/sid'
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Fri, 29 Nov 2013 20:04:22 +0000 (20:04 +0000)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Fri, 29 Nov 2013 20:04:22 +0000 (20:04 +0000)
Done with merge -s ours.  We are a descendant of this, logically
speaking, although the archive version is missing .gitignore.

15 files changed:
.gitignore [new file with mode: 0644]
cprogs/Makefile
cprogs/really.8
cprogs/really.c
cprogs/watershed.c
cprogs/xbatmon-simple.c
cprogs/xduplic-copier.1 [new file with mode: 0644]
cprogs/xduplic-copier.c [new file with mode: 0644]
debian/changelog
debian/control
debian/copyright
debian/rules
scripts/Makefile
scripts/git-cache-proxy [new file with mode: 0755]
scripts/random-word

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..d7146d6
--- /dev/null
@@ -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
index d328bf52416535031e6fb416e428ed7b9feafe34..5a349c10c2489364ab255da37d0da00211cc6bf3 100644 (file)
@@ -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)
index 45bca20fc4222b4090dd9013c7a8f878534c5af6..2344e14138934c294d30af3af61db55f5ca6d50d 100644 (file)
@@ -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 <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
index 46db5749510169b2302bb826d694bd749be3de56..ef2fb64dcb94f23b3801b297f8d7b1b7666fd681 100644 (file)
@@ -44,7 +44,7 @@ void usagemessage(void) {
             " -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); }
 }
 
index 3bf9b0763e3b1643e9a1cf20f0b85f08295af817..580d2204515a7c367b8cbe385f8420f7b2efe4e8 100644 (file)
 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 [<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) {
@@ -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();
     }
   }
index 6ca3294248233e652bf30437f9d519c5090a1abe..5d64ff482b7f1223704db8b2bb79b13064291eb8 100644 (file)
@@ -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 (file)
index 0000000..2e4977f
--- /dev/null
@@ -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 <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.
diff --git a/cprogs/xduplic-copier.c b/cprogs/xduplic-copier.c
new file mode 100644 (file)
index 0000000..80374f9
--- /dev/null
@@ -0,0 +1,283 @@
+/*
+ * 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;
+    }
+  }
+}
index 02b497241544c313b4c10d4a5c9128fa155702b2..cdee25fa9cf9a0fbcf87d243024cee16e4d004a7 100644 (file)
@@ -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 <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':
index f99a656c75155ee86139127da1c61e5252786047..4c20e5a2ac9922c061f70b787ad1a0a3ea7b1e1c 100644 (file)
@@ -2,7 +2,7 @@ Source: chiark-utils
 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
@@ -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
index 2d0cbc9b105114ce1daaaf8ccddb993dee7d296c..7ad96287c421faf802f95bdf0ab34691f3db6867 100644 (file)
@@ -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 <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>
index 62d84cfccfdc115b37bdda682985667e2c980c76..d1ec8159e94953606bd694d5de08dc8a3925b1e5 100755 (executable)
@@ -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; \
index 94458b6afaee642c5450426f1ea37e8ed90bdcf1..5a8d028300169208cf88fe34b02fcbdd94e60c3c 100644 (file)
@@ -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 (executable)
index 0000000..d12bdb3
--- /dev/null
@@ -0,0 +1,478 @@
+#!/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();
+}
index 66d09a27027afc28589ca4c65e18e08ce2007bbc..649af152a3c074a55be6e3b3ef33e081ec03881f 100755 (executable)
@@ -1,4 +1,4 @@
-#!/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 {
@@ -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];