tfossil: import from plan 9 - plan9port - [fork] Plan 9 from user space
 (HTM) git clone git://src.adamsgaard.dk/plan9port
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit 6f4d00ee45693290fae042b27536b54f77b96acd
 (DIR) parent fea86f063930ea187f1c77e93207ac8d39125520
 (HTM) Author: David du Colombier <0intro@gmail.com>
       Date:   Mon, 23 Sep 2013 23:00:39 +0200
       
       fossil: import from plan 9
       
       R=rsc
       https://codereview.appspot.com/7988047
       
       Diffstat:
         A man/man4/fossil.4                   |     514 +++++++++++++++++++++++++++++++
         A man/man8/fossilcons.8               |    1207 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/9.h                  |     258 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/9auth.c              |     175 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/9dir.c               |     132 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/9excl.c              |     126 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/9fid.c               |     304 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/9fsys.c              |    1896 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/9lstn.c              |     184 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/9p.c                 |    1181 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/9ping.c              |     109 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/9proc.c              |     825 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/9srv.c               |     242 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/9user.c              |     948 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/Ccli.c               |     112 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/Ccmd.c               |     459 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/Ccons.c              |     398 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/Clog.c               |      40 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/archive.c            |     466 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/bwatch.c             |     421 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/cache.c              |    2114 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/check.c              |     799 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/dat.h                |     332 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/disk.c               |     406 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/dump.c               |      86 ++++++++++++++++++++++++++++++
         A src/cmd/fossil/epoch.c              |      51 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/error.c              |      38 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/error.h              |      33 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/file.c               |    1860 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/flchk.c              |     118 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/flfmt.c              |     571 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/flfmt9660.c          |     565 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/flfmt9660.h          |       3 +++
         A src/cmd/fossil/fns.h                |     106 ++++++++++++++++++++++++++++++
         A src/cmd/fossil/fossil.c             |     143 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/fs.c                 |    1099 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/fs.h                 |      67 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/last.c               |      40 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/mkfile               |     136 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/nobwatch.c           |      39 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/pack.c               |     225 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/periodic.c           |      84 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/source.c             |    1068 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/srcload.c            |     270 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/stdinc.h             |      11 +++++++++++
         A src/cmd/fossil/trunc.c              |      19 +++++++++++++++++++
         A src/cmd/fossil/vac.c                |     746 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/vac.h                |     107 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/view.c               |    1127 +++++++++++++++++++++++++++++++
         A src/cmd/fossil/walk.c               |      65 +++++++++++++++++++++++++++++++
       
       50 files changed, 22325 insertions(+), 0 deletions(-)
       ---
 (DIR) diff --git a/man/man4/fossil.4 b/man/man4/fossil.4
       t@@ -0,0 +1,514 @@
       +.TH FOSSIL 4
       +.SH NAME
       +fossil, flchk, flfmt \- archival file server
       +.SH SYNOPSIS
       +.B fossil/fossil
       +[
       +.B -Dt
       +]
       +[
       +.B -c
       +.I cmd
       +]...
       +[
       +.B -f
       +.I file
       +]
       +[
       +.B -m
       +.I free-memory-percent
       +]
       +.PP
       +.B fossil/flchk
       +[
       +.B -f
       +]
       +[
       +.B -c
       +.I ncache
       +]
       +[
       +.B -h
       +.I host
       +]
       +.I file
       +.PP
       +.B fossil/flfmt
       +[
       +.B -y
       +]
       +[
       +.B -b
       +.I blocksize
       +]
       +[
       +.B -h
       +.I host
       +]
       +[
       +.B -l
       +.I label
       +]
       +[
       +.B -v
       +.I score
       +]
       +.I file
       +.PP
       +.B fossil/conf
       +[
       +.B -w
       +]
       +.I file
       +[
       +.I config
       +]
       +.PP
       +.B fossil/last
       +.I file
       +.SH DESCRIPTION
       +.I Fossil
       +is the main file system for Plan 9.
       +Unlike the Plan 9 file servers of old,
       +.I fossil
       +is a collection of user-space programs that run on a standard Plan 9 kernel.
       +The name of the main fossil file server at Murray Hill is
       +.BR pie .
       +The Plan 9 distribution file server,
       +.BR sources ,
       +is also a fossil server.
       +.PP
       +.I Fossil
       +is structured as a magnetic disk write buffer
       +optionally backed by a Venti server for archival storage.
       +It serves the Plan 9 protocol via TCP.
       +A
       +.I fossil
       +file server conventionally presents
       +three trees in the root directory of each file system:
       +.BR active ,
       +.BR archive ,
       +and
       +.BR snapshot .
       +.B /active
       +is the root of a conventional file system
       +whose blocks are stored in a disk file.
       +In a typical configuration, the file server periodically
       +marks the entire file system copy-on-write, effectively
       +taking a snapshot of the file system at that moment.
       +This snapshot is made available in a name
       +created from the date and time of the snapshot:
       +.BI /snapshot/ yyyy / mmdd / hhmm \fR,
       +where
       +.I yyyy
       +is the full year,
       +.I mm
       +is the month number,
       +.I dd
       +is the day number,
       +.I hh
       +is the hour,
       +and
       +.I mm
       +is the minute.
       +The snapshots in
       +.B /snapshot
       +are ephemeral: eventually they are deleted
       +to reclaim the disk space they occupy.
       +Long-lasting snapshots stored on a Venti server
       +are kept in 
       +.B /archive
       +and also named from the date (though not the time) of the snapshot:
       +.BI /archive/ yyyy / mmdds \fR,
       +where
       +.IR yyyy ,
       +.IR mm ,
       +and
       +.I dd
       +are year, month, and day as before,
       +and
       +.I s
       +is a sequence number if more than one
       +archival snapshot is done in a day.
       +For the first snapshot,
       +.I s
       +is null.
       +For the subsequent snapshots,
       +.I s
       +is
       +.BR .1 ,
       +.BR .2 ,
       +.BR .3 ,
       +etc.
       +The root of the main file system that is frozen
       +for the first archival snapshot of December 15, 2002
       +will be named
       +.BR /archive/2002/1215/ .
       +.PP
       +The attach name used in
       +.I mount
       +(see
       +.IR bind (1),
       +.IR bind (2)
       +and
       +.IR attach (5))
       +selects a file system to be served
       +and optionally a subtree,
       +in the format
       +.IB fs \fR[\fB/ dir \fR].
       +An empty attach name selects
       +.BR main/active .
       +.PP
       +.I Fossil
       +normally requires all users except
       +.L none
       +to provide authentication tickets on each
       +.IR attach (5).
       +To keep just anyone from connecting,
       +.L none
       +is only allowed to attach after another user
       +has successfully attached on the same
       +connection.
       +The other user effectively acts as a chaperone
       +for
       +.LR none .
       +Authentication can be disabled using the
       +.B -A
       +flag to
       +.B open
       +or
       +.B srv
       +(see
       +.IR fossilcons (8)).
       +.PP
       +The groups called
       +.B noworld
       +and
       +.B write
       +are special on the file server.
       +Any user belonging to
       +.B noworld
       +has attenuated access privileges.
       +Specifically, when checking such a user's access to files,
       +the file's permission bits are first ANDed
       +with 0770 for normal files and 0771 for directories.
       +The effect is to deny world access permissions to
       +.B noworld
       +users, except when walking into directories.
       +If the
       +.B write
       +group exists, then the file system appears read-only
       +to users not in the group.
       +This is used to make the Plan 9 distribution file server
       +.RI ( sources.cs.bell-labs.com )
       +readable by the world but writable only to the developers.
       +.PP
       +.I Fossil
       +starts a new instance of the fossil file server.
       +It is configured mainly through console commands,
       +documented in
       +.IR fossilcons (8).
       +.PP
       +The options are:
       +.TF "-c\fI cmd
       +.PD
       +.TP
       +.B -D
       +Toggle the debugging flag, which is initially off.
       +When the flag is set, information about authentication
       +and all protocol messages are written to standard error.
       +.TP
       +.B -t
       +Start a file server console on
       +.BR /dev/cons .
       +If this option is given,
       +.I fossil
       +does not fork itself into the background.
       +.TP
       +.BI -c " cmd
       +Execute the console command
       +.IR cmd .
       +This option may be repeated to give multiple
       +commands.
       +Typically the only commands given on the
       +command line are
       +.RB `` ".\fI file" ,''
       +which executes a file containing commands,
       +and
       +.RB `` "srv -p" \fIcons \fR,''
       +which starts a file server console on
       +.BI /srv/ cons \fR.
       +See
       +.IR fossilcons (8)
       +for more information.
       +.TP
       +.BI -f " file
       +Read and execute console commands stored in the Fossil disk 
       +.IR file .
       +.I Conf
       +.RI ( q.v. )
       +reads and writes the command set stored in the disk.
       +.TP
       +.B -m
       +Allocate
       +.I free-memory-percent
       +percent of the available free RAM for buffers.
       +This overrides all other memory sizing parameters,
       +notably the
       +.B -c
       +option to
       +.BR open .
       +30% is a reasonable choice.
       +.PD
       +.PP
       +.I Flchk
       +checks the fossil file system stored in
       +.I file
       +for inconsistencies.
       +.I Flchk
       +is deprecated in favor of the console
       +.B check
       +command (see
       +.IR fossilcons (8)).
       +.I Flchk
       +prints
       +.I fossil
       +console commands that may be
       +executed to take care of
       +bad pointers
       +.RB ( clrp ),
       +bad entries
       +.RB ( clre ),
       +bad directory entries
       +.RB ( clri ),
       +unreachable blocks
       +.RB ( bfree ).
       +Console commands are interspersed with
       +more detailed commentary on the file system.
       +The commands are distinguished by being prefixed with
       +sharp signs.
       +Note that all proposed fixes are rather drastic: offending
       +pieces of file system are simply chopped off.
       +.PP
       +.I Flchk
       +does
       +.I not
       +modify the file system, so it is safe to
       +run concurrently with
       +.IR fossil ,
       +though in this case
       +the list of unreachable
       +blocks and any inconsistencies involving the active file system
       +should be taken with a grain of salt.
       +.PP
       +The options are:
       +.TF "-h\fI host
       +.PD
       +.TP
       +.B -f
       +Fast mode.
       +By default,
       +.I flchk
       +checks the entire file system image for consistency,
       +which includes all the archives to Venti
       +and can take a very long time.
       +In fast mode,
       +.I flchk
       +avoids walking in Venti blocks
       +whenever possible.
       +.TP
       +.BI -c " ncache
       +Keep a cache of
       +.I ncache
       +(by default, 1000)
       +file system blocks in memory during the check.
       +.TP
       +.BI -h " host
       +Use
       +.I host
       +as the Venti server.
       +.PD
       +.PP
       +.I Flfmt
       +prepares
       +.I file
       +as a new fossil file system.
       +The file system is initialized with three empty directories
       +.BR active ,
       +.BR archive ,
       +and
       +.BR snapshot ,
       +as described above.
       +The options are:
       +.TF "-b\fI blocksize
       +.PD
       +.TP
       +.B -y
       +Yes mode.
       +By default,
       +.I flfmt
       +will prompt for confirmation before formatting
       +a file that already contains a fossil file system,
       +and before formatting a file that is not served
       +directly by a kernel device.
       +If the
       +.B -y
       +flag is given, no such checks are made.
       +.TP
       +.BI -b " blocksize
       +Set the file system block size (by default, 8192).
       +.TP
       +.BI -h " host
       +Use
       +.I host
       +as the Venti server.
       +.TP
       +.BI -l " label
       +Set the textual label on the file system to
       +.IR label .
       +The label is only a comment.
       +.TP
       +.BI -v " score
       +Initialize the file system using the vac file
       +system stored on Venti at
       +.IR score .
       +The score should have been generated by
       +.I fossil
       +rather than by
       +.IR vac (1),
       +so that the appropriate snapshot metadata is present.
       +.PD
       +.PP
       +.I Conf
       +reads or writes the configuration branded on the Fossil disk
       +.IR file .
       +By default, it reads the configuration from the disk and prints it to
       +standard output.
       +If the
       +.B -w
       +flag is given,
       +.I conf
       +reads a new configuration from 
       +.I config
       +(or else from standard input)
       +and writes it to the disk.
       +Inside the configuration file, the argument
       +.L *
       +may be used to stand in for the name of the disk holding the configuration.
       +The Plan 9 kernel boot process runs
       +.RB `` fossil
       +.B -f
       +.IR disk ''
       +to start a Fossil file server.
       +The disk is just a convenient place to store configuration
       +information.
       +.PP
       +.I Last
       +prints the vac score that resulted after the most recent archival snapshot 
       +of the fossil in
       +.I file.
       +.SH EXAMPLES
       +.PP
       +Place the root of the archive file system on
       +.B /n/dump
       +and show the modified times of the MIPS C compiler
       +over all dumps in December 2002:
       +.IP
       +.EX
       +9fs dump
       +ls -l /n/dump/2002/12*/mips/bin/vc
       +.EE
       +.PP
       +To get only one line of output for each version of the compiler:
       +.IP
       +.EX
       +ls -lp /n/dump/2002/12*/mips/bin/vc | uniq
       +.EE
       +.ne 14
       +.PP
       +Initialize a new file system, start the server with permission
       +checking turned off, create a users file, and mount the server:
       +.IP
       +.EX
       +fossil/flfmt /dev/sdC0/fossil
       +fossil/conf -w /dev/sdC0/fossil <<EOF
       +fsys main config
       +fsys main open -AWP
       +fsys main
       +create /active/adm adm sys d775
       +create /active/adm/users adm sys 664
       +users -w
       +srv -p fscons
       +srv fossil
       +EOF
       +fossil/fossil -f /dev/sdC0/fossil
       +mount /srv/fossil /n/fossil
       +.EE
       +.LP
       +See the discussion of the
       +.B users
       +and
       +.B uname
       +commands in
       +.IR fossilcons (8)
       +for more about the user table.
       +.ne 3
       +.PP
       +Perhaps because the disk has been corrupted or replaced,
       +format a new file system using the last archive score printed
       +on the console:
       +.IP
       +.EX
       +fossil/flfmt -v b9b3...5559 /dev/sdC0/fossil
       +.EE
       +.LP
       +Note that while
       +.B /snapshot
       +will be lost,
       +.B /active
       +and
       +.B /archive
       +will be restored to their contents at the time of the
       +last archival snapshot.
       +.ne 3
       +.PP
       +Blindly accept the changes prescribed by
       +.I flchk
       +(not recommended):
       +.IP
       +.EX
       +fossil/flchk /dev/sdC0/fossil | sed -n 's/^# //p' >>/srv/fscons
       +.EE
       +.LP
       +A better strategy is to vet the output,
       +filter out any suggestions you're not comfortable with,
       +and then use the
       +.I sed
       +command to prepare the script.
       +.SH SOURCE
       +.B /sys/src/cmd/fossil
       +.SH SEE ALSO
       +.IR yesterday (1),
       +.IR fs (3),
       +.IR fs (4),
       +.IR srv (4),
       +.IR fossilcons (8),
       +.IR loadfossil (8),
       +.IR venti (8)
       +.SH BUGS
       +It is possible that the disk format (but not the Venti format)
       +will change in the future, to make the disk a full cache
       +rather than just a write buffer.
       +Changing to the new format will require reformatting
       +the disk as in the example above,
       +but note that this will preserve most of the file system
       +(all but
       +.BR /snapshot )
       +with little effort.
       +.PP
       +The
       +.B -m
       +option currently assumes a block size of 8K bytes,
       +and a single file system per
       +.I fossil
       +instance.
 (DIR) diff --git a/man/man8/fossilcons.8 b/man/man8/fossilcons.8
       t@@ -0,0 +1,1207 @@
       +.TH FOSSILCONS 8
       +.SH NAME
       +fossilcons \- fossil console commands
       +.SH SYNOPSIS
       +.B
       +con /srv/fscons
       +.PP
       +.PD 0.1
       +.B .
       +.I file
       +.PP
       +.B 9p
       +.I T-message
       +...
       +.PP
       +.B bind
       +[
       +.B -b|-a|-c|-bc|-ac
       +]
       +.I new
       +.I old
       +.PP
       +.B dflag
       +.PP
       +.B echo
       +[
       +.B -n
       +]
       +[
       +.I arg
       +...
       +]
       +.PP
       +.B listen
       +[
       +.B -INd
       +]
       +[
       +.I address
       +]
       +.PP
       +.B msg
       +[
       +.B -m
       +.I nmsg
       +]
       +[
       +.B -p
       +.I nproc
       +]
       +.PP
       +.B printconfig
       +.PP
       +.B srv
       +[
       +.B -APWdp
       +]
       +.I name
       +.PP
       +.B uname
       +.I name
       +[
       +.I id
       +|
       +.BI : id
       +|
       +.BI % newname
       +|
       +.BI = leader
       +|
       +.BI + member
       +|
       +.BI - member
       +]
       +.PP
       +.B users
       +[
       +.B -d
       +|
       +.B -r
       +.I file
       +]
       +[
       +.B -w
       +]
       +.PP
       +.B who
       +.sp
       +.PP
       +.B fsys
       +.I name
       +.PP
       +.B fsys
       +.I name
       +.B config
       +[
       +.I device
       +]
       +.PP
       +.B fsys
       +.I name
       +.B venti
       +[
       +.I host
       +]
       +.PP
       +.B fsys
       +.I name
       +.B open
       +[
       +.B -APVWar
       +]
       +[
       +.B -c
       +.I ncache
       +]
       +.PP
       +[
       +.B fsys
       +.I name
       +]
       +.B close
       +.PP
       +.B fsys
       +.I name
       +.B unconfig
       +.sp
       +.PP
       +[
       +.B fsys
       +.I name
       +]
       +.B bfree
       +.I addr
       +.PP
       +[
       +.B fsys
       +.I name
       +]
       +.B block
       +.I addr
       +.I offset
       +[
       +.I count
       +[
       +.I data
       +]]
       +.PP
       +.in +1i
       +.ti -1i
       +[
       +.B fsys
       +.I name
       +]
       +.B check
       +[
       +.B pblock
       +] [
       +.B pdir
       +] [
       +.B pfile
       +] [
       +.B bclose
       +] [
       +.B clri
       +] [
       +.B clre
       +] [
       +.B clrp
       +] [
       +.B fix
       +] [
       +.B venti
       +] [
       +.B snapshot
       +]
       +.PP
       +[
       +.B fsys
       +.I name
       +]
       +.B clre
       +.I addr
       +.I offsets
       +\&...
       +.PP
       +[
       +.B fsys
       +.I name
       +]
       +.B clri
       +.I files
       +\&...
       +.PP
       +[
       +.B fsys
       +.I name
       +]
       +.B clrp
       +.I addr
       +.I offset
       +\&...
       +.PP
       +[
       +.B fsys
       +.I name
       +]
       +.B create
       +.I path
       +.I uid
       +.I gid
       +.I perm
       +.PP
       +[
       +.B fsys
       +.I name
       +]
       +.B df
       +.PP
       +[
       +.B fsys
       +.I name
       +]
       +.B epoch
       +[[
       +.B -ry
       +]
       +.I n
       +]
       +.PP
       +[
       +.B fsys
       +.I name
       +]
       +.B halt
       +.PP
       +[
       +.B fsys
       +.I name
       +]
       +.B label
       +.I addr
       +[
       +.I type
       +.I state
       +.I epoch
       +.I epochclose
       +.I tag
       +]
       +.PP
       +[
       +.B fsys
       +.I name
       +]
       +.B remove
       +.I files
       +\&...
       +.PP
       +[
       +.B fsys
       +.I name
       +]
       +.B snap
       +[
       +.B -a
       +]
       +[
       +.B -s
       +.I src
       +]
       +[
       +.B -d
       +.I dst
       +]
       +.PP
       +[
       +.B fsys
       +.I name
       +]
       +.B snapclean
       +[
       +.I timeout
       +]
       +.PP
       +[
       +.B fsys
       +.I name
       +]
       +.B snaptime
       +[
       +.B -a
       +.I hhmm
       +]
       +[
       +.B -s
       +.I interval
       +]
       +[
       +.B -t
       +.I timeout
       +]
       +.PP
       +[
       +.B fsys
       +.I name
       +]
       +.B stat
       +.IR files ...
       +.PP
       +[
       +.B fsys
       +.I name
       +]
       +.B sync
       +.PP
       +[
       +.B fsys
       +.I name
       +]
       +.B unhalt
       +.PP
       +[
       +.B fsys
       +.I name
       +]
       +.B vac
       +.I dir
       +.PP
       +[
       +.B fsys
       +.I name
       +]
       +.B wstat
       +.I file
       +.I elem
       +.I uid
       +.I gid
       +.I perm
       +.I length
       +.SH DESCRIPTION
       +These are configuration and maintenance commands
       +executed at the console of a 
       +.IR fossil (4)
       +file server.
       +The commands are split into three groups above:
       +file server configuration,
       +file system configuration,
       +and file system maintenance.
       +This manual page is split in the same way.
       +.SS File server configuration
       +.PP
       +The
       +dot
       +.RI ( . )
       +command
       +reads
       +.IR file ,
       +treating each line as a command to be executed.
       +Blank lines and lines beginning with a 
       +.L #
       +character are ignored.
       +Errors during execution are printed but do not stop the script.
       +Note that
       +.I file
       +is a file in the name space in which
       +.I fossil
       +was started,
       +.I not
       +a file in any file system served by
       +.IR fossil .
       +.PP
       +.I 9p
       +executes a 9P transaction; the arguments
       +are in the same format used by
       +.IR 9pcon (8).
       +.PP
       +.I Bind
       +behaves similarly to
       +.IR bind (1).
       +It is useful when fossil
       +is started without devices it needs configured
       +into its namespace.
       +.PP
       +.I Dflag
       +toggles the debug flag and prints the new setting.
       +When the debug flag is set, all protocol messages
       +and information about authentication is printed to
       +standard error.
       +.PP
       +.I Echo
       +behaves identically to
       +.IR echo (1),
       +writing to the console.
       +.PP
       +.I Listen
       +manages the network addresses at which
       +fossil is listening.
       +With no arguments,
       +.I listen
       +prints the current list of addresses and their network directories.
       +With one argument, listen
       +.I address
       +starts a new listener at
       +.IR address ;
       +the
       +.B -d
       +flag causes 
       +.I listen
       +to remove the listener
       +at the given address.
       +By default, the user
       +.I none
       +is only allowed to attach on a connection after
       +at least one other user has successfully attached.
       +The
       +.B -N
       +flag allows connections from
       +.I none
       +at any time.
       +The
       +.B -I
       +flag causes
       +.I fossil
       +to check the IP address of incoming connections
       +against
       +.BR /mnt/ipok ,
       +rejecting attaches from disallowed addresses.
       +This mechanism is not intended for general use.
       +The server
       +.I sources.cs.bell-labs.com
       +uses it to comply with U.S. crytography
       +export regulations.
       +.PP
       +.I Msg
       +prints the maximum internal 9P message queue size
       +and the maximum number of 9P processes to
       +allocate for serving the queue.
       +The
       +.B -m
       +and
       +.B -p
       +options set the two variables.
       +.PP
       +.I Printconfig
       +prints the
       +.B config
       +line for each configured file system
       +and prints the
       +.B venti
       +line, if any, used to configure this file server.
       +.PP
       +.I Srv
       +behaves like listen but uses
       +.BI /srv/ name
       +rather than a network address.
       +With the
       +.B -p
       +flag, 
       +.I srv 
       +edits a list of console services rather than 9P services.
       +With no arguments,
       +.I srv
       +prints the current list of services.
       +With one argument, srv
       +.I name
       +starts a new service at
       +.IR /srv/name ;
       +the
       +.B -d
       +flag causes 
       +.I srv
       +to remove the named service.
       +See the
       +.I [fsys] open
       +command below for a description of the
       +.B -APW
       +options.
       +.PP
       +.I Uname
       +manipulates entries in the user table.
       +There is no distinction between users and groups:
       +a user is a group with one member.
       +For each user, the user table records:
       +.TF \fImembers
       +.PD
       +.TP
       +.I id
       +the string used to represent this user in the on-disk structures
       +.TP
       +.I name
       +the string used to represent this user in the 9P protocol
       +.TP
       +.I leader
       +the group's leader (see
       +.IR stat (5)
       +for a description of the special privileges held by a group leader)
       +.TP
       +.I members
       +a comma-separated list of members in this group
       +.PP
       +The
       +.I id
       +and
       +.I name
       +are usually the same string, but need not be.
       +Once an
       +.I id
       +is used in file system structures archived to Venti,
       +it is impossible to change those disk structures,
       +and thus impossible to rename the
       +.IR id .
       +The translation from
       +.I name
       +to
       +.I id
       +allows the appearance of renaming the user even
       +though the on-disk structures still record the old name.
       +(In a conventional Unix file system, the
       +.I id
       +is stored as a small integer rather than a string.)
       +.I Leader
       +and
       +.I members
       +are names, not ids.
       +.PP
       +The first argument to
       +.I uname
       +is the
       +.I name
       +of a user.
       +The second argument is a verb, one of:
       +.TF \fI%newname
       +.PD
       +.TP
       +.I id
       +create a user with name
       +.RI ` name '
       +and id
       +.RI ` id ;'
       +also create a home directory
       +.BI /active/usr/ uname \fR
       +.TP
       +.BI : id
       +create a user with name
       +.RI ` name '
       +and id
       +.RI ` id ,'
       +but do not create a home directory
       +.TP
       +.BI % newname
       +rename user
       +.RI ` name '
       +to
       +.RI ` newname ,'
       +throughout the user table
       +.TP
       +.BI = leader
       +set
       +.IR name 's
       +group leader
       +to
       +.IR leader .
       +.TP
       +.BI =
       +remove
       +.IR name 's
       +group leader; then all members will be
       +considered leaders
       +.TP
       +.BI + member
       +add
       +.I member
       +to
       +.IR name 's
       +list of members
       +.TP
       +.BI - member
       +remove
       +.I member
       +from
       +.IR name 's
       +list of members
       +.LP
       +If the verb is omitted, the entire entry for
       +.I name
       +is printed, in the form
       +`\fIid\fL:\fIname\fL:\fIleader\fL:\fImembers\fR.'
       +.LP
       +The end of this manual page gives examples.
       +.PP
       +.I Users
       +manipulates the user table.
       +The user table is a list of lines in the form printed
       +by the
       +.I uname
       +command.
       +The
       +.B -d
       +flag resets the user table with the default:
       +.IP
       +.EX
       +adm:adm:adm:sys
       +none:none::
       +noworld:noworld::
       +sys:sys::
       +glenda:glenda:glenda:
       +.EE
       +.PP
       +Except
       +.BR glenda ,
       +these users are mandatory: they must appear in all user
       +files and cannot be renamed.
       +.PP
       +The
       +.B -r
       +flag reads a user table from the named
       +.I file
       +in file system
       +.BR main .
       +The
       +.B -w
       +flag writes the table to
       +.B /active/adm/users
       +on the file system
       +.BR main .
       +.B /active/adm
       +and
       +.B /active/adm/users
       +will be created if they do not exist.
       +.PP
       +.I Users
       +.B -r
       +.B /active/adm/users
       +is automatically executed when the file system
       +.B main
       +is opened.
       +.PP
       +.I Users
       +.B -w
       +is automatically executed after each change to the user
       +table by the
       +.I uname
       +command.
       +.PP
       +.I Who
       +prints a list of users attached to each active connection.
       +.SS File system configuration
       +.I Fsys
       +sets the current file system to
       +.IR name ,
       +which must be configured and open (q.v.).
       +The current file system name is
       +displayed as the file server prompt.
       +The special name
       +.B all
       +stands for all file systems;
       +commands applied to
       +.B all
       +are applied to each file system in turn.
       +The commands
       +.BR config ,
       +.BR open ,
       +.BR venti ,
       +and
       +.B close
       +cannot be applied to
       +.BR all .
       +.PP
       +.I Fsys
       +takes as an optional argument
       +(after
       +.BR name )
       +a command to execute on the named file system.
       +Most commands require that the named file system
       +be configured and open; these commands can be invoked
       +without the
       +.BI fsys " name
       +prefix, in which case the current file system is used.
       +A few commands
       +.RB ( config ,
       +.BR open ,
       +and
       +.BR unconfig )
       +operate on unopened file systems; they require the prefix.
       +.PP
       +.I Config
       +creates a new file system named
       +.I name
       +using disk file
       +.IR device .
       +This just adds an entry to fossil's internal table.
       +If
       +.I device
       +is missing,
       +the
       +.I file
       +argument to
       +.IR fossil 's
       +.B -f
       +option will be used instead;
       +this allows the
       +.I fossil
       +configuration file to avoid naming the partition that it is embedded in,
       +making it more portable.
       +.PP
       +.I Venti
       +establishes a connection to the Venti server
       +.I host
       +(by default, the environment variable
       +.B $venti
       +or the network variable
       +.BR $venti )
       +for use by the named file system.
       +If no
       +.I venti
       +command is issued before
       +.IR open ,
       +the default Venti server will be used.
       +If the file system is open,
       +and was not opened with the
       +.B -V
       +flag,
       +the command redials the Venti server.
       +This can be used to reestablish broken connections.
       +It is not a good idea to use the command to switch
       +between Venti servers, since Fossil does not keep track
       +of which blocks are stored on which servers.
       +.PP
       +.I Open
       +opens the file system, reading the
       +root and super blocks and allocating an in-memory
       +cache for disk and Venti blocks.
       +The options are:
       +.TF "-c\fI ncache
       +.PD
       +.TP
       +.B -A
       +run with no authentication
       +.TP
       +.B -P
       +run with no permission checking
       +.TP
       +.B -V
       +do not attempt to connect to a Venti server
       +.TP
       +.B -W
       +allow wstat to make arbitrary changes to the user and group fields
       +.TP
       +.B -a
       +do not update file access times;
       +primarily to avoid wear on flash memories
       +.TP
       +.B -r
       +open the file system read-only
       +.TP
       +.BI -c " ncache
       +allocate an in-memory cache of 
       +.I ncache
       +(by default, 1000)
       +blocks
       +.PP
       +The
       +.I -APW
       +settings can be overridden on a per-connection basis
       +by the
       +.I srv
       +command above.
       +.PP
       +.I Close
       +flushes all dirty file system blocks to disk
       +and then closes the device file.
       +.PP
       +.I Unconfig
       +removes the named file system (which must be closed)
       +from fossil's internal table.
       +.br
       +.ne 3
       +.SS File system maintenance
       +.I Bfree
       +marks the block at disk address
       +.I addr
       +as available for allocation.
       +Before doing so, it prints a
       +.I label
       +command (q.v.)
       +that can be used to restore the block to its previous state.
       +.PP
       +.I Block
       +displays (in hexadecimal)
       +the contents of the block at disk address
       +.IR addr ,
       +starting at
       +.I offset
       +and continuing for
       +.I count
       +bytes or until the end of the block.
       +If 
       +.I data
       +(also hexadecimal)
       +is given, the contents in that range are
       +replaced with data.
       +When writing to a block,
       +.I block
       +prints the old and new contents,
       +so that the change is easily undone.
       +Editing blocks is discouraged.
       +.PP
       +.I Clre
       +zeros an entry from a disk block.
       +Before doing so, it prints a
       +.I block
       +command that can be used 
       +to restore the entry.
       +.PP
       +.I Clri
       +removes the internal directory entry
       +and abandons storage associated with
       +.IR files .
       +It ignores the usual rules for sanity, such as checking against
       +removing a non-empty directory.
       +A subsequent
       +.I flchk
       +(see
       +.IR fossil (4))
       +will identify the abandoned storage so it can be reclaimed with
       +.I bfree
       +commands.
       +.PP
       +.I Clrp
       +zeros a pointer in a disk block.
       +Before doing so, it prints a 
       +.I block
       +command that can be used to restore the entry.
       +.PP
       +.I Check
       +checks the file system for various inconsistencies.
       +If the file system is not already halted, it is halted for
       +the duration of the check.
       +If the archiver is currently sending a snapshot to Venti,
       +the check will refuse to run; the only recourse is to wait
       +for the archiver to finish.
       +.PP
       +A list of keyword options control the check.
       +The
       +.BR pblock ,
       +.BR pdir ,
       +and
       +.B pfile
       +options cause 
       +.I check
       +to print the name of each block, directory, or file encountered.
       +.PP
       +By default,
       +.I check
       +reports errors but does not fix them.
       +The
       +.BR bclose ,
       +.BR clri ,
       +.BR clre ,
       +and
       +.B clrp
       +options specify correcting actions that may be taken:
       +closing leaked blocks, clearing bad file directory entries,
       +clearing bad pointers, and clearing bad entries.
       +The
       +.B fix
       +option enables all of these; it is equivalent to
       +.B bclose
       +.B clri
       +.B clre
       +.BR clrp .
       +.PP
       +By default,
       +.I check
       +scans the portion of the active file system held in the write buffer,
       +avoiding blocks stored on Venti or used only in snapshots.
       +The
       +.B venti
       +option causes
       +.I check
       +to scan the portion of the file system stored on Venti,
       +and the
       +.B snapshot
       +option causes
       +.I check
       +to scan old snapshots.
       +Specifying
       +.B snapshot
       +causes
       +.I check
       +to take a long time;
       +specifying
       +.B venti
       +or
       +(worse)
       +.B venti
       +.B snapshot
       +causes
       +.I check
       +to take a very long time.
       +.PP
       +.I Create
       +creates a file on the current file system.
       +.I Uid
       +and
       +.I gid
       +are uids
       +.RI ( not
       +unames;
       +see the discussion above, in the description
       +of the 
       +.I uname
       +command).
       +.I Perm
       +is the low 9 bits of the permission mode of the file,
       +in octal.
       +The 
       +.BR a ,
       +.BR d ,
       +and
       +.B l
       +mode prefixes
       +set the append-only, directory, and lock bits.
       +The
       +.I perm
       +is formatted as described in the
       +.I stat
       +command;
       +creating files or directories with the
       +.BR snapshot (s)
       +bit set is not allowed.
       +.PP
       +.I Df
       +prints the amount of used disk space in the write buffer.
       +.PP
       +.I Epoch
       +sets the low file system epoch.
       +Snapshots in the file system are given increasing epoch numbers.
       +The file system maintains a low and a high epoch number,
       +and only allows access to snapshots in that range.
       +The low epoch number can be moved forward to discard old snapshots
       +and reclaim the disk space they occupy.
       +(The high epoch number is always the epoch of the currently
       +active file system.)
       +.PP
       +With no argument
       +.I epoch
       +reports the current low and high epoch numbers.
       +The command
       +``\fLepoch\fI n''\fR
       +is used to propose changing the low epoch to
       +.IR n .
       +In response, 
       +.I fossil
       +scans
       +.B /archive
       +and
       +.B /snapshot
       +for snapshots that would be discarded, printing their
       +epoch numbers and the
       +.I clri
       +commands necessary to remove them.
       +The epoch is changed only if no such paths are found.
       +The usual sequence of commands is (1) run epoch to
       +print the snapshots and their epochs, (2) clri some snapshots,
       +(3) run epoch again.
       +If the file system is completely full (there are no free blocks),
       +.I clri
       +may fail because it needs to allocate blocks.
       +For this situation,
       +the
       +.B -y
       +flag to epoch forces the epoch change even when
       +it means discarding currently accessible snapshots.
       +Note that when there are still snapshots in
       +.BR /archive ,
       +the archiver should take care
       +of those snapshots (moving the blocks from disk to Venti)
       +if you give it more time.
       +.PP
       +The
       +.B -r
       +flag to epoch causes it to remove any now-inaccessible
       +snapshot directories once it has changed the epoch.
       +This flag only makes sense in conjunction with the
       +.B -y
       +flag.
       +.PP
       +.I Epoch
       +is a very low-level way to retire snapshots.
       +The preferred way is by setting an automatic timer
       +with
       +.IR snaptime .
       +.PP
       +.I Halt
       +suspends all file system activity;
       +.I unhalt
       +resumes activity.
       +.PP
       +.I Label
       +displays and edits the label associated with a block.
       +When editing, a parameter of
       +.B -
       +means leave that field unchanged.
       +Editing labels is discouraged.
       +.PP
       +.I Remove
       +removes
       +.IR files .
       +.PP
       +.I Snap
       +takes a temporary snapshot of the current file system,
       +recording it in 
       +.BI /snapshot/ yyyy / mmdd / hhmm \fR,
       +as described in 
       +.IR fossil (4).
       +The
       +.B -a
       +flag causes 
       +.I snap
       +to take an archival snapshot, recording it in
       +.BI /archive/ yyyy / mmdd \fR,
       +also described in
       +.IR fossil (4).
       +By default the snapshot is taken of
       +.BR /active ,
       +the root of the active file system.
       +The 
       +.B -s
       +flag specifies a different source path.
       +The
       +.B -d
       +flag specifies a different destination path.
       +These  two flags are useful together for moving snapshots into
       +the archive tree.
       +.PP
       +.I Snapclean
       +immediately discards all snapshots that are more than
       +.I timeout
       +minutes old.
       +The default timeout is the one set by the
       +.I snaptime
       +command.
       +The discarding is a one-time event rather than
       +a recurring event as in
       +.IR snaptime .
       +.PP
       +.I Snaptime
       +displays and edits the times at which snapshots are automatically
       +taken.
       +An archival snapshot is taken once a day, at
       +.IR hhmm ,
       +while temporary snapshots are taken at multiples of
       +.I interval
       +minutes.
       +Temporary snapshots are discarded after they are
       +.I timeout
       +minutes old.
       +The snapshot cleanup runs every
       +.I timeout
       +minutes or once a day, whichever is more frequent,
       +so snapshots may grow to an age of almost twice the timeout
       +before actually being discarded.
       +With no arguments,
       +.I snaptime
       +prints the current snapshot times.
       +The
       +.B -a
       +and
       +.B -s
       +options set the archive and snapshot times.
       +An
       +.I hhmm
       +or
       +.I interval
       +of
       +.L none
       +can be used to disable that kind of automatic snapshot.
       +The
       +.B -t
       +option sets the snapshot timeout.
       +If
       +.I timeout
       +is
       +.LR none ,
       +temporary snapshots are not automatically discarded.
       +By default, all three times are set to
       +.LR none .
       +.PP
       +.I Stat
       +displays metadata for each of the named
       +.IR files ,
       +in the form:
       +.IP
       +.EX
       +stat \fIfile elem uid gid perm length
       +.EE
       +.LP
       +(Replacing
       +.B stat
       +with
       +.B wstat
       +yields a valid command.)
       +The
       +.I perm
       +is an octal number less than or equal to 777,
       +prefixed with any of the following letters
       +to indicate additional bits.
       +.IP
       +.EX
       +.ta +4n
       +a        \fRappend only
       +d        \fRdirectory
       +l        \fRexclusive use
       +s        \fRis the root of a snapshot
       +t        \fRtemporary bit
       +A        \fRMS-DOS archive bit
       +G        \fRsetgid
       +H        \fRMS-DOS hidden bit
       +L        \fRsymbolic link
       +S        \fRMS-DOS system bit
       +U        \fRsetuid
       +Y        \fRsticky
       +.EE
       +.PP
       +The bits denoted by capital letters are included
       +to support non-Plan 9 systems.
       +They are not made visible by the 9P protocol.
       +.PP
       +.I Sync
       +writes dirty blocks in memory to the disk.
       +.PP
       +.I Vac
       +prints the Venti score for a
       +.IR vac (1)
       +archive containing the tree rooted
       +at
       +.IR dir ,
       +which must already be archived to Venti
       +(typically
       +.IR dir
       +is a directory in the
       +.B /archive
       +tree).
       +.PP
       +.I Wstat
       +changes the metadata of the named
       +.IR file .
       +Specifying
       +.B -
       +for any of the fields means ``don't change.''
       +Attempts to change the
       +.B d
       +or
       +.B s
       +bits in the
       +.I perm
       +are silently ignored.
       +.SH EXAMPLES
       +.IR Sources ,
       +the Plan 9 distribution file server,
       +uses the following configuration file:
       +.IP
       +.EX
       +srv -p fscons.sources
       +srv -p fscons.sources.adduserd
       +srv sources
       +fsys main config /dev/sdC0/fossil.outside
       +fsys main open -c 25600
       +fsys main
       +users /active/adm/users
       +listen tcp!*!564
       +msg -m 40 -p 10
       +snaptime -a 0000 -s 15
       +.EE
       +.LP
       +The second console is used by the daemon
       +that creates new accounts.
       +.PP
       +To add a new user with
       +.I name
       +and
       +.I id
       +.B rob
       +and create his home directory:
       +.IP
       +.EX
       +uname rob rob
       +.EE
       +.PP
       +To create a new group
       +.B sys
       +(with no home directory)
       +and add
       +.B rob
       +to it:
       +.IP
       +.EX
       +uname sys :sys
       +uname sys +rob
       +.EE
       +.PP
       +To save an old (but not yet discarded) snapshot into the archive tree:
       +.IP
       +.EX
       +snap -a -s /snapshot/2003/1220/0700 -d /archive/2003/1220
       +.EE
 (DIR) diff --git a/src/cmd/fossil/9.h b/src/cmd/fossil/9.h
       t@@ -0,0 +1,258 @@
       +#include <auth.h>
       +#include <fcall.h>
       +
       +enum {
       +        NFidHash        = 503,
       +};
       +
       +typedef struct Con Con;
       +typedef struct DirBuf DirBuf;
       +typedef struct Excl Excl;
       +typedef struct Fid Fid;
       +typedef struct Fsys Fsys;
       +typedef struct Msg Msg;
       +
       +#pragma incomplete DirBuf
       +#pragma incomplete Excl
       +#pragma incomplete Fsys
       +
       +struct Msg {
       +        uchar*        data;
       +        u32int        msize;                        /* actual size of data */
       +        Fcall        t;
       +        Fcall        r;
       +        Con*        con;
       +
       +        Msg*        anext;                        /* allocation free list */
       +
       +        Msg*        mnext;                        /* all active messsages on this Con */
       +        Msg*         mprev;
       +
       +        int        state;                        /* */
       +
       +        Msg*        flush;                        /* flushes waiting for this Msg */
       +
       +        Msg*        rwnext;                        /* read/write queue */
       +        int        nowq;                        /* do not place on write queue */
       +};
       +
       +enum {
       +        MsgN                = 0,
       +        MsgR                = 1,
       +        Msg9                = 2,
       +        MsgW                = 3,
       +        MsgF                = 4,
       +};
       +
       +enum {
       +        ConNoneAllow        = 1<<0,
       +        ConNoAuthCheck        = 1<<1,
       +        ConNoPermCheck        = 1<<2,
       +        ConWstatAllow        = 1<<3,
       +        ConIPCheck        = 1<<4,
       +};
       +struct Con {
       +        char*        name;
       +        uchar*        data;                        /* max, not negotiated */
       +        int        isconsole;                /* immutable */
       +        int        flags;                        /* immutable */
       +        char        remote[128];                /* immutable */
       +        VtLock*        lock;
       +        int        state;
       +        int        fd;
       +        Msg*        version;
       +        u32int        msize;                        /* negotiated with Tversion */
       +        VtRendez* rendez;
       +
       +        Con*        anext;                        /* alloc */
       +        Con*        cnext;                        /* in use */
       +        Con*        cprev;
       +
       +        VtLock*        alock;
       +        int        aok;                        /* authentication done */
       +
       +        VtLock*        mlock;
       +        Msg*        mhead;                        /* all Msgs on this connection */
       +        Msg*        mtail;
       +        VtRendez* mrendez;
       +
       +        VtLock*        wlock;
       +        Msg*        whead;                        /* write queue */
       +        Msg*        wtail;
       +        VtRendez* wrendez;
       +
       +        VtLock*        fidlock;                /* */
       +        Fid*        fidhash[NFidHash];
       +        Fid*        fhead;
       +        Fid*        ftail;
       +        int        nfid;
       +};
       +
       +enum {
       +        ConDead                = 0,
       +        ConNew                = 1,
       +        ConDown                = 2,
       +        ConInit                = 3,
       +        ConUp                = 4,
       +        ConMoribund        = 5,
       +};
       +
       +struct Fid {
       +        VtLock*        lock;
       +        Con*        con;
       +        u32int        fidno;
       +        int        ref;                        /* inc/dec under Con.fidlock */
       +        int        flags;
       +
       +        int        open;
       +        Fsys*        fsys;
       +        File*        file;
       +        Qid        qid;
       +        char*        uid;
       +        char*        uname;
       +        DirBuf*        db;
       +        Excl*        excl;
       +
       +        VtLock*        alock;                        /* Tauth/Tattach */
       +        AuthRpc* rpc;
       +        char*        cuname;
       +
       +        Fid*        sort;                        /* sorted by uname in cmdWho */
       +        Fid*        hash;                        /* lookup by fidno */
       +        Fid*        next;                        /* clunk session with Tversion */
       +        Fid*        prev;
       +};
       +
       +enum {                                        /* Fid.flags and fidGet(..., flags) */
       +        FidFCreate        = 0x01,
       +        FidFWlock        = 0x02,
       +};
       +
       +enum {                                        /* Fid.open */
       +        FidOCreate        = 0x01,
       +        FidORead        = 0x02,
       +        FidOWrite        = 0x04,
       +        FidORclose        = 0x08,
       +};
       +
       +/*
       + * 9p.c
       + */
       +extern int (*rFcall[Tmax])(Msg*);
       +extern int validFileName(char*);
       +
       +/*
       + * 9auth.c
       + */
       +extern int authCheck(Fcall*, Fid*, Fsys*);
       +extern int authRead(Fid*, void*, int);
       +extern int authWrite(Fid*, void*, int);
       +
       +/*
       + * 9dir.c
       + */
       +extern void dirBufFree(DirBuf*);
       +extern int dirDe2M(DirEntry*, uchar*, int);
       +extern int dirRead(Fid*, uchar*, int, vlong);
       +
       +/*
       + * 9excl.c
       + */
       +extern int exclAlloc(Fid*);
       +extern void exclFree(Fid*);
       +extern void exclInit(void);
       +extern int exclUpdate(Fid*);
       +
       +/*
       + * 9fid.c
       + */
       +extern void fidClunk(Fid*);
       +extern void fidClunkAll(Con*);
       +extern Fid* fidGet(Con*, u32int, int);
       +extern void fidInit(void);
       +extern void fidPut(Fid*);
       +
       +/*
       + * 9fsys.c
       + */
       +extern void fsysFsRlock(Fsys*);
       +extern void fsysFsRUnlock(Fsys*);
       +extern Fs* fsysGetFs(Fsys*);
       +extern Fsys* fsysGet(char*);
       +extern char* fsysGetName(Fsys*);
       +extern File* fsysGetRoot(Fsys*, char*);
       +extern Fsys* fsysIncRef(Fsys*);
       +extern int fsysInit(void);
       +extern int fsysNoAuthCheck(Fsys*);
       +extern int fsysNoPermCheck(Fsys*);
       +extern void fsysPut(Fsys*);
       +extern int fsysWstatAllow(Fsys*);
       +
       +/*
       + * 9lstn.c
       + */
       +extern int lstnInit(void);
       +
       +/*
       + * 9proc.c
       + */
       +extern Con* conAlloc(int, char*, int);
       +extern void conInit(void);
       +extern void msgFlush(Msg*);
       +extern void msgInit(void);
       +
       +/*
       + * 9srv.c
       + */
       +extern int srvInit(void);
       +
       +/*
       + * 9user.c
       + */
       +extern int groupLeader(char*, char*);
       +extern int groupMember(char*, char*);
       +extern int groupWriteMember(char*);
       +extern char* unameByUid(char*);
       +extern char* uidByUname(char*);
       +extern int usersInit(void);
       +extern int usersFileRead(char*);
       +extern int validUserName(char*);
       +
       +extern char* uidadm;
       +extern char* unamenone;
       +extern char* uidnoworld;
       +
       +/*
       + * Ccli.c
       + */
       +extern int cliAddCmd(char*, int (*)(int, char*[]));
       +extern int cliError(char*, ...);
       +extern int cliInit(void);
       +extern int cliExec(char*);
       +#pragma        varargck        argpos        cliError        1
       +
       +/*
       + * Ccmd.c
       + */
       +extern int cmdInit(void);
       +
       +/*
       + * Ccons.c
       + */
       +extern int consPrompt(char*);
       +extern int consInit(void);
       +extern int consOpen(int, int, int);
       +extern int consTTY(void);
       +extern int consWrite(char*, int);
       +
       +/*
       + * Clog.c
       + */
       +extern int consPrint(char*, ...);
       +extern int consVPrint(char*, va_list);
       +#pragma        varargck        argpos        consPrint        1
       +
       +/*
       + * fossil.c
       + */
       +extern int Dflag;
 (DIR) diff --git a/src/cmd/fossil/9auth.c b/src/cmd/fossil/9auth.c
       t@@ -0,0 +1,175 @@
       +#include "stdinc.h"
       +#include "9.h"
       +
       +int
       +authRead(Fid* afid, void* data, int count)
       +{
       +        AuthInfo *ai;
       +        AuthRpc *rpc;
       +
       +        if((rpc = afid->rpc) == nil){
       +                vtSetError("not an auth fid");
       +                return -1;
       +        }
       +
       +        switch(auth_rpc(rpc, "read", nil, 0)){
       +        default:
       +                vtSetError("fossil authRead: auth protocol not finished");
       +                return -1;
       +        case ARdone:
       +                if((ai = auth_getinfo(rpc)) == nil){
       +                        vtSetError("%r");
       +                        break;
       +                }
       +                if(ai->cuid == nil || *ai->cuid == '\0'){
       +                        vtSetError("auth with no cuid");
       +                        auth_freeAI(ai);
       +                        break;
       +                }
       +                assert(afid->cuname == nil);
       +                afid->cuname = vtStrDup(ai->cuid);
       +                auth_freeAI(ai);
       +                if(Dflag)
       +                        fprint(2, "authRead cuname %s\n", afid->cuname);
       +                assert(afid->uid == nil);
       +                if((afid->uid = uidByUname(afid->cuname)) == nil){
       +                        vtSetError("unknown user %#q", afid->cuname);
       +                        break;
       +                }
       +                return 0;
       +        case ARok:
       +                if(count < rpc->narg){
       +                        vtSetError("not enough data in auth read");
       +                        break;
       +                }
       +                memmove(data, rpc->arg, rpc->narg);
       +                return rpc->narg;
       +        case ARphase:
       +                vtSetError("%r");
       +                break;
       +        }
       +        return -1;
       +}
       +
       +int
       +authWrite(Fid* afid, void* data, int count)
       +{
       +        assert(afid->rpc != nil);
       +        if(auth_rpc(afid->rpc, "write", data, count) != ARok)
       +                return -1;
       +        return count;
       +}
       +
       +int
       +authCheck(Fcall* t, Fid* fid, Fsys* fsys)
       +{
       +        Con *con;
       +        Fid *afid;
       +        uchar buf[1];
       +
       +        /*
       +         * Can't lookup with FidWlock here as there may be
       +         * protocol to do. Use a separate lock to protect altering
       +         * the auth information inside afid.
       +         */
       +        con = fid->con;
       +        if(t->afid == NOFID){
       +                /*
       +                 * If no authentication is asked for, allow
       +                 * "none" provided the connection has already
       +                 * been authenticatated.
       +                 *
       +                 * The console is allowed to attach without
       +                 * authentication.
       +                 */
       +                vtRLock(con->alock);
       +                if(con->isconsole){
       +                        /* anything goes */
       +                }else if((con->flags&ConNoneAllow) || con->aok){
       +                        static int noneprint;
       +
       +                        if(noneprint++ < 10)
       +                                consPrint("attach %s as %s: allowing as none\n",
       +                                        fsysGetName(fsys), fid->uname);
       +                        vtMemFree(fid->uname);
       +                        fid->uname = vtStrDup(unamenone);
       +                }else{
       +                        vtRUnlock(con->alock);
       +                        consPrint("attach %s as %s: connection not authenticated, not console\n",
       +                                fsysGetName(fsys), fid->uname);
       +                        vtSetError("cannot attach as none before authentication");
       +                        return 0;
       +                }
       +                vtRUnlock(con->alock);
       +
       +                if((fid->uid = uidByUname(fid->uname)) == nil){
       +                        consPrint("attach %s as %s: unknown uname\n",
       +                                fsysGetName(fsys), fid->uname);
       +                        vtSetError("unknown user");
       +                        return 0;
       +                }
       +                return 1;
       +        }
       +
       +        if((afid = fidGet(con, t->afid, 0)) == nil){
       +                consPrint("attach %s as %s: bad afid\n",
       +                        fsysGetName(fsys), fid->uname);
       +                vtSetError("bad authentication fid");
       +                return 0;
       +        }
       +
       +        /*
       +         * Check valid afid;
       +         * check uname and aname match.
       +         */
       +        if(!(afid->qid.type & QTAUTH)){
       +                consPrint("attach %s as %s: afid not an auth file\n",
       +                        fsysGetName(fsys), fid->uname);
       +                fidPut(afid);
       +                vtSetError("bad authentication fid");
       +                return 0;
       +        }
       +        if(strcmp(afid->uname, fid->uname) != 0 || afid->fsys != fsys){
       +                consPrint("attach %s as %s: afid is for %s as %s\n",
       +                        fsysGetName(fsys), fid->uname,
       +                        fsysGetName(afid->fsys), afid->uname);
       +                fidPut(afid);
       +                vtSetError("attach/auth mismatch");
       +                return 0;
       +        }
       +
       +        vtLock(afid->alock);
       +        if(afid->cuname == nil){
       +                if(authRead(afid, buf, 0) != 0 || afid->cuname == nil){
       +                        vtUnlock(afid->alock);
       +                        consPrint("attach %s as %s: %R\n",
       +                                fsysGetName(fsys), fid->uname);
       +                        fidPut(afid);
       +                        vtSetError("fossil authCheck: auth protocol not finished");
       +                        return 0;
       +                }
       +        }
       +        vtUnlock(afid->alock);
       +
       +        assert(fid->uid == nil);
       +        if((fid->uid = uidByUname(afid->cuname)) == nil){
       +                consPrint("attach %s as %s: unknown cuname %s\n",
       +                        fsysGetName(fsys), fid->uname, afid->cuname);
       +                fidPut(afid);
       +                vtSetError("unknown user");
       +                return 0;
       +        }
       +
       +        vtMemFree(fid->uname);
       +        fid->uname = vtStrDup(afid->cuname);
       +        fidPut(afid);
       +
       +        /*
       +         * Allow "none" once the connection has been authenticated.
       +         */
       +        vtLock(con->alock);
       +        con->aok = 1;
       +        vtUnlock(con->alock);
       +
       +        return 1;
       +}
 (DIR) diff --git a/src/cmd/fossil/9dir.c b/src/cmd/fossil/9dir.c
       t@@ -0,0 +1,132 @@
       +#include "stdinc.h"
       +
       +#include "9.h"
       +
       +/* one entry buffer for reading directories */
       +struct DirBuf {
       +        DirEntryEnum*        dee;
       +        int                valid;
       +        DirEntry        de;
       +};
       +
       +static DirBuf*
       +dirBufAlloc(File* file)
       +{
       +        DirBuf *db;
       +
       +        db = vtMemAllocZ(sizeof(DirBuf));
       +        db->dee = deeOpen(file);
       +        if(db->dee == nil){
       +                /* can happen if dir is removed from under us */
       +                vtMemFree(db);
       +                return nil;
       +        }
       +        return db;
       +}
       +
       +void
       +dirBufFree(DirBuf* db)
       +{
       +        if(db == nil)
       +                return;
       +
       +        if(db->valid)
       +                deCleanup(&db->de);
       +        deeClose(db->dee);
       +        vtMemFree(db);
       +}
       +
       +int
       +dirDe2M(DirEntry* de, uchar* p, int np)
       +{
       +        int n;
       +        Dir dir;
       +
       +        memset(&dir, 0, sizeof(Dir));
       +
       +        dir.qid.path = de->qid;
       +        dir.qid.vers = de->mcount;
       +        dir.mode = de->mode & 0777;
       +        if(de->mode & ModeAppend){
       +                dir.qid.type |= QTAPPEND;
       +                dir.mode |= DMAPPEND;
       +        }
       +        if(de->mode & ModeExclusive){
       +                dir.qid.type |= QTEXCL;
       +                dir.mode |= DMEXCL;
       +        }
       +        if(de->mode & ModeDir){
       +                dir.qid.type |= QTDIR;
       +                dir.mode |= DMDIR;
       +        }
       +        if(de->mode & ModeSnapshot){
       +                dir.qid.type |= QTMOUNT;        /* just for debugging */
       +                dir.mode |= DMMOUNT;
       +        }
       +        if(de->mode & ModeTemporary){
       +                dir.qid.type |= QTTMP;
       +                dir.mode |= DMTMP;
       +        }
       +
       +        dir.atime = de->atime;
       +        dir.mtime = de->mtime;
       +        dir.length = de->size;
       +
       +        dir.name = de->elem;
       +        if((dir.uid = unameByUid(de->uid)) == nil)
       +                dir.uid = smprint("(%s)", de->uid);
       +        if((dir.gid = unameByUid(de->gid)) == nil)
       +                dir.gid = smprint("(%s)", de->gid);
       +        if((dir.muid = unameByUid(de->mid)) == nil)
       +                dir.muid = smprint("(%s)", de->mid);
       +
       +        n = convD2M(&dir, p, np);
       +
       +        vtMemFree(dir.muid);
       +        vtMemFree(dir.gid);
       +        vtMemFree(dir.uid);
       +
       +        return n;
       +}
       +
       +int
       +dirRead(Fid* fid, uchar* p, int count, vlong offset)
       +{
       +        int n, nb;
       +        DirBuf *db;
       +
       +        /*
       +         * special case of rewinding a directory
       +         * otherwise ignore the offset
       +         */
       +        if(offset == 0 && fid->db){
       +                dirBufFree(fid->db);
       +                fid->db = nil;
       +        }
       +
       +        if(fid->db == nil){
       +                fid->db = dirBufAlloc(fid->file);
       +                if(fid->db == nil)
       +                        return -1;
       +        }
       +
       +        db = fid->db;
       +
       +        for(nb = 0; nb < count; nb += n){
       +                if(!db->valid){
       +                        n = deeRead(db->dee, &db->de);
       +                        if(n < 0)
       +                                return -1;
       +                        if(n == 0)
       +                                break;
       +                        db->valid = 1;
       +                }
       +                n = dirDe2M(&db->de, p+nb, count-nb);
       +                if(n <= BIT16SZ)
       +                        break;
       +                db->valid = 0;
       +                deCleanup(&db->de);
       +        }
       +
       +        return nb;
       +}
 (DIR) diff --git a/src/cmd/fossil/9excl.c b/src/cmd/fossil/9excl.c
       t@@ -0,0 +1,126 @@
       +#include "stdinc.h"
       +
       +#include "9.h"
       +
       +static struct {
       +        VtLock*        lock;
       +
       +        Excl*        head;
       +        Excl*        tail;
       +} ebox;
       +
       +struct Excl {
       +        Fsys*        fsys;
       +        uvlong        path;
       +        ulong        time;
       +
       +        Excl*        next;
       +        Excl*        prev;
       +};
       +
       +enum {
       +        LifeTime        = (5*60),
       +};
       +
       +int
       +exclAlloc(Fid* fid)
       +{
       +        ulong t;
       +        Excl *excl;
       +
       +        assert(fid->excl == nil);
       +
       +        t = time(0L);
       +        vtLock(ebox.lock);
       +        for(excl = ebox.head; excl != nil; excl = excl->next){
       +                if(excl->fsys != fid->fsys || excl->path != fid->qid.path)
       +                        continue;
       +                /*
       +                 * Found it.
       +                 * Now, check if it's timed out.
       +                 * If not, return error, it's locked.
       +                 * If it has timed out, zap the old
       +                 * one and continue on to allocate a
       +                 * a new one.
       +                 */
       +                if(excl->time >= t){
       +                        vtUnlock(ebox.lock);
       +                        vtSetError("exclusive lock");
       +                        return 0;
       +                }
       +                excl->fsys = nil;
       +        }
       +
       +        /*
       +         * Not found or timed-out.
       +         * Alloc a new one and initialise.
       +         */
       +        excl = vtMemAllocZ(sizeof(Excl));
       +        excl->fsys = fid->fsys;
       +        excl->path = fid->qid.path;
       +        excl->time = t+LifeTime;
       +        if(ebox.tail != nil){
       +                excl->prev = ebox.tail;
       +                ebox.tail->next = excl;
       +        }
       +        else{
       +                ebox.head = excl;
       +                excl->prev = nil;
       +        }
       +        ebox.tail = excl;
       +        excl->next = nil;
       +        vtUnlock(ebox.lock);
       +
       +        fid->excl = excl;
       +        return 1;
       +}
       +
       +int
       +exclUpdate(Fid* fid)
       +{
       +        ulong t;
       +        Excl *excl;
       +
       +        excl = fid->excl;
       +
       +        t = time(0L);
       +        vtLock(ebox.lock);
       +        if(excl->time < t || excl->fsys != fid->fsys){
       +                vtUnlock(ebox.lock);
       +                vtSetError("exclusive lock broken");
       +                return 0;
       +        }
       +        excl->time = t+LifeTime;
       +        vtUnlock(ebox.lock);
       +
       +        return 1;
       +}
       +
       +void
       +exclFree(Fid* fid)
       +{
       +        Excl *excl;
       +
       +        if((excl = fid->excl) == nil)
       +                return;
       +        fid->excl = nil;
       +
       +        vtLock(ebox.lock);
       +        if(excl->prev != nil)
       +                excl->prev->next = excl->next;
       +        else
       +                ebox.head = excl->next;
       +        if(excl->next != nil)
       +                excl->next->prev = excl->prev;
       +        else
       +                ebox.tail = excl->prev;
       +        vtUnlock(ebox.lock);
       +
       +        vtMemFree(excl);
       +}
       +
       +void
       +exclInit(void)
       +{
       +        ebox.lock = vtLockAlloc();
       +}
 (DIR) diff --git a/src/cmd/fossil/9fid.c b/src/cmd/fossil/9fid.c
       t@@ -0,0 +1,304 @@
       +#include "stdinc.h"
       +
       +#include "9.h"
       +
       +static struct {
       +        VtLock*        lock;
       +
       +        Fid*        free;
       +        int        nfree;
       +        int        inuse;
       +} fbox;
       +
       +static void
       +fidLock(Fid* fid, int flags)
       +{
       +        if(flags & FidFWlock){
       +                vtLock(fid->lock);
       +                fid->flags = flags;
       +        }
       +        else
       +                vtRLock(fid->lock);
       +
       +        /*
       +         * Callers of file* routines are expected to lock fsys->fs->elk
       +         * before making any calls in order to make sure the epoch doesn't
       +         * change underfoot. With the exception of Tversion and Tattach,
       +         * that implies all 9P functions need to lock on entry and unlock
       +         * on exit. Fortunately, the general case is the 9P functions do
       +         * fidGet on entry and fidPut on exit, so this is a convenient place
       +         * to do the locking.
       +         * No fsys->fs->elk lock is required if the fid is being created
       +         * (Tauth, Tattach and Twalk). FidFCreate is always accompanied by
       +         * FidFWlock so the setting and testing of FidFCreate here and in
       +         * fidUnlock below is always done under fid->lock.
       +         * A side effect is that fidFree is called with the fid locked, and
       +         * must call fidUnlock only after it has disposed of any File
       +         * resources still held.
       +         */
       +        if(!(flags & FidFCreate))
       +                fsysFsRlock(fid->fsys);
       +}
       +
       +static void
       +fidUnlock(Fid* fid)
       +{
       +        if(!(fid->flags & FidFCreate))
       +                fsysFsRUnlock(fid->fsys);
       +        if(fid->flags & FidFWlock){
       +                fid->flags = 0;
       +                vtUnlock(fid->lock);
       +                return;
       +        }
       +        vtRUnlock(fid->lock);
       +}
       +
       +static Fid*
       +fidAlloc(void)
       +{
       +        Fid *fid;
       +
       +        vtLock(fbox.lock);
       +        if(fbox.nfree > 0){
       +                fid = fbox.free;
       +                fbox.free = fid->hash;
       +                fbox.nfree--;
       +        }
       +        else{
       +                fid = vtMemAllocZ(sizeof(Fid));
       +                fid->lock = vtLockAlloc();
       +                fid->alock = vtLockAlloc();
       +        }
       +        fbox.inuse++;
       +        vtUnlock(fbox.lock);
       +
       +        fid->con = nil;
       +        fid->fidno = NOFID;
       +        fid->ref = 0;
       +        fid->flags = 0;
       +        fid->open = FidOCreate;
       +        assert(fid->fsys == nil);
       +        assert(fid->file == nil);
       +        fid->qid = (Qid){0, 0, 0};
       +        assert(fid->uid == nil);
       +        assert(fid->uname == nil);
       +        assert(fid->db == nil);
       +        assert(fid->excl == nil);
       +        assert(fid->rpc == nil);
       +        assert(fid->cuname == nil);
       +        fid->hash = fid->next = fid->prev = nil;
       +
       +        return fid;
       +}
       +
       +static void
       +fidFree(Fid* fid)
       +{
       +        if(fid->file != nil){
       +                fileDecRef(fid->file);
       +                fid->file = nil;
       +        }
       +        if(fid->db != nil){
       +                dirBufFree(fid->db);
       +                fid->db = nil;
       +        }
       +        fidUnlock(fid);
       +
       +        if(fid->uid != nil){
       +                vtMemFree(fid->uid);
       +                fid->uid = nil;
       +        }
       +        if(fid->uname != nil){
       +                vtMemFree(fid->uname);
       +                fid->uname = nil;
       +        }
       +        if(fid->excl != nil)
       +                exclFree(fid);
       +        if(fid->rpc != nil){
       +                close(fid->rpc->afd);
       +                auth_freerpc(fid->rpc);
       +                fid->rpc = nil;
       +        }
       +        if(fid->fsys != nil){
       +                fsysPut(fid->fsys);
       +                fid->fsys = nil;
       +        }
       +        if(fid->cuname != nil){
       +                vtMemFree(fid->cuname);
       +                fid->cuname = nil;
       +        }
       +
       +        vtLock(fbox.lock);
       +        fbox.inuse--;
       +        if(fbox.nfree < 10){
       +                fid->hash = fbox.free;
       +                fbox.free = fid;
       +                fbox.nfree++;
       +        }
       +        else{
       +                vtLockFree(fid->alock);
       +                vtLockFree(fid->lock);
       +                vtMemFree(fid);
       +        }
       +        vtUnlock(fbox.lock);
       +}
       +
       +static void
       +fidUnHash(Fid* fid)
       +{
       +        Fid *fp, **hash;
       +
       +        assert(fid->ref == 0);
       +
       +        hash = &fid->con->fidhash[fid->fidno % NFidHash];
       +        for(fp = *hash; fp != nil; fp = fp->hash){
       +                if(fp == fid){
       +                        *hash = fp->hash;
       +                        break;
       +                }
       +                hash = &fp->hash;
       +        }
       +        assert(fp == fid);
       +
       +        if(fid->prev != nil)
       +                fid->prev->next = fid->next;
       +        else
       +                fid->con->fhead = fid->next;
       +        if(fid->next != nil)
       +                fid->next->prev = fid->prev;
       +        else
       +                fid->con->ftail = fid->prev;
       +        fid->prev = fid->next = nil;
       +
       +        fid->con->nfid--;
       +}
       +
       +Fid*
       +fidGet(Con* con, u32int fidno, int flags)
       +{
       +        Fid *fid, **hash;
       +
       +        if(fidno == NOFID)
       +                return nil;
       +
       +        hash = &con->fidhash[fidno % NFidHash];
       +        vtLock(con->fidlock);
       +        for(fid = *hash; fid != nil; fid = fid->hash){
       +                if(fid->fidno != fidno)
       +                        continue;
       +
       +                /*
       +                 * Already in use is an error
       +                 * when called from attach, clone or walk.
       +                 */
       +                if(flags & FidFCreate){
       +                        vtUnlock(con->fidlock);
       +                        vtSetError("%s: fid 0x%ud in use", argv0, fidno);
       +                        return nil;
       +                }
       +                fid->ref++;
       +                vtUnlock(con->fidlock);
       +
       +                fidLock(fid, flags);
       +                if((fid->open & FidOCreate) || fid->fidno == NOFID){
       +                        fidPut(fid);
       +                        vtSetError("%s: fid invalid", argv0);
       +                        return nil;
       +                }
       +                return fid;
       +        }
       +
       +        if((flags & FidFCreate) && (fid = fidAlloc()) != nil){
       +                assert(flags & FidFWlock);
       +                fid->con = con;
       +                fid->fidno = fidno;
       +                fid->ref = 1;
       +
       +                fid->hash = *hash;
       +                *hash = fid;
       +                if(con->ftail != nil){
       +                        fid->prev = con->ftail;
       +                        con->ftail->next = fid;
       +                }
       +                else{
       +                        con->fhead = fid;
       +                        fid->prev = nil;
       +                }
       +                con->ftail = fid;
       +                fid->next = nil;
       +
       +                con->nfid++;
       +                vtUnlock(con->fidlock);
       +
       +                /*
       +                 * The FidOCreate flag is used to prevent any
       +                 * accidental access to the Fid between unlocking the
       +                 * hash and acquiring the Fid lock for return.
       +                 */
       +                fidLock(fid, flags);
       +                fid->open &= ~FidOCreate;
       +                return fid;
       +        }
       +        vtUnlock(con->fidlock);
       +
       +        vtSetError("%s: fid not found", argv0);
       +        return nil;
       +}
       +
       +void
       +fidPut(Fid* fid)
       +{
       +        vtLock(fid->con->fidlock);
       +        assert(fid->ref > 0);
       +        fid->ref--;
       +        vtUnlock(fid->con->fidlock);
       +
       +        if(fid->ref == 0 && fid->fidno == NOFID){
       +                fidFree(fid);
       +                return;
       +        }
       +        fidUnlock(fid);
       +}
       +
       +void
       +fidClunk(Fid* fid)
       +{
       +        assert(fid->flags & FidFWlock);
       +
       +        vtLock(fid->con->fidlock);
       +        assert(fid->ref > 0);
       +        fid->ref--;
       +        fidUnHash(fid);
       +        fid->fidno = NOFID;
       +        vtUnlock(fid->con->fidlock);
       +
       +        if(fid->ref > 0){
       +                /* not reached - fidUnHash requires ref == 0 */
       +                fidUnlock(fid);
       +                return;
       +        }
       +        fidFree(fid);
       +}
       +
       +void
       +fidClunkAll(Con* con)
       +{
       +        Fid *fid;
       +        u32int fidno;
       +
       +        vtLock(con->fidlock);
       +        while(con->fhead != nil){
       +                fidno = con->fhead->fidno;
       +                vtUnlock(con->fidlock);
       +                if((fid = fidGet(con, fidno, FidFWlock)) != nil)
       +                        fidClunk(fid);
       +                vtLock(con->fidlock);
       +        }
       +        vtUnlock(con->fidlock);
       +}
       +
       +void
       +fidInit(void)
       +{
       +        fbox.lock = vtLockAlloc();
       +}
 (DIR) diff --git a/src/cmd/fossil/9fsys.c b/src/cmd/fossil/9fsys.c
       t@@ -0,0 +1,1896 @@
       +#include "stdinc.h"
       +#include <bio.h>
       +#include "dat.h"
       +#include "fns.h"
       +#include "9.h"
       +
       +struct Fsys {
       +        VtLock* lock;
       +
       +        char*        name;                /* copy here & Fs to ease error reporting */
       +        char*        dev;
       +        char*        venti;
       +
       +        Fs*        fs;
       +        VtSession* session;
       +        int        ref;
       +
       +        int        noauth;
       +        int        noperm;
       +        int        wstatallow;
       +
       +        Fsys*        next;
       +};
       +
       +int mempcnt;                        /* from fossil.c */
       +
       +int        fsGetBlockSize(Fs *fs);
       +
       +static struct {
       +        VtLock*        lock;
       +        Fsys*        head;
       +        Fsys*        tail;
       +
       +        char*        curfsys;
       +} sbox;
       +
       +static char *_argv0;
       +#define argv0 _argv0
       +
       +static char FsysAll[] = "all";
       +
       +static char EFsysBusy[] = "fsys: '%s' busy";
       +static char EFsysExists[] = "fsys: '%s' already exists";
       +static char EFsysNoCurrent[] = "fsys: no current fsys";
       +static char EFsysNotFound[] = "fsys: '%s' not found";
       +static char EFsysNotOpen[] = "fsys: '%s' not open";
       +
       +static char *
       +ventihost(char *host)
       +{
       +        if(host != nil)
       +                return vtStrDup(host);
       +        host = getenv("venti");
       +        if(host == nil)
       +                host = vtStrDup("$venti");
       +        return host;
       +}
       +
       +static void
       +prventihost(char *host)
       +{
       +        char *vh;
       +
       +        vh = ventihost(host);
       +        fprint(2, "%s: dialing venti at %s\n",
       +                argv0, netmkaddr(vh, 0, "venti"));
       +        free(vh);
       +}
       +
       +static VtSession *
       +myDial(char *host, int canfail)
       +{
       +        prventihost(host);
       +        return vtDial(host, canfail);
       +}
       +
       +static int
       +myRedial(VtSession *z, char *host)
       +{
       +        prventihost(host);
       +        return vtRedial(z, host);
       +}
       +
       +static Fsys*
       +_fsysGet(char* name)
       +{
       +        Fsys *fsys;
       +
       +        if(name == nil || name[0] == '\0')
       +                name = "main";
       +
       +        vtRLock(sbox.lock);
       +        for(fsys = sbox.head; fsys != nil; fsys = fsys->next){
       +                if(strcmp(name, fsys->name) == 0){
       +                        fsys->ref++;
       +                        break;
       +                }
       +        }
       +        vtRUnlock(sbox.lock);
       +        if(fsys == nil)
       +                vtSetError(EFsysNotFound, name);
       +        return fsys;
       +}
       +
       +static int
       +cmdPrintConfig(int argc, char* argv[])
       +{
       +        Fsys *fsys;
       +        char *usage = "usage: printconfig";
       +
       +        ARGBEGIN{
       +        default:
       +                return cliError(usage);
       +        }ARGEND
       +
       +        if(argc)
       +                return cliError(usage);
       +
       +        vtRLock(sbox.lock);
       +        for(fsys = sbox.head; fsys != nil; fsys = fsys->next){
       +                consPrint("\tfsys %s config %s\n", fsys->name, fsys->dev);
       +                if(fsys->venti && fsys->venti[0])
       +                        consPrint("\tfsys %s venti %q\n", fsys->name,
       +                                fsys->venti);
       +        }
       +        vtRUnlock(sbox.lock);
       +        return 1;
       +}
       +
       +Fsys*
       +fsysGet(char* name)
       +{
       +        Fsys *fsys;
       +
       +        if((fsys = _fsysGet(name)) == nil)
       +                return nil;
       +
       +        vtLock(fsys->lock);
       +        if(fsys->fs == nil){
       +                vtSetError(EFsysNotOpen, fsys->name);
       +                vtUnlock(fsys->lock);
       +                fsysPut(fsys);
       +                return nil;
       +        }
       +        vtUnlock(fsys->lock);
       +
       +        return fsys;
       +}
       +
       +char*
       +fsysGetName(Fsys* fsys)
       +{
       +        return fsys->name;
       +}
       +
       +Fsys*
       +fsysIncRef(Fsys* fsys)
       +{
       +        vtLock(sbox.lock);
       +        fsys->ref++;
       +        vtUnlock(sbox.lock);
       +
       +        return fsys;
       +}
       +
       +void
       +fsysPut(Fsys* fsys)
       +{
       +        vtLock(sbox.lock);
       +        assert(fsys->ref > 0);
       +        fsys->ref--;
       +        vtUnlock(sbox.lock);
       +}
       +
       +Fs*
       +fsysGetFs(Fsys* fsys)
       +{
       +        assert(fsys != nil && fsys->fs != nil);
       +
       +        return fsys->fs;
       +}
       +
       +void
       +fsysFsRlock(Fsys* fsys)
       +{
       +        vtRLock(fsys->fs->elk);
       +}
       +
       +void
       +fsysFsRUnlock(Fsys* fsys)
       +{
       +        vtRUnlock(fsys->fs->elk);
       +}
       +
       +int
       +fsysNoAuthCheck(Fsys* fsys)
       +{
       +        return fsys->noauth;
       +}
       +
       +int
       +fsysNoPermCheck(Fsys* fsys)
       +{
       +        return fsys->noperm;
       +}
       +
       +int
       +fsysWstatAllow(Fsys* fsys)
       +{
       +        return fsys->wstatallow;
       +}
       +
       +static char modechars[] = "YUGalLdHSATs";
       +static ulong modebits[] = {
       +        ModeSticky,
       +        ModeSetUid,
       +        ModeSetGid,
       +        ModeAppend,
       +        ModeExclusive,
       +        ModeLink,
       +        ModeDir,
       +        ModeHidden,
       +        ModeSystem,
       +        ModeArchive,
       +        ModeTemporary,
       +        ModeSnapshot,
       +        0
       +};
       +
       +char*
       +fsysModeString(ulong mode, char *buf)
       +{
       +        int i;
       +        char *p;
       +
       +        p = buf;
       +        for(i=0; modebits[i]; i++)
       +                if(mode & modebits[i])
       +                        *p++ = modechars[i];
       +        sprint(p, "%luo", mode&0777);
       +        return buf;
       +}
       +
       +int
       +fsysParseMode(char* s, ulong* mode)
       +{
       +        ulong x, y;
       +        char *p;
       +
       +        x = 0;
       +        for(; *s < '0' || *s > '9'; s++){
       +                if(*s == 0)
       +                        return 0;
       +                p = strchr(modechars, *s);
       +                if(p == nil)
       +                        return 0;
       +                x |= modebits[p-modechars];
       +        }
       +        y = strtoul(s, &p, 8);
       +        if(*p != '\0' || y > 0777)
       +                return 0;
       +        *mode = x|y;
       +        return 1;
       +}
       +
       +File*
       +fsysGetRoot(Fsys* fsys, char* name)
       +{
       +        File *root, *sub;
       +
       +        assert(fsys != nil && fsys->fs != nil);
       +
       +        root = fsGetRoot(fsys->fs);
       +        if(name == nil || strcmp(name, "") == 0)
       +                return root;
       +
       +        sub = fileWalk(root, name);
       +        fileDecRef(root);
       +
       +        return sub;
       +}
       +
       +static Fsys*
       +fsysAlloc(char* name, char* dev)
       +{
       +        Fsys *fsys;
       +
       +        vtLock(sbox.lock);
       +        for(fsys = sbox.head; fsys != nil; fsys = fsys->next){
       +                if(strcmp(fsys->name, name) != 0)
       +                        continue;
       +                vtSetError(EFsysExists, name);
       +                vtUnlock(sbox.lock);
       +                return nil;
       +        }
       +
       +        fsys = vtMemAllocZ(sizeof(Fsys));
       +        fsys->lock = vtLockAlloc();
       +        fsys->name = vtStrDup(name);
       +        fsys->dev = vtStrDup(dev);
       +
       +        fsys->ref = 1;
       +
       +        if(sbox.tail != nil)
       +                sbox.tail->next = fsys;
       +        else
       +                sbox.head = fsys;
       +        sbox.tail = fsys;
       +        vtUnlock(sbox.lock);
       +
       +        return fsys;
       +}
       +
       +static int
       +fsysClose(Fsys* fsys, int argc, char* argv[])
       +{
       +        char *usage = "usage: [fsys name] close";
       +
       +        ARGBEGIN{
       +        default:
       +                return cliError(usage);
       +        }ARGEND
       +        if(argc)
       +                return cliError(usage);
       +
       +        return cliError("close isn't working yet; halt %s and then kill fossil",
       +                fsys->name);
       +
       +        /*
       +         * Oooh. This could be hard. What if fsys->ref != 1?
       +         * Also, fsClose() either does the job or panics, can we
       +         * gracefully detect it's still busy?
       +         *
       +         * More thought and care needed here.
       +        fsClose(fsys->fs);
       +        fsys->fs = nil;
       +        vtClose(fsys->session);
       +        fsys->session = nil;
       +
       +        if(sbox.curfsys != nil && strcmp(fsys->name, sbox.curfsys) == 0){
       +                sbox.curfsys = nil;
       +                consPrompt(nil);
       +        }
       +
       +        return 1;
       +         */
       +}
       +
       +static int
       +fsysVac(Fsys* fsys, int argc, char* argv[])
       +{
       +        uchar score[VtScoreSize];
       +        char *usage = "usage: [fsys name] vac path";
       +
       +        ARGBEGIN{
       +        default:
       +                return cliError(usage);
       +        }ARGEND
       +        if(argc != 1)
       +                return cliError(usage);
       +
       +        if(!fsVac(fsys->fs, argv[0], score))
       +                return 0;
       +
       +        consPrint("vac:%V\n", score);
       +        return 1;
       +}
       +
       +static int
       +fsysSnap(Fsys* fsys, int argc, char* argv[])
       +{
       +        int doarchive;
       +        char *usage = "usage: [fsys name] snap [-a] [-s /active] [-d /archive/yyyy/mmmm]";
       +        char *src, *dst;
       +
       +        src = nil;
       +        dst = nil;
       +        doarchive = 0;
       +        ARGBEGIN{
       +        default:
       +                return cliError(usage);
       +        case 'a':
       +                doarchive = 1;
       +                break;
       +        case 'd':
       +                if((dst = ARGF()) == nil)
       +                        return cliError(usage);
       +                break;
       +        case 's':
       +                if((src = ARGF()) == nil)
       +                        return cliError(usage);
       +                break;
       +        }ARGEND
       +        if(argc)
       +                return cliError(usage);
       +
       +        if(!fsSnapshot(fsys->fs, src, dst, doarchive))
       +                return 0;
       +
       +        return 1;
       +}
       +
       +static int
       +fsysSnapClean(Fsys *fsys, int argc, char* argv[])
       +{
       +        u32int arch, snap, life;
       +        char *usage = "usage: [fsys name] snapclean [maxminutes]\n";
       +
       +        ARGBEGIN{
       +        default:
       +                return cliError(usage);
       +        }ARGEND
       +
       +        if(argc > 1)
       +                return cliError(usage);
       +        if(argc == 1)
       +                life = atoi(argv[0]);
       +        else
       +                snapGetTimes(fsys->fs->snap, &arch, &snap, &life);
       +
       +        fsSnapshotCleanup(fsys->fs, life);
       +        return 1;
       +}
       +
       +static int
       +fsysSnapTime(Fsys* fsys, int argc, char* argv[])
       +{
       +        char buf[128], *x;
       +        int hh, mm, changed;
       +        u32int arch, snap, life;
       +        char *usage = "usage: [fsys name] snaptime [-a hhmm] [-s snapminutes] [-t maxminutes]";
       +
       +        changed = 0;
       +        snapGetTimes(fsys->fs->snap, &arch, &snap, &life);
       +        ARGBEGIN{
       +        case 'a':
       +                changed = 1;
       +                x = ARGF();
       +                if(x == nil)
       +                        return cliError(usage);
       +                if(strcmp(x, "none") == 0){
       +                        arch = ~(u32int)0;
       +                        break;
       +                }
       +                if(strlen(x) != 4 || strspn(x, "0123456789") != 4)
       +                        return cliError(usage);
       +                hh = (x[0]-'0')*10 + x[1]-'0';
       +                mm = (x[2]-'0')*10 + x[3]-'0';
       +                if(hh >= 24 || mm >= 60)
       +                        return cliError(usage);
       +                arch = hh*60+mm;
       +                break;
       +        case 's':
       +                changed = 1;
       +                x = ARGF();
       +                if(x == nil)
       +                        return cliError(usage);
       +                if(strcmp(x, "none") == 0){
       +                        snap = ~(u32int)0;
       +                        break;
       +                }
       +                snap = atoi(x);
       +                break;
       +        case 't':
       +                changed = 1;
       +                x = ARGF();
       +                if(x == nil)
       +                        return cliError(usage);
       +                if(strcmp(x, "none") == 0){
       +                        life = ~(u32int)0;
       +                        break;
       +                }
       +                life = atoi(x);
       +                break;
       +        default:
       +                return cliError(usage);
       +        }ARGEND
       +        if(argc > 0)
       +                return cliError(usage);
       +
       +        if(changed){
       +                snapSetTimes(fsys->fs->snap, arch, snap, life);
       +                return 1;
       +        }
       +        snapGetTimes(fsys->fs->snap, &arch, &snap, &life);
       +        if(arch != ~(u32int)0)
       +                sprint(buf, "-a %02d%02d", arch/60, arch%60);
       +        else
       +                sprint(buf, "-a none");
       +        if(snap != ~(u32int)0)
       +                sprint(buf+strlen(buf), " -s %d", snap);
       +        else
       +                sprint(buf+strlen(buf), " -s none");
       +        if(life != ~(u32int)0)
       +                sprint(buf+strlen(buf), " -t %ud", life);
       +        else
       +                sprint(buf+strlen(buf), " -t none");
       +        consPrint("\tsnaptime %s\n", buf);
       +        return 1;
       +}
       +
       +static int
       +fsysSync(Fsys* fsys, int argc, char* argv[])
       +{
       +        char *usage = "usage: [fsys name] sync";
       +        int n;
       +        
       +        ARGBEGIN{
       +        default:
       +                return cliError(usage);
       +        }ARGEND
       +        if(argc > 0)
       +                return cliError(usage);
       +
       +        n = cacheDirty(fsys->fs->cache);
       +        fsSync(fsys->fs);
       +        consPrint("\t%s sync: wrote %d blocks\n", fsys->name, n);
       +        return 1;
       +}
       +
       +static int
       +fsysHalt(Fsys *fsys, int argc, char* argv[])
       +{
       +        char *usage = "usage: [fsys name] halt";
       +
       +        ARGBEGIN{
       +        default:
       +                return cliError(usage);
       +        }ARGEND
       +        if(argc > 0)
       +                return cliError(usage);
       +
       +        fsHalt(fsys->fs);
       +        return 1;
       +}
       +
       +static int
       +fsysUnhalt(Fsys *fsys, int argc, char* argv[])
       +{
       +        char *usage = "usage: [fsys name] unhalt";
       +
       +        ARGBEGIN{
       +        default:
       +                return cliError(usage);
       +        }ARGEND
       +        if(argc > 0)
       +                return cliError(usage);
       +
       +        if(!fsys->fs->halted)
       +                return cliError("file system %s not halted", fsys->name);
       +
       +        fsUnhalt(fsys->fs);
       +        return 1;
       +}
       +
       +static int
       +fsysRemove(Fsys* fsys, int argc, char* argv[])
       +{
       +        File *file;
       +        char *usage = "usage: [fsys name] remove path ...";
       +
       +        ARGBEGIN{
       +        default:
       +                return cliError(usage);
       +        }ARGEND
       +        if(argc == 0)
       +                return cliError(usage);
       +
       +        vtRLock(fsys->fs->elk);
       +        while(argc > 0){
       +                if((file = fileOpen(fsys->fs, argv[0])) == nil)
       +                        consPrint("%s: %R\n", argv[0]);
       +                else{
       +                        if(!fileRemove(file, uidadm))
       +                                consPrint("%s: %R\n", argv[0]);
       +                        fileDecRef(file);
       +                }
       +                argc--;
       +                argv++;
       +        }
       +        vtRUnlock(fsys->fs->elk);
       +
       +        return 1;
       +}
       +
       +static int
       +fsysClri(Fsys* fsys, int argc, char* argv[])
       +{
       +        char *usage = "usage: [fsys name] clri path ...";
       +
       +        ARGBEGIN{
       +        default:
       +                return cliError(usage);
       +        }ARGEND
       +        if(argc == 0)
       +                return cliError(usage);
       +
       +        vtRLock(fsys->fs->elk);
       +        while(argc > 0){
       +                if(!fileClriPath(fsys->fs, argv[0], uidadm))
       +                        consPrint("clri %s: %R\n", argv[0]);
       +                argc--;
       +                argv++;
       +        }
       +        vtRUnlock(fsys->fs->elk);
       +
       +        return 1;
       +}
       +
       +/*
       + * Inspect and edit the labels for blocks on disk.
       + */
       +static int
       +fsysLabel(Fsys* fsys, int argc, char* argv[])
       +{
       +        Fs *fs;
       +        Label l;
       +        int n, r;
       +        u32int addr;
       +        Block *b, *bb;
       +        char *usage = "usage: [fsys name] label addr [type state epoch epochClose tag]";
       +
       +        ARGBEGIN{
       +        default:
       +                return cliError(usage);
       +        }ARGEND
       +        if(argc != 1 && argc != 6)
       +                return cliError(usage);
       +
       +        r = 0;
       +        vtRLock(fsys->fs->elk);
       +
       +        fs = fsys->fs;
       +        addr = strtoul(argv[0], 0, 0);
       +        b = cacheLocal(fs->cache, PartData, addr, OReadOnly);
       +        if(b == nil)
       +                goto Out0;
       +
       +        l = b->l;
       +        consPrint("%slabel %#ux %ud %ud %ud %ud %#x\n",
       +                argc==6 ? "old: " : "", addr, l.type, l.state,
       +                l.epoch, l.epochClose, l.tag);
       +
       +        if(argc == 6){
       +                if(strcmp(argv[1], "-") != 0)
       +                        l.type = atoi(argv[1]);
       +                if(strcmp(argv[2], "-") != 0)
       +                        l.state = atoi(argv[2]);
       +                if(strcmp(argv[3], "-") != 0)
       +                        l.epoch = strtoul(argv[3], 0, 0);
       +                if(strcmp(argv[4], "-") != 0)
       +                        l.epochClose = strtoul(argv[4], 0, 0);
       +                if(strcmp(argv[5], "-") != 0)
       +                        l.tag = strtoul(argv[5], 0, 0);
       +
       +                consPrint("new: label %#ux %ud %ud %ud %ud %#x\n",
       +                        addr, l.type, l.state, l.epoch, l.epochClose, l.tag);
       +                bb = _blockSetLabel(b, &l);
       +                if(bb == nil)
       +                        goto Out1;
       +                n = 0;
       +                for(;;){
       +                        if(blockWrite(bb, Waitlock)){
       +                                while(bb->iostate != BioClean){
       +                                        assert(bb->iostate == BioWriting);
       +                                        vtSleep(bb->ioready);
       +                                }
       +                                break;
       +                        }
       +                        consPrint("blockWrite: %R\n");
       +                        if(n++ >= 5){
       +                                consPrint("giving up\n");
       +                                break;
       +                        }
       +                        sleep(5*1000);
       +                }
       +                blockPut(bb);
       +        }
       +        r = 1;
       +Out1:
       +        blockPut(b);
       +Out0:
       +        vtRUnlock(fs->elk);
       +
       +        return r;
       +}
       +
       +/*
       + * Inspect and edit the blocks on disk.
       + */
       +static int
       +fsysBlock(Fsys* fsys, int argc, char* argv[])
       +{
       +        Fs *fs;
       +        char *s;
       +        Block *b;
       +        uchar *buf;
       +        u32int addr;
       +        int c, count, i, offset;
       +        char *usage = "usage: [fsys name] block addr offset [count [data]]";
       +
       +        ARGBEGIN{
       +        default:
       +                return cliError(usage);
       +        }ARGEND
       +        if(argc < 2 || argc > 4)
       +                return cliError(usage);
       +
       +        fs = fsys->fs;
       +        addr = strtoul(argv[0], 0, 0);
       +        offset = strtoul(argv[1], 0, 0);
       +        if(offset < 0 || offset >= fs->blockSize){
       +                vtSetError("bad offset");
       +                return 0;
       +        }
       +        if(argc > 2)
       +                count = strtoul(argv[2], 0, 0);
       +        else
       +                count = 100000000;
       +        if(offset+count > fs->blockSize)
       +                count = fs->blockSize - count;
       +
       +        vtRLock(fs->elk);
       +
       +        b = cacheLocal(fs->cache, PartData, addr, argc==4 ? OReadWrite : OReadOnly);
       +        if(b == nil){
       +                vtSetError("cacheLocal %#ux: %R", addr);
       +                vtRUnlock(fs->elk);
       +                return 0;
       +        }
       +
       +        consPrint("\t%sblock %#ux %ud %ud %.*H\n",
       +                argc==4 ? "old: " : "", addr, offset, count, count, b->data+offset);
       +
       +        if(argc == 4){
       +                s = argv[3];
       +                if(strlen(s) != 2*count){
       +                        vtSetError("bad data count");
       +                        goto Out;
       +                }
       +                buf = vtMemAllocZ(count);
       +                for(i = 0; i < count*2; i++){
       +                        if(s[i] >= '0' && s[i] <= '9')
       +                                c = s[i] - '0';
       +                        else if(s[i] >= 'a' && s[i] <= 'f')
       +                                c = s[i] - 'a' + 10;
       +                        else if(s[i] >= 'A' && s[i] <= 'F')
       +                                c = s[i] - 'A' + 10;
       +                        else{
       +                                vtSetError("bad hex");
       +                                vtMemFree(buf);
       +                                goto Out;
       +                        }
       +                        if((i & 1) == 0)
       +                                c <<= 4;
       +                        buf[i>>1] |= c;
       +                }
       +                memmove(b->data+offset, buf, count);
       +                consPrint("\tnew: block %#ux %ud %ud %.*H\n",
       +                        addr, offset, count, count, b->data+offset);
       +                blockDirty(b);
       +        }
       +
       +Out:
       +        blockPut(b);
       +        vtRUnlock(fs->elk);
       +
       +        return 1;
       +}
       +
       +/*
       + * Free a disk block.
       + */
       +static int
       +fsysBfree(Fsys* fsys, int argc, char* argv[])
       +{
       +        Fs *fs;
       +        Label l;
       +        char *p;
       +        Block *b;
       +        u32int addr;
       +        char *usage = "usage: [fsys name] bfree addr ...";
       +
       +        ARGBEGIN{
       +        default:
       +                return cliError(usage);
       +        }ARGEND
       +        if(argc == 0)
       +                return cliError(usage);
       +
       +        fs = fsys->fs;
       +        vtRLock(fs->elk);
       +        while(argc > 0){
       +                addr = strtoul(argv[0], &p, 0);
       +                if(*p != '\0'){
       +                        consPrint("bad address - '%ud'\n", addr);
       +                        /* syntax error; let's stop */
       +                        vtRUnlock(fs->elk);
       +                        return 0;
       +                }
       +                b = cacheLocal(fs->cache, PartData, addr, OReadOnly);
       +                if(b == nil){
       +                        consPrint("loading %#ux: %R\n", addr);
       +                        continue;
       +                }
       +                l = b->l;
       +                if(l.state == BsFree)
       +                        consPrint("%#ux is already free\n", addr);
       +                else{
       +                        consPrint("label %#ux %ud %ud %ud %ud %#x\n",
       +                                addr, l.type, l.state, l.epoch, l.epochClose, l.tag);
       +                        l.state = BsFree;
       +                        l.type = BtMax;
       +                        l.tag = 0;
       +                        l.epoch = 0;
       +                        l.epochClose = 0;
       +                        if(!blockSetLabel(b, &l, 0))
       +                                consPrint("freeing %#ux: %R\n", addr);
       +                }
       +                blockPut(b);
       +                argc--;
       +                argv++;
       +        }
       +        vtRUnlock(fs->elk);
       +
       +        return 1;
       +}
       +
       +static int
       +fsysDf(Fsys *fsys, int argc, char* argv[])
       +{
       +        char *usage = "usage: [fsys name] df";
       +        u32int used, tot, bsize;
       +        Fs *fs;
       +
       +        ARGBEGIN{
       +        default:
       +                return cliError(usage);
       +        }ARGEND
       +        if(argc != 0)
       +                return cliError(usage);
       +
       +        fs = fsys->fs;
       +        cacheCountUsed(fs->cache, fs->elo, &used, &tot, &bsize);
       +        consPrint("\t%s: %,llud used + %,llud free = %,llud (%.1f%% used)\n",
       +                fsys->name, used*(vlong)bsize, (tot-used)*(vlong)bsize,
       +                tot*(vlong)bsize, used*100.0/tot);
       +        return 1;
       +}
       +
       +/*
       + * Zero an entry or a pointer.
       + */
       +static int
       +fsysClrep(Fsys* fsys, int argc, char* argv[], int ch)
       +{
       +        Fs *fs;
       +        Entry e;
       +        Block *b;
       +        u32int addr;
       +        int i, max, offset, sz;
       +        uchar zero[VtEntrySize];
       +        char *usage = "usage: [fsys name] clr%c addr offset ...";
       +
       +        ARGBEGIN{
       +        default:
       +                return cliError(usage, ch);
       +        }ARGEND
       +        if(argc < 2)
       +                return cliError(usage, ch);
       +
       +        fs = fsys->fs;
       +        vtRLock(fsys->fs->elk);
       +
       +        addr = strtoul(argv[0], 0, 0);
       +        b = cacheLocal(fs->cache, PartData, addr, argc==4 ? OReadWrite : OReadOnly);
       +        if(b == nil){
       +                vtSetError("cacheLocal %#ux: %R", addr);
       +        Err:
       +                vtRUnlock(fsys->fs->elk);
       +                return 0;
       +        }
       +
       +        switch(ch){
       +        default:
       +                vtSetError("clrep");
       +                goto Err;
       +        case 'e':
       +                if(b->l.type != BtDir){
       +                        vtSetError("wrong block type");
       +                        goto Err;
       +                }
       +                sz = VtEntrySize;
       +                memset(&e, 0, sizeof e);
       +                entryPack(&e, zero, 0);
       +                break;
       +        case 'p':
       +                if(b->l.type == BtDir || b->l.type == BtData){
       +                        vtSetError("wrong block type");
       +                        goto Err;
       +                }
       +                sz = VtScoreSize;
       +                memmove(zero, vtZeroScore, VtScoreSize);
       +                break;
       +        }
       +        max = fs->blockSize/sz;
       +
       +        for(i = 1; i < argc; i++){
       +                offset = atoi(argv[i]);
       +                if(offset >= max){
       +                        consPrint("\toffset %d too large (>= %d)\n", i, max);
       +                        continue;
       +                }
       +                consPrint("\tblock %#ux %d %d %.*H\n", addr, offset*sz, sz, sz, b->data+offset*sz);
       +                memmove(b->data+offset*sz, zero, sz);
       +        }
       +        blockDirty(b);
       +        blockPut(b);
       +        vtRUnlock(fsys->fs->elk);
       +
       +        return 1;
       +}
       +
       +static int
       +fsysClre(Fsys* fsys, int argc, char* argv[])
       +{
       +        return fsysClrep(fsys, argc, argv, 'e');
       +}
       +
       +static int
       +fsysClrp(Fsys* fsys, int argc, char* argv[])
       +{
       +        return fsysClrep(fsys, argc, argv, 'p');
       +}
       +
       +static int
       +fsysEsearch1(File* f, char* s, u32int elo)
       +{
       +        int n, r;
       +        DirEntry de;
       +        DirEntryEnum *dee;
       +        File *ff;
       +        Entry e, ee;
       +        char *t;
       +
       +        dee = deeOpen(f);
       +        if(dee == nil)
       +                return 0;
       +
       +        n = 0;
       +        for(;;){
       +                r = deeRead(dee, &de);
       +                if(r < 0){
       +                        consPrint("\tdeeRead %s/%s: %R\n", s, de.elem);
       +                        break;
       +                }
       +                if(r == 0)
       +                        break;
       +                if(de.mode & ModeSnapshot){
       +                        if((ff = fileWalk(f, de.elem)) == nil)
       +                                consPrint("\tcannot walk %s/%s: %R\n", s, de.elem);
       +                        else{
       +                                if(!fileGetSources(ff, &e, &ee))
       +                                        consPrint("\tcannot get sources for %s/%s: %R\n", s, de.elem);
       +                                else if(e.snap != 0 && e.snap < elo){
       +                                        consPrint("\t%ud\tclri %s/%s\n", e.snap, s, de.elem);
       +                                        n++;
       +                                }
       +                                fileDecRef(ff);
       +                        }
       +                }
       +                else if(de.mode & ModeDir){
       +                        if((ff = fileWalk(f, de.elem)) == nil)
       +                                consPrint("\tcannot walk %s/%s: %R\n", s, de.elem);
       +                        else{
       +                                t = smprint("%s/%s", s, de.elem);
       +                                n += fsysEsearch1(ff, t, elo);
       +                                vtMemFree(t);
       +                                fileDecRef(ff);
       +                        }
       +                }
       +                deCleanup(&de);
       +                if(r < 0)
       +                        break;
       +        }
       +        deeClose(dee);
       +
       +        return n;
       +}
       +
       +static int
       +fsysEsearch(Fs* fs, char* path, u32int elo)
       +{
       +        int n;
       +        File *f;
       +        DirEntry de;
       +
       +        f = fileOpen(fs, path);
       +        if(f == nil)
       +                return 0;
       +        if(!fileGetDir(f, &de)){
       +                consPrint("\tfileGetDir %s failed: %R\n", path);
       +                fileDecRef(f);
       +                return 0;
       +        }
       +        if((de.mode & ModeDir) == 0){
       +                fileDecRef(f);
       +                deCleanup(&de);
       +                return 0;
       +        }
       +        deCleanup(&de);
       +        n = fsysEsearch1(f, path, elo);
       +        fileDecRef(f);
       +        return n;
       +}
       +
       +static int
       +fsysEpoch(Fsys* fsys, int argc, char* argv[])
       +{
       +        Fs *fs;
       +        int force, n, remove;
       +        u32int low, old;
       +        char *usage = "usage: [fsys name] epoch [[-ry] low]";
       +
       +        force = 0;
       +        remove = 0;
       +        ARGBEGIN{
       +        case 'y':
       +                force = 1;
       +                break;
       +        case 'r':
       +                remove = 1;
       +                break;
       +        default:
       +                return cliError(usage);
       +        }ARGEND
       +        if(argc > 1)
       +                return cliError(usage);
       +        if(argc > 0)
       +                low = strtoul(argv[0], 0, 0);
       +        else
       +                low = ~(u32int)0;
       +
       +        if(low == 0)
       +                return cliError("low epoch cannot be zero");
       +
       +        fs = fsys->fs;
       +
       +        vtRLock(fs->elk);
       +        consPrint("\tlow %ud hi %ud\n", fs->elo, fs->ehi);
       +        if(low == ~(u32int)0){
       +                vtRUnlock(fs->elk);
       +                return 1;
       +        }
       +        n = fsysEsearch(fsys->fs, "/archive", low);
       +        n += fsysEsearch(fsys->fs, "/snapshot", low);
       +        consPrint("\t%d snapshot%s found with epoch < %ud\n", n, n==1 ? "" : "s", low);
       +        vtRUnlock(fs->elk);
       +
       +        /*
       +         * There's a small race here -- a new snapshot with epoch < low might
       +         * get introduced now that we unlocked fs->elk.  Low has to
       +         * be <= fs->ehi.  Of course, in order for this to happen low has
       +         * to be equal to the current fs->ehi _and_ a snapshot has to
       +         * run right now.  This is a small enough window that I don't care.
       +         */
       +        if(n != 0 && !force){
       +                consPrint("\tnot setting low epoch\n");
       +                return 1;
       +        }
       +        old = fs->elo;
       +        if(!fsEpochLow(fs, low))
       +                consPrint("\tfsEpochLow: %R\n");
       +        else{
       +                consPrint("\told: epoch%s %ud\n", force ? " -y" : "", old);
       +                consPrint("\tnew: epoch%s %ud\n", force ? " -y" : "", fs->elo);
       +                if(fs->elo < low)
       +                        consPrint("\twarning: new low epoch < old low epoch\n");
       +                if(force && remove)
       +                        fsSnapshotRemove(fs);
       +        }
       +
       +        return 1;
       +}
       +
       +static int
       +fsysCreate(Fsys* fsys, int argc, char* argv[])
       +{
       +        int r;
       +        ulong mode;
       +        char *elem, *p, *path;
       +        char *usage = "usage: [fsys name] create path uid gid perm";
       +        DirEntry de;
       +        File *file, *parent;
       +
       +        ARGBEGIN{
       +        default:
       +                return cliError(usage);
       +        }ARGEND
       +        if(argc != 4)
       +                return cliError(usage);
       +
       +        if(!fsysParseMode(argv[3], &mode))
       +                return cliError(usage);
       +        if(mode&ModeSnapshot)
       +                return cliError("create - cannot create with snapshot bit set");
       +
       +        if(strcmp(argv[1], uidnoworld) == 0)
       +                return cliError("permission denied");
       +
       +        vtRLock(fsys->fs->elk);
       +        path = vtStrDup(argv[0]);
       +        if((p = strrchr(path, '/')) != nil){
       +                *p++ = '\0';
       +                elem = p;
       +                p = path;
       +                if(*p == '\0')
       +                        p = "/";
       +        }
       +        else{
       +                p = "/";
       +                elem = path;
       +        }
       +
       +        r = 0;
       +        if((parent = fileOpen(fsys->fs, p)) == nil)
       +                goto out;
       +
       +        file = fileCreate(parent, elem, mode, argv[1]);
       +        fileDecRef(parent);
       +        if(file == nil){
       +                vtSetError("create %s/%s: %R", p, elem);
       +                goto out;
       +        }
       +
       +        if(!fileGetDir(file, &de)){
       +                vtSetError("stat failed after create: %R");
       +                goto out1;
       +        }
       +
       +        if(strcmp(de.gid, argv[2]) != 0){
       +                vtMemFree(de.gid);
       +                de.gid = vtStrDup(argv[2]);
       +                if(!fileSetDir(file, &de, argv[1])){
       +                        vtSetError("wstat failed after create: %R");
       +                        goto out2;
       +                }
       +        }
       +        r = 1;
       +
       +out2:
       +        deCleanup(&de);
       +out1:
       +        fileDecRef(file);
       +out:
       +        vtMemFree(path);
       +        vtRUnlock(fsys->fs->elk);
       +
       +        return r;
       +}
       +
       +static void
       +fsysPrintStat(char *prefix, char *file, DirEntry *de)
       +{
       +        char buf[64];
       +
       +        if(prefix == nil)
       +                prefix = "";
       +        consPrint("%sstat %q %q %q %q %s %llud\n", prefix,
       +                file, de->elem, de->uid, de->gid, fsysModeString(de->mode, buf), de->size);
       +}
       +
       +static int
       +fsysStat(Fsys* fsys, int argc, char* argv[])
       +{
       +        int i;
       +        File *f;
       +        DirEntry de;
       +        char *usage = "usage: [fsys name] stat files...";
       +
       +        ARGBEGIN{
       +        default:
       +                return cliError(usage);
       +        }ARGEND
       +
       +        if(argc == 0)
       +                return cliError(usage);
       +
       +        vtRLock(fsys->fs->elk);
       +        for(i=0; i<argc; i++){
       +                if((f = fileOpen(fsys->fs, argv[i])) == nil){
       +                        consPrint("%s: %R\n", argv[i]);
       +                        continue;
       +                }
       +                if(!fileGetDir(f, &de)){
       +                        consPrint("%s: %R\n", argv[i]);
       +                        fileDecRef(f);
       +                        continue;
       +                }
       +                fsysPrintStat("\t", argv[i], &de);
       +                deCleanup(&de);
       +                fileDecRef(f);
       +        }
       +        vtRUnlock(fsys->fs->elk);
       +        return 1;
       +}
       +
       +static int
       +fsysWstat(Fsys *fsys, int argc, char* argv[])
       +{
       +        File *f;
       +        char *p;
       +        DirEntry de;
       +        char *usage = "usage: [fsys name] wstat file elem uid gid mode length\n"
       +                "\tuse - for any field to mean don't change";
       +
       +        ARGBEGIN{
       +        default:
       +                return cliError(usage);
       +        }ARGEND
       +
       +        if(argc != 6)
       +                return cliError(usage);
       +
       +        vtRLock(fsys->fs->elk);
       +        if((f = fileOpen(fsys->fs, argv[0])) == nil){
       +                vtSetError("console wstat - walk - %R");
       +                vtRUnlock(fsys->fs->elk);
       +                return 0;
       +        }
       +        if(!fileGetDir(f, &de)){
       +                vtSetError("console wstat - stat - %R");
       +                fileDecRef(f);
       +                vtRUnlock(fsys->fs->elk);
       +                return 0;
       +        }
       +        fsysPrintStat("\told: w", argv[0], &de);
       +
       +        if(strcmp(argv[1], "-") != 0){
       +                if(!validFileName(argv[1])){
       +                        vtSetError("console wstat - bad elem");
       +                        goto error;
       +                }
       +                vtMemFree(de.elem);
       +                de.elem = vtStrDup(argv[1]);
       +        }
       +        if(strcmp(argv[2], "-") != 0){
       +                if(!validUserName(argv[2])){
       +                        vtSetError("console wstat - bad uid");
       +                        goto error;
       +                }
       +                vtMemFree(de.uid);
       +                de.uid = vtStrDup(argv[2]);
       +        }
       +        if(strcmp(argv[3], "-") != 0){
       +                if(!validUserName(argv[3])){
       +                        vtSetError("console wstat - bad gid");
       +                        goto error;
       +                }
       +                vtMemFree(de.gid);
       +                de.gid = vtStrDup(argv[3]);
       +        }
       +        if(strcmp(argv[4], "-") != 0){
       +                if(!fsysParseMode(argv[4], &de.mode)){
       +                        vtSetError("console wstat - bad mode");
       +                        goto error;
       +                }
       +        }
       +        if(strcmp(argv[5], "-") != 0){
       +                de.size = strtoull(argv[5], &p, 0);
       +                if(argv[5][0] == '\0' || *p != '\0' || (vlong)de.size < 0){
       +                        vtSetError("console wstat - bad length");
       +                        goto error;
       +                }
       +        }
       +
       +        if(!fileSetDir(f, &de, uidadm)){
       +                vtSetError("console wstat - %R");
       +                goto error;
       +        }
       +        deCleanup(&de);
       +
       +        if(!fileGetDir(f, &de)){
       +                vtSetError("console wstat - stat2 - %R");
       +                goto error;
       +        }
       +        fsysPrintStat("\tnew: w", argv[0], &de);
       +        deCleanup(&de);
       +        fileDecRef(f);
       +        vtRUnlock(fsys->fs->elk);
       +
       +        return 1;
       +
       +error:
       +        deCleanup(&de);        /* okay to do this twice */
       +        fileDecRef(f);
       +        vtRUnlock(fsys->fs->elk);
       +        return 0;
       +}
       +
       +static void
       +fsckClri(Fsck *fsck, char *name, MetaBlock *mb, int i, Block *b)
       +{
       +        USED(name);
       +
       +        if((fsck->flags&DoClri) == 0)
       +                return;
       +
       +        mbDelete(mb, i);
       +        mbPack(mb);
       +        blockDirty(b);        
       +}
       +
       +static void
       +fsckClose(Fsck *fsck, Block *b, u32int epoch)
       +{
       +        Label l;
       +
       +        if((fsck->flags&DoClose) == 0)
       +                return;
       +        l = b->l;
       +        if(l.state == BsFree || (l.state&BsClosed)){
       +                consPrint("%#ux is already closed\n", b->addr);
       +                return;
       +        }
       +        if(epoch){        
       +                l.state |= BsClosed;
       +                l.epochClose = epoch;
       +        }else
       +                l.state = BsFree;
       +                
       +        if(!blockSetLabel(b, &l, 0))
       +                consPrint("%#ux setlabel: %R\n", b->addr);
       +}
       +
       +static void
       +fsckClre(Fsck *fsck, Block *b, int offset)
       +{
       +        Entry e;
       +
       +        if((fsck->flags&DoClre) == 0)
       +                return;
       +        if(offset<0 || offset*VtEntrySize >= fsck->bsize){
       +                consPrint("bad clre\n");
       +                return;
       +        }
       +        memset(&e, 0, sizeof e);
       +        entryPack(&e, b->data, offset);
       +        blockDirty(b);
       +}
       +
       +static void
       +fsckClrp(Fsck *fsck, Block *b, int offset)
       +{
       +        if((fsck->flags&DoClrp) == 0)
       +                return;
       +        if(offset<0 || offset*VtScoreSize >= fsck->bsize){
       +                consPrint("bad clre\n");
       +                return;
       +        }
       +        memmove(b->data+offset*VtScoreSize, vtZeroScore, VtScoreSize);
       +        blockDirty(b);
       +}
       +
       +static int
       +fsysCheck(Fsys *fsys, int argc, char *argv[])
       +{
       +        int i, halting;
       +        char *usage = "usage: [fsys name] check [-v] [options]";
       +        Fsck fsck;
       +        Block *b;
       +        Super super;
       +
       +        memset(&fsck, 0, sizeof fsck);
       +        fsck.fs = fsys->fs;
       +        fsck.clri = fsckClri;
       +        fsck.clre = fsckClre;
       +        fsck.clrp = fsckClrp;
       +        fsck.close = fsckClose;
       +        fsck.print = consPrint;
       +
       +        ARGBEGIN{
       +        default:
       +                return cliError(usage);
       +        }ARGEND
       +
       +        for(i=0; i<argc; i++){
       +                if(strcmp(argv[i], "pblock") == 0)
       +                        fsck.printblocks = 1;
       +                else if(strcmp(argv[i], "pdir") == 0)
       +                        fsck.printdirs = 1;
       +                else if(strcmp(argv[i], "pfile") == 0)
       +                        fsck.printfiles = 1;
       +                else if(strcmp(argv[i], "bclose") == 0)
       +                        fsck.flags |= DoClose;
       +                else if(strcmp(argv[i], "clri") == 0)
       +                        fsck.flags |= DoClri;
       +                else if(strcmp(argv[i], "clre") == 0)
       +                        fsck.flags |= DoClre;
       +                else if(strcmp(argv[i], "clrp") == 0)
       +                        fsck.flags |= DoClrp;
       +                else if(strcmp(argv[i], "fix") == 0)
       +                        fsck.flags |= DoClose|DoClri|DoClre|DoClrp;
       +                else if(strcmp(argv[i], "venti") == 0)
       +                        fsck.useventi = 1;
       +                else if(strcmp(argv[i], "snapshot") == 0)
       +                        fsck.walksnapshots = 1;
       +                else{
       +                        consPrint("unknown option '%s'\n", argv[i]);
       +                        return cliError(usage);
       +                }
       +        }
       +
       +        halting = fsys->fs->halted==0;
       +        if(halting)
       +                fsHalt(fsys->fs);
       +        if(fsys->fs->arch){
       +                b = superGet(fsys->fs->cache, &super);
       +                if(b == nil){
       +                        consPrint("could not load super block\n");
       +                        goto Out;
       +                }
       +                blockPut(b);
       +                if(super.current != NilBlock){
       +                        consPrint("cannot check fs while archiver is running; "
       +                                "wait for it to finish\n");
       +                        goto Out;
       +                }
       +        }
       +        fsCheck(&fsck);
       +        consPrint("fsck: %d clri, %d clre, %d clrp, %d bclose\n",
       +                fsck.nclri, fsck.nclre, fsck.nclrp, fsck.nclose);
       +Out:
       +        if(halting)
       +                fsUnhalt(fsys->fs);
       +        return 1;
       +}
       +
       +static int
       +fsysVenti(char* name, int argc, char* argv[])
       +{
       +        int r;
       +        char *host;
       +        char *usage = "usage: [fsys name] venti [address]";
       +        Fsys *fsys;
       +
       +        ARGBEGIN{
       +        default:
       +                return cliError(usage);
       +        }ARGEND
       +
       +        if(argc == 0)
       +                host = nil;
       +        else if(argc == 1)
       +                host = argv[0];
       +        else
       +                return cliError(usage);
       +
       +        if((fsys = _fsysGet(name)) == nil)
       +                return 0;
       +
       +        vtLock(fsys->lock);
       +        if(host == nil)
       +                host = fsys->venti;
       +        else{
       +                vtMemFree(fsys->venti);
       +                if(host[0])
       +                        fsys->venti = vtStrDup(host);
       +                else{
       +                        host = nil;
       +                        fsys->venti = nil;
       +                }
       +        }
       +
       +        /* already open: do a redial */
       +        if(fsys->fs != nil){
       +                if(fsys->session == nil){
       +                        vtSetError("file system was opened with -V");
       +                        r = 0;
       +                        goto out;
       +                }
       +                r = 1;
       +                if(!myRedial(fsys->session, host)
       +                || !vtConnect(fsys->session, 0))
       +                        r = 0;
       +                goto out;
       +        }
       +
       +        /* not yet open: try to dial */
       +        if(fsys->session)
       +                vtClose(fsys->session);
       +        r = 1;
       +        if((fsys->session = myDial(host, 0)) == nil
       +        || !vtConnect(fsys->session, 0))
       +                r = 0;
       +out:
       +        vtUnlock(fsys->lock);
       +        fsysPut(fsys);
       +        return r;
       +}
       +
       +static ulong
       +freemem(void)
       +{
       +        int nf, pgsize = 0;
       +        uvlong size, userpgs = 0, userused = 0;
       +        char *ln, *sl;
       +        char *fields[2];
       +        Biobuf *bp;
       +
       +        size = 64*1024*1024;
       +        bp = Bopen("#c/swap", OREAD);
       +        if (bp != nil) {
       +                while ((ln = Brdline(bp, '\n')) != nil) {
       +                        ln[Blinelen(bp)-1] = '\0';
       +                        nf = tokenize(ln, fields, nelem(fields));
       +                        if (nf != 2)
       +                                continue;
       +                        if (strcmp(fields[1], "pagesize") == 0)
       +                                pgsize = atoi(fields[0]);
       +                        else if (strcmp(fields[1], "user") == 0) {
       +                                sl = strchr(fields[0], '/');
       +                                if (sl == nil)
       +                                        continue;
       +                                userpgs = atoll(sl+1);
       +                                userused = atoll(fields[0]);
       +                        }
       +                }
       +                Bterm(bp);
       +                if (pgsize > 0 && userpgs > 0)
       +                        size = (userpgs - userused) * pgsize;
       +        }
       +        /* cap it to keep the size within 32 bits */
       +        if (size >= 3840UL * 1024 * 1024)
       +                size = 3840UL * 1024 * 1024;
       +        return size;
       +}
       +
       +static int
       +fsysOpen(char* name, int argc, char* argv[])
       +{
       +        char *p, *host;
       +        Fsys *fsys;
       +        int noauth, noventi, noperm, rflag, wstatallow, noatimeupd;
       +        long ncache;
       +        char *usage = "usage: fsys name open [-APVWr] [-c ncache]";
       +
       +        ncache = 1000;
       +        noauth = noperm = wstatallow = noventi = noatimeupd = 0;
       +        rflag = OReadWrite;
       +
       +        ARGBEGIN{
       +        default:
       +                return cliError(usage);
       +        case 'A':
       +                noauth = 1;
       +                break;
       +        case 'P':
       +                noperm = 1;
       +                break;
       +        case 'V':
       +                noventi = 1;
       +                break;
       +        case 'W':
       +                wstatallow = 1;
       +                break;
       +        case 'a':
       +                noatimeupd = 1;
       +                break;
       +        case 'c':
       +                p = ARGF();
       +                if(p == nil)
       +                        return cliError(usage);
       +                ncache = strtol(argv[0], &p, 0);
       +                if(ncache <= 0 || p == argv[0] || *p != '\0')
       +                        return cliError(usage);
       +                break;
       +        case 'r':
       +                rflag = OReadOnly;
       +                break;
       +        }ARGEND
       +        if(argc)
       +                return cliError(usage);
       +
       +        if((fsys = _fsysGet(name)) == nil)
       +                return 0;
       +
       +        /* automatic memory sizing? */
       +        if(mempcnt > 0) {
       +                /* TODO: 8K is a hack; use the actual block size */
       +                ncache = (((vlong)freemem() * mempcnt) / 100) / (8*1024);
       +                if (ncache < 100)
       +                        ncache = 100;
       +        }
       +
       +        vtLock(fsys->lock);
       +        if(fsys->fs != nil){
       +                vtSetError(EFsysBusy, fsys->name);
       +                vtUnlock(fsys->lock);
       +                fsysPut(fsys);
       +                return 0;
       +        }
       +
       +        if(noventi){
       +                if(fsys->session){
       +                        vtClose(fsys->session);
       +                        fsys->session = nil;
       +                }
       +        }
       +        else if(fsys->session == nil){
       +                if(fsys->venti && fsys->venti[0])
       +                        host = fsys->venti;
       +                else
       +                        host = nil;
       +                fsys->session = myDial(host, 1);
       +                if(!vtConnect(fsys->session, nil) && !noventi)
       +                        fprint(2, "warning: connecting to venti: %R\n");
       +        }
       +        if((fsys->fs = fsOpen(fsys->dev, fsys->session, ncache, rflag)) == nil){
       +                vtSetError("fsOpen: %R");
       +                vtUnlock(fsys->lock);
       +                fsysPut(fsys);
       +                return 0;
       +        }
       +        fsys->fs->name = fsys->name;        /* for better error messages */
       +        fsys->noauth = noauth;
       +        fsys->noperm = noperm;
       +        fsys->wstatallow = wstatallow;
       +        fsys->fs->noatimeupd = noatimeupd;
       +        vtUnlock(fsys->lock);
       +        fsysPut(fsys);
       +
       +        if(strcmp(name, "main") == 0)
       +                usersFileRead(nil);
       +
       +        return 1;
       +}
       +
       +static int
       +fsysUnconfig(char* name, int argc, char* argv[])
       +{
       +        Fsys *fsys, **fp;
       +        char *usage = "usage: fsys name unconfig";
       +
       +        ARGBEGIN{
       +        default:
       +                return cliError(usage);
       +        }ARGEND
       +        if(argc)
       +                return cliError(usage);
       +
       +        vtLock(sbox.lock);
       +        fp = &sbox.head;
       +        for(fsys = *fp; fsys != nil; fsys = fsys->next){
       +                if(strcmp(fsys->name, name) == 0)
       +                        break;
       +                fp = &fsys->next;
       +        }
       +        if(fsys == nil){
       +                vtSetError(EFsysNotFound, name);
       +                vtUnlock(sbox.lock);
       +                return 0;
       +        }
       +        if(fsys->ref != 0 || fsys->fs != nil){
       +                vtSetError(EFsysBusy, fsys->name);
       +                vtUnlock(sbox.lock);
       +                return 0;
       +        }
       +        *fp = fsys->next;
       +        vtUnlock(sbox.lock);
       +
       +        if(fsys->session != nil){
       +                vtClose(fsys->session);
       +                vtFree(fsys->session);
       +        }
       +        if(fsys->venti != nil)
       +                vtMemFree(fsys->venti);
       +        if(fsys->dev != nil)
       +                vtMemFree(fsys->dev);
       +        if(fsys->name != nil)
       +                vtMemFree(fsys->name);
       +        vtMemFree(fsys);
       +
       +        return 1;
       +}
       +
       +static int
       +fsysConfig(char* name, int argc, char* argv[])
       +{
       +        Fsys *fsys;
       +        char *part;
       +        char *usage = "usage: fsys name config [dev]";
       +
       +        ARGBEGIN{
       +        default:
       +                return cliError(usage);
       +        }ARGEND
       +        if(argc > 1)
       +                return cliError(usage);
       +
       +        if(argc == 0)
       +                part = foptname;
       +        else
       +                part = argv[0];
       +
       +        if((fsys = _fsysGet(part)) != nil){
       +                vtLock(fsys->lock);
       +                if(fsys->fs != nil){
       +                        vtSetError(EFsysBusy, fsys->name);
       +                        vtUnlock(fsys->lock);
       +                        fsysPut(fsys);
       +                        return 0;
       +                }
       +                vtMemFree(fsys->dev);
       +                fsys->dev = vtStrDup(part);
       +                vtUnlock(fsys->lock);
       +        }
       +        else if((fsys = fsysAlloc(name, part)) == nil)
       +                return 0;
       +
       +        fsysPut(fsys);
       +        return 1;
       +}
       +
       +static struct {
       +        char*        cmd;
       +        int        (*f)(Fsys*, int, char**);
       +        int        (*f1)(char*, int, char**);
       +} fsyscmd[] = {
       +        { "close",        fsysClose, },
       +        { "config",        nil, fsysConfig, },
       +        { "open",        nil, fsysOpen, },
       +        { "unconfig",        nil, fsysUnconfig, },
       +        { "venti",        nil, fsysVenti, },
       +
       +        { "bfree",        fsysBfree, },
       +        { "block",        fsysBlock, },
       +        { "check",        fsysCheck, },
       +        { "clre",        fsysClre, },
       +        { "clri",        fsysClri, },
       +        { "clrp",        fsysClrp, },
       +        { "create",        fsysCreate, },
       +        { "df",                fsysDf, },
       +        { "epoch",        fsysEpoch, },
       +        { "halt",        fsysHalt, },
       +        { "label",        fsysLabel, },
       +        { "remove",        fsysRemove, },
       +        { "snap",        fsysSnap, },
       +        { "snaptime",        fsysSnapTime, },
       +        { "snapclean",        fsysSnapClean, },
       +        { "stat",        fsysStat, },
       +        { "sync",        fsysSync, },
       +        { "unhalt",        fsysUnhalt, },
       +        { "wstat",        fsysWstat, },
       +        { "vac",        fsysVac, },
       +
       +        { nil,                nil, },
       +};
       +
       +static int
       +fsysXXX1(Fsys *fsys, int i, int argc, char* argv[])
       +{
       +        int r;
       +
       +        vtLock(fsys->lock);
       +        if(fsys->fs == nil){
       +                vtUnlock(fsys->lock);
       +                vtSetError(EFsysNotOpen, fsys->name);
       +                return 0;
       +        }
       +
       +        if(fsys->fs->halted
       +        && fsyscmd[i].f != fsysUnhalt && fsyscmd[i].f != fsysCheck){
       +                vtSetError("file system %s is halted", fsys->name);
       +                vtUnlock(fsys->lock);
       +                return 0;
       +        }
       +
       +        r = (*fsyscmd[i].f)(fsys, argc, argv);
       +        vtUnlock(fsys->lock);
       +        return r;
       +}
       +
       +static int
       +fsysXXX(char* name, int argc, char* argv[])
       +{
       +        int i, r;
       +        Fsys *fsys;
       +
       +        for(i = 0; fsyscmd[i].cmd != nil; i++){
       +                if(strcmp(fsyscmd[i].cmd, argv[0]) == 0)
       +                        break;
       +        }
       +
       +        if(fsyscmd[i].cmd == nil){
       +                vtSetError("unknown command - '%s'", argv[0]);
       +                return 0;
       +        }
       +
       +        /* some commands want the name... */
       +        if(fsyscmd[i].f1 != nil){
       +                if(strcmp(name, FsysAll) == 0){
       +                        vtSetError("cannot use fsys %#q with %#q command", FsysAll, argv[0]);
       +                        return 0;
       +                }
       +                return (*fsyscmd[i].f1)(name, argc, argv);
       +        }
       +
       +        /* ... but most commands want the Fsys */
       +        if(strcmp(name, FsysAll) == 0){
       +                r = 1;
       +                vtRLock(sbox.lock);
       +                for(fsys = sbox.head; fsys != nil; fsys = fsys->next){
       +                        fsys->ref++;
       +                        r = fsysXXX1(fsys, i, argc, argv) && r;
       +                        fsys->ref--;
       +                }
       +                vtRUnlock(sbox.lock);
       +        }else{
       +                if((fsys = _fsysGet(name)) == nil)
       +                        return 0;
       +                r = fsysXXX1(fsys, i, argc, argv);
       +                fsysPut(fsys);
       +        }
       +        return r;
       +}
       +
       +static int
       +cmdFsysXXX(int argc, char* argv[])
       +{
       +        char *name;
       +
       +        if((name = sbox.curfsys) == nil){
       +                vtSetError(EFsysNoCurrent, argv[0]);
       +                return 0;
       +        }
       +
       +        return fsysXXX(name, argc, argv);
       +}
       +
       +static int
       +cmdFsys(int argc, char* argv[])
       +{
       +        Fsys *fsys;
       +        char *usage = "usage: fsys [name ...]";
       +
       +        ARGBEGIN{
       +        default:
       +                return cliError(usage);
       +        }ARGEND
       +
       +        if(argc == 0){
       +                vtRLock(sbox.lock);
       +                currfsysname = sbox.head->name;
       +                for(fsys = sbox.head; fsys != nil; fsys = fsys->next)
       +                        consPrint("\t%s\n", fsys->name);
       +                vtRUnlock(sbox.lock);
       +                return 1;
       +        }
       +        if(argc == 1){
       +                fsys = nil;
       +                if(strcmp(argv[0], FsysAll) != 0 && (fsys = fsysGet(argv[0])) == nil)
       +                        return 0;
       +                sbox.curfsys = vtStrDup(argv[0]);
       +                consPrompt(sbox.curfsys);
       +                if(fsys)
       +                        fsysPut(fsys);
       +                return 1;
       +        }
       +
       +        return fsysXXX(argv[0], argc-1, argv+1);
       +}
       +
       +int
       +fsysInit(void)
       +{
       +        int i;
       +
       +        fmtinstall('H', encodefmt);
       +        fmtinstall('V', scoreFmt);
       +        fmtinstall('R', vtErrFmt);
       +        fmtinstall('L', labelFmt);
       +
       +        sbox.lock = vtLockAlloc();
       +
       +        cliAddCmd("fsys", cmdFsys);
       +        for(i = 0; fsyscmd[i].cmd != nil; i++){
       +                if(fsyscmd[i].f != nil)
       +                        cliAddCmd(fsyscmd[i].cmd, cmdFsysXXX);
       +        }
       +        /* the venti cmd is special: the fs can be either open or closed */
       +        cliAddCmd("venti", cmdFsysXXX);
       +        cliAddCmd("printconfig", cmdPrintConfig);
       +
       +        return 1;
       +}
 (DIR) diff --git a/src/cmd/fossil/9lstn.c b/src/cmd/fossil/9lstn.c
       t@@ -0,0 +1,184 @@
       +#include "stdinc.h"
       +
       +#include "9.h"
       +
       +typedef struct Lstn Lstn;
       +struct Lstn {
       +        int        afd;
       +        int        flags;
       +        char*        address;
       +        char        dir[NETPATHLEN];
       +
       +        Lstn*        next;
       +        Lstn*        prev;
       +};
       +
       +static struct {
       +        VtLock*        lock;
       +
       +        Lstn*        head;
       +        Lstn*        tail;
       +} lbox;
       +
       +static void
       +lstnFree(Lstn* lstn)
       +{
       +        vtLock(lbox.lock);
       +        if(lstn->prev != nil)
       +                lstn->prev->next = lstn->next;
       +        else
       +                lbox.head = lstn->next;
       +        if(lstn->next != nil)
       +                lstn->next->prev = lstn->prev;
       +        else
       +                lbox.tail = lstn->prev;
       +        vtUnlock(lbox.lock);
       +
       +        if(lstn->afd != -1)
       +                close(lstn->afd);
       +        vtMemFree(lstn->address);
       +        vtMemFree(lstn);
       +}
       +
       +static void
       +lstnListen(void* a)
       +{
       +        Lstn *lstn;
       +        int dfd, lfd;
       +        char newdir[NETPATHLEN];
       +        
       +         vtThreadSetName("listen");
       +
       +        lstn = a;
       +        for(;;){
       +                if((lfd = listen(lstn->dir, newdir)) < 0){
       +                        fprint(2, "listen: listen '%s': %r", lstn->dir);
       +                        break;
       +                }
       +                if((dfd = accept(lfd, newdir)) >= 0)
       +                        conAlloc(dfd, newdir, lstn->flags);
       +                else
       +                        fprint(2, "listen: accept %s: %r\n", newdir);
       +                close(lfd);
       +        }
       +        lstnFree(lstn);
       +}
       +
       +static Lstn*
       +lstnAlloc(char* address, int flags)
       +{
       +        int afd;
       +        Lstn *lstn;
       +        char dir[NETPATHLEN];
       +
       +        vtLock(lbox.lock);
       +        for(lstn = lbox.head; lstn != nil; lstn = lstn->next){
       +                if(strcmp(lstn->address, address) != 0)
       +                        continue;
       +                vtSetError("listen: already serving '%s'", address);
       +                vtUnlock(lbox.lock);
       +                return nil;
       +        }
       +
       +        if((afd = announce(address, dir)) < 0){
       +                vtSetError("listen: announce '%s': %r", address);
       +                vtUnlock(lbox.lock);
       +                return nil;
       +        }
       +
       +        lstn = vtMemAllocZ(sizeof(Lstn));
       +        lstn->afd = afd;
       +        lstn->address = vtStrDup(address);
       +        lstn->flags = flags;
       +        memmove(lstn->dir, dir, NETPATHLEN);
       +
       +        if(lbox.tail != nil){
       +                lstn->prev = lbox.tail;
       +                lbox.tail->next = lstn;
       +        }
       +        else{
       +                lbox.head = lstn;
       +                lstn->prev = nil;
       +        }
       +        lbox.tail = lstn;
       +        vtUnlock(lbox.lock);
       +
       +        if(vtThread(lstnListen, lstn) < 0){
       +                vtSetError("listen: thread '%s': %r", lstn->address);
       +                lstnFree(lstn);
       +                return nil;
       +        }
       +
       +        return lstn;
       +}
       +
       +static int
       +cmdLstn(int argc, char* argv[])
       +{
       +        int dflag, flags;
       +        Lstn *lstn;
       +        char *usage = "usage: listen [-dIN] [address]";
       +
       +        dflag = 0;
       +        flags = 0;
       +        ARGBEGIN{
       +        default:
       +                return cliError(usage);
       +        case 'd':
       +                dflag = 1;
       +                break;
       +        case 'I':
       +                flags |= ConIPCheck;
       +                break;
       +        case 'N':
       +                flags |= ConNoneAllow;
       +                break;
       +        }ARGEND
       +
       +        switch(argc){
       +        default:
       +                return cliError(usage);
       +        case 0:
       +                vtRLock(lbox.lock);
       +                for(lstn = lbox.head; lstn != nil; lstn = lstn->next)
       +                        consPrint("\t%s\t%s\n", lstn->address, lstn->dir);
       +                vtRUnlock(lbox.lock);
       +                break;
       +        case 1:
       +                if(!dflag){
       +                        if(lstnAlloc(argv[0], flags) == nil)
       +                                return 0;
       +                        break;
       +                }
       +
       +                vtLock(lbox.lock);
       +                for(lstn = lbox.head; lstn != nil; lstn = lstn->next){
       +                        if(strcmp(lstn->address, argv[0]) != 0)
       +                                continue;
       +                        if(lstn->afd != -1){
       +                                close(lstn->afd);
       +                                lstn->afd = -1;
       +                        }
       +                        break;
       +                }
       +                vtUnlock(lbox.lock);
       +
       +                if(lstn == nil){
       +                        vtSetError("listen: '%s' not found", argv[0]);
       +                        return 0;
       +                }
       +                break;
       +        }
       +
       +        return 1;
       +}
       +
       +int
       +lstnInit(void)
       +{
       +        lbox.lock = vtLockAlloc();
       +
       +        cliAddCmd("listen", cmdLstn);
       +
       +        return 1;
       +}
 (DIR) diff --git a/src/cmd/fossil/9p.c b/src/cmd/fossil/9p.c
       t@@ -0,0 +1,1181 @@
       +#include "stdinc.h"
       +
       +#include "9.h"
       +
       +enum {
       +        OMODE                = 0x7,                /* Topen/Tcreate mode */
       +};
       +
       +enum {
       +        PermX                = 1,
       +        PermW                = 2,
       +        PermR                = 4,
       +};
       +
       +static char EPermission[] = "permission denied";
       +
       +static int
       +permFile(File* file, Fid* fid, int perm)
       +{
       +        char *u;
       +        DirEntry de;
       +
       +        if(!fileGetDir(file, &de))
       +                return -1;
       +
       +        /*
       +         * User none only gets other permissions.
       +         */
       +        if(strcmp(fid->uname, unamenone) != 0){
       +                /*
       +                 * There is only one uid<->uname mapping
       +                 * and it's already cached in the Fid, but
       +                 * it might have changed during the lifetime
       +                 * if this Fid.
       +                 */
       +                if((u = unameByUid(de.uid)) != nil){
       +                        if(strcmp(fid->uname, u) == 0 && ((perm<<6) & de.mode)){
       +                                vtMemFree(u);
       +                                deCleanup(&de);
       +                                return 1;
       +                        }
       +                        vtMemFree(u);
       +                }
       +                if(groupMember(de.gid, fid->uname) && ((perm<<3) & de.mode)){
       +                        deCleanup(&de);
       +                        return 1;
       +                }
       +        }
       +        if(perm & de.mode){
       +                if(perm == PermX && (de.mode & ModeDir)){
       +                        deCleanup(&de);
       +                        return 1;
       +                }
       +                if(!groupMember(uidnoworld, fid->uname)){
       +                        deCleanup(&de);
       +                        return 1;
       +                }
       +        }
       +        if(fsysNoPermCheck(fid->fsys) || (fid->con->flags&ConNoPermCheck)){
       +                deCleanup(&de);
       +                return 1;
       +        }
       +        vtSetError(EPermission);
       +
       +        deCleanup(&de);
       +        return 0;
       +}
       +
       +static int
       +permFid(Fid* fid, int p)
       +{
       +        return permFile(fid->file, fid, p);
       +}
       +
       +static int
       +permParent(Fid* fid, int p)
       +{
       +        int r;
       +        File *parent;
       +
       +        parent = fileGetParent(fid->file);
       +        r = permFile(parent, fid, p);
       +        fileDecRef(parent);
       +
       +        return r;
       +}
       +
       +int
       +validFileName(char* name)
       +{
       +        char *p;
       +
       +        if(name == nil || name[0] == '\0'){
       +                vtSetError("no file name");
       +                return 0;
       +        }
       +        if(name[0] == '.'){
       +                if(name[1] == '\0' || (name[1] == '.' && name[2] == '\0')){
       +                        vtSetError(". and .. illegal as file name");
       +                        return 0;
       +                }
       +        }
       +
       +        for(p = name; *p != '\0'; p++){
       +                if((*p & 0xFF) < 040){
       +                        vtSetError("bad character in file name");
       +                        return 0;
       +                }
       +        }
       +
       +        return 1;
       +}
       +
       +static int
       +rTwstat(Msg* m)
       +{
       +        Dir dir;
       +        Fid *fid;
       +        ulong mode, oldmode;
       +        DirEntry de;
       +        char *gid, *strs, *uid;
       +        int gl, op, retval, tsync, wstatallow;
       +
       +        if((fid = fidGet(m->con, m->t.fid, FidFWlock)) == nil)
       +                return 0;
       +
       +        gid = uid = nil;
       +        retval = 0;
       +
       +        if(strcmp(fid->uname, unamenone) == 0 || (fid->qid.type & QTAUTH)){
       +                vtSetError(EPermission);
       +                goto error0;
       +        }
       +        if(fileIsRoFs(fid->file) || !groupWriteMember(fid->uname)){
       +                vtSetError("read-only filesystem");
       +                goto error0;
       +        }
       +
       +        if(!fileGetDir(fid->file, &de))
       +                goto error0;
       +
       +        strs = vtMemAlloc(m->t.nstat);
       +        if(convM2D(m->t.stat, m->t.nstat, &dir, strs) == 0){
       +                vtSetError("wstat -- protocol botch");
       +                goto error;
       +        }
       +
       +        /*
       +         * Run through each of the (sub-)fields in the provided Dir
       +         * checking for validity and whether it's a default:
       +         * .type, .dev and .atime are completely ignored and not checked;
       +         * .qid.path, .qid.vers and .muid are checked for validity but
       +         * any attempt to change them is an error.
       +         * .qid.type/.mode, .mtime, .name, .length, .uid and .gid can
       +         * possibly be changed.
       +         *
       +         * 'Op' flags there are changed fields, i.e. it's not a no-op.
       +         * 'Tsync' flags all fields are defaulted.
       +         */
       +        tsync = 1;
       +        if(dir.qid.path != ~0){
       +                if(dir.qid.path != de.qid){
       +                        vtSetError("wstat -- attempt to change qid.path");
       +                        goto error;
       +                }
       +                tsync = 0;
       +        }
       +        if(dir.qid.vers != ~0){
       +                if(dir.qid.vers != de.mcount){
       +                        vtSetError("wstat -- attempt to change qid.vers");
       +                        goto error;
       +                }
       +                tsync = 0;
       +        }
       +        if(dir.muid != nil && *dir.muid != '\0'){
       +                if((uid = uidByUname(dir.muid)) == nil){
       +                        vtSetError("wstat -- unknown muid");
       +                        goto error;
       +                }
       +                if(strcmp(uid, de.mid) != 0){
       +                        vtSetError("wstat -- attempt to change muid");
       +                        goto error;
       +                }
       +                vtMemFree(uid);
       +                uid = nil;
       +                tsync = 0;
       +        }
       +
       +        /*
       +         * Check .qid.type and .mode agree if neither is defaulted.
       +         */
       +        if(dir.qid.type != (uchar)~0 && dir.mode != ~0){
       +                if(dir.qid.type != ((dir.mode>>24) & 0xFF)){
       +                        vtSetError("wstat -- qid.type/mode mismatch");
       +                        goto error;
       +                }
       +        }
       +
       +        op = 0;
       +
       +        oldmode = de.mode;
       +        if(dir.qid.type != (uchar)~0 || dir.mode != ~0){
       +                /*
       +                 * .qid.type or .mode isn't defaulted, check for unknown bits.
       +                 */
       +                if(dir.mode == ~0)
       +                        dir.mode = (dir.qid.type<<24)|(de.mode & 0777);
       +                if(dir.mode & ~(DMDIR|DMAPPEND|DMEXCL|DMTMP|0777)){
       +                        vtSetError("wstat -- unknown bits in qid.type/mode");
       +                        goto error;
       +                }
       +
       +                /*
       +                 * Synthesise a mode to check against the current settings.
       +                 */
       +                mode = dir.mode & 0777;
       +                if(dir.mode & DMEXCL)
       +                        mode |= ModeExclusive;
       +                if(dir.mode & DMAPPEND)
       +                        mode |= ModeAppend;
       +                if(dir.mode & DMDIR)
       +                        mode |= ModeDir;
       +                if(dir.mode & DMTMP)
       +                        mode |= ModeTemporary;
       +
       +                if((de.mode^mode) & ModeDir){
       +                        vtSetError("wstat -- attempt to change directory bit");
       +                        goto error;
       +                }
       +
       +                if((de.mode & (ModeAppend|ModeExclusive|ModeTemporary|0777)) != mode){
       +                        de.mode &= ~(ModeAppend|ModeExclusive|ModeTemporary|0777);
       +                        de.mode |= mode;
       +                        op = 1;
       +                }
       +                tsync = 0;
       +        }
       +
       +        if(dir.mtime != ~0){
       +                if(dir.mtime != de.mtime){
       +                        de.mtime = dir.mtime;
       +                        op = 1;
       +                }
       +                tsync = 0;
       +        }
       +
       +        if(dir.length != ~0){
       +                if(dir.length != de.size){
       +                        /*
       +                         * Cannot change length on append-only files.
       +                         * If we're changing the append bit, it's okay.
       +                         */
       +                        if(de.mode & oldmode & ModeAppend){
       +                                vtSetError("wstat -- attempt to change length of append-only file");
       +                                goto error;
       +                        }
       +                        if(de.mode & ModeDir){
       +                                vtSetError("wstat -- attempt to change length of directory");
       +                                goto error;
       +                        }
       +                        de.size = dir.length;
       +                        op = 1;
       +                }
       +                tsync = 0;
       +        }
       +
       +        /*
       +         * Check for permission to change .mode, .mtime or .length,
       +         * must be owner or leader of either group, for which test gid
       +         * is needed; permission checks on gid will be done later.
       +         */
       +        if(dir.gid != nil && *dir.gid != '\0'){
       +                if((gid = uidByUname(dir.gid)) == nil){
       +                        vtSetError("wstat -- unknown gid");
       +                        goto error;
       +                }
       +                tsync = 0;
       +        }
       +        else
       +                gid = vtStrDup(de.gid);
       +
       +        wstatallow = (fsysWstatAllow(fid->fsys) || (m->con->flags&ConWstatAllow));
       +
       +        /*
       +         * 'Gl' counts whether neither, one or both groups are led.
       +         */
       +        gl = groupLeader(gid, fid->uname) != 0;
       +        gl += groupLeader(de.gid, fid->uname) != 0;
       +
       +        if(op && !wstatallow){
       +                if(strcmp(fid->uid, de.uid) != 0 && !gl){
       +                        vtSetError("wstat -- not owner or group leader");
       +                        goto error;
       +                }
       +        }
       +
       +        /*
       +         * Check for permission to change group, must be
       +         * either owner and in new group or leader of both groups.
       +         * If gid is nil here then
       +         */
       +        if(strcmp(gid, de.gid) != 0){
       +                if(!wstatallow
       +                && !(strcmp(fid->uid, de.uid) == 0 && groupMember(gid, fid->uname))
       +                && !(gl == 2)){
       +                        vtSetError("wstat -- not owner and not group leaders");
       +                        goto error;
       +                }
       +                vtMemFree(de.gid);
       +                de.gid = gid;
       +                gid = nil;
       +                op = 1;
       +                tsync = 0;
       +        }
       +
       +        /*
       +         * Rename.
       +         * Check .name is valid and different to the current.
       +         * If so, check write permission in parent.
       +         */
       +        if(dir.name != nil && *dir.name != '\0'){
       +                if(!validFileName(dir.name))
       +                        goto error;
       +                if(strcmp(dir.name, de.elem) != 0){
       +                        if(permParent(fid, PermW) <= 0)
       +                                goto error;
       +                        vtMemFree(de.elem);
       +                        de.elem = vtStrDup(dir.name);
       +                        op = 1;
       +                }
       +                tsync = 0;
       +        }
       +
       +        /*
       +         * Check for permission to change owner - must be god.
       +         */
       +        if(dir.uid != nil && *dir.uid != '\0'){
       +                if((uid = uidByUname(dir.uid)) == nil){
       +                        vtSetError("wstat -- unknown uid");
       +                        goto error;
       +                }
       +                if(strcmp(uid, de.uid) != 0){
       +                        if(!wstatallow){
       +                                vtSetError("wstat -- not owner");
       +                                goto error;
       +                        }
       +                        if(strcmp(uid, uidnoworld) == 0){
       +                                vtSetError(EPermission);
       +                                goto error;
       +                        }
       +                        vtMemFree(de.uid);
       +                        de.uid = uid;
       +                        uid = nil;
       +                        op = 1;
       +                }
       +                tsync = 0;
       +        }
       +
       +        if(op)
       +                retval = fileSetDir(fid->file, &de, fid->uid);
       +        else
       +                retval = 1;
       +
       +        if(tsync){
       +                /*
       +                 * All values were defaulted,
       +                 * make the state of the file exactly what it
       +                 * claims to be before returning...
       +                 */
       +                USED(tsync);
       +        }
       +
       +error:
       +        deCleanup(&de);
       +        vtMemFree(strs);
       +        if(gid != nil)
       +                vtMemFree(gid);
       +        if(uid != nil)
       +                vtMemFree(uid);
       +error0:
       +        fidPut(fid);
       +        return retval;
       +};
       +
       +static int
       +rTstat(Msg* m)
       +{
       +        Dir dir;
       +        Fid *fid;
       +        DirEntry de;
       +
       +        if((fid = fidGet(m->con, m->t.fid, 0)) == nil)
       +                return 0;
       +        if(fid->qid.type & QTAUTH){
       +                memset(&dir, 0, sizeof(Dir));
       +                dir.qid = fid->qid;
       +                dir.mode = DMAUTH;
       +                dir.atime = time(0L);
       +                dir.mtime = dir.atime;
       +                dir.length = 0;
       +                dir.name = "#¿";
       +                dir.uid = fid->uname;
       +                dir.gid = fid->uname;
       +                dir.muid = fid->uname;
       +
       +                if((m->r.nstat = convD2M(&dir, m->data, m->con->msize)) == 0){
       +                        vtSetError("stat QTAUTH botch");
       +                        fidPut(fid);
       +                        return 0;
       +                }
       +                m->r.stat = m->data;
       +
       +                fidPut(fid);
       +                return 1;
       +        }
       +        if(!fileGetDir(fid->file, &de)){
       +                fidPut(fid);
       +                return 0;
       +        }
       +        fidPut(fid);
       +
       +        /*
       +         * TODO: optimise this copy (in convS2M) away somehow.
       +         * This pettifoggery with m->data will do for the moment.
       +         */
       +        m->r.nstat = dirDe2M(&de, m->data, m->con->msize);
       +        m->r.stat = m->data;
       +        deCleanup(&de);
       +
       +        return 1;
       +}
       +
       +static int
       +_rTclunk(Fid* fid, int remove)
       +{
       +        int rok;
       +
       +        if(fid->excl)
       +                exclFree(fid);
       +
       +        rok = 1;
       +        if(remove && !(fid->qid.type & QTAUTH)){
       +                if((rok = permParent(fid, PermW)) > 0)
       +                        rok = fileRemove(fid->file, fid->uid);
       +        }
       +        fidClunk(fid);
       +
       +        return rok;
       +}
       +
       +static int
       +rTremove(Msg* m)
       +{
       +        Fid *fid;
       +
       +        if((fid = fidGet(m->con, m->t.fid, FidFWlock)) == nil)
       +                return 0;
       +        return _rTclunk(fid, 1);
       +}
       +
       +static int
       +rTclunk(Msg* m)
       +{
       +        Fid *fid;
       +
       +        if((fid = fidGet(m->con, m->t.fid, FidFWlock)) == nil)
       +                return 0;
       +        _rTclunk(fid, (fid->open & FidORclose));
       +
       +        return 1;
       +}
       +
       +static int
       +rTwrite(Msg* m)
       +{
       +        Fid *fid;
       +        int count, n;
       +
       +        if((fid = fidGet(m->con, m->t.fid, 0)) == nil)
       +                return 0;
       +        if(!(fid->open & FidOWrite)){
       +                vtSetError("fid not open for write");
       +                goto error;
       +        }
       +
       +        count = m->t.count;
       +        if(count < 0 || count > m->con->msize-IOHDRSZ){
       +                vtSetError("write count too big");
       +                goto error;
       +        }
       +        if(m->t.offset < 0){
       +                vtSetError("write offset negative");
       +                goto error;
       +        }
       +        if(fid->excl != nil && !exclUpdate(fid))
       +                goto error;
       +
       +        if(fid->qid.type & QTDIR){
       +                vtSetError("is a directory");
       +                goto error;
       +        }
       +        else if(fid->qid.type & QTAUTH)
       +                n = authWrite(fid, m->t.data, count);
       +        else
       +                n = fileWrite(fid->file, m->t.data, count, m->t.offset, fid->uid);
       +        if(n < 0)
       +                goto error;
       +
       +
       +        m->r.count = n;
       +
       +        fidPut(fid);
       +        return 1;
       +
       +error:
       +        fidPut(fid);
       +        return 0;
       +}
       +
       +static int
       +rTread(Msg* m)
       +{
       +        Fid *fid;
       +        uchar *data;
       +        int count, n;
       +
       +        if((fid = fidGet(m->con, m->t.fid, 0)) == nil)
       +                return 0;
       +        if(!(fid->open & FidORead)){
       +                vtSetError("fid not open for read");
       +                goto error;
       +        }
       +
       +        count = m->t.count;
       +        if(count < 0 || count > m->con->msize-IOHDRSZ){
       +                vtSetError("read count too big");
       +                goto error;
       +        }
       +        if(m->t.offset < 0){
       +                vtSetError("read offset negative");
       +                goto error;
       +        }
       +        if(fid->excl != nil && !exclUpdate(fid))
       +                goto error;
       +
       +        /*
       +         * TODO: optimise this copy (in convS2M) away somehow.
       +         * This pettifoggery with m->data will do for the moment.
       +         */
       +        data = m->data+IOHDRSZ;
       +        if(fid->qid.type & QTDIR)
       +                n = dirRead(fid, data, count, m->t.offset);
       +        else if(fid->qid.type & QTAUTH)
       +                n = authRead(fid, data, count);
       +        else
       +                n = fileRead(fid->file, data, count, m->t.offset);
       +        if(n < 0)
       +                goto error;
       +
       +        m->r.count = n;
       +        m->r.data = (char*)data;
       +
       +        fidPut(fid);
       +        return 1;
       +
       +error:
       +        fidPut(fid);
       +        return 0;
       +}
       +
       +static int
       +rTcreate(Msg* m)
       +{
       +        Fid *fid;
       +        File *file;
       +        ulong mode;
       +        int omode, open, perm;
       +
       +        if((fid = fidGet(m->con, m->t.fid, FidFWlock)) == nil)
       +                return 0;
       +        if(fid->open){
       +                vtSetError("fid open for I/O");
       +                goto error;
       +        }
       +        if(fileIsRoFs(fid->file) || !groupWriteMember(fid->uname)){
       +                vtSetError("read-only filesystem");
       +                goto error;
       +        }
       +        if(!fileIsDir(fid->file)){
       +                vtSetError("not a directory");
       +                goto error;
       +        }
       +        if(permFid(fid, PermW) <= 0)
       +                goto error;
       +        if(!validFileName(m->t.name))
       +                goto error;
       +        if(strcmp(fid->uid, uidnoworld) == 0){
       +                vtSetError(EPermission);
       +                goto error;
       +        }
       +
       +        omode = m->t.mode & OMODE;
       +        open = 0;
       +
       +        if(omode == OREAD || omode == ORDWR || omode == OEXEC)
       +                open |= FidORead;
       +        if(omode == OWRITE || omode == ORDWR)
       +                open |= FidOWrite;
       +        if((open & (FidOWrite|FidORead)) == 0){
       +                vtSetError("unknown mode");
       +                goto error;
       +        }
       +        if(m->t.perm & DMDIR){
       +                if((m->t.mode & (ORCLOSE|OTRUNC)) || (open & FidOWrite)){
       +                        vtSetError("illegal mode");
       +                        goto error;
       +                }
       +                if(m->t.perm & DMAPPEND){
       +                        vtSetError("illegal perm");
       +                        goto error;
       +                }
       +        }
       +
       +        mode = fileGetMode(fid->file);
       +        perm = m->t.perm;
       +        if(m->t.perm & DMDIR)
       +                perm &= ~0777|(mode & 0777);
       +        else
       +                perm &= ~0666|(mode & 0666);
       +        mode = perm & 0777;
       +        if(m->t.perm & DMDIR)
       +                mode |= ModeDir;
       +        if(m->t.perm & DMAPPEND)
       +                mode |= ModeAppend;
       +        if(m->t.perm & DMEXCL)
       +                mode |= ModeExclusive;
       +        if(m->t.perm & DMTMP)
       +                mode |= ModeTemporary;
       +
       +        if((file = fileCreate(fid->file, m->t.name, mode, fid->uid)) == nil){
       +                fidPut(fid);
       +                return 0;
       +        }
       +        fileDecRef(fid->file);
       +
       +        fid->qid.vers = fileGetMcount(file);
       +        fid->qid.path = fileGetId(file);
       +        fid->file = file;
       +        mode = fileGetMode(fid->file);
       +        if(mode & ModeDir)
       +                fid->qid.type = QTDIR;
       +        else
       +                fid->qid.type = QTFILE;
       +        if(mode & ModeAppend)
       +                fid->qid.type |= QTAPPEND;
       +        if(mode & ModeExclusive){
       +                fid->qid.type |= QTEXCL;
       +                assert(exclAlloc(fid) != 0);
       +        }
       +        if(m->t.mode & ORCLOSE)
       +                open |= FidORclose;
       +        fid->open = open;
       +
       +        m->r.qid = fid->qid;
       +        m->r.iounit = m->con->msize-IOHDRSZ;
       +
       +        fidPut(fid);
       +        return 1;
       +
       +error:
       +        fidPut(fid);
       +        return 0;
       +}
       +
       +static int
       +rTopen(Msg* m)
       +{
       +        Fid *fid;
       +        int isdir, mode, omode, open, rofs;
       +
       +        if((fid = fidGet(m->con, m->t.fid, FidFWlock)) == nil)
       +                return 0;
       +        if(fid->open){
       +                vtSetError("fid open for I/O");
       +                goto error;
       +        }
       +
       +        isdir = fileIsDir(fid->file);
       +        open = 0;
       +        rofs = fileIsRoFs(fid->file) || !groupWriteMember(fid->uname);
       +
       +        if(m->t.mode & ORCLOSE){
       +                if(isdir){
       +                        vtSetError("is a directory");
       +                        goto error;
       +                }
       +                if(rofs){
       +                        vtSetError("read-only filesystem");
       +                        goto error;
       +                }
       +                if(permParent(fid, PermW) <= 0)
       +                        goto error;
       +
       +                open |= FidORclose;
       +        }
       +
       +        omode = m->t.mode & OMODE;
       +        if(omode == OREAD || omode == ORDWR){
       +                if(permFid(fid, PermR) <= 0)
       +                        goto error;
       +                open |= FidORead;
       +        }
       +        if(omode == OWRITE || omode == ORDWR || (m->t.mode & OTRUNC)){
       +                if(isdir){
       +                        vtSetError("is a directory");
       +                        goto error;
       +                }
       +                if(rofs){
       +                        vtSetError("read-only filesystem");
       +                        goto error;
       +                }
       +                if(permFid(fid, PermW) <= 0)
       +                        goto error;
       +                open |= FidOWrite;
       +        }
       +        if(omode == OEXEC){
       +                if(isdir){
       +                        vtSetError("is a directory");
       +                        goto error;
       +                }
       +                if(permFid(fid, PermX) <= 0)
       +                        goto error;
       +                open |= FidORead;
       +        }
       +        if((open & (FidOWrite|FidORead)) == 0){
       +                vtSetError("unknown mode");
       +                goto error;
       +        }
       +
       +        mode = fileGetMode(fid->file);
       +        if((mode & ModeExclusive) && exclAlloc(fid) == 0)
       +                goto error;
       +
       +        /*
       +         * Everything checks out, try to commit any changes.
       +         */
       +        if((m->t.mode & OTRUNC) && !(mode & ModeAppend))
       +                if(!fileTruncate(fid->file, fid->uid))
       +                        goto error;
       +
       +        if(isdir && fid->db != nil){
       +                dirBufFree(fid->db);
       +                fid->db = nil;
       +        }
       +
       +        fid->qid.vers = fileGetMcount(fid->file);
       +        m->r.qid = fid->qid;
       +        m->r.iounit = m->con->msize-IOHDRSZ;
       +
       +        fid->open = open;
       +
       +        fidPut(fid);
       +        return 1;
       +
       +error:
       +        if(fid->excl != nil)
       +                exclFree(fid);
       +        fidPut(fid);
       +        return 0;
       +}
       +
       +static int
       +rTwalk(Msg* m)
       +{
       +        Qid qid;
       +        Fcall *r, *t;
       +        int nwname, wlock;
       +        File *file, *nfile;
       +        Fid *fid, *ofid, *nfid;
       +
       +        t = &m->t;
       +        if(t->fid == t->newfid)
       +                wlock = FidFWlock;
       +        else
       +                wlock = 0;
       +
       +        /*
       +         * The file identified by t->fid must be valid in the
       +         * current session and must not have been opened for I/O
       +         * by an open or create message.
       +         */
       +        if((ofid = fidGet(m->con, t->fid, wlock)) == nil)
       +                return 0;
       +        if(ofid->open){
       +                vtSetError("file open for I/O");
       +                fidPut(ofid);
       +                return 0;
       +        }
       +
       +        /*
       +         * If newfid is not the same as fid, allocate a new file;
       +         * a side effect is checking newfid is not already in use (error);
       +         * if there are no names to walk this will be equivalent to a
       +         * simple 'clone' operation.
       +         * It's a no-op if newfid is the same as fid and t->nwname is 0.
       +         */
       +        nfid = nil;
       +        if(t->fid != t->newfid){
       +                nfid = fidGet(m->con, t->newfid, FidFWlock|FidFCreate);
       +                if(nfid == nil){
       +                        vtSetError("%s: walk: newfid 0x%ud in use",
       +                                argv0, t->newfid);
       +                        fidPut(ofid);
       +                        return 0;
       +                }
       +                nfid->open = ofid->open & ~FidORclose;
       +                nfid->file = fileIncRef(ofid->file);
       +                nfid->qid = ofid->qid;
       +                nfid->uid = vtStrDup(ofid->uid);
       +                nfid->uname = vtStrDup(ofid->uname);
       +                nfid->fsys = fsysIncRef(ofid->fsys);
       +                fid = nfid;
       +        }
       +        else
       +                fid = ofid;
       +
       +        r = &m->r;
       +        r->nwqid = 0;
       +
       +        if(t->nwname == 0){
       +                if(nfid != nil)
       +                        fidPut(nfid);
       +                fidPut(ofid);
       +
       +                return 1;
       +        }
       +
       +        file = fid->file;
       +        fileIncRef(file);
       +        qid = fid->qid;
       +
       +        for(nwname = 0; nwname < t->nwname; nwname++){
       +                /*
       +                 * Walked elements must represent a directory and
       +                 * the implied user must have permission to search
       +                 * the directory.  Walking .. is always allowed, so that
       +                 * you can't walk into a directory and then not be able
       +                 * to walk out of it.
       +                 */
       +                if(!(qid.type & QTDIR)){
       +                        vtSetError("not a directory");
       +                        break;
       +                }
       +                switch(permFile(file, fid, PermX)){
       +                case 1:
       +                        break;
       +                case 0:
       +                        if(strcmp(t->wname[nwname], "..") == 0)
       +                                break;
       +                case -1:
       +                        goto Out;
       +                }
       +                if((nfile = fileWalk(file, t->wname[nwname])) == nil)
       +                        break;
       +                fileDecRef(file);
       +                file = nfile;
       +                qid.type = QTFILE;
       +                if(fileIsDir(file))
       +                        qid.type = QTDIR;
       +                if(fileIsAppend(file))
       +                        qid.type |= QTAPPEND;
       +                if(fileIsTemporary(file))
       +                        qid.type |= QTTMP;
       +                if(fileIsExclusive(file))
       +                        qid.type |= QTEXCL;
       +                qid.vers = fileGetMcount(file);
       +                qid.path = fileGetId(file);
       +                r->wqid[r->nwqid++] = qid;
       +        }
       +
       +        if(nwname == t->nwname){
       +                /*
       +                 * Walked all elements. Update the target fid
       +                 * from the temporary qid used during the walk,
       +                 * and tidy up.
       +                 */
       +                fid->qid = r->wqid[r->nwqid-1];
       +                fileDecRef(fid->file);
       +                fid->file = file;
       +
       +                if(nfid != nil)
       +                        fidPut(nfid);
       +
       +                fidPut(ofid);
       +                return 1;
       +        }
       +
       +Out:
       +        /*
       +         * Didn't walk all elements, 'clunk' nfid if it exists
       +         * and leave fid untouched.
       +         * It's not an error if some of the elements were walked OK.
       +         */
       +        fileDecRef(file);
       +        if(nfid != nil)
       +                fidClunk(nfid);
       +
       +        fidPut(ofid);
       +        if(nwname == 0)
       +                return 0;
       +        return 1;
       +}
       +
       +static int
       +rTflush(Msg* m)
       +{
       +        if(m->t.oldtag != NOTAG)
       +                msgFlush(m);
       +        return 1;
       +}
       +
       +static void
       +parseAname(char *aname, char **fsname, char **path)
       +{
       +        char *s;
       +
       +        if(aname && aname[0])
       +                s = vtStrDup(aname);
       +        else
       +                s = vtStrDup("main/active");
       +        *fsname = s;
       +        if((*path = strchr(s, '/')) != nil)
       +                *(*path)++ = '\0';
       +        else
       +                *path = "";
       +}
       +
       +/*
       + * Check remote IP address against /mnt/ipok.
       + * Sources.cs.bell-labs.com uses this to disallow
       + * network connections from Sudan, Libya, etc., 
       + * following U.S. cryptography export regulations.
       + */
       +static int
       +conIPCheck(Con* con)
       +{
       +        char ok[256], *p;
       +        int fd;
       +
       +        if(con->flags&ConIPCheck){
       +                if(con->remote[0] == 0){
       +                        vtSetError("cannot verify unknown remote address");
       +                        return 0;
       +                }
       +                if(access("/mnt/ipok/ok", AEXIST) < 0){
       +                        /* mount closes the fd on success */
       +                        if((fd = open("/srv/ipok", ORDWR)) >= 0 
       +                        && mount(fd, -1, "/mnt/ipok", MREPL, "") < 0)
       +                                close(fd);
       +                        if(access("/mnt/ipok/ok", AEXIST) < 0){
       +                                vtSetError("cannot verify remote address");
       +                                return 0;
       +                        }
       +                }
       +                snprint(ok, sizeof ok, "/mnt/ipok/ok/%s", con->remote);
       +                if((p = strchr(ok, '!')) != nil)
       +                        *p = 0;
       +                if(access(ok, AEXIST) < 0){
       +                        vtSetError("restricted remote address");
       +                        return 0;
       +                }
       +        }
       +        return 1;
       +}
       +
       +static int
       +rTattach(Msg* m)
       +{
       +        Fid *fid;
       +        Fsys *fsys;
       +        char *fsname, *path;
       +
       +        if((fid = fidGet(m->con, m->t.fid, FidFWlock|FidFCreate)) == nil)
       +                return 0;
       +
       +        parseAname(m->t.aname, &fsname, &path);
       +        if((fsys = fsysGet(fsname)) == nil){
       +                fidClunk(fid);
       +                vtMemFree(fsname);
       +                return 0;
       +        }
       +        fid->fsys = fsys;
       +
       +        if(m->t.uname[0] != '\0')
       +                fid->uname = vtStrDup(m->t.uname);
       +        else
       +                fid->uname = vtStrDup(unamenone);
       +
       +        if((fid->con->flags&ConIPCheck) && !conIPCheck(fid->con)){
       +                consPrint("reject %s from %s: %R\n", fid->uname, fid->con->remote);
       +                fidClunk(fid);
       +                vtMemFree(fsname);
       +                return 0;
       +        }
       +        if(fsysNoAuthCheck(fsys) || (m->con->flags&ConNoAuthCheck)){
       +                if((fid->uid = uidByUname(fid->uname)) == nil)
       +                        fid->uid = vtStrDup(unamenone);
       +        }
       +        else if(!authCheck(&m->t, fid, fsys)){
       +                fidClunk(fid);
       +                vtMemFree(fsname);
       +                return 0;
       +        }
       +
       +        fsysFsRlock(fsys);
       +        if((fid->file = fsysGetRoot(fsys, path)) == nil){
       +                fsysFsRUnlock(fsys);
       +                fidClunk(fid);
       +                vtMemFree(fsname);
       +                return 0;
       +        }
       +        fsysFsRUnlock(fsys);
       +        vtMemFree(fsname);
       +
       +        fid->qid = (Qid){fileGetId(fid->file), 0, QTDIR};
       +        m->r.qid = fid->qid;
       +
       +        fidPut(fid);
       +        return 1;
       +}
       +
       +static int
       +rTauth(Msg* m)
       +{
       +        int afd;
       +        Con *con;
       +        Fid *afid;
       +        Fsys *fsys;
       +        char *fsname, *path;
       +
       +        parseAname(m->t.aname, &fsname, &path);
       +        if((fsys = fsysGet(fsname)) == nil){
       +                vtMemFree(fsname);
       +                return 0;
       +        }
       +        vtMemFree(fsname);
       +
       +        if(fsysNoAuthCheck(fsys) || (m->con->flags&ConNoAuthCheck)){
       +                m->con->aok = 1;
       +                vtSetError("authentication disabled");
       +                fsysPut(fsys);
       +                return 0;
       +        }
       +        if(strcmp(m->t.uname, unamenone) == 0){
       +                vtSetError("user 'none' requires no authentication");
       +                fsysPut(fsys);
       +                return 0;
       +        }
       +
       +        con = m->con;
       +        if((afid = fidGet(con, m->t.afid, FidFWlock|FidFCreate)) == nil){
       +                fsysPut(fsys);
       +                return 0;
       +        }
       +        afid->fsys = fsys;
       +
       +        if((afd = open("/mnt/factotum/rpc", ORDWR)) < 0){
       +                vtSetError("can't open \"/mnt/factotum/rpc\"");
       +                fidClunk(afid);
       +                return 0;
       +        }
       +        if((afid->rpc = auth_allocrpc(afd)) == nil){
       +                close(afd);
       +                vtSetError("can't auth_allocrpc");
       +                fidClunk(afid);
       +                return 0;
       +        }
       +        if(auth_rpc(afid->rpc, "start", "proto=p9any role=server", 23) != ARok){
       +                vtSetError("can't auth_rpc");
       +                fidClunk(afid);
       +                return 0;
       +        }
       +
       +        afid->open = FidOWrite|FidORead;
       +        afid->qid.type = QTAUTH;
       +        afid->qid.path = m->t.afid;
       +        afid->uname = vtStrDup(m->t.uname);
       +
       +        m->r.qid = afid->qid;
       +
       +        fidPut(afid);
       +        return 1;
       +}
       +
       +static int
       +rTversion(Msg* m)
       +{
       +        int v;
       +        Con *con;
       +        Fcall *r, *t;
       +
       +        t = &m->t;
       +        r = &m->r;
       +        con = m->con;
       +
       +        vtLock(con->lock);
       +        if(con->state != ConInit){
       +                vtUnlock(con->lock);
       +                vtSetError("Tversion: down");
       +                return 0;
       +        }
       +        con->state = ConNew;
       +
       +        /*
       +         * Release the karma of past lives and suffering.
       +         * Should this be done before or after checking the
       +         * validity of the Tversion?
       +         */
       +        fidClunkAll(con);
       +
       +        if(t->tag != NOTAG){
       +                vtUnlock(con->lock);
       +                vtSetError("Tversion: invalid tag");
       +                return 0;
       +        }
       +
       +        if(t->msize < 256){
       +                vtUnlock(con->lock);
       +                vtSetError("Tversion: message size too small");
       +                return 0;
       +        }
       +        if(t->msize < con->msize)
       +                r->msize = t->msize;
       +        else
       +                r->msize = con->msize;
       +
       +        r->version = "unknown";
       +        if(t->version[0] == '9' && t->version[1] == 'P'){
       +                /*
       +                 * Currently, the only defined version
       +                 * is "9P2000"; ignore any later versions.
       +                 */
       +                v = strtol(&t->version[2], 0, 10);
       +                if(v >= 2000){
       +                        r->version = VERSION9P;
       +                        con->msize = r->msize;
       +                        con->state = ConUp;
       +                }
       +                else if(strcmp(t->version, "9PEoF") == 0){
       +                        r->version = "9PEoF";
       +                        con->msize = r->msize;
       +                        con->state = ConMoribund;
       +
       +                        /*
       +                         * Don't want to attempt to write this
       +                         * message as the connection may be already
       +                         * closed.
       +                         */
       +                        m->state = MsgF;
       +                }
       +        }
       +        vtUnlock(con->lock);
       +
       +        return 1;
       +}
       +
       +int (*rFcall[Tmax])(Msg*) = {
       +        [Tversion]        = rTversion,
       +        [Tauth]                = rTauth,
       +        [Tattach]        = rTattach,
       +        [Tflush]        = rTflush,
       +        [Twalk]                = rTwalk,
       +        [Topen]                = rTopen,
       +        [Tcreate]        = rTcreate,
       +        [Tread]                = rTread,
       +        [Twrite]        = rTwrite,
       +        [Tclunk]        = rTclunk,
       +        [Tremove]        = rTremove,
       +        [Tstat]                = rTstat,
       +        [Twstat]        = rTwstat,
       +};
 (DIR) diff --git a/src/cmd/fossil/9ping.c b/src/cmd/fossil/9ping.c
       t@@ -0,0 +1,109 @@
       +#include <u.h>
       +#include <libc.h>
       +
       +typedef uvlong u64int;
       +
       +#define TWID64        ((u64int)~(u64int)0)
       +
       +
       +u64int
       +unittoull(char *s)
       +{
       +        char *es;
       +        u64int n;
       +
       +        if(s == nil)
       +                return TWID64;
       +        n = strtoul(s, &es, 0);
       +        if(*es == 'k' || *es == 'K'){
       +                n *= 1024;
       +                es++;
       +        }else if(*es == 'm' || *es == 'M'){
       +                n *= 1024*1024;
       +                es++;
       +        }else if(*es == 'g' || *es == 'G'){
       +                n *= 1024*1024*1024;
       +                es++;
       +        }
       +        if(*es != '\0')
       +                return TWID64;
       +        return n;
       +}
       +
       +void
       +main(int argc, char *argv[])
       +{
       +        int fd, i;
       +        int n = 1000, m;
       +        int s = 1;
       +        double *t, t0, t1;
       +        uchar *buf;        
       +        double a, d, max, min;
       +
       +        m = OREAD;
       +        ARGBEGIN{
       +        case 'n':
       +                n = atoi(ARGF());
       +                break;
       +        case 's':
       +                s = unittoull(ARGF());
       +                if(s < 1 || s > 1024*1024)
       +                        sysfatal("bad size");
       +                break;
       +        case 'r':
       +                m = OREAD;
       +                break;
       +        case 'w':
       +                m = OWRITE;
       +                break;
       +        }ARGEND
       +
       +        fd = 0;
       +        if(argc == 1){
       +                fd = open(argv[0], m);
       +                if(fd < 0)
       +                        sysfatal("could not open file: %s: %r", argv[0]);
       +        }
       +
       +        buf = malloc(s);
       +        t = malloc(n*sizeof(double));
       +        
       +        t0 = nsec();
       +        for(i=0; i<n; i++){
       +                if(m == OREAD){
       +                        if(pread(fd, buf, s, 0) < s)
       +                                sysfatal("bad read: %r");
       +                }else{
       +                        if(pwrite(fd, buf, s, 0) < s)
       +                                sysfatal("bad write: %r");
       +                }
       +                t1 = nsec();
       +                t[i] = (t1 - t0)*1e-3;
       +                t0 = t1;
       +        }
       +
       +        a = 0.;
       +        d = 0.;
       +        max = 0.;
       +        min = 1e12;
       +
       +        for(i=0; i<n; i++){
       +                a += t[i];
       +                if(max < t[i])
       +                        max = t[i];
       +                if(min > t[i])
       +                        min = t[i];
       +        }
       +
       +        a /= n;
       +        
       +        for(i=0; i<n; i++)
       +                d += (a - t[i]) * (a - t[i]);
       +        d /= n;
       +        d = sqrt(d);
       +
       +        print("avg = %.0fµs min = %.0fµs max = %.0fµs dev = %.0fµs\n", a, min, max, d);
       +
       +        exits(0);
       +}
       +
 (DIR) diff --git a/src/cmd/fossil/9proc.c b/src/cmd/fossil/9proc.c
       t@@ -0,0 +1,825 @@
       +#include "stdinc.h"
       +
       +#include "9.h"
       +#include "dat.h"
       +#include "fns.h"
       +
       +enum {
       +        NConInit        = 128,
       +        NMsgInit        = 384,
       +        NMsgProcInit        = 64,
       +        NMsizeInit        = 8192+IOHDRSZ,
       +};
       +
       +static struct {
       +        VtLock*        alock;                        /* alloc */
       +        Msg*        ahead;
       +        VtRendez* arendez;
       +
       +        int        maxmsg;
       +        int        nmsg;
       +        int        nmsgstarve;
       +
       +        VtLock*        rlock;                        /* read */
       +        Msg*        rhead;
       +        Msg*        rtail;
       +        VtRendez* rrendez;
       +
       +        int        maxproc;
       +        int        nproc;
       +        int        nprocstarve;
       +
       +        u32int        msize;                        /* immutable */
       +} mbox;
       +
       +static struct {
       +        VtLock*        alock;                        /* alloc */
       +        Con*        ahead;
       +        VtRendez* arendez;
       +
       +        VtLock*        clock;
       +        Con*        chead;
       +        Con*        ctail;
       +
       +        int        maxcon;
       +        int        ncon;
       +        int        nconstarve;
       +
       +        u32int        msize;
       +} cbox;
       +
       +static void
       +conFree(Con* con)
       +{
       +        assert(con->version == nil);
       +        assert(con->mhead == nil);
       +        assert(con->whead == nil);
       +        assert(con->nfid == 0);
       +        assert(con->state == ConMoribund);
       +
       +        if(con->fd >= 0){
       +                close(con->fd);
       +                con->fd = -1;
       +        }
       +        con->state = ConDead;
       +        con->aok = 0;
       +        con->flags = 0;
       +        con->isconsole = 0;
       +
       +        vtLock(cbox.alock);
       +        if(con->cprev != nil)
       +                con->cprev->cnext = con->cnext;
       +        else
       +                cbox.chead = con->cnext;
       +        if(con->cnext != nil)
       +                con->cnext->cprev = con->cprev;
       +        else
       +                cbox.ctail = con->cprev;
       +        con->cprev = con->cnext = nil;
       +
       +        if(cbox.ncon > cbox.maxcon){
       +                if(con->name != nil)
       +                        vtMemFree(con->name);
       +                vtLockFree(con->fidlock);
       +                vtMemFree(con->data);
       +                vtRendezFree(con->wrendez);
       +                vtLockFree(con->wlock);
       +                vtRendezFree(con->mrendez);
       +                vtLockFree(con->mlock);
       +                vtRendezFree(con->rendez);
       +                vtLockFree(con->lock);
       +                vtMemFree(con);
       +                cbox.ncon--;
       +                vtUnlock(cbox.alock);
       +                return;
       +        }
       +        con->anext = cbox.ahead;
       +        cbox.ahead = con;
       +        if(con->anext == nil)
       +                vtWakeup(cbox.arendez);
       +        vtUnlock(cbox.alock);
       +}
       +
       +static void
       +msgFree(Msg* m)
       +{
       +        assert(m->rwnext == nil);
       +        assert(m->flush == nil);
       +
       +        vtLock(mbox.alock);
       +        if(mbox.nmsg > mbox.maxmsg){
       +                vtMemFree(m->data);
       +                vtMemFree(m);
       +                mbox.nmsg--;
       +                vtUnlock(mbox.alock);
       +                return;
       +        }
       +        m->anext = mbox.ahead;
       +        mbox.ahead = m;
       +        if(m->anext == nil)
       +                vtWakeup(mbox.arendez);
       +        vtUnlock(mbox.alock);
       +}
       +
       +static Msg*
       +msgAlloc(Con* con)
       +{
       +        Msg *m;
       +
       +        vtLock(mbox.alock);
       +        while(mbox.ahead == nil){
       +                if(mbox.nmsg >= mbox.maxmsg){
       +                        mbox.nmsgstarve++;
       +                        vtSleep(mbox.arendez);
       +                        continue;
       +                }
       +                m = vtMemAllocZ(sizeof(Msg));
       +                m->data = vtMemAlloc(mbox.msize);
       +                m->msize = mbox.msize;
       +                mbox.nmsg++;
       +                mbox.ahead = m;
       +                break;
       +        }
       +        m = mbox.ahead;
       +        mbox.ahead = m->anext;
       +        m->anext = nil;
       +        vtUnlock(mbox.alock);
       +
       +        m->con = con;
       +        m->state = MsgR;
       +        m->nowq = 0;
       +
       +        return m;
       +}
       +
       +static void
       +msgMunlink(Msg* m)
       +{
       +        Con *con;
       +
       +        con = m->con;
       +
       +        if(m->mprev != nil)
       +                m->mprev->mnext = m->mnext;
       +        else
       +                con->mhead = m->mnext;
       +        if(m->mnext != nil)
       +                m->mnext->mprev = m->mprev;
       +        else
       +                con->mtail = m->mprev;
       +        m->mprev = m->mnext = nil;
       +}
       +
       +void
       +msgFlush(Msg* m)
       +{
       +        Con *con;
       +        Msg *flush, *old;
       +
       +        con = m->con;
       +
       +        if(Dflag)
       +                fprint(2, "msgFlush %F\n", &m->t);
       +
       +        /*
       +         * If this Tflush has been flushed, nothing to do.
       +         * Look for the message to be flushed in the
       +         * queue of all messages still on this connection.
       +         * If it's not found must assume Elvis has already
       +         * left the building and reply normally.
       +         */
       +        vtLock(con->mlock);
       +        if(m->state == MsgF){
       +                vtUnlock(con->mlock);
       +                return;
       +        }
       +        for(old = con->mhead; old != nil; old = old->mnext)
       +                if(old->t.tag == m->t.oldtag)
       +                        break;
       +        if(old == nil){
       +                if(Dflag)
       +                        fprint(2, "msgFlush: cannot find %d\n", m->t.oldtag);
       +                vtUnlock(con->mlock);
       +                return;
       +        }
       +
       +        if(Dflag)
       +                fprint(2, "\tmsgFlush found %F\n", &old->t);
       +
       +        /*
       +         * Found it.
       +         * There are two cases where the old message can be
       +         * truly flushed and no reply to the original message given.
       +         * The first is when the old message is in MsgR state; no
       +         * processing has been done yet and it is still on the read
       +         * queue. The second is if old is a Tflush, which doesn't
       +         * affect the server state. In both cases, put the old
       +         * message into MsgF state and let MsgWrite toss it after
       +         * pulling it off the queue.
       +         */
       +        if(old->state == MsgR || old->t.type == Tflush){
       +                old->state = MsgF;
       +                if(Dflag)
       +                        fprint(2, "msgFlush: change %d from MsgR to MsgF\n",
       +                                m->t.oldtag);
       +        }
       +
       +        /*
       +         * Link this flush message and the old message
       +         * so multiple flushes can be coalesced (if there are
       +         * multiple Tflush messages for a particular pending
       +         * request, it is only necessary to respond to the last
       +         * one, so any previous can be removed) and to be
       +         * sure flushes wait for their corresponding old
       +         * message to go out first.
       +         * Waiting flush messages do not go on the write queue,
       +         * they are processed after the old message is dealt
       +         * with. There's no real need to protect the setting of
       +         * Msg.nowq, the only code to check it runs in this
       +         * process after this routine returns.
       +         */
       +        if((flush = old->flush) != nil){
       +                if(Dflag)
       +                        fprint(2, "msgFlush: remove %d from %d list\n",
       +                                old->flush->t.tag, old->t.tag);
       +                m->flush = flush->flush;
       +                flush->flush = nil;
       +                msgMunlink(flush);
       +                msgFree(flush);
       +        }
       +        old->flush = m;
       +        m->nowq = 1;
       +
       +        if(Dflag)
       +                fprint(2, "msgFlush: add %d to %d queue\n",
       +                        m->t.tag, old->t.tag);
       +        vtUnlock(con->mlock);
       +}
       +
       +static void
       +msgProc(void*)
       +{
       +        Msg *m;
       +        char *e;
       +        Con *con;
       +
       +        vtThreadSetName("msgProc");
       +
       +        for(;;){
       +                /*
       +                 * If surplus to requirements, exit.
       +                 * If not, wait for and pull a message off
       +                 * the read queue.
       +                 */
       +                vtLock(mbox.rlock);
       +                if(mbox.nproc > mbox.maxproc){
       +                        mbox.nproc--;
       +                        vtUnlock(mbox.rlock);
       +                        break;
       +                }
       +                while(mbox.rhead == nil)
       +                        vtSleep(mbox.rrendez);
       +                m = mbox.rhead;
       +                mbox.rhead = m->rwnext;
       +                m->rwnext = nil;
       +                vtUnlock(mbox.rlock);
       +
       +                con = m->con;
       +                e = nil;
       +
       +                /*
       +                 * If the message has been flushed before
       +                 * any 9P processing has started, mark it so
       +                 * none will be attempted.
       +                 */
       +                vtLock(con->mlock);
       +                if(m->state == MsgF)
       +                        e = "flushed";
       +                else
       +                        m->state = Msg9;
       +                vtUnlock(con->mlock);
       +
       +                if(e == nil){
       +                        /*
       +                         * explain this
       +                         */
       +                        vtLock(con->lock);
       +                        if(m->t.type == Tversion){
       +                                con->version = m;
       +                                con->state = ConDown;
       +                                while(con->mhead != m)
       +                                        vtSleep(con->rendez);
       +                                assert(con->state == ConDown);
       +                                if(con->version == m){
       +                                        con->version = nil;
       +                                        con->state = ConInit;
       +                                }
       +                                else
       +                                        e = "Tversion aborted";
       +                        }
       +                        else if(con->state != ConUp)
       +                                e = "connection not ready";
       +                        vtUnlock(con->lock);
       +                }
       +
       +                /*
       +                 * Dispatch if not error already.
       +                 */
       +                m->r.tag = m->t.tag;
       +                if(e == nil && !(*rFcall[m->t.type])(m))
       +                        e = vtGetError();
       +                if(e != nil){
       +                        m->r.type = Rerror;
       +                        m->r.ename = e;
       +                }
       +                else
       +                        m->r.type = m->t.type+1;
       +
       +                /*
       +                 * Put the message (with reply) on the
       +                 * write queue and wakeup the write process.
       +                 */
       +                if(!m->nowq){
       +                        vtLock(con->wlock);
       +                        if(con->whead == nil)
       +                                con->whead = m;
       +                        else
       +                                con->wtail->rwnext = m;
       +                        con->wtail = m;
       +                        vtWakeup(con->wrendez);
       +                        vtUnlock(con->wlock);
       +                }
       +        }
       +}
       +
       +static void
       +msgRead(void* v)
       +{
       +        Msg *m;
       +        Con *con;
       +        int eof, fd, n;
       +
       +        vtThreadSetName("msgRead");
       +
       +        con = v;
       +        fd = con->fd;
       +        eof = 0;
       +
       +        while(!eof){
       +                m = msgAlloc(con);
       +
       +                while((n = read9pmsg(fd, m->data, con->msize)) == 0)
       +                        ;
       +                if(n < 0){
       +                        m->t.type = Tversion;
       +                        m->t.fid = NOFID;
       +                        m->t.tag = NOTAG;
       +                        m->t.msize = con->msize;
       +                        m->t.version = "9PEoF";
       +                        eof = 1;
       +                }
       +                else if(convM2S(m->data, n, &m->t) != n){
       +                        if(Dflag)
       +                                fprint(2, "msgRead: convM2S error: %s\n",
       +                                        con->name);
       +                        msgFree(m);
       +                        continue;
       +                }
       +                if(Dflag)
       +                        fprint(2, "msgRead %p: t %F\n", con, &m->t);
       +
       +                vtLock(con->mlock);
       +                if(con->mtail != nil){
       +                        m->mprev = con->mtail;
       +                        con->mtail->mnext = m;
       +                }
       +                else{
       +                        con->mhead = m;
       +                        m->mprev = nil;
       +                }
       +                con->mtail = m;
       +                vtUnlock(con->mlock);
       +
       +                vtLock(mbox.rlock);
       +                if(mbox.rhead == nil){
       +                        mbox.rhead = m;
       +                        if(!vtWakeup(mbox.rrendez)){
       +                                if(mbox.nproc < mbox.maxproc){
       +                                        if(vtThread(msgProc, nil) > 0)
       +                                                mbox.nproc++;
       +                                }
       +                                else
       +                                        mbox.nprocstarve++;
       +                        }
       +                        /*
       +                         * don't need this surely?
       +                        vtWakeup(mbox.rrendez);
       +                         */
       +                }
       +                else
       +                        mbox.rtail->rwnext = m;
       +                mbox.rtail = m;
       +                vtUnlock(mbox.rlock);
       +        }
       +}
       +
       +static void
       +msgWrite(void* v)
       +{
       +        Con *con;
       +        int eof, n;
       +        Msg *flush, *m;
       +
       +        vtThreadSetName("msgWrite");
       +
       +        con = v;
       +        if(vtThread(msgRead, con) < 0){
       +                conFree(con);
       +                return;
       +        }
       +
       +        for(;;){
       +                /*
       +                 * Wait for and pull a message off the write queue.
       +                 */
       +                vtLock(con->wlock);
       +                while(con->whead == nil)
       +                        vtSleep(con->wrendez);
       +                m = con->whead;
       +                con->whead = m->rwnext;
       +                m->rwnext = nil;
       +                assert(!m->nowq);
       +                vtUnlock(con->wlock);
       +
       +                eof = 0;
       +
       +                /*
       +                 * Write each message (if it hasn't been flushed)
       +                 * followed by any messages waiting for it to complete.
       +                 */
       +                vtLock(con->mlock);
       +                while(m != nil){
       +                        msgMunlink(m);
       +
       +                        if(Dflag)
       +                                fprint(2, "msgWrite %d: r %F\n",
       +                                        m->state, &m->r);
       +
       +                        if(m->state != MsgF){
       +                                m->state = MsgW;
       +                                vtUnlock(con->mlock);
       +
       +                                n = convS2M(&m->r, con->data, con->msize);
       +                                if(write(con->fd, con->data, n) != n)
       +                                        eof = 1;
       +
       +                                vtLock(con->mlock);
       +                        }
       +
       +                        if((flush = m->flush) != nil){
       +                                assert(flush->nowq);
       +                                m->flush = nil;
       +                        }
       +                        msgFree(m);
       +                        m = flush;
       +                }
       +                vtUnlock(con->mlock);
       +
       +                vtLock(con->lock);
       +                if(eof && con->fd >= 0){
       +                        close(con->fd);
       +                        con->fd = -1;
       +                }
       +                if(con->state == ConDown)
       +                        vtWakeup(con->rendez);
       +                if(con->state == ConMoribund && con->mhead == nil){
       +                        vtUnlock(con->lock);
       +                        conFree(con);
       +                        break;
       +                }
       +                vtUnlock(con->lock);
       +        }
       +}
       +
       +Con*
       +conAlloc(int fd, char* name, int flags)
       +{
       +        Con *con;
       +        char buf[128], *p;
       +        int rfd, n;
       +
       +        vtLock(cbox.alock);
       +        while(cbox.ahead == nil){
       +                if(cbox.ncon >= cbox.maxcon){
       +                        cbox.nconstarve++;
       +                        vtSleep(cbox.arendez);
       +                        continue;
       +                }
       +                con = vtMemAllocZ(sizeof(Con));
       +                con->lock = vtLockAlloc();
       +                con->rendez = vtRendezAlloc(con->lock);
       +                con->data = vtMemAlloc(cbox.msize);
       +                con->msize = cbox.msize;
       +                con->alock = vtLockAlloc();
       +                con->mlock = vtLockAlloc();
       +                con->mrendez = vtRendezAlloc(con->mlock);
       +                con->wlock = vtLockAlloc();
       +                con->wrendez = vtRendezAlloc(con->wlock);
       +                con->fidlock = vtLockAlloc();
       +
       +                cbox.ncon++;
       +                cbox.ahead = con;
       +                break;
       +        }
       +        con = cbox.ahead;
       +        cbox.ahead = con->anext;
       +        con->anext = nil;
       +
       +        if(cbox.ctail != nil){
       +                con->cprev = cbox.ctail;
       +                cbox.ctail->cnext = con;
       +        }
       +        else{
       +                cbox.chead = con;
       +                con->cprev = nil;
       +        }
       +        cbox.ctail = con;
       +
       +        assert(con->mhead == nil);
       +        assert(con->whead == nil);
       +        assert(con->fhead == nil);
       +        assert(con->nfid == 0);
       +
       +        con->state = ConNew;
       +        con->fd = fd;
       +        if(con->name != nil){
       +                vtMemFree(con->name);
       +                con->name = nil;
       +        }
       +        if(name != nil)
       +                con->name = vtStrDup(name);
       +        else
       +                con->name = vtStrDup("unknown");
       +        con->remote[0] = 0;
       +        snprint(buf, sizeof buf, "%s/remote", con->name);
       +        if((rfd = open(buf, OREAD)) >= 0){
       +                n = read(rfd, buf, sizeof buf-1);
       +                close(rfd);
       +                if(n > 0){
       +                        buf[n] = 0;
       +                        if((p = strchr(buf, '\n')) != nil)
       +                                *p = 0;
       +                        strecpy(con->remote, con->remote+sizeof con->remote, buf);
       +                }
       +        }
       +        con->flags = flags;
       +        con->isconsole = 0;
       +        vtUnlock(cbox.alock);
       +
       +        if(vtThread(msgWrite, con) < 0){
       +                conFree(con);
       +                return nil;
       +        }
       +
       +        return con;
       +}
       +
       +static int
       +cmdMsg(int argc, char* argv[])
       +{
       +        char *p;
       +        char *usage = "usage: msg [-m nmsg] [-p nproc]";
       +        int maxmsg, nmsg, nmsgstarve, maxproc, nproc, nprocstarve;
       +
       +        maxmsg = maxproc = 0;
       +
       +        ARGBEGIN{
       +        default:
       +                return cliError(usage);
       +        case 'm':
       +                p = ARGF();
       +                if(p == nil)
       +                        return cliError(usage);
       +                maxmsg = strtol(argv[0], &p, 0);
       +                if(maxmsg <= 0 || p == argv[0] || *p != '\0')
       +                        return cliError(usage);
       +                break;
       +        case 'p':
       +                p = ARGF();
       +                if(p == nil)
       +                        return cliError(usage);
       +                maxproc = strtol(argv[0], &p, 0);
       +                if(maxproc <= 0 || p == argv[0] || *p != '\0')
       +                        return cliError(usage);
       +                break;
       +        }ARGEND
       +        if(argc)
       +                return cliError(usage);
       +
       +        vtLock(mbox.alock);
       +        if(maxmsg)
       +                mbox.maxmsg = maxmsg;
       +        maxmsg = mbox.maxmsg;
       +        nmsg = mbox.nmsg;
       +        nmsgstarve = mbox.nmsgstarve;
       +        vtUnlock(mbox.alock);
       +
       +        vtLock(mbox.rlock);
       +        if(maxproc)
       +                mbox.maxproc = maxproc;
       +        maxproc = mbox.maxproc;
       +        nproc = mbox.nproc;
       +        nprocstarve = mbox.nprocstarve;
       +        vtUnlock(mbox.rlock);
       +
       +        consPrint("\tmsg -m %d -p %d\n", maxmsg, maxproc);
       +        consPrint("\tnmsg %d nmsgstarve %d nproc %d nprocstarve %d\n",
       +                nmsg, nmsgstarve, nproc, nprocstarve);
       +
       +        return 1;
       +}
       +
       +static int
       +scmp(Fid *a, Fid *b)
       +{
       +        if(a == 0)
       +                return 1;
       +        if(b == 0)
       +                return -1;
       +        return strcmp(a->uname, b->uname);
       +}
       +
       +static Fid*
       +fidMerge(Fid *a, Fid *b)
       +{
       +        Fid *s, **l;
       +
       +        l = &s;
       +        while(a || b){
       +                if(scmp(a, b) < 0){
       +                        *l = a;
       +                        l = &a->sort;
       +                        a = a->sort;
       +                }else{
       +                        *l = b;
       +                        l = &b->sort;
       +                        b = b->sort;
       +                }
       +        }
       +        *l = 0;
       +        return s;
       +}
       +
       +static Fid*
       +fidMergeSort(Fid *f)
       +{
       +        int delay;
       +        Fid *a, *b;
       +
       +        if(f == nil)
       +                return nil;
       +        if(f->sort == nil)
       +                return f;
       +
       +        a = b = f;
       +        delay = 1;
       +        while(a && b){
       +                if(delay)        /* easy way to handle 2-element list */
       +                        delay = 0;
       +                else
       +                        a = a->sort;
       +                if(b = b->sort)
       +                        b = b->sort;
       +        }
       +
       +        b = a->sort;
       +        a->sort = nil;
       +
       +        a = fidMergeSort(f);
       +        b = fidMergeSort(b);
       +
       +        return fidMerge(a, b);
       +}
       +
       +static int
       +cmdWho(int argc, char* argv[])
       +{
       +        char *usage = "usage: who";
       +        int i, l1, l2, l;
       +        Con *con;
       +        Fid *fid, *last;
       +
       +        ARGBEGIN{
       +        default:
       +                return cliError(usage);
       +        }ARGEND
       +
       +        if(argc > 0)
       +                return cliError(usage);
       +
       +        vtRLock(cbox.clock);
       +        l1 = 0;
       +        l2 = 0;
       +        for(con=cbox.chead; con; con=con->cnext){
       +                if((l = strlen(con->name)) > l1)
       +                        l1 = l;
       +                if((l = strlen(con->remote)) > l2)
       +                        l2 = l;
       +        }
       +        for(con=cbox.chead; con; con=con->cnext){
       +                consPrint("\t%-*s %-*s", l1, con->name, l2, con->remote);
       +                vtLock(con->fidlock);
       +                last = nil;
       +                for(i=0; i<NFidHash; i++)
       +                        for(fid=con->fidhash[i]; fid; fid=fid->hash)
       +                                if(fid->fidno != NOFID && fid->uname){
       +                                        fid->sort = last;
       +                                        last = fid;
       +                                }
       +                fid = fidMergeSort(last);
       +                last = nil;
       +                for(; fid; last=fid, fid=fid->sort)
       +                        if(last==nil || strcmp(fid->uname, last->uname) != 0)
       +                                consPrint(" %q", fid->uname);
       +                vtUnlock(con->fidlock);
       +                consPrint("\n");
       +        }
       +        vtRUnlock(cbox.clock);
       +        return 1;
       +}
       +
       +void
       +msgInit(void)
       +{
       +        mbox.alock = vtLockAlloc();
       +        mbox.arendez = vtRendezAlloc(mbox.alock);
       +
       +        mbox.rlock = vtLockAlloc();
       +        mbox.rrendez = vtRendezAlloc(mbox.rlock);
       +
       +        mbox.maxmsg = NMsgInit;
       +        mbox.maxproc = NMsgProcInit;
       +        mbox.msize = NMsizeInit;
       +
       +        cliAddCmd("msg", cmdMsg);
       +}
       +
       +static int
       +cmdCon(int argc, char* argv[])
       +{
       +        char *p;
       +        Con *con;
       +        char *usage = "usage: con [-m ncon]";
       +        int maxcon, ncon, nconstarve;
       +
       +        maxcon = 0;
       +
       +        ARGBEGIN{
       +        default:
       +                return cliError(usage);
       +        case 'm':
       +                p = ARGF();
       +                if(p == nil)
       +                        return cliError(usage);
       +                maxcon = strtol(argv[0], &p, 0);
       +                if(maxcon <= 0 || p == argv[0] || *p != '\0')
       +                        return cliError(usage);
       +                break;
       +        }ARGEND
       +        if(argc)
       +                return cliError(usage);
       +
       +        vtLock(cbox.clock);
       +        if(maxcon)
       +                cbox.maxcon = maxcon;
       +        maxcon = cbox.maxcon;
       +        ncon = cbox.ncon;
       +        nconstarve = cbox.nconstarve;
       +        vtUnlock(cbox.clock);
       +
       +        consPrint("\tcon -m %d\n", maxcon);
       +        consPrint("\tncon %d nconstarve %d\n", ncon, nconstarve);
       +
       +        vtRLock(cbox.clock);
       +        for(con = cbox.chead; con != nil; con = con->cnext){
       +                consPrint("\t%s\n", con->name);
       +        }
       +        vtRUnlock(cbox.clock);
       +
       +        return 1;
       +}
       +
       +void
       +conInit(void)
       +{
       +        cbox.alock = vtLockAlloc();
       +        cbox.arendez = vtRendezAlloc(cbox.alock);
       +
       +        cbox.clock = vtLockAlloc();
       +
       +        cbox.maxcon = NConInit;
       +        cbox.msize = NMsizeInit;
       +
       +        cliAddCmd("con", cmdCon);
       +        cliAddCmd("who", cmdWho);
       +}
 (DIR) diff --git a/src/cmd/fossil/9srv.c b/src/cmd/fossil/9srv.c
       t@@ -0,0 +1,242 @@
       +#include "stdinc.h"
       +
       +#include "9.h"
       +
       +typedef struct Srv Srv;
       +struct Srv {
       +        int        fd;
       +        int        srvfd;
       +        char*        service;
       +        char*        mntpnt;
       +
       +        Srv*        next;
       +        Srv*        prev;
       +};
       +
       +static struct {
       +        VtLock*        lock;
       +
       +        Srv*        head;
       +        Srv*        tail;
       +} sbox;
       +
       +static int
       +srvFd(char* name, int mode, int fd, char** mntpnt)
       +{
       +        int n, srvfd;
       +        char *p, buf[10];
       +
       +        /*
       +         * Drop a file descriptor with given name and mode into /srv.
       +         * Create with ORCLOSE and don't close srvfd so it will be removed
       +         * automatically on process exit.
       +         */
       +        p = smprint("/srv/%s", name);
       +        if((srvfd = create(p, ORCLOSE|OWRITE, mode)) < 0){
       +                vtMemFree(p);
       +                p = smprint("#s/%s", name);
       +                if((srvfd = create(p, ORCLOSE|OWRITE, mode)) < 0){
       +                        vtSetError("create %s: %r", p);
       +                        vtMemFree(p);
       +                        return -1;
       +                }
       +        }
       +
       +        n = snprint(buf, sizeof(buf), "%d", fd);
       +        if(write(srvfd, buf, n) < 0){
       +                close(srvfd);
       +                vtSetError("write %s: %r", p);
       +                vtMemFree(p);
       +                return -1;
       +        }
       +
       +        *mntpnt = p;
       +
       +        return srvfd;
       +}
       +
       +static void
       +srvFree(Srv* srv)
       +{
       +        if(srv->prev != nil)
       +                srv->prev->next = srv->next;
       +        else
       +                sbox.head = srv->next;
       +        if(srv->next != nil)
       +                srv->next->prev = srv->prev;
       +        else
       +                sbox.tail = srv->prev;
       +
       +        if(srv->srvfd != -1)
       +                close(srv->srvfd);
       +        vtMemFree(srv->service);
       +        vtMemFree(srv->mntpnt);
       +        vtMemFree(srv);
       +}
       +
       +static Srv*
       +srvAlloc(char* service, int mode, int fd)
       +{
       +        Dir *dir;
       +        Srv *srv;
       +        int srvfd;
       +        char *mntpnt;
       +
       +        vtLock(sbox.lock);
       +        for(srv = sbox.head; srv != nil; srv = srv->next){
       +                if(strcmp(srv->service, service) != 0)
       +                        continue;
       +                /*
       +                 * If the service exists, but is stale,
       +                 * free it up and let the name be reused.
       +                 */
       +                if((dir = dirfstat(srv->srvfd)) != nil){
       +                        free(dir);
       +                        vtSetError("srv: already serving '%s'", service);
       +                        vtUnlock(sbox.lock);
       +                        return nil;
       +                }
       +                srvFree(srv);
       +                break;
       +        }
       +
       +        if((srvfd = srvFd(service, mode, fd, &mntpnt)) < 0){
       +                vtUnlock(sbox.lock);
       +                return nil;
       +        }
       +        close(fd);
       +
       +        srv = vtMemAllocZ(sizeof(Srv));
       +        srv->srvfd = srvfd;
       +        srv->service = vtStrDup(service);
       +        srv->mntpnt = mntpnt;
       +
       +        if(sbox.tail != nil){
       +                srv->prev = sbox.tail;
       +                sbox.tail->next = srv;
       +        }
       +        else{
       +                sbox.head = srv;
       +                srv->prev = nil;
       +        }
       +        sbox.tail = srv;
       +        vtUnlock(sbox.lock);
       +
       +        return srv;
       +}
       +
       +static int
       +cmdSrv(int argc, char* argv[])
       +{
       +        Con *con;
       +        Srv *srv;
       +        char *usage = "usage: srv [-APWdp] [service]";
       +        int conflags, dflag, fd[2], mode, pflag, r;
       +
       +        dflag = 0;
       +        pflag = 0;
       +        conflags = 0;
       +        mode = 0666;
       +
       +        ARGBEGIN{
       +        default:
       +                return cliError(usage);
       +        case 'A':
       +                conflags |= ConNoAuthCheck;
       +                break;
       +        case 'I':
       +                conflags |= ConIPCheck;
       +                break;
       +        case 'N':
       +                conflags |= ConNoneAllow;
       +                break;
       +        case 'P':
       +                conflags |= ConNoPermCheck;
       +                mode = 0600;
       +                break;
       +        case 'W':
       +                conflags |= ConWstatAllow;
       +                mode = 0600;
       +                break;
       +        case 'd':
       +                dflag = 1;
       +                break;
       +        case 'p':
       +                pflag = 1;
       +                mode = 0600;
       +                break;
       +        }ARGEND
       +
       +        if(pflag && (conflags&ConNoPermCheck)){
       +                vtSetError("srv: cannot use -P with -p");
       +                return 0;
       +        }
       +
       +        switch(argc){
       +        default:
       +                return cliError(usage);
       +        case 0:
       +                vtRLock(sbox.lock);
       +                for(srv = sbox.head; srv != nil; srv = srv->next)
       +                        consPrint("\t%s\t%d\n", srv->service, srv->srvfd);
       +                vtRUnlock(sbox.lock);
       +
       +                return 1;
       +        case 1:
       +                if(!dflag)
       +                        break;
       +
       +                vtLock(sbox.lock);
       +                for(srv = sbox.head; srv != nil; srv = srv->next){
       +                        if(strcmp(srv->service, argv[0]) != 0)
       +                                continue;
       +                        srvFree(srv);
       +                        break;
       +                }
       +                vtUnlock(sbox.lock);
       +
       +                if(srv == nil){
       +                        vtSetError("srv: '%s' not found", argv[0]);
       +                        return 0;
       +                }
       +
       +                return 1;
       +        }
       +
       +        if(pipe(fd) < 0){
       +                vtSetError("srv pipe: %r");
       +                return 0;
       +        }
       +        if((srv = srvAlloc(argv[0], mode, fd[0])) == nil){
       +                close(fd[0]); close(fd[1]);
       +                return 0;
       +        }
       +
       +        if(pflag)
       +                r = consOpen(fd[1], srv->srvfd, -1);
       +        else{
       +                con = conAlloc(fd[1], srv->mntpnt, conflags);
       +                if(con == nil)
       +                        r = 0;
       +                else
       +                        r = 1;
       +        }
       +        if(r == 0){
       +                close(fd[1]);
       +                vtLock(sbox.lock);
       +                srvFree(srv);
       +                vtUnlock(sbox.lock);
       +        }
       +
       +        return r;
       +}
       +
       +int
       +srvInit(void)
       +{
       +        sbox.lock = vtLockAlloc();
       +
       +        cliAddCmd("srv", cmdSrv);
       +
       +        return 1;
       +}
 (DIR) diff --git a/src/cmd/fossil/9user.c b/src/cmd/fossil/9user.c
       t@@ -0,0 +1,948 @@
       +#include "stdinc.h"
       +
       +#include "9.h"
       +
       +enum {
       +        NUserHash        = 1009,
       +};
       +
       +typedef struct Ubox Ubox;
       +typedef struct User User;
       +
       +struct User {
       +        char*        uid;
       +        char*        uname;
       +        char*        leader;
       +        char**        group;
       +        int        ngroup;
       +
       +        User*        next;                        /* */
       +        User*        ihash;                        /* lookup by .uid */
       +        User*        nhash;                        /* lookup by .uname */
       +};
       +
       +#pragma varargck type "U"   User*
       +
       +struct Ubox {
       +        User*        head;
       +        User*        tail;
       +        int        nuser;
       +        int        len;
       +
       +        User*        ihash[NUserHash];        /* lookup by .uid */
       +        User*        nhash[NUserHash];        /* lookup by .uname */
       +};
       +
       +static struct {
       +        VtLock*        lock;
       +
       +        Ubox*        box;
       +} ubox;
       +
       +static char usersDefault[] = {
       +        "adm:adm:adm:sys\n"
       +        "none:none::\n"
       +        "noworld:noworld::\n"
       +        "sys:sys::glenda\n"
       +        "glenda:glenda:glenda:\n"
       +};
       +
       +static char* usersMandatory[] = {
       +        "adm",
       +        "none",
       +        "noworld",
       +        "sys",
       +        nil,
       +};
       +
       +char* uidadm = "adm";
       +char* unamenone = "none";
       +char* uidnoworld = "noworld";
       +
       +static u32int
       +userHash(char* s)
       +{
       +        uchar *p;
       +        u32int hash;
       +
       +        hash = 0;
       +        for(p = (uchar*)s; *p != '\0'; p++)
       +                hash = hash*7 + *p;
       +
       +        return hash % NUserHash;
       +}
       +
       +static User*
       +_userByUid(Ubox* box, char* uid)
       +{
       +        User *u;
       +
       +        if(box != nil){
       +                for(u = box->ihash[userHash(uid)]; u != nil; u = u->ihash){
       +                        if(strcmp(u->uid, uid) == 0)
       +                                return u;
       +                }
       +        }
       +        vtSetError("uname: uid '%s' not found", uid);
       +        return nil;
       +}
       +
       +char*
       +unameByUid(char* uid)
       +{
       +        User *u;
       +        char *uname;
       +
       +        vtRLock(ubox.lock);
       +        if((u = _userByUid(ubox.box, uid)) == nil){
       +                vtRUnlock(ubox.lock);
       +                return nil;
       +        }
       +        uname = vtStrDup(u->uname);
       +        vtRUnlock(ubox.lock);
       +
       +        return uname;
       +}
       +
       +static User*
       +_userByUname(Ubox* box, char* uname)
       +{
       +        User *u;
       +
       +        if(box != nil){
       +                for(u = box->nhash[userHash(uname)]; u != nil; u = u->nhash){
       +                        if(strcmp(u->uname, uname) == 0)
       +                                return u;
       +                }
       +        }
       +        vtSetError("uname: uname '%s' not found", uname);
       +        return nil;
       +}
       +
       +char*
       +uidByUname(char* uname)
       +{
       +        User *u;
       +        char *uid;
       +
       +        vtRLock(ubox.lock);
       +        if((u = _userByUname(ubox.box, uname)) == nil){
       +                vtRUnlock(ubox.lock);
       +                return nil;
       +        }
       +        uid = vtStrDup(u->uid);
       +        vtRUnlock(ubox.lock);
       +
       +        return uid;
       +}
       +
       +static int
       +_groupMember(Ubox* box, char* group, char* member, int whenNoGroup)
       +{
       +        int i;
       +        User *g, *m;
       +
       +        /*
       +         * Is 'member' a member of 'group'?
       +         * Note that 'group' is a 'uid' and not a 'uname'.
       +         * A 'member' is automatically in their own group.
       +         */
       +        if((g = _userByUid(box, group)) == nil)
       +                return whenNoGroup;
       +        if((m = _userByUname(box, member)) == nil)
       +                return 0;
       +        if(m == g)
       +                return 1;
       +        for(i = 0; i < g->ngroup; i++){
       +                if(strcmp(g->group[i], member) == 0)
       +                        return 1;
       +        }
       +        return 0;
       +}
       +
       +int
       +groupWriteMember(char* uname)
       +{
       +        int ret;
       +
       +        /*
       +         * If there is a ``write'' group, then only its members can write
       +         * to the file system, no matter what the permission bits say.
       +         *
       +         * To users not in the ``write'' group, the file system appears
       +         * read only.  This is used to serve sources.cs.bell-labs.com
       +         * to the world.
       +         *
       +         * Note that if there is no ``write'' group, then this routine
       +         * makes it look like everyone is a member -- the opposite
       +         * of what groupMember does.
       +         *
       +         * We use this for sources.cs.bell-labs.com.
       +         * If this slows things down too much on systems that don't
       +         * use this functionality, we could cache the write group lookup.
       +         */
       +
       +        vtRLock(ubox.lock);
       +        ret = _groupMember(ubox.box, "write", uname, 1);
       +        vtRUnlock(ubox.lock);
       +        return ret;
       +}
       +
       +static int
       +_groupRemMember(Ubox* box, User* g, char* member)
       +{
       +        int i;
       +
       +        if(_userByUname(box, member) == nil)
       +                return 0;
       +
       +        for(i = 0; i < g->ngroup; i++){
       +                if(strcmp(g->group[i], member) == 0)
       +                        break;
       +        }
       +        if(i >= g->ngroup){
       +                if(strcmp(g->uname, member) == 0)
       +                        vtSetError("uname: '%s' always in own group", member);
       +                else
       +                        vtSetError("uname: '%s' not in group '%s'",
       +                                member, g->uname);
       +                return 0;
       +        }
       +
       +        vtMemFree(g->group[i]);
       +
       +        box->len -= strlen(member);
       +        if(g->ngroup > 1)
       +                box->len--;
       +        g->ngroup--;
       +        switch(g->ngroup){
       +        case 0:
       +                vtMemFree(g->group);
       +                g->group = nil;
       +                break;
       +        default:
       +                for(; i < g->ngroup; i++)
       +                        g->group[i] = g->group[i+1];
       +                g->group[i] = nil;                /* prevent accidents */
       +                g->group = vtMemRealloc(g->group, g->ngroup * sizeof(char*));
       +                break;
       +        }
       +
       +        return 1;
       +}
       +
       +static int
       +_groupAddMember(Ubox* box, User* g, char* member)
       +{
       +        User *u;
       +
       +        if((u = _userByUname(box, member)) == nil)
       +                return 0;
       +        if(_groupMember(box, g->uid, u->uname, 0)){
       +                if(strcmp(g->uname, member) == 0)
       +                        vtSetError("uname: '%s' always in own group", member);
       +                else
       +                        vtSetError("uname: '%s' already in group '%s'",
       +                                member, g->uname);
       +                return 0;
       +        }
       +
       +        g->group = vtMemRealloc(g->group, (g->ngroup+1)*sizeof(char*));
       +        g->group[g->ngroup] = vtStrDup(member);
       +        box->len += strlen(member);
       +        g->ngroup++;
       +        if(g->ngroup > 1)
       +                box->len++;
       +
       +        return 1;
       +}
       +
       +int
       +groupMember(char* group, char* member)
       +{
       +        int r;
       +
       +        if(group == nil)
       +                return 0;
       +
       +        vtRLock(ubox.lock);
       +        r = _groupMember(ubox.box, group, member, 0);
       +        vtRUnlock(ubox.lock);
       +
       +        return r;
       +}
       +
       +int
       +groupLeader(char* group, char* member)
       +{
       +        int r;
       +        User *g;
       +
       +        /*
       +         * Is 'member' the leader of 'group'?
       +         * Note that 'group' is a 'uid' and not a 'uname'.
       +         * Uname 'none' cannot be a group leader.
       +         */
       +        if(strcmp(member, unamenone) == 0 || group == nil)
       +                return 0;
       +
       +        vtRLock(ubox.lock);
       +        if((g = _userByUid(ubox.box, group)) == nil){
       +                vtRUnlock(ubox.lock);
       +                return 0;
       +        }
       +        if(g->leader != nil){
       +                if(strcmp(g->leader, member) == 0){
       +                        vtRUnlock(ubox.lock);
       +                        return 1;
       +                }
       +                r = 0;
       +        }
       +        else
       +                r = _groupMember(ubox.box, group, member, 0);
       +        vtRUnlock(ubox.lock);
       +
       +        return r;
       +}
       +
       +static void
       +userFree(User* u)
       +{
       +        int i;
       +
       +        vtMemFree(u->uid);
       +        vtMemFree(u->uname);
       +        if(u->leader != nil)
       +                vtMemFree(u->leader);
       +        if(u->ngroup){
       +                for(i = 0; i < u->ngroup; i++)
       +                        vtMemFree(u->group[i]);
       +                vtMemFree(u->group);
       +        }
       +        vtMemFree(u);
       +}
       +
       +static User*
       +userAlloc(char* uid, char* uname)
       +{
       +        User *u;
       +
       +        u = vtMemAllocZ(sizeof(User));
       +        u->uid = vtStrDup(uid);
       +        u->uname = vtStrDup(uname);
       +
       +        return u;
       +}
       +
       +int
       +validUserName(char* name)
       +{
       +        Rune *r;
       +        static Rune invalid[] = L"#:,()";
       +
       +        for(r = invalid; *r != '\0'; r++){
       +                if(utfrune(name, *r))
       +                        return 0;
       +        }
       +        return 1;
       +}
       +
       +static int
       +userFmt(Fmt* fmt)
       +{
       +        User *u;
       +        int i, r;
       +
       +        u = va_arg(fmt->args, User*);
       +
       +        r = fmtprint(fmt, "%s:%s:", u->uid, u->uname);
       +        if(u->leader != nil)
       +                r += fmtprint(fmt, u->leader);
       +        r += fmtprint(fmt, ":");
       +        if(u->ngroup){
       +                r += fmtprint(fmt, u->group[0]);
       +                for(i = 1; i < u->ngroup; i++)
       +                        r += fmtprint(fmt, ",%s", u->group[i]);
       +        }
       +
       +        return r;
       +}
       +
       +static int
       +usersFileWrite(Ubox* box)
       +{
       +        Fs *fs;
       +        User *u;
       +        int i, r;
       +        Fsys *fsys;
       +        char *p, *q, *s;
       +        File *dir, *file;
       +
       +        if((fsys = fsysGet("main")) == nil)
       +                return 0;
       +        fsysFsRlock(fsys);
       +        fs = fsysGetFs(fsys);
       +
       +        /*
       +         * BUG:
       +         *         the owner/group/permissions need to be thought out.
       +         */
       +        r = 0;
       +        if((dir = fileOpen(fs, "/active")) == nil)
       +                goto tidy0;
       +        if((file = fileWalk(dir, uidadm)) == nil)
       +                file = fileCreate(dir, uidadm, ModeDir|0775, uidadm);
       +        fileDecRef(dir);
       +        if(file == nil)
       +                goto tidy;
       +        dir = file;
       +        if((file = fileWalk(dir, "users")) == nil)
       +                file = fileCreate(dir, "users", 0664, uidadm);
       +        fileDecRef(dir);
       +        if(file == nil)
       +                goto tidy;
       +        if(!fileTruncate(file, uidadm))
       +                goto tidy;
       +
       +        p = s = vtMemAlloc(box->len+1);
       +        q = p + box->len+1;
       +        for(u = box->head; u != nil; u = u->next){
       +                p += snprint(p, q-p, "%s:%s:", u->uid, u->uname);
       +                if(u->leader != nil)
       +                        p+= snprint(p, q-p, u->leader);
       +                p += snprint(p, q-p, ":");
       +                if(u->ngroup){
       +                        p += snprint(p, q-p, u->group[0]);
       +                        for(i = 1; i < u->ngroup; i++)
       +                                p += snprint(p, q-p, ",%s", u->group[i]);
       +                }
       +                p += snprint(p, q-p, "\n");
       +        }
       +        r = fileWrite(file, s, box->len, 0, uidadm);
       +        vtMemFree(s);
       +
       +tidy:
       +        if(file != nil)
       +                fileDecRef(file);
       +tidy0:
       +        fsysFsRUnlock(fsys);
       +        fsysPut(fsys);
       +
       +        return r;
       +}
       +
       +static void
       +uboxRemUser(Ubox* box, User *u)
       +{
       +        User **h, *up;
       +
       +        h = &box->ihash[userHash(u->uid)];
       +        for(up = *h; up != nil && up != u; up = up->ihash)
       +                h = &up->ihash;
       +        assert(up == u);
       +        *h = up->ihash;
       +        box->len -= strlen(u->uid);
       +
       +        h = &box->nhash[userHash(u->uname)];
       +        for(up = *h; up != nil && up != u; up = up->nhash)
       +                h = &up->nhash;
       +        assert(up == u);
       +        *h = up->nhash;
       +        box->len -= strlen(u->uname);
       +
       +        h = &box->head;
       +        for(up = *h; up != nil && strcmp(up->uid, u->uid) != 0; up = up->next)
       +                h = &up->next;
       +        assert(up == u);
       +        *h = u->next;
       +        u->next = nil;
       +
       +        box->len -= 4;
       +        box->nuser--;
       +}
       +
       +static void
       +uboxAddUser(Ubox* box, User* u)
       +{
       +        User **h, *up;
       +
       +        h = &box->ihash[userHash(u->uid)];
       +        u->ihash = *h;
       +        *h = u;
       +        box->len += strlen(u->uid);
       +
       +        h = &box->nhash[userHash(u->uname)];
       +        u->nhash = *h;
       +        *h = u;
       +        box->len += strlen(u->uname);
       +
       +        h = &box->head;
       +        for(up = *h; up != nil && strcmp(up->uid, u->uid) < 0; up = up->next)
       +                h = &up->next;
       +        u->next = *h;
       +        *h = u;
       +
       +        box->len += 4;
       +        box->nuser++;
       +}
       +
       +static void
       +uboxDump(Ubox* box)
       +{
       +        User* u;
       +
       +        consPrint("nuser %d len = %d\n", box->nuser, box->len);
       +
       +        for(u = box->head; u != nil; u = u->next)
       +                consPrint("%U\n", u);
       +}
       +
       +static void
       +uboxFree(Ubox* box)
       +{
       +        User *next, *u;
       +
       +        for(u = box->head; u != nil; u = next){
       +                next = u->next;
       +                userFree(u);
       +        }
       +        vtMemFree(box);
       +}
       +
       +static int
       +uboxInit(char* users, int len)
       +{
       +        User *g, *u;
       +        Ubox *box, *obox;
       +        int blank, comment, i, nline, nuser;
       +        char *buf, *f[5], **line, *p, *q, *s;
       +
       +        /*
       +         * Strip out whitespace and comments.
       +         * Note that comments are pointless, they disappear
       +         * when the server writes the database back out.
       +         */
       +        blank = 1;
       +        comment = nline = 0;
       +
       +        s = p = buf = vtMemAlloc(len+1);
       +        for(q = users; *q != '\0'; q++){
       +                if(*q == '\r' || *q == '\t' || *q == ' ')
       +                        continue;
       +                if(*q == '\n'){
       +                        if(!blank){
       +                                if(p != s){
       +                                        *p++ = '\n';
       +                                        nline++;
       +                                        s = p;
       +                                }
       +                                blank = 1;
       +                        }
       +                        comment = 0;
       +                        continue;
       +                }
       +                if(*q == '#')
       +                        comment = 1;
       +                blank = 0;
       +                if(!comment)
       +                        *p++ = *q;
       +        }
       +        *p = '\0';
       +
       +        line = vtMemAllocZ((nline+2)*sizeof(char*));
       +        if((i = gettokens(buf, line, nline+2, "\n")) != nline){
       +                fprint(2, "nline %d (%d) botch\n", nline, i);
       +                vtMemFree(line);
       +                vtMemFree(buf);
       +                return 0;
       +        }
       +
       +        /*
       +         * Everything is updated in a local Ubox until verified.
       +         */
       +        box = vtMemAllocZ(sizeof(Ubox));
       +
       +        /*
       +         * First pass - check format, check for duplicates
       +         * and enter in hash buckets.
       +         */
       +        nuser = 0;
       +        for(i = 0; i < nline; i++){
       +                s = vtStrDup(line[i]);
       +                if(getfields(s, f, nelem(f), 0, ":") != 4){
       +                        fprint(2, "bad line '%s'\n", line[i]);
       +                        vtMemFree(s);
       +                        continue;
       +                }
       +                if(*f[0] == '\0' || *f[1] == '\0'){
       +                        fprint(2, "bad line '%s'\n", line[i]);
       +                        vtMemFree(s);
       +                        continue;
       +                }
       +                if(!validUserName(f[0])){
       +                        fprint(2, "invalid uid '%s'\n", f[0]);
       +                        vtMemFree(s);
       +                        continue;
       +                }
       +                if(_userByUid(box, f[0]) != nil){
       +                        fprint(2, "duplicate uid '%s'\n", f[0]);
       +                        vtMemFree(s);
       +                        continue;
       +                }
       +                if(!validUserName(f[1])){
       +                        fprint(2, "invalid uname '%s'\n", f[0]);
       +                        vtMemFree(s);
       +                        continue;
       +                }
       +                if(_userByUname(box, f[1]) != nil){
       +                        fprint(2, "duplicate uname '%s'\n", f[1]);
       +                        vtMemFree(s);
       +                        continue;
       +                }
       +
       +                u = userAlloc(f[0], f[1]);
       +                uboxAddUser(box, u);
       +                line[nuser] = line[i];
       +                nuser++;
       +
       +                vtMemFree(s);
       +        }
       +        assert(box->nuser == nuser);
       +
       +        /*
       +         * Second pass - fill in leader and group information.
       +         */
       +        for(i = 0; i < nuser; i++){
       +                s = vtStrDup(line[i]);
       +                getfields(s, f, nelem(f), 0, ":");
       +
       +                assert(g = _userByUname(box, f[1]));
       +                if(*f[2] != '\0'){
       +                        if((u = _userByUname(box, f[2])) == nil)
       +                                g->leader = vtStrDup(g->uname);
       +                        else
       +                                g->leader = vtStrDup(u->uname);
       +                        box->len += strlen(g->leader);
       +                }
       +                for(p = f[3]; p != nil; p = q){
       +                        if((q = utfrune(p, L',')) != nil)
       +                                *q++ = '\0';
       +                        if(!_groupAddMember(box, g, p)){
       +                                // print/log error here
       +                        }
       +                }
       +
       +                vtMemFree(s);
       +        }
       +
       +        vtMemFree(line);
       +        vtMemFree(buf);
       +
       +        for(i = 0; usersMandatory[i] != nil; i++){
       +                if((u = _userByUid(box, usersMandatory[i])) == nil){
       +                        vtSetError("user '%s' is mandatory", usersMandatory[i]);
       +                        uboxFree(box);
       +                        return 0;
       +                }
       +                if(strcmp(u->uid, u->uname) != 0){
       +                        vtSetError("uid/uname for user '%s' must match",
       +                                usersMandatory[i]);
       +                        uboxFree(box);
       +                        return 0;
       +                }
       +        }
       +
       +        vtLock(ubox.lock);
       +        obox = ubox.box;
       +        ubox.box = box;
       +        vtUnlock(ubox.lock);
       +
       +        if(obox != nil)
       +                uboxFree(obox);
       +
       +        return 1;
       +}
       +
       +int
       +usersFileRead(char* path)
       +{
       +        char *p;
       +        File *file;
       +        Fsys *fsys;
       +        int len, r;
       +        uvlong size;
       +
       +        if((fsys = fsysGet("main")) == nil)
       +                return 0;
       +        fsysFsRlock(fsys);
       +
       +        if(path == nil)
       +                path = "/active/adm/users";
       +
       +        r = 0;
       +        if((file = fileOpen(fsysGetFs(fsys), path)) != nil){
       +                if(fileGetSize(file, &size)){
       +                        len = size;
       +                        p = vtMemAlloc(size+1);
       +                        if(fileRead(file, p, len, 0) == len){
       +                                p[len] = '\0';
       +                                r = uboxInit(p, len);
       +                        }
       +                }
       +                fileDecRef(file);
       +        }
       +
       +        fsysFsRUnlock(fsys);
       +        fsysPut(fsys);
       +
       +        return r;
       +}
       +
       +static int
       +cmdUname(int argc, char* argv[])
       +{
       +        User *u, *up;
       +        int d, dflag, i, r;
       +        char *p, *uid, *uname;
       +        char *createfmt = "fsys main create /active/usr/%s %s %s d775";
       +        char *usage = "usage: uname [-d] uname [uid|:uid|%%newname|=leader|+member|-member]";
       +
       +        dflag = 0;
       +
       +        ARGBEGIN{
       +        default:
       +                return cliError(usage);
       +        case 'd':
       +                dflag = 1;
       +                break;
       +        }ARGEND
       +
       +        if(argc < 1){
       +                if(!dflag)
       +                        return cliError(usage);
       +                vtRLock(ubox.lock);
       +                uboxDump(ubox.box);
       +                vtRUnlock(ubox.lock);
       +                return 1;
       +        }
       +
       +        uname = argv[0];
       +        argc--; argv++;
       +
       +        if(argc == 0){
       +                vtRLock(ubox.lock);
       +                if((u = _userByUname(ubox.box, uname)) == nil){
       +                        vtRUnlock(ubox.lock);
       +                        return 0;
       +                }
       +                consPrint("\t%U\n", u);
       +                vtRUnlock(ubox.lock);
       +                return 1;
       +        }
       +
       +        vtLock(ubox.lock);
       +        u = _userByUname(ubox.box, uname);
       +        while(argc--){
       +                if(argv[0][0] == '%'){
       +                        if(u == nil){
       +                                vtUnlock(ubox.lock);
       +                                return 0;
       +                        }
       +                        p = &argv[0][1];
       +                        if((up = _userByUname(ubox.box, p)) != nil){
       +                                vtSetError("uname: uname '%s' already exists",
       +                                        up->uname);
       +                                vtUnlock(ubox.lock);
       +                                return 0;
       +                        }
       +                        for(i = 0; usersMandatory[i] != nil; i++){
       +                                if(strcmp(usersMandatory[i], uname) != 0)
       +                                        continue;
       +                                vtSetError("uname: uname '%s' is mandatory",
       +                                        uname);
       +                                vtUnlock(ubox.lock);
       +                                return 0;
       +                        }
       +
       +                        d = strlen(p) - strlen(u->uname);
       +                        for(up = ubox.box->head; up != nil; up = up->next){
       +                                if(up->leader != nil){
       +                                        if(strcmp(up->leader, u->uname) == 0){
       +                                                vtMemFree(up->leader);
       +                                                up->leader = vtStrDup(p);
       +                                                ubox.box->len += d;
       +                                        }
       +                                }
       +                                for(i = 0; i < up->ngroup; i++){
       +                                        if(strcmp(up->group[i], u->uname) != 0)
       +                                                continue;
       +                                        vtMemFree(up->group[i]);
       +                                        up->group[i] = vtStrDup(p);
       +                                        ubox.box->len += d;
       +                                        break;
       +                                }
       +                        }
       +
       +                        uboxRemUser(ubox.box, u);
       +                        vtMemFree(u->uname);
       +                        u->uname = vtStrDup(p);
       +                        uboxAddUser(ubox.box, u);
       +                }
       +                else if(argv[0][0] == '='){
       +                        if(u == nil){
       +                                vtUnlock(ubox.lock);
       +                                return 0;
       +                        }
       +                        if((up = _userByUname(ubox.box, &argv[0][1])) == nil){
       +                                if(argv[0][1] != '\0'){
       +                                        vtUnlock(ubox.lock);
       +                                        return 0;
       +                                }
       +                        }
       +                        if(u->leader != nil){
       +                                ubox.box->len -= strlen(u->leader);
       +                                vtMemFree(u->leader);
       +                                u->leader = nil;
       +                        }
       +                        if(up != nil){
       +                                u->leader = vtStrDup(up->uname);
       +                                ubox.box->len += strlen(u->leader);
       +                        }
       +                }
       +                else if(argv[0][0] == '+'){
       +                        if(u == nil){
       +                                vtUnlock(ubox.lock);
       +                                return 0;
       +                        }
       +                        if((up = _userByUname(ubox.box, &argv[0][1])) == nil){
       +                                vtUnlock(ubox.lock);
       +                                return 0;
       +                        }
       +                        if(!_groupAddMember(ubox.box, u, up->uname)){
       +                                vtUnlock(ubox.lock);
       +                                return 0;
       +                        }
       +                }
       +                else if(argv[0][0] == '-'){
       +                        if(u == nil){
       +                                vtUnlock(ubox.lock);
       +                                return 0;
       +                        }
       +                        if((up = _userByUname(ubox.box, &argv[0][1])) == nil){
       +                                vtUnlock(ubox.lock);
       +                                return 0;
       +                        }
       +                        if(!_groupRemMember(ubox.box, u, up->uname)){
       +                                vtUnlock(ubox.lock);
       +                                return 0;
       +                        }
       +                }
       +                else{
       +                        if(u != nil){
       +                                vtSetError("uname: uname '%s' already exists",
       +                                        u->uname);
       +                                vtUnlock(ubox.lock);
       +                                return 0;
       +                        }
       +
       +                        uid = argv[0];
       +                        if(*uid == ':')
       +                                uid++;
       +                        if((u = _userByUid(ubox.box, uid)) != nil){
       +                                vtSetError("uname: uid '%s' already exists",
       +                                        u->uid);
       +                                vtUnlock(ubox.lock);
       +                                return 0;
       +                        }
       +
       +                        u = userAlloc(uid, uname);
       +                        uboxAddUser(ubox.box, u);
       +                        if(argv[0][0] != ':'){
       +                                // should have an option for the mode and gid
       +                                p = smprint(createfmt, uname, uname, uname);
       +                                r = cliExec(p);
       +                                vtMemFree(p);
       +                                if(r == 0){
       +                                        vtUnlock(ubox.lock);
       +                                        return 0;
       +                                }
       +                        }
       +                }
       +                argv++;
       +        }
       +
       +        if(usersFileWrite(ubox.box) == 0){
       +                vtUnlock(ubox.lock);
       +                return 0;
       +        }
       +        if(dflag)
       +                uboxDump(ubox.box);
       +        vtUnlock(ubox.lock);
       +
       +        return 1;
       +}
       +
       +static int
       +cmdUsers(int argc, char* argv[])
       +{
       +        Ubox *box;
       +        int dflag, r, wflag;
       +        char *file;
       +        char *usage = "usage: users [-d | -r file] [-w]";
       +
       +        dflag = wflag = 0;
       +        file = nil;
       +
       +        ARGBEGIN{
       +        default:
       +                return cliError(usage);
       +        case 'd':
       +                dflag = 1;
       +                break;
       +        case 'r':
       +                file = ARGF();
       +                if(file == nil)
       +                        return cliError(usage);
       +                break;
       +        case 'w':
       +                wflag = 1;
       +                break;
       +        }ARGEND
       +
       +        if(argc)
       +                return cliError(usage);
       +
       +        if(dflag && file)
       +                return cliError("cannot use -d and -r together");
       +
       +        if(dflag)
       +                uboxInit(usersDefault, sizeof(usersDefault));
       +        else if(file){
       +                if(usersFileRead(file) == 0)
       +                        return 0;
       +        }
       +
       +        vtRLock(ubox.lock);
       +        box = ubox.box;
       +        consPrint("\tnuser %d len %d\n", box->nuser, box->len);
       +
       +        r = 1;
       +        if(wflag)
       +                r = usersFileWrite(box);
       +        vtRUnlock(ubox.lock);
       +        return r;
       +}
       +
       +int
       +usersInit(void)
       +{
       +        fmtinstall('U', userFmt);
       +
       +        ubox.lock = vtLockAlloc();
       +        uboxInit(usersDefault, sizeof(usersDefault));
       +
       +        cliAddCmd("users", cmdUsers);
       +        cliAddCmd("uname", cmdUname);
       +
       +        return 1;
       +}
 (DIR) diff --git a/src/cmd/fossil/Ccli.c b/src/cmd/fossil/Ccli.c
       t@@ -0,0 +1,112 @@
       +#include "stdinc.h"
       +
       +#include "9.h"
       +
       +typedef struct {
       +        char*        argv0;
       +        int        (*cmd)(int, char*[]);
       +} Cmd;
       +
       +static struct {
       +        VtLock*        lock;
       +        Cmd*        cmd;
       +        int        ncmd;
       +        int        hi;
       +} cbox;
       +
       +enum {
       +        NCmdIncr        = 20,
       +};
       +
       +int
       +cliError(char* fmt, ...)
       +{
       +        char *p;
       +        va_list arg;
       +
       +        va_start(arg, fmt);
       +        p = vsmprint(fmt, arg);
       +        vtSetError("%s", p);
       +        free(p);
       +        va_end(arg);
       +
       +        return 0;
       +}
       +
       +int
       +cliExec(char* buf)
       +{
       +        int argc, i, r;
       +        char *argv[20], *p;
       +
       +        p = vtStrDup(buf);
       +        if((argc = tokenize(p, argv, nelem(argv)-1)) == 0){
       +                vtMemFree(p);
       +                return 1;
       +        }
       +        argv[argc] = 0;
       +
       +        if(argv[0][0] == '#'){
       +                vtMemFree(p);
       +                return 1;
       +        }
       +
       +        vtLock(cbox.lock);
       +        for(i = 0; i < cbox.hi; i++){
       +                if(strcmp(cbox.cmd[i].argv0, argv[0]) == 0){
       +                        vtUnlock(cbox.lock);
       +                        if(!(r = cbox.cmd[i].cmd(argc, argv)))
       +                                consPrint("%s\n", vtGetError());
       +                        vtMemFree(p);
       +                        return r;
       +                }
       +        }
       +        vtUnlock(cbox.lock);
       +
       +        consPrint("%s: - eh?\n", argv[0]);
       +        vtMemFree(p);
       +
       +        return 0;
       +}
       +
       +int
       +cliAddCmd(char* argv0, int (*cmd)(int, char*[]))
       +{
       +        int i;
       +        Cmd *opt;
       +
       +        vtLock(cbox.lock);
       +        for(i = 0; i < cbox.hi; i++){
       +                if(strcmp(argv0, cbox.cmd[i].argv0) == 0){
       +                        vtUnlock(cbox.lock);
       +                        return 0;
       +                }
       +        }
       +        if(i >= cbox.hi){
       +                if(cbox.hi >= cbox.ncmd){
       +                        cbox.cmd = vtMemRealloc(cbox.cmd,
       +                                        (cbox.ncmd+NCmdIncr)*sizeof(Cmd));
       +                        memset(&cbox.cmd[cbox.ncmd], 0, NCmdIncr*sizeof(Cmd));
       +                        cbox.ncmd += NCmdIncr;
       +                }
       +        }
       +
       +        opt = &cbox.cmd[cbox.hi];
       +        opt->argv0 = argv0;
       +        opt->cmd = cmd;
       +        cbox.hi++;
       +        vtUnlock(cbox.lock);
       +
       +        return 1;
       +}
       +
       +int
       +cliInit(void)
       +{
       +        cbox.lock = vtLockAlloc();
       +        cbox.cmd = vtMemAllocZ(NCmdIncr*sizeof(Cmd));
       +        cbox.ncmd = NCmdIncr;
       +        cbox.hi = 0;
       +
       +        return 1;
       +}
 (DIR) diff --git a/src/cmd/fossil/Ccmd.c b/src/cmd/fossil/Ccmd.c
       t@@ -0,0 +1,459 @@
       +#include "stdinc.h"
       +
       +#include "9.h"
       +
       +static struct {
       +        VtLock*        lock;
       +
       +        Con*        con;
       +        int        confd[2];
       +        ushort        tag;
       +} cbox;
       +
       +static ulong
       +cmd9pStrtoul(char* s)
       +{
       +        if(strcmp(s, "~0") == 0)
       +                return ~0UL;
       +        return strtoul(s, 0, 0);
       +}
       +
       +static uvlong
       +cmd9pStrtoull(char* s)
       +{
       +        if(strcmp(s, "~0") == 0)
       +                return ~0ULL;
       +        return strtoull(s, 0, 0);
       +}
       +
       +static int
       +cmd9pTag(Fcall*, int, char **argv)
       +{
       +        cbox.tag = strtoul(argv[0], 0, 0)-1;
       +
       +        return 1;
       +}
       +
       +static int
       +cmd9pTwstat(Fcall* f, int, char **argv)
       +{
       +        Dir d;
       +        static uchar buf[DIRMAX];
       +
       +        memset(&d, 0, sizeof d);
       +        nulldir(&d);
       +        d.name = argv[1];
       +        d.uid = argv[2];
       +        d.gid = argv[3];
       +        d.mode = cmd9pStrtoul(argv[4]);
       +        d.mtime = cmd9pStrtoul(argv[5]);
       +        d.length = cmd9pStrtoull(argv[6]);
       +
       +        f->fid = strtol(argv[0], 0, 0);
       +        f->stat = buf;
       +        f->nstat = convD2M(&d, buf, sizeof buf);
       +        if(f->nstat < BIT16SZ){
       +                vtSetError("Twstat: convD2M failed (internal error)");
       +                return 0;
       +        }
       +
       +        return 1;
       +}
       +
       +static int
       +cmd9pTstat(Fcall* f, int, char** argv)
       +{
       +        f->fid = strtol(argv[0], 0, 0);
       +
       +        return 1;
       +}
       +
       +static int
       +cmd9pTremove(Fcall* f, int, char** argv)
       +{
       +        f->fid = strtol(argv[0], 0, 0);
       +
       +        return 1;
       +}
       +
       +static int
       +cmd9pTclunk(Fcall* f, int, char** argv)
       +{
       +        f->fid = strtol(argv[0], 0, 0);
       +
       +        return 1;
       +}
       +
       +static int
       +cmd9pTwrite(Fcall* f, int, char** argv)
       +{
       +        f->fid = strtol(argv[0], 0, 0);
       +        f->offset = strtoll(argv[1], 0, 0);
       +        f->data = argv[2];
       +        f->count = strlen(argv[2]);
       +
       +        return 1;
       +}
       +
       +static int
       +cmd9pTread(Fcall* f, int, char** argv)
       +{
       +        f->fid = strtol(argv[0], 0, 0);
       +        f->offset = strtoll(argv[1], 0, 0);
       +        f->count = strtol(argv[2], 0, 0);
       +
       +        return 1;
       +}
       +
       +static int
       +cmd9pTcreate(Fcall* f, int, char** argv)
       +{
       +        f->fid = strtol(argv[0], 0, 0);
       +        f->name = argv[1];
       +        f->perm = strtol(argv[2], 0, 8);
       +        f->mode = strtol(argv[3], 0, 0);
       +
       +        return 1;
       +}
       +
       +static int
       +cmd9pTopen(Fcall* f, int, char** argv)
       +{
       +        f->fid = strtol(argv[0], 0, 0);
       +        f->mode = strtol(argv[1], 0, 0);
       +
       +        return 1;
       +}
       +
       +static int
       +cmd9pTwalk(Fcall* f, int argc, char** argv)
       +{
       +        int i;
       +
       +        if(argc < 2){
       +                vtSetError("usage: Twalk tag fid newfid [name...]");
       +                return 0;
       +        }
       +        f->fid = strtol(argv[0], 0, 0);
       +        f->newfid = strtol(argv[1], 0, 0);
       +        f->nwname = argc-2;
       +        if(f->nwname > MAXWELEM){
       +                vtSetError("Twalk: too many names");
       +                return 0;
       +        }
       +        for(i = 0; i < argc-2; i++)
       +                f->wname[i] = argv[2+i];
       +
       +        return 1;
       +}
       +
       +static int
       +cmd9pTflush(Fcall* f, int, char** argv)
       +{
       +        f->oldtag = strtol(argv[0], 0, 0);
       +
       +        return 1;
       +}
       +
       +static int
       +cmd9pTattach(Fcall* f, int, char** argv)
       +{
       +        f->fid = strtol(argv[0], 0, 0);
       +        f->afid = strtol(argv[1], 0, 0);
       +        f->uname = argv[2];
       +        f->aname = argv[3];
       +
       +        return 1;
       +}
       +
       +static int
       +cmd9pTauth(Fcall* f, int, char** argv)
       +{
       +        f->afid = strtol(argv[0], 0, 0);
       +        f->uname = argv[1];
       +        f->aname = argv[2];
       +
       +        return 1;
       +}
       +
       +static int
       +cmd9pTversion(Fcall* f, int, char** argv)
       +{
       +        f->msize = strtoul(argv[0], 0, 0);
       +        if(f->msize > cbox.con->msize){
       +                vtSetError("msize too big");
       +                return 0;
       +        }
       +        f->version = argv[1];
       +
       +        return 1;
       +}
       +
       +typedef struct Cmd9p Cmd9p;
       +struct Cmd9p {
       +        char*        name;
       +        int        type;
       +        int        argc;
       +        char*        usage;
       +        int        (*f)(Fcall*, int, char**);
       +};
       +
       +static Cmd9p cmd9pTmsg[] = {
       +        "Tversion", Tversion, 2, "msize version", cmd9pTversion,
       +        "Tauth", Tauth, 3, "afid uname aname", cmd9pTauth,
       +        "Tflush", Tflush, 1, "oldtag", cmd9pTflush,
       +        "Tattach", Tattach, 4, "fid afid uname aname", cmd9pTattach,
       +        "Twalk", Twalk, 0, "fid newfid [name...]", cmd9pTwalk,
       +        "Topen", Topen, 2, "fid mode", cmd9pTopen,
       +        "Tcreate", Tcreate, 4, "fid name perm mode", cmd9pTcreate,
       +        "Tread", Tread, 3, "fid offset count", cmd9pTread,
       +        "Twrite", Twrite, 3, "fid offset data", cmd9pTwrite,
       +        "Tclunk", Tclunk, 1, "fid", cmd9pTclunk,
       +        "Tremove", Tremove, 1, "fid", cmd9pTremove,
       +        "Tstat", Tstat, 1, "fid", cmd9pTstat,
       +        "Twstat", Twstat, 7, "fid name uid gid mode mtime length", cmd9pTwstat,
       +        "nexttag", 0, 0, "", cmd9pTag,
       +};
       +
       +static int
       +cmd9p(int argc, char* argv[])
       +{
       +        int i, n;
       +        Fcall f, t;
       +        uchar *buf;
       +        char *usage;
       +        u32int msize;
       +
       +        usage = "usage: 9p T-message ...";
       +
       +        ARGBEGIN{
       +        default:
       +                return cliError(usage);
       +        }ARGEND
       +        if(argc < 1)
       +                return cliError(usage);
       +
       +        for(i = 0; i < nelem(cmd9pTmsg); i++){
       +                if(strcmp(cmd9pTmsg[i].name, argv[0]) == 0)
       +                        break;
       +        }
       +        if(i == nelem(cmd9pTmsg))
       +                return cliError(usage);
       +        argc--;
       +        argv++;
       +        if(cmd9pTmsg[i].argc && argc != cmd9pTmsg[i].argc){
       +                vtSetError("usage: %s %s",
       +                        cmd9pTmsg[i].name, cmd9pTmsg[i].usage);
       +                return 0;
       +        }
       +
       +        memset(&t, 0, sizeof(t));
       +        t.type = cmd9pTmsg[i].type;
       +        if(t.type == Tversion)
       +                t.tag = NOTAG;
       +        else
       +                t.tag = ++cbox.tag;
       +        msize = cbox.con->msize;
       +        if(!cmd9pTmsg[i].f(&t, argc, argv))
       +                return 0;
       +        buf = vtMemAlloc(msize);
       +        n = convS2M(&t, buf, msize);
       +        if(n <= BIT16SZ){
       +                vtSetError("%s: convS2M error", cmd9pTmsg[i].name);
       +                vtMemFree(buf);
       +                return 0;
       +        }
       +        if(write(cbox.confd[0], buf, n) != n){
       +                vtSetError("%s: write error: %r", cmd9pTmsg[i].name);
       +                vtMemFree(buf);
       +                return 0;
       +        }
       +        consPrint("\t-> %F\n", &t);
       +
       +        if((n = read9pmsg(cbox.confd[0], buf, msize)) <= 0){
       +                vtSetError("%s: read error: %r", cmd9pTmsg[i].name);
       +                vtMemFree(buf);
       +                return 0;
       +        }
       +        if(convM2S(buf, n, &f) == 0){
       +                vtSetError("%s: convM2S error", cmd9pTmsg[i].name);
       +                vtMemFree(buf);
       +                return 0;
       +        }
       +        consPrint("\t<- %F\n", &f);
       +
       +        vtMemFree(buf);
       +        return 1;
       +}
       +
       +static int
       +cmdDot(int argc, char* argv[])
       +{
       +        long l;
       +        Dir *dir;
       +        int fd, r;
       +        vlong length;
       +        char *f, *p, *s, *usage;
       +
       +        usage = "usage: . file";
       +
       +        ARGBEGIN{
       +        default:
       +                return cliError(usage);
       +        }ARGEND
       +        if(argc != 1)
       +                return cliError(usage);
       +
       +        if((dir = dirstat(argv[0])) == nil)
       +                return cliError(". dirstat %s: %r", argv[0]);
       +        length = dir->length;
       +        free(dir);
       +
       +        r = 1;
       +        if(length != 0){
       +                /*
       +                 * Read the whole file in.
       +                 */
       +                if((fd = open(argv[0], OREAD)) < 0)
       +                        return cliError(". open %s: %r", argv[0]);
       +                f = vtMemAlloc(dir->length+1);
       +                if((l = read(fd, f, length)) < 0){
       +                        vtMemFree(f);
       +                        close(fd);
       +                        return cliError(". read %s: %r", argv[0]);
       +                }
       +                close(fd);
       +                f[l] = '\0';
       +
       +                /*
       +                 * Call cliExec() for each line.
       +                 */
       +                for(p = s = f; *p != '\0'; p++){
       +                        if(*p == '\n'){
       +                                *p = '\0';
       +                                if(cliExec(s) == 0){
       +                                        r = 0;
       +                                        consPrint("%s: %R\n", s);
       +                                }
       +                                s = p+1;
       +                        }
       +                }
       +                vtMemFree(f);
       +        }
       +
       +        if(r == 0)
       +                vtSetError("errors in . %#q", argv[0]);
       +        return r;
       +}
       +
       +static int
       +cmdDflag(int argc, char* argv[])
       +{
       +        char *usage;
       +
       +        usage = "usage: dflag";
       +
       +        ARGBEGIN{
       +        default:
       +                return cliError(usage);
       +        }ARGEND
       +        if(argc)
       +                return cliError(usage);
       +
       +        Dflag ^= 1;
       +        consPrint("dflag %d\n", Dflag);
       +
       +        return 1;
       +}
       +
       +static int
       +cmdEcho(int argc, char* argv[])
       +{
       +        char *usage;
       +        int i, nflag;
       +
       +        nflag = 0;
       +        usage = "usage: echo [-n] ...";
       +
       +        ARGBEGIN{
       +        default:
       +                return cliError(usage);
       +        case 'n':
       +                nflag = 1;
       +                break;
       +        }ARGEND
       +
       +        for(i = 0; i < argc; i++){
       +                if(i != 0)
       +                        consPrint(" %s", argv[i]);
       +                else
       +                        consPrint(argv[i]);
       +        }
       +        if(!nflag)
       +                consPrint("\n");
       +
       +        return 1;
       +}
       +
       +static int
       +cmdBind(int argc, char* argv[])
       +{
       +        ulong flag = 0;
       +        char *usage;
       +
       +        usage = "usage: bind [-b|-a|-c|-bc|-ac] new old";
       +
       +        ARGBEGIN{
       +        case 'a':
       +                flag |= MAFTER;
       +                break;
       +        case 'b':
       +                flag |= MBEFORE;
       +                break;
       +        case 'c':
       +                flag |= MCREATE;
       +                break;
       +        default:
       +                return cliError(usage);
       +        }ARGEND
       +
       +        if(argc != 2 || (flag&MAFTER)&&(flag&MBEFORE))
       +                return cliError(usage);
       +
       +        if(bind(argv[0], argv[1], flag) < 0){
       +                /* try to give a less confusing error than the default */
       +                if(access(argv[0], 0) < 0)
       +                        return cliError("bind: %s: %r", argv[0]);
       +                else if(access(argv[1], 0) < 0)
       +                        return cliError("bind: %s: %r", argv[1]);
       +                else
       +                        return cliError("bind %s %s: %r", argv[0], argv[1]);
       +        }
       +        return 1;
       +}
       +
       +int
       +cmdInit(void)
       +{
       +        cbox.lock = vtLockAlloc();
       +        cbox.confd[0] = cbox.confd[1] = -1;
       +
       +        cliAddCmd(".", cmdDot);
       +        cliAddCmd("9p", cmd9p);
       +        cliAddCmd("dflag", cmdDflag);
       +        cliAddCmd("echo", cmdEcho);
       +        cliAddCmd("bind", cmdBind);
       +
       +        if(pipe(cbox.confd) < 0)
       +                return 0;
       +        if((cbox.con = conAlloc(cbox.confd[1], "console", 0)) == nil){
       +                close(cbox.confd[0]);
       +                close(cbox.confd[1]);
       +                cbox.confd[0] = cbox.confd[1] = -1;
       +                return 0;
       +
       +        }
       +        cbox.con->isconsole = 1;
       +
       +        return 1;
       +}
 (DIR) diff --git a/src/cmd/fossil/Ccons.c b/src/cmd/fossil/Ccons.c
       t@@ -0,0 +1,398 @@
       +#include "stdinc.h"
       +
       +#include "9.h"
       +
       +enum {
       +        Nl        = 256,                        /* max. command line length */
       +        Nq        = 8*1024,                /* amount of I/O buffered */
       +};
       +
       +typedef struct Q {
       +        VtLock*        lock;
       +        VtRendez* full;
       +        VtRendez* empty;
       +
       +        char        q[Nq];
       +        int        n;
       +        int        r;
       +        int        w;
       +} Q;
       +
       +typedef struct Cons {
       +        VtLock*        lock;
       +        int        ref;
       +        int        closed;
       +        int        fd;
       +        int        srvfd;
       +        int        ctlfd;
       +        Q*        iq;                /* points to console.iq */
       +        Q*        oq;                /* points to console.oq */
       +} Cons;
       +
       +char *currfsysname;
       +
       +static struct {
       +        Q*        iq;                /* input */
       +        Q*        oq;                /* output */
       +        char        l[Nl];                /* command line assembly */
       +        int        nl;                /* current line length */
       +        int        nopens;
       +
       +        char*        prompt;
       +        int        np;
       +} console;
       +
       +static void
       +consClose(Cons* cons)
       +{
       +        vtLock(cons->lock);
       +        cons->closed = 1;
       +
       +        cons->ref--;
       +        if(cons->ref > 0){
       +                vtLock(cons->iq->lock);
       +                vtWakeup(cons->iq->full);
       +                vtUnlock(cons->iq->lock);
       +                vtLock(cons->oq->lock);
       +                vtWakeup(cons->oq->empty);
       +                vtUnlock(cons->oq->lock);
       +                vtUnlock(cons->lock);
       +                return;
       +        }
       +
       +        if(cons->ctlfd != -1){
       +                close(cons->ctlfd);
       +                cons->srvfd = -1;
       +        }
       +        if(cons->srvfd != -1){
       +                close(cons->srvfd);
       +                cons->srvfd = -1;
       +        }
       +        if(cons->fd != -1){
       +                close(cons->fd);
       +                cons->fd = -1;
       +        }
       +        vtUnlock(cons->lock);
       +        vtLockFree(cons->lock);
       +        vtMemFree(cons);
       +        console.nopens--;
       +}
       +
       +static void
       +consIProc(void* v)
       +{
       +        Q *q;
       +        Cons *cons;
       +        int n, w;
       +        char buf[Nq/4];
       +
       +        vtThreadSetName("consI");
       +
       +        cons = v;
       +        q = cons->iq;
       +        for(;;){
       +                /*
       +                 * Can't tell the difference between zero-length read
       +                 * and eof, so keep calling read until we get an error.
       +                 */
       +                if(cons->closed || (n = read(cons->fd, buf, Nq/4)) < 0)
       +                        break;
       +                vtLock(q->lock);
       +                while(Nq - q->n < n && !cons->closed)
       +                        vtSleep(q->full);
       +                w = Nq - q->w;
       +                if(w < n){
       +                        memmove(&q->q[q->w], buf, w);
       +                        memmove(&q->q[0], buf + w, n - w);
       +                }
       +                else
       +                        memmove(&q->q[q->w], buf, n);
       +                q->w = (q->w + n) % Nq;
       +                q->n += n;
       +                vtWakeup(q->empty);
       +                vtUnlock(q->lock);
       +        }
       +        consClose(cons);
       +}
       +
       +static void
       +consOProc(void* v)
       +{
       +        Q *q;
       +        Cons *cons;
       +        char buf[Nq];
       +        int lastn, n, r;
       +
       +        vtThreadSetName("consO");
       +
       +        cons = v;
       +        q = cons->oq;
       +        vtLock(q->lock);
       +        lastn = 0;
       +        for(;;){
       +                while(lastn == q->n && !cons->closed)
       +                        vtSleep(q->empty);
       +                if((n = q->n - lastn) > Nq)
       +                        n = Nq;
       +                if(n > q->w){
       +                        r = n - q->w;
       +                        memmove(buf, &q->q[Nq - r], r);
       +                        memmove(buf+r, &q->q[0], n - r);
       +                }
       +                else
       +                        memmove(buf, &q->q[q->w - n], n);
       +                lastn = q->n;
       +                vtUnlock(q->lock);
       +                if(cons->closed || write(cons->fd, buf, n) < 0)
       +                        break;
       +                vtLock(q->lock);
       +                vtWakeup(q->empty);
       +        }
       +        consClose(cons);
       +}
       +
       +int
       +consOpen(int fd, int srvfd, int ctlfd)
       +{
       +        Cons *cons;
       +
       +        cons = vtMemAllocZ(sizeof(Cons));
       +        cons->lock = vtLockAlloc();
       +        cons->fd = fd;
       +        cons->srvfd = srvfd;
       +        cons->ctlfd = ctlfd;
       +        cons->iq = console.iq;
       +        cons->oq = console.oq;
       +        console.nopens++;
       +
       +        vtLock(cons->lock);
       +        cons->ref = 2;
       +        cons->closed = 0;
       +        if(vtThread(consOProc, cons) < 0){
       +                cons->ref--;
       +                vtUnlock(cons->lock);
       +                consClose(cons);
       +                return 0;
       +        }
       +        vtUnlock(cons->lock);
       +
       +        if(ctlfd >= 0)
       +                consIProc(cons);
       +        else if(vtThread(consIProc, cons) < 0){
       +                consClose(cons);
       +                return 0;
       +        }
       +
       +        return 1;
       +}
       +
       +static int
       +qWrite(Q* q, char* p, int n)
       +{
       +        int w;
       +
       +        vtLock(q->lock);
       +        if(n > Nq - q->w){
       +                w = Nq - q->w;
       +                memmove(&q->q[q->w], p, w);
       +                memmove(&q->q[0], p + w, n - w);
       +                q->w = n - w;
       +        }
       +        else{
       +                memmove(&q->q[q->w], p, n);
       +                q->w += n;
       +        }
       +        q->n += n;
       +        vtWakeup(q->empty);
       +        vtUnlock(q->lock);
       +
       +        return n;
       +}
       +
       +static Q*
       +qAlloc(void)
       +{
       +        Q *q;
       +
       +        q = vtMemAllocZ(sizeof(Q));
       +        q->lock = vtLockAlloc();
       +        q->full = vtRendezAlloc(q->lock);
       +        q->empty = vtRendezAlloc(q->lock);
       +        q->n = q->r = q->w = 0;
       +
       +        return q;
       +}
       +
       +static void
       +consProc(void*)
       +{
       +        Q *q;
       +        int argc, i, n, r;
       +        char *argv[20], buf[Nq], *lp, *wbuf;
       +        char procname[64];
       +
       +        snprint(procname, sizeof procname, "cons %s", currfsysname);
       +        vtThreadSetName(procname);
       +
       +        q = console.iq;
       +        qWrite(console.oq, console.prompt, console.np);
       +        vtLock(q->lock);
       +        for(;;){
       +                while((n = q->n) == 0)
       +                        vtSleep(q->empty);
       +                r = Nq - q->r;
       +                if(r < n){
       +                        memmove(buf, &q->q[q->r], r);
       +                        memmove(buf + r, &q->q[0], n - r);
       +                }
       +                else
       +                        memmove(buf, &q->q[q->r], n);
       +                q->r = (q->r + n) % Nq;
       +                q->n -= n;
       +                vtWakeup(q->full);
       +                vtUnlock(q->lock);
       +
       +                for(i = 0; i < n; i++){
       +                        switch(buf[i]){
       +                        case '\004':                                /* ^D */
       +                                if(console.nl == 0){
       +                                        qWrite(console.oq, "\n", 1);
       +                                        break;
       +                                }
       +                                /*FALLTHROUGH*/
       +                        default:
       +                                if(console.nl < Nl-1){
       +                                        qWrite(console.oq, &buf[i], 1);
       +                                        console.l[console.nl++] = buf[i];
       +                                }
       +                                continue;
       +                        case '\b':
       +                                if(console.nl != 0){
       +                                        qWrite(console.oq, &buf[i], 1);
       +                                        console.nl--;
       +                                }
       +                                continue;
       +                        case '\n':
       +                                qWrite(console.oq, &buf[i], 1);
       +                                break;
       +                        case '\025':                                /* ^U */
       +                                qWrite(console.oq, "^U\n", 3);
       +                                console.nl = 0;
       +                                break;
       +                        case '\027':                                /* ^W */
       +                                console.l[console.nl] = '\0';
       +                                wbuf = vtMemAlloc(console.nl+1);
       +                                memmove(wbuf, console.l, console.nl+1);
       +                                argc = tokenize(wbuf, argv, nelem(argv));
       +                                if(argc > 0)
       +                                        argc--;
       +                                console.nl = 0;
       +                                lp = console.l;
       +                                for(i = 0; i < argc; i++)
       +                                        lp += sprint(lp, "%q ", argv[i]);
       +                                console.nl = lp - console.l;
       +                                vtMemFree(wbuf);
       +                                qWrite(console.oq, "^W\n", 3);
       +                                if(console.nl == 0)
       +                                        break;
       +                                qWrite(console.oq, console.l, console.nl);
       +                                continue;
       +                        case '\177':
       +                                qWrite(console.oq, "\n", 1);
       +                                console.nl = 0;
       +                                break;
       +                        }
       +
       +                        console.l[console.nl] = '\0';
       +                        if(console.nl != 0)
       +                                cliExec(console.l);
       +
       +                        console.nl = 0;
       +                        qWrite(console.oq, console.prompt, console.np);
       +                }
       +
       +                vtLock(q->lock);
       +        }
       +}
       +
       +int
       +consWrite(char* buf, int len)
       +{
       +        if(console.oq == nil)
       +                return write(2, buf, len);
       +        if(console.nopens == 0)
       +                write(2, buf, len);
       +        return qWrite(console.oq, buf, len);
       +}
       +
       +int
       +consPrompt(char* prompt)
       +{
       +        char buf[ERRMAX];
       +
       +        if(prompt == nil)
       +                prompt = "prompt";
       +
       +        vtMemFree(console.prompt);
       +        console.np = snprint(buf, sizeof(buf), "%s: ", prompt);
       +        console.prompt = vtStrDup(buf);
       +
       +        return console.np;
       +}
       +
       +int
       +consTTY(void)
       +{
       +        int ctl, fd;
       +        char *name, *p;
       +
       +        name = "/dev/cons";
       +        if((fd = open(name, ORDWR)) < 0){
       +                name = "#c/cons";
       +                if((fd = open(name, ORDWR)) < 0){
       +                        vtSetError("consTTY: open %s: %r", name);
       +                        return 0;
       +                }
       +        }
       +
       +        p = smprint("%sctl", name);
       +        if((ctl = open(p, OWRITE)) < 0){
       +                close(fd);
       +                vtSetError("consTTY: open %s: %r", p);
       +                free(p);
       +                return 0;
       +        }
       +        if(write(ctl, "rawon", 5) < 0){
       +                close(ctl);
       +                close(fd);
       +                vtSetError("consTTY: write %s: %r", p);
       +                free(p);
       +                return 0;
       +        }
       +        free(p);
       +
       +        if(consOpen(fd, fd, ctl) == 0){
       +                close(ctl);
       +                close(fd);
       +                return 0;
       +        }
       +
       +        return 1;
       +}
       +
       +int
       +consInit(void)
       +{
       +        console.iq = qAlloc();
       +        console.oq = qAlloc();
       +        console.nl = 0;
       +
       +        consPrompt(nil);
       +
       +        if(vtThread(consProc, nil) < 0){
       +                vtFatal("can't start console proc");
       +                return 0;
       +        }
       +
       +        return 1;
       +}
 (DIR) diff --git a/src/cmd/fossil/Clog.c b/src/cmd/fossil/Clog.c
       t@@ -0,0 +1,40 @@
       +#include "stdinc.h"
       +#include "9.h"
       +
       +/*
       + * To do: This will become something else ('vprint'?).
       + */
       +int
       +consVPrint(char* fmt, va_list args)
       +{
       +        int len, ret;
       +        char buf[256];
       +
       +        len = vsnprint(buf, sizeof(buf), fmt, args);
       +        ret = consWrite(buf, len);
       +
       +        while (len-- > 0 && buf[len] == '\n')
       +                buf[len] = '\0';
       +        /*
       +         * if we do this, checking the root fossil (if /sys/log/fossil is there)
       +         * will spew all over the console.
       +         */
       +        if (0)
       +                syslog(0, "fossil", "%s", buf);
       +        return ret;
       +}
       +
       +/*
       + * To do: This will become 'print'.
       + */
       +int
       +consPrint(char* fmt, ...)
       +{
       +        int ret;
       +        va_list args;
       +
       +        va_start(args, fmt);
       +        ret = consVPrint(fmt, args);
       +        va_end(args);
       +        return ret;
       +}
 (DIR) diff --git a/src/cmd/fossil/archive.c b/src/cmd/fossil/archive.c
       t@@ -0,0 +1,466 @@
       +/*
       + * Archiver.  In charge of sending blocks to Venti.
       + */
       +
       +#include "stdinc.h"
       +#include "dat.h"
       +#include "fns.h"
       +#include "error.h"
       +
       +#include "9.h"        /* for consPrint */
       +
       +#define DEBUG 0
       +
       +static void archThread(void*);
       +
       +struct Arch
       +{
       +        int ref;
       +        uint blockSize;
       +        uint diskSize;
       +        Cache *c;
       +        Fs *fs;
       +        VtSession *z;
       +
       +        VtLock *lk;
       +        VtRendez *starve;
       +        VtRendez *die;
       +};
       +
       +Arch *
       +archInit(Cache *c, Disk *disk, Fs *fs, VtSession *z)
       +{
       +        Arch *a;
       +
       +        a = vtMemAllocZ(sizeof(Arch));
       +
       +        a->c = c;
       +        a->z = z;
       +        a->fs = fs;
       +        a->blockSize = diskBlockSize(disk);
       +        a->lk = vtLockAlloc();
       +        a->starve = vtRendezAlloc(a->lk);
       +
       +        a->ref = 2;
       +        vtThread(archThread, a);
       +
       +        return a;
       +}
       +
       +void
       +archFree(Arch *a)
       +{
       +        /* kill slave */
       +        vtLock(a->lk);
       +        a->die = vtRendezAlloc(a->lk);
       +        vtWakeup(a->starve);
       +        while(a->ref > 1)
       +                vtSleep(a->die);
       +        vtUnlock(a->lk);
       +        vtRendezFree(a->starve);
       +        vtRendezFree(a->die);
       +        vtLockFree(a->lk);
       +        vtMemFree(a);
       +}
       +
       +static int
       +ventiSend(Arch *a, Block *b, uchar *data)
       +{
       +        uint n;
       +        uchar score[VtScoreSize];
       +
       +        if(DEBUG > 1)
       +                fprint(2, "ventiSend: sending %#ux %L to venti\n", b->addr, &b->l);
       +        n = vtZeroTruncate(vtType[b->l.type], data, a->blockSize);
       +        if(DEBUG > 1)
       +                fprint(2, "ventiSend: truncate %d to %d\n", a->blockSize, n);
       +        if(!vtWrite(a->z, score, vtType[b->l.type], data, n)){
       +                fprint(2, "ventiSend: vtWrite block %#ux failed: %R\n", b->addr);
       +                return 0;
       +        }
       +        if(!vtSha1Check(score, data, n)){
       +                uchar score2[VtScoreSize];
       +                vtSha1(score2, data, n);
       +                fprint(2, "ventiSend: vtWrite block %#ux failed vtSha1Check %V %V\n",
       +                        b->addr, score, score2);
       +                return 0;
       +        }
       +        if(!vtSync(a->z))
       +                return 0;
       +        return 1;
       +}
       +
       +/*
       + * parameters for recursion; there are so many,
       + * and some only change occasionally.  this is
       + * easier than spelling things out at each call.
       + */
       +typedef struct Param Param;
       +struct Param
       +{
       +        /* these never change */
       +        uint snapEpoch;        /* epoch for snapshot being archived */
       +        uint blockSize;
       +        Cache *c;
       +        Arch *a;
       +
       +        /* changes on every call */
       +        uint depth;
       +
       +        /* statistics */
       +        uint nfixed;
       +        uint nsend;
       +        uint nvisit;
       +        uint nfailsend;
       +        uint maxdepth;
       +        uint nreclaim;
       +        uint nfake;
       +        uint nreal;
       +
       +        /* these occasionally change (must save old values and put back) */
       +        uint dsize;
       +        uint psize;
       +
       +        /* return value; avoids using stack space */
       +        Label l;
       +        uchar score[VtScoreSize];
       +};
       +
       +static void
       +shaBlock(uchar score[VtScoreSize], Block *b, uchar *data, uint bsize)
       +{
       +        vtSha1(score, data, vtZeroTruncate(vtType[b->l.type], data, bsize));
       +}
       +
       +static uint
       +etype(Entry *e)
       +{
       +        uint t;
       +
       +        if(e->flags&VtEntryDir)
       +                t = BtDir;
       +        else
       +                t = BtData;
       +        return t+e->depth;
       +}
       +
       +static uchar*
       +copyBlock(Block *b, u32int blockSize)
       +{
       +        uchar *data;
       +
       +        data = vtMemAlloc(blockSize);
       +        if(data == nil)
       +                return nil;
       +        memmove(data, b->data, blockSize);
       +        return data;
       +}
       +
       +/*
       + * Walk over the block tree, archiving it to Venti.
       + *
       + * We don't archive the snapshots. Instead we zero the
       + * entries in a temporary copy of the block and archive that.
       + *
       + * Return value is:
       + *
       + *        ArchFailure        some error occurred
       + *        ArchSuccess        block and all children archived
       + *         ArchFaked        success, but block or children got copied
       + */
       +enum
       +{
       +        ArchFailure,
       +        ArchSuccess,
       +        ArchFaked,
       +};
       +static int
       +archWalk(Param *p, u32int addr, uchar type, u32int tag)
       +{
       +        int ret, i, x, psize, dsize;
       +        uchar *data, score[VtScoreSize];
       +        Block *b;
       +        Label l;
       +        Entry *e;
       +        WalkPtr w;
       +
       +        p->nvisit++;
       +
       +        b = cacheLocalData(p->c, addr, type, tag, OReadWrite,0);
       +        if(b == nil){
       +                fprint(2, "archive(%ud, %#ux): cannot find block: %R\n", p->snapEpoch, addr);
       +                if(strcmp(vtGetError(), ELabelMismatch) == 0){
       +                        /* might as well plod on so we write _something_ to Venti */
       +                        memmove(p->score, vtZeroScore, VtScoreSize);
       +                        return ArchFaked;
       +                }
       +                return ArchFailure;
       +        }
       +
       +        if(DEBUG) fprint(2, "%*sarchive(%ud, %#ux): block label %L\n",
       +                p->depth*2, "",  p->snapEpoch, b->addr, &b->l);
       +        p->depth++;
       +        if(p->depth > p->maxdepth)
       +                p->maxdepth = p->depth;
       +
       +        data = b->data;
       +        if((b->l.state&BsVenti) == 0){
       +                initWalk(&w, b, b->l.type==BtDir ? p->dsize : p->psize);
       +                for(i=0; nextWalk(&w, score, &type, &tag, &e); i++){
       +                        if(e){
       +                                if(!(e->flags&VtEntryActive))
       +                                        continue;
       +                                if((e->snap && !e->archive)
       +                                || (e->flags&VtEntryNoArchive)){
       +                                        if(0) fprint(2, "snap; faking %#ux\n", b->addr);
       +                                        if(data == b->data){
       +                                                data = copyBlock(b, p->blockSize);
       +                                                if(data == nil){
       +                                                        ret = ArchFailure;
       +                                                        goto Out;
       +                                                }
       +                                                w.data = data;
       +                                        }
       +                                        memmove(e->score, vtZeroScore, VtScoreSize);
       +                                        e->depth = 0;
       +                                        e->size = 0;
       +                                        e->tag = 0;
       +                                        e->flags &= ~VtEntryLocal;
       +                                        entryPack(e, data, w.n-1);
       +                                        continue;
       +                                }
       +                        }
       +                        addr = globalToLocal(score);
       +                        if(addr == NilBlock)
       +                                continue;
       +                        dsize = p->dsize;
       +                        psize = p->psize;
       +                        if(e){
       +                                p->dsize= e->dsize;
       +                                p->psize = e->psize;
       +                        }
       +                        vtUnlock(b->lk);
       +                        x = archWalk(p, addr, type, tag);
       +                        vtLock(b->lk);
       +                        if(e){
       +                                p->dsize = dsize;
       +                                p->psize = psize;
       +                        }
       +                        while(b->iostate != BioClean && b->iostate != BioDirty)
       +                                vtSleep(b->ioready);
       +                        switch(x){
       +                        case ArchFailure:
       +                                fprint(2, "archWalk %#ux failed; ptr is in %#ux offset %d\n",
       +                                        addr, b->addr, i);
       +                                ret = ArchFailure;
       +                                goto Out;
       +                        case ArchFaked:
       +                                /*
       +                                 * When we're writing the entry for an archive directory
       +                                 * (like /archive/2003/1215) then even if we've faked
       +                                 * any data, record the score unconditionally.
       +                                 * This way, we will always record the Venti score here.
       +                                 * Otherwise, temporary data or corrupted file system
       +                                 * would cause us to keep holding onto the on-disk
       +                                 * copy of the archive.
       +                                 */
       +                                if(e==nil || !e->archive)
       +                                if(data == b->data){
       +if(0) fprint(2, "faked %#ux, faking %#ux (%V)\n", addr, b->addr, p->score);
       +                                        data = copyBlock(b, p->blockSize);
       +                                        if(data == nil){
       +                                                ret = ArchFailure;
       +                                                goto Out;
       +                                        }
       +                                        w.data = data;
       +                                }
       +                                /* fall through */
       +if(0) fprint(2, "falling\n");
       +                        case ArchSuccess:
       +                                if(e){
       +                                        memmove(e->score, p->score, VtScoreSize);
       +                                        e->flags &= ~VtEntryLocal;
       +                                        entryPack(e, data, w.n-1);
       +                                }else
       +                                        memmove(data+(w.n-1)*VtScoreSize, p->score, VtScoreSize);
       +                                if(data == b->data){
       +                                        blockDirty(b);
       +                                        /*
       +                                         * If b is in the active tree, then we need to note that we've
       +                                         * just removed addr from the active tree (replacing it with the 
       +                                         * copy we just stored to Venti).  If addr is in other snapshots,
       +                                         * this will close addr but not free it, since it has a non-empty
       +                                         * epoch range.
       +                                         *
       +                                         * If b is in the active tree but has been copied (this can happen
       +                                         * if we get killed at just the right moment), then we will
       +                                         * mistakenly leak its kids.  
       +                                         *
       +                                         * The children of an archive directory (e.g., /archive/2004/0604)
       +                                         * are not treated as in the active tree.
       +                                         */
       +                                        if((b->l.state&BsCopied)==0 && (e==nil || e->snap==0))
       +                                                blockRemoveLink(b, addr, p->l.type, p->l.tag, 0);
       +                                }
       +                                break;
       +                        }
       +                }
       +
       +                if(!ventiSend(p->a, b, data)){
       +                        p->nfailsend++;
       +                        ret = ArchFailure;
       +                        goto Out;
       +                }
       +                p->nsend++;
       +                if(data != b->data)
       +                        p->nfake++;
       +                if(data == b->data){        /* not faking it, so update state */
       +                        p->nreal++;
       +                        l = b->l;
       +                        l.state |= BsVenti;
       +                        if(!blockSetLabel(b, &l, 0)){
       +                                ret = ArchFailure;
       +                                goto Out;
       +                        }
       +                }
       +        }
       +
       +        shaBlock(p->score, b, data, p->blockSize);
       +if(0) fprint(2, "ventisend %V %p %p %p\n", p->score, data, b->data, w.data);
       +        ret = data!=b->data ? ArchFaked : ArchSuccess;
       +        p->l = b->l;
       +Out:
       +        if(data != b->data)
       +                vtMemFree(data);
       +        p->depth--;
       +        blockPut(b);
       +        return ret;
       +}
       +
       +static void
       +archThread(void *v)
       +{
       +        Arch *a = v;
       +        Block *b;
       +        Param p;
       +        Super super;
       +        int ret;
       +        u32int addr;
       +        uchar rbuf[VtRootSize];
       +        VtRoot root;
       +
       +        vtThreadSetName("arch");
       +
       +        for(;;){
       +                /* look for work */
       +                vtLock(a->fs->elk);
       +                b = superGet(a->c, &super);
       +                if(b == nil){
       +                        vtUnlock(a->fs->elk);
       +                        fprint(2, "archThread: superGet: %R\n");
       +                        sleep(60*1000);
       +                        continue;
       +                }
       +                addr = super.next;
       +                if(addr != NilBlock && super.current == NilBlock){
       +                        super.current = addr;
       +                        super.next = NilBlock;
       +                        superPack(&super, b->data);
       +                        blockDirty(b);
       +                }else
       +                        addr = super.current;
       +                blockPut(b);
       +                vtUnlock(a->fs->elk);
       +
       +                if(addr == NilBlock){
       +                        /* wait for work */
       +                        vtLock(a->lk);
       +                        vtSleep(a->starve);
       +                        if(a->die != nil)
       +                                goto Done;
       +                        vtUnlock(a->lk);
       +                        continue;
       +                }
       +
       +sleep(10*1000);        /* window of opportunity to provoke races */
       +
       +                /* do work */
       +                memset(&p, 0, sizeof p);
       +                p.blockSize = a->blockSize;
       +                p.dsize = 3*VtEntrySize;        /* root has three Entries */
       +                p.c = a->c;
       +                p.a = a;
       +
       +                ret = archWalk(&p, addr, BtDir, RootTag);
       +                switch(ret){
       +                default:
       +                        abort();
       +                case ArchFailure:
       +                        fprint(2, "archiveBlock %#ux: %R\n", addr);
       +                        sleep(60*1000);
       +                        continue;
       +                case ArchSuccess:
       +                case ArchFaked:
       +                        break;
       +                }
       +
       +                if(0) fprint(2, "archiveSnapshot 0x%#ux: maxdepth %ud nfixed %ud"
       +                        " send %ud nfailsend %ud nvisit %ud"
       +                        " nreclaim %ud nfake %ud nreal %ud\n",
       +                        addr, p.maxdepth, p.nfixed,
       +                        p.nsend, p.nfailsend, p.nvisit,
       +                        p.nreclaim, p.nfake, p.nreal);
       +                if(0) fprint(2, "archiveBlock %V (%ud)\n", p.score, p.blockSize);
       +
       +                /* tie up vac root */
       +                memset(&root, 0, sizeof root);
       +                root.version = VtRootVersion;
       +                strecpy(root.type, root.type+sizeof root.type, "vac");
       +                strecpy(root.name, root.name+sizeof root.name, "fossil");
       +                memmove(root.score, p.score, VtScoreSize);
       +                memmove(root.prev, super.last, VtScoreSize);
       +                root.blockSize = a->blockSize;
       +                vtRootPack(&root, rbuf);
       +                if(!vtWrite(a->z, p.score, VtRootType, rbuf, VtRootSize)
       +                || !vtSha1Check(p.score, rbuf, VtRootSize)){
       +                        fprint(2, "vtWriteBlock %#ux: %R\n", addr);
       +                        sleep(60*1000);
       +                        continue;
       +                }
       +
       +                /* record success */
       +                vtLock(a->fs->elk);
       +                b = superGet(a->c, &super);
       +                if(b == nil){
       +                        vtUnlock(a->fs->elk);
       +                        fprint(2, "archThread: superGet: %R\n");
       +                        sleep(60*1000);
       +                        continue;
       +                }
       +                super.current = NilBlock;
       +                memmove(super.last, p.score, VtScoreSize);
       +                superPack(&super, b->data);
       +                blockDirty(b);
       +                blockPut(b);
       +                vtUnlock(a->fs->elk);
       +
       +                consPrint("archive vac:%V\n", p.score);
       +        }
       +
       +Done:
       +        a->ref--;
       +        vtWakeup(a->die);
       +        vtUnlock(a->lk);
       +}
       +
       +void
       +archKick(Arch *a)
       +{
       +        if(a == nil){
       +                fprint(2, "warning: archKick nil\n");
       +                return;
       +        }
       +        vtLock(a->lk);
       +        vtWakeup(a->starve);
       +        vtUnlock(a->lk);
       +}
 (DIR) diff --git a/src/cmd/fossil/bwatch.c b/src/cmd/fossil/bwatch.c
       t@@ -0,0 +1,421 @@
       +#include "stdinc.h"
       +#include "dat.h"
       +#include "fns.h"
       +#include "error.h"
       +
       +/*
       + * Lock watcher.  Check that locking of blocks is always down.
       + *
       + * This is REALLY slow, and it won't work when the blocks aren't
       + * arranged in a tree (e.g., after the first snapshot).  But it's great
       + * for debugging.
       + */
       +enum
       +{
       +        MaxLock = 16,
       +        HashSize = 1009,
       +};
       +
       +/*
       + * Thread-specific watch state.
       + */
       +typedef struct WThread WThread;
       +struct WThread
       +{
       +        Block *b[MaxLock];        /* blocks currently held */
       +        uint nb;
       +        uint pid;
       +};
       +
       +typedef struct WMap WMap;
       +typedef struct WEntry WEntry;
       +
       +struct WEntry
       +{
       +        uchar c[VtScoreSize];
       +        uchar p[VtScoreSize];
       +        int off;
       +
       +        WEntry *cprev;
       +        WEntry *cnext;
       +        WEntry *pprev;
       +        WEntry *pnext;
       +};
       +
       +struct WMap
       +{
       +        VtLock *lk;
       +
       +        WEntry *hchild[HashSize];
       +        WEntry *hparent[HashSize];
       +};
       +
       +static WMap map;
       +static void **wp;
       +static uint blockSize;
       +static WEntry *pool;
       +uint bwatchDisabled;
       +
       +static uint
       +hash(uchar score[VtScoreSize])
       +{
       +        uint i, h;
       +
       +        h = 0;
       +        for(i=0; i<VtScoreSize; i++)
       +                h = h*37 + score[i];
       +        return h%HashSize;
       +}
       +
       +#include <pool.h>
       +static void
       +freeWEntry(WEntry *e)
       +{
       +        memset(e, 0, sizeof(WEntry));
       +        e->pnext = pool;
       +        pool = e;
       +}
       +
       +static WEntry*
       +allocWEntry(void)
       +{
       +        int i;
       +        WEntry *w;
       +
       +        w = pool;
       +        if(w == nil){
       +                w = vtMemAllocZ(1024*sizeof(WEntry));
       +                for(i=0; i<1024; i++)
       +                        freeWEntry(&w[i]);
       +                w = pool;
       +        }
       +        pool = w->pnext;
       +        memset(w, 0, sizeof(WEntry));
       +        return w;
       +}
       +
       +/*
       + * remove all dependencies with score as a parent
       + */
       +static void
       +_bwatchResetParent(uchar *score)
       +{
       +        WEntry *w, *next;
       +        uint h;
       +
       +        h = hash(score);
       +        for(w=map.hparent[h]; w; w=next){
       +                next = w->pnext;
       +                if(memcmp(w->p, score, VtScoreSize) == 0){
       +                        if(w->pnext)
       +                                w->pnext->pprev = w->pprev;
       +                        if(w->pprev)
       +                                w->pprev->pnext = w->pnext;
       +                        else
       +                                map.hparent[h] = w->pnext;
       +                        if(w->cnext)
       +                                w->cnext->cprev = w->cprev;
       +                        if(w->cprev)
       +                                w->cprev->cnext = w->cnext;
       +                        else
       +                                map.hchild[hash(w->c)] = w->cnext;
       +                        freeWEntry(w);
       +                }
       +        }
       +}
       +/*
       + * and child 
       + */
       +static void
       +_bwatchResetChild(uchar *score)
       +{
       +        WEntry *w, *next;
       +        uint h;
       +
       +        h = hash(score);
       +        for(w=map.hchild[h]; w; w=next){
       +                next = w->cnext;
       +                if(memcmp(w->c, score, VtScoreSize) == 0){
       +                        if(w->pnext)
       +                                w->pnext->pprev = w->pprev;
       +                        if(w->pprev)
       +                                w->pprev->pnext = w->pnext;
       +                        else
       +                                map.hparent[hash(w->p)] = w->pnext;
       +                        if(w->cnext)
       +                                w->cnext->cprev = w->cprev;
       +                        if(w->cprev)
       +                                w->cprev->cnext = w->cnext;
       +                        else
       +                                map.hchild[h] = w->cnext;
       +                        freeWEntry(w);
       +                }
       +        }
       +}
       +
       +static uchar*
       +parent(uchar c[VtScoreSize], int *off)
       +{
       +        WEntry *w;
       +        uint h;
       +
       +        h = hash(c);
       +        for(w=map.hchild[h]; w; w=w->cnext)
       +                if(memcmp(w->c, c, VtScoreSize) == 0){
       +                        *off = w->off;
       +                        return w->p;
       +                }
       +        return nil;
       +}
       +
       +static void
       +addChild(uchar p[VtEntrySize], uchar c[VtEntrySize], int off)
       +{
       +        uint h;
       +        WEntry *w;
       +
       +        w = allocWEntry();
       +        memmove(w->p, p, VtScoreSize);
       +        memmove(w->c, c, VtScoreSize);
       +        w->off = off;
       +
       +        h = hash(p);
       +        w->pnext = map.hparent[h];
       +        if(w->pnext)
       +                w->pnext->pprev = w;
       +        map.hparent[h] = w;
       +
       +        h = hash(c);
       +        w->cnext = map.hchild[h];
       +        if(w->cnext)
       +                w->cnext->cprev = w;
       +        map.hchild[h] = w;
       +}
       +
       +void
       +bwatchReset(uchar score[VtScoreSize])
       +{
       +        vtLock(map.lk);
       +        _bwatchResetParent(score);
       +        _bwatchResetChild(score);
       +        vtUnlock(map.lk);
       +}
       +
       +void
       +bwatchInit(void)
       +{
       +        map.lk = vtLockAlloc();
       +        wp = privalloc();
       +        *wp = nil;
       +}
       +
       +void
       +bwatchSetBlockSize(uint bs)
       +{
       +        blockSize = bs;
       +}
       +
       +static WThread*
       +getWThread(void)
       +{
       +        WThread *w;
       +
       +        w = *wp;
       +        if(w == nil || w->pid != getpid()){
       +                w = vtMemAllocZ(sizeof(WThread));
       +                *wp = w;
       +                w->pid = getpid();
       +        }
       +        return w;
       +}
       +
       +/*
       + * Derive dependencies from the contents of b.
       + */
       +void
       +bwatchDependency(Block *b)
       +{
       +        int i, epb, ppb;
       +        Entry e;
       +
       +        if(bwatchDisabled)
       +                return;
       +
       +        vtLock(map.lk);
       +        _bwatchResetParent(b->score);
       +
       +        switch(b->l.type){
       +        case BtData:
       +                break;
       +
       +        case BtDir:
       +                epb = blockSize / VtEntrySize;
       +                for(i=0; i<epb; i++){
       +                        entryUnpack(&e, b->data, i);
       +                        if(!(e.flags & VtEntryActive))
       +                                continue;
       +                        addChild(b->score, e.score, i);
       +                }
       +                break;
       +
       +        default:
       +                ppb = blockSize / VtScoreSize;
       +                for(i=0; i<ppb; i++)
       +                        addChild(b->score, b->data+i*VtScoreSize, i);
       +                break;
       +        }
       +        vtUnlock(map.lk);
       +}
       +
       +static int
       +depth(uchar *s)
       +{
       +        int d, x;
       +
       +        d = -1;
       +        while(s){
       +                d++;
       +                s = parent(s, &x);
       +        }
       +        return d;
       +}
       +
       +static int
       +lockConflicts(uchar xhave[VtScoreSize], uchar xwant[VtScoreSize])
       +{
       +        uchar *have, *want;
       +        int havedepth, wantdepth, havepos, wantpos;
       +
       +        have = xhave;
       +        want = xwant;
       +
       +        havedepth = depth(have);
       +        wantdepth = depth(want);
       +
       +        /*
       +         * walk one or the other up until they're both
       +          * at the same level.
       +         */
       +        havepos = -1;
       +        wantpos = -1;
       +        have = xhave;
       +        want = xwant;
       +        while(wantdepth > havedepth){
       +                wantdepth--;
       +                want = parent(want, &wantpos);
       +        }
       +        while(havedepth > wantdepth){
       +                havedepth--;
       +                have = parent(have, &havepos);
       +        }
       +
       +        /*
       +         * walk them up simultaneously until we reach
       +         * a common ancestor.
       +         */
       +        while(have && want && memcmp(have, want, VtScoreSize) != 0){
       +                have = parent(have, &havepos);
       +                want = parent(want, &wantpos);
       +        }
       +
       +        /*
       +         * not part of same tree.  happens mainly with
       +         * newly allocated blocks.
       +         */
       +        if(!have || !want)
       +                return 0;
       +
       +        /*
       +         * never walked want: means we want to lock
       +         * an ancestor of have.  no no.
       +         */
       +        if(wantpos == -1)
       +                return 1;
       +
       +        /*
       +         * never walked have: means we want to lock a
       +         * child of have.  that's okay.
       +         */
       +        if(havepos == -1)
       +                return 0;
       +
       +        /*
       +         * walked both: they're from different places in the tree.
       +         * require that the left one be locked before the right one.
       +         * (this is questionable, but it puts a total order on the block tree).
       +         */
       +        return havepos < wantpos;
       +}
       +
       +static void
       +stop(void)
       +{
       +        int fd;
       +        char buf[32];
       +
       +        snprint(buf, sizeof buf, "#p/%d/ctl", getpid());
       +        fd = open(buf, OWRITE);
       +        write(fd, "stop", 4);
       +        close(fd);
       +}
       +
       +/*
       + * Check whether the calling thread can validly lock b.
       + * That is, check that the calling thread doesn't hold
       + * locks for any of b's children.
       + */
       +void
       +bwatchLock(Block *b)
       +{
       +        int i;
       +        WThread *w;
       +
       +        if(bwatchDisabled)
       +                return;
       +
       +        if(b->part != PartData)
       +                return;
       +
       +        vtLock(map.lk);
       +        w = getWThread();
       +        for(i=0; i<w->nb; i++){
       +                if(lockConflicts(w->b[i]->score, b->score)){
       +                        fprint(2, "%d: have block %V; shouldn't lock %V\n",
       +                                w->pid, w->b[i]->score, b->score);
       +                        stop();
       +                }
       +        }
       +        vtUnlock(map.lk);
       +        if(w->nb >= MaxLock){
       +                fprint(2, "%d: too many blocks held\n", w->pid);
       +                stop();
       +        }else
       +                w->b[w->nb++] = b;
       +}
       +
       +/*
       + * Note that the calling thread is about to unlock b.
       + */
       +void
       +bwatchUnlock(Block *b)
       +{
       +        int i;
       +        WThread *w;
       +
       +        if(bwatchDisabled)
       +                return;
       +
       +        if(b->part != PartData)
       +                return;
       +
       +        w = getWThread();
       +        for(i=0; i<w->nb; i++)
       +                if(w->b[i] == b)
       +                        break;
       +        if(i>=w->nb){
       +                fprint(2, "%d: unlock of unlocked block %V\n", w->pid, b->score);
       +                stop();
       +        }else
       +                w->b[i] = w->b[--w->nb];
       +}
       +
 (DIR) diff --git a/src/cmd/fossil/cache.c b/src/cmd/fossil/cache.c
       t@@ -0,0 +1,2114 @@
       +#include "stdinc.h"
       +#include "dat.h"
       +#include "fns.h"
       +#include "error.h"
       +
       +#include "9.h"        /* for cacheFlush */
       +
       +typedef struct FreeList FreeList;
       +typedef struct BAddr BAddr;
       +
       +enum {
       +        BadHeap = ~0,
       +};
       +
       +/*
       + * Store data to the memory cache in c->size blocks
       + * with the block zero extended to fill it out.  When writing to
       + * Venti, the block will be zero truncated.  The walker will also check
       + * that the block fits within psize or dsize as the case may be.
       + */
       +
       +struct Cache
       +{
       +        VtLock        *lk;
       +        int         ref;
       +        int        mode;
       +
       +        Disk         *disk;
       +        int        size;                        /* block size */
       +        int        ndmap;                /* size of per-block dirty pointer map used in blockWrite */
       +        VtSession *z;
       +        u32int        now;                        /* ticks for usage timestamps */
       +        Block        **heads;                /* hash table for finding address */
       +        int        nheap;                        /* number of available victims */
       +        Block        **heap;                        /* heap for locating victims */
       +        long        nblocks;                /* number of blocks allocated */
       +        Block        *blocks;                /* array of block descriptors */
       +        u8int        *mem;                        /* memory for all block data & blists */
       +
       +        BList        *blfree;
       +        VtRendez *blrend;
       +
       +        int         ndirty;                        /* number of dirty blocks in the cache */
       +        int         maxdirty;                /* max number of dirty blocks */
       +        u32int        vers;
       +
       +        long hashSize;
       +
       +        FreeList *fl;
       +
       +        VtRendez *die;                        /* daemon threads should die when != nil */
       +
       +        VtRendez *flush;
       +        VtRendez *flushwait;
       +        VtRendez *heapwait;
       +        BAddr *baddr;
       +        int bw, br, be;
       +        int nflush;
       +
       +        Periodic *sync;
       +
       +        /* unlink daemon */
       +        BList *uhead;
       +        BList *utail;
       +        VtRendez *unlink;
       +
       +        /* block counts */
       +        int nused;
       +        int ndisk;
       +};
       +
       +struct BList {
       +        int part;
       +        u32int addr;
       +        uchar type;
       +        u32int tag;
       +        u32int epoch;
       +        u32int vers;
       +
       +        int recurse;        /* for block unlink */
       +
       +        /* for roll back */
       +        int index;                        /* -1 indicates not valid */
       +        union {
       +                uchar score[VtScoreSize];
       +                uchar entry[VtEntrySize];
       +        } old;
       +        BList *next;
       +};
       +
       +struct BAddr {
       +        int part;
       +        u32int addr;
       +        u32int vers;
       +};
       +
       +struct FreeList {
       +        VtLock *lk;
       +        u32int last;                /* last block allocated */
       +        u32int end;                /* end of data partition */
       +        u32int nused;                /* number of used blocks */
       +        u32int epochLow;        /* low epoch when last updated nused */
       +};
       +
       +static FreeList *flAlloc(u32int end);
       +static void flFree(FreeList *fl);
       +
       +static Block *cacheBumpBlock(Cache *c);
       +static void heapDel(Block*);
       +static void heapIns(Block*);
       +static void cacheCheck(Cache*);
       +static void unlinkThread(void *a);
       +static void flushThread(void *a);
       +static void unlinkBody(Cache *c);
       +static int cacheFlushBlock(Cache *c);
       +static void cacheSync(void*);
       +static BList *blistAlloc(Block*);
       +static void blistFree(Cache*, BList*);
       +static void doRemoveLink(Cache*, BList*);
       +
       +/*
       + * Mapping from local block type to Venti type
       + */
       +int vtType[BtMax] = {
       +        VtDataType,                /* BtData | 0  */
       +        VtPointerType0,                /* BtData | 1  */
       +        VtPointerType1,                /* BtData | 2  */
       +        VtPointerType2,                /* BtData | 3  */
       +        VtPointerType3,                /* BtData | 4  */
       +        VtPointerType4,                /* BtData | 5  */
       +        VtPointerType5,                /* BtData | 6  */
       +        VtPointerType6,                /* BtData | 7  */
       +        VtDirType,                /* BtDir | 0  */
       +        VtPointerType0,                /* BtDir | 1  */
       +        VtPointerType1,                /* BtDir | 2  */
       +        VtPointerType2,                /* BtDir | 3  */
       +        VtPointerType3,                /* BtDir | 4  */
       +        VtPointerType4,                /* BtDir | 5  */
       +        VtPointerType5,                /* BtDir | 6  */
       +        VtPointerType6,                /* BtDir | 7  */
       +};
       +
       +/*
       + * Allocate the memory cache.
       + */
       +Cache *
       +cacheAlloc(Disk *disk, VtSession *z, ulong nblocks, int mode)
       +{
       +        int i;
       +        Cache *c;
       +        Block *b;
       +        BList *bl;
       +        u8int *p;
       +        int nbl;
       +
       +        c = vtMemAllocZ(sizeof(Cache));
       +
       +        /* reasonable number of BList elements */
       +        nbl = nblocks * 4;
       +
       +        c->lk = vtLockAlloc();
       +        c->ref = 1;
       +        c->disk = disk;
       +        c->z = z;
       +        c->size = diskBlockSize(disk);
       +bwatchSetBlockSize(c->size);
       +        /* round c->size up to be a nice multiple */
       +        c->size = (c->size + 127) & ~127;
       +        c->ndmap = (c->size/20 + 7) / 8;
       +        c->nblocks = nblocks;
       +        c->hashSize = nblocks;
       +        c->heads = vtMemAllocZ(c->hashSize*sizeof(Block*));
       +        c->heap = vtMemAllocZ(nblocks*sizeof(Block*));
       +        c->blocks = vtMemAllocZ(nblocks*sizeof(Block));
       +        c->mem = vtMemAllocZ(nblocks * (c->size + c->ndmap) + nbl * sizeof(BList));
       +        c->baddr = vtMemAllocZ(nblocks * sizeof(BAddr));
       +        c->mode = mode;
       +        c->vers++;
       +        p = c->mem;
       +        for(i = 0; i < nblocks; i++){
       +                b = &c->blocks[i];
       +                b->lk = vtLockAlloc();
       +                b->c = c;
       +                b->data = p;
       +                b->heap = i;
       +                b->ioready = vtRendezAlloc(b->lk);
       +                c->heap[i] = b;
       +                p += c->size;
       +        }
       +        c->nheap = nblocks;
       +        for(i = 0; i < nbl; i++){
       +                bl = (BList*)p;
       +                bl->next = c->blfree;
       +                c->blfree = bl;
       +                p += sizeof(BList);
       +        }
       +        /* separate loop to keep blocks and blists reasonably aligned */
       +        for(i = 0; i < nblocks; i++){
       +                b = &c->blocks[i];
       +                b->dmap = p;
       +                p += c->ndmap;
       +        }
       +
       +        c->blrend = vtRendezAlloc(c->lk);
       +
       +        c->maxdirty = nblocks*(DirtyPercentage*0.01);
       +
       +        c->fl = flAlloc(diskSize(disk, PartData));
       +
       +        c->unlink = vtRendezAlloc(c->lk);
       +        c->flush = vtRendezAlloc(c->lk);
       +        c->flushwait = vtRendezAlloc(c->lk);
       +        c->heapwait = vtRendezAlloc(c->lk);
       +        c->sync = periodicAlloc(cacheSync, c, 30*1000);
       +
       +        if(mode == OReadWrite){
       +                c->ref += 2;
       +                vtThread(unlinkThread, c);
       +                vtThread(flushThread, c);
       +        }
       +        cacheCheck(c);
       +
       +        return c;
       +}
       +
       +/*
       + * Free the whole memory cache, flushing all dirty blocks to the disk.
       + */
       +void
       +cacheFree(Cache *c)
       +{
       +        int i;
       +
       +        /* kill off daemon threads */
       +        vtLock(c->lk);
       +        c->die = vtRendezAlloc(c->lk);
       +        periodicKill(c->sync);
       +        vtWakeup(c->flush);
       +        vtWakeup(c->unlink);
       +        while(c->ref > 1)
       +                vtSleep(c->die);
       +
       +        /* flush everything out */
       +        do {
       +                unlinkBody(c);
       +                vtUnlock(c->lk);
       +                while(cacheFlushBlock(c))
       +                        ;
       +                diskFlush(c->disk);
       +                vtLock(c->lk);
       +        } while(c->uhead || c->ndirty);
       +        vtUnlock(c->lk);
       +
       +        cacheCheck(c);
       +
       +        for(i = 0; i < c->nblocks; i++){
       +                assert(c->blocks[i].ref == 0);
       +                vtRendezFree(c->blocks[i].ioready);
       +                vtLockFree(c->blocks[i].lk);
       +        }
       +        flFree(c->fl);
       +        vtMemFree(c->baddr);
       +        vtMemFree(c->heads);
       +        vtMemFree(c->blocks);
       +        vtMemFree(c->mem);
       +        vtLockFree(c->lk);
       +        diskFree(c->disk);
       +        vtRendezFree(c->blrend);
       +        /* don't close vtSession */
       +        vtMemFree(c);
       +}
       +
       +static void
       +cacheDump(Cache *c)
       +{
       +        int i;
       +        Block *b;
       +
       +        for(i = 0; i < c->nblocks; i++){
       +                b = &c->blocks[i];
       +                fprint(2, "%d. p=%d a=%ud %V t=%d ref=%d state=%s io=%s pc=%#p\n",
       +                        i, b->part, b->addr, b->score, b->l.type, b->ref,
       +                        bsStr(b->l.state), bioStr(b->iostate), b->pc);
       +        }
       +}
       +
       +static void
       +cacheCheck(Cache *c)
       +{
       +        u32int size, now;
       +        int i, k, refed;
       +        static uchar zero[VtScoreSize];
       +        Block *b;
       +
       +        size = c->size;
       +        now = c->now;
       +
       +        for(i = 0; i < c->nheap; i++){
       +                if(c->heap[i]->heap != i)
       +                        vtFatal("mis-heaped at %d: %d", i, c->heap[i]->heap);
       +                if(i > 0 && c->heap[(i - 1) >> 1]->used - now > c->heap[i]->used - now)
       +                        vtFatal("bad heap ordering");
       +                k = (i << 1) + 1;
       +                if(k < c->nheap && c->heap[i]->used - now > c->heap[k]->used - now)
       +                        vtFatal("bad heap ordering");
       +                k++;
       +                if(k < c->nheap && c->heap[i]->used - now > c->heap[k]->used - now)
       +                        vtFatal("bad heap ordering");
       +        }
       +
       +        refed = 0;
       +        for(i = 0; i < c->nblocks; i++){
       +                b = &c->blocks[i];
       +                if(b->data != &c->mem[i * size])
       +                        vtFatal("mis-blocked at %d", i);
       +                if(b->ref && b->heap == BadHeap){
       +                        refed++;
       +                }
       +        }
       +if(c->nheap + refed != c->nblocks){
       +fprint(2, "%s: cacheCheck: nheap %d refed %d nblocks %ld\n", argv0, c->nheap, refed, c->nblocks);
       +cacheDump(c);
       +}
       +        assert(c->nheap + refed == c->nblocks);
       +        refed = 0;
       +        for(i = 0; i < c->nblocks; i++){
       +                b = &c->blocks[i];
       +                if(b->ref){
       +if(1)fprint(2, "%s: p=%d a=%ud %V ref=%d %L\n", argv0, b->part, b->addr, b->score, b->ref, &b->l);
       +                        refed++;
       +                }
       +        }
       +if(refed > 0)fprint(2, "%s: cacheCheck: in used %d\n", argv0, refed);
       +}
       +
       +
       +/*
       + * locate the block with the oldest second to last use.
       + * remove it from the heap, and fix up the heap.
       + */
       +/* called with c->lk held */
       +static Block *
       +cacheBumpBlock(Cache *c)
       +{
       +        int printed;
       +        Block *b;
       +
       +        /*
       +         * locate the block with the oldest second to last use.
       +         * remove it from the heap, and fix up the heap.
       +         */
       +        printed = 0;
       +        if(c->nheap == 0){
       +                while(c->nheap == 0){
       +                        vtWakeup(c->flush);
       +                        vtSleep(c->heapwait);
       +                        if(c->nheap == 0){
       +                                printed = 1;
       +                                fprint(2, "%s: entire cache is busy, %d dirty "
       +                                        "-- waking flush thread\n",
       +                                        argv0, c->ndirty);
       +                        }
       +                }
       +                if(printed)
       +                        fprint(2, "%s: cache is okay again, %d dirty\n",
       +                                argv0, c->ndirty);
       +        }
       +
       +        b = c->heap[0];
       +        heapDel(b);
       +
       +        assert(b->heap == BadHeap);
       +        assert(b->ref == 0);
       +        assert(b->iostate != BioDirty && b->iostate != BioReading && b->iostate != BioWriting);
       +        assert(b->prior == nil);
       +        assert(b->uhead == nil);
       +
       +        /*
       +         * unchain the block from hash chain
       +         */
       +        if(b->prev){
       +                *(b->prev) = b->next;
       +                if(b->next)
       +                        b->next->prev = b->prev;
       +                b->prev = nil;
       +        }
       +
       +
       +if(0)fprint(2, "%s: dropping %d:%x:%V\n", argv0, b->part, b->addr, b->score);
       +        /* set block to a reasonable state */
       +        b->ref = 1;
       +        b->part = PartError;
       +        memset(&b->l, 0, sizeof(b->l));
       +        b->iostate = BioEmpty;
       +
       +        return b;
       +}
       +
       +/*
       + * look for a particular version of the block in the memory cache.
       + */
       +static Block *
       +_cacheLocalLookup(Cache *c, int part, u32int addr, u32int vers,
       +        int waitlock, int *lockfailure)
       +{
       +        Block *b;
       +        ulong h;
       +
       +        h = addr % c->hashSize;
       +
       +        if(lockfailure)
       +                *lockfailure = 0;
       +
       +        /*
       +         * look for the block in the cache
       +         */
       +        vtLock(c->lk);
       +        for(b = c->heads[h]; b != nil; b = b->next){
       +                if(b->part == part && b->addr == addr)
       +                        break;
       +        }
       +        if(b == nil || b->vers != vers){
       +                vtUnlock(c->lk);
       +                return nil;
       +        }
       +        if(!waitlock && !vtCanLock(b->lk)){
       +                *lockfailure = 1;
       +                vtUnlock(c->lk);
       +                return nil;
       +        }
       +        heapDel(b);
       +        b->ref++;
       +        vtUnlock(c->lk);
       +
       +        bwatchLock(b);
       +        if(waitlock)
       +                vtLock(b->lk);
       +        b->nlock = 1;
       +
       +        for(;;){
       +                switch(b->iostate){
       +                default:
       +                        abort();
       +                case BioEmpty:
       +                case BioLabel:
       +                case BioClean:
       +                case BioDirty:
       +                        if(b->vers != vers){
       +                                blockPut(b);
       +                                return nil;
       +                        }
       +                        return b;
       +                case BioReading:
       +                case BioWriting:
       +                        vtSleep(b->ioready);
       +                        break;
       +                case BioVentiError:
       +                        blockPut(b);
       +                        vtSetError("venti i/o error block 0x%.8ux", addr);
       +                        return nil;
       +                case BioReadError:
       +                        blockPut(b);
       +                        vtSetError("error reading block 0x%.8ux", addr);
       +                        return nil;
       +                }
       +        }
       +        /* NOT REACHED */
       +}
       +static Block*
       +cacheLocalLookup(Cache *c, int part, u32int addr, u32int vers)
       +{
       +        return _cacheLocalLookup(c, part, addr, vers, Waitlock, 0);
       +}
       +
       +
       +/*
       + * fetch a local (on-disk) block from the memory cache.
       + * if it's not there, load it, bumping some other block.
       + */
       +Block *
       +_cacheLocal(Cache *c, int part, u32int addr, int mode, u32int epoch)
       +{
       +        Block *b;
       +        ulong h;
       +
       +        assert(part != PartVenti);
       +
       +        h = addr % c->hashSize;
       +
       +        /*
       +         * look for the block in the cache
       +         */
       +        vtLock(c->lk);
       +        for(b = c->heads[h]; b != nil; b = b->next){
       +                if(b->part != part || b->addr != addr)
       +                        continue;
       +                if(epoch && b->l.epoch != epoch){
       +fprint(2, "%s: _cacheLocal want epoch %ud got %ud\n", argv0, epoch, b->l.epoch);
       +                        vtUnlock(c->lk);
       +                        vtSetError(ELabelMismatch);
       +                        return nil;
       +                }
       +                heapDel(b);
       +                b->ref++;
       +                break;
       +        }
       +
       +        if(b == nil){
       +                b = cacheBumpBlock(c);
       +
       +                b->part = part;
       +                b->addr = addr;
       +                localToGlobal(addr, b->score);
       +
       +                /* chain onto correct hash */
       +                b->next = c->heads[h];
       +                c->heads[h] = b;
       +                if(b->next != nil)
       +                        b->next->prev = &b->next;
       +                b->prev = &c->heads[h];
       +        }
       +
       +        vtUnlock(c->lk);
       +
       +        /*
       +         * BUG: what if the epoch changes right here?
       +         * In the worst case, we could end up in some weird
       +         * lock loop, because the block we want no longer exists,
       +         * and instead we're trying to lock a block we have no
       +         * business grabbing.
       +         *
       +         * For now, I'm not going to worry about it.
       +         */
       +
       +if(0)fprint(2, "%s: cacheLocal: %d: %d %x\n", argv0, getpid(), b->part, b->addr);
       +        bwatchLock(b);
       +        vtLock(b->lk);
       +        b->nlock = 1;
       +
       +        if(part == PartData && b->iostate == BioEmpty){
       +                if(!readLabel(c, &b->l, addr)){
       +                        blockPut(b);
       +                        return nil;
       +                }
       +                blockSetIOState(b, BioLabel);
       +        }
       +        if(epoch && b->l.epoch != epoch){
       +                blockPut(b);
       +fprint(2, "%s: _cacheLocal want epoch %ud got %ud\n", argv0, epoch, b->l.epoch);
       +                vtSetError(ELabelMismatch);
       +                return nil;
       +        }
       +
       +        b->pc = getcallerpc(&c);
       +        for(;;){
       +                switch(b->iostate){
       +                default:
       +                        abort();
       +                case BioLabel:
       +                        if(mode == OOverWrite)
       +                                /*
       +                                 * leave iostate as BioLabel because data
       +                                 * hasn't been read.
       +                                 */
       +                                return b;
       +                        /* fall through */
       +                case BioEmpty:
       +                        diskRead(c->disk, b);
       +                        vtSleep(b->ioready);
       +                        break;
       +                case BioClean:
       +                case BioDirty:
       +                        return b;
       +                case BioReading:
       +                case BioWriting:
       +                        vtSleep(b->ioready);
       +                        break;
       +                case BioReadError:
       +                        blockSetIOState(b, BioEmpty);
       +                        blockPut(b);
       +                        vtSetError("error reading block 0x%.8ux", addr);
       +                        return nil;
       +                }
       +        }
       +        /* NOT REACHED */
       +}
       +
       +Block *
       +cacheLocal(Cache *c, int part, u32int addr, int mode)
       +{
       +        return _cacheLocal(c, part, addr, mode, 0);
       +}
       +
       +/*
       + * fetch a local (on-disk) block from the memory cache.
       + * if it's not there, load it, bumping some other block.
       + * check tag and type.
       + */
       +Block *
       +cacheLocalData(Cache *c, u32int addr, int type, u32int tag, int mode, u32int epoch)
       +{
       +        Block *b;
       +
       +        b = _cacheLocal(c, PartData, addr, mode, epoch);
       +        if(b == nil)
       +                return nil;
       +        if(b->l.type != type || b->l.tag != tag){
       +                fprint(2, "%s: cacheLocalData: addr=%d type got %d exp %d: tag got %ux exp %ux\n",
       +                        argv0, addr, b->l.type, type, b->l.tag, tag);
       +                vtSetError(ELabelMismatch);
       +                blockPut(b);
       +                return nil;
       +        }
       +        b->pc = getcallerpc(&c);
       +        return b;
       +}
       +
       +/*
       + * fetch a global (Venti) block from the memory cache.
       + * if it's not there, load it, bumping some other block.
       + * check tag and type if it's really a local block in disguise.
       + */
       +Block *
       +cacheGlobal(Cache *c, uchar score[VtScoreSize], int type, u32int tag, int mode)
       +{
       +        int n;
       +        Block *b;
       +        ulong h;
       +        u32int addr;
       +
       +        addr = globalToLocal(score);
       +        if(addr != NilBlock){
       +                b = cacheLocalData(c, addr, type, tag, mode, 0);
       +                if(b)
       +                        b->pc = getcallerpc(&c);
       +                return b;
       +        }
       +
       +        h = (u32int)(score[0]|(score[1]<<8)|(score[2]<<16)|(score[3]<<24)) % c->hashSize;
       +
       +        /*
       +         * look for the block in the cache
       +         */
       +        vtLock(c->lk);
       +        for(b = c->heads[h]; b != nil; b = b->next){
       +                if(b->part != PartVenti || memcmp(b->score, score, VtScoreSize) != 0 || b->l.type != type)
       +                        continue;
       +                heapDel(b);
       +                b->ref++;
       +                break;
       +        }
       +
       +        if(b == nil){
       +if(0)fprint(2, "%s: cacheGlobal %V %d\n", argv0, score, type);
       +
       +                b = cacheBumpBlock(c);
       +
       +                b->part = PartVenti;
       +                b->addr = NilBlock;
       +                b->l.type = type;
       +                memmove(b->score, score, VtScoreSize);
       +
       +                /* chain onto correct hash */
       +                b->next = c->heads[h];
       +                c->heads[h] = b;
       +                if(b->next != nil)
       +                        b->next->prev = &b->next;
       +                b->prev = &c->heads[h];
       +        }
       +        vtUnlock(c->lk);
       +
       +        bwatchLock(b);
       +        vtLock(b->lk);
       +        b->nlock = 1;
       +        b->pc = getcallerpc(&c);
       +
       +        switch(b->iostate){
       +        default:
       +                abort();
       +        case BioEmpty:
       +                n = vtRead(c->z, score, vtType[type], b->data, c->size);
       +                if(n < 0 || !vtSha1Check(score, b->data, n)){
       +                        blockSetIOState(b, BioVentiError);
       +                        blockPut(b);
       +                        vtSetError(
       +                        "venti error reading block %V or wrong score: %r",
       +                                score);
       +                        return nil;
       +                }
       +                vtZeroExtend(vtType[type], b->data, n, c->size);
       +                blockSetIOState(b, BioClean);
       +                return b;
       +        case BioClean:
       +                return b;
       +        case BioVentiError:
       +                blockPut(b);
       +                vtSetError("venti i/o error or wrong score, block %V", score);
       +                return nil;
       +        case BioReadError:
       +                blockPut(b);
       +                vtSetError("error reading block %V", b->score);
       +                return nil;
       +        }
       +        /* NOT REACHED */
       +}
       +
       +/*
       + * allocate a new on-disk block and load it into the memory cache.
       + * BUG: if the disk is full, should we flush some of it to Venti?
       + */
       +static u32int lastAlloc;
       +
       +Block *
       +cacheAllocBlock(Cache *c, int type, u32int tag, u32int epoch, u32int epochLow)
       +{
       +        FreeList *fl;
       +        u32int addr;
       +        Block *b;
       +        int n, nwrap;
       +        Label lab;
       +
       +        n = c->size / LabelSize;
       +        fl = c->fl;
       +
       +        vtLock(fl->lk);
       +        addr = fl->last;
       +        b = cacheLocal(c, PartLabel, addr/n, OReadOnly);
       +        if(b == nil){
       +                fprint(2, "%s: cacheAllocBlock: xxx %R\n", argv0);
       +                vtUnlock(fl->lk);
       +                return nil;
       +        }
       +        nwrap = 0;
       +        for(;;){
       +                if(++addr >= fl->end){
       +                        addr = 0;
       +                        if(++nwrap >= 2){
       +                                blockPut(b);
       +                                vtSetError("disk is full");
       +                                /*
       +                                 * try to avoid a continuous spew of console
       +                                 * messages.
       +                                 */
       +                                if (fl->last != 0)
       +                                        fprint(2, "%s: cacheAllocBlock: xxx1 %R\n",
       +                                                argv0);
       +                                fl->last = 0;
       +                                vtUnlock(fl->lk);
       +                                return nil;
       +                        }
       +                }
       +                if(addr%n == 0){
       +                        blockPut(b);
       +                        b = cacheLocal(c, PartLabel, addr/n, OReadOnly);
       +                        if(b == nil){
       +                                fl->last = addr;
       +                                fprint(2, "%s: cacheAllocBlock: xxx2 %R\n", argv0);
       +                                vtUnlock(fl->lk);
       +                                return nil;
       +                        }
       +                }
       +                if(!labelUnpack(&lab, b->data, addr%n))
       +                        continue;
       +                if(lab.state == BsFree)
       +                        goto Found;
       +                if(lab.state&BsClosed)
       +                if(lab.epochClose <= epochLow || lab.epoch==lab.epochClose)
       +                        goto Found;
       +        }
       +Found:
       +        blockPut(b);
       +        b = cacheLocal(c, PartData, addr, OOverWrite);
       +        if(b == nil){
       +                fprint(2, "%s: cacheAllocBlock: xxx3 %R\n", argv0);
       +                return nil;
       +        }
       +assert(b->iostate == BioLabel || b->iostate == BioClean);
       +        fl->last = addr;
       +        lab.type = type;
       +        lab.tag = tag;
       +        lab.state = BsAlloc;
       +        lab.epoch = epoch;
       +        lab.epochClose = ~(u32int)0;
       +        if(!blockSetLabel(b, &lab, 1)){
       +                fprint(2, "%s: cacheAllocBlock: xxx4 %R\n", argv0);
       +                blockPut(b);
       +                return nil;
       +        }
       +        vtZeroExtend(vtType[type], b->data, 0, c->size);
       +if(0)diskWrite(c->disk, b);
       +
       +if(0)fprint(2, "%s: fsAlloc %ud type=%d tag = %ux\n", argv0, addr, type, tag);
       +        lastAlloc = addr;
       +        fl->nused++;
       +        vtUnlock(fl->lk);
       +        b->pc = getcallerpc(&c);
       +        return b;
       +}
       +
       +int
       +cacheDirty(Cache *c)
       +{
       +        return c->ndirty;
       +}
       +
       +void
       +cacheCountUsed(Cache *c, u32int epochLow, u32int *used, u32int *total, u32int *bsize)
       +{
       +        int n;
       +        u32int addr, nused;
       +        Block *b;
       +        Label lab;
       +        FreeList *fl;
       +
       +        fl = c->fl;
       +        n = c->size / LabelSize;
       +        *bsize = c->size;
       +        vtLock(fl->lk);
       +        if(fl->epochLow == epochLow){
       +                *used = fl->nused;
       +                *total = fl->end;
       +                vtUnlock(fl->lk);
       +                return;
       +        }
       +        b = nil;
       +        nused = 0;
       +        for(addr=0; addr<fl->end; addr++){
       +                if(addr%n == 0){
       +                        blockPut(b);
       +                        b = cacheLocal(c, PartLabel, addr/n, OReadOnly);
       +                        if(b == nil){
       +                                fprint(2, "%s: flCountUsed: loading %ux: %R\n",
       +                                        argv0, addr/n);
       +                                break;
       +                        }
       +                }
       +                if(!labelUnpack(&lab, b->data, addr%n))
       +                        continue;
       +                if(lab.state == BsFree)
       +                        continue;
       +                if(lab.state&BsClosed)
       +                if(lab.epochClose <= epochLow || lab.epoch==lab.epochClose)
       +                        continue;
       +                nused++;
       +        }
       +        blockPut(b);
       +        if(addr == fl->end){
       +                fl->nused = nused;
       +                fl->epochLow = epochLow;
       +        }
       +        *used = nused;
       +        *total = fl->end;
       +        vtUnlock(fl->lk);
       +        return;
       +}
       +
       +static FreeList *
       +flAlloc(u32int end)
       +{
       +        FreeList *fl;
       +
       +        fl = vtMemAllocZ(sizeof(*fl));
       +        fl->lk = vtLockAlloc();
       +        fl->last = 0;
       +        fl->end = end;
       +        return fl;
       +}
       +
       +static void
       +flFree(FreeList *fl)
       +{
       +        vtLockFree(fl->lk);
       +        vtMemFree(fl);
       +}
       +
       +u32int
       +cacheLocalSize(Cache *c, int part)
       +{
       +        return diskSize(c->disk, part);
       +}
       +
       +/*
       + * The thread that has locked b may refer to it by
       + * multiple names.  Nlock counts the number of
       + * references the locking thread holds.  It will call
       + * blockPut once per reference.
       + */
       +void
       +blockDupLock(Block *b)
       +{
       +        assert(b->nlock > 0);
       +        b->nlock++;
       +}
       +
       +/*
       + * we're done with the block.
       + * unlock it.  can't use it after calling this.
       + */
       +void
       +blockPut(Block* b)
       +{
       +        Cache *c;
       +
       +        if(b == nil)
       +                return;
       +
       +if(0)fprint(2, "%s: blockPut: %d: %d %x %d %s\n", argv0, getpid(), b->part, b->addr, c->nheap, bioStr(b->iostate));
       +
       +        if(b->iostate == BioDirty)
       +                bwatchDependency(b);
       +
       +        if(--b->nlock > 0)
       +                return;
       +
       +        /*
       +         * b->nlock should probably stay at zero while
       +         * the block is unlocked, but diskThread and vtSleep
       +         * conspire to assume that they can just vtLock(b->lk); blockPut(b),
       +         * so we have to keep b->nlock set to 1 even
       +         * when the block is unlocked.
       +         */
       +        assert(b->nlock == 0);
       +        b->nlock = 1;
       +//        b->pc = 0;
       +
       +        bwatchUnlock(b);
       +        vtUnlock(b->lk);
       +        c = b->c;
       +        vtLock(c->lk);
       +
       +        if(--b->ref > 0){
       +                vtUnlock(c->lk);
       +                return;
       +        }
       +
       +        assert(b->ref == 0);
       +        switch(b->iostate){
       +        default:
       +                b->used = c->now++;
       +                heapIns(b);
       +                break;
       +        case BioEmpty:
       +        case BioLabel:
       +                if(c->nheap == 0)
       +                        b->used = c->now++;
       +                else
       +                        b->used = c->heap[0]->used;
       +                heapIns(b);
       +                break;
       +        case BioDirty:
       +                break;
       +        }
       +        vtUnlock(c->lk);
       +}
       +
       +/*
       + * set the label associated with a block.
       + */
       +Block*
       +_blockSetLabel(Block *b, Label *l)
       +{
       +        int lpb;
       +        Block *bb;
       +        u32int a;
       +        Cache *c;
       +
       +        c = b->c;
       +
       +        assert(b->part == PartData);
       +        assert(b->iostate == BioLabel || b->iostate == BioClean || b->iostate == BioDirty);
       +        lpb = c->size / LabelSize;
       +        a = b->addr / lpb;
       +        bb = cacheLocal(c, PartLabel, a, OReadWrite);
       +        if(bb == nil){
       +                blockPut(b);
       +                return nil;
       +        }
       +        b->l = *l;
       +        labelPack(l, bb->data, b->addr%lpb);
       +        blockDirty(bb);
       +        return bb;
       +}
       +
       +int
       +blockSetLabel(Block *b, Label *l, int allocating)
       +{
       +        Block *lb;
       +        Label oldl;
       +
       +        oldl = b->l;
       +        lb = _blockSetLabel(b, l);
       +        if(lb == nil)
       +                return 0;
       +
       +        /*
       +         * If we're allocating the block, make sure the label (bl)
       +         * goes to disk before the data block (b) itself.  This is to help
       +         * the blocks that in turn depend on b.
       +         *
       +         * Suppose bx depends on (must be written out after) b.
       +         * Once we write b we'll think it's safe to write bx.
       +         * Bx can't get at b unless it has a valid label, though.
       +         *
       +         * Allocation is the only case in which having a current label
       +         * is vital because:
       +         *
       +         *        - l.type is set at allocation and never changes.
       +         *        - l.tag is set at allocation and never changes.
       +         *        - l.state is not checked when we load blocks.
       +         *        - the archiver cares deeply about l.state being
       +         *                BaActive vs. BaCopied, but that's handled
       +         *                by direct calls to _blockSetLabel.
       +         */
       +
       +        if(allocating)
       +                blockDependency(b, lb, -1, nil, nil);
       +        blockPut(lb);
       +        return 1;
       +}
       +
       +/*
       + * Record that bb must be written out before b.
       + * If index is given, we're about to overwrite the score/e
       + * at that index in the block.  Save the old value so we
       + * can write a safer ``old'' version of the block if pressed.
       + */
       +void
       +blockDependency(Block *b, Block *bb, int index, uchar *score, Entry *e)
       +{
       +        BList *p;
       +
       +        if(bb->iostate == BioClean)
       +                return;
       +
       +        /*
       +         * Dependencies for blocks containing Entry structures
       +         * or scores must always be explained.  The problem with
       +         * only explaining some of them is this.  Suppose we have two
       +         * dependencies for the same field, the first explained
       +         * and the second not.  We try to write the block when the first
       +         * dependency is not written but the second is.  We will roll back
       +         * the first change even though the second trumps it.
       +         */
       +        if(index == -1 && bb->part == PartData)
       +                assert(b->l.type == BtData);
       +
       +        if(bb->iostate != BioDirty){
       +                fprint(2, "%s: %d:%x:%d iostate is %d in blockDependency\n",
       +                        argv0, bb->part, bb->addr, bb->l.type, bb->iostate);
       +                abort();
       +        }
       +
       +        p = blistAlloc(bb);
       +        if(p == nil)
       +                return;
       +
       +        assert(bb->iostate == BioDirty);
       +if(0)fprint(2, "%s: %d:%x:%d depends on %d:%x:%d\n", argv0, b->part, b->addr, b->l.type, bb->part, bb->addr, bb->l.type);
       +
       +        p->part = bb->part;
       +        p->addr = bb->addr;
       +        p->type = bb->l.type;
       +        p->vers = bb->vers;
       +        p->index = index;
       +        if(p->index >= 0){
       +                /*
       +                 * This test would just be b->l.type==BtDir except
       +                 * we need to exclude the super block.
       +                 */
       +                if(b->l.type == BtDir && b->part == PartData)
       +                        entryPack(e, p->old.entry, 0);
       +                else
       +                        memmove(p->old.score, score, VtScoreSize);
       +        }
       +        p->next = b->prior;
       +        b->prior = p;
       +}
       +
       +/*
       + * Mark an in-memory block as dirty.  If there are too many
       + * dirty blocks, start writing some out to disk. 
       + * 
       + * If there were way too many dirty blocks, we used to
       + * try to do some flushing ourselves, but it's just too dangerous -- 
       + * it implies that the callers cannot have any of our priors locked,
       + * but this is hard to avoid in some cases.
       + */
       +int
       +blockDirty(Block *b)
       +{
       +        Cache *c;
       +
       +        c = b->c;
       +
       +        assert(b->part != PartVenti);
       +
       +        if(b->iostate == BioDirty)
       +                return 1;
       +        assert(b->iostate == BioClean || b->iostate == BioLabel);
       +
       +        vtLock(c->lk);
       +        b->iostate = BioDirty;
       +        c->ndirty++;
       +        if(c->ndirty > (c->maxdirty>>1))
       +                vtWakeup(c->flush);
       +        vtUnlock(c->lk);
       +
       +        return 1;
       +}
       +
       +/*
       + * We've decided to write out b.  Maybe b has some pointers to blocks
       + * that haven't yet been written to disk.  If so, construct a slightly out-of-date
       + * copy of b that is safe to write out.  (diskThread will make sure the block
       + * remains marked as dirty.)
       + */
       +uchar *
       +blockRollback(Block *b, uchar *buf)
       +{
       +        u32int addr;
       +        BList *p;
       +        Super super;
       +
       +        /* easy case */
       +        if(b->prior == nil)
       +                return b->data;
       +
       +        memmove(buf, b->data, b->c->size);
       +        for(p=b->prior; p; p=p->next){
       +                /*
       +                 * we know p->index >= 0 because blockWrite has vetted this block for us.
       +                 */
       +                assert(p->index >= 0);
       +                assert(b->part == PartSuper || (b->part == PartData && b->l.type != BtData));
       +                if(b->part == PartSuper){
       +                        assert(p->index == 0);
       +                        superUnpack(&super, buf);
       +                        addr = globalToLocal(p->old.score);
       +                        if(addr == NilBlock){
       +                                fprint(2, "%s: rolling back super block: "
       +                                        "bad replacement addr %V\n",
       +                                        argv0, p->old.score);
       +                                abort();
       +                        }
       +                        super.active = addr;
       +                        superPack(&super, buf);
       +                        continue;
       +                }
       +                if(b->l.type == BtDir)
       +                        memmove(buf+p->index*VtEntrySize, p->old.entry, VtEntrySize);
       +                else
       +                        memmove(buf+p->index*VtScoreSize, p->old.score, VtScoreSize);
       +        }
       +        return buf;
       +}
       +
       +/*
       + * Try to write block b.
       + * If b depends on other blocks:
       + *
       + *        If the block has been written out, remove the dependency.
       + *        If the dependency is replaced by a more recent dependency,
       + *                throw it out.
       + *        If we know how to write out an old version of b that doesn't
       + *                depend on it, do that.
       + *
       + *        Otherwise, bail.
       + */
       +int
       +blockWrite(Block *b, int waitlock)
       +{
       +        uchar *dmap;
       +        Cache *c;
       +        BList *p, **pp;
       +        Block *bb;
       +        int lockfail;
       +
       +        c = b->c;
       +
       +        if(b->iostate != BioDirty)
       +                return 1;
       +
       +        dmap = b->dmap;
       +        memset(dmap, 0, c->ndmap);
       +        pp = &b->prior;
       +        for(p=*pp; p; p=*pp){
       +                if(p->index >= 0){
       +                        /* more recent dependency has succeeded; this one can go */
       +                        if(dmap[p->index/8] & (1<<(p->index%8)))
       +                                goto ignblock;
       +                }
       +
       +                lockfail = 0;
       +                bb = _cacheLocalLookup(c, p->part, p->addr, p->vers, waitlock,
       +                        &lockfail);
       +                if(bb == nil){
       +                        if(lockfail)
       +                                return 0;
       +                        /* block not in cache => was written already */
       +                        dmap[p->index/8] |= 1<<(p->index%8);
       +                        goto ignblock;
       +                }
       +
       +                /*
       +                 * same version of block is still in cache.
       +                 *
       +                 * the assertion is true because the block still has version p->vers,
       +                 * which means it hasn't been written out since we last saw it.
       +                 */
       +                if(bb->iostate != BioDirty){
       +                        fprint(2, "%s: %d:%x:%d iostate is %d in blockWrite\n",
       +                                argv0, bb->part, bb->addr, bb->l.type, bb->iostate);
       +                        /* probably BioWriting if it happens? */
       +                        if(bb->iostate == BioClean)
       +                                goto ignblock;
       +                }
       +
       +                blockPut(bb);
       +
       +                if(p->index < 0){
       +                        /*
       +                         * We don't know how to temporarily undo
       +                         * b's dependency on bb, so just don't write b yet.
       +                         */
       +                        if(0) fprint(2, "%s: blockWrite skipping %d %x %d %d; need to write %d %x %d\n",
       +                                argv0, b->part, b->addr, b->vers, b->l.type, p->part, p->addr, bb->vers);
       +                        return 0;
       +                }
       +                /* keep walking down the list */
       +                pp = &p->next;
       +                continue;
       +
       +ignblock:
       +                *pp = p->next;
       +                blistFree(c, p);
       +                continue;
       +        }
       +
       +        /*
       +         * DiskWrite must never be called with a double-locked block.
       +         * This call to diskWrite is okay because blockWrite is only called
       +         * from the cache flush thread, which never double-locks a block.
       +         */
       +        diskWrite(c->disk, b);
       +        return 1;
       +}
       +
       +/*
       + * Change the I/O state of block b.
       + * Just an assignment except for magic in
       + * switch statement (read comments there).
       + */
       +void
       +blockSetIOState(Block *b, int iostate)
       +{
       +        int dowakeup;
       +        Cache *c;
       +        BList *p, *q;
       +
       +if(0) fprint(2, "%s: iostate part=%d addr=%x %s->%s\n", argv0, b->part, b->addr, bioStr(b->iostate), bioStr(iostate));
       +
       +        c = b->c;
       +
       +        dowakeup = 0;
       +        switch(iostate){
       +        default:
       +                abort();
       +        case BioEmpty:
       +                assert(!b->uhead);
       +                break;
       +        case BioLabel:
       +                assert(!b->uhead);
       +                break;
       +        case BioClean:
       +                bwatchDependency(b);
       +                /*
       +                 * If b->prior is set, it means a write just finished.
       +                 * The prior list isn't needed anymore.
       +                 */
       +                for(p=b->prior; p; p=q){
       +                        q = p->next;
       +                        blistFree(c, p);
       +                }
       +                b->prior = nil;
       +                /*
       +                 * Freeing a block or just finished a write.
       +                 * Move the blocks from the per-block unlink
       +                 * queue to the cache unlink queue.
       +                 */
       +                if(b->iostate == BioDirty || b->iostate == BioWriting){
       +                        vtLock(c->lk);
       +                        c->ndirty--;
       +                        b->iostate = iostate;        /* change here to keep in sync with ndirty */
       +                        b->vers = c->vers++;
       +                        if(b->uhead){
       +                                /* add unlink blocks to unlink queue */
       +                                if(c->uhead == nil){
       +                                        c->uhead = b->uhead;
       +                                        vtWakeup(c->unlink);
       +                                }else
       +                                        c->utail->next = b->uhead;
       +                                c->utail = b->utail;
       +                                b->uhead = nil;
       +                        }
       +                        vtUnlock(c->lk);
       +                }
       +                assert(!b->uhead);
       +                dowakeup = 1;
       +                break;
       +        case BioDirty:
       +                /*
       +                 * Wrote out an old version of the block (see blockRollback).
       +                 * Bump a version count, leave it dirty.
       +                 */
       +                if(b->iostate == BioWriting){
       +                        vtLock(c->lk);
       +                        b->vers = c->vers++;
       +                        vtUnlock(c->lk);
       +                        dowakeup = 1;
       +                }
       +                break;
       +        case BioReading:
       +        case BioWriting:
       +                /*
       +                 * Adding block to disk queue.  Bump reference count.
       +                 * diskThread decs the count later by calling blockPut.
       +                 * This is here because we need to lock c->lk to
       +                 * manipulate the ref count.
       +                 */
       +                vtLock(c->lk);
       +                b->ref++;
       +                vtUnlock(c->lk);
       +                break;
       +        case BioReadError:
       +        case BioVentiError:
       +                /*
       +                 * Oops.
       +                 */
       +                dowakeup = 1;
       +                break;
       +        }
       +        b->iostate = iostate;
       +        /*
       +         * Now that the state has changed, we can wake the waiters.
       +         */
       +        if(dowakeup)
       +                vtWakeupAll(b->ioready);
       +}
       +
       +/*
       + * The active file system is a tree of blocks. 
       + * When we add snapshots to the mix, the entire file system
       + * becomes a dag and thus requires a bit more care.
       + * 
       + * The life of the file system is divided into epochs.  A snapshot
       + * ends one epoch and begins the next.  Each file system block
       + * is marked with the epoch in which it was created (b.epoch).
       + * When the block is unlinked from the file system (closed), it is marked
       + * with the epoch in which it was removed (b.epochClose).  
       + * Once we have discarded or archived all snapshots up to 
       + * b.epochClose, we can reclaim the block.
       + *
       + * If a block was created in a past epoch but is not yet closed,
       + * it is treated as copy-on-write.  Of course, in order to insert the
       + * new pointer into the tree, the parent must be made writable,
       + * and so on up the tree.  The recursion stops because the root
       + * block is always writable.
       + *
       + * If blocks are never closed, they will never be reused, and
       + * we will run out of disk space.  But marking a block as closed
       + * requires some care about dependencies and write orderings.
       + *
       + * (1) If a block p points at a copy-on-write block b and we
       + * copy b to create bb, then p must be written out after bb and
       + * lbb (bb's label block).
       + *
       + * (2) We have to mark b as closed, but only after we switch
       + * the pointer, so lb must be written out after p.  In fact, we 
       + * can't even update the in-memory copy, or the cache might
       + * mistakenly give out b for reuse before p gets written.
       + *
       + * CacheAllocBlock's call to blockSetLabel records a "bb after lbb" dependency.
       + * The caller is expected to record a "p after bb" dependency
       + * to finish (1), and also expected to call blockRemoveLink
       + * to arrange for (2) to happen once p is written.
       + *
       + * Until (2) happens, some pieces of the code (e.g., the archiver)
       + * still need to know whether a block has been copied, so we 
       + * set the BsCopied bit in the label and force that to disk *before*
       + * the copy gets written out.
       + */
       +Block*
       +blockCopy(Block *b, u32int tag, u32int ehi, u32int elo)
       +{
       +        Block *bb, *lb;
       +        Label l;
       +
       +        if((b->l.state&BsClosed) || b->l.epoch >= ehi)
       +                fprint(2, "%s: blockCopy %#ux %L but fs is [%ud,%ud]\n",
       +                        argv0, b->addr, &b->l, elo, ehi);
       +
       +        bb = cacheAllocBlock(b->c, b->l.type, tag, ehi, elo);
       +        if(bb == nil){
       +                blockPut(b);
       +                return nil;
       +        }
       +
       +        /*
       +         * Update label so we know the block has been copied.
       +         * (It will be marked closed once it has been unlinked from
       +         * the tree.)  This must follow cacheAllocBlock since we
       +         * can't be holding onto lb when we call cacheAllocBlock.
       +         */
       +        if((b->l.state&BsCopied)==0)
       +        if(b->part == PartData){        /* not the superblock */
       +                l = b->l;
       +                l.state |= BsCopied;
       +                lb = _blockSetLabel(b, &l);
       +                if(lb == nil){
       +                        /* can't set label => can't copy block */
       +                        blockPut(b);
       +                        l.type = BtMax;
       +                        l.state = BsFree;
       +                        l.epoch = 0;
       +                        l.epochClose = 0;
       +                        l.tag = 0;
       +                        blockSetLabel(bb, &l, 0);
       +                        blockPut(bb);
       +                        return nil;
       +                }
       +                blockDependency(bb, lb, -1, nil, nil);
       +                blockPut(lb);
       +        }
       +
       +        memmove(bb->data, b->data, b->c->size);
       +        blockDirty(bb);
       +        blockPut(b);
       +        return bb;
       +}
       +
       +/*
       + * Block b once pointed at the block bb at addr/type/tag, but no longer does.
       + * If recurse is set, we are unlinking all of bb's children as well.
       + *
       + * We can't reclaim bb (or its kids) until the block b gets written to disk.  We add
       + * the relevant information to b's list of unlinked blocks.  Once b is written,
       + * the list will be queued for processing.
       + *
       + * If b depends on bb, it doesn't anymore, so we remove bb from the prior list.
       + */
       +void
       +blockRemoveLink(Block *b, u32int addr, int type, u32int tag, int recurse)
       +{
       +        BList *p, **pp, bl;
       +        
       +        /* remove bb from prior list */
       +        for(pp=&b->prior; (p=*pp)!=nil; ){
       +                if(p->part == PartData && p->addr == addr){
       +                        *pp = p->next;
       +                        blistFree(b->c, p);
       +                }else
       +                        pp = &p->next;
       +        }
       +
       +        bl.part = PartData;
       +        bl.addr = addr;
       +        bl.type = type;
       +        bl.tag = tag;
       +        if(b->l.epoch == 0)
       +                assert(b->part == PartSuper);
       +        bl.epoch = b->l.epoch;
       +        bl.next = nil;
       +        bl.recurse = recurse;
       +
       +        if(b->part == PartSuper && b->iostate == BioClean)
       +                p = nil;
       +        else
       +                p = blistAlloc(b);
       +        if(p == nil){
       +                /*
       +                 * b has already been written to disk.
       +                 */
       +                doRemoveLink(b->c, &bl);
       +                return;
       +        }
       +
       +        /* Uhead is only processed when the block goes from Dirty -> Clean */
       +        assert(b->iostate == BioDirty);
       +
       +        *p = bl;
       +        if(b->uhead == nil)
       +                b->uhead = p;
       +        else
       +                b->utail->next = p;
       +        b->utail = p;
       +}
       +
       +/* 
       + * Process removal of a single block and perhaps its children.
       + */
       +static void
       +doRemoveLink(Cache *c, BList *p)
       +{
       +        int i, n, recurse;
       +        u32int a;
       +        Block *b;
       +        Label l;
       +        BList bl;
       +
       +        recurse = (p->recurse && p->type != BtData && p->type != BtDir);
       +
       +        /*
       +         * We're not really going to overwrite b, but if we're not
       +         * going to look at its contents, there is no point in reading
       +         * them from the disk.
       +         */
       +        b = cacheLocalData(c, p->addr, p->type, p->tag, recurse ? OReadOnly : OOverWrite, 0);
       +        if(b == nil)
       +                return;
       +
       +        /*
       +         * When we're unlinking from the superblock, close with the next epoch.
       +         */
       +        if(p->epoch == 0)
       +                p->epoch = b->l.epoch+1;
       +
       +        /* sanity check */
       +        if(b->l.epoch > p->epoch){
       +                fprint(2, "%s: doRemoveLink: strange epoch %ud > %ud\n",
       +                        argv0, b->l.epoch, p->epoch);
       +                blockPut(b);
       +                return;
       +        }
       +
       +        if(recurse){
       +                n = c->size / VtScoreSize;
       +                for(i=0; i<n; i++){
       +                        a = globalToLocal(b->data + i*VtScoreSize);
       +                        if(a == NilBlock || !readLabel(c, &l, a))
       +                                continue;
       +                        if(l.state&BsClosed)
       +                                continue;
       +                        /*
       +                         * If stack space becomes an issue...
       +                        p->addr = a;
       +                        p->type = l.type;
       +                        p->tag = l.tag;
       +                        doRemoveLink(c, p);
       +                         */
       +
       +                        bl.part = PartData;
       +                        bl.addr = a;
       +                        bl.type = l.type;
       +                        bl.tag = l.tag;
       +                        bl.epoch = p->epoch;
       +                        bl.next = nil;
       +                        bl.recurse = 1;
       +                        /* give up the block lock - share with others */
       +                        blockPut(b);
       +                        doRemoveLink(c, &bl);
       +                        b = cacheLocalData(c, p->addr, p->type, p->tag, OReadOnly, 0);
       +                        if(b == nil){
       +                                fprint(2, "%s: warning: lost block in doRemoveLink\n",
       +                                        argv0);
       +                                return;
       +                        }
       +                }
       +        }
       +
       +        l = b->l;
       +        l.state |= BsClosed;
       +        l.epochClose = p->epoch;
       +        if(l.epochClose == l.epoch){
       +                vtLock(c->fl->lk);
       +                if(l.epoch == c->fl->epochLow)
       +                        c->fl->nused--;
       +                blockSetLabel(b, &l, 0);
       +                vtUnlock(c->fl->lk);
       +        }else
       +                blockSetLabel(b, &l, 0);
       +        blockPut(b);
       +}
       +
       +/*
       + * Allocate a BList so that we can record a dependency
       + * or queue a removal related to block b.
       + * If we can't find a BList, we write out b and return nil.
       + */
       +static BList *
       +blistAlloc(Block *b)
       +{
       +        Cache *c;
       +        BList *p;
       +
       +        if(b->iostate != BioDirty){
       +                /*
       +                 * should not happen anymore -
       +                  * blockDirty used to flush but no longer does.
       +                 */
       +                assert(b->iostate == BioClean);
       +                fprint(2, "%s: blistAlloc: called on clean block\n", argv0);
       +                return nil;
       +        }
       +
       +        c = b->c;
       +        vtLock(c->lk);
       +        if(c->blfree == nil){
       +                /*
       +                 * No free BLists.  What are our options?
       +                 */
       +        
       +                /* Block has no priors? Just write it. */
       +                if(b->prior == nil){
       +                        vtUnlock(c->lk);
       +                        diskWriteAndWait(c->disk, b);
       +                        return nil;
       +                }
       +
       +                /*
       +                 * Wake the flush thread, which will hopefully free up
       +                 * some BLists for us.  We used to flush a block from
       +                 * our own prior list and reclaim that BList, but this is
       +                 * a no-no: some of the blocks on our prior list may
       +                 * be locked by our caller.  Or maybe their label blocks
       +                 * are locked by our caller.  In any event, it's too hard
       +                 * to make sure we can do I/O for ourselves.  Instead,
       +                 * we assume the flush thread will find something.
       +                 * (The flush thread never blocks waiting for a block,
       +                 * so it can't deadlock like we can.)
       +                 */
       +                while(c->blfree == nil){
       +                        vtWakeup(c->flush);
       +                        vtSleep(c->blrend);
       +                        if(c->blfree == nil)
       +                                fprint(2, "%s: flushing for blists\n", argv0);
       +                }
       +        }
       +
       +        p = c->blfree;
       +        c->blfree = p->next;
       +        vtUnlock(c->lk);
       +        return p;
       +}
       +
       +static void
       +blistFree(Cache *c, BList *bl)
       +{
       +        vtLock(c->lk);
       +        bl->next = c->blfree;
       +        c->blfree = bl;
       +        vtWakeup(c->blrend);
       +        vtUnlock(c->lk);
       +}
       +
       +char*
       +bsStr(int state)
       +{
       +        static char s[100];
       +
       +        if(state == BsFree)
       +                return "Free";
       +        if(state == BsBad)
       +                return "Bad";
       +
       +        sprint(s, "%x", state);
       +        if(!(state&BsAlloc))
       +                strcat(s, ",Free");        /* should not happen */
       +        if(state&BsCopied)
       +                strcat(s, ",Copied");
       +        if(state&BsVenti)
       +                strcat(s, ",Venti");
       +        if(state&BsClosed)
       +                strcat(s, ",Closed");
       +        return s;
       +}
       +
       +char *
       +bioStr(int iostate)
       +{
       +        switch(iostate){
       +        default:
       +                return "Unknown!!";
       +        case BioEmpty:
       +                return "Empty";
       +        case BioLabel:
       +                return "Label";
       +        case BioClean:
       +                return "Clean";
       +        case BioDirty:
       +                return "Dirty";
       +        case BioReading:
       +                return "Reading";
       +        case BioWriting:
       +                return "Writing";
       +        case BioReadError:
       +                return "ReadError";
       +        case BioVentiError:
       +                return "VentiError";
       +        case BioMax:
       +                return "Max";
       +        }
       +}
       +
       +static char *bttab[] = {
       +        "BtData",
       +        "BtData+1",
       +        "BtData+2",
       +        "BtData+3",
       +        "BtData+4",
       +        "BtData+5",
       +        "BtData+6",
       +        "BtData+7",
       +        "BtDir",
       +        "BtDir+1",
       +        "BtDir+2",
       +        "BtDir+3",
       +        "BtDir+4",
       +        "BtDir+5",
       +        "BtDir+6",
       +        "BtDir+7",
       +};
       +
       +char*
       +btStr(int type)
       +{
       +        if(type < nelem(bttab))
       +                return bttab[type];
       +        return "unknown";
       +}
       +
       +int
       +labelFmt(Fmt *f)
       +{
       +        Label *l;
       +
       +        l = va_arg(f->args, Label*);
       +        return fmtprint(f, "%s,%s,e=%ud,%d,tag=%#ux",
       +                btStr(l->type), bsStr(l->state), l->epoch, (int)l->epochClose, l->tag);
       +}
       +
       +int
       +scoreFmt(Fmt *f)
       +{
       +        uchar *v;
       +        int i;
       +        u32int addr;
       +
       +        v = va_arg(f->args, uchar*);
       +        if(v == nil){
       +                fmtprint(f, "*");
       +        }else if((addr = globalToLocal(v)) != NilBlock)
       +                fmtprint(f, "0x%.8ux", addr);
       +        else{
       +                for(i = 0; i < VtScoreSize; i++)
       +                        fmtprint(f, "%2.2ux", v[i]);
       +        }
       +
       +        return 0;
       +}
       +
       +static int
       +upHeap(int i, Block *b)
       +{
       +        Block *bb;
       +        u32int now;
       +        int p;
       +        Cache *c;
       +
       +        c = b->c;
       +        now = c->now;
       +        for(; i != 0; i = p){
       +                p = (i - 1) >> 1;
       +                bb = c->heap[p];
       +                if(b->used - now >= bb->used - now)
       +                        break;
       +                c->heap[i] = bb;
       +                bb->heap = i;
       +        }
       +        c->heap[i] = b;
       +        b->heap = i;
       +
       +        return i;
       +}
       +
       +static int
       +downHeap(int i, Block *b)
       +{
       +        Block *bb;
       +        u32int now;
       +        int k;
       +        Cache *c;
       +
       +        c = b->c;
       +        now = c->now;
       +        for(; ; i = k){
       +                k = (i << 1) + 1;
       +                if(k >= c->nheap)
       +                        break;
       +                if(k + 1 < c->nheap && c->heap[k]->used - now > c->heap[k + 1]->used - now)
       +                        k++;
       +                bb = c->heap[k];
       +                if(b->used - now <= bb->used - now)
       +                        break;
       +                c->heap[i] = bb;
       +                bb->heap = i;
       +        }
       +        c->heap[i] = b;
       +        b->heap = i;
       +        return i;
       +}
       +
       +/*
       + * Delete a block from the heap.
       + * Called with c->lk held.
       + */
       +static void
       +heapDel(Block *b)
       +{
       +        int i, si;
       +        Cache *c;
       +
       +        c = b->c;
       +
       +        si = b->heap;
       +        if(si == BadHeap)
       +                return;
       +        b->heap = BadHeap;
       +        c->nheap--;
       +        if(si == c->nheap)
       +                return;
       +        b = c->heap[c->nheap];
       +        i = upHeap(si, b);
       +        if(i == si)
       +                downHeap(i, b);
       +}
       +
       +/*
       + * Insert a block into the heap.
       + * Called with c->lk held.
       + */
       +static void
       +heapIns(Block *b)
       +{
       +        assert(b->heap == BadHeap);
       +        upHeap(b->c->nheap++, b);
       +        vtWakeup(b->c->heapwait);
       +}
       +
       +/*
       + * Get just the label for a block.
       + */
       +int
       +readLabel(Cache *c, Label *l, u32int addr)
       +{
       +        int lpb;
       +        Block *b;
       +        u32int a;
       +
       +        lpb = c->size / LabelSize;
       +        a = addr / lpb;
       +        b = cacheLocal(c, PartLabel, a, OReadOnly);
       +        if(b == nil){
       +                blockPut(b);
       +                return 0;
       +        }
       +
       +        if(!labelUnpack(l, b->data, addr%lpb)){
       +                blockPut(b);
       +                return 0;
       +        }
       +        blockPut(b);
       +        return 1;
       +}
       +
       +/*
       + * Process unlink queue.
       + * Called with c->lk held.
       + */
       +static void
       +unlinkBody(Cache *c)
       +{
       +        BList *p;
       +
       +        while(c->uhead != nil){
       +                p = c->uhead;
       +                c->uhead = p->next;
       +                vtUnlock(c->lk);
       +                doRemoveLink(c, p);
       +                vtLock(c->lk);
       +                p->next = c->blfree;
       +                c->blfree = p;
       +        }
       +}
       +
       +/*
       + * Occasionally unlink the blocks on the cache unlink queue.
       + */
       +static void
       +unlinkThread(void *a)
       +{
       +        Cache *c = a;
       +
       +        vtThreadSetName("unlink");
       +
       +        vtLock(c->lk);
       +        for(;;){
       +                while(c->uhead == nil && c->die == nil)
       +                        vtSleep(c->unlink);
       +                if(c->die != nil)
       +                        break;
       +                unlinkBody(c);
       +        }
       +        c->ref--;
       +        vtWakeup(c->die);
       +        vtUnlock(c->lk);
       +}
       +
       +static int
       +baddrCmp(void *a0, void *a1)
       +{
       +        BAddr *b0, *b1;
       +        b0 = a0;
       +        b1 = a1;
       +
       +        if(b0->part < b1->part)
       +                return -1;
       +        if(b0->part > b1->part)
       +                return 1;
       +        if(b0->addr < b1->addr)
       +                return -1;
       +        if(b0->addr > b1->addr)
       +                return 1;
       +        return 0;
       +}
       +
       +/*
       + * Scan the block list for dirty blocks; add them to the list c->baddr.
       + */
       +static void
       +flushFill(Cache *c)
       +{
       +        int i, ndirty;
       +        BAddr *p;
       +        Block *b;
       +
       +        vtLock(c->lk);
       +        if(c->ndirty == 0){
       +                vtUnlock(c->lk);
       +                return;
       +        }
       +
       +        p = c->baddr;
       +        ndirty = 0;
       +        for(i=0; i<c->nblocks; i++){
       +                b = c->blocks + i;
       +                if(b->part == PartError)
       +                        continue;
       +                if(b->iostate == BioDirty || b->iostate == BioWriting)
       +                        ndirty++;
       +                if(b->iostate != BioDirty)
       +                        continue;
       +                p->part = b->part;
       +                p->addr = b->addr;
       +                p->vers = b->vers;
       +                p++;
       +        }
       +        if(ndirty != c->ndirty){
       +                fprint(2, "%s: ndirty mismatch expected %d found %d\n",
       +                        argv0, c->ndirty, ndirty);
       +                c->ndirty = ndirty;
       +        }
       +        vtUnlock(c->lk);
       +
       +        c->bw = p - c->baddr;
       +        qsort(c->baddr, c->bw, sizeof(BAddr), baddrCmp);
       +}
       +
       +/*
       + * This is not thread safe, i.e. it can't be called from multiple threads.
       + *
       + * It's okay how we use it, because it only gets called in
       + * the flushThread.  And cacheFree, but only after
       + * cacheFree has killed off the flushThread.
       + */
       +static int
       +cacheFlushBlock(Cache *c)
       +{
       +        Block *b;
       +        BAddr *p;
       +        int lockfail, nfail;
       +
       +        nfail = 0;
       +        for(;;){
       +                if(c->br == c->be){
       +                        if(c->bw == 0 || c->bw == c->be)
       +                                flushFill(c);
       +                        c->br = 0;
       +                        c->be = c->bw;
       +                        c->bw = 0;
       +                        c->nflush = 0;
       +                }
       +
       +                if(c->br == c->be)
       +                        return 0;
       +                p = c->baddr + c->br;
       +                c->br++;
       +                b = _cacheLocalLookup(c, p->part, p->addr, p->vers, Nowaitlock,
       +                        &lockfail);
       +
       +                if(b && blockWrite(b, Nowaitlock)){
       +                        c->nflush++;
       +                        blockPut(b);
       +                        return 1;
       +                }
       +                if(b)
       +                        blockPut(b);
       +
       +                /*
       +                 * Why didn't we write the block?
       +                 */
       +
       +                /* Block already written out */
       +                if(b == nil && !lockfail)
       +                        continue;
       +
       +                /* Failed to acquire lock; sleep if happens a lot. */
       +                if(lockfail && ++nfail > 100){
       +                        sleep(500);
       +                        nfail = 0;
       +                }
       +                /* Requeue block. */
       +                if(c->bw < c->be)
       +                        c->baddr[c->bw++] = *p;
       +        }
       +}
       +
       +/*
       + * Occasionally flush dirty blocks from memory to the disk.
       + */
       +static void
       +flushThread(void *a)
       +{
       +        Cache *c = a;
       +        int i;
       +
       +        vtThreadSetName("flush");
       +        vtLock(c->lk);
       +        while(c->die == nil){
       +                vtSleep(c->flush);
       +                vtUnlock(c->lk);
       +                for(i=0; i<FlushSize; i++)
       +                        if(!cacheFlushBlock(c)){
       +                                /*
       +                                 * If i==0, could be someone is waking us repeatedly
       +                                 * to flush the cache but there's no work to do.
       +                                 * Pause a little.
       +                                 */
       +                                if(i==0){
       +                                        // fprint(2, "%s: flushthread found "
       +                                        //        "nothing to flush - %d dirty\n",
       +                                        //        argv0, c->ndirty);
       +                                        sleep(250);
       +                                }
       +                                break;
       +                        }
       +                if(i==0 && c->ndirty){
       +                        /*
       +                         * All the blocks are being written right now -- there's nothing to do.
       +                         * We might be spinning with cacheFlush though -- he'll just keep
       +                         * kicking us until c->ndirty goes down.  Probably we should sleep
       +                         * on something that the diskThread can kick, but for now we'll
       +                         * just pause for a little while waiting for disks to finish.
       +                         */
       +                        sleep(100);
       +                }
       +                vtLock(c->lk);
       +                vtWakeupAll(c->flushwait);
       +        }
       +        c->ref--;
       +        vtWakeup(c->die);
       +        vtUnlock(c->lk);
       +}
       +
       +/*
       + * Flush the cache.
       + */
       +void
       +cacheFlush(Cache *c, int wait)
       +{
       +        vtLock(c->lk);
       +        if(wait){
       +                while(c->ndirty){
       +                //        consPrint("cacheFlush: %d dirty blocks, uhead %p\n",
       +                //                c->ndirty, c->uhead);
       +                        vtWakeup(c->flush);
       +                        vtSleep(c->flushwait);
       +                }
       +        //        consPrint("cacheFlush: done (uhead %p)\n", c->ndirty, c->uhead);
       +        }else if(c->ndirty)
       +                vtWakeup(c->flush);
       +        vtUnlock(c->lk);
       +}
       +
       +/*
       + * Kick the flushThread every 30 seconds.
       + */
       +static void
       +cacheSync(void *v)
       +{
       +        Cache *c;
       +
       +        c = v;
       +        cacheFlush(c, 0);
       +}
 (DIR) diff --git a/src/cmd/fossil/check.c b/src/cmd/fossil/check.c
       t@@ -0,0 +1,799 @@
       +#include "stdinc.h"
       +#include "dat.h"
       +#include "fns.h"
       +
       +static void        checkDirs(Fsck*);
       +static void        checkEpochs(Fsck*);
       +static void        checkLeak(Fsck*);
       +static void        closenop(Fsck*, Block*, u32int);
       +static void        clrenop(Fsck*, Block*, int);
       +static void        clrinop(Fsck*, char*, MetaBlock*, int, Block*);
       +static void        error(Fsck*, char*, ...);
       +static int        getBit(uchar*, u32int);
       +static int        printnop(char*, ...);
       +static void        setBit(uchar*, u32int);
       +static int        walkEpoch(Fsck *chk, Block *b, uchar score[VtScoreSize],
       +                        int type, u32int tag, u32int epoch);
       +static void        warn(Fsck*, char*, ...);
       +
       +#pragma varargck argpos error 2
       +#pragma varargck argpos printnop 1
       +#pragma varargck argpos warn 2
       +
       +static Fsck*
       +checkInit(Fsck *chk)
       +{
       +        chk->cache = chk->fs->cache;
       +        chk->nblocks = cacheLocalSize(chk->cache, PartData);;
       +        chk->bsize = chk->fs->blockSize;
       +        chk->walkdepth = 0;
       +        chk->hint = 0;
       +        chk->quantum = chk->nblocks/100;
       +        if(chk->quantum == 0)
       +                chk->quantum = 1;
       +        if(chk->print == nil)
       +                chk->print = printnop;
       +        if(chk->clre == nil)
       +                chk->clre = clrenop;
       +        if(chk->close == nil)
       +                chk->close = closenop;
       +        if(chk->clri == nil)
       +                chk->clri = clrinop;
       +        return chk;
       +}
       +
       +/*
       + * BUG: Should merge checkEpochs and checkDirs so that
       + * bad blocks are only reported once, and so that errors in checkEpochs
       + * can have the affected file names attached, and so that the file system
       + * is only read once.
       + *
       + * Also should summarize the errors instead of printing for every one
       + * (e.g., XXX bad or unreachable blocks in /active/usr/rsc/foo).
       + */
       +
       +void
       +fsCheck(Fsck *chk)
       +{
       +        Block *b;
       +        Super super;
       +
       +        checkInit(chk);
       +        b = superGet(chk->cache, &super);
       +        if(b == nil){
       +                chk->print("could not load super block: %R");
       +                return;
       +        }
       +        blockPut(b);
       +
       +        chk->hint = super.active;
       +        checkEpochs(chk);
       +
       +        chk->smap = vtMemAllocZ(chk->nblocks/8+1);
       +        checkDirs(chk);
       +        vtMemFree(chk->smap);
       +}
       +
       +static void checkEpoch(Fsck*, u32int);
       +
       +/*
       + * Walk through all the blocks in the write buffer.
       + * Then we can look for ones we missed -- those are leaks.
       + */
       +static void
       +checkEpochs(Fsck *chk)
       +{
       +        u32int e;
       +        uint nb;
       +
       +        nb = chk->nblocks;
       +        chk->amap = vtMemAllocZ(nb/8+1);
       +        chk->emap = vtMemAllocZ(nb/8+1);
       +        chk->xmap = vtMemAllocZ(nb/8+1);
       +        chk->errmap = vtMemAllocZ(nb/8+1);
       +
       +        for(e = chk->fs->ehi; e >= chk->fs->elo; e--){
       +                memset(chk->emap, 0, chk->nblocks/8+1);
       +                memset(chk->xmap, 0, chk->nblocks/8+1);
       +                checkEpoch(chk, e);
       +        }
       +        checkLeak(chk);
       +        vtMemFree(chk->amap);
       +        vtMemFree(chk->emap);
       +        vtMemFree(chk->xmap);
       +        vtMemFree(chk->errmap);
       +}
       +
       +static void
       +checkEpoch(Fsck *chk, u32int epoch)
       +{
       +        u32int a;
       +        Block *b;
       +        Entry e;
       +        Label l;
       +
       +        chk->print("checking epoch %ud...\n", epoch);
       +
       +        for(a=0; a<chk->nblocks; a++){
       +                if(!readLabel(chk->cache, &l, (a+chk->hint)%chk->nblocks)){
       +                        error(chk, "could not read label for addr 0x%.8#ux", a);
       +                        continue;
       +                }
       +                if(l.tag == RootTag && l.epoch == epoch)
       +                        break;
       +        }
       +
       +        if(a == chk->nblocks){
       +                chk->print("could not find root block for epoch %ud", epoch);
       +                return;
       +        }
       +
       +        a = (a+chk->hint)%chk->nblocks;
       +        b = cacheLocalData(chk->cache, a, BtDir, RootTag, OReadOnly, 0);
       +        if(b == nil){
       +                error(chk, "could not read root block 0x%.8#ux: %R", a);
       +                return;
       +        }
       +
       +        /* no one should point at root blocks */
       +        setBit(chk->amap, a);
       +        setBit(chk->emap, a);
       +        setBit(chk->xmap, a);
       +
       +        /*
       +         * First entry is the rest of the file system.
       +         * Second entry is link to previous epoch root,
       +         * just a convenience to help the search.
       +         */
       +        if(!entryUnpack(&e, b->data, 0)){
       +                error(chk, "could not unpack root block 0x%.8#ux: %R", a);
       +                blockPut(b);
       +                return;
       +        }
       +        walkEpoch(chk, b, e.score, BtDir, e.tag, epoch);
       +        if(entryUnpack(&e, b->data, 1))
       +                chk->hint = globalToLocal(e.score);
       +        blockPut(b);
       +}
       +
       +/*
       + * When b points at bb, need to check:
       + *
       + * (i) b.e in [bb.e, bb.eClose)
       + * (ii) if b.e==bb.e,  then no other b' in e points at bb.
       + * (iii) if !(b.state&Copied) and b.e==bb.e then no other b' points at bb.
       + * (iv) if b is active then no other active b' points at bb.
       + * (v) if b is a past life of b' then only one of b and b' is active
       + *        (too hard to check)
       + */
       +static int
       +walkEpoch(Fsck *chk, Block *b, uchar score[VtScoreSize], int type, u32int tag,
       +        u32int epoch)
       +{
       +        int i, ret;
       +        u32int addr, ep;
       +        Block *bb;
       +        Entry e;
       +
       +        if(b && chk->walkdepth == 0 && chk->printblocks)
       +                chk->print("%V %d %#.8ux %#.8ux\n", b->score, b->l.type,
       +                        b->l.tag, b->l.epoch);
       +
       +        if(!chk->useventi && globalToLocal(score) == NilBlock)
       +                return 1;
       +
       +        chk->walkdepth++;
       +
       +        bb = cacheGlobal(chk->cache, score, type, tag, OReadOnly);
       +        if(bb == nil){
       +                error(chk, "could not load block %V type %d tag %ux: %R",
       +                        score, type, tag);
       +                chk->walkdepth--;
       +                return 0;
       +        }
       +        if(chk->printblocks)
       +                chk->print("%*s%V %d %#.8ux %#.8ux\n", chk->walkdepth*2, "",
       +                        score, type, tag, bb->l.epoch);
       +
       +        ret = 0;
       +        addr = globalToLocal(score);
       +        if(addr == NilBlock){
       +                ret = 1;
       +                goto Exit;
       +        }
       +
       +        if(b){
       +                /* (i) */
       +                if(b->l.epoch < bb->l.epoch || bb->l.epochClose <= b->l.epoch){
       +                        error(chk, "walk: block %#ux [%ud, %ud) points at %#ux [%ud, %ud)",
       +                                b->addr, b->l.epoch, b->l.epochClose,
       +                                bb->addr, bb->l.epoch, bb->l.epochClose);
       +                        goto Exit;
       +                }
       +
       +                /* (ii) */
       +                if(b->l.epoch == epoch && bb->l.epoch == epoch){
       +                        if(getBit(chk->emap, addr)){
       +                                error(chk, "walk: epoch join detected: addr %#ux %L",
       +                                        bb->addr, &bb->l);
       +                                goto Exit;
       +                        }
       +                        setBit(chk->emap, addr);
       +                }
       +
       +                /* (iii) */
       +                if(!(b->l.state&BsCopied) && b->l.epoch == bb->l.epoch){
       +                        if(getBit(chk->xmap, addr)){
       +                                error(chk, "walk: copy join detected; addr %#ux %L",
       +                                        bb->addr, &bb->l);
       +                                goto Exit;
       +                        }
       +                        setBit(chk->xmap, addr);
       +                }
       +        }
       +
       +        /* (iv) */
       +        if(epoch == chk->fs->ehi){
       +                /*
       +                 * since epoch==fs->ehi is first, amap is same as
       +                 * ``have seen active''
       +                 */
       +                if(getBit(chk->amap, addr)){
       +                        error(chk, "walk: active join detected: addr %#ux %L",
       +                                bb->addr, &bb->l);
       +                        goto Exit;
       +                }
       +                if(bb->l.state&BsClosed)
       +                        error(chk, "walk: addr %#ux: block is in active tree but is closed",
       +                                addr);
       +        }else
       +                if(!getBit(chk->amap, addr))
       +                        if(!(bb->l.state&BsClosed)){
       +                                // error(chk, "walk: addr %#ux: block is not in active tree, not closed (%d)",
       +                                // addr, bb->l.epochClose);
       +                                chk->close(chk, bb, epoch+1);
       +                                chk->nclose++;
       +                        }
       +
       +        if(getBit(chk->amap, addr)){
       +                ret = 1;
       +                goto Exit;
       +        }
       +        setBit(chk->amap, addr);
       +
       +        if(chk->nseen++%chk->quantum == 0)
       +                chk->print("check: visited %d/%d blocks (%.0f%%)\n",
       +                        chk->nseen, chk->nblocks, chk->nseen*100./chk->nblocks);
       +
       +        b = nil;                /* make sure no more refs to parent */
       +        USED(b);
       +
       +        switch(type){
       +        default:
       +                /* pointer block */
       +                for(i = 0; i < chk->bsize/VtScoreSize; i++)
       +                        if(!walkEpoch(chk, bb, bb->data + i*VtScoreSize,
       +                            type-1, tag, epoch)){
       +                                setBit(chk->errmap, bb->addr);
       +                                chk->clrp(chk, bb, i);
       +                                chk->nclrp++;
       +                        }
       +                break;
       +        case BtData:
       +                break;
       +        case BtDir:
       +                for(i = 0; i < chk->bsize/VtEntrySize; i++){
       +                        if(!entryUnpack(&e, bb->data, i)){
       +                                // error(chk, "walk: could not unpack entry: %ux[%d]: %R",
       +                                //        addr, i);
       +                                setBit(chk->errmap, bb->addr);
       +                                chk->clre(chk, bb, i);
       +                                chk->nclre++;
       +                                continue;
       +                        }
       +                        if(!(e.flags & VtEntryActive))
       +                                continue;
       +if(0)                        fprint(2, "%x[%d] tag=%x snap=%d score=%V\n",
       +                                addr, i, e.tag, e.snap, e.score);
       +                        ep = epoch;
       +                        if(e.snap != 0){
       +                                if(e.snap >= epoch){
       +                                        // error(chk, "bad snap in entry: %ux[%d] snap = %ud: epoch = %ud",
       +                                        //        addr, i, e.snap, epoch);
       +                                        setBit(chk->errmap, bb->addr);
       +                                        chk->clre(chk, bb, i);
       +                                        chk->nclre++;
       +                                        continue;
       +                                }
       +                                continue;
       +                        }
       +                        if(e.flags & VtEntryLocal){
       +                                if(e.tag < UserTag)
       +                                if(e.tag != RootTag || tag != RootTag || i != 1){
       +                                        // error(chk, "bad tag in entry: %ux[%d] tag = %ux",
       +                                        //        addr, i, e.tag);
       +                                        setBit(chk->errmap, bb->addr);
       +                                        chk->clre(chk, bb, i);
       +                                        chk->nclre++;
       +                                        continue;
       +                                }
       +                        }else
       +                                if(e.tag != 0){
       +                                        // error(chk, "bad tag in entry: %ux[%d] tag = %ux",
       +                                        //        addr, i, e.tag);
       +                                        setBit(chk->errmap, bb->addr);
       +                                        chk->clre(chk, bb, i);
       +                                        chk->nclre++;
       +                                        continue;
       +                                }
       +                        if(!walkEpoch(chk, bb, e.score, entryType(&e),
       +                            e.tag, ep)){
       +                                setBit(chk->errmap, bb->addr);
       +                                chk->clre(chk, bb, i);
       +                                chk->nclre++;
       +                        }
       +                }
       +                break;
       +        }
       +
       +        ret = 1;
       +
       +Exit:
       +        chk->walkdepth--;
       +        blockPut(bb);
       +        return ret;
       +}
       +
       +/*
       + * We've just walked the whole write buffer.  Notice blocks that
       + * aren't marked available but that we didn't visit.  They are lost.
       + */
       +static void
       +checkLeak(Fsck *chk)
       +{
       +        u32int a, nfree, nlost;
       +        Block *b;
       +        Label l;
       +
       +        nfree = 0;
       +        nlost = 0;
       +
       +        for(a = 0; a < chk->nblocks; a++){
       +                if(!readLabel(chk->cache, &l, a)){
       +                        error(chk, "could not read label: addr 0x%ux %d %d: %R",
       +                                a, l.type, l.state);
       +                        continue;
       +                }
       +                if(getBit(chk->amap, a))
       +                        continue;
       +                if(l.state == BsFree || l.epochClose <= chk->fs->elo ||
       +                    l.epochClose == l.epoch){
       +                        nfree++;
       +                        setBit(chk->amap, a);
       +                        continue;
       +                }
       +                if(l.state&BsClosed)
       +                        continue;
       +                nlost++;
       +//                warn(chk, "unreachable block: addr 0x%ux type %d tag 0x%ux "
       +//                        "state %s epoch %ud close %ud", a, l.type, l.tag,
       +//                        bsStr(l.state), l.epoch, l.epochClose);
       +                b = cacheLocal(chk->cache, PartData, a, OReadOnly);
       +                if(b == nil){
       +                        error(chk, "could not read block 0x%#.8ux", a);
       +                        continue;
       +                }
       +                chk->close(chk, b, 0);
       +                chk->nclose++;
       +                setBit(chk->amap, a);
       +                blockPut(b);
       +        }
       +        chk->print("fsys blocks: total=%ud used=%ud(%.1f%%) free=%ud(%.1f%%) lost=%ud(%.1f%%)\n",
       +                chk->nblocks,
       +                chk->nblocks - nfree-nlost,
       +                100.*(chk->nblocks - nfree - nlost)/chk->nblocks,
       +                nfree, 100.*nfree/chk->nblocks,
       +                nlost, 100.*nlost/chk->nblocks);
       +}
       +
       +
       +/*
       + * Check that all sources in the tree are accessible.
       + */
       +static Source *
       +openSource(Fsck *chk, Source *s, char *name, uchar *bm, u32int offset,
       +        u32int gen, int dir, MetaBlock *mb, int i, Block *b)
       +{
       +        Source *r;
       +
       +        r = nil;
       +        if(getBit(bm, offset)){
       +                warn(chk, "multiple references to source: %s -> %d",
       +                        name, offset);
       +                goto Err;
       +        }
       +        setBit(bm, offset);
       +
       +        r = sourceOpen(s, offset, OReadOnly, 0);
       +        if(r == nil){
       +                warn(chk, "could not open source: %s -> %d: %R", name, offset);
       +                goto Err;
       +        }
       +
       +        if(r->gen != gen){
       +                warn(chk, "source has been removed: %s -> %d", name, offset);
       +                goto Err;
       +        }
       +
       +        if(r->dir != dir){
       +                warn(chk, "dir mismatch: %s -> %d", name, offset);
       +                goto Err;
       +        }
       +        return r;
       +Err:
       +        chk->clri(chk, name, mb, i, b);
       +        chk->nclri++;
       +        if(r)
       +                sourceClose(r);
       +        return nil;
       +}
       +
       +typedef struct MetaChunk MetaChunk;
       +struct MetaChunk {
       +        ushort        offset;
       +        ushort        size;
       +        ushort        index;
       +};
       +
       +static int
       +offsetCmp(void *s0, void *s1)
       +{
       +        MetaChunk *mc0, *mc1;
       +
       +        mc0 = s0;
       +        mc1 = s1;
       +        if(mc0->offset < mc1->offset)
       +                return -1;
       +        if(mc0->offset > mc1->offset)
       +                return 1;
       +        return 0;
       +}
       +
       +/*
       + * Fsck that MetaBlock has reasonable header, sorted entries,
       + */
       +static int
       +chkMetaBlock(MetaBlock *mb)
       +{
       +        MetaChunk *mc;
       +        int oo, o, n, i;
       +        uchar *p;
       +
       +        mc = vtMemAlloc(mb->nindex*sizeof(MetaChunk));
       +        p = mb->buf + MetaHeaderSize;
       +        for(i = 0; i < mb->nindex; i++){
       +                mc[i].offset = p[0]<<8 | p[1];
       +                mc[i].size =   p[2]<<8 | p[3];
       +                mc[i].index = i;
       +                p += MetaIndexSize;
       +        }
       +
       +        qsort(mc, mb->nindex, sizeof(MetaChunk), offsetCmp);
       +
       +        /* check block looks ok */
       +        oo = MetaHeaderSize + mb->maxindex*MetaIndexSize;
       +        o = oo;
       +        n = 0;
       +        for(i = 0; i < mb->nindex; i++){
       +                o = mc[i].offset;
       +                n = mc[i].size;
       +                if(o < oo)
       +                        goto Err;
       +                oo += n;
       +        }
       +        if(o+n > mb->size || mb->size - oo != mb->free)
       +                goto Err;
       +
       +        vtMemFree(mc);
       +        return 1;
       +
       +Err:
       +if(0){
       +        fprint(2, "metaChunks failed!\n");
       +        oo = MetaHeaderSize + mb->maxindex*MetaIndexSize;
       +        for(i=0; i<mb->nindex; i++){
       +                fprint(2, "\t%d: %d %d\n", i, mc[i].offset,
       +                        mc[i].offset + mc[i].size);
       +                oo += mc[i].size;
       +        }
       +        fprint(2, "\tused=%d size=%d free=%d free2=%d\n",
       +                oo, mb->size, mb->free, mb->size - oo);
       +}
       +        vtMemFree(mc);
       +        return 0;
       +}
       +
       +static void
       +scanSource(Fsck *chk, char *name, Source *r)
       +{
       +        u32int a, nb, o;
       +        Block *b;
       +        Entry e;
       +
       +        if(!chk->useventi && globalToLocal(r->score)==NilBlock)
       +                return;
       +        if(!sourceGetEntry(r, &e)){
       +                error(chk, "could not get entry for %s", name);
       +                return;
       +        }
       +        a = globalToLocal(e.score);
       +        if(!chk->useventi && a==NilBlock)
       +                return;
       +        if(getBit(chk->smap, a))
       +                return;
       +        setBit(chk->smap, a);
       +
       +        nb = (sourceGetSize(r) + r->dsize-1) / r->dsize;
       +        for(o = 0; o < nb; o++){
       +                b = sourceBlock(r, o, OReadOnly);
       +                if(b == nil){
       +                        error(chk, "could not read block in data file %s", name);
       +                        continue;
       +                }
       +                if(b->addr != NilBlock && getBit(chk->errmap, b->addr)){
       +                        warn(chk, "previously reported error in block %ux is in file %s",
       +                                b->addr, name);
       +                }
       +                blockPut(b);
       +        }
       +}
       +
       +/*
       + * Walk the source tree making sure that the BtData
       + * sources containing directory entries are okay.
       + */
       +static void
       +chkDir(Fsck *chk, char *name, Source *source, Source *meta)
       +{
       +        int i;
       +        u32int a1, a2, nb, o;
       +        char *s, *nn;
       +        uchar *bm;
       +        Block *b, *bb;
       +        DirEntry de;
       +        Entry e1, e2;
       +        MetaBlock mb;
       +        MetaEntry me;
       +        Source *r, *mr;
       +
       +        if(!chk->useventi && globalToLocal(source->score)==NilBlock &&
       +            globalToLocal(meta->score)==NilBlock)
       +                return;
       +
       +        if(!sourceLock2(source, meta, OReadOnly)){
       +                warn(chk, "could not lock sources for %s: %R", name);
       +                return;
       +        }
       +        if(!sourceGetEntry(source, &e1) || !sourceGetEntry(meta, &e2)){
       +                warn(chk, "could not load entries for %s: %R", name);
       +                return;
       +        }
       +        a1 = globalToLocal(e1.score);
       +        a2 = globalToLocal(e2.score);
       +        if((!chk->useventi && a1==NilBlock && a2==NilBlock)
       +        || (getBit(chk->smap, a1) && getBit(chk->smap, a2))){
       +                sourceUnlock(source);
       +                sourceUnlock(meta);
       +                return;
       +        }
       +        setBit(chk->smap, a1);
       +        setBit(chk->smap, a2);
       +
       +        bm = vtMemAllocZ(sourceGetDirSize(source)/8 + 1);
       +
       +        nb = (sourceGetSize(meta) + meta->dsize - 1)/meta->dsize;
       +        for(o = 0; o < nb; o++){
       +                b = sourceBlock(meta, o, OReadOnly);
       +                if(b == nil){
       +                        error(chk, "could not read block in meta file: %s[%ud]: %R",
       +                                name, o);
       +                        continue;
       +                }
       +if(0)                fprint(2, "source %V:%d block %d addr %d\n", source->score,
       +                        source->offset, o, b->addr);
       +                if(b->addr != NilBlock && getBit(chk->errmap, b->addr))
       +                        warn(chk, "previously reported error in block %ux is in %s",
       +                                b->addr, name);
       +
       +                if(!mbUnpack(&mb, b->data, meta->dsize)){
       +                        error(chk, "could not unpack meta block: %s[%ud]: %R",
       +                                name, o);
       +                        blockPut(b);
       +                        continue;
       +                }
       +                if(!chkMetaBlock(&mb)){
       +                        error(chk, "bad meta block: %s[%ud]: %R", name, o);
       +                        blockPut(b);
       +                        continue;
       +                }
       +                s = nil;
       +                for(i=mb.nindex-1; i>=0; i--){
       +                        meUnpack(&me, &mb, i);
       +                        if(!deUnpack(&de, &me)){
       +                                error(chk,
       +                                  "could not unpack dir entry: %s[%ud][%d]: %R",
       +                                        name, o, i);
       +                                continue;
       +                        }
       +                        if(s && strcmp(s, de.elem) <= 0)
       +                                error(chk,
       +                           "dir entry out of order: %s[%ud][%d] = %s last = %s",
       +                                        name, o, i, de.elem, s);
       +                        vtMemFree(s);
       +                        s = vtStrDup(de.elem);
       +                        nn = smprint("%s/%s", name, de.elem);
       +                        if(nn == nil){
       +                                error(chk, "out of memory");
       +                                continue;
       +                        }
       +                        if(chk->printdirs)
       +                                if(de.mode&ModeDir)
       +                                        chk->print("%s/\n", nn);
       +                        if(chk->printfiles)
       +                                if(!(de.mode&ModeDir))
       +                                        chk->print("%s\n", nn);
       +                        if(!(de.mode & ModeDir)){
       +                                r = openSource(chk, source, nn, bm, de.entry,
       +                                        de.gen, 0, &mb, i, b);
       +                                if(r != nil){
       +                                        if(sourceLock(r, OReadOnly)){
       +                                                scanSource(chk, nn, r);
       +                                                sourceUnlock(r);
       +                                        }
       +                                        sourceClose(r);
       +                                }
       +                                deCleanup(&de);
       +                                free(nn);
       +                                continue;
       +                        }
       +
       +                        r = openSource(chk, source, nn, bm, de.entry,
       +                                de.gen, 1, &mb, i, b);
       +                        if(r == nil){
       +                                deCleanup(&de);
       +                                free(nn);
       +                                continue;
       +                        }
       +
       +                        mr = openSource(chk, source, nn, bm, de.mentry,
       +                                de.mgen, 0, &mb, i, b);
       +                        if(mr == nil){
       +                                sourceClose(r);
       +                                deCleanup(&de);
       +                                free(nn);
       +                                continue;
       +                        }
       +
       +                        if(!(de.mode&ModeSnapshot) || chk->walksnapshots)
       +                                chkDir(chk, nn, r, mr);
       +
       +                        sourceClose(mr);
       +                        sourceClose(r);
       +                        deCleanup(&de);
       +                        free(nn);
       +                        deCleanup(&de);
       +
       +                }
       +                vtMemFree(s);
       +                blockPut(b);
       +        }
       +
       +        nb = sourceGetDirSize(source);
       +        for(o=0; o<nb; o++){
       +                if(getBit(bm, o))
       +                        continue;
       +                r = sourceOpen(source, o, OReadOnly, 0);
       +                if(r == nil)
       +                        continue;
       +                warn(chk, "non referenced entry in source %s[%d]", name, o);
       +                if((bb = sourceBlock(source, o/(source->dsize/VtEntrySize),
       +                    OReadOnly)) != nil){
       +                        if(bb->addr != NilBlock){
       +                                setBit(chk->errmap, bb->addr);
       +                                chk->clre(chk, bb, o%(source->dsize/VtEntrySize));
       +                                chk->nclre++;
       +                        }
       +                        blockPut(bb);
       +                }
       +                sourceClose(r);
       +        }
       +
       +        sourceUnlock(source);
       +        sourceUnlock(meta);
       +        vtMemFree(bm);
       +}
       +
       +static void
       +checkDirs(Fsck *chk)
       +{
       +        Source *r, *mr;
       +
       +        sourceLock(chk->fs->source, OReadOnly);
       +        r = sourceOpen(chk->fs->source, 0, OReadOnly, 0);
       +        mr = sourceOpen(chk->fs->source, 1, OReadOnly, 0);
       +        sourceUnlock(chk->fs->source);
       +        chkDir(chk, "", r, mr);
       +
       +        sourceClose(r);
       +        sourceClose(mr);
       +}
       +
       +static void
       +setBit(uchar *bmap, u32int addr)
       +{
       +        if(addr == NilBlock)
       +                return;
       +
       +        bmap[addr>>3] |= 1 << (addr & 7);
       +}
       +
       +static int
       +getBit(uchar *bmap, u32int addr)
       +{
       +        if(addr == NilBlock)
       +                return 0;
       +
       +        return (bmap[addr>>3] >> (addr & 7)) & 1;
       +}
       +
       +static void
       +error(Fsck *chk, char *fmt, ...)
       +{
       +        char buf[256];
       +        va_list arg;
       +        static int nerr;
       +
       +        va_start(arg, fmt);
       +        vseprint(buf, buf+sizeof buf, fmt, arg);
       +        va_end(arg);
       +
       +        chk->print("error: %s\n", buf);
       +
       +//        if(nerr++ > 20)
       +//                vtFatal("too many errors");
       +}
       +
       +static void
       +warn(Fsck *chk, char *fmt, ...)
       +{
       +        char buf[256];
       +        va_list arg;
       +        static int nerr;
       +
       +        va_start(arg, fmt);
       +        vseprint(buf, buf+sizeof buf, fmt, arg);
       +        va_end(arg);
       +
       +        chk->print("error: %s\n", buf);
       +}
       +
       +static void
       +clrenop(Fsck*, Block*, int)
       +{
       +}
       +
       +static void
       +closenop(Fsck*, Block*, u32int)
       +{
       +}
       +
       +static void
       +clrinop(Fsck*, char*, MetaBlock*, int, Block*)
       +{
       +}
       +
       +static int
       +printnop(char*, ...)
       +{
       +        return 0;
       +}
 (DIR) diff --git a/src/cmd/fossil/dat.h b/src/cmd/fossil/dat.h
       t@@ -0,0 +1,332 @@
       +typedef struct Arch Arch;
       +typedef struct BList BList;
       +typedef struct Block Block;
       +typedef struct Cache Cache;
       +typedef struct Disk Disk;
       +typedef struct Entry Entry;
       +typedef struct Fsck Fsck;
       +typedef struct Header Header;
       +typedef struct Label Label;
       +typedef struct Periodic Periodic;
       +typedef struct Snap Snap;
       +typedef struct Source Source;
       +typedef struct Super Super;
       +typedef struct WalkPtr WalkPtr;
       +
       +#pragma incomplete Arch
       +#pragma incomplete BList
       +#pragma incomplete Cache
       +#pragma incomplete Disk
       +#pragma incomplete Periodic
       +#pragma incomplete Snap
       +
       +/* tunable parameters - probably should not be constants */
       +enum {
       +        /*
       +         * estimate of bytes per dir entries - determines number
       +         * of index entries in the block
       +         */
       +        BytesPerEntry = 100,
       +        /* don't allocate in block if more than this percentage full */
       +        FullPercentage = 80,
       +        FlushSize = 200,        /* number of blocks to flush */
       +        DirtyPercentage = 50,        /* maximum percentage of dirty blocks */
       +};
       +
       +enum {
       +        Nowaitlock,
       +        Waitlock,
       +
       +        NilBlock        = (~0UL),
       +        MaxBlock        = (1UL<<31),
       +};
       +
       +enum {
       +        HeaderMagic = 0x3776ae89,
       +        HeaderVersion = 1,
       +        HeaderOffset = 128*1024,
       +        HeaderSize = 512,
       +        SuperMagic = 0x2340a3b1,
       +        SuperSize = 512,
       +        SuperVersion = 1,
       +        LabelSize = 14,
       +};
       +
       +/* well known tags */
       +enum {
       +        BadTag = 0,                /* this tag should not be used */
       +        RootTag = 1,                /* root of fs */
       +        EnumTag,                /* root of a dir listing */
       +        UserTag = 32,                /* all other tags should be >= UserTag */
       +};
       +
       +struct Super {
       +        u16int version;
       +        u32int epochLow;
       +        u32int epochHigh;
       +        u64int qid;                        /* next qid */
       +        u32int active;                        /* root of active file system */
       +        u32int next;                        /* root of next snapshot to archive */
       +        u32int current;                        /* root of snapshot currently archiving */
       +        uchar last[VtScoreSize];        /* last snapshot successfully archived */
       +        char name[128];                        /* label */
       +};
       +
       +
       +struct Fs {
       +        Arch        *arch;                /* immutable */
       +        Cache        *cache;                /* immutable */
       +        int        mode;                /* immutable */
       +        int        noatimeupd;        /* immutable */
       +        int        blockSize;        /* immutable */
       +        VtSession *z;                /* immutable */
       +        Snap        *snap;                /* immutable */
       +        /* immutable; copy here & Fsys to ease error reporting */
       +        char        *name;
       +
       +        Periodic *metaFlush; /* periodically flushes metadata cached in files */
       +
       +        /*
       +         * epoch lock.
       +         * Most operations on the fs require a read lock of elk, ensuring that
       +         * the current high and low epochs do not change under foot.
       +         * This lock is mostly acquired via a call to fileLock or fileRlock.
       +         * Deletion and creation of snapshots occurs under a write lock of elk,
       +         * ensuring no file operations are occurring concurrently.
       +         */
       +        VtLock        *elk;                /* epoch lock */
       +        u32int        ehi;                /* epoch high */
       +        u32int        elo;                /* epoch low */
       +
       +        int        halted;        /* epoch lock is held to halt (console initiated) */
       +
       +        Source        *source;        /* immutable: root of sources */
       +        File        *file;                /* immutable: root of files */
       +};
       +
       +/*
       + * variant on VtEntry
       + * there are extra fields when stored locally
       + */
       +struct Entry {
       +        u32int        gen;                        /* generation number */
       +        ushort        psize;                        /* pointer block size */
       +        ushort        dsize;                        /* data block size */
       +        uchar        depth;                        /* unpacked from flags */
       +        uchar        flags;
       +        uvlong        size;
       +        uchar        score[VtScoreSize];
       +        u32int        tag;        /* tag for local blocks: zero if stored on Venti */
       +        u32int        snap;        /* non-zero -> entering snapshot of given epoch */
       +        uchar        archive; /* archive this snapshot: only valid for snap != 0 */
       +};
       +
       +/*
       + * This is called a `stream' in the fossil paper.  There used to be Sinks too.
       + * We believe that Sources and Files are one-to-one.
       + */
       +struct Source {
       +        Fs        *fs;                /* immutable */
       +        int        mode;                /* immutable */
       +        int        issnapshot;        /* immutable */
       +        u32int        gen;                /* immutable */
       +        int        dsize;                /* immutable */
       +        int        dir;                /* immutable */
       +
       +        Source        *parent;        /* immutable */
       +        File        *file;                /* immutable; point back */
       +
       +        VtLock        *lk;
       +        int        ref;
       +        /*
       +         * epoch for the source
       +         * for ReadWrite sources, epoch is used to lazily notice
       +         * sources that must be split from the snapshots.
       +         * for ReadOnly sources, the epoch represents the minimum epoch
       +         * along the chain from the root, and is used to lazily notice
       +         * sources that have become invalid because they belong to an old
       +         * snapshot.
       +         */
       +        u32int        epoch;
       +        Block        *b;                /* block containing this source */
       +        uchar        score[VtScoreSize]; /* score of block containing this source */
       +        u32int        scoreEpoch;        /* epoch of block containing this source */
       +        int        epb;                /* immutable: entries per block in parent */
       +        u32int        tag;                /* immutable: tag of parent */
       +        u32int        offset;         /* immutable: entry offset in parent */
       +};
       +
       +
       +struct Header {
       +        ushort version;
       +        ushort blockSize;
       +        ulong super;        /* super blocks */
       +        ulong label;        /* start of labels */
       +        ulong data;        /* end of labels - start of data blocks */
       +        ulong end;        /* end of data blocks */
       +};
       +
       +/*
       + * contains a one block buffer
       + * to avoid problems of the block changing underfoot
       + * and to enable an interface that supports unget.
       + */
       +struct DirEntryEnum {
       +        File        *file;
       +
       +        u32int        boff;                 /* block offset */
       +
       +        int        i, n;
       +        DirEntry *buf;
       +};
       +
       +/* Block states */
       +enum {
       +        BsFree = 0,                /* available for allocation */
       +        BsBad = 0xFF,                /* something is wrong with this block */
       +
       +        /* bit fields */
       +        BsAlloc = 1<<0,        /* block is in use */
       +        BsCopied = 1<<1,/* block has been copied (usually in preparation for unlink) */
       +        BsVenti = 1<<2,        /* block has been stored on Venti */
       +        BsClosed = 1<<3,/* block has been unlinked on disk from active file system */
       +        BsMask = BsAlloc|BsCopied|BsVenti|BsClosed,
       +};
       +
       +/*
       + * block types
       + * more regular than Venti block types
       + * bit 3 -> block or data block
       + * bits 2-0 -> level of block
       + */
       +enum {
       +        BtData,
       +        BtDir = 1<<3,
       +        BtLevelMask = 7,
       +        BtMax = 1<<4,
       +};
       +
       +/* io states */
       +enum {
       +        BioEmpty,        /* label & data are not valid */
       +        BioLabel,        /* label is good */
       +        BioClean,        /* data is on the disk */
       +        BioDirty,        /* data is not yet on the disk */
       +        BioReading,        /* in process of reading data */
       +        BioWriting,        /* in process of writing data */
       +        BioReadError,        /* error reading: assume disk always handles write errors */
       +        BioVentiError,        /* error reading from venti (probably disconnected) */
       +        BioMax
       +};
       +
       +struct Label {
       +        uchar type;
       +        uchar state;
       +        u32int tag;
       +        u32int epoch;
       +        u32int epochClose;
       +};
       +
       +struct Block {
       +        Cache        *c;
       +        int        ref;
       +        int        nlock;
       +        uintptr        pc;                /* pc that fetched this block from the cache */
       +
       +        VtLock        *lk;
       +
       +        int         part;
       +        u32int        addr;
       +        uchar        score[VtScoreSize];        /* score */
       +        Label        l;
       +
       +        uchar        *dmap;
       +
       +        uchar         *data;
       +
       +        /* the following is private; used by cache */
       +
       +        Block        *next;                        /* doubly linked hash chains */
       +        Block        **prev;
       +        u32int        heap;                        /* index in heap table */
       +        u32int        used;                        /* last reference times */
       +
       +        u32int        vers;                        /* version of dirty flag */
       +
       +        BList        *uhead;        /* blocks to unlink when this block is written */
       +        BList        *utail;
       +
       +        /* block ordering for cache -> disk */
       +        BList        *prior;                        /* list of blocks before this one */
       +
       +        Block        *ionext;
       +        int        iostate;
       +        VtRendez *ioready;
       +};
       +
       +/* tree walker, for gc and archiver */
       +struct WalkPtr
       +{
       +        uchar        *data;
       +        int        isEntry;
       +        int        n;
       +        int        m;
       +        Entry        e;
       +        uchar        type;
       +        u32int        tag;
       +};
       +
       +enum
       +{
       +        DoClose = 1<<0,
       +        DoClre = 1<<1,
       +        DoClri = 1<<2,
       +        DoClrp = 1<<3,
       +};
       +
       +struct Fsck
       +{
       +        /* filled in by caller */
       +        int        printblocks;
       +        int        useventi;
       +        int        flags;
       +        int        printdirs;
       +        int        printfiles;
       +        int        walksnapshots;
       +        int        walkfs;
       +        Fs        *fs;
       +        int        (*print)(char*, ...);
       +        void        (*clre)(Fsck*, Block*, int);
       +        void        (*clrp)(Fsck*, Block*, int);
       +        void        (*close)(Fsck*, Block*, u32int);
       +        void        (*clri)(Fsck*, char*, MetaBlock*, int, Block*);
       +
       +        /* used internally */
       +        Cache        *cache;
       +        uchar        *amap;        /* all blocks seen so far */
       +        uchar        *emap;        /* all blocks seen in this epoch */
       +        uchar        *xmap;        /* all blocks in this epoch with parents in this epoch */
       +        uchar        *errmap;        /* blocks with errors */
       +        uchar        *smap;                /* walked sources */
       +        int        nblocks;
       +        int        bsize;
       +        int        walkdepth;
       +        u32int        hint;                /* where the next root probably is */
       +        int        nseen;
       +        int        quantum;
       +        int        nclre;
       +        int        nclrp;
       +        int        nclose;
       +        int        nclri;
       +};
       +
       +/* disk partitions; keep in sync with partname[] in disk.c */
       +enum {
       +        PartError,
       +        PartSuper,
       +        PartLabel,
       +        PartData,
       +        PartVenti,        /* fake partition */
       +};
       +
       +extern vtType[BtMax];
 (DIR) diff --git a/src/cmd/fossil/disk.c b/src/cmd/fossil/disk.c
       t@@ -0,0 +1,406 @@
       +#include "stdinc.h"
       +#include "dat.h"
       +#include "fns.h"
       +#include "error.h"
       +
       +static void diskThread(void *a);
       +
       +enum {
       +        /*
       +         * disable measurement since it gets alignment faults on BG
       +         * and the guts used to be commented out.
       +         */
       +        Timing        = 0,                        /* flag */
       +        QueueSize = 100,                /* maximum block to queue */
       +};
       +
       +struct Disk {
       +        VtLock *lk;
       +        int ref;
       +
       +        int fd;
       +        Header h;
       +
       +        VtRendez *flow;
       +        VtRendez *starve;
       +        VtRendez *flush;
       +        VtRendez *die;
       +
       +        int nqueue;
       +
       +        Block *cur;                /* block to do on current scan */
       +        Block *next;                /* blocks to do next scan */
       +};
       +
       +/* keep in sync with Part* enum in dat.h */
       +static char *partname[] = {
       +        [PartError]        "error",
       +        [PartSuper]        "super",
       +        [PartLabel]        "label",
       +        [PartData]        "data",
       +        [PartVenti]        "venti",
       +};
       +
       +Disk *
       +diskAlloc(int fd)
       +{
       +        u8int buf[HeaderSize];
       +        Header h;
       +        Disk *disk;
       +
       +        if(pread(fd, buf, HeaderSize, HeaderOffset) < HeaderSize){
       +                vtSetError("short read: %r");
       +                vtOSError();
       +                return nil;
       +        }
       +
       +        if(!headerUnpack(&h, buf)){
       +                vtSetError("bad disk header");
       +                return nil;
       +        }
       +        disk = vtMemAllocZ(sizeof(Disk));
       +        disk->lk = vtLockAlloc();
       +        disk->starve = vtRendezAlloc(disk->lk);
       +        disk->flow = vtRendezAlloc(disk->lk);
       +        disk->flush = vtRendezAlloc(disk->lk);
       +        disk->fd = fd;
       +        disk->h = h;
       +
       +        disk->ref = 2;
       +        vtThread(diskThread, disk);
       +
       +        return disk;
       +}
       +
       +void
       +diskFree(Disk *disk)
       +{
       +        diskFlush(disk);
       +
       +        /* kill slave */
       +        vtLock(disk->lk);
       +        disk->die = vtRendezAlloc(disk->lk);
       +        vtWakeup(disk->starve);
       +        while(disk->ref > 1)
       +                vtSleep(disk->die);
       +        vtUnlock(disk->lk);
       +        vtRendezFree(disk->flow);
       +        vtRendezFree(disk->starve);
       +        vtRendezFree(disk->die);
       +        vtLockFree(disk->lk);
       +        close(disk->fd);
       +        vtMemFree(disk);
       +}
       +
       +static u32int
       +partStart(Disk *disk, int part)
       +{
       +        switch(part){
       +        default:
       +                assert(0);
       +        case PartSuper:
       +                return disk->h.super;
       +        case PartLabel:
       +                return disk->h.label;
       +        case PartData:
       +                return disk->h.data;
       +        }
       +}
       +
       +
       +static u32int
       +partEnd(Disk *disk, int part)
       +{
       +        switch(part){
       +        default:
       +                assert(0);
       +        case PartSuper:
       +                return disk->h.super+1;
       +        case PartLabel:
       +                return disk->h.data;
       +        case PartData:
       +                return disk->h.end;
       +        }
       +}
       +
       +int
       +diskReadRaw(Disk *disk, int part, u32int addr, uchar *buf)
       +{
       +        ulong start, end;
       +        u64int offset;
       +        int n, nn;
       +
       +        start = partStart(disk, part);
       +        end = partEnd(disk, part);
       +
       +        if(addr >= end-start){
       +                vtSetError(EBadAddr);
       +                return 0;
       +        }
       +
       +        offset = ((u64int)(addr + start))*disk->h.blockSize;
       +        n = disk->h.blockSize;
       +        while(n > 0){
       +                nn = pread(disk->fd, buf, n, offset);
       +                if(nn < 0){
       +                        vtOSError();
       +                        return 0;
       +                }
       +                if(nn == 0){
       +                        vtSetError("eof reading disk");
       +                        return 0;
       +                }
       +                n -= nn;
       +                offset += nn;
       +                buf += nn;
       +        }
       +        return 1;
       +}
       +
       +int
       +diskWriteRaw(Disk *disk, int part, u32int addr, uchar *buf)
       +{
       +        ulong start, end;
       +        u64int offset;
       +        int n;
       +
       +        start = partStart(disk, part);
       +        end = partEnd(disk, part);
       +
       +        if(addr >= end - start){
       +                vtSetError(EBadAddr);
       +                return 0;
       +        }
       +
       +        offset = ((u64int)(addr + start))*disk->h.blockSize;
       +        n = pwrite(disk->fd, buf, disk->h.blockSize, offset);
       +        if(n < 0){
       +                vtOSError();
       +                return 0;
       +        }
       +        if(n < disk->h.blockSize) {
       +                vtSetError("short write");
       +                return 0;
       +        }
       +
       +        return 1;
       +}
       +
       +static void
       +diskQueue(Disk *disk, Block *b)
       +{
       +        Block **bp, *bb;
       +
       +        vtLock(disk->lk);
       +        while(disk->nqueue >= QueueSize)
       +                vtSleep(disk->flow);
       +        if(disk->cur == nil || b->addr > disk->cur->addr)
       +                bp = &disk->cur;
       +        else
       +                bp = &disk->next;
       +
       +        for(bb=*bp; bb; bb=*bp){
       +                if(b->addr < bb->addr)
       +                        break;
       +                bp = &bb->ionext;
       +        }
       +        b->ionext = bb;
       +        *bp = b;
       +        if(disk->nqueue == 0)
       +                vtWakeup(disk->starve);
       +        disk->nqueue++;
       +        vtUnlock(disk->lk);
       +}
       +
       +
       +void
       +diskRead(Disk *disk, Block *b)
       +{
       +        assert(b->iostate == BioEmpty || b->iostate == BioLabel);
       +        blockSetIOState(b, BioReading);
       +        diskQueue(disk, b);
       +}
       +
       +void
       +diskWrite(Disk *disk, Block *b)
       +{
       +        assert(b->nlock == 1);
       +        assert(b->iostate == BioDirty);
       +        blockSetIOState(b, BioWriting);
       +        diskQueue(disk, b);
       +}
       +
       +void
       +diskWriteAndWait(Disk *disk, Block *b)
       +{
       +        int nlock;
       +
       +        /*
       +         * If b->nlock > 1, the block is aliased within
       +         * a single thread.  That thread is us.
       +         * DiskWrite does some funny stuff with VtLock
       +         * and blockPut that basically assumes b->nlock==1.
       +         * We humor diskWrite by temporarily setting
       +         * nlock to 1.  This needs to be revisited.
       +         */
       +        nlock = b->nlock;
       +        if(nlock > 1)
       +                b->nlock = 1;
       +        diskWrite(disk, b);
       +        while(b->iostate != BioClean)
       +                vtSleep(b->ioready);
       +        b->nlock = nlock;
       +}
       +
       +int
       +diskBlockSize(Disk *disk)
       +{
       +        return disk->h.blockSize;        /* immuttable */
       +}
       +
       +int
       +diskFlush(Disk *disk)
       +{
       +        Dir dir;
       +
       +        vtLock(disk->lk);
       +        while(disk->nqueue > 0)
       +                vtSleep(disk->flush);
       +        vtUnlock(disk->lk);
       +
       +        /* there really should be a cleaner interface to flush an fd */
       +        nulldir(&dir);
       +        if(dirfwstat(disk->fd, &dir) < 0){
       +                vtOSError();
       +                return 0;
       +        }
       +        return 1;
       +}
       +
       +u32int
       +diskSize(Disk *disk, int part)
       +{
       +        return partEnd(disk, part) - partStart(disk, part);
       +}
       +
       +static uintptr
       +mypc(int x)
       +{
       +        return getcallerpc(&x);
       +}
       +
       +static char *
       +disk2file(Disk *disk)
       +{
       +        static char buf[256];
       +
       +        if (fd2path(disk->fd, buf, sizeof buf) < 0)
       +                strncpy(buf, "GOK", sizeof buf);
       +        return buf;
       +}
       +
       +static void
       +diskThread(void *a)
       +{
       +        Disk *disk = a;
       +        Block *b;
       +        uchar *buf, *p;
       +        double t;
       +        int nio;
       +
       +        vtThreadSetName("disk");
       +
       +//fprint(2, "diskThread %d\n", getpid());
       +
       +        buf = vtMemAlloc(disk->h.blockSize);
       +
       +        vtLock(disk->lk);
       +        if (Timing) {
       +                nio = 0;
       +                t = -nsec();
       +        }
       +        for(;;){
       +                while(disk->nqueue == 0){
       +                        if (Timing) {
       +                                t += nsec();
       +                                if(nio >= 10000){
       +                                        fprint(2, "disk: io=%d at %.3fms\n",
       +                                                nio, t*1e-6/nio);
       +                                        nio = 0;
       +                                        t = 0;
       +                                }
       +                        }
       +                        if(disk->die != nil)
       +                                goto Done;
       +                        vtSleep(disk->starve);
       +                        if (Timing)
       +                                t -= nsec();
       +                }
       +                assert(disk->cur != nil || disk->next != nil);
       +
       +                if(disk->cur == nil){
       +                        disk->cur = disk->next;
       +                        disk->next = nil;
       +                }
       +                b = disk->cur;
       +                disk->cur = b->ionext;
       +                vtUnlock(disk->lk);
       +
       +                /*
       +                 * no one should hold onto blocking in the
       +                 * reading or writing state, so this lock should
       +                 * not cause deadlock.
       +                 */
       +if(0)fprint(2, "fossil: diskThread: %d:%d %x\n", getpid(), b->part, b->addr);
       +                bwatchLock(b);
       +                vtLock(b->lk);
       +                b->pc = mypc(0);
       +                assert(b->nlock == 1);
       +                switch(b->iostate){
       +                default:
       +                        abort();
       +                case BioReading:
       +                        if(!diskReadRaw(disk, b->part, b->addr, b->data)){
       +                                fprint(2, "fossil: diskReadRaw failed: %s: "
       +                                        "score %V: part=%s block %ud: %r\n",
       +                                        disk2file(disk), b->score,
       +                                        partname[b->part], b->addr);
       +                                blockSetIOState(b, BioReadError);
       +                        }else
       +                                blockSetIOState(b, BioClean);
       +                        break;
       +                case BioWriting:
       +                        p = blockRollback(b, buf);
       +                        /* NB: ctime result ends with a newline */
       +                        if(!diskWriteRaw(disk, b->part, b->addr, p)){
       +                                fprint(2, "fossil: diskWriteRaw failed: %s: "
       +                                    "score %V: date %s part=%s block %ud: %r\n",
       +                                        disk2file(disk), b->score,
       +                                        ctime(time(0)),
       +                                        partname[b->part], b->addr);
       +                                break;
       +                        }
       +                        if(p != buf)
       +                                blockSetIOState(b, BioClean);
       +                        else
       +                                blockSetIOState(b, BioDirty);
       +                        break;
       +                }
       +
       +                blockPut(b);                /* remove extra reference, unlock */
       +                vtLock(disk->lk);
       +                disk->nqueue--;
       +                if(disk->nqueue == QueueSize-1)
       +                        vtWakeup(disk->flow);
       +                if(disk->nqueue == 0)
       +                        vtWakeup(disk->flush);
       +                if(Timing)
       +                        nio++;
       +        }
       +Done:
       +//fprint(2, "diskThread done\n");
       +        disk->ref--;
       +        vtWakeup(disk->die);
       +        vtUnlock(disk->lk);
       +        vtMemFree(buf);
       +}
 (DIR) diff --git a/src/cmd/fossil/dump.c b/src/cmd/fossil/dump.c
       t@@ -0,0 +1,86 @@
       +/*
       + * Clumsy hack to take snapshots and dumps.
       + */
       +#include <u.h>
       +#include <libc.h>
       +
       +void
       +usage(void)
       +{
       +        fprint(2, "usage: fossil/dump [-i snap-interval] [-n name] fscons /n/fossil\n");
       +        exits("usage");
       +}
       +
       +char*
       +snapnow(void)
       +{
       +        Tm t;
       +        static char buf[100];
       +
       +        t = *localtime(time(0)-5*60*60);        /* take dumps at 5:00 am */
       +
       +        sprint(buf, "archive/%d/%02d%02d", t.year+1900, t.mon+1, t.mday);
       +        return buf;
       +}
       +
       +void
       +main(int argc, char **argv)
       +{
       +        int onlyarchive, cons, s;
       +        ulong t, i;
       +        char *name;
       +
       +        name = "main";
       +        s = 0;
       +        onlyarchive = 0;
       +        i = 60*60;                /* one hour */
       +        ARGBEGIN{
       +        case 'i':
       +                i = atoi(EARGF(usage()));
       +                if(i == 0){
       +                        onlyarchive = 1;
       +                        i = 60*60;
       +                }
       +                break;
       +        case 'n':
       +                name = EARGF(usage());
       +                break;
       +        case 's':
       +                s = atoi(EARGF(usage()));
       +                break;
       +        }ARGEND
       +
       +        if(argc != 2)
       +                usage();
       +
       +        if((cons = open(argv[0], OWRITE)) < 0)
       +                sysfatal("open %s: %r", argv[0]);
       +
       +        if(chdir(argv[1]) < 0)
       +                sysfatal("chdir %s: %r", argv[1]);
       +
       +        rfork(RFNOTEG);
       +        switch(fork()){
       +        case -1:
       +                sysfatal("fork: %r");
       +        case 0:
       +                break;
       +        default:
       +                exits(0);
       +        }
       +
       +        /*
       +         * pause at boot time to let clock stabilize.
       +         */
       +        if(s)
       +                sleep(s*1000);
       +
       +        for(;;){
       +                if(access(snapnow(), AEXIST) < 0)
       +                        fprint(cons, "\nfsys %s snap -a\n", name);
       +                t = time(0);
       +                sleep((i - t%i)*1000+200);
       +                if(!onlyarchive)
       +                        fprint(cons, "\nfsys %s snap\n", name);
       +        }
       +}
 (DIR) diff --git a/src/cmd/fossil/epoch.c b/src/cmd/fossil/epoch.c
       t@@ -0,0 +1,51 @@
       +#include "stdinc.h"
       +#include "dat.h"
       +#include "fns.h"
       +
       +uchar buf[65536];
       +
       +void
       +usage(void)
       +{
       +        fprint(2, "usage: fossil/epoch fs [new-low-epoch]\n");
       +        exits("usage");
       +}
       +
       +void
       +main(int argc, char **argv)
       +{
       +        int fd;
       +        Header h;
       +        Super s;
       +
       +        ARGBEGIN{
       +        default:
       +                usage();
       +        }ARGEND
       +
       +        if(argc == 0 || argc > 2)
       +                usage();
       +
       +        if((fd = open(argv[0], argc==2 ? ORDWR : OREAD)) < 0)
       +                sysfatal("open %s: %r", argv[0]);
       +
       +        if(pread(fd, buf, HeaderSize, HeaderOffset) != HeaderSize)
       +                sysfatal("reading header: %r");
       +        if(!headerUnpack(&h, buf))
       +                sysfatal("unpacking header: %r");
       +
       +        if(pread(fd, buf, h.blockSize, (vlong)h.super*h.blockSize) != h.blockSize)
       +                sysfatal("reading super block: %r");
       +
       +        if(!superUnpack(&s, buf))
       +                sysfatal("unpacking super block: %r");
       +
       +        print("epoch %d\n", s.epochLow);
       +        if(argc == 2){
       +                s.epochLow = strtoul(argv[1], 0, 0);
       +                superPack(&s, buf);
       +                if(pwrite(fd, buf, h.blockSize, (vlong)h.super*h.blockSize) != h.blockSize)
       +                        sysfatal("writing super block: %r");
       +        }
       +        exits(0);
       +}
 (DIR) diff --git a/src/cmd/fossil/error.c b/src/cmd/fossil/error.c
       t@@ -0,0 +1,38 @@
       +#include "stdinc.h"
       +#include "dat.h"
       +#include "fns.h"
       +#include "error.h"
       +
       +char EBadAddr[] = "illegal block address";
       +char EBadDir[] = "corrupted directory entry";
       +char EBadEntry[] = "corrupted file entry";
       +char EBadLabel[] = "corrupted block label";
       +char EBadMeta[] = "corrupted meta data";
       +char EBadMode[] = "illegal mode";
       +char EBadOffset[] = "illegal offset";
       +char EBadPath[] = "illegal path element";
       +char EBadRoot[] = "root of file system is corrupted";
       +char EBadSuper[] = "corrupted super block";
       +char EBlockTooBig[] = "block too big";
       +char ECacheFull[] = "no free blocks in memory cache";
       +char EConvert[] = "protocol botch";
       +char EExists[] = "file already exists";
       +char EFsFill[] = "file system is full";
       +char EIO[] = "i/o error";
       +char EInUse[] = "file is in use";
       +char ELabelMismatch[] = "block label mismatch";
       +char ENilBlock[] = "illegal block address";
       +char ENoDir[] = "directory entry is not allocated";
       +char ENoFile[] = "file does not exist";
       +char ENotDir[] = "not a directory";
       +char ENotEmpty[] = "directory not empty";
       +char ENotFile[] = "not a file";
       +char EReadOnly[] = "file is read only";
       +char ERemoved[] = "file has been removed";
       +char ENotArchived[] = "file is not archived";
       +char EResize[] = "only support truncation to zero length";
       +char ERoot[] = "cannot remove root";
       +char ESnapOld[] = "snapshot has been deleted";
       +char ESnapRO[] = "snapshot is read only";
       +char ETooBig[] = "file too big";
       +char EVentiIO[] = "venti i/o error";
 (DIR) diff --git a/src/cmd/fossil/error.h b/src/cmd/fossil/error.h
       t@@ -0,0 +1,33 @@
       +extern char EBadAddr[];
       +extern char EBadDir[];
       +extern char EBadEntry[];
       +extern char EBadLabel[];
       +extern char EBadMeta[];
       +extern char EBadMode[];
       +extern char EBadOffset[];
       +extern char EBadPath[];
       +extern char EBadRoot[];
       +extern char EBadSuper[];
       +extern char EBlockTooBig[];
       +extern char ECacheFull[];
       +extern char EConvert[];
       +extern char EExists[];
       +extern char EFsFill[];
       +extern char EIO[];
       +extern char EInUse[];
       +extern char ELabelMismatch[];
       +extern char ENilBlock[];
       +extern char ENoDir[];
       +extern char ENoFile[];
       +extern char ENotDir[];
       +extern char ENotEmpty[];
       +extern char ENotFile[];
       +extern char EReadOnly[];
       +extern char ERemoved[];
       +extern char ENotArchived[];
       +extern char EResize[];
       +extern char ERoot[];
       +extern char ESnapOld[];
       +extern char ESnapRO[];
       +extern char ETooBig[];
       +extern char EVentiIO[];
 (DIR) diff --git a/src/cmd/fossil/file.c b/src/cmd/fossil/file.c
       t@@ -0,0 +1,1860 @@
       +#include "stdinc.h"
       +#include "9.h"                        /* for consPrint */
       +#include "dat.h"
       +#include "fns.h"
       +#include "error.h"
       +
       +/*
       + * locking order is upwards.  A thread can hold the lock for a File
       + * and then acquire the lock of its parent
       + */
       +
       +struct File {
       +        Fs        *fs;                /* immutable */
       +
       +        /* meta data for file: protected by the lk in the parent */
       +        int        ref;                /* holds this data structure up */
       +
       +        int        partial;        /* file was never really open */
       +        int        removed;        /* file has been removed */
       +        int        dirty;        /* dir is dirty with respect to meta data in block */
       +        u32int        boff;        /* block offset within msource for this file's meta data */
       +
       +        DirEntry dir;        /* meta data for this file, including component name */
       +
       +        File        *up;                /* parent file (directory) */
       +        File        *next;                /* sibling */
       +
       +        /* data for file */
       +        VtLock        *lk;                /* lock for the following */
       +        Source        *source;
       +        Source        *msource;        /* for directories: meta data for children */
       +        File        *down;                /* children */
       +
       +        int        mode;
       +        int        issnapshot;
       +};
       +
       +static int fileMetaFlush2(File*, char*);
       +static u32int fileMetaAlloc(File*, DirEntry*, u32int);
       +static int fileRLock(File*);
       +static void fileRUnlock(File*);
       +static int fileLock(File*);
       +static void fileUnlock(File*);
       +static void fileMetaLock(File*);
       +static void fileMetaUnlock(File*);
       +static void fileRAccess(File*);
       +static void fileWAccess(File*, char*);
       +
       +static File *
       +fileAlloc(Fs *fs)
       +{
       +        File *f;
       +
       +        f = vtMemAllocZ(sizeof(File));
       +        f->lk = vtLockAlloc();
       +        f->ref = 1;
       +        f->fs = fs;
       +        f->boff = NilBlock;
       +        f->mode = fs->mode;
       +        return f;
       +}
       +
       +static void
       +fileFree(File *f)
       +{
       +        sourceClose(f->source);
       +        vtLockFree(f->lk);
       +        sourceClose(f->msource);
       +        deCleanup(&f->dir);
       +
       +        memset(f, ~0, sizeof(File));
       +        vtMemFree(f);
       +}
       +
       +/*
       + * the file is locked already
       + * f->msource is unlocked
       + */
       +static File *
       +dirLookup(File *f, char *elem)
       +{
       +        int i;
       +        MetaBlock mb;
       +        MetaEntry me;
       +        Block *b;
       +        Source *meta;
       +        File *ff;
       +        u32int bo, nb;
       +
       +        meta = f->msource;
       +        b = nil;
       +        if(!sourceLock(meta, -1))
       +                return nil;
       +        nb = (sourceGetSize(meta)+meta->dsize-1)/meta->dsize;
       +        for(bo=0; bo<nb; bo++){
       +                b = sourceBlock(meta, bo, OReadOnly);
       +                if(b == nil)
       +                        goto Err;
       +                if(!mbUnpack(&mb, b->data, meta->dsize))
       +                        goto Err;
       +                if(mbSearch(&mb, elem, &i, &me)){
       +                        ff = fileAlloc(f->fs);
       +                        if(!deUnpack(&ff->dir, &me)){
       +                                fileFree(ff);
       +                                goto Err;
       +                        }
       +                        sourceUnlock(meta);
       +                        blockPut(b);
       +                        ff->boff = bo;
       +                        ff->mode = f->mode;
       +                        ff->issnapshot = f->issnapshot;
       +                        return ff;
       +                }
       +
       +                blockPut(b);
       +                b = nil;
       +        }
       +        vtSetError(ENoFile);
       +        /* fall through */
       +Err:
       +        sourceUnlock(meta);
       +        blockPut(b);
       +        return nil;
       +}
       +
       +File *
       +fileRoot(Source *r)
       +{
       +        Block *b;
       +        Source *r0, *r1, *r2;
       +        MetaBlock mb;
       +        MetaEntry me;
       +        File *root, *mr;
       +        Fs *fs;
       +
       +        b = nil;
       +        root = nil;
       +        mr = nil;
       +        r1 = nil;
       +        r2 = nil;
       +
       +        fs = r->fs;
       +        if(!sourceLock(r, -1))
       +                return nil;
       +        r0 = sourceOpen(r, 0, fs->mode, 0);
       +        if(r0 == nil)
       +                goto Err;
       +        r1 = sourceOpen(r, 1, fs->mode, 0);
       +        if(r1 == nil)
       +                goto Err;
       +        r2 = sourceOpen(r, 2, fs->mode, 0);
       +        if(r2 == nil)
       +                goto Err;
       +
       +        mr = fileAlloc(fs);
       +        mr->msource = r2;
       +        r2 = nil;
       +
       +        root = fileAlloc(fs);
       +        root->boff = 0;
       +        root->up = mr;
       +        root->source = r0;
       +        r0->file = root;                        /* point back to source */
       +        r0 = nil;
       +        root->msource = r1;
       +        r1 = nil;
       +
       +        mr->down = root;
       +
       +        if(!sourceLock(mr->msource, -1))
       +                goto Err;
       +        b = sourceBlock(mr->msource, 0, OReadOnly);
       +        sourceUnlock(mr->msource);
       +        if(b == nil)
       +                goto Err;
       +
       +        if(!mbUnpack(&mb, b->data, mr->msource->dsize))
       +                goto Err;
       +
       +        meUnpack(&me, &mb, 0);
       +        if(!deUnpack(&root->dir, &me))
       +                goto Err;
       +        blockPut(b);
       +        sourceUnlock(r);
       +        fileRAccess(root);
       +
       +        return root;
       +Err:
       +        blockPut(b);
       +        if(r0)
       +                sourceClose(r0);
       +        if(r1)
       +                sourceClose(r1);
       +        if(r2)
       +                sourceClose(r2);
       +        if(mr)
       +                fileFree(mr);
       +        if(root)
       +                fileFree(root);
       +        sourceUnlock(r);
       +
       +        return nil;
       +}
       +
       +static Source *
       +fileOpenSource(File *f, u32int offset, u32int gen, int dir, uint mode,
       +        int issnapshot)
       +{
       +        char *rname, *fname;
       +        Source *r;
       +
       +        if(!sourceLock(f->source, mode))
       +                return nil;
       +        r = sourceOpen(f->source, offset, mode, issnapshot);
       +        sourceUnlock(f->source);
       +        if(r == nil)
       +                return nil;
       +        if(r->gen != gen){
       +                vtSetError(ERemoved);
       +                goto Err;
       +        }
       +        if(r->dir != dir && r->mode != -1){
       +                /* this hasn't been as useful as we hoped it would be. */
       +                rname = sourceName(r);
       +                fname = fileName(f);
       +                consPrint("%s: source %s for file %s: fileOpenSource: "
       +                        "dir mismatch %d %d\n",
       +                        f->source->fs->name, rname, fname, r->dir, dir);
       +                free(rname);
       +                free(fname);
       +
       +                vtSetError(EBadMeta);
       +                goto Err;
       +        }
       +        return r;
       +Err:
       +        sourceClose(r);
       +        return nil;
       +}
       +
       +File *
       +_fileWalk(File *f, char *elem, int partial)
       +{
       +        File *ff;
       +
       +        fileRAccess(f);
       +
       +        if(elem[0] == 0){
       +                vtSetError(EBadPath);
       +                return nil;
       +        }
       +
       +        if(!fileIsDir(f)){
       +                vtSetError(ENotDir);
       +                return nil;
       +        }
       +
       +        if(strcmp(elem, ".") == 0){
       +                return fileIncRef(f);
       +        }
       +
       +        if(strcmp(elem, "..") == 0){
       +                if(fileIsRoot(f))
       +                        return fileIncRef(f);
       +                return fileIncRef(f->up);
       +        }
       +
       +        if(!fileLock(f))
       +                return nil;
       +
       +        for(ff = f->down; ff; ff=ff->next){
       +                if(strcmp(elem, ff->dir.elem) == 0 && !ff->removed){
       +                        ff->ref++;
       +                        goto Exit;
       +                }
       +        }
       +
       +        ff = dirLookup(f, elem);
       +        if(ff == nil)
       +                goto Err;
       +
       +        if(ff->dir.mode & ModeSnapshot){
       +                ff->mode = OReadOnly;
       +                ff->issnapshot = 1;
       +        }
       +
       +        if(partial){
       +                /*
       +                 * Do nothing.  We're opening this file only so we can clri it.
       +                 * Usually the sources can't be opened, hence we won't even bother.
       +                 * Be VERY careful with the returned file.  If you hand it to a routine
       +                 * expecting ff->source and/or ff->msource to be non-nil, we're
       +                 * likely to dereference nil.  FileClri should be the only routine
       +                 * setting partial.
       +                 */
       +                ff->partial = 1;
       +        }else if(ff->dir.mode & ModeDir){
       +                ff->source = fileOpenSource(f, ff->dir.entry, ff->dir.gen,
       +                        1, ff->mode, ff->issnapshot);
       +                ff->msource = fileOpenSource(f, ff->dir.mentry, ff->dir.mgen,
       +                        0, ff->mode, ff->issnapshot);
       +                if(ff->source == nil || ff->msource == nil)
       +                        goto Err;
       +        }else{
       +                ff->source = fileOpenSource(f, ff->dir.entry, ff->dir.gen,
       +                        0, ff->mode, ff->issnapshot);
       +                if(ff->source == nil)
       +                        goto Err;
       +        }
       +
       +        /* link in and up parent ref count */
       +        if (ff->source)
       +                ff->source->file = ff;                /* point back */
       +        ff->next = f->down;
       +        f->down = ff;
       +        ff->up = f;
       +        fileIncRef(f);
       +Exit:
       +        fileUnlock(f);
       +        return ff;
       +Err:
       +        fileUnlock(f);
       +        if(ff != nil)
       +                fileDecRef(ff);
       +        return nil;
       +}
       +
       +File *
       +fileWalk(File *f, char *elem)
       +{
       +        return _fileWalk(f, elem, 0);
       +}
       +
       +File *
       +_fileOpen(Fs *fs, char *path, int partial)
       +{
       +        File *f, *ff;
       +        char *p, elem[VtMaxStringSize], *opath;
       +        int n;
       +
       +        f = fs->file;
       +        fileIncRef(f);
       +        opath = path;
       +        while(*path != 0){
       +                for(p = path; *p && *p != '/'; p++)
       +                        ;
       +                n = p - path;
       +                if(n > 0){
       +                        if(n > VtMaxStringSize){
       +                                vtSetError("%s: element too long", EBadPath);
       +                                goto Err;
       +                        }
       +                        memmove(elem, path, n);
       +                        elem[n] = 0;
       +                        ff = _fileWalk(f, elem, partial && *p=='\0');
       +                        if(ff == nil){
       +                                vtSetError("%.*s: %R", utfnlen(opath, p-opath),
       +                                        opath);
       +                                goto Err;
       +                        }
       +                        fileDecRef(f);
       +                        f = ff;
       +                }
       +                if(*p == '/')
       +                        p++;
       +                path = p;
       +        }
       +        return f;
       +Err:
       +        fileDecRef(f);
       +        return nil;
       +}
       +
       +File*
       +fileOpen(Fs *fs, char *path)
       +{
       +        return _fileOpen(fs, path, 0);
       +}
       +
       +static void
       +fileSetTmp(File *f, int istmp)
       +{
       +        int i;
       +        Entry e;
       +        Source *r;
       +
       +        for(i=0; i<2; i++){
       +                if(i==0)
       +                        r = f->source;
       +                else
       +                        r = f->msource;
       +                if(r == nil)
       +                        continue;
       +                if(!sourceGetEntry(r, &e)){
       +                        fprint(2, "sourceGetEntry failed (cannot happen): %r\n");
       +                        continue;
       +                }
       +                if(istmp)
       +                        e.flags |= VtEntryNoArchive;
       +                else
       +                        e.flags &= ~VtEntryNoArchive;
       +                if(!sourceSetEntry(r, &e)){
       +                        fprint(2, "sourceSetEntry failed (cannot happen): %r\n");
       +                        continue;
       +                }
       +        }
       +}
       +
       +File *
       +fileCreate(File *f, char *elem, ulong mode, char *uid)
       +{
       +        File *ff;
       +        DirEntry *dir;
       +        Source *pr, *r, *mr;
       +        int isdir;
       +
       +        if(!fileLock(f))
       +                return nil;
       +
       +        r = nil;
       +        mr = nil;
       +        for(ff = f->down; ff; ff=ff->next){
       +                if(strcmp(elem, ff->dir.elem) == 0 && !ff->removed){
       +                        ff = nil;
       +                        vtSetError(EExists);
       +                        goto Err1;
       +                }
       +        }
       +
       +        ff = dirLookup(f, elem);
       +        if(ff != nil){
       +                vtSetError(EExists);
       +                goto Err1;
       +        }
       +
       +        pr = f->source;
       +        if(pr->mode != OReadWrite){
       +                vtSetError(EReadOnly);
       +                goto Err1;
       +        }
       +
       +        if(!sourceLock2(f->source, f->msource, -1))
       +                goto Err1;
       +
       +        ff = fileAlloc(f->fs);
       +        isdir = mode & ModeDir;
       +
       +        r = sourceCreate(pr, pr->dsize, isdir, 0);
       +        if(r == nil)
       +                goto Err;
       +        if(isdir){
       +                mr = sourceCreate(pr, pr->dsize, 0, r->offset);
       +                if(mr == nil)
       +                        goto Err;
       +        }
       +
       +        dir = &ff->dir;
       +        dir->elem = vtStrDup(elem);
       +        dir->entry = r->offset;
       +        dir->gen = r->gen;
       +        if(isdir){
       +                dir->mentry = mr->offset;
       +                dir->mgen = mr->gen;
       +        }
       +        dir->size = 0;
       +        if(!fsNextQid(f->fs, &dir->qid))
       +                goto Err;
       +        dir->uid = vtStrDup(uid);
       +        dir->gid = vtStrDup(f->dir.gid);
       +        dir->mid = vtStrDup(uid);
       +        dir->mtime = time(0L);
       +        dir->mcount = 0;
       +        dir->ctime = dir->mtime;
       +        dir->atime = dir->mtime;
       +        dir->mode = mode;
       +
       +        ff->boff = fileMetaAlloc(f, dir, 0);
       +        if(ff->boff == NilBlock)
       +                goto Err;
       +
       +        sourceUnlock(f->source);
       +        sourceUnlock(f->msource);
       +
       +        ff->source = r;
       +        r->file = ff;                        /* point back */
       +        ff->msource = mr;
       +
       +        if(mode&ModeTemporary){
       +                if(!sourceLock2(r, mr, -1))
       +                        goto Err1;
       +                fileSetTmp(ff, 1);
       +                sourceUnlock(r);
       +                if(mr)
       +                        sourceUnlock(mr);
       +        }
       +
       +        /* committed */
       +
       +        /* link in and up parent ref count */
       +        ff->next = f->down;
       +        f->down = ff;
       +        ff->up = f;
       +        fileIncRef(f);
       +
       +        fileWAccess(f, uid);
       +
       +        fileUnlock(f);
       +        return ff;
       +
       +Err:
       +        sourceUnlock(f->source);
       +        sourceUnlock(f->msource);
       +Err1:
       +        if(r){
       +                sourceLock(r, -1);
       +                sourceRemove(r);
       +        }
       +        if(mr){
       +                sourceLock(mr, -1);
       +                sourceRemove(mr);
       +        }
       +        if(ff)
       +                fileDecRef(ff);
       +        fileUnlock(f);
       +        return 0;
       +}
       +
       +int
       +fileRead(File *f, void *buf, int cnt, vlong offset)
       +{
       +        Source *s;
       +        uvlong size;
       +        u32int bn;
       +        int off, dsize, n, nn;
       +        Block *b;
       +        uchar *p;
       +
       +if(0)fprint(2, "fileRead: %s %d, %lld\n", f->dir.elem, cnt, offset);
       +
       +        if(!fileRLock(f))
       +                return -1;
       +
       +        if(offset < 0){
       +                vtSetError(EBadOffset);
       +                goto Err1;
       +        }
       +
       +        fileRAccess(f);
       +
       +        if(!sourceLock(f->source, OReadOnly))
       +                goto Err1;
       +
       +        s = f->source;
       +        dsize = s->dsize;
       +        size = sourceGetSize(s);
       +
       +        if(offset >= size)
       +                offset = size;
       +
       +        if(cnt > size-offset)
       +                cnt = size-offset;
       +        bn = offset/dsize;
       +        off = offset%dsize;
       +        p = buf;
       +        while(cnt > 0){
       +                b = sourceBlock(s, bn, OReadOnly);
       +                if(b == nil)
       +                        goto Err;
       +                n = cnt;
       +                if(n > dsize-off)
       +                        n = dsize-off;
       +                nn = dsize-off;
       +                if(nn > n)
       +                        nn = n;
       +                memmove(p, b->data+off, nn);
       +                memset(p+nn, 0, nn-n);
       +                off = 0;
       +                bn++;
       +                cnt -= n;
       +                p += n;
       +                blockPut(b);
       +        }
       +        sourceUnlock(s);
       +        fileRUnlock(f);
       +        return p-(uchar*)buf;
       +
       +Err:
       +        sourceUnlock(s);
       +Err1:
       +        fileRUnlock(f);
       +        return -1;
       +}
       +
       +/*
       + * Changes the file block bn to be the given block score.
       + * Very sneaky.  Only used by flfmt.
       + */
       +int
       +fileMapBlock(File *f, ulong bn, uchar score[VtScoreSize], ulong tag)
       +{
       +        Block *b;
       +        Entry e;
       +        Source *s;
       +
       +        if(!fileLock(f))
       +                return 0;
       +
       +        s = nil;
       +        if(f->dir.mode & ModeDir){
       +                vtSetError(ENotFile);
       +                goto Err;
       +        }
       +
       +        if(f->source->mode != OReadWrite){
       +                vtSetError(EReadOnly);
       +                goto Err;
       +        }
       +
       +        if(!sourceLock(f->source, -1))
       +                goto Err;
       +
       +        s = f->source;
       +        b = _sourceBlock(s, bn, OReadWrite, 1, tag);
       +        if(b == nil)
       +                goto Err;
       +
       +        if(!sourceGetEntry(s, &e))
       +                goto Err;
       +        if(b->l.type == BtDir){
       +                memmove(e.score, score, VtScoreSize);
       +                assert(e.tag == tag || e.tag == 0);
       +                e.tag = tag;
       +                e.flags |= VtEntryLocal;
       +                entryPack(&e, b->data, f->source->offset % f->source->epb);
       +        }else
       +                memmove(b->data + (bn%(e.psize/VtScoreSize))*VtScoreSize, score, VtScoreSize);
       +        blockDirty(b);
       +        blockPut(b);
       +        sourceUnlock(s);
       +        fileUnlock(f);
       +        return 1;
       +
       +Err:
       +        if(s)
       +                sourceUnlock(s);
       +        fileUnlock(f);
       +        return 0;
       +}
       +
       +int
       +fileSetSize(File *f, uvlong size)
       +{
       +        int r;
       +
       +        if(!fileLock(f))
       +                return 0;
       +        r = 0;
       +        if(f->dir.mode & ModeDir){
       +                vtSetError(ENotFile);
       +                goto Err;
       +        }
       +        if(f->source->mode != OReadWrite){
       +                vtSetError(EReadOnly);
       +                goto Err;
       +        }
       +        if(!sourceLock(f->source, -1))
       +                goto Err;
       +        r = sourceSetSize(f->source, size);
       +        sourceUnlock(f->source);
       +Err:
       +        fileUnlock(f);
       +        return r;
       +}
       +
       +int
       +fileWrite(File *f, void *buf, int cnt, vlong offset, char *uid)
       +{
       +        Source *s;
       +        ulong bn;
       +        int off, dsize, n;
       +        Block *b;
       +        uchar *p;
       +        vlong eof;
       +
       +if(0)fprint(2, "fileWrite: %s %d, %lld\n", f->dir.elem, cnt, offset);
       +
       +        if(!fileLock(f))
       +                return -1;
       +
       +        s = nil;
       +        if(f->dir.mode & ModeDir){
       +                vtSetError(ENotFile);
       +                goto Err;
       +        }
       +
       +        if(f->source->mode != OReadWrite){
       +                vtSetError(EReadOnly);
       +                goto Err;
       +        }
       +        if(offset < 0){
       +                vtSetError(EBadOffset);
       +                goto Err;
       +        }
       +
       +        fileWAccess(f, uid);
       +
       +        if(!sourceLock(f->source, -1))
       +                goto Err;
       +        s = f->source;
       +        dsize = s->dsize;
       +
       +        eof = sourceGetSize(s);
       +        if(f->dir.mode & ModeAppend)
       +                offset = eof;
       +        bn = offset/dsize;
       +        off = offset%dsize;
       +        p = buf;
       +        while(cnt > 0){
       +                n = cnt;
       +                if(n > dsize-off)
       +                        n = dsize-off;
       +                b = sourceBlock(s, bn, n<dsize?OReadWrite:OOverWrite);
       +                if(b == nil){
       +                        if(offset > eof)
       +                                sourceSetSize(s, offset);
       +                        goto Err;
       +                }
       +                memmove(b->data+off, p, n);
       +                off = 0;
       +                cnt -= n;
       +                p += n;
       +                offset += n;
       +                bn++;
       +                blockDirty(b);
       +                blockPut(b);
       +        }
       +        if(offset > eof && !sourceSetSize(s, offset))
       +                goto Err;
       +        sourceUnlock(s);
       +        fileUnlock(f);
       +        return p-(uchar*)buf;
       +Err:
       +        if(s)
       +                sourceUnlock(s);
       +        fileUnlock(f);
       +        return -1;
       +}
       +
       +int
       +fileGetDir(File *f, DirEntry *dir)
       +{
       +        if(!fileRLock(f))
       +                return 0;
       +
       +        fileMetaLock(f);
       +        deCopy(dir, &f->dir);
       +        fileMetaUnlock(f);
       +
       +        if(!fileIsDir(f)){
       +                if(!sourceLock(f->source, OReadOnly)){
       +                        fileRUnlock(f);
       +                        return 0;
       +                }
       +                dir->size = sourceGetSize(f->source);
       +                sourceUnlock(f->source);
       +        }
       +        fileRUnlock(f);
       +
       +        return 1;
       +}
       +
       +int
       +fileTruncate(File *f, char *uid)
       +{
       +        if(fileIsDir(f)){
       +                vtSetError(ENotFile);
       +                return 0;
       +        }
       +
       +        if(!fileLock(f))
       +                return 0;
       +
       +        if(f->source->mode != OReadWrite){
       +                vtSetError(EReadOnly);
       +                fileUnlock(f);
       +                return 0;
       +        }
       +        if(!sourceLock(f->source, -1)){
       +                fileUnlock(f);
       +                return 0;
       +        }
       +        if(!sourceTruncate(f->source)){
       +                sourceUnlock(f->source);
       +                fileUnlock(f);
       +                return 0;
       +        }
       +        sourceUnlock(f->source);
       +        fileUnlock(f);
       +
       +        fileWAccess(f, uid);
       +
       +        return 1;
       +}
       +
       +int
       +fileSetDir(File *f, DirEntry *dir, char *uid)
       +{
       +        File *ff;
       +        char *oelem;
       +        u32int mask;
       +        u64int size;
       +
       +        /* can not set permissions for the root */
       +        if(fileIsRoot(f)){
       +                vtSetError(ERoot);
       +                return 0;
       +        }
       +
       +        if(!fileLock(f))
       +                return 0;
       +
       +        if(f->source->mode != OReadWrite){
       +                vtSetError(EReadOnly);
       +                fileUnlock(f);
       +                return 0;
       +        }
       +
       +        fileMetaLock(f);
       +
       +        /* check new name does not already exist */
       +        if(strcmp(f->dir.elem, dir->elem) != 0){
       +                for(ff = f->up->down; ff; ff=ff->next){
       +                        if(strcmp(dir->elem, ff->dir.elem) == 0 && !ff->removed){
       +                                vtSetError(EExists);
       +                                goto Err;
       +                        }
       +                }
       +
       +                ff = dirLookup(f->up, dir->elem);
       +                if(ff != nil){
       +                        fileDecRef(ff);
       +                        vtSetError(EExists);
       +                        goto Err;
       +                }
       +        }
       +
       +        if(!sourceLock2(f->source, f->msource, -1))
       +                goto Err;
       +        if(!fileIsDir(f)){
       +                size = sourceGetSize(f->source);
       +                if(size != dir->size){
       +                        if(!sourceSetSize(f->source, dir->size)){
       +                                sourceUnlock(f->source);
       +                                if(f->msource)
       +                                        sourceUnlock(f->msource);
       +                                goto Err;
       +                        }
       +                        /* commited to changing it now */
       +                }
       +        }
       +        /* commited to changing it now */
       +        if((f->dir.mode&ModeTemporary) != (dir->mode&ModeTemporary))
       +                fileSetTmp(f, dir->mode&ModeTemporary);
       +        sourceUnlock(f->source);
       +        if(f->msource)
       +                sourceUnlock(f->msource);
       +
       +        oelem = nil;
       +        if(strcmp(f->dir.elem, dir->elem) != 0){
       +                oelem = f->dir.elem;
       +                f->dir.elem = vtStrDup(dir->elem);
       +        }
       +
       +        if(strcmp(f->dir.uid, dir->uid) != 0){
       +                vtMemFree(f->dir.uid);
       +                f->dir.uid = vtStrDup(dir->uid);
       +        }
       +
       +        if(strcmp(f->dir.gid, dir->gid) != 0){
       +                vtMemFree(f->dir.gid);
       +                f->dir.gid = vtStrDup(dir->gid);
       +        }
       +
       +        f->dir.mtime = dir->mtime;
       +        f->dir.atime = dir->atime;
       +
       +//fprint(2, "mode %x %x ", f->dir.mode, dir->mode);
       +        mask = ~(ModeDir|ModeSnapshot);
       +        f->dir.mode &= ~mask;
       +        f->dir.mode |= mask & dir->mode;
       +        f->dirty = 1;
       +//fprint(2, "->%x\n", f->dir.mode);
       +
       +        fileMetaFlush2(f, oelem);
       +        vtMemFree(oelem);
       +
       +        fileMetaUnlock(f);
       +        fileUnlock(f);
       +
       +        fileWAccess(f->up, uid);
       +
       +        return 1;
       +Err:
       +        fileMetaUnlock(f);
       +        fileUnlock(f);
       +        return 0;
       +}
       +
       +int
       +fileSetQidSpace(File *f, u64int offset, u64int max)
       +{
       +        int ret;
       +
       +        if(!fileLock(f))
       +                return 0;
       +        fileMetaLock(f);
       +        f->dir.qidSpace = 1;
       +        f->dir.qidOffset = offset;
       +        f->dir.qidMax = max;
       +        ret = fileMetaFlush2(f, nil)>=0;
       +        fileMetaUnlock(f);
       +        fileUnlock(f);
       +        return ret;
       +}
       +
       +
       +uvlong
       +fileGetId(File *f)
       +{
       +        /* immutable */
       +        return f->dir.qid;
       +}
       +
       +ulong
       +fileGetMcount(File *f)
       +{
       +        ulong mcount;
       +
       +        fileMetaLock(f);
       +        mcount = f->dir.mcount;
       +        fileMetaUnlock(f);
       +        return mcount;
       +}
       +
       +ulong
       +fileGetMode(File *f)
       +{
       +        ulong mode;
       +
       +        fileMetaLock(f);
       +        mode = f->dir.mode;
       +        fileMetaUnlock(f);
       +        return mode;
       +}
       +
       +int
       +fileIsDir(File *f)
       +{
       +        /* immutable */
       +        return (f->dir.mode & ModeDir) != 0;
       +}
       +
       +int
       +fileIsAppend(File *f)
       +{
       +        return (f->dir.mode & ModeAppend) != 0;
       +}
       +
       +int
       +fileIsExclusive(File *f)
       +{
       +        return (f->dir.mode & ModeExclusive) != 0;
       +}
       +
       +int
       +fileIsTemporary(File *f)
       +{
       +        return (f->dir.mode & ModeTemporary) != 0;
       +}
       +
       +int
       +fileIsRoot(File *f)
       +{
       +        return f == f->fs->file;
       +}
       +
       +int
       +fileIsRoFs(File *f)
       +{
       +        return f->fs->mode == OReadOnly;
       +}
       +
       +int
       +fileGetSize(File *f, uvlong *size)
       +{
       +        if(!fileRLock(f))
       +                return 0;
       +        if(!sourceLock(f->source, OReadOnly)){
       +                fileRUnlock(f);
       +                return 0;
       +        }
       +        *size = sourceGetSize(f->source);
       +        sourceUnlock(f->source);
       +        fileRUnlock(f);
       +
       +        return 1;
       +}
       +
       +int
       +fileMetaFlush(File *f, int rec)
       +{
       +        File **kids, *p;
       +        int nkids;
       +        int i, rv;
       +
       +        fileMetaLock(f);
       +        rv = fileMetaFlush2(f, nil);
       +        fileMetaUnlock(f);
       +
       +        if(!rec || !fileIsDir(f))
       +                return rv;
       +
       +        if(!fileLock(f))
       +                return rv;
       +        nkids = 0;
       +        for(p=f->down; p; p=p->next)
       +                nkids++;
       +        kids = vtMemAlloc(nkids*sizeof(File*));
       +        i = 0;
       +        for(p=f->down; p; p=p->next){
       +                kids[i++] = p;
       +                p->ref++;
       +        }
       +        fileUnlock(f);
       +
       +        for(i=0; i<nkids; i++){
       +                rv |= fileMetaFlush(kids[i], 1);
       +                fileDecRef(kids[i]);
       +        }
       +        vtMemFree(kids);
       +        return rv;
       +}
       +
       +/* assumes metaLock is held */
       +static int
       +fileMetaFlush2(File *f, char *oelem)
       +{
       +        File *fp;
       +        Block *b, *bb;
       +        MetaBlock mb;
       +        MetaEntry me, me2;
       +        int i, n;
       +        u32int boff;
       +
       +        if(!f->dirty)
       +                return 0;
       +
       +        if(oelem == nil)
       +                oelem = f->dir.elem;
       +
       +//print("fileMetaFlush %s->%s\n", oelem, f->dir.elem);
       +
       +        fp = f->up;
       +
       +        if(!sourceLock(fp->msource, -1))
       +                return -1;
       +        /* can happen if source is clri'ed out from under us */
       +        if(f->boff == NilBlock)
       +                goto Err1;
       +        b = sourceBlock(fp->msource, f->boff, OReadWrite);
       +        if(b == nil)
       +                goto Err1;
       +
       +        if(!mbUnpack(&mb, b->data, fp->msource->dsize))
       +                goto Err;
       +        if(!mbSearch(&mb, oelem, &i, &me))
       +                goto Err;
       +
       +        n = deSize(&f->dir);
       +if(0)fprint(2, "old size %d new size %d\n", me.size, n);
       +
       +        if(mbResize(&mb, &me, n)){
       +                /* fits in the block */
       +                mbDelete(&mb, i);
       +                if(strcmp(f->dir.elem, oelem) != 0)
       +                        mbSearch(&mb, f->dir.elem, &i, &me2);
       +                dePack(&f->dir, &me);
       +                mbInsert(&mb, i, &me);
       +                mbPack(&mb);
       +                blockDirty(b);
       +                blockPut(b);
       +                sourceUnlock(fp->msource);
       +                f->dirty = 0;
       +
       +                return 1;
       +        }
       +
       +        /*
       +         * moving entry to another block
       +         * it is feasible for the fs to crash leaving two copies
       +         * of the directory entry.  This is just too much work to
       +         * fix.  Given that entries are only allocated in a block that
       +         * is less than PercentageFull, most modifications of meta data
       +         * will fit within the block.  i.e. this code should almost
       +         * never be executed.
       +         */
       +        boff = fileMetaAlloc(fp, &f->dir, f->boff+1);
       +        if(boff == NilBlock){
       +                /* mbResize might have modified block */
       +                mbPack(&mb);
       +                blockDirty(b);
       +                goto Err;
       +        }
       +fprint(2, "fileMetaFlush moving entry from %ud -> %ud\n", f->boff, boff);
       +        f->boff = boff;
       +
       +        /* make sure deletion goes to disk after new entry */
       +        bb = sourceBlock(fp->msource, f->boff, OReadWrite);
       +        mbDelete(&mb, i);
       +        mbPack(&mb);
       +        blockDependency(b, bb, -1, nil, nil);
       +        blockPut(bb);
       +        blockDirty(b);
       +        blockPut(b);
       +        sourceUnlock(fp->msource);
       +
       +        f->dirty = 0;
       +
       +        return 1;
       +
       +Err:
       +        blockPut(b);
       +Err1:
       +        sourceUnlock(fp->msource);
       +        return -1;
       +}
       +
       +static int
       +fileMetaRemove(File *f, char *uid)
       +{
       +        Block *b;
       +        MetaBlock mb;
       +        MetaEntry me;
       +        int i;
       +        File *up;
       +
       +        up = f->up;
       +
       +        fileWAccess(up, uid);
       +
       +        fileMetaLock(f);
       +
       +        sourceLock(up->msource, OReadWrite);
       +        b = sourceBlock(up->msource, f->boff, OReadWrite);
       +        if(b == nil)
       +                goto Err;
       +
       +        if(!mbUnpack(&mb, b->data, up->msource->dsize))
       +{
       +fprint(2, "U\n");
       +                goto Err;
       +}
       +        if(!mbSearch(&mb, f->dir.elem, &i, &me))
       +{
       +fprint(2, "S\n");
       +                goto Err;
       +}
       +        mbDelete(&mb, i);
       +        mbPack(&mb);
       +        sourceUnlock(up->msource);
       +
       +        blockDirty(b);
       +        blockPut(b);
       +
       +        f->removed = 1;
       +        f->boff = NilBlock;
       +        f->dirty = 0;
       +
       +        fileMetaUnlock(f);
       +        return 1;
       +
       +Err:
       +        sourceUnlock(up->msource);
       +        blockPut(b);
       +        fileMetaUnlock(f);
       +        return 0;
       +}
       +
       +/* assume file is locked, assume f->msource is locked */
       +static int
       +fileCheckEmpty(File *f)
       +{
       +        u32int i, n;
       +        Block *b;
       +        MetaBlock mb;
       +        Source *r;
       +
       +        r = f->msource;
       +        n = (sourceGetSize(r)+r->dsize-1)/r->dsize;
       +        for(i=0; i<n; i++){
       +                b = sourceBlock(r, i, OReadOnly);
       +                if(b == nil)
       +                        goto Err;
       +                if(!mbUnpack(&mb, b->data, r->dsize))
       +                        goto Err;
       +                if(mb.nindex > 0){
       +                        vtSetError(ENotEmpty);
       +                        goto Err;
       +                }
       +                blockPut(b);
       +        }
       +        return 1;
       +Err:
       +        blockPut(b);
       +        return 0;
       +}
       +
       +int
       +fileRemove(File *f, char *uid)
       +{
       +        File *ff;
       +
       +        /* can not remove the root */
       +        if(fileIsRoot(f)){
       +                vtSetError(ERoot);
       +                return 0;
       +        }
       +
       +        if(!fileLock(f))
       +                return 0;
       +
       +        if(f->source->mode != OReadWrite){
       +                vtSetError(EReadOnly);
       +                goto Err1;
       +        }
       +        if(!sourceLock2(f->source, f->msource, -1))
       +                goto Err1;
       +        if(fileIsDir(f) && !fileCheckEmpty(f))
       +                goto Err;
       +
       +        for(ff=f->down; ff; ff=ff->next)
       +                assert(ff->removed);
       +
       +        sourceRemove(f->source);
       +        f->source->file = nil;                /* erase back pointer */
       +        f->source = nil;
       +        if(f->msource){
       +                sourceRemove(f->msource);
       +                f->msource = nil;
       +        }
       +
       +        fileUnlock(f);
       +
       +        if(!fileMetaRemove(f, uid))
       +                return 0;
       +
       +        return 1;
       +
       +Err:
       +        sourceUnlock(f->source);
       +        if(f->msource)
       +                sourceUnlock(f->msource);
       +Err1:
       +        fileUnlock(f);
       +        return 0;
       +}
       +
       +static int
       +clri(File *f, char *uid)
       +{
       +        int r;
       +
       +        if(f == nil)
       +                return 0;
       +        if(f->up->source->mode != OReadWrite){
       +                vtSetError(EReadOnly);
       +                fileDecRef(f);
       +                return 0;
       +        }
       +        r = fileMetaRemove(f, uid);
       +        fileDecRef(f);
       +        return r;
       +}
       +
       +int
       +fileClriPath(Fs *fs, char *path, char *uid)
       +{
       +        return clri(_fileOpen(fs, path, 1), uid);
       +}
       +
       +int
       +fileClri(File *dir, char *elem, char *uid)
       +{
       +        return clri(_fileWalk(dir, elem, 1), uid);
       +}
       +
       +File *
       +fileIncRef(File *vf)
       +{
       +        fileMetaLock(vf);
       +        assert(vf->ref > 0);
       +        vf->ref++;
       +        fileMetaUnlock(vf);
       +        return vf;
       +}
       +
       +int
       +fileDecRef(File *f)
       +{
       +        File *p, *q, **qq;
       +
       +        if(f->up == nil){
       +                /* never linked in */
       +                assert(f->ref == 1);
       +                fileFree(f);
       +                return 1;
       +        }
       +
       +        fileMetaLock(f);
       +        f->ref--;
       +        if(f->ref > 0){
       +                fileMetaUnlock(f);
       +                return 0;
       +        }
       +        assert(f->ref == 0);
       +        assert(f->down == nil);
       +
       +        fileMetaFlush2(f, nil);
       +
       +        p = f->up;
       +        qq = &p->down;
       +        for(q = *qq; q; q = *qq){
       +                if(q == f)
       +                        break;
       +                qq = &q->next;
       +        }
       +        assert(q != nil);
       +        *qq = f->next;
       +
       +        fileMetaUnlock(f);
       +        fileFree(f);
       +
       +        fileDecRef(p);
       +        return 1;
       +}
       +
       +File *
       +fileGetParent(File *f)
       +{
       +        if(fileIsRoot(f))
       +                return fileIncRef(f);
       +        return fileIncRef(f->up);
       +}
       +
       +DirEntryEnum *
       +deeOpen(File *f)
       +{
       +        DirEntryEnum *dee;
       +        File *p;
       +
       +        if(!fileIsDir(f)){
       +                vtSetError(ENotDir);
       +                fileDecRef(f);
       +                return nil;
       +        }
       +
       +        /* flush out meta data */
       +        if(!fileLock(f))
       +                return nil;
       +        for(p=f->down; p; p=p->next)
       +                fileMetaFlush2(p, nil);
       +        fileUnlock(f);
       +
       +        dee = vtMemAllocZ(sizeof(DirEntryEnum));
       +        dee->file = fileIncRef(f);
       +
       +        return dee;
       +}
       +
       +static int
       +dirEntrySize(Source *s, ulong elem, ulong gen, uvlong *size)
       +{
       +        Block *b;
       +        ulong bn;
       +        Entry e;
       +        int epb;
       +
       +        epb = s->dsize/VtEntrySize;
       +        bn = elem/epb;
       +        elem -= bn*epb;
       +
       +        b = sourceBlock(s, bn, OReadOnly);
       +        if(b == nil)
       +                goto Err;
       +        if(!entryUnpack(&e, b->data, elem))
       +                goto Err;
       +
       +        /* hanging entries are returned as zero size */
       +        if(!(e.flags & VtEntryActive) || e.gen != gen)
       +                *size = 0;
       +        else
       +                *size = e.size;
       +        blockPut(b);
       +        return 1;
       +
       +Err:
       +        blockPut(b);
       +        return 0;
       +}
       +
       +static int
       +deeFill(DirEntryEnum *dee)
       +{
       +        int i, n;
       +        Source *meta, *source;
       +        MetaBlock mb;
       +        MetaEntry me;
       +        File *f;
       +        Block *b;
       +        DirEntry *de;
       +
       +        /* clean up first */
       +        for(i=dee->i; i<dee->n; i++)
       +                deCleanup(dee->buf+i);
       +        vtMemFree(dee->buf);
       +        dee->buf = nil;
       +        dee->i = 0;
       +        dee->n = 0;
       +
       +        f = dee->file;
       +
       +        source = f->source;
       +        meta = f->msource;
       +
       +        b = sourceBlock(meta, dee->boff, OReadOnly);
       +        if(b == nil)
       +                goto Err;
       +        if(!mbUnpack(&mb, b->data, meta->dsize))
       +                goto Err;
       +
       +        n = mb.nindex;
       +        dee->buf = vtMemAlloc(n * sizeof(DirEntry));
       +
       +        for(i=0; i<n; i++){
       +                de = dee->buf + i;
       +                meUnpack(&me, &mb, i);
       +                if(!deUnpack(de, &me))
       +                        goto Err;
       +                dee->n++;
       +                if(!(de->mode & ModeDir))
       +                if(!dirEntrySize(source, de->entry, de->gen, &de->size))
       +                        goto Err;
       +        }
       +        dee->boff++;
       +        blockPut(b);
       +        return 1;
       +Err:
       +        blockPut(b);
       +        return 0;
       +}
       +
       +int
       +deeRead(DirEntryEnum *dee, DirEntry *de)
       +{
       +        int ret, didread;
       +        File *f;
       +        u32int nb;
       +
       +        if(dee == nil){
       +                vtSetError("cannot happen in deeRead");
       +                return -1;
       +        }
       +
       +        f = dee->file;
       +        if(!fileRLock(f))
       +                return -1;
       +
       +        if(!sourceLock2(f->source, f->msource, OReadOnly)){
       +                fileRUnlock(f);
       +                return -1;
       +        }
       +
       +        nb = (sourceGetSize(f->msource)+f->msource->dsize-1)/f->msource->dsize;
       +
       +        didread = 0;
       +        while(dee->i >= dee->n){
       +                if(dee->boff >= nb){
       +                        ret = 0;
       +                        goto Return;
       +                }
       +                didread = 1;
       +                if(!deeFill(dee)){
       +                        ret = -1;
       +                        goto Return;
       +                }
       +        }
       +
       +        memmove(de, dee->buf + dee->i, sizeof(DirEntry));
       +        dee->i++;
       +        ret = 1;
       +
       +Return:
       +        sourceUnlock(f->source);
       +        sourceUnlock(f->msource);
       +        fileRUnlock(f);
       +
       +        if(didread)
       +                fileRAccess(f);
       +        return ret;
       +}
       +
       +void
       +deeClose(DirEntryEnum *dee)
       +{
       +        int i;
       +        if(dee == nil)
       +                return;
       +        for(i=dee->i; i<dee->n; i++)
       +                deCleanup(dee->buf+i);
       +        vtMemFree(dee->buf);
       +        fileDecRef(dee->file);
       +        vtMemFree(dee);
       +}
       +
       +/*
       + * caller must lock f->source and f->msource
       + * caller must NOT lock the source and msource
       + * referenced by dir.
       + */
       +static u32int
       +fileMetaAlloc(File *f, DirEntry *dir, u32int start)
       +{
       +        u32int nb, bo;
       +        Block *b, *bb;
       +        MetaBlock mb;
       +        int nn;
       +        uchar *p;
       +        int i, n, epb;
       +        MetaEntry me;
       +        Source *s, *ms;
       +
       +        s = f->source;
       +        ms = f->msource;
       +
       +        n = deSize(dir);
       +        nb = (sourceGetSize(ms)+ms->dsize-1)/ms->dsize;
       +        b = nil;
       +        if(start > nb)
       +                start = nb;
       +        for(bo=start; bo<nb; bo++){
       +                b = sourceBlock(ms, bo, OReadWrite);
       +                if(b == nil)
       +                        goto Err;
       +                if(!mbUnpack(&mb, b->data, ms->dsize))
       +                        goto Err;
       +                nn = (mb.maxsize*FullPercentage/100) - mb.size + mb.free;
       +                if(n <= nn && mb.nindex < mb.maxindex)
       +                        break;
       +                blockPut(b);
       +                b = nil;
       +        }
       +
       +        /* add block to meta file */
       +        if(b == nil){
       +                b = sourceBlock(ms, bo, OReadWrite);
       +                if(b == nil)
       +                        goto Err;
       +                sourceSetSize(ms, (nb+1)*ms->dsize);
       +                mbInit(&mb, b->data, ms->dsize, ms->dsize/BytesPerEntry);
       +        }
       +
       +        p = mbAlloc(&mb, n);
       +        if(p == nil){
       +                /* mbAlloc might have changed block */
       +                mbPack(&mb);
       +                blockDirty(b);
       +                vtSetError(EBadMeta);
       +                goto Err;
       +        }
       +
       +        mbSearch(&mb, dir->elem, &i, &me);
       +        assert(me.p == nil);
       +        me.p = p;
       +        me.size = n;
       +        dePack(dir, &me);
       +        mbInsert(&mb, i, &me);
       +        mbPack(&mb);
       +
       +        /* meta block depends on super block for qid ... */
       +        bb = cacheLocal(b->c, PartSuper, 0, OReadOnly);
       +        blockDependency(b, bb, -1, nil, nil);
       +        blockPut(bb);
       +
       +        /* ... and one or two dir entries */
       +        epb = s->dsize/VtEntrySize;
       +        bb = sourceBlock(s, dir->entry/epb, OReadOnly);
       +        blockDependency(b, bb, -1, nil, nil);
       +        blockPut(bb);
       +        if(dir->mode & ModeDir){
       +                bb = sourceBlock(s, dir->mentry/epb, OReadOnly);
       +                blockDependency(b, bb, -1, nil, nil);
       +                blockPut(bb);
       +        }
       +
       +        blockDirty(b);
       +        blockPut(b);
       +        return bo;
       +Err:
       +        blockPut(b);
       +        return NilBlock;
       +}
       +
       +static int
       +chkSource(File *f)
       +{
       +        if(f->partial)
       +                return 1;
       +
       +        if(f->source == nil || (f->dir.mode & ModeDir) && f->msource == nil){
       +                vtSetError(ERemoved);
       +                return 0;
       +        }
       +        return 1;
       +}
       +
       +static int
       +fileRLock(File *f)
       +{
       +        assert(!vtCanLock(f->fs->elk));
       +        vtRLock(f->lk);
       +        if(!chkSource(f)){
       +                fileRUnlock(f);
       +                return 0;
       +        }
       +        return 1;
       +}
       +
       +static void
       +fileRUnlock(File *f)
       +{
       +        vtRUnlock(f->lk);
       +}
       +
       +static int
       +fileLock(File *f)
       +{
       +        assert(!vtCanLock(f->fs->elk));
       +        vtLock(f->lk);
       +        if(!chkSource(f)){
       +                fileUnlock(f);
       +                return 0;
       +        }
       +        return 1;
       +}
       +
       +static void
       +fileUnlock(File *f)
       +{
       +        vtUnlock(f->lk);
       +}
       +
       +/*
       + * f->source and f->msource must NOT be locked.
       + * fileMetaFlush locks the fileMeta and then the source (in fileMetaFlush2).
       + * We have to respect that ordering.
       + */
       +static void
       +fileMetaLock(File *f)
       +{
       +if(f->up == nil)
       +fprint(2, "f->elem = %s\n", f->dir.elem);
       +        assert(f->up != nil);
       +        assert(!vtCanLock(f->fs->elk));
       +        vtLock(f->up->lk);
       +}
       +
       +static void
       +fileMetaUnlock(File *f)
       +{
       +        vtUnlock(f->up->lk);
       +}
       +
       +/*
       + * f->source and f->msource must NOT be locked.
       + * see fileMetaLock.
       + */
       +static void
       +fileRAccess(File* f)
       +{
       +        if(f->mode == OReadOnly || f->fs->noatimeupd)
       +                return;
       +
       +        fileMetaLock(f);
       +        f->dir.atime = time(0L);
       +        f->dirty = 1;
       +        fileMetaUnlock(f);
       +}
       +
       +/*
       + * f->source and f->msource must NOT be locked.
       + * see fileMetaLock.
       + */
       +static void
       +fileWAccess(File* f, char *mid)
       +{
       +        if(f->mode == OReadOnly)
       +                return;
       +
       +        fileMetaLock(f);
       +        f->dir.atime = f->dir.mtime = time(0L);
       +        if(strcmp(f->dir.mid, mid) != 0){
       +                vtMemFree(f->dir.mid);
       +                f->dir.mid = vtStrDup(mid);
       +        }
       +        f->dir.mcount++;
       +        f->dirty = 1;
       +        fileMetaUnlock(f);
       +
       +/*RSC: let's try this */
       +/*presotto - lets not
       +        if(f->up)
       +                fileWAccess(f->up, mid);
       +*/
       +}
       +
       +static int
       +getEntry(Source *r, Entry *e, int checkepoch)
       +{
       +        u32int epoch;
       +        Block *b;
       +
       +        if(r == nil){
       +                memset(&e, 0, sizeof e);
       +                return 1;
       +        }
       +
       +        b = cacheGlobal(r->fs->cache, r->score, BtDir, r->tag, OReadOnly);
       +        if(b == nil)
       +                return 0;
       +        if(!entryUnpack(e, b->data, r->offset % r->epb)){
       +                blockPut(b);
       +                return 0;
       +        }
       +        epoch = b->l.epoch;
       +        blockPut(b);
       +
       +        if(checkepoch){
       +                b = cacheGlobal(r->fs->cache, e->score, entryType(e), e->tag, OReadOnly);
       +                if(b){
       +                        if(b->l.epoch >= epoch)
       +                                fprint(2, "warning: entry %p epoch not older %#.8ux/%d %V/%d in getEntry\n",
       +                                        r, b->addr, b->l.epoch, r->score, epoch);
       +                        blockPut(b);
       +                }
       +        }
       +
       +        return 1;
       +}
       +
       +static int
       +setEntry(Source *r, Entry *e)
       +{
       +        Block *b;
       +        Entry oe;
       +
       +        b = cacheGlobal(r->fs->cache, r->score, BtDir, r->tag, OReadWrite);
       +        if(0) fprint(2, "setEntry: b %#ux %d score=%V\n", b->addr, r->offset % r->epb, e->score);
       +        if(b == nil)
       +                return 0;
       +        if(!entryUnpack(&oe, b->data, r->offset % r->epb)){
       +                blockPut(b);
       +                return 0;
       +        }
       +        e->gen = oe.gen;
       +        entryPack(e, b->data, r->offset % r->epb);
       +
       +        /* BUG b should depend on the entry pointer */
       +
       +        blockDirty(b);
       +        blockPut(b);
       +        return 1;
       +}
       +
       +/* assumes hold elk */
       +int
       +fileSnapshot(File *dst, File *src, u32int epoch, int doarchive)
       +{
       +        Entry e, ee;
       +
       +        /* add link to snapshot */
       +        if(!getEntry(src->source, &e, 1) || !getEntry(src->msource, &ee, 1))
       +                return 0;
       +
       +        e.snap = epoch;
       +        e.archive = doarchive;
       +        ee.snap = epoch;
       +        ee.archive = doarchive;
       +
       +        if(!setEntry(dst->source, &e) || !setEntry(dst->msource, &ee))
       +                return 0;
       +        return 1;
       +}
       +
       +int
       +fileGetSources(File *f, Entry *e, Entry *ee)
       +{
       +        if(!getEntry(f->source, e, 0)
       +        || !getEntry(f->msource, ee, 0))
       +                return 0;
       +        return 1;
       +}
       +
       +/*
       + * Walk down to the block(s) containing the Entries
       + * for f->source and f->msource, copying as we go.
       + */
       +int
       +fileWalkSources(File *f)
       +{
       +        if(f->mode == OReadOnly){
       +                fprint(2, "readonly in fileWalkSources\n");
       +                return 1;
       +        }
       +        if(!sourceLock2(f->source, f->msource, OReadWrite)){
       +                fprint(2, "sourceLock2 failed in fileWalkSources\n");
       +                return 0;
       +        }
       +        sourceUnlock(f->source);
       +        sourceUnlock(f->msource);
       +        return 1;
       +}
       +
       +/*
       + * convert File* to full path name in malloced string.
       + * this hasn't been as useful as we hoped it would be.
       + */
       +char *
       +fileName(File *f)
       +{
       +        char *name, *pname;
       +        File *p;
       +        static char root[] = "/";
       +
       +        if (f == nil)
       +                return vtStrDup("/**GOK**");
       +
       +        p = fileGetParent(f);
       +        if (p == f)
       +                name = vtStrDup(root);
       +        else {
       +                pname = fileName(p);
       +                if (strcmp(pname, root) == 0)
       +                        name = smprint("/%s", f->dir.elem);
       +                else
       +                        name = smprint("%s/%s", pname, f->dir.elem);
       +                free(pname);
       +        }
       +        fileDecRef(p);
       +        return name;
       +}
 (DIR) diff --git a/src/cmd/fossil/flchk.c b/src/cmd/fossil/flchk.c
       t@@ -0,0 +1,118 @@
       +#include "stdinc.h"
       +#include <bio.h>
       +#include "dat.h"
       +#include "fns.h"
       +
       +Biobuf bout;
       +Fsck fsck;
       +
       +static void
       +usage(void)
       +{
       +        fprint(2, "usage: %s [-c cachesize] [-h host] file\n", argv0);
       +        exits("usage");
       +}
       +
       +#pragma        varargck        argpos        flprint        1
       +
       +static int
       +flprint(char *fmt, ...)
       +{
       +        int n;
       +        va_list arg;
       +
       +        va_start(arg, fmt);
       +        n = Bvprint(&bout, fmt, arg);
       +        va_end(arg);
       +        return n;
       +}
       +
       +static void
       +flclre(Fsck*, Block *b, int o)
       +{
       +        Bprint(&bout, "# clre 0x%ux %d\n", b->addr, o);
       +}
       +
       +static void
       +flclrp(Fsck*, Block *b, int o)
       +{
       +        Bprint(&bout, "# clrp 0x%ux %d\n", b->addr, o);
       +}
       +
       +static void
       +flclri(Fsck*, char *name, MetaBlock*, int, Block*)
       +{
       +        Bprint(&bout, "# clri %s\n", name);
       +}
       +
       +static void
       +flclose(Fsck*, Block *b, u32int epoch)
       +{
       +        Bprint(&bout, "# bclose 0x%ux %ud\n", b->addr, epoch);
       +}
       +
       +void
       +main(int argc, char *argv[])
       +{
       +        int csize = 1000;
       +        VtSession *z;
       +        char *host = nil;
       +        
       +        fsck.useventi = 1;
       +        Binit(&bout, 1, OWRITE);
       +        ARGBEGIN{
       +        default:
       +                usage();
       +        case 'c':
       +                csize = atoi(ARGF());
       +                if(csize <= 0)
       +                        usage();
       +                break;
       +        case 'f':
       +                fsck.useventi = 0;
       +                break;
       +        case 'h':
       +                host = ARGF();
       +                break;
       +        case 'v':
       +                fsck.printdirs = 1;
       +                break;
       +        }ARGEND;
       +
       +        if(argc != 1)
       +                usage();
       +
       +        vtAttach();
       +
       +        fmtinstall('L', labelFmt);
       +        fmtinstall('V', scoreFmt);
       +        fmtinstall('R', vtErrFmt);
       +
       +        /*
       +         * Connect to Venti.
       +         */
       +        z = vtDial(host, 0);
       +        if(z == nil){
       +                if(fsck.useventi)
       +                        vtFatal("could not connect to server: %s", vtGetError());
       +        }else if(!vtConnect(z, 0))
       +                vtFatal("vtConnect: %s", vtGetError());
       +
       +        /*
       +         * Initialize file system.
       +         */
       +        fsck.fs = fsOpen(argv[0], z, csize, OReadOnly);
       +        if(fsck.fs == nil)
       +                vtFatal("could not open file system: %R");
       +
       +        fsck.print = flprint;
       +        fsck.clre = flclre;
       +        fsck.clrp = flclrp;
       +        fsck.close = flclose;
       +        fsck.clri = flclri;
       +
       +        fsCheck(&fsck);
       +
       +        exits(0);
       +}
       +
 (DIR) diff --git a/src/cmd/fossil/flfmt.c b/src/cmd/fossil/flfmt.c
       t@@ -0,0 +1,571 @@
       +#include "stdinc.h"
       +#include "dat.h"
       +#include "fns.h"
       +#include "flfmt9660.h"
       +
       +#define blockWrite _blockWrite        /* hack */
       +
       +static void usage(void);
       +static u64int fdsize(int fd);
       +static void partition(int fd, int bsize, Header *h);
       +static u64int unittoull(char *s);
       +static u32int blockAlloc(int type, u32int tag);
       +static void blockRead(int part, u32int addr);
       +static void blockWrite(int part, u32int addr);
       +static void superInit(char *label, u32int root, uchar[VtScoreSize]);
       +static void rootMetaInit(Entry *e);
       +static u32int rootInit(Entry *e);
       +static void topLevel(char *name);
       +static int parseScore(uchar[VtScoreSize], char*);
       +static u32int ventiRoot(char*, char*);
       +static VtSession *z;
       +
       +#define TWID64        ((u64int)~(u64int)0)
       +
       +Disk *disk;
       +Fs *fs;
       +uchar *buf;
       +int bsize = 8*1024;
       +u64int qid = 1;
       +int iso9660off;
       +char *iso9660file;
       +
       +int
       +confirm(char *msg)
       +{
       +        char buf[100];
       +        int n;
       +
       +        fprint(2, "%s [y/n]: ", msg);
       +        n = read(0, buf, sizeof buf - 1);
       +        if(n <= 0)
       +                return 0;
       +        if(buf[0] == 'y')
       +                return 1;
       +        return 0;
       +}
       +
       +void
       +main(int argc, char *argv[])
       +{
       +        int fd, force;
       +        Header h;
       +        ulong bn;
       +        Entry e;
       +        char *label = "vfs";
       +        char *host = nil;
       +        char *score = nil;
       +        u32int root;
       +        Dir *d;
       +
       +        force = 0;
       +        ARGBEGIN{
       +        default:
       +                usage();
       +        case 'b':
       +                bsize = unittoull(EARGF(usage()));
       +                if(bsize == ~0)
       +                        usage();
       +                break;
       +        case 'h':
       +                host = EARGF(usage());
       +                break;
       +        case 'i':
       +                iso9660file = EARGF(usage());
       +                iso9660off = atoi(EARGF(usage()));
       +                break;
       +        case 'l':
       +                label = EARGF(usage());
       +                break;
       +        case 'v':
       +                score = EARGF(usage());
       +                break;
       +
       +        /*
       +         * This is -y instead of -f because flchk has a
       +         * (frequently used) -f option.  I type flfmt instead
       +         * of flchk all the time, and want to make it hard
       +         * to reformat my file system accidentally.
       +         */
       +        case 'y':
       +                force = 1;
       +                break;
       +        }ARGEND
       +
       +        if(argc != 1)
       +                usage();
       +
       +        if(iso9660file && score)
       +                vtFatal("cannot use -i with -v");
       +
       +        vtAttach();
       +
       +        fmtinstall('V', scoreFmt);
       +        fmtinstall('R', vtErrFmt);
       +        fmtinstall('L', labelFmt);
       +
       +        fd = open(argv[0], ORDWR);
       +        if(fd < 0)
       +                vtFatal("could not open file: %s: %r", argv[0]);
       +
       +        buf = vtMemAllocZ(bsize);
       +        if(pread(fd, buf, bsize, HeaderOffset) != bsize)
       +                vtFatal("could not read fs header block: %r");
       +
       +        if(headerUnpack(&h, buf) && !force
       +        && !confirm("fs header block already exists; are you sure?"))
       +                goto Out;
       +
       +        if((d = dirfstat(fd)) == nil)
       +                vtFatal("dirfstat: %r");
       +
       +        if(d->type == 'M' && !force
       +        && !confirm("fs file is mounted via devmnt (is not a kernel device); are you sure?"))
       +                goto Out;
       +
       +        partition(fd, bsize, &h);
       +        headerPack(&h, buf);
       +        if(pwrite(fd, buf, bsize, HeaderOffset) < bsize)
       +                vtFatal("could not write fs header: %r");
       +
       +        disk = diskAlloc(fd);
       +        if(disk == nil)
       +                vtFatal("could not open disk: %r");
       +
       +        if(iso9660file)
       +                iso9660init(fd, &h, iso9660file, iso9660off);
       +
       +        /* zero labels */
       +        memset(buf, 0, bsize);
       +        for(bn = 0; bn < diskSize(disk, PartLabel); bn++)
       +                blockWrite(PartLabel, bn);
       +
       +        if(iso9660file)
       +                iso9660labels(disk, buf, blockWrite);
       +
       +        if(score)
       +                root = ventiRoot(host, score);
       +        else{
       +                rootMetaInit(&e);
       +                root = rootInit(&e);
       +        }
       +
       +        superInit(label, root, vtZeroScore);
       +        diskFree(disk);
       +
       +        if(score == nil)
       +                topLevel(argv[0]);
       +
       +Out:
       +        vtDetach();
       +        exits(0);
       +}
       +
       +static u64int
       +fdsize(int fd)
       +{
       +        Dir *dir;
       +        u64int size;
       +
       +        dir = dirfstat(fd);
       +        if(dir == nil)
       +                vtFatal("could not stat file: %r");
       +        size = dir->length;
       +        free(dir);
       +        return size;
       +}
       +
       +static void
       +usage(void)
       +{
       +        fprint(2, "usage: %s [-b blocksize] [-h host] [-i file offset] "
       +                "[-l label] [-v score] [-y] file\n", argv0);
       +        exits("usage");
       +}
       +
       +static void
       +partition(int fd, int bsize, Header *h)
       +{
       +        ulong nblock, ndata, nlabel;
       +        ulong lpb;
       +
       +        if(bsize % 512 != 0)
       +                sysfatal("block size must be a multiple of 512 bytes");
       +        if(bsize > VtMaxLumpSize)
       +                sysfatal("block size must be less than %d", VtMaxLumpSize);
       +
       +        memset(h, 0, sizeof(*h));
       +        h->blockSize = bsize;
       +
       +        lpb = bsize/LabelSize;
       +
       +        nblock = fdsize(fd)/bsize;
       +
       +        /* sanity check */
       +        if(nblock < (HeaderOffset*10)/bsize)
       +                vtFatal("file too small");
       +
       +        h->super = (HeaderOffset + 2*bsize)/bsize;
       +        h->label = h->super + 1;
       +        ndata = ((u64int)lpb)*(nblock - h->label)/(lpb+1);
       +        nlabel = (ndata + lpb - 1)/lpb;
       +        h->data = h->label + nlabel;
       +        h->end = h->data + ndata;
       +
       +}
       +
       +static u32int
       +tagGen(void)
       +{
       +        u32int tag;
       +
       +        for(;;){
       +                tag = lrand();
       +                if(tag > RootTag)
       +                        break;
       +        }
       +        return tag;
       +}
       +
       +static void
       +entryInit(Entry *e)
       +{
       +        e->gen = 0;
       +        e->dsize = bsize;
       +        e->psize = bsize/VtEntrySize*VtEntrySize;
       +        e->flags = VtEntryActive;
       +        e->depth = 0;
       +        e->size = 0;
       +        memmove(e->score, vtZeroScore, VtScoreSize);
       +        e->tag = tagGen();
       +        e->snap = 0;
       +        e->archive = 0;
       +}
       +
       +static void
       +rootMetaInit(Entry *e)
       +{
       +        u32int addr;
       +        u32int tag;
       +        DirEntry de;
       +        MetaBlock mb;
       +        MetaEntry me;
       +
       +        memset(&de, 0, sizeof(de));
       +        de.elem = vtStrDup("root");
       +        de.entry = 0;
       +        de.gen = 0;
       +        de.mentry = 1;
       +        de.mgen = 0;
       +        de.size = 0;
       +        de.qid = qid++;
       +        de.uid = vtStrDup("adm");
       +        de.gid = vtStrDup("adm");
       +        de.mid = vtStrDup("adm");
       +        de.mtime = time(0);
       +        de.mcount = 0;
       +        de.ctime = time(0);
       +        de.atime = time(0);
       +        de.mode = ModeDir | 0555;
       +
       +        tag = tagGen();
       +        addr = blockAlloc(BtData, tag);
       +
       +        /* build up meta block */
       +        memset(buf, 0, bsize);
       +        mbInit(&mb, buf, bsize, bsize/100);
       +        me.size = deSize(&de);
       +        me.p = mbAlloc(&mb, me.size);
       +        assert(me.p != nil);
       +        dePack(&de, &me);
       +        mbInsert(&mb, 0, &me);
       +        mbPack(&mb);
       +        blockWrite(PartData, addr);
       +        deCleanup(&de);
       +
       +        /* build up entry for meta block */
       +        entryInit(e);
       +        e->flags |= VtEntryLocal;
       +         e->size = bsize;
       +        e->tag = tag;
       +        localToGlobal(addr, e->score);
       +}
       +
       +static u32int
       +rootInit(Entry *e)
       +{
       +        ulong addr;
       +        u32int tag;
       +
       +        tag = tagGen();
       +
       +        addr = blockAlloc(BtDir, tag);
       +        memset(buf, 0, bsize);
       +
       +        /* root meta data is in the third entry */
       +        entryPack(e, buf, 2);
       +
       +        entryInit(e);
       +        e->flags |= VtEntryDir;
       +        entryPack(e, buf, 0);
       +
       +        entryInit(e);
       +        entryPack(e, buf, 1);
       +
       +        blockWrite(PartData, addr);
       +
       +        entryInit(e);
       +        e->flags |= VtEntryLocal|VtEntryDir;
       +         e->size = VtEntrySize*3;
       +        e->tag = tag;
       +        localToGlobal(addr, e->score);
       +
       +        addr = blockAlloc(BtDir, RootTag);
       +        memset(buf, 0, bsize);
       +        entryPack(e, buf, 0);
       +
       +        blockWrite(PartData, addr);
       +
       +        return addr;
       +}
       +
       +
       +static u32int
       +blockAlloc(int type, u32int tag)
       +{
       +        static u32int addr;
       +        Label l;
       +        int lpb;
       +
       +        lpb = bsize/LabelSize;
       +
       +        blockRead(PartLabel, addr/lpb);
       +        if(!labelUnpack(&l, buf, addr % lpb))
       +                vtFatal("bad label: %r");
       +        if(l.state != BsFree)
       +                vtFatal("want to allocate block already in use");
       +        l.epoch = 1;
       +        l.epochClose = ~(u32int)0;
       +        l.type = type;
       +        l.state = BsAlloc;
       +        l.tag = tag;
       +        labelPack(&l, buf, addr % lpb);
       +        blockWrite(PartLabel, addr/lpb);
       +        return addr++;
       +}
       +
       +static void
       +superInit(char *label, u32int root, uchar score[VtScoreSize])
       +{
       +        Super s;
       +
       +        memset(buf, 0, bsize);
       +        memset(&s, 0, sizeof(s));
       +        s.version = SuperVersion;
       +        s.epochLow = 1;
       +        s.epochHigh = 1;
       +        s.qid = qid;
       +        s.active = root;
       +        s.next = NilBlock;
       +        s.current = NilBlock;
       +        strecpy(s.name, s.name+sizeof(s.name), label);
       +        memmove(s.last, score, VtScoreSize);
       +
       +        superPack(&s, buf);
       +        blockWrite(PartSuper, 0);
       +}
       +
       +static u64int
       +unittoull(char *s)
       +{
       +        char *es;
       +        u64int n;
       +
       +        if(s == nil)
       +                return TWID64;
       +        n = strtoul(s, &es, 0);
       +        if(*es == 'k' || *es == 'K'){
       +                n *= 1024;
       +                es++;
       +        }else if(*es == 'm' || *es == 'M'){
       +                n *= 1024*1024;
       +                es++;
       +        }else if(*es == 'g' || *es == 'G'){
       +                n *= 1024*1024*1024;
       +                es++;
       +        }
       +        if(*es != '\0')
       +                return TWID64;
       +        return n;
       +}
       +
       +static void
       +blockRead(int part, u32int addr)
       +{
       +        if(!diskReadRaw(disk, part, addr, buf))
       +                vtFatal("read failed: %r");
       +}
       +
       +static void
       +blockWrite(int part, u32int addr)
       +{
       +        if(!diskWriteRaw(disk, part, addr, buf))
       +                vtFatal("write failed: %r");
       +}
       +
       +static void
       +addFile(File *root, char *name, uint mode)
       +{
       +        File *f;
       +
       +        f = fileCreate(root, name, mode | ModeDir, "adm");
       +        if(f == nil)
       +                vtFatal("could not create file: %s: %r", name);
       +        fileDecRef(f);
       +}
       +
       +static void
       +topLevel(char *name)
       +{
       +        Fs *fs;
       +        File *root;
       +
       +        /* ok, now we can open as a fs */
       +        fs = fsOpen(name, z, 100, OReadWrite);
       +        if(fs == nil)
       +                vtFatal("could not open file system: %r");
       +        vtRLock(fs->elk);
       +        root = fsGetRoot(fs);
       +        if(root == nil)
       +                vtFatal("could not open root: %r");
       +        addFile(root, "active", 0555);
       +        addFile(root, "archive", 0555);
       +        addFile(root, "snapshot", 0555);
       +        fileDecRef(root);
       +        if(iso9660file)
       +                iso9660copy(fs);
       +        vtRUnlock(fs->elk);
       +        fsClose(fs);
       +}
       +
       +static int
       +ventiRead(uchar score[VtScoreSize], int type)
       +{
       +        int n;
       +
       +        n = vtRead(z, score, type, buf, bsize);
       +        if(n < 0)
       +                vtFatal("ventiRead %V (%d) failed: %R", score, type);
       +        vtZeroExtend(type, buf, n, bsize);
       +        return n;
       +}
       +
       +static u32int
       +ventiRoot(char *host, char *s)
       +{
       +        int i, n;
       +        uchar score[VtScoreSize];
       +        u32int addr, tag;
       +        DirEntry de;
       +        MetaBlock mb;
       +        MetaEntry me;
       +        Entry e;
       +        VtRoot root;
       +
       +        if(!parseScore(score, s))
       +                vtFatal("bad score '%s'", s);
       +
       +        if((z = vtDial(host, 0)) == nil
       +        || !vtConnect(z, nil))
       +                vtFatal("connect to venti: %R");
       +
       +        tag = tagGen();
       +        addr = blockAlloc(BtDir, tag);
       +
       +        ventiRead(score, VtRootType);
       +        if(!vtRootUnpack(&root, buf))
       +                vtFatal("corrupted root: vtRootUnpack");
       +        n = ventiRead(root.score, VtDirType);
       +
       +        /*
       +         * Fossil's vac archives start with an extra layer of source,
       +         * but vac's don't.
       +         */
       +        if(n <= 2*VtEntrySize){
       +                if(!entryUnpack(&e, buf, 0))
       +                        vtFatal("bad root: top entry");
       +                n = ventiRead(e.score, VtDirType);
       +        }
       +
       +        /*
       +         * There should be three root sources (and nothing else) here.
       +         */
       +        for(i=0; i<3; i++){
       +                if(!entryUnpack(&e, buf, i)
       +                || !(e.flags&VtEntryActive)
       +                || e.psize < 256
       +                || e.dsize < 256)
       +                        vtFatal("bad root: entry %d", i);
       +                fprint(2, "%V\n", e.score);
       +        }
       +        if(n > 3*VtEntrySize)
       +                vtFatal("bad root: entry count");
       +
       +        blockWrite(PartData, addr);
       +
       +        /*
       +         * Maximum qid is recorded in root's msource, entry #2 (conveniently in e).
       +         */
       +        ventiRead(e.score, VtDataType);
       +        if(!mbUnpack(&mb, buf, bsize))
       +                vtFatal("bad root: mbUnpack");
       +        meUnpack(&me, &mb, 0);
       +        if(!deUnpack(&de, &me))
       +                vtFatal("bad root: dirUnpack");
       +        if(!de.qidSpace)
       +                vtFatal("bad root: no qidSpace");
       +        qid = de.qidMax;
       +
       +        /*
       +         * Recreate the top layer of source.
       +         */
       +        entryInit(&e);
       +        e.flags |= VtEntryLocal|VtEntryDir;
       +        e.size = VtEntrySize*3;
       +        e.tag = tag;
       +        localToGlobal(addr, e.score);
       +
       +        addr = blockAlloc(BtDir, RootTag);
       +        memset(buf, 0, bsize);
       +        entryPack(&e, buf, 0);
       +        blockWrite(PartData, addr);
       +
       +        return addr;
       +}
       +
       +static int
       +parseScore(uchar *score, char *buf)
       +{
       +        int i, c;
       +
       +        memset(score, 0, VtScoreSize);
       +
       +        if(strlen(buf) < VtScoreSize*2)
       +                return 0;
       +        for(i=0; i<VtScoreSize*2; i++){
       +                if(buf[i] >= '0' && buf[i] <= '9')
       +                        c = buf[i] - '0';
       +                else if(buf[i] >= 'a' && buf[i] <= 'f')
       +                        c = buf[i] - 'a' + 10;
       +                else if(buf[i] >= 'A' && buf[i] <= 'F')
       +                        c = buf[i] - 'A' + 10;
       +                else
       +                        return 0;
       +
       +                if((i & 1) == 0)
       +                        c <<= 4;
       +
       +                score[i>>1] |= c;
       +        }
       +        return 1;
       +}
 (DIR) diff --git a/src/cmd/fossil/flfmt9660.c b/src/cmd/fossil/flfmt9660.c
       t@@ -0,0 +1,565 @@
       +/*
       + * Initialize a fossil file system from an ISO9660 image already in the
       + * file system.  This is a fairly bizarre thing to do, but it lets us generate
       + * installation CDs that double as valid Plan 9 disk partitions.  
       + * People having trouble booting the CD can just copy it into a disk
       + * partition and you've got a working Plan 9 system.
       + *
       + * I've tried hard to keep all the associated cruft in this file.
       + * If you deleted this file and cut out the three calls into it from flfmt.c,
       + * no traces would remain.
       + */
       +
       +#include "stdinc.h"
       +#include "dat.h"
       +#include "fns.h"
       +#include "flfmt9660.h"
       +#include <bio.h>
       +#include <ctype.h>
       +
       +static Biobuf *b;
       +
       +enum{
       +        Tag = 0x96609660,
       +        Blocksize = 2048,
       +};
       +
       +#pragma varargck type "s" uchar*
       +#pragma varargck type "L" uchar*
       +#pragma varargck type "B" uchar*
       +#pragma varargck type "N" uchar*
       +#pragma varargck type "T" uchar*
       +#pragma varargck type "D" uchar*
       +
       +typedef struct Voldesc Voldesc;
       +struct Voldesc {
       +        uchar        magic[8];        /* 0x01, "CD001", 0x01, 0x00 */
       +        uchar        systemid[32];        /* system identifier */
       +        uchar        volumeid[32];        /* volume identifier */
       +        uchar        unused[8];        /* character set in secondary desc */
       +        uchar        volsize[8];        /* volume size */
       +        uchar        charset[32];
       +        uchar        volsetsize[4];        /* volume set size = 1 */
       +        uchar        volseqnum[4];        /* volume sequence number = 1 */
       +        uchar        blocksize[4];        /* logical block size */
       +        uchar        pathsize[8];        /* path table size */
       +        uchar        lpathloc[4];        /* Lpath */
       +        uchar        olpathloc[4];        /* optional Lpath */
       +        uchar        mpathloc[4];        /* Mpath */
       +        uchar        ompathloc[4];        /* optional Mpath */
       +        uchar        rootdir[34];        /* root directory */
       +        uchar        volsetid[128];        /* volume set identifier */
       +        uchar        publisher[128];
       +        uchar        prepid[128];        /* data preparer identifier */
       +        uchar        applid[128];        /* application identifier */
       +        uchar        notice[37];        /* copyright notice file */
       +        uchar        abstract[37];        /* abstract file */
       +        uchar        biblio[37];        /* bibliographic file */
       +        uchar        cdate[17];        /* creation date */
       +        uchar        mdate[17];        /* modification date */
       +        uchar        xdate[17];        /* expiration date */
       +        uchar        edate[17];        /* effective date */
       +        uchar        fsvers;                /* file system version = 1 */
       +};
       +
       +static void
       +dumpbootvol(void *a)
       +{
       +        Voldesc *v;
       +
       +        v = a;
       +        print("magic %.2ux %.5s %.2ux %2ux\n",
       +                v->magic[0], v->magic+1, v->magic[6], v->magic[7]);
       +        if(v->magic[0] == 0xFF)
       +                return;
       +
       +        print("system %.32T\n", v->systemid);
       +        print("volume %.32T\n", v->volumeid);
       +        print("volume size %.4N\n", v->volsize);
       +        print("charset %.2ux %.2ux %.2ux %.2ux %.2ux %.2ux %.2ux %.2ux\n",
       +                v->charset[0], v->charset[1], v->charset[2], v->charset[3],
       +                v->charset[4], v->charset[5], v->charset[6], v->charset[7]);
       +        print("volume set size %.2N\n", v->volsetsize);
       +        print("volume sequence number %.2N\n", v->volseqnum);
       +        print("logical block size %.2N\n", v->blocksize);
       +        print("path size %.4L\n", v->pathsize);
       +        print("lpath loc %.4L\n", v->lpathloc);
       +        print("opt lpath loc %.4L\n", v->olpathloc);
       +        print("mpath loc %.4B\n", v->mpathloc);
       +        print("opt mpath loc %.4B\n", v->ompathloc);
       +        print("rootdir %D\n", v->rootdir);
       +        print("volume set identifier %.128T\n", v->volsetid);
       +        print("publisher %.128T\n", v->publisher);
       +        print("preparer %.128T\n", v->prepid);
       +        print("application %.128T\n", v->applid);
       +        print("notice %.37T\n", v->notice);
       +        print("abstract %.37T\n", v->abstract);
       +        print("biblio %.37T\n", v->biblio);
       +        print("creation date %.17s\n", v->cdate);
       +        print("modification date %.17s\n", v->mdate);
       +        print("expiration date %.17s\n", v->xdate);
       +        print("effective date %.17s\n", v->edate);
       +        print("fs version %d\n", v->fsvers);
       +}
       +
       +typedef struct Cdir Cdir;
       +struct Cdir {
       +        uchar        len;
       +        uchar        xlen;
       +        uchar        dloc[8];
       +        uchar        dlen[8];
       +        uchar        date[7];
       +        uchar        flags;
       +        uchar        unitsize;
       +        uchar        gapsize;
       +        uchar        volseqnum[4];
       +        uchar        namelen;
       +        uchar        name[1];        /* chumminess */
       +};
       +#pragma varargck type "D" Cdir*
       +
       +static int
       +Dfmt(Fmt *fmt)
       +{
       +        char buf[128];
       +        Cdir *c;
       +
       +        c = va_arg(fmt->args, Cdir*);
       +        if(c->namelen == 1 && c->name[0] == '\0' || c->name[0] == '\001') {
       +                snprint(buf, sizeof buf, ".%s dloc %.4N dlen %.4N",
       +                        c->name[0] ? "." : "", c->dloc, c->dlen);
       +        } else {
       +                snprint(buf, sizeof buf, "%.*T dloc %.4N dlen %.4N", c->namelen, c->name,
       +                        c->dloc, c->dlen);
       +        }
       +        fmtstrcpy(fmt, buf);
       +        return 0;
       +}
       +
       +char longc, shortc;
       +static void
       +bigend(void)
       +{
       +        longc = 'B';
       +}
       +
       +static void
       +littleend(void)
       +{
       +        longc = 'L';
       +}
       +
       +static ulong
       +big(void *a, int n)
       +{
       +        uchar *p;
       +        ulong v;
       +        int i;
       +
       +        p = a;
       +        v = 0;
       +        for(i=0; i<n; i++)
       +                v = (v<<8) | *p++;
       +        return v;
       +}
       +
       +static ulong
       +little(void *a, int n)
       +{
       +        uchar *p;
       +        ulong v;
       +        int i;
       +
       +        p = a;
       +        v = 0;
       +        for(i=0; i<n; i++)
       +                v |= (*p++<<(i*8));
       +        return v;
       +}
       +
       +/* numbers in big or little endian. */
       +static int
       +BLfmt(Fmt *fmt)
       +{
       +        ulong v;
       +        uchar *p;
       +        char buf[20];
       +
       +        p = va_arg(fmt->args, uchar*);
       +
       +        if(!(fmt->flags&FmtPrec)) {
       +                fmtstrcpy(fmt, "*BL*");
       +                return 0;
       +        }
       +
       +        if(fmt->r == 'B')
       +                v = big(p, fmt->prec);
       +        else
       +                v = little(p, fmt->prec);
       +
       +        sprint(buf, "0x%.*lux", fmt->prec*2, v);
       +        fmt->flags &= ~FmtPrec;
       +        fmtstrcpy(fmt, buf);
       +        return 0;
       +}
       +
       +/* numbers in both little and big endian */
       +static int
       +Nfmt(Fmt *fmt)
       +{
       +        char buf[100];
       +        uchar *p;
       +
       +        p = va_arg(fmt->args, uchar*);
       +
       +        sprint(buf, "%.*L %.*B", fmt->prec, p, fmt->prec, p+fmt->prec);
       +        fmt->flags &= ~FmtPrec;
       +        fmtstrcpy(fmt, buf);
       +        return 0;
       +}
       +
       +static int
       +asciiTfmt(Fmt *fmt)
       +{
       +        char *p, buf[256];
       +        int i;
       +
       +        p = va_arg(fmt->args, char*);
       +        for(i=0; i<fmt->prec; i++)
       +                buf[i] = *p++;
       +        buf[i] = '\0';
       +        for(p=buf+strlen(buf); p>buf && p[-1]==' '; p--)
       +                ;
       +        p[0] = '\0';
       +        fmt->flags &= ~FmtPrec;
       +        fmtstrcpy(fmt, buf);
       +        return 0;
       +}
       +
       +static void
       +ascii(void)
       +{
       +        fmtinstall('T', asciiTfmt);
       +}
       +
       +static int
       +runeTfmt(Fmt *fmt)
       +{
       +        Rune buf[256], *r;
       +        int i;
       +        uchar *p;
       +
       +        p = va_arg(fmt->args, uchar*);
       +        for(i=0; i*2+2<=fmt->prec; i++, p+=2)
       +                buf[i] = (p[0]<<8)|p[1];
       +        buf[i] = L'\0';
       +        for(r=buf+i; r>buf && r[-1]==L' '; r--)
       +                ;
       +        r[0] = L'\0';
       +        fmt->flags &= ~FmtPrec;
       +        return fmtprint(fmt, "%S", buf);
       +}
       +
       +static void
       +getsect(uchar *buf, int n)
       +{
       +        if(Bseek(b, n*2048, 0) != n*2048 || Bread(b, buf, 2048) != 2048)
       +{
       +abort();
       +                sysfatal("reading block at %,d: %r", n*2048);
       +}
       +}
       +
       +static Header *h;
       +static int fd;
       +static char *file9660;
       +static int off9660;
       +static ulong startoff;
       +static ulong endoff;
       +static ulong fsoff;
       +static uchar root[2048];
       +static Voldesc *v;
       +static ulong iso9660start(Cdir*);
       +static void iso9660copydir(Fs*, File*, Cdir*);
       +static void iso9660copyfile(Fs*, File*, Cdir*);
       +
       +void
       +iso9660init(int xfd, Header *xh, char *xfile9660, int xoff9660)
       +{
       +        uchar sect[2048], sect2[2048];
       +
       +        fmtinstall('L', BLfmt);
       +        fmtinstall('B', BLfmt);
       +        fmtinstall('N', Nfmt);
       +        fmtinstall('D', Dfmt);
       +
       +        fd = xfd;
       +        h = xh;
       +        file9660 = xfile9660;
       +        off9660 = xoff9660;
       +
       +        if((b = Bopen(file9660, OREAD)) == nil)
       +                vtFatal("Bopen %s: %r", file9660);
       +
       +        getsect(root, 16);
       +        ascii();
       +
       +        v = (Voldesc*)root;
       +        if(memcmp(v->magic, "\x01CD001\x01\x00", 8) != 0)
       +                vtFatal("%s not a cd image", file9660);
       +
       +        startoff = iso9660start((Cdir*)v->rootdir)*Blocksize;
       +        endoff = little(v->volsize, 4);        /* already in bytes */
       +
       +        fsoff = off9660 + h->data*h->blockSize;
       +        if(fsoff > startoff)
       +                vtFatal("fossil data starts after cd data");
       +        if(off9660 + (vlong)h->end*h->blockSize < endoff)
       +                vtFatal("fossil data ends before cd data");
       +        if(fsoff%h->blockSize)
       +                vtFatal("cd offset not a multiple of fossil block size");
       +
       +        /* Read "same" block via CD image and via Fossil image */
       +        getsect(sect, startoff/Blocksize);
       +        if(seek(fd, startoff-off9660, 0) < 0)
       +                vtFatal("cannot seek to first data sector on cd via fossil");
       +fprint(2, "look for %lud at %lud\n", startoff, startoff-off9660);
       +        if(readn(fd, sect2, Blocksize) != Blocksize)
       +                vtFatal("cannot read first data sector on cd via fossil");
       +        if(memcmp(sect, sect2, Blocksize) != 0)
       +                vtFatal("iso9660 offset is a lie %08ux %08ux", *(long*)sect, *(long*)sect2);
       +}
       +
       +void
       +iso9660labels(Disk *disk, uchar *buf, void (*write)(int, u32int))
       +{
       +        ulong sb, eb, bn, lb, llb;
       +        Label l;
       +        int lpb;
       +        uchar sect[Blocksize];
       +
       +        if(!diskReadRaw(disk, PartData, (startoff-fsoff)/h->blockSize, buf))
       +                vtFatal("disk read failed: %r");
       +        getsect(sect, startoff/Blocksize);
       +        if(memcmp(buf, sect, Blocksize) != 0)
       +                vtFatal("fsoff is wrong");
       +
       +        sb = (startoff-fsoff)/h->blockSize;
       +        eb = (endoff-fsoff+h->blockSize-1)/h->blockSize;
       +
       +        lpb = h->blockSize/LabelSize;
       +
       +        /* for each reserved block, mark label */
       +        llb = ~0;
       +        l.type = BtData;
       +        l.state = BsAlloc;
       +        l.tag = Tag;
       +        l.epoch = 1;
       +        l.epochClose = ~(u32int)0;
       +        for(bn=sb; bn<eb; bn++){
       +                lb = bn/lpb;
       +                if(lb != llb){
       +                        if(llb != ~0)
       +                                (*write)(PartLabel, llb);
       +                        memset(buf, 0, h->blockSize);
       +                }
       +                llb = lb;
       +                labelPack(&l, buf, bn%lpb);
       +        }
       +        if(llb != ~0)
       +                (*write)(PartLabel, llb);
       +}
       +
       +void
       +iso9660copy(Fs *fs)
       +{
       +        File *root;
       +
       +        root = fileOpen(fs, "/active");
       +        iso9660copydir(fs, root, (Cdir*)v->rootdir);
       +        fileDecRef(root);
       +        vtRUnlock(fs->elk);
       +        if(!fsSnapshot(fs, nil, nil, 0))
       +                vtFatal("snapshot failed: %R");
       +        vtRLock(fs->elk);
       +}
       +
       +/*
       + * The first block used is the first data block of the leftmost file in the tree.
       + * (Just an artifact of how mk9660 works.)
       + */
       +static ulong
       +iso9660start(Cdir *c)
       +{
       +        uchar sect[Blocksize];
       +
       +        while(c->flags&2){
       +                getsect(sect, little(c->dloc, 4));
       +                c = (Cdir*)sect;
       +                c = (Cdir*)((uchar*)c+c->len);        /* skip dot */
       +                c = (Cdir*)((uchar*)c+c->len);        /* skip dotdot */
       +                /* oops: might happen if leftmost directory is empty or leftmost file is zero length! */
       +                if(little(c->dloc, 4) == 0)
       +                        vtFatal("error parsing cd image or unfortunate cd image");        
       +        }
       +        return little(c->dloc, 4);
       +}
       +
       +static void
       +iso9660copydir(Fs *fs, File *dir, Cdir *cd)
       +{
       +        ulong off, end, len;
       +        uchar sect[Blocksize], *esect, *p;
       +        Cdir *c;
       +
       +        len = little(cd->dlen, 4);
       +        off = little(cd->dloc, 4)*Blocksize;
       +        end = off+len;
       +        esect = sect+Blocksize;
       +
       +        for(; off<end; off+=Blocksize){
       +                getsect(sect, off/Blocksize);
       +                p = sect;
       +                while(p < esect){
       +                        c = (Cdir*)p;
       +                        if(c->len <= 0)
       +                                break;
       +                        if(c->namelen!=1 || c->name[0]>1)
       +                                iso9660copyfile(fs, dir, c);
       +                        p += c->len;
       +                }
       +        }
       +}
       +
       +static char*
       +getname(uchar **pp)
       +{
       +        uchar *p;
       +        int l;
       +
       +        p = *pp;
       +        l = *p;
       +        *pp = p+1+l;
       +        if(l == 0)
       +                return "";
       +        memmove(p, p+1, l);
       +        p[l] = 0;
       +        return (char*)p;
       +}
       +
       +static char*
       +getcname(Cdir *c)
       +{
       +        uchar *up;
       +        char *p, *q;
       +
       +        up = &c->namelen;
       +        p = getname(&up);
       +        for(q=p; *q; q++)
       +                *q = tolower(*q);
       +        return p;
       +}
       +
       +static char
       +dmsize[12] =
       +{
       +        31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
       +};
       +
       +static ulong
       +getcdate(uchar *p)        /* yMdhmsz */
       +{
       +        Tm tm;
       +        int y, M, d, h, m, s, tz;
       +
       +        y=p[0]; M=p[1]; d=p[2];
       +        h=p[3]; m=p[4]; s=p[5]; tz=p[6];
       +        USED(tz);
       +        if (y < 70)
       +                return 0;
       +        if (M < 1 || M > 12)
       +                return 0;
       +        if (d < 1 || d > dmsize[M-1])
       +                return 0;
       +        if (h > 23)
       +                return 0;
       +        if (m > 59)
       +                return 0;
       +        if (s > 59)
       +                return 0;
       +
       +        memset(&tm, 0, sizeof tm);
       +        tm.sec = s;
       +        tm.min = m;
       +        tm.hour = h;
       +        tm.mday = d;
       +        tm.mon = M-1;
       +        tm.year = 1900+y;
       +        tm.zone[0] = 0;
       +        return tm2sec(&tm);
       +}
       +
       +static int ind;
       +
       +static void
       +iso9660copyfile(Fs *fs, File *dir, Cdir *c)
       +{
       +        Dir d;
       +        DirEntry de;
       +        int sysl;
       +        uchar score[VtScoreSize];
       +        ulong off, foff, len, mode;
       +        uchar *p;
       +        File *f;
       +
       +        ind++;
       +        memset(&d, 0, sizeof d);
       +        p = c->name + c->namelen;
       +        if(((uintptr)p) & 1)
       +                p++;
       +        sysl = (uchar*)c + c->len - p;
       +        if(sysl <= 0)
       +                vtFatal("missing plan9 directory entry on %d/%d/%.*s", c->namelen, c->name[0], c->namelen, c->name);
       +        d.name = getname(&p);
       +        d.uid = getname(&p);
       +        d.gid = getname(&p);
       +        if((uintptr)p & 1)
       +                p++;
       +        d.mode = little(p, 4);
       +        if(d.name[0] == 0)
       +                d.name = getcname(c);
       +        d.mtime = getcdate(c->date);
       +        d.atime = d.mtime;
       +
       +if(d.mode&DMDIR)        print("%*scopy %s %s %s %luo\n", ind*2, "", d.name, d.uid, d.gid, d.mode);
       +
       +        mode = d.mode&0777;
       +        if(d.mode&DMDIR)
       +                mode |= ModeDir;
       +        if((f = fileCreate(dir, d.name, mode, d.uid)) == nil)
       +                vtFatal("could not create file '%s': %r", d.name);
       +        if(d.mode&DMDIR)
       +                iso9660copydir(fs, f, c);
       +        else{
       +                len = little(c->dlen, 4);
       +                off = little(c->dloc, 4)*Blocksize;
       +                for(foff=0; foff<len; foff+=h->blockSize){
       +                        localToGlobal((off+foff-fsoff)/h->blockSize, score);
       +                        if(!fileMapBlock(f, foff/h->blockSize, score, Tag))
       +                                vtFatal("fileMapBlock: %R");
       +                }
       +                if(!fileSetSize(f, len))
       +                        vtFatal("fileSetSize: %R");
       +        }
       +        if(!fileGetDir(f, &de))
       +                vtFatal("fileGetDir: %R");
       +        de.uid = d.uid;
       +        de.gid = d.gid;
       +        de.mtime = d.mtime;
       +        de.atime = d.atime;
       +        de.mode = d.mode&0777;
       +        if(!fileSetDir(f, &de, "sys"))
       +                vtFatal("fileSetDir: %R");
       +        fileDecRef(f);
       +        ind--;
       +}
 (DIR) diff --git a/src/cmd/fossil/flfmt9660.h b/src/cmd/fossil/flfmt9660.h
       t@@ -0,0 +1,3 @@
       +void iso9660init(int fd, Header *h, char*, int);
       +void iso9660labels(Disk*, uchar*, void(*write)(int, u32int));
       +void iso9660copy(Fs*);
 (DIR) diff --git a/src/cmd/fossil/fns.h b/src/cmd/fossil/fns.h
       t@@ -0,0 +1,106 @@
       +Block*        sourceBlock(Source*, ulong, int);
       +Block*        _sourceBlock(Source*, ulong, int, int, ulong);
       +void        sourceClose(Source*);
       +Source*        sourceCreate(Source*, int, int, u32int);
       +ulong        sourceGetDirSize(Source*);
       +int        sourceGetEntry(Source*, Entry*);
       +uvlong        sourceGetSize(Source*);
       +int        sourceLock2(Source*, Source*, int);
       +int        sourceLock(Source*, int);
       +char        *sourceName(Source *s);
       +Source*        sourceOpen(Source*, ulong, int, int);
       +int        sourceRemove(Source*);
       +Source*        sourceRoot(Fs*, u32int, int);
       +int        sourceSetDirSize(Source*, ulong);
       +int        sourceSetEntry(Source*, Entry*);
       +int        sourceSetSize(Source*, uvlong);
       +int        sourceTruncate(Source*);
       +void        sourceUnlock(Source*);
       +
       +Block*        cacheAllocBlock(Cache*, int, u32int, u32int, u32int);
       +Cache*        cacheAlloc(Disk*, VtSession*, ulong, int);
       +void        cacheCountUsed(Cache*, u32int, u32int*, u32int*, u32int*);
       +int        cacheDirty(Cache*);
       +void        cacheFlush(Cache*, int);
       +void        cacheFree(Cache*);
       +Block*        cacheGlobal(Cache*, uchar[VtScoreSize], int, u32int, int);
       +Block*        cacheLocal(Cache*, int, u32int, int);
       +Block*        cacheLocalData(Cache*, u32int, int, u32int, int, u32int);
       +u32int        cacheLocalSize(Cache*, int);
       +int        readLabel(Cache*, Label*, u32int addr);
       +
       +Block*        blockCopy(Block*, u32int, u32int, u32int);
       +void        blockDependency(Block*, Block*, int, uchar*, Entry*);
       +int        blockDirty(Block*);
       +void        blockDupLock(Block*);
       +void        blockPut(Block*);
       +void        blockRemoveLink(Block*, u32int, int, u32int, int);
       +uchar*        blockRollback(Block*, uchar*);
       +void        blockSetIOState(Block*, int);
       +Block*        _blockSetLabel(Block*, Label*);
       +int        blockSetLabel(Block*, Label*, int);
       +int        blockWrite(Block*, int);
       +
       +Disk*        diskAlloc(int);
       +int        diskBlockSize(Disk*);
       +int        diskFlush(Disk*);
       +void        diskFree(Disk*);
       +void        diskRead(Disk*, Block*);
       +int        diskReadRaw(Disk*, int, u32int, uchar*);
       +u32int        diskSize(Disk*, int);
       +void        diskWriteAndWait(Disk*,        Block*);
       +void        diskWrite(Disk*, Block*);
       +int        diskWriteRaw(Disk*, int, u32int, uchar*);
       +
       +char*        bioStr(int);
       +char*        bsStr(int);
       +char*        btStr(int);
       +u32int        globalToLocal(uchar[VtScoreSize]);
       +void        localToGlobal(u32int, uchar[VtScoreSize]);
       +
       +void        headerPack(Header*, uchar*);
       +int        headerUnpack(Header*, uchar*);
       +
       +int        labelFmt(Fmt*);
       +void        labelPack(Label*, uchar*, int);
       +int        labelUnpack(Label*, uchar*, int);
       +
       +int        scoreFmt(Fmt*);
       +
       +void        superPack(Super*, uchar*);
       +int        superUnpack(Super*, uchar*);
       +
       +void        entryPack(Entry*, uchar*, int);
       +int        entryType(Entry*);
       +int        entryUnpack(Entry*, uchar*, int);
       +
       +Periodic* periodicAlloc(void (*)(void*), void*, int);
       +void        periodicKill(Periodic*);
       +
       +int        fileGetSources(File*, Entry*, Entry*);
       +File*        fileRoot(Source*);
       +int        fileSnapshot(File*, File*, u32int, int);
       +int        fsNextQid(Fs*, u64int*);
       +int        mkVac(VtSession*, uint, Entry*, Entry*, DirEntry*, uchar[VtScoreSize]);
       +Block*        superGet(Cache*, Super*);
       +
       +void        archFree(Arch*);
       +Arch*        archInit(Cache*, Disk*, Fs*, VtSession*);
       +void        archKick(Arch*);
       +
       +void        bwatchDependency(Block*);
       +void        bwatchInit(void);
       +void        bwatchLock(Block*);
       +void        bwatchReset(uchar[VtScoreSize]);
       +void        bwatchSetBlockSize(uint);
       +void        bwatchUnlock(Block*);
       +
       +void        initWalk(WalkPtr*, Block*, uint);
       +int        nextWalk(WalkPtr*, uchar[VtScoreSize], uchar*, u32int*, Entry**);
       +
       +void        snapGetTimes(Snap*, u32int*, u32int*, u32int*);
       +void        snapSetTimes(Snap*, u32int, u32int, u32int);
       +
       +void        fsCheck(Fsck*);
       +
       +#pragma varargck type "L" Label*
 (DIR) diff --git a/src/cmd/fossil/fossil.c b/src/cmd/fossil/fossil.c
       t@@ -0,0 +1,143 @@
       +#include "stdinc.h"
       +#include <ctype.h>
       +
       +#include "9.h"
       +
       +int Dflag;
       +int mempcnt;                        /* for 9fsys.c */
       +char* none = "none";
       +char* foptname = "/none/such";
       +
       +static void
       +usage(void)
       +{
       +        fprint(2, "usage: %s [-Dt] [-c cmd] [-f partition] [-m %%]\n", argv0);
       +        exits("usage");
       +}
       +
       +static void
       +readCmdPart(char *file, char ***pcmd, int *pncmd)
       +{
       +        char buf[1024+1], *f[1024];
       +        char tbuf[1024];
       +        int nf;
       +        int i, fd, n;
       +        char **cmd, *p;
       +        int ncmd;
       +
       +        cmd = *pcmd;
       +        ncmd = *pncmd;
       +
       +        if((fd = open(file, OREAD)) < 0)
       +                sysfatal("open %s: %r", file);
       +        if(seek(fd, 127*1024, 0) != 127*1024)
       +                sysfatal("seek %s 127kB: %r", file);
       +        n = readn(fd, buf, sizeof buf-1);
       +        if(n == 0)
       +                sysfatal("short read of %s at 127kB", file);
       +        if(n < 0)
       +                sysfatal("read %s: %r", file);
       +        buf[n] = 0;
       +        if(memcmp(buf, "fossil config\n", 6+1+6+1) != 0)
       +                sysfatal("bad config magic in %s", file);
       +        nf = getfields(buf+6+1+6+1, f, nelem(f), 1, "\n");
       +        for(i=0; i<nf; i++){
       +                if(f[i][0] == '#')
       +                        continue;
       +                cmd = vtMemRealloc(cmd, (ncmd+1)*sizeof(char*));
       +                /* expand argument '*' to mean current file */
       +                if((p = strchr(f[i], '*')) && (p==f[i]||isspace(p[-1])) && (p[1]==0||isspace(p[1]))){
       +                        memmove(tbuf, f[i], p-f[i]);
       +                        strecpy(tbuf+(p-f[i]), tbuf+sizeof tbuf, file);
       +                        strecpy(tbuf+strlen(tbuf), tbuf+sizeof tbuf, p+1);
       +                        f[i] = tbuf;
       +                }
       +                cmd[ncmd++] = vtStrDup(f[i]);
       +        }
       +        close(fd);
       +        *pcmd = cmd;
       +        *pncmd = ncmd;
       +}
       +
       +void
       +main(int argc, char* argv[])
       +{
       +        char **cmd, *p;
       +        int i, ncmd, tflag;
       +
       +        fmtinstall('D', dirfmt);
       +        fmtinstall('F', fcallfmt);
       +        fmtinstall('M', dirmodefmt);
       +        quotefmtinstall();
       +
       +        /*
       +         * Insulate from the invoker's environment.
       +         */
       +        if(rfork(RFREND|RFNOTEG|RFNAMEG) < 0)
       +                sysfatal("rfork: %r");
       +
       +        close(0);
       +        open("/dev/null", OREAD);
       +        close(1);
       +        open("/dev/null", OWRITE);
       +
       +        cmd = nil;
       +        ncmd = tflag = 0;
       +
       +        vtAttach();
       +
       +        ARGBEGIN{
       +        case '?':
       +        default:
       +                usage();
       +                break;
       +        case 'c':
       +                p = EARGF(usage());
       +                currfsysname = p;
       +                cmd = vtMemRealloc(cmd, (ncmd+1)*sizeof(char*));
       +                cmd[ncmd++] = p;
       +                break;
       +        case 'D':
       +                Dflag ^= 1;
       +                break;
       +        case 'f':
       +                p = EARGF(usage());
       +                currfsysname = foptname = p;
       +                readCmdPart(p, &cmd, &ncmd);
       +                break;
       +        case 'm':
       +                mempcnt = atoi(EARGF(usage()));
       +                if(mempcnt <= 0 || mempcnt >= 100)
       +                        usage();
       +                break;
       +        case 't':
       +                tflag = 1;
       +                break;
       +        }ARGEND
       +        if(argc != 0)
       +                usage();
       +
       +        consInit();
       +        cliInit();
       +        msgInit();
       +        conInit();
       +        cmdInit();
       +        fsysInit();
       +        exclInit();
       +        fidInit();
       +
       +        srvInit();
       +        lstnInit();
       +        usersInit();
       +
       +        for(i = 0; i < ncmd; i++)
       +                if(cliExec(cmd[i]) == 0)
       +                        fprint(2, "%s: %R\n", cmd[i]);
       +        vtMemFree(cmd);
       +
       +        if(tflag && consTTY() == 0)
       +                consPrint("%s\n", vtGetError());
       +
       +        vtDetach();
       +        exits(0);
       +}
 (DIR) diff --git a/src/cmd/fossil/fs.c b/src/cmd/fossil/fs.c
       t@@ -0,0 +1,1099 @@
       +#include "stdinc.h"
       +#include "dat.h"
       +#include "fns.h"
       +#include "error.h"
       +
       +static void fsMetaFlush(void *a);
       +static Snap *snapInit(Fs*);
       +static void snapClose(Snap*);
       +
       +Fs *
       +fsOpen(char *file, VtSession *z, long ncache, int mode)
       +{
       +        int fd, m;
       +        uchar oscore[VtScoreSize];
       +        Block *b, *bs;
       +        Disk *disk;
       +        Fs *fs;
       +        Super super;
       +
       +        switch(mode){
       +        default:
       +                vtSetError(EBadMode);
       +                return nil;
       +        case OReadOnly:
       +                m = OREAD;
       +                break;
       +        case OReadWrite:
       +                m = ORDWR;
       +                break;
       +        }
       +        fd = open(file, m);
       +        if(fd < 0){
       +                vtSetError("open %s: %r", file);
       +                return nil;
       +        }
       +
       +        bwatchInit();
       +        disk = diskAlloc(fd);
       +        if(disk == nil){
       +                vtSetError("diskAlloc: %R");
       +                close(fd);
       +                return nil;
       +        }
       +
       +        fs = vtMemAllocZ(sizeof(Fs));
       +        fs->mode = mode;
       +        fs->name = vtStrDup(file);
       +        fs->blockSize = diskBlockSize(disk);
       +        fs->elk = vtLockAlloc();
       +        fs->cache = cacheAlloc(disk, z, ncache, mode);
       +        if(mode == OReadWrite && z)
       +                fs->arch = archInit(fs->cache, disk, fs, z);
       +        fs->z = z;
       +
       +        b = cacheLocal(fs->cache, PartSuper, 0, mode);
       +        if(b == nil)
       +                goto Err;
       +        if(!superUnpack(&super, b->data)){
       +                blockPut(b);
       +                vtSetError("bad super block");
       +                goto Err;
       +        }
       +        blockPut(b);
       +
       +        fs->ehi = super.epochHigh;
       +        fs->elo = super.epochLow;
       +
       +//fprint(2, "%s: fs->ehi %d fs->elo %d active=%d\n", argv0, fs->ehi, fs->elo, super.active);
       +
       +        fs->source = sourceRoot(fs, super.active, mode);
       +        if(fs->source == nil){
       +                /*
       +                 * Perhaps it failed because the block is copy-on-write.
       +                 * Do the copy and try again.
       +                 */
       +                if(mode == OReadOnly || strcmp(vtGetError(), EBadRoot) != 0)
       +                        goto Err;
       +                b = cacheLocalData(fs->cache, super.active, BtDir, RootTag,
       +                        OReadWrite, 0);
       +                if(b == nil){
       +                        vtSetError("cacheLocalData: %R");
       +                        goto Err;
       +                }
       +                if(b->l.epoch == fs->ehi){
       +                        blockPut(b);
       +                        vtSetError("bad root source block");
       +                        goto Err;
       +                }
       +                b = blockCopy(b, RootTag, fs->ehi, fs->elo);
       +                if(b == nil)
       +                        goto Err;
       +                localToGlobal(super.active, oscore);
       +                super.active = b->addr;
       +                bs = cacheLocal(fs->cache, PartSuper, 0, OReadWrite);
       +                if(bs == nil){
       +                        blockPut(b);
       +                        vtSetError("cacheLocal: %R");
       +                        goto Err;
       +                }
       +                superPack(&super, bs->data);
       +                blockDependency(bs, b, 0, oscore, nil);
       +                blockPut(b);
       +                blockDirty(bs);
       +                blockRemoveLink(bs, globalToLocal(oscore), BtDir, RootTag, 0);
       +                blockPut(bs);
       +                fs->source = sourceRoot(fs, super.active, mode);
       +                if(fs->source == nil){
       +                        vtSetError("sourceRoot: %R");
       +                        goto Err;
       +                }
       +        }
       +
       +//fprint(2, "%s: got fs source\n", argv0);
       +
       +        vtRLock(fs->elk);
       +        fs->file = fileRoot(fs->source);
       +        fs->source->file = fs->file;                /* point back */
       +        vtRUnlock(fs->elk);
       +        if(fs->file == nil){
       +                vtSetError("fileRoot: %R");
       +                goto Err;
       +        }
       +
       +//fprint(2, "%s: got file root\n", argv0);
       +
       +        if(mode == OReadWrite){
       +                fs->metaFlush = periodicAlloc(fsMetaFlush, fs, 1000);
       +                fs->snap = snapInit(fs);
       +        }
       +        return fs;
       +
       +Err:
       +fprint(2, "%s: fsOpen error\n", argv0);
       +        fsClose(fs);
       +        return nil;
       +}
       +
       +void
       +fsClose(Fs *fs)
       +{
       +        vtRLock(fs->elk);
       +        periodicKill(fs->metaFlush);
       +        snapClose(fs->snap);
       +        if(fs->file){
       +                fileMetaFlush(fs->file, 0);
       +                if(!fileDecRef(fs->file))
       +                        vtFatal("fsClose: files still in use: %r\n");
       +        }
       +        fs->file = nil;
       +        sourceClose(fs->source);
       +        cacheFree(fs->cache);
       +        if(fs->arch)
       +                archFree(fs->arch);
       +        vtMemFree(fs->name);
       +        vtRUnlock(fs->elk);
       +        vtLockFree(fs->elk);
       +        memset(fs, ~0, sizeof(Fs));
       +        vtMemFree(fs);
       +}
       +
       +int
       +fsRedial(Fs *fs, char *host)
       +{
       +        if(!vtRedial(fs->z, host))
       +                return 0;
       +        if(!vtConnect(fs->z, 0))
       +                return 0;
       +        return 1;
       +}
       +
       +File *
       +fsGetRoot(Fs *fs)
       +{
       +        return fileIncRef(fs->file);
       +}
       +
       +int
       +fsGetBlockSize(Fs *fs)
       +{
       +        return fs->blockSize;
       +}
       +
       +Block*
       +superGet(Cache *c, Super* super)
       +{
       +        Block *b;
       +
       +        if((b = cacheLocal(c, PartSuper, 0, OReadWrite)) == nil){
       +                fprint(2, "%s: superGet: cacheLocal failed: %R\n", argv0);
       +                return nil;
       +        }
       +        if(!superUnpack(super, b->data)){
       +                fprint(2, "%s: superGet: superUnpack failed: %R\n", argv0);
       +                blockPut(b);
       +                return nil;
       +        }
       +
       +        return b;
       +}
       +
       +void
       +superWrite(Block* b, Super* super, int forceWrite)
       +{
       +        superPack(super, b->data);
       +        blockDirty(b);
       +        if(forceWrite){
       +                while(!blockWrite(b, Waitlock)){
       +                        /* this should no longer happen */
       +                        fprint(2, "%s: could not write super block; "
       +                                "waiting 10 seconds\n", argv0);
       +                        sleep(10*1000);
       +                }
       +                while(b->iostate != BioClean && b->iostate != BioDirty){
       +                        assert(b->iostate == BioWriting);
       +                        vtSleep(b->ioready);
       +                }
       +                /*
       +                 * it's okay that b might still be dirty.
       +                 * that means it got written out but with an old root pointer,
       +                 * but the other fields went out, and those are the ones
       +                 * we really care about.  (specifically, epochHigh; see fsSnapshot).
       +                 */
       +        }
       +}
       +
       +/*
       + * Prepare the directory to store a snapshot.
       + * Temporary snapshots go into /snapshot/yyyy/mmdd/hhmm[.#]
       + * Archival snapshots go into /archive/yyyy/mmdd[.#].
       + *
       + * TODO This should be rewritten to eliminate most of the duplication.
       + */
       +static File*
       +fileOpenSnapshot(Fs *fs, char *dstpath, int doarchive)
       +{
       +        int n;
       +        char buf[30], *s, *p, *elem;
       +        File *dir, *f;
       +        Tm now;
       +
       +        if(dstpath){
       +                if((p = strrchr(dstpath, '/')) != nil){
       +                        *p++ = '\0';
       +                        elem = p;
       +                        p = dstpath;
       +                        if(*p == '\0')
       +                                p = "/";
       +                }else{
       +                        p = "/";
       +                        elem = dstpath;
       +                }
       +                if((dir = fileOpen(fs, p)) == nil)
       +                        return nil;
       +                f = fileCreate(dir, elem, ModeDir|ModeSnapshot|0555, "adm");
       +                fileDecRef(dir);
       +                return f;
       +        }else if(doarchive){
       +                /*
       +                 * a snapshot intended to be archived to venti.
       +                 */
       +                dir = fileOpen(fs, "/archive");
       +                if(dir == nil)
       +                        return nil;
       +                now = *localtime(time(0));
       +
       +                /* yyyy */
       +                snprint(buf, sizeof(buf), "%d", now.year+1900);
       +                f = fileWalk(dir, buf);
       +                if(f == nil)
       +                        f = fileCreate(dir, buf, ModeDir|0555, "adm");
       +                fileDecRef(dir);
       +                if(f == nil)
       +                        return nil;
       +                dir = f;
       +
       +                /* mmdd[#] */
       +                snprint(buf, sizeof(buf), "%02d%02d", now.mon+1, now.mday);
       +                s = buf+strlen(buf);
       +                for(n=0;; n++){
       +                        if(n)
       +                                seprint(s, buf+sizeof(buf), ".%d", n);
       +                        f = fileWalk(dir, buf);
       +                        if(f != nil){
       +                                fileDecRef(f);
       +                                continue;
       +                        }
       +                        f = fileCreate(dir, buf, ModeDir|ModeSnapshot|0555, "adm");
       +                        break;
       +                }
       +                fileDecRef(dir);
       +                return f;
       +        }else{
       +                /*
       +                 * Just a temporary snapshot
       +                 * We'll use /snapshot/yyyy/mmdd/hhmm.
       +                 * There may well be a better naming scheme.
       +                 * (I'd have used hh:mm but ':' is reserved in Microsoft file systems.)
       +                 */
       +                dir = fileOpen(fs, "/snapshot");
       +                if(dir == nil)
       +                        return nil;
       +
       +                now = *localtime(time(0));
       +
       +                /* yyyy */
       +                snprint(buf, sizeof(buf), "%d", now.year+1900);
       +                f = fileWalk(dir, buf);
       +                if(f == nil)
       +                        f = fileCreate(dir, buf, ModeDir|0555, "adm");
       +                fileDecRef(dir);
       +                if(f == nil)
       +                        return nil;
       +                dir = f;
       +
       +                /* mmdd */
       +                snprint(buf, sizeof(buf), "%02d%02d", now.mon+1, now.mday);
       +                f = fileWalk(dir, buf);
       +                if(f == nil)
       +                        f = fileCreate(dir, buf, ModeDir|0555, "adm");
       +                fileDecRef(dir);
       +                if(f == nil)
       +                        return nil;
       +                dir = f;
       +
       +                /* hhmm[.#] */
       +                snprint(buf, sizeof buf, "%02d%02d", now.hour, now.min);
       +                s = buf+strlen(buf);
       +                for(n=0;; n++){
       +                        if(n)
       +                                seprint(s, buf+sizeof(buf), ".%d", n);
       +                        f = fileWalk(dir, buf);
       +                        if(f != nil){
       +                                fileDecRef(f);
       +                                continue;
       +                        }
       +                        f = fileCreate(dir, buf, ModeDir|ModeSnapshot|0555, "adm");
       +                        break;
       +                }
       +                fileDecRef(dir);
       +                return f;
       +        }
       +}
       +
       +static int
       +fsNeedArch(Fs *fs, uint archMinute)
       +{
       +        int need;
       +        File *f;
       +        char buf[100];
       +        Tm now;
       +        ulong then;
       +
       +        then = time(0);
       +        now = *localtime(then);
       +
       +        /* back up to yesterday if necessary */
       +        if(now.hour < archMinute/60
       +        || now.hour == archMinute/60 && now.min < archMinute%60)
       +                now = *localtime(then-86400);
       +
       +        snprint(buf, sizeof buf, "/archive/%d/%02d%02d",
       +                now.year+1900, now.mon+1, now.mday);
       +        need = 1;
       +        vtRLock(fs->elk);
       +        f = fileOpen(fs, buf);
       +        if(f){
       +                need = 0;
       +                fileDecRef(f);
       +        }
       +        vtRUnlock(fs->elk);
       +        return need;
       +}
       +
       +int
       +fsEpochLow(Fs *fs, u32int low)
       +{
       +        Block *bs;
       +        Super super;
       +
       +        vtLock(fs->elk);
       +        if(low > fs->ehi){
       +                vtSetError("bad low epoch (must be <= %ud)", fs->ehi);
       +                vtUnlock(fs->elk);
       +                return 0;
       +        }
       +
       +        if((bs = superGet(fs->cache, &super)) == nil){
       +                vtUnlock(fs->elk);
       +                return 0;
       +        }
       +
       +        super.epochLow = low;
       +        fs->elo = low;
       +        superWrite(bs, &super, 1);
       +        blockPut(bs);
       +        vtUnlock(fs->elk);
       +
       +        return 1;
       +}
       +
       +static int
       +bumpEpoch(Fs *fs, int doarchive)
       +{
       +        uchar oscore[VtScoreSize];
       +        u32int oldaddr;
       +        Block *b, *bs;
       +        Entry e;
       +        Source *r;
       +        Super super;
       +
       +        /*
       +         * Duplicate the root block.
       +         *
       +         * As a hint to flchk, the garbage collector,
       +         * and any (human) debuggers, store a pointer
       +         * to the old root block in entry 1 of the new root block.
       +         */
       +        r = fs->source;
       +        b = cacheGlobal(fs->cache, r->score, BtDir, RootTag, OReadOnly);
       +        if(b == nil)
       +                return 0;
       +
       +        memset(&e, 0, sizeof e);
       +        e.flags = VtEntryActive | VtEntryLocal | VtEntryDir;
       +        memmove(e.score, b->score, VtScoreSize);
       +        e.tag = RootTag;
       +        e.snap = b->l.epoch;
       +
       +        b = blockCopy(b, RootTag, fs->ehi+1, fs->elo);
       +        if(b == nil){
       +                fprint(2, "%s: bumpEpoch: blockCopy: %R\n", argv0);
       +                return 0;
       +        }
       +
       +        if(0) fprint(2, "%s: snapshot root from %d to %d\n", argv0, oldaddr, b->addr);
       +        entryPack(&e, b->data, 1);
       +        blockDirty(b);
       +
       +        /*
       +         * Update the superblock with the new root and epoch.
       +         */
       +        if((bs = superGet(fs->cache, &super)) == nil)
       +                return 0;
       +
       +        fs->ehi++;
       +        memmove(r->score, b->score, VtScoreSize);
       +        r->epoch = fs->ehi;
       +
       +        super.epochHigh = fs->ehi;
       +        oldaddr = super.active;
       +        super.active = b->addr;
       +        if(doarchive)
       +                super.next = oldaddr;
       +
       +        /*
       +         * Record that the new super.active can't get written out until
       +         * the new b gets written out.  Until then, use the old value.
       +         */
       +        localToGlobal(oldaddr, oscore);
       +        blockDependency(bs, b, 0, oscore, nil);
       +        blockPut(b);
       +
       +        /*
       +         * We force the super block to disk so that super.epochHigh gets updated.
       +         * Otherwise, if we crash and come back, we might incorrectly treat as active
       +         * some of the blocks that making up the snapshot we just created.
       +         * Basically every block in the active file system and all the blocks in
       +         * the recently-created snapshot depend on the super block now.
       +         * Rather than record all those dependencies, we just force the block to disk.
       +         *
       +         * Note that blockWrite might actually (will probably) send a slightly outdated
       +         * super.active to disk.  It will be the address of the most recent root that has
       +         * gone to disk.
       +         */
       +        superWrite(bs, &super, 1);
       +        blockRemoveLink(bs, globalToLocal(oscore), BtDir, RootTag, 0);
       +        blockPut(bs);
       +
       +        return 1;
       +}
       +
       +int
       +saveQid(Fs *fs)
       +{
       +        Block *b;
       +        Super super;
       +        u64int qidMax;
       +
       +        if((b = superGet(fs->cache, &super)) == nil)
       +                return 0;
       +        qidMax = super.qid;
       +        blockPut(b);
       +
       +        if(!fileSetQidSpace(fs->file, 0, qidMax))
       +                return 0;
       +
       +        return 1;
       +}
       +
       +int
       +fsSnapshot(Fs *fs, char *srcpath, char *dstpath, int doarchive)
       +{
       +        File *src, *dst;
       +
       +        assert(fs->mode == OReadWrite);
       +
       +        dst = nil;
       +
       +        if(fs->halted){
       +                vtSetError("file system is halted");
       +                return 0;
       +        }
       +
       +        /*
       +         * Freeze file system activity.
       +         */
       +        vtLock(fs->elk);
       +
       +        /*
       +         * Get the root of the directory we're going to save.
       +         */
       +        if(srcpath == nil)
       +                srcpath = "/active";
       +        src = fileOpen(fs, srcpath);
       +        if(src == nil)
       +                goto Err;
       +
       +        /*
       +         * It is important that we maintain the invariant that:
       +         *        if both b and bb are marked as Active with start epoch e
       +         *        and b points at bb, then no other pointers to bb exist.
       +         * 
       +         * When bb is unlinked from b, its close epoch is set to b's epoch.
       +         * A block with epoch == close epoch is
       +         * treated as free by cacheAllocBlock; this aggressively
       +         * reclaims blocks after they have been stored to Venti.
       +         *
       +         * Let's say src->source is block sb, and src->msource is block
       +         * mb.  Let's also say that block b holds the Entry structures for
       +         * both src->source and src->msource (their Entry structures might
       +         * be in different blocks, but the argument is the same).
       +         * That is, right now we have:
       +         *
       +         *        b        Active w/ epoch e, holds ptrs to sb and mb.
       +         *        sb        Active w/ epoch e.
       +         *        mb        Active w/ epoch e.
       +         *
       +         * With things as they are now, the invariant requires that
       +         * b holds the only pointers to sb and mb.  We want to record
       +         * pointers to sb and mb in new Entries corresponding to dst,
       +         * which breaks the invariant.  Thus we need to do something
       +         * about b.  Specifically, we bump the file system's epoch and
       +         * then rewalk the path from the root down to and including b.
       +         * This will copy-on-write as we walk, so now the state will be:
       +         *
       +         *        b        Snap w/ epoch e, holds ptrs to sb and mb.
       +         *        new-b        Active w/ epoch e+1, holds ptrs to sb and mb.
       +         *        sb        Active w/ epoch e.
       +         *        mb        Active w/ epoch e.
       +         *
       +         * In this state, it's perfectly okay to make more pointers to sb and mb.
       +         */
       +        if(!bumpEpoch(fs, 0) || !fileWalkSources(src))
       +                goto Err;
       +
       +        /*
       +         * Sync to disk.  I'm not sure this is necessary, but better safe than sorry.
       +         */
       +        cacheFlush(fs->cache, 1);
       +
       +        /*
       +         * Create the directory where we will store the copy of src.
       +         */
       +        dst = fileOpenSnapshot(fs, dstpath, doarchive);
       +        if(dst == nil)
       +                goto Err;
       +
       +        /*
       +         * Actually make the copy by setting dst's source and msource
       +         * to be src's.
       +         */
       +        if(!fileSnapshot(dst, src, fs->ehi-1, doarchive))
       +                goto Err;
       +
       +        fileDecRef(src);
       +        fileDecRef(dst);
       +        src = nil;
       +        dst = nil;
       +
       +        /*
       +         * Make another copy of the file system.  This one is for the
       +         * archiver, so that the file system we archive has the recently
       +         * added snapshot both in /active and in /archive/yyyy/mmdd[.#].
       +         */
       +        if(doarchive){
       +                if(!saveQid(fs))
       +                        goto Err;
       +                if(!bumpEpoch(fs, 1))
       +                        goto Err;
       +        }
       +
       +        vtUnlock(fs->elk);
       +
       +        /* BUG? can fs->arch fall out from under us here? */
       +        if(doarchive && fs->arch)
       +                archKick(fs->arch);
       +
       +        return 1;
       +
       +Err:
       +        fprint(2, "%s: fsSnapshot: %R\n", argv0);
       +        if(src)
       +                fileDecRef(src);
       +        if(dst)
       +                fileDecRef(dst);
       +        vtUnlock(fs->elk);
       +        return 0;
       +}
       +
       +int
       +fsVac(Fs *fs, char *name, uchar score[VtScoreSize])
       +{
       +        int r;
       +        DirEntry de;
       +        Entry e, ee;
       +        File *f;
       +
       +        vtRLock(fs->elk);
       +        f = fileOpen(fs, name);
       +        if(f == nil){
       +                vtRUnlock(fs->elk);
       +                return 0;
       +        }
       +
       +        if(!fileGetSources(f, &e, &ee) || !fileGetDir(f, &de)){
       +                fileDecRef(f);
       +                vtRUnlock(fs->elk);
       +                return 0;
       +        }
       +        fileDecRef(f);
       +
       +        r = mkVac(fs->z, fs->blockSize, &e, &ee, &de, score);
       +        vtRUnlock(fs->elk);
       +        return r;
       +}
       +
       +static int
       +vtWriteBlock(VtSession *z, uchar *buf, uint n, uint type, uchar score[VtScoreSize])
       +{
       +        if(!vtWrite(z, score, type, buf, n))
       +                return 0;
       +        if(!vtSha1Check(score, buf, n))
       +                return 0;
       +        return 1;
       +}
       +
       +int
       +mkVac(VtSession *z, uint blockSize, Entry *pe, Entry *pee, DirEntry *pde, uchar score[VtScoreSize])
       +{
       +        uchar buf[8192];
       +        int i;
       +        uchar *p;
       +        uint n;
       +        DirEntry de;
       +        Entry e, ee, eee;
       +        MetaBlock mb;
       +        MetaEntry me;
       +        VtRoot root;
       +
       +        e = *pe;
       +        ee = *pee;
       +        de = *pde;
       +
       +        if(globalToLocal(e.score) != NilBlock
       +        || (ee.flags&VtEntryActive && globalToLocal(ee.score) != NilBlock)){
       +                vtSetError("can only vac paths already stored on venti");
       +                return 0;
       +        }
       +
       +        /*
       +         * Build metadata source for root.
       +         */
       +        n = deSize(&de);
       +        if(n+MetaHeaderSize+MetaIndexSize > sizeof buf){
       +                vtSetError("DirEntry too big");
       +                return 0;
       +        }
       +        memset(buf, 0, sizeof buf);
       +        mbInit(&mb, buf, n+MetaHeaderSize+MetaIndexSize, 1);
       +        p = mbAlloc(&mb, n);
       +        if(p == nil)
       +                abort();
       +        mbSearch(&mb, de.elem, &i, &me);
       +        assert(me.p == nil);
       +        me.p = p;
       +        me.size = n;
       +        dePack(&de, &me);
       +        mbInsert(&mb, i, &me);
       +        mbPack(&mb);
       +
       +        eee.size = n+MetaHeaderSize+MetaIndexSize;
       +        if(!vtWriteBlock(z, buf, eee.size, VtDataType, eee.score))
       +                return 0;
       +        eee.psize = 8192;
       +        eee.dsize = 8192;
       +        eee.depth = 0;
       +        eee.flags = VtEntryActive;
       +
       +        /*
       +         * Build root source with three entries in it.
       +         */
       +        entryPack(&e, buf, 0);
       +        entryPack(&ee, buf, 1);
       +        entryPack(&eee, buf, 2);
       +
       +        n = VtEntrySize*3;
       +        memset(&root, 0, sizeof root);
       +        if(!vtWriteBlock(z, buf, n, VtDirType, root.score))
       +                return 0;
       +
       +        /*
       +         * Save root.
       +         */
       +        root.version = VtRootVersion;
       +        strecpy(root.type, root.type+sizeof root.type, "vac");
       +        strecpy(root.name, root.name+sizeof root.name, de.elem);
       +        root.blockSize = blockSize;
       +        vtRootPack(&root, buf);
       +        if(!vtWriteBlock(z, buf, VtRootSize, VtRootType, score))
       +                return 0;
       +
       +        return 1;
       +}
       +
       +int
       +fsSync(Fs *fs)
       +{
       +        vtLock(fs->elk);
       +        fileMetaFlush(fs->file, 1);
       +        cacheFlush(fs->cache, 1);
       +        vtUnlock(fs->elk);
       +        return 1;
       +}
       +
       +int
       +fsHalt(Fs *fs)
       +{
       +        vtLock(fs->elk);
       +        fs->halted = 1;
       +        fileMetaFlush(fs->file, 1);
       +        cacheFlush(fs->cache, 1);
       +        return 1;
       +}
       +
       +int
       +fsUnhalt(Fs *fs)
       +{
       +        if(!fs->halted)
       +                return 0;
       +        fs->halted = 0;
       +        vtUnlock(fs->elk);
       +        return 1;
       +}
       +
       +int
       +fsNextQid(Fs *fs, u64int *qid)
       +{
       +        Block *b;
       +        Super super;
       +
       +        if((b = superGet(fs->cache, &super)) == nil)
       +                return 0;
       +
       +        *qid = super.qid++;
       +
       +        /*
       +         * It's okay if the super block doesn't go to disk immediately,
       +         * since fileMetaAlloc will record a dependency between the
       +         * block holding this qid and the super block.  See file.c:/^fileMetaAlloc.
       +         */
       +        superWrite(b, &super, 0);
       +        blockPut(b);
       +        return 1;
       +}
       +
       +static void
       +fsMetaFlush(void *a)
       +{
       +        int rv;
       +        Fs *fs = a;
       +
       +        vtRLock(fs->elk);
       +        rv = fileMetaFlush(fs->file, 1);
       +        vtRUnlock(fs->elk);
       +        if(rv > 0)
       +                cacheFlush(fs->cache, 0);
       +}
       +
       +static int
       +fsEsearch1(File *f, char *path, u32int savetime, u32int *plo)
       +{
       +        int n, r;
       +        DirEntry de;
       +        DirEntryEnum *dee;
       +        File *ff;
       +        Entry e, ee;
       +        char *t;
       +
       +        dee = deeOpen(f);
       +        if(dee == nil)
       +                return 0;
       +
       +        n = 0;
       +        for(;;){
       +                r = deeRead(dee, &de);
       +                if(r <= 0)
       +                        break;
       +                if(de.mode & ModeSnapshot){
       +                        if((ff = fileWalk(f, de.elem)) != nil){
       +                                if(fileGetSources(ff, &e, &ee))
       +                                        if(de.mtime >= savetime && e.snap != 0)
       +                                                if(e.snap < *plo)
       +                                                        *plo = e.snap;
       +                                fileDecRef(ff);
       +                        }
       +                }
       +                else if(de.mode & ModeDir){
       +                        if((ff = fileWalk(f, de.elem)) != nil){
       +                                t = smprint("%s/%s", path, de.elem);
       +                                n += fsEsearch1(ff, t, savetime, plo);
       +                                vtMemFree(t);
       +                                fileDecRef(ff);
       +                        }
       +                }
       +                deCleanup(&de);
       +                if(r < 0)
       +                        break;
       +        }
       +        deeClose(dee);
       +
       +        return n;
       +}
       +
       +static int
       +fsEsearch(Fs *fs, char *path, u32int savetime, u32int *plo)
       +{
       +        int n;
       +        File *f;
       +        DirEntry de;
       +
       +        f = fileOpen(fs, path);
       +        if(f == nil)
       +                return 0;
       +        if(!fileGetDir(f, &de)){
       +                fileDecRef(f);
       +                return 0;
       +        }
       +        if((de.mode & ModeDir) == 0){
       +                fileDecRef(f);
       +                deCleanup(&de);
       +                return 0;
       +        }
       +        deCleanup(&de);
       +        n = fsEsearch1(f, path, savetime, plo);
       +        fileDecRef(f);
       +        return n;
       +}
       +
       +void
       +fsSnapshotCleanup(Fs *fs, u32int age)
       +{
       +        u32int lo;
       +
       +        /*
       +         * Find the best low epoch we can use,
       +         * given that we need to save all the unventied archives
       +         * and all the snapshots younger than age.
       +         */
       +        vtRLock(fs->elk);
       +        lo = fs->ehi;
       +        fsEsearch(fs, "/archive", 0, &lo);
       +        fsEsearch(fs, "/snapshot", time(0)-age*60, &lo);
       +        vtRUnlock(fs->elk);
       +
       +        fsEpochLow(fs, lo);
       +        fsSnapshotRemove(fs);
       +}
       +
       +/* remove all snapshots that have expired */
       +/* return number of directory entries remaining */
       +static int
       +fsRsearch1(File *f, char *s)
       +{
       +        int n, r;
       +        DirEntry de;
       +        DirEntryEnum *dee;
       +        File *ff;
       +        char *t;
       +
       +        dee = deeOpen(f);
       +        if(dee == nil)
       +                return 0;
       +
       +        n = 0;
       +        for(;;){
       +                r = deeRead(dee, &de);
       +                if(r <= 0)
       +                        break;
       +                n++;
       +                if(de.mode & ModeSnapshot){
       +                        if((ff = fileWalk(f, de.elem)) != nil)
       +                                fileDecRef(ff);
       +                        else if(strcmp(vtGetError(), ESnapOld) == 0){
       +                                if(fileClri(f, de.elem, "adm"))
       +                                        n--;
       +                        }
       +                }
       +                else if(de.mode & ModeDir){
       +                        if((ff = fileWalk(f, de.elem)) != nil){
       +                                t = smprint("%s/%s", s, de.elem);
       +                                if(fsRsearch1(ff, t) == 0)
       +                                        if(fileRemove(ff, "adm"))
       +                                                n--;
       +                                vtMemFree(t);
       +                                fileDecRef(ff);
       +                        }
       +                }
       +                deCleanup(&de);
       +                if(r < 0)
       +                        break;
       +        }
       +        deeClose(dee);
       +
       +        return n;
       +}
       +
       +static int
       +fsRsearch(Fs *fs, char *path)
       +{
       +        File *f;
       +        DirEntry de;
       +
       +        f = fileOpen(fs, path);
       +        if(f == nil)
       +                return 0;
       +        if(!fileGetDir(f, &de)){
       +                fileDecRef(f);
       +                return 0;
       +        }
       +        if((de.mode & ModeDir) == 0){
       +                fileDecRef(f);
       +                deCleanup(&de);
       +                return 0;
       +        }
       +        deCleanup(&de);
       +        fsRsearch1(f, path);
       +        fileDecRef(f);
       +        return 1;
       +}
       +
       +void
       +fsSnapshotRemove(Fs *fs)
       +{
       +        vtRLock(fs->elk);
       +        fsRsearch(fs, "/snapshot");
       +        vtRUnlock(fs->elk);
       +}
       +
       +struct Snap
       +{
       +        Fs        *fs;
       +        Periodic*tick;
       +        VtLock        *lk;
       +        uint        snapMinutes;
       +        uint        archMinute;
       +        uint        snapLife;
       +        u32int        lastSnap;
       +        u32int        lastArch;
       +        u32int        lastCleanup;
       +        uint        ignore;
       +};
       +
       +static void
       +snapEvent(void *v)
       +{
       +        Snap *s;
       +        u32int now, min;
       +        Tm tm;
       +        int need;
       +        u32int snaplife;
       +
       +        s = v;
       +
       +        now = time(0)/60;
       +        vtLock(s->lk);
       +
       +        /*
       +         * Snapshots happen every snapMinutes minutes.
       +         * If we miss a snapshot (for example, because we
       +         * were down), we wait for the next one.
       +         */
       +        if(s->snapMinutes != ~0 && s->snapMinutes != 0
       +        && now%s->snapMinutes==0 && now != s->lastSnap){
       +                if(!fsSnapshot(s->fs, nil, nil, 0))
       +                        fprint(2, "%s: fsSnapshot snap: %R\n", argv0);
       +                s->lastSnap = now;
       +        }
       +
       +        /*
       +         * Archival snapshots happen at archMinute.
       +         * If we miss an archive (for example, because we
       +         * were down), we do it as soon as possible.
       +         */
       +        tm = *localtime(now*60);
       +        min = tm.hour*60+tm.min;
       +        if(s->archMinute != ~0){
       +                need = 0;
       +                if(min == s->archMinute && now != s->lastArch)
       +                        need = 1;
       +                if(s->lastArch == 0){
       +                        s->lastArch = 1;
       +                        if(fsNeedArch(s->fs, s->archMinute))
       +                                need = 1;
       +                }
       +                if(need){
       +                        fsSnapshot(s->fs, nil, nil, 1);
       +                        s->lastArch = now;
       +                }
       +        }
       +
       +        /*
       +         * Snapshot cleanup happens every snaplife or every day.
       +         */
       +        snaplife = s->snapLife;
       +        if(snaplife == ~0)
       +                snaplife = 24*60;
       +        if(s->lastCleanup+snaplife < now){
       +                fsSnapshotCleanup(s->fs, s->snapLife);
       +                s->lastCleanup = now;
       +        }
       +        vtUnlock(s->lk);
       +}
       +
       +static Snap*
       +snapInit(Fs *fs)
       +{
       +        Snap *s;
       +
       +        s = vtMemAllocZ(sizeof(Snap));
       +        s->fs = fs;
       +        s->tick = periodicAlloc(snapEvent, s, 10*1000);
       +        s->lk = vtLockAlloc();
       +        s->snapMinutes = -1;
       +        s->archMinute = -1;
       +        s->snapLife = -1;
       +        s->ignore = 5*2;        /* wait five minutes for clock to stabilize */
       +        return s;
       +}
       +
       +void
       +snapGetTimes(Snap *s, u32int *arch, u32int *snap, u32int *snaplen)
       +{
       +        if(s == nil){
       +                *snap = -1;
       +                *arch = -1;
       +                *snaplen = -1;
       +                return;
       +        }
       +
       +        vtLock(s->lk);
       +        *snap = s->snapMinutes;
       +        *arch = s->archMinute;
       +        *snaplen = s->snapLife;
       +        vtUnlock(s->lk);
       +}
       +
       +void
       +snapSetTimes(Snap *s, u32int arch, u32int snap, u32int snaplen)
       +{
       +        if(s == nil)
       +                return;
       +
       +        vtLock(s->lk);
       +        s->snapMinutes = snap;
       +        s->archMinute = arch;
       +        s->snapLife = snaplen;
       +        vtUnlock(s->lk);
       +}
       +
       +static void
       +snapClose(Snap *s)
       +{
       +        if(s == nil)
       +                return;
       +
       +        periodicKill(s->tick);
       +        vtMemFree(s);
       +}
       +
 (DIR) diff --git a/src/cmd/fossil/fs.h b/src/cmd/fossil/fs.h
       t@@ -0,0 +1,67 @@
       +typedef struct Fs Fs;
       +typedef struct File File;
       +typedef struct DirEntryEnum DirEntryEnum;
       +
       +#pragma incomplete Fs
       +#pragma incomplete File
       +#pragma incomplete DirEntryEnum
       +
       +/* modes */
       +
       +enum {
       +        OReadOnly,
       +        OReadWrite,
       +        OOverWrite,
       +};
       +
       +extern char *currfsysname;
       +extern char *foptname;
       +
       +void        fsClose(Fs*);
       +int        fsEpochLow(Fs*, u32int);
       +File        *fsGetRoot(Fs*);
       +int        fsHalt(Fs*);
       +Fs        *fsOpen(char*, VtSession*, long, int);
       +int        fsRedial(Fs*, char*);
       +void        fsSnapshotCleanup(Fs*, u32int);
       +int        fsSnapshot(Fs*, char*, char*, int);
       +void        fsSnapshotRemove(Fs*);
       +int        fsSync(Fs*);
       +int        fsUnhalt(Fs*);
       +int        fsVac(Fs*, char*, uchar[VtScoreSize]);
       +
       +void        deeClose(DirEntryEnum*);
       +DirEntryEnum *deeOpen(File*);
       +int        deeRead(DirEntryEnum*, DirEntry*);
       +int        fileClri(File*, char*, char*);
       +int        fileClriPath(Fs*, char*, char*);
       +File        *fileCreate(File*, char*, ulong, char*);
       +int        fileDecRef(File*);
       +int        fileGetDir(File*, DirEntry*);
       +uvlong        fileGetId(File*);
       +ulong        fileGetMcount(File*);
       +ulong        fileGetMode(File*);
       +File        *fileGetParent(File*);
       +int        fileGetSize(File*, uvlong*);
       +File        *fileIncRef(File*);
       +int        fileIsDir(File*);
       +int        fileIsTemporary(File*);
       +int        fileIsAppend(File*);
       +int        fileIsExclusive(File*);
       +int        fileIsRoFs(File*);
       +int        fileIsRoot(File*);
       +int        fileMapBlock(File*, ulong, uchar[VtScoreSize], ulong);
       +int        fileMetaFlush(File*, int);
       +char        *fileName(File *f);
       +File        *fileOpen(Fs*, char*);
       +int        fileRead(File*, void *, int, vlong);
       +int        fileRemove(File*, char*);
       +int        fileSetDir(File*, DirEntry*, char*);
       +int        fileSetQidSpace(File*, u64int, u64int);
       +int        fileSetSize(File*, uvlong);
       +int        fileSync(File*);
       +int        fileTruncate(File*, char*);
       +File        *fileWalk(File*, char*);
       +File        *_fileWalk(File*, char*, int);
       +int        fileWalkSources(File*);
       +int        fileWrite(File*, void *, int, vlong, char*);
 (DIR) diff --git a/src/cmd/fossil/last.c b/src/cmd/fossil/last.c
       t@@ -0,0 +1,40 @@
       +#include <u.h>
       +#include <libc.h>
       +
       +void
       +usage(void)
       +{
       +        fprint(2, "usage: fossil/last disk\n");
       +        exits("usage");
       +}
       +
       +void
       +main(int argc, char **argv)
       +{
       +        int fd, bs, addr;
       +        char buf[20];
       +
       +        ARGBEGIN{
       +        default:
       +                usage();
       +        }ARGEND
       +
       +        if(argc != 1)
       +                usage();
       +
       +        if((fd = open(argv[0], OREAD)) < 0)
       +                sysfatal("open %s: %r", argv[0]);
       +
       +        werrstr("end of file");
       +        if(seek(fd, 131072, 0) < 0 || readn(fd, buf, 20) != 20)
       +                sysfatal("error reading %s: %r", argv[0]);
       +        fmtinstall('H', encodefmt);
       +        if(memcmp(buf, "\x37\x76\xAE\x89", 4) != 0)
       +                sysfatal("bad magic %.4H != 3776AE89", buf);
       +        bs = buf[7]|(buf[6]<<8);
       +        addr = (buf[8]<<24)|(buf[9]<<16)|(buf[10]<<8)|buf[11];
       +        if(seek(fd, (vlong)bs*addr+34, 0) < 0 || readn(fd, buf, 20) != 20)
       +                sysfatal("error reading %s: %r", argv[0]);
       +        print("vac:%.20lH\n", buf);
       +        exits(0);
       +}
 (DIR) diff --git a/src/cmd/fossil/mkfile b/src/cmd/fossil/mkfile
       t@@ -0,0 +1,136 @@
       +</$objtype/mkfile
       +BIN=/$objtype/bin/fossil
       +
       +TARG=fossil flchk flfmt conf last
       +
       +LIBFILES=\
       +        9p\
       +        9auth\
       +        9dir\
       +        9excl\
       +        9fid\
       +        9fsys\
       +        9lstn\
       +        9proc\
       +        9srv\
       +        9user\
       +        Ccmd\
       +        Ccli\
       +        Ccons\
       +        Clog\
       +        archive\
       +        nobwatch\
       +        cache\
       +        check\
       +        disk\
       +        error\
       +        file\
       +        fs\
       +        pack\
       +        periodic\
       +        source\
       +        vac\
       +        walk\
       +
       +LIBCFILES=${LIBFILES:%=%.c}
       +LIBOFILES=${LIBFILES:%=%.$O}
       +LIB=libfs.a$O
       +
       +HFILES=\
       +        /sys/include/oventi.h\
       +        stdinc.h\
       +        vac.h\
       +        dat.h\
       +        fns.h\
       +        fs.h\
       +        error.h\
       +        9.h\
       +        flfmt9660.h\
       +
       +CFILES=${TARG:%=%.c} $LIBCFILES flfmt9660.c
       +
       +UPDATE=\
       +        mkfile\
       +        $CFILES\
       +        $HFILES\
       +
       +default:V: all
       +
       +test:V: all
       +        rm -f /srv/test.fossil /srv/test.fscons
       +        slay 8.flfmt | rc
       +        slay 8.fossil | rc
       +        unmount /n/fossil || status=''
       +        {syscall seek 1 6400000000 0; echo} >>/tmp/fossil
       +        8.flfmt -y /tmp/fossil
       +        8.conf -w /tmp/fossil flproto
       +        8.fossil -f /tmp/fossil
       +        cat /srv/test.fscons &
       +        echo fsys main >>/srv/test.fscons
       +        mount /srv/test.fossil /n/fossil
       +        cd /n/fossil/tmp
       +        dd -bs 1048576 -count 256 -if /dev/zero -of a
       +        rm a
       +        echo sync >>/srv/test.fscons
       +        echo sync >>/srv/test.fscons
       +        echo sync >>/srv/test.fscons
       +        sleep 1
       +        echo sync >>/srv/test.fscons
       +        sleep 1
       +        echo sync >>/srv/test.fscons
       +        sleep 1
       +        echo sync >>/srv/test.fscons
       +        echo check >>/srv/test.fscons
       +        echo check >>/srv/test.fscons
       +        echo check >>/srv/test.fscons
       +
       +#        cp /env/timezone /n/fossil/tmp
       +#        cp /lib/words /n/fossil/tmp
       +#        dircp /n/sources/plan9/sys/src/cmd/aux /n/fossil/tmp
       +#        >/n/fossil/tmp/lis
       +#        chmod +t /n/fossil/tmp/lis
       +#        echo SHOULD NOT SEE THIS >>/n/fossil/tmp/lis
       +#        echo snap >>/srv/test.fscons
       +#        sleep 2
       +#        mount /srv/test.fossil /n/dump main/archive
       +#        cat /n/dump/*/*/tmp/lis
       +#        @{cd /n/fossil/tmp && time tar xTf /sys/src/cmd/fossil/test.tar}
       +#        unmount /n/fossil
       +#        rm /srv/fossil
       +
       +</sys/src/cmd/mkmany
       +
       +$LIB(%.$O):N: %.$O
       +$LIB:        ${LIBOFILES:%=$LIB(%)}
       +        names = `{echo $newprereq |sed 's/ /\n/g' |sed -n 's/'$LIB'\(([^)]+)\)/\1/gp'}
       +        ar vu $LIB $names
       +#        rm $names
       +
       +$O.flfmt: flfmt9660.$O
       +
       +flfmt%.$O: flfmt9660.h
       +
       +%.page:V: %.ps
       +        page -w $stem.ps
       +
       +%.ps:D: %.ms
       +        tbl $stem.ms | pic | eqn | troff -ms | lp -dstdout >$target
       +
       +bundle:V:
       +        rfork n
       +        ramfs -m /n/kremvax >[2]/dev/null
       +        bind -a /n/kremvax .
       +        cp /sys/doc/fossil.ms /sys/doc/fossil.ps /n/kremvax
       +        cp /sys/man/4/fossil /n/kremvax/fossil.4.man
       +        cp /sys/man/8/fossilcons /n/kremvax/fossilcons.8.man
       +        x=`{ls |grep -v 'TODO|test.tar|fossil.tar.gz'}
       +        tar c $x | gzip > fossil.tar.gz
       +
       +$O.conf:D: conf.rc
       +        {
       +                echo '#!/bin/rc'
       +                echo '# THIS FILE IS AUTOMATICALLY GENERATED'
       +                echo '# FROM /sys/src/cmd/fossil/conf.rc.  DO NOT EDIT.'
       +                echo 
       +                sed 1d conf.rc
       +        } >$target && chmod +x $target
 (DIR) diff --git a/src/cmd/fossil/nobwatch.c b/src/cmd/fossil/nobwatch.c
       t@@ -0,0 +1,39 @@
       +#include "stdinc.h"
       +#include "dat.h"
       +#include "fns.h"
       +#include "error.h"
       +
       +void
       +bwatchReset(uchar score[VtScoreSize])
       +{
       +        USED(score);
       +}
       +
       +void
       +bwatchInit(void)
       +{
       +}
       +
       +void
       +bwatchSetBlockSize(uint)
       +{
       +}
       +
       +void
       +bwatchDependency(Block *b)
       +{
       +        USED(b);
       +}
       +
       +void
       +bwatchLock(Block *b)
       +{
       +        USED(b);
       +}
       +
       +void
       +bwatchUnlock(Block *b)
       +{
       +        USED(b);
       +}
       +
 (DIR) diff --git a/src/cmd/fossil/pack.c b/src/cmd/fossil/pack.c
       t@@ -0,0 +1,225 @@
       +#include "stdinc.h"
       +#include "dat.h"
       +#include "fns.h"
       +#include "error.h"
       +
       +/*
       + * integer conversion routines
       + */
       +#define        U8GET(p)        ((p)[0])
       +#define        U16GET(p)        (((p)[0]<<8)|(p)[1])
       +#define        U32GET(p)        (((p)[0]<<24)|((p)[1]<<16)|((p)[2]<<8)|(p)[3])
       +#define        U48GET(p)        (((uvlong)U16GET(p)<<32)|(uvlong)U32GET((p)+2))
       +#define        U64GET(p)        (((uvlong)U32GET(p)<<32)|(uvlong)U32GET((p)+4))
       +
       +#define        U8PUT(p,v)        (p)[0]=(v)
       +#define        U16PUT(p,v)        (p)[0]=(v)>>8;(p)[1]=(v)
       +#define        U32PUT(p,v)        (p)[0]=(v)>>24;(p)[1]=(v)>>16;(p)[2]=(v)>>8;(p)[3]=(v)
       +#define        U48PUT(p,v,t32)        t32=(v)>>32;U16PUT(p,t32);t32=(v);U32PUT((p)+2,t32)
       +#define        U64PUT(p,v,t32)        t32=(v)>>32;U32PUT(p,t32);t32=(v);U32PUT((p)+4,t32)
       +
       +void
       +headerPack(Header *h, uchar *p)
       +{
       +        memset(p, 0, HeaderSize);
       +        U32PUT(p, HeaderMagic);
       +        U16PUT(p+4, HeaderVersion);
       +        U16PUT(p+6, h->blockSize);
       +        U32PUT(p+8, h->super);
       +        U32PUT(p+12, h->label);
       +        U32PUT(p+16, h->data);
       +        U32PUT(p+20, h->end);
       +}
       +
       +int
       +headerUnpack(Header *h, uchar *p)
       +{
       +        if(U32GET(p) != HeaderMagic){
       +                vtSetError("vac header bad magic");
       +                return 0;
       +        }
       +        h->version = U16GET(p+4);
       +        if(h->version != HeaderVersion){
       +                vtSetError("vac header bad version");
       +                return 0;
       +        }
       +        h->blockSize = U16GET(p+6);
       +        h->super = U32GET(p+8);
       +        h->label = U32GET(p+12);
       +        h->data = U32GET(p+16);
       +        h->end = U32GET(p+20);
       +        return 1;
       +}
       +
       +void
       +labelPack(Label *l, uchar *p, int i)
       +{
       +        p += i*LabelSize;
       +        U8PUT(p, l->state);
       +        U8PUT(p+1, l->type);
       +        U32PUT(p+2, l->epoch);
       +        U32PUT(p+6, l->epochClose);
       +        U32PUT(p+10, l->tag);
       +}
       +
       +int
       +labelUnpack(Label *l, uchar *p, int i)
       +{
       +        p += i*LabelSize;
       +        l->state = p[0];
       +        l->type = p[1];
       +        l->epoch = U32GET(p+2);
       +        l->epochClose = U32GET(p+6);
       +        l->tag = U32GET(p+10);
       +
       +        if(l->type > BtMax){
       +Bad:
       +                vtSetError(EBadLabel);
       +                fprint(2, "%s: labelUnpack: bad label: 0x%.2ux 0x%.2ux 0x%.8ux "
       +                        "0x%.8ux 0x%.8ux\n", argv0, l->state, l->type, l->epoch,
       +                        l->epochClose, l->tag);
       +                return 0;
       +        }
       +        if(l->state != BsBad && l->state != BsFree){
       +                if(!(l->state&BsAlloc) || l->state & ~BsMask)
       +                        goto Bad;
       +                if(l->state&BsClosed){
       +                        if(l->epochClose == ~(u32int)0)
       +                                goto Bad;
       +                }else{
       +                        if(l->epochClose != ~(u32int)0)
       +                                goto Bad;
       +                }
       +        }
       +        return 1;
       +}
       +
       +u32int
       +globalToLocal(uchar score[VtScoreSize])
       +{
       +        int i;
       +
       +        for(i=0; i<VtScoreSize-4; i++)
       +                if(score[i] != 0)
       +                        return NilBlock;
       +
       +        return U32GET(score+VtScoreSize-4);
       +}
       +
       +void
       +localToGlobal(u32int addr, uchar score[VtScoreSize])
       +{
       +        memset(score, 0, VtScoreSize-4);
       +        U32PUT(score+VtScoreSize-4, addr);
       +}
       +
       +void
       +entryPack(Entry *e, uchar *p, int index)
       +{
       +        ulong t32;
       +        int flags;
       +
       +        p += index * VtEntrySize;
       +
       +        U32PUT(p, e->gen);
       +        U16PUT(p+4, e->psize);
       +        U16PUT(p+6, e->dsize);
       +        flags = e->flags | ((e->depth << VtEntryDepthShift) & VtEntryDepthMask);
       +        U8PUT(p+8, flags);
       +        memset(p+9, 0, 5);
       +        U48PUT(p+14, e->size, t32);
       +
       +        if(flags & VtEntryLocal){
       +                if(globalToLocal(e->score) == NilBlock)
       +                        abort();
       +                memset(p+20, 0, 7);
       +                U8PUT(p+27, e->archive);
       +                U32PUT(p+28, e->snap);
       +                U32PUT(p+32, e->tag);
       +                memmove(p+36, e->score+16, 4);
       +        }else
       +                memmove(p+20, e->score, VtScoreSize);
       +}
       +
       +int
       +entryUnpack(Entry *e, uchar *p, int index)
       +{
       +        p += index * VtEntrySize;
       +
       +        e->gen = U32GET(p);
       +        e->psize = U16GET(p+4);
       +        e->dsize = U16GET(p+6);
       +        e->flags = U8GET(p+8);
       +        e->depth = (e->flags & VtEntryDepthMask) >> VtEntryDepthShift;
       +        e->flags &= ~VtEntryDepthMask;
       +        e->size = U48GET(p+14);
       +
       +        if(e->flags & VtEntryLocal){
       +                e->archive = p[27];
       +                e->snap = U32GET(p+28);
       +                e->tag = U32GET(p+32);
       +                memset(e->score, 0, 16);
       +                memmove(e->score+16, p+36, 4);
       +        }else{
       +                e->archive = 0;
       +                e->snap = 0;
       +                e->tag = 0;
       +                memmove(e->score, p+20, VtScoreSize);
       +        }
       +
       +        return 1;
       +}
       +
       +int
       +entryType(Entry *e)
       +{
       +        return (((e->flags & VtEntryDir) != 0) << 3) | e->depth;
       +}
       +
       +
       +void
       +superPack(Super *s, uchar *p)
       +{
       +        u32int t32;
       +
       +        memset(p, 0, SuperSize);
       +        U32PUT(p, SuperMagic);
       +        assert(s->version == SuperVersion);
       +        U16PUT(p+4, s->version);
       +        U32PUT(p+6, s->epochLow);
       +        U32PUT(p+10, s->epochHigh);
       +        U64PUT(p+14, s->qid, t32);
       +        U32PUT(p+22, s->active);
       +        U32PUT(p+26, s->next);
       +        U32PUT(p+30, s->current);
       +        memmove(p+34, s->last, VtScoreSize);
       +        memmove(p+54, s->name, sizeof(s->name));
       +}
       +
       +int
       +superUnpack(Super *s, uchar *p)
       +{
       +        memset(s, 0, sizeof(*s));
       +        if(U32GET(p) != SuperMagic)
       +                goto Err;
       +        s->version = U16GET(p+4);
       +        if(s->version != SuperVersion)
       +                goto Err;
       +        s->epochLow = U32GET(p+6);
       +        s->epochHigh = U32GET(p+10);
       +        s->qid = U64GET(p+14);
       +        if(s->epochLow == 0 || s->epochLow > s->epochHigh || s->qid == 0)
       +                goto Err;
       +        s->active = U32GET(p+22);
       +        s->next = U32GET(p+26);
       +        s->current = U32GET(p+30);
       +        memmove(s->last, p+34, VtScoreSize);
       +        memmove(s->name, p+54, sizeof(s->name));
       +        s->name[sizeof(s->name)-1] = 0;
       +        return 1;
       +Err:
       +        memset(s, 0, sizeof(*s));
       +        vtSetError(EBadSuper);
       +        return 0;
       +}
       +
 (DIR) diff --git a/src/cmd/fossil/periodic.c b/src/cmd/fossil/periodic.c
       t@@ -0,0 +1,84 @@
       +#include "stdinc.h"
       +#include "dat.h"
       +#include "fns.h"
       +#include "error.h"
       +
       +struct Periodic {
       +        VtLock *lk;
       +        int die;                /* flag: quit if set */
       +        void (*f)(void*);        /* call this each period */
       +        void *a;                /* argument to f */
       +        int msec;                /* period */
       +};
       +
       +static void periodicThread(void *a);
       +
       +Periodic *
       +periodicAlloc(void (*f)(void*), void *a, int msec)
       +{
       +        Periodic *p;
       +
       +        p = vtMemAllocZ(sizeof(Periodic));
       +        p->lk = vtLockAlloc();
       +        p->f = f;
       +        p->a = a;
       +        p->msec = msec;
       +        if(p->msec < 10)
       +                p->msec = 10;
       +
       +        vtThread(periodicThread, p);
       +        return p;
       +}
       +
       +void
       +periodicKill(Periodic *p)
       +{
       +        if(p == nil)
       +                return;
       +        vtLock(p->lk);
       +        p->die = 1;
       +        vtUnlock(p->lk);
       +}
       +
       +static void
       +periodicFree(Periodic *p)
       +{
       +        vtLockFree(p->lk);
       +        vtMemFree(p);
       +}
       +
       +static void
       +periodicThread(void *a)
       +{
       +        Periodic *p = a;
       +        vlong t, ct, ts;                /* times in ms. */
       +
       +        vtThreadSetName("periodic");
       +
       +        ct = nsec() / 1000000;
       +        t = ct + p->msec;                /* call p->f at or after this time */
       +
       +        for(;;){
       +                ts = t - ct;                /* ms. to next cycle's start */
       +                if(ts > 1000)
       +                        ts = 1000;        /* bound sleep duration */
       +                if(ts > 0)
       +                        sleep(ts);        /* wait for cycle's start */
       +
       +                vtLock(p->lk);
       +                if(p->die){
       +                        vtUnlock(p->lk);
       +                        break;
       +                }
       +                ct = nsec() / 1000000;
       +                if(t <= ct){                /* due to call p->f? */
       +                        p->f(p->a);
       +                        ct = nsec() / 1000000;
       +                        while(t <= ct)        /* advance t to future cycle start */
       +                                t += p->msec;
       +                }
       +                vtUnlock(p->lk);
       +        }
       +        periodicFree(p);
       +}
       +
 (DIR) diff --git a/src/cmd/fossil/source.c b/src/cmd/fossil/source.c
       t@@ -0,0 +1,1068 @@
       +#include "stdinc.h"
       +#include "dat.h"
       +#include "fns.h"
       +#include "error.h"
       +#include "9.h"
       +
       +static int        sizeToDepth(uvlong s, int psize, int dsize);
       +static u32int         tagGen(void);
       +static Block         *sourceLoad(Source *r, Entry *e);
       +static int        sourceShrinkDepth(Source*, Block*, Entry*, int);
       +static int        sourceShrinkSize(Source*, Entry*, uvlong);
       +static int        sourceGrowDepth(Source*, Block*, Entry*, int);
       +
       +#define sourceIsLocked(r)        ((r)->b != nil)
       +
       +static Source *
       +sourceAlloc(Fs *fs, Block *b, Source *p, u32int offset, int mode, int issnapshot)
       +{
       +        int epb;
       +        u32int epoch;
       +        char *pname = nil;
       +        Source *r;
       +        Entry e;
       +
       +        assert(p==nil || sourceIsLocked(p));
       +
       +        if(p == nil){
       +                assert(offset == 0);
       +                epb = 1;
       +        }else
       +                epb = p->dsize / VtEntrySize;
       +
       +        if(b->l.type != BtDir)
       +                goto Bad;
       +
       +        /*
       +         * a non-active entry is the only thing that
       +         * can legitimately happen here. all the others
       +         * get prints.
       +         */
       +        if(!entryUnpack(&e, b->data, offset % epb)){
       +                pname = sourceName(p);
       +                consPrint("%s: %s %V: sourceAlloc: entryUnpack failed\n",
       +                        fs->name, pname, b->score);
       +                goto Bad;
       +        }
       +        if(!(e.flags & VtEntryActive)){
       +                pname = sourceName(p);
       +                if(0) consPrint("%s: %s %V: sourceAlloc: not active\n",
       +                        fs->name, pname, e.score);
       +                goto Bad;
       +        }
       +        if(e.psize < 256 || e.dsize < 256){
       +                pname = sourceName(p);
       +                consPrint("%s: %s %V: sourceAlloc: psize %ud or dsize %ud < 256\n",
       +                        fs->name, pname, e.score, e.psize, e.dsize);
       +                goto Bad;
       +        }
       +
       +        if(e.depth < sizeToDepth(e.size, e.psize, e.dsize)){
       +                pname = sourceName(p);
       +                consPrint("%s: %s %V: sourceAlloc: depth %ud size %llud "
       +                        "psize %ud dsize %ud\n", fs->name, pname,
       +                        e.score, e.depth, e.size, e.psize, e.dsize);
       +                goto Bad;
       +        }
       +
       +        if((e.flags & VtEntryLocal) && e.tag == 0){
       +                pname = sourceName(p);
       +                consPrint("%s: %s %V: sourceAlloc: flags %#ux tag %#ux\n",
       +                        fs->name, pname, e.score, e.flags, e.tag);
       +                goto Bad;
       +        }
       +
       +        if(e.dsize > fs->blockSize || e.psize > fs->blockSize){
       +                pname = sourceName(p);
       +                consPrint("%s: %s %V: sourceAlloc: psize %ud or dsize %ud "
       +                        "> blocksize %ud\n", fs->name, pname, e.score,
       +                        e.psize, e.dsize, fs->blockSize);
       +                goto Bad;
       +        }
       +
       +        epoch = b->l.epoch;
       +        if(mode == OReadWrite){
       +                if(e.snap != 0){
       +                        vtSetError(ESnapRO);
       +                        return nil;
       +                }
       +        }else if(e.snap != 0){
       +                if(e.snap < fs->elo){
       +                        vtSetError(ESnapOld);
       +                        return nil;
       +                }
       +                if(e.snap >= fs->ehi)
       +                        goto Bad;
       +                epoch = e.snap;
       +        }
       +
       +        r = vtMemAllocZ(sizeof(Source));
       +        r->fs = fs;
       +        r->mode = mode;
       +        r->issnapshot = issnapshot;
       +        r->dsize = e.dsize;
       +        r->gen = e.gen;
       +        r->dir = (e.flags & VtEntryDir) != 0;
       +        r->lk = vtLockAlloc();
       +        r->ref = 1;
       +        r->parent = p;
       +        if(p){
       +                vtLock(p->lk);
       +                assert(mode == OReadOnly || p->mode == OReadWrite);
       +                p->ref++;
       +                vtUnlock(p->lk);
       +        }
       +        r->epoch = epoch;
       +//        consPrint("sourceAlloc: have %V be.%d fse.%d %s\n", b->score,
       +//                b->l.epoch, r->fs->ehi, mode == OReadWrite? "rw": "ro");
       +        memmove(r->score, b->score, VtScoreSize);
       +        r->scoreEpoch = b->l.epoch;
       +        r->offset = offset;
       +        r->epb = epb;
       +        r->tag = b->l.tag;
       +
       +//        consPrint("%s: sourceAlloc: %p -> %V %d\n", r, r->score, r->offset);
       +
       +        return r;
       +Bad:
       +        free(pname);
       +        vtSetError(EBadEntry);
       +        return nil;
       +}
       +
       +Source *
       +sourceRoot(Fs *fs, u32int addr, int mode)
       +{
       +        Source *r;
       +        Block *b;
       +
       +        b = cacheLocalData(fs->cache, addr, BtDir, RootTag, mode, 0);
       +        if(b == nil)
       +                return nil;
       +
       +        if(mode == OReadWrite && b->l.epoch != fs->ehi){
       +                consPrint("sourceRoot: fs->ehi = %ud, b->l = %L\n",
       +                        fs->ehi, &b->l);
       +                blockPut(b);
       +                vtSetError(EBadRoot);
       +                return nil;
       +        }
       +
       +        r = sourceAlloc(fs, b, nil, 0, mode, 0);
       +        blockPut(b);
       +        return r;
       +}
       +
       +Source *
       +sourceOpen(Source *r, ulong offset, int mode, int issnapshot)
       +{
       +        ulong bn;
       +        Block *b;
       +
       +        assert(sourceIsLocked(r));
       +        if(r->mode == OReadWrite)
       +                assert(r->epoch == r->b->l.epoch);
       +        if(!r->dir){
       +                vtSetError(ENotDir);
       +                return nil;
       +        }
       +
       +        bn = offset/(r->dsize/VtEntrySize);
       +
       +        b = sourceBlock(r, bn, mode);
       +        if(b == nil)
       +                return nil;
       +        r = sourceAlloc(r->fs, b, r, offset, mode, issnapshot);
       +        blockPut(b);
       +        return r;
       +}
       +
       +Source *
       +sourceCreate(Source *r, int dsize, int dir, u32int offset)
       +{
       +        int i, epb, psize;
       +        u32int bn, size;
       +        Block *b;
       +        Entry e;
       +        Source *rr;
       +
       +        assert(sourceIsLocked(r));
       +
       +        if(!r->dir){
       +                vtSetError(ENotDir);
       +                return nil;
       +        }
       +
       +        epb = r->dsize/VtEntrySize;
       +        psize = (dsize/VtScoreSize)*VtScoreSize;
       +
       +        size = sourceGetDirSize(r);
       +        if(offset == 0){
       +                /*
       +                 * look at a random block to see if we can find an empty entry
       +                 */
       +                offset = lnrand(size+1);
       +                offset -= offset % epb;
       +        }
       +
       +        /* try the given block and then try the last block */
       +        for(;;){
       +                bn = offset/epb;
       +                b = sourceBlock(r, bn, OReadWrite);
       +                if(b == nil)
       +                        return nil;
       +                for(i=offset%r->epb; i<epb; i++){
       +                        entryUnpack(&e, b->data, i);
       +                        if((e.flags&VtEntryActive) == 0 && e.gen != ~0)
       +                                goto Found;
       +                }
       +                blockPut(b);
       +                if(offset == size){
       +                        fprint(2, "sourceCreate: cannot happen\n");
       +                        vtSetError("sourceCreate: cannot happen");
       +                        return nil;
       +                }
       +                offset = size;
       +        }
       +
       +Found:
       +        /* found an entry - gen already set */
       +        e.psize = psize;
       +        e.dsize = dsize;
       +        assert(psize && dsize);
       +        e.flags = VtEntryActive;
       +        if(dir)
       +                e.flags |= VtEntryDir;
       +        e.depth = 0;
       +        e.size = 0;
       +        memmove(e.score, vtZeroScore, VtScoreSize);
       +        e.tag = 0;
       +        e.snap = 0;
       +        e.archive = 0;
       +        entryPack(&e, b->data, i);
       +        blockDirty(b);
       +
       +        offset = bn*epb + i;
       +        if(offset+1 > size){
       +                if(!sourceSetDirSize(r, offset+1)){
       +                        blockPut(b);
       +                        return nil;
       +                }
       +        }
       +
       +        rr = sourceAlloc(r->fs, b, r, offset, OReadWrite, 0);
       +        blockPut(b);
       +        return rr;
       +}
       +
       +static int
       +sourceKill(Source *r, int doremove)
       +{
       +        Entry e;
       +        Block *b;
       +        u32int addr;
       +        u32int tag;
       +        int type;
       +
       +        assert(sourceIsLocked(r));
       +        b = sourceLoad(r, &e);
       +        if(b == nil)
       +                return 0;
       +
       +        assert(b->l.epoch == r->fs->ehi);
       +
       +        if(doremove==0 && e.size == 0){
       +                /* already truncated */
       +                blockPut(b);
       +                return 1;
       +        }
       +
       +        /* remember info on link we are removing */
       +        addr = globalToLocal(e.score);
       +        type = entryType(&e);
       +        tag = e.tag;
       +
       +        if(doremove){
       +                if(e.gen != ~0)
       +                        e.gen++;
       +                e.dsize = 0;
       +                e.psize = 0;
       +                e.flags = 0;
       +        }else{
       +                e.flags &= ~VtEntryLocal;
       +        }
       +        e.depth = 0;
       +        e.size = 0;
       +        e.tag = 0;
       +        memmove(e.score, vtZeroScore, VtScoreSize);
       +        entryPack(&e, b->data, r->offset % r->epb);
       +        blockDirty(b);
       +        if(addr != NilBlock)
       +                blockRemoveLink(b, addr, type, tag, 1);
       +        blockPut(b);
       +
       +        if(doremove){
       +                sourceUnlock(r);
       +                sourceClose(r);
       +        }
       +
       +        return 1;
       +}
       +
       +int
       +sourceRemove(Source *r)
       +{
       +        return sourceKill(r, 1);
       +}
       +
       +int
       +sourceTruncate(Source *r)
       +{
       +        return sourceKill(r, 0);
       +}
       +
       +uvlong
       +sourceGetSize(Source *r)
       +{
       +        Entry e;
       +        Block *b;
       +
       +        assert(sourceIsLocked(r));
       +        b = sourceLoad(r, &e);
       +        if(b == nil)
       +                return 0;
       +        blockPut(b);
       +
       +        return e.size;
       +}
       +
       +static int
       +sourceShrinkSize(Source *r, Entry *e, uvlong size)
       +{
       +        int i, type, ppb;
       +        uvlong ptrsz;
       +        u32int addr;
       +        uchar score[VtScoreSize];
       +        Block *b;
       +
       +        type = entryType(e);
       +        b = cacheGlobal(r->fs->cache, e->score, type, e->tag, OReadWrite);
       +        if(b == nil)
       +                return 0;
       +
       +        ptrsz = e->dsize;
       +        ppb = e->psize/VtScoreSize;
       +        for(i=0; i+1<e->depth; i++)
       +                ptrsz *= ppb;
       +
       +        while(type&BtLevelMask){
       +                if(b->addr == NilBlock || b->l.epoch != r->fs->ehi){
       +                        /* not worth copying the block just so we can zero some of it */
       +                        blockPut(b);
       +                        return 0;
       +                }
       +
       +                /*
       +                 * invariant: each pointer in the tree rooted at b accounts for ptrsz bytes
       +                 */
       +
       +                /* zero the pointers to unnecessary blocks */
       +                i = (size+ptrsz-1)/ptrsz;
       +                for(; i<ppb; i++){
       +                        addr = globalToLocal(b->data+i*VtScoreSize);
       +                        memmove(b->data+i*VtScoreSize, vtZeroScore, VtScoreSize);
       +                        blockDirty(b);
       +                        if(addr != NilBlock)
       +                                blockRemoveLink(b, addr, type-1, e->tag, 1);
       +                }
       +
       +                /* recurse (go around again) on the partially necessary block */
       +                i = size/ptrsz;
       +                size = size%ptrsz;
       +                if(size == 0){
       +                        blockPut(b);
       +                        return 1;
       +                }
       +                ptrsz /= ppb;
       +                type--;
       +                memmove(score, b->data+i*VtScoreSize, VtScoreSize);
       +                blockPut(b);
       +                b = cacheGlobal(r->fs->cache, score, type, e->tag, OReadWrite);
       +                if(b == nil)
       +                        return 0;
       +        }
       +
       +        if(b->addr == NilBlock || b->l.epoch != r->fs->ehi){
       +                blockPut(b);
       +                return 0;
       +        }
       +
       +        /*
       +         * No one ever truncates BtDir blocks.
       +         */
       +        if(type == BtData && e->dsize > size){
       +                memset(b->data+size, 0, e->dsize-size);
       +                blockDirty(b);
       +        }
       +        blockPut(b);
       +        return 1;
       +}
       +
       +int
       +sourceSetSize(Source *r, uvlong size)
       +{
       +        int depth;
       +        Entry e;
       +        Block *b;
       +
       +        assert(sourceIsLocked(r));
       +        if(size == 0)
       +                return sourceTruncate(r);
       +
       +        if(size > VtMaxFileSize || size > ((uvlong)MaxBlock)*r->dsize){
       +                vtSetError(ETooBig);
       +                return 0;
       +        }
       +
       +        b = sourceLoad(r, &e);
       +        if(b == nil)
       +                return 0;
       +
       +        /* quick out */
       +        if(e.size == size){
       +                blockPut(b);
       +                return 1;
       +        }
       +
       +        depth = sizeToDepth(size, e.psize, e.dsize);
       +
       +        if(depth < e.depth){
       +                if(!sourceShrinkDepth(r, b, &e, depth)){
       +                        blockPut(b);
       +                        return 0;
       +                }
       +        }else if(depth > e.depth){
       +                if(!sourceGrowDepth(r, b, &e, depth)){
       +                        blockPut(b);
       +                        return 0;
       +                }
       +        }
       +
       +        if(size < e.size)
       +                sourceShrinkSize(r, &e, size);
       +
       +        e.size = size;
       +        entryPack(&e, b->data, r->offset % r->epb);
       +        blockDirty(b);
       +        blockPut(b);
       +
       +        return 1;
       +}
       +
       +int
       +sourceSetDirSize(Source *r, ulong ds)
       +{
       +        uvlong size;
       +        int epb;
       +
       +        assert(sourceIsLocked(r));
       +        epb = r->dsize/VtEntrySize;
       +
       +        size = (uvlong)r->dsize*(ds/epb);
       +        size += VtEntrySize*(ds%epb);
       +        return sourceSetSize(r, size);
       +}
       +
       +ulong
       +sourceGetDirSize(Source *r)
       +{
       +        ulong ds;
       +        uvlong size;
       +        int epb;
       +
       +        assert(sourceIsLocked(r));
       +        epb = r->dsize/VtEntrySize;
       +
       +        size = sourceGetSize(r);
       +        ds = epb*(size/r->dsize);
       +        ds += (size%r->dsize)/VtEntrySize;
       +        return ds;
       +}
       +
       +int
       +sourceGetEntry(Source *r, Entry *e)
       +{
       +        Block *b;
       +
       +        assert(sourceIsLocked(r));
       +        b = sourceLoad(r, e);
       +        if(b == nil)
       +                return 0;
       +        blockPut(b);
       +
       +        return 1;
       +}
       +
       +/*
       + * Must be careful with this.  Doesn't record
       + * dependencies, so don't introduce any!
       + */
       +int
       +sourceSetEntry(Source *r, Entry *e)
       +{
       +        Block *b;
       +        Entry oe;
       +
       +        assert(sourceIsLocked(r));
       +        b = sourceLoad(r, &oe);
       +        if(b == nil)
       +                return 0;
       +        entryPack(e, b->data, r->offset%r->epb);
       +        blockDirty(b);
       +        blockPut(b);
       +
       +        return 1;
       +}
       +
       +static Block *
       +blockWalk(Block *p, int index, int mode, Fs *fs, Entry *e)
       +{
       +        Block *b;
       +        Cache *c;
       +        u32int addr;
       +        int type;
       +        uchar oscore[VtScoreSize], score[VtScoreSize];
       +        Entry oe;
       +
       +        c = fs->cache;
       +
       +        if((p->l.type & BtLevelMask) == 0){
       +                assert(p->l.type == BtDir);
       +                type = entryType(e);
       +                b = cacheGlobal(c, e->score, type, e->tag, mode);
       +        }else{
       +                type = p->l.type - 1;
       +                b = cacheGlobal(c, p->data + index*VtScoreSize, type, e->tag, mode);
       +        }
       +
       +        if(b)
       +                b->pc = getcallerpc(&p);
       +
       +        if(b == nil || mode == OReadOnly)
       +                return b;
       +
       +        if(p->l.epoch != fs->ehi){
       +                fprint(2, "blockWalk: parent not writable\n");
       +                abort();
       +        }
       +        if(b->l.epoch == fs->ehi)
       +                return b;
       +
       +        oe = *e;
       +
       +        /*
       +         * Copy on write.
       +         */
       +        if(e->tag == 0){
       +                assert(p->l.type == BtDir);
       +                e->tag = tagGen();
       +                e->flags |= VtEntryLocal;
       +        }
       +
       +        addr = b->addr;
       +        b = blockCopy(b, e->tag, fs->ehi, fs->elo);
       +        if(b == nil)
       +                return nil;
       +
       +        b->pc = getcallerpc(&p);
       +        assert(b->l.epoch == fs->ehi);
       +
       +        blockDirty(b);
       +        memmove(score, b->score, VtScoreSize);
       +        if(p->l.type == BtDir){
       +                memmove(e->score, b->score, VtScoreSize);
       +                entryPack(e, p->data, index);
       +                blockDependency(p, b, index, nil, &oe);
       +        }else{
       +                memmove(oscore, p->data+index*VtScoreSize, VtScoreSize);
       +                memmove(p->data+index*VtScoreSize, b->score, VtScoreSize);
       +                blockDependency(p, b, index, oscore, nil);
       +        }
       +        blockDirty(p);
       +
       +        if(addr != NilBlock)
       +                blockRemoveLink(p, addr, type, e->tag, 0);
       +
       +        return b;
       +}
       +
       +/*
       + * Change the depth of the source r.
       + * The entry e for r is contained in block p.
       + */
       +static int
       +sourceGrowDepth(Source *r, Block *p, Entry *e, int depth)
       +{
       +        Block *b, *bb;
       +        u32int tag;
       +        int type;
       +        Entry oe;
       +
       +        assert(sourceIsLocked(r));
       +        assert(depth <= VtPointerDepth);
       +
       +        type = entryType(e);
       +        b = cacheGlobal(r->fs->cache, e->score, type, e->tag, OReadWrite);
       +        if(b == nil)
       +                return 0;
       +
       +        tag = e->tag;
       +        if(tag == 0)
       +                tag = tagGen();
       +
       +        oe = *e;
       +
       +        /*
       +         * Keep adding layers until we get to the right depth
       +         * or an error occurs.
       +         */
       +        while(e->depth < depth){
       +                bb = cacheAllocBlock(r->fs->cache, type+1, tag, r->fs->ehi, r->fs->elo);
       +                if(bb == nil)
       +                        break;
       +//fprint(2, "alloc %lux grow %V\n", bb->addr, b->score);
       +                memmove(bb->data, b->score, VtScoreSize);
       +                memmove(e->score, bb->score, VtScoreSize);
       +                e->depth++;
       +                type++;
       +                e->tag = tag;
       +                e->flags |= VtEntryLocal;
       +                blockDependency(bb, b, 0, vtZeroScore, nil);
       +                blockPut(b);
       +                b = bb;
       +                blockDirty(b);
       +        }
       +
       +        entryPack(e, p->data, r->offset % r->epb);
       +        blockDependency(p, b, r->offset % r->epb, nil, &oe);
       +        blockPut(b);
       +        blockDirty(p);
       +
       +        return e->depth == depth;
       +}
       +
       +static int
       +sourceShrinkDepth(Source *r, Block *p, Entry *e, int depth)
       +{
       +        Block *b, *nb, *ob, *rb;
       +        u32int tag;
       +        int type, d;
       +        Entry oe;
       +
       +        assert(sourceIsLocked(r));
       +        assert(depth <= VtPointerDepth);
       +
       +        type = entryType(e);
       +        rb = cacheGlobal(r->fs->cache, e->score, type, e->tag, OReadWrite);
       +        if(rb == nil)
       +                return 0;
       +
       +        tag = e->tag;
       +        if(tag == 0)
       +                tag = tagGen();
       +
       +        /*
       +         * Walk down to the new root block.
       +         * We may stop early, but something is better than nothing.
       +         */
       +        oe = *e;
       +
       +        ob = nil;
       +        b = rb;
       +/* BUG: explain type++.  i think it is a real bug */
       +        for(d=e->depth; d > depth; d--, type++){
       +                nb = cacheGlobal(r->fs->cache, b->data, type-1, tag, OReadWrite);
       +                if(nb == nil)
       +                        break;
       +                if(ob!=nil && ob!=rb)
       +                        blockPut(ob);
       +                ob = b;
       +                b = nb;
       +        }
       +
       +        if(b == rb){
       +                blockPut(rb);
       +                return 0;
       +        }
       +
       +        /*
       +         * Right now, e points at the root block rb, b is the new root block,
       +         * and ob points at b.  To update:
       +         *
       +         *        (i) change e to point at b
       +         *        (ii) zero the pointer ob -> b
       +         *        (iii) free the root block
       +         *
       +         * p (the block containing e) must be written before
       +         * anything else.
       +          */
       +
       +        /* (i) */
       +        e->depth = d;
       +        /* might have been local and now global; reverse cannot happen */
       +        if(globalToLocal(b->score) == NilBlock)
       +                e->flags &= ~VtEntryLocal;
       +        memmove(e->score, b->score, VtScoreSize);
       +        entryPack(e, p->data, r->offset % r->epb);
       +        blockDependency(p, b, r->offset % r->epb, nil, &oe);
       +        blockDirty(p);
       +
       +        /* (ii) */
       +        memmove(ob->data, vtZeroScore, VtScoreSize);
       +        blockDependency(ob, p, 0, b->score, nil);
       +        blockDirty(ob);
       +
       +        /* (iii) */
       +        if(rb->addr != NilBlock)
       +                blockRemoveLink(p, rb->addr, rb->l.type, rb->l.tag, 1);
       +
       +        blockPut(rb);
       +        if(ob!=nil && ob!=rb)
       +                blockPut(ob);
       +        blockPut(b);
       +
       +        return d == depth;
       +}
       +
       +/*
       + * Normally we return the block at the given number.
       + * If early is set, we stop earlier in the tree.  Setting early
       + * to 1 gives us the block that contains the pointer to bn.
       + */
       +Block *
       +_sourceBlock(Source *r, ulong bn, int mode, int early, ulong tag)
       +{
       +        Block *b, *bb;
       +        int index[VtPointerDepth+1];
       +        Entry e;
       +        int i, np;
       +        int m;
       +
       +        assert(sourceIsLocked(r));
       +        assert(bn != NilBlock);
       +
       +        /* mode for intermediate block */
       +        m = mode;
       +        if(m == OOverWrite)
       +                m = OReadWrite;
       +
       +        b = sourceLoad(r, &e);
       +        if(b == nil)
       +                return nil;
       +        if(r->issnapshot && (e.flags & VtEntryNoArchive)){
       +                blockPut(b);
       +                vtSetError(ENotArchived);
       +                return nil;
       +        }
       +
       +        if(tag){
       +                if(e.tag == 0)
       +                        e.tag = tag;
       +                else if(e.tag != tag){
       +                        fprint(2, "tag mismatch\n");
       +                        vtSetError("tag mismatch");
       +                        goto Err;
       +                }
       +        }
       +
       +        np = e.psize/VtScoreSize;
       +        memset(index, 0, sizeof(index));
       +        for(i=0; bn > 0; i++){
       +                if(i >= VtPointerDepth){
       +                        vtSetError(EBadAddr);
       +                        goto Err;
       +                }
       +                index[i] = bn % np;
       +                bn /= np;
       +        }
       +
       +        if(i > e.depth){
       +                if(mode == OReadOnly){
       +                        vtSetError(EBadAddr);
       +                        goto Err;
       +                }
       +                if(!sourceGrowDepth(r, b, &e, i))
       +                        goto Err;
       +        }
       +
       +        index[e.depth] = r->offset % r->epb;
       +
       +        for(i=e.depth; i>=early; i--){
       +                bb = blockWalk(b, index[i], m, r->fs, &e);
       +                if(bb == nil)
       +                        goto Err;
       +                blockPut(b);
       +                b = bb;
       +        }
       +        b->pc = getcallerpc(&r);
       +        return b;
       +Err:
       +        blockPut(b);
       +        return nil;
       +}
       +
       +Block*
       +sourceBlock(Source *r, ulong bn, int mode)
       +{
       +        Block *b;
       +
       +        b = _sourceBlock(r, bn, mode, 0, 0);
       +        if(b)
       +                b->pc = getcallerpc(&r);
       +        return b;
       +}
       +
       +void
       +sourceClose(Source *r)
       +{
       +        if(r == nil)
       +                return;
       +        vtLock(r->lk);
       +        r->ref--;
       +        if(r->ref){
       +                vtUnlock(r->lk);
       +                return;
       +        }
       +        assert(r->ref == 0);
       +        vtUnlock(r->lk);
       +        if(r->parent)
       +                sourceClose(r->parent);
       +        vtLockFree(r->lk);
       +        memset(r, ~0, sizeof(*r));
       +        vtMemFree(r);
       +}
       +
       +/*
       + * Retrieve the block containing the entry for r.
       + * If a snapshot has happened, we might need
       + * to get a new copy of the block.  We avoid this
       + * in the common case by caching the score for
       + * the block and the last epoch in which it was valid.
       + *
       + * We use r->mode to tell the difference between active
       + * file system sources (OReadWrite) and sources for the
       + * snapshot file system (OReadOnly).
       + */
       +static Block*
       +sourceLoadBlock(Source *r, int mode)
       +{
       +        u32int addr;
       +        Block *b;
       +
       +        switch(r->mode){
       +        default:
       +                assert(0);
       +        case OReadWrite:
       +                assert(r->mode == OReadWrite);
       +                /*
       +                 * This needn't be true -- we might bump the low epoch
       +                 * to reclaim some old blocks, but since this score is
       +                 * OReadWrite, the blocks must all still be open, so none
       +                 * are reclaimed.  Thus it's okay that the epoch is so low.
       +                 * Proceed.
       +                assert(r->epoch >= r->fs->elo);
       +                 */
       +                if(r->epoch == r->fs->ehi){
       +                        b = cacheGlobal(r->fs->cache, r->score, BtDir, r->tag, OReadWrite);
       +                        if(b == nil)
       +                                return nil;
       +                        assert(r->epoch == b->l.epoch);
       +                        return b;
       +                }
       +                assert(r->parent != nil);
       +                if(!sourceLock(r->parent, OReadWrite))
       +                        return nil;
       +                b = sourceBlock(r->parent, r->offset/r->epb, OReadWrite);
       +                sourceUnlock(r->parent);
       +                if(b == nil)
       +                        return nil;
       +                assert(b->l.epoch == r->fs->ehi);
       +        //        fprint(2, "sourceLoadBlock %p %V => %V\n", r, r->score, b->score);
       +                memmove(r->score, b->score, VtScoreSize);
       +                r->scoreEpoch = b->l.epoch;
       +                r->tag = b->l.tag;
       +                r->epoch = r->fs->ehi;
       +                return b;
       +
       +        case OReadOnly:
       +                addr = globalToLocal(r->score);
       +                if(addr == NilBlock)
       +                        return cacheGlobal(r->fs->cache, r->score, BtDir, r->tag, mode);
       +
       +                b = cacheLocalData(r->fs->cache, addr, BtDir, r->tag, mode, r->scoreEpoch);
       +                if(b)
       +                        return b;
       +
       +                /*
       +                 * If it failed because the epochs don't match, the block has been
       +                 * archived and reclaimed.  Rewalk from the parent and get the
       +                 * new pointer.  This can't happen in the OReadWrite case
       +                 * above because blocks in the current epoch don't get
       +                 * reclaimed.  The fact that we're OReadOnly means we're
       +                 * a snapshot.  (Or else the file system is read-only, but then
       +                 * the archiver isn't going around deleting blocks.)
       +                 */
       +                if(strcmp(vtGetError(), ELabelMismatch) == 0){
       +                        if(!sourceLock(r->parent, OReadOnly))
       +                                return nil;
       +                        b = sourceBlock(r->parent, r->offset/r->epb, OReadOnly);
       +                        sourceUnlock(r->parent);
       +                        if(b){
       +                                fprint(2, "sourceAlloc: lost %V found %V\n",
       +                                        r->score, b->score);
       +                                memmove(r->score, b->score, VtScoreSize);
       +                                r->scoreEpoch = b->l.epoch;
       +                                return b;
       +                        }
       +                }
       +                return nil;
       +        }
       +}
       +
       +int
       +sourceLock(Source *r, int mode)
       +{
       +        Block *b;
       +
       +        if(mode == -1)
       +                mode = r->mode;
       +
       +        b = sourceLoadBlock(r, mode);
       +        if(b == nil)
       +                return 0;
       +        /*
       +         * The fact that we are holding b serves as the
       +         * lock entitling us to write to r->b.
       +         */
       +        assert(r->b == nil);
       +        r->b = b;
       +        if(r->mode == OReadWrite)
       +                assert(r->epoch == r->b->l.epoch);
       +        return 1;
       +}
       +
       +/*
       + * Lock two (usually sibling) sources.  This needs special care
       + * because the Entries for both sources might be in the same block.
       + * We also try to lock blocks in left-to-right order within the tree.
       + */
       +int
       +sourceLock2(Source *r, Source *rr, int mode)
       +{
       +        Block *b, *bb;
       +
       +        if(rr == nil)
       +                return sourceLock(r, mode);
       +
       +        if(mode == -1)
       +                mode = r->mode;
       +
       +        if(r->parent==rr->parent && r->offset/r->epb == rr->offset/rr->epb){
       +                b = sourceLoadBlock(r, mode);
       +                if(b == nil)
       +                        return 0;
       +                if(memcmp(r->score, rr->score, VtScoreSize) != 0){
       +                        memmove(rr->score, b->score, VtScoreSize);
       +                        rr->scoreEpoch = b->l.epoch;
       +                        rr->tag = b->l.tag;
       +                        rr->epoch = rr->fs->ehi;
       +                }
       +                blockDupLock(b);
       +                bb = b;
       +        }else if(r->parent==rr->parent || r->offset > rr->offset){
       +                bb = sourceLoadBlock(rr, mode);
       +                b = sourceLoadBlock(r, mode);
       +        }else{
       +                b = sourceLoadBlock(r, mode);
       +                bb = sourceLoadBlock(rr, mode);
       +        }
       +        if(b == nil || bb == nil){
       +                if(b)
       +                        blockPut(b);
       +                if(bb)
       +                        blockPut(bb);
       +                return 0;
       +        }
       +
       +        /*
       +         * The fact that we are holding b and bb serves
       +         * as the lock entitling us to write to r->b and rr->b.
       +         */
       +        r->b = b;
       +        rr->b = bb;
       +        return 1;
       +}
       +
       +void
       +sourceUnlock(Source *r)
       +{
       +        Block *b;
       +
       +        if(r->b == nil){
       +                fprint(2, "sourceUnlock: already unlocked\n");
       +                abort();
       +        }
       +        b = r->b;
       +        r->b = nil;
       +        blockPut(b);
       +}
       +
       +static Block*
       +sourceLoad(Source *r, Entry *e)
       +{
       +        Block *b;
       +
       +        assert(sourceIsLocked(r));
       +        b = r->b;
       +        if(!entryUnpack(e, b->data, r->offset % r->epb))
       +                return nil;
       +        if(e->gen != r->gen){
       +                vtSetError(ERemoved);
       +                return nil;
       +        }
       +        blockDupLock(b);
       +        return b;
       +}
       +
       +static int
       +sizeToDepth(uvlong s, int psize, int dsize)
       +{
       +        int np;
       +        int d;
       +
       +        /* determine pointer depth */
       +        np = psize/VtScoreSize;
       +        s = (s + dsize - 1)/dsize;
       +        for(d = 0; s > 1; d++)
       +                s = (s + np - 1)/np;
       +        return d;
       +}
       +
       +static u32int
       +tagGen(void)
       +{
       +        u32int tag;
       +
       +        for(;;){
       +                tag = lrand();
       +                if(tag >= UserTag)
       +                        break;
       +        }
       +        return tag;
       +}
       +
       +char *
       +sourceName(Source *s)
       +{
       +        return fileName(s->file);
       +}
 (DIR) diff --git a/src/cmd/fossil/srcload.c b/src/cmd/fossil/srcload.c
       t@@ -0,0 +1,270 @@
       +#include "stdinc.h"
       +#include <bio.h>
       +#include "dat.h"
       +#include "fns.h"
       +#include "error.h"
       +
       +int num = 100;
       +int length = 20*1024;
       +int block= 1024;
       +int bush = 4;
       +int iter = 100;
       +Biobuf *bout;
       +int maxdepth;
       +
       +Source *mkroot(Cache*);
       +void new(Source*, int trace, int);
       +int delete(Source*);
       +int count(Source *s, int);
       +void stats(Source *s);
       +void dump(Source *s, int ident, ulong entry);
       +static void bench(Source *r);
       +
       +void
       +main(int argc, char *argv[])
       +{
       +        int i;
       +        Fs *fs;
       +        int csize = 1000;
       +        ulong t;
       +        Source *r;
       +
       +        ARGBEGIN{
       +        case 'i':
       +                iter = atoi(ARGF());
       +                break;
       +        case 'n':
       +                num = atoi(ARGF());
       +                break;
       +        case 'l':
       +                length = atoi(ARGF());
       +                break;
       +        case 'b':        
       +                block = atoi(ARGF());
       +                break;
       +        case 'u':
       +                bush = atoi(ARGF());
       +                break;
       +        case 'c':
       +                csize = atoi(ARGF());
       +                break;
       +        }ARGEND;
       +
       +        vtAttach();
       +
       +        bout = vtMemAllocZ(sizeof(Biobuf));
       +        Binit(bout, 1, OWRITE);
       +
       +        fmtinstall('V', vtScoreFmt);
       +        fmtinstall('R', vtErrFmt);
       +
       +        fs = fsOpen(argv[0], nil, csize, OReadWrite);
       +        if(fs == nil)
       +                sysfatal("could not open fs: %r");
       +
       +        t = time(0);
       +
       +        srand(0);
       +
       +        r = fs->source;
       +        dump(r, 0, 0);
       +
       +        fprint(2, "count = %d\n", count(r, 1));
       +        for(i=0; i<num; i++)
       +                new(r, 0, 0);
       +
       +        for(i=0; i<iter; i++){
       +                if(i % 10000 == 0)
       +                        stats(r);
       +                new(r, 0, 0);
       +                delete(r);
       +        }
       +
       +//        dump(r, 0, 0);
       +
       +        fprint(2, "count = %d\n", count(r, 1));
       +//        cacheCheck(c);
       +
       +        fprint(2, "deleting\n");
       +        for(i=0; i<num; i++)
       +                delete(r);
       +//        dump(r, 0, 0);
       +
       +        fprint(2, "count = %d\n", count(r, 1));
       +        fprint(2, "total time = %ld\n", time(0)-t);
       +        
       +        fsClose(fs);
       +        vtDetach();
       +        exits(0);
       +}
       +
       +static void
       +bench(Source *r)
       +{
       +        vlong t;
       +        Entry e;
       +        int i;
       +
       +        t = nsec();
       +
       +        for(i=0; i<1000000; i++)
       +                sourceGetEntry(r, &e);
       +
       +        fprint(2, "%f\n", 1e-9*(nsec() - t));
       +}
       +
       +void
       +new(Source *s, int trace, int depth)
       +{
       +        int i, n;
       +        Source *ss;
       +        Entry e;
       +        
       +        if(depth > maxdepth)
       +                maxdepth = depth;
       +
       +        Bflush(bout);
       +
       +        n = sourceGetDirSize(s);
       +        for(i=0; i<n; i++){
       +                ss = sourceOpen(s, nrand(n), OReadWrite);
       +                if(ss == nil || !sourceGetEntry(ss, &e))
       +                        continue;
       +                if((e.flags & VtEntryDir) && frand() < 1./bush){
       +                        if(trace){
       +                                int j;
       +                                for(j=0; j<trace; j++)
       +                                        Bprint(bout, " ");
       +                                Bprint(bout, "decend %d\n", i);
       +                        }
       +                        new(ss, trace?trace+1:0, depth+1);
       +                        sourceClose(ss);
       +                        return;
       +                }
       +                sourceClose(ss);
       +        }
       +        ss = sourceCreate(s, s->dsize, 1+frand()>.5, 0);
       +        if(ss == nil){
       +                Bprint(bout, "could not create directory: %R\n");
       +                return;
       +        }
       +        if(trace){
       +                int j;
       +                for(j=1; j<trace; j++)
       +                        Bprint(bout, " ");
       +                Bprint(bout, "create %d\n", ss->offset);
       +        }
       +        sourceClose(ss);
       +}
       +
       +int
       +delete(Source *s)
       +{
       +        int i, n;
       +        Source *ss;
       +
       +        n = sourceGetDirSize(s);
       +        /* check if empty */
       +        for(i=0; i<n; i++){
       +                ss = sourceOpen(s, i, OReadWrite);
       +                if(ss != nil){
       +                        sourceClose(ss);
       +                        break;
       +                }
       +        }
       +        if(i == n)
       +                return 0;
       +                
       +        for(;;){
       +                ss = sourceOpen(s, nrand(n), OReadWrite);
       +                if(ss == nil)
       +                        continue;
       +                if(s->dir && delete(ss)){
       +                        sourceClose(ss);
       +                        return 1;
       +                }
       +                if(1)
       +                        break;
       +                sourceClose(ss);
       +        }
       +
       +
       +        sourceRemove(ss);
       +        return 1;
       +}
       +
       +void
       +dump(Source *s, int ident, ulong entry)
       +{
       +        ulong i, n;
       +        Source *ss;
       +        Entry e;
       +
       +        for(i=0; i<ident; i++)
       +                Bprint(bout, " ");
       +
       +        if(!sourceGetEntry(s, &e)){
       +                fprint(2, "sourceGetEntry failed: %r\n");
       +                return;
       +        }
       +
       +        Bprint(bout, "%4lud: gen %4ud depth %d tag=%x score=%V",
       +                entry, e.gen, e.depth, e.tag, e.score);
       +        if(!s->dir){
       +                Bprint(bout, " data size: %llud\n", e.size);
       +                return;
       +        }
       +        n = sourceGetDirSize(s);
       +        Bprint(bout, " dir size: %lud\n", n);
       +        for(i=0; i<n; i++){
       +                ss = sourceOpen(s, i, 1);
       +                if(ss == nil)
       +                        continue;
       +                dump(ss, ident+1, i);
       +                sourceClose(ss);
       +        }
       +        return;
       +}
       +
       +int
       +count(Source *s, int rec)
       +{
       +        ulong i, n;
       +        int c;
       +        Source *ss;
       +
       +        n = sourceGetDirSize(s);
       +        c = 0;
       +        for(i=0; i<n; i++){
       +                ss = sourceOpen(s, i, OReadOnly);
       +                if(ss == nil)
       +                        continue;
       +                if(rec)
       +                        c += count(ss, rec);
       +                c++;
       +                sourceClose(ss);
       +        }
       +        return c;
       +}
       +
       +void
       +stats(Source *s)
       +{
       +        int n, i, c, cc, max;
       +        Source *ss;
       +
       +        cc = 0;
       +        max = 0;
       +        n = sourceGetDirSize(s);
       +        for(i=0; i<n; i++){
       +                ss = sourceOpen(s, i, 1);
       +                if(ss == nil)
       +                        continue;
       +                cc++;
       +                c = count(ss, 1);
       +                if(c > max)
       +                        max = c;
       +                sourceClose(ss);
       +        }
       +fprint(2, "count = %d top = %d depth=%d maxcount %d\n", cc, n, maxdepth, max);
       +}
 (DIR) diff --git a/src/cmd/fossil/stdinc.h b/src/cmd/fossil/stdinc.h
       t@@ -0,0 +1,11 @@
       +#include <u.h>
       +#include <libc.h>
       +
       +typedef uvlong        u64int;
       +typedef        uchar        u8int;
       +typedef ushort        u16int;
       +
       +#include "oventi.h"
       +#include "vac.h"
       +#include "fs.h"
       +
 (DIR) diff --git a/src/cmd/fossil/trunc.c b/src/cmd/fossil/trunc.c
       t@@ -0,0 +1,19 @@
       +#include <u.h>
       +#include <libc.h>
       +
       +void
       +main(int argc, char **argv)
       +{
       +        Dir d;
       +
       +        if(argc != 3){
       +                fprint(2, "usage: trunc file size\n");
       +                exits("usage");
       +        }
       +
       +        nulldir(&d);
       +        d.length = strtoull(argv[2], 0, 0);
       +        if(dirwstat(argv[1], &d) < 0)
       +                sysfatal("dirwstat: %r");
       +        exits(0);
       +}
 (DIR) diff --git a/src/cmd/fossil/vac.c b/src/cmd/fossil/vac.c
       t@@ -0,0 +1,746 @@
       +#include "stdinc.h"
       +
       +typedef struct MetaChunk MetaChunk;
       +
       +struct MetaChunk {
       +        ushort offset;
       +        ushort size;
       +        ushort index;
       +};
       +
       +static int stringUnpack(char **s, uchar **p, int *n);
       +static int meCmp(MetaEntry*, char *s);
       +static int meCmpOld(MetaEntry*, char *s);
       +
       +
       +
       +static char EBadMeta[] = "corrupted meta data";
       +static char ENoFile[] = "file does not exist";
       +
       +/*
       + * integer conversion routines
       + */
       +#define        U8GET(p)        ((p)[0])
       +#define        U16GET(p)        (((p)[0]<<8)|(p)[1])
       +#define        U32GET(p)        (((p)[0]<<24)|((p)[1]<<16)|((p)[2]<<8)|(p)[3])
       +#define        U48GET(p)        (((uvlong)U16GET(p)<<32)|(uvlong)U32GET((p)+2))
       +#define        U64GET(p)        (((uvlong)U32GET(p)<<32)|(uvlong)U32GET((p)+4))
       +
       +#define        U8PUT(p,v)        (p)[0]=(v)
       +#define        U16PUT(p,v)        (p)[0]=(v)>>8;(p)[1]=(v)
       +#define        U32PUT(p,v)        (p)[0]=(v)>>24;(p)[1]=(v)>>16;(p)[2]=(v)>>8;(p)[3]=(v)
       +#define        U48PUT(p,v,t32)        t32=(v)>>32;U16PUT(p,t32);t32=(v);U32PUT((p)+2,t32)
       +#define        U64PUT(p,v,t32)        t32=(v)>>32;U32PUT(p,t32);t32=(v);U32PUT((p)+4,t32)
       +
       +static int
       +stringUnpack(char **s, uchar **p, int *n)
       +{
       +        int nn;
       +
       +        if(*n < 2)
       +                return 0;
       +
       +        nn = U16GET(*p);
       +        *p += 2;
       +        *n -= 2;
       +        if(nn > *n)
       +                return 0;
       +        *s = vtMemAlloc(nn+1);
       +        memmove(*s, *p, nn);
       +        (*s)[nn] = 0;
       +        *p += nn;
       +        *n -= nn;
       +        return 1;
       +}
       +
       +static int
       +stringPack(char *s, uchar *p)
       +{
       +        int n;
       +
       +        n = strlen(s);
       +        U16PUT(p, n);
       +        memmove(p+2, s, n);
       +        return n+2;
       +}
       +
       +int
       +mbSearch(MetaBlock *mb, char *elem, int *ri, MetaEntry *me)
       +{
       +        int i;
       +        int b, t, x;
       +if(0)fprint(2, "mbSearch %s\n", elem);
       +
       +        /* binary search within block */
       +        b = 0;
       +        t = mb->nindex;
       +        while(b < t){
       +                i = (b+t)>>1;
       +                meUnpack(me, mb, i);
       +
       +                if(mb->botch)
       +                        x = meCmpOld(me, elem);
       +                else
       +                        x = meCmp(me, elem);
       +
       +                if(x == 0){
       +                        *ri = i;
       +                        return 1;
       +                }
       +
       +                if(x < 0)
       +                        b = i+1;
       +                else /* x > 0 */
       +                        t = i;
       +        }
       +
       +        assert(b == t);
       +
       +        *ri = b;        /* b is the index to insert this entry */
       +        memset(me, 0, sizeof(*me));
       +
       +        vtSetError(ENoFile);
       +        return 0;
       +}
       +
       +void
       +mbInit(MetaBlock *mb, uchar *p, int n, int ne)
       +{
       +        memset(p, 0, n);
       +        mb->maxsize = n;
       +        mb->maxindex = ne;
       +        mb->nindex = 0;
       +        mb->free = 0;
       +        mb->size = MetaHeaderSize + ne*MetaIndexSize;
       +        mb->buf = p;
       +        mb->botch = 0;
       +}
       +
       +int
       +mbUnpack(MetaBlock *mb, uchar *p, int n)
       +{
       +        u32int magic;
       +        int i;
       +        int eo, en, omin;
       +        uchar *q;
       +
       +        mb->maxsize = n;
       +        mb->buf = p;
       +
       +        if(n == 0){
       +                memset(mb, 0, sizeof(MetaBlock));
       +                return 1;
       +        }
       +
       +        magic = U32GET(p);
       +        if(magic != MetaMagic && magic != MetaMagic-1)
       +                goto Err;
       +        mb->size = U16GET(p+4);
       +        mb->free = U16GET(p+6);
       +        mb->maxindex = U16GET(p+8);
       +        mb->nindex = U16GET(p+10);
       +        mb->botch = magic != MetaMagic;
       +        if(mb->size > n)
       +                goto Err;
       +
       +        omin = MetaHeaderSize + mb->maxindex*MetaIndexSize;
       +        if(n < omin)
       +                goto Err;
       +
       +
       +        p += MetaHeaderSize;
       +
       +        /* check the index table - ensures that meUnpack and meCmp never fail */
       +        for(i=0; i<mb->nindex; i++){
       +                eo = U16GET(p);
       +                en = U16GET(p+2);
       +                if(eo < omin || eo+en > mb->size || en < 8)
       +                        goto Err;
       +                q = mb->buf + eo;
       +                if(U32GET(q) != DirMagic)
       +                        goto Err;
       +                p += 4;
       +        }
       +
       +        return 1;
       +Err:
       +        vtSetError(EBadMeta);
       +        return 0;
       +}
       +
       +
       +void
       +mbPack(MetaBlock *mb)
       +{
       +        uchar *p;
       +
       +        p = mb->buf;
       +
       +        assert(!mb->botch);
       +
       +        U32PUT(p, MetaMagic);
       +        U16PUT(p+4, mb->size);
       +        U16PUT(p+6, mb->free);
       +        U16PUT(p+8, mb->maxindex);
       +        U16PUT(p+10, mb->nindex);
       +}
       +
       +
       +void
       +mbDelete(MetaBlock *mb, int i)
       +{
       +        uchar *p;
       +        int n;
       +        MetaEntry me;
       +
       +        assert(i < mb->nindex);
       +        meUnpack(&me, mb, i);
       +        memset(me.p, 0, me.size);
       +
       +        if(me.p - mb->buf + me.size == mb->size)
       +                mb->size -= me.size;
       +        else
       +                mb->free += me.size;
       +
       +        p = mb->buf + MetaHeaderSize + i*MetaIndexSize;
       +        n = (mb->nindex-i-1)*MetaIndexSize;
       +        memmove(p, p+MetaIndexSize, n);
       +        memset(p+n, 0, MetaIndexSize);
       +        mb->nindex--;
       +}
       +
       +void
       +mbInsert(MetaBlock *mb, int i, MetaEntry *me)
       +{
       +        uchar *p;
       +        int o, n;
       +
       +        assert(mb->nindex < mb->maxindex);
       +
       +        o = me->p - mb->buf;
       +        n = me->size;
       +        if(o+n > mb->size){
       +                mb->free -= mb->size - o;
       +                mb->size = o + n;
       +        }else
       +                mb->free -= n;
       +
       +        p = mb->buf + MetaHeaderSize + i*MetaIndexSize;
       +        n = (mb->nindex-i)*MetaIndexSize;
       +        memmove(p+MetaIndexSize, p, n);
       +        U16PUT(p, me->p - mb->buf);
       +        U16PUT(p+2, me->size);
       +        mb->nindex++;
       +}
       +
       +int
       +mbResize(MetaBlock *mb, MetaEntry *me, int n)
       +{
       +        uchar *p, *ep;
       +
       +        /* easy case */
       +        if(n <= me->size){
       +                me->size = n;
       +                return 1;
       +        }
       +
       +        /* try and expand entry */
       +
       +        p = me->p + me->size;
       +        ep = mb->buf + mb->maxsize;
       +        while(p < ep && *p == 0)
       +                p++;
       +        if(n <= p - me->p){
       +                me->size = n;
       +                return 1;
       +        }
       +
       +        p = mbAlloc(mb, n);
       +        if(p != nil){
       +                me->p = p;
       +                me->size = n;
       +                return 1;
       +        }
       +
       +        return 0;
       +}
       +
       +void
       +meUnpack(MetaEntry *me, MetaBlock *mb, int i)
       +{
       +        uchar *p;
       +        int eo, en;
       +
       +        assert(i >= 0 && i < mb->nindex);
       +
       +        p = mb->buf + MetaHeaderSize + i*MetaIndexSize;
       +        eo = U16GET(p);
       +        en = U16GET(p+2);
       +
       +        me->p = mb->buf + eo;
       +        me->size = en;
       +
       +        /* checked by mbUnpack */
       +        assert(me->size >= 8);
       +}
       +
       +/* assumes a small amount of checking has been done in mbEntry */
       +static int
       +meCmp(MetaEntry *me, char *s)
       +{
       +        int n;
       +        uchar *p;
       +
       +        p = me->p;
       +
       +        /* skip magic & version */
       +        p += 6;
       +        n = U16GET(p);
       +        p += 2;
       +
       +        if(n > me->size - 8)
       +                n = me->size - 8;
       +
       +        while(n > 0){
       +                if(*s == 0)
       +                        return 1;
       +                if(*p < (uchar)*s)
       +                        return -1;
       +                if(*p > (uchar)*s)
       +                        return 1;
       +                p++;
       +                s++;
       +                n--;
       +        }
       +        return -(*s != 0);
       +}
       +
       +/*
       + * This is the old and broken meCmp.
       + * This cmp routine reverse the sense of the comparison
       + * when one string is a prefix of the other.
       + * In other words, it put "ab" after "abc" rather
       + * than before.  This behaviour is ok; binary search
       + * and sort still work.  However, it is goes against
       + * the usual convention.
       + */
       +static int
       +meCmpOld(MetaEntry *me, char *s)
       +{
       +        int n;
       +        uchar *p;
       +
       +        p = me->p;
       +
       +        /* skip magic & version */
       +        p += 6;
       +        n = U16GET(p);
       +        p += 2;
       +
       +        if(n > me->size - 8)
       +                n = me->size - 8;
       +
       +        while(n > 0){
       +                if(*s == 0)
       +                        return -1;
       +                if(*p < (uchar)*s)
       +                        return -1;
       +                if(*p > (uchar)*s)
       +                        return 1;
       +                p++;
       +                s++;
       +                n--;
       +        }
       +        return *s != 0;
       +}
       +
       +static int
       +offsetCmp(void *s0, void *s1)
       +{
       +        MetaChunk *mc0, *mc1;
       +
       +        mc0 = s0;
       +        mc1 = s1;
       +        if(mc0->offset < mc1->offset)
       +                return -1;
       +        if(mc0->offset > mc1->offset)
       +                return 1;
       +        return 0;
       +}
       +
       +static MetaChunk *
       +metaChunks(MetaBlock *mb)
       +{
       +        MetaChunk *mc;
       +        int oo, o, n, i;
       +        uchar *p;
       +
       +        mc = vtMemAlloc(mb->nindex*sizeof(MetaChunk));
       +        p = mb->buf + MetaHeaderSize;
       +        for(i = 0; i<mb->nindex; i++){
       +                mc[i].offset = U16GET(p);
       +                mc[i].size = U16GET(p+2);
       +                mc[i].index = i;
       +                p += MetaIndexSize;
       +        }
       +
       +        qsort(mc, mb->nindex, sizeof(MetaChunk), offsetCmp);
       +
       +        /* check block looks ok */
       +        oo = MetaHeaderSize + mb->maxindex*MetaIndexSize;
       +        o = oo;
       +        n = 0;
       +        for(i=0; i<mb->nindex; i++){
       +                o = mc[i].offset;
       +                n = mc[i].size;
       +                if(o < oo)
       +                        goto Err;
       +                oo += n;
       +        }
       +        if(o+n > mb->size)
       +                goto Err;
       +        if(mb->size - oo != mb->free)
       +                goto Err;
       +
       +        return mc;
       +Err:
       +fprint(2, "metaChunks failed!\n");
       +oo = MetaHeaderSize + mb->maxindex*MetaIndexSize;
       +for(i=0; i<mb->nindex; i++){
       +fprint(2, "\t%d: %d %d\n", i, mc[i].offset, mc[i].offset + mc[i].size);
       +oo += mc[i].size;
       +}
       +fprint(2, "\tused=%d size=%d free=%d free2=%d\n", oo, mb->size, mb->free, mb->size - oo);
       +        vtSetError(EBadMeta);
       +        vtMemFree(mc);
       +        return nil;
       +}
       +
       +static void
       +mbCompact(MetaBlock *mb, MetaChunk *mc)
       +{
       +        int oo, o, n, i;
       +
       +        oo = MetaHeaderSize + mb->maxindex*MetaIndexSize;
       +
       +        for(i=0; i<mb->nindex; i++){
       +                o = mc[i].offset;
       +                n = mc[i].size;
       +                if(o != oo){
       +                        memmove(mb->buf + oo, mb->buf + o, n);
       +                        U16PUT(mb->buf + MetaHeaderSize + mc[i].index*MetaIndexSize, oo);
       +                }
       +                oo += n;
       +        }
       +
       +        mb->size = oo;
       +        mb->free = 0;
       +}
       +
       +uchar *
       +mbAlloc(MetaBlock *mb, int n)
       +{
       +        int i, o;
       +        MetaChunk *mc;
       +
       +        /* off the end */
       +        if(mb->maxsize - mb->size >= n)
       +                return mb->buf + mb->size;
       +
       +        /* check if possible */
       +        if(mb->maxsize - mb->size + mb->free < n)
       +                return nil;
       +
       +        mc = metaChunks(mb);
       +        if(mc == nil){
       +fprint(2, "mbAlloc: metaChunks failed: %r\n");
       +                return nil;
       +        }
       +
       +        /* look for hole */
       +        o = MetaHeaderSize + mb->maxindex*MetaIndexSize;
       +        for(i=0; i<mb->nindex; i++){
       +                if(mc[i].offset - o >= n){
       +                        vtMemFree(mc);
       +                        return mb->buf + o;
       +                }
       +                o = mc[i].offset + mc[i].size;
       +        }
       +
       +        if(mb->maxsize - o >= n){
       +                vtMemFree(mc);
       +                return mb->buf + o;
       +        }
       +
       +        /* compact and return off the end */
       +        mbCompact(mb, mc);
       +        vtMemFree(mc);
       +
       +        if(mb->maxsize - mb->size < n){
       +                vtSetError(EBadMeta);
       +                return nil;
       +        }
       +        return mb->buf + mb->size;
       +}
       +
       +int
       +deSize(DirEntry *dir)
       +{
       +        int n;
       +
       +        /* constant part */
       +
       +        n =         4 +        /* magic */
       +                2 +         /* version */
       +                4 +        /* entry */
       +                4 +         /* guid */
       +                4 +         /* mentry */
       +                4 +         /* mgen */
       +                8 +        /* qid */
       +                4 +         /* mtime */
       +                4 +         /* mcount */
       +                4 +         /* ctime */
       +                4 +         /* atime */
       +                4 +        /* mode */
       +                0;
       +
       +        /* strings */
       +        n += 2 + strlen(dir->elem);
       +        n += 2 + strlen(dir->uid);
       +        n += 2 + strlen(dir->gid);
       +        n += 2 + strlen(dir->mid);
       +
       +        /* optional sections */
       +        if(dir->qidSpace){
       +                n +=         3 +         /* option header */
       +                        8 +         /* qidOffset */
       +                        8;        /* qid Max */
       +        }
       +
       +        return n;
       +}
       +
       +void
       +dePack(DirEntry *dir, MetaEntry *me)
       +{
       +        uchar *p;
       +        ulong t32;
       +
       +        p = me->p;
       +
       +        U32PUT(p, DirMagic);
       +        U16PUT(p+4, 9);                /* version */
       +        p += 6;
       +
       +        p += stringPack(dir->elem, p);
       +
       +        U32PUT(p, dir->entry);
       +        U32PUT(p+4, dir->gen);
       +        U32PUT(p+8, dir->mentry);
       +        U32PUT(p+12, dir->mgen);
       +        U64PUT(p+16, dir->qid, t32);
       +        p += 24;
       +
       +        p += stringPack(dir->uid, p);
       +        p += stringPack(dir->gid, p);
       +        p += stringPack(dir->mid, p);
       +
       +        U32PUT(p, dir->mtime);
       +        U32PUT(p+4, dir->mcount);
       +        U32PUT(p+8, dir->ctime);
       +        U32PUT(p+12, dir->atime);
       +        U32PUT(p+16, dir->mode);
       +        p += 5*4;
       +
       +        if(dir->qidSpace){
       +                U8PUT(p, DeQidSpace);
       +                U16PUT(p+1, 2*8);
       +                p += 3;
       +                U64PUT(p, dir->qidOffset, t32);
       +                U64PUT(p+8, dir->qidMax, t32);
       +                p += 16;
       +        }
       +
       +        assert(p == me->p + me->size);
       +}
       +
       +
       +int
       +deUnpack(DirEntry *dir, MetaEntry *me)
       +{
       +        int t, nn, n, version;
       +        uchar *p;
       +
       +        p = me->p;
       +        n = me->size;
       +
       +        memset(dir, 0, sizeof(DirEntry));
       +
       +if(0)print("deUnpack\n");
       +        /* magic */
       +        if(n < 4 || U32GET(p) != DirMagic)
       +                goto Err;
       +        p += 4;
       +        n -= 4;
       +
       +if(0)print("deUnpack: got magic\n");
       +        /* version */
       +        if(n < 2)
       +                goto Err;
       +        version = U16GET(p);
       +        if(version < 7 || version > 9)
       +                goto Err;
       +        p += 2;
       +        n -= 2;
       +
       +if(0)print("deUnpack: got version\n");
       +
       +        /* elem */
       +        if(!stringUnpack(&dir->elem, &p, &n))
       +                goto Err;
       +
       +if(0)print("deUnpack: got elem\n");
       +
       +        /* entry  */
       +        if(n < 4)
       +                goto Err;
       +        dir->entry = U32GET(p);
       +        p += 4;
       +        n -= 4;
       +
       +if(0)print("deUnpack: got entry\n");
       +
       +        if(version < 9){
       +                dir->gen = 0;
       +                dir->mentry = dir->entry+1;
       +                dir->mgen = 0;
       +        }else{
       +                if(n < 3*4)
       +                        goto Err;
       +                dir->gen = U32GET(p);
       +                dir->mentry = U32GET(p+4);
       +                dir->mgen = U32GET(p+8);
       +                p += 3*4;
       +                n -= 3*4;
       +        }
       +
       +if(0)print("deUnpack: got gen etc\n");
       +
       +        /* size is gotten from VtEntry */
       +        dir->size = 0;
       +
       +        /* qid */
       +        if(n < 8)
       +                goto Err;
       +        dir->qid = U64GET(p);
       +        p += 8;
       +        n -= 8;
       +
       +if(0)print("deUnpack: got qid\n");
       +        /* skip replacement */
       +        if(version == 7){
       +                if(n < VtScoreSize)
       +                        goto Err;
       +                p += VtScoreSize;
       +                n -= VtScoreSize;
       +        }
       +
       +        /* uid */
       +        if(!stringUnpack(&dir->uid, &p, &n))
       +                goto Err;
       +
       +        /* gid */
       +        if(!stringUnpack(&dir->gid, &p, &n))
       +                goto Err;
       +
       +        /* mid */
       +        if(!stringUnpack(&dir->mid, &p, &n))
       +                goto Err;
       +
       +if(0)print("deUnpack: got ids\n");
       +        if(n < 5*4)
       +                goto Err;
       +        dir->mtime = U32GET(p);
       +        dir->mcount = U32GET(p+4);
       +        dir->ctime = U32GET(p+8);
       +        dir->atime = U32GET(p+12);
       +        dir->mode = U32GET(p+16);
       +        p += 5*4;
       +        n -= 5*4;
       +
       +if(0)print("deUnpack: got times\n");
       +        /* optional meta data */
       +        while(n > 0){
       +                if(n < 3)
       +                        goto Err;
       +                t = p[0];
       +                nn = U16GET(p+1);
       +                p += 3;
       +                n -= 3;
       +                if(n < nn)
       +                        goto Err;
       +                switch(t){
       +                case DePlan9:
       +                        /* not valid in version >= 9 */
       +                        if(version >= 9)
       +                                break;
       +                        if(dir->plan9 || nn != 12)
       +                                goto Err;
       +                        dir->plan9 = 1;
       +                        dir->p9path = U64GET(p);
       +                        dir->p9version = U32GET(p+8);
       +                        if(dir->mcount == 0)
       +                                dir->mcount = dir->p9version;
       +                        break;
       +                case DeGen:
       +                        /* not valid in version >= 9 */
       +                        if(version >= 9)
       +                                break;
       +                        break;
       +                case DeQidSpace:
       +                        if(dir->qidSpace || nn != 16)
       +                                goto Err;
       +                        dir->qidSpace = 1;
       +                        dir->qidOffset = U64GET(p);
       +                        dir->qidMax = U64GET(p+8);
       +                        break;
       +                }
       +                p += nn;
       +                n -= nn;
       +        }
       +if(0)print("deUnpack: got options\n");
       +
       +        if(p != me->p + me->size)
       +                goto Err;
       +
       +if(0)print("deUnpack: correct size\n");
       +        return 1;
       +Err:
       +if(0)print("deUnpack: XXXXXXXXXXXX EBadMeta\n");
       +        vtSetError(EBadMeta);
       +        deCleanup(dir);
       +        return 0;
       +}
       +
       +void
       +deCleanup(DirEntry *dir)
       +{
       +        vtMemFree(dir->elem);
       +        dir->elem = nil;
       +        vtMemFree(dir->uid);
       +        dir->uid = nil;
       +        vtMemFree(dir->gid);
       +        dir->gid = nil;
       +        vtMemFree(dir->mid);
       +        dir->mid = nil;
       +}
       +
       +void
       +deCopy(DirEntry *dst, DirEntry *src)
       +{
       +        *dst = *src;
       +        dst->elem = vtStrDup(src->elem);
       +        dst->uid = vtStrDup(src->uid);
       +        dst->gid = vtStrDup(src->gid);
       +        dst->mid = vtStrDup(src->mid);
       +}
 (DIR) diff --git a/src/cmd/fossil/vac.h b/src/cmd/fossil/vac.h
       t@@ -0,0 +1,107 @@
       +typedef struct DirEntry DirEntry;
       +typedef struct MetaBlock MetaBlock;
       +typedef struct MetaEntry MetaEntry;
       +
       +enum {
       +        MetaMagic = 0x5656fc7a,
       +        MetaHeaderSize = 12,
       +        MetaIndexSize = 4,
       +        IndexEntrySize = 8,
       +        DirMagic = 0x1c4d9072,
       +};
       +
       +/*
       + * Mode bits
       + */
       +enum {
       +        ModeOtherExec = (1<<0),
       +        ModeOtherWrite = (1<<1),
       +        ModeOtherRead = (1<<2),
       +        ModeGroupExec = (1<<3),
       +        ModeGroupWrite = (1<<4),
       +        ModeGroupRead = (1<<5),
       +        ModeOwnerExec = (1<<6),
       +        ModeOwnerWrite = (1<<7),
       +        ModeOwnerRead = (1<<8),
       +        ModeSticky = (1<<9),
       +        ModeSetUid = (1<<10),
       +        ModeSetGid = (1<<11),
       +        ModeAppend = (1<<12),                /* append only file */
       +        ModeExclusive = (1<<13),        /* lock file - plan 9 */
       +        ModeLink = (1<<14),                /* sym link */
       +        ModeDir        = (1<<15),                /* duplicate of DirEntry */
       +        ModeHidden = (1<<16),                /* MS-DOS */
       +        ModeSystem = (1<<17),                /* MS-DOS */
       +        ModeArchive = (1<<18),                /* MS-DOS */
       +        ModeTemporary = (1<<19),        /* MS-DOS */
       +        ModeSnapshot = (1<<20),                /* read only snapshot */
       +};
       +
       +/* optional directory entry fields */
       +enum {
       +        DePlan9 = 1,        /* not valid in version >= 9 */
       +        DeNT,                /* not valid in version >= 9 */
       +        DeQidSpace,
       +        DeGen,                /* not valid in version >= 9 */
       +};
       +
       +struct DirEntry {
       +        char *elem;                /* path element */
       +        ulong entry;                /* entry in directory for data */
       +        ulong gen;                /* generation of data entry */
       +        ulong mentry;                /* entry in directory for meta */
       +        ulong mgen;                /* generation of meta entry */
       +        uvlong size;                /* size of file */
       +        uvlong qid;                /* unique file id */
       +
       +        char *uid;                /* owner id */
       +        char *gid;                /* group id */
       +        char *mid;                /* last modified by */
       +        ulong mtime;                /* last modified time */
       +        ulong mcount;                /* number of modifications: can wrap! */
       +        ulong ctime;                /* directory entry last changed */
       +        ulong atime;                /* last time accessed */
       +        ulong mode;                /* various mode bits */
       +
       +        /* plan 9 */
       +        int plan9;
       +        uvlong p9path;
       +        ulong p9version;
       +
       +        /* sub space of qid */
       +        int qidSpace;
       +        uvlong qidOffset;        /* qid offset */
       +        uvlong qidMax;                /* qid maximum */
       +};
       +
       +struct MetaEntry {
       +        uchar *p;
       +        ushort size;
       +};
       +
       +struct MetaBlock {
       +        int maxsize;                /* size of block */
       +        int size;                /* size used */
       +        int free;                /* free space within used size */
       +        int maxindex;                /* entries allocated for table */
       +        int nindex;                /* amount of table used */
       +        int botch;                /* compensate for my stupidity */
       +        uchar *buf;
       +};
       +
       +void        deCleanup(DirEntry*);
       +void        deCopy(DirEntry*, DirEntry*);
       +int        deSize(DirEntry*);
       +void        dePack(DirEntry*, MetaEntry*);
       +int        deUnpack(DirEntry*, MetaEntry*);
       +
       +void        mbInit(MetaBlock*, uchar*, int, int);
       +int        mbUnpack(MetaBlock*, uchar*, int);
       +void        mbInsert(MetaBlock*, int, MetaEntry*);
       +void        mbDelete(MetaBlock*, int);
       +void        mbPack(MetaBlock*);
       +uchar        *mbAlloc(MetaBlock*, int);
       +int        mbResize(MetaBlock*, MetaEntry*, int);
       +int        mbSearch(MetaBlock*, char*, int*, MetaEntry*);
       +
       +void        meUnpack(MetaEntry*, MetaBlock*, int);
 (DIR) diff --git a/src/cmd/fossil/view.c b/src/cmd/fossil/view.c
       t@@ -0,0 +1,1127 @@
       +#include "stdinc.h"
       +#include "dat.h"
       +#include "fns.h"
       +#include <draw.h>
       +#include <event.h>
       +
       +/* --- tree.h */
       +typedef struct Tree Tree;
       +typedef struct Tnode Tnode;
       +
       +struct Tree
       +{
       +        Tnode *root;
       +        Point offset;
       +        Image *clipr;
       +};
       +
       +struct Tnode
       +{
       +        Point offset;
       +
       +        char *str;
       +//        char *(*strfn)(Tnode*);
       +//        uint (*draw)(Tnode*, Image*, Image*, Point);
       +        void (*expand)(Tnode*);
       +        void (*collapse)(Tnode*);
       +
       +        uint expanded;
       +        Tnode **kid;
       +        int nkid;
       +        void *aux;
       +};
       +
       +typedef struct Atree Atree;
       +struct Atree
       +{
       +        int resizefd;
       +        Tnode *root;
       +};
       +
       +Atree *atreeinit(char*);
       +
       +/* --- visfossil.c */
       +Tnode *initxheader(void);
       +Tnode *initxcache(char *name);
       +Tnode *initxsuper(void);
       +Tnode *initxlocalroot(char *name, u32int addr);
       +Tnode *initxentry(Entry);
       +Tnode *initxsource(Entry, int);
       +Tnode *initxentryblock(Block*, Entry*);
       +Tnode *initxdatablock(Block*, uint);
       +Tnode *initxroot(char *name, uchar[VtScoreSize]);
       +
       +int fd;
       +Header h;
       +Super super;
       +VtSession *z;
       +VtRoot vac;
       +int showinactive;
       +
       +/*
       + * dumbed down versions of fossil routines
       + */
       +char*
       +bsStr(int state)
       +{
       +        static char s[100];
       +
       +        if(state == BsFree)
       +                return "Free";
       +        if(state == BsBad)
       +                return "Bad";
       +
       +        sprint(s, "%x", state);
       +        if(!(state&BsAlloc))
       +                strcat(s, ",Free");        /* should not happen */
       +        if(state&BsVenti)
       +                strcat(s, ",Venti");
       +        if(state&BsClosed)
       +                strcat(s, ",Closed");
       +        return s;
       +}
       +
       +char *bttab[] = {
       +        "BtData",
       +        "BtData+1",
       +        "BtData+2",
       +        "BtData+3",
       +        "BtData+4",
       +        "BtData+5",
       +        "BtData+6",
       +        "BtData+7",
       +        "BtDir",
       +        "BtDir+1",
       +        "BtDir+2",
       +        "BtDir+3",
       +        "BtDir+4",
       +        "BtDir+5",
       +        "BtDir+6",
       +        "BtDir+7",
       +};
       +
       +char*
       +btStr(int type)
       +{
       +        if(type < nelem(bttab))
       +                return bttab[type];
       +        return "unknown";
       +}
       +#pragma varargck argpos stringnode 1
       +
       +Block*
       +allocBlock(void)
       +{
       +        Block *b;
       +
       +        b = mallocz(sizeof(Block)+h.blockSize, 1);
       +        b->data = (void*)&b[1];
       +        return b;
       +}
       +
       +void
       +blockPut(Block *b)
       +{
       +        free(b);
       +}
       +
       +static u32int
       +partStart(int part)
       +{
       +        switch(part){
       +        default:
       +                assert(0);
       +        case PartSuper:
       +                return h.super;
       +        case PartLabel:
       +                return h.label;
       +        case PartData:
       +                return h.data;
       +        }
       +}
       +
       +
       +static u32int
       +partEnd(int part)
       +{
       +        switch(part){
       +        default:
       +                assert(0);
       +        case PartSuper:
       +                return h.super+1;
       +        case PartLabel:
       +                return h.data;
       +        case PartData:
       +                return h.end;
       +        }
       +}
       +
       +Block*
       +readBlock(int part, u32int addr)
       +{
       +        u32int start, end;
       +        u64int offset;
       +        int n, nn;
       +        Block *b;
       +        uchar *buf;
       +
       +        start = partStart(part);
       +        end = partEnd(part);
       +        if(addr >= end-start){
       +                werrstr("bad addr 0x%.8ux; wanted 0x%.8ux - 0x%.8ux", addr, start, end);
       +                return nil;
       +        }
       +
       +        b = allocBlock();
       +        b->addr = addr;
       +        buf = b->data;
       +        offset = ((u64int)(addr+start))*h.blockSize;
       +        n = h.blockSize;
       +        while(n > 0){
       +                nn = pread(fd, buf, n, offset);
       +                if(nn < 0){
       +                        blockPut(b);
       +                        return nil;
       +                }
       +                if(nn == 0){
       +                        werrstr("short read");
       +                        blockPut(b);
       +                        return nil;
       +                }
       +                n -= nn;
       +                offset += nn;
       +                buf += nn;
       +        }
       +        return b;
       +}
       +
       +int vtType[BtMax] = {
       +        VtDataType,                /* BtData | 0  */
       +        VtPointerType0,                /* BtData | 1  */
       +        VtPointerType1,                /* BtData | 2  */
       +        VtPointerType2,                /* BtData | 3  */
       +        VtPointerType3,                /* BtData | 4  */
       +        VtPointerType4,                /* BtData | 5  */
       +        VtPointerType5,                /* BtData | 6  */
       +        VtPointerType6,                /* BtData | 7  */
       +        VtDirType,                /* BtDir | 0  */
       +        VtPointerType0,                /* BtDir | 1  */
       +        VtPointerType1,                /* BtDir | 2  */
       +        VtPointerType2,                /* BtDir | 3  */
       +        VtPointerType3,                /* BtDir | 4  */
       +        VtPointerType4,                /* BtDir | 5  */
       +        VtPointerType5,                /* BtDir | 6  */
       +        VtPointerType6,                /* BtDir | 7  */
       +};
       +
       +Block*
       +ventiBlock(uchar score[VtScoreSize], uint type)
       +{
       +        int n;
       +        Block *b;
       +
       +        b = allocBlock();
       +        memmove(b->score, score, VtScoreSize);
       +        b->addr = NilBlock;
       +
       +        n = vtRead(z, b->score, vtType[type], b->data, h.blockSize);
       +        if(n < 0){
       +                fprint(2, "vtRead returns %d: %R\n", n);
       +                blockPut(b);
       +                return nil;
       +        }
       +        vtZeroExtend(vtType[type], b->data, n, h.blockSize);
       +        b->l.type = type;
       +        b->l.state = 0;
       +        b->l.tag = 0;
       +        b->l.epoch = 0;
       +        return b;
       +}
       +
       +Block*
       +dataBlock(uchar score[VtScoreSize], uint type, uint tag)
       +{
       +        Block *b, *bl;
       +        int lpb;
       +        Label l;
       +        u32int addr;
       +
       +        addr = globalToLocal(score);
       +        if(addr == NilBlock)
       +                return ventiBlock(score, type);
       +
       +        lpb = h.blockSize/LabelSize;
       +        bl = readBlock(PartLabel, addr/lpb);
       +        if(bl == nil)
       +                return nil;
       +        if(!labelUnpack(&l, bl->data, addr%lpb)){
       +                werrstr("%R");
       +                blockPut(bl);
       +                return nil;
       +        }
       +        blockPut(bl);
       +        if(l.type != type){
       +                werrstr("type mismatch; got %d (%s) wanted %d (%s)",
       +                        l.type, btStr(l.type), type, btStr(type));
       +                return nil;
       +        }
       +        if(tag && l.tag != tag){
       +                werrstr("tag mismatch; got 0x%.8ux wanted 0x%.8ux",
       +                        l.tag, tag);
       +                return nil;
       +        }
       +        b = readBlock(PartData, addr);
       +        if(b == nil)
       +                return nil;
       +        b->l = l;
       +        return b;
       +}
       +
       +Entry*
       +copyEntry(Entry e)
       +{
       +        Entry *p;
       +
       +        p = mallocz(sizeof *p, 1);
       +        *p = e;
       +        return p;
       +}
       +
       +MetaBlock*
       +copyMetaBlock(MetaBlock mb)
       +{
       +        MetaBlock *p;
       +
       +        p = mallocz(sizeof mb, 1);
       +        *p = mb;
       +        return p;
       +}
       +
       +/*
       + * visualizer 
       + */
       +
       +#pragma        varargck        argpos        stringnode        1
       +
       +Tnode*
       +stringnode(char *fmt, ...)
       +{
       +        va_list arg;
       +        Tnode *t;
       +
       +        t = mallocz(sizeof(Tnode), 1);
       +        va_start(arg, fmt);
       +        t->str = vsmprint(fmt, arg);
       +        va_end(arg);
       +        t->nkid = -1;
       +        return t;
       +}
       +
       +void
       +xcacheexpand(Tnode *t)
       +{
       +        if(t->nkid >= 0)
       +                return;
       +
       +        t->nkid = 1;
       +        t->kid = mallocz(sizeof(t->kid[0])*t->nkid, 1);
       +        t->kid[0] = initxheader();
       +}
       +
       +Tnode*
       +initxcache(char *name)
       +{
       +        Tnode *t;
       +
       +        if((fd = open(name, OREAD)) < 0)
       +                sysfatal("cannot open %s: %r", name);
       +
       +        t = stringnode("%s", name);
       +        t->expand = xcacheexpand;
       +        return t;
       +}
       +
       +void
       +xheaderexpand(Tnode *t)
       +{
       +        if(t->nkid >= 0)
       +                return;
       +
       +        t->nkid = 1;
       +        t->kid = mallocz(sizeof(t->kid[0])*t->nkid, 1);
       +        t->kid[0] = initxsuper();
       +        //t->kid[1] = initxlabel(h.label);
       +        //t->kid[2] = initxdata(h.data);
       +}
       +
       +Tnode*
       +initxheader(void)
       +{
       +        u8int buf[HeaderSize];
       +        Tnode *t;
       +
       +        if(pread(fd, buf, HeaderSize, HeaderOffset) < HeaderSize)
       +                return stringnode("error reading header: %r");
       +        if(!headerUnpack(&h, buf))
       +                return stringnode("error unpacking header: %R");
       +
       +        t = stringnode("header "
       +                "version=%#ux (%d) "
       +                "blockSize=%#ux (%d) "
       +                "super=%#lux (%ld) "
       +                "label=%#lux (%ld) "
       +                "data=%#lux (%ld) "
       +                "end=%#lux (%ld)",
       +                h.version, h.version, h.blockSize, h.blockSize,
       +                h.super, h.super,
       +                h.label, h.label, h.data, h.data, h.end, h.end);
       +        t->expand = xheaderexpand;
       +        return t;
       +}
       +
       +void
       +xsuperexpand(Tnode *t)
       +{
       +        if(t->nkid >= 0)
       +                return;
       +
       +        t->nkid = 1;
       +        t->kid = mallocz(sizeof(t->kid[0])*t->nkid, 1);
       +        t->kid[0] = initxlocalroot("active", super.active);
       +//        t->kid[1] = initxlocalroot("next", super.next);
       +//        t->kid[2] = initxlocalroot("current", super.current);
       +}
       +
       +Tnode*
       +initxsuper(void)
       +{
       +        Block *b;
       +        Tnode *t;
       +
       +        b = readBlock(PartSuper, 0);
       +        if(b == nil)
       +                return stringnode("reading super: %r");
       +        if(!superUnpack(&super, b->data)){
       +                blockPut(b);
       +                return stringnode("unpacking super: %R");
       +        }
       +        blockPut(b);
       +        t = stringnode("super "
       +                "version=%#ux "
       +                "epoch=[%#ux,%#ux) "
       +                "qid=%#llux "
       +                "active=%#x "
       +                "next=%#x "
       +                "current=%#x "
       +                "last=%V "
       +                "name=%s",
       +                super.version, super.epochLow, super.epochHigh,
       +                super.qid, super.active, super.next, super.current,
       +                super.last, super.name);
       +        t->expand = xsuperexpand;
       +        return t;
       +}
       +
       +void
       +xvacrootexpand(Tnode *t)
       +{
       +        if(t->nkid >= 0)
       +                return;
       +
       +        t->nkid = 1;
       +        t->kid = mallocz(sizeof(t->kid[0])*t->nkid, 1);
       +        t->kid[0] = initxroot("root", vac.score);
       +}
       +
       +Tnode*
       +initxvacroot(uchar score[VtScoreSize])
       +{
       +        Tnode *t;
       +        uchar buf[VtRootSize];
       +        int n;
       +
       +        if((n = vtRead(z, score, VtRootType, buf, VtRootSize)) < 0)
       +                return stringnode("reading root %V: %R", score);
       +
       +        if(!vtRootUnpack(&vac, buf))
       +                return stringnode("unpack %d-byte root: %R", n);
       +
       +        h.blockSize = vac.blockSize;
       +        t = stringnode("vac version=%#ux name=%s type=%s blockSize=%ud score=%V prev=%V",
       +                vac.version, vac.name, vac.type, vac.blockSize, vac.score, vac.prev);
       +        t->expand = xvacrootexpand;
       +        return t;
       +}
       +
       +Tnode*
       +initxlabel(Label l)
       +{
       +        return stringnode("label type=%s state=%s epoch=%#ux tag=%#ux",
       +                btStr(l.type), bsStr(l.state), l.epoch, l.tag);
       +}
       +
       +typedef struct Xblock Xblock;
       +struct Xblock
       +{
       +        Tnode;
       +        Block *b;
       +        int (*gen)(void*, Block*, int, Tnode**);
       +        void *arg;
       +        int printlabel;
       +};
       +
       +void
       +xblockexpand(Tnode *tt)
       +{
       +        int i, j;
       +        enum { Q = 32 };
       +        Xblock *t = (Xblock*)tt;
       +        Tnode *nn;
       +
       +        if(t->nkid >= 0)
       +                return;
       +
       +        j = 0;
       +        if(t->printlabel){
       +                t->kid = mallocz(Q*sizeof(t->kid[0]), 1);
       +                t->kid[0] = initxlabel(t->b->l);
       +                j = 1;
       +        }
       +
       +        for(i=0;; i++){
       +                switch((*t->gen)(t->arg, t->b, i, &nn)){
       +                case -1:
       +                        t->nkid = j;
       +                        return;
       +                case 0:
       +                        break;
       +                case 1:
       +                        if(j%Q == 0)
       +                                t->kid = realloc(t->kid, (j+Q)*sizeof(t->kid[0]));
       +                        t->kid[j++] = nn;
       +                        break;
       +                }
       +        }
       +}
       +
       +int
       +nilgen(void*, Block*, int, Tnode**)
       +{
       +        return -1;
       +}
       +
       +Tnode*
       +initxblock(Block *b, char *s, int (*gen)(void*, Block*, int, Tnode**), void *arg)
       +{
       +        Xblock *t;
       +
       +        if(gen == nil)
       +                gen = nilgen;
       +        t = mallocz(sizeof(Xblock), 1);
       +        t->b = b;
       +        t->gen = gen;
       +        t->arg = arg;
       +        if(b->addr == NilBlock)
       +                t->str = smprint("Block %V: %s", b->score, s);
       +        else
       +                t->str = smprint("Block %#ux: %s", b->addr, s);
       +        t->printlabel = 1;
       +        t->nkid = -1;
       +        t->expand = xblockexpand;
       +        return t;
       +}
       +
       +int
       +xentrygen(void *v, Block *b, int o, Tnode **tp)
       +{
       +        Entry e;
       +        Entry *ed;
       +
       +        ed = v;
       +        if(o >= ed->dsize/VtEntrySize)
       +                return -1;
       +
       +        entryUnpack(&e, b->data, o);
       +        if(!showinactive && !(e.flags & VtEntryActive))
       +                return 0;
       +        *tp = initxentry(e);
       +        return 1;
       +}
       +
       +Tnode*
       +initxentryblock(Block *b, Entry *ed)
       +{
       +        return initxblock(b, "entry", xentrygen, ed);
       +}
       +
       +typedef struct Xentry Xentry;
       +struct Xentry 
       +{
       +        Tnode;
       +        Entry e;
       +};
       +
       +void
       +xentryexpand(Tnode *tt)
       +{
       +        Xentry *t = (Xentry*)tt;
       +
       +        if(t->nkid >= 0)
       +                return;
       +
       +        t->nkid = 1;
       +        t->kid = mallocz(sizeof(t->kid[0])*t->nkid, 1);
       +        t->kid[0] = initxsource(t->e, 1);
       +}
       +
       +Tnode*
       +initxentry(Entry e)
       +{
       +        Xentry *t;
       +
       +        t = mallocz(sizeof *t, 1);
       +        t->nkid = -1;
       +        t->str = smprint("Entry gen=%#ux psize=%d dsize=%d depth=%d flags=%#ux size=%lld score=%V",
       +                e.gen, e.psize, e.dsize, e.depth, e.flags, e.size, e.score);
       +        if(e.flags & VtEntryLocal)
       +                t->str = smprint("%s archive=%d snap=%d tag=%#ux", t->str, e.archive, e.snap, e.tag);
       +        t->expand = xentryexpand;
       +        t->e = e;
       +        return t;        
       +}
       +
       +int
       +ptrgen(void *v, Block *b, int o, Tnode **tp)
       +{
       +        Entry *ed;
       +        Entry e;
       +
       +        ed = v;
       +        if(o >= ed->psize/VtScoreSize)
       +                return -1;
       +
       +        e = *ed;
       +        e.depth--;
       +        memmove(e.score, b->data+o*VtScoreSize, VtScoreSize);
       +        if(memcmp(e.score, vtZeroScore, VtScoreSize) == 0)
       +                return 0;
       +        *tp = initxsource(e, 0);
       +        return 1;
       +}
       +
       +static int
       +etype(int flags, int depth)
       +{
       +        uint t;
       +
       +        if(flags&VtEntryDir)
       +                t = BtDir;
       +        else
       +                t = BtData;
       +        return t+depth;
       +}
       +
       +Tnode*
       +initxsource(Entry e, int dowrap)
       +{
       +        Block *b;
       +        Tnode *t, *tt;
       +
       +        b = dataBlock(e.score, etype(e.flags, e.depth), e.tag);
       +        if(b == nil)
       +                return stringnode("dataBlock: %r");
       +
       +        if((e.flags & VtEntryActive) == 0)
       +                return stringnode("inactive Entry");
       +
       +        if(e.depth == 0){
       +                if(e.flags & VtEntryDir)
       +                        tt = initxentryblock(b, copyEntry(e));
       +                else
       +                        tt = initxdatablock(b, e.dsize);
       +        }else{
       +                tt = initxblock(b, smprint("%s+%d pointer", (e.flags & VtEntryDir) ? "BtDir" : "BtData", e.depth),
       +                        ptrgen, copyEntry(e));
       +        }
       +
       +        /*
       +         * wrap the contents of the Source in a Source node,
       +         * just so it's closer to what you see in the code.
       +         */
       +        if(dowrap){
       +                t = stringnode("Source");
       +                t->nkid = 1;
       +                t->kid = mallocz(sizeof(Tnode*)*1, 1);
       +                t->kid[0] = tt;
       +                tt = t;
       +        }
       +        return tt;
       +}
       +
       +int
       +xlocalrootgen(void*, Block *b, int o, Tnode **tp)
       +{
       +        Entry e;
       +
       +        if(o >= 1)
       +                return -1;
       +        entryUnpack(&e, b->data, o);
       +        *tp = initxentry(e);
       +        return 1;
       +}
       +
       +Tnode*
       +initxlocalroot(char *name, u32int addr)
       +{
       +        uchar score[VtScoreSize];
       +        Block *b;
       +
       +        localToGlobal(addr, score);
       +        b = dataBlock(score, BtDir, RootTag);
       +        if(b == nil)
       +                return stringnode("read data block %#ux: %R", addr);
       +        return initxblock(b, smprint("'%s' fs root", name), xlocalrootgen, nil);
       +}
       +
       +int
       +xvacrootgen(void*, Block *b, int o, Tnode **tp)
       +{
       +        Entry e;
       +
       +        if(o >= 3)
       +                return -1;
       +        entryUnpack(&e, b->data, o);
       +        *tp = initxentry(e);
       +        return 1;
       +}
       +
       +Tnode*
       +initxroot(char *name, uchar score[VtScoreSize])
       +{
       +        Block *b;
       +
       +        b = dataBlock(score, BtDir, RootTag);
       +        if(b == nil)
       +                return stringnode("read data block %V: %R", score);
       +        return initxblock(b, smprint("'%s' fs root", name), xvacrootgen, nil);
       +}
       +Tnode*
       +initxdirentry(MetaEntry *me)
       +{
       +        DirEntry dir;
       +        Tnode *t;
       +
       +        if(!deUnpack(&dir, me))
       +                return stringnode("deUnpack: %R");
       +
       +        t = stringnode("dirEntry elem=%s size=%llud data=%#lux/%#lux meta=%#lux/%#lux", dir.elem, dir.size, dir.entry, dir.gen, dir.mentry, dir.mgen);
       +        t->nkid = 1;
       +        t->kid = mallocz(sizeof(t->kid[0])*1, 1);
       +        t->kid[0] = stringnode(
       +                "qid=%#llux\n"
       +                "uid=%s gid=%s mid=%s\n"
       +                "mtime=%lud mcount=%lud ctime=%lud atime=%lud\n"
       +                "mode=%luo\n"
       +                "plan9 %d p9path %#llux p9version %lud\n"
       +                "qidSpace %d offset %#llux max %#llux",
       +                dir.qid,
       +                dir.uid, dir.gid, dir.mid,
       +                dir.mtime, dir.mcount, dir.ctime, dir.atime,
       +                dir.mode,
       +                dir.plan9, dir.p9path, dir.p9version,
       +                dir.qidSpace, dir.qidOffset, dir.qidMax);
       +        return t;
       +}
       +
       +int
       +metaentrygen(void *v, Block*, int o, Tnode **tp)
       +{
       +        Tnode *t;
       +        MetaBlock *mb;
       +        MetaEntry me;
       +
       +        mb = v;
       +        if(o >= mb->nindex)
       +                return -1;
       +        meUnpack(&me, mb, o);
       +
       +        t = stringnode("MetaEntry %d bytes", mb->size);
       +        t->kid = mallocz(sizeof(t->kid[0])*1, 1);
       +        t->kid[0] = initxdirentry(&me);
       +        t->nkid = 1;
       +        *tp = t;
       +        return 1;
       +}
       +
       +int
       +metablockgen(void *v, Block *b, int o, Tnode **tp)
       +{
       +        Xblock *t;
       +        MetaBlock *mb;
       +
       +        if(o >= 1)
       +                return -1;
       +
       +        /* hack: reuse initxblock as a generic iterator */
       +        mb = v;
       +        t = (Xblock*)initxblock(b, "", metaentrygen, mb);
       +        t->str = smprint("MetaBlock %d/%d space used, %d add'l free %d/%d table used%s",
       +                mb->size, mb->maxsize, mb->free, mb->nindex, mb->maxindex,
       +                mb->botch ? " [BOTCH]" : "");
       +        t->printlabel = 0;
       +        *tp = t;
       +        return 1;
       +}
       +
       +/*
       + * attempt to guess at the type of data in the block.
       + * it could just be data from a file, but we're hoping it's MetaBlocks.
       + */
       +Tnode*
       +initxdatablock(Block *b, uint n)
       +{
       +        MetaBlock mb;
       +
       +        if(n > h.blockSize)
       +                n = h.blockSize;
       +
       +        if(mbUnpack(&mb, b->data, n))
       +                return initxblock(b, "metadata", metablockgen, copyMetaBlock(mb));
       +
       +        return initxblock(b, "data", nil, nil);
       +}
       +
       +int
       +parseScore(uchar *score, char *buf, int n)
       +{
       +        int i, c;
       +
       +        memset(score, 0, VtScoreSize);
       +
       +        if(n < VtScoreSize*2)
       +                return 0;
       +        for(i=0; i<VtScoreSize*2; i++){
       +                if(buf[i] >= '0' && buf[i] <= '9')
       +                        c = buf[i] - '0';
       +                else if(buf[i] >= 'a' && buf[i] <= 'f')
       +                        c = buf[i] - 'a' + 10;
       +                else if(buf[i] >= 'A' && buf[i] <= 'F')
       +                        c = buf[i] - 'A' + 10;
       +                else{
       +                        return 0;
       +                }
       +
       +                if((i & 1) == 0)
       +                        c <<= 4;
       +        
       +                score[i>>1] |= c;
       +        }
       +        return 1;
       +}
       +
       +int
       +scoreFmt(Fmt *f)
       +{
       +        uchar *v;
       +        int i;
       +        u32int addr;
       +
       +        v = va_arg(f->args, uchar*);
       +        if(v == nil){
       +                fmtprint(f, "*");
       +        }else if((addr = globalToLocal(v)) != NilBlock)
       +                fmtprint(f, "0x%.8ux", addr);
       +        else{
       +                for(i = 0; i < VtScoreSize; i++)
       +                        fmtprint(f, "%2.2ux", v[i]);
       +        }
       +
       +        return 0;
       +}
       +
       +Atree*
       +atreeinit(char *arg)
       +{
       +        Atree *a;
       +        uchar score[VtScoreSize];
       +
       +        vtAttach();
       +
       +        fmtinstall('V', scoreFmt);
       +        fmtinstall('R', vtErrFmt);
       +
       +        z = vtDial(nil, 1);
       +        if(z == nil)
       +                fprint(2, "warning: cannot dial venti: %R\n");
       +        if(!vtConnect(z, 0)){
       +                fprint(2, "warning: cannot connect to venti: %R\n");
       +                z = nil;
       +        }
       +        a = mallocz(sizeof(Atree), 1);
       +        if(strncmp(arg, "vac:", 4) == 0){
       +                if(!parseScore(score, arg+4, strlen(arg+4))){
       +                        fprint(2, "cannot parse score\n");
       +                        return nil;
       +                }
       +                a->root = initxvacroot(score);
       +        }else
       +                a->root = initxcache(arg);
       +        a->resizefd = -1;
       +        return a;
       +}
       +
       +/* --- tree.c */
       +enum
       +{
       +        Nubwidth = 11,
       +        Nubheight = 11,
       +        Linewidth = Nubwidth*2+4,
       +};
       +
       +uint
       +drawtext(char *s, Image *m, Image *clipr, Point o)
       +{
       +        char *t, *nt, *e;
       +        uint dy;
       +
       +        if(s == nil)
       +                s = "???";
       +
       +        dy = 0;
       +        for(t=s; t&&*t; t=nt){
       +                if(nt = strchr(t, '\n')){
       +                        e = nt;
       +                        nt++;
       +                }else
       +                        e = t+strlen(t);
       +
       +                _string(m, Pt(o.x, o.y+dy), display->black, ZP, display->defaultfont,
       +                        t, nil, e-t, clipr->clipr, nil, ZP, SoverD);
       +                dy += display->defaultfont->height;
       +        }
       +        return dy;
       +}
       +
       +void
       +drawnub(Image *m, Image *clipr, Point o, Tnode *t)
       +{
       +        clipr = nil;
       +
       +        if(t->nkid == 0)
       +                return;
       +        if(t->nkid == -1 && t->expand == nil)
       +                return;
       +
       +        o.y += (display->defaultfont->height-Nubheight)/2;
       +        draw(m, rectaddpt(Rect(0,0,1,Nubheight), o), display->black, clipr, ZP);
       +        draw(m, rectaddpt(Rect(0,0,Nubwidth,1), o), display->black, clipr, o);
       +        draw(m, rectaddpt(Rect(Nubwidth-1,0,Nubwidth,Nubheight), o), 
       +                display->black, clipr, addpt(o, Pt(Nubwidth-1, 0)));
       +        draw(m, rectaddpt(Rect(0, Nubheight-1, Nubwidth, Nubheight), o),
       +                display->black, clipr, addpt(o, Pt(0, Nubheight-1)));
       +
       +        draw(m, rectaddpt(Rect(0, Nubheight/2, Nubwidth, Nubheight/2+1), o),
       +                display->black, clipr, addpt(o, Pt(0, Nubheight/2)));
       +        if(!t->expanded)
       +                draw(m, rectaddpt(Rect(Nubwidth/2, 0, Nubwidth/2+1, Nubheight), o),
       +                        display->black, clipr, addpt(o, Pt(Nubwidth/2, 0)));
       +
       +}
       +
       +uint
       +drawnode(Tnode *t, Image *m, Image *clipr, Point o)
       +{
       +        int i;
       +        char *fs, *s;
       +        uint dy;
       +        Point oo;
       +
       +        if(t == nil)
       +                return 0;
       +
       +        t->offset = o;
       +
       +        oo = Pt(o.x+Nubwidth+2, o.y);
       +//        if(t->draw)
       +//                dy = (*t->draw)(t, m, clipr, oo);
       +//        else{
       +                fs = nil;
       +                if(t->str)
       +                        s = t->str;
       +        //        else if(t->strfn)
       +        //                fs = s = (*t->strfn)(t);
       +                else
       +                        s = "???";
       +                dy = drawtext(s, m, clipr, oo);
       +                free(fs);
       +//        }
       +
       +        if(t->expanded){
       +                if(t->nkid == -1 && t->expand)
       +                        (*t->expand)(t);
       +                oo = Pt(o.x+Nubwidth+(Linewidth-Nubwidth)/2, o.y+dy);
       +                for(i=0; i<t->nkid; i++)
       +                        oo.y += drawnode(t->kid[i], m, clipr, oo);
       +                dy = oo.y - o.y;
       +        }
       +        drawnub(m, clipr, o, t);
       +        return dy;
       +}
       +
       +void
       +drawtree(Tree *t, Image *m, Rectangle r)
       +{
       +        Point p;
       +
       +        draw(m, r, display->white, nil, ZP);
       +
       +        replclipr(t->clipr, 1, r);
       +        p = addpt(t->offset, r.min);
       +        drawnode(t->root, m, t->clipr, p);
       +}
       +
       +Tnode*
       +findnode(Tnode *t, Point p)
       +{
       +        int i;
       +        Tnode *tt;
       +
       +        if(ptinrect(p, rectaddpt(Rect(0,0,Nubwidth, Nubheight), t->offset)))
       +                return t;
       +        if(!t->expanded)
       +                return nil;
       +        for(i=0; i<t->nkid; i++)
       +                if(tt = findnode(t->kid[i], p))
       +                        return tt;
       +        return nil;
       +}
       +
       +void
       +usage(void)
       +{
       +        fprint(2, "usage: vtree /dev/sdC0/fossil\n");
       +        exits("usage");
       +}
       +
       +Tree t;
       +
       +void
       +eresized(int new)
       +{
       +        Rectangle r;
       +        r = screen->r;
       +        if(new && getwindow(display, Refnone) < 0)
       +                fprint(2,"can't reattach to window");
       +        drawtree(&t, screen, screen->r);
       +}
       +
       +enum
       +{
       +        Left = 1<<0,
       +        Middle = 1<<1,
       +        Right = 1<<2,
       +
       +        MMenu = 2,
       +};
       +
       +char *items[] = { "exit", 0 };
       +enum { IExit, };
       +
       +Menu menu;
       +
       +void
       +main(int argc, char **argv)
       +{
       +        int n;
       +        char *dir;
       +        Event e;
       +        Point op, p;
       +        Tnode *tn;
       +        Mouse m;
       +        int Eready;
       +        Atree *fs;
       +
       +        ARGBEGIN{
       +        case 'a':
       +                showinactive = 1;
       +                break;
       +        default:
       +                usage();
       +        }ARGEND
       +
       +        switch(argc){
       +        default:
       +                usage();
       +        case 1:
       +                dir = argv[0];
       +                break;
       +        }
       +
       +        fs = atreeinit(dir);
       +        initdraw(0, "/lib/font/bit/lucidasans/unicode.8.font", "tree");
       +        t.root = fs->root;
       +        t.offset = ZP;
       +        t.clipr = allocimage(display, Rect(0,0,1,1), GREY1, 1, DOpaque);
       +
       +        eresized(0);
       +        flushimage(display, 1);
       +
       +        einit(Emouse);
       +
       +        menu.item = items;
       +        menu.gen = 0;
       +        menu.lasthit = 0;
       +        if(fs->resizefd > 0){
       +                Eready = 1<<3;
       +                estart(Eready, fs->resizefd, 1);
       +        }else
       +                Eready = 0;
       +
       +        for(;;){
       +                switch(n=eread(Emouse|Eready, &e)){
       +                default:
       +                        if(Eready && n==Eready)
       +                                eresized(0);
       +                        break;
       +                case Emouse:
       +                        m = e.mouse;
       +                        switch(m.buttons){
       +                        case Left:
       +                                op = t.offset;
       +                                p = m.xy;
       +                                do {
       +                                        t.offset = addpt(t.offset, subpt(m.xy, p));
       +                                        p = m.xy;
       +                                        eresized(0);
       +                                        m = emouse();
       +                                }while(m.buttons == Left);
       +                                if(m.buttons){
       +                                        t.offset = op;
       +                                        eresized(0);
       +                                }
       +                                break;
       +                        case Middle:
       +                                n = emenuhit(MMenu, &m, &menu);
       +                                if(n == -1)
       +                                        break;
       +                                switch(n){
       +                                case IExit:
       +                                        exits(nil);
       +                                }
       +                                break;
       +                        case Right:
       +                                do
       +                                        m = emouse();
       +                                while(m.buttons == Right);
       +                                if(m.buttons)
       +                                        break;
       +                                tn = findnode(t.root, m.xy);
       +                                if(tn){
       +                                        tn->expanded = !tn->expanded;
       +                                        eresized(0);
       +                                }
       +                                break;
       +                        }
       +                }
       +        }
       +}
 (DIR) diff --git a/src/cmd/fossil/walk.c b/src/cmd/fossil/walk.c
       t@@ -0,0 +1,65 @@
       +/*
       + * Generic traversal routines.
       + */
       +
       +#include "stdinc.h"
       +#include "dat.h"
       +#include "fns.h"
       +
       +static uint
       +etype(Entry *e)
       +{
       +        uint t;
       +
       +        if(e->flags&VtEntryDir)
       +                t = BtDir;
       +        else
       +                t = BtData;
       +        return t+e->depth;
       +}
       +
       +void
       +initWalk(WalkPtr *w, Block *b, uint size)
       +{
       +        memset(w, 0, sizeof *w);
       +        switch(b->l.type){
       +        case BtData:
       +                return;
       +
       +        case BtDir:
       +                w->data = b->data;
       +                w->m = size / VtEntrySize;
       +                w->isEntry = 1;
       +                return;
       +
       +        default:
       +                w->data = b->data;
       +                w->m = size / VtScoreSize;
       +                w->type = b->l.type;
       +                w->tag = b->l.tag;
       +                return;
       +        }
       +}
       +
       +int
       +nextWalk(WalkPtr *w, uchar score[VtScoreSize], uchar *type, u32int *tag, Entry **e)
       +{
       +        if(w->n >= w->m)
       +                return 0;
       +
       +        if(w->isEntry){
       +                *e = &w->e;
       +                entryUnpack(&w->e, w->data, w->n);
       +                memmove(score, w->e.score, VtScoreSize);
       +                *type = etype(&w->e);
       +                *tag = w->e.tag;
       +        }else{
       +                *e = nil;
       +                memmove(score, w->data+w->n*VtScoreSize, VtScoreSize);
       +                *type = w->type-1;
       +                *tag = w->tag;
       +        }
       +        w->n++;
       +        return 1;
       +}
       +