build/pack.c: pre-calculate cpio archive size, fail gracefully on >= 4G

The nearest goal is to be able to package files up to 4G, on both 32-bit
64-bit architectures, provided that cpio payload size also does not
exceed 4G.  More specifically, the limit is 2^32-2, while 2^32-1 remains
special (this is not my own invention, I adopt it from rpm.org).

If the limit is exceeded, rpmbuild should fail gracefully instead of
writing malformed packages with truncated integer tags.

(Supporting 4G+ files is a distant goal - neither rpm-4.0 nor apt-rpm
can handle them yet.  Besides, it's not clear if producing such big
packages is even a good idea, unless one wishes to package p0rn.)
This commit is contained in:
Alexey Tourbin 2018-01-29 02:13:14 +03:00
parent a1a075caa2
commit d85641818b
2 changed files with 61 additions and 8 deletions

View File

@ -2023,9 +2023,8 @@ static int finalizeSize(TFI_t fi)
return 0;
int totalFileSize = 0;
int partialHardlinkSets = 0;
int i, j;
for (i = 0; i < fi->fc; i++) {
if (fi->actions[i] == FA_SKIP) // GHOST
for (int i = 0; i < fi->fc; i++) {
if (fi->actions[i] == FA_SKIP) // %ghost
continue;
if (!S_ISREG(fi->fsts[i].st_mode))
continue;
@ -2035,7 +2034,8 @@ static int finalizeSize(TFI_t fi)
}
assert(fi->fsts[i].st_nlink > 1);
int found = 0;
for (j = 0; j < i; j++) {
// Look backwards, more likely in the same dir.
for (int j = i - 1; j >= 0; j--) {
if (fi->actions[j] == FA_SKIP)
continue;
if (fi->fsts[i].st_dev != fi->fsts[j].st_dev)
@ -2050,7 +2050,7 @@ static int finalizeSize(TFI_t fi)
// first hardlink occurrence
totalFileSize += fi->fsts[i].st_size;
int nlink = 1;
for (j = i + 1; j < fi->fc; j++) {
for (int j = i + 1; j < fi->fc; j++) {
if (fi->actions[j] == FA_SKIP)
continue;
if (fi->fsts[i].st_dev != fi->fsts[j].st_dev)

View File

@ -380,6 +380,41 @@ static int rpmLeadVersion(void)
return rpmlead_version;
}
// Estimate the uncompressed size of cpio archive before it is actually written.
static uint64_t calcArchiveSize(TFI_t fi)
{
uint64_t size = 124; // cpio trailer
for (int i = 0; i < fi->fc; i++) {
if (fi->actions[i] == FA_SKIP) // %ghost
continue;
size += 110; // cpio header
size += strlen(fi->apath[i]) + 1;
size = (size + 3) & ~(uint64_t)3;
if (!S_ISREG(fi->fsts[i].st_mode) && !S_ISLNK(fi->fsts[i].st_mode))
continue;
if (fi->fsts[i].st_nlink > 1) {
// Only the first hardlink occurrence counts.
int found = 0;
for (int j = i - 1; j >= 0; j--) {
if (fi->actions[j] == FA_SKIP)
continue;
if (fi->fsts[i].st_dev != fi->fsts[j].st_dev)
continue;
if (fi->fsts[i].st_ino != fi->fsts[j].st_ino)
continue;
found = 1;
break;
}
if (found)
continue;
}
// Valid for symlinks, target not null-terminated.
size += fi->fsts[i].st_size;
size = (size + 3) & ~(uint64_t)3;
}
return size;
}
int writeRPM(Header *hdrp, const char *fileName, int type,
CSA_t csa, char *passPhrase, const char **cookie)
{
@ -394,6 +429,13 @@ int writeRPM(Header *hdrp, const char *fileName, int type,
Header sig = NULL;
int rc = 0;
uint64_t archiveSize = calcArchiveSize(csa->cpioList);
if (archiveSize >= UINT32_MAX) { // sic, UINT32_MAX proper is kind of special
rpmError(RPMERR_FWRITE, "cpio archive too big - %uM\n",
(unsigned)(archiveSize>>20));
return RPMERR_FWRITE;
}
/* Transfer header reference form *hdrp to h. */
h = headerLink(*hdrp);
*hdrp = headerFree(*hdrp);
@ -516,10 +558,21 @@ int writeRPM(Header *hdrp, const char *fileName, int type,
* archive size within the header region.
*/
if (Fileno(csa->cpioFdIn) < 0) {
if (archiveSize != csa->cpioArchiveSize) {
rpmError(RPMERR_FWRITE, "wrong archive size: %" PRIu64 " -> %u\n",
archiveSize, csa->cpioArchiveSize);
rc = RPMERR_FWRITE;
goto exit;
}
HGE_t hge = (HGE_t)headerGetEntryMinMemory;
int_32 * archiveSize;
if (hge(h, RPMTAG_ARCHIVESIZE, NULL, (void *)&archiveSize, NULL))
*archiveSize = csa->cpioArchiveSize;
uint32_t *sizep;
if (hge(h, RPMTAG_ARCHIVESIZE, NULL, (void *)&sizep, NULL))
*sizep = csa->cpioArchiveSize;
else {
rpmError(RPMERR_FWRITE, "cannot add RPMTAG_ARCHIVESIZE\n");
rc = RPMERR_FWRITE;
goto exit;
}
}
(void) Fflush(fd);