/* * 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) 1988 AT&T * All Rights Reserved * * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * PATH setup and search directory functions. */ #include "_synonyms.h" #include #include #include #include #include #include "_rtld.h" #include "msg.h" #include "conv.h" #include "debug.h" /* * Given a search rule type, return a list of directories to search according * to the specified rule. */ static Pnode * get_dir_list(unsigned char rules, Rt_map * lmp, uint_t flags) { Pnode * dirlist = (Pnode *)0; Lm_list * lml = LIST(lmp); int search; /* * Determine whether ldd -s is in effect - ignore when we're searching * for audit libraries as these will be added to their own link-map. */ if ((lml->lm_flags & LML_FLG_TRC_SEARCH) && ((FLAGS1(lmp) & FL1_RT_LDDSTUB) == 0) && ((flags & FLG_RT_AUDIT) == 0)) search = 1; else search = 0; switch (rules) { case RPLENV: /* * Initialize the replaceable environment variable * (LD_LIBRARY_PATH) search path list. Note, we always call * Dbg_libs_path() so that every library lookup diagnostic can * be preceded with the appropriate search path information. */ if (rpl_libpath) { Half mode = LA_SER_LIBPATH; /* * Note, this path may have originated from the users * environment or from a configuration file. */ if (env_info & ENV_INF_PATHCFG) mode |= LA_SER_CONFIG; DBG_CALL(Dbg_libs_path(rpl_libpath, mode, config->c_name)); /* * For ldd(1) -s, indicate the search paths that'll * be used. If this is a secure program then some * search paths may be ignored, therefore reset the * rpl_libdirs pointer each time so that the * diagnostics related to these unsecure directories * will be output for each image loaded. */ if (search) { const char *fmt; if (env_info & ENV_INF_PATHCFG) fmt = MSG_INTL(MSG_LDD_PTH_LIBPATHC); else fmt = MSG_INTL(MSG_LDD_PTH_LIBPATH); (void) printf(fmt, rpl_libpath, config->c_name); } if (rpl_libdirs && (rtld_flags & RT_FL_SECURE) && (search || dbg_mask)) { free(rpl_libdirs); rpl_libdirs = 0; } if (!rpl_libdirs) { /* * If this is a secure application we need to * be selective over what directories we use. */ rpl_libdirs = expand_paths(lmp, rpl_libpath, mode, PN_TKN_HWCAP); } dirlist = rpl_libdirs; } break; case PRMENV: /* * Initialize the permanent (LD_LIBRARY_PATH) search path list. * This can only originate from a configuration file. To be * consistent with the debugging display of DEFENV (above), * always call Dbg_libs_path(). */ if (prm_libpath) { DBG_CALL(Dbg_libs_path(prm_libpath, (LA_SER_LIBPATH | LA_SER_CONFIG), config->c_name)); /* * For ldd(1) -s, indicate the search paths that'll * be used. If this is a secure program then some * search paths may be ignored, therefore reset the * prm_libdirs pointer each time so that the * diagnostics related to these unsecure directories * will be output for each image loaded. */ if (search) (void) printf(MSG_INTL(MSG_LDD_PTH_LIBPATHC), prm_libpath, config->c_name); if (prm_libdirs && (rtld_flags & RT_FL_SECURE) && (search || dbg_mask)) { free(prm_libdirs); prm_libdirs = 0; } if (!prm_libdirs) { /* * If this is a secure application we need to * be selective over what directories we use. */ prm_libdirs = expand_paths(lmp, prm_libpath, (LA_SER_LIBPATH | LA_SER_CONFIG), PN_TKN_HWCAP); } dirlist = prm_libdirs; } break; case RUNPATH: /* * Initialize the runpath search path list. To be consistent * with the debugging display of DEFENV (above), always call * Dbg_libs_path(). */ if (RPATH(lmp)) { DBG_CALL(Dbg_libs_path(RPATH(lmp), LA_SER_RUNPATH, NAME(lmp))); /* * For ldd(1) -s, indicate the search paths that'll * be used. If this is a secure program then some * search paths may be ignored, therefore reset the * runlist pointer each time so that the diagnostics * related to these unsecure directories will be * output for each image loaded. */ if (search) (void) printf(MSG_INTL(MSG_LDD_PTH_RPATH), RPATH(lmp), NAME(lmp)); if (RLIST(lmp) && (rtld_flags & RT_FL_SECURE) && (search || dbg_mask)) { free(RLIST(lmp)); RLIST(lmp) = 0; } if (!(RLIST(lmp))) /* * If this is a secure application we need to * be selective over what directories we use. */ RLIST(lmp) = expand_paths(lmp, RPATH(lmp), LA_SER_RUNPATH, PN_TKN_HWCAP); dirlist = RLIST(lmp); } break; case DEFAULT: if ((FLAGS1(lmp) & FL1_RT_NODEFLIB) == 0) { if ((rtld_flags & RT_FL_SECURE) && (flags & (FLG_RT_PRELOAD | FLG_RT_AUDIT))) dirlist = LM_SECURE_DIRS(lmp); else dirlist = LM_DFLT_DIRS(lmp); } /* * For ldd(1) -s, indicate the default paths that'll be used. */ if (dirlist && (search || dbg_mask)) { Pnode * pnp = dirlist; int num = 0; if (search) (void) printf(MSG_INTL(MSG_LDD_PTH_BGNDFL)); for (; pnp && pnp->p_name; pnp = pnp->p_next, num++) { if (search) { const char *fmt; if (num) fmt = MSG_ORIG(MSG_LDD_FMT_PATHN); else fmt = MSG_ORIG(MSG_LDD_FMT_PATH1); (void) printf(fmt, pnp->p_name); } else DBG_CALL(Dbg_libs_path(pnp->p_name, pnp->p_orig, config->c_name)); } if (search) { if (dirlist->p_orig & LA_SER_CONFIG) (void) printf(MSG_INTL(MSG_LDD_PTH_ENDDFLC), config->c_name); else (void) printf(MSG_INTL(MSG_LDD_PTH_ENDDFL)); } } break; default: break; } return (dirlist); } /* * Get the next dir in the search rules path. */ Pnode * get_next_dir(Pnode ** dirlist, Rt_map * lmp, uint_t flags) { static unsigned char *rules = NULL; /* * Search rules consist of one or more directories names. If this is a * new search, then start at the beginning of the search rules. * Otherwise traverse the list of directories that make up the rule. */ if (!*dirlist) { rules = search_rules; } else { if ((*dirlist = (*dirlist)->p_next) != 0) return (*dirlist); else rules++; } while (*rules) { if ((*dirlist = get_dir_list(*rules, lmp, flags)) != 0) return (*dirlist); else rules++; } /* * If we got here, no more directories to search, return NULL. */ return ((Pnode *) NULL); } /* * Process a directory (runpath) or filename (needed or filter) string looking * for tokens to expand. Allocate a new buffer for the string. */ uint_t expand(char **name, size_t *len, char **list, uint_t orig, uint_t omit, Rt_map * lmp) { char _name[PATH_MAX]; char *token = 0, *oname, *optr, *_optr, *nptr, * _list; size_t olen = 0, nlen = 0, _len; int isaflag = 0; uint_t flags = 0; optr = _optr = oname = *name; nptr = _name; while ((olen < *len) && (nlen < PATH_MAX)) { uint_t _flags; if ((*optr != '$') || ((olen - *len) == 1)) { /* * When expanding paths while a configuration file * exists that contains directory information, determine * whether the path contains "./". If so, we'll resolve * the path later to remove these relative entries. */ if ((rtld_flags & RT_FL_DIRCFG) && (orig & LA_SER_MASK) && (*optr == '/') && (optr != oname) && (*(optr - 1) == '.')) flags |= TKN_DOTSLASH; olen++, optr++; continue; } /* * Copy any string we've presently passed over to the new * buffer. */ if ((_len = (optr - _optr)) != 0) { if ((nlen += _len) < PATH_MAX) { (void) strncpy(nptr, _optr, _len); nptr = nptr + _len; } else { eprintf(ERR_FATAL, MSG_INTL(MSG_ERR_EXPAND1), NAME(lmp), oname); return (0); } } /* * Skip the token delimiter and determine if a reserved token * match is found. */ olen++, optr++; _flags = 0; token = 0; if (strncmp(optr, MSG_ORIG(MSG_TKN_ORIGIN), MSG_TKN_ORIGIN_SIZE) == 0) { token = (char *)MSG_ORIG(MSG_TKN_ORIGIN); /* * $ORIGIN expansion is required. Determine this * objects basename. Expansion of $ORIGIN is allowed * for secure applications but must be checked by the * caller to insure the expanded path matches a * registered secure name. */ if (((omit & PN_TKN_ORIGIN) == 0) && (((_len = DIRSZ(lmp)) != 0) || ((_len = fullpath(lmp, 0)) != 0))) { if ((nlen += _len) < PATH_MAX) { (void) strncpy(nptr, ORIGNAME(lmp), _len); nptr = nptr +_len; olen += MSG_TKN_ORIGIN_SIZE; optr += MSG_TKN_ORIGIN_SIZE; _flags |= PN_TKN_ORIGIN; } else { eprintf(ERR_FATAL, MSG_INTL(MSG_ERR_EXPAND1), NAME(lmp), oname); return (0); } } } else if (strncmp(optr, MSG_ORIG(MSG_TKN_PLATFORM), MSG_TKN_PLATFORM_SIZE) == 0) { token = (char *)MSG_ORIG(MSG_TKN_PLATFORM); /* * $PLATFORM expansion required. This would have been * established from the AT_SUN_PLATFORM aux vector, but * if not attempt to get it from sysconf(). */ if (((omit & PN_TKN_PLATFORM) == 0) && ((platform == 0) && (platform_sz == 0))) { char _info[SYS_NMLN]; long _size; _size = sysinfo(SI_PLATFORM, _info, SYS_NMLN); if ((_size != -1) && ((platform = malloc((size_t)_size)) != 0)) { (void) strcpy(platform, _info); platform_sz = (size_t)_size - 1; } } if (((omit & PN_TKN_PLATFORM) == 0) && (platform != 0)) { if ((nlen += platform_sz) < PATH_MAX) { (void) strncpy(nptr, platform, platform_sz); nptr = nptr + platform_sz; olen += MSG_TKN_PLATFORM_SIZE; optr += MSG_TKN_PLATFORM_SIZE; _flags |= PN_TKN_PLATFORM; } else { eprintf(ERR_FATAL, MSG_INTL(MSG_ERR_EXPAND1), NAME(lmp), oname); return (0); } } } else if (strncmp(optr, MSG_ORIG(MSG_TKN_OSNAME), MSG_TKN_OSNAME_SIZE) == 0) { token = (char *)MSG_ORIG(MSG_TKN_OSNAME); /* * $OSNAME expansion required. This is established * from the sysname[] returned by uname(2). */ if (((omit & PN_TKN_OSNAME) == 0) && (uts == 0)) uts = conv_uts(); if (((omit & PN_TKN_OSNAME) == 0) && (uts && uts->uts_osnamesz)) { if ((nlen += uts->uts_osnamesz) < PATH_MAX) { (void) strncpy(nptr, uts->uts_osname, uts->uts_osnamesz); nptr = nptr + uts->uts_osnamesz; olen += MSG_TKN_OSNAME_SIZE; optr += MSG_TKN_OSNAME_SIZE; _flags |= PN_TKN_OSNAME; } else { eprintf(ERR_FATAL, MSG_INTL(MSG_ERR_EXPAND1), NAME(lmp), oname); return (0); } } } else if (strncmp(optr, MSG_ORIG(MSG_TKN_OSREL), MSG_TKN_OSREL_SIZE) == 0) { token = (char *)MSG_ORIG(MSG_TKN_OSREL); /* * $OSREL expansion required. This is established * from the release[] returned by uname(2). */ if (((omit & PN_TKN_OSREL) == 0) && (uts == 0)) uts = conv_uts(); if (((omit & PN_TKN_OSREL) == 0) && (uts && uts->uts_osrelsz)) { if ((nlen += uts->uts_osrelsz) < PATH_MAX) { (void) strncpy(nptr, uts->uts_osrel, uts->uts_osrelsz); nptr = nptr + uts->uts_osrelsz; olen += MSG_TKN_OSREL_SIZE; optr += MSG_TKN_OSREL_SIZE; _flags |= PN_TKN_OSREL; } else { eprintf(ERR_FATAL, MSG_INTL(MSG_ERR_EXPAND1), NAME(lmp), oname); return (0); } } } else if ((strncmp(optr, MSG_ORIG(MSG_TKN_ISALIST), MSG_TKN_ISALIST_SIZE) == 0)) { int ok; token = (char *)MSG_ORIG(MSG_TKN_ISALIST); /* * $ISALIST expansion required. When accompanied with * a list pointer, this routine updates that pointer * with the new list of potential candidates. Without * this list pointer, only the first expansion is * provided. NOTE, that two $ISLIST expansions within * the same path aren't supported. */ if ((omit & PN_TKN_ISALIST) || isaflag++) ok = 0; else ok = 1; if (ok && (isa == 0)) isa = conv_isalist(); if (ok && isa && isa->isa_listsz) { size_t no, mlen, tlen, hlen = olen - 1; char *lptr; Isa_opt *opt = isa->isa_opt; if ((nlen += opt->isa_namesz) < PATH_MAX) { (void) strncpy(nptr, opt->isa_name, opt->isa_namesz); nptr = nptr + opt->isa_namesz; olen += MSG_TKN_ISALIST_SIZE; optr += MSG_TKN_ISALIST_SIZE; _flags |= PN_TKN_ISALIST; } else { eprintf(ERR_FATAL, MSG_INTL(MSG_ERR_EXPAND1), NAME(lmp), oname); return (0); } if (list) { tlen = *len - olen; mlen = ((hlen + tlen) * (isa->isa_optno - 1)) + isa->isa_listsz - opt->isa_namesz + strlen(*list); if ((_list = lptr = malloc(mlen)) == 0) return (0); for (no = 1, opt++; no < isa->isa_optno; no++, opt++) { (void) strncpy(lptr, *name, hlen); lptr = lptr + hlen; (void) strncpy(lptr, opt->isa_name, opt->isa_namesz); lptr = lptr + opt->isa_namesz; (void) strncpy(lptr, optr, tlen); lptr = lptr + tlen; *lptr++ = ':'; } if (**list) (void) strcpy(lptr, *list); else *--lptr = '\0'; } } } else if (strncmp(optr, MSG_ORIG(MSG_TKN_HWCAP), MSG_TKN_HWCAP_SIZE) == 0) { char *bptr = nptr - 1; char *eptr = optr + MSG_TKN_HWCAP_SIZE; token = (char *)MSG_ORIG(MSG_TKN_HWCAP); /* * $HWCAP expansion required. For compatibility with * older environments, only expand this token when hard- * ware capability information is available. This * expansion is only allowed for non-simple pathnames * (must contain a '/'), with the token itself being the * last element of the path. Therefore, all we need do * is test the existence of the string "/$HWCAP\0". */ if (((omit & PN_TKN_HWCAP) == 0) && (rtld_flags2 & RT_FL2_HWCAP) && ((bptr > _name) && (*bptr == '/') && ((*eptr == '\0') || (*eptr == ':')))) { /* * Decrement the present pointer so that the * directories trailing "/" gets nuked later. */ nptr--, nlen--; olen += MSG_TKN_HWCAP_SIZE; optr += MSG_TKN_HWCAP_SIZE; _flags |= PN_TKN_HWCAP; } } else { /* * If reserved token was not found, copy the * character. */ *nptr++ = '$'; nlen++; } /* * If reserved token was found, and could not be expanded, * this is an error. */ if (token) { if (_flags) flags |= _flags; else { eprintf(ERR_FATAL, MSG_INTL(MSG_ERR_EXPAND2), NAME(lmp), oname, token); return (0); } } _optr = optr; } /* * First make sure the current length is shorter than PATH_MAX. We may * arrive here if the given path contains '$' characters which are not * the lead of a reserved token. */ if (nlen >= PATH_MAX) { eprintf(ERR_FATAL, MSG_INTL(MSG_ERR_EXPAND1), NAME(lmp), oname); return (0); } /* * If any ISALIST processing has occurred not only do we return the * expanded node we're presently working on, but we can also update the * remaining list so that it is effectively prepended with this node * expanded to all remaining ISALIST options. Note that we can only * handle one ISALIST per node. For more than one ISALIST to be * processed we'd need a better algorithm than above to replace the * newly generated list. Whether we want to encourage the number of * pathname permutations this would provide is another question. So, for * now if more than one ISALIST is encountered we return the original * node untouched. */ if (isaflag) { if (isaflag == 1) { if (list) *list = _list; } else { flags &= ~PN_TKN_ISALIST; if ((nptr = calloc(1, (*len + 1))) == 0) return (0); (void) strncpy(nptr, *name, *len); *name = nptr; return (TKN_NONE); } } /* * Copy any remaining string. Terminate the new string with a null as * this string can be displayed via debugging diagnostics. */ if ((_len = (optr - _optr)) != 0) { if ((nlen += _len) < PATH_MAX) { (void) strncpy(nptr, _optr, _len); nptr = nptr + _len; } else { eprintf(ERR_FATAL, MSG_INTL(MSG_ERR_EXPAND1), NAME(lmp), oname); return (0); } } *nptr = '\0'; /* * A path that has been expanded, is typically used to create full * pathnames for objects that will be opened. The final pathname is * resolved to simplify it, and set the stage for possible $ORIGIN * processing. Therefore, it's usually unncessary to resolve the path * at this point. However, if a configuration file, containing * directory information is in use, then we might need to lookup this * path in the configuration file. To keep the number of pathname * resolutions to a minimum, only resolve paths that contain "./". The * use of "$ORIGIN/../lib" will probably only match a configuration file * entry after resolution. */ if (list && ((rtld_flags & (RT_FL_DIRCFG | RT_FL_EXECNAME)) == (RT_FL_DIRCFG | RT_FL_EXECNAME)) && (flags & TKN_DOTSLASH)) { int len; if ((len = resolvepath(_name, _name, (PATH_MAX - 1))) >= 0) { nlen = (size_t)len; _name[nlen] = '\0'; } } /* * Allocate permanent storage for the new string and return to the user. */ if ((nptr = malloc(nlen + 1)) == 0) return (0); (void) strcpy(nptr, _name); *name = nptr; *len = nlen; /* * Return an indication of any token expansion that may have occurred. * Under security, any pathname expanded with the $ORIGIN token must be * validated against any registered secure directories. */ return (flags ? flags : TKN_NONE); } /* * Determine whether a pathname is secure. */ static int is_path_secure(const char *opath, Rt_map * clmp, uint_t info, uint_t flags) { Pnode *sdir = LM_SECURE_DIRS(LIST(clmp)->lm_head); char buffer[PATH_MAX], *npath; Lm_list *lml; /* * If a pathname originates from a configuration file, use it. The use * of a configuration file is already validated for secure applications, * so if we're using a configuration file, we must be able to use all * that it defines. */ if (info & LA_SER_CONFIG) return (1); if ((info & LA_SER_MASK) == 0) { char *str; /* * If the pathname specifies a file (rather than a directory), * peel off the file before making the comparison. */ str = strrchr(opath, '/'); /* * A simple filename (one containing no "/") is fine, as this * will be combined with search paths to determine the complete * path. Other paths are checked: * * . a full path (one starting with "/") is fine, provided * it isn't a preload/audit path. * . any $ORIGIN expansion * . any relative path */ if (((str == 0) || ((*opath == '/') && (str != opath) && ((info & PN_SER_EXTLOAD) == 0))) && ((flags & PN_TKN_ORIGIN) == 0)) return (1); if (str == opath) npath = (char *)MSG_ORIG(MSG_STR_SLASH); else { size_t size; if ((size = str - opath) >= PATH_MAX) return (0); (void) strncpy(buffer, opath, size); buffer[size] = '\0'; npath = buffer; } } else { /* * A search path, i.e., RPATH, configuration file path, etc. is * used as is. Exceptions to this are: * * . LD_LIBRARY_PATH * . any $ORIGIN expansion * . any relative path */ if (((info & LA_SER_LIBPATH) == 0) && (*opath == '/') && ((flags & PN_TKN_ORIGIN) == 0)) return (1); npath = (char *)opath; } while (sdir) { if (strcmp(npath, sdir->p_name) == 0) return (1); sdir = sdir->p_next; } lml = LIST(clmp); /* * The path is insecure, so depending on the caller, provide a * diagnostic. Preloaded, or audit libraries generate a warning, as * the process will run without them. */ if (info & PN_SER_EXTLOAD) { if (lml->lm_flags & LML_FLG_TRC_ENABLE) { if ((FLAGS1(clmp) & FL1_RT_LDDSTUB) == 0) (void) printf(MSG_INTL(MSG_LDD_FIL_ILLEGAL), opath); } else eprintf(ERR_WARNING, MSG_INTL(MSG_SEC_ILLEGAL), opath); return (0); } /* * Explicit file references are fatal. */ if ((info & LA_SER_MASK) == 0) { if (lml->lm_flags & LML_FLG_TRC_ENABLE) { if ((FLAGS1(clmp) & FL1_RT_LDDSTUB) == 0) { if (lml->lm_flags & (LML_FLG_TRC_VERBOSE | LML_FLG_TRC_SEARCH)) (void) printf(MSG_INTL(MSG_LDD_FIL_FIND), opath, NAME(clmp)); if (((rtld_flags & RT_FL_SILENCERR) == 0) || (lml->lm_flags & LML_FLG_TRC_VERBOSE)) (void) printf(MSG_INTL(MSG_LDD_FIL_ILLEGAL), opath); } } else eprintf(ERR_FATAL, MSG_INTL(MSG_SYS_OPEN), opath, strerror(EACCES)); } else { /* * Search paths. */ DBG_CALL(Dbg_libs_ignore(opath)); if ((lml->lm_flags & LML_FLG_TRC_SEARCH) && ((FLAGS1(clmp) & FL1_RT_LDDSTUB) == 0)) (void) printf(MSG_INTL(MSG_LDD_PTH_IGNORE), opath); } return (0); } /* * Expand one or more pathnames. This routine is called for all path strings, * i.e., NEEDED, rpaths, default search paths, configuration file search paths, * filtees, etc. The path may be a single pathname, or a colon separated list * of pathnames. Each individual pathname is processed for possible reserved * token expansion. All string nodes are maintained in allocated memory * (regardless of whether they are constant (":"), or token expanded) to * simplify pnode removal. * * The info argument passes in auxiliary information regarding the callers * intended use of the pathnames. This information may be maintained in the * pnode element produced to describe the pathname (i.e., LA_SER_LIBPATH etc.), * or may be used to determine additional security or diagnostic processing. */ Pnode * expand_paths(Rt_map * clmp, const char *list, uint_t orig, uint_t omit) { char *str, *olist = 0, *nlist = (char *)list; Pnode *pnp, *npnp, *opnp; for (pnp = opnp = 0, str = nlist; *nlist; str = nlist) { char *ostr; size_t len, olen; uint_t tkns = 0; if (*nlist == ';') ++nlist, ++str; if (*nlist == ':') { if ((str = strdup(MSG_ORIG(MSG_FMT_CWD))) == NULL) return ((Pnode *)0); len = MSG_FMT_CWD_SIZE; if (*nlist) nlist++; } else { char *elist; len = 0; while (*nlist && (*nlist != ':') && (*nlist != ';')) { nlist++, len++; } if (*nlist) nlist++; /* * Expand the captured string. Besides expanding the * present path/file entry, we may have a new list to * deal with (ISALIST expands to multiple new entries). */ elist = nlist; ostr = str; olen = len; if ((tkns = expand(&str, &len, &elist, orig, omit, clmp)) == 0) return ((Pnode *)0); if (elist != nlist) { if (olist) free(olist); nlist = olist = elist; } } /* * If this a secure application, validation of the expanded * pathname may be necessary. */ if (rtld_flags & RT_FL_SECURE) { if (is_path_secure((const char *)str, clmp, orig, tkns) == 0) continue; } /* * Allocate a new Pnode for this string. */ if ((npnp = calloc(1, sizeof (Pnode))) == 0) return ((Pnode *)0); if (opnp == 0) pnp = npnp; else opnp->p_next = npnp; if ((orig & PN_SER_MASK) && (tkns & PN_TKN_MASK)) { char *oname; /* * If this is a pathname, and any token expansion * occurred, maintain the original string for possible * diagnostic use. */ if ((oname = malloc(olen + 1)) == 0) return ((Pnode *)0); (void) strncpy(oname, ostr, olen); oname[olen] = '\0'; npnp->p_oname = oname; } npnp->p_name = str; npnp->p_len = len; npnp->p_orig = (orig & (LA_SER_MASK | PN_SER_MASK)) | (tkns & PN_TKN_MASK); opnp = npnp; } if (olist) free(olist); return (pnp); }