summaryrefslogtreecommitdiff
path: root/contrib/pg_upgrade/exec.c
blob: 9eaab2fbdf0dd9d0dfe1c7acfe8f552080b3dc6a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
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
/*
 *	exec.c
 *
 *	execution functions
 *
 *	Copyright (c) 2010, PostgreSQL Global Development Group
 *	$PostgreSQL: pgsql/contrib/pg_upgrade/exec.c,v 1.7 2010/07/03 16:33:14 momjian Exp $
 */

#include "pg_upgrade.h"

#include <fcntl.h>
#include <grp.h>


static void checkBinDir(migratorContext *ctx, ClusterInfo *cluster);
static int check_exec(migratorContext *ctx, const char *dir, const char *cmdName);
static const char *validate_exec(const char *path);
static int	check_data_dir(migratorContext *ctx, const char *pg_data);


/*
 * exec_prog()
 *
 *	Formats a command from the given argument list and executes that
 *	command.  If the command executes, exec_prog() returns 1 otherwise
 *	exec_prog() logs an error message and returns 0.
 *
 *	If throw_error is TRUE, this function will throw a PG_FATAL error
 *	instead of returning should an error occur.
 */
int
exec_prog(migratorContext *ctx, bool throw_error, const char *fmt,...)
{
	va_list		args;
	int			result;
	char		cmd[MAXPGPATH];

	va_start(args, fmt);
	vsnprintf(cmd, MAXPGPATH, fmt, args);
	va_end(args);

	pg_log(ctx, PG_INFO, "%s\n", cmd);

	result = system(cmd);

	if (result != 0)
	{
		pg_log(ctx, throw_error ? PG_FATAL : PG_INFO,
			   "\nThere were problems executing %s\n", cmd);
		return 1;
	}

	return 0;
}


/*
 * verify_directories()
 *
 * does all the hectic work of verifying directories and executables
 * of old and new server.
 *
 * NOTE: May update the values of all parameters
 */
void
verify_directories(migratorContext *ctx)
{
	prep_status(ctx, "Checking old data directory (%s)", ctx->old.pgdata);
	if (check_data_dir(ctx, ctx->old.pgdata) != 0)
		pg_log(ctx, PG_FATAL, "Failed\n");
	checkBinDir(ctx, &ctx->old);
	check_ok(ctx);

	prep_status(ctx, "Checking new data directory (%s)", ctx->new.pgdata);
	if (check_data_dir(ctx, ctx->new.pgdata) != 0)
		pg_log(ctx, PG_FATAL, "Failed\n");
	checkBinDir(ctx, &ctx->new);
	check_ok(ctx);
}


/*
 * checkBinDir()
 *
 *	This function searches for the executables that we expect to find
 *	in the binaries directory.	If we find that a required executable
 *	is missing (or secured against us), we display an error message and
 *	exit().
 */
static void
checkBinDir(migratorContext *ctx, ClusterInfo *cluster)
{
	check_exec(ctx, cluster->bindir, "postgres");
	check_exec(ctx, cluster->bindir, "psql");
	check_exec(ctx, cluster->bindir, "pg_ctl");
	check_exec(ctx, cluster->bindir, "pg_dumpall");
}


/*
 * is_server_running()
 *
 * checks whether postmaster on the given data directory is running or not.
 * The check is performed by looking for the existence of postmaster.pid file.
 */
bool
is_server_running(migratorContext *ctx, const char *datadir)
{
	char		path[MAXPGPATH];
	int			fd;

	snprintf(path, sizeof(path), "%s/postmaster.pid", datadir);

	if ((fd = open(path, O_RDONLY, 0)) < 0)
	{
		if (errno != ENOENT)
			pg_log(ctx, PG_FATAL, "\ncould not open file \"%s\" for reading\n",
				   path);

		return false;
	}

	close(fd);
	return true;
}


/*
 * check_exec()
 *
 *	Checks whether either of the two command names (cmdName and alternative)
 *	appears to be an executable (in the given directory).  If dir/cmdName is
 *	an executable, this function returns 1. If dir/alternative is an
 *	executable, this function returns 2.  If neither of the given names is
 *	a valid executable, this function returns 0 to indicated failure.
 */
static int
check_exec(migratorContext *ctx, const char *dir, const char *cmdName)
{
	char		path[MAXPGPATH];
	const char *errMsg;

	snprintf(path, sizeof(path), "%s/%s", dir, cmdName);

	if ((errMsg = validate_exec(path)) == NULL)
		return 1;				/* 1 -> first alternative OK */
	else
		pg_log(ctx, PG_FATAL, "check for %s failed - %s\n", cmdName, errMsg);

	return 0;					/* 0 -> neither alternative is acceptable */
}


/*
 * validate_exec()
 *
 * validate "path" as an executable file
 * returns 0 if the file is found and no error is encountered.
 *		  -1 if the regular file "path" does not exist or cannot be executed.
 *		  -2 if the file is otherwise valid but cannot be read.
 */
static const char *
validate_exec(const char *path)
{
	struct stat buf;

#ifndef WIN32
	uid_t		euid;
	struct group *gp;
	struct passwd *pwp;
	int			in_grp = 0;
#else
	char		path_exe[MAXPGPATH + sizeof(EXE_EXT) - 1];
#endif

#ifdef WIN32
	/* Win32 requires a .exe suffix for stat() */

	if (strlen(path) >= strlen(EXE_EXT) &&
		pg_strcasecmp(path + strlen(path) - strlen(EXE_EXT), EXE_EXT) != 0)
	{
		strcpy(path_exe, path);
		strcat(path_exe, EXE_EXT);
		path = path_exe;
	}
#endif

	/*
	 * Ensure that the file exists and is a regular file.
	 */
	if (stat(path, &buf) < 0)
		return getErrorText(errno);

	if ((buf.st_mode & S_IFMT) != S_IFREG)
		return "not an executable file";

	/*
	 * Ensure that we are using an authorized executable.
	 */

	/*
	 * Ensure that the file is both executable and readable (required for
	 * dynamic loading).
	 */
#ifndef WIN32
	euid = geteuid();

	/* If owned by us, just check owner bits */
	if (euid == buf.st_uid)
	{
		if ((buf.st_mode & S_IRUSR) == 0)
			return "can't read file (permission denied)";
		if ((buf.st_mode & S_IXUSR) == 0)
			return "can't execute (permission denied)";
		return NULL;
	}

	/* OK, check group bits */
	pwp = getpwuid(euid);		/* not thread-safe */

	if (pwp)
	{
		if (pwp->pw_gid == buf.st_gid)	/* my primary group? */
			++in_grp;
		else if (pwp->pw_name &&
				 (gp = getgrgid(buf.st_gid)) != NULL &&
				  /* not thread-safe */ gp->gr_mem != NULL)
		{
			/* try list of member groups */
			int			i;

			for (i = 0; gp->gr_mem[i]; ++i)
			{
				if (!strcmp(gp->gr_mem[i], pwp->pw_name))
				{
					++in_grp;
					break;
				}
			}
		}

		if (in_grp)
		{
			if ((buf.st_mode & S_IRGRP) == 0)
				return "can't read file (permission denied)";
			if ((buf.st_mode & S_IXGRP) == 0)
				return "can't execute (permission denied)";
			return NULL;
		}
	}

	/* Check "other" bits */
	if ((buf.st_mode & S_IROTH) == 0)
		return "can't read file (permission denied)";
	if ((buf.st_mode & S_IXOTH) == 0)
		return "can't execute (permission denied)";
	return NULL;
#else
	if ((buf.st_mode & S_IRUSR) == 0)
		return "can't read file (permission denied)";
	if ((buf.st_mode & S_IXUSR) == 0)
		return "can't execute (permission denied)";
	return NULL;
#endif
}


/*
 * check_data_dir()
 *
 *	This function validates the given cluster directory - we search for a
 *	small set of subdirectories that we expect to find in a valid $PGDATA
 *	directory.	If any of the subdirectories are missing (or secured against
 *	us) we display an error message and exit()
 *
 */
static int
check_data_dir(migratorContext *ctx, const char *pg_data)
{
	char		subDirName[MAXPGPATH];
	const char *requiredSubdirs[] = {"base", "global", "pg_clog",
		"pg_multixact", "pg_subtrans",
	"pg_tblspc", "pg_twophase", "pg_xlog"};
	bool		fail = false;
	int			subdirnum;

	for (subdirnum = 0; subdirnum < sizeof(requiredSubdirs) / sizeof(requiredSubdirs[0]); ++subdirnum)
	{
		struct stat statBuf;

		snprintf(subDirName, sizeof(subDirName), "%s/%s", pg_data,
				 requiredSubdirs[subdirnum]);

		if ((stat(subDirName, &statBuf)) != 0)
		{
			report_status(ctx, PG_WARNING, "check for %s warning:  %s",
						  requiredSubdirs[subdirnum], getErrorText(errno));
			fail = true;
		}
		else
		{
			if (!S_ISDIR(statBuf.st_mode))
			{
				report_status(ctx, PG_WARNING, "%s is not a directory",
							  requiredSubdirs[subdirnum]);
				fail = true;
			}
		}
	}

	return (fail) ? -1 : 0;
}