1 /*-------------------------------------------------------------------------
5 * Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
6 * Portions Copyright (c) 1994, Regents of the University of California
10 * $PostgreSQL: pgsql/src/port/exec.c,v 1.31 2004/11/06 01:16:22 tgl Exp $
12 *-------------------------------------------------------------------------
18 #include "postgres_fe.h"
25 #ifndef WIN32_CLIENT_ONLY
29 #define _(x) gettext(x)
31 #ifndef S_IRUSR /* XXX [TRH] should be in a header */
32 #define S_IRUSR S_IREAD
33 #define S_IWUSR S_IWRITE
34 #define S_IXUSR S_IEXEC
35 #define S_IRGRP ((S_IRUSR)>>3)
36 #define S_IWGRP ((S_IWUSR)>>3)
37 #define S_IXGRP ((S_IXUSR)>>3)
38 #define S_IROTH ((S_IRUSR)>>6)
39 #define S_IWOTH ((S_IWUSR)>>6)
40 #define S_IXOTH ((S_IXUSR)>>6)
44 /* We use only 3-parameter elog calls in this file, for simplicity */
45 #define log_error(str, param) elog(LOG, str, param)
47 #define log_error(str, param) (fprintf(stderr, str, param), fputc('\n', stderr))
52 * validate_exec -- validate "path" as an executable file
54 * returns 0 if the file is found and no error is encountered.
55 * -1 if the regular file "path" does not exist or cannot be executed.
56 * -2 if the file is otherwise valid but cannot be read.
59 validate_exec(const char *path)
71 char path_exe[MAXPGPATH + sizeof(".exe") - 1];
77 /* Win32 requires a .exe suffix for stat() */
78 if (strlen(path) >= strlen(".exe") &&
79 pg_strcasecmp(path + strlen(path) - strlen(".exe"), ".exe") != 0)
81 strcpy(path_exe, path);
82 strcat(path_exe, ".exe");
88 * Ensure that the file exists and is a regular file.
90 * XXX if you have a broken system where stat() looks at the symlink
91 * instead of the underlying file, you lose.
93 if (stat(path, &buf) < 0)
96 if ((buf.st_mode & S_IFMT) != S_IFREG)
100 * Ensure that we are using an authorized executable.
104 * Ensure that the file is both executable and readable (required for
108 is_r = buf.st_mode & S_IRUSR;
109 is_x = buf.st_mode & S_IXUSR;
110 return is_x ? (is_r ? 0 : -2) : -1;
114 /* If owned by us, just check owner bits */
115 if (euid == buf.st_uid)
117 is_r = buf.st_mode & S_IRUSR;
118 is_x = buf.st_mode & S_IXUSR;
119 return is_x ? (is_r ? 0 : -2) : -1;
122 /* OK, check group bits */
124 pwp = getpwuid(euid); /* not thread-safe */
127 if (pwp->pw_gid == buf.st_gid) /* my primary group? */
129 else if (pwp->pw_name &&
130 (gp = getgrgid(buf.st_gid)) != NULL && /* not thread-safe */
132 { /* try list of member groups */
133 for (i = 0; gp->gr_mem[i]; ++i)
135 if (!strcmp(gp->gr_mem[i], pwp->pw_name))
144 is_r = buf.st_mode & S_IRGRP;
145 is_x = buf.st_mode & S_IXGRP;
146 return is_x ? (is_r ? 0 : -2) : -1;
150 /* Check "other" bits */
151 is_r = buf.st_mode & S_IROTH;
152 is_x = buf.st_mode & S_IXOTH;
153 return is_x ? (is_r ? 0 : -2) : -1;
158 * find_my_exec -- find an absolute path to a valid executable
160 * The reason we have to work so hard to find an absolute path is that
161 * on some platforms we can't do dynamic loading unless we know the
162 * executable's location. Also, we need a full path not a relative
163 * path because we will later change working directory.
165 * This function is not thread-safe because it calls validate_exec(),
166 * which calls getgrgid(). This function should be used only in
167 * non-threaded binaries, not in library routines.
170 find_my_exec(const char *argv0, char *retpath)
173 test_path[MAXPGPATH];
176 #ifndef WIN32_CLIENT_ONLY
177 if (!getcwd(cwd, MAXPGPATH))
178 strcpy(cwd, "."); /* cheesy, but better than nothing */
180 if (!GetCurrentDirectory(MAXPGPATH, cwd))
181 strcpy(cwd, "."); /* cheesy, but better than nothing */
185 * If argv0 contains a separator, then PATH wasn't used.
187 if (first_dir_separator(argv0) != NULL)
189 if (is_absolute_path(argv0))
190 StrNCpy(retpath, argv0, MAXPGPATH);
192 join_path_components(retpath, cwd, argv0);
193 canonicalize_path(retpath);
195 if (validate_exec(retpath) == 0)
198 log_error("invalid binary \"%s\"", retpath);
203 /* Win32 checks the current directory first for names without slashes */
204 join_path_components(retpath, cwd, argv0);
205 if (validate_exec(retpath) == 0)
210 * Since no explicit path was supplied, the user must have
211 * been relying on PATH. We'll search the same PATH.
213 if ((path = getenv("PATH")) && *path)
225 endp = first_path_separator(startp);
227 endp = startp + strlen(startp); /* point to end */
229 StrNCpy(test_path, startp, Min(endp - startp + 1, MAXPGPATH));
231 if (is_absolute_path(test_path))
232 join_path_components(retpath, test_path, argv0);
235 join_path_components(retpath, cwd, test_path);
236 join_path_components(retpath, retpath, argv0);
238 canonicalize_path(retpath);
240 switch (validate_exec(retpath))
242 case 0: /* found ok */
244 case -1: /* wasn't even a candidate, keep looking */
246 case -2: /* found but disqualified */
247 log_error("could not read binary \"%s\"", retpath);
253 log_error("could not find a \"%s\" to execute", argv0);
258 * The runtime library's popen() on win32 does not work when being
259 * called from a service when running on windows <= 2000, because
260 * there is no stdin/stdout/stderr.
262 * Executing a command in a pipe and reading the first line from it
267 pipe_read_line(char *cmd, char *line, int maxsize)
272 /* flush output buffers in case popen does not... */
276 if ((pgver = popen(cmd, "r")) == NULL)
279 if (fgets(line, maxsize, pgver) == NULL)
281 perror("fgets failure");
285 if (pclose_check(pgver))
291 SECURITY_ATTRIBUTES sattr;
292 HANDLE childstdoutrd,
295 PROCESS_INFORMATION pi;
299 sattr.nLength = sizeof(SECURITY_ATTRIBUTES);
300 sattr.bInheritHandle = TRUE;
301 sattr.lpSecurityDescriptor = NULL;
303 if (!CreatePipe(&childstdoutrd, &childstdoutwr, &sattr, 0))
306 if (!DuplicateHandle(GetCurrentProcess(),
312 DUPLICATE_SAME_ACCESS))
314 CloseHandle(childstdoutrd);
315 CloseHandle(childstdoutwr);
319 CloseHandle(childstdoutrd);
321 ZeroMemory(&pi, sizeof(pi));
322 ZeroMemory(&si, sizeof(si));
324 si.dwFlags = STARTF_USESTDHANDLES;
325 si.hStdError = childstdoutwr;
326 si.hStdOutput = childstdoutwr;
327 si.hStdInput = INVALID_HANDLE_VALUE;
329 if (CreateProcess(NULL,
342 /* Successfully started the process */
344 ZeroMemory(line, maxsize);
346 /* Let's see if we can read */
347 if (WaitForSingleObject(childstdoutrddup, 10000) != WAIT_OBJECT_0)
350 CloseHandle(pi.hProcess);
351 CloseHandle(pi.hThread);
352 CloseHandle(childstdoutwr);
353 CloseHandle(childstdoutrddup);
357 /* We try just once */
358 if (ReadFile(childstdoutrddup, line, maxsize, &bytesread, NULL) &&
361 /* So we read some data */
362 int len = strlen(line);
366 * If EOL is \r\n, convert to just \n. Because stdout is a
367 * text-mode stream, the \n output by the child process is
368 * received as \r\n, so we convert it to \n. The server
369 * main.c sets setvbuf(stdout, NULL, _IONBF, 0) which has the
370 * effect of disabling \n to \r\n expansion for stdout.
372 if (len >= 2 && line[len - 2] == '\r' && line[len - 1] == '\n')
374 line[len - 2] = '\n';
375 line[len - 1] = '\0';
380 * We emulate fgets() behaviour. So if there is no newline at
381 * the end, we add one...
383 if (len == 0 || line[len - 1] != '\n')
387 CloseHandle(pi.hProcess);
388 CloseHandle(pi.hThread);
391 CloseHandle(childstdoutwr);
392 CloseHandle(childstdoutrddup);
400 * Find another program in our binary's directory,
401 * then make sure it is the proper version.
404 find_other_exec(const char *argv0, const char *target,
405 const char *versionstr, char *retpath)
410 if (find_my_exec(argv0, retpath) < 0)
413 /* Trim off program name and keep just directory */
414 *last_dir_separator(retpath) = '\0';
415 canonicalize_path(retpath);
417 /* Now append the other program's name */
418 snprintf(retpath + strlen(retpath), MAXPGPATH - strlen(retpath),
419 "/%s%s", target, EXE);
421 if (validate_exec(retpath))
424 snprintf(cmd, sizeof(cmd), "\"%s\" -V 2>%s", retpath, DEVNULL);
426 if (!pipe_read_line(cmd, line, sizeof(line)))
429 if (strcmp(line, versionstr) != 0)
437 * pclose() plus useful error reporting
438 * Is this necessary? bjm 2004-05-11
439 * It is better here because pipe.c has win32 backend linkage.
442 pclose_check(FILE *stream)
446 exitstatus = pclose(stream);
449 return 0; /* all is well */
451 if (exitstatus == -1)
453 /* pclose() itself failed, and hopefully set errno */
454 perror("pclose failed");
456 else if (WIFEXITED(exitstatus))
458 log_error(_("child process exited with exit code %d"),
459 WEXITSTATUS(exitstatus));
461 else if (WIFSIGNALED(exitstatus))
463 log_error(_("child process was terminated by signal %d"),
464 WTERMSIG(exitstatus));
468 log_error(_("child process exited with unrecognized status %d"),