]> granicus.if.org Git - postgresql/blob - src/port/exec.c
Fix Win32 bug with missing errno for strerror().
[postgresql] / src / port / exec.c
1 /*-------------------------------------------------------------------------
2  *
3  * exec.c
4  *
5  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
6  * Portions Copyright (c) 1994, Regents of the University of California
7  *
8  *
9  * IDENTIFICATION
10  *        $PostgreSQL: pgsql/src/port/exec.c,v 1.3 2004/05/13 01:47:12 momjian Exp $
11  *
12  *-------------------------------------------------------------------------
13  */
14
15 #ifndef FRONTEND
16 #include "postgres.h"
17 #else
18 #include "postgres_fe.h"
19 #endif
20
21 #include <grp.h>
22 #include <pwd.h>
23 #include <sys/stat.h>
24 #include <unistd.h>
25
26 #include "miscadmin.h"
27
28 #ifndef S_IRUSR                                 /* XXX [TRH] should be in a header */
29 #define S_IRUSR          S_IREAD
30 #define S_IWUSR          S_IWRITE
31 #define S_IXUSR          S_IEXEC
32 #define S_IRGRP          ((S_IRUSR)>>3)
33 #define S_IWGRP          ((S_IWUSR)>>3)
34 #define S_IXGRP          ((S_IXUSR)>>3)
35 #define S_IROTH          ((S_IRUSR)>>6)
36 #define S_IWOTH          ((S_IWUSR)>>6)
37 #define S_IXOTH          ((S_IXUSR)>>6)
38 #endif
39
40 #ifndef FRONTEND
41 /* We use only 3-parameter elog calls in this file, for simplicity */
42 #define log_debug(str, param)   elog(DEBUG2, str, param)
43 #else
44 #define log_debug(str, param)   {}      /* do nothing */
45 #endif
46
47 static void win32_make_absolute(char *path);
48
49 /*
50  * validate_exec -- validate "path" as an executable file
51  *
52  * returns 0 if the file is found and no error is encountered.
53  *                -1 if the regular file "path" does not exist or cannot be executed.
54  *                -2 if the file is otherwise valid but cannot be read.
55  */
56 static int
57 validate_exec(char *path)
58 {
59         struct stat buf;
60
61 #ifndef WIN32
62         uid_t           euid;
63         struct group *gp;
64         struct passwd *pwp;
65         int                     i;
66         int                     in_grp = 0;
67 #else
68         char            path_exe[MAXPGPATH + 2 + strlen(".exe")];
69 #endif
70         int                     is_r = 0;
71         int                     is_x = 0;
72
73 #ifdef WIN32
74         /* Win32 requires a .exe suffix for stat() */
75         if (strlen(path) >= strlen(".exe") &&
76                 pg_strcasecmp(path + strlen(path) - strlen(".exe"), ".exe") != 0)
77         {
78                 strcpy(path_exe, path);
79                 strcat(path_exe, ".exe");
80                 path = path_exe;
81         }
82 #endif
83
84         /*
85          * Ensure that the file exists and is a regular file.
86          *
87          * XXX if you have a broken system where stat() looks at the symlink
88          * instead of the underlying file, you lose.
89          */
90         if (stat(path, &buf) < 0)
91         {
92                 log_debug("could not stat \"%s\": %m", path);
93                 return -1;
94         }
95
96         if ((buf.st_mode & S_IFMT) != S_IFREG)
97         {
98                 log_debug("\"%s\" is not a regular file", path);
99                 return -1;
100         }
101
102         /*
103          * Ensure that we are using an authorized executable.
104          */
105
106         /*
107          * Ensure that the file is both executable and readable (required for
108          * dynamic loading).
109          */
110 #ifdef WIN32
111         is_r = buf.st_mode & S_IRUSR;
112         is_x = buf.st_mode & S_IXUSR;
113         return is_x ? (is_r ? 0 : -2) : -1;
114 #else
115         euid = geteuid();
116
117         /* If owned by us, just check owner bits */
118         if (euid == buf.st_uid)
119         {
120                 is_r = buf.st_mode & S_IRUSR;
121                 is_x = buf.st_mode & S_IXUSR;
122                 if (!(is_r && is_x))
123                         log_debug("\"%s\" is not user read/execute", path);
124                 return is_x ? (is_r ? 0 : -2) : -1;
125         }
126
127         /* OK, check group bits */
128         
129         pwp = getpwuid(euid);   /* not thread-safe */
130         if (pwp)
131         {
132                 if (pwp->pw_gid == buf.st_gid)  /* my primary group? */
133                         ++in_grp;
134                 else if (pwp->pw_name &&
135                                  (gp = getgrgid(buf.st_gid)) != NULL && /* not thread-safe */
136                                  gp->gr_mem != NULL)
137                 {       /* try list of member groups */
138                         for (i = 0; gp->gr_mem[i]; ++i)
139                         {
140                                 if (!strcmp(gp->gr_mem[i], pwp->pw_name))
141                                 {
142                                         ++in_grp;
143                                         break;
144                                 }
145                         }
146                 }
147                 if (in_grp)
148                 {
149                         is_r = buf.st_mode & S_IRGRP;
150                         is_x = buf.st_mode & S_IXGRP;
151                         if (!(is_r && is_x))
152                                 log_debug("\"%s\" is not group read/execute", path);
153                         return is_x ? (is_r ? 0 : -2) : -1;
154                 }
155         }
156
157         /* Check "other" bits */
158         is_r = buf.st_mode & S_IROTH;
159         is_x = buf.st_mode & S_IXOTH;
160         if (!(is_r && is_x))
161                 log_debug("\"%s\" is not other read/execute", path);
162         return is_x ? (is_r ? 0 : -2) : -1;
163
164 #endif
165 }
166
167 /*
168  * find_my_exec -- find an absolute path to a valid executable
169  *
170  * The reason we have to work so hard to find an absolute path is that
171  * on some platforms we can't do dynamic loading unless we know the
172  * executable's location.  Also, we need a full path not a relative
173  * path because we will later change working directory.
174  *
175  * This function is not thread-safe because of it calls validate_exec(),
176  * which calls getgrgid().  This function should be used only in
177  * non-threaded binaries, not in library routines.
178  */
179 int
180 find_my_exec(char *full_path, const char *argv0)
181 {
182         char            buf[MAXPGPATH + 2];
183         char       *p;
184         char       *path,
185                            *startp,
186                            *endp;
187         const char *binary_name = get_progname(argv0);
188
189         /*
190          * First try: use the binary that's located in the
191          * same directory if it was invoked with an explicit path.
192          * Presumably the user used an explicit path because it
193          * wasn't in PATH, and we don't want to use incompatible executables.
194          *
195          * This has the neat property that it works for installed binaries, old
196          * source trees (obj/support/post{master,gres}) and new source
197          * trees (obj/post{master,gres}) because they all put the two binaries
198          * in the same place.
199          *
200          * for the binary: First try: if we're given some kind of path, use it
201          * (making sure that a relative path is made absolute before returning
202          * it).
203          */
204         if (argv0 && (p = last_path_separator(argv0)) && *++p)
205         {
206                 if (is_absolute_path(argv0) || !getcwd(buf, MAXPGPATH))
207                         buf[0] = '\0';
208                 else
209                         strcat(buf, "/");
210                 strcat(buf, argv0);
211                 p = last_path_separator(buf);
212                 strcpy(++p, binary_name);
213                 if (validate_exec(buf) == 0)
214                 {
215                         strncpy(full_path, buf, MAXPGPATH);
216                         win32_make_absolute(full_path);
217                         log_debug("found \"%s\" using argv[0]", full_path);
218                         return 0;
219                 }
220                 log_debug("invalid binary \"%s\"", buf);
221                 return -1;
222         }
223
224         /*
225          * Second try: since no explicit path was supplied, the user must have
226          * been relying on PATH.  We'll use the same PATH.
227          */
228         if ((p = getenv("PATH")) && *p)
229         {
230                 log_debug("searching PATH for executable%s", "");
231                 path = strdup(p);               /* make a modifiable copy */
232                 for (startp = path, endp = strchr(path, PATHSEP);
233                          startp && *startp;
234                          startp = endp + 1, endp = strchr(startp, PATHSEP))
235                 {
236                         if (startp == endp) /* it's a "::" */
237                                 continue;
238                         if (endp)
239                                 *endp = '\0';
240                         if (is_absolute_path(startp) || !getcwd(buf, MAXPGPATH))
241                                 buf[0] = '\0';
242                         else
243                                 strcat(buf, "/");
244                         strcat(buf, startp);
245                         strcat(buf, "/");
246                         strcat(buf, binary_name);
247                         switch (validate_exec(buf))
248                         {
249                                 case 0: /* found ok */
250                                         strncpy(full_path, buf, MAXPGPATH);
251                                         win32_make_absolute(full_path);
252                                         log_debug("found \"%s\" using PATH", full_path);
253                                         free(path);
254                                         return 0;
255                                 case -1:                /* wasn't even a candidate, keep looking */
256                                         break;
257                                 case -2:                /* found but disqualified */
258                                         log_debug("could not read binary \"%s\"", buf);
259                                         free(path);
260                                         return -1;
261                         }
262                         if (!endp)                      /* last one */
263                                 break;
264                 }
265                 free(path);
266         }
267
268         log_debug("could not find a \"%s\" to execute", binary_name);
269         return -1;
270 }
271
272
273 /*
274  * Find our binary directory, then make sure the "target" executable
275  * is the proper version.
276  */
277 int find_other_exec(char *retpath, const char *argv0,
278                             char const *target, const char *versionstr)
279 {
280         char            cmd[MAXPGPATH];
281         char            line[100];
282         FILE       *pgver;
283
284         if (find_my_exec(retpath, argv0) < 0)
285                 return -1;
286
287         /* Trim off program name and keep just directory */     
288         *last_path_separator(retpath) = '\0';
289
290         snprintf(retpath + strlen(retpath), MAXPGPATH - strlen(retpath),
291                          "/%s%s", target, EXE);
292
293         if (validate_exec(retpath))
294                 return -1;
295         
296         snprintf(cmd, sizeof(cmd), "\"%s\" -V 2>%s", retpath, DEVNULL);
297
298         /* flush output buffers in case popen does not... */
299         fflush(stdout);
300         fflush(stderr);
301
302         if ((pgver = popen(cmd, "r")) == NULL)
303                 return -1;
304
305         if (fgets(line, sizeof(line), pgver) == NULL)
306                 perror("fgets failure");
307
308         if (pclose_check(pgver))
309                 return -1;
310
311         if (strcmp(line, versionstr) != 0)
312                 return -2;
313
314         return 0;
315 }
316
317
318 /*
319  * Windows doesn't like relative paths to executables (other things work fine)
320  * so we call its builtin function to expand them. Elsewhere this is a NOOP
321  *
322  * Returns malloc'ed memory.
323  */
324 static void
325 win32_make_absolute(char *path)
326 {
327 #ifdef WIN32
328         char            abspath[MAXPGPATH];
329
330         if (_fullpath(abspath, path, MAXPGPATH) == NULL)
331         {
332                 log_debug("Win32 path expansion failed:  %s", strerror(errno));
333                 return path;
334         }
335         canonicalize_path(abspath);
336
337         StrNCpy(path, abspath, MAXPGPATH);
338 #endif
339         return;
340 }
341