* Makefile.in, configure.in: break out server lib into a
[krb5.git] / src / lib / kadm5 / srv / adb_openclose.c
1 /*
2  * Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved
3  *
4  * $Header$ 
5  */
6
7 #if !defined(lint) && !defined(__CODECENTER__)
8 static char *rcsid = "$Header$";
9 #endif
10
11 #include        <sys/file.h>
12 #include        <fcntl.h>
13 #include        <unistd.h>
14 #include        "adb.h"
15 #include        <stdlib.h>
16
17 #define MAX_LOCK_TRIES 5
18
19 struct _locklist {
20      osa_adb_lock_ent lockinfo;
21      struct _locklist *next;
22 };
23
24 osa_adb_ret_t osa_adb_create_db(char *filename, char *lockfilename,
25                                 int magic)
26 {
27      FILE *lf;
28      DB *db;
29      HASHINFO info;
30      
31      lf = fopen(lockfilename, "w+");
32      if (lf == NULL)
33           return errno;
34      (void) fclose(lf);
35
36      memset(&info, 0, sizeof(info));
37      info.hash = NULL;
38      info.bsize = 256;
39      info.ffactor = 8;
40      info.nelem = 25000;
41      info.lorder = 0;
42      db = dbopen(filename, O_RDWR | O_CREAT | O_EXCL, 0600, DB_HASH, &info);
43      if (db == NULL)
44           return errno;
45      if (db->close(db) < 0)
46           return errno;
47      return OSA_ADB_OK;
48 }
49
50 osa_adb_ret_t osa_adb_destroy_db(char *filename, char *lockfilename,
51                                  int magic)
52 {
53      /* the admin databases do not contain security-critical data */
54      if (unlink(filename) < 0 ||
55          unlink(lockfilename) < 0)
56           return errno;
57      return OSA_ADB_OK;
58 }
59
60 osa_adb_ret_t osa_adb_init_db(osa_adb_db_t *dbp, char *filename,
61                               char *lockfilename, int magic)
62 {
63      osa_adb_db_t db;
64      static struct _locklist *locklist = NULL;
65      struct _locklist *lockp;
66      krb5_error_code code;
67      
68      if (dbp == NULL || filename == NULL)
69           return EINVAL;
70
71      db = (osa_adb_princ_t) malloc(sizeof(osa_adb_db_ent));
72      if (db == NULL)
73           return ENOMEM;
74
75      memset(db, 0, sizeof(*db));
76      db->info.hash = NULL;
77      db->info.bsize = 256;
78      db->info.ffactor = 8;
79      db->info.nelem = 25000;
80      db->info.lorder = 0;
81
82      /*
83       * A process is allowed to open the same database multiple times
84       * and access it via different handles.  If the handles use
85       * distinct lockinfo structures, things get confused: lock(A),
86       * lock(B), release(B) will result in the kernel unlocking the
87       * lock file but handle A will still think the file is locked.
88       * Therefore, all handles using the same lock file must share a
89       * single lockinfo structure.
90       *
91       * It is not sufficient to have a single lockinfo structure,
92       * however, because a single process may also wish to open
93       * multiple different databases simultaneously, with different
94       * lock files.  This code used to use a single static lockinfo
95       * structure, which means that the second database opened used
96       * the first database's lock file.  This was Bad.
97       *
98       * We now maintain a linked list of lockinfo structures, keyed by
99       * lockfilename.  An entry is added when this function is called
100       * with a new lockfilename, and all subsequent calls with that
101       * lockfilename use the existing entry, updating the refcnt.
102       * When the database is closed with fini_db(), the refcnt is
103       * decremented, and when it is zero the lockinfo structure is
104       * freed and reset.  The entry in the linked list, however, is
105       * never removed; it will just be reinitialized the next time
106       * init_db is called with the right lockfilename.
107       */
108
109      /* find or create the lockinfo structure for lockfilename */
110      lockp = locklist;
111      while (lockp) {
112           if (strcmp(lockp->lockinfo.filename, lockfilename) == 0)
113                break;
114           else
115                lockp = lockp->next;
116      }
117      if (lockp == NULL) {
118           /* doesn't exist, create it, add to list */
119           lockp = (struct _locklist *) malloc(sizeof(*lockp));
120           if (lockp == NULL) {
121                free(db);
122                return ENOMEM;
123           }
124           memset(lockp, 0, sizeof(*lockp));
125           lockp->next = locklist;
126           locklist = lockp;
127      }
128
129      /* now initialize lockp->lockinfo if necessary */
130      if (lockp->lockinfo.lockfile == NULL) {
131           if (code = krb5_init_context(&lockp->lockinfo.context)) {
132                free(db);
133                return((osa_adb_ret_t) code);
134           }
135
136           /*
137            * needs be open read/write so that write locking can work with
138            * POSIX systems
139            */
140           lockp->lockinfo.filename = strdup(lockfilename);
141           if ((lockp->lockinfo.lockfile = fopen(lockfilename, "r+")) == NULL) {
142                /*
143                 * maybe someone took away write permission so we could only
144                 * get shared locks?
145                 */
146                if ((lockp->lockinfo.lockfile = fopen(lockfilename, "r"))
147                    == NULL) {
148                     free(db);
149                     return OSA_ADB_NOLOCKFILE;
150                }
151           }
152           lockp->lockinfo.lockmode = lockp->lockinfo.lockcnt = 0;
153      }
154
155      /* lockp is set, lockinfo is initialized, update the reference count */
156      db->lock = &lockp->lockinfo;
157      db->lock->refcnt++;
158
159      db->filename = strdup(filename);
160      db->magic = magic;
161
162      *dbp = db;
163      
164      return OSA_ADB_OK;
165 }
166
167 osa_adb_ret_t osa_adb_fini_db(osa_adb_db_t db, int magic)
168 {
169      if (db->magic != magic)
170           return EINVAL;
171      if (db->lock->refcnt == 0) {
172           /* barry says this can't happen */
173           return OSA_ADB_FAILURE;
174      } else {
175           db->lock->refcnt--;
176      }
177
178      if (db->lock->refcnt == 0) {
179           /*
180            * Don't free db->lock->filename, it is used as a key to
181            * find the lockinfo entry in the linked list.  If the
182            * lockfile doesn't exist, we must be closing the database
183            * after trashing it.  This has to be allowed, so don't
184            * generate an error.
185            */
186           (void) fclose(db->lock->lockfile);
187           db->lock->lockfile = NULL;
188           krb5_free_context(db->lock->context);
189      }
190
191      db->magic = 0;
192      free(db->filename);
193      free(db);
194      return OSA_ADB_OK;
195 }     
196      
197 osa_adb_ret_t osa_adb_get_lock(osa_adb_db_t db, int mode)
198 {
199      int tries, gotlock, perm, krb5_mode, ret;
200
201      if (db->lock->lockmode >= mode) {
202           /* No need to upgrade lock, just incr refcnt and return */
203           db->lock->lockcnt++;
204           return(OSA_ADB_OK);
205      }
206
207      perm = 0;
208      switch (mode) {
209         case OSA_ADB_PERMANENT:
210           perm = 1;
211         case OSA_ADB_EXCLUSIVE:
212           krb5_mode = KRB5_LOCKMODE_EXCLUSIVE;
213           break;
214         case OSA_ADB_SHARED:
215           krb5_mode = KRB5_LOCKMODE_SHARED;
216           break;
217         default:
218           return(EINVAL);
219      }
220
221      for (gotlock = tries = 0; tries < MAX_LOCK_TRIES; tries++) {
222           if ((ret = krb5_lock_file(db->lock->context,
223                                     fileno(db->lock->lockfile),
224                                     krb5_mode|KRB5_LOCKMODE_DONTBLOCK)) == 0) {
225                gotlock++;
226                break;
227           } else if (ret == EBADF && mode == OSA_ADB_EXCLUSIVE)
228                /* tried to exclusive-lock something we don't have */
229                /* write access to */
230                return OSA_ADB_NOEXCL_PERM;
231
232           sleep(1);
233      }
234
235      /* test for all the likely "can't get lock" error codes */
236      if (ret == EACCES || ret == EAGAIN || ret == EWOULDBLOCK)
237           return OSA_ADB_CANTLOCK_DB;
238      else if (ret != 0)
239           return ret;
240
241      /*
242       * If the file no longer exists, someone acquired a permanent
243       * lock.  If that process terminates its exclusive lock is lost,
244       * but if we already had the file open we can (probably) lock it
245       * even though it has been unlinked.  So we need to insist that
246       * it exist.
247       */
248      if (access(db->lock->filename, F_OK) < 0) {
249           (void) krb5_lock_file(db->lock->context,
250                                 fileno(db->lock->lockfile),
251                                 KRB5_LOCKMODE_UNLOCK);
252           return OSA_ADB_NOLOCKFILE;
253      }
254      
255      /* we have the shared/exclusive lock */
256      
257      if (perm) {
258           if (unlink(db->lock->filename) < 0) {
259                int ret;
260
261                /* somehow we can't delete the file, but we already */
262                /* have the lock, so release it and return */
263
264                ret = errno;
265                (void) krb5_lock_file(db->lock->context,
266                                      fileno(db->lock->lockfile),
267                                      KRB5_LOCKMODE_UNLOCK);
268                
269                /* maybe we should return CANTLOCK_DB.. but that would */
270                /* look just like the db was already locked */
271                return ret;
272           }
273
274           /* this releases our exclusive lock.. which is okay because */
275           /* now no one else can get one either */
276           (void) fclose(db->lock->lockfile);
277      }
278      
279      db->lock->lockmode = mode;
280      db->lock->lockcnt++;
281      return OSA_ADB_OK;
282 }
283
284 osa_adb_ret_t osa_adb_release_lock(osa_adb_db_t db)
285 {
286      int ret;
287      
288      if (!db->lock->lockcnt)            /* lock already unlocked */
289           return OSA_ADB_NOTLOCKED;
290
291      if (--db->lock->lockcnt == 0) {
292           if (db->lock->lockmode == OSA_ADB_PERMANENT) {
293                /* now we need to create the file since it does not exist */
294                if ((db->lock->lockfile = fopen(db->lock->filename,
295                                                "w+")) == NULL)
296                     return OSA_ADB_NOLOCKFILE;
297           } else if (ret = krb5_lock_file(db->lock->context,
298                                           fileno(db->lock->lockfile),
299                                           KRB5_LOCKMODE_UNLOCK))
300                return ret;
301           
302           db->lock->lockmode = 0;
303      }
304      return OSA_ADB_OK;
305 }
306
307 osa_adb_ret_t osa_adb_open_and_lock(osa_adb_princ_t db, int locktype)
308 {
309      int ret;
310
311      ret = osa_adb_get_lock(db, locktype);
312      if (ret != OSA_ADB_OK)
313           return ret;
314      
315      db->db = dbopen(db->filename, O_RDWR, 0600, DB_HASH, &db->info);
316      if (db->db == NULL) {
317           (void) osa_adb_release_lock(db);
318           if(errno == EINVAL)
319                return OSA_ADB_BAD_DB;
320           return errno;
321      }
322      return OSA_ADB_OK;
323 }
324
325 osa_adb_ret_t osa_adb_close_and_unlock(osa_adb_princ_t db)
326 {
327      int ret;
328
329      if(db->db->close(db->db) == -1) {
330           (void) osa_adb_release_lock(db);
331           return OSA_ADB_FAILURE;
332      }
333
334      db->db = NULL;
335
336      return(osa_adb_release_lock(db));
337 }
338