/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 (c) 2001 by Sun Microsystems, Inc. * All rights reserved. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * This module contains the private layer API. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Threading notes for private layer consumers: * * The handles returned from open_dd() may be shared across multiple * threads with no adverse side effects. However, it's up to that consumer * to ensure that all threads have finished using an instance before * closing the instance or removing the container it's referencing. * Phrased differently: * * * Consumers must ensure all threads sharing a handle are * finished before calling close_dd(). * * * Consumers must ensure all threads referencing a container are * closed before calling remove_dd(). */ static boolean_t validate_dd_entry(dsvc_handle_t, const void *, boolean_t); static int synch_init(dsvc_handle_t, const char *, uint_t); static void synch_fini(dsvc_handle_t); /* * Order here should match the function array in */ static char *funcnames[] = { "status", "version", "mklocation", "list_dt", "open_dt", "close_dt", "remove_dt", "lookup_dt", "add_dt", "modify_dt", "delete_dt", "list_dn", "open_dn", "close_dn", "remove_dn", "lookup_dn", "add_dn", "modify_dn", "delete_dn" }; extern dsvc_synch_ops_t dsvcd_synch_ops; /* * Retrieve the current version associated with the datastore named by * `resource' and store in `converp'. One might think we could do this via * a simple readlink(2), but on internal release builds $(ROOTLINKS) * installs using hardlinks, not symlinks. For this reason and to make it * harder for us to be fooled, we'll dredge up the actual soname through * ELF. Close your eyes, it's gonna get ugly. */ static int get_conver(const char *resource, int *converp) { int elf_fd; int i; GElf_Shdr gelf_shdr; GElf_Dyn gelf_dyn; Elf_Scn *elf_scn = NULL; Elf *elf_file; Elf_Data *elf_data; char *soname = NULL; char path[MAXPATHLEN]; (void) snprintf(path, sizeof (path), "%s%s/%s_%s.so", DHCP_CONFOPT_ROOT, DSVC_MODULE_DIR, DSVC_PUBLIC_PREFIX, resource); elf_fd = open(path, O_RDONLY); if (elf_fd == -1) return (DSVC_MODULE_ERR); if (elf_version(EV_CURRENT) == EV_NONE) { (void) close(elf_fd); return (DSVC_INTERNAL); } elf_file = elf_begin(elf_fd, ELF_C_READ, NULL); if (elf_file == NULL || elf_kind(elf_file) != ELF_K_ELF) { (void) close(elf_fd); return (DSVC_INTERNAL); } while ((elf_scn = elf_nextscn(elf_file, elf_scn)) != NULL) { if (gelf_getshdr(elf_scn, &gelf_shdr) == 0) continue; if (gelf_shdr.sh_type != SHT_DYNAMIC) continue; elf_data = elf_getdata(elf_scn, NULL); if (elf_data == NULL) continue; i = 0; do { (void) gelf_getdyn(elf_data, i++, &gelf_dyn); if (gelf_dyn.d_tag == DT_SONAME) soname = elf_strptr(elf_file, gelf_shdr.sh_link, gelf_dyn.d_un.d_ptr); } while (gelf_dyn.d_tag != DT_NULL && soname == NULL); } if (soname == NULL || sscanf(soname, "%*[^.].so.%d", converp) != 1) { (void) elf_end(elf_file); (void) close(elf_fd); return (DSVC_MODULE_ERR); } (void) elf_end(elf_file); (void) close(elf_fd); return (DSVC_SUCCESS); } /* * Unload a public datastore module. */ static int unload_public_module(void **instance, dsvc_splapi_t *api) { static dsvc_splapi_t null_api; if (dlclose(*instance) != 0) return (DSVC_MODULE_UNLOAD_ERR); *instance = NULL; *api = null_api; return (DSVC_SUCCESS); } /* * Load public datastore module. Validates version of module. Returns * instance of opened module, and populates the api argument with the * function addresses exporting the API. */ static int load_public_module(dsvc_datastore_t *ddp, void **instance, dsvc_splapi_t *api) { int i, v; dsvc_splfuncp_t configure; char path[MAXPATHLEN]; (void) snprintf(path, sizeof (path), "%s%s/%s_%s.so", DHCP_CONFOPT_ROOT, DSVC_MODULE_DIR, DSVC_PUBLIC_PREFIX, ddp->d_resource); if (ddp->d_conver != DSVC_CUR_CONVER) (void) snprintf(path, sizeof (path), "%s.%d", path, ddp->d_conver); *instance = dlopen(path, RTLD_LAZY|RTLD_GROUP|RTLD_WORLD); if (*instance == NULL) return (DSVC_MODULE_LOAD_ERR); /* * No requirement to duplicate the names - we can always reference * the same set. */ api->version = (dsvc_splfuncp_t)dlsym(*instance, "version"); if (api->version == NULL || api->version(&v) != DSVC_SUCCESS || v != DSVC_PUBLIC_VERSION) { (void) unload_public_module(instance, api); return (DSVC_MODULE_VERSION); } configure = (dsvc_splfuncp_t)dlsym(*instance, "configure"); if (configure != NULL) { if (configure(ddp->d_config) != DSVC_SUCCESS) { (void) unload_public_module(instance, api); return (DSVC_MODULE_CFG_ERR); } } for (i = 0; i < DSVC_NSPLFUNCS; i++) { if ((((dsvc_splfuncp_t *)api)[i] = (dsvc_splfuncp_t)dlsym(*instance, funcnames[i])) == NULL) { (void) unload_public_module(instance, api); return (DSVC_MODULE_ERR); } } /* * Caller requested the current version; fill in what that current * version is. */ if (ddp->d_conver == DSVC_CUR_CONVER) { int error; error = get_conver(ddp->d_resource, &ddp->d_conver); if (error != DSVC_SUCCESS) { (void) unload_public_module(instance, api); return (error); } } return (DSVC_SUCCESS); } /* * Return a dynamically-allocated null-terminated list of the available * modules stored in the module directory. A count of the available * modules is stored in the num argument. Caller is responsible for * freeing the list. */ int enumerate_dd(char ***modules, int *nump) { int i, retval; char *ptr; glob_t globbuf; char globpat[MAXPATHLEN]; if (modules == NULL || nump == NULL) return (DSVC_INVAL); (void) snprintf(globpat, sizeof (globpat), "%s%s/%s_*\\.so", DHCP_CONFOPT_ROOT, DSVC_MODULE_DIR, DSVC_PUBLIC_PREFIX); retval = glob(globpat, GLOB_NOSORT, NULL, &globbuf); if (retval != 0) { globfree(&globbuf); switch (retval) { case GLOB_NOMATCH: *nump = 0; *modules = NULL; return (DSVC_SUCCESS); case GLOB_NOSPACE: return (DSVC_NO_MEMORY); default: return (DSVC_INTERNAL); } } *modules = calloc(globbuf.gl_pathc, sizeof (char **)); if (*modules == NULL) { globfree(&globbuf); return (DSVC_NO_MEMORY); } for (i = 0; i < globbuf.gl_pathc; i++) { ptr = strrchr(globbuf.gl_pathv[i], '/'); if (ptr == NULL) ptr = globbuf.gl_pathv[i]; else ptr++; (*modules)[i] = malloc(strlen(ptr) + 1); if ((*modules)[i] == NULL) { while (i--) free((*modules)[i]); free(modules); globfree(&globbuf); return (DSVC_NO_MEMORY); } (void) sscanf(ptr, "%*[^_]_%[^.]", (*modules)[i]); } globfree(&globbuf); *nump = i; return (DSVC_SUCCESS); } /* * Check the status of the underlying service supporting the data store. * Caller is responsible for freeing any dynamically allocated arguments. */ int status_dd(dsvc_datastore_t *ddp) { void *instance; dsvc_splapi_t api; int error; error = load_public_module(ddp, &instance, &api); if (error != DSVC_SUCCESS) return (error); error = api.status(ddp->d_location); (void) unload_public_module(&instance, &api); return (error); } /* * Create within the data store the "location" where containers will be * stored. */ int mklocation_dd(dsvc_datastore_t *ddp) { void *instance; dsvc_splapi_t api; int error; error = load_public_module(ddp, &instance, &api); if (error != DSVC_SUCCESS) return (error); error = api.mklocation(ddp->d_location); (void) unload_public_module(&instance, &api); return (error); } /* * Return a list of the current container objects of type 'type' located at * 'location' in listppp. Return the number of list elements in 'count'. */ int list_dd(dsvc_datastore_t *ddp, dsvc_contype_t type, char ***listppp, uint_t *count) { void *instance; dsvc_splapi_t api; int error; error = load_public_module(ddp, &instance, &api); if (error != DSVC_SUCCESS) return (error); if (type == DSVC_DHCPTAB) error = api.list_dt(ddp->d_location, listppp, count); else error = api.list_dn(ddp->d_location, listppp, count); (void) unload_public_module(&instance, &api); return (error); } /* * Creates or opens the DHCP container of type called name within the * specific datastore referenced by ddp, and returns a handle to this * container in the handp argument. New containers are created with * the identity of the caller. Caller is responsible for freeing any * dynamically allocated arguments. The returned handle instance must * be released by calling close_dd(). */ int open_dd(dsvc_handle_t *handp, dsvc_datastore_t *ddp, dsvc_contype_t type, const char *name, uint_t flags) { int error; dsvc_handle_t hp; *handp = NULL; if (type == DSVC_DHCPNETWORK && name == NULL) return (DSVC_INVAL); if (flags & DSVC_CREATE && (flags & DSVC_WRITE) == 0) return (DSVC_INVAL); if ((hp = calloc(1, sizeof (struct dsvc_handle))) == NULL) return (DSVC_NO_MEMORY); if (type == DSVC_DHCPNETWORK) { hp->d_conid.c_net.s_addr = ntohl(inet_addr(name)); if (hp->d_conid.c_net.s_addr == INADDR_BROADCAST) { free(hp); return (DSVC_INVAL); } get_netmask4(&hp->d_conid.c_net, &hp->d_conid.c_mask); } error = load_public_module(ddp, &hp->d_instance, &hp->d_api); if (error != DSVC_SUCCESS) { free(hp); return (error); } hp->d_type = type; hp->d_desc.d_conver = ddp->d_conver; hp->d_desc.d_resource = strdup(ddp->d_resource); hp->d_desc.d_location = strdup(ddp->d_location); if (hp->d_desc.d_resource == NULL || hp->d_desc.d_location == NULL) { error = DSVC_NO_MEMORY; goto error; } /* * Initialize the synchronization strategy (may not be any). */ error = synch_init(hp, name, flags); if (error != DSVC_SUCCESS) goto error; if (type == DSVC_DHCPTAB) error = hp->d_api.open_dt(&hp->d_hand, ddp->d_location, flags); else error = hp->d_api.open_dn(&hp->d_hand, ddp->d_location, flags, &hp->d_conid.c_net, &hp->d_conid.c_mask); if (error != DSVC_SUCCESS) { if (hp->d_synch != NULL) synch_fini(hp); goto error; } *handp = hp; return (DSVC_SUCCESS); error: (void) unload_public_module(&hp->d_instance, &hp->d_api); free(hp->d_desc.d_resource); free(hp->d_desc.d_location); free(hp); return (error); } /* * Remove DHCP container called name of type within the specific datastore * referenced by ddp. Caller is responsible for freeing any dynamically * allocated arguments. */ int remove_dd(dsvc_datastore_t *ddp, dsvc_contype_t type, const char *name) { void *instance; int error; dsvc_splapi_t api; struct in_addr ip, mask; if (type != DSVC_DHCPTAB) { if ((ip.s_addr = inet_addr(name)) == INADDR_BROADCAST) return (DSVC_INVAL); ip.s_addr = ntohl(ip.s_addr); get_netmask4(&ip, &mask); } error = load_public_module(ddp, &instance, &api); if (error != DSVC_SUCCESS) return (error); /* remove the DHCP container */ if (type == DSVC_DHCPTAB) error = api.remove_dt(ddp->d_location); else error = api.remove_dn(ddp->d_location, &ip, &mask); (void) unload_public_module(&instance, &api); return (error); } /* * Delete the handle instance referenced by hand. Frees hand if the close * operation was successful. NOTE: Caller is responsible for synchronizing * multiple threads such that close_dd() is called when all consuming * threads have exited. */ int close_dd(dsvc_handle_t *handp) { int error; if (handp == NULL || DSVC_HANDLE_INVAL(*handp)) return (DSVC_INVAL); if ((*handp)->d_type == DSVC_DHCPTAB) error = (*handp)->d_api.close_dt(&((*handp)->d_hand)); else error = (*handp)->d_api.close_dn(&((*handp)->d_hand)); if (error == DSVC_SUCCESS) { error = unload_public_module(&(*handp)->d_instance, &(*handp)->d_api); if ((*handp)->d_synch != NULL) synch_fini(*handp); free((*handp)->d_desc.d_resource); free((*handp)->d_desc.d_location); free(*handp); *handp = NULL; } return (error); } /* * Searches hand container for records that match the query described by * the combination of query and targetp. If the partial field is true, then * lookup operations that have located some records but are unable to * complete entirely are allowed. The query argument consists of 2 fields, * each 16 bits long. The lower 16 bits selects which fields in the targetp * record are to be considered in the query. The upper 16 bits identifies * whether a particular field value must match (bit set) or not match (bit * clear). Unused bits in both 16 bit fields must be 0. The count argument * specifies the maximum number of matching records to return. A count * value of -1 requests that all matching records be returned. recordsp is * set to point to the resulting list of records; if recordsp is NULL then * no records are actually returned. Note that these records are * dynamically allocated, thus the caller is responsible for freeing them. * The number of records found is returned in nrecordsp; a value of 0 means * that no records matched the query. */ int lookup_dd(dsvc_handle_t hand, boolean_t partial, uint_t query, int count, const void *targetp, void **recordsp, uint_t *nrecordsp) { uint_t mask = 0; int error; void *unlock_cookie; int (*lookup)(); if (targetp == NULL || nrecordsp == NULL || DSVC_HANDLE_INVAL(hand)) return (DSVC_INVAL); if (hand->d_type == DSVC_DHCPTAB) { mask = (uint_t)~DT_QALL; lookup = hand->d_api.lookup_dt; } else { mask = (uint_t)~DN_QALL; lookup = hand->d_api.lookup_dn; } /* validate query */ if (((query & 0xffff) & mask) || ((query >> 16) & mask)) return (DSVC_INVAL); /* * XXX: need to validate the `targetp' -- what a mess cuz only the * fields lit in `query' need to be valid. */ if (hand->d_synch != NULL) { error = DSVC_SYNCH_RDLOCK(hand->d_synch, &unlock_cookie); if (error != DSVC_SUCCESS) return (error); } error = lookup(hand->d_hand, partial, query, count, targetp, recordsp, nrecordsp); if (hand->d_synch != NULL) (void) DSVC_SYNCH_UNLOCK(hand->d_synch, unlock_cookie); return (error); } /* * Frees the record pointed to by entryp. */ void free_dd(dsvc_handle_t hand, void *entryp) { if (DSVC_HANDLE_INVAL(hand) || entryp == NULL) return; if (hand->d_type == DSVC_DHCPTAB) free_dtrec((dt_rec_t *)entryp); else free_dnrec((dn_rec_t *)entryp); } /* * Frees the list of records pointed to by listp. */ void free_dd_list(dsvc_handle_t hand, void *listp) { if (DSVC_HANDLE_INVAL(hand) || listp == NULL) return; if (hand->d_type == DSVC_DHCPTAB) free_dtrec_list((dt_rec_list_t *)listp); else free_dnrec_list((dn_rec_list_t *)listp); } /* * Add the record newp to the DHCP container hand. newp's update signature * will be updated by the public layer module doing the update. Caller is * responsible for freeing newp if it was dynamically allocated. */ int add_dd_entry(dsvc_handle_t hand, void *newp) { int error; void *unlock_cookie; if (DSVC_HANDLE_INVAL(hand)) return (DSVC_INVAL); if (!validate_dd_entry(hand, newp, B_FALSE)) return (DSVC_INVAL); if (hand->d_synch != NULL) { error = DSVC_SYNCH_WRLOCK(hand->d_synch, &unlock_cookie); if (error != DSVC_SUCCESS) return (error); } if (hand->d_type == DSVC_DHCPTAB) error = hand->d_api.add_dt(hand->d_hand, newp); else error = hand->d_api.add_dn(hand->d_hand, newp); if (hand->d_synch != NULL) (void) DSVC_SYNCH_UNLOCK(hand->d_synch, unlock_cookie); return (error); } /* * Modify the record origp with the record newp in the DHCP container hand. * newp's update signature will be updated by the public layer module doing * the update. Caller is responsible for freeing origp and/or newp if they * were dynamically allocated. */ int modify_dd_entry(dsvc_handle_t hand, const void *origp, void *newp) { int error; void *unlock_cookie; if (DSVC_HANDLE_INVAL(hand)) return (DSVC_INVAL); if (!validate_dd_entry(hand, origp, B_TRUE)) return (DSVC_INVAL); if (!validate_dd_entry(hand, newp, B_FALSE)) return (DSVC_INVAL); if (hand->d_synch != NULL) { error = DSVC_SYNCH_WRLOCK(hand->d_synch, &unlock_cookie); if (error != DSVC_SUCCESS) return (error); } if (hand->d_type == DSVC_DHCPTAB) error = hand->d_api.modify_dt(hand->d_hand, origp, newp); else error = hand->d_api.modify_dn(hand->d_hand, origp, newp); if (hand->d_synch != NULL) (void) DSVC_SYNCH_UNLOCK(hand->d_synch, unlock_cookie); return (error); } /* * Deletes the record referred to by entryp from the DHCP container hand. * Caller is responsible for freeing entryp if it was dynamically * allocated. */ int delete_dd_entry(dsvc_handle_t hand, void *entryp) { int error; void *unlock_cookie; if (DSVC_HANDLE_INVAL(hand)) return (DSVC_INVAL); if (!validate_dd_entry(hand, entryp, B_TRUE)) return (DSVC_INVAL); if (hand->d_synch != NULL) { error = DSVC_SYNCH_WRLOCK(hand->d_synch, &unlock_cookie); if (error != DSVC_SUCCESS) return (error); } if (hand->d_type == DSVC_DHCPTAB) error = hand->d_api.delete_dt(hand->d_hand, entryp); else error = hand->d_api.delete_dn(hand->d_hand, entryp); if (hand->d_synch != NULL) (void) DSVC_SYNCH_UNLOCK(hand->d_synch, unlock_cookie); return (error); } /* * Validate that the DHCP network record `dn' is correctly formed; returns * B_TRUE if it is, B_FALSE if it's not. If `justkey' is set, then only * validate the key. */ static boolean_t validate_dnrec(dsvc_handle_t hand, const dn_rec_t *dn, boolean_t justkey) { /* CIP must be on container's network */ if (hand->d_conid.c_net.s_addr != (dn->dn_cip.s_addr & hand->d_conid.c_mask.s_addr)) return (B_FALSE); if (justkey) return (B_TRUE); if (dn->dn_cid_len < 1 || dn->dn_cid_len > DN_MAX_CID_LEN) return (B_FALSE); if ((dn->dn_flags & ~DN_FALL) != 0) return (B_FALSE); return (B_TRUE); } /* * Validate that the dhcptab record `dt' is correctly formed; returns * B_TRUE if it is, B_FALSE if it's not. If `justkey' is set, then only * validate the key. */ /* ARGSUSED */ static boolean_t validate_dtrec(dsvc_handle_t hand, const dt_rec_t *dt, boolean_t justkey) { return (dt->dt_type == DT_SYMBOL || dt->dt_type == DT_MACRO); } /* * Validate that a DHCP record of type `hand->d_type' is correctly formed; * returns B_TRUE if it is, B_FALSE if it's not. If `justkey' is set, then * only validate the key. */ static boolean_t validate_dd_entry(dsvc_handle_t hand, const void *entryp, boolean_t justkey) { if (entryp == NULL) return (B_FALSE); if (hand->d_type == DSVC_DHCPTAB) return (validate_dtrec(hand, (dt_rec_t *)entryp, justkey)); else if (hand->d_type == DSVC_DHCPNETWORK) return (validate_dnrec(hand, (dn_rec_t *)entryp, justkey)); return (B_FALSE); } /* * Get the type of synchronization needed for this module and store in * `synchtypep'. Returns a DSVC_* code. This function is exported so that * dsvclockd(1M) can use it. */ int module_synchtype(dsvc_datastore_t *ddp, dsvc_synchtype_t *synchtypep) { void *instance; dsvc_splapi_t api; dsvc_synchtype_t *dsvc_synchtypep; if (load_public_module(ddp, &instance, &api) != DSVC_SUCCESS) return (DSVC_INTERNAL); dsvc_synchtypep = dlsym(instance, "dsvc_synchtype"); if (dsvc_synchtypep != NULL) *synchtypep = *dsvc_synchtypep; else *synchtypep = DSVC_SYNCH_NONE; (void) unload_public_module(&instance, &api); return (DSVC_SUCCESS); } /* * Initialize private-layer synchronization on handle `hand' for container * `conname'; `flags' is the same flags passed into open_dd(). If there's * no synchronization needed, always succeeds. Returns a DSVC_* code. */ int synch_init(dsvc_handle_t hand, const char *conname, uint_t flags) { dsvc_synchtype_t synchtype; dsvc_synch_t *sp; int error; int (*mkloctoken)(const char *, char *, size_t); error = module_synchtype(&hand->d_desc, &synchtype); if (error != DSVC_SUCCESS) return (error); if (synchtype == DSVC_SYNCH_NONE) return (DSVC_SUCCESS); sp = malloc(sizeof (dsvc_synch_t)); if (sp == NULL) return (DSVC_NO_MEMORY); sp->s_conname = strdup(conname); if (sp->s_conname == NULL) { free(sp); return (DSVC_NO_MEMORY); } sp->s_nonblock = flags & DSVC_NONBLOCK; sp->s_datastore = &hand->d_desc; mkloctoken = (int (*)())dlsym(hand->d_instance, "mkloctoken"); if (mkloctoken == NULL) { (void) strlcpy(sp->s_loctoken, sp->s_datastore->d_location, sizeof (sp->s_loctoken)); } else { error = mkloctoken(sp->s_datastore->d_location, sp->s_loctoken, sizeof (sp->s_loctoken)); if (error != DSVC_SUCCESS) { free(sp->s_conname); free(sp); return (error); } } /* * The only synchtype supported is DSVC_SYNCH_DSVCD; if this * changes, we'll need to enhance this. */ assert((synchtype & DSVC_SYNCH_STRATMASK) == DSVC_SYNCH_DSVCD); sp->s_ops = &dsvcd_synch_ops; error = DSVC_SYNCH_INIT(sp, synchtype & DSVC_SYNCH_FLAGMASK); if (error != DSVC_SUCCESS) { free(sp->s_conname); free(sp); return (error); } hand->d_synch = sp; return (DSVC_SUCCESS); } /* * Finish using private-layer synchronization on handle `hand'. */ void synch_fini(dsvc_handle_t hand) { DSVC_SYNCH_FINI(hand->d_synch); free(hand->d_synch->s_conname); free(hand->d_synch); hand->d_synch = NULL; }