exec.c 8.4 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.6 2004/05/17 14:35:34 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 <unistd.h>
25

B
Bruce Momjian 已提交
26
#include "miscadmin.h"
27

28 29 30 31 32 33 34
/* $PATH (or %PATH%) path separator */
#ifdef WIN32
#define PATHSEP ';'
#else
#define PATHSEP ':'
#endif

35 36 37 38 39 40 41 42 43 44
#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)
45 46
#endif

47 48 49 50 51 52 53 54
#ifndef FRONTEND
/* We use only 3-parameter elog calls in this file, for simplicity */
#define log_debug(str, param)	elog(DEBUG2, str, param)
#else
#define log_debug(str, param)	{}	/* do nothing */
#endif

static void win32_make_absolute(char *path);
55

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

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

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

91 92 93 94 95 96 97 98
	/*
	 * 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)
	{
99
		log_debug("could not stat \"%s\": %m", path);
100
		return -1;
101
	}
102 103

	if ((buf.st_mode & S_IFMT) != S_IFREG)
104
	{
105
		log_debug("\"%s\" is not a regular file", path);
106
		return -1;
107 108 109
	}

	/*
110
	 * Ensure that we are using an authorized executable.
111 112 113 114 115 116
	 */

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

	/* If owned by us, just check owner bits */
125 126 127 128
	if (euid == buf.st_uid)
	{
		is_r = buf.st_mode & S_IRUSR;
		is_x = buf.st_mode & S_IXUSR;
129
		if (!(is_r && is_x))
130
			log_debug("\"%s\" is not user read/execute", path);
131
		return is_x ? (is_r ? 0 : -2) : -1;
132
	}
133 134 135 136

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

	/* Check "other" bits */
165 166
	is_r = buf.st_mode & S_IROTH;
	is_x = buf.st_mode & S_IXOTH;
167
	if (!(is_r && is_x))
168
		log_debug("\"%s\" is not other read/execute", path);
169
	return is_x ? (is_r ? 0 : -2) : -1;
170

171
#endif
172 173 174
}

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

	/*
196 197 198
	 * 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
199 200
	 * wasn't in PATH, and we don't want to use incompatible executables.
	 *
201
	 * For the binary: First try: if we're given some kind of path, use it
202 203
	 * (making sure that a relative path is made absolute before returning
	 * it).
204
	 */
205 206
	/* Does argv0 have a separator? */
	if (argv0 && (p = last_path_separator(argv0)))
207
	{
208 209 210 211 212
		if (*++p == '\0')
		{
			log_debug("argv[0] ends with a path separator \"%s\"", argv0);
			return -1;
		}
213
		if (is_absolute_path(argv0) || !getcwd(buf, MAXPGPATH))
214
			buf[0] = '\0';
215
		else	/* path is not absolute and getcwd worked */
216 217
			strcat(buf, "/");
		strcat(buf, argv0);
218
		if (validate_exec(buf) == 0)
219
		{
220
			strncpy(full_path, buf, MAXPGPATH);
221 222
			win32_make_absolute(full_path);
			log_debug("found \"%s\" using argv[0]", full_path);
223
			return 0;
224
		}
225
		log_debug("invalid binary \"%s\"", buf);
226
		return -1;
227
	}
228 229 230 231 232 233 234

	/*
	 * 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)
	{
235
		log_debug("searching PATH for executable%s", "");
236
		path = strdup(p);		/* make a modifiable copy */
B
Bruce Momjian 已提交
237
		for (startp = path, endp = strchr(path, PATHSEP);
238
			 startp && *startp;
B
Bruce Momjian 已提交
239
			 startp = endp + 1, endp = strchr(startp, PATHSEP))
240 241 242 243 244
		{
			if (startp == endp) /* it's a "::" */
				continue;
			if (endp)
				*endp = '\0';
245
			if (is_absolute_path(startp) || !getcwd(buf, MAXPGPATH))
246
				buf[0] = '\0';
247
			else	/* path is not absolute and getcwd worked */
248
				strcat(buf, "/");
249
			strcat(buf, startp);
250
			strcat(buf, "/");
251
			strcat(buf, argv0);
252
			switch (validate_exec(buf))
253
			{
254
				case 0: /* found ok */
255
					strncpy(full_path, buf, MAXPGPATH);
256 257
					win32_make_absolute(full_path);
					log_debug("found \"%s\" using PATH", full_path);
258
					free(path);
259
					return 0;
260 261 262
				case -1:		/* wasn't even a candidate, keep looking */
					break;
				case -2:		/* found but disqualified */
263
					log_debug("could not read binary \"%s\"", buf);
264
					free(path);
265
					return -1;
266 267 268 269
			}
			if (!endp)			/* last one */
				break;
		}
270 271 272
		free(path);
	}

273
	log_debug("could not find a \"%s\" to execute", argv0);
274
	return -1;
275 276 277 278 279 280 281 282 283 284

#if 0
	/*
	 *	Win32 has a native way to find the executable name, but the above
	 *	method works too.
	 */
	if (GetModuleFileName(NULL,basename,MAXPGPATH) == 0)
		ereport(FATAL,
				(errmsg("GetModuleFileName failed (%i)",(int)GetLastError())));
#endif
285
}
286 287 288 289 290 291


/*
 * Find our binary directory, then make sure the "target" executable
 * is the proper version.
 */
292 293
int find_other_exec(const char *argv0, char const *target,
					const char *versionstr, char *retpath)
294 295 296 297 298
{
	char		cmd[MAXPGPATH];
	char		line[100];
	FILE	   *pgver;

299
	if (find_my_exec(argv0, retpath) < 0)
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
		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;
}


/*
 * 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
 *
 * Returns malloc'ed memory.
 */
static void
win32_make_absolute(char *path)
{
#ifdef WIN32
	char		abspath[MAXPGPATH];

	if (_fullpath(abspath, path, MAXPGPATH) == NULL)
	{
347
		log_debug("Win32 path expansion failed:  %s", strerror(errno));
348 349 350 351 352 353 354 355 356
		return path;
	}
	canonicalize_path(abspath);

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