X-Git-Url: https://arthur.barton.de/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=libatalk%2Fcnid%2Fcnid_open.c;h=f56314b9468b4d773b04a852cf38a4b13f8ab7e6;hb=2726bc50a91ff4fc06813edaab91e3596df37e8f;hp=87bbed1e7fab9821761b93b298d88a5fe833d490;hpb=7ed55356c3bb57b1132d4678fdf800b19e5b4d73;p=netatalk.git diff --git a/libatalk/cnid/cnid_open.c b/libatalk/cnid/cnid_open.c index 87bbed1e..f56314b9 100644 --- a/libatalk/cnid/cnid_open.c +++ b/libatalk/cnid/cnid_open.c @@ -1,4 +1,38 @@ - +/* + * $Id: cnid_open.c,v 1.54 2003-07-10 13:50:39 rlewczuk Exp $ + * + * Copyright (c) 1999. Adrian Sun (asun@zoology.washington.edu) + * All Rights Reserved. See COPYRIGHT. + * + * CNID database support. + * + * here's the deal: + * 1) afpd already caches did's. + * 2) the database stores cnid's as both did/name and dev/ino pairs. + * 3) RootInfo holds the value of the NextID. + * 4) the cnid database gets called in the following manner -- + * start a database: + * cnid = cnid_open(root_dir); + * + * allocate a new id: + * newid = cnid_add(cnid, dev, ino, parent did, + * name, id); id is a hint for a specific id. pass 0 if you don't + * care. if the id is already assigned, you won't get what you + * requested. + * + * given an id, get a did/name and dev/ino pair. + * name = cnid_get(cnid, &id); given an id, return the corresponding + * info. + * return code = cnid_delete(cnid, id); delete an entry. + * + * with AFP, CNIDs 0-2 have special meanings. here they are: + * 0 -- invalid cnid + * 1 -- parent of root directory (handled by afpd) + * 2 -- root directory (handled by afpd) + * + * CNIDs 4-16 are reserved according to page 31 of the AFP 3.0 spec so, + * CNID_START begins at 17. + */ #ifdef HAVE_CONFIG_H #include "config.h" @@ -16,7 +50,10 @@ #endif /* HAVE_FCNTL_H */ #include #include -#include +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif /* HAVE_SYS_TIME_H */ #include @@ -30,14 +67,19 @@ #define MIN(a, b) ((a) < (b) ? (a) : (b)) #endif /* ! MIN */ -#define DBHOME ".AppleDB" -#define DBCNID "cnid.db" -#define DBDEVINO "devino.db" -#define DBDIDNAME "didname.db" /* did/full name mapping */ -#define DBSHORTNAME "shortname.db" /* did/8+3 mapping */ -#define DBMACNAME "macname.db" /* did/31 mapping */ -#define DBLONGNAME "longname.db" /* did/unicode mapping */ -#define DBLOCKFILE "cnid.lock" +#ifndef MAX +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#endif /* ! MIN */ + + +#define DBHOME ".AppleDB" +#define DBCNID "cnid.db" +#define DBDEVINO "devino.db" +#define DBDIDNAME "didname.db" /* did/full name mapping */ +#define DBMANGLE "mangle.db" /* filename mangling */ +#define DBLOCKFILE "cnid.lock" +#define DBRECOVERFILE "cnid.dbrecover" +#define DBCLOSEFILE "cnid.close" #define DBHOMELEN 8 #define DBLEN 10 @@ -50,422 +92,674 @@ #define DBVERSION1 0x00000001U #define DBVERSION DBVERSION1 -#if DB_VERSION_MINOR > 1 +#ifdef CNID_DB_CDB +#define DBOPTIONS (DB_CREATE | DB_INIT_CDB | DB_INIT_MPOOL) +#else /* !CNID_DB_CDB */ +#if DB_VERSION_MAJOR >= 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR > 1) #define DBOPTIONS (DB_CREATE | DB_INIT_MPOOL | DB_INIT_LOCK | \ DB_INIT_LOG | DB_INIT_TXN) #else /* DB_VERSION_MINOR < 1 */ +/*#define DBOPTIONS (DB_CREATE | DB_INIT_MPOOL | DB_INIT_LOCK | \ +DB_INIT_LOG | DB_INIT_TXN | DB_TXN_NOSYNC)*/ #define DBOPTIONS (DB_CREATE | DB_INIT_MPOOL | DB_INIT_LOCK | \ -DB_INIT_LOG | DB_INIT_TXN | DB_TXN_NOSYNC) -#endif /* DB_VERSION_MINOR */ - -#define MAXITER 0xFFFF /* maximum number of simultaneously open CNID - * databases. */ - -/* the first compare that's always done. */ -static __inline__ int compare_did(const DBT *a, const DBT *b) -{ - u_int32_t dida, didb; - - memcpy(&dida, a->data, sizeof(dida)); - memcpy(&didb, b->data, sizeof(didb)); - return dida - didb; -} - -/* sort did's and then names. this is for unix paths. - * i.e., did/unixname lookups. */ -#if DB_VERSION_MINOR > 1 -static int compare_unix(DB *db, const DBT *a, const DBT *b) -#else /* DB_VERSION_MINOR < 1 */ -static int compare_unix(const DBT *a, const DBT *b) +DB_INIT_LOG | DB_INIT_TXN) #endif /* DB_VERSION_MINOR */ -{ - u_int8_t *sa, *sb; - int len, ret; +#endif /* CNID_DB_CDB */ - /* sort by did */ - if ((ret = compare_did(a, b))) - return ret; +#ifndef CNID_DB_CDB +/* Let's try and use the youngest lock detector if present. + * If we can't do that, then let BDB use its default deadlock detector. */ +#if defined DB_LOCK_YOUNGEST +#define DEAD_LOCK_DETECT DB_LOCK_YOUNGEST +#else /* DB_LOCK_YOUNGEST */ +#define DEAD_LOCK_DETECT DB_LOCK_DEFAULT +#endif /* DB_LOCK_YOUNGEST */ +#endif /* CNID_DB_CDB */ - sa = (u_int8_t *) a->data + 4; /* shift past did */ - sb = (u_int8_t *) b->data + 4; - for (len = MIN(a->size, b->size); len-- > 4; sa++, sb++) - if ((ret = (*sa - *sb))) - return ret; /* sort by lexical ordering */ +static cnid_t cnid_rebuild (CNID_private *, mode_t); +static int cnid_recover (char *, mode_t); - return a->size - b->size; /* sort by length */ -} +#define MAXITER 0xFFFF /* maximum number of simultaneously open CNID +* databases. */ -/* sort did's and then names. this is for macified paths (i.e., - * did/macname, and did/shortname. i think did/longname needs a - * unicode table to work. also, we can't use strdiacasecmp as that - * returns a match if a < b. */ -#if DB_VERSION_MINOR > 1 -static int compare_mac(DB *db, const DBT *a, const DBT *b) -#else /* DB_VERSION_MINOR < 1 */ -static int compare_mac(const DBT *a, const DBT *b) -#endif /* DB_VERSION_MINOR */ +/* ----------------------- + * bandaid for LanTest performance pb. +*/ +static int my_yield(void) { - u_int8_t *sa, *sb; - int len, ret; + struct timeval t; + int ret; - /* sort by did */ - if ((ret = compare_did(a, b))) - return ret; - - sa = (u_int8_t *) a->data + 4; - sb = (u_int8_t *) b->data + 4; - for (len = MIN(a->size, b->size); len-- > 4; sa++, sb++) - if ((ret = (_diacasemap[*sa] - _diacasemap[*sb]))) - return ret; /* sort by lexical ordering */ - - return a->size - b->size; /* sort by length */ + t.tv_sec = 0; + t.tv_usec = 1000; + ret = select(0, NULL, NULL, NULL, &t); + return 0; } - -/* for unicode names -- right now it's the same as compare_mac. */ -#if DB_VERSION_MINOR > 1 -static int compare_unicode(DB *db, const DBT *a, const DBT *b) -#else /* DB_VERSION_MINOR < 1 */ -static int compare_unicode(const DBT *a, const DBT *b) -#endif /* DB_VERSION_MINOR */ +/* --------------- */ +static int my_open(DB *p, const char *f, const char *d, DBTYPE t, u_int32_t flags, int mode) { -#if DB_VERSION_MINOR > 1 - return compare_mac(db,a,b); -#else /* DB_VERSION_MINOR < 1 */ - return compare_mac(a,b); -#endif /* DB_VERSION_MINOR */ +#if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 1) + return p->open(p, NULL, f, d, t, flags, mode); +#else + return p->open(p, f, d, t, flags, mode); +#endif } -void *cnid_open(const char *dir) { - struct stat st; - struct flock lock; - char path[MAXPATHLEN + 1]; - CNID_private *db; - DBT key, data; - DB_TXN *tid; - int open_flag, len; - int rc; - - if (!dir) { - return NULL; +void *cnid_open(const char *dir, mode_t mask) { + struct stat st; + struct flock lock; + char path[MAXPATHLEN + 1]; + CNID_private *db; + DBT key, data; + DB_TXN *tid; + int open_flag, len; + int rc; + int can_check = 0, require_rebuild = 0; + u_int32_t ncnid, ndidname, ndevino; + DB_HASH_STAT *sp; + static int first = 0; + + ncnid = ndidname = ndevino = 0; + + + if (!dir) { + return NULL; + } + + /* this checks .AppleDB */ + if ((len = strlen(dir)) > (MAXPATHLEN - DBLEN - 1)) { + LOG(log_error, logtype_default, "cnid_open: Pathname too large: %s", dir); + return NULL; + } + + if ((db = (CNID_private *)calloc(1, sizeof(CNID_private))) == NULL) { + LOG(log_error, logtype_default, "cnid_open: Unable to allocate memory for database"); + return NULL; + } + + db->magic = CNID_DB_MAGIC; + + strcpy(path, dir); + if (path[len - 1] != '/') { + strcat(path, "/"); + len++; + } + + strcpy(path + len, DBHOME); + if ((stat(path, &st) < 0) && (ad_mkdir(path, 0777 & ~mask) < 0)) { + LOG(log_error, logtype_default, "cnid_open: DBHOME mkdir failed for %s", path); + goto fail_adouble; + } + + lock.l_type = F_WRLCK; + lock.l_whence = SEEK_SET; + /* Make sure cnid.lock goes in .AppleDB. */ + strcat(path, "/"); + len++; + + strcat(path, DBLOCKFILE); + strcpy(db->lock_file, path); + if ( stat (path, &st) == 0) + can_check = 1; + + if ((db->lockfd = open(path, O_RDWR | O_CREAT, 0666 & ~mask)) > -1) { + lock.l_start = 0; + lock.l_len = 1; + if (fcntl(db->lockfd, F_SETLK, &lock) < 0) { + /* Now try to get a shared lock */ + lock.l_type = F_RDLCK; + + while (fcntl(db->lockfd, F_SETLKW, &lock) < 0) { + if ( errno != EINTR ) + goto fail_db; + } + can_check = 0; } - - /* this checks .AppleDB */ - if ((len = strlen(dir)) > (MAXPATHLEN - DBLEN - 1)) { - syslog(LOG_ERR, "cnid_open: Pathname too large: %s", dir); - return NULL; + else { + /* We got an exclusive lock, so we're the first/only process accessing the database */ + if ( can_check ) { + /* cnid.lock existed but we got an exclusive lock */ + /* This means the previous afpd probably crashed */ + /* or got terminated so we run recovery */ + + path[len + DBHOMELEN] = '\0'; + if ( cnid_recover( path, mask) != 0) + goto fail_lock; + } + else { + /* We're the only process accesing the db, so lets do some constistency checks */ + can_check = 1; + } + } + } + else + { + LOG(log_error, logtype_default, "cnid_open: Cannot open cnid.lock file in %s lock (lock failed)", path); + goto fail_db; + } + + 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; + } + + if ((rc = db->dbenv->open(db->dbenv, path, DB_JOINENV, 0666 & ~mask)) !=0 && + (rc = db->dbenv->open(db->dbenv, path, DBOPTIONS, 0666 & ~mask)) != 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 & ~mask)) != 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 & ~mask)) != 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); + } + + /* 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; + } + + if ((rc = my_open(db->db_didname, DBDIDNAME, NULL, DB_HASH, open_flag, 0666 & ~mask))) { + LOG(log_error, logtype_default, "cnid_open: Failed to open did/name database: %s", + db_strerror(rc)); + /* We leak some memory here, but otherwise sometime we run into a SIGSEGV in close */ + db->db_didname = NULL; + if (can_check) { + require_rebuild = 1; + goto rebuild; } + else + 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; + + if ((rc = db->db_didname->get(db->db_didname, NULL, &key, &data, 0)) != 0) { + int ret; + { + u_int32_t version = htonl(DBVERSION); + + data.data = &version; + data.size = sizeof(version); + } + if ((ret = db->db_didname->put(db->db_didname, NULL, &key, &data, + DB_NOOVERWRITE))) { + LOG(log_error, logtype_default, "cnid_open: Error putting new version: %s", + db_strerror(ret)); + goto fail_appinit; + } + } + + /* TODO In the future we might check for version number here. */ +#if 0 + memcpy(&version, data.data, sizeof(version)); + if (version != ntohl(DBVERSION)) { + /* Do stuff here. */ + } +#endif /* 0 */ - if ((db = (CNID_private *)calloc(1, sizeof(CNID_private))) == NULL) { - syslog(LOG_ERR, "cnid_open: Unable to allocate memory for database"); - return NULL; + /* dev/ino reverse mapping. Use a hash for this one. */ + if ((rc = db_create(&db->db_devino, db->dbenv, 0)) != 0) { + LOG(log_error, logtype_default, "cnid_open: Failed to create dev/ino database: %s", + db_strerror(rc)); + goto fail_appinit; + } + + if ((rc = my_open(db->db_devino, DBDEVINO, NULL, DB_HASH, open_flag, 0666 & ~mask))) { + LOG(log_error, logtype_default, "cnid_open: Failed to open devino database: %s", + db_strerror(rc)); + db->db_devino = NULL; + if (can_check) { + require_rebuild = 1; + goto rebuild; + } + else + goto fail_appinit; + } + +rebuild: + + /* Main CNID database. Use a hash for this one. */ + if ((rc = db_create(&db->db_cnid, db->dbenv, 0)) != 0) { + LOG(log_error, logtype_default, "cnid_open: Failed to create cnid database: %s", + db_strerror(rc)); + goto fail_appinit; + } + + if ((rc = my_open(db->db_cnid, DBCNID, NULL, DB_HASH, open_flag, 0666 & ~mask))) { + LOG(log_error, logtype_default, "cnid_open: Failed to open cnid database: %s", + db_strerror(rc)); + if ( require_rebuild ) + LOG(log_error, logtype_default, "cnid_open: CNID Databases are corrupted beyond repair, your probably should delete all files in %s", path); + goto fail_appinit; + } + + /* Check database consistency */ + if ( can_check ) { + if ((db->db_didname) && ((rc = db->db_didname->stat(db->db_didname, &sp, 0)) != 0)) { + LOG(log_error, logtype_default, "cnid_open: Failed to stat did/macname database: %s", + db_strerror(rc)); + require_rebuild = 1; + } + if (sp) { + ndidname = (sp->hash_nkeys > 2) ? sp->hash_nkeys-2 : 0; + free (sp); } - db->magic = CNID_DB_MAGIC; + if ((db->db_devino) && ((rc = db->db_devino->stat(db->db_devino, &sp, 0)) != 0)) { + LOG(log_error, logtype_default, "cnid_open: Failed to stat dev/ino database: %s", + db_strerror(rc)); + require_rebuild = 1; + } + if (sp) { + ndevino = sp->hash_nkeys; + free (sp); + } - strcpy(path, dir); - if (path[len - 1] != '/') { - strcat(path, "/"); - len++; + if ((rc = db->db_cnid->stat(db->db_cnid, &sp, 0)) != 0) { + LOG(log_error, logtype_default, "cnid_open: Failed to stat cnid database: %s", + db_strerror(rc)); + LOG(log_error, logtype_default, "cnid_open: CNID Databases are corrupted beyond repair, your probably should delete all files in %s", path); + goto fail_appinit; + } + ncnid = sp->hash_nkeys; + + if ( require_rebuild || ndidname != ncnid || ndevino != ncnid ) { + LOG(log_error, logtype_default, "cnid_open: databases corrupt, number of entries mismatch (didname: %u, devino: %u, cnid: %u)", ndidname, ndevino, ncnid); + if ( CNID_INVALID == cnid_rebuild(db, mask) ) + goto fail_appinit; + } + /* now downgrade to a shared lock */ + + lock.l_type = F_RDLCK; + if (fcntl(db->lockfd, F_SETLK, &lock) < 0) { + LOG (log_error, logtype_default, "cnid_open: Cannot set shared lock"); } + can_check = 0; + } + +#ifdef FILE_MANGLING + /* filename mangling database. Use a hash for this one. */ + if ((rc = db_create(&db->db_mangle, db->dbenv, 0)) != 0) { + LOG(log_error, logtype_default, "cnid_open: Failed to create mangle database: %s", db_strerror(rc)); + goto fail_appinit; + } + + if ((rc = my_open(db->db_mangle, DBMANGLE, NULL, DB_HASH, open_flag, 0666 & ~mask))) { + LOG(log_error, logtype_default, "cnid_open: Failed to open mangle database: %s", db_strerror(rc)); + goto fail_appinit; + } +#endif /* FILE_MANGLING */ + + /* Print out the version of BDB we're linked against. only once */ + if (!first) { + first = 1; + LOG(log_info, logtype_default, "CNID DB initialized using %s", db_version(NULL, NULL, NULL)); + } + + db_env_set_func_yield(my_yield); + return db; - lock.l_type = F_WRLCK; - lock.l_whence = SEEK_SET; +fail_appinit: + if (db->db_didname) db->db_didname->close(db->db_didname, 0); + if (db->db_devino) db->db_devino->close(db->db_devino, 0); + if (db->db_cnid) db->db_cnid->close(db->db_cnid, 0); + LOG(log_error, logtype_default, "cnid_open: Failed to setup CNID DB environment"); + db->dbenv->close(db->dbenv, 0); - strcpy(path + len, DBHOME); - if ((stat(path, &st) < 0) && (ad_mkdir(path, 0777) < 0)) { - syslog(LOG_ERR, "cnid_open: DBHOME mkdir failed"); - goto fail_adouble; - } +fail_lock: + if (db->lockfd > -1 && !can_check ) { + close(db->lockfd); + (void)remove(db->lock_file); + } - /* 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) { - syslog(LOG_INFO, "cnid_open: Cannot establish logfile cleanup lock."); - close(db->lockfd); - db->lockfd = -1; - break; - } - } - } +fail_adouble: - path[len + DBHOMELEN] = '\0'; - open_flag = DB_CREATE; +fail_db: + free(db); + return NULL; +} - /* 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) { - syslog(LOG_ERR, "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, DB_LOCK_DEFAULT)) != 0) { - syslog(LOG_ERR, "cnid_open: set_lk_detect: %s", db_strerror(rc)); - goto fail_lock; - } +/*-------------------------*/ -#if DB_VERSION_MINOR > 1 - /* 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) { - syslog(LOG_ERR, "cnid_open: set_flags: %s", db_strerror(rc)); - goto fail_lock; - } -#endif /* DB_VERSION_MINOR > 1 */ - - /* Open the database environment. */ - if ((rc = db->dbenv->open(db->dbenv, path, DBOPTIONS, 0666)) != 0) { - if (rc == DB_RUNRECOVERY) { - /* This is the mother of all errors. We _must_ fail here. */ - syslog(LOG_ERR, "cnid_open: CATASTROPHIC ERROR opening database environment. Run db_recovery immediately"); - goto fail_lock; - } +static int cnid_recover(char *path, mode_t mask) +{ + DB_ENV * dbenv; + int open_flag = 0, rc = 0; - /* We can't get a full transactional environment, so multi-access - * is out of the question. Let's assume a read-only environment, - * and trry 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) { - syslog(LOG_ERR, "cnid_open: dbenv->open of %s failed: %s", - path, db_strerror(rc)); - goto fail_lock; - } - } - db->flags |= CNIDFLAG_DB_RO; - open_flag = DB_RDONLY; - syslog(LOG_INFO, "cnid_open: Obtained read-only database environment"); - } + if ( !path || strlen(path) == 0) + return -1; - /* did/name reverse mapping. We use a BTree for this one. */ - if ((rc = db_create(&db->db_didname, db->dbenv, 0)) != 0) { - syslog(LOG_ERR, "cnid_open: Failed to create did/name database: %s", - db_strerror(rc)); - goto fail_appinit; - } + LOG (log_info, logtype_default, "cnid_open: running recover on %s", path); - db->db_didname->set_bt_compare(db->db_didname, &compare_unix); - if ((rc = db->db_didname->open(db->db_didname, DBDIDNAME, NULL, - DB_BTREE, open_flag, 0666))) { - syslog(LOG_ERR, "cnid_open: Failed to open did/name database: %s", - db_strerror(rc)); - goto fail_appinit; - } + /* Remove any existing environment */ - /* 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; - -dbversion_retry: - if ((rc = txn_begin(db->dbenv, NULL, &tid, 0)) != 0) { - syslog(LOG_ERR, "cnid_open: txn_begin: failed to check db version: %s", - db_strerror(rc)); - db->db_didname->close(db->db_didname, 0); - goto fail_appinit; + if ((rc = db_env_create(&dbenv, 0)) != 0) { + LOG(log_error, logtype_default, "cnid_recover: db_env_create: %s", db_strerror(rc)); + return rc; + } + if ((rc = dbenv->remove(dbenv, path, DB_FORCE)) != 0) { + LOG(log_error, logtype_default, "cnid_recover db_env_remove: %s", db_strerror(rc)); + return rc; } - while ((rc = db->db_didname->get(db->db_didname, tid, &key, &data, DB_RMW))) { - int ret; - switch (rc) { - case DB_LOCK_DEADLOCK: - if ((ret = txn_abort(tid)) != 0) { - syslog(LOG_ERR, "cnid_open: txn_abort: %s", db_strerror(ret)); - db->db_didname->close(db->db_didname, 0); - goto fail_appinit; - } - goto dbversion_retry; - case DB_NOTFOUND: - { - u_int32_t version = htonl(DBVERSION); + /* Create and open recover environment */ - data.data = &version; - data.size = sizeof(version); - } + if ((rc = db_env_create(&dbenv, 0)) != 0) { + LOG(log_error, logtype_default, "cnid_recover: db_env_create: %s", db_strerror(rc)); + return rc; + } - if ((ret = db->db_didname->put(db->db_didname, tid, &key, &data, - DB_NOOVERWRITE))) { - if (ret == DB_LOCK_DEADLOCK) { - if ((ret = txn_abort(tid)) != 0) { - syslog(LOG_ERR, "cnid_open: txn_abort: %s", - db_strerror(ret)); - db->db_didname->close(db->db_didname, 0); - goto fail_appinit; - } - goto dbversion_retry; - } - else if (ret == DB_RUNRECOVERY) { - /* At this point, we don't care if the transaction aborts - * successfully or not. */ - txn_abort(tid); - syslog(LOG_ERR, "cnid_open: Error putting new version: %s", - db_strerror(ret)); - db->db_didname->close(db->db_didname, 0); - goto fail_appinit; - } - } - break; /* while loop */ - default: - txn_abort(tid); - syslog(LOG_ERR, "cnid_open: Failed to check db version: %s", - db_strerror(rc)); - db->db_didname->close(db->db_didname, 0); - goto fail_appinit; - } + open_flag = DB_CREATE | DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_TXN | DB_RECOVER | DB_PRIVATE; + if ((rc = dbenv->open(dbenv, path, open_flag, 0666 & ~mask)) != 0) { + LOG (log_error, logtype_default, "cnid_recover: RECOVERY failed with %s", db_strerror(rc)); + return rc; } - if ((rc = txn_commit(tid, 0)) != 0) { - syslog(LOG_ERR, "cnid_open: Failed to commit db version: %s", - db_strerror(rc)); - db->db_didname->close(db->db_didname, 0); - goto fail_appinit; + if ((rc = dbenv->close(dbenv, 0)) != 0) { + LOG(log_error, logtype_default, "cnid_recover: dbenv->close: %s", db_strerror(rc)); + return rc; + } + + /* Remove recover environment */ + + if ((rc = db_env_create(&dbenv, 0)) != 0) { + LOG(log_error, logtype_default, "cnid_recover: db_env_create: %s", db_strerror(rc)); + return rc; + } + + if ((rc = dbenv->remove(dbenv, path, DB_FORCE)) != 0) { + LOG(log_error, logtype_default, "cnid_recover: db_env_remove: %s", db_strerror(rc)); + return rc; } - /* TODO In the future we might check for version number here. */ -#if 0 - memcpy(&version, data.data, sizeof(version)); - if (version != ntohl(DBVERSION)) { - /* Do stuff here. */ - } -#endif /* 0 */ + return 0; +} -#ifdef EXTENDED_DB - /* did/macname (31 character) mapping. Use a BTree for this one. */ - if ((rc = db_create(&db->db_macname, db->dbenv, 0)) != 0) { - syslog(LOG_ERR, "cnid_open: Failed to create did/macname database: %s", - db_strerror(rc)); - db->db_didname->close(db->db_didname, 0); - goto fail_appinit; - } - db->db_macname->set_bt_compare(db->db_macname, &compare_mac); - if ((rc = db->db_macname->open(db->db_macname, DBMACNAME, NULL, DB_BTREE, open_flag, 0666)) != 0) { - syslog(LOG_ERR, "cnid_open: Failed to open did/macname database: %s", - db_strerror(rc)); - db->db_didname->close(db->db_didname, 0); - goto fail_appinit; - } +static cnid_t cnid_rebuild (CNID_private *db, mode_t mask) +{ - /* did/shortname (DOS 8.3) mapping. Use a BTree for this one. */ - if ((rc = db_create(&db->db_shortname, db->dbenv, 0)) != 0) { - syslog(LOG_ERR, "cnid_open: Failed to create did/shortname database: %s", - db_strerror(rc)); - db->db_didname->close(db->db_didname, 0); - db->db_macname->close(db->db_macname, 0); - goto fail_appinit; - } +#ifdef CNID_DB_CDB + DB *tmpdb; + DBC *cursor; + DBT key, data, altkey, altdata, dupkey, dupdata; + int rc; + cnid_t id, dupid, maxcnid = CNID_START; + u_int32_t countp; + u_int32_t version; - db->db_shortname->set_bt_compare(db->db_shortname, &compare_mac); - if ((rc = db->db_shortname->open(db->db_shortname, DBSHORTNAME, NULL, DB_BTREE, open_flag, 0666)) != 0) { - syslog(LOG_ERR, "cnid_open: Failed to open did/shortname database: %s", - db_strerror(rc)); - db->db_didname->close(db->db_didname, 0); - db->db_macname->close(db->db_macname, 0); - goto fail_appinit; - } + static char buffer[MAXPATHLEN + CNID_HEADER_LEN + 1]; + LOG(log_info, logtype_default, "starting rebuild of databases"); - /* did/longname (Unicode) mapping. Use a BTree for this one. */ - if ((rc = db_create(&db->db_longname, db->dbenv, 0)) != 0) { - syslog(LOG_ERR, "cnid_open: Failed to create did/longname database: %s", - db_strerror(rc)); - db->db_didname->close(db->db_didname, 0); - db->db_macname->close(db->db_macname, 0); - db->db_shortname->close(db->db_shortname, 0); - goto fail_appinit; - } + if ((rc = db->db_cnid->cursor(db->db_cnid, NULL, &cursor, DB_WRITECURSOR) ) != 0) { + LOG(log_error, logtype_default, "cnid_rebuild: Unable to get a cursor: %s", db_strerror(rc)); + return CNID_INVALID; + } - db->db_longname->set_bt_compare(db->db_longname, &compare_unicode); - if ((rc = db->db_longname->open(db->db_longname, DBLONGNAME, NULL, DB_BTREE, open_flag, 0666)) != 0) { - syslog(LOG_ERR, "cnid_open: Failed to open did/longname database: %s", - db_strerror(rc)); - db->db_didname->close(db->db_didname, 0); - db->db_macname->close(db->db_macname, 0); - db->db_shortname->close(db->db_shortname, 0); - goto fail_appinit; - } -#endif /* EXTENDED_DB */ - - /* dev/ino reverse mapping. Use a hash for this one. */ - if ((rc = db_create(&db->db_devino, db->dbenv, 0)) != 0) { - syslog(LOG_ERR, "cnid_open: Failed to create dev/ino database: %s", - db_strerror(rc)); - db->db_didname->close(db->db_didname, 0); -#ifdef EXTENDED_DB - db->db_macname->close(db->db_macname, 0); - db->db_shortname->close(db->db_shortname, 0); - db->db_longname->close(db->db_longname, 0); -#endif /* EXTENDED_DB */ - goto fail_appinit; - } + memset(&key, 0, sizeof(key)); + memset(&data, 0, sizeof(data)); + memset(&altkey, 0, sizeof(key)); + memset(&altdata, 0, sizeof(data)); + memset(&dupdata, 0, sizeof(dupdata)); + memset(&dupkey, 0, sizeof(dupkey)); + + /* close didname and devino, then recreate them */ + if (db->db_didname) db->db_didname->close(db->db_didname, 0); + if (db->db_devino) db->db_devino->close(db->db_devino, 0); + + if ((rc = db_create(&db->db_didname, db->dbenv, 0)) != 0) { + LOG(log_error, logtype_default, "cnid_rebuild: Failed to recreate did/name database: %s", + db_strerror(rc)); + goto abort; + } + + if ((rc = my_open(db->db_didname, DBDIDNAME, NULL, DB_HASH, DB_CREATE|DB_TRUNCATE, 0666 & ~mask))) { + LOG(log_error, logtype_default, "cnid_rebuild: Failed to open did/name database: %s", + db_strerror(rc)); + goto abort; + } + + if ((rc = db_create(&db->db_devino, db->dbenv, 0)) != 0) { + LOG(log_error, logtype_default, "cnid_rebuild: Failed to recreate dev/ino database: %s", + db_strerror(rc)); + goto abort; + } + + if ((rc = my_open(db->db_devino, DBDEVINO, NULL, DB_HASH, DB_CREATE|DB_TRUNCATE, 0666 & ~mask))) { + LOG(log_error, logtype_default, "cnid_rebuild: Failed to open dev/ino database: %s", + db_strerror(rc)); + goto abort; + } + + db->db_devino->sync( db->db_devino, 0); + db->db_didname->sync( db->db_didname, 0); + + + /* now create the temporary cnid database */ + if ((rc = db_create(&tmpdb, db->dbenv, 0)) != 0) { + LOG(log_error, logtype_default, "cnid_rebuild: Failed to create tmp database: %s", + db_strerror(rc)); + goto abort; + } + + if ((rc = my_open(tmpdb, DBRECOVERFILE, NULL, DB_HASH, DB_CREATE|DB_TRUNCATE, 0666 & ~mask))) { + LOG(log_error, logtype_default, "cnid_rebuild: Failed to open recover database: %s", + db_strerror(rc)); + goto abort; + } + + /* add rootinfo and version to didname */ + id = 0; + key.data = ROOTINFO_KEY; + key.size = ROOTINFO_KEYLEN; + data.data = &id; + data.size = sizeof(id); + + if ((rc = db->db_didname->put(db->db_didname, NULL, &key, &data, 0))) { + LOG(log_error, logtype_default, "cnid_rebuild: Error adding ROOTINFO: %s", db_strerror(rc)); + goto abort; + } + + memset(&key, 0, sizeof(key)); + memset(&data, 0, sizeof(data)); + key.data = DBVERSION_KEY; + key.size = DBVERSION_KEYLEN; + version = htonl(DBVERSION); + data.data = &version; + data.size = sizeof(version); - if ((rc = db->db_devino->open(db->db_devino, DBDEVINO, NULL, DB_HASH, open_flag, 0666)) != 0) { - syslog(LOG_ERR, "cnid_open: Failed to open devino database: %s", - db_strerror(rc)); - db->db_didname->close(db->db_didname, 0); -#ifdef EXTENDED_DB - db->db_macname->close(db->db_macname, 0); - db->db_shortname->close(db->db_shortname, 0); - db->db_longname->close(db->db_longname, 0); -#endif /* EXTENDED_DB */ - goto fail_appinit; - } + if ((rc = db->db_didname->put(db->db_didname, NULL, &key, &data, 0))) { + LOG(log_error, logtype_default, "cnid_rebuild: Error putting version: %s", + db_strerror(rc)); + goto abort; + } - /* Main CNID database. Use a hash for this one. */ - if ((rc = db_create(&db->db_cnid, db->dbenv, 0)) != 0) { - syslog(LOG_ERR, "cnid_open: Failed to create cnid database: %s", - db_strerror(rc)); - db->db_didname->close(db->db_didname, 0); -#ifdef EXTENDED_DB - db->db_macname->close(db->db_macname, 0); - db->db_shortname->close(db->db_shortname, 0); - db->db_longname->close(db->db_longname, 0); -#endif /* EXTENDED_DB */ - db->db_devino->close(db->db_devino, 0); - goto fail_appinit; - } + /* now recover the databases from cnid.db*/ + memset(&key, 0, sizeof(key)); + memset(&data, 0, sizeof(data)); + data.data = buffer; + data.ulen = MAXPATHLEN + CNID_HEADER_LEN +1; + data.flags = DB_DBT_USERMEM; + countp = 0; + + while ((rc = cursor->c_get(cursor, &key, &data, DB_NEXT)) == 0) { + memcpy ( &id, key.data, sizeof(id)); + id = ntohl (id); + if ( id < CNID_START ) { + LOG(log_error, logtype_default, "cnid_rebuild: Dropping invalid entry with id: %u", id); + continue; + } + + maxcnid = MAX( id, maxcnid); - if ((rc = db->db_cnid->open(db->db_cnid, DBCNID, NULL, DB_HASH, open_flag, 0666)) != 0) { - syslog(LOG_ERR, "cnid_open: Failed to open dev/ino database: %s", - db_strerror(rc)); - db->db_didname->close(db->db_didname, 0); -#ifdef EXTENDED_DB - db->db_macname->close(db->db_macname, 0); - db->db_shortname->close(db->db_shortname, 0); - db->db_longname->close(db->db_longname, 0); -#endif /* EXTENDED_DB */ - goto fail_appinit; - } + /* check for duplicate cnid */ - return db; + if ((rc = tmpdb->get(tmpdb, NULL, &key, &dupdata, 0))) { + if ( rc != DB_NOTFOUND ) { + LOG(log_error, logtype_default, "cnid_rebuild: Error getting entry from cnid: %s", + db_strerror(rc)); + goto abort; + } + } + else /* duplicate cnid */ + { + LOG(log_error, logtype_default, "cnid_rebuild: Dropping duplicate file entry: %u", id); + continue; + } -fail_appinit: - syslog(LOG_ERR, "cnid_open: Failed to setup CNID DB environment"); - db->dbenv->close(db->dbenv, 0); + + /* dev/ino database */ + altkey.data = data.data; + altkey.size = CNID_DEVINO_LEN; + altdata.data = key.data; + altdata.size = key.size; + if ((rc = db->db_devino->put(db->db_devino, NULL, &altkey, &altdata, DB_NOOVERWRITE))) { + if ( rc != DB_KEYEXIST) { + LOG(log_error, logtype_default, "cnid_rebuild: Error adding entry %u to dev/ino: %s", + id, db_strerror(rc)); + goto abort; + } -fail_lock: - if (db->lockfd > -1) { - close(db->lockfd); + /* handle duplicate (file with 2 ids) */ + + dupkey.data = data.data; + dupkey.size = CNID_DEVINO_LEN; + if (( rc = db->db_devino->get(db->db_devino, NULL, &dupkey, &dupdata, 0))) { + LOG(log_error, logtype_default, "cnid_rebuild: Error getting entry from did/name: %s", + db_strerror(rc)); + goto abort; + } + memcpy ( &dupid, dupdata.data, sizeof(id)); + dupid = ntohl (dupid); + LOG(log_error, logtype_default, "cnid_rebuild: Dropping duplicate file entry: %u", MIN(id,dupid)); + + /* Use the entry with a higher cnid */ + if ( id < dupid) + continue; + else + if ((rc = db->db_devino->put(db->db_devino, NULL, &altkey, &altdata, 0))) { + LOG(log_error, logtype_default, "cnid_rebuild: Error adding entry %u to dev/ino: %s", + id, db_strerror(rc)); + goto abort; + } + } + + /* did/name database */ + altkey.data = (char *) data.data + CNID_DEVINO_LEN; + altkey.size = data.size - CNID_DEVINO_LEN; + if ((rc = db->db_didname->put(db->db_didname, NULL, &altkey, &altdata, 0))) { + LOG(log_error, logtype_default, "cnid_rebuild: Error adding entry to did/name: %s", + db_strerror(rc)); + goto abort; + } + + /* recover database */ + + if ((rc = tmpdb->put(tmpdb, NULL, &key, &data, 0))) { + LOG(log_error, logtype_default, "cnid_rebuild: Error adding entry to recoverdb: %s", + db_strerror(rc)); + goto abort; + } + countp++; } -fail_adouble: + /* set ROOTINFO to maxcnid */ + maxcnid = htonl(maxcnid); + key.data = ROOTINFO_KEY; + key.size = ROOTINFO_KEYLEN; + data.data = &maxcnid; + data.size = sizeof(maxcnid); + + if ((rc = db->db_didname->put(db->db_didname, NULL, &key, &data, 0))) { + LOG (log_error, logtype_default, "cnid_rebuild: Failed to update ROOTINFO %s", db_strerror(rc)); + goto abort; + } + + /* delete cnid.db and rename cniddb.recover to cnid.db */ + + cursor->c_close(cursor); + db->db_cnid->close(db->db_cnid, 0); + + if ((rc = db_create(&db->db_cnid, db->dbenv, 0)) != 0) { + LOG(log_error, logtype_default, "cnid_rebuild: Failed to create database: %s", db_strerror(rc)); + goto fail; + } + if ((rc = db->db_cnid->remove(db->db_cnid, DBCNID, NULL, 0)) != 0) { + LOG(log_error, logtype_default, "cnid_rebuild: Failed to remove database: %s", db_strerror(rc)); + db->db_cnid = NULL; + goto fail; + } + + if ((rc = tmpdb->close(tmpdb, 0)) != 0) { + LOG(log_error, logtype_default, "cnid_rebuild: Failed to close database: %s", db_strerror(rc)); + goto fail; + } + if ((rc = db_create(&tmpdb, db->dbenv, 0)) != 0) { + LOG(log_error, logtype_default, "cnid_rebuild: Failed to create database: %s", db_strerror(rc)); + goto fail; + } + if ((rc = tmpdb->rename(tmpdb, DBRECOVERFILE, NULL, DBCNID, 0)) != 0) { + LOG(log_error, logtype_default, "cnid_rebuild: Rename database failed: %s", db_strerror(rc)); + goto fail; + } + + if ((rc = db_create(&db->db_cnid, db->dbenv, 0)) != 0) { + LOG(log_error, logtype_default, "cnid_rebuild: Failed to recreate cnid database: %s", db_strerror(rc)); + goto fail; + } + + if ((rc = my_open(db->db_cnid, DBCNID, NULL, DB_HASH, 0, 0666 & ~mask))) { + LOG(log_error, logtype_default, "cnid_rebuild: Failed to open cnid database: %s", db_strerror(rc)); + goto fail; + } + + db->db_devino->sync( db->db_devino, 0); + db->db_didname->sync( db->db_didname, 0); + db->db_cnid->sync( db->db_cnid, 0); + + LOG (log_info, logtype_default, "cnid_rebuild: Recovered %u entries, database rebuild complete", countp); + return 1; + +abort: + cursor->c_close(cursor); +fail: + LOG (log_error, logtype_default, "cnid_rebuild: Database rebuild failed"); + return CNID_INVALID; +#else + return CNID_INVALID; +#endif -fail_db: - free(db); - return NULL; } -#endif /* CNID_DB */ - +#endif /* CNID_DB */