- }
-
- lock.l_type = F_WRLCK;
- lock.l_whence = SEEK_SET;
-
- /* we create and initialize RootInfo if it doesn't exist. */
- strcat(path, ROOTINFO);
- if (ad_open(path, ADFLAGS_HF, O_RDWR, 0666, &db->rootinfo) < 0) {
- cnid_t id;
-
- /* see if we can open it read-only. if it's read-only, we can't
- * add CNIDs. */
- memset(&db->rootinfo, 0, sizeof(db->rootinfo));
- if (ad_open(path, ADFLAGS_HF, O_RDONLY, 0666, &db->rootinfo) == 0) {
- db->flags = CNIDFLAG_ROOTINFO_RO;
- syslog(LOG_INFO, "cnid_open: read-only RootInfo");
- goto mkdir_appledb;
- }
-
- /* create the file */
- memset(&db->rootinfo, 0, sizeof(db->rootinfo));
- if (ad_open(path, ADFLAGS_HF, O_CREAT | O_RDWR, 0666,
- &db->rootinfo) < 0) {
- syslog(LOG_ERR, "cnid_open: ad_open(RootInfo)");
- goto fail_db;
- }
-
- /* lock the RootInfo file. this and cnid_add are the only places
- * that should fiddle with RootInfo. */
- lock.l_start = ad_getentryoff(&db->rootinfo, ADEID_DID);
- lock.l_len = ad_getentrylen(&db->rootinfo, ADEID_DID);
- if (fcntl(ad_hfileno(&db->rootinfo), F_SETLKW, &lock) < 0) {
- syslog(LOG_ERR, "cnid_open: can't establish lock: %m");
- goto fail_adouble;
- }
-
- /* store the beginning CNID */
- id = htonl(CNID_START);
- memcpy(ad_entry(&db->rootinfo, ADEID_DID), &id, sizeof(id));
- ad_flush(&db->rootinfo, ADFLAGS_HF);
-
- /* unlock it */
- lock.l_type = F_UNLCK;
- fcntl(ad_hfileno(&db->rootinfo), F_SETLK, &lock);
- lock.l_type = F_WRLCK;
- }
-
-mkdir_appledb:
- strcpy(path + len, DBHOME);
- if ((stat(path, &st) < 0) && (ad_mkdir(path, 0777) < 0)) {
- syslog(LOG_ERR, "cnid_open: mkdir failed");
- goto fail_adouble;
- }
-
- /* search for a byte lock. this allows us to cleanup the log files
- * at cnid_close() in a clean fashion.
- *
- * NOTE: this won't work if multiple volumes for the same user refer
- * to the same directory. */
- strcat(path, DBLOCKFILE);
- if ((db->lockfd = open(path, O_RDWR | O_CREAT, 0666)) > -1) {
- lock.l_start = 0;
- lock.l_len = 1;
- while (fcntl(db->lockfd, F_SETLK, &lock) < 0) {
- if (++lock.l_start > MAXITER) {
- syslog(LOG_INFO, "cnid_open: can't establish logfile cleanup lock.");
- close(db->lockfd);
- db->lockfd = -1;
- break;
- }
- }
- }
-
- path[len + DBHOMELEN] = '\0';
- open_flag = DB_CREATE;
- /* try a full-blown transactional environment */
- if (db_appinit(path, NULL, &db->dbenv, DBOPTIONS)) {
-
- /* try with a shared memory pool */
- memset(&db->dbenv, 0, sizeof(db->dbenv));
- if (db_appinit(path, NULL, &db->dbenv, DB_INIT_MPOOL)) {
-
- /* try without any options. */
- memset(&db->dbenv, 0, sizeof(db->dbenv));
- if (db_appinit(path, NULL, &db->dbenv, 0)) {
- syslog(LOG_ERR, "cnid_open: db_appinit failed");
- goto fail_lock;
- }
- }
- db->flags |= CNIDFLAG_DB_RO;
- open_flag = DB_RDONLY;
- syslog(LOG_INFO, "cnid_open: read-only CNID database");
- }
-
- memset(&dbi, 0, sizeof(dbi));
-
- /* did/name reverse mapping. we use a btree for this one. */
- dbi.bt_compare = compare_unix;
- if (db_open(DBDIDNAME, DB_BTREE, open_flag, 0666, &db->dbenv, &dbi,
- &db->db_didname)) {
- goto fail_appinit;
- }
-
- /* check for version. this way we can update the database if we need
- to change the format in any way. */
- memset(&key, 0, sizeof(key));
- memset(&data, 0, sizeof(data));
- key.data = DBVERSION_KEY;
- key.len = DBVERSION_KEY_LEN;
- while (errno = db->db_didname->get(db->db_didname, NULL, &key, &data, 0)) {
- switch (errno) {
- case EAGAIN:
- continue;
-
- case DB_NOTFOUND:
- u_int32_t version = htonl(DBVERSION);
-
- data.data = &version;
- data.len = sizeof(version);
+
+ strcpy(db->close_file, path);
+ strcat(db->close_file, DBCLOSEFILE);
+
+ /* Check to make sure that a client isn't in the process of closing
+ * the database environment. To do this, select on the close file. */
+ while(stat(db->close_file, &csb) == 0) {
+ struct timeval ct;
+ ct.tv_sec = 1;
+ ct.tv_usec = 0;
+ (void)select(0, NULL, NULL, NULL, &ct);
+ }
+
+ strcpy(recover_file, path);
+ strcat(recover_file, DBRECOVERFILE);
+
+ /* Search for a byte lock. This allows us to cleanup the log files
+ * at cnid_close() in a clean fashion.
+ *
+ * NOTE: This won't work if multiple volumes for the same user refer
+ * to the sahe directory. */
+ strcat(path, DBLOCKFILE);
+ if ((db->lockfd = open(path, O_RDWR | O_CREAT, 0666)) > -1) {
+ lock.l_start = 0;
+ lock.l_len = 1;
+ while (fcntl(db->lockfd, F_SETLK, &lock) < 0) {
+ if (++lock.l_start > MAXITER) {
+ LOG(log_error, logtype_default, "cnid_open: Cannot establish logfile cleanup for database environment %s lock (lock failed)", path);
+ close(db->lockfd);
+ db->lockfd = -1;
+ break;
+ }
+ }
+ }
+ else {
+ LOG(log_error, logtype_default, "cnid_open: Cannot establish logfile cleanup lock for database environment %s (open() failed)", path);
+ }
+
+ /* Create a file to represent database recovery. While this file
+ * exists, the database is being recovered, and all other clients will
+ * select until recovery is complete, and this file goes away. */
+ if (!have_lock && db->lockfd > -1 && lock.l_start == 0) {
+ if (stat(recover_file, &rsb) == 0) {
+ (void)remove(recover_file);
+ }
+ if ((rfd = open(recover_file, O_RDWR | O_CREAT, 0666)) > -1) {
+ DBEXTRAS |= DB_RECOVER;
+ have_lock = 1;
+ }
+ }
+ else if (!have_lock) {
+ while (stat(recover_file, &rsb) == 0) {
+ struct timeval rt;
+ rt.tv_sec = 1;
+ rt.tv_usec = 0;
+ (void)select(0, NULL, NULL, NULL, &rt);
+ }
+ }
+
+ path[len + DBHOMELEN] = '\0';
+ open_flag = DB_CREATE;
+
+ /* We need to be able to open the database environment with full
+ * transaction, logging, and locking support if we ever hope to
+ * be a true multi-acess file server. */
+ if ((rc = db_env_create(&db->dbenv, 0)) != 0) {
+ LOG(log_error, logtype_default, "cnid_open: db_env_create: %s", db_strerror(rc));
+ goto fail_lock;
+ }
+
+ /* Setup internal deadlock detection. */
+ if ((rc = db->dbenv->set_lk_detect(db->dbenv, DEAD_LOCK_DETECT)) != 0) {
+ LOG(log_error, logtype_default, "cnid_open: set_lk_detect: %s", db_strerror(rc));
+ goto fail_lock;
+ }
+
+#if DB_VERSION_MINOR > 1
+#if 0
+ /* Take care of setting the DB_TXN_NOSYNC flag in db3 > 3.1.x. */
+ if ((rc = db->dbenv->set_flags(db->dbenv, DB_TXN_NOSYNC, 1)) != 0) {
+ LOG(log_error, logtype_default, "cnid_open: set_flags: %s", db_strerror(rc));
+ goto fail_lock;
+ }
+#endif
+#endif /* DB_VERSION_MINOR > 1 */
+
+ /* Open the database environment. */
+ if ((rc = db->dbenv->open(db->dbenv, path, DBOPTIONS | DBEXTRAS, 0666)) != 0) {
+ if (rc == DB_RUNRECOVERY) {
+ /* This is the mother of all errors. We _must_ fail here. */
+ LOG(log_error, logtype_default, "cnid_open: CATASTROPHIC ERROR opening database environment %s. Run db_recovery -c immediately", path);
+ goto fail_lock;
+ }
+
+ /* We can't get a full transactional environment, so multi-access
+ * is out of the question. Let's assume a read-only environment,
+ * and try to at least get a shared memory pool. */
+ if ((rc = db->dbenv->open(db->dbenv, path, DB_INIT_MPOOL, 0666)) != 0) {
+ /* Nope, not a MPOOL, either. Last-ditch effort: we'll try to
+ * open the environment with no flags. */
+ if ((rc = db->dbenv->open(db->dbenv, path, 0, 0666)) != 0) {
+ LOG(log_error, logtype_default, "cnid_open: dbenv->open of %s failed: %s",
+ path, db_strerror(rc));
+ goto fail_lock;
+ }
+ }
+ db->flags |= CNIDFLAG_DB_RO;
+ open_flag = DB_RDONLY;
+ LOG(log_info, logtype_default, "cnid_open: Obtained read-only database environment %s", path);
+ }
+
+ /* If we have the recovery lock, close the file, remove it, so other
+ * clients can proceed opening the DB environment. */
+ if (rfd > -1) {
+ (void)remove(recover_file);
+ switch(errno) {
+ case 0:
+ case ENOENT:
+ break;
+ default:
+ LOG(log_error, logtype_default, "cnid_open: Unable to remove %s: %s",
+ recover_file, strerror(errno));
+ }
+ close(rfd);
+ rfd = -1;
+ }
+
+ /* did/name reverse mapping. We use a BTree for this one. */
+ if ((rc = db_create(&db->db_didname, db->dbenv, 0)) != 0) {
+ LOG(log_error, logtype_default, "cnid_open: Failed to create did/name database: %s",
+ db_strerror(rc));
+ goto fail_appinit;
+ }
+
+ /*db->db_didname->set_bt_compare(db->db_didname, &compare_unix);*/
+ if ((rc = db->db_didname->open(db->db_didname, DBDIDNAME, NULL,
+ DB_HASH, open_flag, 0666))) {
+ LOG(log_error, logtype_default, "cnid_open: Failed to open did/name database: %s",
+ db_strerror(rc));
+ goto fail_appinit;
+ }
+
+ /* Check for version. This way we can update the database if we need
+ * to change the format in any way. */
+ memset(&key, 0, sizeof(key));
+ memset(&data, 0, sizeof(data));
+ key.data = DBVERSION_KEY;
+ key.size = DBVERSION_KEYLEN;
+