exec.c 11.5 KB
Newer Older
1 2
/*-------------------------------------------------------------------------
 *
3
 * exec.c
4
 *
B
Bruce Momjian 已提交
5
 * Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
B
Add:  
Bruce Momjian 已提交
6
 * Portions Copyright (c) 1994, Regents of the University of California
7 8 9
 *
 *
 * IDENTIFICATION
B
Bruce Momjian 已提交
10
 *	  $PostgreSQL: pgsql/src/port/exec.c,v 1.24 2004/08/29 04:13:12 momjian Exp $
11 12 13
 *
 *-------------------------------------------------------------------------
 */
14 15

#ifndef FRONTEND
16
#include "postgres.h"
17 18 19
#else
#include "postgres_fe.h"
#endif
20

21 22
#include <grp.h>
#include <pwd.h>
B
Bruce Momjian 已提交
23
#include <sys/stat.h>
24
#include <sys/wait.h>
25
#include <unistd.h>
26

27
#include "miscadmin.h"
28

29
#define _(x) gettext(x)
30

31 32 33 34 35 36 37 38 39 40
#ifndef S_IRUSR					/* XXX [TRH] should be in a header */
#define S_IRUSR		 S_IREAD
#define S_IWUSR		 S_IWRITE
#define S_IXUSR		 S_IEXEC
#define S_IRGRP		 ((S_IRUSR)>>3)
#define S_IWGRP		 ((S_IWUSR)>>3)
#define S_IXGRP		 ((S_IXUSR)>>3)
#define S_IROTH		 ((S_IRUSR)>>6)
#define S_IWOTH		 ((S_IWUSR)>>6)
#define S_IXOTH		 ((S_IXUSR)>>6)
41 42
#endif

43 44 45 46 47 48 49
#ifndef FRONTEND
/* We use only 3-parameter elog calls in this file, for simplicity */
#define log_error(str, param)	elog(LOG, (str), (param))
#else
#define log_error(str, param)	fprintf(stderr, (str), (param))
#endif

50

51
static void win32_make_absolute(char *path);
52

53

54
/*
55
 * validate_exec -- validate "path" as an executable file
56 57
 *
 * returns 0 if the file is found and no error is encountered.
58 59
 *		  -1 if the regular file "path" does not exist or cannot be executed.
 *		  -2 if the file is otherwise valid but cannot be read.
60
 */
61
static int
62
validate_exec(const char *path)
63
{
64
	struct stat buf;
B
Bruce Momjian 已提交
65

66
#ifndef WIN32
67 68 69
	uid_t		euid;
	struct group *gp;
	struct passwd *pwp;
70 71
	int			i;
	int			in_grp = 0;
B
Bruce Momjian 已提交
72
#else
73
	char		path_exe[MAXPGPATH + 2 + strlen(".exe")];
74
#endif
75 76
	int			is_r = 0;
	int			is_x = 0;
77

78 79
#ifdef WIN32
	/* Win32 requires a .exe suffix for stat() */
80 81
	if (strlen(path) >= strlen(".exe") &&
		pg_strcasecmp(path + strlen(path) - strlen(".exe"), ".exe") != 0)
82 83 84 85 86 87 88
	{
		strcpy(path_exe, path);
		strcat(path_exe, ".exe");
		path = path_exe;
	}
#endif

89 90 91 92 93 94 95
	/*
	 * Ensure that the file exists and is a regular file.
	 *
	 * XXX if you have a broken system where stat() looks at the symlink
	 * instead of the underlying file, you lose.
	 */
	if (stat(path, &buf) < 0)
96
		return -1;
97 98

	if ((buf.st_mode & S_IFMT) != S_IFREG)
99
		return -1;
100 101

	/*
102
	 * Ensure that we are using an authorized executable.
103 104 105 106 107 108
	 */

	/*
	 * Ensure that the file is both executable and readable (required for
	 * dynamic loading).
	 */
109
#ifdef WIN32
B
Bruce Momjian 已提交
110 111 112
	is_r = buf.st_mode & S_IRUSR;
	is_x = buf.st_mode & S_IXUSR;
	return is_x ? (is_r ? 0 : -2) : -1;
113
#else
114
	euid = geteuid();
115 116

	/* If owned by us, just check owner bits */
117 118 119 120
	if (euid == buf.st_uid)
	{
		is_r = buf.st_mode & S_IRUSR;
		is_x = buf.st_mode & S_IXUSR;
121
		return is_x ? (is_r ? 0 : -2) : -1;
122
	}
123 124 125 126

	/* OK, check group bits */
	
	pwp = getpwuid(euid);	/* not thread-safe */
127 128
	if (pwp)
	{
129
		if (pwp->pw_gid == buf.st_gid)	/* my primary group? */
130 131
			++in_grp;
		else if (pwp->pw_name &&
132
				 (gp = getgrgid(buf.st_gid)) != NULL && /* not thread-safe */
133
				 gp->gr_mem != NULL)
134
		{	/* try list of member groups */
135 136 137 138 139 140 141 142 143 144 145 146 147
			for (i = 0; gp->gr_mem[i]; ++i)
			{
				if (!strcmp(gp->gr_mem[i], pwp->pw_name))
				{
					++in_grp;
					break;
				}
			}
		}
		if (in_grp)
		{
			is_r = buf.st_mode & S_IRGRP;
			is_x = buf.st_mode & S_IXGRP;
148
			return is_x ? (is_r ? 0 : -2) : -1;
149 150
		}
	}
151 152

	/* Check "other" bits */
153 154
	is_r = buf.st_mode & S_IROTH;
	is_x = buf.st_mode & S_IXOTH;
155
	return is_x ? (is_r ? 0 : -2) : -1;
156

157
#endif
158 159 160
}

/*
161
 * find_my_exec -- find an absolute path to a valid executable
162 163
 *
 * The reason we have to work so hard to find an absolute path is that
164 165 166
 * on some platforms we can't do dynamic loading unless we know the
 * executable's location.  Also, we need a full path not a relative
 * path because we will later change working directory.
167 168 169 170
 *
 * This function is not thread-safe because of it calls validate_exec(),
 * which calls getgrgid().  This function should be used only in
 * non-threaded binaries, not in library routines.
171 172
 */
int
B
Bruce Momjian 已提交
173
find_my_exec(const char *argv0, char *retpath)
174
{
175 176
	char		cwd[MAXPGPATH], test_path[MAXPGPATH];
 	char	   *path;
177

178 179 180
	if (!getcwd(cwd, MAXPGPATH))
		cwd[0] = '\0';

181
	/*
182 183 184
	 * First try: use the binary that's located in the
	 * same directory if it was invoked with an explicit path.
	 * Presumably the user used an explicit path because it
185 186
	 * wasn't in PATH, and we don't want to use incompatible executables.
	 *
187
	 * For the binary: First try: if we're given some kind of path, use it
188 189
	 * (making sure that a relative path is made absolute before returning
	 * it).
190
	 */
191
	/* Does argv0 have a separator? */
192
	if ((path = last_dir_separator(argv0)))
193
	{
194
		if (*++path == '\0')
195
		{
196
			log_error("argv[0] ends with a path separator \"%s\"", argv0);
197 198
			return -1;
		}
199 200

		if (is_absolute_path(argv0))
B
Bruce Momjian 已提交
201
			StrNCpy(retpath, argv0, MAXPGPATH);
202
		else
B
Bruce Momjian 已提交
203
			snprintf(retpath, MAXPGPATH, "%s/%s", cwd, argv0);
204
		
B
Bruce Momjian 已提交
205 206
		canonicalize_path(retpath);
		if (validate_exec(retpath) == 0)
207
		{
B
Bruce Momjian 已提交
208
			win32_make_absolute(retpath);
209
			return 0;
210
		}
211 212
		else
		{
B
Bruce Momjian 已提交
213
			log_error("invalid binary \"%s\"", retpath);
214 215
			return -1;
		}
216
	}
217

218 219 220 221
#ifdef WIN32
	/* Win32 checks the current directory first for names without slashes */
	if (validate_exec(argv0) == 0)
	{
B
Bruce Momjian 已提交
222 223
		snprintf(retpath, MAXPGPATH, "%s/%s", cwd, argv0);
		win32_make_absolute(retpath);
224 225 226 227
		return 0;
	}
#endif

228 229 230 231
	/*
	 * Second try: since no explicit path was supplied, the user must have
	 * been relying on PATH.  We'll use the same PATH.
	 */
232
	if ((path = getenv("PATH")) && *path)
233
	{
234 235 236
	 	char	   *startp = NULL, *endp = NULL;

		do
237
		{
238 239 240 241
			if (!startp)
				startp = path;
			else
				startp = endp + 1;
242

243
			endp = first_path_separator(startp);
244 245 246 247 248 249 250
			if (!endp)
				endp = startp + strlen(startp);	/* point to end */

			StrNCpy(test_path, startp, Min(endp - startp + 1, MAXPGPATH));

			if (is_absolute_path(test_path))
				snprintf(retpath, MAXPGPATH, "%s/%s", test_path, argv0);
251
			else
252
				snprintf(retpath, MAXPGPATH, "%s/%s/%s", cwd, test_path, argv0);
253

B
Bruce Momjian 已提交
254 255
			canonicalize_path(retpath);
			switch (validate_exec(retpath))
256
			{
257
				case 0: /* found ok */
B
Bruce Momjian 已提交
258
					win32_make_absolute(retpath);
259
					return 0;
260
				case -1:		/* wasn't even a candidate, keep looking */
261
					continue;
262
				case -2:		/* found but disqualified */
B
Bruce Momjian 已提交
263
					log_error("could not read binary \"%s\"", retpath);
264
					continue;
265
			}
266
		} while (*endp);
267 268
	}

269
	log_error("could not find a \"%s\" to execute", argv0);
270
	return -1;
271 272 273 274 275 276

#if 0
	/*
	 *	Win32 has a native way to find the executable name, but the above
	 *	method works too.
	 */
B
Bruce Momjian 已提交
277
	if (GetModuleFileName(NULL, retpath, MAXPGPATH) == 0)
278
		log_error("GetModuleFileName failed (%i)",(int)GetLastError());
279
#endif
280
}
281

282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376
/*
 * The runtime librarys popen() on win32 does not work when being
 * called from a service when running on windows <= 2000, because
 * there is no stdin/stdout/stderr.
 *
 * Executing a command in a pipe and reading the first line from it
 * is all we need.
 */
	
static char *pipe_read_line(char *cmd, char *line, int maxsize)
{
#ifndef WIN32
	FILE *pgver;

	/* flush output buffers in case popen does not... */
	fflush(stdout);
	fflush(stderr);

	if ((pgver = popen(cmd, "r")) == NULL)
		return NULL;
	
	if (fgets(line, maxsize, pgver) == NULL)
	{
		perror("fgets failure");
		return NULL;
	}

	if (pclose_check(pgver))
		return NULL;
	
	return line;
#else
	/* Win32 */
	SECURITY_ATTRIBUTES sattr;
	HANDLE childstdoutrd, childstdoutwr, childstdoutrddup;
	PROCESS_INFORMATION pi;
	STARTUPINFO si;
	char *retval = NULL;

	sattr.nLength = sizeof(SECURITY_ATTRIBUTES);
	sattr.bInheritHandle = TRUE;
	sattr.lpSecurityDescriptor = NULL;

	if (!CreatePipe(&childstdoutrd, &childstdoutwr, &sattr, 0))
		return NULL;
	
	if (!DuplicateHandle(GetCurrentProcess(),
						 childstdoutrd,
						 GetCurrentProcess(),
						 &childstdoutrddup,
						 0,
						 FALSE,
						 DUPLICATE_SAME_ACCESS))
	{
		CloseHandle(childstdoutrd);
		CloseHandle(childstdoutwr);
		return NULL;
	}

	CloseHandle(childstdoutrd);
	
	ZeroMemory(&pi,sizeof(pi));
	ZeroMemory(&si,sizeof(si));
	si.cb = sizeof(si);
	si.dwFlags = STARTF_USESTDHANDLES;
	si.hStdError = childstdoutwr;
	si.hStdOutput = childstdoutwr;
	si.hStdInput = INVALID_HANDLE_VALUE;
	
	if (CreateProcess(NULL,
					  cmd,
					  NULL,
					  NULL,
					  TRUE,
					  0,
					  NULL,
					  NULL,
					  &si,
					  &pi))
	{
		DWORD bytesread = 0;
		/* Successfully started the process */

		ZeroMemory(line,maxsize);
		
		/* Let's see if we can read */
		if (WaitForSingleObject(childstdoutrddup, 10000) != WAIT_OBJECT_0) 
		{
			/* Got timeout */
			CloseHandle(pi.hProcess);
			CloseHandle(pi.hThread);
			CloseHandle(childstdoutwr);
			CloseHandle(childstdoutrddup);
			return NULL;
		}
377

378 379 380 381 382 383
		/* We try just once */
		if (ReadFile(childstdoutrddup, line, maxsize, &bytesread, NULL) &&
			bytesread > 0)
		{
			/* So we read some data */
			retval = line;
384
			int len = strlen(line);
385

386
			/*
387 388 389 390 391 392
			 *	If EOL is \r\n, convert to just \n.
			 *	Because stdout is a text-mode stream, the \n output by
			 *	the child process is received as \r\n, so we convert it
			 *	to \n.  The server main.c sets
			 *	setvbuf(stdout, NULL, _IONBF, 0) which has the effect
			 *	of disabling \n to \r\n expansion for stdout.
393
			 */
394
			if (len >= 2 && line[len-2] == '\r' && line[len-1] == '\n')
395
			{
396 397 398
				line[len-2] = '\n';
				line[len-1] = '\0';
				len--;
399 400
			}

B
Bruce Momjian 已提交
401 402 403 404
			/*
			 *	We emulate fgets() behaviour. So if there is no newline
			 *	at the end, we add one...
			 */
405
			if (len == 0 || line[len-1] != '\n')
406 407 408 409 410 411 412 413 414 415 416 417 418 419 420
				strcat(line,"\n");
		}

		CloseHandle(pi.hProcess);
		CloseHandle(pi.hThread);
	}
	
	CloseHandle(childstdoutwr);
	CloseHandle(childstdoutrddup);

	return retval;
#endif
}
	

421 422 423 424 425

/*
 * Find our binary directory, then make sure the "target" executable
 * is the proper version.
 */
426 427 428
int
find_other_exec(const char *argv0, const char *target,
				const char *versionstr, char *retpath)
429 430 431
{
	char		cmd[MAXPGPATH];
	char		line[100];
432
	
433
	if (find_my_exec(argv0, retpath) < 0)
434 435 436
		return -1;

	/* Trim off program name and keep just directory */	
437
	*last_dir_separator(retpath) = '\0';
438 439 440
	canonicalize_path(retpath);

	/* Now append the other program's name */
441 442 443 444 445 446 447 448
	snprintf(retpath + strlen(retpath), MAXPGPATH - strlen(retpath),
			 "/%s%s", target, EXE);

	if (validate_exec(retpath))
		return -1;
	
	snprintf(cmd, sizeof(cmd), "\"%s\" -V 2>%s", retpath, DEVNULL);

449
	if (!pipe_read_line(cmd, line, sizeof(line)))
450
		return -1;
451
	
452 453 454 455 456 457 458
	if (strcmp(line, versionstr) != 0)
		return -2;

	return 0;
}


459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480
/*
 * pclose() plus useful error reporting
 * Is this necessary?  bjm 2004-05-11
 * It is better here because pipe.c has win32 backend linkage.
 */
int
pclose_check(FILE *stream)
{
	int		exitstatus;

	exitstatus = pclose(stream);

	if (exitstatus == 0)
		return 0;					/* all is well */

	if (exitstatus == -1)
	{
		/* pclose() itself failed, and hopefully set errno */
		perror("pclose failed");
	}
	else if (WIFEXITED(exitstatus))
	{
481
		log_error(_("child process exited with exit code %d\n"),
482 483 484 485
				WEXITSTATUS(exitstatus));
	}
	else if (WIFSIGNALED(exitstatus))
	{
486
		log_error(_("child process was terminated by signal %d\n"),
487 488 489 490
				WTERMSIG(exitstatus));
	}
	else
	{
491
		log_error(_("child process exited with unrecognized status %d\n"),
492 493 494 495 496 497 498
				exitstatus);
	}

	return -1;
}


499 500 501 502 503 504 505 506 507 508 509 510
/*
 * Windows doesn't like relative paths to executables (other things work fine)
 * so we call its builtin function to expand them. Elsewhere this is a NOOP
 */
static void
win32_make_absolute(char *path)
{
#ifdef WIN32
	char		abspath[MAXPGPATH];

	if (_fullpath(abspath, path, MAXPGPATH) == NULL)
	{
511
		log_error("Win32 path expansion failed: %s", strerror(errno));
512
		StrNCpy(abspath, path, MAXPGPATH);
513 514 515 516 517 518
	}
	canonicalize_path(abspath);

	StrNCpy(path, abspath, MAXPGPATH);
#endif
}