exports.c 13.7 KB
Newer Older
1
/*  This file is part of "reprepro"
2
 *  Copyright (C) 2005,2007,2008,2009,2016 Bernhard R. Link
3
 *  This program is free software; you can redistribute it and/or modify
4
 *  it under the terms of the GNU General Public License version 2 as
Bernhard Link's avatar
Bernhard Link committed
5
 *  published by the Free Software Foundation.
6 7 8 9 10 11 12 13
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
Bernhard Link's avatar
Bernhard Link committed
14
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02111-1301  USA
15 16 17 18 19 20
 */
#include <config.h>

#include <errno.h>
#include <assert.h>
#include <stdio.h>
21 22
#include <stdlib.h>
#include <unistd.h>
23
#include <sys/types.h>
24
#include <sys/wait.h>
25 26 27 28 29 30 31
#include <sys/stat.h>
#include <string.h>
#include <ctype.h>

#include "error.h"
#include "mprintf.h"
#include "strlist.h"
32
#include "names.h"
33
#include "dirs.h"
34
#include "database.h"
Bernhard Link's avatar
Bernhard Link committed
35
#include "target.h"
36
#include "exports.h"
Bernhard Link's avatar
Bernhard Link committed
37
#include "configparser.h"
38
#include "filecntl.h"
39
#include "hooks.h"
40
#include "package.h"
41

42
static const char *exportdescription(const struct exportmode *mode, char *buffer, size_t buffersize) {
43 44 45 46 47 48 49
	char *result = buffer;
	enum indexcompression ic;
	static const char* compression_names[ic_count] = {
		"uncompressed"
		,"gzipped"
#ifdef HAVE_LIBBZ2
		,"bzip2ed"
50 51 52
#endif
#ifdef HAVE_LIBLZMA
		,"xzed"
53 54
#endif
	};
55 56
	bool needcomma = false,
	     needellipsis = false;
57

58
	assert (buffersize > 50);
59 60
	*buffer++ = ' '; buffersize--;
	*buffer++ = '('; buffersize--;
61 62
	for (ic = ic_first ; ic < ic_count ; ic++) {
		if ((mode->compressions & IC_FLAG(ic)) != 0) {
63
			size_t l = strlen(compression_names[ic]);
64 65
			assert (buffersize > l+3);
			if (needcomma) {
66 67 68 69
				*buffer++ = ','; buffersize--;
			}
			memcpy(buffer, compression_names[ic], l);
			buffer += l; buffersize -= l;
70
			needcomma = true;
71 72
		}
	}
73
	/* should be long enough for the previous things in all cases */
74 75
	assert (buffersize > 10);
	if (mode->hooks.count > 0) {
76
		int i;
77

78
		if (needcomma) {
79 80 81 82
			*buffer++ = ','; buffersize--;
		}
		strcpy(buffer, "script: ");
		buffer += 8; buffersize -= 8;
83 84
		needcomma = false;

85
		for (i = 0 ; i < mode->hooks.count ; i++) {
86 87 88
			const char *hook = dirs_basename(mode->hooks.values[i]);
			size_t l = strlen(hook);

89
			if (buffersize < 6) {
90 91 92
				needellipsis = true;
				break;
			}
93
			if (needcomma) {
94 95 96
				*buffer++ = ','; buffersize--;
			}

97
			if (l > buffersize - 5) {
98 99 100 101 102 103 104 105
				memcpy(buffer, hook, buffersize-5);
				buffer += (buffersize-5);
				buffersize -= (buffersize-5);
				needellipsis = true;
				break;
			} else {
				memcpy(buffer, hook, l);
				buffer += l; buffersize -= l;
106
				assert (buffersize >= 2);
107 108
			}
			needcomma = true;
109 110
		}
	}
111
	if (needellipsis) {
112
		/* moveing backward here is easier than checking above */
113
		if (buffersize < 5) {
114 115 116 117 118 119 120
			buffer -= (5 - buffersize);
			buffersize = 5;
		}
		*buffer++ = '.'; buffersize--;
		*buffer++ = '.'; buffersize--;
		*buffer++ = '.'; buffersize--;
	}
121
	assert (buffersize >= 2);
122 123 124 125 126
	*buffer++ = ')'; buffersize--;
	*buffer = '\0';
	return result;
}

127
retvalue exportmode_init(/*@out@*/struct exportmode *mode, bool uncompressed, /*@null@*/const char *release, const char *indexfile) {
128
	strlist_init(&mode->hooks);
129 130
	mode->compressions = IC_FLAG(ic_gzip) | (uncompressed
			? IC_FLAG(ic_uncompressed) : 0);
Bernhard Link's avatar
Bernhard Link committed
131
	mode->filename = strdup(indexfile);
132
	if (FAILEDTOALLOC(mode->filename))
Bernhard Link's avatar
Bernhard Link committed
133
		return RET_ERROR_OOM;
134
	if (release == NULL)
Bernhard Link's avatar
Bernhard Link committed
135 136 137
		mode->release = NULL;
	else {
		mode->release = strdup(release);
138
		if (FAILEDTOALLOC(mode->release))
139
			return RET_ERROR_OOM;
Bernhard Link's avatar
Bernhard Link committed
140 141 142 143 144
	}
	return RET_OK;
}

// TODO: check for scripts in confdir early...
145
retvalue exportmode_set(struct exportmode *mode, struct configiterator *iter) {
Bernhard Link's avatar
Bernhard Link committed
146 147 148 149
	retvalue r;
	char *word;

	r = config_getword(iter, &word);
150
	if (RET_WAS_ERROR(r))
Bernhard Link's avatar
Bernhard Link committed
151
		return r;
152
	if (r == RET_NOTHING) {
Bernhard Link's avatar
Bernhard Link committed
153
		fprintf(stderr,
154
"Error parsing %s, line %u, column %u: Unexpected end of field!\n"
Bernhard Link's avatar
Bernhard Link committed
155 156 157 158 159
"Filename to use for index files (Packages, Sources, ...) missing.\n",
			config_filename(iter),
			config_markerline(iter), config_markercolumn(iter));
		return RET_ERROR_MISSING;
	}
160
	assert (word[0] != '\0');
161

162
	if (word[0] == '.') {
163 164 165 166 167 168 169 170
		free(word);
		fprintf(stderr,
"Error parsing %s, line %u, column %u: filename for index files expected!\n",
			config_filename(iter),
			config_markerline(iter), config_markercolumn(iter));
		return RET_ERROR;
	}

Bernhard Link's avatar
Bernhard Link committed
171 172 173 174
	free(mode->filename);
	mode->filename = word;

	r = config_getword(iter, &word);
175
	if (RET_WAS_ERROR(r))
Bernhard Link's avatar
Bernhard Link committed
176
		return r;
177
	if (r == RET_NOTHING)
Bernhard Link's avatar
Bernhard Link committed
178
		word = NULL;
179 180
	if (r != RET_NOTHING && word[0] != '.') {
		assert (word[0] != '\0');
Bernhard Link's avatar
Bernhard Link committed
181 182 183
		free(mode->release);
		mode->release = word;
		r = config_getword(iter, &word);
184
		if (RET_WAS_ERROR(r))
Bernhard Link's avatar
Bernhard Link committed
185 186
			return r;
	}
187
	if (r == RET_NOTHING) {
Bernhard Link's avatar
Bernhard Link committed
188
		fprintf(stderr,
189
"Error parsing %s, line %u, column %u: Unexpected end of field!\n"
Bernhard Link's avatar
Bernhard Link committed
190 191 192 193 194
"Compression identifiers ('.', '.gz' or '.bz2') missing.\n",
			config_filename(iter),
			config_markerline(iter), config_markercolumn(iter));
		return RET_ERROR;
	}
195
	if (word[0] != '.') {
Bernhard Link's avatar
Bernhard Link committed
196 197 198 199 200 201 202 203 204
		fprintf(stderr,
"Error parsing %s, line %u, column %u:\n"
"Compression extension ('.', '.gz' or '.bz2') expected.\n",
			config_filename(iter),
			config_markerline(iter), config_markercolumn(iter));
		free(word);
		return RET_ERROR;
	}
	mode->compressions = 0;
205 206
	while (r != RET_NOTHING && word[0] == '.') {
		if (word[1] == '\0')
Bernhard Link's avatar
Bernhard Link committed
207
			mode->compressions |= IC_FLAG(ic_uncompressed);
208
		else if (word[1] == 'g' && word[2] == 'z' &&
Bernhard Link's avatar
Bernhard Link committed
209 210
				word[3] == '\0')
			mode->compressions |= IC_FLAG(ic_gzip);
211
#ifdef HAVE_LIBBZ2
212
		else if (word[1] == 'b' && word[2] == 'z' && word[3] == '2' &&
Bernhard Link's avatar
Bernhard Link committed
213 214
				word[4] == '\0')
			mode->compressions |= IC_FLAG(ic_bzip2);
215 216 217 218
#endif
#ifdef HAVE_LIBLZMA
		else if (word[1] == 'x' && word[2] == 'z' &&word[3] == '\0')
			mode->compressions |= IC_FLAG(ic_xz);
219
#endif
Bernhard Link's avatar
Bernhard Link committed
220 221 222 223 224 225 226 227 228
		else {
			fprintf(stderr,
"Error parsing %s, line %u, column %u:\n"
"Unsupported compression extension '%s'!\n",
					config_filename(iter),
					config_markerline(iter),
					config_markercolumn(iter),
					word);
			free(word);
229 230
			return RET_ERROR;
		}
Bernhard Link's avatar
Bernhard Link committed
231 232
		free(word);
		r = config_getword(iter, &word);
233
		if (RET_WAS_ERROR(r))
Bernhard Link's avatar
Bernhard Link committed
234 235
			return r;
	}
236 237
	while (r != RET_NOTHING) {
		if (word[0] == '.') {
238 239 240 241 242 243 244 245 246 247
			fprintf(stderr,
"Error parsing %s, line %u, column %u:\n"
"Scripts starting with dot are forbidden to avoid ambiguity ('%s')!\n"
"Try to put all compressions first and then all scripts to avoid this.\n",
					config_filename(iter),
					config_markerline(iter),
					config_markercolumn(iter),
					word);
			free(word);
			return RET_ERROR;
248
		} else {
249
			char *fullfilename = configfile_expandname(word, word);
250
			if (FAILEDTOALLOC(fullfilename))
251
				return RET_ERROR_OOM;
252
			r = strlist_add(&mode->hooks, fullfilename);
253
			if (RET_WAS_ERROR(r))
254
				return r;
255
		}
256
		r = config_getword(iter, &word);
257
		if (RET_WAS_ERROR(r))
258
			return r;
259
	}
260 261 262
	return RET_OK;
}

263 264
static retvalue gotfilename(const char *relname, size_t l, struct release *release) {

265
	if (l > 12 && memcmp(relname+l-12, ".tobedeleted", 12) == 0) {
266 267
		char *filename;

268 269
		filename = strndup(relname, l - 12);
		if (FAILEDTOALLOC(filename))
270
			return RET_ERROR_OOM;
271
		return release_adddel(release, filename);
272

273 274
	} else if (l > 4 && memcmp(relname+(l-4), ".new", 4) == 0) {
		char *filename, *tmpfilename;
275

276 277
		filename = strndup(relname, l - 4);
		if (FAILEDTOALLOC(filename))
278
			return RET_ERROR_OOM;
279 280
		tmpfilename = strndup(relname, l);
		if (FAILEDTOALLOC(tmpfilename)) {
281 282 283
			free(filename);
			return RET_ERROR_OOM;
		}
284 285
		return release_addnew(release, tmpfilename, filename);
	} else if (l > 5 && memcmp(relname + (l-5), ".new.", 5) == 0) {
286
		char *filename, *tmpfilename;
287

288
		filename = strndup(relname, l-5);
289
		if (FAILEDTOALLOC(filename))
290 291
			return RET_ERROR_OOM;
		tmpfilename = strndup(relname, l-1);
292
		if (FAILEDTOALLOC(tmpfilename)) {
293 294 295 296
			free(filename);
			return RET_ERROR_OOM;
		}
		return release_addsilentnew(release, tmpfilename, filename);
297
	} else if (l > 5 && memcmp(relname + (l-5), ".keep", 5) == 0) {
298
		return RET_OK;
299 300 301
	} else {
		char *filename;

302 303
		filename = strndup(relname, l);
		if (FAILEDTOALLOC(filename))
304
			return RET_ERROR_OOM;
305
		return release_addold(release, filename);
306 307 308
	}
}

309
static retvalue callexporthook(/*@null@*/const char *hook, const char *relfilename, const char *mode, struct release *release) {
310
	pid_t f, c;
311
	int status;
312 313 314 315
	int io[2];
	char buffer[1000];
	int already = 0;

316
	if (hook == NULL)
317 318 319
		return RET_NOTHING;

	status = pipe(io);
320
	if (status < 0) {
321
		int e = errno;
322 323
		fprintf(stderr, "Error %d creating pipe: %s!\n",
				e, strerror(e));
324 325 326 327
		return RET_ERRNO(e);
	}

	f = fork();
328
	if (f < 0) {
Bernhard Link's avatar
Bernhard Link committed
329
		int e = errno;
330 331
		(void)close(io[0]);
		(void)close(io[1]);
Bernhard Link's avatar
Bernhard Link committed
332 333 334
		fprintf(stderr, "Error %d while forking for exporthook: %s\n",
				e, strerror(e));
		return RET_ERRNO(e);
335
	}
336
	if (f == 0) {
337
		char *reltmpfilename;
Bernhard Link's avatar
Bernhard Link committed
338
		int e;
339

340
		if (dup2(io[1], 3) < 0) {
341
			e = errno;
Bernhard Link's avatar
Bernhard Link committed
342 343
			fprintf(stderr, "Error %d dup2'ing fd %d to 3: %s\n",
					e, io[1], strerror(e));
344 345
			exit(255);
		}
346
		/* "Doppelt haelt besser": */
347
		if (io[0] != 3)
348
			(void)close(io[0]);
349
		if (io[1] != 3)
350 351
			(void)close(io[1]);
		closefrom(4);
352
		/* backward compatibilty */
353 354
		reltmpfilename = calc_addsuffix(relfilename, "new");
		if (reltmpfilename == NULL) {
355 356
			exit(255);
		}
357
		sethookenvironment(causingfile, NULL, NULL, NULL);
358
		(void)execl(hook, hook, release_dirofdist(release),
359 360
				reltmpfilename, relfilename, mode,
				ENDOFARGUMENTS);
Bernhard Link's avatar
Bernhard Link committed
361 362 363
		e = errno;
		fprintf(stderr, "Error %d while executing '%s': %s\n",
				e, hook, strerror(e));
364 365 366
		exit(255);
	}
	close(io[1]);
367
	markcloseonexec(io[0]);
368

369
	if (verbose > 6)
370
		printf("Called %s '%s' '%s.new' '%s' '%s'\n",
371 372
			hook, release_dirofdist(release),
			relfilename, relfilename, mode);
373
	/* read what comes from the client */
374
	while (true) {
375
		ssize_t r;
376
		int last, j;
377

378 379
		r = read(io[0], buffer + already, 999 - already);
		if (r < 0) {
380
			int e = errno;
381 382
			fprintf(stderr,
"Error %d reading from exporthook: %s!\n",
Bernhard Link's avatar
Bernhard Link committed
383
					e, strerror(e));
384 385 386 387
			break;
		}

		already += r;
388
		if (r == 0) {
389 390 391 392
			buffer[already] = '\0';
			already++;
		}
		last = 0;
393 394
		for (j = 0 ; j < already ; j++) {
			if (buffer[j] == '\n' || buffer[j] == '\0') {
395
				int next = j+1;
396 397
				int e = (j>0)?(j-1):j;
				retvalue ret;
398

399
				while (last < j && xisspace(buffer[last]))
400
						last++;
401
				if (last >= j) {
402 403 404
					last = next;
					continue;
				}
405
				while (xisspace(buffer[e])) {
406
					e--;
407
					assert (e >= last);
408
				}
409

410 411 412
				ret = gotfilename(buffer + last, e - last + 1,
						release);
				if (RET_WAS_ERROR(ret)) {
413 414
					(void)close(io[0]);
					return ret;
415 416 417 418
				}
				last = next;
			}
		}
419 420 421
		if (last > 0) {
			if (already > last)
				memmove(buffer, buffer + last, already - last);
422 423
			already -= last;
		}
424
		if (r == 0)
425 426
			break;
	}
427
	(void)close(io[0]);
428
	do {
429 430
		c = waitpid(f, &status, WUNTRACED);
		if (c < 0) {
Bernhard Link's avatar
Bernhard Link committed
431 432 433 434
			int e = errno;
			fprintf(stderr,
"Error %d while waiting for hook '%s' to finish: %s\n", e, hook, strerror(e));
			return RET_ERRNO(e);
435
		}
436 437 438 439
	} while (c != f);
	if (WIFEXITED(status)) {
		if (WEXITSTATUS(status) == 0) {
			if (verbose > 6)
440
				printf("Exporthook successfully returned!\n");
441 442
			return RET_OK;
		} else {
443 444 445
			fprintf(stderr,
"Exporthook failed with exitcode %d!\n",
					(int)WEXITSTATUS(status));
446 447
			return RET_ERROR;
		}
448 449
	} else if (WIFSIGNALED(status)) {
		fprintf(stderr, "Exporthook killed by signal %d!\n",
450 451
				(int)(WTERMSIG(status)));
		return RET_ERROR;
452
	} else {
453 454 455
		fprintf(stderr,
"Exporthook terminated abnormally. (status is %x)!\n",
				status);
456 457 458 459
		return RET_ERROR;
	}
}

460
retvalue export_target(const char *relativedir, struct target *target,  const struct exportmode *exportmode, struct release *release, bool onlyifmissing, bool snapshot) {
461 462 463 464
	retvalue r;
	struct filetorelease *file;
	const char *status;
	char *relfilename;
465
	char buffer[100];
466
	struct package_cursor iterator;
467

468 469
	relfilename = calc_dirconcat(relativedir, exportmode->filename);
	if (FAILEDTOALLOC(relfilename))
470
		return RET_ERROR_OOM;
471

472 473 474
	r = release_startfile(release, relfilename, exportmode->compressions,
		       onlyifmissing, &file);
	if (RET_WAS_ERROR(r)) {
475 476 477
		free(relfilename);
		return r;
	}
478 479 480
	if (RET_IS_OK(r)) {
		if (release_oldexists(file)) {
			if (verbose > 5)
481 482 483
				printf("  replacing '%s/%s'%s\n",
					release_dirofdist(release), relfilename,
					exportdescription(exportmode, buffer, 100));
484
			status = "change";
485
		} else {
486
			if (verbose > 5)
487 488 489
				printf("  creating '%s/%s'%s\n",
					release_dirofdist(release), relfilename,
					exportdescription(exportmode, buffer, 100));
490
			status = "new";
491
		}
492
		r = package_openiterator(target, READONLY, &iterator);
493
		if (RET_WAS_ERROR(r)) {
494 495 496 497
			release_abortfile(file);
			free(relfilename);
			return r;
		}
498 499
		while (package_next(&iterator)) {
			if (iterator.current.controllen == 0)
500
				continue;
501 502
			(void)release_writedata(file, iterator.current.control,
					iterator.current.controllen);
503
			(void)release_writestring(file, "\n");
504
			if (iterator.current.control[iterator.current.controllen-1] != '\n')
505 506
				(void)release_writestring(file, "\n");
		}
507
		r = package_closeiterator(&iterator);
508
		if (RET_WAS_ERROR(r)) {
509
			release_abortfile(file);
510
			free(relfilename);
511 512
			return r;
		}
513 514
		r = release_finishfile(release, file);
		if (RET_WAS_ERROR(r)) {
515 516
			free(relfilename);
			return r;
517
		}
518
	} else {
519
		if (verbose > 9)
Bernhard Link's avatar
Bernhard Link committed
520
			printf("  keeping old '%s/%s'%s\n",
521 522
				release_dirofdist(release), relfilename,
				exportdescription(exportmode, buffer, 100));
523
		status = "old";
524
	}
525
	if (!snapshot) {
526 527
		int i;

528
		for (i = 0 ; i < exportmode->hooks.count ; i++) {
529 530 531
			const char *hook = exportmode->hooks.values[i];

			r = callexporthook(hook, relfilename, status, release);
532
			if (RET_WAS_ERROR(r)) {
533 534 535 536 537
				free(relfilename);
				return r;
			}
		}
	}
538 539
	free(relfilename);
	return RET_OK;
540 541 542
}

void exportmode_done(struct exportmode *mode) {
543
	assert (mode != NULL);
544
	free(mode->filename);
545
	strlist_done(&mode->hooks);
546
	free(mode->release);
547
}