can1357/oh-my-pi · error · ArchiveError

GNU multi-volume tar members are not supported

Error message

GNU multi-volume tar members are not supported

What it means

Thrown when a tar header has GNU type flag 'M', which marks a continuation member from a GNU multi-volume (split) archive. This library indexes single-volume archives only and does not implement splicing member data across volumes, so it rejects the archive explicitly instead of producing a member with missing bytes. The archive itself may be perfectly valid multi-volume tar — it is just out of scope for this parser.

Source

Thrown at packages/utils/src/ar/tar.ts:486

			offset += dataBlocks;
			continue;
		}
		if (typeFlag === "N") {
			applyOldGnuNameRecords(data, entries, pendingLinks, limits);
			offset += dataBlocks;
			continue;
		}
		if (typeFlag === "x" || typeFlag === "X") {
			localPax = parsePaxRecords(data, limits);
			offset += dataBlocks;
			continue;
		}
		if (typeFlag === "g") {
			applyGlobalPax(globalPax, parsePaxRecords(data, limits));
			offset += dataBlocks;
			continue;
		}
		if (typeFlag === "M") throw new ArchiveError("GNU multi-volume tar members are not supported");

		if (longName !== undefined) name = longName;
		if (longLink !== undefined) linkName = longLink;
		const paxPath = paxAttribute(globalPax, localPax, "path");
		if (paxPath !== undefined) name = paxPath;
		const paxLinkPath = paxAttribute(globalPax, localPax, "linkpath");
		if (paxLinkPath !== undefined) linkName = paxLinkPath;
		const paxSize = paxAttribute(globalPax, localPax, "size");
		if (paxSize !== undefined) size = parsePaxSize(paxSize, "member size");
		const paxSparseName = paxAttribute(globalPax, localPax, "GNU.sparse.name");
		if (paxSparseName !== undefined) name = paxSparseName;
		let displaySize = size;
		const paxSparseRealSize = paxAttribute(globalPax, localPax, "GNU.sparse.realsize");
		if (paxSparseRealSize !== undefined) displaySize = parsePaxSize(paxSparseRealSize, "sparse real size");
		const sparse = typeFlag === "S" || paxDeclaresSparse(globalPax, localPax);
		if (typeFlag === "S" && buffer[headerOffset + GNU_SPARSE_ISEXTENDED_OFFSET] === 1) {
			let extended = true;
			while (extended) {

View on GitHub (pinned to 9690622007)

Solutions

  1. Re-create the archive without multi-volume mode: `tar -cf out.tar dir/` (split afterwards with plain file splitting if size-limited).
  2. Concatenate volumes only works for raw-split files, not true -M archives; use GNU tar to extract fully first (`tar -xMf vol1 -M -f vol2`) and repack as a single tar.
  3. Use a tar library that supports multi-volume archives for the read side.
  4. If you control the producer, switch to per-chunk archives (chunk1.tar, chunk2.tar) and parse each independently.

Example fix

// before
tar -M -L 1G -F split-script -cf backup.tar data/
// after: single archive, split as plain bytes if needed
tar -cf backup.tar data/
split -b 1G backup.tar backup.tar.part-
Defensive patterns

Strategy: fallback

Validate before calling

// peek first volume headers for GNU multi-volume flag 'M' before parsing
function usesMultiVolume(bytes) {
  for (let off = 0; off + 512 <= bytes.byteLength; off += 512) {
    if (bytes.every.call(bytes.subarray(off, off + 512), (b) => b === 0)) return false;
    if (String.fromCharCode(bytes[off + 156]) === 'M') return true;
    const size = parseInt(bytes.subarray(off + 124, off + 136).toString().replace(/[^0-7]/g, ''), 8) || 0;
    off += Math.ceil(size / 512) * 512; // skip data blocks
  }
  return false;
}

Try / catch

try {
  return readTarEntriesFromBuffer(bytes, { limits });
} catch (err) {
  if (err instanceof ArchiveError && /multi-volume/.test(err.message)) {
    const merged = await mergeMultiVolumeWithGnuTar(volumePaths); // tar -xM then repack
    return readTarEntriesFromBuffer(merged, { limits });
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling readTarEntriesFromBuffer/readTar on an archive created with `tar -M -L <size> -F` (multi-volume) or GNU split backups, at the first header belonging to volume 2+.

Common situations: Old GNU backup scripts (level-0 dumps with -M), tapes split across media converted to files, or someone splitting a tar with an OS-aware splitter that emits proper multi-volume headers rather than raw byte cuts.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/46771fd832746e98. Report an issue: GitHub.