1 /** \ingroup rpmbuild
2  * \file build/parsePrep.c
3  *  Parse %prep section from spec file.
4  */
5 
6 #include "system.h"
7 
8 #include <errno.h>
9 #include <libgen.h>
10 
11 #include <rpm/header.h>
12 #include <rpm/rpmlog.h>
13 #include <rpm/rpmfileutil.h>
14 #include "build/rpmbuild_internal.h"
15 #include "build/rpmbuild_misc.h"
16 #include "lib/rpmug.h"
17 #include "debug.h"
18 
19 /**
20  * Check that file owner and group are known.
21  * @param urlfn		file url
22  * @return		RPMRC_OK on success
23  */
checkOwners(const char * urlfn)24 static rpmRC checkOwners(const char * urlfn)
25 {
26     struct stat sb;
27 
28     if (lstat(urlfn, &sb)) {
29 	rpmlog(RPMLOG_ERR, _("Bad source: %s: %s\n"),
30 		urlfn, strerror(errno));
31 	return RPMRC_FAIL;
32     }
33 
34     return RPMRC_OK;
35 }
36 
37 /**
38  * Expand %patchN macro into %prep scriptlet.
39  * @param spec		build info
40  * @param c		patch index
41  * @param strip		patch level (i.e. patch -p argument)
42  * @param db		saved file suffix (i.e. patch --suffix argument)
43  * @param reverse	include -R?
44  * @param removeEmpties	include -E?
45  * @param fuzz		fuzz factor, fuzz<0 means no fuzz set
46  * @param dir		dir to change to (i.e. patch -d argument)
47  * @param outfile	send output to this file (i.e. patch -o argument)
48  * @param setUtc	include -Z?
49  * @return		expanded %patch macro (NULL on error)
50  */
51 
doPatch(rpmSpec spec,uint32_t c,int strip,const char * db,int reverse,int removeEmpties,int fuzz,const char * dir,const char * outfile,int setUtc)52 static char *doPatch(rpmSpec spec, uint32_t c, int strip, const char *db,
53 		     int reverse, int removeEmpties, int fuzz, const char *dir,
54 		     const char *outfile, int setUtc)
55 {
56     char *buf = NULL;
57     char *arg_backup = NULL;
58     char *arg_fuzz = NULL;
59     char *arg_dir = NULL;
60     char *arg_outfile = NULL;
61     char *args = NULL;
62     char *arg_patch_flags = rpmExpand("%{?_default_patch_flags}", NULL);
63     struct Source *sp;
64     char *patchcmd;
65     rpmCompressedMagic compressed = COMPRESSED_NOT;
66 
67     if ((sp = findSource(spec, c, RPMBUILD_ISPATCH)) == NULL) {
68 	rpmlog(RPMLOG_ERR, _("No patch number %u\n"), c);
69 	goto exit;
70     }
71     const char *fn = sp->path;
72 
73     /* On non-build parse's, file cannot be stat'd or read. */
74     if ((spec->flags & RPMSPEC_FORCE) || checkOwners(fn) || rpmFileIsCompressed(fn, &compressed)) goto exit;
75 
76     if (db) {
77 	rasprintf(&arg_backup, "-b --suffix %s", db);
78     } else arg_backup = xstrdup("");
79 
80     if (dir) {
81 	rasprintf(&arg_dir, " -d %s", dir);
82     } else arg_dir = xstrdup("");
83 
84     if (outfile) {
85 	rasprintf(&arg_outfile, " -o %s", outfile);
86     } else arg_outfile = xstrdup("");
87 
88     if (fuzz >= 0) {
89 	rasprintf(&arg_fuzz, " --fuzz=%d", fuzz);
90     } else arg_fuzz = xstrdup("");
91 
92     rasprintf(&args, "%s -p%d %s%s%s%s%s%s%s", arg_patch_flags, strip, arg_backup, arg_fuzz, arg_dir, arg_outfile,
93 		reverse ? " -R" : "",
94 		removeEmpties ? " -E" : "",
95 		setUtc ? " -Z" : "");
96 
97     /* Avoid the extra cost of fork and pipe for uncompressed patches */
98     if (compressed != COMPRESSED_NOT) {
99 	patchcmd = rpmExpand("{ %{uncompress: ", fn, "} || echo patch_fail ; } | "
100                              "%{__patch} ", args, NULL);
101     } else {
102 	patchcmd = rpmExpand("%{__patch} ", args, " < ", fn, NULL);
103     }
104 
105     free(arg_fuzz);
106     free(arg_outfile);
107     free(arg_dir);
108     free(arg_backup);
109     free(args);
110 
111     rasprintf(&buf, "echo \"Patch #%u (%s):\"\n"
112 			"%s\n",
113 			c, sp->source, patchcmd);
114     free(patchcmd);
115 
116 exit:
117     free(arg_patch_flags);
118     return buf;
119 }
120 
121 /**
122  * Expand %setup macro into %prep scriptlet.
123  * @param spec		build info
124  * @param c		source index
125  * @param quietly	should -vv be omitted from tar?
126  * @return		expanded %setup macro (NULL on error)
127  */
doUntar(rpmSpec spec,uint32_t c,int quietly)128 static char *doUntar(rpmSpec spec, uint32_t c, int quietly)
129 {
130     char *buf = NULL;
131     char *tar = NULL;
132     const char *taropts = ((rpmIsVerbose() && !quietly) ? "-xvvof" : "-xof");
133     struct Source *sp;
134     rpmCompressedMagic compressed = COMPRESSED_NOT;
135 
136     if ((sp = findSource(spec, c, RPMBUILD_ISSOURCE)) == NULL) {
137 	rpmlog(RPMLOG_ERR, _("No source number %u\n"), c);
138 	goto exit;
139     }
140     const char *fn = sp->path;
141 
142     /* XXX On non-build parse's, file cannot be stat'd or read */
143     if (!(spec->flags & RPMSPEC_FORCE) && (checkOwners(fn) || rpmFileIsCompressed(fn, &compressed))) {
144 	goto exit;
145     }
146 
147     tar = rpmGetPath("%{__tar}", NULL);
148     if (compressed != COMPRESSED_NOT) {
149 	char *zipper, *t = NULL;
150 	int needtar = 1;
151 	int needgemspec = 0;
152 
153 	switch (compressed) {
154 	case COMPRESSED_NOT:	/* XXX can't happen */
155 	case COMPRESSED_OTHER:
156 	    t = "%{__gzip} -dc";
157 	    break;
158 	case COMPRESSED_BZIP2:
159 	    t = "%{__bzip2} -dc";
160 	    break;
161 	case COMPRESSED_ZIP:
162 	    if (rpmIsVerbose() && !quietly)
163 		t = "%{__unzip}";
164 	    else
165 		t = "%{__unzip} -qq";
166 	    needtar = 0;
167 	    break;
168 	case COMPRESSED_LZMA:
169 	case COMPRESSED_XZ:
170 	    t = "%{__xz} -dc";
171 	    break;
172 	case COMPRESSED_LZIP:
173 	    t = "%{__lzip} -dc";
174 	    break;
175 	case COMPRESSED_LRZIP:
176 	    t = "%{__lrzip} -dqo-";
177 	    break;
178 	case COMPRESSED_7ZIP:
179 	    t = "%{__7zip} x";
180 	    needtar = 0;
181 	    break;
182 	case COMPRESSED_ZSTD:
183 	    t = "%{__zstd} -dc";
184 	    break;
185 	case COMPRESSED_GEM:
186 	    t = "%{__gem} unpack";
187 	    needtar = 0;
188 	    needgemspec = 1;
189 	    break;
190 	}
191 	zipper = rpmGetPath(t, NULL);
192 	if (needtar) {
193 	    rasprintf(&buf, "%s '%s' | %s %s -", zipper, fn, tar, taropts);
194 	} else if (needgemspec) {
195 	    char *gem = rpmGetPath("%{__gem}", NULL);
196 	    char *gemspec = NULL;
197 	    char gemnameversion[strlen(sp->source) - 3];
198 
199 	    rstrlcpy(gemnameversion, sp->source, strlen(sp->source) - 3);
200 	    gemspec = rpmGetPath("%{_builddir}/", gemnameversion, ".gemspec", NULL);
201 
202 	    rasprintf(&buf, "%s '%s' && %s spec '%s' --ruby > '%s'",
203 			zipper, fn, gem, fn, gemspec);
204 
205 	    free(gemspec);
206 	    free(gem);
207 	} else {
208 	    rasprintf(&buf, "%s '%s'", zipper, fn);
209 	}
210 	free(zipper);
211     } else {
212 	rasprintf(&buf, "%s %s '%s'", tar, taropts, fn);
213     }
214 
215 exit:
216     free(tar);
217     return buf ? rstrcat(&buf,
218 		    "\nSTATUS=$?\n"
219 		    "if [ $STATUS -ne 0 ]; then\n"
220 		    "  exit $STATUS\n"
221 		    "fi") : NULL;
222 }
223 
224 /**
225  * Parse %setup macro.
226  * @param spec		build info
227  * @param line		current line from spec file
228  * @return		RPMRC_OK on success
229  */
doSetupMacro(rpmSpec spec,const char * line)230 static int doSetupMacro(rpmSpec spec, const char *line)
231 {
232     char *buf = NULL;
233     StringBuf before = newStringBuf();
234     StringBuf after = newStringBuf();
235     poptContext optCon = NULL;
236     int argc;
237     const char ** argv = NULL;
238     int arg;
239     int xx;
240     rpmRC rc = RPMRC_FAIL;
241     uint32_t num;
242     int leaveDirs = 0, skipDefaultAction = 0;
243     int createDir = 0, quietly = 0;
244     char * dirName = NULL;
245     struct poptOption optionsTable[] = {
246 	    { NULL, 'a', POPT_ARG_STRING, NULL, 'a',	NULL, NULL},
247 	    { NULL, 'b', POPT_ARG_STRING, NULL, 'b',	NULL, NULL},
248 	    { NULL, 'c', 0, &createDir, 0,		NULL, NULL},
249 	    { NULL, 'D', 0, &leaveDirs, 0,		NULL, NULL},
250 	    { NULL, 'n', POPT_ARG_STRING, &dirName, 0,	NULL, NULL},
251 	    { NULL, 'T', 0, &skipDefaultAction, 0,	NULL, NULL},
252 	    { NULL, 'q', 0, &quietly, 0,		NULL, NULL},
253 	    { 0, 0, 0, 0, 0,	NULL, NULL}
254     };
255 
256     if (strstr(line+6, " -q")) quietly = 1;
257 
258     if ((xx = poptParseArgvString(line, &argc, &argv))) {
259 	rpmlog(RPMLOG_ERR, _("Error parsing %%setup: %s\n"), poptStrerror(xx));
260 	goto exit;
261     }
262 
263     if (rpmExpandNumeric("%{_build_in_place}")) {
264 	rc = RPMRC_OK;
265 	goto exit;
266     }
267 
268     optCon = poptGetContext(NULL, argc, argv, optionsTable, 0);
269     while ((arg = poptGetNextOpt(optCon)) > 0) {
270 	char *optArg = poptGetOptArg(optCon);
271 
272 	/* We only parse -a and -b here */
273 
274 	if (parseUnsignedNum(optArg, &num)) {
275 	    rpmlog(RPMLOG_ERR, _("line %d: Bad arg to %%setup: %s\n"),
276 		     spec->lineNum, (optArg ? optArg : "???"));
277 	    goto exit;
278 	}
279 
280 	{   char *chptr = doUntar(spec, num, quietly);
281 	    if (chptr == NULL)
282 		goto exit;
283 
284 	    appendLineStringBuf((arg == 'a' ? after : before), chptr);
285 	    free(chptr);
286 	}
287 	free(optArg);
288     }
289 
290     if (arg < -1) {
291 	rpmlog(RPMLOG_ERR, _("line %d: Bad %%setup option %s: %s\n"),
292 		 spec->lineNum,
293 		 poptBadOption(optCon, POPT_BADOPTION_NOALIAS),
294 		 poptStrerror(arg));
295 	goto exit;
296     }
297 
298     if (dirName) {
299 	spec->buildSubdir = xstrdup(dirName);
300     } else {
301 	rasprintf(&spec->buildSubdir, "%s-%s",
302 		  headerGetString(spec->packages->header, RPMTAG_NAME),
303 		  headerGetString(spec->packages->header, RPMTAG_VERSION));
304     }
305     rpmPushMacroFlags(spec->macros, "buildsubdir", NULL, spec->buildSubdir, RMIL_SPEC, RPMMACRO_LITERAL);
306 
307     /* cd to the build dir */
308     {	char * buildDir = rpmGenPath(spec->rootDir, "%{_builddir}", "");
309 
310 	rasprintf(&buf, "cd '%s'", buildDir);
311 	appendLineStringBuf(spec->prep, buf);
312 	free(buf);
313 	free(buildDir);
314     }
315 
316     /* delete any old sources */
317     if (!leaveDirs) {
318 	rasprintf(&buf, "rm -rf '%s'", spec->buildSubdir);
319 	appendLineStringBuf(spec->prep, buf);
320 	free(buf);
321     }
322 
323     appendStringBuf(spec->prep, getStringBuf(before));
324 
325     /* if necessary, create and cd into the proper dir */
326     if (createDir) {
327 	buf = rpmExpand("%{__mkdir_p} ", spec->buildSubdir, "\n",
328 			"cd '", spec->buildSubdir, "'", NULL);
329 	appendLineStringBuf(spec->prep, buf);
330 	free(buf);
331     }
332 
333     /* do the default action */
334    if (!createDir && !skipDefaultAction) {
335 	char *chptr = doUntar(spec, 0, quietly);
336 	if (!chptr)
337 	    goto exit;
338 	appendLineStringBuf(spec->prep, chptr);
339 	free(chptr);
340     }
341 
342     if (!createDir) {
343 	rasprintf(&buf, "cd '%s'", spec->buildSubdir);
344 	appendLineStringBuf(spec->prep, buf);
345 	free(buf);
346     }
347 
348     if (createDir && !skipDefaultAction) {
349 	char *chptr = doUntar(spec, 0, quietly);
350 	if (chptr == NULL)
351 	    goto exit;
352 	appendLineStringBuf(spec->prep, chptr);
353 	free(chptr);
354     }
355 
356     appendStringBuf(spec->prep, getStringBuf(after));
357 
358     /* Fix the permissions of the setup build tree */
359     {	char *fix = rpmExpand("%{_fixperms} .", NULL);
360 	if (fix && *fix != '%') {
361 	    appendLineStringBuf(spec->prep, fix);
362 	}
363 	free(fix);
364     }
365     rc = RPMRC_OK;
366 
367 exit:
368     freeStringBuf(before);
369     freeStringBuf(after);
370     poptFreeContext(optCon);
371     free(dirName);
372     free(argv);
373 
374     return rc;
375 }
376 
377 /**
378  * Parse %patch line.
379  * This supports too many crazy syntaxes:
380  * - %patchN is equal to %patch -P\<N\>
381  * - -P\<N\> -P\<N+1\>... can be used to apply several patch on a single line
382  * - Any trailing arguments are treated as patch numbers
383  * - Any combination of the above, except unless at least one -P is specified,
384  *   %patch is treated as "numberless patch" so that "%patch 1" actually tries
385  *   to pull in numberless "Patch:" and numbered "Patch1:".
386  *
387  * @param spec		build info
388  * @param line		current line from spec file
389  * @return		RPMRC_OK on success
390  */
doPatchMacro(rpmSpec spec,const char * line)391 static rpmRC doPatchMacro(rpmSpec spec, const char *line)
392 {
393     char *opt_b, *opt_d, *opt_o;
394     char *buf = NULL;
395     int opt_p, opt_R, opt_E, opt_F, opt_Z;
396     int argc, c;
397     const char **argv = NULL;
398     ARGV_t patch, patchnums = NULL;
399     rpmRC rc = RPMRC_FAIL; /* assume failure */
400 
401     struct poptOption const patchOpts[] = {
402 	{ NULL, 'P', POPT_ARG_STRING, NULL, 'P', NULL, NULL },
403 	{ NULL, 'p', POPT_ARG_INT, &opt_p, 'p', NULL, NULL },
404 	{ NULL, 'R', POPT_ARG_NONE, &opt_R, 'R', NULL, NULL },
405 	{ NULL, 'E', POPT_ARG_NONE, &opt_E, 'E', NULL, NULL },
406 	{ NULL, 'b', POPT_ARG_STRING, &opt_b, 'b', NULL, NULL },
407 	{ NULL, 'z', POPT_ARG_STRING, &opt_b, 'z', NULL, NULL },
408 	{ NULL, 'F', POPT_ARG_INT, &opt_F, 'F', NULL, NULL },
409 	{ NULL, 'd', POPT_ARG_STRING, &opt_d, 'd', NULL, NULL },
410 	{ NULL, 'o', POPT_ARG_STRING, &opt_o, 'o', NULL, NULL },
411 	{ NULL, 'Z', POPT_ARG_NONE, &opt_Z, 'Z', NULL, NULL},
412 	{ NULL, 0, 0, NULL, 0, NULL, NULL }
413     };
414     poptContext optCon = NULL;
415 
416     opt_p = opt_R = opt_E = opt_Z = 0;
417     opt_F = rpmExpandNumeric("%{_default_patch_fuzz}");		/* get default fuzz factor for %patch */
418     opt_b = opt_d = opt_o = NULL;
419 
420     /* Convert %patchN to %patch -PN to simplify further processing */
421     if (! strchr(" \t\n", line[6])) {
422 	rasprintf(&buf, "%%patch -P %s", line + 6);
423     } else {
424 	/* %patch without a number refers to patch 0 */
425 	if (strstr(line+6, " -P") == NULL)
426 	    rasprintf(&buf, "%%patch -P %d %s", 0, line + 6);
427 	else
428 	    buf = xstrdup(line);
429     }
430     poptParseArgvString(buf, &argc, &argv);
431     free(buf);
432 
433     /*
434      * Grab all -P<N> numbers for later processing. Stored as strings
435      * at this point so we only have to worry about conversion in one place.
436      */
437     optCon = poptGetContext(NULL, argc, argv, patchOpts, 0);
438     while ((c = poptGetNextOpt(optCon)) > 0) {
439 	switch (c) {
440 	case 'P': {
441 	    char *arg = poptGetOptArg(optCon);
442 	    if (arg) {
443 	    	argvAdd(&patchnums, arg);
444 	    	free(arg);
445 	    }
446 	    break;
447 	}
448 	default:
449 	    break;
450 	}
451     }
452 
453     if (c < -1) {
454 	rpmlog(RPMLOG_ERR, "%s: %s: %s\n", poptStrerror(c),
455 		poptBadOption(optCon, POPT_BADOPTION_NOALIAS), line);
456 	goto exit;
457     }
458 
459     /* Any trailing arguments are treated as patch numbers */
460     argvAppend(&patchnums, (ARGV_const_t) poptGetArgs(optCon));
461 
462     /* Convert to number, generate patch command and append to %prep script */
463     for (patch = patchnums; *patch; patch++) {
464 	uint32_t pnum;
465 	char *s;
466 	if (parseUnsignedNum(*patch, &pnum)) {
467 	    rpmlog(RPMLOG_ERR, _("Invalid patch number %s: %s\n"),
468 		     *patch, line);
469 	    goto exit;
470 	}
471 	s = doPatch(spec, pnum, opt_p, opt_b, opt_R, opt_E, opt_F, opt_d, opt_o, opt_Z);
472 	if (s == NULL) {
473 	    goto exit;
474 	}
475 	appendLineStringBuf(spec->prep, s);
476 	free(s);
477     }
478 
479     rc = RPMRC_OK;
480 
481 exit:
482     argvFree(patchnums);
483     free(opt_b);
484     free(opt_d);
485     free(opt_o);
486     free(argv);
487     poptFreeContext(optCon);
488     return rc;
489 }
490 
parsePrep(rpmSpec spec)491 int parsePrep(rpmSpec spec)
492 {
493     int rc, res = PART_ERROR;
494     ARGV_t saveLines = NULL;
495 
496     if (spec->prep != NULL) {
497 	rpmlog(RPMLOG_ERR, _("line %d: second %%prep\n"), spec->lineNum);
498 	goto exit;
499     }
500 
501     spec->prep = newStringBuf();
502 
503     /* There are no options to %prep */
504     if ((res = parseLines(spec, STRIP_NOTHING, &saveLines, NULL)) == PART_ERROR)
505 	goto exit;
506 
507     for (ARGV_const_t lines = saveLines; lines && *lines; lines++) {
508 	rc = RPMRC_OK;
509 	if (rstreqn(*lines, "%setup", sizeof("%setup")-1)) {
510 	    rc = doSetupMacro(spec, *lines);
511 	} else if (rstreqn(*lines, "%patch", sizeof("%patch")-1)) {
512 	    rc = doPatchMacro(spec, *lines);
513 	} else {
514 	    appendStringBuf(spec->prep, *lines);
515 	}
516 	if (rc != RPMRC_OK && !(spec->flags & RPMSPEC_FORCE)) {
517 	    res = PART_ERROR;
518 	    goto exit;
519 	}
520     }
521 
522 exit:
523     argvFree(saveLines);
524 
525     return res;
526 }
527