exec.c 8.9 KB
Newer Older
1 2
/*-------------------------------------------------------------------------
 *
3
 * exec.c
4
 *
B
Bruce Momjian 已提交
5
 * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
B
Add:  
Bruce Momjian 已提交
6
 * Portions Copyright (c) 1994, Regents of the University of California
7 8 9
 *
 *
 * IDENTIFICATION
10
 *	  $PostgreSQL: pgsql/src/port/exec.c,v 1.13 2004/05/21 16:06:23 tgl 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
#ifdef FRONTEND
#undef pstrdup
#define pstrdup(p)	strdup(p)
#define pfree(p)	free(p)
#endif
36

37 38 39 40 41 42 43
/* $PATH (or %PATH%) path separator */
#ifdef WIN32
#define PATHSEP ';'
#else
#define PATHSEP ':'
#endif

44 45 46 47 48 49 50 51 52 53
#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)
54 55
#endif

56 57 58 59 60 61 62
#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

63

64
static void win32_make_absolute(char *path);
65

66

67
/*
68
 * validate_exec -- validate "path" as an executable file
69 70
 *
 * returns 0 if the file is found and no error is encountered.
71 72
 *		  -1 if the regular file "path" does not exist or cannot be executed.
 *		  -2 if the file is otherwise valid but cannot be read.
73
 */
74
static int
75
validate_exec(char *path)
76
{
77
	struct stat buf;
B
Bruce Momjian 已提交
78

79
#ifndef WIN32
80 81 82
	uid_t		euid;
	struct group *gp;
	struct passwd *pwp;
83 84
	int			i;
	int			in_grp = 0;
B
Bruce Momjian 已提交
85
#else
86
	char		path_exe[MAXPGPATH + 2 + strlen(".exe")];
87
#endif
88 89
	int			is_r = 0;
	int			is_x = 0;
90

91 92
#ifdef WIN32
	/* Win32 requires a .exe suffix for stat() */
93 94
	if (strlen(path) >= strlen(".exe") &&
		pg_strcasecmp(path + strlen(path) - strlen(".exe"), ".exe") != 0)
95 96 97 98 99 100 101
	{
		strcpy(path_exe, path);
		strcat(path_exe, ".exe");
		path = path_exe;
	}
#endif

102 103 104 105 106 107 108
	/*
	 * 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)
109
		return -1;
110 111

	if ((buf.st_mode & S_IFMT) != S_IFREG)
112
		return -1;
113 114

	/*
115
	 * Ensure that we are using an authorized executable.
116 117 118 119 120 121
	 */

	/*
	 * Ensure that the file is both executable and readable (required for
	 * dynamic loading).
	 */
122
#ifdef WIN32
B
Bruce Momjian 已提交
123 124 125
	is_r = buf.st_mode & S_IRUSR;
	is_x = buf.st_mode & S_IXUSR;
	return is_x ? (is_r ? 0 : -2) : -1;
126
#else
127
	euid = geteuid();
128 129

	/* If owned by us, just check owner bits */
130 131 132 133
	if (euid == buf.st_uid)
	{
		is_r = buf.st_mode & S_IRUSR;
		is_x = buf.st_mode & S_IXUSR;
134
		return is_x ? (is_r ? 0 : -2) : -1;
135
	}
136 137 138 139

	/* OK, check group bits */
	
	pwp = getpwuid(euid);	/* not thread-safe */
140 141
	if (pwp)
	{
142
		if (pwp->pw_gid == buf.st_gid)	/* my primary group? */
143 144
			++in_grp;
		else if (pwp->pw_name &&
145
				 (gp = getgrgid(buf.st_gid)) != NULL && /* not thread-safe */
146
				 gp->gr_mem != NULL)
147
		{	/* try list of member groups */
148 149 150 151 152 153 154 155 156 157 158 159 160
			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;
161
			return is_x ? (is_r ? 0 : -2) : -1;
162 163
		}
	}
164 165

	/* Check "other" bits */
166 167
	is_r = buf.st_mode & S_IROTH;
	is_x = buf.st_mode & S_IXOTH;
168
	return is_x ? (is_r ? 0 : -2) : -1;
169

170
#endif
171 172 173
}

/*
174
 * find_my_exec -- find an absolute path to a valid executable
175 176
 *
 * The reason we have to work so hard to find an absolute path is that
177 178 179
 * 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.
180 181 182 183
 *
 * 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.
184 185
 */
int
B
Bruce Momjian 已提交
186
find_my_exec(const char *argv0, char *retpath)
187
{
188
	char		cwd[MAXPGPATH];
189 190 191 192
	char	   *p;
	char	   *path,
			   *startp,
			   *endp;
193

194 195 196
	if (!getcwd(cwd, MAXPGPATH))
		cwd[0] = '\0';

197
	/*
198 199 200
	 * 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
201 202
	 * wasn't in PATH, and we don't want to use incompatible executables.
	 *
203
	 * For the binary: First try: if we're given some kind of path, use it
204 205
	 * (making sure that a relative path is made absolute before returning
	 * it).
206
	 */
207
	/* Does argv0 have a separator? */
208
	if ((p = last_path_separator(argv0)))
209
	{
210 211
		if (*++p == '\0')
		{
212
			log_error("argv[0] ends with a path separator \"%s\"", argv0);
213 214
			return -1;
		}
215 216

		if (is_absolute_path(argv0))
B
Bruce Momjian 已提交
217
			StrNCpy(retpath, argv0, MAXPGPATH);
218
		else
B
Bruce Momjian 已提交
219
			snprintf(retpath, MAXPGPATH, "%s/%s", cwd, argv0);
220
		
B
Bruce Momjian 已提交
221 222
		canonicalize_path(retpath);
		if (validate_exec(retpath) == 0)
223
		{
B
Bruce Momjian 已提交
224
			win32_make_absolute(retpath);
225
			return 0;
226
		}
227 228
		else
		{
B
Bruce Momjian 已提交
229
			log_error("invalid binary \"%s\"", retpath);
230 231
			return -1;
		}
232
	}
233

234 235 236 237
#ifdef WIN32
	/* Win32 checks the current directory first for names without slashes */
	if (validate_exec(argv0) == 0)
	{
B
Bruce Momjian 已提交
238 239
		snprintf(retpath, MAXPGPATH, "%s/%s", cwd, argv0);
		win32_make_absolute(retpath);
240 241 242 243
		return 0;
	}
#endif

244 245 246 247 248 249
	/*
	 * Second try: since no explicit path was supplied, the user must have
	 * been relying on PATH.  We'll use the same PATH.
	 */
	if ((p = getenv("PATH")) && *p)
	{
250
		path = pstrdup(p);		/* make a modifiable copy */
B
Bruce Momjian 已提交
251
		for (startp = path, endp = strchr(path, PATHSEP);
252
			 startp && *startp;
B
Bruce Momjian 已提交
253
			 startp = endp + 1, endp = strchr(startp, PATHSEP))
254 255 256 257 258
		{
			if (startp == endp) /* it's a "::" */
				continue;
			if (endp)
				*endp = '\0';
259 260

			if (is_absolute_path(startp))
B
Bruce Momjian 已提交
261
				snprintf(retpath, MAXPGPATH, "%s/%s", startp, argv0);
262
			else
B
Bruce Momjian 已提交
263
				snprintf(retpath, MAXPGPATH, "%s/%s/%s", cwd, startp, argv0);
264

B
Bruce Momjian 已提交
265 266
			canonicalize_path(retpath);
			switch (validate_exec(retpath))
267
			{
268
				case 0: /* found ok */
B
Bruce Momjian 已提交
269
					win32_make_absolute(retpath);
270
					pfree(path);
271
					return 0;
272 273 274
				case -1:		/* wasn't even a candidate, keep looking */
					break;
				case -2:		/* found but disqualified */
B
Bruce Momjian 已提交
275
					log_error("could not read binary \"%s\"", retpath);
276
					pfree(path);
277
					return -1;
278 279 280 281
			}
			if (!endp)			/* last one */
				break;
		}
282
		pfree(path);
283 284
	}

285
	log_error("could not find a \"%s\" to execute", argv0);
286
	return -1;
287 288 289 290 291 292

#if 0
	/*
	 *	Win32 has a native way to find the executable name, but the above
	 *	method works too.
	 */
B
Bruce Momjian 已提交
293
	if (GetModuleFileName(NULL, retpath, MAXPGPATH) == 0)
294
		log_error("GetModuleFileName failed (%i)",(int)GetLastError());
295
#endif
296
}
297 298 299 300 301 302


/*
 * Find our binary directory, then make sure the "target" executable
 * is the proper version.
 */
303 304 305
int
find_other_exec(const char *argv0, const char *target,
				const char *versionstr, char *retpath)
306 307 308 309 310
{
	char		cmd[MAXPGPATH];
	char		line[100];
	FILE	   *pgver;

311
	if (find_my_exec(argv0, retpath) < 0)
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
		return -1;

	/* Trim off program name and keep just directory */	
	*last_path_separator(retpath) = '\0';

	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);

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

	if ((pgver = popen(cmd, "r")) == NULL)
		return -1;

	if (fgets(line, sizeof(line), pgver) == NULL)
		perror("fgets failure");

	if (pclose_check(pgver))
		return -1;

	if (strcmp(line, versionstr) != 0)
		return -2;

	return 0;
}


345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366
/*
 * 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))
	{
367
		log_error(_("child process exited with exit code %d\n"),
368 369 370 371
				WEXITSTATUS(exitstatus));
	}
	else if (WIFSIGNALED(exitstatus))
	{
372
		log_error(_("child process was terminated by signal %d\n"),
373 374 375 376
				WTERMSIG(exitstatus));
	}
	else
	{
377
		log_error(_("child process exited with unrecognized status %d\n"),
378 379 380 381 382 383 384
				exitstatus);
	}

	return -1;
}


385 386 387 388 389 390 391 392 393 394 395 396
/*
 * 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)
	{
397
		log_error("Win32 path expansion failed: %s", strerror(errno));
398
		StrNCpy(abspath, path, MAXPGPATH);
399 400 401 402 403 404
	}
	canonicalize_path(abspath);

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