2912 lines
		
	
	
		
			66 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			2912 lines
		
	
	
		
			66 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * Copyright (c) 2013, 2015
 | 
						|
 *	The President and Fellows of Harvard College.
 | 
						|
 *      Written by David A. Holland.
 | 
						|
 *
 | 
						|
 * Redistribution and use in source and binary forms, with or without
 | 
						|
 * modification, are permitted provided that the following conditions
 | 
						|
 * are met:
 | 
						|
 * 1. Redistributions of source code must retain the above copyright
 | 
						|
 *    notice, this list of conditions and the following disclaimer.
 | 
						|
 * 2. Redistributions in binary form must reproduce the above copyright
 | 
						|
 *    notice, this list of conditions and the following disclaimer in the
 | 
						|
 *    documentation and/or other materials provided with the distribution.
 | 
						|
 * 3. Neither the name of the University nor the names of its contributors
 | 
						|
 *    may be used to endorse or promote products derived from this software
 | 
						|
 *    without specific prior written permission.
 | 
						|
 *
 | 
						|
 * THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY AND CONTRIBUTORS ``AS IS'' AND
 | 
						|
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 | 
						|
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 | 
						|
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE UNIVERSITY OR CONTRIBUTORS BE LIABLE
 | 
						|
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 | 
						|
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 | 
						|
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 | 
						|
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 | 
						|
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 | 
						|
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 | 
						|
 * SUCH DAMAGE.
 | 
						|
 */
 | 
						|
 | 
						|
#include <sys/types.h>
 | 
						|
#include <sys/stat.h>
 | 
						|
#include <stdint.h>
 | 
						|
#include <stdio.h>
 | 
						|
#include <stdarg.h>
 | 
						|
#include <string.h>
 | 
						|
#include <unistd.h>
 | 
						|
#include <limits.h>
 | 
						|
#include <assert.h>
 | 
						|
#include <errno.h>
 | 
						|
#include <err.h>
 | 
						|
 | 
						|
#include "name.h"
 | 
						|
#include "data.h"
 | 
						|
#include "pool.h"
 | 
						|
#include "check.h"
 | 
						|
 | 
						|
/*
 | 
						|
 * Check pass.
 | 
						|
 *
 | 
						|
 * We first replay the workload into a model that keeps track of what
 | 
						|
 * should be on the volume at each step; then we inspect the real
 | 
						|
 * volume and compare it to the model.
 | 
						|
 */
 | 
						|
 | 
						|
////////////////////////////////////////////////////////////
 | 
						|
// model representation
 | 
						|
 | 
						|
#define UNKNOWN_ID ((unsigned)-1)
 | 
						|
 | 
						|
/*
 | 
						|
 * 1. log of changes to a filesystem
 | 
						|
 * (this is not quite fully general but supports only things we do)
 | 
						|
 */
 | 
						|
 | 
						|
enum fschanges {
 | 
						|
	FC_NEWFS,		/* create the volume */
 | 
						|
	FC_TRUNCATE,		/* truncate a file */
 | 
						|
	FC_WRITE,		/* write to a file */
 | 
						|
	FC_CREAT,		/* create a file */
 | 
						|
	FC_MKDIR,		/* create a directory */
 | 
						|
	FC_RMDIR,		/* remove a directory */
 | 
						|
	FC_UNLINK,		/* remove a file */
 | 
						|
	FC_LINK,		/* hardlink a file */
 | 
						|
	FC_RENAMEFILE,		/* rename a file */
 | 
						|
	FC_RENAMEDIR,		/* rename a directory */
 | 
						|
};
 | 
						|
struct fschange {
 | 
						|
	/* all changes are kept in order on a doubly linked list */
 | 
						|
	struct fschange *prev;
 | 
						|
	struct fschange *next;
 | 
						|
 | 
						|
	/* version number for this change */
 | 
						|
	unsigned version;
 | 
						|
 | 
						|
	/* whether this change reflects a partially committed operation */
 | 
						|
	int partial;
 | 
						|
 | 
						|
	/* the change type, parameters, and metadata */
 | 
						|
	enum fschanges type;
 | 
						|
	union {
 | 
						|
		struct {
 | 
						|
			/* object id of the root directory */
 | 
						|
			unsigned rootdirnum;
 | 
						|
		} fc_newfs;
 | 
						|
		struct {
 | 
						|
			/* cached: previous change affecting this file */
 | 
						|
			struct fschange *prev_thisfile;
 | 
						|
			/* truncate params: file oid and new length */
 | 
						|
			unsigned file;
 | 
						|
			off_t len;
 | 
						|
		} fc_truncate;
 | 
						|
		struct {
 | 
						|
			/* cached: previous change affecting this file */
 | 
						|
			struct fschange *prev_thisfile;
 | 
						|
			/* write params: file oid, position, length */
 | 
						|
			unsigned file;
 | 
						|
			off_t pos;
 | 
						|
			off_t len;
 | 
						|
			/* size of file before the write */
 | 
						|
			off_t oldfilesize;
 | 
						|
			/* key for generating the write contents */
 | 
						|
			unsigned code;
 | 
						|
			unsigned seq;
 | 
						|
		} fc_write;
 | 
						|
		struct {
 | 
						|
			/* cached: previous change affecting containing dir */
 | 
						|
			struct fschange *prev_thisdir;
 | 
						|
			/* creat params: containing dir oid, name, new oid */
 | 
						|
			unsigned dir;
 | 
						|
			unsigned name;
 | 
						|
			unsigned newfile;
 | 
						|
		} fc_creat;
 | 
						|
		struct {
 | 
						|
			/* cached: previous change affecting containing dir */
 | 
						|
			struct fschange *prev_thisdir;
 | 
						|
			/* mkdir params: containing dir oid, name, new oid */
 | 
						|
			unsigned dir;
 | 
						|
			unsigned name;
 | 
						|
			unsigned newdir;
 | 
						|
		} fc_mkdir;
 | 
						|
		struct {
 | 
						|
			/* cached: previous change affecting either dir */
 | 
						|
			struct fschange *prev_thisdir;
 | 
						|
			struct fschange *prev_victimdir;
 | 
						|
			/* rmdir params */
 | 
						|
			unsigned dir;
 | 
						|
			unsigned name;
 | 
						|
			unsigned victimdir;
 | 
						|
		} fc_rmdir;
 | 
						|
		struct {
 | 
						|
			/* cached: previous change affecting either object */
 | 
						|
			struct fschange *prev_thisdir;
 | 
						|
			struct fschange *prev_victimfile;
 | 
						|
			/* unlink params */
 | 
						|
			unsigned dir;
 | 
						|
			unsigned name;
 | 
						|
			unsigned victimfile;
 | 
						|
		} fc_unlink;
 | 
						|
		struct {
 | 
						|
			/* cached: previous change affecting each object */
 | 
						|
			struct fschange *prev_fromdir;
 | 
						|
			struct fschange *prev_todir;
 | 
						|
			struct fschange *prev_thisfile;
 | 
						|
			/* link params */
 | 
						|
			unsigned fromdir;
 | 
						|
			unsigned fromname;
 | 
						|
			unsigned todir;
 | 
						|
			unsigned toname;
 | 
						|
			unsigned file;
 | 
						|
		} fc_link;
 | 
						|
		struct {
 | 
						|
			/* cached: previous change affecting each object */
 | 
						|
			struct fschange *prev_fromdir;
 | 
						|
			struct fschange *prev_todir;
 | 
						|
			struct fschange *prev_movedfile;
 | 
						|
			/* rename params */
 | 
						|
			unsigned fromdir;
 | 
						|
			unsigned fromname;
 | 
						|
			unsigned todir;
 | 
						|
			unsigned toname;
 | 
						|
			unsigned movedfile;
 | 
						|
		} fc_renamefile;
 | 
						|
		struct {
 | 
						|
			/* cached: previous change affecting each object */
 | 
						|
			struct fschange *prev_fromdir;
 | 
						|
			struct fschange *prev_todir;
 | 
						|
			struct fschange *prev_moveddir;
 | 
						|
			/* rename params */
 | 
						|
			unsigned fromdir;
 | 
						|
			unsigned fromname;
 | 
						|
			unsigned todir;
 | 
						|
			unsigned toname;
 | 
						|
			unsigned moveddir;
 | 
						|
		} fc_renamedir;
 | 
						|
	};
 | 
						|
};
 | 
						|
 | 
						|
/*
 | 
						|
 * 2. representation of a current (visible) filesystem state
 | 
						|
 *
 | 
						|
 * The state starts at the root directory; each directory is a singly
 | 
						|
 * linked list of directory entries, each of which holds another
 | 
						|
 * directory or a file. We don't keep track of file contents, only the
 | 
						|
 * length.
 | 
						|
 *
 | 
						|
 * The identity field is within the union structure because file and
 | 
						|
 * directory object ids are different namespaces.
 | 
						|
 */
 | 
						|
 | 
						|
struct fsdirent {
 | 
						|
	unsigned name;
 | 
						|
	struct fsobject *obj;
 | 
						|
	struct fsdirent *next;
 | 
						|
};
 | 
						|
 | 
						|
struct fsobject {
 | 
						|
	unsigned isdir;
 | 
						|
	unsigned refcount;
 | 
						|
	union {
 | 
						|
		struct {
 | 
						|
			unsigned identity;
 | 
						|
			off_t len;
 | 
						|
		} obj_file;
 | 
						|
		struct {
 | 
						|
			unsigned identity;
 | 
						|
			struct fsdirent *entries;
 | 
						|
			struct fsobject *parent;
 | 
						|
		} obj_dir;
 | 
						|
	};
 | 
						|
};
 | 
						|
 | 
						|
////////////////////////////////////////////////////////////
 | 
						|
// allocator pools
 | 
						|
 | 
						|
/*
 | 
						|
 * Allocate from static space instead of using malloc, to help avoid
 | 
						|
 * unnecessary lossage on kernels where malloc doesn't work.
 | 
						|
 */
 | 
						|
 | 
						|
#define MAXCHANGES	16384
 | 
						|
#define MAXOBJECTS	16384
 | 
						|
#define MAXDIRENTS	16384
 | 
						|
 | 
						|
DECLPOOL(fschange, MAXCHANGES);
 | 
						|
DECLPOOL(fsobject, MAXOBJECTS);
 | 
						|
DECLPOOL(fsdirent, MAXDIRENTS);
 | 
						|
 | 
						|
static
 | 
						|
struct fschange *
 | 
						|
getchange(void)
 | 
						|
{
 | 
						|
	return POOLALLOC(fschange);
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
struct fsobject *
 | 
						|
getobject(void)
 | 
						|
{
 | 
						|
	return POOLALLOC(fsobject);
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
struct fsdirent *
 | 
						|
getdirent(void)
 | 
						|
{
 | 
						|
	return POOLALLOC(fsdirent);
 | 
						|
}
 | 
						|
 | 
						|
#if 0
 | 
						|
static
 | 
						|
void
 | 
						|
putchange(struct fschange *fc)
 | 
						|
{
 | 
						|
	POOLFREE(fschange, fc);
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
static
 | 
						|
void
 | 
						|
putobject(struct fsobject *obj)
 | 
						|
{
 | 
						|
	POOLFREE(fsobject, obj);
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
void
 | 
						|
putdirent(struct fsdirent *dirent)
 | 
						|
{
 | 
						|
	POOLFREE(fsdirent, dirent);
 | 
						|
}
 | 
						|
 | 
						|
////////////////////////////////////////////////////////////
 | 
						|
// record constructors for change records
 | 
						|
 | 
						|
static
 | 
						|
struct fschange *
 | 
						|
fc_create(enum fschanges type)
 | 
						|
{
 | 
						|
	struct fschange *fc;
 | 
						|
 | 
						|
	fc = getchange();
 | 
						|
	fc->prev = fc->next = NULL;
 | 
						|
	fc->version = 0;
 | 
						|
	fc->partial = 0;
 | 
						|
	fc->type = type;
 | 
						|
	return fc;
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
struct fschange *
 | 
						|
fc_create_newfs(unsigned rootdirnum)
 | 
						|
{
 | 
						|
	struct fschange *fc;
 | 
						|
 | 
						|
	fc = fc_create(FC_NEWFS);
 | 
						|
	fc->fc_newfs.rootdirnum = rootdirnum;
 | 
						|
	return fc;
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
struct fschange *
 | 
						|
fc_create_truncate(struct fschange *prev_thisfile, unsigned file, off_t len)
 | 
						|
{
 | 
						|
	struct fschange *fc;
 | 
						|
 | 
						|
	fc = fc_create(FC_TRUNCATE);
 | 
						|
	fc->fc_truncate.prev_thisfile = prev_thisfile;
 | 
						|
	fc->fc_truncate.file = file;
 | 
						|
	fc->fc_truncate.len = len;
 | 
						|
	return fc;
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
struct fschange *
 | 
						|
fc_create_write(struct fschange *prev_thisfile, unsigned file,
 | 
						|
		off_t pos, off_t len, off_t oldfilesize,
 | 
						|
		unsigned code, unsigned seq)
 | 
						|
{
 | 
						|
	struct fschange *fc;
 | 
						|
 | 
						|
	fc = fc_create(FC_WRITE);
 | 
						|
	fc->fc_write.prev_thisfile = prev_thisfile;
 | 
						|
	fc->fc_write.file = file;
 | 
						|
	fc->fc_write.pos = pos;
 | 
						|
	fc->fc_write.len = len;
 | 
						|
	fc->fc_write.oldfilesize = oldfilesize;
 | 
						|
	fc->fc_write.code = code;
 | 
						|
	fc->fc_write.seq = seq;
 | 
						|
	return fc;
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
struct fschange *
 | 
						|
fc_create_creat(struct fschange *prev_thisdir,
 | 
						|
		unsigned dir, unsigned name, unsigned newfile)
 | 
						|
{
 | 
						|
	struct fschange *mdc;
 | 
						|
 | 
						|
	mdc = fc_create(FC_CREAT);
 | 
						|
	mdc->fc_creat.prev_thisdir = prev_thisdir;
 | 
						|
	mdc->fc_creat.dir = dir;
 | 
						|
	mdc->fc_creat.name = name;
 | 
						|
	mdc->fc_creat.newfile = newfile;
 | 
						|
	return mdc;
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
struct fschange *
 | 
						|
fc_create_mkdir(struct fschange *prev_thisdir,
 | 
						|
		unsigned dir, unsigned name, unsigned newdir)
 | 
						|
{
 | 
						|
	struct fschange *mdc;
 | 
						|
 | 
						|
	mdc = fc_create(FC_MKDIR);
 | 
						|
	mdc->fc_mkdir.prev_thisdir = prev_thisdir;
 | 
						|
	mdc->fc_mkdir.dir = dir;
 | 
						|
	mdc->fc_mkdir.name = name;
 | 
						|
	mdc->fc_mkdir.newdir = newdir;
 | 
						|
	return mdc;
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
struct fschange *
 | 
						|
fc_create_rmdir(struct fschange *prev_thisdir, struct fschange *prev_victimdir,
 | 
						|
		unsigned dir, unsigned name, unsigned victimdir)
 | 
						|
{
 | 
						|
	struct fschange *mdc;
 | 
						|
 | 
						|
	mdc = fc_create(FC_RMDIR);
 | 
						|
	mdc->fc_rmdir.prev_thisdir = prev_thisdir;
 | 
						|
	mdc->fc_rmdir.prev_victimdir = prev_victimdir;
 | 
						|
	mdc->fc_rmdir.dir = dir;
 | 
						|
	mdc->fc_rmdir.name = name;
 | 
						|
	mdc->fc_rmdir.victimdir = victimdir;
 | 
						|
	return mdc;
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
struct fschange *
 | 
						|
fc_create_unlink(struct fschange *prev_thisdir,
 | 
						|
		 struct fschange *prev_victimfile,
 | 
						|
		 unsigned dir, unsigned name, unsigned victimfile)
 | 
						|
{
 | 
						|
	struct fschange *mdc;
 | 
						|
 | 
						|
	mdc = fc_create(FC_UNLINK);
 | 
						|
	mdc->fc_unlink.prev_thisdir = prev_thisdir;
 | 
						|
	mdc->fc_unlink.prev_victimfile = prev_victimfile;
 | 
						|
	mdc->fc_unlink.dir = dir;
 | 
						|
	mdc->fc_unlink.name = name;
 | 
						|
	mdc->fc_unlink.victimfile = victimfile;
 | 
						|
	return mdc;
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
struct fschange *
 | 
						|
fc_create_link(struct fschange *prev_fromdir, struct fschange *prev_todir,
 | 
						|
	       struct fschange *prev_thisfile,
 | 
						|
	       unsigned fromdir, unsigned fromname,
 | 
						|
	       unsigned todir, unsigned toname,
 | 
						|
	       unsigned file)
 | 
						|
{
 | 
						|
	struct fschange *mdc;
 | 
						|
 | 
						|
	mdc = fc_create(FC_LINK);
 | 
						|
	mdc->fc_link.prev_fromdir = prev_fromdir;
 | 
						|
	mdc->fc_link.prev_todir = prev_todir;
 | 
						|
	mdc->fc_link.prev_thisfile = prev_thisfile;
 | 
						|
	mdc->fc_link.fromdir = fromdir;
 | 
						|
	mdc->fc_link.fromname = fromname;
 | 
						|
	mdc->fc_link.todir = todir;
 | 
						|
	mdc->fc_link.toname = toname;
 | 
						|
	mdc->fc_link.file = file;
 | 
						|
	return mdc;
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
struct fschange *
 | 
						|
fc_create_renamefile(struct fschange *prev_fromdir,
 | 
						|
		     struct fschange *prev_todir,
 | 
						|
		     struct fschange *prev_movedfile,
 | 
						|
		     unsigned fromdir, unsigned fromname,
 | 
						|
		     unsigned todir, unsigned toname,
 | 
						|
		     unsigned movedfile)
 | 
						|
{
 | 
						|
	struct fschange *mdc;
 | 
						|
 | 
						|
	mdc = fc_create(FC_RENAMEFILE);
 | 
						|
	mdc->fc_renamefile.prev_fromdir = prev_fromdir;
 | 
						|
	mdc->fc_renamefile.prev_todir = prev_todir;
 | 
						|
	mdc->fc_renamefile.prev_movedfile = prev_movedfile;
 | 
						|
	mdc->fc_renamefile.fromdir = fromdir;
 | 
						|
	mdc->fc_renamefile.fromname = fromname;
 | 
						|
	mdc->fc_renamefile.todir = todir;
 | 
						|
	mdc->fc_renamefile.toname = toname;
 | 
						|
	mdc->fc_renamefile.movedfile = movedfile;
 | 
						|
	return mdc;
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
struct fschange *
 | 
						|
fc_create_renamedir(struct fschange *prev_fromdir,
 | 
						|
		    struct fschange *prev_todir,
 | 
						|
		    struct fschange *prev_moveddir,
 | 
						|
		    unsigned fromdir, unsigned fromname,
 | 
						|
		    unsigned todir, unsigned toname,
 | 
						|
		    unsigned moveddir)
 | 
						|
{
 | 
						|
	struct fschange *fc;
 | 
						|
 | 
						|
	fc = fc_create(FC_RENAMEDIR);
 | 
						|
	fc->fc_renamedir.prev_fromdir = prev_fromdir;
 | 
						|
	fc->fc_renamedir.prev_todir = prev_todir;
 | 
						|
	fc->fc_renamedir.prev_moveddir = prev_moveddir;
 | 
						|
	fc->fc_renamedir.fromdir = fromdir;
 | 
						|
	fc->fc_renamedir.fromname = fromname;
 | 
						|
	fc->fc_renamedir.todir = todir;
 | 
						|
	fc->fc_renamedir.toname = toname;
 | 
						|
	fc->fc_renamedir.moveddir = moveddir;
 | 
						|
	return fc;
 | 
						|
}
 | 
						|
 | 
						|
////////////////////////////////////////////////////////////
 | 
						|
// constructors/destructors for current state objects
 | 
						|
 | 
						|
static
 | 
						|
struct fsdirent *
 | 
						|
fsdirent_create(unsigned name, struct fsobject *obj)
 | 
						|
{
 | 
						|
	struct fsdirent *ret;
 | 
						|
 | 
						|
	ret = getdirent();
 | 
						|
	ret->name = name;
 | 
						|
	ret->obj = obj;
 | 
						|
	ret->next = NULL;
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
void
 | 
						|
fsdirent_destroy(struct fsdirent *de)
 | 
						|
{
 | 
						|
	assert(de->obj == NULL);
 | 
						|
	assert(de->next == NULL);
 | 
						|
	putdirent(de);
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
struct fsobject *
 | 
						|
fsobject_create_file(unsigned id)
 | 
						|
{
 | 
						|
	struct fsobject *ret;
 | 
						|
 | 
						|
	ret = getobject();
 | 
						|
	ret->isdir = 0;
 | 
						|
	ret->refcount = 1;
 | 
						|
	ret->obj_file.identity = id;
 | 
						|
	ret->obj_file.len = 0;
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
struct fsobject *
 | 
						|
fsobject_create_dir(unsigned id, struct fsobject *parent)
 | 
						|
{
 | 
						|
	struct fsobject *ret;
 | 
						|
 | 
						|
	ret = getobject();
 | 
						|
	ret->isdir = 1;
 | 
						|
	ret->refcount = 1;
 | 
						|
	ret->obj_dir.identity = id;
 | 
						|
	ret->obj_dir.entries = NULL;
 | 
						|
	ret->obj_dir.parent = parent;
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
void
 | 
						|
fsobject_incref(struct fsobject *obj)
 | 
						|
{
 | 
						|
	assert(obj->refcount > 0);
 | 
						|
	obj->refcount++;
 | 
						|
	assert(obj->refcount > 0);
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
void
 | 
						|
fsobject_decref(struct fsobject *obj)
 | 
						|
{
 | 
						|
	assert(obj->refcount > 0);
 | 
						|
	obj->refcount--;
 | 
						|
	if (obj->refcount > 0) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	if (obj->isdir) {
 | 
						|
		assert(obj->obj_dir.entries == NULL);
 | 
						|
	}
 | 
						|
	putobject(obj);
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
void
 | 
						|
fsobject_destroytree(struct fsobject *obj)
 | 
						|
{
 | 
						|
	struct fsdirent *de;
 | 
						|
 | 
						|
	if (obj->isdir) {
 | 
						|
		while (obj->obj_dir.entries != NULL) {
 | 
						|
			de = obj->obj_dir.entries;
 | 
						|
			obj->obj_dir.entries = de->next;
 | 
						|
			de->next = NULL;
 | 
						|
			fsobject_destroytree(de->obj);
 | 
						|
			de->obj = NULL;
 | 
						|
			fsdirent_destroy(de);
 | 
						|
		}
 | 
						|
	}
 | 
						|
	fsobject_decref(obj);
 | 
						|
}
 | 
						|
 | 
						|
////////////////////////////////////////////////////////////
 | 
						|
// operations on current state objects
 | 
						|
 | 
						|
/*
 | 
						|
 * Bail out if something's broken.
 | 
						|
 */
 | 
						|
static
 | 
						|
void
 | 
						|
die(const char *fmt, ...)
 | 
						|
{
 | 
						|
	char buf[256];
 | 
						|
	va_list ap;
 | 
						|
 | 
						|
	va_start(ap, fmt);
 | 
						|
	vsnprintf(buf, sizeof(buf), fmt, ap);
 | 
						|
	va_end(ap);
 | 
						|
	errx(1, "Inconsistency: %s", buf);
 | 
						|
}
 | 
						|
 | 
						|
#if 0 /* not used */
 | 
						|
/*
 | 
						|
 * Count a list of directory entries.
 | 
						|
 */
 | 
						|
static
 | 
						|
unsigned
 | 
						|
fsdirent_count(struct fsdirent *de)
 | 
						|
{
 | 
						|
	if (de == NULL) {
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
	return 1 + fsdirent_count(de->next);
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
/*
 | 
						|
 * Add an entry to a directory.
 | 
						|
 */
 | 
						|
static
 | 
						|
void
 | 
						|
fsdir_add_entry(struct fsobject *dir, struct fsdirent *nde)
 | 
						|
{
 | 
						|
	struct fsdirent *ode;
 | 
						|
 | 
						|
	assert(dir->isdir);
 | 
						|
	for (ode = dir->obj_dir.entries; ode != NULL; ode = ode->next) {
 | 
						|
		if (ode->name == nde->name) {
 | 
						|
			die("In directory %u, %s already existed",
 | 
						|
			    dir->obj_dir.identity, name_get(nde->name));
 | 
						|
		}
 | 
						|
	}
 | 
						|
	nde->next = dir->obj_dir.entries;
 | 
						|
	dir->obj_dir.entries = nde;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Find an entry in a directory by name. If CROAK is set, bail out if
 | 
						|
 * it's not there.
 | 
						|
 */
 | 
						|
static
 | 
						|
struct fsdirent *
 | 
						|
fsdir_find_entry(struct fsobject *dir, unsigned name, int croak)
 | 
						|
{
 | 
						|
	struct fsdirent *de;
 | 
						|
 | 
						|
	assert(dir->isdir);
 | 
						|
	for (de = dir->obj_dir.entries; de != NULL; de = de->next) {
 | 
						|
		if (de->name == name) {
 | 
						|
			return de;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (croak) {
 | 
						|
		die("In directory %u, did not find %s",
 | 
						|
		    dir->obj_dir.identity, name_get(name));
 | 
						|
	}
 | 
						|
	return NULL;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Remove an entry from a directory.
 | 
						|
 */
 | 
						|
static
 | 
						|
struct fsdirent *
 | 
						|
fsdir_remove_entry(struct fsobject *dir, unsigned name)
 | 
						|
{
 | 
						|
	struct fsdirent *de, **prevptr;
 | 
						|
 | 
						|
	assert(dir->isdir);
 | 
						|
	prevptr = &dir->obj_dir.entries;
 | 
						|
	for (de = *prevptr; de != NULL; prevptr = &de->next, de = *prevptr) {
 | 
						|
		if (de->name == name) {
 | 
						|
			*prevptr = de->next;
 | 
						|
			de->next = NULL;
 | 
						|
			return de;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	die("In directory %u, did not find %s",
 | 
						|
	    dir->obj_dir.identity, name_get(name));
 | 
						|
	return NULL;
 | 
						|
}
 | 
						|
 | 
						|
////////////////////////////////////////////////////////////
 | 
						|
// apply a change record to a current state
 | 
						|
 | 
						|
static
 | 
						|
struct fsobject *
 | 
						|
findsub(struct fsobject *obj, unsigned isdir, unsigned id)
 | 
						|
{
 | 
						|
	struct fsobject *ret;
 | 
						|
	struct fsdirent *de;
 | 
						|
	unsigned objid;
 | 
						|
 | 
						|
	/* Are we on the object we're looking for? */
 | 
						|
	objid = obj->isdir ? obj->obj_dir.identity : obj->obj_file.identity;
 | 
						|
	if (obj->isdir == isdir && objid == id) {
 | 
						|
		return obj;
 | 
						|
	}
 | 
						|
 | 
						|
	/* If the object we're on isn't a dir, can't search any further */
 | 
						|
	if (!obj->isdir) {
 | 
						|
		return NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	for (de = obj->obj_dir.entries; de != NULL; de = de->next) {
 | 
						|
		ret = findsub(de->obj, isdir, id);
 | 
						|
		if (ret != NULL) {
 | 
						|
			return ret;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return NULL;
 | 
						|
}
 | 
						|
 | 
						|
#if 0
 | 
						|
static
 | 
						|
struct fsobject *
 | 
						|
findfile(struct fsobject *rootdir, unsigned id)
 | 
						|
{
 | 
						|
	struct fsobject *ret;
 | 
						|
 | 
						|
	ret = findsub(rootdir, 0/*!isdir*/, id);
 | 
						|
	if (ret == NULL) {
 | 
						|
		die("File %u not found in current state", id);
 | 
						|
	}
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
static
 | 
						|
struct fsobject *
 | 
						|
findfile_maybe(struct fsobject *rootdir, unsigned id)
 | 
						|
{
 | 
						|
	return findsub(rootdir, 0/*!isdir*/, id);
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
struct fsobject *
 | 
						|
finddir(struct fsobject *rootdir, unsigned id)
 | 
						|
{
 | 
						|
	struct fsobject *ret;
 | 
						|
 | 
						|
	ret = findsub(rootdir, 1/*isdir*/, id);
 | 
						|
	if (ret == NULL) {
 | 
						|
		die("Directory %u not found in current state", id);
 | 
						|
	}
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Apply change CHANGE to the volume state encoded in/under ROOTDIR.
 | 
						|
 */
 | 
						|
static
 | 
						|
void
 | 
						|
apply_change(struct fsobject *rootdir, struct fschange *change)
 | 
						|
{
 | 
						|
	struct fsobject *obj1, *obj2;
 | 
						|
	struct fsdirent *de;
 | 
						|
	off_t endpos;
 | 
						|
 | 
						|
	assert(rootdir->isdir);
 | 
						|
 | 
						|
	switch (change->type) {
 | 
						|
	    case FC_NEWFS:
 | 
						|
		/*
 | 
						|
		 * Creating the volume; crosscheck the root directory.
 | 
						|
		 */
 | 
						|
		assert(rootdir->isdir);
 | 
						|
		assert(rootdir->refcount == 1);
 | 
						|
		assert(rootdir->obj_dir.identity ==
 | 
						|
		       change->fc_newfs.rootdirnum);
 | 
						|
		assert(rootdir->obj_dir.entries == NULL);
 | 
						|
		assert(rootdir->obj_dir.parent == rootdir);
 | 
						|
		break;
 | 
						|
	    case FC_TRUNCATE:
 | 
						|
		/*
 | 
						|
		 * Truncate a file. (Only files, not directories.)
 | 
						|
		 *
 | 
						|
		 * Because truncates can and do get posted after a
 | 
						|
		 * file is unlinked, we have to tolerate not finding
 | 
						|
		 * the file. We don't model unlinked files, because
 | 
						|
		 * they aren't visible and our goal is to check the
 | 
						|
		 * visible/observable state.
 | 
						|
		 */
 | 
						|
		obj1 = findfile_maybe(rootdir, change->fc_truncate.file);
 | 
						|
		if (obj1 != NULL) {
 | 
						|
			obj1->obj_file.len = change->fc_truncate.len;
 | 
						|
		}
 | 
						|
		break;
 | 
						|
	    case FC_WRITE:
 | 
						|
		/*
 | 
						|
		 * Write to a file. (Only files, not directories.)
 | 
						|
		 * All this actually does is update the file size if
 | 
						|
		 * needed.
 | 
						|
		 *
 | 
						|
		 * We also have to tolerate writes to unlinked files.
 | 
						|
		 */
 | 
						|
		obj1 = findfile_maybe(rootdir, change->fc_write.file);
 | 
						|
		if (obj1 != NULL) {
 | 
						|
			endpos = change->fc_write.pos + change->fc_write.len;
 | 
						|
			if (obj1->obj_file.len < endpos) {
 | 
						|
				obj1->obj_file.len = endpos;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		break;
 | 
						|
	    case FC_CREAT:
 | 
						|
		/*
 | 
						|
		 * Create a file. This creates a new object and a
 | 
						|
		 * directory entry to hold it, then inserts the
 | 
						|
		 * directory entry in the containing directory.
 | 
						|
		 */
 | 
						|
		obj1 = finddir(rootdir, change->fc_creat.dir);
 | 
						|
		obj2 = fsobject_create_file(change->fc_creat.newfile);
 | 
						|
		de = fsdirent_create(change->fc_creat.name, obj2);
 | 
						|
		fsdir_add_entry(obj1, de);
 | 
						|
		break;
 | 
						|
	    case FC_MKDIR:
 | 
						|
		/*
 | 
						|
		 * Create a directory. The same as creating a file,
 | 
						|
		 * except the new object is a directory.
 | 
						|
		 */
 | 
						|
		obj1 = finddir(rootdir, change->fc_mkdir.dir);
 | 
						|
		obj2 = fsobject_create_dir(change->fc_mkdir.newdir, obj1);
 | 
						|
		de = fsdirent_create(change->fc_mkdir.name, obj2);
 | 
						|
		fsdir_add_entry(obj1, de);
 | 
						|
		break;
 | 
						|
	    case FC_RMDIR:
 | 
						|
		/*
 | 
						|
		 * Remove a directory. First take out the directory
 | 
						|
		 * entry (by name); then examine the object found;
 | 
						|
		 * then delete everything.
 | 
						|
		 *
 | 
						|
		 * XXX: why do these checks use assert vs. testing and
 | 
						|
		 * calling die()?
 | 
						|
		 */
 | 
						|
		obj1 = finddir(rootdir, change->fc_rmdir.dir);
 | 
						|
		de = fsdir_remove_entry(obj1, change->fc_rmdir.name);
 | 
						|
		obj2 = de->obj;
 | 
						|
		de->obj = NULL;
 | 
						|
		assert(obj2->isdir);
 | 
						|
		assert(obj2->obj_dir.entries == NULL);
 | 
						|
		assert(obj2->obj_dir.identity == change->fc_rmdir.victimdir);
 | 
						|
		assert(obj2->obj_dir.parent == obj1);
 | 
						|
		fsdirent_destroy(de);
 | 
						|
		fsobject_decref(obj2);
 | 
						|
		break;
 | 
						|
	    case FC_UNLINK:
 | 
						|
		/*
 | 
						|
		 * Remove a file. Much the same as removing a directory.
 | 
						|
		 */
 | 
						|
		obj1 = finddir(rootdir, change->fc_unlink.dir);
 | 
						|
		de = fsdir_remove_entry(obj1, change->fc_unlink.name);
 | 
						|
		obj2 = de->obj;
 | 
						|
		de->obj = NULL;
 | 
						|
		assert(!obj2->isdir);
 | 
						|
		assert(obj2->obj_file.identity ==
 | 
						|
		       change->fc_unlink.victimfile);
 | 
						|
		fsdirent_destroy(de);
 | 
						|
		fsobject_decref(obj2);
 | 
						|
		break;
 | 
						|
	    case FC_LINK:
 | 
						|
		/*
 | 
						|
		 * Hard-link a file.
 | 
						|
		 */
 | 
						|
		obj1 = finddir(rootdir, change->fc_link.fromdir);
 | 
						|
		de = fsdir_find_entry(obj1, change->fc_link.fromname, 1);
 | 
						|
		obj2 = de->obj;
 | 
						|
		assert(!obj2->isdir);
 | 
						|
		assert(obj2->obj_file.identity == change->fc_link.file);
 | 
						|
		obj1 = finddir(rootdir, change->fc_link.todir);
 | 
						|
		fsobject_incref(obj2);
 | 
						|
		de = fsdirent_create(change->fc_link.toname, obj2);
 | 
						|
		fsdir_add_entry(obj1, de);
 | 
						|
		break;
 | 
						|
	    case FC_RENAMEFILE:
 | 
						|
		/*
 | 
						|
		 * Rename a file. XXX: this appears to do the wrong
 | 
						|
		 * thing if you rename one file over another.
 | 
						|
		 */
 | 
						|
		obj1 = finddir(rootdir, change->fc_renamefile.fromdir);
 | 
						|
		de = fsdir_remove_entry(obj1, change->fc_renamefile.fromname);
 | 
						|
		obj2 = de->obj;
 | 
						|
		assert(!obj2->isdir);
 | 
						|
		assert(obj2->obj_file.identity ==
 | 
						|
		       change->fc_renamefile.movedfile);
 | 
						|
		obj1 = finddir(rootdir, change->fc_renamefile.todir);
 | 
						|
		de->name = change->fc_renamefile.toname;
 | 
						|
		fsdir_add_entry(obj1, de);
 | 
						|
		break;
 | 
						|
	    case FC_RENAMEDIR:
 | 
						|
		/*
 | 
						|
		 * Rename a directory. Same as renaming a file, except
 | 
						|
		 * that we update the parent of the directory being
 | 
						|
		 * moved. XXX: also seems to do the wrong thing if you
 | 
						|
		 * rename one directory over another. And, XXX: should
 | 
						|
		 * this be updating the refcount as the parent
 | 
						|
		 * changes? Shouldn't the refcount be the same as the
 | 
						|
		 * on-disk linkcount, and shouldn't the on-disk
 | 
						|
		 * linkcount be part of the state we examine and
 | 
						|
		 * verify?
 | 
						|
		 */
 | 
						|
		obj1 = finddir(rootdir, change->fc_renamedir.fromdir);
 | 
						|
		de = fsdir_remove_entry(obj1, change->fc_renamedir.fromname);
 | 
						|
		obj2 = de->obj;
 | 
						|
		assert(obj2->isdir);
 | 
						|
		assert(obj2->obj_dir.identity ==
 | 
						|
		       change->fc_renamedir.moveddir);
 | 
						|
		assert(obj2->obj_dir.parent == obj1);
 | 
						|
		obj1 = finddir(rootdir, change->fc_renamedir.todir);
 | 
						|
		de->name = change->fc_renamedir.toname;
 | 
						|
		obj2->obj_dir.parent = obj1;
 | 
						|
		fsdir_add_entry(obj1, de);
 | 
						|
		break;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
////////////////////////////////////////////////////////////
 | 
						|
// global fs state
 | 
						|
 | 
						|
/*
 | 
						|
 * The change records. FIRSTCHANGE is the first change record; CHANGES
 | 
						|
 * is the list root, which given the current linked list implementation
 | 
						|
 * is the *last* (most recent) change record.
 | 
						|
 */
 | 
						|
static struct fschange *firstchange;
 | 
						|
static struct fschange *changes;
 | 
						|
 | 
						|
/*
 | 
						|
 * The current volume state that we're working with. As we add change
 | 
						|
 * records while replaying the workload, we roll this forward so we
 | 
						|
 * can inspect it; for some things this is necessary to be able to
 | 
						|
 * replay the workload fully.
 | 
						|
 */
 | 
						|
static struct fsobject *state;
 | 
						|
 | 
						|
/*
 | 
						|
 * The next oids of each type to assign. File and directory ids are
 | 
						|
 * counted independently, which might or might not have been a good
 | 
						|
 * idea.
 | 
						|
 */
 | 
						|
static unsigned nextfilenum, nextdirnum;
 | 
						|
 | 
						|
/*
 | 
						|
 * Add a new change record. This allocates the version number;
 | 
						|
 * however, the change record should otherwise be fully filled in
 | 
						|
 * already.
 | 
						|
 *
 | 
						|
 * usage: fc_attach(newrecord);
 | 
						|
 */
 | 
						|
static
 | 
						|
void
 | 
						|
fc_attach(struct fschange *new)
 | 
						|
{
 | 
						|
	struct fschange *prev;
 | 
						|
 | 
						|
	prev = changes;
 | 
						|
 | 
						|
	assert(prev->next == NULL);
 | 
						|
	assert(new->prev == NULL);
 | 
						|
	assert(new->next == NULL);
 | 
						|
	prev->next = new;
 | 
						|
	new->prev = prev;
 | 
						|
	new->version = prev->version + 1;
 | 
						|
 | 
						|
	changes = new;
 | 
						|
	apply_change(state, new);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Rewind the volume state kept in the global STATE to the beginning.
 | 
						|
 */
 | 
						|
static
 | 
						|
void
 | 
						|
rewindstate(void)
 | 
						|
{
 | 
						|
	/* If we already have a state, throw it away. */
 | 
						|
	if (state != NULL) {
 | 
						|
		fsobject_destroytree(state);
 | 
						|
	}
 | 
						|
 | 
						|
	assert(firstchange->type == FC_NEWFS);
 | 
						|
 | 
						|
	state = fsobject_create_dir(firstchange->fc_newfs.rootdirnum, NULL);
 | 
						|
	/* root directory's parent is itself */
 | 
						|
	state->obj_dir.parent = state;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Roll the volume state kept in STATE forward to a specific change
 | 
						|
 * entry.
 | 
						|
 */
 | 
						|
static
 | 
						|
void
 | 
						|
advancestateto(struct fschange *targetchange)
 | 
						|
{
 | 
						|
	struct fschange *change;
 | 
						|
 | 
						|
	for (change = firstchange; change != NULL; change = change->next) {
 | 
						|
		apply_change(state, change);
 | 
						|
		if (change == targetchange) {
 | 
						|
			return;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	assert(0);
 | 
						|
}
 | 
						|
 | 
						|
////////////////////////////////////////////////////////////
 | 
						|
// lookup in the fs state
 | 
						|
// (used during model construction)
 | 
						|
 | 
						|
/*
 | 
						|
 * Find the most recent previous record that mentions a particular file.
 | 
						|
 *
 | 
						|
 * If there isn't one, something's wrong; because we start with a
 | 
						|
 * fresh volume every file that ever exists must have been created at
 | 
						|
 * some point.
 | 
						|
 *
 | 
						|
 * This is used while replaying the workload and accumulating change
 | 
						|
 * records, so it can always just start from the most recent end
 | 
						|
 * instead of from a specific place.
 | 
						|
 */
 | 
						|
static
 | 
						|
struct fschange *
 | 
						|
changes_findprevfile(unsigned filenum)
 | 
						|
{
 | 
						|
	struct fschange *here;
 | 
						|
 | 
						|
	for (here = changes; here != NULL; here = here->prev) {
 | 
						|
		switch (here->type) {
 | 
						|
		    case FC_NEWFS:
 | 
						|
			break;
 | 
						|
		    case FC_TRUNCATE:
 | 
						|
			if (here->fc_truncate.file == filenum) {
 | 
						|
				return here;
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		    case FC_WRITE:
 | 
						|
			if (here->fc_write.file == filenum) {
 | 
						|
				return here;
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		    case FC_CREAT:
 | 
						|
			if (here->fc_creat.newfile == filenum) {
 | 
						|
				return here;
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		    case FC_MKDIR:
 | 
						|
		    case FC_RMDIR:
 | 
						|
			break;
 | 
						|
		    case FC_UNLINK:
 | 
						|
			if (here->fc_unlink.victimfile == filenum) {
 | 
						|
				return here;
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		    case FC_LINK:
 | 
						|
			if (here->fc_link.file == filenum) {
 | 
						|
				return here;
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		    case FC_RENAMEFILE:
 | 
						|
			if (here->fc_renamefile.movedfile == filenum) {
 | 
						|
				return here;
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		    case FC_RENAMEDIR:
 | 
						|
			break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	die("No previous record for file %u", filenum);
 | 
						|
	return NULL;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Find the most recent previous record that mentions a particular dir.
 | 
						|
 *
 | 
						|
 * Likewise, if there isn't one, something's wrong.
 | 
						|
 */
 | 
						|
static
 | 
						|
struct fschange *
 | 
						|
changes_findprevdir(unsigned dirnum)
 | 
						|
{
 | 
						|
	struct fschange *here;
 | 
						|
 | 
						|
	for (here = changes; here != NULL; here = here->prev) {
 | 
						|
		switch (here->type) {
 | 
						|
		    case FC_NEWFS:
 | 
						|
			if (here->fc_newfs.rootdirnum == dirnum) {
 | 
						|
				return here;
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		    case FC_TRUNCATE:
 | 
						|
		    case FC_WRITE:
 | 
						|
			break;
 | 
						|
		    case FC_CREAT:
 | 
						|
			if (here->fc_creat.dir == dirnum) {
 | 
						|
				return here;
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		    case FC_MKDIR:
 | 
						|
			if (here->fc_mkdir.dir == dirnum ||
 | 
						|
			    here->fc_mkdir.newdir == dirnum) {
 | 
						|
				return here;
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		    case FC_RMDIR:
 | 
						|
			if (here->fc_rmdir.dir == dirnum ||
 | 
						|
			    here->fc_rmdir.victimdir == dirnum) {
 | 
						|
				return here;
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		    case FC_UNLINK:
 | 
						|
			if (here->fc_unlink.dir == dirnum) {
 | 
						|
				return here;
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		    case FC_LINK:
 | 
						|
			if (here->fc_link.fromdir == dirnum ||
 | 
						|
			    here->fc_link.todir == dirnum) {
 | 
						|
				return here;
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		    case FC_RENAMEFILE:
 | 
						|
			if (here->fc_renamefile.fromdir == dirnum ||
 | 
						|
			    here->fc_renamefile.todir == dirnum) {
 | 
						|
				return here;
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		    case FC_RENAMEDIR:
 | 
						|
			if (here->fc_renamedir.fromdir == dirnum ||
 | 
						|
			    here->fc_renamedir.todir == dirnum ||
 | 
						|
			    here->fc_renamedir.moveddir == dirnum) {
 | 
						|
				return here;
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	die("No previous record for directory %u", dirnum);
 | 
						|
	return NULL;
 | 
						|
}
 | 
						|
 | 
						|
#if 0
 | 
						|
/*
 | 
						|
 * Back up to the previous record mentioning the same file.
 | 
						|
 *
 | 
						|
 * XXX: No longer used and should be removed.
 | 
						|
 */
 | 
						|
static
 | 
						|
struct fschange *
 | 
						|
fs_prevforfile(struct fschange *fc, unsigned filenum)
 | 
						|
{
 | 
						|
	switch (fc->type) {
 | 
						|
	    case FC_TRUNCATE:
 | 
						|
		if (fc->fc_truncate.file == filenum) {
 | 
						|
			return fc->fc_truncate.prev_thisfile;
 | 
						|
		}
 | 
						|
		break;
 | 
						|
	    case FC_WRITE:
 | 
						|
		if (fc->fc_write.file == filenum) {
 | 
						|
			return fc->fc_write.prev_thisfile;
 | 
						|
		}
 | 
						|
		break;
 | 
						|
	    case FC_UNLINK:
 | 
						|
		if (fc->fc_unlink.victimfile == filenum) {
 | 
						|
			return fc->fc_unlink.prev_victimfile;
 | 
						|
		}
 | 
						|
		break;
 | 
						|
	    case FC_LINK:
 | 
						|
		if (fc->fc_link.file == filenum) {
 | 
						|
			return fc->fc_link.prev_thisfile;
 | 
						|
		}
 | 
						|
		break;
 | 
						|
	    case FC_RENAMEFILE:
 | 
						|
		if (fc->fc_renamefile.movedfile == filenum) {
 | 
						|
			return fc->fc_renamefile.prev_movedfile;
 | 
						|
		}
 | 
						|
		break;
 | 
						|
	    default:
 | 
						|
		break;
 | 
						|
	}
 | 
						|
	return fc->prev;
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
#if 0
 | 
						|
/*
 | 
						|
 * Back up to the previous record mentioning the same directory.
 | 
						|
 *
 | 
						|
 * XXX: No longer used and should be removed.
 | 
						|
 */
 | 
						|
static
 | 
						|
struct fschange *
 | 
						|
fs_prevfordir(struct fschange *fc, unsigned dirnum)
 | 
						|
{
 | 
						|
	switch (fc->type) {
 | 
						|
	    case FC_CREAT:
 | 
						|
		if (fc->fc_creat.dir == dirnum) {
 | 
						|
			return fc->fc_creat.prev_thisdir;
 | 
						|
		}
 | 
						|
		break;
 | 
						|
	    case FC_MKDIR:
 | 
						|
		if (fc->fc_mkdir.dir == dirnum) {
 | 
						|
			return fc->fc_mkdir.prev_thisdir;
 | 
						|
		}
 | 
						|
		break;
 | 
						|
	    case FC_RMDIR:
 | 
						|
		if (fc->fc_rmdir.dir == dirnum) {
 | 
						|
			return fc->fc_rmdir.prev_thisdir;
 | 
						|
		}
 | 
						|
		if (fc->fc_rmdir.victimdir == dirnum) {
 | 
						|
			return fc->fc_rmdir.prev_victimdir;
 | 
						|
		}
 | 
						|
		break;
 | 
						|
	    case FC_UNLINK:
 | 
						|
		if (fc->fc_unlink.dir == dirnum) {
 | 
						|
			return fc->fc_unlink.prev_thisdir;
 | 
						|
		}
 | 
						|
		break;
 | 
						|
	    case FC_LINK:
 | 
						|
		if (fc->fc_link.fromdir == dirnum) {
 | 
						|
			return fc->fc_link.prev_fromdir;
 | 
						|
		}
 | 
						|
		if (fc->fc_link.todir == dirnum) {
 | 
						|
			return fc->fc_link.prev_todir;
 | 
						|
		}
 | 
						|
		break;
 | 
						|
	    case FC_RENAMEFILE:
 | 
						|
		if (fc->fc_renamefile.fromdir == dirnum) {
 | 
						|
			return fc->fc_renamefile.prev_fromdir;
 | 
						|
		}
 | 
						|
		if (fc->fc_renamefile.todir == dirnum) {
 | 
						|
			return fc->fc_renamefile.prev_todir;
 | 
						|
		}
 | 
						|
		break;
 | 
						|
	    case FC_RENAMEDIR:
 | 
						|
		if (fc->fc_renamedir.fromdir == dirnum) {
 | 
						|
			return fc->fc_renamedir.prev_fromdir;
 | 
						|
		}
 | 
						|
		if (fc->fc_renamedir.todir == dirnum) {
 | 
						|
			return fc->fc_renamedir.prev_todir;
 | 
						|
		}
 | 
						|
		if (fc->fc_renamedir.moveddir == dirnum) {
 | 
						|
			return fc->fc_renamedir.prev_moveddir;
 | 
						|
		}
 | 
						|
		break;
 | 
						|
	    default:
 | 
						|
		break;
 | 
						|
	}
 | 
						|
	return fc->prev;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Find an object by name and containing directory by searching the
 | 
						|
 * change history.
 | 
						|
 *
 | 
						|
 * No longer used (instead we maintain the current state and search
 | 
						|
 * that, which is a lot simpler) and should probably be removed.
 | 
						|
 */
 | 
						|
struct findresult {
 | 
						|
	int isfile;
 | 
						|
	unsigned objnum;
 | 
						|
};
 | 
						|
 | 
						|
static
 | 
						|
int
 | 
						|
fs_find(unsigned dirnum, unsigned name, struct findresult *res)
 | 
						|
{
 | 
						|
	struct fschange *here;
 | 
						|
 | 
						|
	for (here = fs; here != NULL; here = fs_prevfordir(here, dirnum)) {
 | 
						|
		switch (here->type) {
 | 
						|
		    case FC_NEWFS:
 | 
						|
		    case FC_TRUNCATE:
 | 
						|
		    case FC_WRITE:
 | 
						|
			break;
 | 
						|
		    case FC_CREAT:
 | 
						|
			if (here->fc_creat.dir == dirnum &&
 | 
						|
			    here->fc_creat.name == name) {
 | 
						|
				res->isfile = 1;
 | 
						|
				res->objnum = here->fc_creat.newfile;
 | 
						|
				return 0;
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		    case FC_MKDIR:
 | 
						|
			if (here->fc_mkdir.dir == dirnum &&
 | 
						|
			    here->fc_mkdir.name == name) {
 | 
						|
				res->isfile = 0;
 | 
						|
				res->objnum = here->fc_mkdir.newdir;
 | 
						|
				return 0;
 | 
						|
			}
 | 
						|
			if (here->fc_mkdir.newdir == dirnum) {
 | 
						|
				return -1;
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		    case FC_RMDIR:
 | 
						|
			if (here->fc_rmdir.dir == dirnum &&
 | 
						|
			    here->fc_rmdir.name == name) {
 | 
						|
				return -1;
 | 
						|
			}
 | 
						|
			if (here->fc_rmdir.victimdir == dirnum) {
 | 
						|
				return -1;
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		    case FC_UNLINK:
 | 
						|
			if (here->fc_unlink.dir == dirnum &&
 | 
						|
			    here->fc_unlink.name == name) {
 | 
						|
				return -1;
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		    case FC_LINK:
 | 
						|
			if ((here->fc_link.todir == dirnum &&
 | 
						|
			    here->fc_link.toname == name) ||
 | 
						|
			    (here->fc_link.fromdir == dirnum &&
 | 
						|
			     here->fc_link.fromname == name)) {
 | 
						|
				res->isfile = 1;
 | 
						|
				res->objnum = here->fc_link.file;
 | 
						|
				return 0;
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		    case FC_RENAMEFILE:
 | 
						|
			if (here->fc_renamefile.fromdir == dirnum &&
 | 
						|
			    here->fc_renamefile.fromname == name) {
 | 
						|
				return -1;
 | 
						|
			}
 | 
						|
			if (here->fc_renamefile.todir == dirnum &&
 | 
						|
			    here->fc_renamefile.toname == name) {
 | 
						|
				res->isfile = 1;
 | 
						|
				res->objnum = here->fc_renamefile.movedfile;
 | 
						|
				return 0;
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		    case FC_RENAMEDIR:
 | 
						|
			if (here->fc_renamedir.fromdir == dirnum &&
 | 
						|
			    here->fc_renamedir.fromname == name) {
 | 
						|
				return -1;
 | 
						|
			}
 | 
						|
			if (here->fc_renamedir.todir == dirnum &&
 | 
						|
			    here->fc_renamedir.toname == name) {
 | 
						|
				res->isfile = 0;
 | 
						|
				res->objnum = here->fc_renamedir.moveddir;
 | 
						|
				return 0;
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return -1;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Find a file by name and containing directory, using fs_find to
 | 
						|
 * search the history.
 | 
						|
 *
 | 
						|
 * No longer used; should be removed.
 | 
						|
 */
 | 
						|
static
 | 
						|
unsigned
 | 
						|
fs_findfile(unsigned dirnum, unsigned name)
 | 
						|
{
 | 
						|
	struct findresult res;
 | 
						|
 | 
						|
	if (fs_find(dirnum, name, &res) < 0) {
 | 
						|
		die("In directory %u, did not find %s",
 | 
						|
		    dirnum, name_get(name));
 | 
						|
	}
 | 
						|
	if (res.isfile == 0) {
 | 
						|
		die("In directory %u, %s was a directory",
 | 
						|
		    dirnum, name_get(name));
 | 
						|
	}
 | 
						|
	return res.objnum;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Find a directory by name and containing directory, using fs_find to
 | 
						|
 * search the history.
 | 
						|
 *
 | 
						|
 * No longer used; should be removed.
 | 
						|
 */
 | 
						|
static
 | 
						|
unsigned
 | 
						|
fs_finddir(unsigned dirnum, unsigned name)
 | 
						|
{
 | 
						|
	struct findresult res;
 | 
						|
 | 
						|
	if (fs_find(dirnum, name, &res) < 0) {
 | 
						|
		die("In directory %u, did not find %s",
 | 
						|
		    dirnum, name_get(name));
 | 
						|
	}
 | 
						|
	if (res.isfile == 1) {
 | 
						|
		die("In directory %u, %s was not a directory",
 | 
						|
		    dirnum, name_get(name));
 | 
						|
	}
 | 
						|
	return res.objnum;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Check if a name (in a specific containing directory) is a file,
 | 
						|
 * using fs_find.
 | 
						|
 *
 | 
						|
 * No longer used; should be removed.
 | 
						|
 */
 | 
						|
static
 | 
						|
int
 | 
						|
fs_isfile(unsigned dirnum, unsigned name)
 | 
						|
{
 | 
						|
	struct findresult res;
 | 
						|
 | 
						|
	if (fs_find(dirnum, name, &res) < 0) {
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
	return res.isfile;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Check if a name (in a specific containing directory) is a
 | 
						|
 * directory, using fs_find.
 | 
						|
 *
 | 
						|
 * No longer used; should be removed.
 | 
						|
 */
 | 
						|
static
 | 
						|
int
 | 
						|
fs_isdir(unsigned dirnum, unsigned name)
 | 
						|
{
 | 
						|
	struct findresult res;
 | 
						|
 | 
						|
	if (fs_find(dirnum, name, &res) < 0) {
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
	return !res.isfile;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Find the parent of a directory by its object id, by searching the
 | 
						|
 * history.
 | 
						|
 *
 | 
						|
 * No longer used; should be removed.
 | 
						|
 */
 | 
						|
static
 | 
						|
unsigned
 | 
						|
fs_findparent(unsigned dirnum)
 | 
						|
{
 | 
						|
	struct fschange *here;
 | 
						|
 | 
						|
	for (here = fs; here != NULL; here = fs_prevfordir(here, dirnum)) {
 | 
						|
		switch (here->type) {
 | 
						|
		    case FC_NEWFS:
 | 
						|
			if (here->fc_newfs.rootdirnum == dirnum) {
 | 
						|
				return dirnum;
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		    case FC_TRUNCATE:
 | 
						|
		    case FC_WRITE:
 | 
						|
		    case FC_CREAT:
 | 
						|
			break;
 | 
						|
		    case FC_MKDIR:
 | 
						|
			if (here->fc_mkdir.newdir == dirnum) {
 | 
						|
				return here->fc_mkdir.dir;
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		    case FC_RMDIR:
 | 
						|
			if (here->fc_rmdir.victimdir == dirnum) {
 | 
						|
				die("Directory %u was removed", dirnum);
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		    case FC_UNLINK:
 | 
						|
		    case FC_LINK:
 | 
						|
		    case FC_RENAMEFILE:
 | 
						|
			break;
 | 
						|
		    case FC_RENAMEDIR:
 | 
						|
			if (here->fc_renamedir.moveddir == dirnum) {
 | 
						|
				return here->fc_renamedir.todir;
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	die("Directory %u not found", dirnum);
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
#endif /* 0 */
 | 
						|
 | 
						|
/*
 | 
						|
 * Find a file by name and containing directory, by searching the
 | 
						|
 * volume state.
 | 
						|
 */
 | 
						|
static
 | 
						|
unsigned
 | 
						|
model_findfile(unsigned dirnum, unsigned name)
 | 
						|
{
 | 
						|
	struct fsobject *obj;
 | 
						|
	struct fsdirent *de;
 | 
						|
 | 
						|
	obj = finddir(state, dirnum);
 | 
						|
	de = fsdir_find_entry(obj, name, 1);
 | 
						|
	if (de->obj->isdir) {
 | 
						|
		die("In directory %u, %s was a directory",
 | 
						|
		    dirnum, name_get(name));
 | 
						|
	}
 | 
						|
	return de->obj->obj_file.identity;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Find a directory by name and containing directory, by searching the
 | 
						|
 * volume state.
 | 
						|
 */
 | 
						|
static
 | 
						|
unsigned
 | 
						|
model_finddir(unsigned dirnum, unsigned name)
 | 
						|
{
 | 
						|
	struct fsobject *obj;
 | 
						|
	struct fsdirent *de;
 | 
						|
 | 
						|
	obj = finddir(state, dirnum);
 | 
						|
	de = fsdir_find_entry(obj, name, 1);
 | 
						|
	if (!de->obj->isdir) {
 | 
						|
		die("In directory %u, %s was not a directory",
 | 
						|
		    dirnum, name_get(name));
 | 
						|
	}
 | 
						|
	return de->obj->obj_dir.identity;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Find a directory's parent by object id, by searching the volume
 | 
						|
 * state.
 | 
						|
 */
 | 
						|
static
 | 
						|
unsigned
 | 
						|
model_findparent(unsigned dirnum)
 | 
						|
{
 | 
						|
	struct fsobject *obj;
 | 
						|
 | 
						|
	obj = finddir(state, dirnum);
 | 
						|
	assert(obj->isdir);
 | 
						|
	assert(obj->obj_dir.parent->isdir);
 | 
						|
	return obj->obj_dir.parent->obj_dir.identity;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Check if a name (in a specific containing directory) is a file, by
 | 
						|
 * searching the volume state.
 | 
						|
 */
 | 
						|
static
 | 
						|
unsigned
 | 
						|
model_isfile(unsigned dirnum, unsigned name)
 | 
						|
{
 | 
						|
	struct fsobject *obj;
 | 
						|
	struct fsdirent *de;
 | 
						|
 | 
						|
	obj = finddir(state, dirnum);
 | 
						|
	de = fsdir_find_entry(obj, name, 0);
 | 
						|
	return de != NULL && !de->obj->isdir;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Check if a name (in a specific containing directory) is a
 | 
						|
 * directory, by searching the volume state.
 | 
						|
 */
 | 
						|
static
 | 
						|
unsigned
 | 
						|
model_isdir(unsigned dirnum, unsigned name)
 | 
						|
{
 | 
						|
	struct fsobject *obj;
 | 
						|
	struct fsdirent *de;
 | 
						|
 | 
						|
	obj = finddir(state, dirnum);
 | 
						|
	de = fsdir_find_entry(obj, name, 0);
 | 
						|
	return de != NULL && de->obj->isdir;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Get the current size of a file in the current volume state.
 | 
						|
 */
 | 
						|
static
 | 
						|
off_t
 | 
						|
model_getfilesize(unsigned filenum)
 | 
						|
{
 | 
						|
	struct fsobject *obj;
 | 
						|
 | 
						|
	obj = findfile_maybe(state, filenum);
 | 
						|
	if (obj == NULL) {
 | 
						|
		/* file is unlinked */
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
	assert(!obj->isdir);
 | 
						|
	return obj->obj_file.len;
 | 
						|
}
 | 
						|
 | 
						|
////////////////////////////////////////////////////////////
 | 
						|
// model construction (replaying the workload)
 | 
						|
 | 
						|
/* The workload's current directory. */
 | 
						|
static unsigned cwd;
 | 
						|
 | 
						|
/*
 | 
						|
 * Set up for replying the workload: allocate the root directory,
 | 
						|
 * generate the newfs record, set up the volume state, initialize
 | 
						|
 * the current directory.
 | 
						|
 */
 | 
						|
void
 | 
						|
check_setup(void)
 | 
						|
{
 | 
						|
	unsigned rootdir;
 | 
						|
 | 
						|
	assert(firstchange == NULL);
 | 
						|
	assert(changes == NULL);
 | 
						|
	assert(state == NULL);
 | 
						|
	assert(nextfilenum == 0);
 | 
						|
	assert(nextdirnum == 0);
 | 
						|
 | 
						|
	rootdir = nextdirnum++;
 | 
						|
	firstchange = changes = fc_create_newfs(rootdir);
 | 
						|
 | 
						|
	rewindstate();
 | 
						|
 | 
						|
	/* apply the first change (the newfs record) */
 | 
						|
	apply_change(state, changes);
 | 
						|
 | 
						|
	cwd = rootdir;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Workload replay: create a file
 | 
						|
 */
 | 
						|
int
 | 
						|
check_createfile(unsigned name)
 | 
						|
{
 | 
						|
	struct fschange *prevdir;
 | 
						|
	unsigned filenum;
 | 
						|
 | 
						|
	prevdir = changes_findprevdir(cwd);
 | 
						|
 | 
						|
	filenum = nextfilenum++;
 | 
						|
	fc_attach(fc_create_creat(prevdir, cwd, name, filenum));
 | 
						|
	return filenum;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Workload replay: open a file
 | 
						|
 */
 | 
						|
int
 | 
						|
check_openfile(unsigned name)
 | 
						|
{
 | 
						|
	return model_findfile(cwd, name);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Workload replay: close a file
 | 
						|
 */
 | 
						|
void
 | 
						|
check_closefile(int handle, unsigned name)
 | 
						|
{
 | 
						|
	/* don't need to do anything */
 | 
						|
	(void)handle;
 | 
						|
	(void)name;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Workload replay: write to a file
 | 
						|
 *
 | 
						|
 * CODE and SEQ encode the contents to be written.
 | 
						|
 */
 | 
						|
void
 | 
						|
check_write(int handle, unsigned name, unsigned code, unsigned seq,
 | 
						|
	    off_t pos, off_t len)
 | 
						|
{
 | 
						|
	unsigned filenum;
 | 
						|
	struct fschange *prevfile;
 | 
						|
	off_t prevlen;
 | 
						|
 | 
						|
	filenum = handle;
 | 
						|
	assert(filenum < nextfilenum);
 | 
						|
	(void)name;
 | 
						|
 | 
						|
	prevlen = model_getfilesize(filenum);
 | 
						|
 | 
						|
	prevfile = changes_findprevfile(filenum);
 | 
						|
	fc_attach(fc_create_write(prevfile, filenum, pos, len, prevlen,
 | 
						|
				  code, seq));
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Workload replay: truncate a file
 | 
						|
 */
 | 
						|
void
 | 
						|
check_truncate(int handle, unsigned name, off_t len)
 | 
						|
{
 | 
						|
	unsigned filenum;
 | 
						|
	struct fschange *prevfile;
 | 
						|
 | 
						|
	filenum = handle;
 | 
						|
	assert(filenum < nextfilenum);
 | 
						|
	(void)name;
 | 
						|
 | 
						|
	prevfile = changes_findprevfile(filenum);
 | 
						|
	fc_attach(fc_create_truncate(prevfile, filenum, len));
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Workload replay: create a directory
 | 
						|
 */
 | 
						|
void
 | 
						|
check_mkdir(unsigned name)
 | 
						|
{
 | 
						|
	struct fschange *prevdir;
 | 
						|
	unsigned dirnum;
 | 
						|
 | 
						|
	prevdir = changes_findprevdir(cwd);
 | 
						|
	dirnum = nextdirnum++;
 | 
						|
	fc_attach(fc_create_mkdir(prevdir, cwd, name, dirnum));
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Workload replay: remove a directory
 | 
						|
 */
 | 
						|
void
 | 
						|
check_rmdir(unsigned name)
 | 
						|
{
 | 
						|
	struct fschange *prevdir, *prevvictim;
 | 
						|
	unsigned victim;
 | 
						|
 | 
						|
	prevdir = changes_findprevdir(cwd);
 | 
						|
	victim = model_finddir(cwd, name);
 | 
						|
	prevvictim = changes_findprevdir(victim);
 | 
						|
 | 
						|
	fc_attach(fc_create_rmdir(prevdir, prevvictim, cwd, name, victim));
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Workload replay: remove a file
 | 
						|
 */
 | 
						|
void
 | 
						|
check_unlink(unsigned name)
 | 
						|
{
 | 
						|
	struct fschange *prevdir, *prevvictim;
 | 
						|
	unsigned victim;
 | 
						|
 | 
						|
	prevdir = changes_findprevdir(cwd);
 | 
						|
	victim = model_findfile(cwd, name);
 | 
						|
	prevvictim = changes_findprevfile(victim);
 | 
						|
 | 
						|
	fc_attach(fc_create_unlink(prevdir, prevvictim, cwd, name, victim));
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Workload replay: hard link a file
 | 
						|
 */
 | 
						|
void
 | 
						|
check_link(unsigned fromname, unsigned toname)
 | 
						|
{
 | 
						|
	struct fschange *prevdir, *prevfile;
 | 
						|
	unsigned filenum;
 | 
						|
 | 
						|
	prevdir = changes_findprevdir(cwd);
 | 
						|
	filenum = model_findfile(cwd, fromname);
 | 
						|
	prevfile = changes_findprevfile(filenum);
 | 
						|
 | 
						|
	fc_attach(fc_create_link(prevdir, prevdir, prevfile,
 | 
						|
				 cwd, fromname, cwd, toname, filenum));
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Workload replay: rename something
 | 
						|
 */
 | 
						|
static
 | 
						|
void
 | 
						|
common_rename(unsigned fromdirnum, unsigned fromname,
 | 
						|
	      unsigned todirnum, unsigned toname)
 | 
						|
{
 | 
						|
	struct fschange *prevfromdir, *prevtodir, *prevfrom, *prevto;
 | 
						|
	unsigned fromnum, tonum;
 | 
						|
	struct fschange *fc;
 | 
						|
	int isfile;
 | 
						|
 | 
						|
	prevfromdir = changes_findprevdir(fromdirnum);
 | 
						|
	prevtodir = changes_findprevdir(todirnum);
 | 
						|
 | 
						|
	if (model_isfile(todirnum, toname)) {
 | 
						|
		isfile = 1;
 | 
						|
		assert(model_isfile(fromdirnum, fromname));
 | 
						|
		tonum = model_findfile(todirnum, toname);
 | 
						|
		prevto = changes_findprevfile(tonum);
 | 
						|
		fc = fc_create_unlink(prevtodir, prevto,
 | 
						|
				      todirnum, toname, tonum);
 | 
						|
	}
 | 
						|
	else if (model_isdir(todirnum, toname)) {
 | 
						|
		isfile = 0;
 | 
						|
		assert(model_isdir(fromdirnum, fromname));
 | 
						|
		tonum = model_finddir(todirnum, toname);
 | 
						|
		prevto = changes_findprevdir(tonum);
 | 
						|
		fc = fc_create_rmdir(prevtodir, prevto,
 | 
						|
				     todirnum, toname, tonum);
 | 
						|
	}
 | 
						|
	else {
 | 
						|
		isfile = model_isfile(fromdirnum, fromname);
 | 
						|
		fc = NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	if (fc) {
 | 
						|
		fc->partial = 1;
 | 
						|
		fc_attach(fc);
 | 
						|
	}
 | 
						|
 | 
						|
	if (isfile) {
 | 
						|
		fromnum = model_findfile(fromdirnum, fromname);
 | 
						|
		prevfrom = changes_findprevfile(fromnum);
 | 
						|
		fc = fc_create_renamefile(prevfromdir, prevtodir, prevfrom,
 | 
						|
					  fromdirnum, fromname,
 | 
						|
					  todirnum, toname, fromnum);
 | 
						|
	}
 | 
						|
	else {
 | 
						|
		fromnum = model_finddir(fromdirnum, fromname);
 | 
						|
		prevfrom = changes_findprevdir(fromnum);
 | 
						|
		fc = fc_create_renamedir(prevfromdir, prevtodir, prevfrom,
 | 
						|
					 fromdirnum, fromname,
 | 
						|
					 todirnum, toname, fromnum);
 | 
						|
	}
 | 
						|
	fc_attach(fc);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Workload replay: rename within the current directory
 | 
						|
 */
 | 
						|
void
 | 
						|
check_rename(unsigned from, unsigned to)
 | 
						|
{
 | 
						|
	common_rename(cwd, from, cwd, to);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Workload replay: cross-directory rename
 | 
						|
 */
 | 
						|
void
 | 
						|
check_renamexd(unsigned fromdir, unsigned from, unsigned todir, unsigned to)
 | 
						|
{
 | 
						|
	unsigned fromdirnum, todirnum;
 | 
						|
 | 
						|
	/* fromdir/todir are names in cwd */
 | 
						|
	fromdirnum = model_finddir(cwd, fromdir);
 | 
						|
	todirnum = model_finddir(cwd, todir);
 | 
						|
 | 
						|
	common_rename(fromdirnum, from, todirnum, to);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Workload replay: change directory
 | 
						|
 */
 | 
						|
void
 | 
						|
check_chdir(unsigned name)
 | 
						|
{
 | 
						|
	cwd = model_finddir(cwd, name);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Workload replay: change directory to ..
 | 
						|
 */
 | 
						|
void
 | 
						|
check_chdirup(void)
 | 
						|
{
 | 
						|
	cwd = model_findparent(cwd);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Workload replay: sync
 | 
						|
 */
 | 
						|
void
 | 
						|
check_sync(void)
 | 
						|
{
 | 
						|
	/* nothing */
 | 
						|
}
 | 
						|
 | 
						|
////////////////////////////////////////////////////////////
 | 
						|
// inspection of the fs
 | 
						|
 | 
						|
/* the root of the state we found (as opposed to the state we modeled) */
 | 
						|
static struct fsobject *found;
 | 
						|
 | 
						|
/* count of how much we found */
 | 
						|
static unsigned found_subdirs, found_files;
 | 
						|
 | 
						|
/*
 | 
						|
 * Wrapper. As of this writing OS/161 provides fstat but not stat...
 | 
						|
 * grr
 | 
						|
 */
 | 
						|
static
 | 
						|
int
 | 
						|
xstat(const char *path, struct stat *buf)
 | 
						|
{
 | 
						|
	int fd, ret, serrno;
 | 
						|
 | 
						|
	fd = open(path, O_RDONLY);
 | 
						|
	if (fd < 0) {
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
	ret = fstat(fd, buf);
 | 
						|
	serrno = errno;
 | 
						|
	close(fd);
 | 
						|
	errno = serrno;
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Inspect a directory. PARENTOBJ is the *containing* directory;
 | 
						|
 * PARENTINO is its inode number, and DIRNAMESTR is the name of the
 | 
						|
 * directory to inspect. The arguments are set up this way (rather
 | 
						|
 * than e.g. passing the fsobject of the directory to inspect) to
 | 
						|
 * improve the error reporting, and to allow checking .., and so
 | 
						|
 * forth.
 | 
						|
 */
 | 
						|
static
 | 
						|
struct fsobject *
 | 
						|
inspectdir(struct fsobject *parentobj, ino_t parentino, const char *dirnamestr)
 | 
						|
{
 | 
						|
	char subnamestr[NAME_MAX+1];
 | 
						|
	size_t subnamelen;
 | 
						|
	struct stat dirstat, dotstat, substat;
 | 
						|
	int dirfd;
 | 
						|
	struct fsobject *subobj, *ret;
 | 
						|
	struct fsdirent *contents, *de;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Stat the target, and cd into it.
 | 
						|
	 */
 | 
						|
 | 
						|
	if (xstat(dirnamestr, &dirstat)) {
 | 
						|
		err(1, "%s: stat", dirnamestr);
 | 
						|
	}
 | 
						|
 | 
						|
	assert(S_ISDIR(dirstat.st_mode));
 | 
						|
	if (chdir(dirnamestr)) {
 | 
						|
		err(1, "%s: chdir", dirnamestr);
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Check that . is correct
 | 
						|
	 */
 | 
						|
 | 
						|
	if (xstat(".", &dotstat)) {
 | 
						|
		err(1, "In %s: .: stat", dirnamestr);
 | 
						|
	}
 | 
						|
	if (dotstat.st_dev != dirstat.st_dev) {
 | 
						|
		errx(1, "in %s: .: wrong volume id; found %u, expected %u",
 | 
						|
		     dirnamestr, dotstat.st_dev, dirstat.st_dev);
 | 
						|
	}
 | 
						|
	if (dotstat.st_ino != dirstat.st_ino) {
 | 
						|
		errx(1, "%s/.: wrong inode number; found %u, expected %u",
 | 
						|
		     dirnamestr, dotstat.st_ino, dirstat.st_ino);
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Check that .. leads back
 | 
						|
	 */
 | 
						|
 | 
						|
	if (xstat("..", &dotstat)) {
 | 
						|
		err(1, "In %s: ..: stat", dirnamestr);
 | 
						|
	}
 | 
						|
	if (dotstat.st_dev != dirstat.st_dev) {
 | 
						|
		errx(1, "In %s: ..: wrong volume id; found %u, expected %u",
 | 
						|
		     dirnamestr, dotstat.st_dev, dirstat.st_dev);
 | 
						|
	}
 | 
						|
	if (dotstat.st_ino != parentino) {
 | 
						|
		errx(1, "In %s: ..: wrong inode number; found %u, expected %u",
 | 
						|
		     dirnamestr, dotstat.st_ino, parentino);
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Create a directory fsobject.
 | 
						|
	 */
 | 
						|
 | 
						|
	dirfd = open(".", O_RDONLY);
 | 
						|
	if (dirfd < 0) {
 | 
						|
		err(1, "In %s: .: open", dirnamestr);
 | 
						|
	}
 | 
						|
 | 
						|
	ret = fsobject_create_dir(UNKNOWN_ID, parentobj);
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Read the contents of the target directory and create
 | 
						|
	 * directory entries for them. Recurse for ones that are
 | 
						|
	 * themselves directories.
 | 
						|
	 */
 | 
						|
	contents = NULL;
 | 
						|
	while (1) {
 | 
						|
		subnamelen = getdirentry(dirfd, subnamestr,
 | 
						|
					 sizeof(subnamestr)-1);
 | 
						|
		if (subnamelen == 0) {
 | 
						|
			break;
 | 
						|
		}
 | 
						|
		subnamestr[subnamelen] = 0;
 | 
						|
 | 
						|
		if (!strcmp(subnamestr, ".") || !strcmp(subnamestr, "..")) {
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
		if (xstat(subnamestr, &substat)) {
 | 
						|
			err(1, "In %s: %s: stat", dirnamestr, subnamestr);
 | 
						|
		}
 | 
						|
		if (S_ISDIR(substat.st_mode)) {
 | 
						|
			subobj = inspectdir(ret, dirstat.st_ino, subnamestr);
 | 
						|
			found_subdirs++;
 | 
						|
		}
 | 
						|
		else {
 | 
						|
			subobj = fsobject_create_file(UNKNOWN_ID);
 | 
						|
			subobj->obj_file.len = substat.st_size;
 | 
						|
			found_files++;
 | 
						|
		}
 | 
						|
		de = fsdirent_create(name_find(subnamestr), subobj);
 | 
						|
		de->next = contents;
 | 
						|
		contents = de;
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Done scanning; cd out, save the contents, and return the
 | 
						|
	 * new object.
 | 
						|
	 */
 | 
						|
 | 
						|
	close(dirfd);
 | 
						|
	if (chdir("..")) {
 | 
						|
		err(1, "In %s; ..: chdir", dirnamestr);
 | 
						|
	}
 | 
						|
 | 
						|
	ret->obj_dir.entries = contents;
 | 
						|
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Inspect the whole volume by starting with "." -- we assume that we
 | 
						|
 * were run in the root directory.
 | 
						|
 */
 | 
						|
static
 | 
						|
void
 | 
						|
inspectfs(void)
 | 
						|
{
 | 
						|
	struct stat st;
 | 
						|
 | 
						|
	if (xstat(".", &st)) {
 | 
						|
		err(1, ".: stat");
 | 
						|
	}
 | 
						|
	found = inspectdir(NULL, st.st_ino, ".");
 | 
						|
}
 | 
						|
 | 
						|
////////////////////////////////////////////////////////////
 | 
						|
// comparison of state trees
 | 
						|
 | 
						|
/*
 | 
						|
 * Count the number of objects at and below a particular fsobject.
 | 
						|
 */
 | 
						|
static
 | 
						|
unsigned
 | 
						|
count_subtree(struct fsobject *obj)
 | 
						|
{
 | 
						|
	struct fsdirent *de;
 | 
						|
	unsigned ret = 1;
 | 
						|
 | 
						|
	if (obj->isdir) {
 | 
						|
		for (de = obj->obj_dir.entries; de != NULL; de = de->next) {
 | 
						|
			ret += count_subtree(de->obj);
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Compare two fsobjects. Return the matching score. (lower scores are
 | 
						|
 * better matches)
 | 
						|
 */
 | 
						|
static
 | 
						|
unsigned
 | 
						|
compare_objects(struct fsobject *obja, struct fsobject *objb)
 | 
						|
{
 | 
						|
	struct fsdirent *enta, *entb;
 | 
						|
	unsigned ret, found;
 | 
						|
 | 
						|
	if (obja->isdir != objb->isdir) {
 | 
						|
		/*
 | 
						|
		 * One object's a file, the other's a directory.
 | 
						|
		 *
 | 
						|
		 * Return one point for each name in the missing
 | 
						|
		 * subtree. This includes one point for the top dir,
 | 
						|
		 * which is mismatched rather than missing.
 | 
						|
		 */
 | 
						|
		if (obja->isdir) {
 | 
						|
			return count_subtree(obja);
 | 
						|
		}
 | 
						|
		assert(objb->isdir);
 | 
						|
		return count_subtree(objb);
 | 
						|
	}
 | 
						|
 | 
						|
	if (!obja->isdir) {
 | 
						|
		/* 
 | 
						|
		 * Both objects are files
 | 
						|
		 */
 | 
						|
		assert(!objb->isdir);
 | 
						|
		if (obja->obj_file.len != objb->obj_file.len) {
 | 
						|
			/* one point for the size being different */
 | 
						|
			return 1;
 | 
						|
		}
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
 | 
						|
	/* 
 | 
						|
	 * Both objects are directories. Recurse on all pairs of
 | 
						|
	 * entries that have the same name. Return one point for each
 | 
						|
	 * name that's present (recursively) in one object but not the
 | 
						|
	 * other.
 | 
						|
	 *
 | 
						|
	 * XXX: sort the entries first or something instead of naively
 | 
						|
	 * searching 2*N^2 times.
 | 
						|
	 */
 | 
						|
	assert(obja->isdir);
 | 
						|
	assert(objb->isdir);
 | 
						|
 | 
						|
	ret = 0;
 | 
						|
	for (enta = obja->obj_dir.entries; enta != NULL; enta = enta->next) {
 | 
						|
		found = 0;
 | 
						|
		for (entb = objb->obj_dir.entries; entb != NULL;
 | 
						|
		     entb = entb->next) {
 | 
						|
			if (enta->name == entb->name) {
 | 
						|
				ret += compare_objects(enta->obj, entb->obj);
 | 
						|
				found = 1;
 | 
						|
				break;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if (!found) {
 | 
						|
			if (enta->obj->isdir) {
 | 
						|
				/* whole subtree is missing */
 | 
						|
				ret += count_subtree(enta->obj);
 | 
						|
			}
 | 
						|
			/* one file is missing */
 | 
						|
			ret += 1;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
 | 
						|
	for (entb = objb->obj_dir.entries; entb != NULL; entb = entb->next) {
 | 
						|
		found = 0;
 | 
						|
		for (enta = obja->obj_dir.entries; enta != NULL;
 | 
						|
		     enta = enta->next) {
 | 
						|
			if (enta->name == entb->name) {
 | 
						|
				found = 1;
 | 
						|
				break;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if (!found) {
 | 
						|
			if (entb->obj->isdir) {
 | 
						|
				/* whole subtree is missing */
 | 
						|
				ret += count_subtree(entb->obj);
 | 
						|
			}
 | 
						|
			/* one file is missing */
 | 
						|
			ret += 1;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Print an indentation.
 | 
						|
 */
 | 
						|
static
 | 
						|
void
 | 
						|
doindent(unsigned depth)
 | 
						|
{
 | 
						|
	unsigned i;
 | 
						|
 | 
						|
	for (i=0; i<depth; i++) {
 | 
						|
		tprintf("   ");
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Print out the differences between two fsobjects. Otherwise much the
 | 
						|
 * same as compare_objects.
 | 
						|
 *
 | 
						|
 * OBJA is "expected" (the model); OBJB is "found" (what we saw on disk).
 | 
						|
 */
 | 
						|
static
 | 
						|
void
 | 
						|
printdiffs(unsigned indent, struct fsobject *obja, struct fsobject *objb)
 | 
						|
{
 | 
						|
	struct fsdirent *enta, *entb;
 | 
						|
	unsigned found;
 | 
						|
 | 
						|
	assert(obja->isdir);
 | 
						|
	assert(objb->isdir);
 | 
						|
 | 
						|
	for (enta = obja->obj_dir.entries; enta != NULL; enta = enta->next) {
 | 
						|
		found = 0;
 | 
						|
		for (entb = objb->obj_dir.entries; entb != NULL;
 | 
						|
		     entb = entb->next) {
 | 
						|
			if (enta->name == entb->name) {
 | 
						|
				doindent(indent);
 | 
						|
				tprintf("%s", name_get(enta->name));
 | 
						|
				if (enta->obj->isdir &&
 | 
						|
				    !entb->obj->isdir) {
 | 
						|
					tprintf(": expected dir, found file;");
 | 
						|
					tprintf(" %u names missing.\n",
 | 
						|
					       count_subtree(enta->obj) - 1);
 | 
						|
				}
 | 
						|
				else if (!enta->obj->isdir &&
 | 
						|
					 entb->obj->isdir) {
 | 
						|
					tprintf(": expected file, found dir;");
 | 
						|
					tprintf(" %u extra names.\n",
 | 
						|
					       count_subtree(entb->obj) - 1);
 | 
						|
				}
 | 
						|
				else if (!enta->obj->isdir &&
 | 
						|
					 !entb->obj->isdir) {
 | 
						|
					off_t alen, blen;
 | 
						|
 | 
						|
					alen = enta->obj->obj_file.len;
 | 
						|
					blen = entb->obj->obj_file.len;
 | 
						|
					if (alen == blen) {
 | 
						|
						tprintf("\t\t%lld bytes (ok)\n",
 | 
						|
						       alen);
 | 
						|
					}
 | 
						|
					else {
 | 
						|
						tprintf(": found %lld bytes, "
 | 
						|
						       "expected %lld "
 | 
						|
						       "bytes.\n",
 | 
						|
						       blen, alen);
 | 
						|
					}
 | 
						|
				}
 | 
						|
				else {
 | 
						|
					tprintf("/\n");
 | 
						|
					printdiffs(indent + 1,
 | 
						|
						   enta->obj, entb->obj);
 | 
						|
				}
 | 
						|
				found = 1;
 | 
						|
				break;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if (!found) {
 | 
						|
			doindent(indent);
 | 
						|
			tprintf("%s: missing ", name_get(enta->name));
 | 
						|
			if (enta->obj->isdir) {
 | 
						|
				tprintf("subtree with %u names.\n",
 | 
						|
				       count_subtree(enta->obj) - 1);
 | 
						|
			}
 | 
						|
			else {
 | 
						|
				tprintf("file\n");
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
 | 
						|
	for (entb = objb->obj_dir.entries; entb != NULL; entb = entb->next) {
 | 
						|
		found = 0;
 | 
						|
		for (enta = obja->obj_dir.entries; enta != NULL;
 | 
						|
		     enta = enta->next) {
 | 
						|
			if (enta->name == entb->name) {
 | 
						|
				found = 1;
 | 
						|
				break;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if (!found) {
 | 
						|
			doindent(indent);
 | 
						|
			tprintf("%s: extra ", name_get(entb->name));
 | 
						|
			if (entb->obj->isdir) {
 | 
						|
				tprintf("subtree with %u names.\n",
 | 
						|
				       count_subtree(entb->obj) - 1);
 | 
						|
			}
 | 
						|
			else {
 | 
						|
				tprintf("file\n");
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
////////////////////////////////////////////////////////////
 | 
						|
// comparison of file contents
 | 
						|
 | 
						|
/*
 | 
						|
 * Return the version of the oldest change with the same directory
 | 
						|
 * structure as CHANGE. This skips past truncate and write operations
 | 
						|
 * that only change file sizes.
 | 
						|
 */
 | 
						|
static
 | 
						|
unsigned
 | 
						|
findokversion(struct fschange *change)
 | 
						|
{
 | 
						|
	while (change != NULL) {
 | 
						|
		switch (change->type) {
 | 
						|
		    case FC_TRUNCATE:
 | 
						|
		    case FC_WRITE:
 | 
						|
			break;
 | 
						|
		    default:
 | 
						|
			return change->version;
 | 
						|
		}
 | 
						|
		change = change->prev;
 | 
						|
	}
 | 
						|
	assert(0);
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Back up to previous change entries until we find one affecting the
 | 
						|
 * contents of the specified file. (That means: truncate, write, or
 | 
						|
 * create.)
 | 
						|
 *
 | 
						|
 * Returns the current (passed-in) change if that matches.
 | 
						|
 */
 | 
						|
static
 | 
						|
struct fschange *
 | 
						|
backup_for_file(struct fschange *change, unsigned filenum)
 | 
						|
{
 | 
						|
	while (change != NULL) {
 | 
						|
		switch (change->type) {
 | 
						|
		    case FC_TRUNCATE:
 | 
						|
			if (change->fc_truncate.file == filenum) {
 | 
						|
				return change;
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		    case FC_WRITE:
 | 
						|
			if (change->fc_write.file == filenum) {
 | 
						|
				return change;
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		    case FC_CREAT:
 | 
						|
			if (change->fc_creat.newfile == filenum) {
 | 
						|
				return change;
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		    default:
 | 
						|
			break;
 | 
						|
		}
 | 
						|
		change = change->prev;
 | 
						|
	}
 | 
						|
	return NULL;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Expect zeros in a file from START to END. The file is given by FD
 | 
						|
 * and named NAMESTR. Report if we find stuff other than zeros.
 | 
						|
 */
 | 
						|
static
 | 
						|
void
 | 
						|
checkfilezeros(int fd, const char *namestr, off_t start, off_t end)
 | 
						|
{
 | 
						|
	char buf[1024];
 | 
						|
	size_t len, i;
 | 
						|
	ssize_t ret;
 | 
						|
	unsigned poison = 0, trash = 0;
 | 
						|
	off_t origstart = start;
 | 
						|
 | 
						|
	tprintf("   %lld - %lld (expecting zeros)\n", start, end);
 | 
						|
 | 
						|
	if (lseek(fd, start, SEEK_SET) == -1) {
 | 
						|
		err(1, "%s: lseek to %lld", namestr, start);
 | 
						|
	}
 | 
						|
	while (start < end) {
 | 
						|
		/* XXX this assumes end - start fits in size_t */
 | 
						|
		len = end - start;
 | 
						|
		if (len > sizeof(buf)) {
 | 
						|
			len = sizeof(buf);
 | 
						|
		}
 | 
						|
		ret = read(fd, buf, len);
 | 
						|
		if (ret == -1) {
 | 
						|
			err(1, "%s: read %u at %lld", namestr, len, start);
 | 
						|
		}
 | 
						|
		if (ret == 0) {
 | 
						|
			errx(1, "%s: read %u at %lld: Unexpected EOF",
 | 
						|
			     namestr, len, start);
 | 
						|
		}
 | 
						|
		for (i=0; i<(size_t)ret; i++) {
 | 
						|
			if ((unsigned char)buf[i] == POISON_VAL) {
 | 
						|
				poison++;
 | 
						|
			}
 | 
						|
			else if (buf[i] != 0) {
 | 
						|
				trash++;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		start += ret;
 | 
						|
	}
 | 
						|
	if (poison > 0 || trash > 0) {
 | 
						|
		tprintf("ERROR: File %s: expected zeros from %lld to %lld; "
 | 
						|
		       "found",
 | 
						|
		       namestr, origstart, end);
 | 
						|
		if (poison > 0) {
 | 
						|
			tprintf(" %u poison bytes", poison);
 | 
						|
			if (trash > 0) {
 | 
						|
				tprintf(" and");
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if (trash > 0) {
 | 
						|
			tprintf(" %u trash bytes", trash);
 | 
						|
		}
 | 
						|
		tprintf("\n");
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Read data in from a file, into data.c's read buffer where it will
 | 
						|
 * be checked for correctness. The data.c region involved is
 | 
						|
 * REGIONSTART..REGIONEND; all of that space is needed so the expected
 | 
						|
 * data can be generated correctly. However, we're only looking at the
 | 
						|
 * range CHECKSTART..CHECKEND, which may be only part of the data.c
 | 
						|
 * region.
 | 
						|
 */
 | 
						|
static
 | 
						|
void
 | 
						|
readfiledata(int fd, const char *namestr,
 | 
						|
	      off_t regionstart, off_t checkstart,
 | 
						|
	      off_t checkend, off_t regionend)
 | 
						|
{
 | 
						|
	char *readbuf;
 | 
						|
	size_t bufpos, len;
 | 
						|
	ssize_t ret;
 | 
						|
 | 
						|
	/* CHECKSTART..CHECKEND must be within REGIONSTART..REGIONEND */
 | 
						|
	assert(regionstart <= checkstart);
 | 
						|
	assert(checkstart <= checkend);
 | 
						|
	assert(checkend <= regionend);
 | 
						|
 | 
						|
	readbuf = data_mapreadbuf(regionend - regionstart);
 | 
						|
	bufpos = checkstart - regionstart;
 | 
						|
	len = checkend - checkstart;
 | 
						|
	if (lseek(fd, checkstart, SEEK_SET) == -1) {
 | 
						|
		err(1, "%s: lseek to %lld", namestr, checkstart);
 | 
						|
	}
 | 
						|
 | 
						|
	while (len > 0) {
 | 
						|
		ret = read(fd, readbuf + bufpos, len);
 | 
						|
		if (ret == -1) {
 | 
						|
			err(1, "%s: read %u at %lld",
 | 
						|
			    namestr, len, regionstart + bufpos);
 | 
						|
		}
 | 
						|
		if (ret == 0) {
 | 
						|
			errx(1, "%s: read %u at %lld: Unexpected EOF",
 | 
						|
			     namestr, len, regionstart + bufpos);
 | 
						|
		}
 | 
						|
		bufpos += ret;
 | 
						|
		assert(len >= (size_t)ret);
 | 
						|
		len -= ret;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Check the data found in the file FD named NAMESTR, in the range
 | 
						|
 * CHECKSTART..CHECKEND, which is within or equal to the data.c
 | 
						|
 * region REGIONSTART..REGIONEND. ZEROSTART is the place where the
 | 
						|
 * data can legitimately be zeroed rather than the expected data
 | 
						|
 * (the portion of a write that extended a file) and the data is
 | 
						|
 * generated from CODE and SEQ.
 | 
						|
 */
 | 
						|
static
 | 
						|
void
 | 
						|
checkfiledata(int fd, const char *namestr, unsigned code, unsigned seq,
 | 
						|
	      off_t zerostart,
 | 
						|
	      off_t regionstart, off_t checkstart,
 | 
						|
	      off_t checkend, off_t regionend)
 | 
						|
{
 | 
						|
	if (checkstart < regionstart) {
 | 
						|
		checkstart = regionstart;
 | 
						|
	}
 | 
						|
	if (checkend > regionend) {
 | 
						|
		checkend = regionend;
 | 
						|
	}
 | 
						|
 | 
						|
	tprintf("   %lld - %lld\n", checkstart, checkend);
 | 
						|
 | 
						|
	readfiledata(fd, namestr,
 | 
						|
		     regionstart, checkstart, checkend, regionend);
 | 
						|
 | 
						|
	data_check(namestr, regionstart,
 | 
						|
		   code, seq, zerostart - regionstart, regionend - regionstart,
 | 
						|
		   checkstart - regionstart, checkend - checkstart);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Check a range of the file FD named NAMESTR, from START to END,
 | 
						|
 * against the model state expected as of CHANGE.
 | 
						|
 */
 | 
						|
static
 | 
						|
void
 | 
						|
checkfilerange(int fd, const char *namestr, struct fschange *change,
 | 
						|
	       off_t start, off_t end)
 | 
						|
{
 | 
						|
	assert(start < end);
 | 
						|
	if (change->type == FC_TRUNCATE) {
 | 
						|
		off_t tlen;
 | 
						|
		struct fschange *prev;
 | 
						|
 | 
						|
		/*
 | 
						|
		 * The most recent change was a truncate. Anything
 | 
						|
		 * beyond the truncate length should read as zeroes;
 | 
						|
		 * recurse on the part before (if any) using the
 | 
						|
		 * previous change affecting this file.
 | 
						|
		 *
 | 
						|
		 * We might be checking that a chunk of the file
 | 
						|
		 * beyond the truncate length is zero (even though as
 | 
						|
		 * of this change it's past EOF) if a later change
 | 
						|
		 * made that region into a hole in a sparse file.
 | 
						|
		 */
 | 
						|
 | 
						|
		tlen = change->fc_truncate.len;
 | 
						|
		prev = change->fc_truncate.prev_thisfile;
 | 
						|
 | 
						|
		if (tlen < start) {
 | 
						|
			checkfilezeros(fd, namestr, start, end);
 | 
						|
		}
 | 
						|
		else if (tlen < end) {
 | 
						|
			checkfilerange(fd, namestr, prev, start, tlen);
 | 
						|
			checkfilezeros(fd, namestr, tlen, end);
 | 
						|
		}
 | 
						|
		else {
 | 
						|
			checkfilerange(fd, namestr, prev, start, end);
 | 
						|
		}
 | 
						|
	}
 | 
						|
	else if (change->type == FC_WRITE) {
 | 
						|
		off_t wstart, wend;
 | 
						|
		struct fschange *prev;
 | 
						|
		unsigned code, seq;
 | 
						|
		off_t oldfilesize, zerostart;
 | 
						|
 | 
						|
		/*
 | 
						|
		 * The most recent change was a write.
 | 
						|
		 */
 | 
						|
 | 
						|
		wstart = change->fc_write.pos;
 | 
						|
		wend = change->fc_write.pos + change->fc_write.len;
 | 
						|
		prev = change->fc_write.prev_thisfile;
 | 
						|
		code = change->fc_write.code;
 | 
						|
		seq = change->fc_write.seq;
 | 
						|
		oldfilesize = change->fc_write.oldfilesize;
 | 
						|
 | 
						|
		/*
 | 
						|
		 * ZEROSTART (where we begin to allow the file to
 | 
						|
		 * contain zeros) should be the point at the write
 | 
						|
		 * where we began to extend the file, if any.
 | 
						|
		 */
 | 
						|
		if (oldfilesize < wstart) {
 | 
						|
			zerostart = wstart;
 | 
						|
		}
 | 
						|
		else if (oldfilesize < wend) {
 | 
						|
			zerostart = oldfilesize;
 | 
						|
		}
 | 
						|
		else {
 | 
						|
			zerostart = wend;
 | 
						|
		}
 | 
						|
 | 
						|
		/*
 | 
						|
		 * Six cases for the range overlap:
 | 
						|
		 *     (1)    (2)    (3)    (4)   (5)    (6)
 | 
						|
		 *    **     ***    ******   *     ***      **
 | 
						|
		 *       ***   ***    ***   ***  ***    ***
 | 
						|
		 *
 | 
						|
		 * We call checkfilerange recursively using the
 | 
						|
		 * previous change for this file for the
 | 
						|
		 * non-overlapping parts, because this write didn't
 | 
						|
		 * touch those so they must reflect the previous file
 | 
						|
		 * state; and checkfiledata for the overlapping parts
 | 
						|
		 * that this write did touch.
 | 
						|
		 */
 | 
						|
 | 
						|
		if (end <= wstart || start >= wend) {
 | 
						|
			/* cases 1 and 6 */
 | 
						|
			checkfilerange(fd, namestr, prev, start, end);
 | 
						|
		}
 | 
						|
		else {
 | 
						|
			/* cases 2-5 */
 | 
						|
			if (start < wstart) {
 | 
						|
				/* case 2 or 3 */
 | 
						|
				checkfilerange(fd, namestr, prev,
 | 
						|
					       start, wstart);
 | 
						|
			}
 | 
						|
			checkfiledata(fd, namestr, code, seq, zerostart,
 | 
						|
				      wstart, start, end, wend);
 | 
						|
			if (end > wend) {
 | 
						|
				/* cases 3 or 5 */
 | 
						|
				checkfilerange(fd, namestr, prev, wend, end);
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	else if (change->type == FC_RENAMEFILE) {
 | 
						|
		struct fschange *prev;
 | 
						|
 | 
						|
		/*
 | 
						|
		 * The most recent change was a rename. As rename
 | 
						|
		 * doesn't affect contents, recurse on the previous
 | 
						|
		 * change affecting the file.
 | 
						|
		 */
 | 
						|
 | 
						|
		prev = change->fc_renamefile.prev_movedfile;
 | 
						|
		checkfilerange(fd, namestr, prev, start, end);
 | 
						|
	}
 | 
						|
	else {
 | 
						|
		assert(change->type == FC_CREAT);
 | 
						|
 | 
						|
		/*
 | 
						|
		 * The most recent change was the file being created.
 | 
						|
		 * Like with truncate, check that the range is zero
 | 
						|
		 * even though it's past EOF, as a later write might
 | 
						|
		 * have converted past-EOF space into a hole in a
 | 
						|
		 * sparse file.
 | 
						|
		 */
 | 
						|
		checkfilezeros(fd, namestr, start, end);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Check if a change CHANGE to a file is present in the observed file
 | 
						|
 * FD, whose name is NAMESTR and size is FILESIZE.
 | 
						|
 *
 | 
						|
 * For writes it checks the data, so should be accurate; for truncate
 | 
						|
 * all it can check is the size, so it can readily be fooled by
 | 
						|
 * sequences of truncates or multiple truncates to the same length.
 | 
						|
 */
 | 
						|
static
 | 
						|
int
 | 
						|
change_is_present(int fd, const char *namestr, off_t filesize,
 | 
						|
		  struct fschange *change)
 | 
						|
{
 | 
						|
	off_t pos, len, oldfilesize, zerostart;
 | 
						|
	unsigned code, seq;
 | 
						|
 | 
						|
	switch (change->type) {
 | 
						|
	    case FC_TRUNCATE:
 | 
						|
		return filesize == change->fc_truncate.len;
 | 
						|
	    case FC_WRITE:
 | 
						|
		pos = change->fc_write.pos;
 | 
						|
		len = change->fc_write.len;
 | 
						|
		code = change->fc_write.code;
 | 
						|
		seq = change->fc_write.seq;
 | 
						|
		oldfilesize = change->fc_write.oldfilesize;
 | 
						|
		if (oldfilesize < pos) {
 | 
						|
			zerostart = 0;
 | 
						|
		}
 | 
						|
		else if (oldfilesize < pos + len) {
 | 
						|
			zerostart = oldfilesize - pos;
 | 
						|
		}
 | 
						|
		else {
 | 
						|
			zerostart = len;
 | 
						|
		}
 | 
						|
		readfiledata(fd, namestr, pos, pos, pos+len, pos+len);
 | 
						|
		return data_matches(namestr, pos,
 | 
						|
				    code, seq, zerostart, len, 0, len);
 | 
						|
	    case FC_CREAT:
 | 
						|
		return 1;
 | 
						|
	    default:
 | 
						|
		break;
 | 
						|
	}
 | 
						|
	assert(0);
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Check the contents of the file called NAMESTR, which is the model
 | 
						|
 * file FILE, as of change CHANGE.
 | 
						|
 */
 | 
						|
static
 | 
						|
void
 | 
						|
checkonefilecontents(const char *namestr, struct fsobject *file,
 | 
						|
		     struct fschange *change)
 | 
						|
{
 | 
						|
	unsigned okversion;
 | 
						|
	int fd;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Open the file.
 | 
						|
	 */
 | 
						|
 | 
						|
	assert(!file->isdir);
 | 
						|
 | 
						|
	fd = open(namestr, O_RDONLY);
 | 
						|
	if (fd < 0) {
 | 
						|
		err(1, "%s: open", namestr);
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Find the oldest version that has the same directory
 | 
						|
	 * structure as CHANGE, and thus reflects the earliest
 | 
						|
	 * contents we can legitimately find in the file.
 | 
						|
	 *
 | 
						|
	 * XXX: the matching we do to pick the change to examine takes
 | 
						|
	 * file sizes into account, but findokversion specifically
 | 
						|
	 * doesn't stop going backwards if just the file size changes.
 | 
						|
	 * This seems wrong: if we match on file size, ok versions to
 | 
						|
	 * see are only those that have the same file size.
 | 
						|
	 */
 | 
						|
	okversion = findokversion(change);
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Find the first change (going backwards) that affects this
 | 
						|
	 * file. There should always be at least the create. This
 | 
						|
	 * change might be before or after OKVERSION.
 | 
						|
	 */
 | 
						|
	change = backup_for_file(change, file->obj_file.identity);
 | 
						|
	if (change == NULL) {
 | 
						|
		die("File %s was never even created?", namestr);
 | 
						|
	}
 | 
						|
 | 
						|
	if (file->obj_file.len == 0) {
 | 
						|
		/*
 | 
						|
		 * The model expects the length to be 0.
 | 
						|
		 *
 | 
						|
		 * XXX: I think the code here was written thinking
 | 
						|
		 * FILE is from the inspection results; but it's not,
 | 
						|
		 * it's the model from the workload replay. So these
 | 
						|
		 * checks appear to be wrong.
 | 
						|
		 */
 | 
						|
		if (change->type == FC_CREAT) {
 | 
						|
			/* file was created empty and never written to */
 | 
						|
			return;
 | 
						|
		}
 | 
						|
		if (change->type == FC_TRUNCATE) {
 | 
						|
			/* if the length is wrong we shouldn't get this far */
 | 
						|
			assert(change->fc_truncate.len == 0);
 | 
						|
 | 
						|
			/* so, nothing to check */
 | 
						|
			close(fd);
 | 
						|
			return;
 | 
						|
		}
 | 
						|
		assert(change->type == FC_WRITE);
 | 
						|
		tprintf("ERROR: File %s is zero length but was expected to "
 | 
						|
		       "contain at least %lld bytes at offset %lld!\n",
 | 
						|
		       namestr, change->fc_write.pos, change->fc_write.len);
 | 
						|
		close(fd);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	/* XXX: this check is wrong too. */
 | 
						|
	if (change->type == FC_CREAT) {
 | 
						|
		tprintf("ERROR: File %s was never written to but has "
 | 
						|
		       "length %lld\n",
 | 
						|
		       namestr, file->obj_file.len);
 | 
						|
		close(fd);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * If CHANGE isn't reflected in the file, back up. If this
 | 
						|
	 * causes us to back up past OKVERSION, complain that the
 | 
						|
	 * change should be present.
 | 
						|
	 *
 | 
						|
	 * XXX: the call to change_is_present should be using the
 | 
						|
	 * observed file size, not the modeled file size.
 | 
						|
	 *
 | 
						|
	 * XXX: More seriously, however, this logic is not really
 | 
						|
	 * right. If the workload contains multiple writes that don't
 | 
						|
	 * affect the file length, interspersed with truncates that
 | 
						|
	 * are all to the same length, we'll stop looking back at the
 | 
						|
	 * first truncate (because the length matches) regardless of
 | 
						|
	 * which writes are present. None of the workloads currently
 | 
						|
	 * does anything like this, but it's still wrong.
 | 
						|
	 *
 | 
						|
	 * I think the right thing to do (especially since the version
 | 
						|
	 * matching checks file lengths) is to check only writes for
 | 
						|
	 * presence and ignore truncates; and probably, rather than
 | 
						|
	 * back up based on what writes appear to be present and then
 | 
						|
	 * call checkfilerange, which will then complain loudly if
 | 
						|
	 * parts of an older write didn't make it out, divide the file
 | 
						|
	 * into ranges based on blocks and writes and figure out which
 | 
						|
	 * version the data found in each such range corresponds to,
 | 
						|
	 * then object only to the ones that are crazy and warn about
 | 
						|
	 * ones that reflect data buffers not making it out. But this
 | 
						|
	 * is a fairly big rewrite.
 | 
						|
	 */
 | 
						|
	while (!change_is_present(fd, namestr, file->obj_file.len, change)) {
 | 
						|
		if (change->version < okversion) {
 | 
						|
			tprintf("File %s: change for version %u is missing\n",
 | 
						|
			       namestr, change->version);
 | 
						|
		}
 | 
						|
		change = backup_for_file(change->prev,file->obj_file.identity);
 | 
						|
		if (change == NULL) {
 | 
						|
			tprintf("File %s: no matching version found\n",
 | 
						|
			       namestr);
 | 
						|
			close(fd);
 | 
						|
			return;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Now we've found a version that we think the file contents
 | 
						|
	 * should correspond to; check that it's what we actually
 | 
						|
	 * have.
 | 
						|
	 *
 | 
						|
	 * XXX: should this use the model length or the observed
 | 
						|
	 * length?
 | 
						|
	 */
 | 
						|
 | 
						|
	checkfilerange(fd, namestr, change, 0, file->obj_file.len);
 | 
						|
	close(fd);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Check the contents of all files under DIR with respect to the
 | 
						|
 * change CHANGE. Recurses on subdirectories.
 | 
						|
 */
 | 
						|
static
 | 
						|
void
 | 
						|
checkallfilecontents(struct fsobject *dir, struct fschange *change)
 | 
						|
{
 | 
						|
	struct fsdirent *de;
 | 
						|
	const char *namestr;
 | 
						|
 | 
						|
	assert(dir->isdir);
 | 
						|
	for (de = dir->obj_dir.entries; de != NULL; de = de->next) {
 | 
						|
		namestr = name_get(de->name);
 | 
						|
		if (de->obj->isdir) {
 | 
						|
			tprintf(" >>> Entering %s\n", namestr);
 | 
						|
			if (chdir(namestr)) {
 | 
						|
				err(1, "%s: chdir", namestr);
 | 
						|
			}
 | 
						|
			checkallfilecontents(de->obj, change);
 | 
						|
			tprintf(" <<< Leaving %s\n", namestr);
 | 
						|
			if (chdir("..")) {
 | 
						|
				err(1, "..: chdir");
 | 
						|
			}
 | 
						|
		}
 | 
						|
		else {
 | 
						|
			tprintf("%s...\n", namestr);
 | 
						|
			checkonefilecontents(namestr, de->obj, change);
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
////////////////////////////////////////////////////////////
 | 
						|
// model validation
 | 
						|
 | 
						|
/*
 | 
						|
 * Compare the on-disk state we see to the model we've built.
 | 
						|
 */
 | 
						|
void
 | 
						|
checkfs(void)
 | 
						|
{
 | 
						|
	struct fschange *change, *best;
 | 
						|
	unsigned score, bestscore;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * We just built the model; talk about it.
 | 
						|
	 */
 | 
						|
	tprintf("Established %u versions across %u directories and %u files\n",
 | 
						|
	       changes->version + 1, nextdirnum, nextfilenum);
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Inspect the volume state we've got. Initializes the global
 | 
						|
	 * FOUND holding the found volume state.
 | 
						|
	 */
 | 
						|
	inspectfs();
 | 
						|
	tprintf("Found %u subdirs and %u files on the volume\n",
 | 
						|
	       found_subdirs, found_files);
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Rewind the model state to the beginning.
 | 
						|
	 */
 | 
						|
	rewindstate();
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Loop through all the changes; apply each one to the model
 | 
						|
	 * state, and score the model state against the found state.
 | 
						|
	 * Keep track of the best matching version.
 | 
						|
	 *
 | 
						|
	 * XXX: there might be several versions with the same score,
 | 
						|
	 * in which case we'll blindly take the most recent one. If
 | 
						|
	 * there are several versions that match exactly (which can
 | 
						|
	 * easily happen for workloads that don't explicitly avoid it)
 | 
						|
	 * we'll also blindly take the most recent one, and then the
 | 
						|
	 * matching of file contents can blow up. We should collect
 | 
						|
	 * all the versions with the same score, and score each one
 | 
						|
	 * on file contents too. Or something like that.
 | 
						|
	 */
 | 
						|
 | 
						|
	change = firstchange;
 | 
						|
	assert(change->type == FC_NEWFS);
 | 
						|
	best = NULL;
 | 
						|
	bestscore = 0;
 | 
						|
 | 
						|
	while (change != NULL) {
 | 
						|
		apply_change(state, change);
 | 
						|
		score = compare_objects(state, found);
 | 
						|
		if (best == NULL || score <= bestscore) {
 | 
						|
			best = change;
 | 
						|
			bestscore = score;
 | 
						|
		}
 | 
						|
		//tprintf("version %u score %u\n", change->version, score);
 | 
						|
		change = change->next;
 | 
						|
	}
 | 
						|
	assert(best != NULL);
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Set the model state to the best matching state we found.
 | 
						|
	 */
 | 
						|
 | 
						|
	rewindstate();
 | 
						|
	advancestateto(best);
 | 
						|
 | 
						|
	if (bestscore > 0) {
 | 
						|
		/*
 | 
						|
		 * We didn't get an exact match, so print how the
 | 
						|
		 * differences. XXX: this results in not checking file
 | 
						|
		 * data...
 | 
						|
		 */
 | 
						|
		tprintf("FAILURE: Directory tree does not match on any "
 | 
						|
		       "version.\n");
 | 
						|
		tprintf("Best version is %u; describing differences:\n",
 | 
						|
		       best->version);
 | 
						|
		printdiffs(1, state, found);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Ok, we did get an exact match. Print it.
 | 
						|
	 */
 | 
						|
 | 
						|
	tprintf("Directory tree matched in version %u.\n", best->version);
 | 
						|
	if (best->partial) {
 | 
						|
		tprintf("WARNING: this is a version from a partially committed "
 | 
						|
		       "operation.\n");
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * XXX we should check hard links here; that is, that all the
 | 
						|
	 * files that are supposed to be hard links of each other are,
 | 
						|
	 * and that no other files are randomly hardlinked.
 | 
						|
	 *
 | 
						|
	 * However, if things are wrong it should result in bad
 | 
						|
	 * contents. It would be very unlikely for even a very broken
 | 
						|
	 * filesystem to, on its own, copy files that are supposed to
 | 
						|
	 * be hardlinked.
 | 
						|
	 */
 | 
						|
 | 
						|
	/* now check the file contents */
 | 
						|
 | 
						|
	tprintf("Checking file contents...\n");
 | 
						|
	checkallfilecontents(state, best);
 | 
						|
	tprintf("Done.\n");
 | 
						|
}
 |