/* * aegis - project change supervisor * Copyright (C) 1999, 2001 Peter Miller; * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. * * MANIFEST: wrappers around operating system functions * * This file, in selected areas, DOES NOT use the glue interface. * This is intentional - there can be no security breach, and no * distortion of semantics. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * NAME * os_curdir_sub - get current directory * * SYNOPSIS * string_ty *os_curdir_sub(void); * * DESCRIPTION * The os_curdir_sub function is used to determine the system's idea * of the current directory. * * RETURNS * A pointer to a string in dynamic memory is returned. * A null pointer is returned on error. * * CAVEAT * DO NOT use str_free() on the value returned. */ static string_ty *os_curdir_sub _((void)); static string_ty * os_curdir_sub() { static string_ty *s; os_become_must_be_active(); if (!s) { char buffer[2000]; char *pwd; pwd = getenv("PWD"); if (pwd && *pwd == '/') { struct stat st1; struct stat st2; if ( glue_stat(".", &st1) == 0 && glue_stat(pwd, &st2) == 0 && st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino ) { s = str_from_c(pwd); return s; } } if (!glue_getcwd(buffer, sizeof(buffer))) { sub_context_ty *scp; scp = sub_context_new(); sub_errno_set(scp); fatal_intl(scp, i18n("getcwd: $errno")); /* NOTREACHED */ } assert(buffer[0] == '/'); s = str_from_c(buffer); } return s; } /* * NAME * os_curdir - full current directory path * * SYNOPSIS * string_ty *os_curdir(void); * * DESCRIPTION * Os_curdir is used to determine the pathname * of the current directory. Automounter vaguaries will be elided. * * RETURNS * A pointer to a string in dynamic memory is returned. * A null pointer is returned on error. * * CAVEAT * Use str_free() when you are done with the value returned. */ string_ty * os_curdir() { static string_ty *result; os_become_must_be_active(); if (!result) { string_ty *dot; dot = str_from_c("."); result = os_pathname(dot, 1); str_free(dot); } return str_copy(result); } /* * NAME * has_prefix * * SYNOPSIS * string_ty *has_prefix(string_ty *prefix, string_ty *candidate); * * DESCRIPTION * The has_prefix function is used to test if the ``prefix'' string * is a prefix of the ``candidate'' string. * * RETURNS * On success: the remainder of the string with the prefix removed. * On failure: NULL. */ static string_ty *has_prefix _((string_ty *, string_ty *)); static string_ty * has_prefix(pfx, path) string_ty *pfx; string_ty *path; { if (str_equal(pfx, path)) return str_copy(path); if ( pfx->str_length < path->str_length && path->str_text[pfx->str_length] == '/' && 0 == memcmp(pfx->str_text, path->str_text, pfx->str_length) ) { return str_n_from_c ( path->str_text + pfx->str_length, path->str_length - pfx->str_length ); } return 0; } /* * NAME * has_a_prefix * * SYNOPSIS * string_ty *has-a_prefix(string_list_ty *prefix, string_ty *candidate); * * DESCRIPTION * The has_a_prefix function is used to test if any one of the * ``prefix'' strings is a prefix of the ``candidate'' string. * * RETURNS * On success: the remainder of the string with the prefix removed. * On failure: NULL. */ static string_ty *has_a_prefix _((string_list_ty *, string_ty *)); static string_ty * has_a_prefix(pfx, path) string_list_ty *pfx; string_ty *path; { size_t j; string_ty *result; for (j = 0; j < pfx->nstrings; ++j) { result = has_prefix(pfx->string[j], path); if (result) return result; } return 0; } /* * NAME * get_prefix_list * * SYNOPSIS * string_list_ty *get_prefix_list(void); * * DESCRIPTION * The get_prefix_list function is used to get the list of possible * auto mount points from the AEGIS_AUTOMOUNT_POINTS environment * variable. * * The value is colon separated. values not starting with slash, * and the root directory, are silently ignored. * * RETURNS * string list of prefixes * DO NOT string_list_delete is *ever* because it is cached. */ static string_list_ty *get_prefix_list _((void)); static string_list_ty * get_prefix_list() { static string_list_ty *prefix; char *cp; string_list_ty tmp; string_ty *s; size_t j; if (prefix) return prefix; /* * Pull the value out of the relevant environment * variable, and break it into pieces (it's colon * separated). */ prefix = string_list_new(); cp = getenv("AEGIS_AUTOMOUNT_POINTS"); if (!cp) cp = "/tmp_mnt:/a:/.automount"; s = str_from_c(cp); string_list_constructor(&tmp); str2wl(&tmp, s, ":", 0); str_free(s); /* * Rip off any trailing slashes. */ for (j = 0; j < tmp.nstrings; ++j) { size_t max; s = tmp.string[j]; if (s->str_text[0] != '/') continue; max = s->str_length; while (max > 0 && s->str_text[max - 1] == '/') --max; if (max != s->str_length) { if (max > 0) { s = str_n_from_c(s->str_text, max); string_list_append_unique(prefix, s); str_free(s); } } else string_list_append_unique(prefix, s); } string_list_destructor(&tmp); return prefix; } /* * NAME * get_auto_mount_dirs * * SYNOPSIS * string_list_ty *get_auto_mount_dirs(void); * * DESCRIPTION * The get_auto_mount_dirs function is used to grab all the active * mount points with an automount prefix. * * Because we are called (indirectly) by os_pathname, all of * the relevant auto mount activities have taken place, and thus * the mount table (obtained through the getmntent api) will be * up-to-date, and contain mount entries with auto mount point prefixes. * * We cache, and re-read if this the MOUNTED file changes. * * RETURNS * String list of actual automounted mount points. * DO NOT string_list_delete is *ever* because it is cached. */ static string_list_ty *get_auto_mount_dirs _((string_list_ty *)); static string_list_ty * get_auto_mount_dirs(prefix) string_list_ty *prefix; { FILE *fp; static string_list_ty *dirs; static struct stat mntent_st; static string_ty *slash; int err; struct stat st; string_ty *p1; struct stat st1; string_ty *p2; struct stat st2; fp = setmntent(MOUNTED, "r"); if (!fp) { if (dirs && dirs->nstrings) { string_list_delete(dirs); dirs = string_list_new(); } return dirs; } /* * The ``fp'' variable is probably not a real FILE*, so don't * use it like one! E.g. Cygwin cast it to FILE*, but there's * actually a whole 'nother data structure behind there, and it * GPFs if you try to access it as if it were a FILE*. */ err = stat(MOUNTED, &st); if (err) memset(&st, 0, sizeof(st)); if (dirs && mntent_st.st_mtime == st.st_mtime) { endmntent(fp); return dirs; } mntent_st = st; if (!slash) slash = str_from_c("/"); if (dirs) string_list_delete(dirs); dirs = string_list_new(); for (;;) { struct mntent *mep; string_ty *dir; string_ty *tmp; mep = getmntent(fp); if (!mep) break; dir = str_from_c(mep->mnt_dir); /* * Ignore ``/'' because everything is below it. */ if (str_equal(dir, slash)) { the_next_one: str_free(dir); continue; } /* * If the mount point doesn't have an automount * point prefix, skip this mount point. * * We are called by os_pathname, which has just * exersized all of the symbolic links and automount * thingies. So, they will all be in the mount table. * (Except for really weird cases of symlinks between * NFS file systems, with some servers fast and some * really really slow, but don't worry about that.) */ tmp = has_a_prefix(prefix, dir); if (!tmp) goto the_next_one; /* * Simply fiddling with the path is a security * hole. We must make sure that the auto-mounted * and un-auto-mounted paths give the same answer. */ p1 = str_format("%S/.", dir); err = lstat(p1->str_text, &st1); str_free(p1); if (err) goto the_next_one; p2 = str_format("%S/.", tmp); str_free(tmp); err = lstat(p2->str_text, &st2); str_free(p2); if (err) goto the_next_one; if ( st1.st_ino != st2.st_ino || st1.st_dev != st2.st_dev ) /*lint !e81*/ goto the_next_one; /* * Everything checks out, * remember this one. */ string_list_append(dirs, dir); str_free(dir); } endmntent(fp); return dirs; } /* * NAME * remove_automounter_prefix * * SYNOPSIS * string_ty *remove_automounter_prefix(string_ty *path); * * DESCRIPTION * The remove_automounter_prefix function is used to remove * any automounter prefix that may be present on an absolute * path name. The prefixes to check for are obtained from the * AEGIS_AUTOMOUNT_POINTS environment variable. * * RETURNS * string_ty * - pointer dynamically allocated string. Use str_free * when done with it. * * CAVEAT * This function is dangerous. Use with extreme care. */ static string_ty *remove_automounter_prefix _((string_ty *)); static string_ty * remove_automounter_prefix(path) string_ty *path; { string_list_ty *prefix; string_list_ty *amdl; string_ty *result; /* * Get the list of possible automount prefixes. */ prefix = get_prefix_list(); /* * Get the list of automounted mount points. * It's cached, so it usually doesn't take long. */ amdl = get_auto_mount_dirs(prefix); /* * Look for leading path prefixes. */ result = has_a_prefix(prefix, path); if (result) { string_ty *tmp; /* * Now see if the path (known to have an auto mount * prefix) is below an automounted mount point (also * known to have an auto mount prefix). */ tmp = has_a_prefix(amdl, path); if (tmp) { str_free(tmp); return result; } /* * The path is below an auto mount directory, but * not in the mount table, so it's bogus in some way. * Ignore it, in case it is an attempt to subvert Aegis * into a security breach. */ str_free(result); } /* * No match, return the original. */ return str_copy(path); } /* * NAME * memb * * SYNOPSIS * string_ty *memb(void); * * DESCRIPTION * The memb function is used to determine the member name of an * OSF/1 cluster member name. * * CAVEAT * This is only meaningful on OSF/1 */ #ifdef HAVE_CLU_INFO static string_ty *memb _((void)); static string_ty * memb() { static string_ty *result; if (!result) { char name[MAXHOSTNAMELEN]; memberid_t my_id; if ( clu_info(CLU_INFO_MY_ID, &my_id) < 0 || clu_info(CLU_INFO_NODENAME_BY_ID, my_id, name, sizeof(name)) < 0 ) result = str_from_c("member0"); else result = str_from_c(name); } return result; } /* * NAME * magic_memb_replace * * SYNOPSIS * string_ty *magic_memb_replace(string_ty *); * * DESCRIPTION * The magic_memb_replace function is used to replace instances * of "{memb}" in a symbolic link's value with the name of the * cluster member. * * CAVEAT * This is only meaningful on OSF/1 */ static string_ty *magic_memb_replace _((string_ty *)); static string_ty * magic_memb_replace(s) string_ty *s; { static stracc_t sa; char *cp; char *end; char *ep; stracc_open(&sa); cp = s->str_text; end = s->str_text + s->str_length; while (cp < end) { ep = memchr(cp, '{', end - cp); if (!ep) { stracc_chars(&sa, cp, end - cp); break; } if (ep > cp) { stracc_chars(&sa, cp, ep - cp); cp = ep; } if (cp + 6 <= end && 0 == memcmp(cp, "{memb}", 6)) { string_ty *name = memb(); stracc_chars(&sa, name->str_text, name->str_length); cp += 6; } else stracc_char(&sa, *cp++); } return stracc_close(&sa); } #endif /* * NAME * os_pathname - determine full file name * * SYNOPSIS * string_ty *os_pathname(string_ty *path, int resolve); * * DESCRIPTION * Os_pathname is used to determine the full path name * of a partial path given. * * ARGUMENTS * path - path to canonicalize * resolve - non-zero if should resolve symlinks, 0 if not * * RETURNS * pointer to dynamically allocated string. * * CAVEAT * Use str_free() when you are done with the value returned. */ string_ty * os_pathname(path, resolve) string_ty *path; int resolve; { static char *tmp; static size_t tmplen; static size_t ipos; static size_t opos; int c; int found; #ifdef S_IFLNK int loop; #endif string_ty *result; /* * Change relative pathnames to absolute */ trace(("os_pathname(path = %08lX)\n{\n"/*}*/, path)); if (!path) path = os_curdir(); if (resolve) os_become_must_be_active(); trace_string(path->str_text); if (path->str_text[0] != '/') path = str_format("%S/%S", os_curdir_sub(), path); else path = str_copy(path); /* * Take kinks out of the pathname */ ipos = 0; opos = 0; found = 0; #ifdef S_IFLNK loop = 0; #endif while (!found) { /* * get the next character */ c = path->str_text[ipos]; if (c) ipos++; else { found = 1; c = '/'; } /* * remember the normal characters * until get to slash */ if (c != '/') goto remember; /* * leave root alone */ if (!opos) goto remember; /* * "/.." -> "/" */ if (opos == 3 && tmp[1] == '.' && tmp[2] == '.') { opos = 1; continue; } /* * "a//" -> "a/" */ if (tmp[opos - 1] == '/') continue; /* * "a/./" -> "a/" */ if (opos >= 2 && tmp[opos - 1] == '.' && tmp[opos - 2] == '/') { opos--; continue; } /* * "a/b/../" -> "a/" */ if ( opos > 3 && tmp[opos - 1] == '.' && tmp[opos - 2] == '.' && tmp[opos - 3] == '/' ) { opos -= 4; assert(opos > 0); while (tmp[opos - 1] != '/') opos--; continue; } /* * see if the path so far is a symbolic link */ #ifdef S_IFLNK if (resolve) { char pointer[2000]; int nbytes; string_ty *s; s = str_n_from_c(tmp, opos); nbytes = glue_readlink(s->str_text, pointer, sizeof(pointer) - 1); if (nbytes < 0) { /* * probably not a symbolic link */ if ( errno != ENXIO && errno != EINVAL && errno != ENOENT && errno != ENOTDIR ) { sub_context_ty *scp; scp = sub_context_new(); sub_errno_set(scp); sub_var_set_string(scp, "File_Name", s); fatal_intl(scp, i18n("readlink $filename: $errno")); /* NOTREACHED */ } str_free(s); } else { string_ty *newpath; string_ty *link1; if (nbytes == 0) { pointer[0] = '.'; nbytes = 1; } if (++loop > 1000) { sub_context_ty *scp; scp = sub_context_new(); sub_errno_setx(scp, ELOOP); sub_var_set_string(scp, "File_Name", s); fatal_intl(scp, i18n("readlink $filename: $errno")); /* NOTREACHED */ } link1 = str_n_from_c(pointer, nbytes); #ifdef HAVE_CLU_INFO { string_ty *link2; /* * OSF/1 has magic symlinks, * where the string "{memb}" * is replaced by the cluster * member name. * * This is like the DG/UX "elink" * concept, where environment * variables can be substituted * into symlinks. */ link2 = magic_memb_replace(link1); str_free(link1); link1 = link2; } #endif str_free(s); if (link1->str_text[0] == '/') { newpath = str_format ( "%s/%s", link1->str_text, path->str_text + ipos ); } else { while (tmp[opos - 1] != '/') opos--; tmp[opos] = 0; newpath = str_format ( "%s/%s/%s", tmp, link1->str_text, path->str_text + ipos ); } str_free(link1); str_free(path); path = newpath; ipos = 0; opos = 0; found = 0; continue; } } #endif /* * keep the slash */ remember: if (opos >= tmplen) { tmplen = tmplen * 2 + 8; tmp = mem_change_size(tmp, tmplen); } tmp[opos++] = c; } str_free(path); assert(opos >= 1); assert(tmp[0] == '/'); assert(tmp[opos - 1] == '/'); if (opos >= 2) opos--; path = str_n_from_c(tmp, opos); trace_string(path->str_text); /* * Check for automounter prefixes, and remove them if you * find them. The user needs to use this sparingly, because * extreme chaos can result. */ result = remove_automounter_prefix(path); str_free(path); trace_string(result->str_text); trace((/*{*/"}\n")); return result; }