/* * Copyright (c) 1990 - 1994, Julianne Frances Haugh * Copyright (c) 1996 - 2001, Marek Michałkiewicz * Copyright (c) 2001 - 2006, Tomasz Kłoczko * Copyright (c) 2007 - 2009, Nicolas François * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the copyright holders or contributors may not be used to * endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #ident "$Id$" #include "defines.h" #include #include #include #include #include #include #include #include #include #include "nscd.h" #ifdef WITH_SELINUX #include #endif /* WITH_SELINUX */ #ifdef WITH_TCB #include #endif /* WITH_TCB */ #include "prototypes.h" #include "commonio.h" /* local function prototypes */ static int lrename (const char *, const char *); static int check_link_count (const char *file); static int do_lock_file (const char *file, const char *lock); static /*@null@*/ /*@dependent@*/FILE *fopen_set_perms ( const char *name, const char *mode, const struct stat *sb); static int create_backup (const char *, FILE *); static void free_linked_list (struct commonio_db *); static void add_one_entry ( struct commonio_db *db, /*@owned@*/struct commonio_entry *p); static bool name_is_nis (const char *name); static int write_all (const struct commonio_db *); static /*@dependent@*/ /*@null@*/struct commonio_entry *find_entry_by_name ( struct commonio_db *, const char *); static /*@dependent@*/ /*@null@*/struct commonio_entry *next_entry_by_name ( struct commonio_db *, /*@null@*/struct commonio_entry *pos, const char *); static int lock_count = 0; static bool nscd_need_reload = false; /* * Simple rename(P) alternative that attempts to rename to symlink * target. */ int lrename (const char *old, const char *new) { int res; char *r = NULL; #if defined(S_ISLNK) #ifndef __GLIBC__ char resolved_path[PATH_MAX]; #endif /* !__GLIBC__ */ struct stat sb; if (lstat (new, &sb) == 0 && S_ISLNK (sb.st_mode)) { #ifdef __GLIBC__ /* now a POSIX.1-2008 feature */ r = realpath (new, NULL); #else /* !__GLIBC__ */ r = realpath (new, resolved_path); #endif /* !__GLIBC__ */ if (NULL == r) { perror ("realpath in lrename()"); } else { new = r; } } #endif /* S_ISLNK */ res = rename (old, new); #ifdef __GLIBC__ if (NULL != r) { free (r); } #endif /* __GLIBC__ */ return res; } static int check_link_count (const char *file) { struct stat sb; if (stat (file, &sb) != 0) { return 0; } if (sb.st_nlink != 2) { return 0; } return 1; } static int do_lock_file (const char *file, const char *lock) { int fd; pid_t pid; ssize_t len; int retval; char buf[32]; fd = open (file, O_CREAT | O_EXCL | O_WRONLY, 0600); if (-1 == fd) { return 0; } pid = getpid (); snprintf (buf, sizeof buf, "%lu", (unsigned long) pid); len = (ssize_t) strlen (buf) + 1; if (write (fd, buf, (size_t) len) != len) { (void) close (fd); unlink (file); return 0; } close (fd); if (link (file, lock) == 0) { retval = check_link_count (file); unlink (file); return retval; } fd = open (lock, O_RDWR); if (-1 == fd) { unlink (file); errno = EINVAL; return 0; } len = read (fd, buf, sizeof (buf) - 1); close (fd); if (len <= 0) { unlink (file); errno = EINVAL; return 0; } buf[len] = '\0'; if (get_pid (buf, &pid) == 0) { unlink (file); errno = EINVAL; return 0; } if (kill (pid, 0) == 0) { unlink (file); errno = EEXIST; return 0; } if (unlink (lock) != 0) { unlink (file); return 0; } retval = 0; if ((link (file, lock) == 0) && (check_link_count (file) != 0)) { retval = 1; } unlink (file); return retval; } static /*@null@*/ /*@dependent@*/FILE *fopen_set_perms ( const char *name, const char *mode, const struct stat *sb) { FILE *fp; mode_t mask; mask = umask (0777); fp = fopen (name, mode); (void) umask (mask); if (NULL == fp) { return NULL; } #ifdef HAVE_FCHOWN if (fchown (fileno (fp), sb->st_uid, sb->st_gid) != 0) { goto fail; } #else /* !HAVE_FCHOWN */ if (chown (name, sb->st_mode) != 0) { goto fail; } #endif /* !HAVE_FCHOWN */ #ifdef HAVE_FCHMOD if (fchmod (fileno (fp), sb->st_mode & 0664) != 0) { goto fail; } #else /* !HAVE_FCHMOD */ if (chmod (name, sb->st_mode & 0664) != 0) { goto fail; } #endif /* !HAVE_FCHMOD */ return fp; fail: (void) fclose (fp); /* fopen_set_perms is used for intermediate files */ (void) unlink (name); return NULL; } static int create_backup (const char *backup, FILE * fp) { struct stat sb; struct utimbuf ub; FILE *bkfp; int c; mode_t mask; if (fstat (fileno (fp), &sb) != 0) { return -1; } mask = umask (077); bkfp = fopen (backup, "w"); (void) umask (mask); if (NULL == bkfp) { return -1; } /* TODO: faster copy, not one-char-at-a-time. --marekm */ c = 0; if (fseek (fp, 0, SEEK_SET) == 0) { while ((c = getc (fp)) != EOF) { if (putc (c, bkfp) == EOF) { break; } } } if ((c != EOF) || (ferror (fp) != 0) || (fflush (bkfp) != 0)) { (void) fclose (bkfp); /* FIXME: unlink the backup file? */ return -1; } if ( (fsync (fileno (bkfp)) != 0) || (fclose (bkfp) != 0)) { /* FIXME: unlink the backup file? */ return -1; } ub.actime = sb.st_atime; ub.modtime = sb.st_mtime; (void) utime (backup, &ub); return 0; } static void free_linked_list (struct commonio_db *db) { struct commonio_entry *p; while (NULL != db->head) { p = db->head; db->head = p->next; if (NULL != p->line) { free (p->line); } if (NULL != p->eptr) { db->ops->free (p->eptr); } free (p); } db->tail = NULL; } int commonio_setname (struct commonio_db *db, const char *name) { snprintf (db->filename, sizeof (db->filename), "%s", name); return 1; } bool commonio_present (const struct commonio_db *db) { return (access (db->filename, F_OK) == 0); } int commonio_lock_nowait (struct commonio_db *db) { char file[1024]; char lock[1024]; if (db->locked) { return 1; } snprintf (file, sizeof file, "%s.%lu", db->filename, (unsigned long) getpid ()); snprintf (lock, sizeof lock, "%s.lock", db->filename); if (do_lock_file (file, lock) != 0) { db->locked = true; lock_count++; return 1; } return 0; } int commonio_lock (struct commonio_db *db) { #ifdef HAVE_LCKPWDF /* * only if the system libc has a real lckpwdf() - the one from * lockpw.c calls us and would cause infinite recursion! */ /* * Call lckpwdf() on the first lock. * If it succeeds, call *_lock() only once * (no retries, it should always succeed). */ if (0 == lock_count) { if (lckpwdf () == -1) { return 0; /* failure */ } } if (commonio_lock_nowait (db) != 0) { return 1; /* success */ } ulckpwdf (); return 0; /* failure */ #else /* !HAVE_LCKPWDF */ int i; /* * lckpwdf() not used - do it the old way. */ #ifndef LOCK_TRIES #define LOCK_TRIES 15 #endif #ifndef LOCK_SLEEP #define LOCK_SLEEP 1 #endif for (i = 0; i < LOCK_TRIES; i++) { if (i > 0) { sleep (LOCK_SLEEP); /* delay between retries */ } if (commonio_lock_nowait (db) != 0) { return 1; /* success */ } /* no unnecessary retries on "permission denied" errors */ if (geteuid () != 0) { return 0; } } return 0; /* failure */ #endif /* !HAVE_LCKPWDF */ } static void dec_lock_count (void) { if (lock_count > 0) { lock_count--; if (lock_count == 0) { /* Tell nscd when lock count goes to zero, if any of the files were changed. */ if (nscd_need_reload) { nscd_flush_cache ("passwd"); nscd_flush_cache ("group"); nscd_need_reload = false; } #ifdef HAVE_LCKPWDF ulckpwdf (); #endif /* HAVE_LCKPWDF */ } } } int commonio_unlock (struct commonio_db *db) { char lock[1024]; if (db->isopen) { db->readonly = true; if (commonio_close (db) == 0) { if (db->locked) { dec_lock_count (); } return 0; } } if (db->locked) { /* * Unlock in reverse order: remove the lock file, * then call ulckpwdf() (if used) on last unlock. */ db->locked = false; snprintf (lock, sizeof lock, "%s.lock", db->filename); unlink (lock); dec_lock_count (); return 1; } return 0; } /* * Add an entry at the end. * * defines p->next, p->prev * (unfortunately, owned special are not supported) */ static void add_one_entry (struct commonio_db *db, /*@owned@*/struct commonio_entry *p) { /*@-mustfreeonly@*/ p->next = NULL; p->prev = db->tail; /*@=mustfreeonly@*/ if (NULL == db->head) { db->head = p; } if (NULL != db->tail) { db->tail->next = p; } db->tail = p; } static bool name_is_nis (const char *name) { return (('+' == name[0]) || ('-' == name[0])); } /* * New entries are inserted before the first NIS entry. Order is preserved * when db is written out. */ #ifndef KEEP_NIS_AT_END #define KEEP_NIS_AT_END 1 #endif #if KEEP_NIS_AT_END static void add_one_entry_nis (struct commonio_db *db, /*@owned@*/struct commonio_entry *newp); /* * Insert an entry between the regular entries, and the NIS entries. * * defines newp->next, newp->prev * (unfortunately, owned special are not supported) */ static void add_one_entry_nis (struct commonio_db *db, /*@owned@*/struct commonio_entry *newp) { struct commonio_entry *p; for (p = db->head; NULL != p; p = p->next) { if (name_is_nis (p->eptr ? db->ops->getname (p->eptr) : p->line)) { /*@-mustfreeonly@*/ newp->next = p; newp->prev = p->prev; /*@=mustfreeonly@*/ if (NULL != p->prev) { p->prev->next = newp; } else { db->head = newp; } p->prev = newp; return; } } add_one_entry (db, newp); } #endif /* KEEP_NIS_AT_END */ /* Initial buffer size, as well as increment if not sufficient (for reading very long lines in group files). */ #define BUFLEN 4096 int commonio_open (struct commonio_db *db, int mode) { char *buf; char *cp; char *line; struct commonio_entry *p; void *eptr = NULL; int flags = mode; size_t buflen; int fd; int saved_errno; mode &= ~O_CREAT; if ( db->isopen || ( (O_RDONLY != mode) && (O_RDWR != mode))) { errno = EINVAL; return 0; } db->readonly = (mode == O_RDONLY); if (!db->readonly && !db->locked) { errno = EACCES; return 0; } db->head = db->tail = NULL; db->cursor = NULL; db->changed = false; fd = open (db->filename, (db->readonly ? O_RDONLY : O_RDWR) | O_NOCTTY | O_NONBLOCK | O_NOFOLLOW); saved_errno = errno; db->fp = NULL; if (fd >= 0) { #ifdef WITH_TCB if (tcb_is_suspect (fd) != 0) { (void) close (fd); errno = EINVAL; return 0; } #endif /* WITH_TCB */ db->fp = fdopen (fd, db->readonly ? "r" : "r+"); saved_errno = errno; if (NULL == db->fp) { (void) close (fd); } } errno = saved_errno; /* * If O_CREAT was specified and the file didn't exist, it will be * created by commonio_close(). We have no entries to read yet. --marekm */ if (NULL == db->fp) { if (((flags & O_CREAT) != 0) && (ENOENT == errno)) { db->isopen = true; return 1; } return 0; } /* Do not inherit fd in spawned processes (e.g. nscd) */ fcntl (fileno (db->fp), F_SETFD, FD_CLOEXEC); #ifdef WITH_SELINUX db->scontext = NULL; if ((is_selinux_enabled () > 0) && (!db->readonly)) { if (fgetfilecon (fileno (db->fp), &db->scontext) < 0) { goto cleanup_errno; } } #endif /* WITH_SELINUX */ buflen = BUFLEN; buf = (char *) malloc (buflen); if (NULL == buf) { goto cleanup_ENOMEM; } while (db->ops->fgets (buf, (int) buflen, db->fp) == buf) { while ( ((cp = strrchr (buf, '\n')) == NULL) && (feof (db->fp) == 0)) { size_t len; buflen += BUFLEN; cp = (char *) realloc (buf, buflen); if (NULL == cp) { goto cleanup_buf; } buf = cp; len = strlen (buf); if (db->ops->fgets (buf + len, (int) (buflen - len), db->fp) == NULL) { goto cleanup_buf; } } cp = strrchr (buf, '\n'); if (NULL != cp) { *cp = '\0'; } line = strdup (buf); if (NULL == line) { goto cleanup_buf; } if (name_is_nis (line)) { eptr = NULL; } else { eptr = db->ops->parse (line); if (NULL != eptr) { eptr = db->ops->dup (eptr); if (NULL == eptr) { goto cleanup_line; } } } p = (struct commonio_entry *) malloc (sizeof *p); if (NULL == p) { goto cleanup_entry; } p->eptr = eptr; p->line = line; p->changed = false; add_one_entry (db, p); } free (buf); if (ferror (db->fp) != 0) { goto cleanup_errno; } if ((NULL != db->ops->open_hook) && (db->ops->open_hook () == 0)) { goto cleanup_errno; } db->isopen = true; return 1; cleanup_entry: if (NULL != eptr) { db->ops->free (eptr); } cleanup_line: free (line); cleanup_buf: free (buf); cleanup_ENOMEM: errno = ENOMEM; cleanup_errno: saved_errno = errno; free_linked_list (db); #ifdef WITH_SELINUX if (db->scontext != NULL) { freecon (db->scontext); db->scontext = NULL; } #endif /* WITH_SELINUX */ fclose (db->fp); db->fp = NULL; errno = saved_errno; return 0; } /* * Sort given db according to cmp function (usually compares uids) */ int commonio_sort (struct commonio_db *db, int (*cmp) (const void *, const void *)) { struct commonio_entry **entries, *ptr; size_t n = 0, i; #if KEEP_NIS_AT_END struct commonio_entry *nis = NULL #endif for (ptr = db->head; (NULL != ptr) #if KEEP_NIS_AT_END && ('+' != ptr->line[0]) #endif ; ptr = ptr->next) { n++; } #if KEEP_NIS_AT_END if (NULL != ptr) { nis = ptr; } #endif if (n <= 1) { return 0; } entries = malloc (n * sizeof (struct commonio_entry *)); if (entries == NULL) { return -1; } n = 0; for (ptr = db->head; #if KEEP_NIS_AT_END nis != ptr; #else NULL != ptr; #endif ptr = ptr->next) { entries[n++] = ptr; } qsort (entries, n, sizeof (struct commonio_entry *), cmp); /* Take care of the head and tail separately */ db->head = entries[0]; n--; #if KEEP_NIS_AT_END if (NULL == nis) #endif { db->tail = entries[n]; } db->head->prev = NULL; db->head->next = entries[1]; entries[n]->prev = entries[n - 1]; entries[n]->next = NULL; /* Now other elements have prev and next entries */ for (i = 1; i < n; i++) { entries[i]->prev = entries[i - 1]; entries[i]->next = entries[i + 1]; } free (entries); db->changed = true; return 0; } /* * Sort entries in db according to order in another. */ int commonio_sort_wrt (struct commonio_db *shadow, struct commonio_db *passwd) { struct commonio_entry *head = NULL, *pw_ptr, *spw_ptr; const char *name; if ((NULL == shadow) || (NULL == shadow->head)) { return 0; } for (pw_ptr = passwd->head; NULL != pw_ptr; pw_ptr = pw_ptr->next) { if (NULL == pw_ptr->eptr) { continue; } name = passwd->ops->getname (pw_ptr->eptr); for (spw_ptr = shadow->head; NULL != spw_ptr; spw_ptr = spw_ptr->next) { if (NULL == spw_ptr->eptr) { continue; } if (strcmp (name, shadow->ops->getname (spw_ptr->eptr)) == 0) { break; } } if (NULL == spw_ptr) { continue; } commonio_del_entry (shadow, spw_ptr); spw_ptr->next = head; head = spw_ptr; } for (spw_ptr = head; NULL != spw_ptr; spw_ptr = head) { head = head->next; if (NULL != shadow->head) { shadow->head->prev = spw_ptr; } spw_ptr->next = shadow->head; shadow->head = spw_ptr; } shadow->head->prev = NULL; shadow->changed = true; return 0; } /* * write_all - Write the database to its file. * * It returns 0 if all the entries could be written correctly. */ static int write_all (const struct commonio_db *db) /*@requires notnull db->fp@*/ { const struct commonio_entry *p; void *eptr; for (p = db->head; NULL != p; p = p->next) { if (p->changed) { eptr = p->eptr; assert (NULL != eptr); if (db->ops->put (eptr, db->fp) != 0) { return -1; } } else if (NULL != p->line) { if (db->ops->fputs (p->line, db->fp) == EOF) { return -1; } if (putc ('\n', db->fp) == EOF) { return -1; } } } return 0; } int commonio_close (struct commonio_db *db) /*@requires notnull db->fp@*/ { char buf[1024]; int errors = 0; struct stat sb; #ifdef WITH_SELINUX /*@null@*/security_context_t old_context = NULL; #endif /* WITH_SELINUX */ if (!db->isopen) { errno = EINVAL; return 0; } db->isopen = false; if (!db->changed || db->readonly) { fclose (db->fp); db->fp = NULL; goto success; } if ((NULL != db->ops->close_hook) && (db->ops->close_hook () == 0)) { goto fail; } memzero (&sb, sizeof sb); if (NULL != db->fp) { if (fstat (fileno (db->fp), &sb) != 0) { fclose (db->fp); db->fp = NULL; goto fail; } #ifdef WITH_SELINUX if (db->scontext != NULL) { if (getfscreatecon (&old_context) < 0) { errors++; goto fail; } if (setfscreatecon (db->scontext) < 0) { errors++; goto fail; } } #endif /* WITH_SELINUX */ /* * Create backup file. */ snprintf (buf, sizeof buf, "%s-", db->filename); if (create_backup (buf, db->fp) != 0) { errors++; } if (fclose (db->fp) != 0) { errors++; } if (errors != 0) { db->fp = NULL; goto fail; } } else { /* * Default permissions for new [g]shadow files. * (passwd and group always exist...) */ sb.st_mode = 0400; sb.st_uid = 0; sb.st_gid = 0; } snprintf (buf, sizeof buf, "%s+", db->filename); db->fp = fopen_set_perms (buf, "w", &sb); if (NULL == db->fp) { goto fail; } if (write_all (db) != 0) { errors++; } if (fflush (db->fp) != 0) { errors++; } #ifdef HAVE_FSYNC if (fsync (fileno (db->fp)) != 0) { errors++; } #else /* !HAVE_FSYNC */ sync (); #endif /* !HAVE_FSYNC */ if (fclose (db->fp) != 0) { errors++; } db->fp = NULL; if (errors != 0) { unlink (buf); goto fail; } if (lrename (buf, db->filename) != 0) { goto fail; } nscd_need_reload = true; goto success; fail: errors++; success: #ifdef WITH_SELINUX if (db->scontext != NULL) { if (NULL != old_context) { if (setfscreatecon (old_context) < 0) { errors++; } freecon (old_context); old_context = NULL; } freecon (db->scontext); db->scontext = NULL; } #endif /* WITH_SELINUX */ free_linked_list (db); return errors == 0; } static /*@dependent@*/ /*@null@*/struct commonio_entry *next_entry_by_name ( struct commonio_db *db, /*@null@*/struct commonio_entry *pos, const char *name) { struct commonio_entry *p; void *ep; if (NULL == pos) { return NULL; } for (p = pos; NULL != p; p = p->next) { ep = p->eptr; if ( (NULL != ep) && (strcmp (db->ops->getname (ep), name) == 0)) { break; } } return p; } static /*@dependent@*/ /*@null@*/struct commonio_entry *find_entry_by_name ( struct commonio_db *db, const char *name) { return next_entry_by_name (db, db->head, name); } int commonio_update (struct commonio_db *db, const void *eptr) { struct commonio_entry *p; void *nentry; if (!db->isopen || db->readonly) { errno = EINVAL; return 0; } nentry = db->ops->dup (eptr); if (NULL == nentry) { errno = ENOMEM; return 0; } p = find_entry_by_name (db, db->ops->getname (eptr)); if (NULL != p) { if (next_entry_by_name (db, p->next, db->ops->getname (eptr)) != NULL) { fprintf (stderr, _("Multiple entries named '%s' in %s. Please fix this with pwck or grpck.\n"), db->ops->getname (eptr), db->filename); return 0; } db->ops->free (p->eptr); p->eptr = nentry; p->changed = true; db->cursor = p; db->changed = true; return 1; } /* not found, new entry */ p = (struct commonio_entry *) malloc (sizeof *p); if (NULL == p) { db->ops->free (nentry); errno = ENOMEM; return 0; } p->eptr = nentry; p->line = NULL; p->changed = true; #if KEEP_NIS_AT_END add_one_entry_nis (db, p); #else /* !KEEP_NIS_AT_END */ add_one_entry (db, p); #endif /* !KEEP_NIS_AT_END */ db->changed = true; return 1; } void commonio_del_entry (struct commonio_db *db, const struct commonio_entry *p) { if (p == db->cursor) { db->cursor = p->next; } if (NULL != p->prev) { p->prev->next = p->next; } else { db->head = p->next; } if (NULL != p->next) { p->next->prev = p->prev; } else { db->tail = p->prev; } db->changed = true; } /* * commonio_remove - Remove the entry of the given name from the database. */ int commonio_remove (struct commonio_db *db, const char *name) { struct commonio_entry *p; if (!db->isopen || db->readonly) { errno = EINVAL; return 0; } p = find_entry_by_name (db, name); if (NULL == p) { errno = ENOENT; return 0; } if (next_entry_by_name (db, p->next, name) != NULL) { fprintf (stderr, _("Multiple entries named '%s' in %s. Please fix this with pwck or grpck.\n"), name, db->filename); return 0; } commonio_del_entry (db, p); if (NULL != p->line) { free (p->line); } if (NULL != p->eptr) { db->ops->free (p->eptr); } return 1; } /* * commonio_locate - Find the first entry with the specified name in * the database. * * If found, it returns the entry and set the cursor of the database to * that entry. * * Otherwise, it returns NULL. */ /*@observer@*/ /*@null@*/const void *commonio_locate (struct commonio_db *db, const char *name) { struct commonio_entry *p; if (!db->isopen) { errno = EINVAL; return NULL; } p = find_entry_by_name (db, name); if (NULL == p) { errno = ENOENT; return NULL; } db->cursor = p; return p->eptr; } /* * commonio_rewind - Restore the database cursor to the first entry. * * It returns 0 on error, 1 on success. */ int commonio_rewind (struct commonio_db *db) { if (!db->isopen) { errno = EINVAL; return 0; } db->cursor = NULL; return 1; } /* * commonio_next - Return the next entry of the specified database * * It returns the next entry, or NULL if no other entries could be found. */ /*@observer@*/ /*@null@*/const void *commonio_next (struct commonio_db *db) { void *eptr; if (!db->isopen) { errno = EINVAL; return 0; } if (NULL == db->cursor) { db->cursor = db->head; } else { db->cursor = db->cursor->next; } while (NULL != db->cursor) { eptr = db->cursor->eptr; if (NULL != eptr) { return eptr; } db->cursor = db->cursor->next; } return NULL; }