1/* 2 * Copyright (c) 2008 Apple Inc. All rights reserved. 3 * 4 * @APPLE_LICENSE_HEADER_START@ 5 * 6 * This file contains Original Code and/or Modifications of Original Code 7 * as defined in and that are subject to the Apple Public Source License 8 * Version 2.0 (the 'License'). You may not use this file except in 9 * compliance with the License. Please obtain a copy of the License at 10 * http://www.opensource.apple.com/apsl/ and read it before using this 11 * file. 12 * 13 * The Original Code and all software distributed under the License are 14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 18 * Please see the License for the specific language governing rights and 19 * limitations under the License. 20 * 21 * @APPLE_LICENSE_HEADER_END@ 22 */ 23 24#include <fts.h> 25#include <stdio.h> 26#include <stdlib.h> 27#include <string.h> 28#include <unistd.h> 29#include <limits.h> 30 31static void usage() { 32 fprintf(stderr, "usage: path_helper [-c | -s]"); 33 exit(1); 34} 35 36// Append path segment if it does not exist. Reallocate 37// the path buffer as necessary. 38 39int append_path_segment(char** path, const char* segment) { 40 if (*path == NULL || segment == NULL) return -1; 41 42 size_t pathlen = strlen(*path); 43 size_t seglen = strlen(segment); 44 45 if (seglen == 0) return 0; 46 47 // Does the segment already exist in the path? 48 // (^|:)segment(:|$) 49 char* match = strstr(*path, segment); 50 while (match) { 51 if ((match == *path || match[-1] == ':') && 52 (match[seglen] == ':' || match[seglen] == 0)) { 53 return 0; 54 } 55 match = strstr(match+1, segment); 56 } 57 58 // size = pathlen + ':' + segment + '\0' 59 size_t size = pathlen + seglen + 2; 60 *path = reallocf(*path, size); 61 if (*path == NULL) return -1; 62 63 if (pathlen > 0) strlcat(*path, ":", size); 64 strlcat(*path, segment, size); 65 return 0; 66} 67 68// Convert fgetln output into a sanitized segment 69// escape quotes, dollars, etc. 70char* read_segment(const char* line, size_t len) { 71 int escapes = 0; 72 size_t i, j; 73 74 for (i = 0; i < len; ++i) { 75 char c = line[i]; 76 if (c == '\"' || c == '\'' || c == '$') { 77 ++escapes; 78 } 79 } 80 81 size_t size = len + escapes + 1; 82 83 char* segment = malloc(size); 84 if (segment == NULL) return NULL; 85 86 for (i = 0, j = 0; i < len; ++i, ++j) { 87 char c = line[i]; 88 if (c == '\"' || c == '\'' || c == '$') { 89 segment[j++] = '\\'; 90 segment[j] = c; 91 } else if (c == '\n') { 92 segment[j] = 0; 93 break; 94 } else { 95 segment[j] = line[i]; 96 } 97 } 98 99 return segment; 100} 101 102// Construct a path variable, starting with the contents 103// of the given environment variable, adding the contents 104// of the default file and files in the path directory. 105 106char* construct_path(char* env_var, char* defaults_path, char* dir_path) { 107 FTS* fts; 108 FTSENT* ent; 109 110 char* result = calloc(sizeof(char), 1); 111 112 char* dirpathv[] = { defaults_path, dir_path, NULL }; 113 fts = fts_open(dirpathv, FTS_PHYSICAL | FTS_XDEV, NULL); 114 if (!fts) { 115 perror(dir_path); 116 return NULL; 117 } 118 119 while ((ent = fts_read(fts)) != NULL) { 120 // only interested in regular files, one level deep 121 if (ent->fts_info != FTS_F) { 122 if (ent->fts_level >= 1) fts_set(fts, ent, FTS_SKIP); 123 continue; 124 } 125 126 FILE* f = fopen(ent->fts_accpath, "r"); 127 if (f == NULL) { 128 perror(ent->fts_accpath); 129 continue; 130 } 131 132 for (;;) { 133 size_t len; 134 char* line = fgetln(f, &len); 135 if (line == NULL) break; 136 char* segment = read_segment(line, len); 137 138 append_path_segment(&result, segment); 139 } 140 141 fclose(f); 142 } 143 fts_close(fts); 144 145 // merge in any existing custom PATH elemenets 146 char* str = getenv(env_var); 147 if (str) str = strdup(str); 148 while (str) { 149 char* sep = strchr(str, ':'); 150 if (sep) *sep = 0; 151 152 append_path_segment(&result, str); 153 if (sep) { 154 str = sep + 1; 155 } else { 156 str = NULL; 157 } 158 } 159 160 return result; 161} 162 163enum { 164 STYLE_CSH, 165 STYLE_SH 166}; 167 168int main(int argc, char* argv[]) { 169 int style = STYLE_SH; 170 171 if (argc > 2) usage(); 172 173 // default to csh style, if $SHELL ends with "csh". 174 char* shell = getenv("SHELL"); 175 if (shell) { 176 char* str = strstr(shell, "csh"); 177 if (str) style = STYLE_CSH; 178 } 179 180 if (argc == 2 && strcmp(argv[1], "-c") == 0) style = STYLE_CSH; 181 if (argc == 2 && strcmp(argv[1], "-s") == 0) style = STYLE_SH; 182 183 char* path = construct_path("PATH", "/etc/paths", "/etc/paths.d"); 184 char* manpath = NULL; 185 186 // only adjust manpath if already set 187 int do_manpath = (getenv("MANPATH") != NULL); 188 if (do_manpath) { 189 manpath = construct_path("MANPATH", "/etc/manpaths", "/etc/manpaths.d"); 190 } 191 192 if (style == STYLE_CSH) { 193 printf("setenv PATH \"%s\";\n", path); 194 if (do_manpath) printf("setenv MANPATH \"%s\";\n", manpath); 195 } else { 196 printf("PATH=\"%s\"; export PATH;\n", path); 197 if (do_manpath) printf("MANPATH=\"%s\"; export MANPATH;\n", manpath); 198 } 199 200 return 0; 201} 202