]> git.saurik.com Git - apple/shell_cmds.git/blobdiff - path_helper/path_helper.c
shell_cmds-149.tar.gz
[apple/shell_cmds.git] / path_helper / path_helper.c
diff --git a/path_helper/path_helper.c b/path_helper/path_helper.c
new file mode 100644 (file)
index 0000000..ff5c55a
--- /dev/null
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) 2008 Apple Inc. All rights reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ * 
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ * 
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ * 
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+#include <fts.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <limits.h>
+
+static void usage() {
+       fprintf(stderr, "usage: path_helper [-c | -s]");
+       exit(1);
+}
+
+// Append path segment if it does not exist.  Reallocate
+// the path buffer as necessary.
+
+int append_path_segment(char** path, const char* segment) {
+       if (*path == NULL || segment == NULL) return -1;
+
+       size_t pathlen = strlen(*path);
+       size_t seglen = strlen(segment);
+
+       if (seglen == 0) return 0;
+
+       // Does the segment already exist in the path?
+       // (^|:)segment(:|$)
+       char* match = strstr(*path, segment);
+       while (match) {
+               if ((match == *path || match[-1] == ':') &&
+                       (match[seglen] == ':' || match[seglen] == 0)) {
+                       return 0;
+               }
+               match = strstr(match+1, segment);
+       }
+       
+       // size = pathlen + ':' + segment + '\0'
+       size_t size = pathlen + seglen + 2;
+       *path = reallocf(*path, size);
+       if (*path == NULL) return -1;
+
+       if (pathlen > 0) strlcat(*path, ":", size);
+       strlcat(*path, segment, size);
+       return 0;
+}
+
+// Convert fgetln output into a sanitized segment
+// escape quotes, dollars, etc.
+char* read_segment(const char* line, size_t len) {
+       int escapes = 0;
+       size_t i, j;
+       
+       for (i = 0; i < len; ++i) {
+               char c = line[i];
+               if (c == '\"' || c == '\'' || c == '$') {
+                       ++escapes;
+               }
+       }
+
+       size_t size = len + escapes + 1;
+
+       char* segment = malloc(size);
+       if (segment == NULL) return NULL;
+       
+       for (i = 0, j = 0; i < len; ++i, ++j) {
+               char c = line[i];
+               if (c == '\"' || c == '\'' || c == '$') {
+                       segment[j++] = '\\';
+                       segment[j] = c;
+               } else if (c == '\n') {
+                       segment[j] = 0;
+                       break;
+               } else {
+                       segment[j] = line[i];
+               }
+       }
+
+       return segment;
+}
+
+// Construct a path variable, starting with the contents
+// of the given environment variable, adding the contents
+// of the default file and files in the path directory.
+
+char* construct_path(char* env_var, char* defaults_path, char* dir_path) {
+       FTS* fts;
+       FTSENT* ent;
+
+       char* result = calloc(sizeof(char), 1);
+
+       char* dirpathv[] = { defaults_path, dir_path, NULL };
+       fts = fts_open(dirpathv, FTS_PHYSICAL | FTS_XDEV, NULL);
+       if (!fts) {
+               perror(dir_path);
+               return NULL;
+       }
+
+       while ((ent = fts_read(fts)) != NULL) {
+               // only interested in regular files, one level deep
+               if (ent->fts_info != FTS_F) {
+                       if (ent->fts_level >= 1) fts_set(fts, ent, FTS_SKIP);
+                       continue;
+               }
+
+               FILE* f = fopen(ent->fts_accpath, "r");
+               if (f == NULL) {
+                       perror(ent->fts_accpath);
+                       continue;
+               }
+
+               for (;;) {
+                       size_t len;
+                       char* line = fgetln(f, &len);
+                       if (line == NULL) break;
+                       char* segment = read_segment(line, len);
+                       
+                       append_path_segment(&result, segment);
+               }
+
+               fclose(f);
+       }
+       fts_close(fts);
+       
+       // merge in any existing custom PATH elemenets
+       char* str = getenv(env_var);
+       if (str) str = strdup(str);
+       while (str) {
+               char* sep = strchr(str, ':');
+               if (sep) *sep = 0;
+               
+               append_path_segment(&result, str);
+               if (sep) {
+                       str = sep + 1;
+               } else {
+                       str = NULL;
+               }
+       }
+       
+       return result;
+}
+
+enum {
+       STYLE_CSH,
+       STYLE_SH
+};
+
+int main(int argc, char* argv[]) {
+       int style = STYLE_SH;
+
+       if (argc > 2) usage();
+       
+       // default to csh style, if $SHELL ends with "csh".
+       char* shell = getenv("SHELL");
+       if (shell) {
+               char* str = strstr(shell, "csh");
+               if (str) style = STYLE_CSH;
+       }
+       
+       if (argc == 2 && strcmp(argv[1], "-c") == 0) style = STYLE_CSH;
+       if (argc == 2 && strcmp(argv[1], "-s") == 0) style = STYLE_SH;
+
+       char* path = construct_path("PATH", "/etc/paths", "/etc/paths.d");
+       char* manpath = NULL;
+
+       // only adjust manpath if already set
+       int do_manpath = (getenv("MANPATH") != NULL);
+       if (do_manpath) {
+               manpath = construct_path("MANPATH", "/etc/manpaths", "/etc/manpaths.d");
+       }
+
+       if (style == STYLE_CSH) {
+               printf("setenv PATH \"%s\";\n", path);
+               if (do_manpath) printf("setenv MANPATH \"%s\";\n", manpath);
+       } else {
+               printf("PATH=\"%s\"; export PATH;\n", path);
+               if (do_manpath) printf("MANPATH=\"%s\"; export MANPATH;\n", manpath);
+       }
+
+       return 0;
+}