/** \ingroup rpmbuild * \file build/files.c * The post-build, pre-packaging file tree walk to assemble the package * manifest. */ #include "system.h" #define MYALLPERMS 07777 #include #include #if WITH_CAP #include #endif #include #include #include #include /* rpmDoDigest() */ #include #include #if HAVE_GELF_H #include #endif #include "rpmio/rpmio_internal.h" /* XXX rpmioSlurp */ #include "misc/fts.h" #include "lib/cpio.h" #include "lib/rpmfi_internal.h" /* XXX fi->apath */ #include "lib/rpmug.h" #include "build/rpmbuild_internal.h" #include "build/rpmbuild_misc.h" #include "debug.h" #include #define SKIPSPACE(s) { while (*(s) && risspace(*(s))) (s)++; } #define SKIPWHITE(_x) {while(*(_x) && (risspace(*_x) || *(_x) == ',')) (_x)++;} #define SKIPNONWHITE(_x){while(*(_x) &&!(risspace(*_x) || *(_x) == ',')) (_x)++;} /** */ enum specfFlags_e { SPECD_DEFFILEMODE = (1 << 0), SPECD_DEFDIRMODE = (1 << 1), SPECD_DEFUID = (1 << 2), SPECD_DEFGID = (1 << 3), SPECD_DEFVERIFY = (1 << 4), SPECD_FILEMODE = (1 << 8), SPECD_DIRMODE = (1 << 9), SPECD_UID = (1 << 10), SPECD_GID = (1 << 11), SPECD_VERIFY = (1 << 12) }; typedef rpmFlags specfFlags; /* internal %files parsing state attributes */ enum parseAttrs_e { RPMFILE_EXCLUDE = (1 << 16), /*!< from %%exclude */ RPMFILE_DOCDIR = (1 << 17), /*!< from %%docdir */ RPMFILE_DIR = (1 << 18), /*!< from %%dir */ RPMFILE_SPECIALDIR = (1 << 19), /*!< from special %%doc */ }; /* bits up to 15 (for now) reserved for exported rpmfileAttrs */ #define PARSEATTR_MASK 0x0000ffff /** */ typedef struct FileListRec_s { struct stat fl_st; #define fl_dev fl_st.st_dev #define fl_ino fl_st.st_ino #define fl_mode fl_st.st_mode #define fl_nlink fl_st.st_nlink #define fl_uid fl_st.st_uid #define fl_gid fl_st.st_gid #define fl_rdev fl_st.st_rdev #define fl_size fl_st.st_size #define fl_mtime fl_st.st_mtime char *diskPath; /* get file from here */ char *cpioPath; /* filename in cpio archive */ const char *uname; const char *gname; unsigned flags; specfFlags specdFlags; /* which attributes have been explicitly specified. */ rpmVerifyFlags verifyFlags; char *langs; /* XXX locales separated with | */ char *caps; } * FileListRec; /** */ typedef struct AttrRec_s { char *ar_fmodestr; char *ar_dmodestr; char *ar_user; char *ar_group; mode_t ar_fmode; mode_t ar_dmode; } * AttrRec; static struct AttrRec_s root_ar = { NULL, NULL, "root", "root", 0, 0 }; /* list of files */ static StringBuf check_fileList = NULL; typedef struct specialDir_s { char * dirname; ARGV_t files; struct AttrRec_s ar; struct AttrRec_s def_ar; rpmFlags sdtype; } * specialDir; typedef struct FileEntry_s { rpmfileAttrs attrFlags; specfFlags specdFlags; rpmVerifyFlags verifyFlags; struct AttrRec_s ar; ARGV_t langs; char *caps; /* these are only ever relevant for current entry */ unsigned devtype; unsigned devmajor; int devminor; int isDir; } * FileEntry; typedef struct FileRecords_s { FileListRec recs; int alloced; int used; } * FileRecords; /** * Package file tree walk data. */ typedef struct FileList_s { /* global filelist state */ char * buildRoot; int processingFailed; int haveCaps; int largeFiles; ARGV_t docDirs; rpmBuildPkgFlags pkgFlags; /* actual file records */ struct FileRecords_s files; /* active defaults */ struct FileEntry_s def; /* current file-entry state */ struct FileEntry_s cur; } * FileList; /** */ static void nullAttrRec(AttrRec ar) { ar->ar_fmodestr = NULL; ar->ar_dmodestr = NULL; ar->ar_user = NULL; ar->ar_group = NULL; ar->ar_fmode = 0; ar->ar_dmode = 0; } /** */ static void freeAttrRec(AttrRec ar) { ar->ar_fmodestr = _free(ar->ar_fmodestr); ar->ar_dmodestr = _free(ar->ar_dmodestr); ar->ar_user = _free(ar->ar_user); ar->ar_group = _free(ar->ar_group); /* XXX doesn't free ar (yet) */ return; } /** */ static void dupAttrRec(const AttrRec oar, AttrRec nar) { if (oar == nar) return; freeAttrRec(nar); nar->ar_fmodestr = (oar->ar_fmodestr ? xstrdup(oar->ar_fmodestr) : NULL); nar->ar_dmodestr = (oar->ar_dmodestr ? xstrdup(oar->ar_dmodestr) : NULL); nar->ar_user = (oar->ar_user ? xstrdup(oar->ar_user) : NULL); nar->ar_group = (oar->ar_group ? xstrdup(oar->ar_group) : NULL); nar->ar_fmode = oar->ar_fmode; nar->ar_dmode = oar->ar_dmode; } #if 0 /** */ static void dumpAttrRec(const char * msg, AttrRec ar) { if (msg) fprintf(stderr, "%s:\t", msg); fprintf(stderr, "(%s, %s, %s, %s)\n", ar->ar_fmodestr, ar->ar_user, ar->ar_group, ar->ar_dmodestr); } #endif static void FileEntryFree(FileEntry entry) { freeAttrRec(&(entry->ar)); argvFree(entry->langs); memset(entry, 0, sizeof(*entry)); } /** * strtokWithQuotes. * @param s * @param delim */ static char *strtokWithQuotes(char *s, const char *delim) { static char *olds = NULL; char *token; if (s == NULL) s = olds; if (s == NULL) return NULL; /* Skip leading delimiters */ s += strspn(s, delim); if (*s == '\0') return NULL; /* Find the end of the token. */ token = s; if (*token == '"') { token++; /* Find next " char */ s = strchr(token, '"'); } else { s = strpbrk(token, delim); } /* Terminate it */ if (s == NULL) { /* This token finishes the string */ olds = strchr(token, '\0'); } else { /* Terminate the token and make olds point past it */ *s = '\0'; olds = s+1; } return token; } /** */ typedef const struct VFA { const char * attribute; int flag; } VFA_t; /** */ static VFA_t const verifyAttrs[] = { { "md5", RPMVERIFY_FILEDIGEST }, { "filedigest", RPMVERIFY_FILEDIGEST }, { "size", RPMVERIFY_FILESIZE }, { "link", RPMVERIFY_LINKTO }, { "user", RPMVERIFY_USER }, { "owner", RPMVERIFY_USER }, { "group", RPMVERIFY_GROUP }, { "mtime", RPMVERIFY_MTIME }, { "mode", RPMVERIFY_MODE }, { "rdev", RPMVERIFY_RDEV }, { "caps", RPMVERIFY_CAPS }, { NULL, 0 } }; static rpmFlags vfaMatch(VFA_t *attrs, const char *token, rpmFlags *flags) { VFA_t *vfa; for (vfa = attrs; vfa->attribute != NULL; vfa++) { if (rstreq(token, vfa->attribute)) { *flags |= vfa->flag; break; } } return vfa->flag; } /** * Parse %verify and %defverify from file manifest. * @param buf current spec file line * @param def parse for %defverify or %verify? * @param entry file entry data (current or default) * @return RPMRC_OK on success */ static rpmRC parseForVerify(char * buf, int def, FileEntry entry) { char *p, *pe, *q = NULL; const char *name = def ? "%defverify" : "%verify"; int negated = 0; rpmVerifyFlags verifyFlags = RPMVERIFY_NONE; rpmRC rc = RPMRC_FAIL; if ((p = strstr(buf, name)) == NULL) return RPMRC_OK; for (pe = p; (pe-p) < strlen(name); pe++) *pe = ' '; SKIPSPACE(pe); if (*pe != '(') { rpmlog(RPMLOG_ERR, _("Missing '(' in %s %s\n"), name, pe); goto exit; } /* Bracket %*verify args */ *pe++ = ' '; for (p = pe; *pe && *pe != ')'; pe++) {}; if (*pe == '\0') { rpmlog(RPMLOG_ERR, _("Missing ')' in %s(%s\n"), name, p); goto exit; } /* Localize. Erase parsed string */ q = xmalloc((pe-p) + 1); rstrlcpy(q, p, (pe-p) + 1); while (p <= pe) *p++ = ' '; for (p = q; *p != '\0'; p = pe) { SKIPWHITE(p); if (*p == '\0') break; pe = p; SKIPNONWHITE(pe); if (*pe != '\0') *pe++ = '\0'; if (vfaMatch(verifyAttrs, p, &verifyFlags)) continue; if (rstreq(p, "not")) { negated ^= 1; } else { rpmlog(RPMLOG_ERR, _("Invalid %s token: %s\n"), name, p); goto exit; } } entry->verifyFlags = negated ? ~(verifyFlags) : verifyFlags; entry->specdFlags |= SPECD_VERIFY; rc = RPMRC_OK; exit: free(q); return rc; } #define isAttrDefault(_ars) ((_ars)[0] == '-' && (_ars)[1] == '\0') /** * Parse %dev from file manifest. * @param buf current spec file line * @param cur current file entry data * @return RPMRC_OK on success */ static rpmRC parseForDev(char * buf, FileEntry cur) { const char * name; const char * errstr = NULL; char *p, *pe, *q = NULL; rpmRC rc = RPMRC_FAIL; /* assume error */ if ((p = strstr(buf, (name = "%dev"))) == NULL) return RPMRC_OK; for (pe = p; (pe-p) < strlen(name); pe++) *pe = ' '; SKIPSPACE(pe); if (*pe != '(') { errstr = "'('"; goto exit; } /* Bracket %dev args */ *pe++ = ' '; for (p = pe; *pe && *pe != ')'; pe++) {}; if (*pe != ')') { errstr = "')'"; goto exit; } /* Localize. Erase parsed string */ q = xmalloc((pe-p) + 1); rstrlcpy(q, p, (pe-p) + 1); while (p <= pe) *p++ = ' '; p = q; SKIPWHITE(p); pe = p; SKIPNONWHITE(pe); if (*pe != '\0') *pe++ = '\0'; if (*p == 'b') cur->devtype = 'b'; else if (*p == 'c') cur->devtype = 'c'; else { errstr = "devtype"; goto exit; } p = pe; SKIPWHITE(p); pe = p; SKIPNONWHITE(pe); if (*pe != '\0') *pe = '\0'; for (pe = p; *pe && risdigit(*pe); pe++) {} ; if (*pe == '\0') { cur->devmajor = atoi(p); if (!(cur->devmajor >= 0 && cur->devmajor < 256)) { errstr = "devmajor"; goto exit; } pe++; } else { errstr = "devmajor"; goto exit; } p = pe; SKIPWHITE(p); pe = p; SKIPNONWHITE(pe); if (*pe != '\0') *pe = '\0'; for (pe = p; *pe && risdigit(*pe); pe++) {} ; if (*pe == '\0') { cur->devminor = atoi(p); if (!(cur->devminor >= 0 && cur->devminor < 256)) { errstr = "devminor"; goto exit; } } else { errstr = "devminor"; goto exit; } rc = RPMRC_OK; exit: if (rc) { rpmlog(RPMLOG_ERR, _("Missing %s in %s %s\n"), errstr, name, p); } free(q); return rc; } /** * Parse %attr and %defattr from file manifest. * @param buf current spec file line * @param def parse for %defattr or %attr? * @param entry file entry data (current / default) * @return 0 on success */ static rpmRC parseForAttr(char * buf, int def, FileEntry entry) { const char *name = def ? "%defattr" : "%attr"; char *p, *pe, *q = NULL; int x; struct AttrRec_s arbuf; AttrRec ar = &arbuf; rpmRC rc = RPMRC_FAIL; if ((p = strstr(buf, name)) == NULL) return RPMRC_OK; for (pe = p; (pe-p) < strlen(name); pe++) *pe = ' '; SKIPSPACE(pe); if (*pe != '(') { rpmlog(RPMLOG_ERR, _("Missing '(' in %s %s\n"), name, pe); goto exit; } /* Bracket %*attr args */ *pe++ = ' '; for (p = pe; *pe && *pe != ')'; pe++) {}; if (def) { /* %defattr */ char *r = pe; r++; SKIPSPACE(r); if (*r != '\0') { rpmlog(RPMLOG_ERR, _("Non-white space follows %s(): %s\n"), name, r); goto exit; } } /* Localize. Erase parsed string */ q = xmalloc((pe-p) + 1); rstrlcpy(q, p, (pe-p) + 1); while (p <= pe) *p++ = ' '; nullAttrRec(ar); p = q; SKIPWHITE(p); if (*p != '\0') { pe = p; SKIPNONWHITE(pe); if (*pe != '\0') *pe++ = '\0'; ar->ar_fmodestr = p; p = pe; SKIPWHITE(p); } if (*p != '\0') { pe = p; SKIPNONWHITE(pe); if (*pe != '\0') *pe++ = '\0'; ar->ar_user = p; p = pe; SKIPWHITE(p); } if (*p != '\0') { pe = p; SKIPNONWHITE(pe); if (*pe != '\0') *pe++ = '\0'; ar->ar_group = p; p = pe; SKIPWHITE(p); } if (*p != '\0' && def) { /* %defattr */ pe = p; SKIPNONWHITE(pe); if (*pe != '\0') *pe++ = '\0'; ar->ar_dmodestr = p; p = pe; SKIPWHITE(p); } if (!(ar->ar_fmodestr && ar->ar_user && ar->ar_group) || *p != '\0') { rpmlog(RPMLOG_ERR, _("Bad syntax: %s(%s)\n"), name, q); goto exit; } /* Do a quick test on the mode argument and adjust for "-" */ if (ar->ar_fmodestr && !isAttrDefault(ar->ar_fmodestr)) { unsigned int ui; x = sscanf(ar->ar_fmodestr, "%o", &ui); if ((x == 0) || (ar->ar_fmode & ~MYALLPERMS)) { rpmlog(RPMLOG_ERR, _("Bad mode spec: %s(%s)\n"), name, q); goto exit; } ar->ar_fmode = ui; } else { ar->ar_fmodestr = NULL; } if (ar->ar_dmodestr && !isAttrDefault(ar->ar_dmodestr)) { unsigned int ui; x = sscanf(ar->ar_dmodestr, "%o", &ui); if ((x == 0) || (ar->ar_dmode & ~MYALLPERMS)) { rpmlog(RPMLOG_ERR, _("Bad dirmode spec: %s(%s)\n"), name, q); goto exit; } ar->ar_dmode = ui; } else { ar->ar_dmodestr = NULL; } if (!(ar->ar_user && !isAttrDefault(ar->ar_user))) { ar->ar_user = NULL; } if (!(ar->ar_group && !isAttrDefault(ar->ar_group))) { ar->ar_group = NULL; } dupAttrRec(ar, &(entry->ar)); /* XXX fix all this */ entry->specdFlags |= SPECD_UID | SPECD_GID | SPECD_FILEMODE | SPECD_DIRMODE; rc = RPMRC_OK; exit: free(q); return rc; } static VFA_t const configAttrs[] = { { "missingok", RPMFILE_MISSINGOK }, { "noreplace", RPMFILE_NOREPLACE }, { NULL, 0 } }; /** * Parse %config from file manifest. * @param buf current spec file line * @param cur current file entry data * @return RPMRC_OK on success */ static rpmRC parseForConfig(char * buf, FileEntry cur) { char *p, *pe, *q = NULL; const char *name; rpmRC rc = RPMRC_FAIL; if ((p = strstr(buf, (name = "%config"))) == NULL) return RPMRC_OK; cur->attrFlags |= RPMFILE_CONFIG; /* Erase "%config" token. */ for (pe = p; (pe-p) < strlen(name); pe++) *pe = ' '; SKIPSPACE(pe); if (*pe != '(') return RPMRC_OK; /* Bracket %config args */ *pe++ = ' '; for (p = pe; *pe && *pe != ')'; pe++) {}; if (*pe == '\0') { rpmlog(RPMLOG_ERR, _("Missing ')' in %s(%s\n"), name, p); goto exit; } /* Localize. Erase parsed string. */ q = xmalloc((pe-p) + 1); rstrlcpy(q, p, (pe-p) + 1); while (p <= pe) *p++ = ' '; for (p = q; *p != '\0'; p = pe) { SKIPWHITE(p); if (*p == '\0') break; pe = p; SKIPNONWHITE(pe); if (*pe != '\0') *pe++ = '\0'; if (!vfaMatch(configAttrs, p, &(cur->attrFlags))) { rpmlog(RPMLOG_ERR, _("Invalid %s token: %s\n"), name, p); goto exit; } } rc = RPMRC_OK; exit: free(q); return rc; } static rpmRC addLang(ARGV_t *av, const char *lang, size_t n, const char *ent) { rpmRC rc = RPMRC_FAIL; char lbuf[n + 1]; rstrlcpy(lbuf, lang, sizeof(lbuf)); SKIPWHITE(ent); /* Sanity check locale length */ if (n < 1 || (n == 1 && *lang != 'C') || n >= 32) { rpmlog(RPMLOG_ERR, _("Unusual locale length: \"%s\" in %%lang(%s)\n"), lbuf, ent); goto exit; } /* Check for duplicate locales */ if (argvSearch(*av, lbuf, NULL)) { rpmlog(RPMLOG_WARNING, _("Duplicate locale %s in %%lang(%s)\n"), lbuf, ent); } else { argvAdd(av, lbuf); argvSort(*av, NULL); } rc = RPMRC_OK; exit: return rc; } /** * Parse %lang from file manifest. * @param buf current spec file line * @param cur current file entry data * @return RPMRC_OK on success */ static rpmRC parseForLang(char * buf, FileEntry cur) { char *p, *pe, *q = NULL; const char *name; rpmRC rc = RPMRC_FAIL; while ((p = strstr(buf, (name = "%lang"))) != NULL) { for (pe = p; (pe-p) < strlen(name); pe++) *pe = ' '; SKIPSPACE(pe); if (*pe != '(') { rpmlog(RPMLOG_ERR, _("Missing '(' in %s %s\n"), name, pe); goto exit; } /* Bracket %lang args */ *pe = ' '; for (pe = p; *pe && *pe != ')'; pe++) {}; if (*pe == '\0') { rpmlog(RPMLOG_ERR, _("Missing ')' in %s(%s\n"), name, p); goto exit; } /* Localize. Erase parsed string. */ q = xmalloc((pe-p) + 1); rstrlcpy(q, p, (pe-p) + 1); while (p <= pe) *p++ = ' '; /* Parse multiple arguments from %lang */ for (p = q; *p != '\0'; p = pe) { SKIPWHITE(p); pe = p; SKIPNONWHITE(pe); if (addLang(&(cur->langs), p, (pe-p), q)) goto exit; if (*pe == ',') pe++; /* skip , if present */ } } rc = RPMRC_OK; exit: free(q); return rc; } /** * Parse %caps from file manifest. * @param buf current spec file line * @param cur current file entry data * @return RPMRC_OK on success */ static rpmRC parseForCaps(char * buf, FileEntry cur) { char *p, *pe, *q = NULL; const char *name; rpmRC rc = RPMRC_FAIL; if ((p = strstr(buf, (name = "%caps"))) == NULL) return RPMRC_OK; /* Erase "%caps" token. */ for (pe = p; (pe-p) < strlen(name); pe++) *pe = ' '; SKIPSPACE(pe); if (*pe != '(') return RPMRC_OK; /* Bracket %caps args */ *pe++ = ' '; for (p = pe; *pe && *pe != ')'; pe++) {}; if (*pe == '\0') { rpmlog(RPMLOG_ERR, _("Missing ')' in %s(%s\n"), name, p); goto exit; } /* Localize. Erase parsed string. */ q = xmalloc((pe-p) + 1); rstrlcpy(q, p, (pe-p) + 1); while (p <= pe) *p++ = ' '; #if WITH_CAP { char *captxt = NULL; cap_t fcaps = cap_from_text(q); if (fcaps == NULL) { rpmlog(RPMLOG_ERR, _("Invalid capability: %s\n"), q); goto exit; } /* run our string through cap_to_text() to get libcap presentation */ captxt = cap_to_text(fcaps, NULL); cur->caps = xstrdup(captxt); cap_free(captxt); cap_free(fcaps); } #else rpmlog(RPMLOG_ERR, _("File capability support not built in\n")); goto exit; #endif rc = RPMRC_OK; exit: free(q); return rc; } /** */ static VFA_t const virtualAttrs[] = { { "%dir", RPMFILE_DIR }, { "%docdir", RPMFILE_DOCDIR }, { "%doc", RPMFILE_DOC }, { "%ghost", RPMFILE_GHOST }, { "%exclude", RPMFILE_EXCLUDE }, { "%readme", RPMFILE_README }, { "%license", RPMFILE_LICENSE }, { "%pubkey", RPMFILE_PUBKEY }, { NULL, 0 } }; /** * Parse simple attributes (e.g. %dir) from file manifest. * @param buf current spec file line * @param cur current file entry data * @retval *fileNames file names * @return RPMRC_OK on success */ static rpmRC parseForSimple(char * buf, FileEntry cur, ARGV_t * fileNames) { char *s, *t; rpmRC res = RPMRC_OK; int allow_relative = (RPMFILE_PUBKEY|RPMFILE_DOC|RPMFILE_LICENSE); t = buf; while ((s = strtokWithQuotes(t, " \t\n")) != NULL) { t = NULL; /* Set flags for virtual file attributes */ if (vfaMatch(virtualAttrs, s, &(cur->attrFlags))) continue; /* normally paths need to be absolute */ if (*s != '/') { if (!(cur->attrFlags & allow_relative)) { rpmlog(RPMLOG_ERR, _("File must begin with \"/\": %s\n"), s); res = RPMRC_FAIL; continue; } /* non-absolute %doc and %license paths are special */ if (cur->attrFlags & (RPMFILE_DOC | RPMFILE_LICENSE)) cur->attrFlags |= RPMFILE_SPECIALDIR; } argvAdd(fileNames, s); } return res; } /** */ static int compareFileListRecs(const void * ap, const void * bp) { const char *a = ((FileListRec)ap)->cpioPath; const char *b = ((FileListRec)bp)->cpioPath; return strcmp(a, b); } /** * Test if file is located in a %docdir. * @param docDirs doc dirs * @param fileName file path * @return 1 if doc file, 0 if not */ static int isDoc(ARGV_const_t docDirs, const char * fileName) { size_t k, l; k = strlen(fileName); for (ARGV_const_t dd = docDirs; *dd; dd++) { l = strlen(*dd); if (l < k && rstreqn(fileName, *dd, l) && fileName[l] == '/') return 1; } return 0; } static int isHardLink(FileListRec flp, FileListRec tlp) { return ((S_ISREG(flp->fl_mode) && S_ISREG(tlp->fl_mode)) && ((flp->fl_nlink > 1) && (flp->fl_nlink == tlp->fl_nlink)) && (flp->fl_ino == tlp->fl_ino) && (flp->fl_dev == tlp->fl_dev)); } /** * Verify that file attributes scope over hardlinks correctly. * If partial hardlink sets are possible, then add tracking dependency. * @param fl package file records * @return 1 if partial hardlink sets can exist, 0 otherwise. */ static int checkHardLinks(FileRecords files) { FileListRec ilp, jlp; int i, j; for (i = 0; i < files->used; i++) { ilp = files->recs + i; if (!(S_ISREG(ilp->fl_mode) && ilp->fl_nlink > 1)) continue; for (j = i + 1; j < files->used; j++) { jlp = files->recs + j; if (isHardLink(ilp, jlp)) { return 1; } } } return 0; } static int seenHardLink(FileRecords files, FileListRec flp, rpm_ino_t *fileid) { for (FileListRec ilp = files->recs; ilp < flp; ilp++) { if (isHardLink(flp, ilp)) { *fileid = ilp - files->recs; return 1; } } return 0; } /** * Add file entries to header. * @todo Should directories have %doc/%config attributes? (#14531) * @todo Remove RPMTAG_OLDFILENAMES, add dirname/basename instead. * @param fl package file tree walk data * @retval *fip file info for package * @param h * @param isSrc */ static void genCpioListAndHeader(FileList fl, rpmfi * fip, Header h, int isSrc) { int _addDotSlash = !(isSrc || rpmExpandNumeric("%{_noPayloadPrefix}")); size_t apathlen = 0; size_t dpathlen = 0; size_t skipLen = 0; FileListRec flp; char buf[BUFSIZ]; int i; uint32_t defaultalgo = PGPHASHALGO_MD5, digestalgo; rpm_loff_t totalFileSize = 0; /* * See if non-md5 file digest algorithm is requested. If not * specified, quietly assume md5. Otherwise check if supported type. */ digestalgo = rpmExpandNumeric(isSrc ? "%{_source_filedigest_algorithm}" : "%{_binary_filedigest_algorithm}"); if (digestalgo == 0) { digestalgo = defaultalgo; } if (rpmDigestLength(digestalgo) == 0) { rpmlog(RPMLOG_WARNING, _("Unknown file digest algorithm %u, falling back to MD5\n"), digestalgo); digestalgo = defaultalgo; } /* Sort the big list */ qsort(fl->files.recs, fl->files.used, sizeof(*(fl->files.recs)), compareFileListRecs); /* Generate the header. */ if (! isSrc) { skipLen = 1; } for (i = 0, flp = fl->files.recs; i < fl->files.used; i++, flp++) { rpm_ino_t fileid = flp - fl->files.recs; /* Merge duplicate entries. */ while (i < (fl->files.used - 1) && rstreq(flp->cpioPath, flp[1].cpioPath)) { /* Two entries for the same file found, merge the entries. */ /* Note that an %exclude is a duplication of a file reference */ /* file flags */ flp[1].flags |= flp->flags; if (!(flp[1].flags & RPMFILE_EXCLUDE)) rpmlog(RPMLOG_WARNING, _("File listed twice: %s\n"), flp->cpioPath); /* file mode */ if (S_ISDIR(flp->fl_mode)) { if ((flp[1].specdFlags & (SPECD_DIRMODE | SPECD_DEFDIRMODE)) < (flp->specdFlags & (SPECD_DIRMODE | SPECD_DEFDIRMODE))) flp[1].fl_mode = flp->fl_mode; } else { if ((flp[1].specdFlags & (SPECD_FILEMODE | SPECD_DEFFILEMODE)) < (flp->specdFlags & (SPECD_FILEMODE | SPECD_DEFFILEMODE))) flp[1].fl_mode = flp->fl_mode; } /* uid */ if ((flp[1].specdFlags & (SPECD_UID | SPECD_DEFUID)) < (flp->specdFlags & (SPECD_UID | SPECD_DEFUID))) { flp[1].fl_uid = flp->fl_uid; flp[1].uname = flp->uname; } /* gid */ if ((flp[1].specdFlags & (SPECD_GID | SPECD_DEFGID)) < (flp->specdFlags & (SPECD_GID | SPECD_DEFGID))) { flp[1].fl_gid = flp->fl_gid; flp[1].gname = flp->gname; } /* verify flags */ if ((flp[1].specdFlags & (SPECD_VERIFY | SPECD_DEFVERIFY)) < (flp->specdFlags & (SPECD_VERIFY | SPECD_DEFVERIFY))) flp[1].verifyFlags = flp->verifyFlags; /* XXX to-do: language */ flp++; i++; } /* Skip files that were marked with %exclude. */ if (flp->flags & RPMFILE_EXCLUDE) continue; /* Omit '/' and/or URL prefix, leave room for "./" prefix */ apathlen += (strlen(flp->cpioPath) - skipLen + (_addDotSlash ? 3 : 1)); /* Leave room for both dirname and basename NUL's */ dpathlen += (strlen(flp->diskPath) + 2); /* * Make the header. Store the on-disk path to OLDFILENAMES for * cpio list generation purposes for now, final path temporarily * to ORIGFILENAMES, to be swapped later into OLDFILENAMES. */ headerPutString(h, RPMTAG_OLDFILENAMES, flp->diskPath); headerPutString(h, RPMTAG_ORIGFILENAMES, flp->cpioPath); headerPutString(h, RPMTAG_FILEUSERNAME, flp->uname); headerPutString(h, RPMTAG_FILEGROUPNAME, flp->gname); /* Only use 64bit filesizes tag if required. */ if (fl->largeFiles) { rpm_loff_t rsize64 = (rpm_loff_t)flp->fl_size; headerPutUint64(h, RPMTAG_LONGFILESIZES, &rsize64, 1); /* XXX TODO: add rpmlib() dependency for large files */ } else { rpm_off_t rsize32 = (rpm_off_t)flp->fl_size; headerPutUint32(h, RPMTAG_FILESIZES, &rsize32, 1); } /* Excludes and dupes have been filtered out by now. */ if (S_ISREG(flp->fl_mode)) { if (flp->fl_nlink == 1 || !seenHardLink(&fl->files, flp, &fileid)) { totalFileSize += flp->fl_size; } } /* * For items whose size varies between systems, always explicitly * cast to the header type before inserting. * TODO: check and warn if header type overflows for each case. */ { rpm_time_t rtime = (rpm_time_t) flp->fl_mtime; headerPutUint32(h, RPMTAG_FILEMTIMES, &rtime, 1); } { rpm_mode_t rmode = (rpm_mode_t) flp->fl_mode; headerPutUint16(h, RPMTAG_FILEMODES, &rmode, 1); } { rpm_rdev_t rrdev = (rpm_rdev_t) flp->fl_rdev; headerPutUint16(h, RPMTAG_FILERDEVS, &rrdev, 1); } /* * To allow rpmbuild to work on filesystems with 64bit inodes numbers, * remap them into 32bit integers based on filelist index, just * preserving semantics for determining hardlinks. * Start at 1 as inode zero as that could be considered as an error. * Since we flatten all the inodes to appear within a single fs, * we also need to flatten the devices. */ { rpm_ino_t rino = fileid + 1; rpm_dev_t rdev = flp->fl_dev ? 1 : 0; headerPutUint32(h, RPMTAG_FILEINODES, &rino, 1); headerPutUint32(h, RPMTAG_FILEDEVICES, &rdev, 1); } headerPutString(h, RPMTAG_FILELANGS, flp->langs); if (fl->haveCaps) { headerPutString(h, RPMTAG_FILECAPS, flp->caps); } buf[0] = '\0'; if (S_ISREG(flp->fl_mode)) (void) rpmDoDigest(digestalgo, flp->diskPath, 1, (unsigned char *)buf, NULL); headerPutString(h, RPMTAG_FILEDIGESTS, buf); buf[0] = '\0'; if (S_ISLNK(flp->fl_mode)) { ssize_t llen = readlink(flp->diskPath, buf, BUFSIZ-1); if (llen == -1) { rpmlog(RPMLOG_ERR, _("reading symlink %s failed: %s\n"), flp->diskPath, strerror(errno)); fl->processingFailed = 1; } else { buf[llen] = '\0'; if (buf[0] == '/' && !rstreq(fl->buildRoot, "/") && rstreqn(buf, fl->buildRoot, strlen(fl->buildRoot))) { rpmlog(RPMLOG_ERR, _("Symlink points to BuildRoot: %s -> %s\n"), flp->cpioPath, buf); fl->processingFailed = 1; } } } headerPutString(h, RPMTAG_FILELINKTOS, buf); if (flp->flags & RPMFILE_GHOST) { flp->verifyFlags &= ~(RPMVERIFY_FILEDIGEST | RPMVERIFY_FILESIZE | RPMVERIFY_LINKTO | RPMVERIFY_MTIME); } headerPutUint32(h, RPMTAG_FILEVERIFYFLAGS, &(flp->verifyFlags),1); if (!isSrc && isDoc(fl->docDirs, flp->cpioPath)) flp->flags |= RPMFILE_DOC; /* XXX Should directories have %doc/%config attributes? (#14531) */ if (S_ISDIR(flp->fl_mode)) flp->flags &= ~(RPMFILE_CONFIG|RPMFILE_DOC|RPMFILE_LICENSE); /* Strip internal parse data */ flp->flags &= PARSEATTR_MASK; headerPutUint32(h, RPMTAG_FILEFLAGS, &(flp->flags) ,1); } if (totalFileSize < UINT32_MAX) { rpm_off_t totalsize = totalFileSize; headerPutUint32(h, RPMTAG_SIZE, &totalsize, 1); } else { rpm_loff_t totalsize = totalFileSize; headerPutUint64(h, RPMTAG_LONGSIZE, &totalsize, 1); } if (digestalgo != defaultalgo) { headerPutUint32(h, RPMTAG_FILEDIGESTALGO, &digestalgo, 1); rpmlibNeedsFeature(h, "FileDigests", "4.6.0-1"); } if (fl->haveCaps) { rpmlibNeedsFeature(h, "FileCaps", "4.6.1-1"); } if (_addDotSlash) (void) rpmlibNeedsFeature(h, "PayloadFilesHavePrefix", "4.0-1"); { struct rpmtd_s filenames; rpmfiFlags flags = RPMFI_NOHEADER|RPMFI_NOFILEUSER|RPMFI_NOFILEGROUP; rpmfi fi; int fc; const char *fn; char *a, **apath; /* rpmfiNew() only groks compressed filelists */ headerConvert(h, HEADERCONV_COMPRESSFILELIST); fi = rpmfiNew(NULL, h, RPMTAG_BASENAMES, flags); if (fi == NULL) { fl->processingFailed = 1; return; } /* * Grab the real filenames from ORIGFILENAMES and put into OLDFILENAMES, * remove temporary cruft and side-effects from filelist compression * for rpmfiNew(). */ headerGet(h, RPMTAG_ORIGFILENAMES, &filenames, HEADERGET_ALLOC); headerDel(h, RPMTAG_ORIGFILENAMES); headerDel(h, RPMTAG_BASENAMES); headerDel(h, RPMTAG_DIRNAMES); headerDel(h, RPMTAG_DIRINDEXES); rpmtdSetTag(&filenames, RPMTAG_OLDFILENAMES); headerPut(h, &filenames, HEADERPUT_DEFAULT); /* Create hge-style archive path array, normally adding "./" */ fc = rpmtdCount(&filenames); apath = xmalloc(fc * sizeof(*apath) + apathlen + 1); a = (char *)(apath + fc); *a = '\0'; rpmtdInit(&filenames); for (int i = 0; (fn = rpmtdNextString(&filenames)); i++) { apath[i] = a; if (_addDotSlash) a = stpcpy(a, "./"); a = stpcpy(a, (fn + skipLen)); a++; /* skip apath NUL */ } fi->apath = apath; *fip = fi; rpmtdFreeData(&filenames); } /* Compress filelist unless legacy format requested */ if (!(fl->pkgFlags & RPMBUILD_PKG_NODIRTOKENS)) { headerConvert(h, HEADERCONV_COMPRESSFILELIST); /* Binary packages with dirNames cannot be installed by legacy rpm. */ (void) rpmlibNeedsFeature(h, "CompressedFileNames", "3.0.4-1"); } } static FileRecords FileRecordsFree(FileRecords files) { for (int i = 0; i < files->used; i++) { free(files->recs[i].diskPath); free(files->recs[i].cpioPath); free(files->recs[i].langs); free(files->recs[i].caps); } free(files->recs); return NULL; } static void FileListFree(FileList fl) { FileEntryFree(&(fl->cur)); FileEntryFree(&(fl->def)); FileRecordsFree(&(fl->files)); free(fl->buildRoot); argvFree(fl->docDirs); } /* forward ref */ static rpmRC recurseDir(FileList fl, const char * diskPath); /* Hack up a stat structure for a %dev or non-existing %ghost */ static struct stat * fakeStat(FileEntry cur, struct stat * statp) { time_t now = time(NULL); if (cur->devtype) { statp->st_rdev = ((cur->devmajor & 0xff) << 8) | (cur->devminor & 0xff); statp->st_dev = statp->st_rdev; statp->st_mode = (cur->devtype == 'b' ? S_IFBLK : S_IFCHR); } else { /* non-existing %ghost file or directory */ statp->st_mode = cur->isDir ? S_IFDIR : S_IFREG; /* can't recurse into non-existing directory */ if (cur->isDir) cur->isDir = 1; } statp->st_mode |= (cur->ar.ar_fmode & 0777); statp->st_atime = now; statp->st_mtime = now; statp->st_ctime = now; statp->st_nlink = 1; return statp; } /** * Add a file to the package manifest. * @param fl package file tree walk data * @param diskPath path to file * @param statp file stat (possibly NULL) * @return RPMRC_OK on success */ static rpmRC addFile(FileList fl, const char * diskPath, struct stat * statp) { size_t plen = strlen(diskPath); char buf[plen + 1]; const char *cpioPath; struct stat statbuf; mode_t fileMode; uid_t fileUid; gid_t fileGid; const char *fileUname; const char *fileGname; rpmRC rc = RPMRC_FAIL; /* assume failure */ /* Strip trailing slash. The special case of '/' path is handled below. */ if (plen > 0 && diskPath[plen - 1] == '/') { diskPath = strcpy(buf, diskPath); buf[plen - 1] = '\0'; } cpioPath = diskPath; /* Path may have prepended buildRoot, so locate the original filename. */ /* * XXX There are 3 types of entry into addFile: * * From diskUrl statp * ===================================================== * processBinaryFile path NULL * processBinaryFile glob result path NULL * myftw path stat * */ if (fl->buildRoot && !rstreq(fl->buildRoot, "/")) cpioPath += strlen(fl->buildRoot); /* XXX make sure '/' can be packaged also */ if (*cpioPath == '\0') cpioPath = "/"; /* * Unless recursing, we dont have stat() info at hand. Handle the * various cases, preserving historical behavior wrt %dev(): * - for %dev() entries we fake it up whether the file exists or not * - otherwise try to grab the data by lstat() * - %ghost entries might not exist, fake it up */ if (statp == NULL) { memset(&statbuf, 0, sizeof(statbuf)); if (fl->cur.devtype) { statp = fakeStat(&(fl->cur), &statbuf); } else if (lstat(diskPath, &statbuf) == 0) { statp = &statbuf; } else if (fl->cur.attrFlags & RPMFILE_GHOST) { statp = fakeStat(&(fl->cur), &statbuf); } else { int lvl = RPMLOG_ERR; const char *msg = fl->cur.isDir ? _("Directory not found: %s\n") : _("File not found: %s\n"); if (fl->cur.attrFlags & RPMFILE_EXCLUDE) { lvl = RPMLOG_WARNING; rc = RPMRC_OK; } rpmlog(lvl, msg, diskPath); goto exit; } } /* Don't recurse into explicit %dir, don't double-recurse from fts */ if ((fl->cur.isDir != 1) && (statp == &statbuf) && S_ISDIR(statp->st_mode)) { return recurseDir(fl, diskPath); } fileMode = statp->st_mode; fileUid = statp->st_uid; fileGid = statp->st_gid; /* Explicit %attr() always wins */ if (fl->cur.ar.ar_fmodestr != NULL) { fileMode &= S_IFMT; fileMode |= fl->cur.ar.ar_fmode; } else { /* ...but %defattr() for directories and files is different */ if (S_ISDIR(fileMode)) { if (fl->def.ar.ar_dmodestr) { fileMode &= S_IFMT; fileMode |= fl->def.ar.ar_dmode; } } else if (fl->def.ar.ar_fmodestr) { fileMode &= S_IFMT; fileMode |= fl->def.ar.ar_fmode; } } if (fl->cur.ar.ar_user) { fileUname = fl->cur.ar.ar_user; } else if (fl->def.ar.ar_user) { fileUname = fl->def.ar.ar_user; } else { fileUname = rpmugUname(fileUid); } if (fl->cur.ar.ar_group) { fileGname = fl->cur.ar.ar_group; } else if (fl->def.ar.ar_group) { fileGname = fl->def.ar.ar_group; } else { fileGname = rpmugGname(fileGid); } /* Default user/group to builder's user/group */ if (fileUname == NULL) fileUname = rpmugUname(getuid()); if (fileGname == NULL) fileGname = rpmugGname(getgid()); /* S_XXX macro must be consistent with type in find call at check-files script */ if (check_fileList && (S_ISREG(fileMode) || S_ISLNK(fileMode))) { appendStringBuf(check_fileList, diskPath); appendStringBuf(check_fileList, "\n"); } /* Add to the file list */ if (fl->files.used == fl->files.alloced) { fl->files.alloced += 128; fl->files.recs = xrealloc(fl->files.recs, fl->files.alloced * sizeof(*(fl->files.recs))); } { FileListRec flp = &fl->files.recs[fl->files.used]; flp->fl_st = *statp; /* structure assignment */ flp->fl_mode = fileMode; flp->fl_uid = fileUid; flp->fl_gid = fileGid; flp->cpioPath = xstrdup(cpioPath); flp->diskPath = xstrdup(diskPath); flp->uname = rpmugStashStr(fileUname); flp->gname = rpmugStashStr(fileGname); if (fl->cur.langs) { flp->langs = argvJoin(fl->cur.langs, "|"); } else { flp->langs = xstrdup(""); } if (fl->cur.caps) { flp->caps = fl->cur.caps; } else { flp->caps = xstrdup(""); } flp->flags = fl->cur.attrFlags; flp->specdFlags = fl->cur.specdFlags; flp->verifyFlags = fl->cur.verifyFlags; if (!(flp->flags & RPMFILE_EXCLUDE) && S_ISREG(flp->fl_mode)) { if (flp->fl_size >= UINT32_MAX) { fl->largeFiles = 1; } } } rc = RPMRC_OK; fl->files.used++; exit: if (rc != RPMRC_OK) fl->processingFailed = 1; return rc; } /** * Add directory (and all of its files) to the package manifest. * @param fl package file tree walk data * @param diskPath path to file * @return RPMRC_OK on success */ static rpmRC recurseDir(FileList fl, const char * diskPath) { char * ftsSet[2]; FTS * ftsp; FTSENT * fts; int myFtsOpts = (FTS_COMFOLLOW | FTS_NOCHDIR | FTS_PHYSICAL); rpmRC rc = RPMRC_FAIL; ftsSet[0] = (char *) diskPath; ftsSet[1] = NULL; ftsp = Fts_open(ftsSet, myFtsOpts, NULL); while ((fts = Fts_read(ftsp)) != NULL) { switch (fts->fts_info) { case FTS_D: /* preorder directory */ case FTS_F: /* regular file */ case FTS_SL: /* symbolic link */ case FTS_SLNONE: /* symbolic link without target */ case FTS_DEFAULT: /* none of the above */ rc = addFile(fl, fts->fts_accpath, fts->fts_statp); break; case FTS_DOT: /* dot or dot-dot */ case FTS_DP: /* postorder directory */ rc = RPMRC_OK; break; case FTS_NS: /* stat(2) failed */ case FTS_DNR: /* unreadable directory */ case FTS_ERR: /* error; errno is set */ case FTS_DC: /* directory that causes cycles */ case FTS_NSOK: /* no stat(2) requested */ case FTS_INIT: /* initialized only */ case FTS_W: /* whiteout object */ default: rc = RPMRC_FAIL; break; } if (rc) break; } (void) Fts_close(ftsp); return rc; } /** * Add a pubkey/icon to a binary package. * @param pkg * @param fl package file tree walk data * @param fileName path to file, relative is builddir, absolute buildroot. * @param tag tag to add * @return RPMRC_OK on success */ static rpmRC processMetadataFile(Package pkg, FileList fl, const char * fileName, rpmTagVal tag) { const char * buildDir = "%{_builddir}/%{?buildsubdir}/"; char * fn = NULL; char * apkt = NULL; uint8_t * pkt = NULL; ssize_t pktlen = 0; int absolute = 0; rpmRC rc = RPMRC_FAIL; int xx; if (*fileName == '/') { fn = rpmGenPath(fl->buildRoot, NULL, fileName); absolute = 1; } else fn = rpmGenPath(buildDir, NULL, fileName); switch (tag) { default: rpmlog(RPMLOG_ERR, _("%s: can't load unknown tag (%d).\n"), fn, tag); goto exit; break; case RPMTAG_PUBKEYS: { if ((xx = pgpReadPkts(fn, &pkt, (size_t *)&pktlen)) <= 0) { rpmlog(RPMLOG_ERR, _("%s: public key read failed.\n"), fn); goto exit; } if (xx != PGPARMOR_PUBKEY) { rpmlog(RPMLOG_ERR, _("%s: not an armored public key.\n"), fn); goto exit; } apkt = pgpArmorWrap(PGPARMOR_PUBKEY, pkt, pktlen); break; } } if (!apkt) { rpmlog(RPMLOG_ERR, _("%s: failed to encode\n"), fn); goto exit; } headerPutString(pkg->header, tag, apkt); rc = RPMRC_OK; if (absolute) rc = addFile(fl, fn, NULL); exit: free(apkt); free(pkt); free(fn); if (rc) { fl->processingFailed = 1; rc = RPMRC_FAIL; } return rc; } /** * Add a file to a binary package. * @param pkg * @param fl package file tree walk data * @param fileName file to add * @return RPMRC_OK on success */ static rpmRC processBinaryFile(Package pkg, FileList fl, const char * fileName) { int quote = 1; /* XXX permit quoted glob characters. */ int doGlob; char *diskPath = NULL; rpmRC rc = RPMRC_OK; size_t fnlen = strlen(fileName); int trailing_slash = (fnlen > 0 && fileName[fnlen-1] == '/'); /* XXX differentiate other directories from explicit %dir */ if (trailing_slash && !fl->cur.isDir) fl->cur.isDir = -1; doGlob = rpmIsGlob(fileName, quote); /* Check that file starts with leading "/" */ if (*fileName != '/') { rpmlog(RPMLOG_ERR, _("File needs leading \"/\": %s\n"), fileName); rc = RPMRC_FAIL; goto exit; } /* Copy file name or glob pattern removing multiple "/" chars. */ /* * Note: rpmGetPath should guarantee a "canonical" path. That means * that the following pathologies should be weeded out: * //bin//sh * //usr//bin/ * /.././../usr/../bin//./sh */ diskPath = rpmGenPath(fl->buildRoot, NULL, fileName); /* Arrange trailing slash on directories */ if (fl->cur.isDir) diskPath = rstrcat(&diskPath, "/"); if (doGlob) { ARGV_t argv = NULL; int argc = 0; int i; if (fl->cur.devtype) { rpmlog(RPMLOG_ERR, _("%%dev glob not permitted: %s\n"), diskPath); rc = RPMRC_FAIL; goto exit; } if (rpmGlob(diskPath, &argc, &argv) == 0 && argc >= 1) { for (i = 0; i < argc; i++) { rc = addFile(fl, argv[i], NULL); } argvFree(argv); } else { int lvl = RPMLOG_WARNING; const char *msg = (fl->cur.isDir) ? _("Directory not found by glob: %s\n") : _("File not found by glob: %s\n"); if (!(fl->cur.attrFlags & RPMFILE_EXCLUDE)) { lvl = RPMLOG_ERR; rc = RPMRC_FAIL; } rpmlog(lvl, msg, diskPath); goto exit; } } else { rc = addFile(fl, diskPath, NULL); } exit: free(diskPath); if (rc) { fl->processingFailed = 1; rc = RPMRC_FAIL; } return rc; } static rpmRC readFilesManifest(rpmSpec spec, Package pkg, const char *path) { char *fn, buf[BUFSIZ]; FILE *fd = NULL; rpmRC rc = RPMRC_FAIL; if (*path == '/') { fn = rpmGetPath(path, NULL); } else { fn = rpmGetPath("%{_builddir}/", (spec->buildSubdir ? spec->buildSubdir : "") , "/", path, NULL); } fd = fopen(fn, "r"); if (fd == NULL) { rpmlog(RPMLOG_ERR, _("Could not open %%files file %s: %m\n"), fn); goto exit; } while (fgets(buf, sizeof(buf), fd)) { handleComments(buf); if (expandMacros(spec, spec->macros, buf, sizeof(buf))) { rpmlog(RPMLOG_ERR, _("line: %s\n"), buf); goto exit; } argvAdd(&(pkg->fileList), buf); } if (ferror(fd)) rpmlog(RPMLOG_ERR, _("Error reading %%files file %s: %m\n"), fn); else rc = RPMRC_OK; exit: if (fd) fclose(fd); free(fn); return rc; } static char * getSpecialDocDir(Header h, rpmFlags sdtype) { const char *errstr = NULL; const char *dirtype = (sdtype == RPMFILE_DOC) ? "docdir" : "licensedir"; const char *fmt_default = "%{NAME}-%{VERSION}"; char *fmt_macro = rpmExpand("%{?_docdir_fmt}", NULL); char *fmt = NULL; char *res = NULL; if (fmt_macro && strlen(fmt_macro) > 0) { fmt = headerFormat(h, fmt_macro, &errstr); if (errstr) { rpmlog(RPMLOG_WARNING, _("illegal _docdir_fmt %s: %s\n"), fmt_macro, errstr); } } if (fmt == NULL) fmt = headerFormat(h, fmt_default, &errstr); res = rpmGetPath("%{_", dirtype, "}/", fmt, NULL); free(fmt); free(fmt_macro); return res; } static specialDir specialDirNew(Header h, rpmFlags sdtype, AttrRec ar, AttrRec def_ar) { specialDir sd = xcalloc(1, sizeof(*sd)); dupAttrRec(ar, &(sd->ar)); dupAttrRec(def_ar, &(sd->def_ar)); sd->dirname = getSpecialDocDir(h, sdtype); sd->sdtype = sdtype; return sd; } static specialDir specialDirFree(specialDir sd) { if (sd) { argvFree(sd->files); freeAttrRec(&(sd->ar)); freeAttrRec(&(sd->def_ar)); free(sd->dirname); free(sd); } return NULL; } static void processSpecialDir(rpmSpec spec, Package pkg, FileList fl, specialDir sd, int install, int test) { const char *sdenv = (sd->sdtype == RPMFILE_DOC) ? "DOCDIR" : "LICENSEDIR"; const char *sdname = (sd->sdtype == RPMFILE_DOC) ? "%doc" : "%license"; char *mkdocdir = rpmExpand("%{__mkdir_p} $", sdenv, NULL); StringBuf docScript = newStringBuf(); appendStringBuf(docScript, sdenv); appendStringBuf(docScript, "=$RPM_BUILD_ROOT"); appendLineStringBuf(docScript, sd->dirname); appendStringBuf(docScript, "export "); appendLineStringBuf(docScript, sdenv); appendLineStringBuf(docScript, mkdocdir); for (ARGV_const_t fn = sd->files; fn && *fn; fn++) { /* Quotes would break globs, escape spaces instead */ char *efn = rpmEscapeSpaces(*fn); appendStringBuf(docScript, "cp -pr "); appendStringBuf(docScript, efn); appendStringBuf(docScript, " $"); appendLineStringBuf(docScript, sdenv); free(efn); } if (install) { rpmRC rc = doScript(spec, RPMBUILD_STRINGBUF, sdname, getStringBuf(docScript), test); if (rc && rpmExpandNumeric("%{?_missing_doc_files_terminate_build}")) fl->processingFailed = 1; } /* Reset for %doc */ FileEntryFree(&fl->cur); fl->cur.attrFlags |= sd->sdtype; fl->cur.verifyFlags = fl->def.verifyFlags; dupAttrRec(&(sd->ar), &(fl->cur.ar)); dupAttrRec(&(sd->def_ar), &(fl->def.ar)); (void) processBinaryFile(pkg, fl, sd->dirname); freeStringBuf(docScript); free(mkdocdir); } static rpmRC processPackageFiles(rpmSpec spec, rpmBuildPkgFlags pkgFlags, Package pkg, int installSpecialDoc, int test) { struct FileList_s fl; ARGV_t fileNames = NULL; specialDir specialDoc = NULL; specialDir specialLic = NULL; pkg->cpioList = NULL; for (ARGV_const_t fp = pkg->fileFile; fp && *fp != NULL; fp++) { if (readFilesManifest(spec, pkg, *fp)) return RPMRC_FAIL; } /* Init the file list structure */ memset(&fl, 0, sizeof(fl)); /* XXX spec->buildRoot == NULL, then xstrdup("") is returned */ fl.buildRoot = rpmGenPath(spec->rootDir, spec->buildRoot, NULL); dupAttrRec(&root_ar, &fl.def.ar); /* XXX assume %defattr(-,root,root) */ fl.def.verifyFlags = RPMVERIFY_ALL; fl.pkgFlags = pkgFlags; { char *docs = rpmGetPath("%{?__docdir_path}", NULL); argvSplit(&fl.docDirs, docs, ":"); free(docs); } for (ARGV_const_t fp = pkg->fileList; *fp != NULL; fp++) { char buf[strlen(*fp) + 1]; const char *s = *fp; SKIPSPACE(s); if (*s == '\0') continue; fileNames = argvFree(fileNames); rstrlcpy(buf, s, sizeof(buf)); /* Reset for a new line in %files */ FileEntryFree(&fl.cur); /* turn explicit flags into %def'd ones (gosh this is hacky...) */ fl.cur.specdFlags = ((unsigned)fl.def.specdFlags) >> 8; fl.cur.verifyFlags = fl.def.verifyFlags; if (parseForVerify(buf, 0, &fl.cur) || parseForVerify(buf, 1, &fl.def) || parseForAttr(buf, 0, &fl.cur) || parseForAttr(buf, 1, &fl.def) || parseForDev(buf, &fl.cur) || parseForConfig(buf, &fl.cur) || parseForLang(buf, &fl.cur) || parseForCaps(buf, &fl.cur) || parseForSimple(buf, &fl.cur, &fileNames)) { fl.processingFailed = 1; continue; } for (ARGV_const_t fn = fileNames; fn && *fn; fn++) { if (fl.cur.attrFlags & RPMFILE_SPECIALDIR) { rpmFlags oattrs = (fl.cur.attrFlags & ~RPMFILE_SPECIALDIR); specialDir *sdp = NULL; if (oattrs == RPMFILE_DOC) { sdp = &specialDoc; } else if (oattrs == RPMFILE_LICENSE) { sdp = &specialLic; } if (sdp == NULL || **fn == '/') { rpmlog(RPMLOG_ERR, _("Can't mix special %s with other forms: %s\n"), (oattrs & RPMFILE_DOC) ? "%doc" : "%license", *fn); fl.processingFailed = 1; continue; } /* save attributes on first special doc/license for later use */ if (*sdp == NULL) { *sdp = specialDirNew(pkg->header, oattrs, &fl.cur.ar, &fl.def.ar); } argvAdd(&(*sdp)->files, *fn); continue; } /* this is now an artificial limitation */ if (fn != fileNames) { rpmlog(RPMLOG_ERR, _("More than one file on a line: %s\n"),*fn); fl.processingFailed = 1; continue; } if (fl.cur.attrFlags & RPMFILE_DOCDIR) { argvAdd(&(fl.docDirs), *fn); } else if (fl.cur.attrFlags & RPMFILE_PUBKEY) { (void) processMetadataFile(pkg, &fl, *fn, RPMTAG_PUBKEYS); } else { if (fl.cur.attrFlags & RPMFILE_DIR) fl.cur.isDir = 1; (void) processBinaryFile(pkg, &fl, *fn); } } if (fl.cur.caps) fl.haveCaps = 1; } /* Now process special docs and licenses if present */ if (specialDoc) processSpecialDir(spec, pkg, &fl, specialDoc, installSpecialDoc, test); if (specialLic) processSpecialDir(spec, pkg, &fl, specialLic, installSpecialDoc, test); if (fl.processingFailed) goto exit; /* Verify that file attributes scope over hardlinks correctly. */ if (checkHardLinks(&fl.files)) (void) rpmlibNeedsFeature(pkg->header, "PartialHardlinkSets", "4.0.4-1"); genCpioListAndHeader(&fl, &pkg->cpioList, pkg->header, 0); exit: FileListFree(&fl); specialDirFree(specialDoc); specialDirFree(specialLic); return fl.processingFailed ? RPMRC_FAIL : RPMRC_OK; } static void genSourceRpmName(rpmSpec spec) { if (spec->sourceRpmName == NULL) { char *nvr = headerGetAsString(spec->packages->header, RPMTAG_NVR); rasprintf(&spec->sourceRpmName, "%s.%ssrc.rpm", nvr, spec->noSource ? "no" : ""); free(nvr); } } rpmRC processSourceFiles(rpmSpec spec, rpmBuildPkgFlags pkgFlags) { struct Source *srcPtr; struct FileList_s fl; ARGV_t files = NULL; Package pkg; static char *_srcdefattr; static int oneshot; if (!oneshot) { _srcdefattr = rpmExpand("%{?_srcdefattr}", NULL); if (_srcdefattr && !*_srcdefattr) _srcdefattr = _free(_srcdefattr); oneshot = 1; } genSourceRpmName(spec); /* Construct the file list and source entries */ argvAdd(&files, spec->specFile); for (srcPtr = spec->sources; srcPtr != NULL; srcPtr = srcPtr->next) { char * sfn = rpmGetPath( ((srcPtr->flags & RPMBUILD_ISNO) ? "!" : ""), "%{_sourcedir}/", srcPtr->source, NULL); argvAdd(&files, sfn); free(sfn); } for (pkg = spec->packages; pkg != NULL; pkg = pkg->next) { for (srcPtr = pkg->icon; srcPtr != NULL; srcPtr = srcPtr->next) { char * sfn; sfn = rpmGetPath( ((srcPtr->flags & RPMBUILD_ISNO) ? "!" : ""), "%{_sourcedir}/", srcPtr->source, NULL); argvAdd(&files, sfn); free(sfn); } } spec->sourceCpioList = NULL; /* Init the file list structure */ memset(&fl, 0, sizeof(fl)); if (_srcdefattr) { char *a = rstrscat(NULL, "%defattr ", _srcdefattr, NULL); parseForAttr(a, 1, &fl.def); free(a); } fl.files.alloced = spec->numSources + 1; fl.files.recs = xcalloc(fl.files.alloced, sizeof(*fl.files.recs)); fl.pkgFlags = pkgFlags; for (ARGV_const_t fp = files; *fp != NULL; fp++) { const char *diskPath = *fp; char *tmp; FileListRec flp; SKIPSPACE(diskPath); if (! *diskPath) continue; flp = &fl.files.recs[fl.files.used]; /* The first source file is the spec file */ flp->flags = (fl.files.used == 0) ? RPMFILE_SPECFILE : 0; /* files with leading ! are no source files */ if (*diskPath == '!') { flp->flags |= RPMFILE_GHOST; diskPath++; } tmp = xstrdup(diskPath); /* basename() might modify */ flp->diskPath = xstrdup(diskPath); flp->cpioPath = xstrdup(basename(tmp)); flp->verifyFlags = RPMVERIFY_ALL; free(tmp); if (stat(diskPath, &flp->fl_st)) { rpmlog(RPMLOG_ERR, _("Bad file: %s: %s\n"), diskPath, strerror(errno)); fl.processingFailed = 1; } else { if (S_ISREG(flp->fl_mode) && flp->fl_size >= UINT32_MAX) fl.largeFiles = 1; } if (fl.def.ar.ar_fmodestr) { flp->fl_mode &= S_IFMT; flp->fl_mode |= fl.def.ar.ar_fmode; } if (fl.def.ar.ar_user) { flp->uname = rpmugStashStr(fl.def.ar.ar_user); } else { flp->uname = rpmugStashStr(rpmugUname(flp->fl_uid)); } if (fl.def.ar.ar_group) { flp->gname = rpmugStashStr(fl.def.ar.ar_group); } else { flp->gname = rpmugStashStr(rpmugGname(flp->fl_gid)); } flp->langs = xstrdup(""); if (! (flp->uname && flp->gname)) { rpmlog(RPMLOG_ERR, _("Bad owner/group: %s\n"), diskPath); fl.processingFailed = 1; } fl.files.used++; } argvFree(files); if (! fl.processingFailed) { if (spec->sourceHeader != NULL) { genCpioListAndHeader(&fl, &spec->sourceCpioList, spec->sourceHeader, 1); } } FileListFree(&fl); return fl.processingFailed ? RPMRC_FAIL : RPMRC_OK; } /** * Check packaged file list against what's in the build root. * @param fileList packaged file list * @return -1 if skipped, 0 on OK, 1 on error */ static int checkFiles(const char *buildRoot, StringBuf fileList) { static char * const av_ckfile[] = { "%{?__check_files}", NULL }; StringBuf sb_stdout = NULL; int rc = -1; char * s = rpmExpand(av_ckfile[0], NULL); if (!(s && *s)) goto exit; rpmlog(RPMLOG_NOTICE, _("Checking for unpackaged file(s): %s\n"), s); rc = rpmfcExec(av_ckfile, fileList, &sb_stdout, 0, buildRoot); if (rc < 0) goto exit; if (sb_stdout) { int _unpackaged_files_terminate_build = rpmExpandNumeric("%{?_unpackaged_files_terminate_build}"); const char * t = getStringBuf(sb_stdout); if ((*t != '\0') && (*t != '\n')) { rc = (_unpackaged_files_terminate_build) ? 1 : 0; rpmlog((rc ? RPMLOG_ERR : RPMLOG_WARNING), _("Installed (but unpackaged) file(s) found:\n%s"), t); } } exit: freeStringBuf(sb_stdout); free(s); return rc; } #if HAVE_GELF_H && HAVE_LIBELF /* Query the build-id from the ELF file NAME and store it in the newly allocated *build_id array of size *build_id_size. Returns -1 on error. */ int getELFBuildId (const char *name, unsigned char **id, size_t *id_size) { int fd, i; Elf *elf; GElf_Ehdr ehdr; Elf_Data *build_id = NULL; size_t build_id_offset = 0, build_id_size = 0; /* Now query the build-id of the file and add the corresponding links in the .build-id tree. The following code is based on tools/debugedit.c. */ fd = open (name, O_RDONLY); if (fd < 0) return -1; elf = elf_begin (fd, ELF_C_READ_MMAP, NULL); if (elf == NULL) { fprintf (stderr, "cannot open ELF file: %s", elf_errmsg (-1)); close (fd); return -1; } if (elf_kind (elf) != ELF_K_ELF || gelf_getehdr (elf, &ehdr) == NULL || (ehdr.e_type != ET_DYN && ehdr.e_type != ET_EXEC && ehdr.e_type != ET_REL)) { elf_end (elf); close (fd); return -1; } for (i = 0; i < ehdr.e_shnum; ++i) { Elf_Scn *s = elf_getscn (elf, i); GElf_Shdr shdr; Elf_Data *data; Elf32_Nhdr nh; Elf_Data dst = { .d_version = EV_CURRENT, .d_type = ELF_T_NHDR, .d_buf = &nh, .d_size = sizeof nh }; Elf_Data src = dst; gelf_getshdr (s, &shdr); if (shdr.sh_type != SHT_NOTE || !(shdr.sh_flags & SHF_ALLOC)) continue; /* Look for a build-ID note here. */ data = elf_rawdata (s, NULL); src.d_buf = data->d_buf; assert (sizeof (Elf32_Nhdr) == sizeof (Elf64_Nhdr)); while (data->d_buf + data->d_size - src.d_buf > (int) sizeof nh && elf32_xlatetom (&dst, &src, ehdr.e_ident[EI_DATA])) { Elf32_Word len = sizeof nh + nh.n_namesz; len = (len + 3) & ~3; if (nh.n_namesz == sizeof "GNU" && nh.n_type == 3 && !memcmp (src.d_buf + sizeof nh, "GNU", sizeof "GNU")) { build_id = data; build_id_offset = src.d_buf + len - data->d_buf; build_id_size = nh.n_descsz; break; } len += nh.n_descsz; len = (len + 3) & ~3; src.d_buf += len; } if (build_id != NULL) break; } if (build_id == NULL) return -1; *id = malloc (build_id_size); *id_size = build_id_size; memcpy (*id, build_id->d_buf + build_id_offset, build_id_size); elf_end (elf); close (fd); return 0; } static rpmTag copyTagsForDebug[] = { RPMTAG_EPOCH, RPMTAG_VERSION, RPMTAG_RELEASE, RPMTAG_LICENSE, RPMTAG_PACKAGER, RPMTAG_DISTRIBUTION, RPMTAG_DISTURL, RPMTAG_VENDOR, RPMTAG_ICON, RPMTAG_URL, RPMTAG_CHANGELOGTIME, RPMTAG_CHANGELOGNAME, RPMTAG_CHANGELOGTEXT, RPMTAG_PREFIXES, RPMTAG_RHNPLATFORM, RPMTAG_OS, RPMTAG_DISTTAG, RPMTAG_CVSID, RPMTAG_ARCH, 0 }; static void addDebuginfoPackage(rpmSpec spec, Package pkg, char *buildroot) { const char *a; elf_version(EV_CURRENT); a = headerGetString(pkg->header, RPMTAG_ARCH); if (strcmp(a, "noarch") != 0 && strcmp(a, "src") != 0 && strcmp(a, "nosrc") != 0) { Package dbg; rpmfi fi = pkg->cpioList; char tmp[1024]; const char *name; ARGV_t files = NULL; int seen_build_id = 0; /* Check if the current package has files with debug info and record them. */ fi = rpmfiInit (fi, 0); while (rpmfiNext (fi) >= 0) { const char *base; int i; unsigned char *build_id; size_t build_id_size = 0; struct stat sbuf; name = rpmfiFN (fi); /* Skip leading buildroot. */ base = name + strlen (buildroot); /* Pre-pend %buildroot/usr/lib/debug and append .debug. */ snprintf (tmp, 1024, "%s/usr/lib/debug%s.debug", buildroot, base); /* If that file exists we have debug information for it. */ if (access (tmp, F_OK) != 0) continue; /* Append the file list preamble. */ if (!files) { argvAdd(&files, "%defattr(-,root,root)"); argvAdd(&files, "%dir /usr/lib/debug"); } /* Add the files main debug-info file. */ snprintf (tmp, 1024, "/usr/lib/debug/%s.debug", base); argvAdd(&files, tmp); /* Do not bother to check build-ids for symbolic links. We'll handle them for the link target. */ if (lstat (name, &sbuf) == -1 || S_ISLNK (sbuf.st_mode)) continue; /* Try to gather the build-id from the binary. */ if (getELFBuildId (name, &build_id, &build_id_size) == -1) continue; /* If we see build-id links for the first time add the directory. */ if (!seen_build_id) argvAdd(&files, "%dir /usr/lib/debug/.build-id"); /* From the build-id construct the two links pointing back to the debug information file and the binary. */ snprintf (tmp, 1024, "/usr/lib/debug/.build-id/%02x/", build_id[0]); for (i = 1; i < build_id_size; ++i) sprintf (tmp + strlen (tmp), "%02x", build_id[i]); argvAdd(&files, tmp); sprintf (tmp + strlen (tmp), ".debug"); argvAdd(&files, tmp); free (build_id); } /* If there are debuginfo files for this package add a new debuginfo package. */ if (files) { dbg = newPackage (spec); headerNVR (pkg->header, &name, NULL, NULL); /* Set name, summary and group. */ snprintf (tmp, 1024, "%s-debuginfo", name); headerPutString(dbg->header, RPMTAG_NAME, tmp); snprintf (tmp, 1024, "Debug information for package %s", name); headerPutString(dbg->header, RPMTAG_SUMMARY, tmp); snprintf (tmp, 1024, "This package provides debug information for package %s.\n" "Debug information is useful when developing applications that use this\n" "package or when debugging this package.", name); headerPutString(dbg->header, RPMTAG_DESCRIPTION, tmp); headerPutString(dbg->header, RPMTAG_GROUP, "Development/Debug"); /* Inherit other tags from parent. */ headerCopyTags (pkg->header, dbg->header, copyTagsForDebug); /* Build up the files list. */ dbg->fileList = files; } } } #endif rpmRC processBinaryFiles(rpmSpec spec, rpmBuildPkgFlags pkgFlags, int installSpecialDoc, int test) { Package pkg; rpmRC rc = RPMRC_OK; char *buildroot; check_fileList = newStringBuf(); buildroot = rpmGenPath(spec->rootDir, spec->buildRoot, NULL); genSourceRpmName(spec); for (pkg = spec->packages; pkg != NULL; pkg = pkg->next) { char *nvr; const char *a; int header_color; int arch_color; if (pkg->fileList == NULL) continue; headerPutString(pkg->header, RPMTAG_SOURCERPM, spec->sourceRpmName); nvr = headerGetAsString(pkg->header, RPMTAG_NVRA); rpmlog(RPMLOG_NOTICE, _("Processing files: %s\n"), nvr); free(nvr); if ((rc = processPackageFiles(spec, pkgFlags, pkg, installSpecialDoc, test)) != RPMRC_OK) goto exit; #if HAVE_GELF_H && HAVE_LIBELF addDebuginfoPackage(spec, pkg, buildroot); #endif if ((rc = rpmfcGenerateDepends(spec, pkg)) != RPMRC_OK) goto exit; a = headerGetString(pkg->header, RPMTAG_ARCH); header_color = headerGetNumber(pkg->header, RPMTAG_HEADERCOLOR); if (!rstreq(a, "noarch")) { arch_color = rpmGetArchColor(a); if (arch_color > 0 && header_color > 0 && !(arch_color & header_color)) { rpmlog(RPMLOG_WARNING, _("Binaries arch (%d) not matching the package arch (%d).\n"), header_color, arch_color); } } else if (header_color != 0) { int terminate = rpmExpandNumeric("%{?_binaries_in_noarch_packages_terminate_build}"); rpmlog(terminate ? RPMLOG_ERR : RPMLOG_WARNING, _("Arch dependent binaries in noarch package\n")); if (terminate) { rc = RPMRC_FAIL; goto exit; } } } /* Now we have in fileList list of files from all packages. * We pass it to a script which does the work of finding missing * and duplicated files. */ if (checkFiles(spec->buildRoot, check_fileList) > 0) { rc = RPMRC_FAIL; } exit: check_fileList = freeStringBuf(check_fileList); return rc; }