// // aegis - project change supervisor // Copyright (C) 1999, 2001-2006, 2008 Peter Miller // // 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 3 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, see // . // // 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 *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; int errno_old; errno_old = errno; scp = sub_context_new(); sub_errno_setx(scp, errno_old); 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(void) { 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 *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 *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 delete it *ever* because it is cached. // static string_list_ty * get_prefix_list(void) { static string_list_ty *prefix; const char *cp; 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 = new string_list_ty(); cp = getenv("AEGIS_AUTOMOUNT_POINTS"); if (!cp) cp = "/tmp_mnt:/a:/.automount"; s = str_from_c(cp); string_list_ty tmp; tmp.split(s, ":"); str_free(s); // // Rip off any trailing slashes. // for (j = 0; j < tmp.nstrings; ++j) { s = tmp.string[j]; if (s->str_text[0] != '/') continue; size_t len = s->str_length; while (len > 0 && s->str_text[len - 1] == '/') --len; if (len != s->str_length) { if (len > 0) { s = str_n_from_c(s->str_text, len); prefix->push_back_unique(s); str_free(s); } } else prefix->push_back_unique(s); } 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 delete it *ever* because it is cached. // static string_list_ty * get_auto_mount_dirs(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->clear(); 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) dirs->clear(); else dirs = new string_list_ty(); 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->str_text); err = lstat(p1->str_text, &st1); str_free(p1); if (err) goto the_next_one; p2 = str_format("%s/.", tmp->str_text); 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. // dirs->push_back(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 *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 *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 *s) { static stracc_t sa; char *cp; char *end; char *ep; sa.clear(); 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; name = memb(); stracc_chars(&sa, name->str_text, name->str_length); cp += 6; } else sa.push_back(*cp++); } return sa.mkstr(); } #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(string_ty *path, int resolve) { int found; string_ty *result; // // Change relative pathnames to absolute // trace(("os_pathname(path = %08lX)\n{\n", (long)path)); if (!path) path = os_curdir(); if (resolve) os_become_must_be_active(); trace_string(path->str_text); if (path->str_text[0] != '/') path = os_path_join(os_curdir_sub(), path); else path = str_copy(path); // // Take kinks out of the pathname // static stracc_t ac; ac.clear(); size_t ipos = 0; found = 0; #ifdef S_IFLNK int loop = 0; #endif while (!found) { // // get the next character // unsigned char c = path->str_text[ipos]; if (c) ipos++; else { found = 1; c = '/'; } // // remember the normal characters // until get to slash // if (c != '/') { ac.push_back(c); continue; } // // leave root alone // if (ac.empty()) { ac.push_back(c); continue; } // // "/.." -> "/" // if (ac.size() == 3 && ac[1] == '.' && ac[2] == '.') { ac.pop_back(); ac.pop_back(); continue; } // // "a//" -> "a/" // if (ac.back() == '/') continue; // // "a/./" -> "a/" // if (ac.size() >= 2 && ac.back() == '.' && ac[ac.size() - 2] == '/') { ac.pop_back(); continue; } // // "a/b/../" -> "a/" // if ( ac.size() > 3 && ac[ac.size() - 1] == '.' && ac[ac.size() - 2] == '.' && ac[ac.size() - 3] == '/' ) { ac.pop_back(); ac.pop_back(); ac.pop_back(); ac.pop_back(); assert(!ac.empty()); while (!ac.empty() && ac.back() != '/') ac.pop_back(); continue; } // // see if the path so far is a symbolic link // #ifdef S_IFLNK if (resolve) { string_ty *s = ac.mkstr(); char pointer[2000]; int nbytes = glue_readlink(s->str_text, pointer, sizeof(pointer) - 1); if (nbytes < 0) { int errno_old = errno; // // probably not a symbolic link // if ( // // Some broken Unixes say this when they should // say EINVAL. // errno_old != ENXIO && // // The file definitely isn't a symbolic link. // errno_old != EINVAL && // // The file doesn't exist. // errno_old != ENOENT && // // One of the path elements isn't a directory. // errno_old != ENOTDIR && // // If we can't read the link (or more likely, one // of the directories above it wont let us search) // assume it isn't a link. // errno_old != EACCES ) { sub_context_ty *scp; scp = sub_context_new(); sub_errno_setx(scp, errno_old); 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 (ac.back() != '/') ac.pop_back(); newpath = str_format ( "%.*s/%s/%s", int(ac.size()), ac.get_data(), link1->str_text, path->str_text + ipos ); } str_free(link1); str_free(path); path = newpath; ipos = 0; ac.clear(); found = 0; continue; } } #endif // // keep the slash // ac.push_back(c); } str_free(path); assert(!ac.empty()); assert(ac[0] == '/'); assert(ac.back() == '/'); if (ac.size() >= 2) ac.pop_back(); path = ac.mkstr(); 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; } nstring os_pathname(const nstring &path, bool resolve) { return nstring(os_pathname(path.get_ref(), (int)resolve)); }