exec.c 8.0 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.5 2004/05/14 17:04:48 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 35 36 37
#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)
38 39
#endif

40 41 42 43 44 45 46 47
#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);
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 54
 *		  -1 if the regular file "path" does not exist or cannot be executed.
 *		  -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;
B
Bruce Momjian 已提交
60

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

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

84 85 86 87 88 89 90 91
	/*
	 * 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)
	{
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 108 109
	 */

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

	/* If owned by us, just check owner bits */
118 119 120 121
	if (euid == buf.st_uid)
	{
		is_r = buf.st_mode & S_IRUSR;
		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 128 129

	/* OK, check group bits */
	
	pwp = getpwuid(euid);	/* not thread-safe */
130 131
	if (pwp)
	{
132
		if (pwp->pw_gid == buf.st_gid)	/* my primary group? */
133 134
			++in_grp;
		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 139 140 141 142 143 144 145 146 147 148 149 150
			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;
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 159
	is_r = buf.st_mode & S_IROTH;
	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 172 173
 * 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.
174 175 176 177
 *
 * 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.
178 179
 */
int
180
find_my_exec(const char *argv0, char *full_path)
181
{
182 183 184 185 186
	char		buf[MAXPGPATH + 2];
	char	   *p;
	char	   *path,
			   *startp,
			   *endp;
187 188

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

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

266
	log_debug("could not find a \"%s\" to execute", argv0);
267
	return -1;
268
}
269 270 271 272 273 274


/*
 * Find our binary directory, then make sure the "target" executable
 * is the proper version.
 */
275 276
int find_other_exec(const char *argv0, char const *target,
					const char *versionstr, char *retpath)
277 278 279 280 281
{
	char		cmd[MAXPGPATH];
	char		line[100];
	FILE	   *pgver;

282
	if (find_my_exec(argv0, retpath) < 0)
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
		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)
	{
330
		log_debug("Win32 path expansion failed:  %s", strerror(errno));
331 332 333 334 335 336 337 338 339
		return path;
	}
	canonicalize_path(abspath);

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