root/usr/src/lib/libnisdb/yptol/map_ctrl.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * DESCRIPTION: Contains functions relating to the creation and manipulation
 *              of map_ctrl structures. These are used to hold information
 *              specific to one NIS map.
 *
 *              Because each of these contains a significant amount of state
 *              information about an individual map they are created (on the
 *              heap) when a map is opened and destroyed when it is closed.
 *              The overhead of doing this is less than maintaining a pool
 *              of map_ctrls.
 *
 *              If two processes access the same map two map_ctrls will be
 *              created with similar contents (but differing DBM pointers).
 *              Both will have the same hash value so when one is locked
 *              access to the other will also be prevented.
 */

#include <unistd.h>
#include <stdlib.h>
#include <syslog.h>
#include <ndbm.h>
#include <string.h>
#include <sys/param.h>
#include "ypsym.h"
#include "ypdefs.h"
#include "shim.h"
#include "yptol.h"
#include "../ldap_util.h"

extern int hash(char *s);
extern bool_t add_map_domain_to_list(char *domain, char ***map_list);

/*
 * Static variables for locking mechanism in
 * N2L mode.
 *  map_id_list: hash table for map lists
 *  max_map: max number of maps in map_id_list
 *      it is also used as the map ID for
 *      unknown maps, see get_map_id()
 *      in usr/src/cmd/ypcmd/shared/lockmap.c
 */
static map_id_elt_t *map_id_list[MAXHASH];
static int max_map = 0;

/* Switch on parts of ypdefs.h */
USE_DBM
USE_YPDBPATH

/*
 * FUNCTION:    create_map_ctrl();
 *
 * DESCRIPTION: Create and a new map_ctrl in a non opened state.
 *
 * INPUTS:      Fully qualified map name
 *
 * OUTPUTS:     Pointer to map_ctrl
 *              NULL on failure.
 *
 */
map_ctrl *
create_map_ctrl(char *name)
{
        char *myself = "create_map_ctrl";
        map_ctrl *map;

        map = (map_ctrl *)am(myself, sizeof (map_ctrl));
        if (NULL == map) {
                logmsg(MSG_NOTIMECHECK, LOG_ERR, "Could not alloc map_ctrl");
                return (NULL);
        }

        /* Clear new map (in case we have to free it) */
        map->entries = NULL;
        map->hash_val = 0;
        map->map_name = NULL;
        map->domain = NULL;
        map->map_path = NULL;
        map->ttl = NULL;
        map->ttl_path = NULL;
        map->trad_map_path = NULL;
        map->key_data.dptr = NULL;
        map->open_mode = 0;
        map->open_flags = 0;

        /*
         * Initialize the fields of the map_ctrl. By doing this once here we
         * can save a lot of work as map entries are accessed.
         */
        if (SUCCESS != map_ctrl_init(map, name)) {
                logmsg(MSG_NOTIMECHECK, LOG_ERR,
                                "Could not initialize map_ctrl for %s", name);
                free_map_ctrl(map);
                return (NULL);
        }

        return (map);
}

/*
 * FUNCTION :   map_ctrl_init()
 *
 * DESCRIPTION: Initializes the fields of a map_ctrl structure.
 *
 *              By doing this once (when the map_ctrl is created) we avoid
 *              numerous other function having to repeat this string
 *              manipulation.
 *
 * GIVEN :      Pointer to the structure
 *              Fully qualified name of the map
 *
 * RETURNS :    SUCCESS = map_ctrl fully set up.
 *              FAILURE = map_ctrl not set up CALLER MUST FREE.
 */
suc_code
map_ctrl_init(map_ctrl *map, char *name)
{
        char *myself = "map_ctrl_init";
        char *p, *q;

        /* Save map path for future reference */
        map->map_path = (char *)strdup(name);
        if (NULL ==  map->map_path) {
                logmsg(MSG_NOMEM, LOG_ERR,
                                "Could not duplicate map path %s", map);
                return (FAILURE);
        }

        /* Work out map's unqualified name from path */
        p = strrchr(name, SEP_CHAR);
        if (NULL == p) {
                /* Must be at least a domain and name */
                logmsg(MSG_NOTIMECHECK, LOG_ERR,
                        "Could not find separator in map path %s", map);
                return (FAILURE);
        }
        q = p + 1;

        /* Check for and remove N2L prefix */
        if (yptol_mode) {
                /*
                 * Check for and remove N2L prefix. If not found not a problem
                 * we open some old style maps during DIT initialization.
                 */
                if (0 == strncmp(q, NTOL_PREFIX, strlen(NTOL_PREFIX)))
                        q += strlen(NTOL_PREFIX);
        } else {
                if (0 == strncmp(q, NTOL_PREFIX, strlen(NTOL_PREFIX)))
                        logmsg(MSG_NOTIMECHECK, LOG_ERR,
                                "Working in non N2L mode and path %s "
                                "contains N2L prefix", name);
        }

        /* Save unqualified map name */
        map->map_name = strdup(q);
        if (NULL == map->map_name) {
                logmsg(MSG_NOMEM, LOG_ERR,
                                "Could not duplicate map name %s", q);
                return (FAILURE);
        }

        /* Work out map's domain name from path */
        for (q = p-1; (SEP_CHAR != *q) && (q > name); q--);

        if (q <= name) {
                /* Didn't find separator */
                logmsg(MSG_NOTIMECHECK, LOG_ERR,
                                "Could not find domain in map path %s", name);
                return (FAILURE);
        }

        map->domain = (char *)am(myself, p - q);
        if (NULL == map->domain) {
                logmsg(MSG_NOMEM, LOG_ERR,
                        "Could not alloc memory for domain in path %s", name);
                return (FAILURE);
        }
        (void) strncpy(map->domain, q + 1, p-q-1);
        map->domain[p-q-1] = '\0';

        /* Work out extra names required by N2L */
        if (yptol_mode) {
                /*
                 * Work out what old style NIS path would have been. This is
                 * used to check for date of DBM file so add the DBM
                 * extension.
                 */
                map->trad_map_path = (char *)am(myself, strlen(map->map_name) +
                                        + strlen(dbm_pag) + (p - name) + 2);
                if (NULL == map->trad_map_path) {
                        logmsg(MSG_NOMEM, LOG_ERR,
                                "Could not alocate memory for "
                                "traditional map path derived from %s", name);
                        return (FAILURE);
                }

                strncpy(map->trad_map_path, name, p - name + 1);
                map->trad_map_path[p - name + 1] = '\0';
                strcat(map->trad_map_path, map->map_name);
                strcat(map->trad_map_path, dbm_pag);

                /* Generate qualified TTL file name */
                map->ttl_path = (char *)am(myself, strlen(map->map_path) +
                                                strlen(TTL_POSTFIX) + 1);
                if (NULL == map->ttl_path) {
                        logmsg(MSG_NOMEM, LOG_ERR,
                                "Could not alocate memory for "
                                "ttl path derived from %s", name);
                        return (FAILURE);
                }

                strcpy(map->ttl_path, map->map_path);
                strcat(map->ttl_path, TTL_POSTFIX);
        }

        /* Work out hash value */
        map->hash_val = hash(name);

        /* Set up magic number */
        map->magic = MAP_MAGIC;

        /* Null out pointers */
        map->entries = NULL;
        map->ttl = NULL;

        /* No key data yet */
        map->key_data.dptr = NULL;
        map->key_data.dsize = 0;

        return (SUCCESS);
}

/*
 * FUNCTION:    get_map_crtl();
 *
 * DESCRIPTION: Find an existing map_ctrl for a map of a given DBM * (i.e.
 *              handle) . If none exists return an error.
 *
 * INPUTS:      Map handle
 *
 * OUTPUTS:     Pointer to map_ctrl
 *              NULL on failure.
 *
 */
map_ctrl *
get_map_ctrl(DBM *db)
{
        /* Check that this really is a map_ctrl not a DBM */
        if (((map_ctrl *)db)->magic != MAP_MAGIC) {
                logmsg(MSG_NOTIMECHECK, LOG_ERR,
                                "SHIM called with DBM ptr not map_crtl ptr");
                return (NULL);
        }

        /* Since this is an opaque pointer just cast it */
        return ((map_ctrl *)db);
}

/*
 * FUNCTION:    dup_map_ctrl()
 *
 * DESCRIPTION: Duplicates a map_ctrl structure
 *
 * GIVEN :      Map_ctrl to duplicate
 *
 * RETURNS :    Pointer to a new malloced map_ctrl. CALLER MUST FREE
 *              NULL on failure.
 */
map_ctrl *
dup_map_ctrl(map_ctrl *old_map)
{
        map_ctrl *new_map;

        /*
         * Could save a little bit of time by duplicating the static parts
         * of the old map but on balance it is cleaner to just make a new one
         * from scratch
         */
        new_map = create_map_ctrl(old_map->map_path);

        if (NULL == new_map)
                return (NULL);

        /* If old map had open handles duplicate them */
        if (NULL != old_map->entries) {
                new_map->open_flags = old_map->open_flags;
                new_map->open_mode = old_map->open_mode;
                if (FAILURE == open_yptol_files(new_map)) {
                        free_map_ctrl(new_map);
                        return (NULL);
                }
        }

        return (new_map);
}

/*
 * FUNCTION:    free_map_crtl();
 *
 * DESCRIPTION: Free contents of a map_ctr structure and closed any open
 *              DBM files.
 *
 * INPUTS:      Pointer to pointer to a map_ctrl.
 *
 * OUTPUTS:     Nothing
 *
 */
void
free_map_ctrl(map_ctrl *map)
{

        if (NULL != map->entries) {
                dbm_close(map->entries);
                map->entries = NULL;
        }

        if (NULL != map->map_name) {
                sfree(map->map_name);
                map->map_name = NULL;
        }

        if (NULL != map->map_path) {
                sfree(map->map_path);
                map->map_path = NULL;
        }

        if (NULL != map->domain) {
                sfree(map->domain);
                map->domain = NULL;
        }

        if (yptol_mode) {
                if (NULL != map->ttl) {
                        dbm_close(map->ttl);
                        map->ttl = NULL;
                }

                if (NULL != map->trad_map_path) {
                        sfree(map->trad_map_path);
                        map->trad_map_path = NULL;
                }

                if (NULL != map->ttl_path) {
                        sfree(map->ttl_path);
                        map->ttl_path = NULL;
                }

                if (NULL != map->key_data.dptr) {
                        sfree(map->key_data.dptr);
                        map->key_data.dptr = NULL;
                        map->key_data.dsize = 0;
                }
        }

        map->magic = 0;

        /* Since map_ctrls are now always in malloced memory */
        sfree(map);

}

/*
 * FUNCTION :   get_map_name()
 *
 * DESCRIPTION: Get the name of a map from its map_ctrl. This could be done
 *              as a simple dereference but this function hides the internal
 *              implementation of map_ctrl from higher layers.
 *
 * GIVEN :      A map_ctrl pointer
 *
 * RETURNS :    A pointer to the map_ctrl. Higher levels treat this as an
 *              opaque DBM pointer.
 *              NULL on failure.
 */
char *
get_map_name(DBM *db)
{
        map_ctrl *map = (map_ctrl *)db;

        if (NULL == map)
                return (NULL);

        return (map->map_name);
}

/*
 * FUNCTION :   set_key_data()
 *
 * DESCRIPTION: Sets up the key data freeing any that already exists.
 *
 * GIVEN :      Pointer to the map_ctrl to set up.
 *              Datum containing the key. The dptr of this will be set to
 *              point to the key data.
 *
 * RETURNS :    Nothing
 */
void
set_key_data(map_ctrl *map, datum *data)
{
        char *myself = "set_key_data";

        /*
         * Free up any existing key data. Because each dbm file can only have
         * one enumeration going at a time this is safe.
         */
        if (NULL != map->key_data.dptr) {
                sfree(map->key_data.dptr);
                map->key_data.dptr = NULL;
                map->key_data.dsize = 0;
        }

        /* If nothing in key just return */
        if (NULL == data->dptr)
                return;

        /* Something is in the key so must duplicate out of static memory */
        map->key_data.dptr = (char *)am(myself, data->dsize);
        if (NULL == map->key_data.dptr) {
                logmsg(MSG_NOMEM, LOG_ERR, "Cannot alloc memory for key data");
        } else {
                memcpy(map->key_data.dptr, data->dptr, data->dsize);
                map->key_data.dsize = data->dsize;
        }

        /* Set datum to point to malloced version of the data */
        data->dptr = map->key_data.dptr;

        return;

}

/*
 * FUNCTION :   open_yptol_files()
 *
 * DESCRIPTION: Opens both yptol files for a map. This is called both when a
 *              map is opened and when it is reopened as a result of an update
 *              operation. Must be called with map locked.
 *
 * GIVEN :      Initialized map_ctrl
 *
 * RETURNS :    SUCCESS = Maps opened
 *              FAILURE = Maps not opened (and mess tidied up)
 */
suc_code
open_yptol_files(map_ctrl *map)
{

        /* Open entries map */
        map->entries = dbm_open(map->map_path, map->open_flags, map->open_mode);

        if (NULL == map->entries) {
                /* Maybe we were asked to open a non-existent map. No problem */
                return (FAILURE);
        }

        if (yptol_mode) {
                /* Open TTLs map. Must always be writable */
                map->ttl = dbm_open(map->ttl_path, O_RDWR | O_CREAT, 0644);
                if (NULL == map->ttl) {
                        logmsg(MSG_NOTIMECHECK, LOG_ERR,
                                "Cannot open TTL file %s", map->ttl_path);
                        dbm_close(map->entries);
                        map->entries = NULL;
                        return (FAILURE);
                }
        }

        return (SUCCESS);
}

/*
 * FUNCTION :   insert_map_in_list()
 *
 * DESCRIPTION: add a map in map_id_list[]
 *
 * GIVEN :      map name
 *              map unique ID
 *
 * RETURNS :    SUCCESS = map added
 *              FAILURE = map not added
 */
suc_code
insert_map_in_list(char *map_name, int unique_value)
{
        int index;
        bool_t yptol_nl_sav = yptol_newlock;
        map_id_elt_t *new_elt;

        /*
         * Index in the hash table is computed from the original
         * hash function: make sure yptol_newlock is set to false.
         */
        yptol_newlock = FALSE;
        index = hash(map_name);
        yptol_newlock = yptol_nl_sav;

        new_elt = (map_id_elt_t *)calloc(1, sizeof (map_id_elt_t));
        if (new_elt == NULL) {
                return (FAILURE);
        }
        new_elt->map_name = strdup(map_name);
        if (new_elt->map_name == NULL) { /* strdup() failed */
                sfree(new_elt);
                return (FAILURE);
        }
        new_elt->map_id = unique_value;

        if (map_id_list[index] == NULL) {
                new_elt->next = NULL;
        } else {
                new_elt->next = map_id_list[index];
        }
        /* insert at begining */
        map_id_list[index] = new_elt;

        return (SUCCESS);
}

#ifdef NISDB_LDAP_DEBUG
/*
 * FUNCTION :   dump_map_id_list()
 *
 * DESCRIPTION: display max_map and dump map_id_list[]
 *              not called, here for debug convenience only
 *
 * GIVEN :      nothing
 *
 * RETURNS :    nothing
 */
void
dump_map_id_list()
{
        int i;
        map_id_elt_t *cur_elt;

        logmsg(MSG_NOTIMECHECK, LOG_DEBUG,
                "dump_map_id_list: max_map is: %d, dumping map_idlist ...",
                max_map);

        for (i = 0; i < MAXHASH; i++) {
                if (map_id_list[i] == NULL) {
                        logmsg(MSG_NOTIMECHECK, LOG_DEBUG,
                                "no map for index %d", i);
                } else {
                        logmsg(MSG_NOTIMECHECK, LOG_DEBUG,
                                "index %d has the following maps", i);
                        cur_elt = map_id_list[i];
                        do {
                                logmsg(MSG_NOTIMECHECK, LOG_DEBUG,
                                        "%s, unique id: %d",
                                        cur_elt->map_name,
                                        cur_elt->map_id);
                                cur_elt = cur_elt->next;
                        } while (cur_elt != NULL);
                }
        }
}
#endif

/*
 * FUNCTION :   free_map_id_list()
 *
 * DESCRIPTION: free all previously allocated elements of map_id_list[]
 *              reset max_map to 0
 *
 * GIVEN :      nothing
 *
 * RETURNS :    nothing
 */
void
free_map_id_list()
{
        int i;
        map_id_elt_t *cur_elt, *next_elt;

        for (i = 0; i < MAXHASH; i++) {
                if (map_id_list[i] != NULL) {
                        cur_elt = map_id_list[i];
                        do {
                                next_elt = cur_elt->next;
                                if (cur_elt->map_name)
                                        sfree(cur_elt->map_name);
                                sfree(cur_elt);
                                cur_elt = next_elt;
                        } while (cur_elt != NULL);
                        map_id_list[i] = NULL;
                }
        }
        max_map = 0;
}

/*
 * FUNCTION :   map_id_list_init()
 *
 * DESCRIPTION: initializes map_id_list[] and max_map
 *
 * GIVEN :      nothing
 *
 * RETURNS :     0 if OK
 *              -1 if failure
 */
int
map_id_list_init()
{
        char **domain_list, **map_list = NULL;
        int domain_num;
        int i, j;
        char *myself = "map_id_list_init";
        char mapbuf[MAXPATHLEN];
        int mapbuf_len = sizeof (mapbuf);
        int map_name_len;
        int seqnum = 0;
        int rc = 0;

        for (i = 0; i < MAXHASH; i++) {
                map_id_list[i] = NULL;
        }

        domain_num = get_mapping_domain_list(&domain_list);
        for (i = 0; i < domain_num; i++) {
                if (map_list) {
                        free_map_list(map_list);
                        map_list = NULL;
                }

                /* get map list from mapping file */
                map_list = get_mapping_map_list(domain_list[i]);
                if (map_list == NULL) {
                        /* no map for this domain in mapping file */
                        logmsg(MSG_NOTIMECHECK, LOG_DEBUG,
                            "%s: get_mapping_map_list()"
                            " found no map for domain %s",
                            myself, domain_list[i]);
                }

                /* add maps from /var/yp/<domain> */
                if (add_map_domain_to_list(domain_list[i], &map_list) ==
                    FALSE) {
                        logmsg(MSG_NOTIMECHECK, LOG_ERR,
                            "%s: add_map_domain_to_list() failed", myself);
                        free_map_id_list();
                        if (map_list) free_map_list(map_list);
                        return (-1);
                }

                if (map_list == NULL || map_list[0] == NULL) {
                        logmsg(MSG_NOTIMECHECK, LOG_DEBUG,
                            "%s: no map in domain %s",
                            myself, domain_list[i]);
                        continue;
                }

                for (j = 0; map_list[j] != NULL; j++) {
                        /* build long name */
                        map_name_len = ypdbpath_sz + 1 +
                                        strlen(domain_list[i]) + 1 +
                                        strlen(NTOL_PREFIX) +
                                        strlen(map_list[j]) + 1;
                        if (map_name_len > mapbuf_len) {
                                logmsg(MSG_NOTIMECHECK, LOG_ERR,
                                    "%s: map name too long for %s",
                                    " in domain %s", myself, map_list[j],
                                    domain_list[i]);
                                free_map_id_list();
                                if (map_list) free_map_list(map_list);
                                return (-1);
                        }
                        (void) memset(mapbuf, 0, mapbuf_len);
                        (void) snprintf(mapbuf, map_name_len, "%s%c%s%c%s%s",
                                ypdbpath, SEP_CHAR, domain_list[i], SEP_CHAR,
                                NTOL_PREFIX, map_list[j]);

                        if (insert_map_in_list(mapbuf, seqnum)
                                    == FAILURE) {
                                logmsg(MSG_NOTIMECHECK, LOG_ERR,
                                    "%s: failed to insert map %s",
                                    " in domain %s", myself, map_list[j]);
                                free_map_id_list();
                                if (map_list) free_map_list(map_list);
                                return (-1);
                        }
                        seqnum++;
                }
        }

        max_map = seqnum;

#ifdef NISDB_LDAP_DEBUG
        dump_map_id_list();
#endif

        /*
         * If more maps than allocated spaces in shared memory, that's a failure
         * probably need to free previously allocated memory if failure,
         * before returning.
         */
        if (max_map > MAXHASH) {
                rc = -1;
                logmsg(MSG_NOTIMECHECK, LOG_ERR,
                    "%s: too many maps (%d)",
                    myself, max_map);
                free_map_id_list();
        }
        if (map_list) free_map_list(map_list);
        return (rc);
}

/*
 * FUNCTION :   get_list_max()
 *
 * DESCRIPTION: return references to static variables map_id_list
 *              and max_map;
 *
 * GIVEN :      address for referencing map_id_list
 *              address for referencing max_map
 *
 * RETURNS :    nothing
 */
void
get_list_max(map_id_elt_t ***list, int *max)
{
        *list = map_id_list;
        *max = max_map;
}