1 /* -*-pgsql-c-*- */
2 /*
3  * $Header$
4  *
5  * pgpool-recovery: exec online recovery script from SELECT statement.
6  *
7  * Copyright (c) 2003-2019	PgPool Global Development Group
8  *
9  * Permission to use, copy, modify, and distribute this software and
10  * its documentation for any purpose and without fee is hereby
11  * granted, provided that the above copyright notice appear in all
12  * copies and that both that copyright notice and this permission
13  * notice appear in supporting documentation, and that the name of the
14  * author not be used in advertising or publicity pertaining to
15  * distribution of the software without specific, written prior
16  * permission. The author makes no representations about the
17  * suitability of this software for any purpose.  It is provided "as
18  * is" without express or implied warranty.
19  */
20 
21 #include <sys/types.h>
22 #include <sys/stat.h>
23 #include <unistd.h>
24 #include "postgres.h"
25 #include "fmgr.h"
26 #include "miscadmin.h"
27 #include "executor/spi.h"
28 #include "funcapi.h"
29 #include "catalog/namespace.h"
30 #include "catalog/pg_proc.h"
31 #include "utils/syscache.h"
32 #include "utils/builtins.h"		/* PostgreSQL 8.4 needs this for textout */
33 #include "utils/guc.h"
34 #if defined(PG_VERSION_NUM) && (PG_VERSION_NUM >= 90300)
35 #include "access/htup_details.h"		/* PostgreSQL 9.3 or later needs this */
36 #endif
37 
38 #define REMOTE_START_FILE "pgpool_remote_start"
39 
40 #include <stdlib.h>
41 
42 #ifdef PG_MODULE_MAGIC
43 PG_MODULE_MAGIC;
44 #endif
45 
46 PG_FUNCTION_INFO_V1(pgpool_recovery);
47 PG_FUNCTION_INFO_V1(pgpool_remote_start);
48 PG_FUNCTION_INFO_V1(pgpool_pgctl);
49 PG_FUNCTION_INFO_V1(pgpool_switch_xlog);
50 
51 extern Datum pgpool_recovery(PG_FUNCTION_ARGS);
52 extern Datum pgpool_remote_start(PG_FUNCTION_ARGS);
53 extern Datum pgpool_pgctl(PG_FUNCTION_ARGS);
54 extern Datum pgpool_switch_xlog(PG_FUNCTION_ARGS);
55 
56 static char recovery_script[1024];
57 static char command_text[1024];
58 
59 static Oid get_function_oid(const char *funcname, const char *argtype, const char *nspname);
60 char       *Log_line_prefix = NULL;
61 
62 Datum
pgpool_recovery(PG_FUNCTION_ARGS)63 pgpool_recovery(PG_FUNCTION_ARGS)
64 {
65 	int r;
66 	char *script = DatumGetCString(DirectFunctionCall1(textout,
67 													   PointerGetDatum(PG_GETARG_TEXT_P(0))));
68 
69 	char *remote_host = DatumGetCString(DirectFunctionCall1(textout,
70 															PointerGetDatum(PG_GETARG_TEXT_P(1))));
71 	char *remote_data_directory = DatumGetCString(DirectFunctionCall1(textout,
72 																	  PointerGetDatum(PG_GETARG_TEXT_P(2))));
73 
74 	if (!superuser())
75 #ifdef ERRCODE_INSUFFICIENT_PRIVILEGE
76 		ereport(ERROR,
77 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
78 				 (errmsg("must be superuser to use pgpool_recovery function"))));
79 #else
80 		elog(ERROR, "must be superuser to use pgpool_recovery function");
81 #endif
82 
83 	if (PG_NARGS() >= 4)		/* post pgpool-II 3.4 */
84 	{
85 		char *remote_port = DatumGetCString(DirectFunctionCall1(textout,
86 														  PointerGetDatum(PG_GETARG_TEXT_P(3))));
87 		snprintf(recovery_script, sizeof(recovery_script), "\"%s/%s\" \"%s\" \"%s\" \"%s\" \"%s\"",
88 				 DataDir, script, DataDir, remote_host,
89 				 remote_data_directory, remote_port);
90 	}
91 	else
92 	{
93 		snprintf(recovery_script, sizeof(recovery_script), "\"%s/%s\" \"%s\" \"%s\" \"%s\"",
94 				 DataDir, script, DataDir, remote_host,
95 				 remote_data_directory);
96 	}
97 
98 	elog(DEBUG1, "recovery_script: %s", recovery_script);
99 	r = system(recovery_script);
100 
101 	if (r != 0)
102 	{
103 		elog(ERROR, "pgpool_recovery failed");
104 	}
105 
106 	PG_RETURN_BOOL(true);
107 }
108 
109 Datum
pgpool_remote_start(PG_FUNCTION_ARGS)110 pgpool_remote_start(PG_FUNCTION_ARGS)
111 {
112 	int r;
113 	char *remote_host = DatumGetCString(DirectFunctionCall1(textout,
114 															PointerGetDatum(PG_GETARG_TEXT_P(0))));
115 	char *remote_data_directory = DatumGetCString(DirectFunctionCall1(textout,
116 																	  PointerGetDatum(PG_GETARG_TEXT_P(1))));
117 	if (!superuser())
118 #ifdef ERRCODE_INSUFFICIENT_PRIVILEGE
119 		ereport(ERROR,
120 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
121 				 (errmsg("must be superuser to use pgpool_remote_start function"))));
122 #else
123 		elog(ERROR, "must be superuser to use pgpool_remote_start function");
124 #endif
125 
126 	snprintf(recovery_script, sizeof(recovery_script),
127 			 "%s/%s %s %s", DataDir, REMOTE_START_FILE,
128 			 remote_host, remote_data_directory);
129 	elog(DEBUG1, "recovery_script: %s", recovery_script);
130 	r = system(recovery_script);
131 
132 	if (r != 0)
133 	{
134 		elog(ERROR, "pgpool_remote_start failed");
135 	}
136 
137 	PG_RETURN_BOOL(true);
138 }
139 
140 Datum
pgpool_pgctl(PG_FUNCTION_ARGS)141 pgpool_pgctl(PG_FUNCTION_ARGS)
142 {
143 	int r;
144 	char *action = DatumGetCString(DirectFunctionCall1(textout,
145 	                                                   PointerGetDatum(PG_GETARG_TEXT_P(0))));
146 	char *stop_mode = DatumGetCString(DirectFunctionCall1(textout,
147 	                                                   PointerGetDatum(PG_GETARG_TEXT_P(1))));
148 	char *pg_ctl;
149 	char *data_directory;
150 
151 	if (!superuser())
152 #ifdef ERRCODE_INSUFFICIENT_PRIVILEGE
153 		ereport(ERROR,
154 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
155 				 (errmsg("must be superuser to use pgpool_pgctl function"))));
156 #else
157 		elog(ERROR, "must be superuser to use pgpool_pgctl function");
158 #endif
159 
160 #if defined(PG_VERSION_NUM) && (PG_VERSION_NUM >= 90600)
161 	pg_ctl = GetConfigOptionByName("pgpool.pg_ctl", NULL,false);
162 	data_directory = GetConfigOptionByName("data_directory", NULL,false);
163 #else
164 	pg_ctl = GetConfigOptionByName("pgpool.pg_ctl", NULL);
165 	data_directory = GetConfigOptionByName("data_directory", NULL);
166 #endif
167 
168 	if (strcmp(stop_mode, "") != 0)
169 	{
170 		snprintf(command_text, sizeof(command_text),
171 				 "%s %s -D %s -m %s 2>/dev/null 1>/dev/null < /dev/null &",
172 				 pg_ctl, action, data_directory, stop_mode);
173 
174 	} else {
175 		snprintf(command_text, sizeof(command_text),
176 				 "%s %s -D %s 2>/dev/null 1>/dev/null < /dev/null &",
177 				 pg_ctl, action, data_directory);
178 	}
179 
180 	elog(DEBUG1, "command_text: %s", command_text);
181 	r = system(command_text);
182 
183 	if (strcmp(action, "reload") == 0 && r != 0)
184 	{
185 		elog(ERROR, "pgpool_pgctl failed");
186 	}
187 
188 	PG_RETURN_BOOL(true);
189 }
190 
191 /*
192  * pgpool_switch_log is the same as pg_switch_xlog except that
193  * it wait till archiving is completed.
194  * We call xlog functions with the oid to avoid a compile error
195  * at old PostgreSQL.
196  */
197 Datum
pgpool_switch_xlog(PG_FUNCTION_ARGS)198 pgpool_switch_xlog(PG_FUNCTION_ARGS)
199 {
200 	char *archive_dir;
201 	char *filename;
202 	char path[MAXPGPATH];
203 	struct stat fst;
204 	Datum location;
205 	text *filename_t;
206 	text *result;
207 	Oid switch_xlog_oid;
208 	Oid xlogfile_name_oid;
209 
210 #if !defined(PG_VERSION_NUM) || (PG_VERSION_NUM < 90400)
211 	char* pg_xlogfile_name_arg_type = "text";
212 #else
213 	/*
214 	 * The argument data type of PG's pg_xlogfile_name() function
215 	 * has been changed from text to pg_lsn since PostgreSQL 9.4
216 	 */
217 	char* pg_xlogfile_name_arg_type = "pg_lsn";
218 #endif
219 
220 
221 	archive_dir = DatumGetCString(DirectFunctionCall1(textout,
222 													  PointerGetDatum(PG_GETARG_TEXT_P(0))));
223 
224 	if (stat(archive_dir, &fst) < 0)
225 #ifdef ERRCODE_INSUFFICIENT_PRIVILEGE
226 		ereport(ERROR,
227                 (errcode_for_file_access(),
228                  errmsg("could not stat file \"%s\": %m", archive_dir)));
229 #else
230 		elog(ERROR, "could not stat file \"%s\"", archive_dir);
231 #endif
232 
233 	switch_xlog_oid = get_function_oid("pg_switch_xlog", NULL, "pg_catalog");
234 	xlogfile_name_oid = get_function_oid("pg_xlogfile_name", pg_xlogfile_name_arg_type, "pg_catalog");
235 
236 	if (!switch_xlog_oid || !xlogfile_name_oid)
237 	{
238 		/* probably PostgreSQL is 10 or greater */
239 		switch_xlog_oid = get_function_oid("pg_switch_wal", NULL, "pg_catalog");
240 		xlogfile_name_oid = get_function_oid("pg_walfile_name", pg_xlogfile_name_arg_type, "pg_catalog");
241 
242 		if (!switch_xlog_oid || !xlogfile_name_oid)
243 			elog(ERROR, "cannot find xlog functions");
244 	}
245 
246 	location = OidFunctionCall1(switch_xlog_oid, PointerGetDatum(NULL));
247 	filename_t = DatumGetTextP(OidFunctionCall1(xlogfile_name_oid, location));
248 	filename = DatumGetCString(DirectFunctionCall1(textout,
249 												   PointerGetDatum(filename_t)));
250 
251 	snprintf(path, MAXPGPATH, "%s/%s", archive_dir, filename);
252 	elog(LOG, "pgpool_switch_xlog: waiting for \"%s\"", path);
253 
254 	while(stat(path, &fst) != 0 || fst.st_size == 0 ||
255 		  fst.st_size % (1024 * 1024) != 0)
256 	{
257 		CHECK_FOR_INTERRUPTS();
258 		sleep(1);
259 	}
260 
261     result = DatumGetTextP(DirectFunctionCall1(textin,
262                                                CStringGetDatum(path)));
263 
264 	PG_RETURN_TEXT_P(result);
265 }
266 
267 static Oid
get_function_oid(const char * funcname,const char * argtype,const char * nspname)268 get_function_oid(const char *funcname, const char *argtype, const char *nspname)
269 {
270 #ifndef PROCNAMENSP
271 	Oid typid;
272 	Oid nspid;
273 	Oid funcid;
274 	Oid oids[1];
275 	oidvector *oid_v;
276 	HeapTuple tup;
277 
278 	if (argtype)
279 	{
280 		typid = TypenameGetTypid(argtype);
281 		elog(DEBUG1, "get_function_oid: %s typid: %d", argtype, typid);
282 		oids[0] = typid;
283 		oid_v = buildoidvector(oids, 1);
284 	}
285 	else
286 	{
287 		oid_v = buildoidvector(NULL, 0);
288 	}
289 
290 #if !defined(PG_VERSION_NUM) || (PG_VERSION_NUM < 90300)
291 	nspid = LookupExplicitNamespace(nspname);
292 #else
293 	/* LookupExplicitNamespace() of PostgreSQL 9.3 or later, has third
294 	 * argument "missing_ok" which suppresses ERROR exception, but
295 	 * returns invlaid_oid. See include/catalog/namespace.h */
296 	nspid = LookupExplicitNamespace(nspname, false);
297 #endif
298 
299 	elog(DEBUG1, "get_function_oid: oid of \"%s\": %d", nspname, nspid);
300 
301 
302 	tup = SearchSysCache(PROCNAMEARGSNSP,
303 						 PointerGetDatum(funcname),
304 						 PointerGetDatum(oid_v),
305 						 ObjectIdGetDatum(nspid),
306 						 0);
307 
308 	if (HeapTupleIsValid(tup))
309 	{
310 #if defined(PG_VERSION_NUM) && (PG_VERSION_NUM >= 120000)
311 		Form_pg_proc proctup = (Form_pg_proc) GETSTRUCT(tup);
312 		funcid = proctup->oid;
313 #else
314 		funcid = HeapTupleGetOid(tup);
315 #endif
316 		elog(DEBUG1, "get_function_oid: oid of \"%s\": %d", funcname, funcid);
317 		ReleaseSysCache(tup);
318 		return funcid;
319 	}
320 #endif
321 	return 0;
322 }
323 
324