From: Aaron Burrow Date: Thu, 9 Apr 2026 19:06:13 +0000 (+0000) Subject: patch 9.2.0325: runtime(tar): bug in zstd handling X-Git-Tag: v9.2.0325^0 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=00285c035aa7d92971bc50b7b124e1408dda53ad;p=thirdparty%2Fvim.git patch 9.2.0325: runtime(tar): bug in zstd handling Problem: patch 9.2.0325: runtime(tar): bug in zstd handling Solution: use correct --zstd argument, separated from other arguments, rework testing framework (Aaron Burrow). The tar.vim plugin allows vim to read and manipulate zstd archives, but it had a bug that caused extraction attempts to fail. Specifically, if the archive has a .tar.zst or .tzst extension, then the code was generating invalid extraction commands that looked like this: tar --zstdpxf foo.tar.zst foo When they should be like this: tar --zstd -pxf foo.tar.zst foo This patch changes the flag manipulation logic so that --zstd isn't glued to pxf. The labor for this change was divided between ChatGPT 5.4 and me. ChatGPT 5.4 identified the issue (from a code scan?), and I wrote the patch and tested vim. related: #19930 Signed-off-by: Aaron Burrow Signed-off-by: Christian Brabandt --- diff --git a/runtime/autoload/tar.vim b/runtime/autoload/tar.vim index 110327e95b..b068bd181b 100644 --- a/runtime/autoload/tar.vim +++ b/runtime/autoload/tar.vim @@ -21,6 +21,7 @@ " 2026 Feb 06 by Vim Project: consider 'nowrapscan' (#19333) " 2026 Feb 07 by Vim Project: make the path traversal detection more robust (#19341) " 2026 Apr 06 by Vim Project: fix bugs with lz4 support (#19925) +" 2026 Apr 09 by Vim Project: fix bugs with zstd support (#19930) " " Contains many ideas from Michael Toren's " @@ -687,7 +688,7 @@ fun! tar#Extract() endif elseif filereadable(tarbase.".tzst") - let extractcmd= substitute(extractcmd,"-","--zstd","") + let extractcmd= substitute(extractcmd,"-","--zstd -","") call system(extractcmd." ".shellescape(tarbase).".tzst ".shellescape(fname)) if v:shell_error != 0 call s:Msg('tar#Extract', 'error', $"{extractcmd} {tarbase}.tzst {fname}: failed!") @@ -696,7 +697,7 @@ fun! tar#Extract() endif elseif filereadable(tarbase.".tar.zst") - let extractcmd= substitute(extractcmd,"-","--zstd","") + let extractcmd= substitute(extractcmd,"-","--zstd -","") call system(extractcmd." ".shellescape(tarbase).".tar.zst ".shellescape(fname)) if v:shell_error != 0 call s:Msg('tar#Extract', 'error', $"{extractcmd} {tarbase}.tar.zst {fname}: failed!") diff --git a/src/testdir/test_plugin_tar.vim b/src/testdir/test_plugin_tar.vim index d6dbdd7613..28cf709f85 100644 --- a/src/testdir/test_plugin_tar.vim +++ b/src/testdir/test_plugin_tar.vim @@ -148,56 +148,118 @@ def g:Test_tar_path_traversal_with_nowrapscan() bw! enddef -def g:Test_tar_lz4_extract() - CheckExecutable lz4 - - delete('X.txt') - delete('Xarchive.tar') - delete('Xarchive.tar.lz4') - call writefile(['hello'], 'X.txt') - call system('tar -cf Xarchive.tar X.txt') +def CreateTar(archivename: string, content: string, outputdir: string) + var tempdir = tempname() + mkdir(tempdir, 'R') + call writefile([content], tempdir .. '/X.txt') + assert_true(filereadable(tempdir .. '/X.txt')) + call system('tar -C ' .. tempdir .. ' -cf ' .. outputdir .. '/' .. archivename .. ' X.txt') assert_equal(0, v:shell_error) +enddef - call system('lz4 -z Xarchive.tar Xarchive.tar.lz4') +def CreateTgz(archivename: string, content: string, outputdir: string) + var tempdir = tempname() + mkdir(tempdir, 'R') + call writefile([content], tempdir .. '/X.txt') + assert_true(filereadable(tempdir .. '/X.txt')) + call system('tar -C ' .. tempdir .. ' -czf ' .. outputdir .. '/' .. archivename .. ' X.txt') assert_equal(0, v:shell_error) +enddef - delete('X.txt') - delete('Xarchive.tar') - defer delete('Xarchive.tar.lz4') - - e Xarchive.tar.lz4 - assert_match('X.txt', getline(5)) - :5 - normal x - assert_true(filereadable('X.txt')) - assert_equal(['hello'], readfile('X.txt')) - delete('X.txt') - bw! +def CreateTbz(archivename: string, content: string, outputdir: string) + var tempdir = tempname() + mkdir(tempdir, 'R') + call writefile([content], tempdir .. '/X.txt') + assert_true(filereadable(tempdir .. '/X.txt')) + call system('tar -C ' .. tempdir .. ' -cjf ' .. outputdir .. '/' .. archivename .. ' X.txt') + assert_equal(0, v:shell_error) enddef -def g:Test_tlz4_extract() - CheckExecutable lz4 +def CreateTxz(archivename: string, content: string, outputdir: string) + var tempdir = tempname() + mkdir(tempdir, 'R') + call writefile([content], tempdir .. '/X.txt') + assert_true(filereadable(tempdir .. '/X.txt')) + call system('tar -C ' .. tempdir .. ' -cJf ' .. outputdir .. '/' .. archivename .. ' X.txt') + assert_equal(0, v:shell_error) +enddef - delete('X.txt') - delete('Xarchive.tar') - delete('Xarchive.tlz4') - call writefile(['goodbye'], 'X.txt') - call system('tar -cf Xarchive.tar X.txt') +def CreateTzst(archivename: string, content: string, outputdir: string) + var tempdir = tempname() + mkdir(tempdir, 'R') + call writefile([content], tempdir .. '/X.txt') + assert_true(filereadable(tempdir .. '/X.txt')) + call system('tar --zstd -C ' .. tempdir .. ' -cf ' .. outputdir .. '/' .. archivename .. ' X.txt') assert_equal(0, v:shell_error) +enddef - call system('lz4 -z Xarchive.tar Xarchive.tlz4') +def CreateTlz4(archivename: string, content: string, outputdir: string) + var tempdir = tempname() + mkdir(tempdir, 'R') + call writefile([content], tempdir .. '/X.txt') + assert_true(filereadable(tempdir .. '/X.txt')) + call system('tar -C ' .. tempdir .. ' -cf ' .. tempdir .. '/Xarchive.tar X.txt') + assert_equal(0, v:shell_error) + assert_true(filereadable(tempdir .. '/Xarchive.tar')) + call system('lz4 -z ' .. tempdir .. '/Xarchive.tar ' .. outputdir .. '/' .. archivename) assert_equal(0, v:shell_error) +enddef - delete('X.txt') - delete('Xarchive.tar') - defer delete('Xarchive.tlz4') +# XXX: Add test for .tar.bz3 +def g:Test_extraction() + var control = [ + {create: CreateTar, + archive: 'Xarchive.tar'}, + {create: CreateTgz, + archive: 'Xarchive.tgz'}, + {create: CreateTgz, + archive: 'Xarchive.tar.gz'}, + {create: CreateTbz, + archive: 'Xarchive.tbz'}, + {create: CreateTbz, + archive: 'Xarchive.tar.bz2'}, + {create: CreateTxz, + archive: 'Xarchive.txz'}, + {create: CreateTxz, + archive: 'Xarchive.tar.xz'}, + ] + + if executable('lz4') == 1 + control->add({ + create: CreateTlz4, + archive: 'Xarchive.tar.lz4' + }) + control->add({ + create: CreateTlz4, + archive: 'Xarchive.tlz4' + }) + endif + if executable('zstd') == 1 + control->add({ + create: CreateTzst, + archive: 'Xarchive.tar.zst' + }) + control->add({ + create: CreateTzst, + archive: 'Xarchive.tzst' + }) + endif - e Xarchive.tlz4 - assert_match('X.txt', getline(5)) - :5 - normal x - assert_true(filereadable('X.txt')) - assert_equal(['goodbye'], readfile('X.txt')) - delete('X.txt') - bw! + for c in control + var dir = tempname() + mkdir(dir, 'R') + call(c.create, [c.archive, 'hello', dir]) + + delete('X.txt') + execute 'edit ' .. dir .. '/' .. c.archive + assert_match('X.txt', getline(5), 'line 5 wrong in archive: ' .. c.archive) + :5 + normal x + assert_equal(0, v:shell_error, 'vshell error not 0') + assert_true(filereadable('X.txt'), 'X.txt not readable for archive: ' .. c.archive) + assert_equal(['hello'], readfile('X.txt'), 'X.txt wrong contents for archive: ' .. c.archive) + delete('X.txt') + delete(dir .. '/' .. c.archive) + bw! + endfor enddef diff --git a/src/version.c b/src/version.c index 99a25eb7c1..5fc6428c2c 100644 --- a/src/version.c +++ b/src/version.c @@ -734,6 +734,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 325, /**/ 324, /**/