diff options
Diffstat (limited to '')
-rw-r--r-- | lib/commonio.c | 1271 |
1 files changed, 1271 insertions, 0 deletions
diff --git a/lib/commonio.c b/lib/commonio.c new file mode 100644 index 0000000..31edbaa --- /dev/null +++ b/lib/commonio.c @@ -0,0 +1,1271 @@ +/* + * Copyright (c) 1990 - 1994, Julianne Frances Haugh + * Copyright (c) 1996 - 2001, Marek Michałkiewicz + * Copyright (c) 2001 - 2006, Tomasz Kłoczko + * Copyright (c) 2007 - 2011, 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 <config.h> + +#ident "$Id$" + +#include "defines.h" +#include <assert.h> +#include <sys/stat.h> +#include <stdlib.h> +#include <limits.h> +#include <utime.h> +#include <fcntl.h> +#include <errno.h> +#include <stdio.h> +#include <signal.h> +#include "nscd.h" +#ifdef WITH_TCB +#include <tcb.h> +#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, bool log); +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, bool log) +{ + 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) { + if (log) { + (void) fprintf (stderr, + "%s: %s: %s\n", + Prog, file, strerror (errno)); + } + 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) { + if (log) { + (void) fprintf (stderr, + "%s: %s: %s\n", + Prog, file, strerror (errno)); + } + (void) close (fd); + unlink (file); + return 0; + } + close (fd); + + if (link (file, lock) == 0) { + retval = check_link_count (file); + if ((0==retval) && log) { + (void) fprintf (stderr, + "%s: %s: lock file already used\n", + Prog, file); + } + unlink (file); + return retval; + } + + fd = open (lock, O_RDWR); + if (-1 == fd) { + if (log) { + (void) fprintf (stderr, + "%s: %s: %s\n", + Prog, lock, strerror (errno)); + } + unlink (file); + errno = EINVAL; + return 0; + } + len = read (fd, buf, sizeof (buf) - 1); + close (fd); + if (len <= 0) { + if (log) { + (void) fprintf (stderr, + "%s: existing lock file %s without a PID\n", + Prog, lock); + } + unlink (file); + errno = EINVAL; + return 0; + } + buf[len] = '\0'; + if (get_pid (buf, &pid) == 0) { + if (log) { + (void) fprintf (stderr, + "%s: existing lock file %s with an invalid PID '%s'\n", + Prog, lock, buf); + } + unlink (file); + errno = EINVAL; + return 0; + } + if (kill (pid, 0) == 0) { + if (log) { + (void) fprintf (stderr, + "%s: lock %s already used by PID %lu\n", + Prog, lock, (unsigned long) pid); + } + unlink (file); + errno = EEXIST; + return 0; + } + if (unlink (lock) != 0) { + if (log) { + (void) fprintf (stderr, + "%s: cannot get lock %s: %s\n", + Prog, lock, strerror (errno)); + } + unlink (file); + return 0; + } + + retval = 0; + if (link (file, lock) == 0) { + retval = check_link_count (file); + if ((0==retval) && log) { + (void) fprintf (stderr, + "%s: %s: lock file already used\n", + Prog, file); + } + } else { + if (log) { + (void) fprintf (stderr, + "%s: cannot get lock %s: %s\n", + Prog, lock, strerror (errno)); + } + } + + 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; + + if (fstat (fileno (fp), &sb) != 0) { + return -1; + } + + bkfp = fopen_set_perms (backup, "w", &sb); + 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, bool log) +{ + 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, log) != 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) { + if (geteuid () != 0) { + (void) fprintf (stderr, + "%s: Permission denied.\n", + Prog); + } + return 0; /* failure */ + } + } + + if (commonio_lock_nowait (db, true) != 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, i==LOCK_TRIES-1) != 0) { + return 1; /* success */ + } + /* no unnecessary retries on "permission denied" errors */ + if (geteuid () != 0) { + (void) fprintf (stderr, "%s: Permission denied.\n", + Prog); + 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 = NULL; + 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); + + 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); + 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 + && ((NULL == ptr->line) + || (('+' != ptr->line[0]) + && ('-' != 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 +/*@ -nullderef @*/ + ptr = ptr->next +/*@ +nullderef @*/ + ) { + entries[n] = ptr; + n++; + } + 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]; +#if KEEP_NIS_AT_END + entries[n]->next = nis; +#else + entries[n]->next = NULL; +#endif + + /* 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, + const 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; + + if (!db->isopen) { + errno = EINVAL; + return 0; + } + db->isopen = false; + + if (!db->changed || db->readonly) { + (void) 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) { + (void) fclose (db->fp); + db->fp = NULL; + goto fail; + } + + /* + * Create backup file. + */ + snprintf (buf, sizeof buf, "%s-", db->filename); + +#ifdef WITH_SELINUX + if (set_selinux_file_context (buf) != 0) { + errors++; + } +#endif + if (create_backup (buf, db->fp) != 0) { + errors++; + } + + if (fclose (db->fp) != 0) { + errors++; + } + +#ifdef WITH_SELINUX + if (reset_selinux_file_context () != 0) { + errors++; + } +#endif + if (errors != 0) { + db->fp = NULL; + goto fail; + } + } else { + /* + * Default permissions for new [g]shadow files. + */ + sb.st_mode = db->st_mode; + sb.st_uid = db->st_uid; + sb.st_gid = db->st_gid; + } + + snprintf (buf, sizeof buf, "%s+", db->filename); + +#ifdef WITH_SELINUX + if (set_selinux_file_context (buf) != 0) { + errors++; + } +#endif + + 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; + } + +#ifdef WITH_SELINUX + if (reset_selinux_file_context () != 0) { + goto fail; + } +#endif + + nscd_need_reload = true; + goto success; + fail: + errors++; + success: + + 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); + db->ops->free (nentry); + 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; +} + +#ifdef ENABLE_SUBIDS +int commonio_append (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; + } + /* 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; + add_one_entry (db, p); + + db->changed = true; + return 1; +} +#endif /* ENABLE_SUBIDS */ + +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; +} + |