Login
Hex Artifact Content
Login

Artifact b5a96c5e7f8ab949d003655efb335bb50da22daa88fe9ee313702ab1e8e32fb0:

Attachment "scripts-for-review-2.tar" to tech note [bae6999eb9] added by opus-ocx2 2026-10-29 01:50:10.
0000: 73 63 72 69 70 74 73 2d 66 6f 72 2d 72 65 76 69  scripts-for-revi
0010: 65 77 2d 32 2f 00 00 00 00 00 00 00 00 00 00 00  ew-2/...........
0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0060: 00 00 00 00 30 30 30 30 37 35 35 00 30 30 30 30  ....0000755.0000
0070: 30 30 30 00 30 30 30 30 30 30 30 00 30 30 30 30  000.0000000.0000
0080: 30 30 30 30 30 30 30 00 31 35 32 37 30 35 32 33  0000000.15270523
0090: 31 32 32 00 30 31 33 34 36 34 00 20 35 00 00 00  122.013464. 5...
00a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0100: 00 75 73 74 61 72 20 20 00 72 6f 6f 74 00 00 00  .ustar  .root...
0110: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0120: 00 00 00 00 00 00 00 00 00 72 6f 6f 74 00 00 00  .........root...
0130: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0140: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0150: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0160: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0170: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0180: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0190: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
01a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
01b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
01c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
01d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
01e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
01f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0200: 73 63 72 69 70 74 73 2d 66 6f 72 2d 72 65 76 69  scripts-for-revi
0210: 65 77 2d 32 2f 73 74 61 72 74 2d 6f 63 78 2e 63  ew-2/start-ocx.c
0220: 72 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  r...............
0230: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0240: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0250: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0260: 00 00 00 00 30 30 30 30 36 34 34 00 30 30 30 30  ....0000644.0000
0270: 30 30 30 00 30 30 30 30 30 30 30 00 30 30 30 30  000.0000000.0000
0280: 30 30 32 33 34 35 30 00 31 35 32 37 30 35 32 33  0023450.15270523
0290: 31 32 32 00 30 31 35 37 34 32 00 20 30 00 00 00  122.015742. 0...
02a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
02b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
02c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
02d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
02e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
02f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0300: 00 75 73 74 61 72 20 20 00 72 6f 6f 74 00 00 00  .ustar  .root...
0310: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0320: 00 00 00 00 00 00 00 00 00 72 6f 6f 74 00 00 00  .........root...
0330: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0340: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0350: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0360: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0370: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0380: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0390: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
03a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
03b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
03c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
03d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
03e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
03f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0400: 23 20 41 67 65 6e 74 73 3a 20 64 6f 20 6e 6f 74  # Agents: do not
0410: 20 63 68 61 6e 67 65 20 74 68 69 73 20 66 69 6c   change this fil
0420: 65 20 6f 6e 20 61 6e 79 20 62 72 61 6e 63 68 20  e on any branch 
0430: 77 69 74 68 6f 75 74 20 74 68 65 20 6d 61 69 6e  without the main
0440: 74 61 69 6e 65 72 27 73 20 65 78 70 6c 69 63 69  tainer's explici
0450: 74 20 61 70 70 72 6f 76 61 6c 20 6f 6e 20 74 68  t approval on th
0460: 65 20 72 65 63 6f 72 64 2e 0a 23 20 53 65 6c 66  e record..# Self
0470: 2d 63 6f 6e 74 61 69 6e 65 64 20 68 6f 73 74 20  -contained host 
0480: 6c 61 75 6e 63 68 65 72 3a 20 6f 6e 6c 79 20 74  launcher: only t
0490: 68 65 20 70 61 74 68 73 20 62 65 6c 6f 77 2c 20  he paths below, 
04a0: 72 65 6c 65 61 73 65 20 6c 69 6e 6b 73 2c 20 2f  release links, /
04b0: 70 72 6f 63 2f 73 65 6c 66 2f 66 64 2c 0a 23 20  proc/self/fd,.# 
04c0: 65 78 65 63 75 74 61 62 6c 65 20 6c 6f 63 61 74  executable locat
04d0: 69 6f 6e 2c 20 65 78 69 73 74 69 6e 67 20 68 6f  ion, existing ho
04e0: 6d 65 20 70 61 74 68 73 20 61 6e 64 20 46 6f 73  me paths and Fos
04f0: 73 69 6c 27 73 20 43 47 49 20 65 6e 76 69 72 6f  sil's CGI enviro
0500: 6e 6d 65 6e 74 20 61 72 65 20 72 65 61 64 2e 0a  nment are read..
0510: 23 20 42 75 69 6c 64 20 62 65 73 69 64 65 20 72  # Build beside r
0520: 65 63 65 69 70 74 73 20 77 69 74 68 20 60 73 68  eceipts with `sh
0530: 61 72 64 73 20 62 75 69 6c 64 20 73 74 61 72 74  ards build start
0540: 2d 6f 63 78 20 72 65 63 65 69 70 74 73 60 20 69  -ocx receipts` i
0550: 6e 20 61 20 63 68 65 63 6b 6f 75 74 2e 0a 72 65  n a checkout..re
0560: 71 75 69 72 65 20 22 6a 73 6f 6e 22 0a 72 65 71  quire "json".req
0570: 75 69 72 65 20 22 73 6f 63 6b 65 74 22 0a 0a 6c  uire "socket"..l
0580: 69 62 20 4c 69 62 43 0a 20 20 53 4f 5f 53 4e 44  ib LibC.  SO_SND
0590: 54 49 4d 45 4f 20 3d 20 32 31 0a 0a 20 20 66 75  TIMEO = 21..  fu
05a0: 6e 20 65 78 65 63 76 28 70 61 74 68 20 3a 20 43  n execv(path : C
05b0: 68 61 72 2a 2c 20 61 72 67 76 20 3a 20 43 68 61  har*, argv : Cha
05c0: 72 2a 2a 29 20 3a 20 49 6e 74 0a 65 6e 64 0a 0a  r**) : Int.end..
05d0: 6d 6f 64 75 6c 65 20 53 74 61 72 74 4f 43 58 0a  module StartOCX.
05e0: 20 20 4c 49 56 45 5f 48 4f 53 54 20 20 20 20 20    LIVE_HOST     
05f0: 20 20 20 20 3d 20 22 69 70 2d 31 37 32 2d 33 31      = "ip-172-31
0600: 2d 31 37 2d 31 37 37 2e 75 73 2d 77 65 73 74 2d  -17-177.us-west-
0610: 32 2e 63 6f 6d 70 75 74 65 2e 69 6e 74 65 72 6e  2.compute.intern
0620: 61 6c 22 0a 20 20 4c 49 56 45 5f 52 45 50 4f 53  al".  LIVE_REPOS
0630: 49 54 4f 52 49 45 53 20 3d 20 22 2f 76 61 72 2f  ITORIES = "/var/
0640: 6c 69 62 2f 66 6f 73 73 69 6c 22 0a 20 20 4c 49  lib/fossil".  LI
0650: 56 45 5f 53 4f 43 4b 45 54 5f 44 49 52 20 20 20  VE_SOCKET_DIR   
0660: 3d 20 22 2f 72 75 6e 2f 6f 63 78 22 0a 20 20 4c  = "/run/ocx".  L
0670: 49 56 45 5f 52 45 4c 45 41 53 45 20 20 20 20 20  IVE_RELEASE     
0680: 20 3d 20 22 2f 76 61 72 2f 6c 69 62 2f 6f 63 78   = "/var/lib/ocx
0690: 2f 72 65 6c 65 61 73 65 32 22 0a 20 20 4c 49 56  /release2".  LIV
06a0: 45 5f 44 41 45 4d 4f 4e 20 20 20 20 20 20 20 3d  E_DAEMON       =
06b0: 20 22 2f 76 61 72 2f 6c 69 62 2f 6f 63 78 2f 61   "/var/lib/ocx/a
06c0: 67 65 6e 74 73 2d 64 61 65 6d 6f 6e 22 0a 20 20  gents-daemon".  
06d0: 23 20 74 68 65 20 64 61 65 6d 6f 6e 27 73 20 61  # the daemon's a
06e0: 63 63 6f 75 6e 74 20 6b 65 65 70 73 20 69 74 73  ccount keeps its
06f0: 20 68 6f 6d 65 73 20 68 65 72 65 3b 20 69 6e 73   homes here; ins
0700: 69 64 65 20 69 74 73 20 6e 61 6d 65 73 70 61 63  ide its namespac
0710: 65 20 74 68 65 79 20 61 72 65 20 61 74 0a 20 20  e they are at.  
0720: 23 20 2f 72 6f 6f 74 2c 20 77 68 65 72 65 20 72  # /root, where r
0730: 75 6e 73 27 20 70 61 74 74 65 72 6e 73 20 61 6e  uns' patterns an
0740: 64 20 72 2d 73 65 6e 64 65 72 73 2e 6a 73 6f 6e  d r-senders.json
0750: 20 6e 61 6d 65 20 74 68 65 6d 0a 20 20 4c 49 56   name them.  LIV
0760: 45 5f 48 4f 4d 45 5f 53 4f 55 52 43 45 20 3d 20  E_HOME_SOURCE = 
0770: 22 2f 68 6f 6d 65 2f 6f 63 78 22 0a 20 20 4c 49  "/home/ocx".  LI
0780: 56 45 5f 48 4f 4d 45 20 20 20 20 20 20 20 20 3d  VE_HOME        =
0790: 20 22 2f 72 6f 6f 74 22 0a 20 20 43 47 49 5f 46   "/root".  CGI_F
07a0: 49 45 4c 44 53 20 20 20 20 20 20 20 3d 20 25 77  IELDS       = %w
07b0: 28 52 45 51 55 45 53 54 5f 4d 45 54 48 4f 44 20  (REQUEST_METHOD 
07c0: 51 55 45 52 59 5f 53 54 52 49 4e 47 20 50 41 54  QUERY_STRING PAT
07d0: 48 5f 49 4e 46 4f 20 43 4f 4e 54 45 4e 54 5f 4c  H_INFO CONTENT_L
07e0: 45 4e 47 54 48 20 43 4f 4e 54 45 4e 54 5f 54 59  ENGTH CONTENT_TY
07f0: 50 45 20 48 54 54 50 53 20 48 54 54 50 5f 48 4f  PE HTTPS HTTP_HO
0800: 53 54 20 48 54 54 50 5f 52 45 46 45 52 45 52 20  ST HTTP_REFERER 
0810: 46 4f 53 53 49 4c 5f 55 53 45 52 20 46 4f 53 53  FOSSIL_USER FOSS
0820: 49 4c 5f 43 41 50 41 42 49 4c 49 54 49 45 53 20  IL_CAPABILITIES 
0830: 46 4f 53 53 49 4c 5f 55 52 49 29 0a 20 20 4c 49  FOSSIL_URI).  LI
0840: 42 52 41 52 49 45 53 20 20 20 20 20 20 20 20 3d  BRARIES        =
0850: 20 25 77 28 2f 6c 69 62 20 2f 6c 69 62 36 34 29   %w(/lib /lib64)
0860: 0a 20 20 44 41 45 4d 4f 4e 5f 53 59 53 54 45 4d  .  DAEMON_SYSTEM
0870: 20 20 20 20 3d 20 25 77 28 2f 6c 69 62 20 2f 6c      = %w(/lib /l
0880: 69 62 36 34 20 2f 62 69 6e 20 2f 73 62 69 6e 20  ib64 /bin /sbin 
0890: 2f 65 74 63 2f 73 73 6c 20 2f 65 74 63 2f 70 6b  /etc/ssl /etc/pk
08a0: 69 20 2f 65 74 63 2f 72 65 73 6f 6c 76 2e 63 6f  i /etc/resolv.co
08b0: 6e 66 20 2f 65 74 63 2f 68 6f 73 74 73 20 2f 65  nf /etc/hosts /e
08c0: 74 63 2f 6e 73 73 77 69 74 63 68 2e 63 6f 6e 66  tc/nsswitch.conf
08d0: 20 2f 65 74 63 2f 70 61 73 73 77 64 20 2f 65 74   /etc/passwd /et
08e0: 63 2f 67 72 6f 75 70 29 0a 0a 20 20 23 20 65 61  c/group)..  # ea
08f0: 63 68 20 68 6f 6d 65 3a 20 77 68 65 72 65 20 74  ch home: where t
0900: 68 65 20 61 63 63 6f 75 6e 74 20 6b 65 65 70 73  he account keeps
0910: 20 69 74 2c 20 77 68 65 72 65 20 74 68 65 20 64   it, where the d
0920: 61 65 6d 6f 6e 20 73 65 65 73 20 69 74 2c 20 61  aemon sees it, a
0930: 6e 64 20 77 68 65 74 68 65 72 20 69 74 20 6d 61  nd whether it ma
0940: 79 20 77 72 69 74 65 20 74 68 65 72 65 0a 20 20  y write there.  
0950: 72 65 63 6f 72 64 20 50 61 74 68 73 2c 20 72 65  record Paths, re
0960: 70 6f 73 69 74 6f 72 69 65 73 20 3a 20 53 74 72  positories : Str
0970: 69 6e 67 2c 20 73 6f 63 6b 65 74 5f 64 69 72 20  ing, socket_dir 
0980: 3a 20 53 74 72 69 6e 67 2c 20 72 65 6c 65 61 73  : String, releas
0990: 65 20 3a 20 53 74 72 69 6e 67 2c 20 64 61 65 6d  e : String, daem
09a0: 6f 6e 20 3a 20 53 74 72 69 6e 67 2c 20 68 6f 6d  on : String, hom
09b0: 65 20 3a 20 53 74 72 69 6e 67 2c 20 68 6f 6d 65  e : String, home
09c0: 73 20 3a 20 41 72 72 61 79 28 54 75 70 6c 65 28  s : Array(Tuple(
09d0: 53 74 72 69 6e 67 2c 20 53 74 72 69 6e 67 2c 20  String, String, 
09e0: 42 6f 6f 6c 29 29 20 64 6f 0a 20 20 20 20 64 65  Bool)) do.    de
09f0: 66 20 73 6f 63 6b 65 74 20 3a 20 53 74 72 69 6e  f socket : Strin
0a00: 67 0a 20 20 20 20 20 20 46 69 6c 65 2e 6a 6f 69  g.      File.joi
0a10: 6e 28 73 6f 63 6b 65 74 5f 64 69 72 2c 20 22 72  n(socket_dir, "r
0a20: 65 63 65 69 70 74 73 2e 73 6f 63 6b 22 29 0a 20  eceipts.sock"). 
0a30: 20 20 20 65 6e 64 0a 20 20 65 6e 64 0a 0a 20 20     end.  end..  
0a40: 64 65 66 20 73 65 6c 66 2e 70 61 74 68 73 28 73  def self.paths(s
0a50: 63 72 61 74 63 68 20 3a 20 53 74 72 69 6e 67 3f  cratch : String?
0a60: 2c 20 68 6f 73 74 20 3a 20 53 74 72 69 6e 67 29  , host : String)
0a70: 20 3a 20 50 61 74 68 73 0a 20 20 20 20 69 66 20   : Paths.    if 
0a80: 73 63 72 61 74 63 68 0a 20 20 20 20 20 20 72 61  scratch.      ra
0a90: 69 73 65 20 41 72 67 75 6d 65 6e 74 45 72 72 6f  ise ArgumentErro
0aa0: 72 2e 6e 65 77 28 22 73 63 72 61 74 63 68 20 64  r.new("scratch d
0ab0: 69 72 65 63 74 6f 72 79 20 6d 75 73 74 20 62 65  irectory must be
0ac0: 20 61 62 73 6f 6c 75 74 65 22 29 20 75 6e 6c 65   absolute") unle
0ad0: 73 73 20 73 63 72 61 74 63 68 2e 73 74 61 72 74  ss scratch.start
0ae0: 73 5f 77 69 74 68 3f 28 22 2f 22 29 0a 20 20 20  s_with?("/").   
0af0: 20 20 20 72 6f 6f 74 20 3d 20 46 69 6c 65 2e 65     root = File.e
0b00: 78 70 61 6e 64 5f 70 61 74 68 28 73 63 72 61 74  xpand_path(scrat
0b10: 63 68 29 0a 20 20 20 20 20 20 68 6f 6d 65 20 3d  ch).      home =
0b20: 20 46 69 6c 65 2e 6a 6f 69 6e 28 72 6f 6f 74 2c   File.join(root,
0b30: 20 22 68 6f 6d 65 22 29 0a 20 20 20 20 20 20 50   "home").      P
0b40: 61 74 68 73 2e 6e 65 77 28 46 69 6c 65 2e 6a 6f  aths.new(File.jo
0b50: 69 6e 28 72 6f 6f 74 2c 20 22 66 6f 73 73 69 6c  in(root, "fossil
0b60: 22 29 2c 20 46 69 6c 65 2e 6a 6f 69 6e 28 72 6f  "), File.join(ro
0b70: 6f 74 2c 20 22 72 75 6e 22 29 2c 20 46 69 6c 65  ot, "run"), File
0b80: 2e 6a 6f 69 6e 28 72 6f 6f 74 2c 20 22 72 65 6c  .join(root, "rel
0b90: 65 61 73 65 22 29 2c 20 46 69 6c 65 2e 6a 6f 69  ease"), File.joi
0ba0: 6e 28 72 6f 6f 74 2c 20 22 64 61 65 6d 6f 6e 22  n(root, "daemon"
0bb0: 29 2c 20 68 6f 6d 65 2c 20 5b 7b 46 69 6c 65 2e  ), home, [{File.
0bc0: 6a 6f 69 6e 28 68 6f 6d 65 2c 20 22 2e 63 6f 64  join(home, ".cod
0bd0: 65 78 22 29 2c 20 46 69 6c 65 2e 6a 6f 69 6e 28  ex"), File.join(
0be0: 68 6f 6d 65 2c 20 22 2e 63 6f 64 65 78 22 29 2c  home, ".codex"),
0bf0: 20 74 72 75 65 7d 5d 29 0a 20 20 20 20 65 6c 73   true}]).    els
0c00: 69 66 20 68 6f 73 74 20 3d 3d 20 4c 49 56 45 5f  if host == LIVE_
0c10: 48 4f 53 54 0a 20 20 20 20 20 20 50 61 74 68 73  HOST.      Paths
0c20: 2e 6e 65 77 28 4c 49 56 45 5f 52 45 50 4f 53 49  .new(LIVE_REPOSI
0c30: 54 4f 52 49 45 53 2c 20 4c 49 56 45 5f 53 4f 43  TORIES, LIVE_SOC
0c40: 4b 45 54 5f 44 49 52 2c 20 4c 49 56 45 5f 52 45  KET_DIR, LIVE_RE
0c50: 4c 45 41 53 45 2c 20 4c 49 56 45 5f 44 41 45 4d  LEASE, LIVE_DAEM
0c60: 4f 4e 2c 20 4c 49 56 45 5f 48 4f 4d 45 2c 0a 20  ON, LIVE_HOME,. 
0c70: 20 20 20 20 20 20 20 5b 22 2e 63 6f 64 65 78 22         [".codex"
0c80: 2c 20 22 2e 63 6c 61 75 64 65 22 2c 20 22 2e 63  , ".claude", ".c
0c90: 6c 61 75 64 65 2e 6a 73 6f 6e 22 5d 2e 6d 61 70  laude.json"].map
0ca0: 20 7b 20 7c 68 7c 20 7b 46 69 6c 65 2e 6a 6f 69   { |h| {File.joi
0cb0: 6e 28 4c 49 56 45 5f 48 4f 4d 45 5f 53 4f 55 52  n(LIVE_HOME_SOUR
0cc0: 43 45 2c 20 68 29 2c 20 46 69 6c 65 2e 6a 6f 69  CE, h), File.joi
0cd0: 6e 28 4c 49 56 45 5f 48 4f 4d 45 2c 20 68 29 2c  n(LIVE_HOME, h),
0ce0: 20 74 72 75 65 7d 20 7d 20 2b 0a 20 20 20 20 20   true} } +.     
0cf0: 20 20 20 5b 7b 46 69 6c 65 2e 6a 6f 69 6e 28 4c     [{File.join(L
0d00: 49 56 45 5f 48 4f 4d 45 5f 53 4f 55 52 43 45 2c  IVE_HOME_SOURCE,
0d10: 20 22 2e 6c 6f 63 61 6c 22 29 2c 20 46 69 6c 65   ".local"), File
0d20: 2e 6a 6f 69 6e 28 4c 49 56 45 5f 48 4f 4d 45 2c  .join(LIVE_HOME,
0d30: 20 22 2e 6c 6f 63 61 6c 22 29 2c 20 66 61 6c 73   ".local"), fals
0d40: 65 7d 5d 29 0a 20 20 20 20 65 6c 73 65 0a 20 20  e}]).    else.  
0d50: 20 20 20 20 72 61 69 73 65 20 41 72 67 75 6d 65      raise Argume
0d60: 6e 74 45 72 72 6f 72 2e 6e 65 77 28 22 75 6e 6b  ntError.new("unk
0d70: 6e 6f 77 6e 20 68 6f 73 74 3b 20 75 73 65 20 2d  nown host; use -
0d80: 2d 73 63 72 61 74 63 68 20 44 49 52 22 29 0a 20  -scratch DIR"). 
0d90: 20 20 20 65 6e 64 0a 20 20 65 6e 64 0a 0a 20 20     end.  end..  
0da0: 23 20 54 68 65 73 65 20 74 68 72 65 65 20 62 75  # These three bu
0db0: 69 6c 64 65 72 73 20 72 65 61 64 20 6e 6f 74 68  ilders read noth
0dc0: 69 6e 67 3a 20 74 68 65 69 72 20 66 75 6c 6c 20  ing: their full 
0dd0: 61 72 67 75 6d 65 6e 74 20 6c 69 73 74 73 20 63  argument lists c
0de0: 61 6e 20 62 65 20 69 6e 73 70 65 63 74 65 64 2e  an be inspected.
0df0: 0a 20 20 64 65 66 20 73 65 6c 66 2e 63 67 69 5f  .  def self.cgi_
0e00: 61 72 67 73 28 72 65 6c 65 61 73 65 20 3a 20 53  args(release : S
0e10: 74 72 69 6e 67 2c 20 72 65 70 6f 73 69 74 6f 72  tring, repositor
0e20: 79 20 3a 20 53 74 72 69 6e 67 2c 20 65 6e 76 69  y : String, envi
0e30: 72 6f 6e 6d 65 6e 74 20 3a 20 48 61 73 68 28 53  ronment : Hash(S
0e40: 74 72 69 6e 67 2c 20 53 74 72 69 6e 67 29 29 20  tring, String)) 
0e50: 3a 20 41 72 72 61 79 28 53 74 72 69 6e 67 29 0a  : Array(String).
0e60: 20 20 20 20 61 72 67 73 20 3d 20 25 77 28 2d 2d      args = %w(--
0e70: 75 6e 73 68 61 72 65 2d 61 6c 6c 20 2d 2d 64 69  unshare-all --di
0e80: 65 2d 77 69 74 68 2d 70 61 72 65 6e 74 20 2d 2d  e-with-parent --
0e90: 6e 65 77 2d 73 65 73 73 69 6f 6e 20 2d 2d 63 61  new-session --ca
0ea0: 70 2d 64 72 6f 70 20 41 4c 4c 20 2d 2d 63 6c 65  p-drop ALL --cle
0eb0: 61 72 65 6e 76 20 2d 2d 72 6f 2d 62 69 6e 64 20  arenv --ro-bind 
0ec0: 2f 75 73 72 20 2f 75 73 72 29 0a 20 20 20 20 28  /usr /usr).    (
0ed0: 4c 49 42 52 41 52 49 45 53 20 2b 20 5b 22 2f 62  LIBRARIES + ["/b
0ee0: 69 6e 22 2c 20 22 2f 65 74 63 2f 70 61 73 73 77  in", "/etc/passw
0ef0: 64 22 5d 29 2e 65 61 63 68 20 7b 20 7c 70 61 74  d"]).each { |pat
0f00: 68 7c 20 61 72 67 73 2e 63 6f 6e 63 61 74 28 5b  h| args.concat([
0f10: 22 2d 2d 72 6f 2d 62 69 6e 64 2d 74 72 79 22 2c  "--ro-bind-try",
0f20: 20 70 61 74 68 2c 20 70 61 74 68 5d 29 20 7d 0a   path, path]) }.
0f30: 20 20 20 20 61 72 67 73 2e 63 6f 6e 63 61 74 28      args.concat(
0f40: 5b 22 2d 2d 70 72 6f 63 22 2c 20 22 2f 70 72 6f  ["--proc", "/pro
0f50: 63 22 2c 20 22 2d 2d 64 65 76 22 2c 20 22 2f 64  c", "--dev", "/d
0f60: 65 76 22 2c 20 22 2d 2d 74 6d 70 66 73 22 2c 20  ev", "--tmpfs", 
0f70: 22 2f 74 6d 70 22 2c 20 22 2d 2d 72 6f 2d 62 69  "/tmp", "--ro-bi
0f80: 6e 64 22 2c 20 46 69 6c 65 2e 6a 6f 69 6e 28 72  nd", File.join(r
0f90: 65 6c 65 61 73 65 2c 20 22 6f 63 78 32 22 29 2c  elease, "ocx2"),
0fa0: 20 22 2f 72 65 6c 65 61 73 65 2f 6f 63 78 32 2d   "/release/ocx2-
0fb0: 63 67 69 22 5d 29 0a 20 20 20 20 43 47 49 5f 46  cgi"]).    CGI_F
0fc0: 49 45 4c 44 53 2e 65 61 63 68 20 64 6f 20 7c 6b  IELDS.each do |k
0fd0: 65 79 7c 0a 20 20 20 20 20 20 69 66 20 76 61 6c  ey|.      if val
0fe0: 75 65 20 3d 20 65 6e 76 69 72 6f 6e 6d 65 6e 74  ue = environment
0ff0: 5b 6b 65 79 5d 3f 0a 20 20 20 20 20 20 20 20 61  [key]?.        a
1000: 72 67 73 2e 63 6f 6e 63 61 74 28 5b 22 2d 2d 73  rgs.concat(["--s
1010: 65 74 65 6e 76 22 2c 20 6b 65 79 2c 20 76 61 6c  etenv", key, val
1020: 75 65 5d 29 0a 20 20 20 20 20 20 65 6e 64 0a 20  ue]).      end. 
1030: 20 20 20 65 6e 64 0a 20 20 20 20 61 72 67 73 2e     end.    args.
1040: 63 6f 6e 63 61 74 28 5b 22 2d 2d 73 65 74 65 6e  concat(["--seten
1050: 76 22 2c 20 22 46 4f 53 53 49 4c 5f 52 45 50 4f  v", "FOSSIL_REPO
1060: 53 49 54 4f 52 59 22 2c 20 72 65 70 6f 73 69 74  SITORY", reposit
1070: 6f 72 79 2c 20 22 2d 2d 73 65 74 65 6e 76 22 2c  ory, "--setenv",
1080: 20 22 50 41 54 48 22 2c 20 22 2f 75 73 72 2f 62   "PATH", "/usr/b
1090: 69 6e 22 2c 20 22 2f 72 65 6c 65 61 73 65 2f 6f  in", "/release/o
10a0: 63 78 32 2d 63 67 69 22 2c 20 22 2d 2d 72 65 63  cx2-cgi", "--rec
10b0: 65 69 70 74 2d 66 64 22 2c 20 22 33 22 5d 29 0a  eipt-fd", "3"]).
10c0: 20 20 65 6e 64 0a 0a 20 20 64 65 66 20 73 65 6c    end..  def sel
10d0: 66 2e 72 65 63 65 69 70 74 73 5f 61 72 67 73 28  f.receipts_args(
10e0: 70 61 74 68 73 20 3a 20 50 61 74 68 73 2c 20 65  paths : Paths, e
10f0: 78 65 63 75 74 61 62 6c 65 5f 64 69 72 20 3a 20  xecutable_dir : 
1100: 53 74 72 69 6e 67 29 20 3a 20 41 72 72 61 79 28  String) : Array(
1110: 53 74 72 69 6e 67 29 0a 20 20 20 20 61 72 67 73  String).    args
1120: 20 3d 20 25 77 28 2d 2d 75 6e 73 68 61 72 65 2d   = %w(--unshare-
1130: 61 6c 6c 20 2d 2d 64 69 65 2d 77 69 74 68 2d 70  all --die-with-p
1140: 61 72 65 6e 74 20 2d 2d 6e 65 77 2d 73 65 73 73  arent --new-sess
1150: 69 6f 6e 20 2d 2d 63 61 70 2d 64 72 6f 70 20 41  ion --cap-drop A
1160: 4c 4c 20 2d 2d 63 6c 65 61 72 65 6e 76 20 2d 2d  LL --clearenv --
1170: 72 6f 2d 62 69 6e 64 20 2f 75 73 72 20 2f 75 73  ro-bind /usr /us
1180: 72 29 0a 20 20 20 20 4c 49 42 52 41 52 49 45 53  r).    LIBRARIES
1190: 2e 65 61 63 68 20 7b 20 7c 70 61 74 68 7c 20 61  .each { |path| a
11a0: 72 67 73 2e 63 6f 6e 63 61 74 28 5b 22 2d 2d 72  rgs.concat(["--r
11b0: 6f 2d 62 69 6e 64 2d 74 72 79 22 2c 20 70 61 74  o-bind-try", pat
11c0: 68 2c 20 70 61 74 68 5d 29 20 7d 0a 20 20 20 20  h, path]) }.    
11d0: 61 72 67 73 2e 63 6f 6e 63 61 74 28 5b 22 2d 2d  args.concat(["--
11e0: 64 65 76 22 2c 20 22 2f 64 65 76 22 2c 20 22 2d  dev", "/dev", "-
11f0: 2d 74 6d 70 66 73 22 2c 20 22 2f 74 6d 70 22 2c  -tmpfs", "/tmp",
1200: 20 22 2d 2d 72 6f 2d 62 69 6e 64 22 2c 20 70 61   "--ro-bind", pa
1210: 74 68 73 2e 72 65 70 6f 73 69 74 6f 72 69 65 73  ths.repositories
1220: 2c 20 70 61 74 68 73 2e 72 65 70 6f 73 69 74 6f  , paths.reposito
1230: 72 69 65 73 2c 0a 20 20 20 20 20 20 20 20 20 20  ries,.          
1240: 20 20 20 20 20 20 20 22 2d 2d 62 69 6e 64 22 2c         "--bind",
1250: 20 70 61 74 68 73 2e 73 6f 63 6b 65 74 5f 64 69   paths.socket_di
1260: 72 2c 20 70 61 74 68 73 2e 73 6f 63 6b 65 74 5f  r, paths.socket_
1270: 64 69 72 2c 20 22 2d 2d 72 6f 2d 62 69 6e 64 22  dir, "--ro-bind"
1280: 2c 20 46 69 6c 65 2e 6a 6f 69 6e 28 65 78 65 63  , File.join(exec
1290: 75 74 61 62 6c 65 5f 64 69 72 2c 20 22 72 65 63  utable_dir, "rec
12a0: 65 69 70 74 73 22 29 2c 20 22 2f 72 65 63 65 69  eipts"), "/recei
12b0: 70 74 73 22 2c 0a 20 20 20 20 20 20 20 20 20 20  pts",.          
12c0: 20 20 20 20 20 20 20 22 2f 72 65 63 65 69 70 74         "/receipt
12d0: 73 22 2c 20 70 61 74 68 73 2e 73 6f 63 6b 65 74  s", paths.socket
12e0: 2c 20 70 61 74 68 73 2e 72 65 70 6f 73 69 74 6f  , paths.reposito
12f0: 72 69 65 73 5d 29 0a 20 20 65 6e 64 0a 0a 20 20  ries]).  end..  
1300: 64 65 66 20 73 65 6c 66 2e 64 61 65 6d 6f 6e 5f  def self.daemon_
1310: 61 72 67 73 28 70 61 74 68 73 20 3a 20 50 61 74  args(paths : Pat
1320: 68 73 2c 20 68 6f 6d 65 73 20 3a 20 41 72 72 61  hs, homes : Arra
1330: 79 28 54 75 70 6c 65 28 53 74 72 69 6e 67 2c 20  y(Tuple(String, 
1340: 53 74 72 69 6e 67 2c 20 42 6f 6f 6c 29 29 29 20  String, Bool))) 
1350: 3a 20 41 72 72 61 79 28 53 74 72 69 6e 67 29 0a  : Array(String).
1360: 20 20 20 20 61 72 67 73 20 3d 20 5b 22 2d 2d 75      args = ["--u
1370: 6e 73 68 61 72 65 2d 75 73 65 72 22 2c 20 22 2d  nshare-user", "-
1380: 2d 75 6e 73 68 61 72 65 2d 69 70 63 22 2c 20 22  -unshare-ipc", "
1390: 2d 2d 75 6e 73 68 61 72 65 2d 75 74 73 22 2c 20  --unshare-uts", 
13a0: 22 2d 2d 63 6c 65 61 72 65 6e 76 22 2c 20 22 2d  "--clearenv", "-
13b0: 2d 73 65 74 65 6e 76 22 2c 20 22 48 4f 4d 45 22  -setenv", "HOME"
13c0: 2c 20 70 61 74 68 73 2e 68 6f 6d 65 2c 0a 20 20  , paths.home,.  
13d0: 20 20 20 20 20 20 20 20 20 20 22 2d 2d 73 65 74            "--set
13e0: 65 6e 76 22 2c 20 22 50 41 54 48 22 2c 20 22 2f  env", "PATH", "/
13f0: 75 73 72 2f 62 69 6e 3a 2f 62 69 6e 22 2c 20 22  usr/bin:/bin", "
1400: 2d 2d 72 6f 2d 62 69 6e 64 22 2c 20 22 2f 75 73  --ro-bind", "/us
1410: 72 22 2c 20 22 2f 75 73 72 22 5d 0a 20 20 20 20  r", "/usr"].    
1420: 44 41 45 4d 4f 4e 5f 53 59 53 54 45 4d 2e 65 61  DAEMON_SYSTEM.ea
1430: 63 68 20 7b 20 7c 70 61 74 68 7c 20 61 72 67 73  ch { |path| args
1440: 2e 63 6f 6e 63 61 74 28 5b 22 2d 2d 72 6f 2d 62  .concat(["--ro-b
1450: 69 6e 64 2d 74 72 79 22 2c 20 70 61 74 68 2c 20  ind-try", path, 
1460: 70 61 74 68 5d 29 20 7d 0a 20 20 20 20 61 72 67  path]) }.    arg
1470: 73 2e 63 6f 6e 63 61 74 28 5b 22 2d 2d 70 72 6f  s.concat(["--pro
1480: 63 22 2c 20 22 2f 70 72 6f 63 22 2c 20 22 2d 2d  c", "/proc", "--
1490: 64 65 76 22 2c 20 22 2f 64 65 76 22 2c 20 22 2d  dev", "/dev", "-
14a0: 2d 74 6d 70 66 73 22 2c 20 22 2f 74 6d 70 22 2c  -tmpfs", "/tmp",
14b0: 20 22 2d 2d 74 6d 70 66 73 22 2c 20 70 61 74 68   "--tmpfs", path
14c0: 73 2e 68 6f 6d 65 5d 29 0a 20 20 20 20 68 6f 6d  s.home]).    hom
14d0: 65 73 2e 65 61 63 68 20 7b 20 7c 73 6f 75 72 63  es.each { |sourc
14e0: 65 2c 20 64 65 73 74 2c 20 77 72 69 74 61 62 6c  e, dest, writabl
14f0: 65 7c 20 61 72 67 73 2e 63 6f 6e 63 61 74 28 5b  e| args.concat([
1500: 77 72 69 74 61 62 6c 65 20 3f 20 22 2d 2d 62 69  writable ? "--bi
1510: 6e 64 2d 74 72 79 22 20 3a 20 22 2d 2d 72 6f 2d  nd-try" : "--ro-
1520: 62 69 6e 64 2d 74 72 79 22 2c 20 73 6f 75 72 63  bind-try", sourc
1530: 65 2c 20 64 65 73 74 5d 29 20 7d 0a 20 20 20 20  e, dest]) }.    
1540: 61 72 67 73 2e 63 6f 6e 63 61 74 28 5b 22 2d 2d  args.concat(["--
1550: 62 69 6e 64 22 2c 20 70 61 74 68 73 2e 64 61 65  bind", paths.dae
1560: 6d 6f 6e 2c 20 70 61 74 68 73 2e 64 61 65 6d 6f  mon, paths.daemo
1570: 6e 2c 20 46 69 6c 65 2e 6a 6f 69 6e 28 70 61 74  n, File.join(pat
1580: 68 73 2e 64 61 65 6d 6f 6e 2c 20 22 63 75 72 72  hs.daemon, "curr
1590: 65 6e 74 22 2c 20 22 6f 63 78 32 22 29 2c 20 22  ent", "ocx2"), "
15a0: 64 61 65 6d 6f 6e 22 2c 20 46 69 6c 65 2e 6a 6f  daemon", File.jo
15b0: 69 6e 28 70 61 74 68 73 2e 64 61 65 6d 6f 6e 2c  in(paths.daemon,
15c0: 20 22 6e 6f 64 65 22 2c 20 22 6e 6f 64 65 73 2e   "node", "nodes.
15d0: 6a 73 6f 6e 22 29 5d 29 0a 20 20 65 6e 64 0a 0a  json")]).  end..
15e0: 20 20 23 20 52 65 73 6f 6c 76 65 20 65 78 69 73    # Resolve exis
15f0: 74 69 6e 67 20 61 6e 63 65 73 74 6f 72 73 20 74  ting ancestors t
1600: 6f 6f 2c 20 73 6f 20 61 6e 20 61 62 73 65 6e 74  oo, so an absent
1610: 20 6c 65 61 66 20 63 61 6e 6e 6f 74 20 68 69 64   leaf cannot hid
1620: 65 20 61 20 73 79 6d 6c 69 6e 6b 2e 0a 20 20 64  e a symlink..  d
1630: 65 66 20 73 65 6c 66 2e 72 65 61 6c 5f 62 6f 75  ef self.real_bou
1640: 6e 64 28 70 61 74 68 20 3a 20 53 74 72 69 6e 67  nd(path : String
1650: 29 20 3a 20 53 74 72 69 6e 67 0a 20 20 20 20 72  ) : String.    r
1660: 65 74 75 72 6e 20 46 69 6c 65 2e 72 65 61 6c 70  eturn File.realp
1670: 61 74 68 28 70 61 74 68 29 20 69 66 20 46 69 6c  ath(path) if Fil
1680: 65 2e 65 78 69 73 74 73 3f 28 70 61 74 68 29 20  e.exists?(path) 
1690: 7c 7c 20 46 69 6c 65 2e 73 79 6d 6c 69 6e 6b 3f  || File.symlink?
16a0: 28 70 61 74 68 29 0a 20 20 20 20 65 78 70 61 6e  (path).    expan
16b0: 64 65 64 20 3d 20 46 69 6c 65 2e 65 78 70 61 6e  ded = File.expan
16c0: 64 5f 70 61 74 68 28 70 61 74 68 29 0a 20 20 20  d_path(path).   
16d0: 20 72 65 74 75 72 6e 20 65 78 70 61 6e 64 65 64   return expanded
16e0: 20 69 66 20 65 78 70 61 6e 64 65 64 20 3d 3d 20   if expanded == 
16f0: 22 2f 22 0a 20 20 20 20 46 69 6c 65 2e 6a 6f 69  "/".    File.joi
1700: 6e 28 72 65 61 6c 5f 62 6f 75 6e 64 28 46 69 6c  n(real_bound(Fil
1710: 65 2e 64 69 72 6e 61 6d 65 28 65 78 70 61 6e 64  e.dirname(expand
1720: 65 64 29 29 2c 20 46 69 6c 65 2e 62 61 73 65 6e  ed)), File.basen
1730: 61 6d 65 28 65 78 70 61 6e 64 65 64 29 29 0a 20  ame(expanded)). 
1740: 20 65 6e 64 0a 0a 20 20 64 65 66 20 73 65 6c 66   end..  def self
1750: 2e 67 75 61 72 64 5f 64 61 65 6d 6f 6e 28 61 72  .guard_daemon(ar
1760: 67 73 20 3a 20 41 72 72 61 79 28 53 74 72 69 6e  gs : Array(Strin
1770: 67 29 2c 20 72 65 70 6f 73 69 74 6f 72 69 65 73  g), repositories
1780: 20 3a 20 53 74 72 69 6e 67 29 20 3a 20 4e 69 6c   : String) : Nil
1790: 0a 20 20 20 20 72 65 70 6f 73 69 74 6f 72 79 20  .    repository 
17a0: 3d 20 72 65 61 6c 5f 62 6f 75 6e 64 28 72 65 70  = real_bound(rep
17b0: 6f 73 69 74 6f 72 69 65 73 29 2e 72 63 68 6f 70  ositories).rchop
17c0: 28 27 2f 27 29 0a 20 20 20 20 61 72 67 73 2e 65  ('/').    args.e
17d0: 61 63 68 5f 77 69 74 68 5f 69 6e 64 65 78 20 64  ach_with_index d
17e0: 6f 20 7c 61 72 67 2c 20 69 6e 64 65 78 7c 0a 20  o |arg, index|. 
17f0: 20 20 20 20 20 23 20 6f 6e 6c 79 20 61 20 62 69       # only a bi
1800: 6e 64 20 62 72 69 6e 67 73 20 61 20 68 6f 73 74  nd brings a host
1810: 20 70 61 74 68 20 69 6e 3b 20 2d 2d 74 6d 70 66   path in; --tmpf
1820: 73 2c 20 2d 2d 70 72 6f 63 20 61 6e 64 20 2d 2d  s, --proc and --
1830: 64 65 76 20 6d 61 6b 65 20 66 72 65 73 68 2c 20  dev make fresh, 
1840: 65 6d 70 74 79 20 6f 6e 65 73 0a 20 20 20 20 20  empty ones.     
1850: 20 6e 65 78 74 20 75 6e 6c 65 73 73 20 7b 22 2d   next unless {"-
1860: 2d 62 69 6e 64 22 2c 20 22 2d 2d 62 69 6e 64 2d  -bind", "--bind-
1870: 74 72 79 22 2c 20 22 2d 2d 72 6f 2d 62 69 6e 64  try", "--ro-bind
1880: 22 2c 20 22 2d 2d 72 6f 2d 62 69 6e 64 2d 74 72  ", "--ro-bind-tr
1890: 79 22 7d 2e 69 6e 63 6c 75 64 65 73 3f 28 61 72  y"}.includes?(ar
18a0: 67 29 0a 20 20 20 20 20 20 70 61 74 68 20 3d 20  g).      path = 
18b0: 72 65 61 6c 5f 62 6f 75 6e 64 28 61 72 67 73 5b  real_bound(args[
18c0: 69 6e 64 65 78 20 2b 20 31 5d 29 2e 72 63 68 6f  index + 1]).rcho
18d0: 70 28 27 2f 27 29 0a 20 20 20 20 20 20 69 66 20  p('/').      if 
18e0: 70 61 74 68 20 3d 3d 20 72 65 70 6f 73 69 74 6f  path == reposito
18f0: 72 79 20 7c 7c 20 70 61 74 68 2e 73 74 61 72 74  ry || path.start
1900: 73 5f 77 69 74 68 3f 28 72 65 70 6f 73 69 74 6f  s_with?(reposito
1910: 72 79 20 2b 20 22 2f 22 29 20 7c 7c 20 72 65 70  ry + "/") || rep
1920: 6f 73 69 74 6f 72 79 2e 73 74 61 72 74 73 5f 77  ository.starts_w
1930: 69 74 68 3f 28 70 61 74 68 20 2b 20 22 2f 22 29  ith?(path + "/")
1940: 0a 20 20 20 20 20 20 20 20 72 61 69 73 65 20 41  .        raise A
1950: 72 67 75 6d 65 6e 74 45 72 72 6f 72 2e 6e 65 77  rgumentError.new
1960: 28 22 64 61 65 6d 6f 6e 20 62 6f 75 6e 64 20 70  ("daemon bound p
1970: 61 74 68 20 6f 76 65 72 6c 61 70 73 20 73 65 72  ath overlaps ser
1980: 76 65 64 20 72 65 70 6f 73 69 74 6f 72 69 65 73  ved repositories
1990: 22 29 0a 20 20 20 20 20 20 65 6e 64 0a 20 20 20  ").      end.   
19a0: 20 65 6e 64 0a 20 20 65 6e 64 0a 0a 20 20 64 65   end.  end..  de
19b0: 66 20 73 65 6c 66 2e 63 6c 6f 73 65 5f 69 6e 68  f self.close_inh
19c0: 65 72 69 74 65 64 20 3a 20 4e 69 6c 0a 20 20 20  erited : Nil.   
19d0: 20 64 69 72 20 3d 20 4c 69 62 43 2e 6f 70 65 6e   dir = LibC.open
19e0: 64 69 72 28 22 2f 70 72 6f 63 2f 73 65 6c 66 2f  dir("/proc/self/
19f0: 66 64 22 29 0a 20 20 20 20 72 61 69 73 65 20 49  fd").    raise I
1a00: 4f 3a 3a 45 72 72 6f 72 2e 66 72 6f 6d 5f 65 72  O::Error.from_er
1a10: 72 6e 6f 28 22 6f 70 65 6e 64 69 72 22 29 20 69  rno("opendir") i
1a20: 66 20 64 69 72 2e 6e 75 6c 6c 3f 0a 20 20 20 20  f dir.null?.    
1a30: 62 65 67 69 6e 0a 20 20 20 20 20 20 6f 77 6e 20  begin.      own 
1a40: 3d 20 4c 69 62 43 2e 64 69 72 66 64 28 64 69 72  = LibC.dirfd(dir
1a50: 29 0a 20 20 20 20 20 20 77 68 69 6c 65 20 65 6e  ).      while en
1a60: 74 72 79 20 3d 20 4c 69 62 43 2e 72 65 61 64 64  try = LibC.readd
1a70: 69 72 28 64 69 72 29 0a 20 20 20 20 20 20 20 20  ir(dir).        
1a80: 66 64 20 3d 20 53 74 72 69 6e 67 2e 6e 65 77 28  fd = String.new(
1a90: 65 6e 74 72 79 2e 76 61 6c 75 65 2e 64 5f 6e 61  entry.value.d_na
1aa0: 6d 65 2e 74 6f 5f 75 6e 73 61 66 65 29 2e 74 6f  me.to_unsafe).to
1ab0: 5f 69 3f 0a 20 20 20 20 20 20 20 20 4c 69 62 43  _i?.        LibC
1ac0: 2e 63 6c 6f 73 65 28 66 64 29 20 69 66 20 66 64  .close(fd) if fd
1ad0: 20 26 26 20 66 64 20 3e 20 32 20 26 26 20 66 64   && fd > 2 && fd
1ae0: 20 21 3d 20 6f 77 6e 0a 20 20 20 20 20 20 65 6e   != own.      en
1af0: 64 0a 20 20 20 20 65 6e 73 75 72 65 0a 20 20 20  d.    ensure.   
1b00: 20 20 20 4c 69 62 43 2e 63 6c 6f 73 65 64 69 72     LibC.closedir
1b10: 28 64 69 72 29 0a 20 20 20 20 65 6e 64 0a 20 20  (dir).    end.  
1b20: 65 6e 64 0a 0a 20 20 64 65 66 20 73 65 6c 66 2e  end..  def self.
1b30: 63 67 69 28 70 61 74 68 73 20 3a 20 50 61 74 68  cgi(paths : Path
1b40: 73 29 20 3a 20 4e 6f 52 65 74 75 72 6e 0a 20 20  s) : NoReturn.  
1b50: 20 20 72 65 6c 65 61 73 65 20 3d 20 46 69 6c 65    release = File
1b60: 2e 72 65 61 6c 70 61 74 68 28 46 69 6c 65 2e 6a  .realpath(File.j
1b70: 6f 69 6e 28 70 61 74 68 73 2e 72 65 6c 65 61 73  oin(paths.releas
1b80: 65 2c 20 22 63 75 72 72 65 6e 74 22 29 29 0a 20  e, "current")). 
1b90: 20 20 20 63 6c 6f 73 65 5f 69 6e 68 65 72 69 74     close_inherit
1ba0: 65 64 0a 20 20 20 20 23 20 4d 69 72 72 6f 72 20  ed.    # Mirror 
1bb0: 73 72 63 2f 6f 63 78 2f 63 67 69 2e 63 72 27 73  src/ocx/cgi.cr's
1bc0: 20 72 75 6c 65 20 66 6f 72 20 74 68 65 20 74 6f   rule for the to
1bd0: 6f 6c 20 61 66 74 65 72 20 74 68 65 20 70 72 6f  ol after the pro
1be0: 67 72 61 6d 27 73 20 62 61 73 65 6e 61 6d 65 2e  gram's basename.
1bf0: 0a 20 20 20 20 77 6f 72 64 73 20 3d 20 28 45 4e  .    words = (EN
1c00: 56 5b 22 50 41 54 48 5f 49 4e 46 4f 22 5d 3f 20  V["PATH_INFO"]? 
1c10: 7c 7c 20 22 22 29 2e 73 70 6c 69 74 28 27 2f 27  || "").split('/'
1c20: 29 2e 72 65 6a 65 63 74 28 26 2e 65 6d 70 74 79  ).reject(&.empty
1c30: 3f 29 0a 20 20 20 20 6e 61 6d 65 20 3d 20 77 6f  ?).    name = wo
1c40: 72 64 73 5b 28 28 77 6f 72 64 73 2e 72 69 6e 64  rds[((words.rind
1c50: 65 78 28 46 69 6c 65 2e 62 61 73 65 6e 61 6d 65  ex(File.basename
1c60: 28 50 52 4f 47 52 41 4d 5f 4e 41 4d 45 29 29 20  (PROGRAM_NAME)) 
1c70: 7c 7c 20 2d 31 29 20 2b 20 31 29 2e 2e 5d 2e 66  || -1) + 1)..].f
1c80: 69 72 73 74 3f 0a 20 20 20 20 75 6e 6c 65 73 73  irst?.    unless
1c90: 20 6e 61 6d 65 20 3d 3d 20 22 72 65 63 65 69 70   name == "receip
1ca0: 74 22 0a 20 20 20 20 20 20 23 20 45 36 3a 20 74  t".      # E6: t
1cb0: 68 69 73 20 70 61 73 73 74 68 72 6f 75 67 68 20  his passthrough 
1cc0: 67 6f 65 73 20 77 68 65 6e 20 74 68 65 20 43 47  goes when the CG
1cd0: 49 20 74 61 6c 6b 73 20 74 6f 20 74 68 65 20 64  I talks to the d
1ce0: 61 65 6d 6f 6e 2e 0a 20 20 20 20 20 20 65 78 65  aemon..      exe
1cf0: 63 75 74 61 62 6c 65 20 3d 20 46 69 6c 65 2e 6a  cutable = File.j
1d00: 6f 69 6e 28 72 65 6c 65 61 73 65 2c 20 22 6f 63  oin(release, "oc
1d10: 78 32 22 29 0a 20 20 20 20 20 20 61 72 67 76 20  x2").      argv 
1d20: 3d 20 5b 46 69 6c 65 2e 6a 6f 69 6e 28 72 65 6c  = [File.join(rel
1d30: 65 61 73 65 2c 20 22 6f 63 78 32 2d 63 67 69 22  ease, "ocx2-cgi"
1d40: 29 2e 74 6f 5f 75 6e 73 61 66 65 2c 20 50 6f 69  ).to_unsafe, Poi
1d50: 6e 74 65 72 28 55 49 6e 74 38 29 2e 6e 75 6c 6c  nter(UInt8).null
1d60: 5d 0a 20 20 20 20 20 20 4c 69 62 43 2e 65 78 65  ].      LibC.exe
1d70: 63 76 28 65 78 65 63 75 74 61 62 6c 65 2c 20 61  cv(executable, a
1d80: 72 67 76 2e 74 6f 5f 75 6e 73 61 66 65 29 0a 20  rgv.to_unsafe). 
1d90: 20 20 20 20 20 72 61 69 73 65 20 49 4f 3a 3a 45       raise IO::E
1da0: 72 72 6f 72 2e 66 72 6f 6d 5f 65 72 72 6e 6f 28  rror.from_errno(
1db0: 22 65 78 65 63 76 22 29 0a 20 20 20 20 65 6e 64  "execv").    end
1dc0: 0a 20 20 20 20 72 65 70 6f 73 69 74 6f 72 79 20  .    repository 
1dd0: 3d 20 46 69 6c 65 2e 62 61 73 65 6e 61 6d 65 28  = File.basename(
1de0: 45 4e 56 5b 22 46 4f 53 53 49 4c 5f 52 45 50 4f  ENV["FOSSIL_REPO
1df0: 53 49 54 4f 52 59 22 5d 3f 20 7c 7c 20 22 22 2c  SITORY"]? || "",
1e00: 20 22 2e 66 6f 73 73 69 6c 22 29 0a 20 20 20 20   ".fossil").    
1e10: 62 65 67 69 6e 0a 20 20 20 20 20 20 23 20 55 73  begin.      # Us
1e20: 65 20 62 6c 6f 63 6b 69 6e 67 20 73 79 73 63 61  e blocking sysca
1e30: 6c 6c 73 20 61 66 74 65 72 20 63 6c 6f 73 69 6e  lls after closin
1e40: 67 20 64 65 73 63 72 69 70 74 6f 72 73 2c 20 69  g descriptors, i
1e50: 6e 63 6c 75 64 69 6e 67 20 72 75 6e 74 69 6d 65  ncluding runtime
1e60: 20 65 76 65 6e 74 20 66 64 73 2e 0a 20 20 20 20   event fds..    
1e70: 20 20 61 64 64 72 65 73 73 20 3d 20 53 6f 63 6b    address = Sock
1e80: 65 74 3a 3a 55 4e 49 58 41 64 64 72 65 73 73 2e  et::UNIXAddress.
1e90: 6e 65 77 28 70 61 74 68 73 2e 73 6f 63 6b 65 74  new(paths.socket
1ea0: 29 0a 20 20 20 20 20 20 66 64 20 3d 20 4c 69 62  ).      fd = Lib
1eb0: 43 2e 73 6f 63 6b 65 74 28 4c 69 62 43 3a 3a 41  C.socket(LibC::A
1ec0: 46 5f 55 4e 49 58 2c 20 4c 69 62 43 3a 3a 53 4f  F_UNIX, LibC::SO
1ed0: 43 4b 5f 53 54 52 45 41 4d 2c 20 30 29 0a 20 20  CK_STREAM, 0).  
1ee0: 20 20 20 20 72 61 69 73 65 20 49 4f 3a 3a 45 72      raise IO::Er
1ef0: 72 6f 72 2e 66 72 6f 6d 5f 65 72 72 6e 6f 28 22  ror.from_errno("
1f00: 73 6f 63 6b 65 74 22 29 20 69 66 20 66 64 20 3d  socket") if fd =
1f10: 3d 20 2d 31 0a 20 20 20 20 20 20 23 20 4c 69 6e  = -1.      # Lin
1f20: 75 78 20 62 6f 75 6e 64 73 20 62 6f 74 68 20 63  ux bounds both c
1f30: 6f 6e 6e 65 63 74 20 74 6f 20 61 20 66 75 6c 6c  onnect to a full
1f40: 20 55 6e 69 78 20 6c 69 73 74 65 6e 65 72 20 71   Unix listener q
1f50: 75 65 75 65 20 61 6e 64 20 74 68 65 20 69 64 65  ueue and the ide
1f60: 6e 74 69 74 79 20 77 72 69 74 65 2e 0a 20 20 20  ntity write..   
1f70: 20 20 20 74 69 6d 65 6f 75 74 20 3d 20 4c 69 62     timeout = Lib
1f80: 43 3a 3a 54 69 6d 65 76 61 6c 2e 6e 65 77 28 74  C::Timeval.new(t
1f90: 76 5f 73 65 63 3a 20 32 2c 20 74 76 5f 75 73 65  v_sec: 2, tv_use
1fa0: 63 3a 20 30 29 0a 20 20 20 20 20 20 72 61 69 73  c: 0).      rais
1fb0: 65 20 49 4f 3a 3a 45 72 72 6f 72 2e 66 72 6f 6d  e IO::Error.from
1fc0: 5f 65 72 72 6e 6f 28 22 73 65 74 73 6f 63 6b 6f  _errno("setsocko
1fd0: 70 74 22 29 20 69 66 20 4c 69 62 43 2e 73 65 74  pt") if LibC.set
1fe0: 73 6f 63 6b 6f 70 74 28 66 64 2c 20 4c 69 62 43  sockopt(fd, LibC
1ff0: 3a 3a 53 4f 4c 5f 53 4f 43 4b 45 54 2c 20 4c 69  ::SOL_SOCKET, Li
2000: 62 43 3a 3a 53 4f 5f 53 4e 44 54 49 4d 45 4f 2c  bC::SO_SNDTIMEO,
2010: 20 70 6f 69 6e 74 65 72 6f 66 28 74 69 6d 65 6f   pointerof(timeo
2020: 75 74 29 2c 20 73 69 7a 65 6f 66 28 4c 69 62 43  ut), sizeof(LibC
2030: 3a 3a 54 69 6d 65 76 61 6c 29 29 20 3d 3d 20 2d  ::Timeval)) == -
2040: 31 0a 20 20 20 20 20 20 72 61 69 73 65 20 49 4f  1.      raise IO
2050: 3a 3a 45 72 72 6f 72 2e 66 72 6f 6d 5f 65 72 72  ::Error.from_err
2060: 6e 6f 28 22 63 6f 6e 6e 65 63 74 22 29 20 69 66  no("connect") if
2070: 20 4c 69 62 43 2e 63 6f 6e 6e 65 63 74 28 66 64   LibC.connect(fd
2080: 2c 20 61 64 64 72 65 73 73 2c 20 61 64 64 72 65  , address, addre
2090: 73 73 2e 73 69 7a 65 29 20 3d 3d 20 2d 31 0a 20  ss.size) == -1. 
20a0: 20 20 20 20 20 69 64 65 6e 74 69 74 79 20 3d 20       identity = 
20b0: 4a 53 4f 4e 2e 62 75 69 6c 64 20 64 6f 20 7c 6a  JSON.build do |j
20c0: 73 6f 6e 7c 0a 20 20 20 20 20 20 20 20 6a 73 6f  son|.        jso
20d0: 6e 2e 6f 62 6a 65 63 74 20 64 6f 0a 20 20 20 20  n.object do.    
20e0: 20 20 20 20 20 20 69 66 20 75 73 65 72 20 3d 20        if user = 
20f0: 45 4e 56 5b 22 46 4f 53 53 49 4c 5f 55 53 45 52  ENV["FOSSIL_USER
2100: 22 5d 3f 0a 20 20 20 20 20 20 20 20 20 20 20 20  "]?.            
2110: 6a 73 6f 6e 2e 66 69 65 6c 64 20 22 75 73 65 72  json.field "user
2120: 22 2c 20 75 73 65 72 0a 20 20 20 20 20 20 20 20  ", user.        
2130: 20 20 65 6e 64 0a 20 20 20 20 20 20 20 20 20 20    end.          
2140: 6a 73 6f 6e 2e 66 69 65 6c 64 20 22 63 61 70 61  json.field "capa
2150: 62 69 6c 69 74 69 65 73 22 2c 20 45 4e 56 5b 22  bilities", ENV["
2160: 46 4f 53 53 49 4c 5f 43 41 50 41 42 49 4c 49 54  FOSSIL_CAPABILIT
2170: 49 45 53 22 5d 3f 20 7c 7c 20 22 22 0a 20 20 20  IES"]? || "".   
2180: 20 20 20 20 20 20 20 6a 73 6f 6e 2e 66 69 65 6c         json.fiel
2190: 64 20 22 72 65 70 6f 73 69 74 6f 72 79 22 2c 20  d "repository", 
21a0: 72 65 70 6f 73 69 74 6f 72 79 0a 20 20 20 20 20  repository.     
21b0: 20 20 20 65 6e 64 0a 20 20 20 20 20 20 65 6e 64     end.      end
21c0: 0a 20 20 20 20 20 20 62 79 74 65 73 20 3d 20 28  .      bytes = (
21d0: 69 64 65 6e 74 69 74 79 20 2b 20 22 5c 6e 22 29  identity + "\n")
21e0: 2e 74 6f 5f 73 6c 69 63 65 0a 20 20 20 20 20 20  .to_slice.      
21f0: 75 6e 74 69 6c 20 62 79 74 65 73 2e 65 6d 70 74  until bytes.empt
2200: 79 3f 0a 20 20 20 20 20 20 20 20 77 72 69 74 74  y?.        writt
2210: 65 6e 20 3d 20 4c 69 62 43 2e 77 72 69 74 65 28  en = LibC.write(
2220: 66 64 2c 20 62 79 74 65 73 2c 20 62 79 74 65 73  fd, bytes, bytes
2230: 2e 73 69 7a 65 29 0a 20 20 20 20 20 20 20 20 6e  .size).        n
2240: 65 78 74 20 69 66 20 77 72 69 74 74 65 6e 20 3d  ext if written =
2250: 3d 20 2d 31 20 26 26 20 45 72 72 6e 6f 2e 76 61  = -1 && Errno.va
2260: 6c 75 65 20 3d 3d 20 45 72 72 6e 6f 3a 3a 45 49  lue == Errno::EI
2270: 4e 54 52 0a 20 20 20 20 20 20 20 20 72 61 69 73  NTR.        rais
2280: 65 20 49 4f 3a 3a 45 72 72 6f 72 2e 66 72 6f 6d  e IO::Error.from
2290: 5f 65 72 72 6e 6f 28 22 77 72 69 74 65 22 29 20  _errno("write") 
22a0: 69 66 20 77 72 69 74 74 65 6e 20 3c 3d 20 30 0a  if written <= 0.
22b0: 20 20 20 20 20 20 20 20 62 79 74 65 73 20 3d 20          bytes = 
22c0: 62 79 74 65 73 5b 77 72 69 74 74 65 6e 2e 74 6f  bytes[written.to
22d0: 5f 69 2e 2e 5d 0a 20 20 20 20 20 20 65 6e 64 0a  _i..].      end.
22e0: 20 20 20 20 72 65 73 63 75 65 20 53 6f 63 6b 65      rescue Socke
22f0: 74 3a 3a 45 72 72 6f 72 20 7c 20 49 4f 3a 3a 45  t::Error | IO::E
2300: 72 72 6f 72 0a 20 20 20 20 20 20 23 20 54 68 65  rror.      # The
2310: 20 72 75 6e 74 69 6d 65 27 73 20 6f 77 6e 20 64   runtime's own d
2320: 65 73 63 72 69 70 74 6f 72 73 20 61 72 65 20 63  escriptors are c
2330: 6c 6f 73 65 64 2c 20 73 6f 20 77 72 69 74 65 20  losed, so write 
2340: 77 69 74 68 20 74 68 65 20 73 79 73 74 65 6d 20  with the system 
2350: 63 61 6c 6c 20 69 74 73 65 6c 66 2e 0a 20 20 20  call itself..   
2360: 20 20 20 61 6e 73 77 65 72 20 3d 20 22 53 74 61     answer = "Sta
2370: 74 75 73 3a 20 35 30 33 5c 72 5c 6e 43 6f 6e 74  tus: 503\r\nCont
2380: 65 6e 74 2d 54 79 70 65 3a 20 61 70 70 6c 69 63  ent-Type: applic
2390: 61 74 69 6f 6e 2f 6a 73 6f 6e 5c 72 5c 6e 5c 72  ation/json\r\n\r
23a0: 5c 6e 7b 5c 22 75 6e 61 76 61 69 6c 61 62 6c 65  \n{\"unavailable
23b0: 5c 22 3a 5c 22 72 65 63 65 69 70 74 20 73 65 72  \":\"receipt ser
23c0: 76 69 63 65 20 6e 6f 74 20 61 6e 73 77 65 72 69  vice not answeri
23d0: 6e 67 5c 22 7d 5c 6e 22 2e 74 6f 5f 73 6c 69 63  ng\"}\n".to_slic
23e0: 65 0a 20 20 20 20 20 20 4c 69 62 43 2e 77 72 69  e.      LibC.wri
23f0: 74 65 28 31 2c 20 61 6e 73 77 65 72 2c 20 61 6e  te(1, answer, an
2400: 73 77 65 72 2e 73 69 7a 65 29 0a 20 20 20 20 20  swer.size).     
2410: 20 65 78 69 74 20 30 0a 20 20 20 20 65 6e 64 0a   exit 0.    end.
2420: 20 20 20 20 72 61 69 73 65 20 49 4f 3a 3a 45 72      raise IO::Er
2430: 72 6f 72 2e 66 72 6f 6d 5f 65 72 72 6e 6f 28 22  ror.from_errno("
2440: 64 75 70 32 22 29 20 69 66 20 4c 69 62 43 2e 64  dup2") if LibC.d
2450: 75 70 32 28 66 64 2c 20 33 29 20 3d 3d 20 2d 31  up2(fd, 3) == -1
2460: 0a 20 20 20 20 4c 69 62 43 2e 63 6c 6f 73 65 28  .    LibC.close(
2470: 66 64 29 20 75 6e 6c 65 73 73 20 66 64 20 3d 3d  fd) unless fd ==
2480: 20 33 0a 20 20 20 20 72 61 69 73 65 20 49 4f 3a   3.    raise IO:
2490: 3a 45 72 72 6f 72 2e 66 72 6f 6d 5f 65 72 72 6e  :Error.from_errn
24a0: 6f 28 22 66 63 6e 74 6c 22 29 20 69 66 20 4c 69  o("fcntl") if Li
24b0: 62 43 2e 66 63 6e 74 6c 28 33 2c 20 4c 69 62 43  bC.fcntl(3, LibC
24c0: 3a 3a 46 5f 53 45 54 46 44 2c 20 30 29 20 3d 3d  ::F_SETFD, 0) ==
24d0: 20 2d 31 0a 20 20 20 20 65 6e 76 69 72 6f 6e 6d   -1.    environm
24e0: 65 6e 74 20 3d 20 7b 7d 20 6f 66 20 53 74 72 69  ent = {} of Stri
24f0: 6e 67 20 3d 3e 20 53 74 72 69 6e 67 0a 20 20 20  ng => String.   
2500: 20 43 47 49 5f 46 49 45 4c 44 53 2e 65 61 63 68   CGI_FIELDS.each
2510: 20 64 6f 20 7c 6b 65 79 7c 0a 20 20 20 20 20 20   do |key|.      
2520: 69 66 20 76 61 6c 75 65 20 3d 20 45 4e 56 5b 6b  if value = ENV[k
2530: 65 79 5d 3f 0a 20 20 20 20 20 20 20 20 65 6e 76  ey]?.        env
2540: 69 72 6f 6e 6d 65 6e 74 5b 6b 65 79 5d 20 3d 20  ironment[key] = 
2550: 76 61 6c 75 65 0a 20 20 20 20 20 20 65 6e 64 0a  value.      end.
2560: 20 20 20 20 65 6e 64 0a 20 20 20 20 65 78 65 63      end.    exec
2570: 5f 72 61 77 28 22 2f 75 73 72 2f 62 69 6e 2f 62  _raw("/usr/bin/b
2580: 77 72 61 70 22 2c 20 63 67 69 5f 61 72 67 73 28  wrap", cgi_args(
2590: 72 65 6c 65 61 73 65 2c 20 72 65 70 6f 73 69 74  release, reposit
25a0: 6f 72 79 2c 20 65 6e 76 69 72 6f 6e 6d 65 6e 74  ory, environment
25b0: 29 29 0a 20 20 65 6e 64 0a 0a 20 20 23 20 65 78  )).  end..  # ex
25c0: 65 63 76 20 77 69 74 68 6f 75 74 20 74 68 65 20  ecv without the 
25d0: 72 75 6e 74 69 6d 65 2c 20 77 68 6f 73 65 20 64  runtime, whose d
25e0: 65 73 63 72 69 70 74 6f 72 73 20 63 6c 6f 73 65  escriptors close
25f0: 5f 69 6e 68 65 72 69 74 65 64 20 68 61 73 20 63  _inherited has c
2600: 6c 6f 73 65 64 2e 0a 20 20 64 65 66 20 73 65 6c  losed..  def sel
2610: 66 2e 65 78 65 63 5f 72 61 77 28 70 72 6f 67 72  f.exec_raw(progr
2620: 61 6d 20 3a 20 53 74 72 69 6e 67 2c 20 61 72 67  am : String, arg
2630: 73 20 3a 20 41 72 72 61 79 28 53 74 72 69 6e 67  s : Array(String
2640: 29 29 20 3a 20 4e 6f 52 65 74 75 72 6e 0a 20 20  )) : NoReturn.  
2650: 20 20 61 72 67 76 20 3d 20 28 5b 70 72 6f 67 72    argv = ([progr
2660: 61 6d 5d 20 2b 20 61 72 67 73 29 2e 6d 61 70 28  am] + args).map(
2670: 26 2e 74 6f 5f 75 6e 73 61 66 65 29 20 2b 20 5b  &.to_unsafe) + [
2680: 50 6f 69 6e 74 65 72 28 55 49 6e 74 38 29 2e 6e  Pointer(UInt8).n
2690: 75 6c 6c 5d 0a 20 20 20 20 4c 69 62 43 2e 65 78  ull].    LibC.ex
26a0: 65 63 76 28 70 72 6f 67 72 61 6d 2c 20 61 72 67  ecv(program, arg
26b0: 76 2e 74 6f 5f 75 6e 73 61 66 65 29 0a 20 20 20  v.to_unsafe).   
26c0: 20 72 61 69 73 65 20 49 4f 3a 3a 45 72 72 6f 72   raise IO::Error
26d0: 2e 66 72 6f 6d 5f 65 72 72 6e 6f 28 22 65 78 65  .from_errno("exe
26e0: 63 76 22 29 0a 20 20 65 6e 64 0a 0a 20 20 64 65  cv").  end..  de
26f0: 66 20 73 65 6c 66 2e 72 75 6e 20 3a 20 4e 6f 52  f self.run : NoR
2700: 65 74 75 72 6e 0a 20 20 20 20 61 72 67 73 20 3d  eturn.    args =
2710: 20 41 52 47 56 2e 64 75 70 0a 20 20 20 20 73 63   ARGV.dup.    sc
2720: 72 61 74 63 68 20 3d 20 6e 69 6c 0a 20 20 20 20  ratch = nil.    
2730: 69 66 20 61 72 67 73 2e 66 69 72 73 74 3f 20 3d  if args.first? =
2740: 3d 20 22 2d 2d 73 63 72 61 74 63 68 22 0a 20 20  = "--scratch".  
2750: 20 20 20 20 61 72 67 73 2e 73 68 69 66 74 0a 20      args.shift. 
2760: 20 20 20 20 20 73 63 72 61 74 63 68 20 3d 20 61       scratch = a
2770: 72 67 73 2e 73 68 69 66 74 3f 20 7c 7c 20 72 61  rgs.shift? || ra
2780: 69 73 65 20 41 72 67 75 6d 65 6e 74 45 72 72 6f  ise ArgumentErro
2790: 72 2e 6e 65 77 28 22 6d 69 73 73 69 6e 67 20 73  r.new("missing s
27a0: 63 72 61 74 63 68 20 64 69 72 65 63 74 6f 72 79  cratch directory
27b0: 22 29 0a 20 20 20 20 65 6e 64 0a 20 20 20 20 63  ").    end.    c
27c0: 67 69 5f 6d 6f 64 65 20 3d 20 46 69 6c 65 2e 62  gi_mode = File.b
27d0: 61 73 65 6e 61 6d 65 28 50 52 4f 47 52 41 4d 5f  asename(PROGRAM_
27e0: 4e 41 4d 45 29 20 3d 3d 20 22 6f 63 78 32 2d 63  NAME) == "ocx2-c
27f0: 67 69 22 0a 20 20 20 20 75 6e 6c 65 73 73 20 28  gi".    unless (
2800: 63 67 69 5f 6d 6f 64 65 20 26 26 20 61 72 67 73  cgi_mode && args
2810: 2e 65 6d 70 74 79 3f 29 20 7c 7c 20 28 21 63 67  .empty?) || (!cg
2820: 69 5f 6d 6f 64 65 20 26 26 20 61 72 67 73 2e 73  i_mode && args.s
2830: 69 7a 65 20 3d 3d 20 31 20 26 26 20 7b 22 72 65  ize == 1 && {"re
2840: 63 65 69 70 74 73 22 2c 20 22 64 61 65 6d 6f 6e  ceipts", "daemon
2850: 22 7d 2e 69 6e 63 6c 75 64 65 73 3f 28 61 72 67  "}.includes?(arg
2860: 73 5b 30 5d 29 29 0a 20 20 20 20 20 20 72 61 69  s[0])).      rai
2870: 73 65 20 41 72 67 75 6d 65 6e 74 45 72 72 6f 72  se ArgumentError
2880: 2e 6e 65 77 28 22 75 6e 72 65 63 6f 67 6e 69 73  .new("unrecognis
2890: 65 64 20 6d 6f 64 65 22 29 0a 20 20 20 20 65 6e  ed mode").    en
28a0: 64 0a 20 20 20 20 70 61 74 68 73 20 3d 20 70 61  d.    paths = pa
28b0: 74 68 73 28 73 63 72 61 74 63 68 2c 20 53 79 73  ths(scratch, Sys
28c0: 74 65 6d 2e 68 6f 73 74 6e 61 6d 65 29 0a 20 20  tem.hostname).  
28d0: 20 20 63 67 69 28 70 61 74 68 73 29 20 69 66 20    cgi(paths) if 
28e0: 63 67 69 5f 6d 6f 64 65 0a 20 20 20 20 65 78 65  cgi_mode.    exe
28f0: 63 75 74 61 62 6c 65 20 3d 20 50 72 6f 63 65 73  cutable = Proces
2900: 73 2e 65 78 65 63 75 74 61 62 6c 65 5f 70 61 74  s.executable_pat
2910: 68 20 7c 7c 20 72 61 69 73 65 20 41 72 67 75 6d  h || raise Argum
2920: 65 6e 74 45 72 72 6f 72 2e 6e 65 77 28 22 63 61  entError.new("ca
2930: 6e 6e 6f 74 20 6c 6f 63 61 74 65 20 65 78 65 63  nnot locate exec
2940: 75 74 61 62 6c 65 22 29 0a 20 20 20 20 69 66 20  utable").    if 
2950: 61 72 67 73 5b 30 5d 20 3d 3d 20 22 72 65 63 65  args[0] == "rece
2960: 69 70 74 73 22 0a 20 20 20 20 20 20 63 6f 6d 6d  ipts".      comm
2970: 61 6e 64 20 3d 20 72 65 63 65 69 70 74 73 5f 61  and = receipts_a
2980: 72 67 73 28 70 61 74 68 73 2c 20 46 69 6c 65 2e  rgs(paths, File.
2990: 64 69 72 6e 61 6d 65 28 65 78 65 63 75 74 61 62  dirname(executab
29a0: 6c 65 29 29 0a 20 20 20 20 65 6c 73 65 0a 20 20  le)).    else.  
29b0: 20 20 20 20 63 6f 6d 6d 61 6e 64 20 3d 20 64 61      command = da
29c0: 65 6d 6f 6e 5f 61 72 67 73 28 70 61 74 68 73 2c  emon_args(paths,
29d0: 20 70 61 74 68 73 2e 68 6f 6d 65 73 2e 73 65 6c   paths.homes.sel
29e0: 65 63 74 20 7b 20 7c 73 6f 75 72 63 65 2c 20 5f  ect { |source, _
29f0: 2c 20 5f 7c 20 46 69 6c 65 2e 65 78 69 73 74 73  , _| File.exists
2a00: 3f 28 73 6f 75 72 63 65 29 20 7d 29 0a 20 20 20  ?(source) }).   
2a10: 20 20 20 67 75 61 72 64 5f 64 61 65 6d 6f 6e 28     guard_daemon(
2a20: 63 6f 6d 6d 61 6e 64 2c 20 70 61 74 68 73 2e 72  command, paths.r
2a30: 65 70 6f 73 69 74 6f 72 69 65 73 29 0a 20 20 20  epositories).   
2a40: 20 65 6e 64 0a 20 20 20 20 50 72 6f 63 65 73 73   end.    Process
2a50: 2e 65 78 65 63 28 22 2f 75 73 72 2f 62 69 6e 2f  .exec("/usr/bin/
2a60: 62 77 72 61 70 22 2c 20 63 6f 6d 6d 61 6e 64 29  bwrap", command)
2a70: 0a 20 20 72 65 73 63 75 65 20 65 78 20 3a 20 41  .  rescue ex : A
2a80: 72 67 75 6d 65 6e 74 45 72 72 6f 72 20 7c 20 49  rgumentError | I
2a90: 4f 3a 3a 45 72 72 6f 72 0a 20 20 20 20 53 54 44  O::Error.    STD
2aa0: 45 52 52 2e 70 75 74 73 20 22 23 7b 65 78 2e 6d  ERR.puts "#{ex.m
2ab0: 65 73 73 61 67 65 7d 5c 6e 55 73 61 67 65 3a 20  essage}\nUsage: 
2ac0: 73 74 61 72 74 2d 6f 63 78 20 5b 2d 2d 73 63 72  start-ocx [--scr
2ad0: 61 74 63 68 20 44 49 52 5d 20 72 65 63 65 69 70  atch DIR] receip
2ae0: 74 73 7c 64 61 65 6d 6f 6e 3b 20 6f 63 78 32 2d  ts|daemon; ocx2-
2af0: 63 67 69 20 5b 2d 2d 73 63 72 61 74 63 68 20 44  cgi [--scratch D
2b00: 49 52 5d 22 0a 20 20 20 20 65 78 69 74 20 32 0a  IR]".    exit 2.
2b10: 20 20 65 6e 64 0a 65 6e 64 0a 0a 53 74 61 72 74    end.end..Start
2b20: 4f 43 58 2e 72 75 6e 0a 00 00 00 00 00 00 00 00  OCX.run.........
2b30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
2b40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
2b50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
2b60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
2b70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
2b80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
2b90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
2ba0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
2bb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
2bc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
2bd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
2be0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
2bf0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
2c00: 73 63 72 69 70 74 73 2d 66 6f 72 2d 72 65 76 69  scripts-for-revi
2c10: 65 77 2d 32 2f 72 65 63 65 69 70 74 73 2e 63 72  ew-2/receipts.cr
2c20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
2c30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
2c40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
2c50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
2c60: 00 00 00 00 30 30 30 30 36 34 34 00 30 30 30 30  ....0000644.0000
2c70: 30 30 30 00 30 30 30 30 30 30 30 00 30 30 30 30  000.0000000.0000
2c80: 30 30 33 32 30 35 35 00 31 35 32 37 30 35 32 33  0032055.15270523
2c90: 31 32 32 00 30 31 35 36 33 35 00 20 30 00 00 00  122.015635. 0...
2ca0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
2cb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
2cc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
2cd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
2ce0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
2cf0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
2d00: 00 75 73 74 61 72 20 20 00 72 6f 6f 74 00 00 00  .ustar  .root...
2d10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
2d20: 00 00 00 00 00 00 00 00 00 72 6f 6f 74 00 00 00  .........root...
2d30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
2d40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
2d50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
2d60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
2d70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
2d80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
2d90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
2da0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
2db0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
2dc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
2dd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
2de0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
2df0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
2e00: 23 20 41 67 65 6e 74 73 3a 20 64 6f 20 6e 6f 74  # Agents: do not
2e10: 20 63 68 61 6e 67 65 20 74 68 69 73 20 66 69 6c   change this fil
2e20: 65 20 6f 6e 20 61 6e 79 20 62 72 61 6e 63 68 20  e on any branch 
2e30: 77 69 74 68 6f 75 74 20 74 68 65 20 6d 61 69 6e  without the main
2e40: 74 61 69 6e 65 72 27 73 20 65 78 70 6c 69 63 69  tainer's explici
2e50: 74 20 61 70 70 72 6f 76 61 6c 20 6f 6e 20 74 68  t approval on th
2e60: 65 20 72 65 63 6f 72 64 2e 0a 23 20 53 65 6c 66  e record..# Self
2e70: 2d 63 6f 6e 74 61 69 6e 65 64 2c 20 73 74 61 6e  -contained, stan
2e80: 64 61 72 64 2d 6c 69 62 72 61 72 79 2d 6f 6e 6c  dard-library-onl
2e90: 79 20 72 65 63 65 69 70 74 20 73 65 72 76 69 63  y receipt servic
2ea0: 65 2e 20 55 73 61 67 65 3a 20 72 65 63 65 69 70  e. Usage: receip
2eb0: 74 73 20 53 4f 43 4b 45 54 20 52 45 50 4f 53 49  ts SOCKET REPOSI
2ec0: 54 4f 52 49 45 53 2e 0a 23 20 52 65 61 64 73 20  TORIES..# Reads 
2ed0: 6f 6e 6c 79 20 69 74 73 20 74 77 6f 20 4a 53 4f  only its two JSO
2ee0: 4e 20 6c 69 6e 65 73 20 61 6e 64 20 52 45 50 4f  N lines and REPO
2ef0: 53 49 54 4f 52 49 45 53 2f 4e 41 4d 45 2e 66 6f  SITORIES/NAME.fo
2f00: 73 73 69 6c 2c 20 72 65 61 64 2d 6f 6e 6c 79 20  ssil, read-only 
2f10: 74 68 72 6f 75 67 68 20 53 51 4c 69 74 65 2e 0a  through SQLite..
2f20: 72 65 71 75 69 72 65 20 22 6a 73 6f 6e 22 0a 72  require "json".r
2f30: 65 71 75 69 72 65 20 22 73 6f 63 6b 65 74 22 0a  equire "socket".
2f40: 72 65 71 75 69 72 65 20 22 75 72 69 22 0a 72 65  require "uri".re
2f50: 71 75 69 72 65 20 22 73 65 74 22 0a 0a 6c 69 62  quire "set"..lib
2f60: 20 52 65 63 65 69 70 74 50 65 65 72 0a 20 20 73   ReceiptPeer.  s
2f70: 74 72 75 63 74 20 43 72 65 64 65 6e 74 69 61 6c  truct Credential
2f80: 73 0a 20 20 20 20 70 69 64 20 3a 20 49 6e 74 33  s.    pid : Int3
2f90: 32 0a 20 20 20 20 75 69 64 20 3a 20 55 49 6e 74  2.    uid : UInt
2fa0: 33 32 0a 20 20 20 20 67 69 64 20 3a 20 55 49 6e  32.    gid : UIn
2fb0: 74 33 32 0a 20 20 65 6e 64 0a 0a 20 20 66 75 6e  t32.  end..  fun
2fc0: 20 67 65 74 73 6f 63 6b 6f 70 74 20 3d 20 67 65   getsockopt = ge
2fd0: 74 73 6f 63 6b 6f 70 74 28 66 64 20 3a 20 49 6e  tsockopt(fd : In
2fe0: 74 33 32 2c 20 6c 65 76 65 6c 20 3a 20 49 6e 74  t32, level : Int
2ff0: 33 32 2c 20 6f 70 74 69 6f 6e 20 3a 20 49 6e 74  32, option : Int
3000: 33 32 2c 20 76 61 6c 75 65 20 3a 20 56 6f 69 64  32, value : Void
3010: 2a 2c 20 73 69 7a 65 20 3a 20 55 49 6e 74 33 32  *, size : UInt32
3020: 2a 29 20 3a 20 49 6e 74 33 32 0a 65 6e 64 0a 0a  *) : Int32.end..
3030: 40 5b 4c 69 6e 6b 28 22 73 71 6c 69 74 65 33 22  @[Link("sqlite3"
3040: 29 5d 0a 6c 69 62 20 4c 69 62 53 51 4c 69 74 65  )].lib LibSQLite
3050: 33 0a 20 20 66 75 6e 20 6f 70 65 6e 5f 76 32 20  3.  fun open_v2 
3060: 3d 20 73 71 6c 69 74 65 33 5f 6f 70 65 6e 5f 76  = sqlite3_open_v
3070: 32 28 70 61 74 68 20 3a 20 55 49 6e 74 38 2a 2c  2(path : UInt8*,
3080: 20 64 62 20 3a 20 56 6f 69 64 2a 2a 2c 20 66 6c   db : Void**, fl
3090: 61 67 73 20 3a 20 49 6e 74 33 32 2c 20 76 66 73  ags : Int32, vfs
30a0: 20 3a 20 55 49 6e 74 38 2a 29 20 3a 20 49 6e 74   : UInt8*) : Int
30b0: 33 32 0a 20 20 66 75 6e 20 63 6c 6f 73 65 5f 76  32.  fun close_v
30c0: 32 20 3d 20 73 71 6c 69 74 65 33 5f 63 6c 6f 73  2 = sqlite3_clos
30d0: 65 5f 76 32 28 64 62 20 3a 20 56 6f 69 64 2a 29  e_v2(db : Void*)
30e0: 20 3a 20 49 6e 74 33 32 0a 20 20 66 75 6e 20 70   : Int32.  fun p
30f0: 72 65 70 61 72 65 5f 76 32 20 3d 20 73 71 6c 69  repare_v2 = sqli
3100: 74 65 33 5f 70 72 65 70 61 72 65 5f 76 32 28 64  te3_prepare_v2(d
3110: 62 20 3a 20 56 6f 69 64 2a 2c 20 73 71 6c 20 3a  b : Void*, sql :
3120: 20 55 49 6e 74 38 2a 2c 20 73 69 7a 65 20 3a 20   UInt8*, size : 
3130: 49 6e 74 33 32 2c 20 73 74 61 74 65 6d 65 6e 74  Int32, statement
3140: 20 3a 20 56 6f 69 64 2a 2a 2c 20 74 61 69 6c 20   : Void**, tail 
3150: 3a 20 55 49 6e 74 38 2a 2a 29 20 3a 20 49 6e 74  : UInt8**) : Int
3160: 33 32 0a 20 20 66 75 6e 20 62 69 6e 64 5f 74 65  32.  fun bind_te
3170: 78 74 20 3d 20 73 71 6c 69 74 65 33 5f 62 69 6e  xt = sqlite3_bin
3180: 64 5f 74 65 78 74 28 73 74 61 74 65 6d 65 6e 74  d_text(statement
3190: 20 3a 20 56 6f 69 64 2a 2c 20 69 6e 64 65 78 20   : Void*, index 
31a0: 3a 20 49 6e 74 33 32 2c 20 74 65 78 74 20 3a 20  : Int32, text : 
31b0: 55 49 6e 74 38 2a 2c 20 73 69 7a 65 20 3a 20 49  UInt8*, size : I
31c0: 6e 74 33 32 2c 20 64 65 73 74 72 75 63 74 6f 72  nt32, destructor
31d0: 20 3a 20 56 6f 69 64 2a 29 20 3a 20 49 6e 74 33   : Void*) : Int3
31e0: 32 0a 20 20 66 75 6e 20 73 74 65 70 20 3d 20 73  2.  fun step = s
31f0: 71 6c 69 74 65 33 5f 73 74 65 70 28 73 74 61 74  qlite3_step(stat
3200: 65 6d 65 6e 74 20 3a 20 56 6f 69 64 2a 29 20 3a  ement : Void*) :
3210: 20 49 6e 74 33 32 0a 20 20 66 75 6e 20 63 6f 6c   Int32.  fun col
3220: 75 6d 6e 5f 69 6e 74 36 34 20 3d 20 73 71 6c 69  umn_int64 = sqli
3230: 74 65 33 5f 63 6f 6c 75 6d 6e 5f 69 6e 74 36 34  te3_column_int64
3240: 28 73 74 61 74 65 6d 65 6e 74 20 3a 20 56 6f 69  (statement : Voi
3250: 64 2a 2c 20 63 6f 6c 75 6d 6e 20 3a 20 49 6e 74  d*, column : Int
3260: 33 32 29 20 3a 20 49 6e 74 36 34 0a 20 20 66 75  32) : Int64.  fu
3270: 6e 20 63 6f 6c 75 6d 6e 5f 74 65 78 74 20 3d 20  n column_text = 
3280: 73 71 6c 69 74 65 33 5f 63 6f 6c 75 6d 6e 5f 74  sqlite3_column_t
3290: 65 78 74 28 73 74 61 74 65 6d 65 6e 74 20 3a 20  ext(statement : 
32a0: 56 6f 69 64 2a 2c 20 63 6f 6c 75 6d 6e 20 3a 20  Void*, column : 
32b0: 49 6e 74 33 32 29 20 3a 20 55 49 6e 74 38 2a 0a  Int32) : UInt8*.
32c0: 20 20 66 75 6e 20 63 6f 6c 75 6d 6e 5f 74 79 70    fun column_typ
32d0: 65 20 3d 20 73 71 6c 69 74 65 33 5f 63 6f 6c 75  e = sqlite3_colu
32e0: 6d 6e 5f 74 79 70 65 28 73 74 61 74 65 6d 65 6e  mn_type(statemen
32f0: 74 20 3a 20 56 6f 69 64 2a 2c 20 63 6f 6c 75 6d  t : Void*, colum
3300: 6e 20 3a 20 49 6e 74 33 32 29 20 3a 20 49 6e 74  n : Int32) : Int
3310: 33 32 0a 20 20 66 75 6e 20 66 69 6e 61 6c 69 7a  32.  fun finaliz
3320: 65 20 3d 20 73 71 6c 69 74 65 33 5f 66 69 6e 61  e = sqlite3_fina
3330: 6c 69 7a 65 28 73 74 61 74 65 6d 65 6e 74 20 3a  lize(statement :
3340: 20 56 6f 69 64 2a 29 20 3a 20 49 6e 74 33 32 0a   Void*) : Int32.
3350: 20 20 66 75 6e 20 65 78 65 63 20 3d 20 73 71 6c    fun exec = sql
3360: 69 74 65 33 5f 65 78 65 63 28 64 62 20 3a 20 56  ite3_exec(db : V
3370: 6f 69 64 2a 2c 20 73 71 6c 20 3a 20 55 49 6e 74  oid*, sql : UInt
3380: 38 2a 2c 20 63 61 6c 6c 62 61 63 6b 20 3a 20 56  8*, callback : V
3390: 6f 69 64 2a 2c 20 63 6f 6e 74 65 78 74 20 3a 20  oid*, context : 
33a0: 56 6f 69 64 2a 2c 20 65 72 72 6f 72 20 3a 20 55  Void*, error : U
33b0: 49 6e 74 38 2a 2a 29 20 3a 20 49 6e 74 33 32 0a  Int8**) : Int32.
33c0: 20 20 66 75 6e 20 62 75 73 79 5f 74 69 6d 65 6f    fun busy_timeo
33d0: 75 74 20 3d 20 73 71 6c 69 74 65 33 5f 62 75 73  ut = sqlite3_bus
33e0: 79 5f 74 69 6d 65 6f 75 74 28 64 62 20 3a 20 56  y_timeout(db : V
33f0: 6f 69 64 2a 2c 20 6d 69 6c 6c 69 73 65 63 6f 6e  oid*, millisecon
3400: 64 73 20 3a 20 49 6e 74 33 32 29 20 3a 20 49 6e  ds : Int32) : In
3410: 74 33 32 0a 20 20 66 75 6e 20 65 78 74 65 6e 64  t32.  fun extend
3420: 65 64 5f 65 72 72 63 6f 64 65 20 3d 20 73 71 6c  ed_errcode = sql
3430: 69 74 65 33 5f 65 78 74 65 6e 64 65 64 5f 65 72  ite3_extended_er
3440: 72 63 6f 64 65 28 64 62 20 3a 20 56 6f 69 64 2a  rcode(db : Void*
3450: 29 20 3a 20 49 6e 74 33 32 0a 20 20 66 75 6e 20  ) : Int32.  fun 
3460: 65 72 72 6d 73 67 20 3d 20 73 71 6c 69 74 65 33  errmsg = sqlite3
3470: 5f 65 72 72 6d 73 67 28 64 62 20 3a 20 56 6f 69  _errmsg(db : Voi
3480: 64 2a 29 20 3a 20 55 49 6e 74 38 2a 0a 65 6e 64  d*) : UInt8*.end
3490: 0a 0a 6d 6f 64 75 6c 65 20 52 65 63 65 69 70 74  ..module Receipt
34a0: 73 0a 20 20 63 6c 61 73 73 20 52 65 66 75 73 65  s.  class Refuse
34b0: 64 20 3c 20 45 78 63 65 70 74 69 6f 6e 3b 20 65  d < Exception; e
34c0: 6e 64 0a 0a 20 20 63 6c 61 73 73 20 55 6e 61 76  nd..  class Unav
34d0: 61 69 6c 61 62 6c 65 20 3c 20 45 78 63 65 70 74  ailable < Except
34e0: 69 6f 6e 3b 20 65 6e 64 0a 0a 20 20 72 65 63 6f  ion; end..  reco
34f0: 72 64 20 49 64 65 6e 74 69 74 79 2c 20 72 65 70  rd Identity, rep
3500: 6f 73 69 74 6f 72 79 20 3a 20 53 74 72 69 6e 67  ository : String
3510: 2c 20 63 61 70 61 62 69 6c 69 74 69 65 73 20 3a  , capabilities :
3520: 20 53 74 72 69 6e 67 0a 20 20 72 65 63 6f 72 64   String.  record
3530: 20 52 65 71 75 65 73 74 2c 20 61 72 74 69 66 61   Request, artifa
3540: 63 74 73 20 3a 20 41 72 72 61 79 28 53 74 72 69  cts : Array(Stri
3550: 6e 67 29 2c 20 63 61 70 61 62 69 6c 69 74 69 65  ng), capabilitie
3560: 73 5f 6f 66 20 3a 20 53 74 72 69 6e 67 3f 0a 0a  s_of : String?..
3570: 20 20 72 65 63 6f 72 64 20 41 72 74 69 66 61 63    record Artifac
3580: 74 2c 20 61 72 74 69 66 61 63 74 20 3a 20 53 74  t, artifact : St
3590: 72 69 6e 67 2c 20 72 65 63 65 69 76 65 64 20 3a  ring, received :
35a0: 20 42 6f 6f 6c 20 3d 20 66 61 6c 73 65 2c 20 73   Bool = false, s
35b0: 65 6e 64 65 72 20 3a 20 53 74 72 69 6e 67 3f 20  ender : String? 
35c0: 3d 20 6e 69 6c 2c 0a 20 20 20 20 63 61 70 61 62  = nil,.    capab
35d0: 69 6c 69 74 69 65 73 20 3a 20 53 74 72 69 6e 67  ilities : String
35e0: 3f 20 3d 20 6e 69 6c 2c 20 72 65 63 65 69 70 74  ? = nil, receipt
35f0: 20 3a 20 49 6e 74 36 34 3f 20 3d 20 6e 69 6c 2c   : Int64? = nil,
3600: 20 72 69 64 20 3a 20 49 6e 74 36 34 3f 20 3d 20   rid : Int64? = 
3610: 6e 69 6c 20 64 6f 0a 20 20 20 20 69 6e 63 6c 75  nil do.    inclu
3620: 64 65 20 4a 53 4f 4e 3a 3a 53 65 72 69 61 6c 69  de JSON::Seriali
3630: 7a 61 62 6c 65 0a 20 20 65 6e 64 0a 0a 20 20 73  zable.  end..  s
3640: 74 72 75 63 74 20 55 73 65 72 0a 20 20 20 20 69  truct User.    i
3650: 6e 63 6c 75 64 65 20 4a 53 4f 4e 3a 3a 53 65 72  nclude JSON::Ser
3660: 69 61 6c 69 7a 61 62 6c 65 0a 20 20 20 20 67 65  ializable.    ge
3670: 74 74 65 72 20 75 73 65 72 20 3a 20 53 74 72 69  tter user : Stri
3680: 6e 67 0a 20 20 20 20 40 5b 4a 53 4f 4e 3a 3a 46  ng.    @[JSON::F
3690: 69 65 6c 64 28 65 6d 69 74 5f 6e 75 6c 6c 3a 20  ield(emit_null: 
36a0: 74 72 75 65 29 5d 0a 20 20 20 20 67 65 74 74 65  true)].    gette
36b0: 72 20 63 61 70 61 62 69 6c 69 74 69 65 73 20 3a  r capabilities :
36c0: 20 53 74 72 69 6e 67 3f 0a 20 20 20 20 67 65 74   String?.    get
36d0: 74 65 72 20 77 69 6b 69 5f 6d 6f 64 65 72 61 74  ter wiki_moderat
36e0: 65 64 20 3a 20 42 6f 6f 6c 3f 0a 20 20 20 20 67  ed : Bool?.    g
36f0: 65 74 74 65 72 20 74 69 63 6b 65 74 5f 6d 6f 64  etter ticket_mod
3700: 65 72 61 74 65 64 20 3a 20 42 6f 6f 6c 3f 0a 0a  erated : Bool?..
3710: 20 20 20 20 64 65 66 20 69 6e 69 74 69 61 6c 69      def initiali
3720: 7a 65 28 40 75 73 65 72 2c 20 40 63 61 70 61 62  ze(@user, @capab
3730: 69 6c 69 74 69 65 73 20 3d 20 6e 69 6c 2c 20 40  ilities = nil, @
3740: 77 69 6b 69 5f 6d 6f 64 65 72 61 74 65 64 20 3d  wiki_moderated =
3750: 20 6e 69 6c 2c 20 40 74 69 63 6b 65 74 5f 6d 6f   nil, @ticket_mo
3760: 64 65 72 61 74 65 64 20 3d 20 6e 69 6c 29 3b 20  derated = nil); 
3770: 65 6e 64 0a 20 20 65 6e 64 0a 0a 20 20 72 65 63  end.  end..  rec
3780: 6f 72 64 20 41 6e 73 77 65 72 2c 20 61 72 74 69  ord Answer, arti
3790: 66 61 63 74 73 20 3a 20 41 72 72 61 79 28 41 72  facts : Array(Ar
37a0: 74 69 66 61 63 74 29 2c 20 75 73 65 72 20 3a 20  tifact), user : 
37b0: 55 73 65 72 3f 20 3d 20 6e 69 6c 20 64 6f 0a 20  User? = nil do. 
37c0: 20 20 20 69 6e 63 6c 75 64 65 20 4a 53 4f 4e 3a     include JSON:
37d0: 3a 53 65 72 69 61 6c 69 7a 61 62 6c 65 0a 20 20  :Serializable.  
37e0: 65 6e 64 0a 0a 20 20 64 65 66 20 73 65 6c 66 2e  end..  def self.
37f0: 69 64 65 6e 74 69 74 79 28 6c 69 6e 65 20 3a 20  identity(line : 
3800: 53 74 72 69 6e 67 2c 20 72 65 70 6f 73 69 74 6f  String, reposito
3810: 72 69 65 73 20 3a 20 53 74 72 69 6e 67 29 20 3a  ries : String) :
3820: 20 49 64 65 6e 74 69 74 79 0a 20 20 20 20 6f 62   Identity.    ob
3830: 6a 65 63 74 20 3d 20 4a 53 4f 4e 2e 70 61 72 73  ject = JSON.pars
3840: 65 28 6c 69 6e 65 29 2e 61 73 5f 68 3f 0a 20 20  e(line).as_h?.  
3850: 20 20 72 61 69 73 65 20 52 65 66 75 73 65 64 2e    raise Refused.
3860: 6e 65 77 28 22 69 64 65 6e 74 69 74 79 20 6d 75  new("identity mu
3870: 73 74 20 62 65 20 61 6e 20 6f 62 6a 65 63 74 22  st be an object"
3880: 29 20 75 6e 6c 65 73 73 20 6f 62 6a 65 63 74 0a  ) unless object.
3890: 20 20 20 20 6e 61 6d 65 20 3d 20 6f 62 6a 65 63      name = objec
38a0: 74 5b 22 72 65 70 6f 73 69 74 6f 72 79 22 5d 3f  t["repository"]?
38b0: 2e 74 72 79 28 26 2e 61 73 5f 73 3f 29 0a 20 20  .try(&.as_s?).  
38c0: 20 20 63 61 70 73 20 3d 20 6f 62 6a 65 63 74 5b    caps = object[
38d0: 22 63 61 70 61 62 69 6c 69 74 69 65 73 22 5d 3f  "capabilities"]?
38e0: 2e 74 72 79 28 26 2e 61 73 5f 73 3f 29 0a 20 20  .try(&.as_s?).  
38f0: 20 20 69 66 20 75 73 65 72 20 3d 20 6f 62 6a 65    if user = obje
3900: 63 74 5b 22 75 73 65 72 22 5d 3f 0a 20 20 20 20  ct["user"]?.    
3910: 20 20 72 61 69 73 65 20 52 65 66 75 73 65 64 2e    raise Refused.
3920: 6e 65 77 28 22 69 6e 76 61 6c 69 64 20 75 73 65  new("invalid use
3930: 72 22 29 20 75 6e 6c 65 73 73 20 75 73 65 72 2e  r") unless user.
3940: 72 61 77 2e 6e 69 6c 3f 20 7c 7c 20 75 73 65 72  raw.nil? || user
3950: 2e 61 73 5f 73 3f 0a 20 20 20 20 65 6e 64 0a 20  .as_s?.    end. 
3960: 20 20 20 72 61 69 73 65 20 52 65 66 75 73 65 64     raise Refused
3970: 2e 6e 65 77 28 22 69 6e 76 61 6c 69 64 20 63 61  .new("invalid ca
3980: 70 61 62 69 6c 69 74 69 65 73 22 29 20 75 6e 6c  pabilities") unl
3990: 65 73 73 20 63 61 70 73 0a 20 20 20 20 72 61 69  ess caps.    rai
39a0: 73 65 20 52 65 66 75 73 65 64 2e 6e 65 77 28 22  se Refused.new("
39b0: 69 6e 76 61 6c 69 64 20 72 65 70 6f 73 69 74 6f  invalid reposito
39c0: 72 79 22 29 20 75 6e 6c 65 73 73 20 6e 61 6d 65  ry") unless name
39d0: 20 26 26 20 2f 5c 41 5b 41 2d 5a 61 2d 7a 30 2d   && /\A[A-Za-z0-
39e0: 39 5f 5d 5b 41 2d 5a 61 2d 7a 30 2d 39 2e 5f 2d  9_][A-Za-z0-9._-
39f0: 5d 7b 30 2c 39 39 7d 5c 7a 2f 2e 6d 61 74 63 68  ]{0,99}\z/.match
3a00: 65 73 3f 28 6e 61 6d 65 29 20 26 26 20 21 6e 61  es?(name) && !na
3a10: 6d 65 2e 65 6e 64 73 5f 77 69 74 68 3f 28 22 2e  me.ends_with?(".
3a20: 66 6f 73 73 69 6c 22 29 0a 20 20 20 20 72 61 69  fossil").    rai
3a30: 73 65 20 52 65 66 75 73 65 64 2e 6e 65 77 28 22  se Refused.new("
3a40: 72 65 70 6f 73 69 74 6f 72 79 20 69 73 20 6e 6f  repository is no
3a50: 74 20 61 20 72 65 67 75 6c 61 72 20 66 69 6c 65  t a regular file
3a60: 22 29 20 75 6e 6c 65 73 73 20 46 69 6c 65 2e 66  ") unless File.f
3a70: 69 6c 65 3f 28 46 69 6c 65 2e 6a 6f 69 6e 28 72  ile?(File.join(r
3a80: 65 70 6f 73 69 74 6f 72 69 65 73 2c 20 6e 61 6d  epositories, nam
3a90: 65 20 2b 20 22 2e 66 6f 73 73 69 6c 22 29 29 0a  e + ".fossil")).
3aa0: 20 20 20 20 49 64 65 6e 74 69 74 79 2e 6e 65 77      Identity.new
3ab0: 28 6e 61 6d 65 2c 20 63 61 70 73 29 0a 20 20 65  (name, caps).  e
3ac0: 6e 64 0a 0a 20 20 64 65 66 20 73 65 6c 66 2e 72  nd..  def self.r
3ad0: 65 71 75 65 73 74 28 6c 69 6e 65 20 3a 20 53 74  equest(line : St
3ae0: 72 69 6e 67 29 20 3a 20 52 65 71 75 65 73 74 0a  ring) : Request.
3af0: 20 20 20 20 6f 62 6a 65 63 74 20 3d 20 4a 53 4f      object = JSO
3b00: 4e 2e 70 61 72 73 65 28 6c 69 6e 65 29 2e 61 73  N.parse(line).as
3b10: 5f 68 3f 0a 20 20 20 20 72 61 69 73 65 20 52 65  _h?.    raise Re
3b20: 66 75 73 65 64 2e 6e 65 77 28 22 72 65 71 75 65  fused.new("reque
3b30: 73 74 20 6d 75 73 74 20 62 65 20 61 6e 20 6f 62  st must be an ob
3b40: 6a 65 63 74 22 29 20 75 6e 6c 65 73 73 20 6f 62  ject") unless ob
3b50: 6a 65 63 74 0a 20 20 20 20 6f 62 6a 65 63 74 2e  ject.    object.
3b60: 65 61 63 68 5f 6b 65 79 20 7b 20 7c 6b 65 79 7c  each_key { |key|
3b70: 20 72 61 69 73 65 20 52 65 66 75 73 65 64 2e 6e   raise Refused.n
3b80: 65 77 28 22 75 6e 6b 6e 6f 77 6e 20 6b 65 79 3a  ew("unknown key:
3b90: 20 23 7b 6b 65 79 7d 22 29 20 75 6e 6c 65 73 73   #{key}") unless
3ba0: 20 7b 22 6f 63 78 5f 72 65 63 65 69 70 74 22 2c   {"ocx_receipt",
3bb0: 20 22 61 72 74 69 66 61 63 74 73 22 2c 20 22 63   "artifacts", "c
3bc0: 61 70 61 62 69 6c 69 74 69 65 73 5f 6f 66 22 7d  apabilities_of"}
3bd0: 2e 69 6e 63 6c 75 64 65 73 3f 28 6b 65 79 29 20  .includes?(key) 
3be0: 7d 0a 20 20 20 20 72 61 69 73 65 20 52 65 66 75  }.    raise Refu
3bf0: 73 65 64 2e 6e 65 77 28 22 69 6e 76 61 6c 69 64  sed.new("invalid
3c00: 20 6f 63 78 5f 72 65 63 65 69 70 74 22 29 20 75   ocx_receipt") u
3c10: 6e 6c 65 73 73 20 6f 62 6a 65 63 74 5b 22 6f 63  nless object["oc
3c20: 78 5f 72 65 63 65 69 70 74 22 5d 3f 2e 74 72 79  x_receipt"]?.try
3c30: 28 26 2e 61 73 5f 69 36 34 3f 29 20 3d 3d 20 31  (&.as_i64?) == 1
3c40: 0a 20 20 20 20 68 61 73 68 65 73 20 3d 20 5b 5d  .    hashes = []
3c50: 20 6f 66 20 53 74 72 69 6e 67 0a 20 20 20 20 69   of String.    i
3c60: 66 20 76 61 6c 75 65 20 3d 20 6f 62 6a 65 63 74  f value = object
3c70: 5b 22 61 72 74 69 66 61 63 74 73 22 5d 3f 0a 20  ["artifacts"]?. 
3c80: 20 20 20 20 20 6c 69 73 74 20 3d 20 76 61 6c 75       list = valu
3c90: 65 2e 61 73 5f 61 3f 0a 20 20 20 20 20 20 72 61  e.as_a?.      ra
3ca0: 69 73 65 20 52 65 66 75 73 65 64 2e 6e 65 77 28  ise Refused.new(
3cb0: 22 69 6e 76 61 6c 69 64 20 61 72 74 69 66 61 63  "invalid artifac
3cc0: 74 73 22 29 20 75 6e 6c 65 73 73 20 6c 69 73 74  ts") unless list
3cd0: 20 26 26 20 6c 69 73 74 2e 73 69 7a 65 20 3c 3d   && list.size <=
3ce0: 20 36 34 0a 20 20 20 20 20 20 6c 69 73 74 2e 65   64.      list.e
3cf0: 61 63 68 20 64 6f 20 7c 69 74 65 6d 7c 0a 20 20  ach do |item|.  
3d00: 20 20 20 20 20 20 68 61 73 68 20 3d 20 69 74 65        hash = ite
3d10: 6d 2e 61 73 5f 73 3f 0a 20 20 20 20 20 20 20 20  m.as_s?.        
3d20: 72 61 69 73 65 20 52 65 66 75 73 65 64 2e 6e 65  raise Refused.ne
3d30: 77 28 22 69 6e 76 61 6c 69 64 20 61 72 74 69 66  w("invalid artif
3d40: 61 63 74 22 29 20 75 6e 6c 65 73 73 20 68 61 73  act") unless has
3d50: 68 20 26 26 20 2f 5c 41 28 5b 30 2d 39 61 2d 66  h && /\A([0-9a-f
3d60: 5d 7b 34 30 7d 7c 5b 30 2d 39 61 2d 66 5d 7b 36  ]{40}|[0-9a-f]{6
3d70: 34 7d 29 5c 7a 2f 2e 6d 61 74 63 68 65 73 3f 28  4})\z/.matches?(
3d80: 68 61 73 68 29 0a 20 20 20 20 20 20 20 20 68 61  hash).        ha
3d90: 73 68 65 73 20 3c 3c 20 68 61 73 68 0a 20 20 20  shes << hash.   
3da0: 20 20 20 65 6e 64 0a 20 20 20 20 65 6e 64 0a 20     end.    end. 
3db0: 20 20 20 75 73 65 72 20 3d 20 6e 69 6c 0a 20 20     user = nil.  
3dc0: 20 20 69 66 20 76 61 6c 75 65 20 3d 20 6f 62 6a    if value = obj
3dd0: 65 63 74 5b 22 63 61 70 61 62 69 6c 69 74 69 65  ect["capabilitie
3de0: 73 5f 6f 66 22 5d 3f 0a 20 20 20 20 20 20 75 73  s_of"]?.      us
3df0: 65 72 20 3d 20 76 61 6c 75 65 2e 61 73 5f 73 3f  er = value.as_s?
3e00: 0a 20 20 20 20 20 20 72 61 69 73 65 20 52 65 66  .      raise Ref
3e10: 75 73 65 64 2e 6e 65 77 28 22 69 6e 76 61 6c 69  used.new("invali
3e20: 64 20 63 61 70 61 62 69 6c 69 74 69 65 73 5f 6f  d capabilities_o
3e30: 66 22 29 20 75 6e 6c 65 73 73 20 75 73 65 72 20  f") unless user 
3e40: 26 26 20 75 73 65 72 2e 73 69 7a 65 20 3c 3d 20  && user.size <= 
3e50: 31 30 30 20 26 26 20 75 73 65 72 2e 65 61 63 68  100 && user.each
3e60: 5f 63 68 61 72 2e 6e 6f 6e 65 3f 28 26 2e 63 6f  _char.none?(&.co
3e70: 6e 74 72 6f 6c 3f 29 0a 20 20 20 20 65 6e 64 0a  ntrol?).    end.
3e80: 20 20 20 20 52 65 71 75 65 73 74 2e 6e 65 77 28      Request.new(
3e90: 68 61 73 68 65 73 2c 20 75 73 65 72 29 0a 20 20  hashes, user).  
3ea0: 65 6e 64 0a 0a 20 20 63 6c 61 73 73 20 44 61 74  end..  class Dat
3eb0: 61 62 61 73 65 0a 20 20 20 20 41 52 54 49 46 41  abase.    ARTIFA
3ec0: 43 54 20 20 20 20 20 20 20 20 3d 20 22 53 45 4c  CT        = "SEL
3ed0: 45 43 54 20 62 2e 72 69 64 2c 20 62 2e 72 63 76  ECT b.rid, b.rcv
3ee0: 69 64 2c 20 45 58 49 53 54 53 28 53 45 4c 45 43  id, EXISTS(SELEC
3ef0: 54 20 31 20 46 52 4f 4d 20 70 72 69 76 61 74 65  T 1 FROM private
3f00: 20 70 20 57 48 45 52 45 20 70 2e 72 69 64 3d 62   p WHERE p.rid=b
3f10: 2e 72 69 64 29 2c 20 75 2e 6c 6f 67 69 6e 2c 20  .rid), u.login, 
3f20: 75 2e 63 61 70 20 46 52 4f 4d 20 62 6c 6f 62 20  u.cap FROM blob 
3f30: 62 20 4c 45 46 54 20 4a 4f 49 4e 20 72 63 76 66  b LEFT JOIN rcvf
3f40: 72 6f 6d 20 72 20 4f 4e 20 72 2e 72 63 76 69 64  rom r ON r.rcvid
3f50: 3d 62 2e 72 63 76 69 64 20 4c 45 46 54 20 4a 4f  =b.rcvid LEFT JO
3f60: 49 4e 20 75 73 65 72 20 75 20 4f 4e 20 75 2e 75  IN user u ON u.u
3f70: 69 64 3d 72 2e 75 69 64 20 57 48 45 52 45 20 62  id=r.uid WHERE b
3f80: 2e 75 75 69 64 3d 3f 31 20 41 4e 44 20 62 2e 73  .uuid=?1 AND b.s
3f90: 69 7a 65 3e 3d 30 22 0a 20 20 20 20 55 53 45 52  ize>=0".    USER
3fa0: 20 20 20 20 20 20 20 20 20 20 20 20 3d 20 22 53              = "S
3fb0: 45 4c 45 43 54 20 63 61 70 20 46 52 4f 4d 20 75  ELECT cap FROM u
3fc0: 73 65 72 20 57 48 45 52 45 20 6c 6f 67 69 6e 3d  ser WHERE login=
3fd0: 3f 31 22 0a 20 20 20 20 44 45 46 41 55 4c 54 53  ?1".    DEFAULTS
3fe0: 20 20 20 20 20 20 20 20 3d 20 22 53 45 4c 45 43          = "SELEC
3ff0: 54 20 6c 6f 67 69 6e 2c 20 63 61 70 20 46 52 4f  T login, cap FRO
4000: 4d 20 75 73 65 72 20 57 48 45 52 45 20 6c 6f 67  M user WHERE log
4010: 69 6e 20 49 4e 20 28 27 6e 6f 62 6f 64 79 27 2c  in IN ('nobody',
4020: 27 61 6e 6f 6e 79 6d 6f 75 73 27 2c 27 72 65 61  'anonymous','rea
4030: 64 65 72 27 2c 27 64 65 76 65 6c 6f 70 65 72 27  der','developer'
4040: 29 22 0a 20 20 20 20 43 4f 4e 46 49 47 20 20 20  )".    CONFIG   
4050: 20 20 20 20 20 20 20 3d 20 22 53 45 4c 45 43 54         = "SELECT
4060: 20 76 61 6c 75 65 20 46 52 4f 4d 20 63 6f 6e 66   value FROM conf
4070: 69 67 20 57 48 45 52 45 20 6e 61 6d 65 3d 3f 31  ig WHERE name=?1
4080: 22 0a 20 20 20 20 45 52 52 4f 52 53 20 20 20 20  ".    ERRORS    
4090: 20 20 20 20 20 20 3d 20 25 77 28 4f 4b 20 45 52        = %w(OK ER
40a0: 52 4f 52 20 49 4e 54 45 52 4e 41 4c 20 50 45 52  ROR INTERNAL PER
40b0: 4d 20 41 42 4f 52 54 20 42 55 53 59 20 4c 4f 43  M ABORT BUSY LOC
40c0: 4b 45 44 20 4e 4f 4d 45 4d 20 52 45 41 44 4f 4e  KED NOMEM READON
40d0: 4c 59 20 49 4e 54 45 52 52 55 50 54 20 49 4f 45  LY INTERRUPT IOE
40e0: 52 52 20 43 4f 52 52 55 50 54 20 4e 4f 54 46 4f  RR CORRUPT NOTFO
40f0: 55 4e 44 20 46 55 4c 4c 20 43 41 4e 54 4f 50 45  UND FULL CANTOPE
4100: 4e 20 50 52 4f 54 4f 43 4f 4c 20 45 4d 50 54 59  N PROTOCOL EMPTY
4110: 20 53 43 48 45 4d 41 20 54 4f 4f 42 49 47 20 43   SCHEMA TOOBIG C
4120: 4f 4e 53 54 52 41 49 4e 54 20 4d 49 53 4d 41 54  ONSTRAINT MISMAT
4130: 43 48 20 4d 49 53 55 53 45 20 4e 4f 4c 46 53 20  CH MISUSE NOLFS 
4140: 41 55 54 48 20 46 4f 52 4d 41 54 20 52 41 4e 47  AUTH FORMAT RANG
4150: 45 20 4e 4f 54 41 44 42 20 4e 4f 54 49 43 45 20  E NOTADB NOTICE 
4160: 57 41 52 4e 49 4e 47 29 0a 20 20 20 20 52 45 41  WARNING).    REA
4170: 44 4f 4e 4c 59 5f 45 52 52 4f 52 53 20 3d 20 25  DONLY_ERRORS = %
4180: 77 28 52 45 43 4f 56 45 52 59 20 43 41 4e 54 4c  w(RECOVERY CANTL
4190: 4f 43 4b 20 52 4f 4c 4c 42 41 43 4b 20 44 42 4d  OCK ROLLBACK DBM
41a0: 4f 56 45 44 20 43 41 4e 54 49 4e 49 54 20 44 49  OVED CANTINIT DI
41b0: 52 45 43 54 4f 52 59 29 0a 20 20 20 20 40 64 62  RECTORY).    @db
41c0: 20 3d 20 50 6f 69 6e 74 65 72 28 56 6f 69 64 29   = Pointer(Void)
41d0: 2e 6e 75 6c 6c 0a 0a 20 20 20 20 64 65 66 20 69  .null..    def i
41e0: 6e 69 74 69 61 6c 69 7a 65 28 70 61 74 68 20 3a  nitialize(path :
41f0: 20 53 74 72 69 6e 67 29 0a 20 20 20 20 20 20 63   String).      c
4200: 68 65 63 6b 20 4c 69 62 53 51 4c 69 74 65 33 2e  heck LibSQLite3.
4210: 6f 70 65 6e 5f 76 32 28 22 66 69 6c 65 3a 22 20  open_v2("file:" 
4220: 2b 20 55 52 49 2e 65 6e 63 6f 64 65 5f 70 61 74  + URI.encode_pat
4230: 68 28 70 61 74 68 29 20 2b 20 22 3f 6d 6f 64 65  h(path) + "?mode
4240: 3d 72 6f 22 2c 20 70 6f 69 6e 74 65 72 6f 66 28  =ro", pointerof(
4250: 40 64 62 29 2c 20 31 20 7c 20 36 34 2c 20 6e 69  @db), 1 | 64, ni
4260: 6c 29 0a 20 20 20 20 20 20 65 78 65 63 75 74 65  l).      execute
4270: 20 22 50 52 41 47 4d 41 20 71 75 65 72 79 5f 6f   "PRAGMA query_o
4280: 6e 6c 79 3d 31 22 0a 20 20 20 20 20 20 63 68 65  nly=1".      che
4290: 63 6b 20 4c 69 62 53 51 4c 69 74 65 33 2e 62 75  ck LibSQLite3.bu
42a0: 73 79 5f 74 69 6d 65 6f 75 74 28 40 64 62 2c 20  sy_timeout(@db, 
42b0: 30 29 0a 20 20 20 20 20 20 65 78 65 63 75 74 65  0).      execute
42c0: 20 22 42 45 47 49 4e 22 0a 20 20 20 20 72 65 73   "BEGIN".    res
42d0: 63 75 65 20 65 78 0a 20 20 20 20 20 20 63 6c 6f  cue ex.      clo
42e0: 73 65 0a 20 20 20 20 20 20 72 61 69 73 65 20 65  se.      raise e
42f0: 78 0a 20 20 20 20 65 6e 64 0a 0a 20 20 20 20 64  x.    end..    d
4300: 65 66 20 63 68 65 63 6b 28 63 6f 64 65 20 3a 20  ef check(code : 
4310: 49 6e 74 33 32 29 20 3a 20 4e 69 6c 0a 20 20 20  Int32) : Nil.   
4320: 20 20 20 72 65 74 75 72 6e 20 69 66 20 63 6f 64     return if cod
4330: 65 20 3d 3d 20 30 0a 20 20 20 20 20 20 65 78 74  e == 0.      ext
4340: 65 6e 64 65 64 20 3d 20 4c 69 62 53 51 4c 69 74  ended = LibSQLit
4350: 65 33 2e 65 78 74 65 6e 64 65 64 5f 65 72 72 63  e3.extended_errc
4360: 6f 64 65 28 40 64 62 29 0a 20 20 20 20 20 20 62  ode(@db).      b
4370: 61 73 65 20 3d 20 65 78 74 65 6e 64 65 64 20 26  ase = extended &
4380: 20 32 35 35 0a 20 20 20 20 20 20 6e 61 6d 65 20   255.      name 
4390: 3d 20 22 53 51 4c 49 54 45 5f 22 20 2b 20 28 45  = "SQLITE_" + (E
43a0: 52 52 4f 52 53 5b 62 61 73 65 5d 3f 20 7c 7c 20  RRORS[base]? || 
43b0: 22 45 52 52 4f 52 22 29 0a 20 20 20 20 20 20 69  "ERROR").      i
43c0: 66 20 62 61 73 65 20 3d 3d 20 38 20 26 26 20 28  f base == 8 && (
43d0: 73 75 66 66 69 78 20 3d 20 52 45 41 44 4f 4e 4c  suffix = READONL
43e0: 59 5f 45 52 52 4f 52 53 5b 28 65 78 74 65 6e 64  Y_ERRORS[(extend
43f0: 65 64 20 3e 3e 20 38 29 20 2d 20 31 5d 3f 29 20  ed >> 8) - 1]?) 
4400: 26 26 20 65 78 74 65 6e 64 65 64 20 3e 20 32 35  && extended > 25
4410: 35 0a 20 20 20 20 20 20 20 20 6e 61 6d 65 20 2b  5.        name +
4420: 3d 20 22 5f 22 20 2b 20 73 75 66 66 69 78 0a 20  = "_" + suffix. 
4430: 20 20 20 20 20 65 6e 64 0a 20 20 20 20 20 20 72       end.      r
4440: 61 69 73 65 20 55 6e 61 76 61 69 6c 61 62 6c 65  aise Unavailable
4450: 2e 6e 65 77 28 22 23 7b 6e 61 6d 65 7d 20 28 23  .new("#{name} (#
4460: 7b 65 78 74 65 6e 64 65 64 7d 29 3a 20 23 7b 53  {extended}): #{S
4470: 74 72 69 6e 67 2e 6e 65 77 28 4c 69 62 53 51 4c  tring.new(LibSQL
4480: 69 74 65 33 2e 65 72 72 6d 73 67 28 40 64 62 29  ite3.errmsg(@db)
4490: 29 7d 22 29 0a 20 20 20 20 65 6e 64 0a 0a 20 20  )}").    end..  
44a0: 20 20 64 65 66 20 65 78 65 63 75 74 65 28 73 71    def execute(sq
44b0: 6c 20 3a 20 53 74 72 69 6e 67 29 20 3a 20 4e 69  l : String) : Ni
44c0: 6c 0a 20 20 20 20 20 20 63 68 65 63 6b 20 4c 69  l.      check Li
44d0: 62 53 51 4c 69 74 65 33 2e 65 78 65 63 28 40 64  bSQLite3.exec(@d
44e0: 62 2c 20 73 71 6c 2c 20 6e 69 6c 2c 20 6e 69 6c  b, sql, nil, nil
44f0: 2c 20 6e 69 6c 29 0a 20 20 20 20 65 6e 64 0a 0a  , nil).    end..
4500: 20 20 20 20 64 65 66 20 72 6f 77 73 28 73 71 6c      def rows(sql
4510: 20 3a 20 53 74 72 69 6e 67 2c 20 76 61 6c 75 65   : String, value
4520: 20 3a 20 53 74 72 69 6e 67 3f 20 3d 20 6e 69 6c   : String? = nil
4530: 2c 20 26 62 6c 6f 63 6b 20 3a 20 50 6f 69 6e 74  , &block : Point
4540: 65 72 28 56 6f 69 64 29 20 2d 3e 29 20 3a 20 4e  er(Void) ->) : N
4550: 69 6c 0a 20 20 20 20 20 20 73 74 61 74 65 6d 65  il.      stateme
4560: 6e 74 20 3d 20 50 6f 69 6e 74 65 72 28 56 6f 69  nt = Pointer(Voi
4570: 64 29 2e 6e 75 6c 6c 0a 20 20 20 20 20 20 62 65  d).null.      be
4580: 67 69 6e 0a 20 20 20 20 20 20 20 20 63 68 65 63  gin.        chec
4590: 6b 20 4c 69 62 53 51 4c 69 74 65 33 2e 70 72 65  k LibSQLite3.pre
45a0: 70 61 72 65 5f 76 32 28 40 64 62 2c 20 73 71 6c  pare_v2(@db, sql
45b0: 2c 20 2d 31 2c 20 70 6f 69 6e 74 65 72 6f 66 28  , -1, pointerof(
45c0: 73 74 61 74 65 6d 65 6e 74 29 2c 20 6e 69 6c 29  statement), nil)
45d0: 0a 20 20 20 20 20 20 20 20 63 68 65 63 6b 20 4c  .        check L
45e0: 69 62 53 51 4c 69 74 65 33 2e 62 69 6e 64 5f 74  ibSQLite3.bind_t
45f0: 65 78 74 28 73 74 61 74 65 6d 65 6e 74 2c 20 31  ext(statement, 1
4600: 2c 20 76 61 6c 75 65 2c 20 76 61 6c 75 65 2e 62  , value, value.b
4610: 79 74 65 73 69 7a 65 2c 20 50 6f 69 6e 74 65 72  ytesize, Pointer
4620: 28 56 6f 69 64 29 2e 6e 65 77 28 55 49 6e 74 36  (Void).new(UInt6
4630: 34 3a 3a 4d 41 58 29 29 20 69 66 20 76 61 6c 75  4::MAX)) if valu
4640: 65 0a 20 20 20 20 20 20 20 20 6c 6f 6f 70 20 64  e.        loop d
4650: 6f 0a 20 20 20 20 20 20 20 20 20 20 63 6f 64 65  o.          code
4660: 20 3d 20 4c 69 62 53 51 4c 69 74 65 33 2e 73 74   = LibSQLite3.st
4670: 65 70 28 73 74 61 74 65 6d 65 6e 74 29 0a 20 20  ep(statement).  
4680: 20 20 20 20 20 20 20 20 62 72 65 61 6b 20 69 66          break if
4690: 20 63 6f 64 65 20 3d 3d 20 31 30 31 0a 20 20 20   code == 101.   
46a0: 20 20 20 20 20 20 20 63 68 65 63 6b 20 63 6f 64         check cod
46b0: 65 20 75 6e 6c 65 73 73 20 63 6f 64 65 20 3d 3d  e unless code ==
46c0: 20 31 30 30 0a 20 20 20 20 20 20 20 20 20 20 79   100.          y
46d0: 69 65 6c 64 20 73 74 61 74 65 6d 65 6e 74 0a 20  ield statement. 
46e0: 20 20 20 20 20 20 20 65 6e 64 0a 20 20 20 20 20         end.     
46f0: 20 65 6e 73 75 72 65 0a 20 20 20 20 20 20 20 20   ensure.        
4700: 4c 69 62 53 51 4c 69 74 65 33 2e 66 69 6e 61 6c  LibSQLite3.final
4710: 69 7a 65 28 73 74 61 74 65 6d 65 6e 74 29 20 75  ize(statement) u
4720: 6e 6c 65 73 73 20 73 74 61 74 65 6d 65 6e 74 2e  nless statement.
4730: 6e 75 6c 6c 3f 0a 20 20 20 20 20 20 65 6e 64 0a  null?.      end.
4740: 20 20 20 20 65 6e 64 0a 0a 20 20 20 20 64 65 66      end..    def
4750: 20 74 65 78 74 28 73 74 61 74 65 6d 65 6e 74 20   text(statement 
4760: 3a 20 50 6f 69 6e 74 65 72 28 56 6f 69 64 29 2c  : Pointer(Void),
4770: 20 63 6f 6c 75 6d 6e 20 3a 20 49 6e 74 33 32 29   column : Int32)
4780: 20 3a 20 53 74 72 69 6e 67 3f 0a 20 20 20 20 20   : String?.     
4790: 20 53 74 72 69 6e 67 2e 6e 65 77 28 4c 69 62 53   String.new(LibS
47a0: 51 4c 69 74 65 33 2e 63 6f 6c 75 6d 6e 5f 74 65  QLite3.column_te
47b0: 78 74 28 73 74 61 74 65 6d 65 6e 74 2c 20 63 6f  xt(statement, co
47c0: 6c 75 6d 6e 29 29 20 75 6e 6c 65 73 73 20 4c 69  lumn)) unless Li
47d0: 62 53 51 4c 69 74 65 33 2e 63 6f 6c 75 6d 6e 5f  bSQLite3.column_
47e0: 74 79 70 65 28 73 74 61 74 65 6d 65 6e 74 2c 20  type(statement, 
47f0: 63 6f 6c 75 6d 6e 29 20 3d 3d 20 35 0a 20 20 20  column) == 5.   
4800: 20 65 6e 64 0a 0a 20 20 20 20 64 65 66 20 65 78   end..    def ex
4810: 70 61 6e 64 65 64 28 63 61 70 20 3a 20 53 74 72  panded(cap : Str
4820: 69 6e 67 2c 20 64 65 66 61 75 6c 74 73 20 3a 20  ing, defaults : 
4830: 48 61 73 68 28 53 74 72 69 6e 67 2c 20 53 74 72  Hash(String, Str
4840: 69 6e 67 29 29 20 3a 20 53 74 72 69 6e 67 0a 20  ing)) : String. 
4850: 20 20 20 20 20 63 61 70 73 20 3d 20 53 65 74 28       caps = Set(
4860: 43 68 61 72 29 2e 6e 65 77 0a 20 20 20 20 20 20  Char).new.      
4870: 61 64 64 20 3d 20 2d 3e 28 76 61 6c 75 65 20 3a  add = ->(value :
4880: 20 53 74 72 69 6e 67 29 20 7b 20 76 61 6c 75 65   String) { value
4890: 2e 65 61 63 68 5f 63 68 61 72 20 7b 20 7c 63 68  .each_char { |ch
48a0: 61 72 7c 20 63 61 70 73 20 3c 3c 20 63 68 61 72  ar| caps << char
48b0: 20 69 66 20 27 30 27 20 3c 3d 20 63 68 61 72 20   if '0' <= char 
48c0: 3c 3d 20 27 7a 27 20 7d 20 7d 0a 20 20 20 20 20  <= 'z' } }.     
48d0: 20 61 64 64 2e 63 61 6c 6c 28 63 61 70 29 0a 20   add.call(cap). 
48e0: 20 20 20 20 20 61 64 64 2e 63 61 6c 6c 28 64 65       add.call(de
48f0: 66 61 75 6c 74 73 5b 22 61 6e 6f 6e 79 6d 6f 75  faults["anonymou
4900: 73 22 5d 3f 20 7c 7c 20 22 22 29 0a 20 20 20 20  s"]? || "").    
4910: 20 20 61 64 64 2e 63 61 6c 6c 28 64 65 66 61 75    add.call(defau
4920: 6c 74 73 5b 22 6e 6f 62 6f 64 79 22 5d 3f 20 7c  lts["nobody"]? |
4930: 7c 20 22 22 29 0a 20 20 20 20 20 20 61 64 64 2e  | "").      add.
4940: 63 61 6c 6c 28 22 62 63 64 65 66 67 68 69 6a 6b  call("bcdefghijk
4950: 6c 6d 6e 6f 70 71 72 74 77 7a 32 33 34 35 36 37  lmnopqrtwz234567
4960: 41 44 22 29 20 69 66 20 63 61 70 73 2e 69 6e 63  AD") if caps.inc
4970: 6c 75 64 65 73 3f 28 27 61 27 29 20 7c 7c 20 63  ludes?('a') || c
4980: 61 70 73 2e 69 6e 63 6c 75 64 65 73 3f 28 27 73  aps.includes?('s
4990: 27 29 0a 20 20 20 20 20 20 64 65 76 65 6c 6f 70  ').      develop
49a0: 65 72 20 3d 20 63 61 70 73 2e 69 6e 63 6c 75 64  er = caps.includ
49b0: 65 73 3f 28 27 76 27 29 0a 20 20 20 20 20 20 61  es?('v').      a
49c0: 64 64 2e 63 61 6c 6c 28 64 65 66 61 75 6c 74 73  dd.call(defaults
49d0: 5b 22 64 65 76 65 6c 6f 70 65 72 22 5d 3f 20 7c  ["developer"]? |
49e0: 7c 20 22 22 29 20 69 66 20 64 65 76 65 6c 6f 70  | "") if develop
49f0: 65 72 0a 20 20 20 20 20 20 69 66 20 63 61 70 73  er.      if caps
4a00: 2e 69 6e 63 6c 75 64 65 73 3f 28 27 75 27 29 0a  .includes?('u').
4a10: 20 20 20 20 20 20 20 20 61 64 64 2e 63 61 6c 6c          add.call
4a20: 28 64 65 66 61 75 6c 74 73 5b 22 72 65 61 64 65  (defaults["reade
4a30: 72 22 5d 3f 20 7c 7c 20 22 22 29 0a 20 20 20 20  r"]? || "").    
4a40: 20 20 20 20 61 64 64 2e 63 61 6c 6c 28 64 65 66      add.call(def
4a50: 61 75 6c 74 73 5b 22 64 65 76 65 6c 6f 70 65 72  aults["developer
4a60: 22 5d 3f 20 7c 7c 20 22 22 29 20 69 66 20 63 61  "]? || "") if ca
4a70: 70 73 2e 69 6e 63 6c 75 64 65 73 3f 28 27 76 27  ps.includes?('v'
4a80: 29 20 26 26 20 21 64 65 76 65 6c 6f 70 65 72 0a  ) && !developer.
4a90: 20 20 20 20 20 20 65 6e 64 0a 20 20 20 20 20 20        end.      
4aa0: 28 28 27 61 27 2e 2e 27 7a 27 29 2e 74 6f 5f 61  (('a'..'z').to_a
4ab0: 20 2b 20 28 27 30 27 2e 2e 27 39 27 29 2e 74 6f   + ('0'..'9').to
4ac0: 5f 61 20 2b 20 28 27 41 27 2e 2e 27 5a 27 29 2e  _a + ('A'..'Z').
4ad0: 74 6f 5f 61 29 2e 73 65 6c 65 63 74 20 7b 20 7c  to_a).select { |
4ae0: 63 68 61 72 7c 20 63 61 70 73 2e 69 6e 63 6c 75  char| caps.inclu
4af0: 64 65 73 3f 28 63 68 61 72 29 20 7d 2e 6a 6f 69  des?(char) }.joi
4b00: 6e 0a 20 20 20 20 65 6e 64 0a 0a 20 20 20 20 64  n.    end..    d
4b10: 65 66 20 6d 6f 64 65 72 61 74 65 64 28 6e 61 6d  ef moderated(nam
4b20: 65 20 3a 20 53 74 72 69 6e 67 2c 20 63 61 70 73  e : String, caps
4b30: 20 3a 20 53 74 72 69 6e 67 2c 20 70 65 72 6d 69   : String, permi
4b40: 73 73 69 6f 6e 20 3a 20 43 68 61 72 29 20 3a 20  ssion : Char) : 
4b50: 42 6f 6f 6c 0a 20 20 20 20 20 20 74 72 75 74 68  Bool.      truth
4b60: 20 3d 20 66 61 6c 73 65 0a 20 20 20 20 20 20 72   = false.      r
4b70: 6f 77 73 28 43 4f 4e 46 49 47 2c 20 6e 61 6d 65  ows(CONFIG, name
4b80: 29 20 7b 20 7c 72 6f 77 7c 20 74 72 75 74 68 20  ) { |row| truth 
4b90: 3d 20 7b 22 6f 6e 22 2c 20 22 79 65 73 22 2c 20  = {"on", "yes", 
4ba0: 22 74 72 75 65 22 2c 20 22 31 22 7d 2e 69 6e 63  "true", "1"}.inc
4bb0: 6c 75 64 65 73 3f 28 28 74 65 78 74 28 72 6f 77  ludes?((text(row
4bc0: 2c 20 30 29 20 7c 7c 20 22 22 29 2e 64 6f 77 6e  , 0) || "").down
4bd0: 63 61 73 65 29 20 7d 0a 20 20 20 20 20 20 74 72  case) }.      tr
4be0: 75 74 68 20 26 26 20 21 63 61 70 73 2e 69 6e 63  uth && !caps.inc
4bf0: 6c 75 64 65 73 3f 28 70 65 72 6d 69 73 73 69 6f  ludes?(permissio
4c00: 6e 29 0a 20 20 20 20 65 6e 64 0a 0a 20 20 20 20  n).    end..    
4c10: 64 65 66 20 61 6e 73 77 65 72 28 69 64 65 6e 74  def answer(ident
4c20: 69 74 79 20 3a 20 49 64 65 6e 74 69 74 79 2c 20  ity : Identity, 
4c30: 72 65 71 75 65 73 74 20 3a 20 52 65 71 75 65 73  request : Reques
4c40: 74 29 20 3a 20 41 6e 73 77 65 72 0a 20 20 20 20  t) : Answer.    
4c50: 20 20 64 65 66 61 75 6c 74 73 20 3d 20 7b 7d 20    defaults = {} 
4c60: 6f 66 20 53 74 72 69 6e 67 20 3d 3e 20 53 74 72  of String => Str
4c70: 69 6e 67 0a 20 20 20 20 20 20 72 6f 77 73 28 44  ing.      rows(D
4c80: 45 46 41 55 4c 54 53 29 20 7b 20 7c 72 6f 77 7c  EFAULTS) { |row|
4c90: 20 64 65 66 61 75 6c 74 73 5b 74 65 78 74 28 72   defaults[text(r
4ca0: 6f 77 2c 20 30 29 2e 6e 6f 74 5f 6e 69 6c 21 5d  ow, 0).not_nil!]
4cb0: 20 3d 20 74 65 78 74 28 72 6f 77 2c 20 31 29 20   = text(row, 1) 
4cc0: 7c 7c 20 22 22 20 7d 0a 20 20 20 20 20 20 61 72  || "" }.      ar
4cd0: 74 69 66 61 63 74 73 20 3d 20 72 65 71 75 65 73  tifacts = reques
4ce0: 74 2e 61 72 74 69 66 61 63 74 73 2e 6d 61 70 20  t.artifacts.map 
4cf0: 64 6f 20 7c 68 61 73 68 7c 0a 20 20 20 20 20 20  do |hash|.      
4d00: 20 20 61 72 74 69 66 61 63 74 20 3d 20 41 72 74    artifact = Art
4d10: 69 66 61 63 74 2e 6e 65 77 28 68 61 73 68 29 0a  ifact.new(hash).
4d20: 20 20 20 20 20 20 20 20 72 6f 77 73 28 41 52 54          rows(ART
4d30: 49 46 41 43 54 2c 20 68 61 73 68 29 20 64 6f 20  IFACT, hash) do 
4d40: 7c 72 6f 77 7c 0a 20 20 20 20 20 20 20 20 20 20  |row|.          
4d50: 6e 65 78 74 20 69 66 20 4c 69 62 53 51 4c 69 74  next if LibSQLit
4d60: 65 33 2e 63 6f 6c 75 6d 6e 5f 69 6e 74 36 34 28  e3.column_int64(
4d70: 72 6f 77 2c 20 32 29 20 21 3d 20 30 20 26 26 20  row, 2) != 0 && 
4d80: 21 69 64 65 6e 74 69 74 79 2e 63 61 70 61 62 69  !identity.capabi
4d90: 6c 69 74 69 65 73 2e 69 6e 63 6c 75 64 65 73 3f  lities.includes?
4da0: 28 27 78 27 29 0a 20 20 20 20 20 20 20 20 20 20  ('x').          
4db0: 73 65 6e 64 65 72 20 3d 20 74 65 78 74 28 72 6f  sender = text(ro
4dc0: 77 2c 20 33 29 0a 20 20 20 20 20 20 20 20 20 20  w, 3).          
4dd0: 61 72 74 69 66 61 63 74 20 3d 20 41 72 74 69 66  artifact = Artif
4de0: 61 63 74 2e 6e 65 77 28 61 72 74 69 66 61 63 74  act.new(artifact
4df0: 3a 20 68 61 73 68 2c 20 72 65 63 65 69 76 65 64  : hash, received
4e00: 3a 20 74 72 75 65 2c 20 73 65 6e 64 65 72 3a 20  : true, sender: 
4e10: 73 65 6e 64 65 72 2c 0a 20 20 20 20 20 20 20 20  sender,.        
4e20: 20 20 20 20 63 61 70 61 62 69 6c 69 74 69 65 73      capabilities
4e30: 3a 20 73 65 6e 64 65 72 20 3f 20 65 78 70 61 6e  : sender ? expan
4e40: 64 65 64 28 74 65 78 74 28 72 6f 77 2c 20 34 29  ded(text(row, 4)
4e50: 20 7c 7c 20 22 22 2c 20 64 65 66 61 75 6c 74 73   || "", defaults
4e60: 29 20 3a 20 6e 69 6c 2c 0a 20 20 20 20 20 20 20  ) : nil,.       
4e70: 20 20 20 20 20 72 65 63 65 69 70 74 3a 20 4c 69       receipt: Li
4e80: 62 53 51 4c 69 74 65 33 2e 63 6f 6c 75 6d 6e 5f  bSQLite3.column_
4e90: 69 6e 74 36 34 28 72 6f 77 2c 20 31 29 2c 20 72  int64(row, 1), r
4ea0: 69 64 3a 20 4c 69 62 53 51 4c 69 74 65 33 2e 63  id: LibSQLite3.c
4eb0: 6f 6c 75 6d 6e 5f 69 6e 74 36 34 28 72 6f 77 2c  olumn_int64(row,
4ec0: 20 30 29 29 0a 20 20 20 20 20 20 20 20 65 6e 64   0)).        end
4ed0: 0a 20 20 20 20 20 20 20 20 61 72 74 69 66 61 63  .        artifac
4ee0: 74 0a 20 20 20 20 20 20 65 6e 64 0a 20 20 20 20  t.      end.    
4ef0: 20 20 75 73 65 72 20 3d 20 6e 69 6c 0a 20 20 20    user = nil.   
4f00: 20 20 20 69 66 20 6e 61 6d 65 20 3d 20 72 65 71     if name = req
4f10: 75 65 73 74 2e 63 61 70 61 62 69 6c 69 74 69 65  uest.capabilitie
4f20: 73 5f 6f 66 0a 20 20 20 20 20 20 20 20 75 73 65  s_of.        use
4f30: 72 20 3d 20 55 73 65 72 2e 6e 65 77 28 6e 61 6d  r = User.new(nam
4f40: 65 29 0a 20 20 20 20 20 20 20 20 72 6f 77 73 28  e).        rows(
4f50: 55 53 45 52 2c 20 6e 61 6d 65 29 20 64 6f 20 7c  USER, name) do |
4f60: 72 6f 77 7c 0a 20 20 20 20 20 20 20 20 20 20 63  row|.          c
4f70: 61 70 73 20 3d 20 65 78 70 61 6e 64 65 64 28 74  aps = expanded(t
4f80: 65 78 74 28 72 6f 77 2c 20 30 29 20 7c 7c 20 22  ext(row, 0) || "
4f90: 22 2c 20 64 65 66 61 75 6c 74 73 29 0a 20 20 20  ", defaults).   
4fa0: 20 20 20 20 20 20 20 75 73 65 72 20 3d 20 55 73         user = Us
4fb0: 65 72 2e 6e 65 77 28 6e 61 6d 65 2c 20 63 61 70  er.new(name, cap
4fc0: 73 2c 20 6d 6f 64 65 72 61 74 65 64 28 22 6d 6f  s, moderated("mo
4fd0: 64 72 65 71 2d 77 69 6b 69 22 2c 20 63 61 70 73  dreq-wiki", caps
4fe0: 2c 20 27 6c 27 29 2c 20 6d 6f 64 65 72 61 74 65  , 'l'), moderate
4ff0: 64 28 22 6d 6f 64 72 65 71 2d 74 6b 74 22 2c 20  d("modreq-tkt", 
5000: 63 61 70 73 2c 20 27 71 27 29 29 0a 20 20 20 20  caps, 'q')).    
5010: 20 20 20 20 65 6e 64 0a 20 20 20 20 20 20 65 6e      end.      en
5020: 64 0a 20 20 20 20 20 20 65 78 65 63 75 74 65 20  d.      execute 
5030: 22 43 4f 4d 4d 49 54 22 0a 20 20 20 20 20 20 41  "COMMIT".      A
5040: 6e 73 77 65 72 2e 6e 65 77 28 61 72 74 69 66 61  nswer.new(artifa
5050: 63 74 73 2c 20 75 73 65 72 29 0a 20 20 20 20 65  cts, user).    e
5060: 6e 64 0a 0a 20 20 20 20 64 65 66 20 63 6c 6f 73  nd..    def clos
5070: 65 20 3a 20 4e 69 6c 0a 20 20 20 20 20 20 4c 69  e : Nil.      Li
5080: 62 53 51 4c 69 74 65 33 2e 63 6c 6f 73 65 5f 76  bSQLite3.close_v
5090: 32 28 40 64 62 29 20 75 6e 6c 65 73 73 20 40 64  2(@db) unless @d
50a0: 62 2e 6e 75 6c 6c 3f 0a 20 20 20 20 20 20 40 64  b.null?.      @d
50b0: 62 20 3d 20 50 6f 69 6e 74 65 72 28 56 6f 69 64  b = Pointer(Void
50c0: 29 2e 6e 75 6c 6c 0a 20 20 20 20 65 6e 64 0a 20  ).null.    end. 
50d0: 20 65 6e 64 0a 0a 20 20 64 65 66 20 73 65 6c 66   end..  def self
50e0: 2e 6c 69 6e 65 28 73 6f 63 6b 65 74 20 3a 20 55  .line(socket : U
50f0: 4e 49 58 53 6f 63 6b 65 74 2c 20 6c 69 6d 69 74  NIXSocket, limit
5100: 20 3a 20 49 6e 74 33 32 2c 20 64 65 61 64 6c 69   : Int32, deadli
5110: 6e 65 20 3a 20 54 69 6d 65 29 20 3a 20 53 74 72  ne : Time) : Str
5120: 69 6e 67 0a 20 20 20 20 62 79 74 65 73 20 3d 20  ing.    bytes = 
5130: 49 4f 3a 3a 4d 65 6d 6f 72 79 2e 6e 65 77 0a 20  IO::Memory.new. 
5140: 20 20 20 6c 6f 6f 70 20 64 6f 0a 20 20 20 20 20     loop do.     
5150: 20 72 65 6d 61 69 6e 69 6e 67 20 3d 20 64 65 61   remaining = dea
5160: 64 6c 69 6e 65 20 2d 20 54 69 6d 65 2e 75 74 63  dline - Time.utc
5170: 0a 20 20 20 20 20 20 72 61 69 73 65 20 49 4f 3a  .      raise IO:
5180: 3a 54 69 6d 65 6f 75 74 45 72 72 6f 72 2e 6e 65  :TimeoutError.ne
5190: 77 28 22 64 65 61 64 6c 69 6e 65 22 29 20 69 66  w("deadline") if
51a0: 20 72 65 6d 61 69 6e 69 6e 67 20 3c 3d 20 54 69   remaining <= Ti
51b0: 6d 65 3a 3a 53 70 61 6e 2e 7a 65 72 6f 0a 20 20  me::Span.zero.  
51c0: 20 20 20 20 73 6f 63 6b 65 74 2e 72 65 61 64 5f      socket.read_
51d0: 74 69 6d 65 6f 75 74 20 3d 20 72 65 6d 61 69 6e  timeout = remain
51e0: 69 6e 67 0a 20 20 20 20 20 20 62 79 74 65 20 3d  ing.      byte =
51f0: 20 73 6f 63 6b 65 74 2e 72 65 61 64 5f 62 79 74   socket.read_byt
5200: 65 0a 20 20 20 20 20 20 72 61 69 73 65 20 52 65  e.      raise Re
5210: 66 75 73 65 64 2e 6e 65 77 28 22 65 78 70 65 63  fused.new("expec
5220: 74 65 64 20 74 77 6f 20 4a 53 4f 4e 20 6c 69 6e  ted two JSON lin
5230: 65 73 22 29 20 75 6e 6c 65 73 73 20 62 79 74 65  es") unless byte
5240: 0a 20 20 20 20 20 20 62 72 65 61 6b 20 69 66 20  .      break if 
5250: 62 79 74 65 20 3d 3d 20 31 30 0a 20 20 20 20 20  byte == 10.     
5260: 20 72 61 69 73 65 20 52 65 66 75 73 65 64 2e 6e   raise Refused.n
5270: 65 77 28 22 6c 69 6e 65 20 74 6f 6f 20 6c 6f 6e  ew("line too lon
5280: 67 22 29 20 69 66 20 62 79 74 65 73 2e 73 69 7a  g") if bytes.siz
5290: 65 20 3e 3d 20 6c 69 6d 69 74 0a 20 20 20 20 20  e >= limit.     
52a0: 20 62 79 74 65 73 2e 77 72 69 74 65 5f 62 79 74   bytes.write_byt
52b0: 65 28 62 79 74 65 29 0a 20 20 20 20 65 6e 64 0a  e(byte).    end.
52c0: 20 20 20 20 62 79 74 65 73 2e 74 6f 5f 73 0a 20      bytes.to_s. 
52d0: 20 72 65 73 63 75 65 20 49 4f 3a 3a 54 69 6d 65   rescue IO::Time
52e0: 6f 75 74 45 72 72 6f 72 0a 20 20 20 20 72 61 69  outError.    rai
52f0: 73 65 20 52 65 66 75 73 65 64 2e 6e 65 77 28 22  se Refused.new("
5300: 72 65 71 75 65 73 74 20 6e 6f 74 20 72 65 63 65  request not rece
5310: 69 76 65 64 20 77 69 74 68 69 6e 20 32 20 73 22  ived within 2 s"
5320: 29 0a 20 20 65 6e 64 0a 0a 20 20 64 65 66 20 73  ).  end..  def s
5330: 65 6c 66 2e 73 65 72 76 65 28 73 6f 63 6b 65 74  elf.serve(socket
5340: 20 3a 20 55 4e 49 58 53 6f 63 6b 65 74 2c 20 72   : UNIXSocket, r
5350: 65 70 6f 73 69 74 6f 72 69 65 73 20 3a 20 53 74  epositories : St
5360: 72 69 6e 67 2c 20 73 74 61 72 74 65 64 20 3a 20  ring, started : 
5370: 54 69 6d 65 2c 20 62 75 73 79 20 3a 20 42 6f 6f  Time, busy : Boo
5380: 6c 29 20 3a 20 4e 69 6c 0a 20 20 20 20 72 65 70  l) : Nil.    rep
5390: 6f 73 69 74 6f 72 79 20 3d 20 22 2d 22 0a 20 20  ository = "-".  
53a0: 20 20 63 6f 75 6e 74 20 3d 20 30 0a 20 20 20 20    count = 0.    
53b0: 6f 75 74 63 6f 6d 65 20 3d 20 22 72 65 66 75 73  outcome = "refus
53c0: 65 64 22 0a 20 20 20 20 73 6f 63 6b 65 74 2e 77  ed".    socket.w
53d0: 72 69 74 65 5f 74 69 6d 65 6f 75 74 20 3d 20 32  rite_timeout = 2
53e0: 2e 73 65 63 6f 6e 64 73 0a 20 20 20 20 63 72 65  .seconds.    cre
53f0: 64 65 6e 74 69 61 6c 73 20 3d 20 52 65 63 65 69  dentials = Recei
5400: 70 74 50 65 65 72 3a 3a 43 72 65 64 65 6e 74 69  ptPeer::Credenti
5410: 61 6c 73 2e 6e 65 77 0a 20 20 20 20 73 69 7a 65  als.new.    size
5420: 20 3d 20 73 69 7a 65 6f 66 28 52 65 63 65 69 70   = sizeof(Receip
5430: 74 50 65 65 72 3a 3a 43 72 65 64 65 6e 74 69 61  tPeer::Credentia
5440: 6c 73 29 2e 74 6f 5f 75 33 32 0a 20 20 20 20 69  ls).to_u32.    i
5450: 66 20 52 65 63 65 69 70 74 50 65 65 72 2e 67 65  f ReceiptPeer.ge
5460: 74 73 6f 63 6b 6f 70 74 28 73 6f 63 6b 65 74 2e  tsockopt(socket.
5470: 66 64 2c 20 31 2c 20 31 37 2c 20 70 6f 69 6e 74  fd, 1, 17, point
5480: 65 72 6f 66 28 63 72 65 64 65 6e 74 69 61 6c 73  erof(credentials
5490: 29 2c 20 70 6f 69 6e 74 65 72 6f 66 28 73 69 7a  ), pointerof(siz
54a0: 65 29 29 20 21 3d 20 30 20 7c 7c 20 73 69 7a 65  e)) != 0 || size
54b0: 20 21 3d 20 73 69 7a 65 6f 66 28 52 65 63 65 69   != sizeof(Recei
54c0: 70 74 50 65 65 72 3a 3a 43 72 65 64 65 6e 74 69  ptPeer::Credenti
54d0: 61 6c 73 29 20 7c 7c 20 63 72 65 64 65 6e 74 69  als) || credenti
54e0: 61 6c 73 2e 75 69 64 20 21 3d 20 4c 69 62 43 2e  als.uid != LibC.
54f0: 67 65 74 75 69 64 0a 20 20 20 20 20 20 72 61 69  getuid.      rai
5500: 73 65 20 52 65 66 75 73 65 64 2e 6e 65 77 28 22  se Refused.new("
5510: 70 65 65 72 20 75 69 64 20 64 69 66 66 65 72 73  peer uid differs
5520: 22 29 0a 20 20 20 20 65 6e 64 0a 20 20 20 20 69  ").    end.    i
5530: 66 20 62 75 73 79 0a 20 20 20 20 20 20 6f 75 74  f busy.      out
5540: 63 6f 6d 65 20 3d 20 22 62 75 73 79 22 0a 20 20  come = "busy".  
5550: 20 20 20 20 73 6f 63 6b 65 74 2e 70 75 74 73 28      socket.puts(
5560: 7b 75 6e 61 76 61 69 6c 61 62 6c 65 3a 20 22 62  {unavailable: "b
5570: 75 73 79 22 2c 20 72 65 74 72 79 5f 61 66 74 65  usy", retry_afte
5580: 72 3a 20 31 7d 2e 74 6f 5f 6a 73 6f 6e 29 0a 20  r: 1}.to_json). 
5590: 20 20 20 20 20 72 65 74 75 72 6e 0a 20 20 20 20       return.    
55a0: 65 6e 64 0a 20 20 20 20 69 64 65 6e 74 69 74 79  end.    identity
55b0: 20 3d 20 69 64 65 6e 74 69 74 79 28 6c 69 6e 65   = identity(line
55c0: 28 73 6f 63 6b 65 74 2c 20 31 30 32 34 2c 20 73  (socket, 1024, s
55d0: 74 61 72 74 65 64 20 2b 20 32 2e 73 65 63 6f 6e  tarted + 2.secon
55e0: 64 73 29 2c 20 72 65 70 6f 73 69 74 6f 72 69 65  ds), repositorie
55f0: 73 29 0a 20 20 20 20 72 65 70 6f 73 69 74 6f 72  s).    repositor
5600: 79 20 3d 20 69 64 65 6e 74 69 74 79 2e 72 65 70  y = identity.rep
5610: 6f 73 69 74 6f 72 79 0a 20 20 20 20 72 65 71 75  ository.    requ
5620: 65 73 74 20 3d 20 72 65 71 75 65 73 74 28 6c 69  est = request(li
5630: 6e 65 28 73 6f 63 6b 65 74 2c 20 38 31 39 32 2c  ne(socket, 8192,
5640: 20 73 74 61 72 74 65 64 20 2b 20 32 2e 73 65 63   started + 2.sec
5650: 6f 6e 64 73 29 29 0a 20 20 20 20 63 6f 75 6e 74  onds)).    count
5660: 20 3d 20 72 65 71 75 65 73 74 2e 61 72 74 69 66   = request.artif
5670: 61 63 74 73 2e 73 69 7a 65 0a 20 20 20 20 69 66  acts.size.    if
5680: 20 69 64 65 6e 74 69 74 79 2e 63 61 70 61 62 69   identity.capabi
5690: 6c 69 74 69 65 73 2e 69 6e 63 6c 75 64 65 73 3f  lities.includes?
56a0: 28 27 6f 27 29 0a 20 20 20 20 20 20 64 65 61 64  ('o').      dead
56b0: 6c 69 6e 65 20 3d 20 54 69 6d 65 2e 75 74 63 20  line = Time.utc 
56c0: 2b 20 32 2e 73 65 63 6f 6e 64 73 0a 20 20 20 20  + 2.seconds.    
56d0: 20 20 61 6e 73 77 65 72 20 3d 20 6c 6f 6f 70 20    answer = loop 
56e0: 64 6f 0a 20 20 20 20 20 20 20 20 62 65 67 69 6e  do.        begin
56f0: 0a 20 20 20 20 20 20 20 20 20 20 64 62 20 3d 20  .          db = 
5700: 44 61 74 61 62 61 73 65 2e 6e 65 77 28 46 69 6c  Database.new(Fil
5710: 65 2e 6a 6f 69 6e 28 72 65 70 6f 73 69 74 6f 72  e.join(repositor
5720: 69 65 73 2c 20 72 65 70 6f 73 69 74 6f 72 79 20  ies, repository 
5730: 2b 20 22 2e 66 6f 73 73 69 6c 22 29 29 0a 20 20  + ".fossil")).  
5740: 20 20 20 20 20 20 20 20 62 65 67 69 6e 0a 20 20          begin.  
5750: 20 20 20 20 20 20 20 20 20 20 62 72 65 61 6b 20            break 
5760: 64 62 2e 61 6e 73 77 65 72 28 69 64 65 6e 74 69  db.answer(identi
5770: 74 79 2c 20 72 65 71 75 65 73 74 29 0a 20 20 20  ty, request).   
5780: 20 20 20 20 20 20 20 65 6e 73 75 72 65 0a 20 20         ensure.  
5790: 20 20 20 20 20 20 20 20 20 20 64 62 2e 63 6c 6f            db.clo
57a0: 73 65 0a 20 20 20 20 20 20 20 20 20 20 65 6e 64  se.          end
57b0: 0a 20 20 20 20 20 20 20 20 72 65 73 63 75 65 20  .        rescue 
57c0: 65 78 20 3a 20 55 6e 61 76 61 69 6c 61 62 6c 65  ex : Unavailable
57d0: 0a 20 20 20 20 20 20 20 20 20 20 72 65 6d 61 69  .          remai
57e0: 6e 69 6e 67 20 3d 20 64 65 61 64 6c 69 6e 65 20  ning = deadline 
57f0: 2d 20 54 69 6d 65 2e 75 74 63 0a 20 20 20 20 20  - Time.utc.     
5800: 20 20 20 20 20 6c 6f 63 6b 65 64 20 3d 20 7b 22       locked = {"
5810: 53 51 4c 49 54 45 5f 42 55 53 59 22 2c 20 22 53  SQLITE_BUSY", "S
5820: 51 4c 49 54 45 5f 4c 4f 43 4b 45 44 22 7d 2e 61  QLITE_LOCKED"}.a
5830: 6e 79 3f 20 7b 20 7c 6e 61 6d 65 7c 20 65 78 2e  ny? { |name| ex.
5840: 6d 65 73 73 61 67 65 2e 74 6f 5f 73 2e 73 74 61  message.to_s.sta
5850: 72 74 73 5f 77 69 74 68 3f 28 6e 61 6d 65 29 20  rts_with?(name) 
5860: 7d 0a 20 20 20 20 20 20 20 20 20 20 72 61 69 73  }.          rais
5870: 65 20 65 78 20 75 6e 6c 65 73 73 20 6c 6f 63 6b  e ex unless lock
5880: 65 64 20 26 26 20 72 65 6d 61 69 6e 69 6e 67 20  ed && remaining 
5890: 3e 20 54 69 6d 65 3a 3a 53 70 61 6e 2e 7a 65 72  > Time::Span.zer
58a0: 6f 0a 20 20 20 20 20 20 20 20 20 20 23 20 53 51  o.          # SQ
58b0: 4c 69 74 65 27 73 20 43 20 62 75 73 79 20 68 61  Lite's C busy ha
58c0: 6e 64 6c 65 72 20 62 6c 6f 63 6b 73 20 65 76 65  ndler blocks eve
58d0: 72 79 20 66 69 62 65 72 3b 20 77 61 69 74 20 69  ry fiber; wait i
58e0: 6e 20 43 72 79 73 74 61 6c 20 69 6e 73 74 65 61  n Crystal instea
58f0: 64 2e 0a 20 20 20 20 20 20 20 20 20 20 73 6c 65  d..          sle
5900: 65 70 20 4d 61 74 68 2e 6d 69 6e 28 35 30 2e 6d  ep Math.min(50.m
5910: 69 6c 6c 69 73 65 63 6f 6e 64 73 2c 20 72 65 6d  illiseconds, rem
5920: 61 69 6e 69 6e 67 29 0a 20 20 20 20 20 20 20 20  aining).        
5930: 20 20 72 61 69 73 65 20 65 78 20 69 66 20 54 69    raise ex if Ti
5940: 6d 65 2e 75 74 63 20 3e 3d 20 64 65 61 64 6c 69  me.utc >= deadli
5950: 6e 65 0a 20 20 20 20 20 20 20 20 65 6e 64 0a 20  ne.        end. 
5960: 20 20 20 20 20 65 6e 64 0a 20 20 20 20 65 6c 73       end.    els
5970: 65 0a 20 20 20 20 20 20 61 6e 73 77 65 72 20 3d  e.      answer =
5980: 20 41 6e 73 77 65 72 2e 6e 65 77 28 72 65 71 75   Answer.new(requ
5990: 65 73 74 2e 61 72 74 69 66 61 63 74 73 2e 6d 61  est.artifacts.ma
59a0: 70 20 7b 20 7c 68 61 73 68 7c 20 41 72 74 69 66  p { |hash| Artif
59b0: 61 63 74 2e 6e 65 77 28 68 61 73 68 29 20 7d 2c  act.new(hash) },
59c0: 20 72 65 71 75 65 73 74 2e 63 61 70 61 62 69 6c   request.capabil
59d0: 69 74 69 65 73 5f 6f 66 2e 74 72 79 20 7b 20 7c  ities_of.try { |
59e0: 6e 61 6d 65 7c 20 55 73 65 72 2e 6e 65 77 28 6e  name| User.new(n
59f0: 61 6d 65 29 20 7d 29 0a 20 20 20 20 65 6e 64 0a  ame) }).    end.
5a00: 20 20 20 20 73 6f 63 6b 65 74 2e 70 75 74 73 28      socket.puts(
5a10: 61 6e 73 77 65 72 2e 74 6f 5f 6a 73 6f 6e 29 0a  answer.to_json).
5a20: 20 20 20 20 6f 75 74 63 6f 6d 65 20 3d 20 22 6f      outcome = "o
5a30: 6b 22 0a 20 20 72 65 73 63 75 65 20 65 78 20 3a  k".  rescue ex :
5a40: 20 52 65 66 75 73 65 64 20 7c 20 4a 53 4f 4e 3a   Refused | JSON:
5a50: 3a 50 61 72 73 65 45 78 63 65 70 74 69 6f 6e 0a  :ParseException.
5a60: 20 20 20 20 73 6f 63 6b 65 74 2e 70 75 74 73 28      socket.puts(
5a70: 7b 72 65 66 75 73 65 64 3a 20 65 78 2e 69 73 5f  {refused: ex.is_
5a80: 61 3f 28 52 65 66 75 73 65 64 29 20 3f 20 65 78  a?(Refused) ? ex
5a90: 2e 6d 65 73 73 61 67 65 20 3a 20 22 69 6e 76 61  .message : "inva
5aa0: 6c 69 64 20 4a 53 4f 4e 22 7d 2e 74 6f 5f 6a 73  lid JSON"}.to_js
5ab0: 6f 6e 29 0a 20 20 72 65 73 63 75 65 20 65 78 20  on).  rescue ex 
5ac0: 3a 20 55 6e 61 76 61 69 6c 61 62 6c 65 0a 20 20  : Unavailable.  
5ad0: 20 20 6f 75 74 63 6f 6d 65 20 3d 20 22 75 6e 61    outcome = "una
5ae0: 76 61 69 6c 61 62 6c 65 22 0a 20 20 20 20 73 6f  vailable".    so
5af0: 63 6b 65 74 2e 70 75 74 73 28 7b 75 6e 61 76 61  cket.puts({unava
5b00: 69 6c 61 62 6c 65 3a 20 65 78 2e 6d 65 73 73 61  ilable: ex.messa
5b10: 67 65 2c 20 72 65 74 72 79 5f 61 66 74 65 72 3a  ge, retry_after:
5b20: 20 31 7d 2e 74 6f 5f 6a 73 6f 6e 29 0a 20 20 72   1}.to_json).  r
5b30: 65 73 63 75 65 20 65 78 20 3a 20 49 4f 3a 3a 45  escue ex : IO::E
5b40: 72 72 6f 72 0a 20 20 20 20 72 61 69 73 65 20 65  rror.    raise e
5b50: 78 0a 20 20 72 65 73 63 75 65 20 65 78 0a 20 20  x.  rescue ex.  
5b60: 20 20 23 20 41 6e 79 74 68 69 6e 67 20 75 6e 66    # Anything unf
5b70: 6f 72 65 73 65 65 6e 20 73 74 69 6c 6c 20 61 6e  oreseen still an
5b80: 73 77 65 72 73 2c 20 73 6f 20 61 20 63 61 6c 6c  swers, so a call
5b90: 65 72 20 6e 65 76 65 72 20 77 61 69 74 73 20 6f  er never waits o
5ba0: 6e 20 61 20 73 69 6c 65 6e 74 20 63 6c 6f 73 65  n a silent close
5bb0: 2e 0a 20 20 20 20 6f 75 74 63 6f 6d 65 20 3d 20  ..    outcome = 
5bc0: 22 75 6e 61 76 61 69 6c 61 62 6c 65 22 0a 20 20  "unavailable".  
5bd0: 20 20 73 6f 63 6b 65 74 2e 70 75 74 73 28 7b 75    socket.puts({u
5be0: 6e 61 76 61 69 6c 61 62 6c 65 3a 20 22 69 6e 74  navailable: "int
5bf0: 65 72 6e 61 6c 20 65 72 72 6f 72 22 2c 20 72 65  ernal error", re
5c00: 74 72 79 5f 61 66 74 65 72 3a 20 31 7d 2e 74 6f  try_after: 1}.to
5c10: 5f 6a 73 6f 6e 29 0a 20 20 65 6e 73 75 72 65 0a  _json).  ensure.
5c20: 20 20 20 20 73 6f 63 6b 65 74 2e 63 6c 6f 73 65      socket.close
5c30: 0a 20 20 20 20 53 54 44 45 52 52 2e 70 75 74 73  .    STDERR.puts
5c40: 20 22 23 7b 54 69 6d 65 2e 75 74 63 2e 74 6f 5f   "#{Time.utc.to_
5c50: 72 66 63 33 33 33 39 7d 20 23 7b 72 65 70 6f 73  rfc3339} #{repos
5c60: 69 74 6f 72 79 7d 20 23 7b 63 6f 75 6e 74 7d 20  itory} #{count} 
5c70: 23 7b 6f 75 74 63 6f 6d 65 7d 20 23 7b 28 54 69  #{outcome} #{(Ti
5c80: 6d 65 2e 75 74 63 20 2d 20 73 74 61 72 74 65 64  me.utc - started
5c90: 29 2e 74 6f 74 61 6c 5f 6d 69 6c 6c 69 73 65 63  ).total_millisec
5ca0: 6f 6e 64 73 2e 74 6f 5f 69 36 34 7d 6d 73 22 0a  onds.to_i64}ms".
5cb0: 20 20 65 6e 64 0a 0a 20 20 23 20 50 61 73 73 69    end..  # Passi
5cc0: 6e 67 20 61 72 67 75 6d 65 6e 74 73 20 74 68 72  ng arguments thr
5cd0: 6f 75 67 68 20 73 70 61 77 6e 20 63 6f 70 69 65  ough spawn copie
5ce0: 73 20 65 61 63 68 20 61 63 63 65 70 74 65 64 20  s each accepted 
5cf0: 63 6f 6e 6e 65 63 74 69 6f 6e 27 73 20 76 61 6c  connection's val
5d00: 75 65 73 2e 0a 20 20 64 65 66 20 73 65 6c 66 2e  ues..  def self.
5d10: 68 61 6e 64 6c 65 28 73 6f 63 6b 65 74 20 3a 20  handle(socket : 
5d20: 55 4e 49 58 53 6f 63 6b 65 74 2c 20 72 65 70 6f  UNIXSocket, repo
5d30: 73 69 74 6f 72 69 65 73 20 3a 20 53 74 72 69 6e  sitories : Strin
5d40: 67 2c 20 73 74 61 72 74 65 64 20 3a 20 54 69 6d  g, started : Tim
5d50: 65 2c 20 62 75 73 79 20 3a 20 42 6f 6f 6c 2c 20  e, busy : Bool, 
5d60: 73 6c 6f 74 73 20 3a 20 43 68 61 6e 6e 65 6c 28  slots : Channel(
5d70: 4e 69 6c 29 29 20 3a 20 4e 69 6c 0a 20 20 20 20  Nil)) : Nil.    
5d80: 23 20 42 6f 75 6e 64 20 74 68 65 20 77 68 6f 6c  # Bound the whol
5d90: 65 20 63 6f 6e 6e 65 63 74 69 6f 6e 2c 20 69 6e  e connection, in
5da0: 63 6c 75 64 69 6e 67 20 61 20 63 61 6c 6c 65 72  cluding a caller
5db0: 20 74 68 61 74 20 73 74 6f 70 73 20 72 65 61 64   that stops read
5dc0: 69 6e 67 20 69 74 73 20 61 6e 73 77 65 72 2e 0a  ing its answer..
5dd0: 20 20 20 20 64 6f 6e 65 20 3d 20 43 68 61 6e 6e      done = Chann
5de0: 65 6c 28 4e 69 6c 29 2e 6e 65 77 0a 20 20 20 20  el(Nil).new.    
5df0: 73 70 61 77 6e 20 64 6f 0a 20 20 20 20 20 20 72  spawn do.      r
5e00: 65 6d 61 69 6e 69 6e 67 20 3d 20 73 74 61 72 74  emaining = start
5e10: 65 64 20 2b 20 35 2e 73 65 63 6f 6e 64 73 20 2d  ed + 5.seconds -
5e20: 20 54 69 6d 65 2e 75 74 63 0a 20 20 20 20 20 20   Time.utc.      
5e30: 69 66 20 72 65 6d 61 69 6e 69 6e 67 20 3e 20 54  if remaining > T
5e40: 69 6d 65 3a 3a 53 70 61 6e 2e 7a 65 72 6f 0a 20  ime::Span.zero. 
5e50: 20 20 20 20 20 20 20 73 65 6c 65 63 74 0a 20 20         select.  
5e60: 20 20 20 20 20 20 77 68 65 6e 20 64 6f 6e 65 2e        when done.
5e70: 72 65 63 65 69 76 65 3f 0a 20 20 20 20 20 20 20  receive?.       
5e80: 20 20 20 6e 65 78 74 0a 20 20 20 20 20 20 20 20     next.        
5e90: 77 68 65 6e 20 74 69 6d 65 6f 75 74 28 72 65 6d  when timeout(rem
5ea0: 61 69 6e 69 6e 67 29 0a 20 20 20 20 20 20 20 20  aining).        
5eb0: 65 6e 64 0a 20 20 20 20 20 20 65 6e 64 0a 20 20  end.      end.  
5ec0: 20 20 20 20 73 6f 63 6b 65 74 2e 63 6c 6f 73 65      socket.close
5ed0: 0a 20 20 20 20 65 6e 64 0a 20 20 20 20 73 65 72  .    end.    ser
5ee0: 76 65 28 73 6f 63 6b 65 74 2c 20 72 65 70 6f 73  ve(socket, repos
5ef0: 69 74 6f 72 69 65 73 2c 20 73 74 61 72 74 65 64  itories, started
5f00: 2c 20 62 75 73 79 29 0a 20 20 72 65 73 63 75 65  , busy).  rescue
5f10: 20 49 4f 3a 3a 45 72 72 6f 72 0a 20 20 20 20 23   IO::Error.    #
5f20: 20 54 68 65 20 63 61 6c 6c 65 72 20 6c 65 66 74   The caller left
5f30: 20 62 65 66 6f 72 65 20 69 74 73 20 61 6e 73 77   before its answ
5f40: 65 72 3b 20 73 65 72 76 65 27 73 20 65 6e 73 75  er; serve's ensu
5f50: 72 65 20 6c 6f 67 67 65 64 20 74 68 65 20 63 6f  re logged the co
5f60: 6e 6e 65 63 74 69 6f 6e 2e 0a 20 20 65 6e 73 75  nnection..  ensu
5f70: 72 65 0a 20 20 20 20 64 6f 6e 65 2e 74 72 79 28  re.    done.try(
5f80: 26 2e 63 6c 6f 73 65 29 0a 20 20 20 20 73 6c 6f  &.close).    slo
5f90: 74 73 2e 72 65 63 65 69 76 65 20 75 6e 6c 65 73  ts.receive unles
5fa0: 73 20 62 75 73 79 0a 20 20 65 6e 64 0a 0a 20 20  s busy.  end..  
5fb0: 64 65 66 20 73 65 6c 66 2e 72 75 6e 20 3a 20 4e  def self.run : N
5fc0: 69 6c 0a 20 20 20 20 61 62 6f 72 74 20 22 55 73  il.    abort "Us
5fd0: 61 67 65 3a 20 72 65 63 65 69 70 74 73 20 53 4f  age: receipts SO
5fe0: 43 4b 45 54 20 52 45 50 4f 53 49 54 4f 52 49 45  CKET REPOSITORIE
5ff0: 53 22 2c 20 32 20 75 6e 6c 65 73 73 20 41 52 47  S", 2 unless ARG
6000: 56 2e 73 69 7a 65 20 3d 3d 20 32 0a 20 20 20 20  V.size == 2.    
6010: 73 6f 63 6b 65 74 5f 70 61 74 68 2c 20 72 65 70  socket_path, rep
6020: 6f 73 69 74 6f 72 69 65 73 20 3d 20 41 52 47 56  ositories = ARGV
6030: 0a 20 20 20 20 46 69 6c 65 2e 64 65 6c 65 74 65  .    File.delete
6040: 28 73 6f 63 6b 65 74 5f 70 61 74 68 29 20 69 66  (socket_path) if
6050: 20 46 69 6c 65 2e 65 78 69 73 74 73 3f 28 73 6f   File.exists?(so
6060: 63 6b 65 74 5f 70 61 74 68 29 0a 20 20 20 20 73  cket_path).    s
6070: 65 72 76 65 72 20 3d 20 55 4e 49 58 53 65 72 76  erver = UNIXServ
6080: 65 72 2e 6e 65 77 28 73 6f 63 6b 65 74 5f 70 61  er.new(socket_pa
6090: 74 68 29 0a 20 20 20 20 46 69 6c 65 2e 63 68 6d  th).    File.chm
60a0: 6f 64 28 73 6f 63 6b 65 74 5f 70 61 74 68 2c 20  od(socket_path, 
60b0: 30 6f 36 30 30 29 0a 20 20 20 20 73 6c 6f 74 73  0o600).    slots
60c0: 20 3d 20 43 68 61 6e 6e 65 6c 28 4e 69 6c 29 2e   = Channel(Nil).
60d0: 6e 65 77 28 38 29 0a 20 20 20 20 77 68 69 6c 65  new(8).    while
60e0: 20 73 6f 63 6b 65 74 20 3d 20 73 65 72 76 65 72   socket = server
60f0: 2e 61 63 63 65 70 74 3f 0a 20 20 20 20 20 20 73  .accept?.      s
6100: 74 61 72 74 65 64 20 3d 20 54 69 6d 65 2e 75 74  tarted = Time.ut
6110: 63 0a 20 20 20 20 20 20 62 75 73 79 20 3d 20 73  c.      busy = s
6120: 65 6c 65 63 74 0a 20 20 20 20 20 20 77 68 65 6e  elect.      when
6130: 20 73 6c 6f 74 73 2e 73 65 6e 64 28 6e 69 6c 29   slots.send(nil)
6140: 20 74 68 65 6e 20 66 61 6c 73 65 0a 20 20 20 20   then false.    
6150: 20 20 65 6c 73 65 0a 20 20 20 20 20 20 20 20 74    else.        t
6160: 72 75 65 0a 20 20 20 20 20 20 65 6e 64 0a 20 20  rue.      end.  
6170: 20 20 20 20 69 66 20 62 75 73 79 0a 20 20 20 20      if busy.    
6180: 20 20 20 20 68 61 6e 64 6c 65 28 73 6f 63 6b 65      handle(socke
6190: 74 2c 20 72 65 70 6f 73 69 74 6f 72 69 65 73 2c  t, repositories,
61a0: 20 73 74 61 72 74 65 64 2c 20 62 75 73 79 2c 20   started, busy, 
61b0: 73 6c 6f 74 73 29 0a 20 20 20 20 20 20 65 6c 73  slots).      els
61c0: 65 0a 20 20 20 20 20 20 20 20 73 70 61 77 6e 20  e.        spawn 
61d0: 68 61 6e 64 6c 65 28 73 6f 63 6b 65 74 2c 20 72  handle(socket, r
61e0: 65 70 6f 73 69 74 6f 72 69 65 73 2c 20 73 74 61  epositories, sta
61f0: 72 74 65 64 2c 20 62 75 73 79 2c 20 73 6c 6f 74  rted, busy, slot
6200: 73 29 0a 20 20 20 20 20 20 65 6e 64 0a 20 20 20  s).      end.   
6210: 20 65 6e 64 0a 20 20 65 6e 64 0a 65 6e 64 0a 0a   end.  end.end..
6220: 52 65 63 65 69 70 74 73 2e 72 75 6e 0a 00 00 00  Receipts.run....
6230: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
6240: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
6250: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
6260: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
6270: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
6280: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
6290: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
62a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
62b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
62c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
62d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
62e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
62f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
6300: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
6310: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
6320: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
6330: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
6340: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
6350: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
6360: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
6370: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
6380: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
6390: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
63a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
63b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
63c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
63d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
63e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
63f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
6400: 73 63 72 69 70 74 73 2d 66 6f 72 2d 72 65 76 69  scripts-for-revi
6410: 65 77 2d 32 2f 72 65 63 6f 72 64 2e 74 78 74 00  ew-2/record.txt.
6420: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
6430: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
6440: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
6450: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
6460: 00 00 00 00 30 30 30 30 36 34 34 00 30 30 30 30  ....0000644.0000
6470: 30 30 30 00 30 30 30 30 30 30 30 00 30 30 30 30  000.0000000.0000
6480: 30 30 31 37 36 35 30 00 31 35 32 37 30 35 32 33  0017650.15270523
6490: 31 32 32 00 30 31 35 35 31 34 00 20 30 00 00 00  122.015514. 0...
64a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
64b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
64c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
64d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
64e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
64f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
6500: 00 75 73 74 61 72 20 20 00 72 6f 6f 74 00 00 00  .ustar  .root...
6510: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
6520: 00 00 00 00 00 00 00 00 00 72 6f 6f 74 00 00 00  .........root...
6530: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
6540: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
6550: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
6560: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
6570: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
6580: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
6590: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
65a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
65b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
65c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
65d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
65e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
65f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
6600: 0a 3d 3d 20 62 75 69 6c 64 3a 20 73 74 61 72 74  .== build: start
6610: 2d 6f 63 78 2c 20 72 65 63 65 69 70 74 73 20 61  -ocx, receipts a
6620: 6e 64 20 6f 63 78 32 20 66 72 6f 6d 20 2f 76 61  nd ocx2 from /va
6630: 72 2f 6c 69 62 2f 6f 63 78 2f 61 67 65 6e 74 73  r/lib/ocx/agents
6640: 2f 6f 70 75 73 2d 6f 63 78 32 2f 70 72 6f 6a 65  /opus-ocx2/proje
6650: 63 74 73 2f 6f 6c 6c 61 6d 61 2f 62 32 64 65 38  cts/ollama/b2de8
6660: 62 30 63 32 63 36 37 30 30 65 62 35 63 36 38 36  b0c2c6700eb5c686
6670: 62 31 37 34 37 62 35 61 61 61 31 65 32 64 32 64  b1747b5aaa1e2d2d
6680: 30 31 33 37 30 61 63 35 35 66 32 65 35 61 31 35  01370ac55f2e5a15
6690: 31 61 36 37 65 30 33 62 34 38 61 2f 74 72 65 65  1a67e03b48a/tree
66a0: 38 0a 0a 3d 3d 20 73 65 72 76 65 64 20 72 65 70  8..== served rep
66b0: 6f 73 69 74 6f 72 79 3a 20 64 65 6d 6f 2e 66 6f  ository: demo.fo
66c0: 73 73 69 6c 2c 20 75 73 65 72 73 2c 20 63 68 61  ssil, users, cha
66d0: 74 20 74 69 6d 65 6c 69 6e 65 0a 2d 72 77 2d 2d  t timeline.-rw--
66e0: 2d 2d 2d 2d 2d 20 20 20 20 31 20 72 6f 6f 74 20  -----    1 root 
66f0: 20 20 20 20 72 6f 6f 74 20 20 20 20 20 20 20 20      root        
6700: 32 31 37 30 38 38 20 4f 63 74 20 32 39 20 30 31  217088 Oct 29 01
6710: 3a 34 38 20 2f 74 6d 70 2f 73 41 2d 33 37 30 37  :48 /tmp/sA-3707
6720: 34 35 32 2f 66 6f 73 73 69 6c 2f 64 65 6d 6f 2e  452/fossil/demo.
6730: 66 6f 73 73 69 6c 0a 0a 3d 3d 20 72 65 63 65 69  fossil..== recei
6740: 70 74 20 73 65 72 76 69 63 65 2c 20 74 68 72 6f  pt service, thro
6750: 75 67 68 20 74 68 65 20 6c 61 75 6e 63 68 65 72  ugh the launcher
6760: 27 73 20 72 65 63 65 69 70 74 73 20 70 6f 6c 69  's receipts poli
6770: 63 79 0a 74 6f 74 61 6c 20 30 0a 73 72 77 2d 2d  cy.total 0.srw--
6780: 2d 2d 2d 2d 2d 20 20 20 20 31 20 72 6f 6f 74 20  -----    1 root 
6790: 20 20 20 20 72 6f 6f 74 20 20 20 20 20 20 20 20      root        
67a0: 20 20 20 20 20 30 20 4f 63 74 20 32 39 20 30 31       0 Oct 29 01
67b0: 3a 34 38 20 72 65 63 65 69 70 74 73 2e 73 6f 63  :48 receipts.soc
67c0: 6b 0a 0a 3d 3d 20 46 6f 73 73 69 6c 20 73 65 72  k..== Fossil ser
67d0: 76 65 72 2c 20 69 74 73 20 65 78 74 72 6f 6f 74  ver, its extroot
67e0: 27 73 20 6f 63 78 32 2d 63 67 69 20 74 68 65 20  's ocx2-cgi the 
67f0: 6c 61 75 6e 63 68 65 72 0a 0a 3d 3d 20 74 68 65  launcher..== the
6800: 20 64 61 65 6d 6f 6e 27 73 20 6e 6f 64 65 3a 20   daemon's node: 
6810: 6e 6f 64 65 73 2e 6a 73 6f 6e 20 61 6e 64 20 63  nodes.json and c
6820: 72 65 64 65 6e 74 69 61 6c 73 0a 0a 3d 3d 20 63  redentials..== c
6830: 6f 6e 66 69 6e 65 64 20 64 61 65 6d 6f 6e 2c 20  onfined daemon, 
6840: 74 68 72 6f 75 67 68 20 74 68 65 20 6c 61 75 6e  through the laun
6850: 63 68 65 72 27 73 20 64 61 65 6d 6f 6e 20 70 6f  cher's daemon po
6860: 6c 69 63 79 0a 7b 22 77 68 65 6e 22 3a 22 32 30  licy.{"when":"20
6870: 32 36 2d 31 30 2d 32 39 54 30 31 3a 34 39 3a 32  26-10-29T01:49:2
6880: 32 5a 22 2c 22 63 6c 6f 6e 65 22 3a 22 2f 74 6d  2Z","clone":"/tm
6890: 70 2f 73 41 2d 33 37 30 37 34 35 32 2f 64 61 65  p/sA-3707452/dae
68a0: 6d 6f 6e 2f 6e 6f 64 65 2f 63 6c 6f 6e 65 73 2f  mon/node/clones/
68b0: 64 65 6d 6f 2e 66 6f 73 73 69 6c 22 2c 22 63 6f  demo.fossil","co
68c0: 6d 70 6c 65 74 69 6f 6e 22 3a 31 2c 22 6e 65 77  mpletion":1,"new
68d0: 22 3a 30 2c 22 68 61 6e 64 65 64 22 3a 5b 5d 2c  ":0,"handed":[],
68e0: 22 73 65 74 74 6c 65 64 22 3a 30 7d 0a 7b 22 77  "settled":0}.{"w
68f0: 68 65 6e 22 3a 22 32 30 32 36 2d 31 30 2d 32 39  hen":"2026-10-29
6900: 54 30 31 3a 34 39 3a 32 35 5a 22 2c 22 63 6c 6f  T01:49:25Z","clo
6910: 6e 65 22 3a 22 2f 74 6d 70 2f 73 41 2d 33 37 30  ne":"/tmp/sA-370
6920: 37 34 35 32 2f 64 61 65 6d 6f 6e 2f 6e 6f 64 65  7452/daemon/node
6930: 2f 63 6c 6f 6e 65 73 2f 64 65 6d 6f 2e 66 6f 73  /clones/demo.fos
6940: 73 69 6c 22 2c 22 63 6f 6d 70 6c 65 74 69 6f 6e  sil","completion
6950: 22 3a 31 2c 22 6e 65 77 22 3a 30 2c 22 68 61 6e  ":1,"new":0,"han
6960: 64 65 64 22 3a 5b 5d 2c 22 73 65 74 74 6c 65 64  ded":[],"settled
6970: 22 3a 30 7d 0a 7b 22 77 68 65 6e 22 3a 22 32 30  ":0}.{"when":"20
6980: 32 36 2d 31 30 2d 32 39 54 30 31 3a 34 39 3a 32  26-10-29T01:49:2
6990: 38 5a 22 2c 22 63 6c 6f 6e 65 22 3a 22 2f 74 6d  8Z","clone":"/tm
69a0: 70 2f 73 41 2d 33 37 30 37 34 35 32 2f 64 61 65  p/sA-3707452/dae
69b0: 6d 6f 6e 2f 6e 6f 64 65 2f 63 6c 6f 6e 65 73 2f  mon/node/clones/
69c0: 64 65 6d 6f 2e 66 6f 73 73 69 6c 22 2c 22 63 6f  demo.fossil","co
69d0: 6d 70 6c 65 74 69 6f 6e 22 3a 31 2c 22 6e 65 77  mpletion":1,"new
69e0: 22 3a 30 2c 22 68 61 6e 64 65 64 22 3a 5b 5d 2c  ":0,"handed":[],
69f0: 22 73 65 74 74 6c 65 64 22 3a 30 7d 0a 0a 3d 3d  "settled":0}..==
6a00: 20 74 77 6f 20 72 75 6e 73 2c 20 70 6f 73 74 65   two runs, poste
6a10: 64 20 62 79 20 62 6f 73 73 20 28 73 65 74 75 70  d by boss (setup
6a20: 29 20 74 68 72 6f 75 67 68 20 46 6f 73 73 69 6c  ) through Fossil
6a30: 27 73 20 66 6f 72 6d 0a 73 61 6e 64 62 6f 78 65  's form.sandboxe
6a40: 64 20 72 75 6e 20 64 30 39 38 39 65 63 64 62 32  d run d0989ecdb2
6a50: 0a 75 6e 63 6f 6e 66 69 6e 65 64 20 72 75 6e 20  .unconfined run 
6a60: 33 64 65 64 62 31 30 64 64 34 0a 0a 3d 3d 20 77  3dedb10dd4..== w
6a70: 68 61 74 20 74 68 65 20 72 75 6e 73 20 70 6f 73  hat the runs pos
6a80: 74 65 64 20 28 73 74 61 72 74 65 64 2c 20 6b 65  ted (started, ke
6a90: 70 74 20 6c 6f 67 73 2c 20 65 6e 64 65 64 29 0a  pt logs, ended).
6aa0: 27 64 30 39 38 39 65 63 64 62 32 27 2c 27 62 6f  'd0989ecdb2','bo
6ab0: 73 73 27 2c 27 44 20 32 30 32 36 2d 31 30 2d 32  ss','D 2026-10-2
6ac0: 39 54 30 31 3a 34 39 3a 33 31 2e 30 34 39 20 48  9T01:49:31.049 H
6ad0: 20 73 61 6e 64 62 6f 78 65 64 5c 73 70 72 6f 62   sandboxed\sprob
6ae0: 65 20 4e 20 74 65 78 74 2f 78 2d 6d 61 72 6b 64  e N text/x-markd
6af0: 6f 77 6e 20 55 20 62 6f 73 73 20 57 20 31 30 30  own U boss W 100
6b00: 37 20 60 60 60 6a 73 6f 6e 20 7b 22 6f 63 78 22  7 ```json {"ocx"
6b10: 3a 20 31 2c 20 22 6b 69 6e 64 22 3a 20 22 72 75  : 1, "kind": "ru
6b20: 6e 22 2c 20 22 6e 6f 64 65 22 3a 20 22 64 65 6d  n", "node": "dem
6b30: 6f 2d 6e 6f 64 65 22 2c 20 22 6f 62 6a 65 63 74  o-node", "object
6b40: 69 76 65 22 3a 20 22 50 72 6f 62 65 20 66 72 6f  ive": "Probe fro
6b50: 6d 20 61 6e 20 61 67 65 6e 74 20 73 61 6e 64 62  m an agent sandb
6b60: 6f 78 2e 22 2c 20 22 77 6f 72 6b 73 70 61 63 65  ox.", "workspace
6b70: 22 3a 20 5b 7b 22 73 74 65 70 22 3a 20 22 64 69  ": [{"step": "di
6b80: 72 65 63 74 6f 72 79 22 2c 20 22 61 74 22 3a 20  rectory", "at": 
6b90: 22 24 24 72 6f 6f 74 24 24 2f 24 24 75 73 65 72  "$$root$$/$$user
6ba0: 24 24 2f 70 72 6f 6a 65 63 74 73 2f 24 24 70 72  $$/projects/$$pr
6bb0: 6f 6a 65 63 74 24 24 2f 24 24 72 75 6e 24 24 22  oject$$/$$run$$"
6bc0: 7d 5d 2c 20 22 73 61 6e 64 62 6f 78 22 3a 20 7b  }], "sandbox": {
6bd0: 22 75 69 64 22 3a 20 31 30 30 30 2c 20 22 67 69  "uid": 1000, "gi
6be0: 64 22 3a 20 31 30 30 30 2c 20 22 63 61 70 61 62  d": 1000, "capab
6bf0: 69 6c 69 74 69 65 73 22 3a 20 5b 5d 2c 20 22 6e  ilities": [], "n
6c00: 65 74 77 6f 72 6b 22 3a 20 66 61 6c 73 65 2c 20  etwork": false, 
6c10: 22 6f 77 6e 5f 73 65 73 73 69 6f 6e 22 3a 20 74  "own_session": t
6c20: 72 75 65 2c 20 22 65 6e 64 73 5f 77 69 74 68 5f  rue, "ends_with_
6c30: 6e 6f 64 65 22 3a 20 74 72 75 65 2c 20 22 72 65  node": true, "re
6c40: 61 64 5f 6f 6e 6c 79 22 3a 20 5b 22 2f 75 73 72  ad_only": ["/usr
6c50: 22 2c 20 22 2f 62 69 6e 22 2c 20 22 2f 6c 69 62  ", "/bin", "/lib
6c60: 22 2c 20 22 2f 6c 69 62 36 34 22 5d 2c 20 22 72  ", "/lib64"], "r
6c70: 65 61 64 5f 77 72 69 74 65 22 3a 20 5b 22 24 24  ead_write": ["$$
6c80: 77 6f 72 6b 73 70 61 63 65 24 24 22 5d 2c 20 22  workspace$$"], "
6c90: 68 6f 6d 65 73 22 3a 20 5b 5d 2c 20 22 66 69 6c  homes": [], "fil
6ca0: 65 73 22 3a 20 5b 5d 2c 20 22 70 72 6f 63 22 3a  es": [], "proc":
6cb0: 20 22 2f 70 72 6f 63 22 2c 20 22 64 65 76 22 3a   "/proc", "dev":
6cc0: 20 22 2f 64 65 76 22 2c 20 22 74 6d 70 22 3a 20   "/dev", "tmp": 
6cd0: 22 2f 74 6d 70 22 7d 2c 20 22 6c 69 6d 69 74 73  "/tmp"}, "limits
6ce0: 22 3a 20 7b 22 77 61 6c 6c 22 3a 20 22 32 6d 22  ": {"wall": "2m"
6cf0: 7d 2c 20 22 70 72 6f 63 65 73 73 22 3a 20 7b 22  }, "process": {"
6d00: 75 73 65 72 22 3a 20 22 62 75 69 6c 64 27 0a 27  user": "build'.'
6d10: 33 64 65 64 62 31 30 64 64 34 27 2c 27 62 6f 73  3dedb10dd4','bos
6d20: 73 27 2c 27 44 20 32 30 32 36 2d 31 30 2d 32 39  s','D 2026-10-29
6d30: 54 30 31 3a 34 39 3a 33 31 2e 31 35 35 20 48 20  T01:49:31.155 H 
6d40: 75 6e 63 6f 6e 66 69 6e 65 64 5c 73 70 72 6f 62  unconfined\sprob
6d50: 65 20 4e 20 74 65 78 74 2f 78 2d 6d 61 72 6b 64  e N text/x-markd
6d60: 6f 77 6e 20 55 20 62 6f 73 73 20 57 20 36 34 32  own U boss W 642
6d70: 20 60 60 60 6a 73 6f 6e 20 7b 22 6f 63 78 22 3a   ```json {"ocx":
6d80: 20 31 2c 20 22 6b 69 6e 64 22 3a 20 22 72 75 6e   1, "kind": "run
6d90: 22 2c 20 22 6e 6f 64 65 22 3a 20 22 64 65 6d 6f  ", "node": "demo
6da0: 2d 6e 6f 64 65 22 2c 20 22 6f 62 6a 65 63 74 69  -node", "objecti
6db0: 76 65 22 3a 20 22 50 72 6f 62 65 20 66 72 6f 6d  ve": "Probe from
6dc0: 20 74 68 65 20 64 61 65 6d 6f 6e 20 69 74 73 65   the daemon itse
6dd0: 6c 66 2c 20 75 6e 63 6f 6e 66 69 6e 65 64 20 62  lf, unconfined b
6de0: 79 20 61 6e 79 20 72 75 6e 20 73 61 6e 64 62 6f  y any run sandbo
6df0: 78 2e 22 2c 20 22 70 72 6f 63 65 73 73 22 3a 20  x.", "process": 
6e00: 7b 22 75 73 65 72 22 3a 20 22 62 75 69 6c 64 65  {"user": "builde
6e10: 72 22 2c 20 22 63 6f 6d 6d 61 6e 64 22 3a 20 5b  r", "command": [
6e20: 22 2f 62 69 6e 2f 73 68 22 2c 20 22 2d 63 22 2c  "/bin/sh", "-c",
6e30: 20 22 65 63 68 6f 20 75 69 64 3d 24 28 69 64 20   "echo uid=$(id 
6e40: 2d 75 29 3b 20 66 6f 72 20 70 20 69 6e 20 2f 74  -u); for p in /t
6e50: 6d 70 2f 73 41 2d 33 37 30 37 34 35 32 2f 66 6f  mp/sA-3707452/fo
6e60: 73 73 69 6c 20 2f 74 6d 70 2f 73 41 2d 33 37 30  ssil /tmp/sA-370
6e70: 37 34 35 32 2f 66 6f 73 73 69 6c 2f 64 65 6d 6f  7452/fossil/demo
6e80: 2e 66 6f 73 73 69 6c 3b 20 64 6f 20 5b 20 2d 65  .fossil; do [ -e
6e90: 20 24 70 20 5d 20 26 26 20 65 63 68 6f 20 56 49   $p ] && echo VI
6ea0: 53 49 42 4c 45 20 24 70 20 7c 7c 20 65 63 68 6f  SIBLE $p || echo
6eb0: 20 61 62 73 65 6e 74 20 24 70 3b 20 64 6f 6e 65   absent $p; done
6ec0: 3b 20 68 65 61 64 20 2d 63 20 31 36 20 2f 74 6d  ; head -c 16 /tm
6ed0: 70 2f 73 41 2d 33 37 30 37 34 35 32 2f 66 6f 73  p/sA-3707452/fos
6ee0: 73 69 6c 2f 64 65 6d 6f 2e 66 6f 73 73 69 6c 20  sil/demo.fossil 
6ef0: 3e 2f 64 65 76 2f 6e 75 6c 6c 20 32 3e 26 31 20  >/dev/null 2>&1 
6f00: 26 26 20 65 63 68 6f 20 52 45 41 44 2d 4f 4b 20  && echo READ-OK 
6f10: 7c 7c 20 65 63 68 6f 20 72 65 61 64 2d 72 65 66  || echo read-ref
6f20: 75 73 65 64 3b 20 6e 3d 30 3b 20 66 6f 72 20 78  used; n=0; for x
6f30: 20 69 6e 20 2f 70 72 6f 63 2f 5b 30 2d 39 5d 2a   in /proc/[0-9]*
6f40: 2f 72 6f 6f 74 2f 74 6d 70 2f 73 41 2d 33 37 30  /root/tmp/sA-370
6f50: 37 34 35 32 2f 66 6f 73 73 69 6c 2f 64 65 6d 6f  7452/fossil/demo
6f60: 2e 66 6f 73 73 69 6c 3b 20 64 6f 20 5b 20 2d 72  .fossil; do [ -r
6f70: 20 24 78 20 5d 20 26 26 20 6e 3d 24 27 0a 27 63   $x ] && n=$'.'c
6f80: 34 37 34 62 64 37 36 37 64 27 2c 27 64 65 6d 6f  474bd767d','demo
6f90: 2d 6e 6f 64 65 27 2c 27 44 20 32 30 32 36 2d 31  -node','D 2026-1
6fa0: 30 2d 32 39 54 30 31 3a 34 39 3a 33 32 2e 35 38  0-29T01:49:32.58
6fb0: 38 20 47 20 64 30 39 38 39 65 63 64 62 32 65 36  8 G d0989ecdb2e6
6fc0: 32 63 63 61 62 66 34 65 38 64 33 62 62 62 61 62  2ccabf4e8d3bbbab
6fd0: 32 65 31 37 37 31 64 34 34 66 32 64 63 36 39 32  2e1771d44f2dc692
6fe0: 38 31 31 62 36 64 66 30 34 62 66 30 31 33 32 35  811b6df04bf01325
6ff0: 61 32 38 64 20 49 20 64 30 39 38 39 65 63 64 62  a28d I d0989ecdb
7000: 32 65 36 32 63 63 61 62 66 34 65 38 64 33 62 62  2e62ccabf4e8d3bb
7010: 62 61 62 32 65 31 37 37 31 64 34 34 66 32 64 63  bab2e1771d44f2dc
7020: 36 39 32 38 31 31 62 36 64 66 30 34 62 66 30 31  692811b6df04bf01
7030: 33 32 35 61 32 38 64 20 4e 20 74 65 78 74 2f 78  325a28d N text/x
7040: 2d 6d 61 72 6b 64 6f 77 6e 20 55 20 64 65 6d 6f  -markdown U demo
7050: 2d 6e 6f 64 65 20 57 20 36 38 37 20 60 60 60 6a  -node W 687 ```j
7060: 73 6f 6e 20 7b 20 20 22 6f 63 78 22 3a 20 31 2c  son {  "ocx": 1,
7070: 20 20 22 6b 69 6e 64 22 3a 20 22 73 74 61 72 74    "kind": "start
7080: 65 64 22 2c 20 20 22 6e 6f 64 65 22 3a 20 22 64  ed",  "node": "d
7090: 65 6d 6f 2d 6e 6f 64 65 22 2c 20 20 22 63 6c 6f  emo-node",  "clo
70a0: 6e 65 22 3a 20 22 30 65 34 35 65 63 36 65 30 66  ne": "0e45ec6e0f
70b0: 35 35 37 30 30 36 66 63 63 62 36 38 37 34 63 31  557006fccb6874c1
70c0: 35 62 39 61 33 37 36 62 38 63 39 62 66 33 22 2c  5b9a376b8c9bf3",
70d0: 20 20 22 72 75 6e 22 3a 20 22 64 30 39 38 39 65    "run": "d0989e
70e0: 63 64 62 32 65 36 32 63 63 61 62 66 34 65 38 64  cdb2e62ccabf4e8d
70f0: 33 62 62 62 61 62 32 65 31 37 37 31 64 34 34 66  3bbbab2e1771d44f
7100: 32 64 63 36 39 32 38 31 31 62 36 64 66 30 34 62  2dc692811b6df04b
7110: 66 30 31 33 32 35 61 32 38 64 22 2c 20 20 22 76  f01325a28d",  "v
7120: 61 6c 75 65 73 22 3a 20 7b 20 20 20 22 72 6f 6f  alues": {   "roo
7130: 74 22 3a 20 22 2f 74 6d 70 2f 73 41 2d 33 37 30  t": "/tmp/sA-370
7140: 37 34 35 32 2f 64 61 65 6d 6f 6e 2f 61 67 65 6e  7452/daemon/agen
7150: 74 73 22 2c 20 20 20 22 70 72 6f 6a 65 63 74 22  ts",   "project"
7160: 3a 20 22 64 65 6d 6f 22 2c 20 20 20 22 75 73 65  : "demo",   "use
7170: 72 22 3a 20 22 62 75 69 6c 64 65 72 22 2c 20 20  r": "builder",  
7180: 20 22 72 75 6e 22 3a 20 22 64 30 39 38 39 65 63   "run": "d0989ec
7190: 64 62 32 65 36 32 63 63 61 62 66 34 65 38 64 33  db2e62ccabf4e8d3
71a0: 62 62 62 61 62 32 65 31 37 37 31 64 34 34 66 32  bbbab2e1771d44f2
71b0: 64 63 36 39 32 38 31 31 62 36 64 66 30 34 62 66  dc692811b6df04bf
71c0: 30 31 33 32 35 61 32 38 64 22 2c 20 20 20 22 6e  01325a28d",   "n
71d0: 6f 64 65 22 3a 20 22 64 65 6d 6f 2d 6e 6f 64 65  ode": "demo-node
71e0: 22 2c 20 20 20 22 73 65 72 76 65 72 22 3a 20 22  ",   "server": "
71f0: 27 0a 27 35 39 66 30 61 62 37 37 33 30 27 2c 27  '.'59f0ab7730','
7200: 64 65 6d 6f 2d 6e 6f 64 65 27 2c 27 44 20 32 30  demo-node','D 20
7210: 32 36 2d 31 30 2d 32 39 54 30 31 3a 34 39 3a 33  26-10-29T01:49:3
7220: 32 2e 36 38 30 20 47 20 64 30 39 38 39 65 63 64  2.680 G d0989ecd
7230: 62 32 65 36 32 63 63 61 62 66 34 65 38 64 33 62  b2e62ccabf4e8d3b
7240: 62 62 61 62 32 65 31 37 37 31 64 34 34 66 32 64  bbab2e1771d44f2d
7250: 63 36 39 32 38 31 31 62 36 64 66 30 34 62 66 30  c692811b6df04bf0
7260: 31 33 32 35 61 32 38 64 20 49 20 64 30 39 38 39  1325a28d I d0989
7270: 65 63 64 62 32 65 36 32 63 63 61 62 66 34 65 38  ecdb2e62ccabf4e8
7280: 64 33 62 62 62 61 62 32 65 31 37 37 31 64 34 34  d3bbbab2e1771d44
7290: 66 32 64 63 36 39 32 38 31 31 62 36 64 66 30 34  f2dc692811b6df04
72a0: 62 66 30 31 33 32 35 61 32 38 64 20 4e 20 74 65  bf01325a28d N te
72b0: 78 74 2f 78 2d 6d 61 72 6b 64 6f 77 6e 20 55 20  xt/x-markdown U 
72c0: 64 65 6d 6f 2d 6e 6f 64 65 20 57 20 32 33 32 20  demo-node W 232 
72d0: 54 68 65 20 70 72 6f 63 65 73 73 20 66 6f 72 20  The process for 
72e0: 74 68 65 20 72 75 6e 20 64 30 39 38 39 65 63 64  the run d0989ecd
72f0: 62 32 65 36 32 63 63 61 62 66 34 65 38 64 33 62  b2e62ccabf4e8d3b
7300: 62 62 61 62 32 65 31 37 37 31 64 34 34 66 32 64  bbab2e1771d44f2d
7310: 63 36 39 32 38 31 31 62 36 64 66 30 34 62 66 30  c692811b6df04bf0
7320: 31 33 32 35 61 32 38 64 20 65 78 69 74 65 64 20  1325a28d exited 
7330: 30 2e 20 7e 7e 7e 7e 20 73 74 64 6f 75 74 20 75  0. ~~~~ stdout u
7340: 69 64 3d 31 30 30 30 20 61 62 73 65 6e 74 20 2f  id=1000 absent /
7350: 74 6d 70 2f 73 41 2d 33 37 30 37 34 35 32 2f 66  tmp/sA-3707452/f
7360: 6f 73 73 69 6c 20 61 62 73 65 6e 74 20 2f 74 6d  ossil absent /tm
7370: 70 2f 73 41 2d 33 37 30 37 34 35 32 2f 66 6f 73  p/sA-3707452/fos
7380: 73 69 6c 2f 64 65 6d 6f 2e 66 6f 73 73 69 6c 20  sil/demo.fossil 
7390: 72 65 61 64 2d 72 65 66 75 73 65 64 20 70 72 6f  read-refused pro
73a0: 63 2d 70 61 74 68 73 2d 72 65 61 64 61 62 6c 65  c-paths-readable
73b0: 3d 30 20 7e 7e 7e 7e 20 5a 20 35 61 66 61 38 34  =0 ~~~~ Z 5afa84
73c0: 37 64 64 36 62 66 61 62 30 63 31 62 39 33 64 39  7dd6bfab0c1b93d9
73d0: 61 66 38 61 36 64 36 33 37 65 20 27 0a 27 34 32  af8a6d637e '.'42
73e0: 33 37 33 62 31 61 31 30 27 2c 27 64 65 6d 6f 2d  373b1a10','demo-
73f0: 6e 6f 64 65 27 2c 27 44 20 32 30 32 36 2d 31 30  node','D 2026-10
7400: 2d 32 39 54 30 31 3a 34 39 3a 33 32 2e 36 39 31  -29T01:49:32.691
7410: 20 47 20 64 30 39 38 39 65 63 64 62 32 65 36 32   G d0989ecdb2e62
7420: 63 63 61 62 66 34 65 38 64 33 62 62 62 61 62 32  ccabf4e8d3bbbab2
7430: 65 31 37 37 31 64 34 34 66 32 64 63 36 39 32 38  e1771d44f2dc6928
7440: 31 31 62 36 64 66 30 34 62 66 30 31 33 32 35 61  11b6df04bf01325a
7450: 32 38 64 20 49 20 64 30 39 38 39 65 63 64 62 32  28d I d0989ecdb2
7460: 65 36 32 63 63 61 62 66 34 65 38 64 33 62 62 62  e62ccabf4e8d3bbb
7470: 61 62 32 65 31 37 37 31 64 34 34 66 32 64 63 36  ab2e1771d44f2dc6
7480: 39 32 38 31 31 62 36 64 66 30 34 62 66 30 31 33  92811b6df04bf013
7490: 32 35 61 32 38 64 20 4e 20 74 65 78 74 2f 78 2d  25a28d N text/x-
74a0: 6d 61 72 6b 64 6f 77 6e 20 55 20 64 65 6d 6f 2d  markdown U demo-
74b0: 6e 6f 64 65 20 57 20 32 33 36 20 60 60 60 6a 73  node W 236 ```js
74c0: 6f 6e 20 7b 20 20 22 6f 63 78 22 3a 20 31 2c 20  on {  "ocx": 1, 
74d0: 20 22 6b 69 6e 64 22 3a 20 22 65 6e 64 65 64 22   "kind": "ended"
74e0: 2c 20 20 22 6e 6f 64 65 22 3a 20 22 64 65 6d 6f  ,  "node": "demo
74f0: 2d 6e 6f 64 65 22 2c 20 20 22 63 6c 6f 6e 65 22  -node",  "clone"
7500: 3a 20 22 30 65 34 35 65 63 36 65 30 66 35 35 37  : "0e45ec6e0f557
7510: 30 30 36 66 63 63 62 36 38 37 34 63 31 35 62 39  006fccb6874c15b9
7520: 61 33 37 36 62 38 63 39 62 66 33 22 2c 20 20 22  a376b8c9bf3",  "
7530: 72 75 6e 22 3a 20 22 64 30 39 38 39 65 63 64 62  run": "d0989ecdb
7540: 32 65 36 32 63 63 61 62 66 34 65 38 64 33 62 62  2e62ccabf4e8d3bb
7550: 62 61 62 32 65 31 37 37 31 64 34 34 66 32 64 63  bab2e1771d44f2dc
7560: 36 39 32 38 31 31 62 36 64 66 30 34 62 66 30 31  692811b6df04bf01
7570: 33 32 35 61 32 38 64 22 2c 20 20 22 6f 75 74 63  325a28d",  "outc
7580: 6f 6d 65 22 3a 20 22 65 78 69 74 65 64 22 2c 20  ome": "exited", 
7590: 20 22 65 78 69 74 22 3a 20 30 20 7d 20 60 60 60   "exit": 0 } ```
75a0: 20 5a 20 36 38 35 34 30 61 36 62 38 64 66 39 61   Z 68540a6b8df9a
75b0: 38 64 31 35 66 38 37 62 61 31 33 34 62 36 62 61  8d15f87ba134b6ba
75c0: 39 32 35 20 27 0a 27 61 61 62 39 30 37 33 33 61  925 '.'aab90733a
75d0: 37 27 2c 27 64 65 6d 6f 2d 6e 6f 64 65 27 2c 27  7','demo-node','
75e0: 44 20 32 30 32 36 2d 31 30 2d 32 39 54 30 31 3a  D 2026-10-29T01:
75f0: 34 39 3a 33 32 2e 38 36 33 20 47 20 33 64 65 64  49:32.863 G 3ded
7600: 62 31 30 64 64 34 31 36 38 32 33 61 33 65 62 64  b10dd416823a3ebd
7610: 32 61 35 37 63 31 61 32 35 66 66 37 31 32 32 61  2a57c1a25ff7122a
7620: 61 39 37 39 39 30 37 37 66 64 34 61 37 64 37 61  a9799077fd4a7d7a
7630: 35 38 38 39 34 31 63 34 34 65 35 39 20 49 20 33  588941c44e59 I 3
7640: 64 65 64 62 31 30 64 64 34 31 36 38 32 33 61 33  dedb10dd416823a3
7650: 65 62 64 32 61 35 37 63 31 61 32 35 66 66 37 31  ebd2a57c1a25ff71
7660: 32 32 61 61 39 37 39 39 30 37 37 66 64 34 61 37  22aa9799077fd4a7
7670: 64 37 61 35 38 38 39 34 31 63 34 34 65 35 39 20  d7a588941c44e59 
7680: 4e 20 74 65 78 74 2f 78 2d 6d 61 72 6b 64 6f 77  N text/x-markdow
7690: 6e 20 55 20 64 65 6d 6f 2d 6e 6f 64 65 20 57 20  n U demo-node W 
76a0: 32 37 37 20 60 60 60 6a 73 6f 6e 20 7b 20 20 22  277 ```json {  "
76b0: 6f 63 78 22 3a 20 31 2c 20 20 22 6b 69 6e 64 22  ocx": 1,  "kind"
76c0: 3a 20 22 65 6e 64 65 64 22 2c 20 20 22 6e 6f 64  : "ended",  "nod
76d0: 65 22 3a 20 22 64 65 6d 6f 2d 6e 6f 64 65 22 2c  e": "demo-node",
76e0: 20 20 22 63 6c 6f 6e 65 22 3a 20 22 30 65 34 35    "clone": "0e45
76f0: 65 63 36 65 30 66 35 35 37 30 30 36 66 63 63 62  ec6e0f557006fccb
7700: 36 38 37 34 63 31 35 62 39 61 33 37 36 62 38 63  6874c15b9a376b8c
7710: 39 62 66 33 22 2c 20 20 22 72 75 6e 22 3a 20 22  9bf3",  "run": "
7720: 33 64 65 64 62 31 30 64 64 34 31 36 38 32 33 61  3dedb10dd416823a
7730: 33 65 62 64 32 61 35 37 63 31 61 32 35 66 66 37  3ebd2a57c1a25ff7
7740: 31 32 32 61 61 39 37 39 39 30 37 37 66 64 34 61  122aa9799077fd4a
7750: 37 64 37 61 35 38 38 39 34 31 63 34 34 65 35 39  7d7a588941c44e59
7760: 22 2c 20 20 22 6f 75 74 63 6f 6d 65 22 3a 20 22  ",  "outcome": "
7770: 72 65 66 75 73 65 64 22 2c 20 20 22 6e 6f 74 65  refused",  "note
7780: 22 3a 20 22 74 68 69 73 20 6e 6f 64 65 20 72 75  ": "this node ru
7790: 6e 73 20 6f 6e 6c 79 20 73 61 6e 64 62 6f 78 65  ns only sandboxe
77a0: 64 20 70 72 6f 63 65 73 73 65 73 22 20 7d 20 60  d processes" } `
77b0: 60 60 20 5a 20 63 62 31 34 30 31 65 65 36 37 34  `` Z cb1401ee674
77c0: 63 38 61 63 33 36 30 62 66 63 34 37 38 39 30 33  c8ac360bfc478903
77d0: 33 33 64 38 30 20 27 0a 0a 3d 3d 20 72 65 63 65  33d80 '..== rece
77e0: 69 70 74 73 20 74 68 72 6f 75 67 68 20 74 68 65  ipts through the
77f0: 20 6c 61 75 6e 63 68 65 72 27 73 20 43 47 49 20   launcher's CGI 
7800: 72 6f 75 74 65 2c 20 61 67 61 69 6e 73 74 20 46  route, against F
7810: 6f 73 73 69 6c 27 73 20 6f 77 6e 20 72 65 63 6f  ossil's own reco
7820: 72 64 0a 62 6f 73 73 20 7b 22 61 72 74 69 66 61  rd.boss {"artifa
7830: 63 74 73 22 3a 20 5b 7b 22 61 72 74 69 66 61 63  cts": [{"artifac
7840: 74 22 3a 20 22 64 30 39 38 39 65 63 64 62 32 65  t": "d0989ecdb2e
7850: 36 32 63 63 61 62 66 34 65 38 64 33 62 62 62 61  62ccabf4e8d3bbba
7860: 62 32 65 31 37 37 31 64 34 34 66 32 64 63 36 39  b2e1771d44f2dc69
7870: 32 38 31 31 62 36 64 66 30 34 62 66 30 31 33 32  2811b6df04bf0132
7880: 35 61 32 38 64 22 2c 20 22 72 65 63 65 69 76 65  5a28d", "receive
7890: 64 22 3a 20 74 72 75 65 2c 20 22 73 65 6e 64 65  d": true, "sende
78a0: 72 22 3a 20 22 62 6f 73 73 22 2c 20 22 63 61 70  r": "boss", "cap
78b0: 61 62 69 6c 69 74 69 65 73 22 3a 20 22 62 63 64  abilities": "bcd
78c0: 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74  efghijklmnopqrst
78d0: 77 7a 32 33 34 35 36 37 41 44 22 2c 20 22 72 65  wz234567AD", "re
78e0: 63 65 69 70 74 22 3a 20 32 2c 20 22 72 69 64 22  ceipt": 2, "rid"
78f0: 3a 20 32 7d 5d 2c 20 22 75 73 65 72 22 3a 20 7b  : 2}], "user": {
7900: 22 75 73 65 72 22 3a 20 22 64 65 6d 6f 2d 6e 6f  "user": "demo-no
7910: 64 65 22 2c 20 22 63 61 70 61 62 69 6c 69 74 69  de", "capabiliti
7920: 65 73 22 3a 20 22 67 68 6a 6f 72 77 7a 34 43 22  es": "ghjorwz4C"
7930: 2c 20 22 77 69 6b 69 5f 6d 6f 64 65 72 61 74 65  , "wiki_moderate
7940: 64 22 3a 20 66 61 6c 73 65 2c 20 22 74 69 63 6b  d": false, "tick
7950: 65 74 5f 6d 6f 64 65 72 61 74 65 64 22 3a 20 66  et_moderated": f
7960: 61 6c 73 65 7d 7d 0a 76 69 65 77 65 72 20 7b 22  alse}}.viewer {"
7970: 61 72 74 69 66 61 63 74 73 22 3a 20 5b 7b 22 61  artifacts": [{"a
7980: 72 74 69 66 61 63 74 22 3a 20 22 64 30 39 38 39  rtifact": "d0989
7990: 65 63 64 62 32 65 36 32 63 63 61 62 66 34 65 38  ecdb2e62ccabf4e8
79a0: 64 33 62 62 62 61 62 32 65 31 37 37 31 64 34 34  d3bbbab2e1771d44
79b0: 66 32 64 63 36 39 32 38 31 31 62 36 64 66 30 34  f2dc692811b6df04
79c0: 62 66 30 31 33 32 35 61 32 38 64 22 2c 20 22 72  bf01325a28d", "r
79d0: 65 63 65 69 76 65 64 22 3a 20 74 72 75 65 2c 20  eceived": true, 
79e0: 22 73 65 6e 64 65 72 22 3a 20 22 62 6f 73 73 22  "sender": "boss"
79f0: 2c 20 22 63 61 70 61 62 69 6c 69 74 69 65 73 22  , "capabilities"
7a00: 3a 20 22 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e  : "bcdefghijklmn
7a10: 6f 70 71 72 73 74 77 7a 32 33 34 35 36 37 41 44  opqrstwz234567AD
7a20: 22 2c 20 22 72 65 63 65 69 70 74 22 3a 20 32 2c  ", "receipt": 2,
7a30: 20 22 72 69 64 22 3a 20 32 7d 5d 2c 20 22 75 73   "rid": 2}], "us
7a40: 65 72 22 3a 20 7b 22 75 73 65 72 22 3a 20 22 64  er": {"user": "d
7a50: 65 6d 6f 2d 6e 6f 64 65 22 2c 20 22 63 61 70 61  emo-node", "capa
7a60: 62 69 6c 69 74 69 65 73 22 3a 20 22 67 68 6a 6f  bilities": "ghjo
7a70: 72 77 7a 34 43 22 2c 20 22 77 69 6b 69 5f 6d 6f  rwz4C", "wiki_mo
7a80: 64 65 72 61 74 65 64 22 3a 20 66 61 6c 73 65 2c  derated": false,
7a90: 20 22 74 69 63 6b 65 74 5f 6d 6f 64 65 72 61 74   "ticket_moderat
7aa0: 65 64 22 3a 20 66 61 6c 73 65 7d 7d 0a 61 6e 6f  ed": false}}.ano
7ab0: 6e 20 7b 22 61 72 74 69 66 61 63 74 73 22 3a 20  n {"artifacts": 
7ac0: 5b 7b 22 61 72 74 69 66 61 63 74 22 3a 20 22 64  [{"artifact": "d
7ad0: 30 39 38 39 65 63 64 62 32 65 36 32 63 63 61 62  0989ecdb2e62ccab
7ae0: 66 34 65 38 64 33 62 62 62 61 62 32 65 31 37 37  f4e8d3bbbab2e177
7af0: 31 64 34 34 66 32 64 63 36 39 32 38 31 31 62 36  1d44f2dc692811b6
7b00: 64 66 30 34 62 66 30 31 33 32 35 61 32 38 64 22  df04bf01325a28d"
7b10: 2c 20 22 72 65 63 65 69 76 65 64 22 3a 20 74 72  , "received": tr
7b20: 75 65 2c 20 22 73 65 6e 64 65 72 22 3a 20 22 62  ue, "sender": "b
7b30: 6f 73 73 22 2c 20 22 63 61 70 61 62 69 6c 69 74  oss", "capabilit
7b40: 69 65 73 22 3a 20 22 62 63 64 65 66 67 68 69 6a  ies": "bcdefghij
7b50: 6b 6c 6d 6e 6f 70 71 72 73 74 77 7a 32 33 34 35  klmnopqrstwz2345
7b60: 36 37 41 44 22 2c 20 22 72 65 63 65 69 70 74 22  67AD", "receipt"
7b70: 3a 20 32 2c 20 22 72 69 64 22 3a 20 32 7d 5d 2c  : 2, "rid": 2}],
7b80: 20 22 75 73 65 72 22 3a 20 7b 22 75 73 65 72 22   "user": {"user"
7b90: 3a 20 22 64 65 6d 6f 2d 6e 6f 64 65 22 2c 20 22  : "demo-node", "
7ba0: 63 61 70 61 62 69 6c 69 74 69 65 73 22 3a 20 22  capabilities": "
7bb0: 67 68 6a 6f 72 77 7a 34 43 22 2c 20 22 77 69 6b  ghjorwz4C", "wik
7bc0: 69 5f 6d 6f 64 65 72 61 74 65 64 22 3a 20 66 61  i_moderated": fa
7bd0: 6c 73 65 2c 20 22 74 69 63 6b 65 74 5f 6d 6f 64  lse, "ticket_mod
7be0: 65 72 61 74 65 64 22 3a 20 66 61 6c 73 65 7d 7d  erated": false}}
7bf0: 0a 46 6f 73 73 69 6c 27 73 20 6f 77 6e 20 72 65  .Fossil's own re
7c00: 63 6f 72 64 3a 20 27 62 6f 73 73 27 2c 27 62 63  cord: 'boss','bc
7c10: 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73  defghijklmnopqrs
7c20: 74 77 7a 32 33 34 35 36 37 41 44 27 2c 32 2c 32  twz234567AD',2,2
7c30: 0a 46 6f 73 73 69 6c 27 73 20 66 75 6c 6c 63 61  .Fossil's fullca
7c40: 70 28 64 65 6d 6f 2d 6e 6f 64 65 29 3a 20 27 67  p(demo-node): 'g
7c50: 68 6a 6f 72 77 7a 34 43 27 0a 0a 3d 3d 20 74 68  hjorwz4C'..== th
7c60: 65 20 63 6f 6e 73 6f 6c 65 2c 20 61 73 20 62 6f  e console, as bo
7c70: 73 73 20 28 74 68 65 20 70 61 67 65 27 73 20 6f  ss (the page's o
7c80: 74 68 65 72 20 70 61 74 68 73 20 73 74 69 6c 6c  ther paths still
7c90: 20 70 61 73 73 20 74 68 72 6f 75 67 68 3a 20 45   pass through: E
7ca0: 36 29 0a 63 6f 6e 73 6f 6c 65 20 6f 6b 3a 20 54  6).console ok: T
7cb0: 72 75 65 20 7c 20 72 75 6e 73 20 72 75 6e 6e 69  rue | runs runni
7cc0: 6e 67 3a 20 30 20 7c 20 65 6e 64 65 64 3a 20 32  ng: 0 | ended: 2
7cd0: 0a 0a 3d 3d 20 77 72 69 74 65 73 20 66 72 6f 6d  ..== writes from
7ce0: 20 74 68 65 20 72 65 63 65 69 70 74 20 73 65 72   the receipt ser
7cf0: 76 69 63 65 27 73 20 70 6f 6c 69 63 79 20 66 61  vice's policy fa
7d00: 69 6c 20 28 61 20 70 72 6f 62 65 20 69 6e 20 70  il (a probe in p
7d10: 6c 61 63 65 20 6f 66 20 72 65 63 65 69 70 74 73  lace of receipts
7d20: 29 0a 46 6f 73 73 69 6c 27 73 20 70 6f 72 74 20  ).Fossil's port 
7d30: 34 35 39 34 35 20 61 6e 73 77 65 72 73 20 6f 6e  45945 answers on
7d40: 20 74 68 65 20 68 6f 73 74 3a 20 32 30 30 0a 47   the host: 200.G
7d50: 43 20 57 61 72 6e 69 6e 67 3a 20 43 6f 75 6c 64  C Warning: Could
7d60: 20 6e 6f 74 20 6f 70 65 6e 20 2f 70 72 6f 63 2f   not open /proc/
7d70: 73 74 61 74 0a 6f 70 65 6e 20 74 68 65 20 72 65  stat.open the re
7d80: 70 6f 73 69 74 6f 72 79 20 66 6f 72 20 77 72 69  pository for wri
7d90: 74 69 6e 67 3a 20 72 65 66 75 73 65 64 20 28 46  ting: refused (F
7da0: 69 6c 65 3a 3a 45 72 72 6f 72 3a 20 45 72 72 6f  ile::Error: Erro
7db0: 72 20 6f 70 65 6e 69 6e 67 20 66 69 6c 65 20 77  r opening file w
7dc0: 69 74 68 20 6d 6f 64 65 20 27 72 2b 27 3a 20 27  ith mode 'r+': '
7dd0: 2f 74 6d 70 2f 73 41 2d 33 37 30 37 34 35 32 2f  /tmp/sA-3707452/
7de0: 66 6f 73 73 69 6c 2f 64 65 6d 6f 2e 66 6f 73 73  fossil/demo.foss
7df0: 69 6c 27 3a 20 52 65 61 64 2d 6f 6e 6c 79 20 66  il': Read-only f
7e00: 69 6c 65 20 73 79 73 74 65 6d 29 0a 63 72 65 61  ile system).crea
7e10: 74 65 20 61 20 66 69 6c 65 20 62 65 73 69 64 65  te a file beside
7e20: 20 69 74 3a 20 72 65 66 75 73 65 64 20 28 46 69   it: refused (Fi
7e30: 6c 65 3a 3a 45 72 72 6f 72 3a 20 45 72 72 6f 72  le::Error: Error
7e40: 20 6f 70 65 6e 69 6e 67 20 66 69 6c 65 20 77 69   opening file wi
7e50: 74 68 20 6d 6f 64 65 20 27 77 27 3a 20 27 2f 74  th mode 'w': '/t
7e60: 6d 70 2f 73 41 2d 33 37 30 37 34 35 32 2f 66 6f  mp/sA-3707452/fo
7e70: 73 73 69 6c 2f 78 27 3a 20 52 65 61 64 2d 6f 6e  ssil/x': Read-on
7e80: 6c 79 20 66 69 6c 65 20 73 79 73 74 65 6d 29 0a  ly file system).
7e90: 72 65 61 64 20 74 68 65 20 72 65 70 6f 73 69 74  read the reposit
7ea0: 6f 72 79 3a 20 72 65 61 64 20 6f 6b 20 28 61 6c  ory: read ok (al
7eb0: 6c 6f 77 65 64 3a 20 72 65 61 64 2d 6f 6e 6c 79  lowed: read-only
7ec0: 29 0a 72 65 61 63 68 20 46 6f 73 73 69 6c 27 73  ).reach Fossil's
7ed0: 20 70 6f 72 74 3a 20 72 65 66 75 73 65 64 20 28   port: refused (
7ee0: 53 6f 63 6b 65 74 3a 3a 43 6f 6e 6e 65 63 74 45  Socket::ConnectE
7ef0: 72 72 6f 72 3a 20 45 72 72 6f 72 20 63 6f 6e 6e  rror: Error conn
7f00: 65 63 74 69 6e 67 20 74 6f 20 27 31 32 37 2e 30  ecting to '127.0
7f10: 2e 30 2e 31 3a 34 35 39 34 35 27 3a 20 43 6f 6e  .0.1:45945': Con
7f20: 6e 65 63 74 69 6f 6e 20 72 65 66 75 73 65 64 29  nection refused)
7f30: 0a 77 72 69 74 65 20 69 6e 20 69 74 73 20 73 6f  .write in its so
7f40: 63 6b 65 74 20 64 69 72 65 63 74 6f 72 79 3a 20  cket directory: 
7f50: 77 72 69 74 74 65 6e 20 28 61 6c 6c 6f 77 65 64  written (allowed
7f60: 3a 20 69 74 73 20 6f 6e 65 20 77 72 69 74 61 62  : its one writab
7f70: 6c 65 20 70 6c 61 63 65 29 0a 0a 3d 3d 20 74 68  le place)..== th
7f80: 65 20 64 61 65 6d 6f 6e 27 73 20 6f 77 6e 20 76  e daemon's own v
7f90: 69 65 77 3a 20 61 20 70 72 6f 62 65 20 69 6e 20  iew: a probe in 
7fa0: 70 6c 61 63 65 20 6f 66 20 69 74 73 20 70 72 6f  place of its pro
7fb0: 67 72 61 6d 2c 20 73 74 61 72 74 65 64 20 74 68  gram, started th
7fc0: 72 6f 75 67 68 20 74 68 65 20 73 61 6d 65 20 64  rough the same d
7fd0: 61 65 6d 6f 6e 20 70 6f 6c 69 63 79 0a 64 61 65  aemon policy.dae
7fe0: 6d 6f 6e 20 70 72 6f 62 65 3a 20 75 69 64 3d 30  mon probe: uid=0
7ff0: 20 63 61 70 73 3d 43 61 70 45 66 66 3a 20 30 30   caps=CapEff: 00
8000: 30 30 30 31 66 66 66 66 66 66 66 66 66 66 0a 61  0001ffffffffff.a
8010: 62 73 65 6e 74 20 2f 74 6d 70 2f 73 41 2d 33 37  bsent /tmp/sA-37
8020: 30 37 34 35 32 2f 66 6f 73 73 69 6c 0a 61 62 73  07452/fossil.abs
8030: 65 6e 74 20 2f 74 6d 70 2f 73 41 2d 33 37 30 37  ent /tmp/sA-3707
8040: 34 35 32 2f 66 6f 73 73 69 6c 2f 64 65 6d 6f 2e  452/fossil/demo.
8050: 66 6f 73 73 69 6c 0a 70 72 6f 63 2d 70 61 74 68  fossil.proc-path
8060: 73 2d 72 65 61 64 61 62 6c 65 3d 30 20 28 6f 74  s-readable=0 (ot
8070: 68 65 72 20 70 72 6f 63 65 73 73 65 73 20 6f 66  her processes of
8080: 20 74 68 65 20 73 61 6d 65 20 61 63 63 6f 75 6e   the same accoun
8090: 74 3b 20 74 68 65 20 6c 69 76 65 20 64 61 65 6d  t; the live daem
80a0: 6f 6e 20 68 61 73 20 61 6e 20 61 63 63 6f 75 6e  on has an accoun
80b0: 74 20 6f 66 20 69 74 73 20 6f 77 6e 29 0a 61 67  t of its own).ag
80c0: 65 6e 74 20 73 61 6e 64 62 6f 78 20 69 6e 73 69  ent sandbox insi
80d0: 64 65 3a 20 73 74 61 72 74 65 64 0a 0a 3d 3d 20  de: started..== 
80e0: 72 65 76 69 65 77 20 74 65 73 74 3a 20 74 68 65  review test: the
80f0: 20 2f 72 65 63 65 69 70 74 20 61 6c 69 61 73 65   /receipt aliase
8100: 73 20 72 65 61 63 68 20 74 68 65 20 72 65 63 65  s reach the rece
8110: 69 70 74 20 73 65 72 76 69 63 65 2c 20 6e 65 76  ipt service, nev
8120: 65 72 20 74 68 65 20 70 61 73 73 74 68 72 6f 75  er the passthrou
8130: 67 68 0a 2f 72 65 63 65 69 70 74 20 2d 3e 20 73  gh./receipt -> s
8140: 65 72 76 69 63 65 20 62 6f 73 73 0a 2f 72 65 63  ervice boss./rec
8150: 65 69 70 74 2f 20 2d 3e 20 73 65 72 76 69 63 65  eipt/ -> service
8160: 20 62 6f 73 73 0a 2f 72 65 63 65 69 70 74 2f 78   boss./receipt/x
8170: 20 2d 3e 20 73 65 72 76 69 63 65 20 62 6f 73 73   -> service boss
8180: 0a 0a 3d 3d 20 72 65 76 69 65 77 20 74 65 73 74  ..== review test
8190: 3a 20 6f 6e 65 20 72 65 70 6f 73 69 74 6f 72 79  : one repository
81a0: 20 68 65 6c 64 20 6c 6f 63 6b 65 64 20 77 68 69   held locked whi
81b0: 6c 65 20 61 6e 6f 74 68 65 72 20 69 73 20 61 73  le another is as
81c0: 6b 65 64 2c 20 73 74 72 61 69 67 68 74 20 61 74  ked, straight at
81d0: 20 74 68 65 20 73 65 72 76 69 63 65 27 73 20 73   the service's s
81e0: 6f 63 6b 65 74 0a 64 65 6d 6f 20 28 6c 6f 63 6b  ocket.demo (lock
81f0: 65 64 3d 54 72 75 65 29 3a 20 32 2e 30 20 73 20  ed=True): 2.0 s 
8200: 20 7b 22 75 6e 61 76 61 69 6c 61 62 6c 65 22 3a   {"unavailable":
8210: 22 53 51 4c 49 54 45 5f 42 55 53 59 20 28 35 29  "SQLITE_BUSY (5)
8220: 3a 20 64 61 74 61 62 61 73 65 20 69 73 20 6c 6f  : database is lo
8230: 63 6b 65 64 22 2c 22 72 65 74 72 79 5f 61 66 74  cked","retry_aft
8240: 65 72 22 3a 31 7d 0a 64 65 6d 6f 32 20 28 6c 6f  er":1}.demo2 (lo
8250: 63 6b 65 64 3d 46 61 6c 73 65 29 3a 20 30 2e 30  cked=False): 0.0
8260: 20 73 20 20 7b 22 61 72 74 69 66 61 63 74 73 22   s  {"artifacts"
8270: 3a 5b 7b 22 61 72 74 69 66 61 63 74 22 3a 22 64  :[{"artifact":"d
8280: 30 39 38 39 65 63 64 62 32 65 36 32 63 63 61 62  0989ecdb2e62ccab
8290: 66 34 65 38 64 33 62 62 62 61 62 32 65 31 37 37  f4e8d3bbbab2e177
82a0: 31 64 34 34 66 32 64 63 36 39 32 38 31 31 62 36  1d44f2dc692811b6
82b0: 64 66 30 34 62 66 30 31 33 32 35 61 32 38 0a 0a  df04bf01325a28..
82c0: 3d 3d 20 72 65 76 69 65 77 20 74 65 73 74 3a 20  == review test: 
82d0: 69 6e 73 69 64 65 20 74 68 65 20 43 47 49 20 73  inside the CGI s
82e0: 61 6e 64 62 6f 78 20 28 61 20 70 72 6f 62 65 20  andbox (a probe 
82f0: 69 6e 20 70 6c 61 63 65 20 6f 66 20 74 68 65 20  in place of the 
8300: 72 65 6c 65 61 73 65 27 73 20 70 72 6f 67 72 61  release's progra
8310: 6d 29 0a 61 72 67 73 3a 20 2d 2d 72 65 63 65 69  m).args: --recei
8320: 70 74 2d 66 64 20 33 0a 43 61 70 45 66 66 3a 20  pt-fd 3.CapEff: 
8330: 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30  0000000000000000
8340: 0a 66 64 20 30 20 2d 3e 20 73 6f 63 6b 65 74 3a  .fd 0 -> socket:
8350: 5b 39 38 36 36 34 33 36 31 33 5d 0a 66 64 20 31  [986643613].fd 1
8360: 20 2d 3e 20 70 69 70 65 3a 5b 39 38 36 36 34 36   -> pipe:[986646
8370: 37 30 35 5d 0a 66 64 20 31 30 20 2d 3e 20 2f 72  705].fd 10 -> /r
8380: 65 6c 65 61 73 65 2f 6f 63 78 32 2d 63 67 69 0a  elease/ocx2-cgi.
8390: 66 64 20 32 20 2d 3e 20 70 69 70 65 3a 5b 39 38  fd 2 -> pipe:[98
83a0: 36 36 34 36 37 30 35 5d 0a 66 64 20 33 20 2d 3e  6646705].fd 3 ->
83b0: 20 73 6f 63 6b 65 74 3a 5b 39 38 36 36 34 39 36   socket:[9866496
83c0: 39 33 5d 0a 66 64 20 34 20 2d 3e 20 70 69 70 65  93].fd 4 -> pipe
83d0: 3a 5b 39 38 36 36 34 34 30 32 32 5d 0a 63 6f 6f  :[986644022].coo
83e0: 6b 69 65 3a 20 61 62 73 65 6e 74 20 6e 6f 6e 63  kie: absent nonc
83f0: 65 3a 20 61 62 73 65 6e 74 20 72 65 70 6f 73 69  e: absent reposi
8400: 74 6f 72 79 3a 20 64 65 6d 6f 0a 61 62 73 65 6e  tory: demo.absen
8410: 74 20 2f 74 6d 70 2f 73 41 2d 33 37 30 37 34 35  t /tmp/sA-370745
8420: 32 2f 66 6f 73 73 69 6c 0a 61 62 73 65 6e 74 20  2/fossil.absent 
8430: 2f 74 6d 70 2f 73 41 2d 33 37 30 37 34 35 32 2f  /tmp/sA-3707452/
8440: 66 6f 73 73 69 6c 2f 64 65 6d 6f 2e 66 6f 73 73  fossil/demo.foss
8450: 69 6c 0a 6e 65 74 77 6f 72 6b 3a 20 30 30 30 0a  il.network: 000.
8460: 0a 3d 3d 20 72 65 76 69 65 77 20 74 65 73 74 3a  .== review test:
8470: 20 61 20 72 65 63 65 69 70 74 20 73 65 72 76 69   a receipt servi
8480: 63 65 20 74 68 61 74 20 6c 69 73 74 65 6e 73 20  ce that listens 
8490: 62 75 74 20 6e 65 76 65 72 20 61 63 63 65 70 74  but never accept
84a0: 73 0a 73 74 61 74 75 73 20 35 30 33 20 61 66 74  s.status 503 aft
84b0: 65 72 20 32 20 73 3a 20 7b 22 75 6e 61 76 61 69  er 2 s: {"unavai
84c0: 6c 61 62 6c 65 22 3a 22 72 65 63 65 69 70 74 20  lable":"receipt 
84d0: 73 65 72 76 69 63 65 20 6e 6f 74 20 61 6e 73 77  service not answ
84e0: 65 72 69 6e 67 22 7d 0a 0a 3d 3d 20 72 6f 6c 6c  ering"}..== roll
84f0: 62 61 63 6b 20 70 6f 69 6e 74 3a 20 74 68 65 20  back point: the 
8500: 6c 61 75 6e 63 68 65 72 27 73 20 43 47 49 20 6c  launcher's CGI l
8510: 69 6e 6b 20 69 73 20 6f 6e 65 20 66 69 6c 65 3b  ink is one file;
8520: 20 74 68 65 20 70 72 65 76 69 6f 75 73 20 70 61   the previous pa
8530: 67 65 20 69 73 20 74 68 65 20 72 65 6c 65 61 73  ge is the releas
8540: 65 27 73 20 6f 77 6e 20 6f 63 78 32 0a 2d 72 77  e's own ocx2.-rw
8550: 78 72 2d 78 72 2d 78 20 20 20 20 31 20 72 6f 6f  xr-xr-x    1 roo
8560: 74 20 20 20 20 20 72 6f 6f 74 20 20 20 20 20 20  t     root      
8570: 20 20 20 20 20 31 39 37 20 4f 63 74 20 32 39 20       197 Oct 29 
8580: 30 31 3a 34 38 20 2f 74 6d 70 2f 73 41 2d 33 37  01:48 /tmp/sA-37
8590: 30 37 34 35 32 2f 65 78 74 2f 6f 63 78 32 2d 63  07452/ext/ocx2-c
85a0: 67 69 0a 64 6f 6e 65 0a 00 00 00 00 00 00 00 00  gi.done.........
85b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
85c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
85d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
85e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
85f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
8600: 73 63 72 69 70 74 73 2d 66 6f 72 2d 72 65 76 69  scripts-for-revi
8610: 65 77 2d 32 2f 73 74 61 67 65 41 2d 64 65 6d 6f  ew-2/stageA-demo
8620: 2e 73 68 00 00 00 00 00 00 00 00 00 00 00 00 00  .sh.............
8630: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
8640: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
8650: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
8660: 00 00 00 00 30 30 30 30 37 35 35 00 30 30 30 30  ....0000755.0000
8670: 30 30 30 00 30 30 30 30 30 30 30 00 30 30 30 30  000.0000000.0000
8680: 30 30 33 32 36 32 32 00 31 35 32 37 30 35 32 33  0032622.15270523
8690: 31 32 32 00 30 31 36 31 35 36 00 20 30 00 00 00  122.016156. 0...
86a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
86b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
86c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
86d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
86e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
86f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
8700: 00 75 73 74 61 72 20 20 00 72 6f 6f 74 00 00 00  .ustar  .root...
8710: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
8720: 00 00 00 00 00 00 00 00 00 72 6f 6f 74 00 00 00  .........root...
8730: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
8740: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
8750: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
8760: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
8770: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
8780: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
8790: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
87a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
87b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
87c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
87d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
87e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
87f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
8800: 23 21 2f 62 69 6e 2f 62 61 73 68 0a 23 20 53 74  #!/bin/bash.# St
8810: 61 67 65 20 41 20 6f 6e 20 61 20 73 63 72 61 74  age A on a scrat
8820: 63 68 20 64 65 70 6c 6f 79 6d 65 6e 74 3a 20 74  ch deployment: t
8830: 68 65 20 72 65 63 65 69 70 74 20 73 65 72 76 69  he receipt servi
8840: 63 65 2c 20 74 68 65 20 6c 61 75 6e 63 68 65 72  ce, the launcher
8850: 27 73 20 43 47 49 0a 23 20 72 6f 75 74 65 20 61  's CGI.# route a
8860: 6e 64 20 74 68 65 20 63 6f 6e 66 69 6e 65 64 20  nd the confined 
8870: 64 61 65 6d 6f 6e 2c 20 77 69 74 68 20 73 74 6f  daemon, with sto
8880: 63 6b 20 46 6f 73 73 69 6c 20 32 2e 32 37 2e 20  ck Fossil 2.27. 
8890: 55 73 61 67 65 3a 0a 23 20 20 20 73 74 61 67 65  Usage:.#   stage
88a0: 41 2d 64 65 6d 6f 2e 73 68 20 54 52 45 45 20 4f  A-demo.sh TREE O
88b0: 55 54 0a 23 20 54 52 45 45 20 69 73 20 74 68 65  UT.# TREE is the
88c0: 20 63 68 65 63 6b 6f 75 74 20 77 68 6f 73 65 20   checkout whose 
88d0: 62 69 6e 2f 20 68 6f 6c 64 73 20 73 74 61 72 74  bin/ holds start
88e0: 2d 6f 63 78 2c 20 72 65 63 65 69 70 74 73 20 61  -ocx, receipts a
88f0: 6e 64 20 6f 63 78 32 3b 20 4f 55 54 0a 23 20 69  nd ocx2; OUT.# i
8900: 73 20 61 20 6e 65 77 20 64 69 72 65 63 74 6f 72  s a new director
8910: 79 20 66 6f 72 20 74 68 65 20 73 63 72 61 74 63  y for the scratc
8920: 68 20 64 65 70 6c 6f 79 6d 65 6e 74 20 61 6e 64  h deployment and
8930: 20 69 74 73 20 72 65 63 6f 72 64 2e 20 45 76 65   its record. Eve
8940: 72 79 0a 23 20 70 72 6f 63 65 73 73 20 69 74 20  ry.# process it 
8950: 73 74 61 72 74 73 20 69 73 20 74 69 6d 65 2d 6c  starts is time-l
8960: 69 6d 69 74 65 64 2c 20 61 6e 64 20 69 74 20 73  imited, and it s
8970: 74 6f 70 73 20 74 68 65 6d 20 61 6c 6c 20 61 74  tops them all at
8980: 20 74 68 65 20 65 6e 64 2e 0a 73 65 74 20 2d 75   the end..set -u
8990: 0a 54 52 45 45 3d 24 31 3b 20 4f 55 54 3d 24 32  .TREE=$1; OUT=$2
89a0: 0a 5b 20 2d 65 20 22 24 4f 55 54 22 20 5d 20 26  .[ -e "$OUT" ] &
89b0: 26 20 7b 20 65 63 68 6f 20 22 24 4f 55 54 20 65  & { echo "$OUT e
89c0: 78 69 73 74 73 22 3b 20 65 78 69 74 20 32 3b 20  xists"; exit 2; 
89d0: 7d 0a 6d 6b 64 69 72 20 2d 70 20 22 24 4f 55 54  }.mkdir -p "$OUT
89e0: 22 3b 20 4f 55 54 3d 24 28 63 64 20 22 24 4f 55  "; OUT=$(cd "$OU
89f0: 54 22 20 26 26 20 70 77 64 29 0a 23 20 61 20 73  T" && pwd).# a s
8a00: 68 6f 72 74 20 70 61 74 68 3a 20 61 20 55 6e 69  hort path: a Uni
8a10: 78 20 73 6f 63 6b 65 74 27 73 20 70 61 74 68 20  x socket's path 
8a20: 6d 61 79 20 62 65 20 61 74 20 6d 6f 73 74 20 31  may be at most 1
8a30: 30 37 20 62 79 74 65 73 0a 44 3d 2f 74 6d 70 2f  07 bytes.D=/tmp/
8a40: 73 41 2d 24 24 3b 20 4c 4f 47 3d 24 4f 55 54 2f  sA-$$; LOG=$OUT/
8a50: 72 65 63 6f 72 64 2e 74 78 74 3b 20 72 6d 20 2d  record.txt; rm -
8a60: 72 66 20 24 44 0a 6d 6b 64 69 72 20 2d 70 20 24  rf $D.mkdir -p $
8a70: 44 2f 66 6f 73 73 69 6c 20 24 44 2f 72 75 6e 20  D/fossil $D/run 
8a80: 24 44 2f 72 65 6c 65 61 73 65 20 24 44 2f 64 61  $D/release $D/da
8a90: 65 6d 6f 6e 2f 6e 6f 64 65 20 24 44 2f 68 6f 6d  emon/node $D/hom
8aa0: 65 2f 2e 63 6f 64 65 78 20 24 44 2f 65 78 74 0a  e/.codex $D/ext.
8ab0: 65 78 65 63 20 3e 20 3e 28 74 65 65 20 2d 61 20  exec > >(tee -a 
8ac0: 24 4c 4f 47 29 20 32 3e 26 31 0a 73 61 79 28 29  $LOG) 2>&1.say()
8ad0: 20 7b 20 65 63 68 6f 3b 20 65 63 68 6f 20 22 3d   { echo; echo "=
8ae0: 3d 20 24 2a 22 3b 20 7d 0a 70 69 64 73 3d 28 29  = $*"; }.pids=()
8af0: 0a 63 6c 65 61 6e 75 70 28 29 20 7b 20 66 6f 72  .cleanup() { for
8b00: 20 70 20 69 6e 20 22 24 7b 70 69 64 73 5b 40 5d   p in "${pids[@]
8b10: 7d 22 3b 20 64 6f 20 6b 69 6c 6c 20 24 70 20 32  }"; do kill $p 2
8b20: 3e 2f 64 65 76 2f 6e 75 6c 6c 3b 20 64 6f 6e 65  >/dev/null; done
8b30: 3b 20 77 61 69 74 20 32 3e 2f 64 65 76 2f 6e 75  ; wait 2>/dev/nu
8b40: 6c 6c 3b 20 7d 0a 74 72 61 70 20 27 63 6c 65 61  ll; }.trap 'clea
8b50: 6e 75 70 3b 20 72 6d 20 2d 72 66 20 24 44 27 20  nup; rm -rf $D' 
8b60: 45 58 49 54 0a 0a 73 61 79 20 22 62 75 69 6c 64  EXIT..say "build
8b70: 3a 20 73 74 61 72 74 2d 6f 63 78 2c 20 72 65 63  : start-ocx, rec
8b80: 65 69 70 74 73 20 61 6e 64 20 6f 63 78 32 20 66  eipts and ocx2 f
8b90: 72 6f 6d 20 24 54 52 45 45 22 0a 42 49 4e 3d 24  rom $TREE".BIN=$
8ba0: 4f 55 54 2f 62 69 6e 3b 20 6d 6b 64 69 72 20 2d  OUT/bin; mkdir -
8bb0: 70 20 24 42 49 4e 0a 66 6f 72 20 74 20 69 6e 20  p $BIN.for t in 
8bc0: 73 74 61 72 74 2d 6f 63 78 20 72 65 63 65 69 70  start-ocx receip
8bd0: 74 73 3b 20 64 6f 20 6e 69 63 65 20 2d 6e 20 31  ts; do nice -n 1
8be0: 39 20 63 72 79 73 74 61 6c 20 62 75 69 6c 64 20  9 crystal build 
8bf0: 2d 2d 72 65 6c 65 61 73 65 20 2d 6f 20 24 42 49  --release -o $BI
8c00: 4e 2f 24 74 20 24 54 52 45 45 2f 73 63 72 69 70  N/$t $TREE/scrip
8c10: 74 73 2f 24 74 2e 63 72 20 7c 7c 20 65 78 69 74  ts/$t.cr || exit
8c20: 20 31 3b 20 64 6f 6e 65 0a 63 70 20 24 54 52 45   1; done.cp $TRE
8c30: 45 2f 62 69 6e 2f 6f 63 78 32 20 24 42 49 4e 2f  E/bin/ocx2 $BIN/
8c40: 6f 63 78 32 20 7c 7c 20 65 78 69 74 20 31 0a 52  ocx2 || exit 1.R
8c50: 45 4c 3d 64 65 6d 6f 30 30 30 30 30 30 30 31 0a  EL=demo00000001.
8c60: 66 6f 72 20 72 20 69 6e 20 24 44 2f 72 65 6c 65  for r in $D/rele
8c70: 61 73 65 20 24 44 2f 64 61 65 6d 6f 6e 3b 20 64  ase $D/daemon; d
8c80: 6f 20 6d 6b 64 69 72 20 2d 70 20 24 72 2f 24 52  o mkdir -p $r/$R
8c90: 45 4c 20 24 72 2f 73 74 61 67 65 64 3b 20 63 70  EL $r/staged; cp
8ca0: 20 24 42 49 4e 2f 6f 63 78 32 20 24 72 2f 24 52   $BIN/ocx2 $r/$R
8cb0: 45 4c 2f 6f 63 78 32 3b 20 6c 6e 20 2d 73 20 24  EL/ocx2; ln -s $
8cc0: 52 45 4c 20 24 72 2f 63 75 72 72 65 6e 74 3b 20  REL $r/current; 
8cd0: 64 6f 6e 65 0a 0a 73 61 79 20 22 73 65 72 76 65  done..say "serve
8ce0: 64 20 72 65 70 6f 73 69 74 6f 72 79 3a 20 64 65  d repository: de
8cf0: 6d 6f 2e 66 6f 73 73 69 6c 2c 20 75 73 65 72 73  mo.fossil, users
8d00: 2c 20 63 68 61 74 20 74 69 6d 65 6c 69 6e 65 22  , chat timeline"
8d10: 0a 46 3d 2f 75 73 72 2f 62 69 6e 2f 66 6f 73 73  .F=/usr/bin/foss
8d20: 69 6c 3b 20 52 3d 24 44 2f 66 6f 73 73 69 6c 2f  il; R=$D/fossil/
8d30: 64 65 6d 6f 2e 66 6f 73 73 69 6c 0a 65 78 70 6f  demo.fossil.expo
8d40: 72 74 20 48 4f 4d 45 3d 24 4f 55 54 2f 61 64 6d  rt HOME=$OUT/adm
8d50: 69 6e 68 6f 6d 65 3b 20 6d 6b 64 69 72 20 2d 70  inhome; mkdir -p
8d60: 20 24 48 4f 4d 45 0a 24 46 20 6e 65 77 20 24 52   $HOME.$F new $R
8d70: 20 2d 2d 61 64 6d 69 6e 2d 75 73 65 72 20 62 6f   --admin-user bo
8d80: 73 73 20 3e 2f 64 65 76 2f 6e 75 6c 6c 0a 24 46  ss >/dev/null.$F
8d90: 20 75 73 65 72 20 70 61 73 73 77 6f 72 64 20 62   user password b
8da0: 6f 73 73 20 62 6f 73 73 2d 70 77 20 2d 52 20 24  oss boss-pw -R $
8db0: 52 0a 66 6f 72 20 75 20 69 6e 20 22 64 65 6d 6f  R.for u in "demo
8dc0: 2d 6e 6f 64 65 20 67 6f 43 34 77 20 6e 6f 64 65  -node goC4w node
8dd0: 2d 70 77 22 20 22 62 75 69 6c 64 65 72 20 67 69  -pw" "builder gi
8de0: 6f 32 34 20 62 75 69 6c 64 65 72 2d 70 77 22 20  o24 builder-pw" 
8df0: 22 76 69 65 77 65 72 20 6f 32 20 76 69 65 77 65  "viewer o2 viewe
8e00: 72 2d 70 77 22 3b 20 64 6f 20 73 65 74 20 2d 2d  r-pw"; do set --
8e10: 20 24 75 0a 20 20 24 46 20 75 73 65 72 20 6e 65   $u.  $F user ne
8e20: 77 20 24 31 20 24 31 40 78 20 24 33 20 2d 52 20  w $1 $1@x $3 -R 
8e30: 24 52 20 3e 2f 64 65 76 2f 6e 75 6c 6c 3b 20 24  $R >/dev/null; $
8e40: 46 20 75 73 65 72 20 63 61 70 61 62 69 6c 69 74  F user capabilit
8e50: 69 65 73 20 24 31 20 24 32 20 2d 52 20 24 52 20  ies $1 $2 -R $R 
8e60: 3e 2f 64 65 76 2f 6e 75 6c 6c 3b 20 64 6f 6e 65  >/dev/null; done
8e70: 0a 24 46 20 73 65 74 74 69 6e 67 73 20 63 68 61  .$F settings cha
8e80: 74 2d 74 69 6d 65 6c 69 6e 65 2d 75 73 65 72 20  t-timeline-user 
8e90: 6f 63 78 2d 74 69 6d 65 6c 69 6e 65 20 2d 52 20  ocx-timeline -R 
8ea0: 24 52 0a 24 46 20 73 65 74 74 69 6e 67 73 20 63  $R.$F settings c
8eb0: 68 61 74 2d 70 6f 6c 6c 2d 74 69 6d 65 6f 75 74  hat-poll-timeout
8ec0: 20 33 20 2d 52 20 24 52 0a 63 68 6d 6f 64 20 36   3 -R $R.chmod 6
8ed0: 30 30 20 24 52 3b 20 6c 73 20 2d 6c 20 24 52 0a  00 $R; ls -l $R.
8ee0: 0a 73 61 79 20 22 72 65 63 65 69 70 74 20 73 65  .say "receipt se
8ef0: 72 76 69 63 65 2c 20 74 68 72 6f 75 67 68 20 74  rvice, through t
8f00: 68 65 20 6c 61 75 6e 63 68 65 72 27 73 20 72 65  he launcher's re
8f10: 63 65 69 70 74 73 20 70 6f 6c 69 63 79 22 0a 74  ceipts policy".t
8f20: 69 6d 65 6f 75 74 20 39 30 30 20 24 42 49 4e 2f  imeout 900 $BIN/
8f30: 73 74 61 72 74 2d 6f 63 78 20 2d 2d 73 63 72 61  start-ocx --scra
8f40: 74 63 68 20 24 44 20 72 65 63 65 69 70 74 73 20  tch $D receipts 
8f50: 32 3e 24 4f 55 54 2f 72 65 63 65 69 70 74 73 2e  2>$OUT/receipts.
8f60: 65 72 72 20 26 20 70 69 64 73 2b 3d 28 24 21 29  err & pids+=($!)
8f70: 0a 73 6c 65 65 70 20 31 3b 20 6c 73 20 2d 6c 20  .sleep 1; ls -l 
8f80: 24 44 2f 72 75 6e 2f 0a 0a 73 61 79 20 22 46 6f  $D/run/..say "Fo
8f90: 73 73 69 6c 20 73 65 72 76 65 72 2c 20 69 74 73  ssil server, its
8fa0: 20 65 78 74 72 6f 6f 74 27 73 20 6f 63 78 32 2d   extroot's ocx2-
8fb0: 63 67 69 20 74 68 65 20 6c 61 75 6e 63 68 65 72  cgi the launcher
8fc0: 22 0a 70 72 69 6e 74 66 20 27 23 21 2f 62 69 6e  ".printf '#!/bin
8fd0: 2f 62 61 73 68 5c 6e 65 78 65 63 20 2d 61 20 6f  /bash\nexec -a o
8fe0: 63 78 32 2d 63 67 69 20 25 73 20 2d 2d 73 63 72  cx2-cgi %s --scr
8ff0: 61 74 63 68 20 25 73 5c 6e 27 20 24 42 49 4e 2f  atch %s\n' $BIN/
9000: 73 74 61 72 74 2d 6f 63 78 20 24 44 20 3e 20 24  start-ocx $D > $
9010: 44 2f 65 78 74 2f 6f 63 78 32 2d 63 67 69 3b 20  D/ext/ocx2-cgi; 
9020: 63 68 6d 6f 64 20 37 35 35 20 24 44 2f 65 78 74  chmod 755 $D/ext
9030: 2f 6f 63 78 32 2d 63 67 69 0a 50 3d 24 28 70 79  /ocx2-cgi.P=$(py
9040: 74 68 6f 6e 33 20 2d 63 20 27 69 6d 70 6f 72 74  thon3 -c 'import
9050: 20 73 6f 63 6b 65 74 3b 73 3d 73 6f 63 6b 65 74   socket;s=socket
9060: 2e 73 6f 63 6b 65 74 28 29 3b 73 2e 62 69 6e 64  .socket();s.bind
9070: 28 28 22 31 32 37 2e 30 2e 30 2e 31 22 2c 30 29  (("127.0.0.1",0)
9080: 29 3b 70 72 69 6e 74 28 73 2e 67 65 74 73 6f 63  );print(s.getsoc
9090: 6b 6e 61 6d 65 28 29 5b 31 5d 29 27 29 0a 55 3d  kname()[1])').U=
90a0: 68 74 74 70 3a 2f 2f 31 32 37 2e 30 2e 30 2e 31  http://127.0.0.1
90b0: 3a 24 50 2f 64 65 6d 6f 0a 74 69 6d 65 6f 75 74  :$P/demo.timeout
90c0: 20 39 30 30 20 24 46 20 73 65 72 76 65 72 20 2d   900 $F server -
90d0: 2d 70 6f 72 74 20 24 50 20 2d 2d 6c 6f 63 61 6c  -port $P --local
90e0: 68 6f 73 74 20 2d 2d 6e 6f 6a 61 69 6c 20 2d 2d  host --nojail --
90f0: 72 65 70 6f 6c 69 73 74 20 24 44 2f 66 6f 73 73  repolist $D/foss
9100: 69 6c 20 2d 2d 65 78 74 72 6f 6f 74 20 24 44 2f  il --extroot $D/
9110: 65 78 74 20 3e 24 4f 55 54 2f 66 6f 73 73 69 6c  ext >$OUT/fossil
9120: 2e 6c 6f 67 20 32 3e 26 31 20 26 20 70 69 64 73  .log 2>&1 & pids
9130: 2b 3d 28 24 21 29 0a 66 6f 72 20 69 20 69 6e 20  +=($!).for i in 
9140: 24 28 73 65 71 20 35 30 29 3b 20 64 6f 20 63 75  $(seq 50); do cu
9150: 72 6c 20 2d 73 20 2d 6f 20 2f 64 65 76 2f 6e 75  rl -s -o /dev/nu
9160: 6c 6c 20 68 74 74 70 3a 2f 2f 31 32 37 2e 30 2e  ll http://127.0.
9170: 30 2e 31 3a 24 50 2f 20 26 26 20 62 72 65 61 6b  0.1:$P/ && break
9180: 3b 20 73 6c 65 65 70 20 30 2e 32 3b 20 64 6f 6e  ; sleep 0.2; don
9190: 65 0a 0a 73 61 79 20 22 74 68 65 20 64 61 65 6d  e..say "the daem
91a0: 6f 6e 27 73 20 6e 6f 64 65 3a 20 6e 6f 64 65 73  on's node: nodes
91b0: 2e 6a 73 6f 6e 20 61 6e 64 20 63 72 65 64 65 6e  .json and creden
91c0: 74 69 61 6c 73 22 0a 6d 6b 64 69 72 20 2d 70 20  tials".mkdir -p 
91d0: 24 44 2f 64 61 65 6d 6f 6e 2f 6e 6f 64 65 2f 63  $D/daemon/node/c
91e0: 72 65 64 65 6e 74 69 61 6c 73 20 24 44 2f 64 61  redentials $D/da
91f0: 65 6d 6f 6e 2f 6e 6f 64 65 2f 63 6c 6f 6e 65 73  emon/node/clones
9200: 3b 20 63 68 6d 6f 64 20 37 30 30 20 24 44 2f 64  ; chmod 700 $D/d
9210: 61 65 6d 6f 6e 2f 6e 6f 64 65 0a 70 72 69 6e 74  aemon/node.print
9220: 66 20 27 7b 22 75 73 65 72 22 3a 22 64 65 6d 6f  f '{"user":"demo
9230: 2d 6e 6f 64 65 22 2c 22 70 61 73 73 77 6f 72 64  -node","password
9240: 22 3a 22 6e 6f 64 65 2d 70 77 22 2c 22 72 75 6e  ":"node-pw","run
9250: 73 22 3a 7b 22 62 75 69 6c 64 65 72 22 3a 22 62  s":{"builder":"b
9260: 75 69 6c 64 65 72 2d 70 77 22 7d 7d 27 20 3e 20  uilder-pw"}}' > 
9270: 24 44 2f 64 61 65 6d 6f 6e 2f 6e 6f 64 65 2f 63  $D/daemon/node/c
9280: 72 65 64 65 6e 74 69 61 6c 73 2f 64 65 6d 6f 2e  redentials/demo.
9290: 6a 73 6f 6e 3b 20 63 68 6d 6f 64 20 36 30 30 20  json; chmod 600 
92a0: 24 44 2f 64 61 65 6d 6f 6e 2f 6e 6f 64 65 2f 63  $D/daemon/node/c
92b0: 72 65 64 65 6e 74 69 61 6c 73 2f 64 65 6d 6f 2e  redentials/demo.
92c0: 6a 73 6f 6e 0a 70 72 69 6e 74 66 20 27 5b 7b 22  json.printf '[{"
92d0: 63 6c 6f 6e 65 22 3a 22 25 73 22 2c 22 73 65 72  clone":"%s","ser
92e0: 76 65 72 22 3a 22 25 73 22 2c 22 63 72 65 64 65  ver":"%s","crede
92f0: 6e 74 69 61 6c 73 22 3a 22 25 73 22 7d 5d 27 20  ntials":"%s"}]' 
9300: 24 44 2f 64 61 65 6d 6f 6e 2f 6e 6f 64 65 2f 63  $D/daemon/node/c
9310: 6c 6f 6e 65 73 2f 64 65 6d 6f 2e 66 6f 73 73 69  lones/demo.fossi
9320: 6c 20 24 55 20 24 44 2f 64 61 65 6d 6f 6e 2f 6e  l $U $D/daemon/n
9330: 6f 64 65 2f 63 72 65 64 65 6e 74 69 61 6c 73 2f  ode/credentials/
9340: 64 65 6d 6f 2e 6a 73 6f 6e 20 3e 20 24 44 2f 64  demo.json > $D/d
9350: 61 65 6d 6f 6e 2f 6e 6f 64 65 2f 6e 6f 64 65 73  aemon/node/nodes
9360: 2e 6a 73 6f 6e 3b 20 63 68 6d 6f 64 20 36 30 30  .json; chmod 600
9370: 20 24 44 2f 64 61 65 6d 6f 6e 2f 6e 6f 64 65 2f   $D/daemon/node/
9380: 6e 6f 64 65 73 2e 6a 73 6f 6e 0a 0a 73 61 79 20  nodes.json..say 
9390: 22 63 6f 6e 66 69 6e 65 64 20 64 61 65 6d 6f 6e  "confined daemon
93a0: 2c 20 74 68 72 6f 75 67 68 20 74 68 65 20 6c 61  , through the la
93b0: 75 6e 63 68 65 72 27 73 20 64 61 65 6d 6f 6e 20  uncher's daemon 
93c0: 70 6f 6c 69 63 79 22 0a 74 69 6d 65 6f 75 74 20  policy".timeout 
93d0: 39 30 30 20 24 42 49 4e 2f 73 74 61 72 74 2d 6f  900 $BIN/start-o
93e0: 63 78 20 2d 2d 73 63 72 61 74 63 68 20 24 44 20  cx --scratch $D 
93f0: 64 61 65 6d 6f 6e 20 3e 20 24 4f 55 54 2f 64 61  daemon > $OUT/da
9400: 65 6d 6f 6e 2e 6c 6f 67 20 32 3e 26 31 20 26 20  emon.log 2>&1 & 
9410: 70 69 64 73 2b 3d 28 24 21 29 0a 66 6f 72 20 69  pids+=($!).for i
9420: 20 69 6e 20 24 28 73 65 71 20 36 30 29 3b 20 64   in $(seq 60); d
9430: 6f 20 5b 20 2d 65 20 24 44 2f 64 61 65 6d 6f 6e  o [ -e $D/daemon
9440: 2f 6e 6f 64 65 2f 63 6c 6f 6e 65 73 2f 64 65 6d  /node/clones/dem
9450: 6f 2e 66 6f 73 73 69 6c 20 5d 20 26 26 20 67 72  o.fossil ] && gr
9460: 65 70 20 2d 71 20 72 6f 75 6e 64 20 24 44 2f 64  ep -q round $D/d
9470: 61 65 6d 6f 6e 2f 6e 6f 64 65 2f 64 61 65 6d 6f  aemon/node/daemo
9480: 6e 2e 6c 6f 67 20 32 3e 2f 64 65 76 2f 6e 75 6c  n.log 2>/dev/nul
9490: 6c 20 26 26 20 62 72 65 61 6b 3b 20 73 6c 65 65  l && break; slee
94a0: 70 20 31 3b 20 64 6f 6e 65 0a 74 61 69 6c 20 2d  p 1; done.tail -
94b0: 33 20 24 44 2f 64 61 65 6d 6f 6e 2f 6e 6f 64 65  3 $D/daemon/node
94c0: 2f 64 61 65 6d 6f 6e 2e 6c 6f 67 20 32 3e 2f 64  /daemon.log 2>/d
94d0: 65 76 2f 6e 75 6c 6c 3b 20 74 61 69 6c 20 2d 35  ev/null; tail -5
94e0: 20 24 4f 55 54 2f 64 61 65 6d 6f 6e 2e 6c 6f 67   $OUT/daemon.log
94f0: 0a 0a 23 20 41 20 70 72 6f 62 65 20 61 20 72 75  ..# A probe a ru
9500: 6e 27 73 20 70 72 6f 63 65 73 73 20 72 75 6e 73  n's process runs
9510: 3a 20 77 68 61 74 20 69 74 20 63 61 6e 20 73 65  : what it can se
9520: 65 20 6f 66 20 74 68 65 20 73 65 72 76 65 64 20  e of the served 
9530: 72 65 70 6f 73 69 74 6f 72 69 65 73 2e 0a 50 52  repositories..PR
9540: 4f 42 45 3d 22 65 63 68 6f 20 75 69 64 3d 5c 24  OBE="echo uid=\$
9550: 28 69 64 20 2d 75 29 3b 20 66 6f 72 20 70 20 69  (id -u); for p i
9560: 6e 20 24 44 2f 66 6f 73 73 69 6c 20 24 52 3b 20  n $D/fossil $R; 
9570: 64 6f 20 5b 20 2d 65 20 5c 24 70 20 5d 20 26 26  do [ -e \$p ] &&
9580: 20 65 63 68 6f 20 56 49 53 49 42 4c 45 20 5c 24   echo VISIBLE \$
9590: 70 20 7c 7c 20 65 63 68 6f 20 61 62 73 65 6e 74  p || echo absent
95a0: 20 5c 24 70 3b 20 64 6f 6e 65 3b 20 68 65 61 64   \$p; done; head
95b0: 20 2d 63 20 31 36 20 24 52 20 3e 2f 64 65 76 2f   -c 16 $R >/dev/
95c0: 6e 75 6c 6c 20 32 3e 26 31 20 26 26 20 65 63 68  null 2>&1 && ech
95d0: 6f 20 52 45 41 44 2d 4f 4b 20 7c 7c 20 65 63 68  o READ-OK || ech
95e0: 6f 20 72 65 61 64 2d 72 65 66 75 73 65 64 3b 20  o read-refused; 
95f0: 6e 3d 30 3b 20 66 6f 72 20 78 20 69 6e 20 2f 70  n=0; for x in /p
9600: 72 6f 63 2f 5b 30 2d 39 5d 2a 2f 72 6f 6f 74 24  roc/[0-9]*/root$
9610: 52 3b 20 64 6f 20 5b 20 2d 72 20 5c 24 78 20 5d  R; do [ -r \$x ]
9620: 20 26 26 20 6e 3d 5c 24 28 28 6e 2b 31 29 29 3b   && n=\$((n+1));
9630: 20 64 6f 6e 65 3b 20 65 63 68 6f 20 70 72 6f 63   done; echo proc
9640: 2d 70 61 74 68 73 2d 72 65 61 64 61 62 6c 65 3d  -paths-readable=
9650: 5c 24 6e 22 0a 70 6f 73 74 5f 72 75 6e 28 29 20  \$n".post_run() 
9660: 7b 20 23 20 24 31 20 74 69 74 6c 65 2c 20 24 32  { # $1 title, $2
9670: 20 72 75 6e 20 4a 53 4f 4e 3a 20 70 6f 73 74 65   run JSON: poste
9680: 64 20 62 79 20 62 6f 73 73 20 74 68 72 6f 75 67  d by boss throug
9690: 68 20 46 6f 73 73 69 6c 27 73 20 6f 77 6e 20 66  h Fossil's own f
96a0: 6f 72 6d 0a 20 20 70 79 74 68 6f 6e 33 20 2d 20  orm.  python3 - 
96b0: 22 24 55 22 20 22 24 31 22 20 22 24 32 22 20 3c  "$U" "$1" "$2" <
96c0: 3c 27 45 27 0a 69 6d 70 6f 72 74 20 68 74 74 70  <'E'.import http
96d0: 2e 63 6f 6f 6b 69 65 6a 61 72 2c 20 72 65 2c 20  .cookiejar, re, 
96e0: 73 79 73 2c 20 75 72 6c 6c 69 62 2e 70 61 72 73  sys, urllib.pars
96f0: 65 2c 20 75 72 6c 6c 69 62 2e 72 65 71 75 65 73  e, urllib.reques
9700: 74 0a 62 61 73 65 2c 20 74 69 74 6c 65 2c 20 72  t.base, title, r
9710: 75 6e 20 3d 20 73 79 73 2e 61 72 67 76 5b 31 3a  un = sys.argv[1:
9720: 34 5d 0a 6a 61 72 20 3d 20 68 74 74 70 2e 63 6f  4].jar = http.co
9730: 6f 6b 69 65 6a 61 72 2e 43 6f 6f 6b 69 65 4a 61  okiejar.CookieJa
9740: 72 28 29 3b 20 6f 70 20 3d 20 75 72 6c 6c 69 62  r(); op = urllib
9750: 2e 72 65 71 75 65 73 74 2e 62 75 69 6c 64 5f 6f  .request.build_o
9760: 70 65 6e 65 72 28 75 72 6c 6c 69 62 2e 72 65 71  pener(urllib.req
9770: 75 65 73 74 2e 48 54 54 50 43 6f 6f 6b 69 65 50  uest.HTTPCookieP
9780: 72 6f 63 65 73 73 6f 72 28 6a 61 72 29 29 0a 6f  rocessor(jar)).o
9790: 70 2e 6f 70 65 6e 28 62 61 73 65 20 2b 20 22 2f  p.open(base + "/
97a0: 6c 6f 67 69 6e 22 2c 20 75 72 6c 6c 69 62 2e 70  login", urllib.p
97b0: 61 72 73 65 2e 75 72 6c 65 6e 63 6f 64 65 28 7b  arse.urlencode({
97c0: 22 75 22 3a 20 22 62 6f 73 73 22 2c 20 22 70 22  "u": "boss", "p"
97d0: 3a 20 22 62 6f 73 73 2d 70 77 22 2c 20 22 69 6e  : "boss-pw", "in
97e0: 22 3a 20 22 31 22 7d 29 2e 65 6e 63 6f 64 65 28  ": "1"}).encode(
97f0: 29 29 0a 63 73 72 66 20 3d 20 72 65 2e 73 65 61  )).csrf = re.sea
9800: 72 63 68 28 72 27 6e 61 6d 65 3d 22 63 73 72 66  rch(r'name="csrf
9810: 22 20 76 61 6c 75 65 3d 22 28 5b 5e 22 5d 2b 29  " value="([^"]+)
9820: 22 27 2c 20 6f 70 2e 6f 70 65 6e 28 62 61 73 65  "', op.open(base
9830: 20 2b 20 22 2f 66 6f 72 75 6d 6e 65 77 22 29 2e   + "/forumnew").
9840: 72 65 61 64 28 29 2e 64 65 63 6f 64 65 28 29 29  read().decode())
9850: 2e 67 72 6f 75 70 28 31 29 0a 72 65 71 20 3d 20  .group(1).req = 
9860: 75 72 6c 6c 69 62 2e 72 65 71 75 65 73 74 2e 52  urllib.request.R
9870: 65 71 75 65 73 74 28 62 61 73 65 20 2b 20 22 2f  equest(base + "/
9880: 66 6f 72 75 6d 65 31 22 2c 20 75 72 6c 6c 69 62  forume1", urllib
9890: 2e 70 61 72 73 65 2e 75 72 6c 65 6e 63 6f 64 65  .parse.urlencode
98a0: 28 7b 22 63 73 72 66 22 3a 20 63 73 72 66 2c 20  ({"csrf": csrf, 
98b0: 22 74 69 74 6c 65 22 3a 20 74 69 74 6c 65 2c 20  "title": title, 
98c0: 22 6d 69 6d 65 74 79 70 65 22 3a 20 22 74 65 78  "mimetype": "tex
98d0: 74 2f 78 2d 6d 61 72 6b 64 6f 77 6e 22 2c 20 22  t/x-markdown", "
98e0: 63 6f 6e 74 65 6e 74 22 3a 20 22 60 60 60 6a 73  content": "```js
98f0: 6f 6e 5c 6e 22 20 2b 20 72 75 6e 20 2b 20 22 5c  on\n" + run + "\
9900: 6e 60 60 60 5c 6e 22 2c 20 22 73 75 62 6d 69 74  n```\n", "submit
9910: 22 3a 20 22 53 75 62 6d 69 74 22 7d 29 2e 65 6e  ": "Submit"}).en
9920: 63 6f 64 65 28 29 29 0a 72 65 71 2e 61 64 64 5f  code()).req.add_
9930: 68 65 61 64 65 72 28 22 52 65 66 65 72 65 72 22  header("Referer"
9940: 2c 20 62 61 73 65 20 2b 20 22 2f 66 6f 72 75 6d  , base + "/forum
9950: 6e 65 77 22 29 0a 70 72 69 6e 74 28 6f 70 2e 6f  new").print(op.o
9960: 70 65 6e 28 72 65 71 29 2e 67 65 74 75 72 6c 28  pen(req).geturl(
9970: 29 2e 72 73 70 6c 69 74 28 22 2f 22 2c 20 31 29  ).rsplit("/", 1)
9980: 5b 2d 31 5d 29 0a 45 0a 7d 0a 4a 50 52 4f 42 45  [-1]).E.}.JPROBE
9990: 3d 24 28 70 79 74 68 6f 6e 33 20 2d 63 20 27 69  =$(python3 -c 'i
99a0: 6d 70 6f 72 74 20 6a 73 6f 6e 2c 73 79 73 3b 20  mport json,sys; 
99b0: 70 72 69 6e 74 28 6a 73 6f 6e 2e 64 75 6d 70 73  print(json.dumps
99c0: 28 73 79 73 2e 61 72 67 76 5b 31 5d 29 29 27 20  (sys.argv[1]))' 
99d0: 22 24 50 52 4f 42 45 22 29 0a 53 41 4e 44 42 4f  "$PROBE").SANDBO
99e0: 58 45 44 3d 27 7b 22 6f 63 78 22 3a 20 31 2c 20  XED='{"ocx": 1, 
99f0: 22 6b 69 6e 64 22 3a 20 22 72 75 6e 22 2c 20 22  "kind": "run", "
9a00: 6e 6f 64 65 22 3a 20 22 64 65 6d 6f 2d 6e 6f 64  node": "demo-nod
9a10: 65 22 2c 20 22 6f 62 6a 65 63 74 69 76 65 22 3a  e", "objective":
9a20: 20 22 50 72 6f 62 65 20 66 72 6f 6d 20 61 6e 20   "Probe from an 
9a30: 61 67 65 6e 74 20 73 61 6e 64 62 6f 78 2e 22 2c  agent sandbox.",
9a40: 20 22 77 6f 72 6b 73 70 61 63 65 22 3a 20 5b 7b   "workspace": [{
9a50: 22 73 74 65 70 22 3a 20 22 64 69 72 65 63 74 6f  "step": "directo
9a60: 72 79 22 2c 20 22 61 74 22 3a 20 22 24 24 72 6f  ry", "at": "$$ro
9a70: 6f 74 24 24 2f 24 24 75 73 65 72 24 24 2f 70 72  ot$$/$$user$$/pr
9a80: 6f 6a 65 63 74 73 2f 24 24 70 72 6f 6a 65 63 74  ojects/$$project
9a90: 24 24 2f 24 24 72 75 6e 24 24 22 7d 5d 2c 20 22  $$/$$run$$"}], "
9aa0: 73 61 6e 64 62 6f 78 22 3a 20 7b 22 75 69 64 22  sandbox": {"uid"
9ab0: 3a 20 31 30 30 30 2c 20 22 67 69 64 22 3a 20 31  : 1000, "gid": 1
9ac0: 30 30 30 2c 20 22 63 61 70 61 62 69 6c 69 74 69  000, "capabiliti
9ad0: 65 73 22 3a 20 5b 5d 2c 20 22 6e 65 74 77 6f 72  es": [], "networ
9ae0: 6b 22 3a 20 66 61 6c 73 65 2c 20 22 6f 77 6e 5f  k": false, "own_
9af0: 73 65 73 73 69 6f 6e 22 3a 20 74 72 75 65 2c 20  session": true, 
9b00: 22 65 6e 64 73 5f 77 69 74 68 5f 6e 6f 64 65 22  "ends_with_node"
9b10: 3a 20 74 72 75 65 2c 20 22 72 65 61 64 5f 6f 6e  : true, "read_on
9b20: 6c 79 22 3a 20 5b 22 2f 75 73 72 22 2c 20 22 2f  ly": ["/usr", "/
9b30: 62 69 6e 22 2c 20 22 2f 6c 69 62 22 2c 20 22 2f  bin", "/lib", "/
9b40: 6c 69 62 36 34 22 5d 2c 20 22 72 65 61 64 5f 77  lib64"], "read_w
9b50: 72 69 74 65 22 3a 20 5b 22 24 24 77 6f 72 6b 73  rite": ["$$works
9b60: 70 61 63 65 24 24 22 5d 2c 20 22 68 6f 6d 65 73  pace$$"], "homes
9b70: 22 3a 20 5b 5d 2c 20 22 66 69 6c 65 73 22 3a 20  ": [], "files": 
9b80: 5b 5d 2c 20 22 70 72 6f 63 22 3a 20 22 2f 70 72  [], "proc": "/pr
9b90: 6f 63 22 2c 20 22 64 65 76 22 3a 20 22 2f 64 65  oc", "dev": "/de
9ba0: 76 22 2c 20 22 74 6d 70 22 3a 20 22 2f 74 6d 70  v", "tmp": "/tmp
9bb0: 22 7d 2c 20 22 6c 69 6d 69 74 73 22 3a 20 7b 22  "}, "limits": {"
9bc0: 77 61 6c 6c 22 3a 20 22 32 6d 22 7d 2c 20 22 70  wall": "2m"}, "p
9bd0: 72 6f 63 65 73 73 22 3a 20 7b 22 75 73 65 72 22  rocess": {"user"
9be0: 3a 20 22 62 75 69 6c 64 65 72 22 2c 20 22 63 6f  : "builder", "co
9bf0: 6d 6d 61 6e 64 22 3a 20 5b 22 2f 62 69 6e 2f 73  mmand": ["/bin/s
9c00: 68 22 2c 20 22 2d 63 22 2c 20 27 22 24 4a 50 52  h", "-c", '"$JPR
9c10: 4f 42 45 22 27 5d 2c 20 22 65 6e 76 22 3a 20 7b  OBE"'], "env": {
9c20: 22 50 41 54 48 22 3a 20 22 2f 75 73 72 2f 62 69  "PATH": "/usr/bi
9c30: 6e 3a 2f 62 69 6e 22 7d 2c 20 22 6b 65 65 70 22  n:/bin"}, "keep"
9c40: 3a 20 7b 22 73 74 64 6f 75 74 5f 77 68 65 6e 22  : {"stdout_when"
9c50: 3a 20 22 61 6c 77 61 79 73 22 7d 7d 7d 27 0a 42  : "always"}}}'.B
9c60: 41 52 45 3d 27 7b 22 6f 63 78 22 3a 20 31 2c 20  ARE='{"ocx": 1, 
9c70: 22 6b 69 6e 64 22 3a 20 22 72 75 6e 22 2c 20 22  "kind": "run", "
9c80: 6e 6f 64 65 22 3a 20 22 64 65 6d 6f 2d 6e 6f 64  node": "demo-nod
9c90: 65 22 2c 20 22 6f 62 6a 65 63 74 69 76 65 22 3a  e", "objective":
9ca0: 20 22 50 72 6f 62 65 20 66 72 6f 6d 20 74 68 65   "Probe from the
9cb0: 20 64 61 65 6d 6f 6e 20 69 74 73 65 6c 66 2c 20   daemon itself, 
9cc0: 75 6e 63 6f 6e 66 69 6e 65 64 20 62 79 20 61 6e  unconfined by an
9cd0: 79 20 72 75 6e 20 73 61 6e 64 62 6f 78 2e 22 2c  y run sandbox.",
9ce0: 20 22 70 72 6f 63 65 73 73 22 3a 20 7b 22 75 73   "process": {"us
9cf0: 65 72 22 3a 20 22 62 75 69 6c 64 65 72 22 2c 20  er": "builder", 
9d00: 22 63 6f 6d 6d 61 6e 64 22 3a 20 5b 22 2f 62 69  "command": ["/bi
9d10: 6e 2f 73 68 22 2c 20 22 2d 63 22 2c 20 27 22 24  n/sh", "-c", '"$
9d20: 4a 50 52 4f 42 45 22 27 5d 2c 20 22 65 6e 76 22  JPROBE"'], "env"
9d30: 3a 20 7b 22 50 41 54 48 22 3a 20 22 2f 75 73 72  : {"PATH": "/usr
9d40: 2f 62 69 6e 3a 2f 62 69 6e 22 7d 2c 20 22 6b 65  /bin:/bin"}, "ke
9d50: 65 70 22 3a 20 7b 22 73 74 64 6f 75 74 5f 77 68  ep": {"stdout_wh
9d60: 65 6e 22 3a 20 22 61 6c 77 61 79 73 22 7d 7d 7d  en": "always"}}}
9d70: 27 0a 0a 73 61 79 20 22 74 77 6f 20 72 75 6e 73  '..say "two runs
9d80: 2c 20 70 6f 73 74 65 64 20 62 79 20 62 6f 73 73  , posted by boss
9d90: 20 28 73 65 74 75 70 29 20 74 68 72 6f 75 67 68   (setup) through
9da0: 20 46 6f 73 73 69 6c 27 73 20 66 6f 72 6d 22 0a   Fossil's form".
9db0: 52 55 4e 31 3d 24 28 70 6f 73 74 5f 72 75 6e 20  RUN1=$(post_run 
9dc0: 22 73 61 6e 64 62 6f 78 65 64 20 70 72 6f 62 65  "sandboxed probe
9dd0: 22 20 22 24 53 41 4e 44 42 4f 58 45 44 22 29 3b  " "$SANDBOXED");
9de0: 20 65 63 68 6f 20 22 73 61 6e 64 62 6f 78 65 64   echo "sandboxed
9df0: 20 72 75 6e 20 24 52 55 4e 31 22 0a 52 55 4e 32   run $RUN1".RUN2
9e00: 3d 24 28 70 6f 73 74 5f 72 75 6e 20 22 75 6e 63  =$(post_run "unc
9e10: 6f 6e 66 69 6e 65 64 20 70 72 6f 62 65 22 20 22  onfined probe" "
9e20: 24 42 41 52 45 22 29 3b 20 65 63 68 6f 20 22 75  $BARE"); echo "u
9e30: 6e 63 6f 6e 66 69 6e 65 64 20 72 75 6e 20 24 52  nconfined run $R
9e40: 55 4e 32 22 0a 66 6f 72 20 69 20 69 6e 20 24 28  UN2".for i in $(
9e50: 73 65 71 20 39 30 29 3b 20 64 6f 20 5b 20 22 24  seq 90); do [ "$
9e60: 28 24 46 20 73 71 6c 20 2d 52 20 24 52 20 22 53  ($F sql -R $R "S
9e70: 45 4c 45 43 54 20 63 6f 75 6e 74 28 2a 29 20 46  ELECT count(*) F
9e80: 52 4f 4d 20 66 6f 72 75 6d 70 6f 73 74 20 66 20  ROM forumpost f 
9e90: 4a 4f 49 4e 20 62 6c 6f 62 20 62 20 4f 4e 20 62  JOIN blob b ON b
9ea0: 2e 72 69 64 3d 66 2e 66 70 69 64 20 57 48 45 52  .rid=f.fpid WHER
9eb0: 45 20 62 2e 63 6f 6e 74 65 6e 74 20 49 53 20 4e  E b.content IS N
9ec0: 4f 54 20 4e 55 4c 4c 22 20 32 3e 2f 64 65 76 2f  OT NULL" 2>/dev/
9ed0: 6e 75 6c 6c 29 22 20 2d 67 65 20 36 20 5d 20 26  null)" -ge 6 ] &
9ee0: 26 20 62 72 65 61 6b 3b 20 73 6c 65 65 70 20 31  & break; sleep 1
9ef0: 3b 20 64 6f 6e 65 0a 73 6c 65 65 70 20 33 0a 73  ; done.sleep 3.s
9f00: 61 79 20 22 77 68 61 74 20 74 68 65 20 72 75 6e  ay "what the run
9f10: 73 20 70 6f 73 74 65 64 20 28 73 74 61 72 74 65  s posted (starte
9f20: 64 2c 20 6b 65 70 74 20 6c 6f 67 73 2c 20 65 6e  d, kept logs, en
9f30: 64 65 64 29 22 0a 24 46 20 73 71 6c 20 2d 52 20  ded)".$F sql -R 
9f40: 24 52 20 22 53 45 4c 45 43 54 20 73 75 62 73 74  $R "SELECT subst
9f50: 72 28 62 2e 75 75 69 64 2c 31 2c 31 30 29 2c 20  r(b.uuid,1,10), 
9f60: 75 2e 6c 6f 67 69 6e 2c 20 73 75 62 73 74 72 28  u.login, substr(
9f70: 72 65 70 6c 61 63 65 28 72 65 70 6c 61 63 65 28  replace(replace(
9f80: 43 41 53 54 28 63 6f 6e 74 65 6e 74 28 62 2e 75  CAST(content(b.u
9f90: 75 69 64 29 20 41 53 20 54 45 58 54 29 2c 63 68  uid) AS TEXT),ch
9fa0: 61 72 28 31 30 29 2c 27 20 27 29 2c 27 20 20 27  ar(10),' '),'  '
9fb0: 2c 27 20 27 29 2c 31 2c 36 30 30 29 20 46 52 4f  ,' '),1,600) FRO
9fc0: 4d 20 66 6f 72 75 6d 70 6f 73 74 20 66 20 4a 4f  M forumpost f JO
9fd0: 49 4e 20 62 6c 6f 62 20 62 20 4f 4e 20 62 2e 72  IN blob b ON b.r
9fe0: 69 64 3d 66 2e 66 70 69 64 20 4c 45 46 54 20 4a  id=f.fpid LEFT J
9ff0: 4f 49 4e 20 72 63 76 66 72 6f 6d 20 72 20 4f 4e  OIN rcvfrom r ON
a000: 20 72 2e 72 63 76 69 64 3d 62 2e 72 63 76 69 64   r.rcvid=b.rcvid
a010: 20 4c 45 46 54 20 4a 4f 49 4e 20 75 73 65 72 20   LEFT JOIN user 
a020: 75 20 4f 4e 20 75 2e 75 69 64 3d 72 2e 75 69 64  u ON u.uid=r.uid
a030: 20 4f 52 44 45 52 20 42 59 20 66 2e 66 6d 74 69   ORDER BY f.fmti
a040: 6d 65 22 20 7c 20 74 61 69 6c 20 2d 6e 20 2b 31  me" | tail -n +1
a050: 0a 0a 73 61 79 20 22 72 65 63 65 69 70 74 73 20  ..say "receipts 
a060: 74 68 72 6f 75 67 68 20 74 68 65 20 6c 61 75 6e  through the laun
a070: 63 68 65 72 27 73 20 43 47 49 20 72 6f 75 74 65  cher's CGI route
a080: 2c 20 61 67 61 69 6e 73 74 20 46 6f 73 73 69 6c  , against Fossil
a090: 27 73 20 6f 77 6e 20 72 65 63 6f 72 64 22 0a 48  's own record".H
a0a0: 31 3d 24 28 24 46 20 73 71 6c 20 2d 52 20 24 52  1=$($F sql -R $R
a0b0: 20 22 53 45 4c 45 43 54 20 62 2e 75 75 69 64 20   "SELECT b.uuid 
a0c0: 46 52 4f 4d 20 62 6c 6f 62 20 62 20 4a 4f 49 4e  FROM blob b JOIN
a0d0: 20 66 6f 72 75 6d 70 6f 73 74 20 66 20 4f 4e 20   forumpost f ON 
a0e0: 66 2e 66 70 69 64 3d 62 2e 72 69 64 20 4f 52 44  f.fpid=b.rid ORD
a0f0: 45 52 20 42 59 20 66 2e 66 6d 74 69 6d 65 20 4c  ER BY f.fmtime L
a100: 49 4d 49 54 20 31 22 20 7c 20 74 72 20 2d 64 20  IMIT 1" | tr -d 
a110: 22 27 22 29 0a 66 6f 72 20 77 68 6f 20 69 6e 20  "'").for who in 
a120: 22 62 6f 73 73 20 62 6f 73 73 2d 70 77 22 20 22  "boss boss-pw" "
a130: 76 69 65 77 65 72 20 76 69 65 77 65 72 2d 70 77  viewer viewer-pw
a140: 22 20 22 61 6e 6f 6e 20 2d 22 3b 20 64 6f 20 73  " "anon -"; do s
a150: 65 74 20 2d 2d 20 24 77 68 6f 0a 20 20 70 79 74  et -- $who.  pyt
a160: 68 6f 6e 33 20 2d 20 22 24 55 22 20 22 24 31 22  hon3 - "$U" "$1"
a170: 20 22 24 32 22 20 22 24 48 31 22 20 3c 3c 27 45   "$2" "$H1" <<'E
a180: 27 0a 69 6d 70 6f 72 74 20 68 74 74 70 2e 63 6f  '.import http.co
a190: 6f 6b 69 65 6a 61 72 2c 20 6a 73 6f 6e 2c 20 73  okiejar, json, s
a1a0: 79 73 2c 20 75 72 6c 6c 69 62 2e 70 61 72 73 65  ys, urllib.parse
a1b0: 2c 20 75 72 6c 6c 69 62 2e 72 65 71 75 65 73 74  , urllib.request
a1c0: 0a 62 61 73 65 2c 20 75 73 65 72 2c 20 70 77 2c  .base, user, pw,
a1d0: 20 68 20 3d 20 73 79 73 2e 61 72 67 76 5b 31 3a   h = sys.argv[1:
a1e0: 35 5d 0a 6a 61 72 20 3d 20 68 74 74 70 2e 63 6f  5].jar = http.co
a1f0: 6f 6b 69 65 6a 61 72 2e 43 6f 6f 6b 69 65 4a 61  okiejar.CookieJa
a200: 72 28 29 3b 20 6f 70 20 3d 20 75 72 6c 6c 69 62  r(); op = urllib
a210: 2e 72 65 71 75 65 73 74 2e 62 75 69 6c 64 5f 6f  .request.build_o
a220: 70 65 6e 65 72 28 75 72 6c 6c 69 62 2e 72 65 71  pener(urllib.req
a230: 75 65 73 74 2e 48 54 54 50 43 6f 6f 6b 69 65 50  uest.HTTPCookieP
a240: 72 6f 63 65 73 73 6f 72 28 6a 61 72 29 29 0a 69  rocessor(jar)).i
a250: 66 20 70 77 20 21 3d 20 22 2d 22 3a 20 6f 70 2e  f pw != "-": op.
a260: 6f 70 65 6e 28 62 61 73 65 20 2b 20 22 2f 6c 6f  open(base + "/lo
a270: 67 69 6e 22 2c 20 75 72 6c 6c 69 62 2e 70 61 72  gin", urllib.par
a280: 73 65 2e 75 72 6c 65 6e 63 6f 64 65 28 7b 22 75  se.urlencode({"u
a290: 22 3a 20 75 73 65 72 2c 20 22 70 22 3a 20 70 77  ": user, "p": pw
a2a0: 2c 20 22 69 6e 22 3a 20 22 31 22 7d 29 2e 65 6e  , "in": "1"}).en
a2b0: 63 6f 64 65 28 29 29 0a 74 72 79 3a 0a 20 20 20  code()).try:.   
a2c0: 20 72 20 3d 20 6f 70 2e 6f 70 65 6e 28 62 61 73   r = op.open(bas
a2d0: 65 20 2b 20 22 2f 65 78 74 2f 6f 63 78 32 2d 63  e + "/ext/ocx2-c
a2e0: 67 69 2f 72 65 63 65 69 70 74 22 2c 20 75 72 6c  gi/receipt", url
a2f0: 6c 69 62 2e 70 61 72 73 65 2e 75 72 6c 65 6e 63  lib.parse.urlenc
a300: 6f 64 65 28 7b 22 6a 73 6f 6e 22 3a 20 22 6f 6e  ode({"json": "on
a310: 22 2c 20 22 61 72 74 69 66 61 63 74 73 22 3a 20  ", "artifacts": 
a320: 68 2c 20 22 63 61 70 61 62 69 6c 69 74 69 65 73  h, "capabilities
a330: 5f 6f 66 22 3a 20 22 64 65 6d 6f 2d 6e 6f 64 65  _of": "demo-node
a340: 22 7d 29 2e 65 6e 63 6f 64 65 28 29 29 0a 20 20  "}).encode()).  
a350: 20 20 61 20 3d 20 6a 73 6f 6e 2e 6c 6f 61 64 28    a = json.load(
a360: 72 29 0a 65 78 63 65 70 74 20 75 72 6c 6c 69 62  r).except urllib
a370: 2e 65 72 72 6f 72 2e 48 54 54 50 45 72 72 6f 72  .error.HTTPError
a380: 20 61 73 20 65 3a 0a 20 20 20 20 61 20 3d 20 7b   as e:.    a = {
a390: 22 68 74 74 70 22 3a 20 65 2e 63 6f 64 65 2c 20  "http": e.code, 
a3a0: 22 62 6f 64 79 22 3a 20 65 2e 72 65 61 64 28 29  "body": e.read()
a3b0: 2e 64 65 63 6f 64 65 28 29 5b 3a 33 30 30 5d 7d  .decode()[:300]}
a3c0: 0a 70 72 69 6e 74 28 75 73 65 72 2c 20 6a 73 6f  .print(user, jso
a3d0: 6e 2e 64 75 6d 70 73 28 61 2e 67 65 74 28 22 72  n.dumps(a.get("r
a3e0: 65 73 75 6c 74 22 2c 20 61 29 29 29 0a 45 0a 64  esult", a))).E.d
a3f0: 6f 6e 65 0a 65 63 68 6f 20 22 46 6f 73 73 69 6c  one.echo "Fossil
a400: 27 73 20 6f 77 6e 20 72 65 63 6f 72 64 3a 20 24  's own record: $
a410: 28 24 46 20 73 71 6c 20 2d 52 20 24 52 20 22 53  ($F sql -R $R "S
a420: 45 4c 45 43 54 20 75 2e 6c 6f 67 69 6e 2c 20 66  ELECT u.login, f
a430: 75 6c 6c 63 61 70 28 75 2e 63 61 70 29 2c 20 62  ullcap(u.cap), b
a440: 2e 72 63 76 69 64 2c 20 62 2e 72 69 64 20 46 52  .rcvid, b.rid FR
a450: 4f 4d 20 62 6c 6f 62 20 62 20 4a 4f 49 4e 20 72  OM blob b JOIN r
a460: 63 76 66 72 6f 6d 20 72 20 55 53 49 4e 47 28 72  cvfrom r USING(r
a470: 63 76 69 64 29 20 4a 4f 49 4e 20 75 73 65 72 20  cvid) JOIN user 
a480: 75 20 55 53 49 4e 47 28 75 69 64 29 20 57 48 45  u USING(uid) WHE
a490: 52 45 20 62 2e 75 75 69 64 3d 27 24 48 31 27 22  RE b.uuid='$H1'"
a4a0: 29 22 0a 65 63 68 6f 20 22 46 6f 73 73 69 6c 27  )".echo "Fossil'
a4b0: 73 20 66 75 6c 6c 63 61 70 28 64 65 6d 6f 2d 6e  s fullcap(demo-n
a4c0: 6f 64 65 29 3a 20 24 28 24 46 20 73 71 6c 20 2d  ode): $($F sql -
a4d0: 52 20 24 52 20 22 53 45 4c 45 43 54 20 66 75 6c  R $R "SELECT ful
a4e0: 6c 63 61 70 28 63 61 70 29 20 46 52 4f 4d 20 75  lcap(cap) FROM u
a4f0: 73 65 72 20 57 48 45 52 45 20 6c 6f 67 69 6e 3d  ser WHERE login=
a500: 27 64 65 6d 6f 2d 6e 6f 64 65 27 22 29 22 0a 0a  'demo-node'")"..
a510: 73 61 79 20 22 74 68 65 20 63 6f 6e 73 6f 6c 65  say "the console
a520: 2c 20 61 73 20 62 6f 73 73 20 28 74 68 65 20 70  , as boss (the p
a530: 61 67 65 27 73 20 6f 74 68 65 72 20 70 61 74 68  age's other path
a540: 73 20 73 74 69 6c 6c 20 70 61 73 73 20 74 68 72  s still pass thr
a550: 6f 75 67 68 3a 20 45 36 29 22 0a 70 79 74 68 6f  ough: E6)".pytho
a560: 6e 33 20 2d 20 22 24 55 22 20 3c 3c 27 45 27 0a  n3 - "$U" <<'E'.
a570: 69 6d 70 6f 72 74 20 68 74 74 70 2e 63 6f 6f 6b  import http.cook
a580: 69 65 6a 61 72 2c 20 73 79 73 2c 20 75 72 6c 6c  iejar, sys, urll
a590: 69 62 2e 70 61 72 73 65 2c 20 75 72 6c 6c 69 62  ib.parse, urllib
a5a0: 2e 72 65 71 75 65 73 74 0a 62 61 73 65 20 3d 20  .request.base = 
a5b0: 73 79 73 2e 61 72 67 76 5b 31 5d 0a 6a 61 72 20  sys.argv[1].jar 
a5c0: 3d 20 68 74 74 70 2e 63 6f 6f 6b 69 65 6a 61 72  = http.cookiejar
a5d0: 2e 43 6f 6f 6b 69 65 4a 61 72 28 29 3b 20 6f 70  .CookieJar(); op
a5e0: 20 3d 20 75 72 6c 6c 69 62 2e 72 65 71 75 65 73   = urllib.reques
a5f0: 74 2e 62 75 69 6c 64 5f 6f 70 65 6e 65 72 28 75  t.build_opener(u
a600: 72 6c 6c 69 62 2e 72 65 71 75 65 73 74 2e 48 54  rllib.request.HT
a610: 54 50 43 6f 6f 6b 69 65 50 72 6f 63 65 73 73 6f  TPCookieProcesso
a620: 72 28 6a 61 72 29 29 0a 6f 70 2e 6f 70 65 6e 28  r(jar)).op.open(
a630: 62 61 73 65 20 2b 20 22 2f 6c 6f 67 69 6e 22 2c  base + "/login",
a640: 20 75 72 6c 6c 69 62 2e 70 61 72 73 65 2e 75 72   urllib.parse.ur
a650: 6c 65 6e 63 6f 64 65 28 7b 22 75 22 3a 20 22 62  lencode({"u": "b
a660: 6f 73 73 22 2c 20 22 70 22 3a 20 22 62 6f 73 73  oss", "p": "boss
a670: 2d 70 77 22 2c 20 22 69 6e 22 3a 20 22 31 22 7d  -pw", "in": "1"}
a680: 29 2e 65 6e 63 6f 64 65 28 29 29 0a 72 20 3d 20  ).encode()).r = 
a690: 6f 70 2e 6f 70 65 6e 28 62 61 73 65 20 2b 20 22  op.open(base + "
a6a0: 2f 65 78 74 2f 6f 63 78 32 2d 63 67 69 2f 63 6f  /ext/ocx2-cgi/co
a6b0: 6e 73 6f 6c 65 3f 6a 73 6f 6e 3d 6f 6e 22 29 3b  nsole?json=on");
a6c0: 20 69 6d 70 6f 72 74 20 6a 73 6f 6e 3b 20 61 20   import json; a 
a6d0: 3d 20 6a 73 6f 6e 2e 6c 6f 61 64 28 72 29 0a 72  = json.load(r).r
a6e0: 65 73 20 3d 20 61 2e 67 65 74 28 22 72 65 73 75  es = a.get("resu
a6f0: 6c 74 22 29 20 6f 72 20 7b 7d 0a 70 72 69 6e 74  lt") or {}.print
a700: 28 22 63 6f 6e 73 6f 6c 65 20 6f 6b 3a 22 2c 20  ("console ok:", 
a710: 61 2e 67 65 74 28 22 73 74 6f 70 70 65 64 22 29  a.get("stopped")
a720: 20 69 73 20 4e 6f 6e 65 2c 20 22 7c 20 72 75 6e   is None, "| run
a730: 73 20 72 75 6e 6e 69 6e 67 3a 22 2c 20 6c 65 6e  s running:", len
a740: 28 72 65 73 2e 67 65 74 28 22 72 75 6e 6e 69 6e  (res.get("runnin
a750: 67 22 2c 20 5b 5d 29 29 2c 20 22 7c 20 65 6e 64  g", [])), "| end
a760: 65 64 3a 22 2c 20 6c 65 6e 28 72 65 73 2e 67 65  ed:", len(res.ge
a770: 74 28 22 65 6e 64 65 64 22 2c 20 5b 5d 29 29 29  t("ended", [])))
a780: 0a 45 0a 0a 73 61 79 20 22 77 72 69 74 65 73 20  .E..say "writes 
a790: 66 72 6f 6d 20 74 68 65 20 72 65 63 65 69 70 74  from the receipt
a7a0: 20 73 65 72 76 69 63 65 27 73 20 70 6f 6c 69 63   service's polic
a7b0: 79 20 66 61 69 6c 20 28 61 20 70 72 6f 62 65 20  y fail (a probe 
a7c0: 69 6e 20 70 6c 61 63 65 20 6f 66 20 72 65 63 65  in place of rece
a7d0: 69 70 74 73 29 22 0a 50 42 3d 24 4f 55 54 2f 70  ipts)".PB=$OUT/p
a7e0: 72 6f 62 65 3b 20 6d 6b 64 69 72 20 2d 70 20 24  robe; mkdir -p $
a7f0: 50 42 3b 20 63 70 20 24 42 49 4e 2f 73 74 61 72  PB; cp $BIN/star
a800: 74 2d 6f 63 78 20 24 50 42 2f 73 74 61 72 74 2d  t-ocx $PB/start-
a810: 6f 63 78 0a 63 61 74 20 3e 20 24 4f 55 54 2f 70  ocx.cat > $OUT/p
a820: 72 6f 62 65 2e 63 72 20 3c 3c 27 45 27 0a 72 65  robe.cr <<'E'.re
a830: 71 75 69 72 65 20 22 73 6f 63 6b 65 74 22 0a 73  quire "socket".s
a840: 6f 63 6b 65 74 2c 20 64 69 72 20 3d 20 41 52 47  ocket, dir = ARG
a850: 56 0a 72 65 70 6f 20 3d 20 44 69 72 2e 63 68 69  V.repo = Dir.chi
a860: 6c 64 72 65 6e 28 64 69 72 29 2e 66 69 6e 64 28  ldren(dir).find(
a870: 26 2e 65 6e 64 73 5f 77 69 74 68 3f 28 22 2e 66  &.ends_with?(".f
a880: 6f 73 73 69 6c 22 29 29 2e 6e 6f 74 5f 6e 69 6c  ossil")).not_nil
a890: 21 0a 70 61 74 68 20 3d 20 46 69 6c 65 2e 6a 6f  !.path = File.jo
a8a0: 69 6e 28 64 69 72 2c 20 72 65 70 6f 29 0a 74 72  in(dir, repo).tr
a8b0: 69 65 73 20 3d 20 7b 0a 20 20 22 6f 70 65 6e 20  ies = {.  "open 
a8c0: 74 68 65 20 72 65 70 6f 73 69 74 6f 72 79 20 66  the repository f
a8d0: 6f 72 20 77 72 69 74 69 6e 67 22 20 3d 3e 20 2d  or writing" => -
a8e0: 3e 20 7b 20 46 69 6c 65 2e 6f 70 65 6e 28 70 61  > { File.open(pa
a8f0: 74 68 2c 20 22 72 2b 22 29 20 7b 20 7d 3b 20 22  th, "r+") { }; "
a900: 4f 50 45 4e 45 44 20 28 62 61 64 29 22 20 7d 2c  OPENED (bad)" },
a910: 0a 20 20 22 63 72 65 61 74 65 20 61 20 66 69 6c  .  "create a fil
a920: 65 20 62 65 73 69 64 65 20 69 74 22 20 20 20 20  e beside it"    
a930: 20 20 20 20 20 3d 3e 20 2d 3e 20 7b 20 46 69 6c       => -> { Fil
a940: 65 2e 77 72 69 74 65 28 46 69 6c 65 2e 6a 6f 69  e.write(File.joi
a950: 6e 28 64 69 72 2c 20 22 78 22 29 2c 20 22 78 22  n(dir, "x"), "x"
a960: 29 3b 20 22 43 52 45 41 54 45 44 20 28 62 61 64  ); "CREATED (bad
a970: 29 22 20 7d 2c 0a 20 20 22 72 65 61 64 20 74 68  )" },.  "read th
a980: 65 20 72 65 70 6f 73 69 74 6f 72 79 22 20 20 20  e repository"   
a990: 20 20 20 20 20 20 20 20 20 20 3d 3e 20 2d 3e 20            => -> 
a9a0: 7b 20 46 69 6c 65 2e 6f 70 65 6e 28 70 61 74 68  { File.open(path
a9b0: 29 20 7b 20 7c 66 7c 20 66 2e 72 65 61 64 5f 62  ) { |f| f.read_b
a9c0: 79 74 65 20 7d 3b 20 22 72 65 61 64 20 6f 6b 20  yte }; "read ok 
a9d0: 28 61 6c 6c 6f 77 65 64 3a 20 72 65 61 64 2d 6f  (allowed: read-o
a9e0: 6e 6c 79 29 22 20 7d 2c 0a 20 20 22 72 65 61 63  nly)" },.  "reac
a9f0: 68 20 46 6f 73 73 69 6c 27 73 20 70 6f 72 74 22  h Fossil's port"
aa00: 20 20 20 20 20 20 20 20 20 20 20 20 20 3d 3e 20               => 
aa10: 2d 3e 20 7b 20 54 43 50 53 6f 63 6b 65 74 2e 6e  -> { TCPSocket.n
aa20: 65 77 28 22 31 32 37 2e 30 2e 30 2e 31 22 2c 20  ew("127.0.0.1", 
aa30: 46 69 6c 65 2e 72 65 61 64 28 46 69 6c 65 2e 6a  File.read(File.j
aa40: 6f 69 6e 28 46 69 6c 65 2e 64 69 72 6e 61 6d 65  oin(File.dirname
aa50: 28 73 6f 63 6b 65 74 29 2c 20 22 70 6f 72 74 22  (socket), "port"
aa60: 29 29 2e 73 74 72 69 70 2e 74 6f 5f 69 2c 20 63  )).strip.to_i, c
aa70: 6f 6e 6e 65 63 74 5f 74 69 6d 65 6f 75 74 3a 20  onnect_timeout: 
aa80: 32 2e 73 65 63 6f 6e 64 73 29 2e 63 6c 6f 73 65  2.seconds).close
aa90: 3b 20 22 43 4f 4e 4e 45 43 54 45 44 20 28 62 61  ; "CONNECTED (ba
aaa0: 64 29 22 20 7d 2c 0a 20 20 22 77 72 69 74 65 20  d)" },.  "write 
aab0: 69 6e 20 69 74 73 20 73 6f 63 6b 65 74 20 64 69  in its socket di
aac0: 72 65 63 74 6f 72 79 22 20 20 20 3d 3e 20 2d 3e  rectory"   => ->
aad0: 20 7b 20 46 69 6c 65 2e 77 72 69 74 65 28 46 69   { File.write(Fi
aae0: 6c 65 2e 6a 6f 69 6e 28 46 69 6c 65 2e 64 69 72  le.join(File.dir
aaf0: 6e 61 6d 65 28 73 6f 63 6b 65 74 29 2c 20 22 70  name(socket), "p
ab00: 72 6f 62 65 22 29 2c 20 22 78 22 29 3b 20 22 77  robe"), "x"); "w
ab10: 72 69 74 74 65 6e 20 28 61 6c 6c 6f 77 65 64 3a  ritten (allowed:
ab20: 20 69 74 73 20 6f 6e 65 20 77 72 69 74 61 62 6c   its one writabl
ab30: 65 20 70 6c 61 63 65 29 22 20 7d 2c 0a 7d 0a 74  e place)" },.}.t
ab40: 72 69 65 73 2e 65 61 63 68 20 64 6f 20 7c 77 68  ries.each do |wh
ab50: 61 74 2c 20 74 72 79 7c 0a 20 20 6f 75 74 63 6f  at, try|.  outco
ab60: 6d 65 20 3d 20 62 65 67 69 6e 0a 20 20 20 20 74  me = begin.    t
ab70: 72 79 2e 63 61 6c 6c 0a 20 20 72 65 73 63 75 65  ry.call.  rescue
ab80: 20 65 0a 20 20 20 20 22 72 65 66 75 73 65 64 20   e.    "refused 
ab90: 28 23 7b 65 2e 63 6c 61 73 73 7d 3a 20 23 7b 65  (#{e.class}: #{e
aba0: 2e 6d 65 73 73 61 67 65 2e 74 72 79 20 26 2e 6c  .message.try &.l
abb0: 69 6e 65 73 2e 66 69 72 73 74 7d 29 22 0a 20 20  ines.first})".  
abc0: 65 6e 64 0a 20 20 70 75 74 73 20 22 23 7b 77 68  end.  puts "#{wh
abd0: 61 74 7d 3a 20 23 7b 6f 75 74 63 6f 6d 65 7d 22  at}: #{outcome}"
abe0: 0a 65 6e 64 0a 45 0a 65 63 68 6f 20 24 50 20 3e  .end.E.echo $P >
abf0: 20 24 44 2f 72 75 6e 2f 70 6f 72 74 3b 20 63 75   $D/run/port; cu
ac00: 72 6c 20 2d 73 20 2d 6f 20 2f 64 65 76 2f 6e 75  rl -s -o /dev/nu
ac10: 6c 6c 20 2d 77 20 22 46 6f 73 73 69 6c 27 73 20  ll -w "Fossil's 
ac20: 70 6f 72 74 20 24 50 20 61 6e 73 77 65 72 73 20  port $P answers 
ac30: 6f 6e 20 74 68 65 20 68 6f 73 74 3a 20 25 7b 68  on the host: %{h
ac40: 74 74 70 5f 63 6f 64 65 7d 5c 6e 22 20 68 74 74  ttp_code}\n" htt
ac50: 70 3a 2f 2f 31 32 37 2e 30 2e 30 2e 31 3a 24 50  p://127.0.0.1:$P
ac60: 2f 3b 20 6e 69 63 65 20 2d 6e 20 31 39 20 63 72  /; nice -n 19 cr
ac70: 79 73 74 61 6c 20 62 75 69 6c 64 20 2d 6f 20 24  ystal build -o $
ac80: 50 42 2f 72 65 63 65 69 70 74 73 20 24 4f 55 54  PB/receipts $OUT
ac90: 2f 70 72 6f 62 65 2e 63 72 20 3e 2f 64 65 76 2f  /probe.cr >/dev/
aca0: 6e 75 6c 6c 20 26 26 20 74 69 6d 65 6f 75 74 20  null && timeout 
acb0: 36 30 20 24 50 42 2f 73 74 61 72 74 2d 6f 63 78  60 $PB/start-ocx
acc0: 20 2d 2d 73 63 72 61 74 63 68 20 24 44 20 72 65   --scratch $D re
acd0: 63 65 69 70 74 73 0a 0a 73 61 79 20 22 74 68 65  ceipts..say "the
ace0: 20 64 61 65 6d 6f 6e 27 73 20 6f 77 6e 20 76 69   daemon's own vi
acf0: 65 77 3a 20 61 20 70 72 6f 62 65 20 69 6e 20 70  ew: a probe in p
ad00: 6c 61 63 65 20 6f 66 20 69 74 73 20 70 72 6f 67  lace of its prog
ad10: 72 61 6d 2c 20 73 74 61 72 74 65 64 20 74 68 72  ram, started thr
ad20: 6f 75 67 68 20 74 68 65 20 73 61 6d 65 20 64 61  ough the same da
ad30: 65 6d 6f 6e 20 70 6f 6c 69 63 79 22 0a 6b 69 6c  emon policy".kil
ad40: 6c 20 24 7b 70 69 64 73 5b 32 5d 7d 20 32 3e 2f  l ${pids[2]} 2>/
ad50: 64 65 76 2f 6e 75 6c 6c 3b 20 73 6c 65 65 70 20  dev/null; sleep 
ad60: 31 0a 6d 6b 64 69 72 20 2d 70 20 24 44 2f 64 61  1.mkdir -p $D/da
ad70: 65 6d 6f 6e 2f 70 72 6f 62 65 30 30 30 30 30 30  emon/probe000000
ad80: 30 31 0a 70 72 69 6e 74 66 20 27 23 21 2f 62 69  01.printf '#!/bi
ad90: 6e 2f 73 68 5c 6e 65 63 68 6f 20 22 64 61 65 6d  n/sh\necho "daem
ada0: 6f 6e 20 70 72 6f 62 65 3a 20 75 69 64 3d 24 28  on probe: uid=$(
adb0: 69 64 20 2d 75 29 20 63 61 70 73 3d 24 28 67 72  id -u) caps=$(gr
adc0: 65 70 20 43 61 70 45 66 66 20 2f 70 72 6f 63 2f  ep CapEff /proc/
add0: 73 65 6c 66 2f 73 74 61 74 75 73 20 7c 20 74 72  self/status | tr
ade0: 20 2d 73 20 22 5c 74 20 22 20 22 20 22 29 22 5c   -s "\t " " ")"\
adf0: 6e 66 6f 72 20 70 20 69 6e 20 25 73 20 25 73 3b  nfor p in %s %s;
ae00: 20 64 6f 20 5b 20 2d 65 20 24 70 20 5d 20 26 26   do [ -e $p ] &&
ae10: 20 65 63 68 6f 20 22 56 49 53 49 42 4c 45 20 24   echo "VISIBLE $
ae20: 70 22 20 7c 7c 20 65 63 68 6f 20 22 61 62 73 65  p" || echo "abse
ae30: 6e 74 20 24 70 22 3b 20 64 6f 6e 65 5c 6e 6e 3d  nt $p"; done\nn=
ae40: 30 3b 20 66 6f 72 20 78 20 69 6e 20 2f 70 72 6f  0; for x in /pro
ae50: 63 2f 5b 30 2d 39 5d 2a 2f 72 6f 6f 74 25 73 3b  c/[0-9]*/root%s;
ae60: 20 64 6f 20 5b 20 2d 72 20 24 78 20 5d 20 26 26   do [ -r $x ] &&
ae70: 20 6e 3d 24 28 28 6e 2b 31 29 29 3b 20 64 6f 6e   n=$((n+1)); don
ae80: 65 3b 20 65 63 68 6f 20 22 70 72 6f 63 2d 70 61  e; echo "proc-pa
ae90: 74 68 73 2d 72 65 61 64 61 62 6c 65 3d 24 6e 20  ths-readable=$n 
aea0: 28 6f 74 68 65 72 20 70 72 6f 63 65 73 73 65 73  (other processes
aeb0: 20 6f 66 20 74 68 65 20 73 61 6d 65 20 61 63 63   of the same acc
aec0: 6f 75 6e 74 3b 20 74 68 65 20 6c 69 76 65 20 64  ount; the live d
aed0: 61 65 6d 6f 6e 20 68 61 73 20 61 6e 20 61 63 63  aemon has an acc
aee0: 6f 75 6e 74 20 6f 66 20 69 74 73 20 6f 77 6e 29  ount of its own)
aef0: 22 5c 6e 62 77 72 61 70 20 2d 2d 75 6e 73 68 61  "\nbwrap --unsha
af00: 72 65 2d 61 6c 6c 20 2d 2d 72 6f 2d 62 69 6e 64  re-all --ro-bind
af10: 20 2f 75 73 72 20 2f 75 73 72 20 2d 2d 72 6f 2d   /usr /usr --ro-
af20: 62 69 6e 64 2d 74 72 79 20 2f 6c 69 62 20 2f 6c  bind-try /lib /l
af30: 69 62 20 2d 2d 72 6f 2d 62 69 6e 64 2d 74 72 79  ib --ro-bind-try
af40: 20 2f 6c 69 62 36 34 20 2f 6c 69 62 36 34 20 2d   /lib64 /lib64 -
af50: 2d 72 6f 2d 62 69 6e 64 20 2f 62 69 6e 20 2f 62  -ro-bind /bin /b
af60: 69 6e 20 2d 2d 70 72 6f 63 20 2f 70 72 6f 63 20  in --proc /proc 
af70: 2d 2d 64 65 76 20 2f 64 65 76 20 2f 62 69 6e 2f  --dev /dev /bin/
af80: 73 68 20 2d 63 20 22 65 63 68 6f 20 61 67 65 6e  sh -c "echo agen
af90: 74 20 73 61 6e 64 62 6f 78 20 69 6e 73 69 64 65  t sandbox inside
afa0: 3a 20 73 74 61 72 74 65 64 22 20 32 3e 26 31 5c  : started" 2>&1\
afb0: 6e 27 20 24 44 2f 66 6f 73 73 69 6c 20 24 52 20  n' $D/fossil $R 
afc0: 24 52 20 3e 20 24 44 2f 64 61 65 6d 6f 6e 2f 70  $R > $D/daemon/p
afd0: 72 6f 62 65 30 30 30 30 30 30 30 31 2f 6f 63 78  robe00000001/ocx
afe0: 32 3b 20 63 68 6d 6f 64 20 37 35 35 20 24 44 2f  2; chmod 755 $D/
aff0: 64 61 65 6d 6f 6e 2f 70 72 6f 62 65 30 30 30 30  daemon/probe0000
b000: 30 30 30 31 2f 6f 63 78 32 0a 6c 6e 20 2d 73 66  0001/ocx2.ln -sf
b010: 6e 20 70 72 6f 62 65 30 30 30 30 30 30 30 31 20  n probe00000001 
b020: 24 44 2f 64 61 65 6d 6f 6e 2f 63 75 72 72 65 6e  $D/daemon/curren
b030: 74 0a 74 69 6d 65 6f 75 74 20 33 30 20 24 42 49  t.timeout 30 $BI
b040: 4e 2f 73 74 61 72 74 2d 6f 63 78 20 2d 2d 73 63  N/start-ocx --sc
b050: 72 61 74 63 68 20 24 44 20 64 61 65 6d 6f 6e 0a  ratch $D daemon.
b060: 6c 6e 20 2d 73 66 6e 20 24 52 45 4c 20 24 44 2f  ln -sfn $REL $D/
b070: 64 61 65 6d 6f 6e 2f 63 75 72 72 65 6e 74 0a 0a  daemon/current..
b080: 73 61 79 20 22 72 65 76 69 65 77 20 74 65 73 74  say "review test
b090: 3a 20 74 68 65 20 2f 72 65 63 65 69 70 74 20 61  : the /receipt a
b0a0: 6c 69 61 73 65 73 20 72 65 61 63 68 20 74 68 65  liases reach the
b0b0: 20 72 65 63 65 69 70 74 20 73 65 72 76 69 63 65   receipt service
b0c0: 2c 20 6e 65 76 65 72 20 74 68 65 20 70 61 73 73  , never the pass
b0d0: 74 68 72 6f 75 67 68 22 0a 66 6f 72 20 70 61 74  through".for pat
b0e0: 68 20 69 6e 20 72 65 63 65 69 70 74 20 72 65 63  h in receipt rec
b0f0: 65 69 70 74 2f 20 72 65 63 65 69 70 74 2f 78 3b  eipt/ receipt/x;
b100: 20 64 6f 0a 20 20 63 75 72 6c 20 2d 73 20 2d 64   do.  curl -s -d
b110: 20 22 6a 73 6f 6e 3d 6f 6e 26 61 72 74 69 66 61   "json=on&artifa
b120: 63 74 73 3d 24 48 31 22 20 22 24 55 2f 65 78 74  cts=$H1" "$U/ext
b130: 2f 6f 63 78 32 2d 63 67 69 2f 24 70 61 74 68 22  /ocx2-cgi/$path"
b140: 20 7c 20 70 79 74 68 6f 6e 33 20 2d 63 20 27 69   | python3 -c 'i
b150: 6d 70 6f 72 74 20 6a 73 6f 6e 2c 73 79 73 3b 20  mport json,sys; 
b160: 61 3d 6a 73 6f 6e 2e 6c 6f 61 64 28 73 79 73 2e  a=json.load(sys.
b170: 73 74 64 69 6e 29 3b 20 6f 70 73 3d 5b 6e 65 78  stdin); ops=[nex
b180: 74 28 69 74 65 72 28 6c 29 29 20 69 66 20 69 73  t(iter(l)) if is
b190: 69 6e 73 74 61 6e 63 65 28 6c 2c 64 69 63 74 29  instance(l,dict)
b1a0: 20 61 6e 64 20 6c 65 6e 28 6c 29 3d 3d 31 20 65   and len(l)==1 e
b1b0: 6c 73 65 20 6c 2e 67 65 74 28 22 6f 70 65 72 61  lse l.get("opera
b1c0: 74 69 6f 6e 22 2c 20 6c 29 20 66 6f 72 20 6c 20  tion", l) for l 
b1d0: 69 6e 20 61 2e 67 65 74 28 22 6c 6f 67 22 2c 20  in a.get("log", 
b1e0: 5b 5d 29 5d 3b 20 70 72 69 6e 74 28 73 79 73 2e  [])]; print(sys.
b1f0: 61 72 67 76 5b 31 5d 2c 20 22 2d 3e 22 2c 20 22  argv[1], "->", "
b200: 73 65 72 76 69 63 65 22 20 69 66 20 22 61 73 6b  service" if "ask
b210: 5f 72 65 63 65 69 70 74 5f 73 65 72 76 69 63 65  _receipt_service
b220: 22 20 69 6e 20 6a 73 6f 6e 2e 64 75 6d 70 73 28  " in json.dumps(
b230: 61 29 20 65 6c 73 65 20 22 50 41 53 53 54 48 52  a) else "PASSTHR
b240: 4f 55 47 48 22 2c 20 28 61 2e 67 65 74 28 22 72  OUGH", (a.get("r
b250: 65 73 75 6c 74 22 29 20 6f 72 20 7b 7d 29 2e 67  esult") or {}).g
b260: 65 74 28 22 61 72 74 69 66 61 63 74 73 22 2c 20  et("artifacts", 
b270: 5b 7b 7d 5d 29 5b 30 5d 2e 67 65 74 28 22 73 65  [{}])[0].get("se
b280: 6e 64 65 72 22 29 29 27 20 22 2f 24 70 61 74 68  nder"))' "/$path
b290: 22 0a 64 6f 6e 65 0a 0a 73 61 79 20 22 72 65 76  ".done..say "rev
b2a0: 69 65 77 20 74 65 73 74 3a 20 6f 6e 65 20 72 65  iew test: one re
b2b0: 70 6f 73 69 74 6f 72 79 20 68 65 6c 64 20 6c 6f  pository held lo
b2c0: 63 6b 65 64 20 77 68 69 6c 65 20 61 6e 6f 74 68  cked while anoth
b2d0: 65 72 20 69 73 20 61 73 6b 65 64 2c 20 73 74 72  er is asked, str
b2e0: 61 69 67 68 74 20 61 74 20 74 68 65 20 73 65 72  aight at the ser
b2f0: 76 69 63 65 27 73 20 73 6f 63 6b 65 74 22 0a 63  vice's socket".c
b300: 70 20 24 52 20 24 44 2f 66 6f 73 73 69 6c 2f 64  p $R $D/fossil/d
b310: 65 6d 6f 32 2e 66 6f 73 73 69 6c 0a 70 79 74 68  emo2.fossil.pyth
b320: 6f 6e 33 20 2d 20 24 44 2f 66 6f 73 73 69 6c 2f  on3 - $D/fossil/
b330: 64 65 6d 6f 2e 66 6f 73 73 69 6c 20 24 44 2f 72  demo.fossil $D/r
b340: 75 6e 2f 72 65 63 65 69 70 74 73 2e 73 6f 63 6b  un/receipts.sock
b350: 20 24 48 31 20 3c 3c 27 50 59 4c 27 0a 69 6d 70   $H1 <<'PYL'.imp
b360: 6f 72 74 20 6a 73 6f 6e 2c 20 73 6f 63 6b 65 74  ort json, socket
b370: 2c 20 73 71 6c 69 74 65 33 2c 20 73 79 73 2c 20  , sqlite3, sys, 
b380: 74 68 72 65 61 64 69 6e 67 2c 20 74 69 6d 65 0a  threading, time.
b390: 6c 6f 63 6b 65 64 2c 20 73 6f 63 6b 2c 20 68 20  locked, sock, h 
b3a0: 3d 20 73 79 73 2e 61 72 67 76 5b 31 3a 34 5d 0a  = sys.argv[1:4].
b3b0: 63 6f 6e 20 3d 20 73 71 6c 69 74 65 33 2e 63 6f  con = sqlite3.co
b3c0: 6e 6e 65 63 74 28 6c 6f 63 6b 65 64 2c 20 69 73  nnect(locked, is
b3d0: 6f 6c 61 74 69 6f 6e 5f 6c 65 76 65 6c 3d 4e 6f  olation_level=No
b3e0: 6e 65 29 3b 20 63 6f 6e 2e 65 78 65 63 75 74 65  ne); con.execute
b3f0: 28 22 42 45 47 49 4e 20 45 58 43 4c 55 53 49 56  ("BEGIN EXCLUSIV
b400: 45 22 29 0a 64 65 66 20 61 73 6b 28 72 65 70 6f  E").def ask(repo
b410: 2c 20 6f 75 74 29 3a 0a 20 20 20 20 74 20 3d 20  , out):.    t = 
b420: 74 69 6d 65 2e 74 69 6d 65 28 29 3b 20 73 20 3d  time.time(); s =
b430: 20 73 6f 63 6b 65 74 2e 73 6f 63 6b 65 74 28 73   socket.socket(s
b440: 6f 63 6b 65 74 2e 41 46 5f 55 4e 49 58 29 3b 20  ocket.AF_UNIX); 
b450: 73 2e 63 6f 6e 6e 65 63 74 28 73 6f 63 6b 29 0a  s.connect(sock).
b460: 20 20 20 20 73 2e 73 65 6e 64 61 6c 6c 28 28 6a      s.sendall((j
b470: 73 6f 6e 2e 64 75 6d 70 73 28 7b 22 63 61 70 61  son.dumps({"capa
b480: 62 69 6c 69 74 69 65 73 22 3a 20 22 6f 22 2c 20  bilities": "o", 
b490: 22 72 65 70 6f 73 69 74 6f 72 79 22 3a 20 72 65  "repository": re
b4a0: 70 6f 7d 29 20 2b 20 22 5c 6e 22 20 2b 20 6a 73  po}) + "\n" + js
b4b0: 6f 6e 2e 64 75 6d 70 73 28 7b 22 6f 63 78 5f 72  on.dumps({"ocx_r
b4c0: 65 63 65 69 70 74 22 3a 20 31 2c 20 22 61 72 74  eceipt": 1, "art
b4d0: 69 66 61 63 74 73 22 3a 20 5b 68 5d 7d 29 20 2b  ifacts": [h]}) +
b4e0: 20 22 5c 6e 22 29 2e 65 6e 63 6f 64 65 28 29 29   "\n").encode())
b4f0: 0a 20 20 20 20 64 61 74 61 20 3d 20 73 2e 6d 61  .    data = s.ma
b500: 6b 65 66 69 6c 65 28 29 2e 72 65 61 64 6c 69 6e  kefile().readlin
b510: 65 28 29 3b 20 6f 75 74 5b 72 65 70 6f 5d 20 3d  e(); out[repo] =
b520: 20 28 72 6f 75 6e 64 28 74 69 6d 65 2e 74 69 6d   (round(time.tim
b530: 65 28 29 20 2d 20 74 2c 20 32 29 2c 20 64 61 74  e() - t, 2), dat
b540: 61 2e 73 74 72 69 70 28 29 5b 3a 39 30 5d 29 0a  a.strip()[:90]).
b550: 6f 75 74 20 3d 20 7b 7d 0a 74 73 20 3d 20 5b 74  out = {}.ts = [t
b560: 68 72 65 61 64 69 6e 67 2e 54 68 72 65 61 64 28  hreading.Thread(
b570: 74 61 72 67 65 74 3d 61 73 6b 2c 20 61 72 67 73  target=ask, args
b580: 3d 28 72 2c 20 6f 75 74 29 29 20 66 6f 72 20 72  =(r, out)) for r
b590: 20 69 6e 20 28 22 64 65 6d 6f 22 2c 20 22 64 65   in ("demo", "de
b5a0: 6d 6f 32 22 29 5d 0a 5b 74 2e 73 74 61 72 74 28  mo2")].[t.start(
b5b0: 29 20 66 6f 72 20 74 20 69 6e 20 74 73 5d 3b 20  ) for t in ts]; 
b5c0: 5b 74 2e 6a 6f 69 6e 28 29 20 66 6f 72 20 74 20  [t.join() for t 
b5d0: 69 6e 20 74 73 5d 0a 63 6f 6e 2e 72 6f 6c 6c 62  in ts].con.rollb
b5e0: 61 63 6b 28 29 3b 20 63 6f 6e 2e 63 6c 6f 73 65  ack(); con.close
b5f0: 28 29 0a 66 6f 72 20 72 20 69 6e 20 28 22 64 65  ().for r in ("de
b600: 6d 6f 22 2c 20 22 64 65 6d 6f 32 22 29 3a 20 70  mo", "demo2"): p
b610: 72 69 6e 74 28 66 22 7b 72 7d 20 28 6c 6f 63 6b  rint(f"{r} (lock
b620: 65 64 3d 7b 72 3d 3d 27 64 65 6d 6f 27 7d 29 3a  ed={r=='demo'}):
b630: 20 7b 6f 75 74 5b 72 5d 5b 30 5d 7d 20 73 20 20   {out[r][0]} s  
b640: 7b 6f 75 74 5b 72 5d 5b 31 5d 7d 22 29 0a 50 59  {out[r][1]}").PY
b650: 4c 0a 0a 73 61 79 20 22 72 65 76 69 65 77 20 74  L..say "review t
b660: 65 73 74 3a 20 69 6e 73 69 64 65 20 74 68 65 20  est: inside the 
b670: 43 47 49 20 73 61 6e 64 62 6f 78 20 28 61 20 70  CGI sandbox (a p
b680: 72 6f 62 65 20 69 6e 20 70 6c 61 63 65 20 6f 66  robe in place of
b690: 20 74 68 65 20 72 65 6c 65 61 73 65 27 73 20 70   the release's p
b6a0: 72 6f 67 72 61 6d 29 22 0a 6d 6b 64 69 72 20 2d  rogram)".mkdir -
b6b0: 70 20 24 44 2f 72 65 6c 65 61 73 65 2f 70 72 6f  p $D/release/pro
b6c0: 62 65 30 30 30 30 30 30 30 31 0a 70 72 69 6e 74  be00000001.print
b6d0: 66 20 27 23 21 2f 62 69 6e 2f 73 68 5c 6e 50 41  f '#!/bin/sh\nPA
b6e0: 54 48 3d 2f 75 73 72 2f 62 69 6e 3a 2f 62 69 6e  TH=/usr/bin:/bin
b6f0: 5c 6e 65 63 68 6f 20 22 61 72 67 73 3a 20 24 2a  \necho "args: $*
b700: 22 3b 20 77 68 69 6c 65 20 72 65 61 64 20 6b 20  "; while read k 
b710: 76 3b 20 64 6f 20 5b 20 22 24 6b 22 20 3d 20 43  v; do [ "$k" = C
b720: 61 70 45 66 66 3a 20 5d 20 26 26 20 65 63 68 6f  apEff: ] && echo
b730: 20 22 43 61 70 45 66 66 3a 20 24 76 22 3b 20 64   "CapEff: $v"; d
b740: 6f 6e 65 20 3c 20 2f 70 72 6f 63 2f 73 65 6c 66  one < /proc/self
b750: 2f 73 74 61 74 75 73 3b 20 66 6f 72 20 78 20 69  /status; for x i
b760: 6e 20 2f 70 72 6f 63 2f 24 24 2f 66 64 2f 2a 3b  n /proc/$$/fd/*;
b770: 20 64 6f 20 65 63 68 6f 20 22 66 64 20 24 7b 78   do echo "fd ${x
b780: 23 23 2a 2f 7d 20 2d 3e 20 24 28 72 65 61 64 6c  ##*/} -> $(readl
b790: 69 6e 6b 20 24 78 29 22 3b 20 64 6f 6e 65 3b 20  ink $x)"; done; 
b7a0: 65 63 68 6f 20 22 63 6f 6f 6b 69 65 3a 20 24 7b  echo "cookie: ${
b7b0: 48 54 54 50 5f 43 4f 4f 4b 49 45 3a 2d 61 62 73  HTTP_COOKIE:-abs
b7c0: 65 6e 74 7d 20 6e 6f 6e 63 65 3a 20 24 7b 46 4f  ent} nonce: ${FO
b7d0: 53 53 49 4c 5f 4e 4f 4e 43 45 3a 2d 61 62 73 65  SSIL_NONCE:-abse
b7e0: 6e 74 7d 20 72 65 70 6f 73 69 74 6f 72 79 3a 20  nt} repository: 
b7f0: 24 46 4f 53 53 49 4c 5f 52 45 50 4f 53 49 54 4f  $FOSSIL_REPOSITO
b800: 52 59 22 3b 20 66 6f 72 20 70 20 69 6e 20 25 73  RY"; for p in %s
b810: 20 25 73 3b 20 64 6f 20 5b 20 2d 65 20 24 70 20   %s; do [ -e $p 
b820: 5d 20 26 26 20 65 63 68 6f 20 22 56 49 53 49 42  ] && echo "VISIB
b830: 4c 45 20 24 70 22 20 7c 7c 20 65 63 68 6f 20 22  LE $p" || echo "
b840: 61 62 73 65 6e 74 20 24 70 22 3b 20 64 6f 6e 65  absent $p"; done
b850: 3b 20 63 75 72 6c 20 2d 73 20 2d 6d 20 32 20 2d  ; curl -s -m 2 -
b860: 6f 20 2f 64 65 76 2f 6e 75 6c 6c 20 2d 77 20 22  o /dev/null -w "
b870: 6e 65 74 77 6f 72 6b 3a 20 25 25 7b 68 74 74 70  network: %%{http
b880: 5f 63 6f 64 65 7d 5c 5c 6e 22 20 68 74 74 70 3a  _code}\\n" http:
b890: 2f 2f 31 32 37 2e 30 2e 30 2e 31 3a 25 73 2f 20  //127.0.0.1:%s/ 
b8a0: 7c 7c 20 65 63 68 6f 20 22 6e 65 74 77 6f 72 6b  || echo "network
b8b0: 3a 20 72 65 66 75 73 65 64 22 5c 6e 27 20 24 44  : refused"\n' $D
b8c0: 2f 66 6f 73 73 69 6c 20 24 52 20 24 50 20 3e 20  /fossil $R $P > 
b8d0: 24 44 2f 72 65 6c 65 61 73 65 2f 70 72 6f 62 65  $D/release/probe
b8e0: 30 30 30 30 30 30 30 31 2f 6f 63 78 32 3b 20 63  00000001/ocx2; c
b8f0: 68 6d 6f 64 20 37 35 35 20 24 44 2f 72 65 6c 65  hmod 755 $D/rele
b900: 61 73 65 2f 70 72 6f 62 65 30 30 30 30 30 30 30  ase/probe0000000
b910: 31 2f 6f 63 78 32 0a 6c 6e 20 2d 73 66 6e 20 70  1/ocx2.ln -sfn p
b920: 72 6f 62 65 30 30 30 30 30 30 30 31 20 24 44 2f  robe00000001 $D/
b930: 72 65 6c 65 61 73 65 2f 63 75 72 72 65 6e 74 0a  release/current.
b940: 65 6e 76 20 2d 69 20 50 41 54 48 5f 49 4e 46 4f  env -i PATH_INFO
b950: 3d 2f 72 65 63 65 69 70 74 20 46 4f 53 53 49 4c  =/receipt FOSSIL
b960: 5f 52 45 50 4f 53 49 54 4f 52 59 3d 24 52 20 46  _REPOSITORY=$R F
b970: 4f 53 53 49 4c 5f 43 41 50 41 42 49 4c 49 54 49  OSSIL_CAPABILITI
b980: 45 53 3d 6f 20 48 54 54 50 5f 43 4f 4f 4b 49 45  ES=o HTTP_COOKIE
b990: 3d 73 65 63 72 65 74 2d 63 6f 6f 6b 69 65 20 46  =secret-cookie F
b9a0: 4f 53 53 49 4c 5f 4e 4f 4e 43 45 3d 6e 31 32 33  OSSIL_NONCE=n123
b9b0: 20 52 45 51 55 45 53 54 5f 4d 45 54 48 4f 44 3d   REQUEST_METHOD=
b9c0: 47 45 54 20 62 61 73 68 20 2d 63 20 22 65 78 65  GET bash -c "exe
b9d0: 63 20 2d 61 20 6f 63 78 32 2d 63 67 69 20 24 42  c -a ocx2-cgi $B
b9e0: 49 4e 2f 73 74 61 72 74 2d 6f 63 78 20 2d 2d 73  IN/start-ocx --s
b9f0: 63 72 61 74 63 68 20 24 44 22 20 32 3e 26 31 20  cratch $D" 2>&1 
ba00: 7c 20 68 65 61 64 20 2d 31 32 0a 6c 6e 20 2d 73  | head -12.ln -s
ba10: 66 6e 20 24 52 45 4c 20 24 44 2f 72 65 6c 65 61  fn $REL $D/relea
ba20: 73 65 2f 63 75 72 72 65 6e 74 0a 0a 73 61 79 20  se/current..say 
ba30: 22 72 65 76 69 65 77 20 74 65 73 74 3a 20 61 20  "review test: a 
ba40: 72 65 63 65 69 70 74 20 73 65 72 76 69 63 65 20  receipt service 
ba50: 74 68 61 74 20 6c 69 73 74 65 6e 73 20 62 75 74  that listens but
ba60: 20 6e 65 76 65 72 20 61 63 63 65 70 74 73 22 0a   never accepts".
ba70: 6b 69 6c 6c 20 24 7b 70 69 64 73 5b 30 5d 7d 20  kill ${pids[0]} 
ba80: 32 3e 2f 64 65 76 2f 6e 75 6c 6c 3b 20 73 6c 65  2>/dev/null; sle
ba90: 65 70 20 31 3b 20 72 6d 20 2d 66 20 24 44 2f 72  ep 1; rm -f $D/r
baa0: 75 6e 2f 72 65 63 65 69 70 74 73 2e 73 6f 63 6b  un/receipts.sock
bab0: 0a 70 79 74 68 6f 6e 33 20 2d 20 24 44 2f 72 75  .python3 - $D/ru
bac0: 6e 2f 72 65 63 65 69 70 74 73 2e 73 6f 63 6b 20  n/receipts.sock 
bad0: 3c 3c 27 50 59 53 27 20 26 0a 69 6d 70 6f 72 74  <<'PYS' &.import
bae0: 20 73 6f 63 6b 65 74 2c 20 73 79 73 2c 20 74 69   socket, sys, ti
baf0: 6d 65 0a 73 20 3d 20 73 6f 63 6b 65 74 2e 73 6f  me.s = socket.so
bb00: 63 6b 65 74 28 73 6f 63 6b 65 74 2e 41 46 5f 55  cket(socket.AF_U
bb10: 4e 49 58 29 3b 20 73 2e 62 69 6e 64 28 73 79 73  NIX); s.bind(sys
bb20: 2e 61 72 67 76 5b 31 5d 29 3b 20 73 2e 6c 69 73  .argv[1]); s.lis
bb30: 74 65 6e 28 30 29 0a 68 65 6c 64 20 3d 20 5b 5d  ten(0).held = []
bb40: 0a 77 68 69 6c 65 20 54 72 75 65 3a 0a 20 20 20  .while True:.   
bb50: 20 63 20 3d 20 73 6f 63 6b 65 74 2e 73 6f 63 6b   c = socket.sock
bb60: 65 74 28 73 6f 63 6b 65 74 2e 41 46 5f 55 4e 49  et(socket.AF_UNI
bb70: 58 29 3b 20 63 2e 73 65 74 62 6c 6f 63 6b 69 6e  X); c.setblockin
bb80: 67 28 46 61 6c 73 65 29 0a 20 20 20 20 74 72 79  g(False).    try
bb90: 3a 20 63 2e 63 6f 6e 6e 65 63 74 28 73 79 73 2e  : c.connect(sys.
bba0: 61 72 67 76 5b 31 5d 29 3b 20 68 65 6c 64 2e 61  argv[1]); held.a
bbb0: 70 70 65 6e 64 28 63 29 0a 20 20 20 20 65 78 63  ppend(c).    exc
bbc0: 65 70 74 20 28 42 6c 6f 63 6b 69 6e 67 49 4f 45  ept (BlockingIOE
bbd0: 72 72 6f 72 2c 20 4f 53 45 72 72 6f 72 29 3a 20  rror, OSError): 
bbe0: 62 72 65 61 6b 0a 74 69 6d 65 2e 73 6c 65 65 70  break.time.sleep
bbf0: 28 33 30 29 0a 50 59 53 0a 53 54 41 4c 4c 3d 24  (30).PYS.STALL=$
bc00: 21 3b 20 73 6c 65 65 70 20 31 0a 74 30 3d 24 28  !; sleep 1.t0=$(
bc10: 64 61 74 65 20 2b 25 73 2e 25 4e 29 3b 20 63 75  date +%s.%N); cu
bc20: 72 6c 20 2d 73 20 2d 6d 20 32 30 20 2d 6f 20 24  rl -s -m 20 -o $
bc30: 4f 55 54 2f 73 74 61 6c 6c 65 64 2e 74 78 74 20  OUT/stalled.txt 
bc40: 2d 77 20 22 73 74 61 74 75 73 20 25 7b 68 74 74  -w "status %{htt
bc50: 70 5f 63 6f 64 65 7d 22 20 2d 64 20 22 6a 73 6f  p_code}" -d "jso
bc60: 6e 3d 6f 6e 26 61 72 74 69 66 61 63 74 73 3d 24  n=on&artifacts=$
bc70: 48 31 22 20 22 24 55 2f 65 78 74 2f 6f 63 78 32  H1" "$U/ext/ocx2
bc80: 2d 63 67 69 2f 72 65 63 65 69 70 74 22 3b 20 65  -cgi/receipt"; e
bc90: 63 68 6f 20 22 20 61 66 74 65 72 20 24 28 65 63  cho " after $(ec
bca0: 68 6f 20 22 24 28 64 61 74 65 20 2b 25 73 2e 25  ho "$(date +%s.%
bcb0: 4e 29 20 2d 20 24 74 30 22 20 7c 20 62 63 20 7c  N) - $t0" | bc |
bcc0: 20 63 75 74 20 2d 63 31 2d 34 29 20 73 3a 20 24   cut -c1-4) s: $
bcd0: 28 68 65 61 64 20 2d 63 20 31 32 30 20 24 4f 55  (head -c 120 $OU
bce0: 54 2f 73 74 61 6c 6c 65 64 2e 74 78 74 29 22 0a  T/stalled.txt)".
bcf0: 6b 69 6c 6c 20 24 53 54 41 4c 4c 20 32 3e 2f 64  kill $STALL 2>/d
bd00: 65 76 2f 6e 75 6c 6c 0a 0a 73 61 79 20 22 72 6f  ev/null..say "ro
bd10: 6c 6c 62 61 63 6b 20 70 6f 69 6e 74 3a 20 74 68  llback point: th
bd20: 65 20 6c 61 75 6e 63 68 65 72 27 73 20 43 47 49  e launcher's CGI
bd30: 20 6c 69 6e 6b 20 69 73 20 6f 6e 65 20 66 69 6c   link is one fil
bd40: 65 3b 20 74 68 65 20 70 72 65 76 69 6f 75 73 20  e; the previous 
bd50: 70 61 67 65 20 69 73 20 74 68 65 20 72 65 6c 65  page is the rele
bd60: 61 73 65 27 73 20 6f 77 6e 20 6f 63 78 32 22 0a  ase's own ocx2".
bd70: 6c 73 20 2d 6c 20 24 44 2f 65 78 74 2f 6f 63 78  ls -l $D/ext/ocx
bd80: 32 2d 63 67 69 0a 65 63 68 6f 20 22 64 6f 6e 65  2-cgi.echo "done
bd90: 22 0a 00 00 00 00 00 00 00 00 00 00 00 00 00 00  "...............
bda0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
bdb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
bdc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
bdd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
bde0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
bdf0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
be00: 73 63 72 69 70 74 73 2d 66 6f 72 2d 72 65 76 69  scripts-for-revi
be10: 65 77 2d 32 2f 73 74 61 67 65 41 2d 66 69 78 65  ew-2/stageA-fixe
be20: 73 2d 70 6c 61 6e 2e 6d 64 00 00 00 00 00 00 00  s-plan.md.......
be30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
be40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
be50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
be60: 00 00 00 00 30 30 30 30 36 34 34 00 30 30 30 30  ....0000644.0000
be70: 30 30 30 00 30 30 30 30 30 30 30 00 30 30 30 30  000.0000000.0000
be80: 30 30 30 35 35 36 34 00 31 35 32 37 30 35 32 33  0005564.15270523
be90: 31 32 32 00 30 31 37 32 37 30 00 20 30 00 00 00  122.017270. 0...
bea0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
beb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
bec0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
bed0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
bee0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
bef0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
bf00: 00 75 73 74 61 72 20 20 00 72 6f 6f 74 00 00 00  .ustar  .root...
bf10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
bf20: 00 00 00 00 00 00 00 00 00 72 6f 6f 74 00 00 00  .........root...
bf30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
bf40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
bf50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
bf60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
bf70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
bf80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
bf90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
bfa0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
bfb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
bfc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
bfd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
bfe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
bff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
c000: 23 20 53 74 61 67 65 20 41 3a 20 66 69 78 65 73  # Stage A: fixes
c010: 20 66 72 6f 6d 20 53 6c 6f 77 70 65 6e 27 73 20   from Slowpen's 
c020: 72 65 76 69 65 77 20 6f 66 20 74 68 65 20 74 77  review of the tw
c030: 6f 20 73 63 72 69 70 74 73 2f 20 70 72 6f 67 72  o scripts/ progr
c040: 61 6d 73 0a 0a 45 64 69 74 20 6f 6e 6c 79 20 60  ams..Edit only `
c050: 73 63 72 69 70 74 73 2f 73 74 61 72 74 2d 6f 63  scripts/start-oc
c060: 78 2e 63 72 60 20 61 6e 64 20 60 73 63 72 69 70  x.cr` and `scrip
c070: 74 73 2f 72 65 63 65 69 70 74 73 2e 63 72 60 2e  ts/receipts.cr`.
c080: 20 42 6f 74 68 20 61 72 65 20 73 74 69 6c 6c 0a   Both are still.
c090: 75 6e 63 6f 6d 6d 69 74 74 65 64 20 61 6e 64 20  uncommitted and 
c0a0: 75 6e 64 65 72 20 74 68 65 20 6d 61 69 6e 74 61  under the mainta
c0b0: 69 6e 65 72 27 73 20 72 65 76 69 65 77 20 28 64  iner's review (d
c0c0: 65 63 69 73 69 6f 6e 20 32 32 29 2e 20 4b 65 65  ecision 22). Kee
c0d0: 70 20 65 61 63 68 0a 73 65 6c 66 2d 63 6f 6e 74  p each.self-cont
c0e0: 61 69 6e 65 64 20 28 73 74 61 6e 64 61 72 64 20  ained (standard 
c0f0: 6c 69 62 72 61 72 79 20 6f 6e 6c 79 29 2c 20 73  library only), s
c100: 6d 61 6c 6c 20 61 6e 64 20 63 6f 6d 6d 65 6e 74  mall and comment
c110: 65 64 20 77 68 65 72 65 20 61 20 72 65 61 64 65  ed where a reade
c120: 72 0a 77 6f 75 6c 64 20 61 73 6b 20 77 68 79 2e  r.would ask why.
c130: 20 4e 6f 20 6e 65 77 20 6d 65 63 68 61 6e 69 73   No new mechanis
c140: 6d 73 20 62 65 79 6f 6e 64 20 77 68 61 74 20 65  ms beyond what e
c150: 61 63 68 20 66 69 78 20 6e 65 65 64 73 2e 0a 0a  ach fix needs...
c160: 23 23 20 31 2e 20 73 74 61 72 74 2d 6f 63 78 3a  ## 1. start-ocx:
c170: 20 62 6f 75 6e 64 20 74 68 65 20 63 6f 6e 6e 65   bound the conne
c180: 63 74 69 6f 6e 20 61 6e 64 20 74 68 65 20 69 64  ction and the id
c190: 65 6e 74 69 74 79 20 77 72 69 74 65 20 28 6c 69  entity write (li
c1a0: 6e 65 73 20 7e 31 32 36 e2 80 93 31 34 33 29 0a  nes ~126–143).
c1b0: 0a 42 65 66 6f 72 65 20 60 63 6f 6e 6e 65 63 74  .Before `connect
c1c0: 60 2c 20 73 65 74 20 60 53 4f 5f 53 4e 44 54 49  `, set `SO_SNDTI
c1d0: 4d 45 4f 60 20 74 6f 20 32 20 73 65 63 6f 6e 64  MEO` to 2 second
c1e0: 73 20 6f 6e 20 74 68 65 20 73 6f 63 6b 65 74 20  s on the socket 
c1f0: 77 69 74 68 0a 60 73 65 74 73 6f 63 6b 6f 70 74  with.`setsockopt
c200: 60 2c 20 64 65 63 6c 61 72 69 6e 67 20 74 68 65  `, declaring the
c210: 20 60 74 69 6d 65 76 61 6c 60 20 73 74 72 75 63   `timeval` struc
c220: 74 20 69 6e 20 61 20 73 6d 61 6c 6c 20 60 6c 69  t in a small `li
c230: 62 60 20 62 69 6e 64 69 6e 67 20 69 66 0a 6e 65  b` binding if.ne
c240: 65 64 65 64 2e 20 4c 69 6e 75 78 20 61 70 70 6c  eded. Linux appl
c250: 69 65 73 20 60 53 4f 5f 53 4e 44 54 49 4d 45 4f  ies `SO_SNDTIMEO
c260: 60 20 74 6f 20 61 20 55 6e 69 78 20 73 74 72 65  ` to a Unix stre
c270: 61 6d 20 73 6f 63 6b 65 74 27 73 20 60 63 6f 6e  am socket's `con
c280: 6e 65 63 74 60 0a 77 68 65 6e 20 74 68 65 20 6c  nect`.when the l
c290: 69 73 74 65 6e 65 72 27 73 20 71 75 65 75 65 20  istener's queue 
c2a0: 69 73 20 66 75 6c 6c 2c 20 61 6e 64 20 74 6f 20  is full, and to 
c2b0: 60 77 72 69 74 65 60 2e 20 41 20 63 6f 6e 6e 65  `write`. A conne
c2c0: 63 74 20 6f 72 20 77 72 69 74 65 20 74 68 61 74  ct or write that
c2d0: 0a 74 69 6d 65 73 20 6f 75 74 20 28 45 41 47 41  .times out (EAGA
c2e0: 49 4e 2f 45 57 4f 55 4c 44 42 4c 4f 43 4b 2c 20  IN/EWOULDBLOCK, 
c2f0: 6f 72 20 45 49 4e 50 52 4f 47 52 45 53 53 29 20  or EINPROGRESS) 
c300: 6f 72 20 66 61 69 6c 73 20 69 6e 20 61 6e 79 20  or fails in any 
c310: 77 61 79 20 67 69 76 65 73 0a 74 68 65 20 35 30  way gives.the 50
c320: 33 20 61 6e 73 77 65 72 20 74 68 61 74 20 69 73  3 answer that is
c330: 20 61 6c 72 65 61 64 79 20 74 68 65 72 65 2e 20   already there. 
c340: 57 72 69 74 65 20 61 20 73 68 6f 72 74 20 63 6f  Write a short co
c350: 6d 6d 65 6e 74 20 73 61 79 69 6e 67 20 77 68 79  mment saying why
c360: 2e 0a 0a 23 23 20 32 2e 20 73 74 61 72 74 2d 6f  ...## 2. start-o
c370: 63 78 3a 20 72 6f 75 74 65 20 60 72 65 63 65 69  cx: route `recei
c380: 70 74 60 20 65 78 61 63 74 6c 79 20 61 73 20 74  pt` exactly as t
c390: 68 65 20 43 47 49 20 72 65 61 64 73 20 61 20 70  he CGI reads a p
c3a0: 61 74 68 20 28 6c 69 6e 65 73 20 7e 31 31 35 e2  ath (lines ~115â
c3b0: 80 93 31 32 30 29 0a 0a 54 68 65 20 43 47 49 20  €“120)..The CGI 
c3c0: 70 69 63 6b 73 20 74 68 65 20 74 6f 6f 6c 20 6c  picks the tool l
c3d0: 69 6b 65 20 74 68 69 73 20 28 73 72 63 2f 6f 63  ike this (src/oc
c3e0: 78 2f 63 67 69 2e 63 72 29 3a 0a 60 77 6f 72 64  x/cgi.cr):.`word
c3f0: 73 20 3d 20 50 41 54 48 5f 49 4e 46 4f 2e 73 70  s = PATH_INFO.sp
c400: 6c 69 74 28 27 2f 27 29 2e 72 65 6a 65 63 74 28  lit('/').reject(
c410: 26 2e 65 6d 70 74 79 3f 29 60 2c 20 74 68 65 6e  &.empty?)`, then
c420: 0a 60 6e 61 6d 65 20 3d 20 77 6f 72 64 73 5b 28  .`name = words[(
c430: 28 77 6f 72 64 73 2e 72 69 6e 64 65 78 28 70 72  (words.rindex(pr
c440: 6f 67 72 61 6d 29 20 7c 7c 20 2d 31 29 20 2b 20  ogram) || -1) + 
c450: 31 29 2e 2e 5d 2e 66 69 72 73 74 3f 60 2c 20 77  1)..].first?`, w
c460: 68 65 72 65 0a 60 70 72 6f 67 72 61 6d 60 20 69  here.`program` i
c470: 73 20 74 68 65 20 70 72 6f 67 72 61 6d 27 73 20  s the program's 
c480: 62 61 73 65 6e 61 6d 65 20 28 60 6f 63 78 32 2d  basename (`ocx2-
c490: 63 67 69 60 29 2e 20 55 73 65 20 74 68 65 20 73  cgi`). Use the s
c4a0: 61 6d 65 20 72 75 6c 65 20 69 6e 20 74 68 65 0a  ame rule in the.
c4b0: 6c 61 75 6e 63 68 65 72 2e 20 54 68 65 20 72 65  launcher. The re
c4c0: 6c 61 79 20 62 72 61 6e 63 68 20 72 75 6e 73 20  lay branch runs 
c4d0: 77 68 65 6e 20 60 6e 61 6d 65 20 3d 3d 20 22 72  when `name == "r
c4e0: 65 63 65 69 70 74 22 60 2c 20 73 6f 20 60 2f 72  eceipt"`, so `/r
c4f0: 65 63 65 69 70 74 2f 60 2c 0a 60 2f 72 65 63 65  eceipt/`,.`/rece
c500: 69 70 74 2f 78 60 20 61 6e 64 20 60 2f 6f 63 78  ipt/x` and `/ocx
c510: 32 2d 63 67 69 2f 72 65 63 65 69 70 74 60 20 61  2-cgi/receipt` a
c520: 6c 6c 20 72 65 61 63 68 20 74 68 65 20 73 65 72  ll reach the ser
c530: 76 69 63 65 2c 20 6e 65 76 65 72 20 74 68 65 0a  vice, never the.
c540: 70 61 73 73 74 68 72 6f 75 67 68 2e 20 57 72 69  passthrough. Wri
c550: 74 65 20 61 20 73 68 6f 72 74 20 63 6f 6d 6d 65  te a short comme
c560: 6e 74 20 6e 61 6d 69 6e 67 20 73 72 63 2f 6f 63  nt naming src/oc
c570: 78 2f 63 67 69 2e 63 72 20 61 73 20 74 68 65 20  x/cgi.cr as the 
c580: 72 75 6c 65 20 69 74 0a 6d 69 72 72 6f 72 73 2e  rule it.mirrors.
c590: 0a 0a 23 23 20 33 2e 20 73 74 61 72 74 2d 6f 63  ..## 3. start-oc
c5a0: 78 3a 20 64 72 6f 70 20 65 76 65 72 79 20 63 61  x: drop every ca
c5b0: 70 61 62 69 6c 69 74 79 20 69 6e 20 74 68 65 20  pability in the 
c5c0: 43 47 49 27 73 20 73 61 6e 64 62 6f 78 0a 0a 41  CGI's sandbox..A
c5d0: 64 64 20 60 2d 2d 63 61 70 2d 64 72 6f 70 20 41  dd `--cap-drop A
c5e0: 4c 4c 60 20 74 6f 20 60 63 67 69 5f 61 72 67 73  LL` to `cgi_args
c5f0: 60 2c 20 61 73 20 60 72 65 63 65 69 70 74 73 5f  `, as `receipts_
c600: 61 72 67 73 60 20 68 61 73 2e 0a 0a 23 23 20 34  args` has...## 4
c610: 2e 20 72 65 63 65 69 70 74 73 3a 20 61 20 64 61  . receipts: a da
c620: 74 61 62 61 73 65 20 6c 6f 63 6b 20 6d 75 73 74  tabase lock must
c630: 20 6e 6f 74 20 73 74 61 6c 6c 20 6f 74 68 65 72   not stall other
c640: 20 72 65 71 75 65 73 74 73 20 28 6c 69 6e 65 73   requests (lines
c650: 20 7e 31 31 30 e2 80 93 31 34 35 2c 20 32 34 36   ~110–145, 246
c660: e2 80 93 32 39 39 29 0a 0a 54 68 65 20 70 72 6f  –299)..The pro
c670: 67 72 61 6d 20 69 73 20 6f 6e 65 20 74 68 72 65  gram is one thre
c680: 61 64 2c 20 73 6f 20 53 51 4c 69 74 65 27 73 20  ad, so SQLite's 
c690: 62 75 73 79 20 68 61 6e 64 6c 65 72 20 73 6c 65  busy handler sle
c6a0: 65 70 69 6e 67 20 69 6e 73 69 64 65 20 43 0a 62  eping inside C.b
c6b0: 6c 6f 63 6b 73 20 65 76 65 72 79 20 66 69 62 65  locks every fibe
c6c0: 72 2e 20 4d 61 6b 65 20 77 61 69 74 69 6e 67 20  r. Make waiting 
c6d0: 68 61 70 70 65 6e 20 69 6e 20 43 72 79 73 74 61  happen in Crysta
c6e0: 6c 20 69 6e 73 74 65 61 64 3a 0a 2d 20 53 65 74  l instead:.- Set
c6f0: 20 60 73 71 6c 69 74 65 33 5f 62 75 73 79 5f 74   `sqlite3_busy_t
c700: 69 6d 65 6f 75 74 28 64 62 2c 20 30 29 60 2c 20  imeout(db, 0)`, 
c710: 73 6f 20 61 20 6c 6f 63 6b 65 64 20 64 61 74 61  so a locked data
c720: 62 61 73 65 20 72 65 74 75 72 6e 73 0a 20 20 53  base returns.  S
c730: 51 4c 49 54 45 5f 42 55 53 59 20 28 6f 72 20 53  QLITE_BUSY (or S
c740: 51 4c 49 54 45 5f 4c 4f 43 4b 45 44 29 20 61 74  QLITE_LOCKED) at
c750: 20 6f 6e 63 65 2e 0a 2d 20 49 6e 20 60 73 65 72   once..- In `ser
c760: 76 65 60 2c 20 72 75 6e 20 74 68 65 20 77 68 6f  ve`, run the who
c770: 6c 65 20 72 65 61 64 20 28 6f 70 65 6e 2c 20 60  le read (open, `
c780: 71 75 65 72 79 5f 6f 6e 6c 79 60 2c 20 60 42 45  query_only`, `BE
c790: 47 49 4e 60 2c 20 74 68 65 0a 20 20 71 75 65 72  GIN`, the.  quer
c7a0: 69 65 73 2c 20 60 43 4f 4d 4d 49 54 60 2c 20 63  ies, `COMMIT`, c
c7b0: 6c 6f 73 65 29 20 61 73 20 6f 6e 65 20 61 74 74  lose) as one att
c7c0: 65 6d 70 74 2e 20 49 66 20 61 6e 20 61 74 74 65  empt. If an atte
c7d0: 6d 70 74 20 72 61 69 73 65 73 0a 20 20 60 55 6e  mpt raises.  `Un
c7e0: 61 76 61 69 6c 61 62 6c 65 60 20 77 68 6f 73 65  available` whose
c7f0: 20 6e 61 6d 65 20 73 74 61 72 74 73 20 77 69 74   name starts wit
c800: 68 20 60 53 51 4c 49 54 45 5f 42 55 53 59 60 20  h `SQLITE_BUSY` 
c810: 6f 72 20 60 53 51 4c 49 54 45 5f 4c 4f 43 4b 45  or `SQLITE_LOCKE
c820: 44 60 2c 0a 20 20 63 6c 6f 73 65 20 61 6e 64 20  D`,.  close and 
c830: 60 73 6c 65 65 70 20 35 30 2e 6d 69 6c 6c 69 73  `sleep 50.millis
c840: 65 63 6f 6e 64 73 60 20 28 77 68 69 63 68 20 79  econds` (which y
c850: 69 65 6c 64 73 20 74 6f 20 6f 74 68 65 72 20 66  ields to other f
c860: 69 62 65 72 73 29 2e 20 54 68 65 6e 0a 20 20 74  ibers). Then.  t
c870: 72 79 20 61 67 61 69 6e 2c 20 75 6e 74 69 6c 20  ry again, until 
c880: 32 20 73 65 63 6f 6e 64 73 20 68 61 76 65 20 70  2 seconds have p
c890: 61 73 73 65 64 20 73 69 6e 63 65 20 74 68 65 20  assed since the 
c8a0: 66 69 72 73 74 20 61 74 74 65 6d 70 74 2e 20 54  first attempt. T
c8b0: 68 65 6e 0a 20 20 61 6e 73 77 65 72 20 60 75 6e  hen.  answer `un
c8c0: 61 76 61 69 6c 61 62 6c 65 60 20 77 69 74 68 20  available` with 
c8d0: 74 68 65 20 6c 61 73 74 20 65 72 72 6f 72 2e 0a  the last error..
c8e0: 2d 20 41 6e 79 20 6f 74 68 65 72 20 60 55 6e 61  - Any other `Una
c8f0: 76 61 69 6c 61 62 6c 65 60 2c 20 73 75 63 68 20  vailable`, such 
c900: 61 73 20 52 45 41 44 4f 4e 4c 59 5f 52 4f 4c 4c  as READONLY_ROLL
c910: 42 41 43 4b 20 66 6f 72 20 61 20 68 6f 74 20 6a  BACK for a hot j
c920: 6f 75 72 6e 61 6c 2c 0a 20 20 61 6e 73 77 65 72  ournal,.  answer
c930: 73 20 61 74 20 6f 6e 63 65 2c 20 61 73 20 6e 6f  s at once, as no
c940: 77 2e 0a 2d 20 54 68 65 20 77 68 6f 6c 65 20 63  w..- The whole c
c950: 6f 6e 6e 65 63 74 69 6f 6e 2c 20 66 72 6f 6d 20  onnection, from 
c960: 61 63 63 65 70 74 20 74 6f 20 61 6e 73 77 65 72  accept to answer
c970: 2c 20 69 73 20 62 6f 75 6e 64 65 64 20 61 74 20  , is bounded at 
c980: 35 20 73 65 63 6f 6e 64 73 3a 0a 20 20 32 20 66  5 seconds:.  2 f
c990: 6f 72 20 69 74 73 20 74 77 6f 20 6c 69 6e 65 73  or its two lines
c9a0: 2c 20 74 68 65 6e 20 61 74 20 6d 6f 73 74 20 32  , then at most 2
c9b0: 20 77 61 69 74 69 6e 67 20 6f 6e 20 61 20 6c 6f   waiting on a lo
c9c0: 63 6b 2e 20 54 68 65 20 73 6c 6f 74 20 69 73 0a  ck. The slot is.
c9d0: 20 20 72 65 6c 65 61 73 65 64 20 69 6e 20 60 68    released in `h
c9e0: 61 6e 64 6c 65 60 27 73 20 60 65 6e 73 75 72 65  andle`'s `ensure
c9f0: 60 2c 20 61 73 20 6e 6f 77 2e 0a 0a 57 72 69 74  `, as now...Writ
ca00: 65 20 61 20 63 6f 6d 6d 65 6e 74 20 73 61 79 69  e a comment sayi
ca10: 6e 67 20 77 68 79 20 77 61 69 74 69 6e 67 20 69  ng why waiting i
ca20: 73 20 69 6e 20 43 72 79 73 74 61 6c 2e 0a 0a 23  s in Crystal...#
ca30: 23 20 44 6f 6e 65 20 77 68 65 6e 0a 0a 2d 20 60  # Done when..- `
ca40: 63 72 79 73 74 61 6c 20 62 75 69 6c 64 20 2d 2d  crystal build --
ca50: 6e 6f 2d 63 6f 64 65 67 65 6e 60 20 61 6e 64 20  no-codegen` and 
ca60: 60 63 72 79 73 74 61 6c 20 74 6f 6f 6c 20 66 6f  `crystal tool fo
ca70: 72 6d 61 74 20 2d 2d 63 68 65 63 6b 60 20 70 61  rmat --check` pa
ca80: 73 73 20 66 6f 72 0a 20 20 62 6f 74 68 20 66 69  ss for.  both fi
ca90: 6c 65 73 2e 0a 2d 20 50 72 69 6e 74 20 74 68 65  les..- Print the
caa0: 20 66 75 6c 6c 20 64 69 66 66 2e 0a 0a 54 68 65   full diff...The
cab0: 20 72 65 76 69 65 77 65 72 20 72 75 6e 73 20 74   reviewer runs t
cac0: 68 65 20 73 63 72 61 74 63 68 20 64 65 70 6c 6f  he scratch deplo
cad0: 79 6d 65 6e 74 2e 20 49 74 20 74 65 73 74 73 3a  yment. It tests:
cae0: 0a 2d 20 61 20 6c 69 73 74 65 6e 65 72 20 74 68  .- a listener th
caf0: 61 74 20 6e 65 76 65 72 20 61 63 63 65 70 74 73  at never accepts
cb00: 3b 0a 2d 20 74 68 65 20 60 2f 72 65 63 65 69 70  ;.- the `/receip
cb10: 74 2f 60 20 61 6c 69 61 73 65 73 3b 0a 2d 20 74  t/` aliases;.- t
cb20: 68 65 20 43 47 49 20 73 61 6e 64 62 6f 78 27 73  he CGI sandbox's
cb30: 20 63 61 70 61 62 69 6c 69 74 69 65 73 3b 0a 2d   capabilities;.-
cb40: 20 6f 6e 65 20 72 65 70 6f 73 69 74 6f 72 79 20   one repository 
cb50: 68 65 6c 64 20 6c 6f 63 6b 65 64 20 77 68 69 6c  held locked whil
cb60: 65 20 61 6e 6f 74 68 65 72 20 69 73 20 61 73 6b  e another is ask
cb70: 65 64 2e 0a 00 00 00 00 00 00 00 00 00 00 00 00  ed..............
cb80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
cb90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
cba0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
cbb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
cbc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
cbd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
cbe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
cbf0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
cc00: 73 63 72 69 70 74 73 2d 66 6f 72 2d 72 65 76 69  scripts-for-revi
cc10: 65 77 2d 32 2f 73 68 61 72 64 2e 79 6d 6c 2e 64  ew-2/shard.yml.d
cc20: 69 66 66 00 00 00 00 00 00 00 00 00 00 00 00 00  iff.............
cc30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
cc40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
cc50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
cc60: 00 00 00 00 30 30 30 30 36 34 34 00 30 30 30 30  ....0000644.0000
cc70: 30 30 30 00 30 30 30 30 30 30 30 00 30 30 30 30  000.0000000.0000
cc80: 30 30 30 31 33 35 32 00 31 35 32 37 30 35 32 33  0001352.15270523
cc90: 31 32 32 00 30 31 36 32 32 30 00 20 30 00 00 00  122.016220. 0...
cca0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
ccb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
ccc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
ccd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
cce0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
ccf0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
cd00: 00 75 73 74 61 72 20 20 00 72 6f 6f 74 00 00 00  .ustar  .root...
cd10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
cd20: 00 00 00 00 00 00 00 00 00 72 6f 6f 74 00 00 00  .........root...
cd30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
cd40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
cd50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
cd60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
cd70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
cd80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
cd90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
cda0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
cdb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
cdc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
cdd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
cde0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
cdf0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
ce00: 49 6e 64 65 78 3a 20 73 68 61 72 64 2e 79 6d 6c  Index: shard.yml
ce10: 0a 3d 3d 3d 3d 3d 3d 3d 3d 3d 3d 3d 3d 3d 3d 3d  .===============
ce20: 3d 3d 3d 3d 3d 3d 3d 3d 3d 3d 3d 3d 3d 3d 3d 3d  ================
ce30: 3d 3d 3d 3d 3d 3d 3d 3d 3d 3d 3d 3d 3d 3d 3d 3d  ================
ce40: 3d 3d 3d 3d 3d 3d 3d 3d 3d 3d 3d 3d 3d 3d 3d 3d  ================
ce50: 3d 3d 3d 0a 2d 2d 2d 20 73 68 61 72 64 2e 79 6d  ===.--- shard.ym
ce60: 6c 0a 2b 2b 2b 20 73 68 61 72 64 2e 79 6d 6c 0a  l.+++ shard.yml.
ce70: 40 40 20 2d 31 30 2c 31 34 20 2b 31 30 2c 31 38  @@ -10,14 +10,18
ce80: 20 40 40 0a 20 20 20 73 71 6c 69 74 65 33 3a 0a   @@.   sqlite3:.
ce90: 20 20 20 20 20 67 69 74 68 75 62 3a 20 63 72 79       github: cry
cea0: 73 74 61 6c 2d 6c 61 6e 67 2f 63 72 79 73 74 61  stal-lang/crysta
ceb0: 6c 2d 73 71 6c 69 74 65 33 0a 20 20 20 20 20 76  l-sqlite3.     v
cec0: 65 72 73 69 6f 6e 3a 20 30 2e 32 33 2e 30 0a 20  ersion: 0.23.0. 
ced0: 0a 20 23 20 54 68 65 20 62 75 69 6c 64 20 73 74  . # The build st
cee0: 65 70 3a 20 60 73 68 61 72 64 73 20 62 75 69 6c  ep: `shards buil
cef0: 64 60 20 70 75 74 73 20 74 68 65 73 65 20 69 6e  d` puts these in
cf00: 20 62 69 6e 2f 2e 20 53 69 6e 63 65 20 73 6c 69   bin/. Since sli
cf10: 63 65 20 31 20 4f 43 58 20 69 73 0a 2d 23 20 6f  ce 1 OCX is.-# o
cf20: 6e 65 20 70 72 6f 67 72 61 6d 2c 20 6f 63 78 32  ne program, ocx2
cf30: 20 28 64 6f 63 73 2f 73 6c 69 63 65 73 2e 6d 64   (docs/slices.md
cf40: 29 2e 20 54 68 65 20 74 77 6f 20 62 75 69 6c 74  ). The two built
cf50: 20 66 72 6f 6d 20 73 63 72 69 70 74 73 2f 20 61   from scripts/ a
cf60: 72 65 20 74 68 65 20 6d 61 69 6e 74 61 69 6e 65  re the maintaine
cf70: 72 27 73 20 61 6e 64 20 72 75 6e 20 66 72 6f 6d  r's and run from
cf80: 0a 2b 23 20 6f 6e 65 20 70 72 6f 67 72 61 6d 2c  .+# one program,
cf90: 20 6f 63 78 32 20 28 64 6f 63 73 2f 73 6c 69 63   ocx2 (docs/slic
cfa0: 65 73 2e 6d 64 29 2e 20 54 68 65 20 66 6f 75 72  es.md). The four
cfb0: 20 62 75 69 6c 74 20 66 72 6f 6d 20 73 63 72 69   built from scri
cfc0: 70 74 73 2f 20 61 72 65 20 74 68 65 20 6d 61 69  pts/ are the mai
cfd0: 6e 74 61 69 6e 65 72 27 73 20 61 6e 64 20 72 75  ntainer's and ru
cfe0: 6e 20 66 72 6f 6d 0a 20 23 20 61 20 63 68 65 63  n from. # a chec
cff0: 6b 6f 75 74 27 73 20 62 69 6e 2f 2e 0a 20 74 61  kout's bin/.. ta
d000: 72 67 65 74 73 3a 0a 20 20 20 6f 63 78 32 3a 0a  rgets:.   ocx2:.
d010: 20 20 20 20 20 6d 61 69 6e 3a 20 74 6f 6f 6c 73       main: tools
d020: 2f 6f 63 78 32 2e 63 72 0a 20 20 20 73 74 61 72  /ocx2.cr.   star
d030: 74 2d 66 6f 73 73 69 6c 3a 0a 20 20 20 20 20 6d  t-fossil:.     m
d040: 61 69 6e 3a 20 73 63 72 69 70 74 73 2f 73 74 61  ain: scripts/sta
d050: 72 74 2d 66 6f 73 73 69 6c 2e 63 72 0a 20 20 20  rt-fossil.cr.   
d060: 73 79 6e 63 2d 66 6f 73 73 69 6c 3a 0a 20 20 20  sync-fossil:.   
d070: 20 20 6d 61 69 6e 3a 20 73 63 72 69 70 74 73 2f    main: scripts/
d080: 73 79 6e 63 2d 66 6f 73 73 69 6c 2e 63 72 0a 2b  sync-fossil.cr.+
d090: 20 20 73 74 61 72 74 2d 6f 63 78 3a 0a 2b 20 20    start-ocx:.+  
d0a0: 20 20 6d 61 69 6e 3a 20 73 63 72 69 70 74 73 2f    main: scripts/
d0b0: 73 74 61 72 74 2d 6f 63 78 2e 63 72 0a 2b 20 20  start-ocx.cr.+  
d0c0: 72 65 63 65 69 70 74 73 3a 0a 2b 20 20 20 20 6d  receipts:.+    m
d0d0: 61 69 6e 3a 20 73 63 72 69 70 74 73 2f 72 65 63  ain: scripts/rec
d0e0: 65 69 70 74 73 2e 63 72 0a 0a 00 00 00 00 00 00  eipts.cr........
d0f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d100: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d110: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d120: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d130: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d140: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d150: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d160: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d170: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d180: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d190: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d1a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d1b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d1c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d1d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d1e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d1f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d200: 73 63 72 69 70 74 73 2d 66 6f 72 2d 72 65 76 69  scripts-for-revi
d210: 65 77 2d 32 2f 52 45 41 44 4d 45 2e 74 78 74 00  ew-2/README.txt.
d220: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d230: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d240: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d250: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d260: 00 00 00 00 30 30 30 30 36 34 34 00 30 30 30 30  ....0000644.0000
d270: 30 30 30 00 30 30 30 30 30 30 30 00 30 30 30 30  000.0000000.0000
d280: 30 30 30 31 32 35 31 00 31 35 32 37 30 35 32 33  0001251.15270523
d290: 31 32 32 00 30 31 35 31 36 31 00 20 30 00 00 00  122.015161. 0...
d2a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d2b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d2c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d2d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d2e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d2f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d300: 00 75 73 74 61 72 20 20 00 72 6f 6f 74 00 00 00  .ustar  .root...
d310: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d320: 00 00 00 00 00 00 00 00 00 72 6f 6f 74 00 00 00  .........root...
d330: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d340: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d350: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d360: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d370: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d380: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d390: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d3a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d3b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d3c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d3d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d3e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d3f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d400: 53 65 63 6f 6e 64 20 72 65 76 69 65 77 20 63 6f  Second review co
d410: 70 79 2c 20 32 30 32 36 2d 31 30 2d 32 39 20 7e  py, 2026-10-29 ~
d420: 30 31 3a 35 30 20 55 54 43 2c 20 61 66 74 65 72  01:50 UTC, after
d430: 20 53 6c 6f 77 70 65 6e 27 73 20 73 6f 75 72 63   Slowpen's sourc
d440: 65 20 72 65 76 69 65 77 2e 20 53 75 70 65 72 73  e review. Supers
d450: 65 64 65 73 20 73 63 72 69 70 74 73 2d 66 6f 72  edes scripts-for
d460: 2d 72 65 76 69 65 77 2e 74 61 72 2e 20 4e 6f 74  -review.tar. Not
d470: 20 63 6f 6d 6d 69 74 74 65 64 2e 0a 73 74 61 72   committed..star
d480: 74 2d 6f 63 78 2e 63 72 3a 20 63 6f 6e 6e 65 63  t-ocx.cr: connec
d490: 74 20 61 6e 64 20 69 64 65 6e 74 69 74 79 20 77  t and identity w
d4a0: 72 69 74 65 20 62 6f 75 6e 64 65 64 20 61 74 20  rite bounded at 
d4b0: 32 20 73 20 28 53 4f 5f 53 4e 44 54 49 4d 45 4f  2 s (SO_SNDTIMEO
d4c0: 29 3b 20 74 68 65 20 72 65 63 65 69 70 74 20 72  ); the receipt r
d4d0: 6f 75 74 65 20 72 65 61 64 20 61 73 20 73 72 63  oute read as src
d4e0: 2f 6f 63 78 2f 63 67 69 2e 63 72 20 72 65 61 64  /ocx/cgi.cr read
d4f0: 73 20 61 20 70 61 74 68 2c 20 73 6f 20 2f 72 65  s a path, so /re
d500: 63 65 69 70 74 2f 20 61 6e 64 20 2f 72 65 63 65  ceipt/ and /rece
d510: 69 70 74 2f 78 20 72 65 61 63 68 20 74 68 65 20  ipt/x reach the 
d520: 73 65 72 76 69 63 65 3b 20 2d 2d 63 61 70 2d 64  service; --cap-d
d530: 72 6f 70 20 41 4c 4c 20 69 6e 20 74 68 65 20 43  rop ALL in the C
d540: 47 49 20 73 61 6e 64 62 6f 78 2e 0a 72 65 63 65  GI sandbox..rece
d550: 69 70 74 73 2e 63 72 3a 20 53 51 4c 69 74 65 20  ipts.cr: SQLite 
d560: 62 75 73 79 20 74 69 6d 65 6f 75 74 20 30 20 61  busy timeout 0 a
d570: 6e 64 20 6c 6f 63 6b 20 77 61 69 74 73 20 69 6e  nd lock waits in
d580: 20 43 72 79 73 74 61 6c 20 28 73 6c 65 65 70 20   Crystal (sleep 
d590: 35 30 20 6d 73 2c 20 61 74 20 6d 6f 73 74 20 32  50 ms, at most 2
d5a0: 20 73 29 2c 20 73 6f 20 61 20 6c 6f 63 6b 65 64   s), so a locked
d5b0: 20 72 65 70 6f 73 69 74 6f 72 79 20 73 74 61 6c   repository stal
d5c0: 6c 73 20 6e 6f 20 6f 74 68 65 72 20 72 65 71 75  ls no other requ
d5d0: 65 73 74 3b 20 74 68 65 20 77 68 6f 6c 65 20 63  est; the whole c
d5e0: 6f 6e 6e 65 63 74 69 6f 6e 20 62 6f 75 6e 64 65  onnection bounde
d5f0: 64 20 61 74 20 35 20 73 2e 0a 72 65 63 6f 72 64  d at 5 s..record
d600: 2e 74 78 74 3a 20 74 68 65 20 73 63 72 61 74 63  .txt: the scratc
d610: 68 20 72 75 6e 20 77 69 74 68 20 74 68 65 20 66  h run with the f
d620: 6f 75 72 20 72 65 76 69 65 77 20 74 65 73 74 73  our review tests
d630: 20 28 61 6c 69 61 73 65 73 2c 20 61 20 6c 6f 63   (aliases, a loc
d640: 6b 20 6f 6e 20 6f 6e 65 20 72 65 70 6f 73 69 74  k on one reposit
d650: 6f 72 79 20 77 68 69 6c 65 20 61 6e 6f 74 68 65  ory while anothe
d660: 72 20 69 73 20 61 73 6b 65 64 2c 20 74 68 65 20  r is asked, the 
d670: 43 47 49 20 73 61 6e 64 62 6f 78 20 66 72 6f 6d  CGI sandbox from
d680: 20 69 6e 73 69 64 65 2c 20 61 20 6c 69 73 74 65   inside, a liste
d690: 6e 65 72 20 74 68 61 74 20 6e 65 76 65 72 20 61  ner that never a
d6a0: 63 63 65 70 74 73 29 2e 0a 00 00 00 00 00 00 00  ccepts).........
d6b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d6c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d6d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d6e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d6f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d700: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d710: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d720: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d730: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d740: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d750: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d760: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d770: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d780: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d790: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d7a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d7b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d7c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d7d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d7e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d7f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d800: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d810: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d820: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d830: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d840: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d850: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d860: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d870: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d880: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d890: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d8a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d8b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d8c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d8d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d8e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d8f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d900: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d910: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d920: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d930: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d940: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d950: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d960: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d970: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d980: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d990: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d9a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d9b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d9c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d9d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d9e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
d9f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
da00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
da10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
da20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
da30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
da40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
da50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
da60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
da70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
da80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
da90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
daa0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
dab0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
dac0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
dad0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
dae0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
daf0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
db00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
db10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
db20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
db30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
db40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
db50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
db60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
db70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
db80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
db90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
dba0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
dbb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
dbc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
dbd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
dbe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
dbf0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................