tdb/common/tdb.c

説明を見る。
00001  /* 
00002    Unix SMB/CIFS implementation.
00003 
00004    trivial database library
00005 
00006    Copyright (C) Andrew Tridgell              1999-2005
00007    Copyright (C) Paul `Rusty' Russell              2000
00008    Copyright (C) Jeremy Allison                    2000-2003
00009    
00010      ** NOTE! The following LGPL license applies to the tdb
00011      ** library. This does NOT imply that all of Samba is released
00012      ** under the LGPL
00013    
00014    This library is free software; you can redistribute it and/or
00015    modify it under the terms of the GNU Lesser General Public
00016    License as published by the Free Software Foundation; either
00017    version 2 of the License, or (at your option) any later version.
00018 
00019    This library is distributed in the hope that it will be useful,
00020    but WITHOUT ANY WARRANTY; without even the implied warranty of
00021    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00022    Lesser General Public License for more details.
00023 
00024    You should have received a copy of the GNU Lesser General Public
00025    License along with this library; if not, write to the Free Software
00026    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00027 */
00028 
00029 #include "tdb_private.h"
00030 
00031 TDB_DATA tdb_null;
00032 
00033 /*
00034   increment the tdb sequence number if the tdb has been opened using
00035   the TDB_SEQNUM flag
00036 */
00037 static void tdb_increment_seqnum(struct tdb_context *tdb)
00038 {
00039         tdb_off_t seqnum=0;
00040         
00041         if (!(tdb->flags & TDB_SEQNUM)) {
00042                 return;
00043         }
00044 
00045         if (tdb_brlock(tdb, TDB_SEQNUM_OFS, F_WRLCK, F_SETLKW, 1, 1) != 0) {
00046                 return;
00047         }
00048 
00049         /* we ignore errors from this, as we have no sane way of
00050            dealing with them.
00051         */
00052         tdb_ofs_read(tdb, TDB_SEQNUM_OFS, &seqnum);
00053         seqnum++;
00054         tdb_ofs_write(tdb, TDB_SEQNUM_OFS, &seqnum);
00055 
00056         tdb_brlock(tdb, TDB_SEQNUM_OFS, F_UNLCK, F_SETLKW, 1, 1);
00057 }
00058 
00059 static int tdb_key_compare(TDB_DATA key, TDB_DATA data, void *private_data)
00060 {
00061         return memcmp(data.dptr, key.dptr, data.dsize);
00062 }
00063 
00064 /* Returns 0 on fail.  On success, return offset of record, and fills
00065    in rec */
00066 static tdb_off_t tdb_find(struct tdb_context *tdb, TDB_DATA key, u32 hash,
00067                         struct list_struct *r)
00068 {
00069         tdb_off_t rec_ptr;
00070         
00071         /* read in the hash top */
00072         if (tdb_ofs_read(tdb, TDB_HASH_TOP(hash), &rec_ptr) == -1)
00073                 return 0;
00074 
00075         /* keep looking until we find the right record */
00076         while (rec_ptr) {
00077                 if (tdb_rec_read(tdb, rec_ptr, r) == -1)
00078                         return 0;
00079 
00080                 if (!TDB_DEAD(r) && hash==r->full_hash
00081                     && key.dsize==r->key_len
00082                     && tdb_parse_data(tdb, key, rec_ptr + sizeof(*r),
00083                                       r->key_len, tdb_key_compare,
00084                                       NULL) == 0) {
00085                         return rec_ptr;
00086                 }
00087                 rec_ptr = r->next;
00088         }
00089         return TDB_ERRCODE(TDB_ERR_NOEXIST, 0);
00090 }
00091 
00092 /* As tdb_find, but if you succeed, keep the lock */
00093 tdb_off_t tdb_find_lock_hash(struct tdb_context *tdb, TDB_DATA key, u32 hash, int locktype,
00094                            struct list_struct *rec)
00095 {
00096         u32 rec_ptr;
00097 
00098         if (tdb_lock(tdb, BUCKET(hash), locktype) == -1)
00099                 return 0;
00100         if (!(rec_ptr = tdb_find(tdb, key, hash, rec)))
00101                 tdb_unlock(tdb, BUCKET(hash), locktype);
00102         return rec_ptr;
00103 }
00104 
00105 
00106 /* update an entry in place - this only works if the new data size
00107    is <= the old data size and the key exists.
00108    on failure return -1.
00109 */
00110 static int tdb_update_hash(struct tdb_context *tdb, TDB_DATA key, u32 hash, TDB_DATA dbuf)
00111 {
00112         struct list_struct rec;
00113         tdb_off_t rec_ptr;
00114 
00115         /* find entry */
00116         if (!(rec_ptr = tdb_find(tdb, key, hash, &rec)))
00117                 return -1;
00118 
00119         /* must be long enough key, data and tailer */
00120         if (rec.rec_len < key.dsize + dbuf.dsize + sizeof(tdb_off_t)) {
00121                 tdb->ecode = TDB_SUCCESS; /* Not really an error */
00122                 return -1;
00123         }
00124 
00125         if (tdb->methods->tdb_write(tdb, rec_ptr + sizeof(rec) + rec.key_len,
00126                       dbuf.dptr, dbuf.dsize) == -1)
00127                 return -1;
00128 
00129         if (dbuf.dsize != rec.data_len) {
00130                 /* update size */
00131                 rec.data_len = dbuf.dsize;
00132                 return tdb_rec_write(tdb, rec_ptr, &rec);
00133         }
00134  
00135         return 0;
00136 }
00137 
00138 /* find an entry in the database given a key */
00139 /* If an entry doesn't exist tdb_err will be set to
00140  * TDB_ERR_NOEXIST. If a key has no data attached
00141  * then the TDB_DATA will have zero length but
00142  * a non-zero pointer
00143  */
00144 TDB_DATA tdb_fetch(struct tdb_context *tdb, TDB_DATA key)
00145 {
00146         tdb_off_t rec_ptr;
00147         struct list_struct rec;
00148         TDB_DATA ret;
00149         u32 hash;
00150 
00151         /* find which hash bucket it is in */
00152         hash = tdb->hash_fn(&key);
00153         if (!(rec_ptr = tdb_find_lock_hash(tdb,key,hash,F_RDLCK,&rec)))
00154                 return tdb_null;
00155 
00156         ret.dptr = tdb_alloc_read(tdb, rec_ptr + sizeof(rec) + rec.key_len,
00157                                   rec.data_len);
00158         ret.dsize = rec.data_len;
00159         tdb_unlock(tdb, BUCKET(rec.full_hash), F_RDLCK);
00160         return ret;
00161 }
00162 
00163 /*
00164  * Find an entry in the database and hand the record's data to a parsing
00165  * function. The parsing function is executed under the chain read lock, so it
00166  * should be fast and should not block on other syscalls.
00167  *
00168  * DONT CALL OTHER TDB CALLS FROM THE PARSER, THIS MIGHT LEAD TO SEGFAULTS.
00169  *
00170  * For mmapped tdb's that do not have a transaction open it points the parsing
00171  * function directly at the mmap area, it avoids the malloc/memcpy in this
00172  * case. If a transaction is open or no mmap is available, it has to do
00173  * malloc/read/parse/free.
00174  *
00175  * This is interesting for all readers of potentially large data structures in
00176  * the tdb records, ldb indexes being one example.
00177  */
00178 
00179 int tdb_parse_record(struct tdb_context *tdb, TDB_DATA key,
00180                      int (*parser)(TDB_DATA key, TDB_DATA data,
00181                                    void *private_data),
00182                      void *private_data)
00183 {
00184         tdb_off_t rec_ptr;
00185         struct list_struct rec;
00186         int ret;
00187         u32 hash;
00188 
00189         /* find which hash bucket it is in */
00190         hash = tdb->hash_fn(&key);
00191 
00192         if (!(rec_ptr = tdb_find_lock_hash(tdb,key,hash,F_RDLCK,&rec))) {
00193                 return TDB_ERRCODE(TDB_ERR_NOEXIST, 0);
00194         }
00195 
00196         ret = tdb_parse_data(tdb, key, rec_ptr + sizeof(rec) + rec.key_len,
00197                              rec.data_len, parser, private_data);
00198 
00199         tdb_unlock(tdb, BUCKET(rec.full_hash), F_RDLCK);
00200 
00201         return ret;
00202 }
00203 
00204 /* check if an entry in the database exists 
00205 
00206    note that 1 is returned if the key is found and 0 is returned if not found
00207    this doesn't match the conventions in the rest of this module, but is
00208    compatible with gdbm
00209 */
00210 static int tdb_exists_hash(struct tdb_context *tdb, TDB_DATA key, u32 hash)
00211 {
00212         struct list_struct rec;
00213         
00214         if (tdb_find_lock_hash(tdb, key, hash, F_RDLCK, &rec) == 0)
00215                 return 0;
00216         tdb_unlock(tdb, BUCKET(rec.full_hash), F_RDLCK);
00217         return 1;
00218 }
00219 
00220 int tdb_exists(struct tdb_context *tdb, TDB_DATA key)
00221 {
00222         u32 hash = tdb->hash_fn(&key);
00223         return tdb_exists_hash(tdb, key, hash);
00224 }
00225 
00226 /* actually delete an entry in the database given the offset */
00227 int tdb_do_delete(struct tdb_context *tdb, tdb_off_t rec_ptr, struct list_struct*rec)
00228 {
00229         tdb_off_t last_ptr, i;
00230         struct list_struct lastrec;
00231 
00232         if (tdb->read_only || tdb->traverse_read) return -1;
00233 
00234         if (tdb_write_lock_record(tdb, rec_ptr) == -1) {
00235                 /* Someone traversing here: mark it as dead */
00236                 rec->magic = TDB_DEAD_MAGIC;
00237                 return tdb_rec_write(tdb, rec_ptr, rec);
00238         }
00239         if (tdb_write_unlock_record(tdb, rec_ptr) != 0)
00240                 return -1;
00241 
00242         /* find previous record in hash chain */
00243         if (tdb_ofs_read(tdb, TDB_HASH_TOP(rec->full_hash), &i) == -1)
00244                 return -1;
00245         for (last_ptr = 0; i != rec_ptr; last_ptr = i, i = lastrec.next)
00246                 if (tdb_rec_read(tdb, i, &lastrec) == -1)
00247                         return -1;
00248 
00249         /* unlink it: next ptr is at start of record. */
00250         if (last_ptr == 0)
00251                 last_ptr = TDB_HASH_TOP(rec->full_hash);
00252         if (tdb_ofs_write(tdb, last_ptr, &rec->next) == -1)
00253                 return -1;
00254 
00255         /* recover the space */
00256         if (tdb_free(tdb, rec_ptr, rec) == -1)
00257                 return -1;
00258         return 0;
00259 }
00260 
00261 static int tdb_count_dead(struct tdb_context *tdb, u32 hash)
00262 {
00263         int res = 0;
00264         tdb_off_t rec_ptr;
00265         struct list_struct rec;
00266         
00267         /* read in the hash top */
00268         if (tdb_ofs_read(tdb, TDB_HASH_TOP(hash), &rec_ptr) == -1)
00269                 return 0;
00270 
00271         while (rec_ptr) {
00272                 if (tdb_rec_read(tdb, rec_ptr, &rec) == -1)
00273                         return 0;
00274 
00275                 if (rec.magic == TDB_DEAD_MAGIC) {
00276                         res += 1;
00277                 }
00278                 rec_ptr = rec.next;
00279         }
00280         return res;
00281 }
00282 
00283 /*
00284  * Purge all DEAD records from a hash chain
00285  */
00286 static int tdb_purge_dead(struct tdb_context *tdb, u32 hash)
00287 {
00288         int res = -1;
00289         struct list_struct rec;
00290         tdb_off_t rec_ptr;
00291 
00292         if (tdb_lock(tdb, -1, F_WRLCK) == -1) {
00293                 return -1;
00294         }
00295         
00296         /* read in the hash top */
00297         if (tdb_ofs_read(tdb, TDB_HASH_TOP(hash), &rec_ptr) == -1)
00298                 goto fail;
00299 
00300         while (rec_ptr) {
00301                 tdb_off_t next;
00302 
00303                 if (tdb_rec_read(tdb, rec_ptr, &rec) == -1) {
00304                         goto fail;
00305                 }
00306 
00307                 next = rec.next;
00308 
00309                 if (rec.magic == TDB_DEAD_MAGIC
00310                     && tdb_do_delete(tdb, rec_ptr, &rec) == -1) {
00311                         goto fail;
00312                 }
00313                 rec_ptr = next;
00314         }
00315         res = 0;
00316  fail:
00317         tdb_unlock(tdb, -1, F_WRLCK);
00318         return res;
00319 }
00320 
00321 /* delete an entry in the database given a key */
00322 static int tdb_delete_hash(struct tdb_context *tdb, TDB_DATA key, u32 hash)
00323 {
00324         tdb_off_t rec_ptr;
00325         struct list_struct rec;
00326         int ret;
00327 
00328         if (tdb->max_dead_records != 0) {
00329 
00330                 /*
00331                  * Allow for some dead records per hash chain, mainly for
00332                  * tdb's with a very high create/delete rate like locking.tdb.
00333                  */
00334 
00335                 if (tdb_lock(tdb, BUCKET(hash), F_WRLCK) == -1)
00336                         return -1;
00337 
00338                 if (tdb_count_dead(tdb, hash) >= tdb->max_dead_records) {
00339                         /*
00340                          * Don't let the per-chain freelist grow too large,
00341                          * delete all existing dead records
00342                          */
00343                         tdb_purge_dead(tdb, hash);
00344                 }
00345 
00346                 if (!(rec_ptr = tdb_find(tdb, key, hash, &rec))) {
00347                         tdb_unlock(tdb, BUCKET(hash), F_WRLCK);
00348                         return -1;
00349                 }
00350 
00351                 /*
00352                  * Just mark the record as dead.
00353                  */
00354                 rec.magic = TDB_DEAD_MAGIC;
00355                 ret = tdb_rec_write(tdb, rec_ptr, &rec);
00356         }
00357         else {
00358                 if (!(rec_ptr = tdb_find_lock_hash(tdb, key, hash, F_WRLCK,
00359                                                    &rec)))
00360                         return -1;
00361 
00362                 ret = tdb_do_delete(tdb, rec_ptr, &rec);
00363         }
00364 
00365         if (ret == 0) {
00366                 tdb_increment_seqnum(tdb);
00367         }
00368 
00369         if (tdb_unlock(tdb, BUCKET(rec.full_hash), F_WRLCK) != 0)
00370                 TDB_LOG((tdb, TDB_DEBUG_WARNING, "tdb_delete: WARNING tdb_unlock failed!\n"));
00371         return ret;
00372 }
00373 
00374 int tdb_delete(struct tdb_context *tdb, TDB_DATA key)
00375 {
00376         u32 hash = tdb->hash_fn(&key);
00377         return tdb_delete_hash(tdb, key, hash);
00378 }
00379 
00380 /*
00381  * See if we have a dead record around with enough space
00382  */
00383 static tdb_off_t tdb_find_dead(struct tdb_context *tdb, u32 hash,
00384                                struct list_struct *r, tdb_len_t length)
00385 {
00386         tdb_off_t rec_ptr;
00387         
00388         /* read in the hash top */
00389         if (tdb_ofs_read(tdb, TDB_HASH_TOP(hash), &rec_ptr) == -1)
00390                 return 0;
00391 
00392         /* keep looking until we find the right record */
00393         while (rec_ptr) {
00394                 if (tdb_rec_read(tdb, rec_ptr, r) == -1)
00395                         return 0;
00396 
00397                 if (TDB_DEAD(r) && r->rec_len >= length) {
00398                         /*
00399                          * First fit for simple coding, TODO: change to best
00400                          * fit
00401                          */
00402                         return rec_ptr;
00403                 }
00404                 rec_ptr = r->next;
00405         }
00406         return 0;
00407 }
00408 
00409 /* store an element in the database, replacing any existing element
00410    with the same key 
00411 
00412    return 0 on success, -1 on failure
00413 */
00414 int tdb_store(struct tdb_context *tdb, TDB_DATA key, TDB_DATA dbuf, int flag)
00415 {
00416         struct list_struct rec;
00417         u32 hash;
00418         tdb_off_t rec_ptr;
00419         char *p = NULL;
00420         int ret = -1;
00421 
00422         if (tdb->read_only || tdb->traverse_read) {
00423                 tdb->ecode = TDB_ERR_RDONLY;
00424                 return -1;
00425         }
00426 
00427         /* find which hash bucket it is in */
00428         hash = tdb->hash_fn(&key);
00429         if (tdb_lock(tdb, BUCKET(hash), F_WRLCK) == -1)
00430                 return -1;
00431 
00432         /* check for it existing, on insert. */
00433         if (flag == TDB_INSERT) {
00434                 if (tdb_exists_hash(tdb, key, hash)) {
00435                         tdb->ecode = TDB_ERR_EXISTS;
00436                         goto fail;
00437                 }
00438         } else {
00439                 /* first try in-place update, on modify or replace. */
00440                 if (tdb_update_hash(tdb, key, hash, dbuf) == 0) {
00441                         goto done;
00442                 }
00443                 if (tdb->ecode == TDB_ERR_NOEXIST &&
00444                     flag == TDB_MODIFY) {
00445                         /* if the record doesn't exist and we are in TDB_MODIFY mode then
00446                          we should fail the store */
00447                         goto fail;
00448                 }
00449         }
00450         /* reset the error code potentially set by the tdb_update() */
00451         tdb->ecode = TDB_SUCCESS;
00452 
00453         /* delete any existing record - if it doesn't exist we don't
00454            care.  Doing this first reduces fragmentation, and avoids
00455            coalescing with `allocated' block before it's updated. */
00456         if (flag != TDB_INSERT)
00457                 tdb_delete_hash(tdb, key, hash);
00458 
00459         /* Copy key+value *before* allocating free space in case malloc
00460            fails and we are left with a dead spot in the tdb. */
00461 
00462         if (!(p = (char *)malloc(key.dsize + dbuf.dsize))) {
00463                 tdb->ecode = TDB_ERR_OOM;
00464                 goto fail;
00465         }
00466 
00467         memcpy(p, key.dptr, key.dsize);
00468         if (dbuf.dsize)
00469                 memcpy(p+key.dsize, dbuf.dptr, dbuf.dsize);
00470 
00471         if (tdb->max_dead_records != 0) {
00472                 /*
00473                  * Allow for some dead records per hash chain, look if we can
00474                  * find one that can hold the new record. We need enough space
00475                  * for key, data and tailer. If we find one, we don't have to
00476                  * consult the central freelist.
00477                  */
00478                 rec_ptr = tdb_find_dead(
00479                         tdb, hash, &rec,
00480                         key.dsize + dbuf.dsize + sizeof(tdb_off_t));
00481 
00482                 if (rec_ptr != 0) {
00483                         rec.key_len = key.dsize;
00484                         rec.data_len = dbuf.dsize;
00485                         rec.full_hash = hash;
00486                         rec.magic = TDB_MAGIC;
00487                         if (tdb_rec_write(tdb, rec_ptr, &rec) == -1
00488                             || tdb->methods->tdb_write(
00489                                     tdb, rec_ptr + sizeof(rec),
00490                                     p, key.dsize + dbuf.dsize) == -1) {
00491                                 goto fail;
00492                         }
00493                         goto done;
00494                 }
00495         }
00496 
00497         /*
00498          * We have to allocate some space from the freelist, so this means we
00499          * have to lock it. Use the chance to purge all the DEAD records from
00500          * the hash chain under the freelist lock.
00501          */
00502 
00503         if (tdb_lock(tdb, -1, F_WRLCK) == -1) {
00504                 goto fail;
00505         }
00506 
00507         if ((tdb->max_dead_records != 0)
00508             && (tdb_purge_dead(tdb, hash) == -1)) {
00509                 tdb_unlock(tdb, -1, F_WRLCK);
00510                 goto fail;
00511         }
00512 
00513         /* we have to allocate some space */
00514         rec_ptr = tdb_allocate(tdb, key.dsize + dbuf.dsize, &rec);
00515 
00516         tdb_unlock(tdb, -1, F_WRLCK);
00517 
00518         if (rec_ptr == 0) {
00519                 goto fail;
00520         }
00521 
00522         /* Read hash top into next ptr */
00523         if (tdb_ofs_read(tdb, TDB_HASH_TOP(hash), &rec.next) == -1)
00524                 goto fail;
00525 
00526         rec.key_len = key.dsize;
00527         rec.data_len = dbuf.dsize;
00528         rec.full_hash = hash;
00529         rec.magic = TDB_MAGIC;
00530 
00531         /* write out and point the top of the hash chain at it */
00532         if (tdb_rec_write(tdb, rec_ptr, &rec) == -1
00533             || tdb->methods->tdb_write(tdb, rec_ptr+sizeof(rec), p, key.dsize+dbuf.dsize)==-1
00534             || tdb_ofs_write(tdb, TDB_HASH_TOP(hash), &rec_ptr) == -1) {
00535                 /* Need to tdb_unallocate() here */
00536                 goto fail;
00537         }
00538 
00539  done:
00540         ret = 0;
00541  fail:
00542         if (ret == 0) {
00543                 tdb_increment_seqnum(tdb);
00544         }
00545 
00546         SAFE_FREE(p); 
00547         tdb_unlock(tdb, BUCKET(hash), F_WRLCK);
00548         return ret;
00549 }
00550 
00551 
00552 /* Append to an entry. Create if not exist. */
00553 int tdb_append(struct tdb_context *tdb, TDB_DATA key, TDB_DATA new_dbuf)
00554 {
00555         u32 hash;
00556         TDB_DATA dbuf;
00557         int ret = -1;
00558 
00559         /* find which hash bucket it is in */
00560         hash = tdb->hash_fn(&key);
00561         if (tdb_lock(tdb, BUCKET(hash), F_WRLCK) == -1)
00562                 return -1;
00563 
00564         dbuf = tdb_fetch(tdb, key);
00565 
00566         if (dbuf.dptr == NULL) {
00567                 dbuf.dptr = (char *)malloc(new_dbuf.dsize);
00568         } else {
00569                 char *new_dptr = (char *)realloc(dbuf.dptr,
00570                                             dbuf.dsize + new_dbuf.dsize);
00571                 if (new_dptr == NULL) {
00572                         free(dbuf.dptr);
00573                 }
00574                 dbuf.dptr = new_dptr;
00575         }
00576 
00577         if (dbuf.dptr == NULL) {
00578                 tdb->ecode = TDB_ERR_OOM;
00579                 goto failed;
00580         }
00581 
00582         memcpy(dbuf.dptr + dbuf.dsize, new_dbuf.dptr, new_dbuf.dsize);
00583         dbuf.dsize += new_dbuf.dsize;
00584 
00585         ret = tdb_store(tdb, key, dbuf, 0);
00586         
00587 failed:
00588         tdb_unlock(tdb, BUCKET(hash), F_WRLCK);
00589         SAFE_FREE(dbuf.dptr);
00590         return ret;
00591 }
00592 
00593 
00594 /*
00595   return the name of the current tdb file
00596   useful for external logging functions
00597 */
00598 const char *tdb_name(struct tdb_context *tdb)
00599 {
00600         return tdb->name;
00601 }
00602 
00603 /*
00604   return the underlying file descriptor being used by tdb, or -1
00605   useful for external routines that want to check the device/inode
00606   of the fd
00607 */
00608 int tdb_fd(struct tdb_context *tdb)
00609 {
00610         return tdb->fd;
00611 }
00612 
00613 /*
00614   return the current logging function
00615   useful for external tdb routines that wish to log tdb errors
00616 */
00617 tdb_log_func tdb_log_fn(struct tdb_context *tdb)
00618 {
00619         return tdb->log.log_fn;
00620 }
00621 
00622 
00623 /*
00624   get the tdb sequence number. Only makes sense if the writers opened
00625   with TDB_SEQNUM set. Note that this sequence number will wrap quite
00626   quickly, so it should only be used for a 'has something changed'
00627   test, not for code that relies on the count of the number of changes
00628   made. If you want a counter then use a tdb record.
00629 
00630   The aim of this sequence number is to allow for a very lightweight
00631   test of a possible tdb change.
00632 */
00633 int tdb_get_seqnum(struct tdb_context *tdb)
00634 {
00635         tdb_off_t seqnum=0;
00636 
00637         tdb_ofs_read(tdb, TDB_SEQNUM_OFS, &seqnum);
00638         return seqnum;
00639 }
00640 
00641 int tdb_hash_size(struct tdb_context *tdb)
00642 {
00643         return tdb->header.hash_size;
00644 }
00645 
00646 size_t tdb_map_size(struct tdb_context *tdb)
00647 {
00648         return tdb->map_size;
00649 }
00650 
00651 int tdb_get_flags(struct tdb_context *tdb)
00652 {
00653         return tdb->flags;
00654 }
00655 

Sambaに対してSat Aug 29 21:23:26 2009に生成されました。  doxygen 1.4.7