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