archive_test.go 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413
  1. package archive // import "github.com/docker/docker/pkg/archive"
  2. import (
  3. "archive/tar"
  4. "bytes"
  5. "compress/gzip"
  6. "errors"
  7. "fmt"
  8. "io"
  9. "os"
  10. "os/exec"
  11. "path/filepath"
  12. "reflect"
  13. "runtime"
  14. "strings"
  15. "testing"
  16. "time"
  17. "github.com/containerd/containerd/pkg/userns"
  18. "github.com/docker/docker/pkg/idtools"
  19. "github.com/docker/docker/pkg/ioutils"
  20. "gotest.tools/v3/assert"
  21. is "gotest.tools/v3/assert/cmp"
  22. "gotest.tools/v3/skip"
  23. )
  24. var tmp string
  25. func init() {
  26. tmp = "/tmp/"
  27. if runtime.GOOS == "windows" {
  28. tmp = os.Getenv("TEMP") + `\`
  29. }
  30. }
  31. var defaultArchiver = NewDefaultArchiver()
  32. func defaultTarUntar(src, dst string) error {
  33. return defaultArchiver.TarUntar(src, dst)
  34. }
  35. func defaultUntarPath(src, dst string) error {
  36. return defaultArchiver.UntarPath(src, dst)
  37. }
  38. func defaultCopyFileWithTar(src, dst string) (err error) {
  39. return defaultArchiver.CopyFileWithTar(src, dst)
  40. }
  41. func defaultCopyWithTar(src, dst string) error {
  42. return defaultArchiver.CopyWithTar(src, dst)
  43. }
  44. func TestIsArchivePathDir(t *testing.T) {
  45. cmd := exec.Command("sh", "-c", "mkdir -p /tmp/archivedir")
  46. output, err := cmd.CombinedOutput()
  47. if err != nil {
  48. t.Fatalf("Fail to create an archive file for test : %s.", output)
  49. }
  50. if IsArchivePath(tmp + "archivedir") {
  51. t.Fatalf("Incorrectly recognised directory as an archive")
  52. }
  53. }
  54. func TestIsArchivePathInvalidFile(t *testing.T) {
  55. cmd := exec.Command("sh", "-c", "dd if=/dev/zero bs=1024 count=1 of=/tmp/archive && gzip --stdout /tmp/archive > /tmp/archive.gz")
  56. output, err := cmd.CombinedOutput()
  57. if err != nil {
  58. t.Fatalf("Fail to create an archive file for test : %s.", output)
  59. }
  60. if IsArchivePath(tmp + "archive") {
  61. t.Fatalf("Incorrectly recognised invalid tar path as archive")
  62. }
  63. if IsArchivePath(tmp + "archive.gz") {
  64. t.Fatalf("Incorrectly recognised invalid compressed tar path as archive")
  65. }
  66. }
  67. func TestIsArchivePathTar(t *testing.T) {
  68. whichTar := "tar"
  69. cmdStr := fmt.Sprintf("touch /tmp/archivedata && %s -cf /tmp/archive /tmp/archivedata && gzip --stdout /tmp/archive > /tmp/archive.gz", whichTar)
  70. cmd := exec.Command("sh", "-c", cmdStr)
  71. output, err := cmd.CombinedOutput()
  72. if err != nil {
  73. t.Fatalf("Fail to create an archive file for test : %s.", output)
  74. }
  75. if !IsArchivePath(tmp + "/archive") {
  76. t.Fatalf("Did not recognise valid tar path as archive")
  77. }
  78. if !IsArchivePath(tmp + "archive.gz") {
  79. t.Fatalf("Did not recognise valid compressed tar path as archive")
  80. }
  81. }
  82. func testDecompressStream(t *testing.T, ext, compressCommand string) io.Reader {
  83. cmd := exec.Command("sh", "-c",
  84. fmt.Sprintf("touch /tmp/archive && %s /tmp/archive", compressCommand))
  85. output, err := cmd.CombinedOutput()
  86. if err != nil {
  87. t.Fatalf("Failed to create an archive file for test : %s.", output)
  88. }
  89. filename := "archive." + ext
  90. archive, err := os.Open(tmp + filename)
  91. if err != nil {
  92. t.Fatalf("Failed to open file %s: %v", filename, err)
  93. }
  94. defer archive.Close()
  95. r, err := DecompressStream(archive)
  96. if err != nil {
  97. t.Fatalf("Failed to decompress %s: %v", filename, err)
  98. }
  99. if _, err = io.ReadAll(r); err != nil {
  100. t.Fatalf("Failed to read the decompressed stream: %v ", err)
  101. }
  102. if err = r.Close(); err != nil {
  103. t.Fatalf("Failed to close the decompressed stream: %v ", err)
  104. }
  105. return r
  106. }
  107. func TestDecompressStreamGzip(t *testing.T) {
  108. testDecompressStream(t, "gz", "gzip -f")
  109. }
  110. func TestDecompressStreamBzip2(t *testing.T) {
  111. testDecompressStream(t, "bz2", "bzip2 -f")
  112. }
  113. func TestDecompressStreamXz(t *testing.T) {
  114. if runtime.GOOS == "windows" {
  115. t.Skip("Xz not present in msys2")
  116. }
  117. testDecompressStream(t, "xz", "xz -f")
  118. }
  119. func TestDecompressStreamZstd(t *testing.T) {
  120. if _, err := exec.LookPath("zstd"); err != nil {
  121. t.Skip("zstd not installed")
  122. }
  123. testDecompressStream(t, "zst", "zstd -f")
  124. }
  125. func TestCompressStreamXzUnsupported(t *testing.T) {
  126. dest, err := os.Create(tmp + "dest")
  127. if err != nil {
  128. t.Fatalf("Fail to create the destination file")
  129. }
  130. defer dest.Close()
  131. _, err = CompressStream(dest, Xz)
  132. if err == nil {
  133. t.Fatalf("Should fail as xz is unsupported for compression format.")
  134. }
  135. }
  136. func TestCompressStreamBzip2Unsupported(t *testing.T) {
  137. dest, err := os.Create(tmp + "dest")
  138. if err != nil {
  139. t.Fatalf("Fail to create the destination file")
  140. }
  141. defer dest.Close()
  142. _, err = CompressStream(dest, Bzip2)
  143. if err == nil {
  144. t.Fatalf("Should fail as bzip2 is unsupported for compression format.")
  145. }
  146. }
  147. func TestCompressStreamInvalid(t *testing.T) {
  148. dest, err := os.Create(tmp + "dest")
  149. if err != nil {
  150. t.Fatalf("Fail to create the destination file")
  151. }
  152. defer dest.Close()
  153. _, err = CompressStream(dest, -1)
  154. if err == nil {
  155. t.Fatalf("Should fail as xz is unsupported for compression format.")
  156. }
  157. }
  158. func TestExtensionInvalid(t *testing.T) {
  159. compression := Compression(-1)
  160. output := compression.Extension()
  161. if output != "" {
  162. t.Fatalf("The extension of an invalid compression should be an empty string.")
  163. }
  164. }
  165. func TestExtensionUncompressed(t *testing.T) {
  166. compression := Uncompressed
  167. output := compression.Extension()
  168. if output != "tar" {
  169. t.Fatalf("The extension of an uncompressed archive should be 'tar'.")
  170. }
  171. }
  172. func TestExtensionBzip2(t *testing.T) {
  173. compression := Bzip2
  174. output := compression.Extension()
  175. if output != "tar.bz2" {
  176. t.Fatalf("The extension of a bzip2 archive should be 'tar.bz2'")
  177. }
  178. }
  179. func TestExtensionGzip(t *testing.T) {
  180. compression := Gzip
  181. output := compression.Extension()
  182. if output != "tar.gz" {
  183. t.Fatalf("The extension of a gzip archive should be 'tar.gz'")
  184. }
  185. }
  186. func TestExtensionXz(t *testing.T) {
  187. compression := Xz
  188. output := compression.Extension()
  189. if output != "tar.xz" {
  190. t.Fatalf("The extension of a xz archive should be 'tar.xz'")
  191. }
  192. }
  193. func TestExtensionZstd(t *testing.T) {
  194. compression := Zstd
  195. output := compression.Extension()
  196. if output != "tar.zst" {
  197. t.Fatalf("The extension of a zstd archive should be 'tar.zst'")
  198. }
  199. }
  200. func TestCmdStreamLargeStderr(t *testing.T) {
  201. cmd := exec.Command("sh", "-c", "dd if=/dev/zero bs=1k count=1000 of=/dev/stderr; echo hello")
  202. out, err := cmdStream(cmd, nil)
  203. if err != nil {
  204. t.Fatalf("Failed to start command: %s", err)
  205. }
  206. errCh := make(chan error, 1)
  207. go func() {
  208. _, err := io.Copy(io.Discard, out)
  209. errCh <- err
  210. }()
  211. select {
  212. case err := <-errCh:
  213. if err != nil {
  214. t.Fatalf("Command should not have failed (err=%.100s...)", err)
  215. }
  216. case <-time.After(5 * time.Second):
  217. t.Fatalf("Command did not complete in 5 seconds; probable deadlock")
  218. }
  219. }
  220. func TestCmdStreamBad(t *testing.T) {
  221. // TODO Windows: Figure out why this is failing in CI but not locally
  222. if runtime.GOOS == "windows" {
  223. t.Skip("Failing on Windows CI machines")
  224. }
  225. badCmd := exec.Command("sh", "-c", "echo hello; echo >&2 error couldn\\'t reverse the phase pulser; exit 1")
  226. out, err := cmdStream(badCmd, nil)
  227. if err != nil {
  228. t.Fatalf("Failed to start command: %s", err)
  229. }
  230. if output, err := io.ReadAll(out); err == nil {
  231. t.Fatalf("Command should have failed")
  232. } else if err.Error() != "exit status 1: error couldn't reverse the phase pulser\n" {
  233. t.Fatalf("Wrong error value (%s)", err)
  234. } else if s := string(output); s != "hello\n" {
  235. t.Fatalf("Command output should be '%s', not '%s'", "hello\\n", output)
  236. }
  237. }
  238. func TestCmdStreamGood(t *testing.T) {
  239. cmd := exec.Command("sh", "-c", "echo hello; exit 0")
  240. out, err := cmdStream(cmd, nil)
  241. if err != nil {
  242. t.Fatal(err)
  243. }
  244. if output, err := io.ReadAll(out); err != nil {
  245. t.Fatalf("Command should not have failed (err=%s)", err)
  246. } else if s := string(output); s != "hello\n" {
  247. t.Fatalf("Command output should be '%s', not '%s'", "hello\\n", output)
  248. }
  249. }
  250. func TestUntarPathWithInvalidDest(t *testing.T) {
  251. tempFolder, err := os.MkdirTemp("", "docker-archive-test")
  252. assert.NilError(t, err)
  253. defer os.RemoveAll(tempFolder)
  254. invalidDestFolder := filepath.Join(tempFolder, "invalidDest")
  255. // Create a src file
  256. srcFile := filepath.Join(tempFolder, "src")
  257. tarFile := filepath.Join(tempFolder, "src.tar")
  258. os.Create(srcFile)
  259. os.Create(invalidDestFolder) // being a file (not dir) should cause an error
  260. // Translate back to Unix semantics as next exec.Command is run under sh
  261. srcFileU := srcFile
  262. tarFileU := tarFile
  263. if runtime.GOOS == "windows" {
  264. tarFileU = "/tmp/" + filepath.Base(filepath.Dir(tarFile)) + "/src.tar"
  265. srcFileU = "/tmp/" + filepath.Base(filepath.Dir(srcFile)) + "/src"
  266. }
  267. cmd := exec.Command("sh", "-c", "tar cf "+tarFileU+" "+srcFileU)
  268. _, err = cmd.CombinedOutput()
  269. assert.NilError(t, err)
  270. err = defaultUntarPath(tarFile, invalidDestFolder)
  271. if err == nil {
  272. t.Fatalf("UntarPath with invalid destination path should throw an error.")
  273. }
  274. }
  275. func TestUntarPathWithInvalidSrc(t *testing.T) {
  276. dest, err := os.MkdirTemp("", "docker-archive-test")
  277. if err != nil {
  278. t.Fatalf("Fail to create the destination file")
  279. }
  280. defer os.RemoveAll(dest)
  281. err = defaultUntarPath("/invalid/path", dest)
  282. if err == nil {
  283. t.Fatalf("UntarPath with invalid src path should throw an error.")
  284. }
  285. }
  286. func TestUntarPath(t *testing.T) {
  287. skip.If(t, runtime.GOOS != "windows" && os.Getuid() != 0, "skipping test that requires root")
  288. tmpFolder, err := os.MkdirTemp("", "docker-archive-test")
  289. assert.NilError(t, err)
  290. defer os.RemoveAll(tmpFolder)
  291. srcFile := filepath.Join(tmpFolder, "src")
  292. tarFile := filepath.Join(tmpFolder, "src.tar")
  293. os.Create(filepath.Join(tmpFolder, "src"))
  294. destFolder := filepath.Join(tmpFolder, "dest")
  295. err = os.MkdirAll(destFolder, 0740)
  296. if err != nil {
  297. t.Fatalf("Fail to create the destination file")
  298. }
  299. // Translate back to Unix semantics as next exec.Command is run under sh
  300. srcFileU := srcFile
  301. tarFileU := tarFile
  302. if runtime.GOOS == "windows" {
  303. tarFileU = "/tmp/" + filepath.Base(filepath.Dir(tarFile)) + "/src.tar"
  304. srcFileU = "/tmp/" + filepath.Base(filepath.Dir(srcFile)) + "/src"
  305. }
  306. cmd := exec.Command("sh", "-c", "tar cf "+tarFileU+" "+srcFileU)
  307. _, err = cmd.CombinedOutput()
  308. assert.NilError(t, err)
  309. err = defaultUntarPath(tarFile, destFolder)
  310. if err != nil {
  311. t.Fatalf("UntarPath shouldn't throw an error, %s.", err)
  312. }
  313. expectedFile := filepath.Join(destFolder, srcFileU)
  314. _, err = os.Stat(expectedFile)
  315. if err != nil {
  316. t.Fatalf("Destination folder should contain the source file but did not.")
  317. }
  318. }
  319. // Do the same test as above but with the destination as file, it should fail
  320. func TestUntarPathWithDestinationFile(t *testing.T) {
  321. tmpFolder, err := os.MkdirTemp("", "docker-archive-test")
  322. if err != nil {
  323. t.Fatal(err)
  324. }
  325. defer os.RemoveAll(tmpFolder)
  326. srcFile := filepath.Join(tmpFolder, "src")
  327. tarFile := filepath.Join(tmpFolder, "src.tar")
  328. os.Create(filepath.Join(tmpFolder, "src"))
  329. // Translate back to Unix semantics as next exec.Command is run under sh
  330. srcFileU := srcFile
  331. tarFileU := tarFile
  332. if runtime.GOOS == "windows" {
  333. tarFileU = "/tmp/" + filepath.Base(filepath.Dir(tarFile)) + "/src.tar"
  334. srcFileU = "/tmp/" + filepath.Base(filepath.Dir(srcFile)) + "/src"
  335. }
  336. cmd := exec.Command("sh", "-c", "tar cf "+tarFileU+" "+srcFileU)
  337. _, err = cmd.CombinedOutput()
  338. if err != nil {
  339. t.Fatal(err)
  340. }
  341. destFile := filepath.Join(tmpFolder, "dest")
  342. _, err = os.Create(destFile)
  343. if err != nil {
  344. t.Fatalf("Fail to create the destination file")
  345. }
  346. err = defaultUntarPath(tarFile, destFile)
  347. if err == nil {
  348. t.Fatalf("UntarPath should throw an error if the destination if a file")
  349. }
  350. }
  351. // Do the same test as above but with the destination folder already exists
  352. // and the destination file is a directory
  353. // It's working, see https://github.com/docker/docker/issues/10040
  354. func TestUntarPathWithDestinationSrcFileAsFolder(t *testing.T) {
  355. tmpFolder, err := os.MkdirTemp("", "docker-archive-test")
  356. if err != nil {
  357. t.Fatal(err)
  358. }
  359. defer os.RemoveAll(tmpFolder)
  360. srcFile := filepath.Join(tmpFolder, "src")
  361. tarFile := filepath.Join(tmpFolder, "src.tar")
  362. os.Create(srcFile)
  363. // Translate back to Unix semantics as next exec.Command is run under sh
  364. srcFileU := srcFile
  365. tarFileU := tarFile
  366. if runtime.GOOS == "windows" {
  367. tarFileU = "/tmp/" + filepath.Base(filepath.Dir(tarFile)) + "/src.tar"
  368. srcFileU = "/tmp/" + filepath.Base(filepath.Dir(srcFile)) + "/src"
  369. }
  370. cmd := exec.Command("sh", "-c", "tar cf "+tarFileU+" "+srcFileU)
  371. _, err = cmd.CombinedOutput()
  372. if err != nil {
  373. t.Fatal(err)
  374. }
  375. destFolder := filepath.Join(tmpFolder, "dest")
  376. err = os.MkdirAll(destFolder, 0740)
  377. if err != nil {
  378. t.Fatalf("Fail to create the destination folder")
  379. }
  380. // Let's create a folder that will has the same path as the extracted file (from tar)
  381. destSrcFileAsFolder := filepath.Join(destFolder, srcFileU)
  382. err = os.MkdirAll(destSrcFileAsFolder, 0740)
  383. if err != nil {
  384. t.Fatal(err)
  385. }
  386. err = defaultUntarPath(tarFile, destFolder)
  387. if err != nil {
  388. t.Fatalf("UntarPath should throw not throw an error if the extracted file already exists and is a folder")
  389. }
  390. }
  391. func TestCopyWithTarInvalidSrc(t *testing.T) {
  392. tempFolder, err := os.MkdirTemp("", "docker-archive-test")
  393. if err != nil {
  394. t.Fatal(nil)
  395. }
  396. destFolder := filepath.Join(tempFolder, "dest")
  397. invalidSrc := filepath.Join(tempFolder, "doesnotexists")
  398. err = os.MkdirAll(destFolder, 0740)
  399. if err != nil {
  400. t.Fatal(err)
  401. }
  402. err = defaultCopyWithTar(invalidSrc, destFolder)
  403. if err == nil {
  404. t.Fatalf("archiver.CopyWithTar with invalid src path should throw an error.")
  405. }
  406. }
  407. func TestCopyWithTarInexistentDestWillCreateIt(t *testing.T) {
  408. skip.If(t, runtime.GOOS != "windows" && os.Getuid() != 0, "skipping test that requires root")
  409. tempFolder, err := os.MkdirTemp("", "docker-archive-test")
  410. if err != nil {
  411. t.Fatal(nil)
  412. }
  413. srcFolder := filepath.Join(tempFolder, "src")
  414. inexistentDestFolder := filepath.Join(tempFolder, "doesnotexists")
  415. err = os.MkdirAll(srcFolder, 0740)
  416. if err != nil {
  417. t.Fatal(err)
  418. }
  419. err = defaultCopyWithTar(srcFolder, inexistentDestFolder)
  420. if err != nil {
  421. t.Fatalf("CopyWithTar with an inexistent folder shouldn't fail.")
  422. }
  423. _, err = os.Stat(inexistentDestFolder)
  424. if err != nil {
  425. t.Fatalf("CopyWithTar with an inexistent folder should create it.")
  426. }
  427. }
  428. // Test CopyWithTar with a file as src
  429. func TestCopyWithTarSrcFile(t *testing.T) {
  430. folder, err := os.MkdirTemp("", "docker-archive-test")
  431. if err != nil {
  432. t.Fatal(err)
  433. }
  434. defer os.RemoveAll(folder)
  435. dest := filepath.Join(folder, "dest")
  436. srcFolder := filepath.Join(folder, "src")
  437. src := filepath.Join(folder, filepath.Join("src", "src"))
  438. err = os.MkdirAll(srcFolder, 0740)
  439. if err != nil {
  440. t.Fatal(err)
  441. }
  442. err = os.MkdirAll(dest, 0740)
  443. if err != nil {
  444. t.Fatal(err)
  445. }
  446. os.WriteFile(src, []byte("content"), 0777)
  447. err = defaultCopyWithTar(src, dest)
  448. if err != nil {
  449. t.Fatalf("archiver.CopyWithTar shouldn't throw an error, %s.", err)
  450. }
  451. _, err = os.Stat(dest)
  452. // FIXME Check the content
  453. if err != nil {
  454. t.Fatalf("Destination file should be the same as the source.")
  455. }
  456. }
  457. // Test CopyWithTar with a folder as src
  458. func TestCopyWithTarSrcFolder(t *testing.T) {
  459. folder, err := os.MkdirTemp("", "docker-archive-test")
  460. if err != nil {
  461. t.Fatal(err)
  462. }
  463. defer os.RemoveAll(folder)
  464. dest := filepath.Join(folder, "dest")
  465. src := filepath.Join(folder, filepath.Join("src", "folder"))
  466. err = os.MkdirAll(src, 0740)
  467. if err != nil {
  468. t.Fatal(err)
  469. }
  470. err = os.MkdirAll(dest, 0740)
  471. if err != nil {
  472. t.Fatal(err)
  473. }
  474. os.WriteFile(filepath.Join(src, "file"), []byte("content"), 0777)
  475. err = defaultCopyWithTar(src, dest)
  476. if err != nil {
  477. t.Fatalf("archiver.CopyWithTar shouldn't throw an error, %s.", err)
  478. }
  479. _, err = os.Stat(dest)
  480. // FIXME Check the content (the file inside)
  481. if err != nil {
  482. t.Fatalf("Destination folder should contain the source file but did not.")
  483. }
  484. }
  485. func TestCopyFileWithTarInvalidSrc(t *testing.T) {
  486. tempFolder, err := os.MkdirTemp("", "docker-archive-test")
  487. if err != nil {
  488. t.Fatal(err)
  489. }
  490. defer os.RemoveAll(tempFolder)
  491. destFolder := filepath.Join(tempFolder, "dest")
  492. err = os.MkdirAll(destFolder, 0740)
  493. if err != nil {
  494. t.Fatal(err)
  495. }
  496. invalidFile := filepath.Join(tempFolder, "doesnotexists")
  497. err = defaultCopyFileWithTar(invalidFile, destFolder)
  498. if err == nil {
  499. t.Fatalf("archiver.CopyWithTar with invalid src path should throw an error.")
  500. }
  501. }
  502. func TestCopyFileWithTarInexistentDestWillCreateIt(t *testing.T) {
  503. tempFolder, err := os.MkdirTemp("", "docker-archive-test")
  504. if err != nil {
  505. t.Fatal(nil)
  506. }
  507. defer os.RemoveAll(tempFolder)
  508. srcFile := filepath.Join(tempFolder, "src")
  509. inexistentDestFolder := filepath.Join(tempFolder, "doesnotexists")
  510. _, err = os.Create(srcFile)
  511. if err != nil {
  512. t.Fatal(err)
  513. }
  514. err = defaultCopyFileWithTar(srcFile, inexistentDestFolder)
  515. if err != nil {
  516. t.Fatalf("CopyWithTar with an inexistent folder shouldn't fail.")
  517. }
  518. _, err = os.Stat(inexistentDestFolder)
  519. if err != nil {
  520. t.Fatalf("CopyWithTar with an inexistent folder should create it.")
  521. }
  522. // FIXME Test the src file and content
  523. }
  524. func TestCopyFileWithTarSrcFolder(t *testing.T) {
  525. folder, err := os.MkdirTemp("", "docker-archive-copyfilewithtar-test")
  526. if err != nil {
  527. t.Fatal(err)
  528. }
  529. defer os.RemoveAll(folder)
  530. dest := filepath.Join(folder, "dest")
  531. src := filepath.Join(folder, "srcfolder")
  532. err = os.MkdirAll(src, 0740)
  533. if err != nil {
  534. t.Fatal(err)
  535. }
  536. err = os.MkdirAll(dest, 0740)
  537. if err != nil {
  538. t.Fatal(err)
  539. }
  540. err = defaultCopyFileWithTar(src, dest)
  541. if err == nil {
  542. t.Fatalf("CopyFileWithTar should throw an error with a folder.")
  543. }
  544. }
  545. func TestCopyFileWithTarSrcFile(t *testing.T) {
  546. folder, err := os.MkdirTemp("", "docker-archive-test")
  547. if err != nil {
  548. t.Fatal(err)
  549. }
  550. defer os.RemoveAll(folder)
  551. dest := filepath.Join(folder, "dest")
  552. srcFolder := filepath.Join(folder, "src")
  553. src := filepath.Join(folder, filepath.Join("src", "src"))
  554. err = os.MkdirAll(srcFolder, 0740)
  555. if err != nil {
  556. t.Fatal(err)
  557. }
  558. err = os.MkdirAll(dest, 0740)
  559. if err != nil {
  560. t.Fatal(err)
  561. }
  562. os.WriteFile(src, []byte("content"), 0777)
  563. err = defaultCopyWithTar(src, dest+"/")
  564. if err != nil {
  565. t.Fatalf("archiver.CopyFileWithTar shouldn't throw an error, %s.", err)
  566. }
  567. _, err = os.Stat(dest)
  568. if err != nil {
  569. t.Fatalf("Destination folder should contain the source file but did not.")
  570. }
  571. }
  572. func TestTarFiles(t *testing.T) {
  573. // try without hardlinks
  574. if err := checkNoChanges(1000, false); err != nil {
  575. t.Fatal(err)
  576. }
  577. // try with hardlinks
  578. if err := checkNoChanges(1000, true); err != nil {
  579. t.Fatal(err)
  580. }
  581. }
  582. func checkNoChanges(fileNum int, hardlinks bool) error {
  583. srcDir, err := os.MkdirTemp("", "docker-test-srcDir")
  584. if err != nil {
  585. return err
  586. }
  587. defer os.RemoveAll(srcDir)
  588. destDir, err := os.MkdirTemp("", "docker-test-destDir")
  589. if err != nil {
  590. return err
  591. }
  592. defer os.RemoveAll(destDir)
  593. _, err = prepareUntarSourceDirectory(fileNum, srcDir, hardlinks)
  594. if err != nil {
  595. return err
  596. }
  597. err = defaultTarUntar(srcDir, destDir)
  598. if err != nil {
  599. return err
  600. }
  601. changes, err := ChangesDirs(destDir, srcDir)
  602. if err != nil {
  603. return err
  604. }
  605. if len(changes) > 0 {
  606. return fmt.Errorf("with %d files and %v hardlinks: expected 0 changes, got %d", fileNum, hardlinks, len(changes))
  607. }
  608. return nil
  609. }
  610. func tarUntar(t *testing.T, origin string, options *TarOptions) ([]Change, error) {
  611. archive, err := TarWithOptions(origin, options)
  612. if err != nil {
  613. t.Fatal(err)
  614. }
  615. defer archive.Close()
  616. buf := make([]byte, 10)
  617. if _, err := archive.Read(buf); err != nil {
  618. return nil, err
  619. }
  620. wrap := io.MultiReader(bytes.NewReader(buf), archive)
  621. detectedCompression := DetectCompression(buf)
  622. compression := options.Compression
  623. if detectedCompression.Extension() != compression.Extension() {
  624. return nil, fmt.Errorf("Wrong compression detected. Actual compression: %s, found %s", compression.Extension(), detectedCompression.Extension())
  625. }
  626. tmp, err := os.MkdirTemp("", "docker-test-untar")
  627. if err != nil {
  628. return nil, err
  629. }
  630. defer os.RemoveAll(tmp)
  631. if err := Untar(wrap, tmp, nil); err != nil {
  632. return nil, err
  633. }
  634. if _, err := os.Stat(tmp); err != nil {
  635. return nil, err
  636. }
  637. return ChangesDirs(origin, tmp)
  638. }
  639. func TestDetectCompressionZstd(t *testing.T) {
  640. // test zstd compression without skippable frames.
  641. compressedData := []byte{
  642. 0x28, 0xb5, 0x2f, 0xfd, // magic number of Zstandard frame: 0xFD2FB528
  643. 0x04, 0x00, 0x31, 0x00, 0x00, // frame header
  644. 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, // data block "docker"
  645. 0x16, 0x0e, 0x21, 0xc3, // content checksum
  646. }
  647. compression := DetectCompression(compressedData)
  648. if compression != Zstd {
  649. t.Fatal("Unexpected compression")
  650. }
  651. // test zstd compression with skippable frames.
  652. hex := []byte{
  653. 0x50, 0x2a, 0x4d, 0x18, // magic number of skippable frame: 0x184D2A50 to 0x184D2A5F
  654. 0x04, 0x00, 0x00, 0x00, // frame size
  655. 0x5d, 0x00, 0x00, 0x00, // user data
  656. 0x28, 0xb5, 0x2f, 0xfd, // magic number of Zstandard frame: 0xFD2FB528
  657. 0x04, 0x00, 0x31, 0x00, 0x00, // frame header
  658. 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, // data block "docker"
  659. 0x16, 0x0e, 0x21, 0xc3, // content checksum
  660. }
  661. compression = DetectCompression(hex)
  662. if compression != Zstd {
  663. t.Fatal("Unexpected compression")
  664. }
  665. }
  666. func TestTarUntar(t *testing.T) {
  667. origin, err := os.MkdirTemp("", "docker-test-untar-origin")
  668. if err != nil {
  669. t.Fatal(err)
  670. }
  671. defer os.RemoveAll(origin)
  672. if err := os.WriteFile(filepath.Join(origin, "1"), []byte("hello world"), 0700); err != nil {
  673. t.Fatal(err)
  674. }
  675. if err := os.WriteFile(filepath.Join(origin, "2"), []byte("welcome!"), 0700); err != nil {
  676. t.Fatal(err)
  677. }
  678. if err := os.WriteFile(filepath.Join(origin, "3"), []byte("will be ignored"), 0700); err != nil {
  679. t.Fatal(err)
  680. }
  681. for _, c := range []Compression{
  682. Uncompressed,
  683. Gzip,
  684. } {
  685. changes, err := tarUntar(t, origin, &TarOptions{
  686. Compression: c,
  687. ExcludePatterns: []string{"3"},
  688. })
  689. if err != nil {
  690. t.Fatalf("Error tar/untar for compression %s: %s", c.Extension(), err)
  691. }
  692. if len(changes) != 1 || changes[0].Path != string(filepath.Separator)+"3" {
  693. t.Fatalf("Unexpected differences after tarUntar: %v", changes)
  694. }
  695. }
  696. }
  697. func TestTarWithOptionsChownOptsAlwaysOverridesIdPair(t *testing.T) {
  698. origin, err := os.MkdirTemp("", "docker-test-tar-chown-opt")
  699. assert.NilError(t, err)
  700. defer os.RemoveAll(origin)
  701. filePath := filepath.Join(origin, "1")
  702. err = os.WriteFile(filePath, []byte("hello world"), 0700)
  703. assert.NilError(t, err)
  704. idMaps := []idtools.IDMap{
  705. 0: {
  706. ContainerID: 0,
  707. HostID: 0,
  708. Size: 65536,
  709. },
  710. 1: {
  711. ContainerID: 0,
  712. HostID: 100000,
  713. Size: 65536,
  714. },
  715. }
  716. cases := []struct {
  717. opts *TarOptions
  718. expectedUID int
  719. expectedGID int
  720. }{
  721. {&TarOptions{ChownOpts: &idtools.Identity{UID: 1337, GID: 42}}, 1337, 42},
  722. {&TarOptions{ChownOpts: &idtools.Identity{UID: 100001, GID: 100001}, UIDMaps: idMaps, GIDMaps: idMaps}, 100001, 100001},
  723. {&TarOptions{ChownOpts: &idtools.Identity{UID: 0, GID: 0}, NoLchown: false}, 0, 0},
  724. {&TarOptions{ChownOpts: &idtools.Identity{UID: 1, GID: 1}, NoLchown: true}, 1, 1},
  725. {&TarOptions{ChownOpts: &idtools.Identity{UID: 1000, GID: 1000}, NoLchown: true}, 1000, 1000},
  726. }
  727. for _, testCase := range cases {
  728. reader, err := TarWithOptions(filePath, testCase.opts)
  729. assert.NilError(t, err)
  730. tr := tar.NewReader(reader)
  731. defer reader.Close()
  732. for {
  733. hdr, err := tr.Next()
  734. if err == io.EOF {
  735. // end of tar archive
  736. break
  737. }
  738. assert.NilError(t, err)
  739. assert.Check(t, is.Equal(hdr.Uid, testCase.expectedUID), "Uid equals expected value")
  740. assert.Check(t, is.Equal(hdr.Gid, testCase.expectedGID), "Gid equals expected value")
  741. }
  742. }
  743. }
  744. func TestTarWithOptions(t *testing.T) {
  745. origin, err := os.MkdirTemp("", "docker-test-untar-origin")
  746. if err != nil {
  747. t.Fatal(err)
  748. }
  749. if _, err := os.MkdirTemp(origin, "folder"); err != nil {
  750. t.Fatal(err)
  751. }
  752. defer os.RemoveAll(origin)
  753. if err := os.WriteFile(filepath.Join(origin, "1"), []byte("hello world"), 0700); err != nil {
  754. t.Fatal(err)
  755. }
  756. if err := os.WriteFile(filepath.Join(origin, "2"), []byte("welcome!"), 0700); err != nil {
  757. t.Fatal(err)
  758. }
  759. cases := []struct {
  760. opts *TarOptions
  761. numChanges int
  762. }{
  763. {&TarOptions{IncludeFiles: []string{"1"}}, 2},
  764. {&TarOptions{ExcludePatterns: []string{"2"}}, 1},
  765. {&TarOptions{ExcludePatterns: []string{"1", "folder*"}}, 2},
  766. {&TarOptions{IncludeFiles: []string{"1", "1"}}, 2},
  767. {&TarOptions{IncludeFiles: []string{"1"}, RebaseNames: map[string]string{"1": "test"}}, 4},
  768. }
  769. for _, testCase := range cases {
  770. changes, err := tarUntar(t, origin, testCase.opts)
  771. if err != nil {
  772. t.Fatalf("Error tar/untar when testing inclusion/exclusion: %s", err)
  773. }
  774. if len(changes) != testCase.numChanges {
  775. t.Errorf("Expected %d changes, got %d for %+v:",
  776. testCase.numChanges, len(changes), testCase.opts)
  777. }
  778. }
  779. }
  780. // Some tar archives such as http://haproxy.1wt.eu/download/1.5/src/devel/haproxy-1.5-dev21.tar.gz
  781. // use PAX Global Extended Headers.
  782. // Failing prevents the archives from being uncompressed during ADD
  783. func TestTypeXGlobalHeaderDoesNotFail(t *testing.T) {
  784. hdr := tar.Header{Typeflag: tar.TypeXGlobalHeader}
  785. tmpDir, err := os.MkdirTemp("", "docker-test-archive-pax-test")
  786. if err != nil {
  787. t.Fatal(err)
  788. }
  789. defer os.RemoveAll(tmpDir)
  790. err = createTarFile(filepath.Join(tmpDir, "pax_global_header"), tmpDir, &hdr, nil, true, nil, false)
  791. if err != nil {
  792. t.Fatal(err)
  793. }
  794. }
  795. // Some tar have both GNU specific (huge uid) and Ustar specific (long name) things.
  796. // Not supposed to happen (should use PAX instead of Ustar for long name) but it does and it should still work.
  797. func TestUntarUstarGnuConflict(t *testing.T) {
  798. f, err := os.Open("testdata/broken.tar")
  799. if err != nil {
  800. t.Fatal(err)
  801. }
  802. defer f.Close()
  803. found := false
  804. tr := tar.NewReader(f)
  805. // Iterate through the files in the archive.
  806. for {
  807. hdr, err := tr.Next()
  808. if err == io.EOF {
  809. // end of tar archive
  810. break
  811. }
  812. if err != nil {
  813. t.Fatal(err)
  814. }
  815. if hdr.Name == "root/.cpanm/work/1395823785.24209/Plack-1.0030/blib/man3/Plack::Middleware::LighttpdScriptNameFix.3pm" {
  816. found = true
  817. break
  818. }
  819. }
  820. if !found {
  821. t.Fatalf("%s not found in the archive", "root/.cpanm/work/1395823785.24209/Plack-1.0030/blib/man3/Plack::Middleware::LighttpdScriptNameFix.3pm")
  822. }
  823. }
  824. func prepareUntarSourceDirectory(numberOfFiles int, targetPath string, makeLinks bool) (int, error) {
  825. fileData := []byte("fooo")
  826. for n := 0; n < numberOfFiles; n++ {
  827. fileName := fmt.Sprintf("file-%d", n)
  828. if err := os.WriteFile(filepath.Join(targetPath, fileName), fileData, 0700); err != nil {
  829. return 0, err
  830. }
  831. if makeLinks {
  832. if err := os.Link(filepath.Join(targetPath, fileName), filepath.Join(targetPath, fileName+"-link")); err != nil {
  833. return 0, err
  834. }
  835. }
  836. }
  837. totalSize := numberOfFiles * len(fileData)
  838. return totalSize, nil
  839. }
  840. func BenchmarkTarUntar(b *testing.B) {
  841. origin, err := os.MkdirTemp("", "docker-test-untar-origin")
  842. if err != nil {
  843. b.Fatal(err)
  844. }
  845. tempDir, err := os.MkdirTemp("", "docker-test-untar-destination")
  846. if err != nil {
  847. b.Fatal(err)
  848. }
  849. target := filepath.Join(tempDir, "dest")
  850. n, err := prepareUntarSourceDirectory(100, origin, false)
  851. if err != nil {
  852. b.Fatal(err)
  853. }
  854. defer os.RemoveAll(origin)
  855. defer os.RemoveAll(tempDir)
  856. b.ResetTimer()
  857. b.SetBytes(int64(n))
  858. for n := 0; n < b.N; n++ {
  859. err := defaultTarUntar(origin, target)
  860. if err != nil {
  861. b.Fatal(err)
  862. }
  863. os.RemoveAll(target)
  864. }
  865. }
  866. func BenchmarkTarUntarWithLinks(b *testing.B) {
  867. origin, err := os.MkdirTemp("", "docker-test-untar-origin")
  868. if err != nil {
  869. b.Fatal(err)
  870. }
  871. tempDir, err := os.MkdirTemp("", "docker-test-untar-destination")
  872. if err != nil {
  873. b.Fatal(err)
  874. }
  875. target := filepath.Join(tempDir, "dest")
  876. n, err := prepareUntarSourceDirectory(100, origin, true)
  877. if err != nil {
  878. b.Fatal(err)
  879. }
  880. defer os.RemoveAll(origin)
  881. defer os.RemoveAll(tempDir)
  882. b.ResetTimer()
  883. b.SetBytes(int64(n))
  884. for n := 0; n < b.N; n++ {
  885. err := defaultTarUntar(origin, target)
  886. if err != nil {
  887. b.Fatal(err)
  888. }
  889. os.RemoveAll(target)
  890. }
  891. }
  892. func TestUntarInvalidFilenames(t *testing.T) {
  893. for i, headers := range [][]*tar.Header{
  894. {
  895. {
  896. Name: "../victim/dotdot",
  897. Typeflag: tar.TypeReg,
  898. Mode: 0644,
  899. },
  900. },
  901. {
  902. {
  903. // Note the leading slash
  904. Name: "/../victim/slash-dotdot",
  905. Typeflag: tar.TypeReg,
  906. Mode: 0644,
  907. },
  908. },
  909. } {
  910. if err := testBreakout("untar", "docker-TestUntarInvalidFilenames", headers); err != nil {
  911. t.Fatalf("i=%d. %v", i, err)
  912. }
  913. }
  914. }
  915. func TestUntarHardlinkToSymlink(t *testing.T) {
  916. skip.If(t, runtime.GOOS != "windows" && os.Getuid() != 0, "skipping test that requires root")
  917. for i, headers := range [][]*tar.Header{
  918. {
  919. {
  920. Name: "symlink1",
  921. Typeflag: tar.TypeSymlink,
  922. Linkname: "regfile",
  923. Mode: 0644,
  924. },
  925. {
  926. Name: "symlink2",
  927. Typeflag: tar.TypeLink,
  928. Linkname: "symlink1",
  929. Mode: 0644,
  930. },
  931. {
  932. Name: "regfile",
  933. Typeflag: tar.TypeReg,
  934. Mode: 0644,
  935. },
  936. },
  937. } {
  938. if err := testBreakout("untar", "docker-TestUntarHardlinkToSymlink", headers); err != nil {
  939. t.Fatalf("i=%d. %v", i, err)
  940. }
  941. }
  942. }
  943. func TestUntarInvalidHardlink(t *testing.T) {
  944. for i, headers := range [][]*tar.Header{
  945. { // try reading victim/hello (../)
  946. {
  947. Name: "dotdot",
  948. Typeflag: tar.TypeLink,
  949. Linkname: "../victim/hello",
  950. Mode: 0644,
  951. },
  952. },
  953. { // try reading victim/hello (/../)
  954. {
  955. Name: "slash-dotdot",
  956. Typeflag: tar.TypeLink,
  957. // Note the leading slash
  958. Linkname: "/../victim/hello",
  959. Mode: 0644,
  960. },
  961. },
  962. { // try writing victim/file
  963. {
  964. Name: "loophole-victim",
  965. Typeflag: tar.TypeLink,
  966. Linkname: "../victim",
  967. Mode: 0755,
  968. },
  969. {
  970. Name: "loophole-victim/file",
  971. Typeflag: tar.TypeReg,
  972. Mode: 0644,
  973. },
  974. },
  975. { // try reading victim/hello (hardlink, symlink)
  976. {
  977. Name: "loophole-victim",
  978. Typeflag: tar.TypeLink,
  979. Linkname: "../victim",
  980. Mode: 0755,
  981. },
  982. {
  983. Name: "symlink",
  984. Typeflag: tar.TypeSymlink,
  985. Linkname: "loophole-victim/hello",
  986. Mode: 0644,
  987. },
  988. },
  989. { // Try reading victim/hello (hardlink, hardlink)
  990. {
  991. Name: "loophole-victim",
  992. Typeflag: tar.TypeLink,
  993. Linkname: "../victim",
  994. Mode: 0755,
  995. },
  996. {
  997. Name: "hardlink",
  998. Typeflag: tar.TypeLink,
  999. Linkname: "loophole-victim/hello",
  1000. Mode: 0644,
  1001. },
  1002. },
  1003. { // Try removing victim directory (hardlink)
  1004. {
  1005. Name: "loophole-victim",
  1006. Typeflag: tar.TypeLink,
  1007. Linkname: "../victim",
  1008. Mode: 0755,
  1009. },
  1010. {
  1011. Name: "loophole-victim",
  1012. Typeflag: tar.TypeReg,
  1013. Mode: 0644,
  1014. },
  1015. },
  1016. } {
  1017. if err := testBreakout("untar", "docker-TestUntarInvalidHardlink", headers); err != nil {
  1018. t.Fatalf("i=%d. %v", i, err)
  1019. }
  1020. }
  1021. }
  1022. func TestUntarInvalidSymlink(t *testing.T) {
  1023. for i, headers := range [][]*tar.Header{
  1024. { // try reading victim/hello (../)
  1025. {
  1026. Name: "dotdot",
  1027. Typeflag: tar.TypeSymlink,
  1028. Linkname: "../victim/hello",
  1029. Mode: 0644,
  1030. },
  1031. },
  1032. { // try reading victim/hello (/../)
  1033. {
  1034. Name: "slash-dotdot",
  1035. Typeflag: tar.TypeSymlink,
  1036. // Note the leading slash
  1037. Linkname: "/../victim/hello",
  1038. Mode: 0644,
  1039. },
  1040. },
  1041. { // try writing victim/file
  1042. {
  1043. Name: "loophole-victim",
  1044. Typeflag: tar.TypeSymlink,
  1045. Linkname: "../victim",
  1046. Mode: 0755,
  1047. },
  1048. {
  1049. Name: "loophole-victim/file",
  1050. Typeflag: tar.TypeReg,
  1051. Mode: 0644,
  1052. },
  1053. },
  1054. { // try reading victim/hello (symlink, symlink)
  1055. {
  1056. Name: "loophole-victim",
  1057. Typeflag: tar.TypeSymlink,
  1058. Linkname: "../victim",
  1059. Mode: 0755,
  1060. },
  1061. {
  1062. Name: "symlink",
  1063. Typeflag: tar.TypeSymlink,
  1064. Linkname: "loophole-victim/hello",
  1065. Mode: 0644,
  1066. },
  1067. },
  1068. { // try reading victim/hello (symlink, hardlink)
  1069. {
  1070. Name: "loophole-victim",
  1071. Typeflag: tar.TypeSymlink,
  1072. Linkname: "../victim",
  1073. Mode: 0755,
  1074. },
  1075. {
  1076. Name: "hardlink",
  1077. Typeflag: tar.TypeLink,
  1078. Linkname: "loophole-victim/hello",
  1079. Mode: 0644,
  1080. },
  1081. },
  1082. { // try removing victim directory (symlink)
  1083. {
  1084. Name: "loophole-victim",
  1085. Typeflag: tar.TypeSymlink,
  1086. Linkname: "../victim",
  1087. Mode: 0755,
  1088. },
  1089. {
  1090. Name: "loophole-victim",
  1091. Typeflag: tar.TypeReg,
  1092. Mode: 0644,
  1093. },
  1094. },
  1095. { // try writing to victim/newdir/newfile with a symlink in the path
  1096. {
  1097. // this header needs to be before the next one, or else there is an error
  1098. Name: "dir/loophole",
  1099. Typeflag: tar.TypeSymlink,
  1100. Linkname: "../../victim",
  1101. Mode: 0755,
  1102. },
  1103. {
  1104. Name: "dir/loophole/newdir/newfile",
  1105. Typeflag: tar.TypeReg,
  1106. Mode: 0644,
  1107. },
  1108. },
  1109. } {
  1110. if err := testBreakout("untar", "docker-TestUntarInvalidSymlink", headers); err != nil {
  1111. t.Fatalf("i=%d. %v", i, err)
  1112. }
  1113. }
  1114. }
  1115. func TestTempArchiveCloseMultipleTimes(t *testing.T) {
  1116. reader := io.NopCloser(strings.NewReader("hello"))
  1117. tempArchive, err := NewTempArchive(reader, "")
  1118. assert.NilError(t, err)
  1119. buf := make([]byte, 10)
  1120. n, err := tempArchive.Read(buf)
  1121. assert.NilError(t, err)
  1122. if n != 5 {
  1123. t.Fatalf("Expected to read 5 bytes. Read %d instead", n)
  1124. }
  1125. for i := 0; i < 3; i++ {
  1126. if err = tempArchive.Close(); err != nil {
  1127. t.Fatalf("i=%d. Unexpected error closing temp archive: %v", i, err)
  1128. }
  1129. }
  1130. }
  1131. // TestXGlobalNoParent is a regression test to check parent directories are not crated for PAX headers
  1132. func TestXGlobalNoParent(t *testing.T) {
  1133. buf := &bytes.Buffer{}
  1134. w := tar.NewWriter(buf)
  1135. err := w.WriteHeader(&tar.Header{
  1136. Name: "foo/bar",
  1137. Typeflag: tar.TypeXGlobalHeader,
  1138. })
  1139. assert.NilError(t, err)
  1140. tmpDir, err := os.MkdirTemp("", "pax-test")
  1141. assert.NilError(t, err)
  1142. defer os.RemoveAll(tmpDir)
  1143. err = Untar(buf, tmpDir, nil)
  1144. assert.NilError(t, err)
  1145. _, err = os.Lstat(filepath.Join(tmpDir, "foo"))
  1146. assert.Check(t, err != nil)
  1147. assert.Check(t, errors.Is(err, os.ErrNotExist))
  1148. }
  1149. func TestReplaceFileTarWrapper(t *testing.T) {
  1150. filesInArchive := 20
  1151. testcases := []struct {
  1152. doc string
  1153. filename string
  1154. modifier TarModifierFunc
  1155. expected string
  1156. fileCount int
  1157. }{
  1158. {
  1159. doc: "Modifier creates a new file",
  1160. filename: "newfile",
  1161. modifier: createModifier(t),
  1162. expected: "the new content",
  1163. fileCount: filesInArchive + 1,
  1164. },
  1165. {
  1166. doc: "Modifier replaces a file",
  1167. filename: "file-2",
  1168. modifier: createOrReplaceModifier,
  1169. expected: "the new content",
  1170. fileCount: filesInArchive,
  1171. },
  1172. {
  1173. doc: "Modifier replaces the last file",
  1174. filename: fmt.Sprintf("file-%d", filesInArchive-1),
  1175. modifier: createOrReplaceModifier,
  1176. expected: "the new content",
  1177. fileCount: filesInArchive,
  1178. },
  1179. {
  1180. doc: "Modifier appends to a file",
  1181. filename: "file-3",
  1182. modifier: appendModifier,
  1183. expected: "fooo\nnext line",
  1184. fileCount: filesInArchive,
  1185. },
  1186. }
  1187. for _, testcase := range testcases {
  1188. sourceArchive, cleanup := buildSourceArchive(t, filesInArchive)
  1189. defer cleanup()
  1190. resultArchive := ReplaceFileTarWrapper(
  1191. sourceArchive,
  1192. map[string]TarModifierFunc{testcase.filename: testcase.modifier})
  1193. actual := readFileFromArchive(t, resultArchive, testcase.filename, testcase.fileCount, testcase.doc)
  1194. assert.Check(t, is.Equal(testcase.expected, actual), testcase.doc)
  1195. }
  1196. }
  1197. // TestPrefixHeaderReadable tests that files that could be created with the
  1198. // version of this package that was built with <=go17 are still readable.
  1199. func TestPrefixHeaderReadable(t *testing.T) {
  1200. skip.If(t, runtime.GOOS != "windows" && os.Getuid() != 0, "skipping test that requires root")
  1201. skip.If(t, userns.RunningInUserNS(), "skipping test that requires more than 010000000 UIDs, which is unlikely to be satisfied when running in userns")
  1202. // https://gist.github.com/stevvooe/e2a790ad4e97425896206c0816e1a882#file-out-go
  1203. var testFile = []byte("\x1f\x8b\x08\x08\x44\x21\x68\x59\x00\x03\x74\x2e\x74\x61\x72\x00\x4b\xcb\xcf\x67\xa0\x35\x30\x80\x00\x86\x06\x10\x47\x01\xc1\x37\x40\x00\x54\xb6\xb1\xa1\xa9\x99\x09\x48\x25\x1d\x40\x69\x71\x49\x62\x91\x02\xe5\x76\xa1\x79\x84\x21\x91\xd6\x80\x72\xaf\x8f\x82\x51\x30\x0a\x46\x36\x00\x00\xf0\x1c\x1e\x95\x00\x06\x00\x00")
  1204. tmpDir, err := os.MkdirTemp("", "prefix-test")
  1205. assert.NilError(t, err)
  1206. defer os.RemoveAll(tmpDir)
  1207. err = Untar(bytes.NewReader(testFile), tmpDir, nil)
  1208. assert.NilError(t, err)
  1209. baseName := "foo"
  1210. pth := strings.Repeat("a", 100-len(baseName)) + "/" + baseName
  1211. _, err = os.Lstat(filepath.Join(tmpDir, pth))
  1212. assert.NilError(t, err)
  1213. }
  1214. func buildSourceArchive(t *testing.T, numberOfFiles int) (io.ReadCloser, func()) {
  1215. srcDir, err := os.MkdirTemp("", "docker-test-srcDir")
  1216. assert.NilError(t, err)
  1217. _, err = prepareUntarSourceDirectory(numberOfFiles, srcDir, false)
  1218. assert.NilError(t, err)
  1219. sourceArchive, err := TarWithOptions(srcDir, &TarOptions{})
  1220. assert.NilError(t, err)
  1221. return sourceArchive, func() {
  1222. os.RemoveAll(srcDir)
  1223. sourceArchive.Close()
  1224. }
  1225. }
  1226. func createOrReplaceModifier(path string, header *tar.Header, content io.Reader) (*tar.Header, []byte, error) {
  1227. return &tar.Header{
  1228. Mode: 0600,
  1229. Typeflag: tar.TypeReg,
  1230. }, []byte("the new content"), nil
  1231. }
  1232. func createModifier(t *testing.T) TarModifierFunc {
  1233. return func(path string, header *tar.Header, content io.Reader) (*tar.Header, []byte, error) {
  1234. assert.Check(t, is.Nil(content))
  1235. return createOrReplaceModifier(path, header, content)
  1236. }
  1237. }
  1238. func appendModifier(path string, header *tar.Header, content io.Reader) (*tar.Header, []byte, error) {
  1239. buffer := bytes.Buffer{}
  1240. if content != nil {
  1241. if _, err := buffer.ReadFrom(content); err != nil {
  1242. return nil, nil, err
  1243. }
  1244. }
  1245. buffer.WriteString("\nnext line")
  1246. return &tar.Header{Mode: 0600, Typeflag: tar.TypeReg}, buffer.Bytes(), nil
  1247. }
  1248. func readFileFromArchive(t *testing.T, archive io.ReadCloser, name string, expectedCount int, doc string) string {
  1249. skip.If(t, runtime.GOOS != "windows" && os.Getuid() != 0, "skipping test that requires root")
  1250. destDir, err := os.MkdirTemp("", "docker-test-destDir")
  1251. assert.NilError(t, err)
  1252. defer os.RemoveAll(destDir)
  1253. err = Untar(archive, destDir, nil)
  1254. assert.NilError(t, err)
  1255. files, _ := os.ReadDir(destDir)
  1256. assert.Check(t, is.Len(files, expectedCount), doc)
  1257. content, err := os.ReadFile(filepath.Join(destDir, name))
  1258. assert.Check(t, err)
  1259. return string(content)
  1260. }
  1261. func TestDisablePigz(t *testing.T) {
  1262. _, err := exec.LookPath("unpigz")
  1263. if err != nil {
  1264. t.Log("Test will not check full path when Pigz not installed")
  1265. }
  1266. os.Setenv("MOBY_DISABLE_PIGZ", "true")
  1267. defer os.Unsetenv("MOBY_DISABLE_PIGZ")
  1268. r := testDecompressStream(t, "gz", "gzip -f")
  1269. // For the bufio pool
  1270. outsideReaderCloserWrapper := r.(*ioutils.ReadCloserWrapper)
  1271. // For the context canceller
  1272. contextReaderCloserWrapper := outsideReaderCloserWrapper.Reader.(*ioutils.ReadCloserWrapper)
  1273. assert.Equal(t, reflect.TypeOf(contextReaderCloserWrapper.Reader), reflect.TypeOf(&gzip.Reader{}))
  1274. }
  1275. func TestPigz(t *testing.T) {
  1276. r := testDecompressStream(t, "gz", "gzip -f")
  1277. // For the bufio pool
  1278. outsideReaderCloserWrapper := r.(*ioutils.ReadCloserWrapper)
  1279. // For the context canceller
  1280. contextReaderCloserWrapper := outsideReaderCloserWrapper.Reader.(*ioutils.ReadCloserWrapper)
  1281. _, err := exec.LookPath("unpigz")
  1282. if err == nil {
  1283. t.Log("Tested whether Pigz is used, as it installed")
  1284. // For the command wait wrapper
  1285. cmdWaitCloserWrapper := contextReaderCloserWrapper.Reader.(*ioutils.ReadCloserWrapper)
  1286. assert.Equal(t, reflect.TypeOf(cmdWaitCloserWrapper.Reader), reflect.TypeOf(&io.PipeReader{}))
  1287. } else {
  1288. t.Log("Tested whether Pigz is not used, as it not installed")
  1289. assert.Equal(t, reflect.TypeOf(contextReaderCloserWrapper.Reader), reflect.TypeOf(&gzip.Reader{}))
  1290. }
  1291. }
  1292. func TestNosysFileInfo(t *testing.T) {
  1293. st, err := os.Stat("archive_test.go")
  1294. assert.NilError(t, err)
  1295. h, err := tar.FileInfoHeader(nosysFileInfo{st}, "")
  1296. assert.NilError(t, err)
  1297. assert.Check(t, h.Uname == "")
  1298. assert.Check(t, h.Gname == "")
  1299. }