summaryrefslogtreecommitdiff
path: root/src/common/munix.c
blob: f7dc6d0a438c8e39fb9767587d6295b4fdca88da (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
/*
 *  munix.c -- special common code from Unix
 *
 *  (Originally used only under Unix, but now on all platforms.)
 */

#include "../h/gsupport.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

/*
 *  relfile(prog, mod) -- find related file.
 *
 *  Given that prog is the argv[0] by which this program was executed,
 *  and assuming that it was set by the shell or other equally correct
 *  invoker, relfile finds the location of a related file and returns
 *  it in an allocated string.  It takes the location of prog, appends
 *  mod, and canonizes the result; thus if argv[0] is icont or its path,
 *  relfile(argv[0],"/../iconx") finds the location of iconx.
 */
char *relfile(char *prog, char *mod) {
   static char baseloc[MaxPath];
   char buf[MaxPath];

   if (baseloc[0] == 0) {		/* if argv[0] not already found */

      if (findexe(prog, baseloc, sizeof(baseloc)) == NULL) {
         fprintf(stderr, "cannot find location of %s\n", prog);
         exit(EXIT_FAILURE);
         }
      if (followsym(baseloc, buf, sizeof(buf)) != NULL)
         strcpy(baseloc, buf);
   }

   strcpy(buf, baseloc);		/* start with base location */
   strcat(buf, mod);			/* append adjustment */
   canonize(buf);			/* canonize result */
   if (mod[strlen(mod)-1] == '/')	/* if trailing slash wanted */
      strcat(buf, "/");			/* append to result */
   return salloc(buf);			/* return allocated string */
   }

/*
 *  findexe(prog, buf, len) -- find absolute executable path, given argv[0]
 *
 *  Finds the absolute path to prog, assuming that prog is the value passed
 *  by the shell in argv[0].  The result is placed in buf, which is returned.
 *  NULL is returned in case of error.
 */

char *findexe(char *name, char *buf, size_t len) {
   int n;
   char *s;

   if (name == NULL)
      return NULL;

   /* if name does not contain a slash, search $PATH for file */
   if (strchr(name, '/') != NULL)
      strcpy(buf, name);
   else if (findonpath(name, buf, len) == NULL)
      return NULL;

   /* if path is not absolute, prepend working directory */
   if (buf[0] != '/') {
      n = strlen(buf) + 1;
      memmove(buf + len - n, buf, n);
      if (getcwd(buf, len - n) == NULL)
         return NULL;
      s = buf + strlen(buf);
      *s = '/';
      memcpy(s + 1, buf + len - n, n);
      }
   canonize(buf);
   return buf;
   }

/*
 *  findonpath(name, buf, len) -- find name on $PATH
 *
 *  Searches $PATH (using POSIX 1003.2 rules) for executable name,
 *  writing the resulting path in buf if found.
 */
char *findonpath(char *name, char *buf, size_t len) {
   int nlen, plen;
   char *path, *next, *sep, *end;
   struct stat status;

   nlen = strlen(name);
   path = getenv("PATH");

   if (path == NULL || *path == '\0')
      path = ".";

   end = path + strlen(path);
   for (next = path; next <= end; next = sep + 1) {
      sep = strchr(next, ':');
      if (sep == NULL)
         sep = end;
      plen = sep - next;
      if (plen == 0) {
         next = ".";
         plen = 1;
         }
      if (plen + 1 + nlen + 1 > len)
         return NULL;
      memcpy(buf, next, plen);
      buf[plen] = '/';
      strcpy(buf + plen + 1, name);
      if (access(buf, X_OK) == 0) {
         if (stat(buf, &status) == 0 && S_ISREG(status.st_mode))
            return buf;
         }
      }
   return NULL;
   }

/*
 *  followsym(name, buf, len) -- follow symlink to final destination.
 *
 *  If name specifies a file that is a symlink, resolves the symlink to
 *  its ultimate destination, and returns buf.  Otherwise, returns NULL.
 *
 *  Note that symlinks in the path to name do not make it a symlink.
 *
 *  buf should be long enough to hold name.
 */

#define MAX_FOLLOWED_LINKS 24

char *followsym(char *name, char *buf, size_t len) {
   int i, n;
   char *s, tbuf[MaxPath];

   strcpy(buf, name);

   for (i = 0; i < MAX_FOLLOWED_LINKS; i++) {
      if ((n = readlink(buf, tbuf, sizeof(tbuf) - 1)) <= 0)
         break;
      tbuf[n] = 0;

      if (tbuf[0] == '/') {
         if (n < len)
            strcpy(buf, tbuf);
         else
            return NULL;
         }
      else {
         s = strrchr(buf, '/');
         if (s != NULL)
            s++;
         else
            s = buf;
         if ((s - buf) + n < len)
            strcpy(s, tbuf);
         else
            return NULL;
         }
      canonize(buf);
      }

   if (i > 0 && i < MAX_FOLLOWED_LINKS)
      return buf;
   else
      return NULL;
   }

/*
 *  canonize(path) -- put file path in canonical form.
 *
 *  Rewrites path in place, and returns it, after excising fragments of
 *  "." or "dir/..".  All leading slashes are preserved but other extra
 *  slashes are deleted.  The path never grows longer except for the
 *  special case of an empty path, which is rewritten to be ".".
 *
 *  No check is made that any component of the path actually exists or
 *  that inner components are truly directories.  From this it follows
 *  that if "foo" is any file path, canonizing "foo/.." produces the path
 *  of the directory containing "foo".
 */

char *canonize(char *path) {
   int len;
   char *root, *end, *in, *out, *prev;

   /* initialize */
   root = path;				/* set barrier for trimming by ".." */
   end = path + strlen(path);		/* set end of input marker */
   while (*root == '/')			/* preserve all leading slashes */
      root++;
   in = root;				/* input pointer */
   out = root;				/* output pointer */

   /* scan string one component at a time */
   while (in < end) {

      /* count component length */
      for (len = 0; in + len < end && in[len] != '/'; len++)
         ;

      /* check for ".", "..", or other */
      if (len == 1 && *in == '.')	/* just ignore "." */
         in++;
      else if (len == 2 && in[0] == '.' && in[1] == '.') {
         in += 2;			/* skip over ".." */
         /* find start of previous component */
         prev = out;
         if (prev > root)
            prev--;			/* skip trailing slash */
         while (prev > root && prev[-1] != '/')
            prev--;			/* find next slash or start of path */
         if (prev < out - 1
         && (out - prev != 3 || strncmp(prev, "../", 3) != 0)) {
            out = prev;		/* trim trailing component */
            }
         else {
            memcpy(out, "../", 3);	/* cannot trim, so must keep ".." */
            out += 3;
            }
         }
      else {
         memmove(out, in, len);		/* copy component verbatim */
         out += len;
         in += len;
         *out++ = '/';			/* add output separator */
         }

      while (in < end && *in == '/')	/* consume input separators */
         in++;
      }

   /* final fixup */
   if (out > root)
      out--;				/* trim trailing slash */
   if (out == path)
      *out++ = '.';			/* change null path to "." */
   *out++ = '\0';
   return path;				/* return result */
   }