[27526] trunk/base/src

source_changes at macosforge.org source_changes at macosforge.org
Mon Aug 6 13:13:12 PDT 2007


Revision: 27526
          http://trac.macosforge.org/projects/macports/changeset/27526
Author:   sfiera at macports.org
Date:     2007-08-06 13:13:12 -0700 (Mon, 06 Aug 2007)

Log Message:
-----------
Whew! Lots of stuff, including: * Transactions can now be delimited outside cregistry * Things that write *need* to be inside a write transaction * Created a real reg_registry object that keeps track of its state * Finally got rid of some sqlite3 char signed-ness errors * registry::entry map/unmap take lists instead of variable argument counts * Removed unique logic from `reg_entry_map` entirely; it's the wrong place

Modified Paths:
--------------
    trunk/base/src/cregistry/entry.c
    trunk/base/src/cregistry/entry.h
    trunk/base/src/cregistry/registry.c
    trunk/base/src/cregistry/registry.h
    trunk/base/src/cregistry/sql.c
    trunk/base/src/cregistry/sql.h
    trunk/base/src/registry2.0/entry.c
    trunk/base/src/registry2.0/entryobj.c
    trunk/base/src/registry2.0/registry.c
    trunk/base/src/registry2.0/registry.h
    trunk/base/src/registry2.0/tests/common.tcl
    trunk/base/src/registry2.0/tests/entry.tcl

Modified: trunk/base/src/cregistry/entry.c
===================================================================
--- trunk/base/src/cregistry/entry.c	2007-08-06 20:13:00 UTC (rev 27525)
+++ trunk/base/src/cregistry/entry.c	2007-08-06 20:13:12 UTC (rev 27526)
@@ -141,7 +141,6 @@
     if (i == entry_count) {
         return 1;
     }
-    begin_exclusive(db);
     if (sqlite3_prepare(db, query, -1, &stmt, NULL)
                 == SQLITE_OK) {
         for (i=0; i<entry_count; i++) {
@@ -156,17 +155,14 @@
             } else {
                 sqlite3_finalize(stmt);
                 reg_sqlite_error(db, errPtr, query);
-                commit_transaction(db);
                 return 0;
             }
         }
         sqlite3_finalize(stmt);
-        commit_transaction(db);
         return 1;
     } else {
         sqlite3_finalize(stmt);
         reg_sqlite_error(db, errPtr, query);
-        commit_transaction(db);
     }
     return 0;
 }
@@ -178,12 +174,15 @@
  * required. That's OK because there's only one place this function is called,
  * and it's called with all of them there.
  */
-reg_entry* reg_entry_create(sqlite3* db, char* name, char* version,
+reg_entry* reg_entry_create(reg_registry* reg, char* name, char* version,
         char* revision, char* variants, char* epoch, reg_error* errPtr) {
     sqlite3_stmt* stmt;
     char* query = "INSERT INTO registry.ports "
         "(name, version, revision, variants, epoch) VALUES (?, ?, ?, ?, ?)";
-    if ((sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK)
+    if (!reg_test_writable(reg, errPtr)) {
+        return NULL;
+    }
+    if ((sqlite3_prepare(reg->db, query, -1, &stmt, NULL) == SQLITE_OK)
             && (sqlite3_bind_text(stmt, 1, name, -1, SQLITE_STATIC)
                 == SQLITE_OK)
             && (sqlite3_bind_text(stmt, 2, version, -1, SQLITE_STATIC)
@@ -197,11 +196,11 @@
             && (sqlite3_step(stmt) == SQLITE_DONE)) {
         char* query = "INSERT INTO entries (id, address) VALUES (?, ?)";
         reg_entry* entry = malloc(sizeof(reg_entry));
-        entry->id = sqlite3_last_insert_rowid(db);
-        entry->db = db;
+        entry->id = sqlite3_last_insert_rowid(reg->db);
+        entry->db = reg->db;
         entry->proc = NULL;
         sqlite3_finalize(stmt);
-        if ((sqlite3_prepare(db, query, -1, &stmt, NULL)
+        if ((sqlite3_prepare(reg->db, query, -1, &stmt, NULL)
                 == SQLITE_OK)
                 && (sqlite3_bind_int64(stmt, 1, entry->id) == SQLITE_OK)
                 && (sqlite3_bind_blob(stmt, 2, &entry, sizeof(reg_entry*),
@@ -209,24 +208,24 @@
                 && (sqlite3_step(stmt) == SQLITE_DONE)) {
             return entry;
         } else {
-            reg_sqlite_error(db, errPtr, query);
+            reg_sqlite_error(reg->db, errPtr, query);
         }
         free(entry);
         return NULL;
     } else {
-        reg_sqlite_error(db, errPtr, query);
+        reg_sqlite_error(reg->db, errPtr, query);
         sqlite3_finalize(stmt);
         return NULL;
     }
 }
 
-reg_entry* reg_entry_open(sqlite3* db, char* name, char* version,
+reg_entry* reg_entry_open(reg_registry* reg, char* name, char* version,
         char* revision, char* variants, char* epoch, reg_error* errPtr) {
     sqlite3_stmt* stmt;
     char* query = "SELECT registry.ports.id, entries.address "
         "FROM registry.ports LEFT OUTER JOIN entries USING (id) "
         "WHERE name=? AND version=? AND revision=? AND variants=? AND epoch=?";
-    if ((sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK)
+    if ((sqlite3_prepare(reg->db, query, -1, &stmt, NULL) == SQLITE_OK)
             && (sqlite3_bind_text(stmt, 1, name, -1, SQLITE_STATIC)
                 == SQLITE_OK)
             && (sqlite3_bind_text(stmt, 2, version, -1, SQLITE_STATIC)
@@ -241,9 +240,9 @@
         reg_entry* entry;
         switch (r) {
             case SQLITE_ROW:
-                if (reg_stmt_to_entry(db, (void**)&entry, stmt, errPtr)) {
+                if (reg_stmt_to_entry(reg->db, (void**)&entry, stmt, errPtr)) {
                     sqlite3_finalize(stmt);
-                    if (reg_save_addresses(db, &entry, 1, errPtr)) {
+                    if (reg_save_addresses(reg->db, &entry, 1, errPtr)) {
                         return entry;
                     }
                 }
@@ -253,13 +252,13 @@
                 errPtr->free = NULL;
                 break;
             default:
-                reg_sqlite_error(db, errPtr, query);
+                reg_sqlite_error(reg->db, errPtr, query);
                 break;
         }
         sqlite3_finalize(stmt);
         return NULL;
     } else {
-        reg_sqlite_error(db, errPtr, query);
+        reg_sqlite_error(reg->db, errPtr, query);
         sqlite3_finalize(stmt);
         return NULL;
     }
@@ -268,16 +267,19 @@
 /**
  * deletes an entry; still needs to be freed
  */
-int reg_entry_delete(sqlite3* db, reg_entry* entry, reg_error* errPtr) {
+int reg_entry_delete(reg_registry* reg, reg_entry* entry, reg_error* errPtr) {
     sqlite3_stmt* stmt;
     char* query = "DELETE FROM registry.ports WHERE id=?";
-    if ((sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK)
+    if (!reg_test_writable(reg, errPtr)) {
+        return 0;
+    }
+    if ((sqlite3_prepare(reg->db, query, -1, &stmt, NULL) == SQLITE_OK)
             && (sqlite3_bind_int64(stmt, 1, entry->id) == SQLITE_OK)
             && (sqlite3_step(stmt) == SQLITE_DONE)) {
-        if (sqlite3_changes(db) > 0) {
+        if (sqlite3_changes(reg->db) > 0) {
             sqlite3_finalize(stmt);
             query = "DELETE FROM entries WHERE id=?";
-            if ((sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK)
+            if ((sqlite3_prepare(reg->db, query, -1, &stmt, NULL) == SQLITE_OK)
                     && (sqlite3_bind_int64(stmt, 1, entry->id) == SQLITE_OK)
                     && (sqlite3_step(stmt) == SQLITE_DONE)) {
                 sqlite3_finalize(stmt);
@@ -289,7 +291,7 @@
             errPtr->free = NULL;
         }
     } else {
-        reg_sqlite_error(db, errPtr, query);
+        reg_sqlite_error(reg->db, errPtr, query);
     }
     sqlite3_finalize(stmt);
     return 0;
@@ -298,7 +300,7 @@
 /*
  * Frees the given entry - not good to expose?
  */
-static void reg_entry_free(sqlite3* db UNUSED, reg_entry* entry) {
+static void reg_entry_free(reg_registry* reg UNUSED, reg_entry* entry) {
     sqlite3_stmt* stmt;
     if (entry->proc != NULL) {
         free(entry->proc);
@@ -366,7 +368,7 @@
  * Vulnerable to SQL-injection attacks in the `keys` field. Pass it valid keys,
  * please.
  */
-int reg_entry_search(sqlite3* db, char** keys, char** vals, int key_count,
+int reg_entry_search(reg_registry* reg, char** keys, char** vals, int key_count,
         int strategy, reg_entry*** entries, reg_error* errPtr) {
     int i;
     char* kwd = " WHERE ";
@@ -389,10 +391,10 @@
         kwd = " AND ";
     }
     /* do the query */
-    result = reg_all_objects(db, query, query_len, (void***)entries,
+    result = reg_all_objects(reg->db, query, query_len, (void***)entries,
             reg_stmt_to_entry, (free_function*)reg_entry_free, errPtr);
     if (result > 0) {
-        if (!reg_save_addresses(db, *entries, result, errPtr)) {
+        if (!reg_save_addresses(reg->db, *entries, result, errPtr)) {
             free(entries);
             return 0;
         }
@@ -407,14 +409,14 @@
  * to `reg_entry_installed`.
  * @todo add more arguments (epoch, revision, variants), maybe
  *
- * @param [in] db       registry database as created by `registry_open`
+ * @param [in] reg      registry object as created by `registry_open`
  * @param [in] name     specific port to find (NULL for any)
  * @param [in] version  specific version to find (NULL for any)
  * @param [out] entries list of ports meeting the criteria
  * @param [out] errPtr  description of error encountered, if any
  * @return              the number of such ports found
  */
-int reg_entry_imaged(sqlite3* db, char* name, char* version, 
+int reg_entry_imaged(reg_registry* reg, char* name, char* version, 
         reg_entry*** entries, reg_error* errPtr) {
     char* format;
     char* query;
@@ -430,10 +432,10 @@
             "AND version='%q'";
     }
     query = sqlite3_mprintf(format, select, name, version);
-    result = reg_all_objects(db, query, -1, (void***)entries,
+    result = reg_all_objects(reg->db, query, -1, (void***)entries,
             reg_stmt_to_entry, (free_function*)reg_entry_free, errPtr);
     if (result > 0) {
-        if (!reg_save_addresses(db, *entries, result, errPtr)) {
+        if (!reg_save_addresses(reg->db, *entries, result, errPtr)) {
             free(entries);
             return 0;
         }
@@ -447,13 +449,13 @@
  * dependencies, and properly own the files they map.
  * @todo add more arguments (epoch, revision, variants), maybe
  *
- * @param [in] db       registry database as created by `registry_open`
+ * @param [in] reg      registry object as created by `registry_open`
  * @param [in] name     specific port to find (NULL for any)
  * @param [out] entries list of ports meeting the criteria
  * @param [out] errPtr  description of error encountered, if any
  * @return              the number of such ports found
  */
-int reg_entry_installed(sqlite3* db, char* name, reg_entry*** entries,
+int reg_entry_installed(reg_registry* reg, char* name, reg_entry*** entries,
         reg_error* errPtr) {
     char* format;
     char* query;
@@ -466,10 +468,10 @@
         format = "%s WHERE state='installed' AND name='%q'";
     }
     query = sqlite3_mprintf(format, select, name);
-    result = reg_all_objects(db, query, -1, (void***)entries,
+    result = reg_all_objects(reg->db, query, -1, (void***)entries,
             reg_stmt_to_entry, (free_function*)reg_entry_free, errPtr);
     if (result > 0) {
-        if (!reg_save_addresses(db, *entries, result, errPtr)) {
+        if (!reg_save_addresses(reg->db, *entries, result, errPtr)) {
             free(entries);
             return 0;
         }
@@ -478,7 +480,7 @@
     return result;
 }
 
-int reg_entry_owner(sqlite3* db, char* path, reg_entry** entry,
+int reg_entry_owner(reg_registry* reg, char* path, reg_entry** entry,
         reg_error* errPtr) {
     sqlite3_stmt* stmt;
     reg_entry* result;
@@ -486,15 +488,15 @@
         "FROM registry.files INNER JOIN registry.ports USING(id)"
         " LEFT OUTER JOIN entries USING(id) "
         "WHERE path=? AND registry.ports.state = 'installed'";
-    if ((sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK)
+    if ((sqlite3_prepare(reg->db, query, -1, &stmt, NULL) == SQLITE_OK)
             && (sqlite3_bind_text(stmt, 1, path, -1, SQLITE_STATIC)
                 == SQLITE_OK)) {
         int r = sqlite3_step(stmt);
         switch (r) {
             case SQLITE_ROW:
-                if (reg_stmt_to_entry(db, (void**)&result, stmt, errPtr)) {
+                if (reg_stmt_to_entry(reg->db, (void**)&result, stmt, errPtr)) {
                     sqlite3_finalize(stmt);
-                    if (reg_save_addresses(db, &result, 1, errPtr)) {
+                    if (reg_save_addresses(reg->db, &result, 1, errPtr)) {
                         *entry = result;
                         return 1;
                     }
@@ -509,22 +511,23 @@
                 return 0;
         }
     } else {
-        reg_sqlite_error(db, errPtr, query);
+        reg_sqlite_error(reg->db, errPtr, query);
         sqlite3_finalize(stmt);
         return 0;
     }
 }
 
-int reg_entry_propget(sqlite3* db, reg_entry* entry, char* key, char** value,
-        reg_error* errPtr) {
+int reg_entry_propget(reg_registry* reg, reg_entry* entry, char* key,
+        char** value, reg_error* errPtr) {
     sqlite3_stmt* stmt;
-    char* query = sqlite3_mprintf("SELECT %q FROM registry.ports "
-            "WHERE id=%lld", key, entry->id);
-    if (sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK) {
+    char* query;
+    query = sqlite3_mprintf("SELECT %q FROM registry.ports WHERE id=%lld", key,
+            entry->id);
+    if (sqlite3_prepare(reg->db, query, -1, &stmt, NULL) == SQLITE_OK) {
         int r = sqlite3_step(stmt);
         switch (r) {
             case SQLITE_ROW:
-                *value = strdup(sqlite3_column_text(stmt, 0));
+                *value = strdup((const char*)sqlite3_column_text(stmt, 0));
                 sqlite3_finalize(stmt);
                 return 1;
             case SQLITE_DONE:
@@ -534,22 +537,26 @@
                 sqlite3_finalize(stmt);
                 return 0;
             default:
-                reg_sqlite_error(db, errPtr, query);
+                reg_sqlite_error(reg->db, errPtr, query);
                 sqlite3_finalize(stmt);
                 return 0;
         }
     } else {
-        reg_sqlite_error(db, errPtr, query);
+        reg_sqlite_error(reg->db, errPtr, query);
         return 0;
     }
 }
 
-int reg_entry_propset(sqlite3* db, reg_entry* entry, char* key, char* value,
-        reg_error* errPtr) {
+int reg_entry_propset(reg_registry* reg, reg_entry* entry, char* key,
+        char* value, reg_error* errPtr) {
     sqlite3_stmt* stmt;
-    char* query = sqlite3_mprintf("UPDATE registry.ports SET %q = '%q' "
-            "WHERE id=%lld", key, value, entry->id);
-    if (sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK) {
+    char* query;
+    if (!reg_test_writable(reg, errPtr)) {
+        return -1;
+    }
+    query = sqlite3_mprintf("UPDATE registry.ports SET %q = '%q' WHERE id=%lld",
+            key, value, entry->id);
+    if (sqlite3_prepare(reg->db, query, -1, &stmt, NULL) == SQLITE_OK) {
         int r = sqlite3_step(stmt);
         switch (r) {
             case SQLITE_DONE:
@@ -564,86 +571,63 @@
                         sqlite3_finalize(stmt);
                         return 0;
                     default:
-                        reg_sqlite_error(db, errPtr, query);
+                        reg_sqlite_error(reg->db, errPtr, query);
                         sqlite3_finalize(stmt);
                         return 0;
                 }
         }
     } else {
-        reg_sqlite_error(db, errPtr, query);
+        reg_sqlite_error(reg->db, errPtr, query);
         return 0;
     }
 }
 
-int reg_entry_map(sqlite3* db, reg_entry* entry, char** files, int file_count,
-        reg_error* errPtr) {
+int reg_entry_map(reg_registry* reg, reg_entry* entry, char** files,
+        int file_count, reg_error* errPtr) {
     sqlite3_stmt* stmt;
-    sqlite3_stmt* stmt2 = NULL;
     char* insert = "INSERT INTO registry.files (id, path) VALUES (?, ?)";
-    char* select = "SELECT registry.ports.name FROM registry.files "
-        "INNER JOIN registry.ports USING(id) WHERE registry.files.path=? AND "
-        "registry.ports.state = 'installed'";
-    begin_exclusive(db);
-    if ((sqlite3_prepare(db, insert, -1, &stmt, NULL) == SQLITE_OK)
-            && (sqlite3_bind_int64(stmt, 1, entry->id) == SQLITE_OK)
-            && (sqlite3_prepare(db, select, -1, &stmt2, NULL) == SQLITE_OK)) {
+    if (!reg_test_writable(reg, errPtr)) {
+        return -1;
+    }
+    if ((sqlite3_prepare(reg->db, insert, -1, &stmt, NULL) == SQLITE_OK)
+            && (sqlite3_bind_int64(stmt, 1, entry->id) == SQLITE_OK)) {
         int i;
         for (i=0; i<file_count; i++) {
-            if ((sqlite3_bind_text(stmt2, 1, files[i], -1, SQLITE_STATIC)
-                    == SQLITE_OK)
-                    && (sqlite3_step(stmt2) == SQLITE_ROW)) {
-                const char* port = sqlite3_column_text(stmt2, 0);
-                errPtr->code = "registry::already-owned";
-                errPtr->description = sqlite3_mprintf("file at path \"%s\" is "
-                        "already owned by port %s", files[i], port);
-                errPtr->free = (reg_error_destructor*)sqlite3_free;
-                sqlite3_finalize(stmt);
-                sqlite3_finalize(stmt2);
-                rollback_transaction(db);
-                return -1;
-            }
             if (sqlite3_bind_text(stmt, 2, files[i], -1, SQLITE_STATIC)
                     == SQLITE_OK) {
                 int r = sqlite3_step(stmt);
                 switch (r) {
                     case SQLITE_DONE:
                         sqlite3_reset(stmt);
-                        sqlite3_reset(stmt2);
                         continue;
                     default:
-                        reg_sqlite_error(db, errPtr, insert);
+                        reg_sqlite_error(reg->db, errPtr, insert);
                         sqlite3_finalize(stmt);
-                        sqlite3_finalize(stmt2);
-                        rollback_transaction(db);
                         return i;
                 }
             } else {
-                reg_sqlite_error(db, errPtr, insert);
+                reg_sqlite_error(reg->db, errPtr, insert);
                 sqlite3_finalize(stmt);
-                sqlite3_finalize(stmt2);
-                rollback_transaction(db);
                 return i;
             }
         }
         sqlite3_finalize(stmt);
-        sqlite3_finalize(stmt2);
-        commit_transaction(db);
         return file_count;
     } else {
-        reg_sqlite_error(db, errPtr, insert);
+        reg_sqlite_error(reg->db, errPtr, insert);
         sqlite3_finalize(stmt);
-        sqlite3_finalize(stmt2);
-        commit_transaction(db);
         return 0;
     }
 }
 
-int reg_entry_unmap(sqlite3* db, reg_entry* entry, char** files, int file_count,
-        reg_error* errPtr) {
+int reg_entry_unmap(reg_registry* reg, reg_entry* entry, char** files,
+        int file_count, reg_error* errPtr) {
     sqlite3_stmt* stmt;
     char* query = "DELETE FROM registry.files WHERE id=? AND path=?";
-    begin_exclusive(db);
-    if ((sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK)
+    if (!reg_test_writable(reg, errPtr)) {
+        return -1;
+    }
+    if ((sqlite3_prepare(reg->db, query, -1, &stmt, NULL) == SQLITE_OK)
             && (sqlite3_bind_int64(stmt, 1, entry->id) == SQLITE_OK)) {
         int i;
         for (i=0; i<file_count; i++) {
@@ -652,47 +636,42 @@
                 int r = sqlite3_step(stmt);
                 switch (r) {
                     case SQLITE_DONE:
-                        if (sqlite3_changes(db) == 0) {
+                        if (sqlite3_changes(reg->db) == 0) {
                             errPtr->code = "registry::not-owned";
                             errPtr->description = "this entry does not own the "
                                 "given file";
                             errPtr->free = NULL;
                             sqlite3_finalize(stmt);
-                            rollback_transaction(db);
                             return i;
                         } else {
                             sqlite3_reset(stmt);
                             continue;
                         }
                     default:
-                        reg_sqlite_error(db, errPtr, query);
+                        reg_sqlite_error(reg->db, errPtr, query);
                         sqlite3_finalize(stmt);
-                        rollback_transaction(db);
                         return i;
                 }
             } else {
-                reg_sqlite_error(db, errPtr, query);
+                reg_sqlite_error(reg->db, errPtr, query);
                 sqlite3_finalize(stmt);
-                rollback_transaction(db);
                 return i;
             }
         }
         sqlite3_finalize(stmt);
-        commit_transaction(db);
         return file_count;
     } else {
-        reg_sqlite_error(db, errPtr, query);
+        reg_sqlite_error(reg->db, errPtr, query);
         sqlite3_finalize(stmt);
-        commit_transaction(db);
         return 0;
     }
 }
 
-int reg_entry_files(sqlite3* db, reg_entry* entry, char*** files,
+int reg_entry_files(reg_registry* reg, reg_entry* entry, char*** files,
         reg_error* errPtr) {
     sqlite3_stmt* stmt;
     char* query = "SELECT path FROM registry.files WHERE id=? ORDER BY path";
-    if ((sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK)
+    if ((sqlite3_prepare(reg->db, query, -1, &stmt, NULL) == SQLITE_OK)
             && (sqlite3_bind_int64(stmt, 1, entry->id) == SQLITE_OK)) {
         char** result = malloc(10*sizeof(char*));
         int result_count = 0;
@@ -704,7 +683,7 @@
             r = sqlite3_step(stmt);
             switch (r) {
                 case SQLITE_ROW:
-                    element = strdup(sqlite3_column_text(stmt, 0));
+                    element = strdup((const char*)sqlite3_column_text(stmt, 0));
                     reg_listcat((void*)&result, &result_count, &result_space,
                             element);
                     break;
@@ -715,7 +694,7 @@
                         free(result[i]);
                     }
                     free(result);
-                    reg_sqlite_error(db, errPtr, query);
+                    reg_sqlite_error(reg->db, errPtr, query);
                     sqlite3_finalize(stmt);
                     return -1;
             }
@@ -724,20 +703,20 @@
         *files = result;
         return result_count;
     } else {
-        reg_sqlite_error(db, errPtr, query);
+        reg_sqlite_error(reg->db, errPtr, query);
         sqlite3_finalize(stmt);
         return -1;
     }
 }
 
-int reg_all_entries(sqlite3* db, reg_entry*** entries, reg_error* errPtr) {
+int reg_all_entries(reg_registry* reg, reg_entry*** entries, reg_error* errPtr){
     reg_entry* entry;
     void** results = malloc(10*sizeof(void*));
     int result_count = 0;
     int result_space = 10;
     sqlite3_stmt* stmt;
     char* query = "SELECT address FROM entries";
-    if (sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK) {
+    if (sqlite3_prepare(reg->db, query, -1, &stmt, NULL) == SQLITE_OK) {
         int r;
         do {
             r = sqlite3_step(stmt);
@@ -749,7 +728,7 @@
                 case SQLITE_DONE:
                     break;
                 default:
-                    reg_sqlite_error(db, errPtr, query);
+                    reg_sqlite_error(reg->db, errPtr, query);
                     free(results);
                     return -1;
             }

Modified: trunk/base/src/cregistry/entry.h
===================================================================
--- trunk/base/src/cregistry/entry.h	2007-08-06 20:13:00 UTC (rev 27525)
+++ trunk/base/src/cregistry/entry.h	2007-08-06 20:13:12 UTC (rev 27526)
@@ -48,38 +48,38 @@
     char* proc; /* name of Tcl proc, if using Tcl */
 } reg_entry;
 
-reg_entry* reg_entry_create(sqlite3* db, char* name, char* version,
+reg_entry* reg_entry_create(reg_registry* reg, char* name, char* version,
         char* revision, char* variants, char* epoch, reg_error* errPtr);
 
-reg_entry* reg_entry_open(sqlite3* db, char* name, char* version,
+reg_entry* reg_entry_open(reg_registry* reg, char* name, char* version,
         char* revision, char* variants, char* epoch, reg_error* errPtr);
 
-int reg_entry_delete(sqlite3* db, reg_entry* entry, reg_error* errPtr);
+int reg_entry_delete(reg_registry* reg, reg_entry* entry, reg_error* errPtr);
 
-int reg_entry_search(sqlite3* db, char** keys, char** vals, int key_count,
+int reg_entry_search(reg_registry* reg, char** keys, char** vals, int key_count,
         int strategy, reg_entry*** entries, reg_error* errPtr);
 
-int reg_entry_imaged(sqlite3* db, char* name, char* version, 
+int reg_entry_imaged(reg_registry* reg, char* name, char* version, 
         reg_entry*** entries, reg_error* errPtr);
-int reg_entry_installed(sqlite3* db, char* name, reg_entry*** entries,
+int reg_entry_installed(reg_registry* reg, char* name, reg_entry*** entries,
         reg_error* errPtr);
 
-int reg_entry_owner(sqlite3* db, char* path, reg_entry** entry,
+int reg_entry_owner(reg_registry* reg, char* path, reg_entry** entry,
         reg_error* errPtr);
 
-int reg_entry_propget(sqlite3* db, reg_entry* entry, char* key, char** value,
-        reg_error* errPtr);
-int reg_entry_propset(sqlite3* db, reg_entry* entry, char* key, char* value,
-        reg_error* errPtr);
+int reg_entry_propget(reg_registry* reg, reg_entry* entry, char* key,
+        char** value, reg_error* errPtr);
+int reg_entry_propset(reg_registry* reg, reg_entry* entry, char* key,
+        char* value, reg_error* errPtr);
 
-int reg_entry_map(sqlite3* db, reg_entry* entry, char** files, int file_count,
+int reg_entry_map(reg_registry* reg, reg_entry* entry, char** files, int file_count,
         reg_error* errPtr);
-int reg_entry_unmap(sqlite3* db, reg_entry* entry, char** files, int file_count,
+int reg_entry_unmap(reg_registry* reg, reg_entry* entry, char** files, int file_count,
         reg_error* errPtr);
 
-int reg_entry_files(sqlite3* db, reg_entry* entry, char*** files,
+int reg_entry_files(reg_registry* reg, reg_entry* entry, char*** files,
         reg_error* errPtr);
 
-int reg_all_entries(sqlite3* db, reg_entry*** entries, reg_error* errPtr);
+int reg_all_entries(reg_registry*, reg_entry*** entries, reg_error* errPtr);
 
 #endif /* _CENTRY_H */

Modified: trunk/base/src/cregistry/registry.c
===================================================================
--- trunk/base/src/cregistry/registry.c	2007-08-06 20:13:00 UTC (rev 27525)
+++ trunk/base/src/cregistry/registry.c	2007-08-06 20:13:12 UTC (rev 27526)
@@ -61,38 +61,73 @@
     }
 }
 
-int reg_open(sqlite3** dbPtr, reg_error* errPtr) {
-    if (sqlite3_open(NULL, dbPtr) == SQLITE_OK) {
-        if (init_db(*dbPtr, errPtr)) {
+/**
+ * Creates a new registry object. To start using a registry, one must first be
+ * attached with `reg_attach`.
+ *
+ * @param [out] regPtr address of the allocated registry
+ * @param [out] errPtr on error, a description of the error that occurred
+ * @return             true if success; false if failure
+ */
+int reg_open(reg_registry** regPtr, reg_error* errPtr) {
+    reg_registry* reg = malloc(sizeof(reg_registry));
+    if (sqlite3_open(NULL, &reg->db) == SQLITE_OK) {
+        if (init_db(reg->db, errPtr)) {
+            reg->status = reg_none;
+            *regPtr = reg;
             return 1;
-        } else {
-            sqlite3_close(*dbPtr);
-            *dbPtr = NULL;
         }
     } else {
-        reg_sqlite_error(*dbPtr, errPtr, NULL);
-        sqlite3_close(*dbPtr);
-        *dbPtr = NULL;
+        reg_sqlite_error(reg->db, errPtr, NULL);
     }
+    sqlite3_close(reg->db);
+    free(reg);
     return 0;
 }
 
-int reg_close(sqlite3* db, reg_error* errPtr) {
-    if (sqlite3_close((sqlite3*)db) == SQLITE_OK) {
+/**
+ * Closes a registry object. Will detach if necessary.
+ *
+ * @param [in] reg     the registry to close
+ * @param [out] errPtr on error, a description of the error that occurred
+ * @return             true if success; false if failure
+ */
+int reg_close(reg_registry* reg, reg_error* errPtr) {
+    if ((reg->status & reg_attached) && !reg_detach(reg, errPtr)) {
+        return 0;
+    }
+    if (sqlite3_close(reg->db) == SQLITE_OK) {
+        free(reg);
         return 1;
     } else {
         errPtr->code = "registry::not-closed";
         errPtr->description = sqlite3_mprintf("error: registry db not closed "
-                "correctly (%s)\n", sqlite3_errmsg((sqlite3*)db));
+                "correctly (%s)\n", sqlite3_errmsg(reg->db));
         errPtr->free = (reg_error_destructor*)sqlite3_free;
         return 0;
     }
 }
 
-int reg_attach(sqlite3* db, const char* path, reg_error* errPtr) {
+/**
+ * Attaches a registry database to the registry object. Prior to calling this,
+ * the registry object is not actually connected to the registry. This function
+ * attaches it so it can be queried and manipulated.
+ *
+ * @param [in] reg     the registry to attach to
+ * @param [in] path    path to the registry db on disk
+ * @param [out] errPtr on error, a description of the error that occurred
+ * @return             true if success; false if failure
+ */
+int reg_attach(reg_registry* reg, const char* path, reg_error* errPtr) {
     struct stat sb;
     int needsInit = 0; /* registry doesn't yet exist */
     int canWrite = 1; /* can write to this location */
+    if (reg->status & reg_attached) {
+        errPtr->code = "registry::already-attached";
+        errPtr->description = "a database is already attached to this registry";
+        errPtr->free = NULL;
+        return 0;
+    }
     if (stat(path, &sb) != 0) {
         if (errno == ENOENT) {
             needsInit = 1;
@@ -103,14 +138,15 @@
     if (!needsInit || canWrite) {
         sqlite3_stmt* stmt;
         char* query = sqlite3_mprintf("ATTACH DATABASE '%q' AS registry", path);
-        if ((sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK)
+        if ((sqlite3_prepare(reg->db, query, -1, &stmt, NULL) == SQLITE_OK)
                 && (sqlite3_step(stmt) == SQLITE_DONE)) {
             sqlite3_finalize(stmt);
-            if (!needsInit || (create_tables(db, errPtr))) {
+            if (!needsInit || (create_tables(reg->db, errPtr))) {
+                reg->status |= reg_attached;
                 return 1;
             }
         } else {
-            reg_sqlite_error(db, errPtr, query);
+            reg_sqlite_error(reg->db, errPtr, query);
             sqlite3_finalize(stmt);
         }
     } else {
@@ -121,14 +157,29 @@
     return 0;
 }
 
-int reg_detach(sqlite3* db, reg_error* errPtr) {
+/**
+ * Detaches a registry database from the registry object. This does some cleanup
+ * for an attached registry, then detaches it. Allocated `reg_entry` objects are
+ * deleted here.
+ *
+ * @param [in] reg     registry to detach from
+ * @param [out] errPtr on error, a description of the error that occurred
+ * @return             true if success; false if failure
+ */
+int reg_detach(reg_registry* reg, reg_error* errPtr) {
     sqlite3_stmt* stmt;
     char* query = "DETACH DATABASE registry";
-    if ((sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK)
+    if (!(reg->status & reg_attached)) {
+        errPtr->code = "registry::not-attached";
+        errPtr->description = "no database is attached to this registry";
+        errPtr->free = NULL;
+        return 0;
+    }
+    if ((sqlite3_prepare(reg->db, query, -1, &stmt, NULL) == SQLITE_OK)
             && (sqlite3_step(stmt) == SQLITE_DONE)) {
         sqlite3_finalize(stmt);
         query = "SELECT address FROM entries";
-        if (sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK) {
+        if (sqlite3_prepare(reg->db, query, -1, &stmt, NULL) == SQLITE_OK) {
             int r;
             reg_entry* entry;
             do {
@@ -140,28 +191,116 @@
                             free(entry->proc);
                         }
                         free(entry);
-                        /* reg_entry_free(db, entry); */
+                        /* reg_entry_free(reg->db, entry); */
                         break;
                     case SQLITE_DONE:
                         break;
                     default:
-                        reg_sqlite_error(db, errPtr, query);
+                        reg_sqlite_error(reg->db, errPtr, query);
                         return 0;
                 }
             } while (r != SQLITE_DONE);
         }
         sqlite3_finalize(stmt);
         query = "DELETE FROM entries";
-        if ((sqlite3_prepare(db, query, -1, &stmt, NULL) != SQLITE_OK)
+        if ((sqlite3_prepare(reg->db, query, -1, &stmt, NULL) != SQLITE_OK)
                 || (sqlite3_step(stmt) != SQLITE_DONE)) {
             sqlite3_finalize(stmt);
             return 0;
         }
+        sqlite3_finalize(stmt);
+        reg->status &= ~reg_attached;
         return 1;
     } else {
-        reg_sqlite_error(db, errPtr, query);
+        reg_sqlite_error(reg->db, errPtr, query);
         sqlite3_finalize(stmt);
         return 0;
     }
 }
 
+static int reg_start(reg_registry* reg, const char* query, reg_error* errPtr) {
+    if (reg->status & reg_transacting) {
+        errPtr->code = "registry::cant-start";
+        errPtr->description = "couldn't start transaction because a "
+            "transaction is already open";
+        errPtr->free = NULL;
+        return 0;
+    } else {
+        int result;
+        do {
+            result = sqlite3_exec(reg->db, query, NULL, NULL, NULL);
+            if (result == SQLITE_ERROR) {
+                reg_sqlite_error(reg->db, errPtr, NULL);
+                return 0;
+            }
+        } while (result != SQLITE_OK);
+        return 1;
+    }
+}
+
+int reg_start_read(reg_registry* reg, reg_error* errPtr) {
+    if (reg_start(reg, "BEGIN", errPtr)) {
+        reg->status |= reg_transacting;
+        return 1;
+    } else {
+        return 0;
+    }
+}
+
+int reg_start_write(reg_registry* reg, reg_error* errPtr) {
+    if (reg_start(reg, "BEGIN EXCLUSIVE", errPtr)) {
+        reg->status |= reg_transacting | reg_can_write;
+        return 1;
+    } else {
+        return 0;
+    }
+}
+
+static int reg_end(reg_registry* reg, const char* query, reg_error* errPtr) {
+    if (!(reg->status & reg_transacting)) {
+        errPtr->code = "registry::cant-end";
+        errPtr->description = "couldn't end transaction because no transaction "
+            "is open";
+        errPtr->free = NULL;
+        return 0;
+    } else {
+        int result;
+        do {
+            result = sqlite3_exec(reg->db, query, NULL, NULL, NULL);
+            if (result == SQLITE_ERROR) {
+                reg_sqlite_error(reg->db, errPtr, NULL);
+                return 0;
+            }
+        } while (result != SQLITE_OK);
+        return 1;
+    }
+}
+
+int reg_commit(reg_registry* reg, reg_error* errPtr) {
+    if (reg_end(reg, "COMMIT", errPtr)) {
+        reg->status &= ~(reg_transacting | reg_can_write);
+        return 1;
+    } else {
+        return 0;
+    }
+}
+
+int reg_rollback(reg_registry* reg, reg_error* errPtr) {
+    if (reg_end(reg, "ROLLBACK", errPtr)) {
+        reg->status &= ~(reg_transacting | reg_can_write);
+        return 1;
+    } else {
+        return 0;
+    }
+}
+
+int reg_test_writable(reg_registry* reg, reg_error* errPtr) {
+    if (reg->status & reg_can_write) {
+        return 1;
+    } else {
+        errPtr->code = "registry::no-write";
+        errPtr->description = "a write transaction has not been started";
+        errPtr->free = NULL;
+        return 0;
+    }
+}

Modified: trunk/base/src/cregistry/registry.h
===================================================================
--- trunk/base/src/cregistry/registry.h	2007-08-06 20:13:00 UTC (rev 27525)
+++ trunk/base/src/cregistry/registry.h	2007-08-06 20:13:12 UTC (rev 27526)
@@ -49,10 +49,29 @@
         reg_error* errPtr);
 typedef void (free_function)(void* userdata, void* item);
 
-int reg_open(sqlite3** dbPtr, reg_error* errPtr);
-int reg_close(sqlite3* db, reg_error* errPtr);
+enum {
+    reg_none = 0,
+    reg_attached = 1,
+    reg_transacting = 2,
+    reg_can_write = 4
+};
 
-int reg_attach(sqlite3* db, const char* path, reg_error* errPtr);
-int reg_detach(sqlite3* db, reg_error* errPtr);
+typedef struct {
+    sqlite3* db;
+    int status;
+} reg_registry;
 
+int reg_open(reg_registry** regPtr, reg_error* errPtr);
+int reg_close(reg_registry* reg, reg_error* errPtr);
+
+int reg_attach(reg_registry* reg, const char* path, reg_error* errPtr);
+int reg_detach(reg_registry* reg, reg_error* errPtr);
+
+int reg_start_read(reg_registry* reg, reg_error* errPtr);
+int reg_start_write(reg_registry* reg, reg_error* errPtr);
+int reg_commit(reg_registry* reg, reg_error* errPtr);
+int reg_rollback(reg_registry* reg, reg_error* errPtr);
+
+int reg_test_writable(reg_registry* reg, reg_error* errPtr);
+
 #endif /* _CREG_H */

Modified: trunk/base/src/cregistry/sql.c
===================================================================
--- trunk/base/src/cregistry/sql.c	2007-08-06 20:13:00 UTC (rev 27525)
+++ trunk/base/src/cregistry/sql.c	2007-08-06 20:13:12 UTC (rev 27526)
@@ -61,47 +61,6 @@
 }
 
 /**
- * Acquires an exclusive lock on the database.
- */
-void begin_exclusive(sqlite3* db) {
-    int status;
-    do {
-        status = sqlite3_exec(db, "BEGIN EXCLUSIVE", NULL, NULL, NULL);
-    } while (status != SQLITE_OK);
-}
-
-/**
- * Acquires a shared lock on the database.
- */
-void begin_shared(sqlite3* db) {
-    int status;
-    do {
-        status = sqlite3_exec(db, "BEGIN", NULL, NULL, NULL);
-    } while (status != SQLITE_OK);
-}
-
-/**
- * Releases a shared or exclusive lock on the database and rolls back changes
- */
-void rollback_transaction(sqlite3* db) {
-    int status;
-    char* err;
-    do {
-        status = sqlite3_exec(db, "ROLLBACK", NULL, NULL, &err);
-    } while (status != SQLITE_OK);
-}
-
-/**
- * Releases a shared or exclusive lock on the database and commits changes
- */
-void commit_transaction(sqlite3* db) {
-    int status;
-    do {
-        status = sqlite3_exec(db, "COMMIT", NULL, NULL, NULL);
-    } while (status != SQLITE_OK);
-}
-
-/**
  * REGEXP function for sqlite3.
  *
  * Takes two arguments; the first is the value and the second the pattern. If
@@ -112,8 +71,8 @@
  */
 static void sql_regexp(sqlite3_context* context, int argc UNUSED,
         sqlite3_value** argv) {
-    const char* value = sqlite3_value_text(argv[0]);
-    const char* pattern = sqlite3_value_text(argv[1]);
+    const char* value = (const char*)sqlite3_value_text(argv[0]);
+    const char* pattern = (const char*)sqlite3_value_text(argv[1]);
     switch (Tcl_RegExpMatch(NULL, value, pattern)) {
         case 0:
             sqlite3_result_int(context, 0);
@@ -298,7 +257,7 @@
         "CREATE TABLE registry.files (id, path, mtime)",
         "CREATE INDEX registry.file_port ON files (id)",
 
-        "END",
+        "COMMIT",
         NULL
     };
     return do_queries(db, queries, errPtr);
@@ -325,7 +284,7 @@
         /* entry addresses */
         "CREATE TEMPORARY TABLE entries (id, address)",
 
-        "END",
+        "COMMIT",
         NULL
     };
 

Modified: trunk/base/src/cregistry/sql.h
===================================================================
--- trunk/base/src/cregistry/sql.h	2007-08-06 20:13:00 UTC (rev 27525)
+++ trunk/base/src/cregistry/sql.h	2007-08-06 20:13:12 UTC (rev 27526)
@@ -36,11 +36,6 @@
 
 #include <cregistry/registry.h>
 
-void begin_exclusive(sqlite3* db);
-void begin_shared(sqlite3* db);
-void rollback_transaction(sqlite3* db);
-void commit_transaction(sqlite3* db);
-
 int create_tables(sqlite3* db, reg_error* errPtr);
 int init_db(sqlite3* db, reg_error* errPtr);
 

Modified: trunk/base/src/registry2.0/entry.c
===================================================================
--- trunk/base/src/registry2.0/entry.c	2007-08-06 20:13:00 UTC (rev 27525)
+++ trunk/base/src/registry2.0/entry.c	2007-08-06 20:13:12 UTC (rev 27526)
@@ -134,12 +134,12 @@
  * and it's called with all of them there.
  */
 static int entry_create(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]) {
-    sqlite3* db = registry_db(interp, 1);
+    reg_registry* reg = registry_for(interp, reg_attached);
     if (objc != 7) {
         Tcl_WrongNumArgs(interp, 2, objv, "name version revision variants "
                 "epoch");
         return TCL_ERROR;
-    } else if (db == NULL) {
+    } else if (reg == NULL) {
         return TCL_ERROR;
     } else {
         char* name = Tcl_GetString(objv[2]);
@@ -148,7 +148,7 @@
         char* variants = Tcl_GetString(objv[5]);
         char* epoch = Tcl_GetString(objv[6]);
         reg_error error;
-        reg_entry* entry = reg_entry_create(db, name, version, revision,
+        reg_entry* entry = reg_entry_create(reg, name, version, revision,
                 variants, epoch, &error);
         if (entry != NULL) {
             Tcl_Obj* result;
@@ -167,17 +167,17 @@
  * Deletes an entry from the registry (then closes it).
  */
 static int entry_delete(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]) {
-    sqlite3* db = registry_db(interp, 1);
+    reg_registry* reg = registry_for(interp, reg_attached);
     if (objc != 3) {
         Tcl_WrongNumArgs(interp, 1, objv, "delete entry");
         return TCL_ERROR;
-    } if (db == NULL) {
+    } if (reg == NULL) {
         return TCL_ERROR;
     } else {
         reg_entry* entry;
         reg_error error;
         if (obj_to_entry(interp, &entry, objv[2], &error)) {
-            if (reg_entry_delete(db, entry, &error)) {
+            if (reg_entry_delete(reg, entry, &error)) {
                 Tcl_DeleteCommand(interp, Tcl_GetString(objv[2]));
                 return TCL_OK;
             }
@@ -192,12 +192,12 @@
  * Opens an entry matching the given parameters.
  */
 static int entry_open(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]) {
-    sqlite3* db = registry_db(interp, 1);
+    reg_registry* reg = registry_for(interp, reg_attached);
     if (objc != 7) {
         Tcl_WrongNumArgs(interp, 1, objv, "open portname version revision "
                 "variants epoch");
         return TCL_ERROR;
-    } else if (db == NULL) {
+    } else if (reg == NULL) {
         return TCL_ERROR;
     } else {
         char* name = Tcl_GetString(objv[2]);
@@ -206,8 +206,8 @@
         char* variants = Tcl_GetString(objv[5]);
         char* epoch = Tcl_GetString(objv[6]);
         reg_error error;
-        reg_entry* entry = reg_entry_open(db, name, version, revision, variants,
-                epoch, &error);
+        reg_entry* entry = reg_entry_open(reg, name, version, revision,
+                variants, epoch, &error);
         if (entry != NULL) {
             Tcl_Obj* result;
             if (entry_to_obj(interp, &result, entry, &error)) {
@@ -252,11 +252,11 @@
  */
 static int entry_search(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]) {
     int i;
-    sqlite3* db = registry_db(interp, 1);
+    reg_registry* reg = registry_for(interp, reg_attached);
     if (objc % 2 == 1) {
         Tcl_WrongNumArgs(interp, 2, objv, "search ?key value ...?");
         return TCL_ERROR;
-    } else if (db == NULL) {
+    } else if (reg == NULL) {
         return TCL_ERROR;
     } else {
         char** keys;
@@ -279,7 +279,7 @@
             keys[i] = Tcl_GetString(objv[2*i+2]);
             vals[i] = Tcl_GetString(objv[2*i+3]);
         }
-        entry_count = reg_entry_search(db, keys, vals, key_count,
+        entry_count = reg_entry_search(reg, keys, vals, key_count,
                 reg_strategy_equal, &entries, &error);
         if (entry_count >= 0) {
             Tcl_Obj* resultObj;
@@ -333,17 +333,18 @@
  * TODO: add more arguments (epoch, revision, variants), maybe
  */
 static int entry_imaged(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]) {
-    sqlite3* db = registry_db(interp, 1);
+    reg_registry* reg = registry_for(interp, reg_attached);
     if (objc > 4) {
         Tcl_WrongNumArgs(interp, 2, objv, "?name? ?version?");
         return TCL_ERROR;
-    } else if (db == NULL) {
+    } else if (reg == NULL) {
         return TCL_ERROR;
     } else {
         char* name = (objc >= 3) ? Tcl_GetString(objv[2]) : NULL;
         char* version = (objc == 4) ? Tcl_GetString(objv[3]) : NULL;
         reg_entry** entries;
         reg_error error;
+        int entry_count;
         /* name or version of "" means not specified */
         if (name != NULL && *name == '\0') {
             name = NULL;
@@ -351,13 +352,11 @@
         if (version != NULL && *version == '\0') {
             version = NULL;
         }
-        int entry_count = reg_entry_imaged(db, name, version, &entries,
-                &error);
+        entry_count = reg_entry_imaged(reg, name, version, &entries, &error);
         if (entry_count >= 0) {
             Tcl_Obj* resultObj;
             Tcl_Obj** objs;
-            list_entry_to_obj(interp, &objs, entries, entry_count,
-                        &error);
+            list_entry_to_obj(interp, &objs, entries, entry_count, &error);
             resultObj = Tcl_NewListObj(entry_count, objs);
             Tcl_SetObjResult(interp, resultObj);
             free(entries);
@@ -379,27 +378,26 @@
  * satisfying a dependency.
  */
 static int entry_installed(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]){
-    sqlite3* db = registry_db(interp, 1);
+    reg_registry* reg = registry_for(interp, reg_attached);
     if (objc > 3) {
         Tcl_WrongNumArgs(interp, 2, objv, "?name?");
         return TCL_ERROR;
-    } else if (db == NULL) {
+    } else if (reg == NULL) {
         return TCL_ERROR;
     } else {
         char* name = (objc == 3) ? Tcl_GetString(objv[2]) : NULL;
         reg_entry** entries;
         reg_error error;
+        int entry_count;
         /* name of "" means not specified */
         if (name != NULL && *name == '\0') {
             name = NULL;
         }
-        int entry_count = reg_entry_installed(db, name, &entries,
-                &error);
+        entry_count = reg_entry_installed(reg, name, &entries, &error);
         if (entry_count >= 0) {
             Tcl_Obj* resultObj;
             Tcl_Obj** objs;
-            list_entry_to_obj(interp, &objs, entries, entry_count,
-                        &error);
+            list_entry_to_obj(interp, &objs, entries, entry_count, &error);
             resultObj = Tcl_NewListObj(entry_count, objs);
             Tcl_SetObjResult(interp, resultObj);
             free(entries);
@@ -413,17 +411,17 @@
 /**
  */
 static int entry_owner(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]) {
-    sqlite3* db = registry_db(interp, 1);
+    reg_registry* reg = registry_for(interp, reg_attached);
     if (objc != 3) {
         Tcl_WrongNumArgs(interp, 2, objv, "path");
         return TCL_ERROR;
-    } else if (db == NULL) {
+    } else if (reg == NULL) {
         return TCL_ERROR;
     } else {
         char* path = Tcl_GetString(objv[2]);
         reg_entry* entry;
         reg_error error;
-        if (reg_entry_owner(db, path, &entry, &error)) {
+        if (reg_entry_owner(reg, path, &entry, &error)) {
             if (entry == NULL) {
                 return TCL_OK;
             } else {

Modified: trunk/base/src/registry2.0/entryobj.c
===================================================================
--- trunk/base/src/registry2.0/entryobj.c	2007-08-06 20:13:00 UTC (rev 27525)
+++ trunk/base/src/registry2.0/entryobj.c	2007-08-06 20:13:12 UTC (rev 27526)
@@ -59,21 +59,22 @@
 static int entry_obj_prop(Tcl_Interp* interp, reg_entry* entry, int objc,
         Tcl_Obj* CONST objv[]) {
     int index;
-    sqlite3* db = registry_db(interp, 1);
     if (objc > 3) {
         Tcl_WrongNumArgs(interp, 2, objv, "?value?");
         return TCL_ERROR;
-    } else if (db == NULL) {
-        return TCL_ERROR;
     }
     if (objc == 2) {
         /* ${entry} prop; return the current value */
+        reg_registry* reg = registry_for(interp, reg_attached);
+        if (reg == NULL) {
+            return TCL_ERROR;
+        }
         if (Tcl_GetIndexFromObj(interp, objv[1], entry_props, "prop", 0, &index)
                 == TCL_OK) {
             char* key = Tcl_GetString(objv[1]);
             char* value;
             reg_error error;
-            if (reg_entry_propget(db, entry, key, &value, &error)) {
+            if (reg_entry_propget(reg, entry, key, &value, &error)) {
                 Tcl_Obj* result = Tcl_NewStringObj(value, -1);
                 Tcl_SetObjResult(interp, result);
                 return TCL_OK;
@@ -83,12 +84,16 @@
         return TCL_ERROR;
     } else {
         /* ${entry} prop name value; set a new value */
+        reg_registry* reg = registry_for(interp, reg_attached | reg_can_write);
+        if (reg == NULL) {
+            return TCL_ERROR;
+        }
         if (Tcl_GetIndexFromObj(interp, objv[1], entry_props, "prop", 0, &index)
                 == TCL_OK) {
             char* key = Tcl_GetString(objv[1]);
             char* value = Tcl_GetString(objv[2]);
             reg_error error;
-            if (reg_entry_propset(db, entry, key, value, &error)) {
+            if (reg_entry_propset(reg, entry, key, value, &error)) {
                 return TCL_OK;
             }
             return registry_failed(interp, &error);
@@ -98,7 +103,7 @@
 }
 
 /*
- * ${entry} map ?file ...?
+ * ${entry} map file-list
  *
  * Maps the listed files to the port represented by ${entry}. This will throw an
  * error if a file is mapped to an already-existing file, but not a very
@@ -108,14 +113,22 @@
  */
 static int entry_obj_map(Tcl_Interp* interp, reg_entry* entry, int objc,
         Tcl_Obj* CONST objv[]) {
-    sqlite3* db = registry_db(interp, 1);
-    if (db == NULL) {
+    reg_registry* reg = registry_for(interp, reg_attached);
+    if (objc != 3) {
+        Tcl_WrongNumArgs(interp, 1, objv, "map file-list");
         return TCL_ERROR;
+    } else if (reg == NULL) {
+        return TCL_ERROR;
     } else {
         char** files;
         reg_error error;
-        if (list_obj_to_string(&files, objv+2, objc-2, &error)) {
-            if (reg_entry_map(db, entry, files, objc-2, &error) == objc-2) {
+        Tcl_Obj** listv;
+        int listc;
+        if (Tcl_ListObjGetElements(interp, objv[2], &listc, &listv) != TCL_OK) {
+            return TCL_ERROR;
+        }
+        if (list_obj_to_string(&files, listv, listc, &error)) {
+            if (reg_entry_map(reg, entry, files, listc, &error) == listc) {
                 return TCL_OK;
             }
         }
@@ -124,21 +137,29 @@
 }
 
 /*
- * ${entry} unmap ?file ...?
+ * ${entry} unmap file-list
  *
  * Unmaps the listed files from the given port. Will throw an error if a file
  * that is not mapped to the port is attempted to be unmapped.
  */
 static int entry_obj_unmap(Tcl_Interp* interp, reg_entry* entry, int objc,
         Tcl_Obj* CONST objv[]) {
-    sqlite3* db = registry_db(interp, 1);
-    if (db == NULL) {
+    reg_registry* reg = registry_for(interp, reg_attached);
+    if (objc != 3) {
+        Tcl_WrongNumArgs(interp, 1, objv, "map file-list");
         return TCL_ERROR;
+    } else if (reg == NULL) {
+        return TCL_ERROR;
     } else {
         char** files;
         reg_error error;
-        if (list_obj_to_string(&files, objv+2, objc-2, &error)) {
-            if (reg_entry_unmap(db, entry, files, objc-2, &error) == objc-2) {
+        Tcl_Obj** listv;
+        int listc;
+        if (Tcl_ListObjGetElements(interp, objv[2], &listc, &listv) != TCL_OK) {
+            return TCL_ERROR;
+        }
+        if (list_obj_to_string(&files, listv, listc, &error)) {
+            if (reg_entry_unmap(reg, entry, files, listc, &error) == listc) {
                 return TCL_OK;
             }
         }
@@ -148,26 +169,26 @@
 
 static int entry_obj_files(Tcl_Interp* interp, reg_entry* entry, int objc,
         Tcl_Obj* CONST objv[]) {
-    sqlite3* db = registry_db(interp, 1);
+    reg_registry* reg = registry_for(interp, reg_attached);
     if (objc != 2) {
         Tcl_WrongNumArgs(interp, 1, objv, "files");
         return TCL_ERROR;
-    } else if (db == NULL) {
+    } else if (reg == NULL) {
         return TCL_ERROR;
     } else {
         char** files;
         reg_error error;
-        int file_count = reg_entry_files(db, entry, &files, &error);
+        int file_count = reg_entry_files(reg, entry, &files, &error);
         if (file_count >= 0) {
             int i;
             Tcl_Obj** objs;
             if (list_string_to_obj(&objs, files, file_count, &error)) {
+                Tcl_Obj* result = Tcl_NewListObj(file_count, objs);
+                Tcl_SetObjResult(interp, result);
                 for (i=0; i<file_count; i++) {
                     free(files[i]);
                 }
                 free(files);
-                Tcl_Obj* result = Tcl_NewListObj(file_count, objs);
-                Tcl_SetObjResult(interp, result);
                 return TCL_OK;
             }
             for (i=0; i<file_count; i++) {

Modified: trunk/base/src/registry2.0/registry.c
===================================================================
--- trunk/base/src/registry2.0/registry.c	2007-08-06 20:13:00 UTC (rev 27525)
+++ trunk/base/src/registry2.0/registry.c	2007-08-06 20:13:12 UTC (rev 27526)
@@ -33,7 +33,6 @@
 #include <stdio.h>
 #include <unistd.h>
 #include <tcl.h>
-#include <sqlite3.h>
 
 #include <cregistry/registry.h>
 #include <cregistry/entry.h>
@@ -51,9 +50,10 @@
     return TCL_ERROR;
 }
 
-int registry_tcl_detach(Tcl_Interp* interp, sqlite3* db, reg_error* errPtr) {
+int registry_tcl_detach(Tcl_Interp* interp, reg_registry* reg,
+        reg_error* errPtr) {
     reg_entry** entries;
-    int entry_count = reg_all_entries(db, &entries, errPtr);
+    int entry_count = reg_all_entries(reg, &entries, errPtr);
     if (entry_count >= 0) {
         int i;
         for (i=0; i<entry_count; i++) {
@@ -61,8 +61,7 @@
                 Tcl_DeleteCommand(interp, entries[i]->proc);
             }
         }
-        if (reg_detach(db, errPtr)) {
-            Tcl_SetAssocData(interp, "registry::attached", NULL, (void*)0);
+        if (reg_detach(reg, errPtr)) {
             return 1;
         }
     }
@@ -73,21 +72,21 @@
  * Deletes the sqlite3 DB associated with interp.
  *
  * This function will close an interp's associated DB, although there doesn't
- * seem to be a way of verifying that it happened properly. This will be a
- * problem if we get lazy and forget to finalize a sqlite3_stmt somewhere, so
- * this function will be noisy and complain if we do.
+ * seem to be a way of ensuring that it happened properly. This will be a
+ * problem if we get lazy and forget to finish a sqlite3_stmt somewhere, so this
+ * function will be noisy and complain if we do.
  *
  * Then it will leak memory :(
  */
-static void delete_db(ClientData db, Tcl_Interp* interp) {
+static void delete_reg(ClientData reg, Tcl_Interp* interp UNUSED) {
     reg_error error;
-    if (Tcl_GetAssocData(interp, "registry::attached", NULL)) {
-        if (!registry_tcl_detach(interp, (sqlite3*)db, &error)) {
+    if (((reg_registry*)reg)->status & reg_attached) {
+        if (!registry_tcl_detach(interp, (reg_registry*)reg, &error)) {
             fprintf(stderr, error.description);
             reg_error_destruct(&error);
         }
     }
-    if (!reg_close((sqlite3*)db, &error)) {
+    if (!reg_close((reg_registry*)reg, &error)) {
         fprintf(stderr, error.description);
         reg_error_destruct(&error);
     }
@@ -108,25 +107,29 @@
  *
  * This function sets its own Tcl result.
  */
-sqlite3* registry_db(Tcl_Interp* interp, int attached) {
-    sqlite3* db = Tcl_GetAssocData(interp, "registry::db", NULL);
-    if (db == NULL) {
+reg_registry* registry_for(Tcl_Interp* interp, int status) {
+    reg_registry* reg = Tcl_GetAssocData(interp, "registry::reg", NULL);
+    if (reg == NULL) {
         reg_error error;
-        if (reg_open(&db, &error)) {
-            Tcl_SetAssocData(interp, "registry::db", delete_db, db);
+        if (reg_open(&reg, &error)) {
+            Tcl_SetAssocData(interp, "registry::reg", delete_reg, reg);
         } else {
             registry_failed(interp, &error);
             return NULL;
         }
     }
-    if (attached) {
-        if (!Tcl_GetAssocData(interp, "registry::attached", NULL)) {
+    if ((reg->status & status) != status) {
+        if (status & reg_can_write) {
+            Tcl_SetErrorCode(interp, "registry::no-write", NULL);
+            Tcl_SetResult(interp, "a write transaction has not been started",
+                    TCL_STATIC);
+        } else {
             Tcl_SetErrorCode(interp, "registry::not-open", NULL);
             Tcl_SetResult(interp, "registry is not open", TCL_STATIC);
-            db = NULL;
         }
+        reg = NULL;
     }
-    return db;
+    return reg;
 }
 
 static int registry_open(ClientData clientData UNUSED, Tcl_Interp* interp,
@@ -136,11 +139,11 @@
         return TCL_ERROR;
     } else {
         char* path = Tcl_GetString(objv[1]);
-        sqlite3* db = registry_db(interp, 0);
+        reg_registry* reg = registry_for(interp, 0);
         reg_error error;
-        if (reg_attach(db, path, &error)) {
-            Tcl_SetAssocData(interp, "registry::attached", NULL,
-                    (void*)1);
+        if (reg == NULL) {
+            return TCL_ERROR;
+        } else if (reg_attach(reg, path, &error)) {
             return TCL_OK;
         } else {
             return registry_failed(interp, &error);
@@ -154,12 +157,12 @@
         Tcl_WrongNumArgs(interp, 1, objv, NULL);
         return TCL_ERROR;
     } else {
-        sqlite3* db = registry_db(interp, 1);
-        if (db == NULL) {
+        reg_registry* reg = registry_for(interp, reg_attached);
+        if (reg == NULL) {
             return TCL_ERROR;
         } else {
             reg_error error;
-            if (registry_tcl_detach(interp, db, &error)) {
+            if (registry_tcl_detach(interp, reg, &error)) {
                 return TCL_OK;
             }
             return registry_failed(interp, &error);
@@ -168,6 +171,78 @@
     return TCL_ERROR;
 }
 
+static int registry_read(ClientData clientData UNUSED, Tcl_Interp* interp,
+        int objc, Tcl_Obj* CONST objv[]) {
+    if (objc != 2) {
+        Tcl_WrongNumArgs(interp, 1, objv, "command");
+        return TCL_ERROR;
+    } else {
+        reg_registry* reg = registry_for(interp, reg_attached);
+        if (reg == NULL) {
+            return TCL_ERROR;
+        } else {
+            reg_error error;
+            if (reg_start_read(reg, &error)) {
+                int status = Tcl_EvalObjEx(interp, objv[1], 0);
+                switch (status) {
+                    case TCL_OK:
+                        if (reg_commit(reg, &error)) {
+                            return TCL_OK;
+                        }
+                        break;
+                    case TCL_BREAK:
+                        if (reg_rollback(reg, &error)) {
+                            return TCL_OK;
+                        }
+                        break;
+                    default:
+                        if (reg_rollback(reg, &error)) {
+                            return status;
+                        }
+                        break;
+                }
+            }
+            return registry_failed(interp, &error);
+        }
+    }
+}
+
+static int registry_write(ClientData clientData UNUSED, Tcl_Interp* interp,
+        int objc, Tcl_Obj* CONST objv[]) {
+    if (objc != 2) {
+        Tcl_WrongNumArgs(interp, 1, objv, "command");
+        return TCL_ERROR;
+    } else {
+        reg_registry* reg = registry_for(interp, reg_attached);
+        if (reg == NULL) {
+            return TCL_ERROR;
+        } else {
+            reg_error error;
+            if (reg_start_write(reg, &error)) {
+                int status = Tcl_EvalObjEx(interp, objv[1], 0);
+                switch (status) {
+                    case TCL_OK:
+                        if (reg_commit(reg, &error)) {
+                            return TCL_OK;
+                        }
+                        break;
+                    case TCL_BREAK:
+                        if (reg_rollback(reg, &error)) {
+                            return TCL_OK;
+                        }
+                        break;
+                    default:
+                        if (reg_rollback(reg, &error)) {
+                            return status;
+                        }
+                        break;
+                }
+            }
+            return registry_failed(interp, &error);
+        }
+    }
+}
+
 /**
  * Initializer for the registry lib.
  *
@@ -178,10 +253,10 @@
     if (Tcl_InitStubs(interp, "8.3", 0) == NULL) {
         return TCL_ERROR;
     }
-    Tcl_CreateObjCommand(interp, "registry::open", registry_open, NULL,
-            NULL);
-    Tcl_CreateObjCommand(interp, "registry::close", registry_close, NULL,
-            NULL);
+    Tcl_CreateObjCommand(interp, "registry::open", registry_open, NULL, NULL);
+    Tcl_CreateObjCommand(interp, "registry::close", registry_close, NULL, NULL);
+    Tcl_CreateObjCommand(interp, "registry::read", registry_read, NULL, NULL);
+    Tcl_CreateObjCommand(interp, "registry::write", registry_write, NULL, NULL);
     /* Tcl_CreateObjCommand(interp, "registry::graph", GraphCmd, NULL, NULL); */
     /* Tcl_CreateObjCommand(interp, "registry::item", item_cmd, NULL, NULL); */
     Tcl_CreateObjCommand(interp, "registry::entry", entry_cmd, NULL, NULL);

Modified: trunk/base/src/registry2.0/registry.h
===================================================================
--- trunk/base/src/registry2.0/registry.h	2007-08-06 20:13:00 UTC (rev 27525)
+++ trunk/base/src/registry2.0/registry.h	2007-08-06 20:13:12 UTC (rev 27526)
@@ -36,7 +36,7 @@
 #include <sqlite3.h>
 #include <cregistry/entry.h>
 
-sqlite3* registry_db(Tcl_Interp* interp, int attached);
+reg_registry* registry_for(Tcl_Interp* interp, int status);
 int registry_failed(Tcl_Interp* interp, reg_error* errPtr);
 
 #endif /* _REGISTRY_H */

Modified: trunk/base/src/registry2.0/tests/common.tcl
===================================================================
--- trunk/base/src/registry2.0/tests/common.tcl	2007-08-06 20:13:00 UTC (rev 27525)
+++ trunk/base/src/registry2.0/tests/common.tcl	2007-08-06 20:13:12 UTC (rev 27526)
@@ -35,7 +35,7 @@
 
 proc test_throws {statement error} {
     uplevel 1 "\
-        puts -nonewline {checking if $statement throws $error... }
+        puts -nonewline {checking if \[$statement\] throws $error... }
         if {\[catch {$statement} error\]} { \n\
             if {\$::errorCode == {$error}} {
                 puts yes

Modified: trunk/base/src/registry2.0/tests/entry.tcl
===================================================================
--- trunk/base/src/registry2.0/tests/entry.tcl	2007-08-06 20:13:00 UTC (rev 27525)
+++ trunk/base/src/registry2.0/tests/entry.tcl	2007-08-06 20:13:12 UTC (rev 27526)
@@ -5,35 +5,46 @@
 proc main {pextlibname} {
     load $pextlibname
 
-	file delete -force test.db
+	file delete [glob -nocomplain test.db*]
 
     # can't use registry before it's opened
-    test_throws {registry::entry search} registry::not-open
+    test_throws {registry::write {}} registry::not-open
     registry::open test.db
 
-    # create some (somewhat contrived) ports to play with
-    set vim1 [registry::entry create vim 7.1.000 0 {multibyte +} 0]
-    set vim2 [registry::entry create vim 7.1.002 0 {} 0]
-    set vim3 [registry::entry create vim 7.1.002 0 {multibyte +} 0]
-    set zlib [registry::entry create zlib 1.2.3 1 {} 0]
-    set pcre [registry::entry create pcre 7.1 1 {utf8 +} 0]
+    test_throws {registry::entry create vim 7.1.000 0 {multibyte +} 0} \
+        registry::no-write
 
-    # check that their properties can be set
-    $vim1 state imaged
-    $vim2 state imaged
-    $vim3 state installed
-    $zlib state installed
-    $pcre state imaged
+    # write transaction
+    registry::write {
 
+        # create some (somewhat contrived) ports to play with
+        set vim1 [registry::entry create vim 7.1.000 0 {multibyte +} 0]
+        set vim2 [registry::entry create vim 7.1.002 0 {} 0]
+        set vim3 [registry::entry create vim 7.1.002 0 {multibyte +} 0]
+        set zlib [registry::entry create zlib 1.2.3 1 {} 0]
+        set pcre [registry::entry create pcre 7.1 1 {utf8 +} 0]
+
+        # check that their properties can be set
+        $vim1 state imaged
+        $vim2 state imaged
+        $vim3 state installed
+        $zlib state installed
+        $pcre state imaged
+
+    }
+
     # check that their properties can be retrieved
-    test_equal {[$vim1 name]} vim
-    test_equal {[$vim2 epoch]} 0
-    test_equal {[$vim3 version]} 7.1.002
-    test_equal {[$zlib revision]} 1
-    test_equal {[$pcre variants]} {utf8 +}
+    # (also try a read transaction)
+    registry::read {
+        test_equal {[$vim1 name]} vim
+        test_equal {[$vim2 epoch]} 0
+        test_equal {[$vim3 version]} 7.1.002
+        test_equal {[$zlib revision]} 1
+        test_equal {[$pcre variants]} {utf8 +}
     
-    set imaged [registry::entry imaged]
-    set installed [registry::entry installed]
+        set imaged [registry::entry imaged]
+        set installed [registry::entry installed]
+    }
 
     # check that imaged and installed give correct results
     # have to sort these because their orders aren't defined
@@ -47,30 +58,44 @@
     test_equal {[lsort $vim71002]} {[lsort "$vim2 $vim3"]}
 
     # try mapping files and checking their owners
-    $vim3 map
-    $vim3 map /opt/local/bin/vim
-    $vim3 map /opt/local/bin/vimdiff /opt/local/bin/vimtutor
-    test_equal {[registry::entry owner /opt/local/bin/vimtutor]} {$vim3}
-    test_equal {[registry::entry owner /opt/local/bin/emacs]} {}
+    registry::write {
+        $vim3 map [list /opt/local/bin/vim]
+        $vim3 map [list /opt/local/bin/vimdiff /opt/local/bin/vimtutor]
+        test_equal {[registry::entry owner /opt/local/bin/vimtutor]} {$vim3}
+        test_equal {[registry::entry owner /opt/local/bin/emacs]} {}
 
-    test_equal {[$vim3 files]} {/opt/local/bin/vim /opt/local/bin/vimdiff /opt/local/bin/vimtutor}
-    test_equal {[$zlib files]} {}
+        # don't have to sort because order is defined as alpha
+        test_equal {[$vim3 files]} {[list /opt/local/bin/vim \
+            /opt/local/bin/vimdiff /opt/local/bin/vimtutor]}
+        test_equal {[$zlib files]} {[list]}
 
-    # try unmapping and remapping
-    $vim3 unmap /opt/local/bin/vim
-    test_equal {[registry::entry owner /opt/local/bin/vim]} {}
-    $vim3 map /opt/local/bin/vim
-    test_equal {[registry::entry owner /opt/local/bin/vim]} {$vim3}
+        # try unmapping and remapping
+        $vim3 unmap {/opt/local/bin/vim}
+        test_equal {[registry::entry owner /opt/local/bin/vim]} {}
+        $vim3 map {/opt/local/bin/vim}
+        test_equal {[registry::entry owner /opt/local/bin/vim]} {$vim3}
 
-    # make sure you can't map an already-owned file or unmap one you don't
-    test_throws {$zlib map /opt/local/bin/vim} registry::already-owned
-    test_throws {$zlib unmap /opt/local/bin/vim} registry::not-owned
-    test_throws {$zlib unmap /opt/local/bin/emacs} registry::not-owned
+        # make sure you can't unmap a file you don't own
+        test_throws {$zlib unmap [list /opt/local/bin/vim]} \
+            registry::not-owned
+        test_throws {$zlib unmap [list /opt/local/bin/emacs]} \
+            registry::not-owned
+    }
 
     # delete pcre
     test_equal {[registry::entry imaged pcre]} {$pcre}
-    registry::entry delete $pcre
-    test_throws {[registry::entry open pcre 7.1 1 {utf8 +} 0]} registry::not-found
+    # try rolling it back
+    registry::write {
+        registry::entry delete $pcre
+        break
+    }
+    test_equal {[registry::entry open pcre 7.1 1 {utf8 +} 0]} {$pcre}
+    # try actually deleting it
+    registry::write {
+        registry::entry delete $pcre
+    }
+    test_throws {registry::entry open pcre 7.1 1 {utf8 +} 0} \
+        registry::not-found
     test {![registry::entry exists $pcre]}
 
     # close vim1
@@ -78,8 +103,8 @@
     registry::entry close $vim1
     test {![registry::entry exists $vim1]}
 
-    # close the registry; make sure the registry isn't usable after being closed
-    # and ensure state persists between open sessions
+    # close the registry; make sure the registry isn't usable after being
+    # closed, then ensure state persists between open sessions
     registry::close
     test_throws {registry::entry search} registry::not-open
     test {![registry::entry exists $vim3]}
@@ -93,12 +118,13 @@
     set zlib [registry::entry open zlib 1.2.3 1 {} 0]
     test {[registry::entry exists $zlib]}
 
-    # check that pcre is gone
-    test_throws {[registry::entry open pcre 7.1 1 {utf8 +} 0]} registry::not-found
+    # check that pcre is still gone
+    test_throws {registry::entry open pcre 7.1 1 {utf8 +} 0} \
+        registry::not-found
 
     registry::close
 
-	file delete -force test.db
+	file delete [glob -nocomplain test.db*]
 }
 
 source tests/common.tcl

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/macports-changes/attachments/20070806/0a05791a/attachment.html


More information about the macports-changes mailing list