fossil: import from plan 9

R=rsc
https://codereview.appspot.com/7988047
This commit is contained in:
David du Colombier 2013-09-23 23:00:39 +02:00
parent fea86f0639
commit 6f4d00ee45
50 changed files with 22325 additions and 0 deletions

514
man/man4/fossil.4 Normal file
View File

@ -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.

1207
man/man8/fossilcons.8 Normal file

File diff suppressed because it is too large Load Diff

258
src/cmd/fossil/9.h Normal file
View File

@ -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;

175
src/cmd/fossil/9auth.c Normal file
View File

@ -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;
}

132
src/cmd/fossil/9dir.c Normal file
View File

@ -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;
}

126
src/cmd/fossil/9excl.c Normal file
View File

@ -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();
}

304
src/cmd/fossil/9fid.c Normal file
View File

@ -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();
}

1896
src/cmd/fossil/9fsys.c Normal file

File diff suppressed because it is too large Load Diff

184
src/cmd/fossil/9lstn.c Normal file
View File

@ -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;
}

1181
src/cmd/fossil/9p.c Normal file

File diff suppressed because it is too large Load Diff

109
src/cmd/fossil/9ping.c Normal file
View File

@ -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);
}

825
src/cmd/fossil/9proc.c Normal file
View File

@ -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);
}

242
src/cmd/fossil/9srv.c Normal file
View File

@ -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;
}

948
src/cmd/fossil/9user.c Normal file
View File

@ -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;
}

112
src/cmd/fossil/Ccli.c Normal file
View File

@ -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;
}

459
src/cmd/fossil/Ccmd.c Normal file
View File

@ -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;
}

398
src/cmd/fossil/Ccons.c Normal file
View File

@ -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;
}

40
src/cmd/fossil/Clog.c Normal file
View File

@ -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;
}

466
src/cmd/fossil/archive.c Normal file
View File

@ -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);
}

421
src/cmd/fossil/bwatch.c Normal file
View File

@ -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];
}

2114
src/cmd/fossil/cache.c Normal file

File diff suppressed because it is too large Load Diff

799
src/cmd/fossil/check.c Normal file
View File

@ -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;
}

332
src/cmd/fossil/dat.h Normal file
View File

@ -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];

406
src/cmd/fossil/disk.c Normal file
View File

@ -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);
}

86
src/cmd/fossil/dump.c Normal file
View File

@ -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);
}
}

51
src/cmd/fossil/epoch.c Normal file
View File

@ -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);
}

38
src/cmd/fossil/error.c Normal file
View File

@ -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";

33
src/cmd/fossil/error.h Normal file
View File

@ -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[];

1860
src/cmd/fossil/file.c Normal file

File diff suppressed because it is too large Load Diff

118
src/cmd/fossil/flchk.c Normal file
View File

@ -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);
}

571
src/cmd/fossil/flfmt.c Normal file
View File

@ -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;
}

565
src/cmd/fossil/flfmt9660.c Normal file
View File

@ -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--;
}

View File

@ -0,0 +1,3 @@
void iso9660init(int fd, Header *h, char*, int);
void iso9660labels(Disk*, uchar*, void(*write)(int, u32int));
void iso9660copy(Fs*);

106
src/cmd/fossil/fns.h Normal file
View File

@ -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*

143
src/cmd/fossil/fossil.c Normal file
View File

@ -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);
}

1099
src/cmd/fossil/fs.c Normal file

File diff suppressed because it is too large Load Diff

67
src/cmd/fossil/fs.h Normal file
View File

@ -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*);

40
src/cmd/fossil/last.c Normal file
View File

@ -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);
}

136
src/cmd/fossil/mkfile Normal file
View File

@ -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

39
src/cmd/fossil/nobwatch.c Normal file
View File

@ -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);
}

225
src/cmd/fossil/pack.c Normal file
View File

@ -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;
}

84
src/cmd/fossil/periodic.c Normal file
View File

@ -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);
}

1068
src/cmd/fossil/source.c Normal file

File diff suppressed because it is too large Load Diff

270
src/cmd/fossil/srcload.c Normal file
View File

@ -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);
}

11
src/cmd/fossil/stdinc.h Normal file
View File

@ -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"

19
src/cmd/fossil/trunc.c Normal file
View File

@ -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);
}

746
src/cmd/fossil/vac.c Normal file
View File

@ -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);
}

107
src/cmd/fossil/vac.h Normal file
View File

@ -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);

1127
src/cmd/fossil/view.c Normal file

File diff suppressed because it is too large Load Diff

65
src/cmd/fossil/walk.c Normal file
View File

@ -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;
}