/* Copyright 1998 by the Massachusetts Institute of Technology. * * Permission to use, copy, modify, and distribute this * software and its documentation for any purpose and without * fee is hereby granted, provided that the above copyright * notice appear in all copies and that both that copyright * notice and this permission notice appear in supporting * documentation, and that the name of M.I.T. not be used in * advertising or publicity pertaining to distribution of the * software without specific, written prior permission. * M.I.T. makes no representations about the suitability of * this software for any purpose. It is provided "as is" * without express or implied warranty. */ /* This is the part of attach that is used by the "add" alias. */ static const char rcsid[] = "$Id: add.c,v 1.15 2005-09-15 14:22:00 rbasch Exp $"; #include #include #include #include #include #include #include #include #include #include "agetopt.h" #include "attach.h" static void usage(void); static void modify_path(char **path, char *elt); static void print_readable_path(char *path); static int add_callback(locker_context context, locker_attachent *at, void *arg); static int ruid_stat(const char *path, struct stat *st); static struct agetopt_option add_options[] = { { "verbose", 'v', 0 }, { "quiet", 'q', 0 }, { "debug", 'd', 0 }, { "front", 'f', 0 }, { "remove", 'r', 0 }, { "print", 'p', 0 }, { "warn", 'w', 0 }, { "bourne", 'b', 0 }, { "path", 'P', 1 }, { "attachopts", 'a', 0 } }; static char *shell_templates[2][2] = { { "setenv PATH %s; setenv MANPATH %s; setenv INFOPATH %s\n", "PATH=%s; export PATH; MANPATH=%s; export MANPATH; INFOPATH=%s; export INFOPATH\n" }, { "set athena_path=(%s); setenv MANPATH %s; setenv INFOPATH %s\n", "athena_path=%s; MANPATH=%s; export MANPATH; INFOPATH=%s; export INFOPATH\n" } }; extern locker_callback attach_callback; static int quiet = 0, give_warnings = 0, remove_from_path = 0; static int add_to_front = 0, bourne_shell = 0, use_athena_path = 0; static char *path, *manpath, *infopath; int add_main(int argc, char **argv) { int print_path, end_args; int opt; print_path = end_args = 0; while (!end_args && (opt = attach_getopt(argc, argv, add_options)) != -1) { switch (opt) { case 'q': quiet = 1; break; case 'f': add_to_front = 1; break; case 'r': remove_from_path = 1; break; case 'p': print_path = 1; break; case 'w': give_warnings = 1; break; case 'b': bourne_shell = 1; break; case 'P': use_athena_path = 1; path = optarg; break; case 'a': if (remove_from_path || print_path) { fprintf(stderr, "%s: can't use -a with -r or -p.\n", whoami); usage(); } end_args = 1; break; case 'd': case 'v': fprintf(stderr, "%s: The '%c' flag is no longer supported.\n", whoami, opt); break; case '?': usage(); } } if (!path) path = getenv("PATH"); if (!path) path = ""; path = strdup(path); if (!path) { fprintf(stderr, "%s: Out of memory.\n", whoami); exit(1); } if (use_athena_path && !bourne_shell) { char *p; for (p = path; *p; p++) { if (*p == ' ') *p = ':'; } } manpath = getenv("MANPATH"); if (manpath) manpath = strdup(manpath); else manpath = strdup(":"); infopath = getenv("INFOPATH"); if (infopath) infopath = strdup(infopath); else infopath = strdup(""); /* If no arguments have been directed to attach, or -p was * specified, we output the path in an easier-to-read format and * we're done. */ if (argc == optind || print_path) { print_readable_path(path); exit(0); } /* If the following args weren't explicitly handed to attach (via * -a), and the first one looks like a pathname, then assume we've * been passed a bunch of pathnames rather than lockernames. */ if (!end_args && (argv[optind][0] == '.' || argv[optind][0] == '/')) { struct stat st; for (; optind < argc; optind++) { if (argv[optind][0] != '.' && argv[optind][0] != '/') { fprintf(stderr, "%s: only pathnames may be specified when " "pathnames are being added\n", whoami); usage(); } /* Make sure the directory exists, if we're adding it to the * path. Otherwise we don't care. */ if (!remove_from_path && ruid_stat(argv[optind], &st) == -1) { fprintf(stderr, "%s: no such path: %s\n", whoami, argv[optind]); } else modify_path(&path, argv[optind]); } } else if (remove_from_path) { locker_context context; int status; if (locker_init(&context, getuid(), NULL, NULL)) exit(1); for (; optind < argc; optind++) { locker_attachent *at; /* Ignore flags, just look at lockers */ if (argv[optind][0] == '-') continue; status = locker_read_attachent(context, argv[optind], &at); if (status != LOCKER_SUCCESS) continue; add_callback(context, at, NULL); locker_free_attachent(context, at); } locker_end(context); } else { /* We are adding lockers. */ attach_callback = add_callback; /* Reinvoke attach's main: optind now points to either the first * attach command-line argument or the first locker. Either way, * let attach deal (using our callback), and return to us when * it's done. */ attach_main(argc, argv); } if (use_athena_path && !bourne_shell) { char *p; for (p = path; *p; p++) { if (*p == ':') *p = ' '; } } printf(shell_templates[use_athena_path][bourne_shell], path, manpath, infopath); free(path); free(manpath); free(infopath); exit(0); } static int add_callback(locker_context context, locker_attachent *at, void *arg) { char **found, **ptr; /* Find the binary directories we want to add to/remove from the path. */ found = athdir_get_paths(at->mountpoint, "bin", NULL, NULL, NULL, NULL, 0); if (found) { for (ptr = found; *ptr; ptr++) { if (!remove_from_path && !athdir_native(*ptr, NULL) && give_warnings) { fprintf(stderr, "%s: warning: using compatibility for %s\n", whoami, at->mountpoint); } modify_path(&path, *ptr); } athdir_free_paths(found); } else { if (give_warnings) { fprintf(stderr, "%s: warning: %s has no binary directory\n", whoami, at->mountpoint); } } /* Find the man directories we want to add to/remove from the manpath. */ found = athdir_get_paths(at->mountpoint, "man", NULL, NULL, NULL, NULL, 0); if (found) { for (ptr = found; *ptr; ptr++) modify_path(&manpath, *ptr); athdir_free_paths(found); } /* Find the info directories we want to add to/remove from the infopath. */ found = athdir_get_paths(at->mountpoint, "info", NULL, NULL, NULL, NULL, 0); if (found) { for (ptr = found; *ptr; ptr++) modify_path(&infopath, *ptr); athdir_free_paths(found); } return 0; } static void modify_path(char **pathp, char *elt) { char *p; int len = strlen(elt); /* If we're adding a string to the front of the path, we need * to remove it from the middle first, if it's already there. */ if (remove_from_path || add_to_front) { p = *pathp; while (p) { if (!strncmp(p, elt, len) && (p[len] == ':' || p[len] == '\0')) { if (p[len] == ':') len++; else if (p != *pathp) { p--; len++; } memmove(p, p + len, strlen(p + len) + 1); } p = strchr(p, ':'); if (p) p++; } } else { /* Adding to end, so make sure the path element isn't already in * the middle. */ if ((p = strstr(*pathp, elt)) && (p[len] == ':' || p[len] == '\0') && (p == *pathp || *(p - 1) == ':')) return; } if (!remove_from_path) { p = malloc(strlen(*pathp) + len + 2); if (!p) { fprintf(stderr, "%s: Out of memory.\n", whoami); exit(1); } if (add_to_front) sprintf(p, "%s%s%s", elt, **pathp ? ":" : "", *pathp); else sprintf(p, "%s%s%s", *pathp, **pathp ? ":" : "", elt); free(*pathp); *pathp = p; } } /* print_readable_path * * A hack to print out a readable version of the user's path. * * It's a hack because it's not currently a nice thing to do * correctly. So, if any path element starts with "/mit" and ends with * "bin" such as "/mit/gnu/arch/sun4x_55/bin," we print "{add gnu}" * instead. This is not always correct; it misses things that are not * mounted under /mit, and is misleading for lockers that do not mount * under /mit/lockername as well as MUL type filesystems. However, * these occasions are infrequent. * * In addition, each path starting with "/mit" and ending with "bin" * is tested for the substring of the machine's $ATHENA_SYS value. If * absent, it is assumed that some form of compatibility system is * being used, and a * is added to the shortened path string. So if * ATHENA_SYS_COMPAT is set to sun4x_55 while ATHENA_SYS is set to * sun4x_56, in the example above "{add gnu*}" would be printed * instead of "{add gnu}." * * XXX We could do a less hacky version of this using * locker_iterate_attachtab to get all of the mountpoints. It's not clear * that there's a lot of benefit to this though. */ static void print_readable_path(char *path) { char *p, *name, *name_end; for (p = strtok(path, ":"); p; p = strtok(NULL, ":")) { if (p != path) putc(bourne_shell ? ':' : ' ', stderr); if (!strncmp(p, "/mit/", 5)) { name = p + 5; name_end = strchr(name, '/'); if (name_end && !strcmp(p + strlen(p) - 3, "bin")) { if (athdir_native(name, NULL)) fprintf(stderr, "{add %.*s}", name_end - name, name); else fprintf(stderr, "{add %.*s*}", name_end - name, name); } else fprintf(stderr, "%s", p); } else fprintf(stderr, "%s", p); } fprintf(stderr, "\n"); } /* stat() the given path after setting the effective UID to the * real UID (if necessary), and return the value returned by stat(). */ static int ruid_stat(const char *path, struct stat *st) { uid_t euid = geteuid(); uid_t ruid = getuid(); int status; if (euid != ruid) seteuid(ruid); status = stat(path, st); if (euid != ruid) seteuid(euid); return status; } static void usage(void) { fprintf(stderr, "Usage: add [-vfrpwbq] [-P $athena_path] [-a attachflags] [lockername ...]\n"); fprintf(stderr, " add [-dfrb] [-P $athena_path] pathname ...\n"); exit(1); }