1 /*
2  * psql - the PostgreSQL interactive terminal
3  *
4  * Copyright (c) 2000-2021, PostgreSQL Global Development Group
5  *
6  * src/bin/psql/large_obj.c
7  */
8 #include "postgres_fe.h"
9 
10 #include "common.h"
11 #include "common/logging.h"
12 #include "fe_utils/cancel.h"
13 #include "large_obj.h"
14 #include "settings.h"
15 
16 static void print_lo_result(const char *fmt,...) pg_attribute_printf(1, 2);
17 
18 static void
print_lo_result(const char * fmt,...)19 print_lo_result(const char *fmt,...)
20 {
21 	va_list		ap;
22 
23 	if (!pset.quiet)
24 	{
25 		if (pset.popt.topt.format == PRINT_HTML)
26 			fputs("<p>", pset.queryFout);
27 
28 		va_start(ap, fmt);
29 		vfprintf(pset.queryFout, fmt, ap);
30 		va_end(ap);
31 
32 		if (pset.popt.topt.format == PRINT_HTML)
33 			fputs("</p>\n", pset.queryFout);
34 		else
35 			fputs("\n", pset.queryFout);
36 	}
37 
38 	if (pset.logfile)
39 	{
40 		va_start(ap, fmt);
41 		vfprintf(pset.logfile, fmt, ap);
42 		va_end(ap);
43 		fputs("\n", pset.logfile);
44 	}
45 }
46 
47 
48 /*
49  * Prepare to do a large-object operation.  We *must* be inside a transaction
50  * block for all these operations, so start one if needed.
51  *
52  * Returns true if okay, false if failed.  *own_transaction is set to indicate
53  * if we started our own transaction or not.
54  */
55 static bool
start_lo_xact(const char * operation,bool * own_transaction)56 start_lo_xact(const char *operation, bool *own_transaction)
57 {
58 	PGTransactionStatusType tstatus;
59 	PGresult   *res;
60 
61 	*own_transaction = false;
62 
63 	if (!pset.db)
64 	{
65 		pg_log_error("%s: not connected to a database", operation);
66 		return false;
67 	}
68 
69 	tstatus = PQtransactionStatus(pset.db);
70 
71 	switch (tstatus)
72 	{
73 		case PQTRANS_IDLE:
74 			/* need to start our own xact */
75 			if (!(res = PSQLexec("BEGIN")))
76 				return false;
77 			PQclear(res);
78 			*own_transaction = true;
79 			break;
80 		case PQTRANS_INTRANS:
81 			/* use the existing xact */
82 			break;
83 		case PQTRANS_INERROR:
84 			pg_log_error("%s: current transaction is aborted", operation);
85 			return false;
86 		default:
87 			pg_log_error("%s: unknown transaction status", operation);
88 			return false;
89 	}
90 
91 	return true;
92 }
93 
94 /*
95  * Clean up after a successful LO operation
96  */
97 static bool
finish_lo_xact(const char * operation,bool own_transaction)98 finish_lo_xact(const char *operation, bool own_transaction)
99 {
100 	PGresult   *res;
101 
102 	if (own_transaction && pset.autocommit)
103 	{
104 		/* close out our own xact */
105 		if (!(res = PSQLexec("COMMIT")))
106 		{
107 			res = PSQLexec("ROLLBACK");
108 			PQclear(res);
109 			return false;
110 		}
111 		PQclear(res);
112 	}
113 
114 	return true;
115 }
116 
117 /*
118  * Clean up after a failed LO operation
119  */
120 static bool
fail_lo_xact(const char * operation,bool own_transaction)121 fail_lo_xact(const char *operation, bool own_transaction)
122 {
123 	PGresult   *res;
124 
125 	if (own_transaction && pset.autocommit)
126 	{
127 		/* close out our own xact */
128 		res = PSQLexec("ROLLBACK");
129 		PQclear(res);
130 	}
131 
132 	return false;				/* always */
133 }
134 
135 
136 /*
137  * do_lo_export()
138  *
139  * Write a large object to a file
140  */
141 bool
do_lo_export(const char * loid_arg,const char * filename_arg)142 do_lo_export(const char *loid_arg, const char *filename_arg)
143 {
144 	int			status;
145 	bool		own_transaction;
146 
147 	if (!start_lo_xact("\\lo_export", &own_transaction))
148 		return false;
149 
150 	SetCancelConn(NULL);
151 	status = lo_export(pset.db, atooid(loid_arg), filename_arg);
152 	ResetCancelConn();
153 
154 	/* of course this status is documented nowhere :( */
155 	if (status != 1)
156 	{
157 		pg_log_info("%s", PQerrorMessage(pset.db));
158 		return fail_lo_xact("\\lo_export", own_transaction);
159 	}
160 
161 	if (!finish_lo_xact("\\lo_export", own_transaction))
162 		return false;
163 
164 	print_lo_result("lo_export");
165 
166 	return true;
167 }
168 
169 
170 /*
171  * do_lo_import()
172  *
173  * Copy large object from file to database
174  */
175 bool
do_lo_import(const char * filename_arg,const char * comment_arg)176 do_lo_import(const char *filename_arg, const char *comment_arg)
177 {
178 	PGresult   *res;
179 	Oid			loid;
180 	char		oidbuf[32];
181 	bool		own_transaction;
182 
183 	if (!start_lo_xact("\\lo_import", &own_transaction))
184 		return false;
185 
186 	SetCancelConn(NULL);
187 	loid = lo_import(pset.db, filename_arg);
188 	ResetCancelConn();
189 
190 	if (loid == InvalidOid)
191 	{
192 		pg_log_info("%s", PQerrorMessage(pset.db));
193 		return fail_lo_xact("\\lo_import", own_transaction);
194 	}
195 
196 	/* insert description if given */
197 	if (comment_arg)
198 	{
199 		char	   *cmdbuf;
200 		char	   *bufptr;
201 		size_t		slen = strlen(comment_arg);
202 
203 		cmdbuf = pg_malloc_extended(slen * 2 + 256, MCXT_ALLOC_NO_OOM);
204 		if (!cmdbuf)
205 			return fail_lo_xact("\\lo_import", own_transaction);
206 		sprintf(cmdbuf, "COMMENT ON LARGE OBJECT %u IS '", loid);
207 		bufptr = cmdbuf + strlen(cmdbuf);
208 		bufptr += PQescapeStringConn(pset.db, bufptr, comment_arg, slen, NULL);
209 		strcpy(bufptr, "'");
210 
211 		if (!(res = PSQLexec(cmdbuf)))
212 		{
213 			free(cmdbuf);
214 			return fail_lo_xact("\\lo_import", own_transaction);
215 		}
216 
217 		PQclear(res);
218 		free(cmdbuf);
219 	}
220 
221 	if (!finish_lo_xact("\\lo_import", own_transaction))
222 		return false;
223 
224 	print_lo_result("lo_import %u", loid);
225 
226 	sprintf(oidbuf, "%u", loid);
227 	SetVariable(pset.vars, "LASTOID", oidbuf);
228 
229 	return true;
230 }
231 
232 
233 /*
234  * do_lo_unlink()
235  *
236  * removes a large object out of the database
237  */
238 bool
do_lo_unlink(const char * loid_arg)239 do_lo_unlink(const char *loid_arg)
240 {
241 	int			status;
242 	Oid			loid = atooid(loid_arg);
243 	bool		own_transaction;
244 
245 	if (!start_lo_xact("\\lo_unlink", &own_transaction))
246 		return false;
247 
248 	SetCancelConn(NULL);
249 	status = lo_unlink(pset.db, loid);
250 	ResetCancelConn();
251 
252 	if (status == -1)
253 	{
254 		pg_log_info("%s", PQerrorMessage(pset.db));
255 		return fail_lo_xact("\\lo_unlink", own_transaction);
256 	}
257 
258 	if (!finish_lo_xact("\\lo_unlink", own_transaction))
259 		return false;
260 
261 	print_lo_result("lo_unlink %u", loid);
262 
263 	return true;
264 }
265 
266 
267 
268 /*
269  * do_lo_list()
270  *
271  * Show all large objects in database with comments
272  */
273 bool
do_lo_list(void)274 do_lo_list(void)
275 {
276 	PGresult   *res;
277 	char		buf[1024];
278 	printQueryOpt myopt = pset.popt;
279 
280 	if (pset.sversion >= 90000)
281 	{
282 		snprintf(buf, sizeof(buf),
283 				 "SELECT oid as \"%s\",\n"
284 				 "  pg_catalog.pg_get_userbyid(lomowner) as \"%s\",\n"
285 				 "  pg_catalog.obj_description(oid, 'pg_largeobject') as \"%s\"\n"
286 				 "  FROM pg_catalog.pg_largeobject_metadata "
287 				 "  ORDER BY oid",
288 				 gettext_noop("ID"),
289 				 gettext_noop("Owner"),
290 				 gettext_noop("Description"));
291 	}
292 	else
293 	{
294 		snprintf(buf, sizeof(buf),
295 				 "SELECT loid as \"%s\",\n"
296 				 "  pg_catalog.obj_description(loid, 'pg_largeobject') as \"%s\"\n"
297 				 "FROM (SELECT DISTINCT loid FROM pg_catalog.pg_largeobject) x\n"
298 				 "ORDER BY 1",
299 				 gettext_noop("ID"),
300 				 gettext_noop("Description"));
301 	}
302 
303 	res = PSQLexec(buf);
304 	if (!res)
305 		return false;
306 
307 	myopt.topt.tuples_only = false;
308 	myopt.nullPrint = NULL;
309 	myopt.title = _("Large objects");
310 	myopt.translate_header = true;
311 
312 	printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
313 
314 	PQclear(res);
315 	return true;
316 }
317