[darwinbuild-changes] [294] trunk

source_changes at macosforge.org source_changes at macosforge.org
Wed Oct 4 02:02:25 PDT 2006


Revision: 294
          http://trac.macosforge.org/projects/darwinbuild/changeset/294
Author:   kevin
Date:     2006-10-04 02:02:24 -0700 (Wed, 04 Oct 2006)

Log Message:
-----------
- initial revision of Darwin Update

Added Paths:
-----------
    trunk/darwinup/
    trunk/darwinup/Archive.cpp
    trunk/darwinup/Archive.h
    trunk/darwinup/Depot.cpp
    trunk/darwinup/Depot.h
    trunk/darwinup/Digest.cpp
    trunk/darwinup/Digest.h
    trunk/darwinup/File.cpp
    trunk/darwinup/File.h
    trunk/darwinup/Makefile
    trunk/darwinup/NOTES
    trunk/darwinup/SerialSet.cpp
    trunk/darwinup/SerialSet.h
    trunk/darwinup/Utils.cpp
    trunk/darwinup/Utils.h
    trunk/darwinup/libredo.o
    trunk/darwinup/main.cpp
    trunk/darwinup/redo_prebinding.h

Added: trunk/darwinup/Archive.cpp
===================================================================
--- trunk/darwinup/Archive.cpp	                        (rev 0)
+++ trunk/darwinup/Archive.cpp	2006-10-04 09:02:24 UTC (rev 294)
@@ -0,0 +1,226 @@
+/*
+ * Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ * 
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ * 
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ * 
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+#include "Archive.h"
+#include "Depot.h"
+#include "File.h"
+#include "Utils.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <libgen.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+extern char** environ;
+
+Archive::Archive(const char* path) {
+	m_serial = 0;
+	uuid_generate_random(m_uuid);
+	m_path = strdup(path);
+	m_name = strdup(basename(m_path));
+	m_info = 0;
+	m_date_installed = time(NULL);
+}
+
+Archive::Archive(uint64_t serial, uuid_t uuid, const char* name, const char* path, uint64_t info, time_t date_installed) {
+	m_serial = serial;
+	uuid_copy(m_uuid, uuid);
+	m_name = name ? strdup(name) : NULL;
+	m_path = path ? strdup(path) : NULL;
+	m_info = info;
+	m_date_installed = date_installed;
+}
+
+
+Archive::~Archive() {
+	if (m_path) free(m_path);
+	if (m_name) free(m_name);
+}
+
+uint64_t	Archive::serial()		{ return m_serial; }
+uint8_t*	Archive::uuid()			{ return m_uuid; }
+const char*	Archive::name()			{ return m_name; }
+const char*	Archive::path()			{ return m_path; }
+uint64_t	Archive::info()			{ return m_info; }
+time_t		Archive::date_installed()	{ return m_date_installed; }
+
+char* Archive::directory_name(const char* prefix) {
+	char* path = NULL;
+	char uuidstr[37];
+	uuid_unparse_upper(m_uuid, uuidstr);
+	asprintf(&path, "%s/%s", prefix, uuidstr);
+	if (path == NULL) {
+		fprintf(stderr, "%s:%d: out of memory\n", __FILE__, __LINE__);
+	}
+	return path;
+}
+
+char* Archive::create_directory(const char* prefix) {
+	int res = 0;
+	char* path = this->directory_name(prefix);
+	if (path && res == 0) res = mkdir(path, 0777);
+	if (res != 0) {
+		fprintf(stderr, "%s:%d: could not create directory: %s: %s (%d)\n", __FILE__, __LINE__, path, strerror(errno), errno);
+		free(path);
+		path = NULL;
+	}
+	if (res == 0) res = chown(path, 0, 0);
+	return path;
+}
+
+int Archive::compact_directory(const char* prefix) {
+	int res = 0;
+	char* tarpath = NULL;
+	char uuidstr[37];
+	uuid_unparse_upper(m_uuid, uuidstr);
+	asprintf(&tarpath, "%s/%s.tar.bz2", prefix, uuidstr);
+	if (tarpath) {
+		const char* args[] = {
+			"/usr/bin/tar",
+			"cjf", tarpath,
+			"-C", prefix,
+			uuidstr,
+			NULL
+		};
+		res = exec_with_args(args);
+		free(tarpath);
+	} else {
+		fprintf(stderr, "%s:%d: out of memory\n", __FILE__, __LINE__);
+		res = -1;
+	}
+	return res;
+}
+
+int Archive::expand_directory(const char* prefix) {
+	int res = 0;
+	char* tarpath = NULL;
+	char uuidstr[37];
+	uuid_unparse_upper(m_uuid, uuidstr);
+	asprintf(&tarpath, "%s/%s.tar.bz2", prefix, uuidstr);
+	if (tarpath) {
+		const char* args[] = {
+			"/usr/bin/tar",
+			"xjf", tarpath,
+			"-C", prefix,
+			"-p",	// --preserve-permissions
+			NULL
+		};
+		res = exec_with_args(args);
+		free(tarpath);
+	} else {
+		fprintf(stderr, "%s:%d: out of memory\n", __FILE__, __LINE__);
+		res = -1;
+	}
+	return res;
+}
+
+
+int Archive::extract(const char* destdir) {
+	// not implemented
+	return -1;
+}
+
+
+
+RollbackArchive::RollbackArchive() : Archive("<Rollback>") {
+	m_info = ARCHIVE_INFO_ROLLBACK;
+}
+
+
+
+DittoArchive::DittoArchive(const char* path) : Archive(path) {}
+
+int DittoArchive::extract(const char* destdir) {
+	const char* args[] = {
+		"/usr/bin/ditto",
+		m_path, destdir,
+		NULL
+	};
+	return exec_with_args(args);
+}
+
+
+TarArchive::TarArchive(const char* path) : Archive(path) {}
+
+int TarArchive::extract(const char* destdir) {
+	const char* args[] = {
+		"/usr/bin/tar",
+		"xf", m_path,
+		"-C", destdir,
+		NULL
+	};
+	return exec_with_args(args);
+}
+
+
+TarGZArchive::TarGZArchive(const char* path) : Archive(path) {}
+
+int TarGZArchive::extract(const char* destdir) {
+	const char* args[] = {
+		"/usr/bin/tar",
+		"xzf", m_path,
+		"-C", destdir,
+		NULL
+	};
+	return exec_with_args(args);
+}
+
+
+TarBZ2Archive::TarBZ2Archive(const char* path) : Archive(path) {}
+
+int TarBZ2Archive::extract(const char* destdir) {
+	const char* args[] = {
+		"/usr/bin/tar",
+		"xjf", m_path,
+		"-C", destdir,
+		NULL
+	};
+	return exec_with_args(args);
+}
+
+
+Archive* ArchiveFactory(const char* path) {
+	Archive* archive = NULL;
+
+	// make sure the archive exists
+	struct stat sb;
+	int res = stat(path, &sb);
+	if (res == -1 && errno == ENOENT) {
+		return NULL;
+	}
+
+	if (is_directory(path)) {
+		archive = new DittoArchive(path);
+	} else if (has_suffix(path, ".tar")) {
+		archive = new TarArchive(path);
+	} else if (has_suffix(path, ".tar.gz") || has_suffix(path, ".tgz")) {
+		archive = new TarGZArchive(path);
+	} else if (has_suffix(path, ".tar.bz2") || has_suffix(path, ".tbz2")) {
+		archive = new TarBZ2Archive(path);
+	} else {
+		fprintf(stderr, "Error: unknown archive type: %s\n", path);
+	}
+	return archive;
+}
\ No newline at end of file


Property changes on: trunk/darwinup/Archive.cpp
___________________________________________________________________
Name: svn:eol-style
   + native

Added: trunk/darwinup/Archive.h
===================================================================
--- trunk/darwinup/Archive.h	                        (rev 0)
+++ trunk/darwinup/Archive.h	2006-10-04 09:02:24 UTC (rev 294)
@@ -0,0 +1,198 @@
+/*
+ * Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ * 
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ * 
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ * 
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <time.h>
+#include <uuid/uuid.h>
+
+
+//
+// ARCHIVE_INFO flags stored in the database
+//
+const uint64_t ARCHIVE_INFO_ROLLBACK	= 0x0001;
+
+struct Archive;
+struct Depot;
+
+////
+//  Archive
+//
+//  Archive is the root class of all archive formats
+//  supported by darwinup.
+//
+//  Conceptually it's an abstract class, although that
+//  hasn't been formalized.
+//
+//  ArchiveFactory exists to return the correct
+//  concrete subclass for a given archive to be
+//  installed.  Currently this is determined
+//  by the file's suffix.
+////
+
+Archive* ArchiveFactory(const char* path);
+
+struct Archive {
+	Archive(const char* path);
+	virtual ~Archive();
+
+	////
+	//  Public Accessor functions
+	////
+
+	// Unique serial number for the archive (used by database).
+	virtual	uint64_t serial();
+	
+	// Universally unique identifier for the archive.
+	virtual	uint8_t* uuid();
+	
+	// The name of the archive as it was installed.
+	// Determined by basename(3).
+	// Do not modify or free(3).
+	virtual const char* name();
+	
+	// The path to the archive as it was installed.
+	// Do not modify or free(3).
+	virtual const char* path();
+	
+	// ARCHIVE_INFO flags.
+	virtual uint64_t info();
+	
+	// The epoch seconds when the archive was installed.
+	virtual time_t date_installed();
+
+
+	////
+	//  Member functions
+	////
+	
+	// Extracts the archive into the specified destination.
+	// Not implemented for Archive, expected to be implemented
+	// by concrete subclasses.
+	virtual int extract(const char* destdir);
+
+	// Returns the backing-store directory name for the archive.
+	// This is prefix/uuid.
+	// The result should be released with free(3).
+	char* directory_name(const char* prefix);
+	
+	// Creates the backing-store directory for the archive.
+	// Same directory name as returned by directory_name().
+	char* create_directory(const char* prefix);
+	
+	// Compacts the backing-store directory into a single file.
+	int compact_directory(const char* prefix);
+	
+	// Expands the backing-store directory from its single file.
+	int expand_directory(const char* prefix);
+
+	protected:
+
+	// Constructor for subclasses and Depot to use when unserializing an archive from the database.
+	Archive(uint64_t serial, uuid_t uuid, const char* name, const char* path, uint64_t info, time_t date_installed);
+
+	uint64_t	m_serial;
+	uuid_t		m_uuid;
+	char*		m_name;
+	char*		m_path;
+	uint64_t	m_info;
+	time_t		m_date_installed;
+	
+	friend struct Depot;
+};
+
+
+////
+//  RollbackArchive
+//
+//  Not a file format.  RollbackArchive is an internal representation
+//  of archives that are created to store the user-data that darwinup
+//  archives as part of installation.
+////
+struct RollbackArchive : public Archive {
+	RollbackArchive();
+};
+
+
+////
+//  DittoArchive
+//
+//  Not a file format, but this allows a directory tree to be installed
+//  using the ditto(1) command line tool.  This is useful for installing
+//  Darwin roots built with DarwinBuild.
+////
+struct DittoArchive : public Archive {
+	DittoArchive(const char* path);
+	virtual int extract(const char* destdir);
+};
+
+
+////
+//  PkgArchive
+//
+//  Corresponds to the Mac OS X installer's .pkg bundle format.
+//  Installs the archive using the pax(1) command line tool.
+//  NOTE: this does not make any attempt to perform any of the
+//  volume checks or run any preflight or postflight actions.
+////
+struct PkgArchive : public Archive {
+	PkgArchive(const char* path);
+	virtual int extract(const char* destdir);
+};
+
+////
+//  TarArchive
+//
+//  Corresponds to the tar(1) file format.  This handles uncompressed tar
+//  archives by using the tar(1) command line tool.
+////
+struct TarArchive : public Archive {
+	TarArchive(const char* path);
+	virtual int extract(const char* destdir);
+};
+
+
+////
+//  TarGZArchive
+//
+//  Corresponds to the tar(1) file format, compressed with gzip(1).
+//  This installs archives using the tar(1) command line tool with
+//  the -z option.
+////
+struct TarGZArchive : public Archive {
+	TarGZArchive(const char* path);
+	virtual int extract(const char* destdir);
+};
+
+
+////
+//  TarBZ2Archive
+//
+//  Corresponds to the tar(1) file format, compressed with bzip2(1).
+//  This installs archives using the tar(1) command line tool with
+//  the -j option.
+////
+struct TarBZ2Archive : public Archive {
+	TarBZ2Archive(const char* path);
+	virtual int extract(const char* destdir);
+};


Property changes on: trunk/darwinup/Archive.h
___________________________________________________________________
Name: svn:eol-style
   + native

Added: trunk/darwinup/Depot.cpp
===================================================================
--- trunk/darwinup/Depot.cpp	                        (rev 0)
+++ trunk/darwinup/Depot.cpp	2006-10-04 09:02:24 UTC (rev 294)
@@ -0,0 +1,1019 @@
+/*
+ * Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ * 
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ * 
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ * 
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+#include "Archive.h"
+#include "Depot.h"
+#include "File.h"
+#include "SerialSet.h"
+#include "Utils.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include <sqlite3.h>
+
+Depot::Depot() {
+	m_depot_path = NULL;
+	m_database_path = NULL;
+	m_archives_path = NULL;
+	m_db = NULL;
+}
+
+Depot::Depot(const char* prefix) {
+	asprintf(&m_depot_path,    "%s/.DarwinDepot",  prefix);
+	asprintf(&m_database_path, "%s/Database-V100", m_depot_path);
+	asprintf(&m_archives_path, "%s/Archives",      m_depot_path);
+
+	mkdir(m_depot_path,    m_depot_mode);
+	mkdir(m_archives_path, m_depot_mode);
+
+	int exists = is_regular_file(m_database_path);
+
+	int res = sqlite3_open(m_database_path, &m_db);
+	if (res != 0) {
+		sqlite3_close(m_db);
+		m_db = NULL;
+	}
+
+	if (m_db && !exists) {
+		this->SQL("CREATE TABLE archives (serial INTEGER PRIMARY KEY AUTOINCREMENT, uuid BLOB UNIQUE, name TEXT, date_added INTEGER, active INTEGER, info INTEGER)");
+		this->SQL("CREATE TABLE files (serial INTEGER PRIMARY KEY AUTOINCREMENT, archive INTEGER, info INTEGER, mode INTEGER, uid INTEGER, gid INTEGER, size INTEGER, digest BLOB, path TEXT)");
+
+		this->SQL("CREATE INDEX archives_uuid ON archives (uuid)");
+		this->SQL("CREATE INDEX files_path ON files (path)");
+	}
+}
+
+Depot::~Depot() {
+	if (m_db)		sqlite3_close(m_db);
+	if (m_depot_path)	free(m_depot_path);
+	if (m_database_path)	free(m_database_path);
+	if (m_archives_path)	free(m_archives_path);
+}
+
+const char*	Depot::archives_path()		{ return m_archives_path; }
+
+// Unserialize an archive from the database.
+// Find the archive by UUID.
+// XXX: should be memoized
+Archive* Depot::archive(uuid_t uuid) {
+	int res = 0;
+	Archive* archive = NULL;
+	static sqlite3_stmt* stmt = NULL;
+	if (stmt == NULL && m_db) {
+		const char* query = "SELECT serial, name, info, date_added FROM archives WHERE uuid=?";
+		res = sqlite3_prepare(m_db, query, -1, &stmt, NULL);
+		if (res != 0) fprintf(stderr, "%s:%d: sqlite3_prepare: %s: %s (%d)\n", __FILE__, __LINE__, query, sqlite3_errmsg(m_db), res);
+	}
+	if (stmt && res == 0) {
+		res = sqlite3_bind_blob(stmt, 1, uuid, sizeof(uuid_t), SQLITE_STATIC);
+		if (res == 0) res = sqlite3_step(stmt);
+		if (res == SQLITE_ROW) {
+			uint64_t serial = sqlite3_column_int64(stmt, 0);
+			const unsigned char* name = sqlite3_column_text(stmt, 1);
+			uint64_t info = sqlite3_column_int64(stmt, 2);
+			time_t date_added = sqlite3_column_int(stmt, 3);
+			archive = new Archive(serial, uuid, (const char*)name, NULL, info, date_added);
+		}
+		sqlite3_reset(stmt);
+	}
+	return archive;
+}
+
+// Unserialize an archive from the database.
+// Find the archive by serial.
+// XXX: should be memoized
+Archive* Depot::archive(uint64_t serial) {
+	int res = 0;
+	Archive* archive = NULL;
+	static sqlite3_stmt* stmt = NULL;
+	if (stmt == NULL && m_db) {
+		const char* query = "SELECT uuid, name, info, date_added FROM archives WHERE serial=?";
+		res = sqlite3_prepare(m_db, query, -1, &stmt, NULL);
+		if (res != 0) fprintf(stderr, "%s:%d: sqlite3_prepare: %s: %s (%d)\n", __FILE__, __LINE__, query, sqlite3_errmsg(m_db), res);
+	}
+	if (stmt && res == 0) {
+		res = sqlite3_bind_int64(stmt, 1, serial);
+		if (res == 0) res = sqlite3_step(stmt);
+		if (res == SQLITE_ROW) {
+			uuid_t uuid;
+			const void* blob = sqlite3_column_blob(stmt, 0);
+			int blobsize = sqlite3_column_bytes(stmt, 0);
+			if (blobsize > 0) {
+				assert(blobsize == sizeof(uuid_t));
+				memcpy(uuid, blob, sizeof(uuid_t));
+			} else {
+				uuid_clear(uuid);
+			}
+			const unsigned char* name = sqlite3_column_text(stmt, 1);
+			uint64_t info = sqlite3_column_int64(stmt, 2);
+			time_t date_added = sqlite3_column_int(stmt, 3);
+			archive = new Archive(serial, uuid, (const char*)name, NULL, info, date_added);
+		}
+		sqlite3_reset(stmt);
+	}
+	return archive;
+}
+
+Archive* Depot::archive(const char* uuid) {
+	uuid_t uu;
+	if (uuid_parse(uuid, uu) == 0) {
+		return Depot::archive(uu);
+	} else {
+		return NULL;
+	}
+}
+
+int Depot::iterate_archives(ArchiveIteratorFunc func, void* context) {
+	int res = 0;
+	static sqlite3_stmt* stmt = NULL;
+	if (stmt == NULL && m_db) {
+		const char* query = "SELECT serial, uuid, name, info, date_added FROM archives ORDER BY serial DESC";
+		res = sqlite3_prepare(m_db, query, -1, &stmt, NULL);
+		if (res != 0) fprintf(stderr, "%s:%d: sqlite3_prepare: %s: %s (%d)\n", __FILE__, __LINE__, query, sqlite3_errmsg(m_db), res);
+	}
+	if (stmt && res == 0) {
+		while (res == 0) {
+			res = sqlite3_step(stmt);
+			if (res == SQLITE_ROW) {
+				res = 0;
+				uuid_t uuid;
+				uint64_t serial = sqlite3_column_int64(stmt, 0);
+				const void* blob = sqlite3_column_blob(stmt, 1);
+				int blobsize = sqlite3_column_bytes(stmt, 1);
+				const unsigned char* name = sqlite3_column_text(stmt, 2);
+				uint64_t info = sqlite3_column_int64(stmt, 3);
+				time_t date_added = sqlite3_column_int(stmt, 4);
+				if (blobsize > 0) {
+					assert(blobsize == sizeof(uuid_t));
+					memcpy(uuid, blob, sizeof(uuid_t));
+				} else {
+					uuid_clear(uuid);
+				}
+				Archive* archive = new Archive(serial, uuid, (const char*)name, NULL, info, date_added);
+				if (archive) {
+					res = func(archive, context);
+					delete archive;
+				} else {
+					fprintf(stderr, "%s:%d: new Archive returned NULL\n", __FILE__, __LINE__);
+					res = -1;
+					break;
+				}
+			} else if (res == SQLITE_DONE) {
+				res = 0;
+				break;
+			}
+		}
+		sqlite3_reset(stmt);
+	}
+	return res;
+}
+
+int Depot::iterate_files(Archive* archive, FileIteratorFunc func, void* context) {
+	int res = 0;
+	static sqlite3_stmt* stmt = NULL;
+	if (stmt == NULL && m_db) {
+		const char* query = "SELECT serial, info, path, mode, uid, gid, size, digest FROM files WHERE archive=? ORDER BY path";
+		res = sqlite3_prepare(m_db, query, -1, &stmt, NULL);
+		if (res != 0) fprintf(stderr, "%s:%d: sqlite3_prepare: %s: %s (%d)\n", __FILE__, __LINE__, query, sqlite3_errmsg(m_db), res);
+	}
+	if (stmt && res == 0) {
+		res = sqlite3_bind_int64(stmt, 1, archive->serial());
+		while (res == 0) {
+			res = sqlite3_step(stmt);
+			if (res == SQLITE_ROW) {
+				res = 0;
+				int i = 0;
+				uint64_t serial = sqlite3_column_int64(stmt, i++);
+				uint32_t info = sqlite3_column_int(stmt, i++);
+				const unsigned char* path = sqlite3_column_text(stmt, i++);
+				mode_t mode = sqlite3_column_int(stmt, i++);
+				uid_t uid = sqlite3_column_int(stmt, i++);
+				gid_t gid = sqlite3_column_int(stmt, i++);
+				off_t size = sqlite3_column_int64(stmt, i++);
+				const void* blob = sqlite3_column_blob(stmt, i);
+				int blobsize = sqlite3_column_bytes(stmt, i++);
+
+				Digest* digest = NULL;
+				if (blobsize > 0) {
+					digest = new Digest();
+					digest->m_size = blobsize;
+					memcpy(digest->m_data, blob, (blobsize < sizeof(digest->m_data)) ? blobsize : sizeof(digest->m_data));
+				}
+
+				File* file = FileFactory(serial, archive, info, (const char*)path, mode, uid, gid, size, digest);
+				if (file) {
+					res = func(file, context);
+					delete file;
+				} else {
+					fprintf(stderr, "%s:%d: FileFactory returned NULL\n", __FILE__, __LINE__);
+					res = -1;
+					break;
+				}
+			} else if (res == SQLITE_DONE) {
+				res = 0;
+				break;
+			}
+		}
+		sqlite3_reset(stmt);
+	}
+	return res;
+}
+
+
+int Depot::analyze_stage(const char* path, Archive* archive, Archive* rollback, int* rollback_files) {
+	int res = 0;
+	assert(archive != NULL);
+	assert(rollback != NULL);
+	assert(rollback_files != NULL);
+
+	*rollback_files = 0;
+
+	const char* path_argv[] = { path, NULL };
+	
+	FTS* fts = fts_open((char**)path_argv, FTS_PHYSICAL | FTS_COMFOLLOW | FTS_XDEV, fts_compare);
+	FTSENT* ent = fts_read(fts); // throw away the entry for path itself
+	while (res != -1 && (ent = fts_read(fts)) != NULL) {
+		File* file = FileFactory(archive, ent);
+		if (file) {
+			char state = '?';
+
+			IF_DEBUG("[analyze] %s\n", file->path());
+
+			// Perform a three-way-diff between the file to be installed (file),
+			// the file we last installed in this location (preceding),
+			// and the file that actually exists in this location (actual).
+		
+			File* actual = FileFactory(file->path());
+			File* preceding = this->file_preceded_by(file);
+			
+			if (actual == NULL) {
+				// No actual file exists already, so we create a placeholder.
+				actual = new NoEntry(file->path());
+				IF_DEBUG("[analyze]    actual == NULL\n");
+			}
+			
+			if (preceding == NULL) {
+				// Nothing is known about this file.
+				// We'll insert this file into the rollback archive as a
+				// base system file.  Back up its data (if not a directory).
+				actual->info_set(FILE_INFO_BASE_SYSTEM);
+				IF_DEBUG("[analyze]    base system\n");
+				if (!S_ISDIR(actual->mode()) && !INFO_TEST(actual->info(), FILE_INFO_NO_ENTRY)) {
+					IF_DEBUG("[analyze]    needs base system backup, and installation\n");
+					actual->info_set(FILE_INFO_ROLLBACK_DATA);
+					file->info_set(FILE_INFO_INSTALL_DATA);
+				}
+				preceding = actual;
+			}
+		
+			uint32_t actual_flags = File::compare(file, actual);
+			uint32_t preceding_flags = File::compare(actual, preceding);
+		
+			// If file == actual && actual == preceding then nothing needs to be done.
+			if (actual_flags == FILE_INFO_IDENTICAL && preceding_flags == FILE_INFO_IDENTICAL) {
+				state = ' ';
+				IF_DEBUG("[analyze]    no changes\n");
+			}
+			
+			// If file != actual, but actual == preceding, then install file
+			//   but we don't need to save actual, since it's already saved by preceding.
+			//   i.e. no user changes since last installation
+			// If file != actual, and actual != preceding, then install file
+			//  after saving actual in the rollback archive.
+			//  i.e. user changes since last installation
+			if (actual_flags != FILE_INFO_IDENTICAL) {
+				if (INFO_TEST(actual->info(), FILE_INFO_NO_ENTRY)) {
+					state = 'A';
+				} else {
+					state = 'U';
+				}
+				
+				if (INFO_TEST(actual_flags, FILE_INFO_TYPE_DIFFERS) ||
+				    INFO_TEST(actual_flags, FILE_INFO_DATA_DIFFERS)) {
+					IF_DEBUG("[analyze]    needs installation\n");
+					file->info_set(FILE_INFO_INSTALL_DATA);
+
+					if ((INFO_TEST(preceding_flags, FILE_INFO_TYPE_DIFFERS) ||
+					    INFO_TEST(preceding_flags, FILE_INFO_DATA_DIFFERS)) &&
+					    !INFO_TEST(actual->info(), FILE_INFO_NO_ENTRY)) {
+						IF_DEBUG("[analyze]    needs user data backup\n");
+						actual->info_set(FILE_INFO_ROLLBACK_DATA);
+					}
+				}				
+			}
+			
+			// XXX: should this be done in backup_file?
+			// If we're going to need to squirrel away data, create
+			// the directory hierarchy now.
+			if (INFO_TEST(actual->info(), FILE_INFO_ROLLBACK_DATA)) {
+				const char* path = actual->path();
+				char* backup_dirpath;
+				
+				const char* dir = dirname(path);
+				assert(dir != NULL);
+				
+				char uuidstr[37];
+				uuid_unparse_upper(rollback->uuid(), uuidstr);
+				
+				asprintf(&backup_dirpath, "%s/%s/%s", m_archives_path, uuidstr, dir);
+				assert(backup_dirpath != NULL);
+				
+				res = mkdir_p(backup_dirpath);
+				if (res != 0 && errno != EEXIST) {
+					fprintf(stderr, "%s:%d: %s: %s (%d)\n", __FILE__, __LINE__, backup_dirpath, strerror(errno), errno);
+				} else {
+					res = 0;
+				}
+			}
+			
+			
+			if ((state != ' ' && preceding_flags != FILE_INFO_IDENTICAL) ||
+				INFO_TEST(actual->info(), FILE_INFO_BASE_SYSTEM | FILE_INFO_ROLLBACK_DATA)) {
+				*rollback_files += 1;
+				IF_DEBUG("[analyze]    insert rollback\n");
+				res = this->insert(rollback, actual);
+				assert(res == 0);
+			}
+
+			fprintf(stderr, "%c %s\n", state, file->path());
+			res = this->insert(archive, file);
+			assert(res == 0);
+			if (preceding && preceding != actual) delete preceding;
+			if (actual) delete actual;
+			delete file;
+		}
+	}
+	if (fts) fts_close(fts);
+	return res;
+}
+
+
+
+struct InstallContext {
+	InstallContext(Depot* d, Archive* a) {
+		depot = d;
+		archive = a;
+		files_modified = 0;
+		files_added = 0;
+		files_removed = 0;
+		files_to_remove = new SerialSet();
+	}
+	
+	~InstallContext() {
+		delete files_to_remove;
+	}
+	
+	Depot* depot;
+	Archive* archive;
+	uint64_t files_modified;
+	uint64_t files_added;
+	uint64_t files_removed;
+	SerialSet* files_to_remove;	// for uninstall
+};
+
+int Depot::backup_file(File* file, void* ctx) {
+	InstallContext* context = (InstallContext*)ctx;
+	int res = 0;
+
+	if (INFO_TEST(file->info(), FILE_INFO_ROLLBACK_DATA)) {
+		char* dstpath;
+		char uuidstr[37];
+		uuid_unparse_upper(context->archive->uuid(), uuidstr);
+		asprintf(&dstpath, "%s/%s/%s", context->depot->m_archives_path, uuidstr, file->path());
+		assert(dstpath != NULL);
+
+		++context->files_modified;
+
+		// XXX: res = file->backup()
+		IF_DEBUG("[backup] rename(%s, %s)\n", file->path(), dstpath);
+		res = rename(file->path(), dstpath);
+		if (res != 0) fprintf(stderr, "%s:%d: backup failed: %s: %s (%d)\n", __FILE__, __LINE__, dstpath, strerror(errno), errno);
+		free(dstpath);
+	}
+	return res;
+}
+
+
+int Depot::install_file(File* file, void* ctx) {
+	InstallContext* context = (InstallContext*)ctx;
+	int res = 0;
+
+	if (INFO_TEST(file->info(), FILE_INFO_INSTALL_DATA)) {
+		++context->files_modified;
+
+		res = file->install(context->depot->m_archives_path);
+	} else {
+		res = file->install_info();
+	}
+	if (res != 0) fprintf(stderr, "%s:%d: install failed: %s: %s (%d)\n", __FILE__, __LINE__, file->path(), strerror(errno), errno);
+	return res;
+}
+
+
+int Depot::install(Archive* archive) {
+	int res = 0;
+	Archive* rollback = new RollbackArchive();
+
+	assert(rollback != NULL);
+	assert(archive != NULL);
+
+	// Check the consistency of the database before proceeding with the installation
+	// If this fails, abort the installation.
+//	res = this->check_consistency();
+//	if (res != 0) return res;
+
+
+	//
+	// The fun starts here
+	//
+	if (res == 0) res = this->begin_transaction();	
+
+	//
+	// Insert the rollback archive before the new archive to install, thus keeping
+	// the chronology of the serial numbers correct.  We may later choose to delete
+	// the rollback archive if we determine that it was not necessary.
+	//
+	if (res == 0) res = this->insert(rollback);
+	if (res == 0) res = this->insert(archive);
+
+	//
+	// Create the stage directory and rollback backing store directories
+	//
+	char* archive_path = archive->create_directory(m_archives_path);
+	assert(archive_path != NULL);
+	char* rollback_path = rollback->create_directory(m_archives_path);
+	assert(rollback_path != NULL);
+
+
+	// Extract the archive into its backing store directory
+	if (res == 0) res = archive->extract(archive_path);
+
+	// Analyze the files in the archive backing store directory
+	// Inserts new file records into the database for both the new archive being
+	// installed and the rollback archive.
+	int rollback_files = 0;
+	if (res == 0) res = this->analyze_stage(archive_path, archive, rollback, &rollback_files);
+	
+	// If no files were added to the rollback archive, delete the rollback archive.
+	if (res == 0 && rollback_files == 0) {
+		res = this->remove(rollback);
+	}
+	
+	// Commit the archive and its list of files to the database.
+	// Note that the archive's "active" flag is still not set.
+	if (res == 0) {
+		res = this->commit_transaction();
+	} else {
+		this->rollback_transaction();
+	}
+
+	// Save a copy of the backing store directory now, we will soon
+	// be moving the files into place.
+	if (res == 0) res = archive->compact_directory(m_archives_path);
+
+	//
+	// Move files from the root file system to the rollback archive's backing store,
+	// then move files from the archive backing directory to the root filesystem
+	//
+	InstallContext rollback_context(this, rollback);
+	if (res == 0) res = this->iterate_files(rollback, &Depot::backup_file, &rollback_context);
+
+	// compact the rollback archive (if we actually added any files)
+	if (rollback_context.files_modified > 0) {
+		if (res == 0) res = rollback->compact_directory(m_archives_path);
+	}
+
+	InstallContext install_context(this, archive);
+	if (res == 0) res = this->iterate_files(archive, &Depot::install_file, &install_context);
+
+	// Installation is complete.  Activate the archive in the database.
+	if (res == 0) res = this->begin_transaction();
+	if (res == 0) res = SQL("UPDATE archives SET active=1 WHERE serial=%lld;", rollback->serial());
+	if (res == 0) res = SQL("UPDATE archives SET active=1 WHERE serial=%lld;", archive->serial());
+	if (res == 0) res = this->commit_transaction();
+
+	// Remove the stage and rollback directories (save disk space)
+	remove_directory(archive_path);
+	remove_directory(rollback_path);
+	if (rollback_path) free(rollback_path);
+	if (archive_path) free(archive_path);
+	
+	return res;
+}
+
+// deletes expanded backing store directories in m_archives_path
+int Depot::prune_directories() {
+	int res = 0;
+	
+	const char* path_argv[] = { m_archives_path, NULL };
+	
+	FTS* fts = fts_open((char**)path_argv, FTS_PHYSICAL | FTS_COMFOLLOW | FTS_XDEV, fts_compare);
+	FTSENT* ent = fts_read(fts); // get the entry for m_archives_path itself
+	ent = fts_children(fts, 0);
+	while (res != -1 && ent != NULL) {
+		if (ent->fts_info == FTS_D) {
+			char path[PATH_MAX];
+			snprintf(path, PATH_MAX, "%s/%s", m_archives_path, ent->fts_name);
+			IF_DEBUG("pruning: %s\n", path);
+			res = remove_directory(path);
+		}
+		ent = ent->fts_link;
+	}
+	if (fts) fts_close(fts);
+	return res;
+}
+
+int Depot::prune_archives() {
+	int res = 0;
+	static sqlite3_stmt* stmt = NULL;
+	if (stmt == NULL && m_db) {
+		const char* query = "DELETE FROM archives WHERE serial IN (SELECT serial FROM archives WHERE serial NOT IN (SELECT DISTINCT archive FROM files));";
+		res = sqlite3_prepare(m_db, query, -1, &stmt, NULL);
+		if (res != 0) fprintf(stderr, "%s:%d: sqlite3_prepare: %s: %s (%d)\n", __FILE__, __LINE__, query, sqlite3_errmsg(m_db), res);
+	}
+	if (stmt && res == 0) {
+		if (res == 0) res = sqlite3_step(stmt);
+		if (res == SQLITE_DONE) {
+			res = 0;
+		} else {
+			fprintf(stderr, "%s:%d: Could not prune archives in database: %s (%d)\n", __FILE__, __LINE__, sqlite3_errmsg(m_db), res);
+		}
+		sqlite3_reset(stmt);
+	}
+	return res;
+}
+
+int Depot::uninstall_file(File* file, void* ctx) {
+	InstallContext* context = (InstallContext*)ctx;
+	int res = 0;
+	char state = ' ';
+
+	IF_DEBUG("[uninstall] %s\n", file->path());
+
+	// We never uninstall a file that was part of the base system
+	if (INFO_TEST(file->info(), FILE_INFO_BASE_SYSTEM)) {
+		IF_DEBUG("[uninstall]    base system; skipping\n");
+		return 0;
+	}
+	
+	File* actual = FileFactory(file->path());
+	uint32_t flags = File::compare(file, actual);
+		
+	if (actual != NULL && flags != FILE_INFO_IDENTICAL) {
+		// XXX: probably not the desired behavior
+		IF_DEBUG("[uninstall]    changes since install; skipping\n");
+	} else {
+		File* superseded = context->depot->file_superseded_by(file);
+		if (superseded == NULL) {
+			// no one's using this file anymore
+			File* preceding = context->depot->file_preceded_by(file);
+			assert(preceding != NULL);
+			if (INFO_TEST(preceding->info(), FILE_INFO_NO_ENTRY)) {
+				state = 'R';
+				IF_DEBUG("[uninstall]    removing file\n");
+				if (actual && res == 0) res = actual->remove();
+			} else {
+				// copy the preceding file back out to the system
+				// if it's different from what's already there
+				uint32_t flags = File::compare(file, preceding);
+				if (INFO_TEST(flags, FILE_INFO_DATA_DIFFERS)) {
+					state = 'U';
+					IF_DEBUG("[uninstall]    restoring\n");
+					if (res == 0) res = preceding->install(context->depot->m_archives_path);
+				} else if (INFO_TEST(flags, FILE_INFO_MODE_DIFFERS) ||
+					   INFO_TEST(flags, FILE_INFO_GID_DIFFERS) ||
+					   INFO_TEST(flags, FILE_INFO_UID_DIFFERS)) {
+					if (res == 0) res = preceding->install_info();
+				} else {
+					IF_DEBUG("[uninstall]    no changes; leaving in place\n");
+				}
+			}
+			uint32_t info = preceding->info();
+			if (INFO_TEST(info, FILE_INFO_NO_ENTRY | FILE_INFO_ROLLBACK_DATA) &&
+			    !INFO_TEST(info, FILE_INFO_BASE_SYSTEM)) {
+				if (res == 0) res = context->files_to_remove->add(preceding->serial());
+			}
+			delete preceding;
+		} else {
+			IF_DEBUG("[uninstall]    in use by newer installation; leaving in place\n");
+			delete superseded;
+		}
+	}
+
+	fprintf(stderr, "%c %s\n", state, file->path());
+
+	if (res != 0) fprintf(stderr, "%s:%d: uninstall failed: %s\n", __FILE__, __LINE__, file->path());
+	return res;
+}
+
+int Depot::uninstall(Archive* archive) {
+	int res = 0;
+
+	assert(archive != NULL);
+	uint64_t serial = archive->serial();
+
+	if (INFO_TEST(archive->info(), ARCHIVE_INFO_ROLLBACK)) {
+		fprintf(stderr, "%s:%d: cannot uninstall a rollback archive.\n", __FILE__, __LINE__);
+		return -1;
+	}
+
+//	res = this->check_consistency();
+//	if (res != 0) return res;
+
+	// XXX: this may be superfluous
+	// uninstall_file should be smart enough to do a mtime check...
+	if (res == 0) res = this->prune_directories();
+
+	// We do this here to get an exclusive lock on the database.
+	if (res == 0) res = this->begin_transaction();
+	if (res == 0) res = SQL("UPDATE archives SET active=0 WHERE serial=%lld;", serial);
+	if (res == 0) res = this->commit_transaction();
+
+	InstallContext context(this, archive);
+	if (res == 0) res = this->iterate_files(archive, &Depot::uninstall_file, &context);
+	
+	if (res == 0) res = this->begin_transaction();
+	int i;
+	for (i = 0; i < context.files_to_remove->count; ++i) {
+		uint64_t serial = context.files_to_remove->values[i];
+		IF_DEBUG("deleting file %lld\n", serial);
+		if (res == 0) res = SQL("DELETE FROM files WHERE serial=%lld;", serial);
+	}
+	if (res == 0) res = this->commit_transaction();
+
+	if (res == 0) res = this->begin_transaction();	
+	if (res == 0) res = this->remove(archive);
+	if (res == 0) res = this->commit_transaction();
+
+	// delete all of the expanded archive backing stores to save disk space
+	if (res == 0) res = this->prune_directories();
+
+	if (res == 0) res = prune_archives();
+
+	return res;
+}
+
+int Depot::verify_file(File* file, void* context) {
+	File* actual = FileFactory(file->path());
+	if (actual) {
+		uint32_t flags = File::compare(file, actual);
+		
+		if (flags != FILE_INFO_IDENTICAL) {
+			fprintf(stdout, "M ");
+		} else {
+			fprintf(stdout, "  ");
+		}
+	} else {
+		fprintf(stdout, "R ");
+	}
+	file->print(stdout);
+	return 0;
+}
+
+int Depot::verify(Archive* archive) {
+	int res = 0;
+	if (res == 0) res = this->iterate_files(archive, &Depot::verify_file, NULL);
+	return res;
+}
+
+int Depot::list_archive(Archive* archive, void* context) {
+	extern uint32_t verbosity;
+	char uuid[37];
+	uuid_unparse_upper(archive->uuid(), uuid);
+
+	char date[100];
+	struct tm local;
+	time_t seconds = archive->date_installed();
+	localtime_r(&seconds, &local);
+	strftime(date, sizeof(date), "%F %T %Z", &local);
+
+	if (!INFO_TEST(archive->info(), ARCHIVE_INFO_ROLLBACK) ||
+	    (verbosity & VERBOSE_DEBUG)) {
+		fprintf((FILE*)context, "%-36s  %-23s  %s\n", uuid, date, archive->name());
+	}
+	
+	return 0;
+}
+
+int Depot::list() {
+	int res = 0;
+	fprintf(stdout, "%-36s  %-23s  %s\n", "UUID", "Date Installed", "Name");
+	fprintf(stdout, "====================================  =======================  =================\n");
+	if (res == 0) res = this->iterate_archives(&Depot::list_archive, stdout);
+	return res;
+}
+
+int Depot::print_file(File* file, void* context) {
+	extern uint32_t verbosity;
+	if (verbosity & VERBOSE_DEBUG) fprintf((FILE*)context, "%04x ", file->info());
+	file->print((FILE*)context);
+	return 0;
+}
+
+int Depot::files(Archive* archive) {
+	int res = 0;
+	fprintf(stdout, "%-36s  %-23s  %s\n", "UUID", "Date Installed", "Name");
+	fprintf(stdout, "====================================  =======================  =================\n");
+	list_archive(archive, stdout);
+	fprintf(stdout, "================================================================================\n");
+	if (res == 0) res = this->iterate_files(archive, &Depot::print_file, stdout);
+	return res;
+}
+
+int Depot::dump_archive(Archive* archive, void* context) {
+	Depot* depot = (Depot*)context;
+	int res = 0;
+	list_archive(archive, stdout);
+	fprintf(stdout, "================================================================================\n");
+	if (res == 0) res = depot->iterate_files(archive, &Depot::print_file, stdout);
+	fprintf(stdout, "================================================================================\n\n\n");
+	return res;
+}
+
+int Depot::dump() {
+	extern uint32_t verbosity;
+	verbosity = 0xFFFFFFFF; // dump is intrinsically a debug command
+	int res = 0;
+	fprintf(stdout, "%-36s  %-23s  %s\n", "UUID", "Date Installed", "Name");
+	fprintf(stdout, "====================================  =======================  =================\n");
+	if (res == 0) res = this->iterate_archives(&Depot::dump_archive, this);
+	return res;
+}
+
+
+File* Depot::file_star_eded_by(File* file, sqlite3_stmt* stmt) {
+	assert(file != NULL);
+	assert(file->archive() != NULL);
+	
+	File* result = NULL;
+	uint64_t serial = 0;
+	int res = 0;
+	if (stmt && res == 0) {
+		if (res == 0) res = sqlite3_bind_int64(stmt, 1, file->archive()->serial());
+		if (res == 0) res = sqlite3_bind_text(stmt, 2, file->path(), -1, SQLITE_STATIC);
+		if (res == 0) res = sqlite3_step(stmt);
+		switch (res) {
+			case SQLITE_DONE:
+				serial = 0;
+				break;
+			case SQLITE_ROW:
+				{
+				int i = 0;
+				uint64_t serial = sqlite3_column_int64(stmt, i++);
+				uint64_t archive_serial = sqlite3_column_int64(stmt, i++);
+				uint32_t info = sqlite3_column_int(stmt, i++);
+				const unsigned char* path = sqlite3_column_text(stmt, i++);
+				mode_t mode = sqlite3_column_int(stmt, i++);
+				uid_t uid = sqlite3_column_int(stmt, i++);
+				gid_t gid = sqlite3_column_int(stmt, i++);
+				off_t size = sqlite3_column_int64(stmt, i++);
+				const void* blob = sqlite3_column_blob(stmt, i);
+				int blobsize = sqlite3_column_bytes(stmt, i++);
+
+				Digest* digest = NULL;
+				if (blobsize > 0) {
+					digest = new Digest();
+					digest->m_size = blobsize;
+					memcpy(digest->m_data, blob, (blobsize < sizeof(digest->m_data)) ? blobsize : sizeof(digest->m_data));
+				}
+
+				Archive* archive = this->archive(archive_serial);
+
+				result = FileFactory(serial, archive, info, (const char*)path, mode, uid, gid, size, digest);
+				}
+				break;
+			default:
+				fprintf(stderr, "%s:%d: unexpected SQL error: %d\n", __FILE__, __LINE__, res);
+				break;
+		}
+		sqlite3_reset(stmt);
+	} else {
+		fprintf(stderr, "%s:%d: unexpected SQL error: %d\n", __FILE__, __LINE__, res);
+	}
+	
+	return result;
+}
+
+File* Depot::file_superseded_by(File* file) {
+	static sqlite3_stmt* stmt = NULL;
+	if (stmt == NULL && m_db) {
+		// archive which installed this file immediately after
+		const char* query = "SELECT serial, archive, info, path, mode, uid, gid, size, digest FROM files WHERE archive>? AND path=? ORDER BY archive ASC LIMIT 1";
+		int res = sqlite3_prepare(m_db, query, -1, &stmt, NULL);
+		if (res != 0) fprintf(stderr, "%s:%d: sqlite3_prepare: %s: %s (%d)\n", __FILE__, __LINE__, query, sqlite3_errmsg(m_db), res);
+	}
+	return this->file_star_eded_by(file, stmt);
+}
+
+File* Depot::file_preceded_by(File* file) {
+	static sqlite3_stmt* stmt = NULL;
+	if (stmt == NULL && m_db) {
+		// archive which installed this file immediately before
+		const char* query = "SELECT serial, archive, info, path, mode, uid, gid, size, digest FROM files WHERE archive<? AND path=? ORDER BY archive DESC LIMIT 1";
+		int res = sqlite3_prepare(m_db, query, -1, &stmt, NULL);
+		if (res != 0) fprintf(stderr, "%s:%d: sqlite3_prepare: %s: %s (%d)\n", __FILE__, __LINE__, query, sqlite3_errmsg(m_db), res);
+	}
+	return this->file_star_eded_by(file, stmt);
+}
+
+int Depot::check_consistency() {
+	int res = 0;
+
+	SerialSet* inactive = new SerialSet();
+	assert(inactive != NULL);
+	
+	static sqlite3_stmt* stmt = NULL;
+	if (stmt == NULL && m_db) {
+		const char* query = "SELECT serial FROM archives WHERE active=0 ORDER BY serial DESC";
+		res = sqlite3_prepare(m_db, query, -1, &stmt, NULL);
+		if (res != 0) fprintf(stderr, "%s:%d: sqlite3_prepare: %s: %s (%d)\n", __FILE__, __LINE__, query, sqlite3_errmsg(m_db), res);
+	}
+	if (stmt && res == 0) {
+		while (res == 0) {
+			res = sqlite3_step(stmt);
+			if (res == SQLITE_ROW) {
+				res = 0;
+				uint64_t serial = sqlite3_column_int64(stmt, 0);
+				inactive->add(serial);
+			} else if (res == SQLITE_DONE) {
+				res = 0;
+				break;
+			} else {
+				fprintf(stderr, "%s:%d: unexpected SQL error: %d\n", __FILE__, __LINE__, res);
+			}
+		}
+		sqlite3_reset(stmt);
+	}
+	
+	if (res == 0 && inactive && inactive->count > 0) {
+		fprintf(stderr, "The following archive%s in an inconsistent state and must be uninstalled before proceeding:\n\n", inactive->count > 1 ? "s are" : " is");
+		uint32_t i;
+		fprintf(stderr, "%-36s %-23s %s\n", "UUID", "Date Installed", "Name");
+		fprintf(stderr, "====================================  =======================  =================\n");
+		for (i = 0; i < inactive->count; ++i) {
+			Archive* archive = this->archive(inactive->values[i]);
+			if (archive) {
+				list_archive(archive, stderr);
+				delete archive;
+			}
+		}
+		fprintf(stderr, "\nWould you like to uninstall %s now? [y/n] ", inactive->count > 1 ? "them" : "it");
+		int c = getchar();
+		fprintf(stderr, "\n");
+		if (c == 'y' || c == 'Y') {
+			for (i = 0; i < inactive->count; ++i) {
+				Archive* archive = this->archive(inactive->values[i]);
+				if (archive) {
+					res = this->uninstall(archive);
+					delete archive;
+				}
+				if (res != 0) break;
+			}
+		}
+	}
+	
+	return res;
+}
+
+
+int Depot::begin_transaction() {
+	return this->SQL("BEGIN TRANSACTION");
+}
+
+int Depot::rollback_transaction() {
+	return this->SQL("ROLLBACK TRANSACTION");
+}
+
+int Depot::commit_transaction() {
+	return this->SQL("COMMIT TRANSACTION");
+}
+
+int Depot::insert(Archive* archive) {
+	// Don't insert an archive that is already in the database
+	assert(archive->serial() == 0);
+	
+	int res = 0;
+	static sqlite3_stmt* stmt = NULL;
+	if (stmt == NULL && m_db) {
+		const char* query = "INSERT INTO archives (uuid, info, name, date_added) VALUES (?, ?, ?, ?)";
+		res = sqlite3_prepare(m_db, query, -1, &stmt, NULL);
+		if (res != 0) fprintf(stderr, "%s:%d: sqlite3_prepare: %s: %s (%d)\n", __FILE__, __LINE__, query, sqlite3_errmsg(m_db), res);
+	}
+	if (stmt && res == 0) {
+		int i = 1;
+		if (res == 0) res = sqlite3_bind_blob(stmt, i++, archive->uuid(), sizeof(uuid_t), SQLITE_STATIC);
+		if (res == 0) res = sqlite3_bind_int(stmt, i++, archive->info());
+		if (res == 0) res = sqlite3_bind_text(stmt, i++, archive->name(), -1, SQLITE_STATIC);
+		if (res == 0) res = sqlite3_bind_int(stmt, i++, archive->date_installed());
+		if (res == 0) res = sqlite3_step(stmt);
+		if (res == SQLITE_DONE) {
+			archive->m_serial = (uint64_t)sqlite3_last_insert_rowid(m_db);
+			res = 0;
+		} else {
+			fprintf(stderr, "%s:%d: Could not add archive to database: %s (%d)\n", __FILE__, __LINE__, sqlite3_errmsg(m_db), res);
+		}
+		sqlite3_reset(stmt);
+	}
+	return res;
+}
+
+int Depot::insert(Archive* archive, File* file) {
+	int res = 0;
+	static sqlite3_stmt* stmt = NULL;
+	if (stmt == NULL && m_db) {
+		const char* query = "INSERT INTO files (archive, info, mode, uid, gid, digest, path) VALUES (?, ?, ?, ?, ?, ?, ?)";
+		res = sqlite3_prepare(m_db, query, -1, &stmt, NULL);
+		if (res != 0) fprintf(stderr, "%s:%d: sqlite3_prepare: %s: %s (%d)\n", __FILE__, __LINE__, query, sqlite3_errmsg(m_db), res);
+	}
+	if (stmt && res == 0) {
+		int i = 1;
+		if (res == 0) res = sqlite3_bind_int64(stmt, i++, archive->serial());
+		if (res == 0) res = sqlite3_bind_int(stmt, i++, file->info());
+		if (res == 0) res = sqlite3_bind_int(stmt, i++, file->mode());
+		if (res == 0) res = sqlite3_bind_int(stmt, i++, file->uid());
+		if (res == 0) res = sqlite3_bind_int(stmt, i++, file->gid());
+		Digest* dig = file->digest();
+		if (res == 0 && dig) res = sqlite3_bind_blob(stmt, i++, dig->data(), dig->size(), SQLITE_STATIC);
+		else if (res == 0) res = sqlite3_bind_blob(stmt, i++, NULL, 0, SQLITE_STATIC);
+		if (res == 0) res = sqlite3_bind_text(stmt, i++, file->path(), -1, SQLITE_STATIC);
+		if (res == 0) res = sqlite3_step(stmt);
+		if (res == SQLITE_DONE) {
+			file->m_serial = (uint64_t)sqlite3_last_insert_rowid(m_db);
+			res = 0;
+		} else {
+			fprintf(stderr, "%s:%d: Could not add file to database: %s (%d)\n", __FILE__, __LINE__, sqlite3_errmsg(m_db), res);
+		}
+		sqlite3_reset(stmt);
+	}
+	return res;
+}
+
+int Depot::remove(Archive* archive) {
+	int res = 0;
+	uint64_t serial = archive->serial();
+	if (res == 0) res = SQL("DELETE FROM files WHERE archive=%lld", serial);
+	if (res == 0) res = SQL("DELETE FROM archives WHERE serial=%lld", serial);
+	return res;
+}
+
+int Depot::remove(File* file) {
+	int res = 0;
+	uint64_t serial = file->serial();
+	if (res == 0) res = SQL("DELETE FROM files WHERE serial=%lld", serial);
+	return res;
+}
+
+
+#define __SQL(callback, context, fmt) \
+	va_list args; \
+	char* errmsg; \
+	va_start(args, fmt); \
+	if (this->m_db) { \
+		char *query = sqlite3_vmprintf(fmt, args); \
+		res = sqlite3_exec(this->m_db, query, callback, context, &errmsg); \
+		if (res != SQLITE_OK) { \
+			fprintf(stderr, "Error: %s (%d)\n  SQL: %s\n", errmsg, res, query); \
+		} \
+		sqlite3_free(query); \
+	} else { \
+		fprintf(stderr, "Error: database not open.\n"); \
+		res = SQLITE_ERROR; \
+	} \
+	va_end(args);
+
+int Depot::SQL(const char* fmt, ...) {
+	int res;
+	__SQL(NULL, NULL, fmt);
+	return res;
+}
+
+#undef __SQL


Property changes on: trunk/darwinup/Depot.cpp
___________________________________________________________________
Name: svn:eol-style
   + native

Added: trunk/darwinup/Depot.h
===================================================================
--- trunk/darwinup/Depot.h	                        (rev 0)
+++ trunk/darwinup/Depot.h	2006-10-04 09:02:24 UTC (rev 294)
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ * 
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ * 
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ * 
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+#include <sys/types.h>
+#include <uuid/uuid.h>
+#include <sqlite3.h>
+
+struct Archive;
+struct File;
+
+typedef int (*ArchiveIteratorFunc)(Archive* archive, void* context);
+typedef int (*FileIteratorFunc)(File* file, void* context);
+
+struct Depot {
+	Depot();
+	Depot(const char* prefix);
+	
+	virtual ~Depot();
+
+	const char*	database_path();
+	const char*	archives_path();
+
+	virtual int	begin_transaction();
+	virtual int	commit_transaction();
+	virtual int	rollback_transaction();
+
+	Archive*	archive(uint64_t serial);
+	Archive*	archive(uuid_t uuid);
+	Archive*	archive(const char* uuid);
+
+	int dump();
+	static int dump_archive(Archive* archive, void* context);
+	
+	int list();
+	static int list_archive(Archive* archive, void* context);
+
+	int install(Archive* archive);
+	static int install_file(File* file, void* context);
+	static int backup_file(File* file, void* context);
+
+	int uninstall(Archive* archive);
+	static int uninstall_file(File* file, void* context);
+
+	int verify(Archive* archive);
+	static int verify_file(File* file, void* context);
+
+	int files(Archive* archive);
+	static int print_file(File* file, void* context);
+
+	int iterate_files(Archive* archive, FileIteratorFunc func, void* context);
+	int iterate_archives(ArchiveIteratorFunc func, void* context);
+
+	protected:
+
+	// Inserts an Archive into the database.
+	// This modifies the Archive's serial number.
+	// If the Archive already has a serial number, it cannot be inserted.
+	int insert(Archive* archive);
+	
+	// Inserts a File into the database, as part of the specified Archive.
+	// This modifies the File's serial number.
+	// This modifies the File's Archive pointer.
+	// If the File already has a serial number, it cannot be inserted.
+	int insert(Archive* archive, File* file);
+
+	// Removes an Archive from the database.
+	int remove(Archive* archive);
+	
+	// Removes a File from the database.
+	int remove(File* file);
+
+	int		analyze_stage(const char* path, Archive* archive, Archive* rollback, int* rollback_files);
+	int		prune_directories();
+	
+	// Removes all archive entries which have no corresponding files entries.
+	int		prune_archives();
+
+	File*		file_superseded_by(File* file);
+	File*		file_preceded_by(File* file);
+	File*		file_star_eded_by(File* file, sqlite3_stmt* stmt);
+
+	int		check_consistency();
+
+
+	virtual int	SQL(const char* fmt, ...);
+
+	sqlite3*	m_db;
+	mode_t		m_depot_mode;
+	char*		m_depot_path;
+	char*		m_database_path;
+	char*		m_archives_path;
+};


Property changes on: trunk/darwinup/Depot.h
___________________________________________________________________
Name: svn:eol-style
   + native

Added: trunk/darwinup/Digest.cpp
===================================================================
--- trunk/darwinup/Digest.cpp	                        (rev 0)
+++ trunk/darwinup/Digest.cpp	2006-10-04 09:02:24 UTC (rev 294)
@@ -0,0 +1,200 @@
+/*
+ * Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ * 
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ * 
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ * 
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+#include "Digest.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+// For SHA1DigestMachO
+#include <mach/mach_init.h>
+#include <mach/vm_map.h>
+extern "C" {
+// <rdar://problem/4319807> redo_prebinding.h should use extern "C"
+//#include <mach-o/redo_prebinding.h> // from cctools_ofiles
+#include "redo_prebinding.h"
+}
+
+Digest::Digest() {
+	memset(m_data, 0, sizeof(m_data));
+	m_size = 0;
+}
+
+Digest::Digest(const EVP_MD* md, int fd) {
+	digest(md, fd);
+}
+
+void Digest::digest(const EVP_MD* md, int fd) {
+	EVP_MD_CTX ctx;
+	EVP_MD_CTX_init(&ctx);
+	EVP_DigestInit(&ctx, md);
+
+	int len;
+	const unsigned int blocklen = 8192;
+	static uint8_t* block = NULL;
+	if (block == NULL) {
+		block = (uint8_t*)malloc(blocklen);
+	}
+	while(1) {
+		len = read(fd, block, blocklen);
+		if (len == 0) { close(fd); break; }
+		if ((len < 0) && (errno == EINTR)) continue;
+		if (len < 0) { close(fd); return; }
+		EVP_DigestUpdate(&ctx, block, len);
+	}
+	if (len >= 0) {
+		EVP_DigestFinal(&ctx, m_data, &m_size);
+	}
+}
+
+Digest::Digest(const EVP_MD* md, uint8_t* data, uint32_t size) {
+	digest(md, data, size);
+}
+
+void Digest::digest(const EVP_MD* md, uint8_t* data, uint32_t size) {
+	EVP_MD_CTX ctx;
+	EVP_MD_CTX_init(&ctx);
+	EVP_DigestInit(&ctx, md);
+	EVP_DigestUpdate(&ctx, data, size);
+	EVP_DigestFinal(&ctx, m_data, &m_size);
+}
+
+uint8_t*	Digest::data() { return m_data; }
+uint32_t	Digest::size() { return m_size; }
+
+char* Digest::string() {
+	static const char* hexabet = "0123456789abcdef";
+	char* result = (char*)malloc(2*m_size+1);
+	int i, j;
+	
+	for (i = 0, j = 0; i < m_size; ++i) {
+		result[j++] = hexabet[(m_data[i] & 0xF0) >> 4];
+		result[j++] = hexabet[(m_data[i] & 0x0F)];
+	}
+	result[j] = 0;
+	
+	return result;
+}
+
+int Digest::equal(Digest* a, Digest* b) {
+	if (a == b) return 1;
+	if (a == NULL) return 0;
+	if (b == NULL) return 0;
+	int a_size = a->size();
+	if (a_size != b->size()) {
+		return 0;
+	} 
+	return (memcmp(a->data(), b->data(), a_size) == 0);
+}
+
+
+const EVP_MD* SHA1Digest::m_md;
+
+SHA1Digest::SHA1Digest() {
+	if (m_md == NULL) {
+		OpenSSL_add_all_digests();
+		m_md = EVP_get_digestbyname("sha1");
+		assert(m_md != NULL);
+	}
+}
+
+SHA1Digest::SHA1Digest(int fd) {
+	if (m_md == NULL) {
+		OpenSSL_add_all_digests();
+		m_md = EVP_get_digestbyname("sha1");
+		assert(m_md != NULL);
+	}
+	digest(m_md, fd);
+}
+
+SHA1Digest::SHA1Digest(const char* filename) {
+	int fd = open(filename, O_RDONLY);
+	if (m_md == NULL) {
+		OpenSSL_add_all_digests();
+		m_md = EVP_get_digestbyname("sha1");
+		assert(m_md != NULL);
+	}
+	digest(m_md, fd);
+}
+
+SHA1Digest::SHA1Digest(uint8_t* data, uint32_t size) {
+	if (m_md == NULL) {
+		OpenSSL_add_all_digests();
+		m_md = EVP_get_digestbyname("sha1");
+		assert(m_md != NULL);
+	}
+	digest(m_md, data, size);
+}
+
+
+SHA1DigestMachO::SHA1DigestMachO(const char* filename) {
+	char* res = NULL;
+	char* error = NULL;
+	
+	// Check for Mach-O
+	int type = object_file_type(filename, NULL, &error);
+	if (type == OFT_EXECUTABLE ||
+		type == OFT_DYLIB ||
+		type == OFT_BUNDLE) {
+		// XXX - type == OFT_ARCHIVE?
+		void* block = NULL;
+		unsigned long blocklen = 0;
+		int ret = unprebind(filename,
+			NULL,
+			NULL,
+			&error,
+			1,
+			NULL,
+			0,
+			&block,
+			&blocklen);
+		if (ret == REDO_PREBINDING_SUCCESS && block != NULL) {
+			digest(SHA1Digest::m_md, (uint8_t*)block, blocklen);
+		} else {
+			fprintf(stderr, "%s:%d: unexpected unprebind result %d: %s\n", __FILE__, __LINE__, ret, error);
+		}
+		if (block != NULL) {
+			kern_return_t ret = vm_deallocate(mach_task_self(), (vm_address_t)block, (vm_size_t)blocklen);
+			assert(ret == 0);
+		}
+	} else {
+		int fd = open(filename, O_RDONLY);
+		digest(SHA1Digest::m_md, fd);
+		close(fd);
+	}
+}
+
+SHA1DigestSymlink::SHA1DigestSymlink(const char* filename) {
+	char link[PATH_MAX];
+	int res = readlink(filename, link, PATH_MAX);
+	if (res == -1) {
+		fprintf(stderr, "%s:%d: readlink: %s: %s (%d)\n", __FILE__, __LINE__, filename, strerror(errno), errno);
+	} else {
+		digest(SHA1Digest::m_md, (uint8_t*)link, res);
+	}
+}


Property changes on: trunk/darwinup/Digest.cpp
___________________________________________________________________
Name: svn:eol-style
   + native

Added: trunk/darwinup/Digest.h
===================================================================
--- trunk/darwinup/Digest.h	                        (rev 0)
+++ trunk/darwinup/Digest.h	2006-10-04 09:02:24 UTC (rev 294)
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ * 
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ * 
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ * 
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+#include <openssl/evp.h>
+
+////
+//  Digest
+//
+//  Digest is the root class for all message digest algorithms
+//  supported by darwinup.
+//
+//  Conceptually it's an abstract class, although that
+//  hasn't been formalized.
+//
+//  SHA1Digest is the only concrete subclass.  There are two
+//  subclasses of SHA1Digest which add convenience functions
+//  for digesting a canonicalized Mach-O binary, and the
+//  target of a symlink obtained by readlink(2).
+//
+//  NOTE: It might be more appropriate to use the CommonCrypto
+//  implementation of these algorithms rather than the OpenSSL
+//  implementation.  However, CommonCrypto is only available on
+//  Tiger.
+////
+
+struct Digest {
+	Digest();
+	Digest(const EVP_MD* md, int fd);
+	Digest(const EVP_MD* md, uint8_t* data, uint32_t size);
+	
+	////
+	//  Accessor functions
+	////
+	
+	// Returns the raw digest.
+	virtual	uint8_t*	data();
+
+	// Returns the size of the raw digest.
+	virtual uint32_t	size();
+	
+	// Returns the digest as an ASCIZ string, represented in hexidecimal.
+	virtual char*		string();
+	
+	////
+	//  Class functions
+	////
+	
+	// Compares two digest objects for equality.
+	// Returns 1 if equal, 0 if not.
+	static	int		equal(Digest* a, Digest* b);
+
+
+	protected:
+
+	virtual	void	digest(const EVP_MD* md, int fd);
+	virtual	void	digest(const EVP_MD* md, uint8_t* data, uint32_t size);
+
+	uint8_t		m_data[EVP_MAX_MD_SIZE];
+	uint32_t	m_size;
+	
+	friend struct Depot;
+};
+
+////
+//  SHA1Digest
+////
+struct SHA1Digest : Digest {
+	static const EVP_MD* m_md;
+	
+	// Creates an empty digest.
+	SHA1Digest();
+	
+	// Computes the SHA-1 digest of data read from the stream.
+	SHA1Digest(int fd);
+	
+	// Computes the SHA-1 digest of data in the file.
+	SHA1Digest(const char* filename);
+	
+	// Computes the SHA-1 digest of the block of memory.
+	SHA1Digest(uint8_t* data, uint32_t size);
+};
+
+////
+//  SHA1DigestMachO
+//  Digests of canonicalized Mach-O file formats.
+////
+struct SHA1DigestMachO : SHA1Digest {
+	// Computes the SHA-1 digest of the data in the file.
+	// If the file is a Mach-O executable or dynamic library,
+	// the SHA-1 digest is computed from its canonical
+	// representation.
+	SHA1DigestMachO(const char* filename);
+};
+
+////
+//  SHA1DigestSymlink
+//  Digests of the target of a symlink.
+////
+struct SHA1DigestSymlink : SHA1Digest {
+	// Computes the SHA-1 digest of the target of the symlink.
+	// The target is obtained via readlink(2).
+	SHA1DigestSymlink(const char* filename);
+};
\ No newline at end of file


Property changes on: trunk/darwinup/Digest.h
___________________________________________________________________
Name: svn:eol-style
   + native

Added: trunk/darwinup/File.cpp
===================================================================
--- trunk/darwinup/File.cpp	                        (rev 0)
+++ trunk/darwinup/File.cpp	2006-10-04 09:02:24 UTC (rev 294)
@@ -0,0 +1,374 @@
+/*
+ * Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ * 
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ * 
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ * 
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+#include "Archive.h"
+#include "File.h"
+#include "Utils.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+File::File() {
+	m_serial = 0;
+	m_archive = NULL;
+	m_info = FILE_INFO_NONE;
+	m_path = NULL;
+	m_mode = 0;
+	m_uid = 0;
+	m_gid = 0;
+	m_size = 0;
+	m_digest = NULL;
+}
+
+File::File(const char* path) {
+	m_serial = 0;
+	m_archive = NULL;
+	m_info = FILE_INFO_NONE;
+	m_mode = 0;
+	m_uid = 0;
+	m_gid = 0;
+	m_size = 0;
+	m_digest = NULL;
+	if (path) m_path = strdup(path);
+}
+
+File::File(Archive* archive, FTSENT* ent) {	
+	char path[PATH_MAX];
+	path[0] = 0;
+	ftsent_filename(ent, path, PATH_MAX);
+	m_path = strdup(path);
+	m_archive = archive;
+	m_info = FILE_INFO_NONE;
+	m_mode = ent->fts_statp->st_mode;
+	m_uid = ent->fts_statp->st_uid;
+	m_gid = ent->fts_statp->st_gid;
+	m_size = ent->fts_statp->st_size;
+	
+	m_digest = NULL;
+}
+
+File::File(uint64_t serial, Archive* archive, uint32_t info, const char* path, mode_t mode, uid_t uid, gid_t gid, off_t size, Digest* digest) {
+	m_serial = serial;
+	m_archive = archive;
+	m_info = info;
+	m_path = strdup(path);
+	m_mode = mode;
+	m_uid = uid;
+	m_gid = gid;
+	m_size = size;
+	m_digest = digest;
+}
+
+
+File::~File() {
+	if (m_path) free(m_path);
+	if (m_digest) delete m_digest;
+}
+
+uint64_t	File::serial()	{ return m_serial; }
+Archive*	File::archive()	{ return m_archive; }
+uint32_t	File::info()	{ return m_info; }
+const char*	File::path()	{ return m_path; }
+mode_t		File::mode()	{ return m_mode; }
+uid_t		File::uid()	{ return m_uid; }
+gid_t		File::gid()	{ return m_gid; }
+off_t		File::size()	{ return m_size; }
+Digest*		File::digest()	{ return m_digest; }
+
+void		File::info_set(uint32_t flag)	{ m_info = INFO_SET(m_info, flag); }
+void		File::info_clr(uint32_t flag)	{ m_info = INFO_CLR(m_info, flag); }
+void		File::archive(Archive* archive) { m_archive = archive; }
+
+uint32_t File::compare(File* a, File* b) {
+	if (a == b) return FILE_INFO_IDENTICAL; // identity
+	if (a == NULL) return 0xFFFFFFFF; // existent and nonexistent file are infinitely different
+	if (b == NULL) return 0xFFFFFFFF; // existent and nonexistent file are infinitely different
+	
+	uint32_t result = FILE_INFO_IDENTICAL;
+	if (a->m_uid != b->m_uid) result |= FILE_INFO_UID_DIFFERS;
+	if (a->m_gid != b->m_gid) result |= FILE_INFO_GID_DIFFERS;
+	if (a->m_mode != b->m_mode) result |= FILE_INFO_MODE_DIFFERS;
+	if ((a->m_mode & S_IFMT) != (b->m_mode & S_IFMT)) result |= FILE_INFO_TYPE_DIFFERS;
+	if ((a->m_mode & ALLPERMS) != (b->m_mode & ALLPERMS)) result |= FILE_INFO_PERM_DIFFERS;
+	//if (a->m_size != b->m_size) result |= FILE_INFO_SIZE_DIFFERS;
+	if (Digest::equal(a->m_digest, b->m_digest) == 0) result |= FILE_INFO_DATA_DIFFERS;
+	return result;
+}
+
+
+void File::print(FILE* stream) {
+	char* dig = m_digest ? m_digest->string() : strdup("");
+	
+	char mode_str[12];
+	strmode(m_mode, mode_str);
+		
+	fprintf(stream, "%s % 4d % 4d % 40s %s\n", mode_str, m_uid, m_gid, dig, m_path);
+	free(dig);
+}
+
+int File::install(const char* prefix) {
+	int res = 0;
+	Archive* archive = this->archive();
+	assert(archive != NULL);
+	char* dirpath = archive->directory_name(prefix);
+
+	char srcpath[PATH_MAX];
+	const char* dstpath = this->path();
+	if (dirpath) {
+		ssize_t len = snprintf(srcpath, sizeof(srcpath), "%s/%s", dirpath, dstpath);
+		if (len > sizeof(srcpath)) {
+			fprintf(stderr, "ERROR: [install] path too long: %s/%s\n", dirpath, dstpath);
+			return -1;
+		}
+		res = rename(srcpath, dstpath);
+		if (res == -1) {
+			if (errno == ENOENT) {
+				// the file wasn't found, try to do on-demand
+				// expansion of the archive that contains it.
+				if (is_directory(dirpath) == 0) {
+					res = archive->expand_directory(prefix);
+					if (res == 0) res = this->install(prefix);
+				} else {
+					// archive was already expanded, so
+					// the file is truly missing (worry).
+					fprintf(stderr, "%s:%d: %s: %s (%d)\n", __FILE__, __LINE__, srcpath, strerror(errno), errno);
+				}
+			//} else if (errno == ENOTDIR) {
+				// a) some part of destination path does not exist
+				// b) from is a directory, but to is not
+			//} else if (errno == EISDIR) {
+				// to is a directory, but from is not
+			//} else if (errno == ENOTEMPTY) {
+				// to is a directory and is not empty
+			} else {
+				fprintf(stderr, "%s:%d: %s: %s (%d)\n", __FILE__, __LINE__, dstpath, strerror(errno), errno);
+			}
+		} else {
+			IF_DEBUG("[install] rename(%s, %s)\n", srcpath, dstpath);
+		}
+		free(dirpath);
+	} else {
+		res = -1;
+	}
+	return res;
+}
+
+int File::remove() {
+	// not implemented
+	fprintf(stderr, "%s:%d: call to abstract function File::remove\n", __FILE__, __LINE__);
+	return -1;
+}
+
+int File::install_info() {
+	int res = 0;
+	const char* path = this->path();
+	uid_t uid = this->uid();
+	gid_t gid = this->gid();
+	mode_t mode = this->mode() & ALLPERMS;
+
+	IF_DEBUG("[install] chown(%s, %d, %d)\n", path, uid, gid);
+	if (res == 0) res = chown(path, uid, gid);
+	IF_DEBUG("[install] chmod(%s, %04o)\n", path, mode);
+	if (res == 0) res = chmod(path, mode);
+
+	return res;
+}
+
+NoEntry::NoEntry(const char* path) : File(path) {
+	m_info = INFO_SET(m_info, FILE_INFO_NO_ENTRY);
+}
+
+NoEntry::NoEntry(uint64_t serial, Archive* archive, uint32_t info, const char* path, mode_t mode, uid_t uid, gid_t gid, off_t size, Digest* digest) : File(serial, archive, info, path, mode, uid, gid, size, digest) {}
+
+Regular::Regular(Archive* archive, FTSENT* ent) : File(archive, ent) {
+	m_digest = new SHA1DigestMachO(ent->fts_accpath);
+}
+
+Regular::Regular(uint64_t serial, Archive* archive, uint32_t info, const char* path, mode_t mode, uid_t uid, gid_t gid, off_t size, Digest* digest) : File(serial, archive, info, path, mode, uid, gid, size, digest) {
+	if (digest == NULL || serial == 0) {
+		m_digest = new SHA1DigestMachO(path);
+	}
+}
+
+int Regular::remove() {
+	int res = 0;
+	const char* path = this->path();
+	res = unlink(path);
+	if (res == -1 && errno == ENOENT) {
+		// We can safely ignore this because we were going to
+		// remove the file anyway
+		res = 0;
+	} else if (res != 0) {
+		fprintf(stderr, "%s:%d: %s: %s (%d)\n", __FILE__, __LINE__, m_path, strerror(errno), errno);
+	}
+	return res;
+}
+
+Symlink::Symlink(Archive* archive, FTSENT* ent) : File(archive, ent) {
+	m_digest = new SHA1DigestSymlink(ent->fts_accpath);
+}
+
+Symlink::Symlink(uint64_t serial, Archive* archive, uint32_t info, const char* path, mode_t mode, uid_t uid, gid_t gid, off_t size, Digest* digest) : File(serial, archive, info, path, mode, uid, gid, size, digest) {
+	if (digest == NULL || serial == 0) {
+		m_digest = new SHA1DigestSymlink(path);
+	}
+}
+
+int Symlink::remove() {
+	int res = 0;
+	const char* path = this->path();
+	res = unlink(path);
+	if (res == -1 && errno == ENOENT) {
+		// We can safely ignore this because we were going to
+		// remove the file anyway
+		res = 0;
+	} else if (res == -1) {
+		fprintf(stderr, "%s:%d: %s (%d)\n", __FILE__, __LINE__, strerror(errno), errno);
+	}
+	return res;
+}
+
+int Symlink::install_info() {
+	int res = 0;
+	const char* path = this->path();
+	mode_t mode = this->mode() & ALLPERMS;
+	uid_t uid = this->uid();
+	gid_t gid = this->gid();
+	IF_DEBUG("[install] lchown(%d, %d)\n", uid, gid);
+	if (res == 0) res = lchown(path, uid, gid);
+	//IF_DEBUG("[install] lchmod(%o)\n", mode);
+	//if (res == 0) res = lchmod(path, mode);
+	return res;
+}
+
+Directory::Directory(Archive* archive, FTSENT* ent) : File(archive, ent) {}
+
+Directory::Directory(uint64_t serial, Archive* archive, uint32_t info, const char* path, mode_t mode, uid_t uid, gid_t gid, off_t size, Digest* digest) : File(serial, archive, info, path, mode, uid, gid, size, digest) {};
+
+int Directory::install(const char* prefix) {
+	// We create a new directory instead of renaming the
+	// existing one, since that would move the entire
+	// sub-tree, and lead to a lot of ENOENT errors.
+	int res = 0;
+	
+	const char* dstpath = this->path();
+	mode_t mode = this->mode() & ALLPERMS;
+	uid_t uid = this->uid();
+	gid_t gid = this->gid();
+	
+	IF_DEBUG("[install] mkdir(%s, %04o)\n", dstpath, mode);
+	if (res == 0) res = mkdir(dstpath, mode);
+	if (res != 0) fprintf(stderr, "ERROR: %s:%d: %s: %s (%d)\n", __FILE__, __LINE__, dstpath, strerror(errno), errno);
+	if (res == 0) res = chown(dstpath, uid, gid);
+	if (res != 0) fprintf(stderr, "ERROR: %s:%d: %s: %s (%d)\n", __FILE__, __LINE__, dstpath, strerror(errno), errno);
+	return res;
+}
+
+int Directory::remove() {
+	int res = 0;
+	const char* path = this->path();
+	res = rmdir(path);
+	if (res == -1 && errno == ENOENT) {
+		// We can safely ignore this because we were going to
+		// remove the directory anyway
+		res = 0;
+	} else if (res == -1 && errno == ENOTEMPTY) {
+		res = remove_directory(path);
+	} else if (res == -1) {
+		fprintf(stderr, "%s:%d: %s (%d)\n", __FILE__, __LINE__, strerror(errno), errno);
+	}
+	return res;
+}
+
+
+File* FileFactory(uint64_t serial, Archive* archive, uint32_t info, const char* path, mode_t mode, uid_t uid, gid_t gid, off_t size, Digest* digest) {
+	File* file = NULL;
+	switch (mode & S_IFMT) {
+		case S_IFDIR:
+			file = new Directory(serial, archive, info, path, mode, uid, gid, size, digest);
+			break;
+		case S_IFREG:
+			file = new Regular(serial, archive, info, path, mode, uid, gid, size, digest);
+			break;
+		case S_IFLNK:
+			file = new Symlink(serial, archive, info, path, mode, uid, gid, size, digest);
+			break;
+		case 0:
+			if (INFO_TEST(info, FILE_INFO_NO_ENTRY)) {
+				file = new NoEntry(serial, archive, info, path, mode, uid, gid, size, digest);
+				break;
+			}
+		default:
+			fprintf(stderr, "%s:%d: unexpected file type %o\n", __FILE__, __LINE__, mode & S_IFMT);
+			break;
+	}
+	return file;
+}
+
+File* FileFactory(Archive* archive, FTSENT* ent) {
+	File* file = NULL;
+	switch (ent->fts_info) {
+		case FTS_D:
+			file = new Directory(archive, ent);
+			break;
+		case FTS_F:
+			file = new Regular(archive, ent);
+			break;
+		case FTS_SL:
+		case FTS_SLNONE:
+			file = new Symlink(archive, ent);
+			break;
+		case FTS_DP:
+			break;
+		case FTS_DEFAULT:
+		case FTS_DNR:
+			fprintf(stderr, "%s:%d: could not read directory.  Run as root.\n", __FILE__, __LINE__, ent->fts_info);
+			break;
+		default:
+			fprintf(stderr, "%s:%d: unexpected fts_info type %d\n", __FILE__, __LINE__, ent->fts_info);
+			break;
+	}
+	return file;
+}
+
+File* FileFactory(const char* path) {
+	File* file = NULL;
+	struct stat sb;
+	int res = 0;
+	
+	res = lstat(path, &sb);
+	if (res == -1 && errno == ENOENT) {
+		return NULL;
+	} else if (res == -1) {
+		fprintf(stderr, "%s:%d: %s: %s (%d)\n", __FILE__, __LINE__, path, strerror(errno), errno);
+		return NULL;
+	}
+	
+	file = FileFactory(0, NULL, FILE_INFO_NONE, path, sb.st_mode, sb.st_uid, sb.st_gid, sb.st_size, NULL);
+	return file;
+}
\ No newline at end of file


Property changes on: trunk/darwinup/File.cpp
___________________________________________________________________
Name: svn:eol-style
   + native

Added: trunk/darwinup/File.h
===================================================================
--- trunk/darwinup/File.h	                        (rev 0)
+++ trunk/darwinup/File.h	2006-10-04 09:02:24 UTC (rev 294)
@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ * 
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ * 
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ * 
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+#include "Digest.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fts.h>
+
+//
+// FILE_INFO flags stored in the database
+//
+const uint32_t FILE_INFO_NONE			= 0x0000;
+const uint32_t FILE_INFO_BASE_SYSTEM		= 0x0001;	// file was part of base system, cannot uninstall
+const uint32_t FILE_INFO_NO_ENTRY		= 0x0002;	// placeholder in the database for non-existent file
+const uint32_t FILE_INFO_INSTALL_DATA		= 0x0010;	// actually install the file
+const uint32_t FILE_INFO_ROLLBACK_DATA		= 0x0020;	// file exists in rollback archive
+
+//
+// FILE_INFO flags returned by File::compare()
+//
+const uint32_t FILE_INFO_IDENTICAL		= 0x00000000;
+
+const uint32_t FILE_INFO_GID_DIFFERS		= 0x00100000;
+const uint32_t FILE_INFO_UID_DIFFERS		= 0x00200000;
+
+const uint32_t FILE_INFO_MODE_DIFFERS		= 0x01000000;	// mode differs overall
+const uint32_t FILE_INFO_TYPE_DIFFERS		= 0x02000000;	//   S_IFMT differs
+const uint32_t FILE_INFO_PERM_DIFFERS		= 0x04000000;	//   ALLPERMS differs
+
+const uint32_t FILE_INFO_SIZE_DIFFERS		= 0x10000000;
+const uint32_t FILE_INFO_DATA_DIFFERS		= 0x20000000;
+
+
+struct Archive;
+struct File;
+
+////
+//  File
+//
+//  File is the root class for all filesystem objects.
+//  Conceptually it's an abstract class, although that
+//  hasn't been formalized.
+//
+//  Concrete subclasses exist for specific file types:
+//  Regular, Symlink, Directory, and NoEntry, indicating
+//  that the given path does not exist.
+//
+//  FileFactory functions exist to return the correct
+//  concrete subclass for a given filesystem object.
+////
+
+File* FileFactory(uint64_t serial, Archive* archive, uint32_t info, const char* path, mode_t mode, uid_t uid, gid_t gid, off_t size, Digest* digest);
+File* FileFactory(const char* path);
+File* FileFactory(Archive* archive, FTSENT* ent);
+
+
+struct File {
+	File();
+	File(File*);
+	File(const char* path);
+	File(Archive* archive, FTSENT* ent);
+	File(uint64_t serial, Archive* archive, uint32_t info, const char* path, mode_t mode, uid_t uid, gid_t gid, off_t size, Digest* digest);
+	virtual ~File();
+
+	////
+	// Public Accessor functions
+	////
+	
+	// Unique serial number for the file (used by database).
+	virtual uint64_t serial();
+	
+	// FILE_INFO flags.
+	virtual uint32_t info();
+	virtual void info_set(uint32_t);
+	virtual void info_clr(uint32_t);
+	
+	// Pointer to the Archive this file belongs to.
+	virtual Archive* archive();
+	virtual void archive(Archive* archive);
+	
+	// Path of the file on disk (absolute path).
+	// Do not modify or free(3).
+	virtual	const char* path();
+	
+	// Mode of the file, including the file type.
+	virtual	mode_t mode();
+	
+	// Uid of the file.
+	virtual	uid_t uid();
+	
+	// Gid of the file.
+	virtual	gid_t gid();
+	
+	// Size of the file.
+	virtual off_t size();
+	
+	// Digest of the file's data.
+	virtual Digest* digest();
+
+	////
+	//  Class functions
+	////
+	
+	// Compare two files, setting the appropriate
+	// FILE_INFO bits in the return value.
+	static uint32_t compare(File* a, File* b);
+
+	////
+	//  Member functions
+	////
+
+	// Installs the file at the given prefix onto the
+	// root volume.  i.e., for regular files:
+	// rename(prefix + this->archive()->uuid() + this->path(), this->path());
+	virtual int install(const char* prefix);
+	
+	// Sets the mode, uid, and gid of the file on the
+	// root volume.
+	// XXX: rename as repair()?
+	virtual int install_info();
+	
+	// Removes the file from the root volume.
+	virtual int remove();
+
+	// Prints one line to the output stream indicating
+	// the file mode, ownership, digest and name.
+	virtual void print(FILE* stream);
+
+	protected:
+
+	uint64_t	m_serial;
+	uint32_t	m_info;
+	Archive*	m_archive;
+	char*		m_path;
+	mode_t		m_mode;
+	uid_t		m_uid;
+	gid_t		m_gid;
+	off_t		m_size;
+	Digest*		m_digest;
+	
+	friend struct Depot;
+};
+
+
+////
+//  Placeholder for rollback archives in the database.
+//  Indicates that the given path had no entry at the time that
+//  the archive was created.
+////
+
+struct NoEntry : File {
+	NoEntry(const char* path);
+	NoEntry(uint64_t serial, Archive* archive, uint32_t info, const char* path, mode_t mode, uid_t uid, gid_t gid, off_t size, Digest* digest);
+};
+
+////
+//  A regular file.
+//  Digest is of the data fork of the file.
+//  NOTE: Extended attributes are not detected or preserved.
+////
+struct Regular : File {
+	Regular(Archive* archive, FTSENT* ent);
+	Regular(uint64_t serial, Archive* archive, uint32_t info, const char* path, mode_t mode, uid_t uid, gid_t gid, off_t size, Digest* digest);
+	virtual int remove();
+};
+
+////
+//  A symbolic link.
+//  Digest is of the target obtained via readlink(2).
+////
+struct Symlink : File {
+	Symlink(Archive* archive, FTSENT* ent);
+	Symlink(uint64_t serial, Archive* archive, uint32_t info, const char* path, mode_t mode, uid_t uid, gid_t gid, off_t size, Digest* digest);
+	virtual int install_info();
+	virtual int remove();
+};
+
+////
+//  A directory.
+//  Digest is null.
+////
+struct Directory : File {
+	Directory(Archive* archive, FTSENT* ent);
+	Directory(uint64_t serial, Archive* archive, uint32_t info, const char* path, mode_t mode, uid_t uid, gid_t gid, off_t size, Digest* digest);
+	virtual int install(const char* prefix);
+	virtual int remove();
+};


Property changes on: trunk/darwinup/File.h
___________________________________________________________________
Name: svn:eol-style
   + native

Added: trunk/darwinup/Makefile
===================================================================
--- trunk/darwinup/Makefile	                        (rev 0)
+++ trunk/darwinup/Makefile	2006-10-04 09:02:24 UTC (rev 294)
@@ -0,0 +1,19 @@
+all: darwinup
+
+CXXFLAGS=-g
+
+Archive.o: Archive.cpp Archive.h Depot.h File.h Utils.h
+Depot.o: Depot.cpp Archive.h Depot.h File.h Utils.h
+Digest.o: Digest.cpp Digest.h
+File.o: File.cpp Archive.h Digest.h File.h Utils.h
+Utils.o: Utils.h
+main.o: main.cpp Depot.h Utils.h
+
+# libredo.o is generated from cctools_ofiles
+
+darwinup: Archive.o Depot.o Digest.o File.o SerialSet.o Utils.o main.o \
+	  libredo.o
+	g++ -lcrypto -lsqlite3 -o $@ $^
+
+clean:
+	rm -f darwinup Archive.o Depot.o Digest.o File.o SerialSet.o Utils.o main.o


Property changes on: trunk/darwinup/Makefile
___________________________________________________________________
Name: svn:eol-style
   + native

Added: trunk/darwinup/NOTES
===================================================================
--- trunk/darwinup/NOTES	                        (rev 0)
+++ trunk/darwinup/NOTES	2006-10-04 09:02:24 UTC (rev 294)
@@ -0,0 +1,110 @@
+darwinup
+01-Sep-2005
+Kevin Van Vechten <kevin at opendarwin.org>
+
+OVERVIEW
+========
+
+The Darwin Update utility provides a transaction-based mechanism to
+install software on the base system that modifies or supersedes existing
+system components.  Darwin Update is not a "package manager," and does
+not specify any package format.  Updates can be installed from a directory,
+a tar archive, or other formats.
+
+Darwin Update provides the following:
+
+ *  Installation of Darwin components (usually built with DarwinBuild)
+ *  Verification of components previously installed with Darwin Update
+ *  Un-installation of components installed with Darwin Update
+
+It is a design goal of Darwin Update that all components installed by
+this utility can subsequently be uninstalled, leaving the system in its
+original state.
+
+
+THE DEPOT
+=========
+
+The Darwin Update utility creates a directory at the root level of the
+filesystem (/.DarwinDepot) where it stores its database of currently
+installed components, and archived copies of the components they superseded.
+This is known as the depot.
+
+The following files are present in the depot:
+
+/.DarwinDepot/Database-V100
+SQLite database containing information about all of the archives and files
+that have been installed with darwinbuild.
+
+/.DarwinDepot/Archives/
+If an archive has any data to be installed, it will have a corresponding entry
+in this directory.  This is known as the backing-store of the archive.
+The backing-store is either in a compressed (.tar.bz2) or expanded (directory)
+state.  The compressed state is always authoritative and the expanded state
+is pruned when darwinup exits.  The backing store for each archive is named
+according to that archive's UUID in the database.
+
+
+OPERATIONS
+==========
+
+1. INSTALLATION
+
+When Darwin Update is used for installing a tar archive (for example), it
+will first create two entries in the database.  One for the archive to be
+installed, and another for the "rollback" archive associated with this
+archive.  The rollback archive is where all of the base system files and
+existing user data are preserved before the new archive is installed.
+
+A backing-store directory is created for both the new archive and the
+rollback archive.  Darwin Update extracts the tar archive into the newly
+created backing store directory.  This effectively pre-allocates space on
+the root volume for the installation.
+
+Next, the location is compared against the software currently installed.
+Records are inserted into the database for each file in the new archive.
+Additionally, records are also inserted into the rollback archive
+representing the initial state of the system.  (Note, that if a new file
+is identical to a file that Darwin Update has previously installed, no
+rollback file will be added since the record of the previously installed
+file is sufficient).
+
+Once all the records have been committed to the database, each file that
+was added to the rollback archive is moved into the backing-store.  At this
+point the rollback archive backing store directory is compacted into a
+.tar.bz2 archive.
+
+Finally, each new file is moved from the backing store to its location on
+the root filesystem.
+
+Installation is complete.
+
+2. UNINSTALLATION
+
+When Darwin Update is used for uninstalling an archive, it will iterate
+through all the files in that archive as recorded in the database.
+
+If another newer archive has installed a file in the same location, then
+no action is taken because the archive to be uninstalled no longer has a
+claim on that file.  However, if there are no newer archives claiming this
+file, it will be uninstalled.
+
+When a file is uninstalled, Darwin Update searches the database for the
+previous version of the file, whether archived from the base system, or
+installed in an earlier archive.  If the file is identical to the previous
+version, then no action is taken.  However, if the previous version differs
+it will need to be restored.
+
+During restoration, if the file did not previously exist at all, it will be
+deleted.  Otherwise, the rollback archive backing store is expanded, and the
+file is moved from teh rollback archive backing store onto the root filesystem.
+The previous file has been restored.
+
+At this point Darwin Update deletes the record of the previous file from the
+database.
+
+Once all files affected by the uninstalled archive have been deleted or
+restored, the archive and its list of files are deleted from the database.
+
+Uninstallation is complete.
+


Property changes on: trunk/darwinup/NOTES
___________________________________________________________________
Name: svn:eol-style
   + native

Added: trunk/darwinup/SerialSet.cpp
===================================================================
--- trunk/darwinup/SerialSet.cpp	                        (rev 0)
+++ trunk/darwinup/SerialSet.cpp	2006-10-04 09:02:24 UTC (rev 294)
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ * 
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ * 
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ * 
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+#include "SerialSet.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+
+SerialSet::SerialSet() {
+	capacity = 0;
+	count = 0;
+	values = (uint64_t*)malloc(0);
+}
+
+SerialSet::~SerialSet() {
+	if (values) free(values);
+}
+
+int SerialSet::add(uint64_t value) {
+	// If the serial already exists in the set, then there's nothing to be done
+	uint32_t i;
+	for (i = 0; i < this->count; ++i) {
+		if (this->values[i] == value) {
+			return 0;
+		}
+	}
+
+	// Otherwise, append it to the end of the set
+	this->count++;
+	if (this->count > this->capacity) {
+		this->capacity += 10;
+		this->values = (uint64_t*)realloc(this->values, this->capacity * sizeof(uint64_t));
+		assert(this->values != NULL);
+	}
+	this->values[this->count-1] = value;
+
+	return 0;
+}


Property changes on: trunk/darwinup/SerialSet.cpp
___________________________________________________________________
Name: svn:eol-style
   + native

Added: trunk/darwinup/SerialSet.h
===================================================================
--- trunk/darwinup/SerialSet.h	                        (rev 0)
+++ trunk/darwinup/SerialSet.h	2006-10-04 09:02:24 UTC (rev 294)
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ * 
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ * 
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ * 
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+#include <sys/types.h>
+
+// a variably lengthed set of serial numbers from the database
+struct SerialSet {	
+	SerialSet();
+	~SerialSet();
+	
+	int add(uint64_t value);
+
+	uint32_t capacity;
+	uint32_t count;
+	uint64_t* values;
+};


Property changes on: trunk/darwinup/SerialSet.h
___________________________________________________________________
Name: svn:eol-style
   + native

Added: trunk/darwinup/Utils.cpp
===================================================================
--- trunk/darwinup/Utils.cpp	                        (rev 0)
+++ trunk/darwinup/Utils.cpp	2006-10-04 09:02:24 UTC (rev 294)
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ * 
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ * 
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ * 
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+#include "Utils.h"
+#include <assert.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+extern char** environ;
+
+int fts_compare(const FTSENT **a, const FTSENT **b) {
+	return strcmp((*a)->fts_name, (*b)->fts_name);
+}
+
+int ftsent_filename(FTSENT* ent, char* filename, size_t bufsiz) {
+	if (ent == NULL) return 0;
+	if (ent->fts_level > 1) {
+		bufsiz = ftsent_filename(ent->fts_parent, filename, bufsiz);
+	}
+	strlcat(filename, "/", bufsiz);
+	bufsiz -= 1;
+	if (ent->fts_name) {
+		strlcat(filename, ent->fts_name, bufsiz);
+		bufsiz -= strlen(ent->fts_name);
+	}
+	return bufsiz;
+}
+
+int mkdir_p(const char* path) {
+        int res;
+
+        for (;;) {
+                // Use 0777, let the umask decide.
+                res = mkdir(path, 0777);
+
+                if (res != 0 && errno == ENOENT) {
+                        char tmp[PATH_MAX];
+                        strncpy(tmp, path, PATH_MAX);
+                        char* slash = strrchr(tmp, '/');
+                        if (slash) { *slash = 0; }
+                        res = mkdir_p(tmp);
+                        if (res != 0) {
+                                break;
+                        }
+                } else {
+                        break;
+                }
+        }
+        return res;
+}
+
+int remove_directory(const char* directory) {
+	int res = 0;
+	const char* path_argv[] = { directory, NULL };
+	FTS* fts = fts_open((char**)path_argv, FTS_PHYSICAL | FTS_COMFOLLOW | FTS_XDEV, fts_compare);
+	FTSENT* ent = fts_read(fts); // throw away the entry for the DSTROOT itself
+	while (res == 0 && (ent = fts_read(fts)) != NULL) {
+		switch (ent->fts_info) {
+			case FTS_D:
+				break;
+			case FTS_F:
+			case FTS_SL:
+			case FTS_SLNONE:
+			case FTS_DEFAULT:
+				res = unlink(ent->fts_accpath);
+				break;
+			case FTS_DP:
+				res = rmdir(ent->fts_accpath);
+				break;
+			default:
+				fprintf(stderr, "%s:%d: unexpected fts_info type %d\n", __FILE__, __LINE__, ent->fts_info);
+				break;
+		}
+	}
+	fts_close(fts);
+	return res;
+}
+
+int is_directory(const char* path) {
+	struct stat sb;
+	int res = stat(path, &sb);
+	return (res == 0 && S_ISDIR(sb.st_mode));
+}
+
+int is_regular_file(const char* path) {
+	struct stat sb;
+	int res = stat(path, &sb);
+	return (res == 0 && S_ISREG(sb.st_mode));
+}
+
+int has_suffix(const char* str, const char* sfx) {
+	str = strstr(str, sfx);
+	return (str && strcmp(str, sfx) == 0);
+}
+
+int exec_with_args(const char** args) {
+	int res = 0;
+	pid_t pid;
+	int status;
+	
+	pid = fork();
+	assert(pid != -1);
+	if (pid == 0) {
+		assert(execve(args[0], (char**)args, environ) != -1);
+		// NOT REACHED
+	}
+	do {
+		res = waitpid(pid, &status, 0);
+	} while (res == -1 && errno == EINTR);
+	if (res != -1) {
+		if (WIFEXITED(status)) {
+			res = WEXITSTATUS(status);
+		} else {
+			res = -1;
+		}
+	}
+	return res;
+}


Property changes on: trunk/darwinup/Utils.cpp
___________________________________________________________________
Name: svn:eol-style
   + native

Added: trunk/darwinup/Utils.h
===================================================================
--- trunk/darwinup/Utils.h	                        (rev 0)
+++ trunk/darwinup/Utils.h	2006-10-04 09:02:24 UTC (rev 294)
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ * 
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ * 
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ * 
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+#include <sys/types.h>
+#include <fts.h>
+
+#include <stdarg.h>
+#include <stdio.h>
+
+const uint32_t VERBOSE		= 0x1;
+const uint32_t VERBOSE_DEBUG	= 0x2;
+
+#define IF_DEBUG(...) do { extern uint32_t verbosity; if (verbosity & VERBOSE_DEBUG) fprintf(stderr, "DEBUG: " __VA_ARGS__); } while (0)
+
+int fts_compare(const FTSENT **a, const FTSENT **b);
+int ftsent_filename(FTSENT* ent, char* filename, size_t bufsiz);
+int mkdir_p(const char* path);
+int remove_directory(const char* path);
+int is_directory(const char* path);
+int is_regular_file(const char* path);
+int has_suffix(const char* str, const char* sfx);
+int exec_with_args(const char** args);
+
+inline int INFO_TEST(uint32_t word, uint32_t flag) { return ((word & flag) != 0); }
+inline int INFO_SET(uint32_t word, uint32_t flag) { return (word | flag); }
+inline int INFO_CLR(uint32_t word, uint32_t flag) { return (word & (~flag)); }
+


Property changes on: trunk/darwinup/Utils.h
___________________________________________________________________
Name: svn:eol-style
   + native

Added: trunk/darwinup/libredo.o
===================================================================
(Binary files differ)


Property changes on: trunk/darwinup/libredo.o
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/darwinup/main.cpp
===================================================================
--- trunk/darwinup/main.cpp	                        (rev 0)
+++ trunk/darwinup/main.cpp	2006-10-04 09:02:24 UTC (rev 294)
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ * 
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ * 
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ * 
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+#include "Archive.h"
+#include "Depot.h"
+#include "Utils.h"
+
+#include <libgen.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+void usage(char* progname) {
+	char* pad = strdup(progname);
+	int i;
+	for (i = 0; i < strlen(pad); ++i) pad[i] = ' ';
+	
+	fprintf(stderr, "usage: %s install   <path>\n", progname);
+	fprintf(stderr, "       %s list\n", pad, progname);
+	fprintf(stderr, "       %s files     <uuid>\n", pad, progname);
+	fprintf(stderr, "       %s uninstall <uuid>\n", pad, progname);
+	fprintf(stderr, "       %s verify    <uuid>\n", pad, progname);
+	exit(1);
+}
+
+// our globals
+uint32_t verbosity;
+
+int main(int argc, char* argv[]) {
+	int res = 0;
+	Depot* depot = new Depot("/");
+	
+	char* progname = strdup(basename(argv[0]));
+	
+	int ch;
+	while ((ch = getopt(argc, argv, "v")) != -1) {
+		switch (ch) {
+		case 'v':
+			verbosity <<= 1;
+			verbosity |= VERBOSE;
+			break;
+		case '?':
+		default:
+			usage(progname);
+		}
+	}
+	argc -= optind;
+	argv += optind;
+
+	if (argc == 2 && strcmp(argv[0], "install") == 0) {
+		char uuid[37];
+		Archive* archive = ArchiveFactory(argv[1]);
+		if (archive) {
+			res = depot->install(archive);
+			if (res == 0) {
+				uuid_unparse_upper(archive->uuid(), uuid);
+				fprintf(stdout, "%s\n", uuid);
+			} else {
+				fprintf(stderr, "An error occurred.\n");
+				res = 1;
+			}
+		} else {
+			fprintf(stderr, "Archive not found: %s\n", argv[1]);
+		}
+	} else if (argc == 1 && strcmp(argv[0], "list") == 0) {
+		depot->list();
+	} else if (argc == 1 && strcmp(argv[0], "dump") == 0) {
+		depot->dump();
+	} else if (argc == 2 && strcmp(argv[0], "files") == 0) {
+		Archive* archive = depot->archive(argv[1]);
+		if (archive) {
+			res = depot->files(archive);
+			delete archive;
+		} else {
+			fprintf(stderr, "Archive not found: %s\n", argv[1]);
+			res = 1;
+		}
+	} else if (argc == 2 && strcmp(argv[0], "uninstall") == 0) {
+		Archive* archive = depot->archive(argv[1]);
+		if (archive) {
+			res = depot->uninstall(archive);
+			if (res != 0) {
+				fprintf(stderr, "An error occurred.\n");
+				res = 1;
+			}
+			delete archive;
+		} else {
+			fprintf(stderr, "Archive not found: %s\n", argv[1]);
+			res = 1;
+		}
+	} else if (argc == 2 && strcmp(argv[0], "verify") == 0) {
+		Archive* archive = depot->archive(argv[1]);
+		if (archive) {
+			res = depot->verify(archive);
+			if (res != 0) {
+				fprintf(stderr, "An error occurred.\n");
+				res = 1;
+			}
+			delete archive;
+		} else {
+			fprintf(stderr, "Archive not found: %s\n", argv[1]);
+			res = 1;
+		}
+	} else {
+		usage(progname);
+	}
+	exit(res);
+	return res;
+}


Property changes on: trunk/darwinup/main.cpp
___________________________________________________________________
Name: svn:eol-style
   + native

Added: trunk/darwinup/redo_prebinding.h
===================================================================
--- trunk/darwinup/redo_prebinding.h	                        (rev 0)
+++ trunk/darwinup/redo_prebinding.h	2006-10-04 09:02:24 UTC (rev 294)
@@ -0,0 +1,264 @@
+/*
+ * Copyright (c) 2003 Apple Computer, Inc. All rights reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ * 
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ * 
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ * 
+ * @APPLE_LICENSE_HEADER_END@
+ */
+#define REDO_PREBINDING_VERSION 2
+#include <mach/machine.h>
+/*
+ * For all APIs in this file the parameters program_name and error_message
+ * are used the same.  For unrecoverable resource errors like being unable to
+ * allocate memory each API prints a message to stderr precede with program_name
+ * then calls exit(2) with the value EXIT_FAILURE.  If an API is unsuccessful
+ * and if error_message pass to it is not NULL it is set to a malloc(3)'ed
+ * buffer with a NULL terminated string with the error message.  For all APIs 
+ * when they return they release all resources (memory, open file descriptors,
+ * etc). 
+ * 
+ * The file_name parameter for these APIs may be of the form "foo(bar)" which is
+ * NOT interpreted as an archive name and a member name in that archive.  As
+ * these API deal with prebinding and prebound binaries ready for execution
+ * can't be in archives.
+ * 
+ * If the executable_path parameter for these APIs is not NULL it is used for
+ * any dependent library has a path that starts with "@executable_path". Then
+ * "@executable_path" is replaced with executable_path. 
+ * 
+ * If the root_dir parameter is not NULL it is prepended to all the rooted
+ * dependent library paths. 
+ */
+
+/*
+ * dependent_libs() takes a file_name of a binary and returns a malloc(3)'ed
+ * array of pointers (NULL terminated) to names (also malloc(3)'ed and '\0'
+ * terminated names) of all the dependent libraries for that binary (not
+ * recursive) for all of the architectures of that binary.  If successful
+ * dependent_libs() returns a non NULL value (at minimum a pointer to one NULL
+ * pointer). If unsuccessful dependent_libs() returns NULL.
+ */ 
+extern
+char **
+dependent_libs(
+const char *file_name,
+const char *program_name,
+char **error_message);
+
+/*
+ * install_name() takes a file_name of a binary and returns a malloc(3)'ed
+ * pointer to a NULL terminated string containing the install_name value for
+ * the binary. If unsuccessful install_name() returns NULL.  In particular,
+ * NULL is returned if the binary is not a dylib and there is no error_message
+ * set.  If the all of the arch's are dylibs but all the install names don't
+ * match NULL is returned and a error_message is set.  If some but not all of
+ * the archs are dylibs NULL is returned and a error_message is set.
+ */ 
+extern
+char *
+install_name(
+const char *file_name,
+const char *program_name,
+char **error_message);
+
+/* return values for redo_prebinding() */
+enum redo_prebinding_retval {
+    REDO_PREBINDING_SUCCESS,
+    REDO_PREBINDING_FAILURE,
+    /* the following can only be returned if the parameter only_if_needed set */
+    REDO_PREBINDING_NOT_NEEDED,
+    REDO_PREBINDING_NOT_PREBOUND,
+    REDO_PREBINDING_NEEDS_REBUILDING
+};
+
+/*
+ * redo_prebinding() takes a file_name of a binary and redoes the prebinding on
+ * it.  If output_file is not NULL the update file is written to output_file,
+ * if not it is written to file_name.  If redo_prebinding() is successful it
+ * returns REDO_PREBINDING_SUCCESS otherwise it returns REDO_PREBINDING_FAILURE.
+ * If the parameter allow_missing_architectures is zero and not all
+ * architectures can be updated it is not successful and nothing is done and
+ * this returns REDO_PREBINDING_FAILURE.  If the parameter
+ * allow_missing_architectures is non-zero then only problems with missing
+ * architectures for the architecure of the cputype specified by 
+ * allow_missing_architectures will cause this call to fail.  Other
+ * architectures that could not be prebound due to missing architectures in
+ * depending libraries will not have their prebinding updated but will not
+ * cause this call to fail.
+ * If the slide_to_address parameter is non-zero and the binary is a
+ * dynamic library it is relocated to have that has its prefered address.  If
+ * only_if_needed is non-zero the prebinding is checked first and only done if
+ * needed.  The checking includes checking the prefered address against the
+ * slide_to_address value if it is non-zero.  If only_if_needed is non-zero
+ * and the prebinding does not have to be redone REDO_PREBINDING_NOT_NEEDED is
+ * returned, if the binary is not prebound REDO_PREBINDING_NOT_PREBOUND is
+ * returned and if the new load commands do not fit in the binary and it needs
+ * to be rebuilt REDO_PREBINDING_NEEDS_REBUILDING is returned.
+ * If zero_out_prebind_checksum is non-zero then the cksum field of the
+ * LC_PREBIND_CKSUM load command (if any) is set to zero on output (this should
+ * always be set by B&I tools and never set by the update_prebinding(1)
+ * command).
+ * If throttle is non-NULL it points to a value of the maximum bytes per second
+ * to use for writting the output.  If the value is ULONG_MAX then the actual
+ * bytes per second is returned indirectly through *throttle.
+ */
+extern 
+enum redo_prebinding_retval
+redo_prebinding(
+const char *file_name,
+const char *executable_path,
+const char *root_dir,
+const char *output_file,
+const char *program_name,
+char **error_message,
+unsigned long slide_to_address,
+int only_if_needed,
+int zero_out_prebind_checksum,
+cpu_type_t allow_missing_architectures,
+unsigned long *throttle);
+
+
+/* return values for needs_redo_prebinding() */
+enum needs_redo_prebinding_retval {
+    PREBINDING_UPTODATE,  /* a binary who's prebinding is up todate */
+    PREBINDING_OUTOFDATE, /* a binary who's prebinding is out of date */
+    NOT_PREBOUND,	  /* a binary, but not built prebound */
+    NOT_PREBINDABLE,	  /* not a binary or statically linked,
+			     prebinding does not apply */
+    PREBINDING_UNKNOWN	  /* a binary who's prebinding can't be determined
+			     because it is malformed, a library it depends
+			     on is missing, etc. */
+};
+
+/*
+ * needs_redo_prebinding() takes a file_name and determines if it is a binary
+ * and if its prebinding is uptodate.  It returns one of the return values
+ * above depending on the state of the binary and libraries. If the parameter
+ * allow_missing_architectures is zero then the value returned is based on the
+ * first architecture for fat files.  If the parameter
+ * allow_missing_architectures is non-zero then the value returned is based on
+ * the cputype specified by allow_missing_architectures.  If that architecture
+ * is not present then PREBINDING_UPTODATE is returned.  If the parameter
+ * expected_address is not zero and the binary is a dynamic library then the
+ * library is checked to see if it is at the expected_address if not the
+ * prebinding is assumed to be out of date and PREBINDING_OUTOFDATE is returned.
+ */
+extern
+enum needs_redo_prebinding_retval
+needs_redo_prebinding(
+const char *file_name,
+const char *executable_path,
+const char *root_dir,
+const char *program_name,
+char **error_message,
+unsigned long expected_address,
+cpu_type_t allow_missing_architectures);
+
+
+/*
+ * unprebind() takes a file_name of a binary and resets or removes prebinding
+ * information from it.  If inbuf is non-NULL, the memory pointed to by inbuf is
+ * used as the input file contents.  Otherwise, the contents are loaded from 
+ * the file at path file_name.  Even if inbuf is non-NULL, a file_name 
+ * parameter should be specified for error reporting.  Similarly, if outbuf is 
+ * non-NULL, upon return, outbuf will point to a buffer containing the 
+ * unprebound binary and outlen will point to the length of the output buffer.  
+ * This buffer is vm_allocate'd and therefore should be vm_deallocate'd when it 
+ * is no longer needed.  If outbuf is NULL, and output_file is not NULL the 
+ * update file is written to output_file, if outbuf is NULL and output_file is 
+ * NULL, it is written to file_name.  
+ * If unprebind() is successful it returns REDO_PREBINDING_SUCCESS otherwise it
+ * returns REDO_PREBINDING_FAILURE If the binary is already unprebound (i.e. it
+ * has the MH_PREBINDABLE flag set) then REDO_PREBINDING_NOT_NEEDED is returned.
+ * If the binary is not prebound and not prebindable, 
+ * REDO_PREBINDING_NOT_PREBOUND is returned.  If zero_checksum is non-zero then
+ * the cksum field the LC_PREBIND_CKSUM load command (if any) is set to zero on
+ * output, otherwise it is left alone.
+ * Unprebinding slides dynamic libraries to address zero, resets prebound 
+ * symbols to address zero and type undefined, resets symbol pointers, removes 
+ * LC_PREBOUND_DYLIB commands, resets library timestamps, resets two-level hints
+ * and updates relocation entries if necessary.  Unprebound binaries have
+ * the MH_PREBINDABLE flag set, but not MH_PREBOUND.  It will also set the the
+ * MH_ALLMODSBOUND flag if all two-level libraries were used and all modules
+ * were found to be bound in the LC_PREBOUND_DYLIB commands.
+ * As unprebinding is intended to produce a canonical Mach-O
+ * binary, bundles and non-prebound executables and dylibs are acceptable
+ * as input.  For these files, the  unprebind operation will zero library 
+ * time stamps and version numbers and zero entries in the two-level hints
+ * table.  These files will not gain the MH_PREBINDABLE flag.
+ * All resulting binaries successfully processed by unprebind() will have
+ * the MH_CANONICAL flag.
+ */
+extern
+enum redo_prebinding_retval
+unprebind(
+const char *file_name,
+const char *output_file,
+const char *program_name,
+char **error_message,
+int zero_checksum,
+void *inbuf,
+unsigned long inlen,
+void **outbuf,
+unsigned long *outlen);
+
+enum object_file_type_retval {
+    OFT_OTHER,
+    OFT_EXECUTABLE,
+    OFT_DYLIB,
+    OFT_BUNDLE,
+    OFT_ARCHIVE,
+    OFT_INCONSISTENT,
+    OFT_FILE_ERROR
+};
+
+/*
+ * object_file_type() takes a file_name and determines what type of object
+ * file it is.  If it is a fat file and the architectures are not of the same
+ * type then OFT_INCONSISTENT is returned.  If the file_name can't be opened,
+ * read or malformed then OFT_FILE_ERROR is returned.
+ */
+extern
+enum object_file_type_retval
+object_file_type(
+const char *file_name,
+const char *program_name,
+char **error_message);
+
+struct prebind_cksum_arch {
+    cpu_type_t cputype;		/* cpu specifier */
+    cpu_subtype_t cpusubtype;	/* machine specifier */
+    unsigned long has_cksum;	/* 1 if the arch as an LC_PREBIND_CKSUM */
+    unsigned long cksum;	/* value of the cksum in LC_PREBIND_CKSUM */
+};
+
+/*
+ * get_prebind_cksums() takes a file_name that is a Mach-O file or fat file
+ * containing Mach-O files and returns a malloc(3)'ed array of
+ * prebind_cksum_arch structs indirectly through the cksums parameter.  The
+ * number of prebind_cksum_arch structs is returned indirectly through the
+ * ncksums parameter.  If successful it returns zero else it returns non-zero.
+ */
+extern
+int
+get_prebind_cksums(
+const char *file_name,
+struct prebind_cksum_arch **cksums,
+unsigned long *ncksums,
+const char *program_name,
+char **error_message);


Property changes on: trunk/darwinup/redo_prebinding.h
___________________________________________________________________
Name: svn:eol-style
   + native

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/darwinbuild-changes/attachments/20061004/ea0e85d7/attachment-0001.html


More information about the darwinbuild-changes mailing list