github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/pkg/uroot/initramfs/files_test.go (about) 1 // Copyright 2018 the u-root Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package initramfs 6 7 import ( 8 "fmt" 9 "io" 10 "io/ioutil" 11 "os" 12 "path/filepath" 13 "reflect" 14 "strings" 15 "testing" 16 17 "github.com/u-root/u-root/pkg/cpio" 18 "github.com/u-root/u-root/pkg/uio" 19 ) 20 21 func TestFilesAddFileNoFollow(t *testing.T) { 22 regularFile, err := ioutil.TempFile("", "archive-files-add-file") 23 if err != nil { 24 t.Error(err) 25 } 26 defer os.RemoveAll(regularFile.Name()) 27 28 dir, err := ioutil.TempDir("", "archive-add-files") 29 if err != nil { 30 t.Error(err) 31 } 32 defer os.RemoveAll(dir) 33 34 dir2, err := ioutil.TempDir("", "archive-add-files") 35 if err != nil { 36 t.Error(err) 37 } 38 defer os.RemoveAll(dir2) 39 40 os.Create(filepath.Join(dir, "foo2")) 41 os.Symlink(filepath.Join(dir, "foo2"), filepath.Join(dir2, "foo3")) 42 43 for i, tt := range []struct { 44 name string 45 af *Files 46 src string 47 dest string 48 result *Files 49 errContains string 50 }{ 51 { 52 name: "just add a file", 53 af: NewFiles(), 54 55 src: regularFile.Name(), 56 dest: "bar/foo", 57 58 result: &Files{ 59 Files: map[string]string{ 60 "bar/foo": regularFile.Name(), 61 }, 62 Records: map[string]cpio.Record{}, 63 }, 64 }, 65 { 66 name: "add symlinked file, NOT following", 67 af: NewFiles(), 68 src: filepath.Join(dir2, "foo3"), 69 dest: "bar/foo", 70 result: &Files{ 71 Files: map[string]string{ 72 "bar/foo": filepath.Join(dir2, "foo3"), 73 }, 74 Records: map[string]cpio.Record{}, 75 }, 76 }, 77 } { 78 t.Run(fmt.Sprintf("Test %02d: %s", i, tt.name), func(t *testing.T) { 79 err := tt.af.AddFileNoFollow(tt.src, tt.dest) 80 if err != nil && !strings.Contains(err.Error(), tt.errContains) { 81 t.Errorf("Error is %v, does not contain %v", err, tt.errContains) 82 } 83 if err == nil && len(tt.errContains) > 0 { 84 t.Errorf("Got no error, want %v", tt.errContains) 85 } 86 87 if tt.result != nil && !reflect.DeepEqual(tt.af, tt.result) { 88 t.Errorf("got %v, want %v", tt.af, tt.result) 89 } 90 }) 91 } 92 } 93 94 func TestFilesAddFile(t *testing.T) { 95 regularFile, err := ioutil.TempFile("", "archive-files-add-file") 96 if err != nil { 97 t.Error(err) 98 } 99 defer os.RemoveAll(regularFile.Name()) 100 101 dir, err := ioutil.TempDir("", "archive-add-files") 102 if err != nil { 103 t.Error(err) 104 } 105 defer os.RemoveAll(dir) 106 107 dir2, err := ioutil.TempDir("", "archive-add-files") 108 if err != nil { 109 t.Error(err) 110 } 111 defer os.RemoveAll(dir2) 112 113 dir3, err := ioutil.TempDir("", "archive-add-files") 114 if err != nil { 115 t.Error(err) 116 } 117 defer os.RemoveAll(dir3) 118 119 os.Create(filepath.Join(dir, "foo")) 120 os.Create(filepath.Join(dir, "foo2")) 121 os.Symlink(filepath.Join(dir, "foo2"), filepath.Join(dir2, "foo3")) 122 123 fooDir := filepath.Join(dir3, "fooDir") 124 os.Mkdir(fooDir, os.ModePerm) 125 symlinkToDir3 := filepath.Join(dir3, "fooSymDir/") 126 os.Symlink(fooDir, symlinkToDir3) 127 os.Create(filepath.Join(fooDir, "foo")) 128 os.Create(filepath.Join(fooDir, "bar")) 129 130 for i, tt := range []struct { 131 name string 132 af *Files 133 src string 134 dest string 135 result *Files 136 errContains string 137 }{ 138 { 139 name: "just add a file", 140 af: NewFiles(), 141 142 src: regularFile.Name(), 143 dest: "bar/foo", 144 145 result: &Files{ 146 Files: map[string]string{ 147 "bar/foo": regularFile.Name(), 148 }, 149 Records: map[string]cpio.Record{}, 150 }, 151 }, 152 { 153 name: "add symlinked file, following", 154 af: NewFiles(), 155 src: filepath.Join(dir2, "foo3"), 156 dest: "bar/foo", 157 result: &Files{ 158 Files: map[string]string{ 159 "bar/foo": filepath.Join(dir, "foo2"), 160 }, 161 Records: map[string]cpio.Record{}, 162 }, 163 }, 164 { 165 name: "add symlinked directory, following", 166 af: NewFiles(), 167 src: symlinkToDir3, 168 dest: "foo/", 169 result: &Files{ 170 Files: map[string]string{ 171 "foo": fooDir, 172 "foo/foo": filepath.Join(fooDir, "foo"), 173 "foo/bar": filepath.Join(fooDir, "bar"), 174 }, 175 Records: map[string]cpio.Record{}, 176 }, 177 }, 178 { 179 name: "add file that exists in Files", 180 af: &Files{ 181 Files: map[string]string{ 182 "bar/foo": "/some/other/place", 183 }, 184 }, 185 src: regularFile.Name(), 186 dest: "bar/foo", 187 result: &Files{ 188 Files: map[string]string{ 189 "bar/foo": "/some/other/place", 190 }, 191 }, 192 errContains: "already exists in archive", 193 }, 194 { 195 name: "add a file that exists in Records", 196 af: &Files{ 197 Records: map[string]cpio.Record{ 198 "bar/foo": cpio.Symlink("bar/foo", "/some/other/place"), 199 }, 200 }, 201 src: regularFile.Name(), 202 dest: "bar/foo", 203 result: &Files{ 204 Records: map[string]cpio.Record{ 205 "bar/foo": cpio.Symlink("bar/foo", "/some/other/place"), 206 }, 207 }, 208 errContains: "already exists in archive", 209 }, 210 { 211 name: "add a file that already exists in Files, but is the same one", 212 af: &Files{ 213 Files: map[string]string{ 214 "bar/foo": regularFile.Name(), 215 }, 216 }, 217 src: regularFile.Name(), 218 dest: "bar/foo", 219 result: &Files{ 220 Files: map[string]string{ 221 "bar/foo": regularFile.Name(), 222 }, 223 }, 224 }, 225 { 226 name: "absolute destination paths are made relative", 227 af: &Files{ 228 Files: map[string]string{}, 229 }, 230 src: dir, 231 dest: "/bar/foo", 232 result: &Files{ 233 Files: map[string]string{ 234 "bar/foo": dir, 235 "bar/foo/foo": filepath.Join(dir, "foo"), 236 "bar/foo/foo2": filepath.Join(dir, "foo2"), 237 }, 238 }, 239 }, 240 { 241 name: "add a directory", 242 af: &Files{ 243 Files: map[string]string{}, 244 }, 245 src: dir, 246 dest: "bar/foo", 247 result: &Files{ 248 Files: map[string]string{ 249 "bar/foo": dir, 250 "bar/foo/foo": filepath.Join(dir, "foo"), 251 "bar/foo/foo2": filepath.Join(dir, "foo2"), 252 }, 253 }, 254 }, 255 { 256 name: "add a different directory to the same destination, no overlapping children", 257 af: &Files{ 258 Files: map[string]string{ 259 "bar/foo": "/some/place/real", 260 "bar/foo/zed": "/some/place/real/zed", 261 }, 262 }, 263 src: dir, 264 dest: "bar/foo", 265 result: &Files{ 266 Files: map[string]string{ 267 "bar/foo": dir, 268 "bar/foo/foo": filepath.Join(dir, "foo"), 269 "bar/foo/foo2": filepath.Join(dir, "foo2"), 270 "bar/foo/zed": "/some/place/real/zed", 271 }, 272 }, 273 }, 274 { 275 name: "add a different directory to the same destination, overlapping children", 276 af: &Files{ 277 Files: map[string]string{ 278 "bar/foo": "/some/place/real", 279 "bar/foo/foo2": "/some/place/real/zed", 280 }, 281 }, 282 src: dir, 283 dest: "bar/foo", 284 errContains: "already exists in archive", 285 }, 286 } { 287 t.Run(fmt.Sprintf("Test %02d: %s", i, tt.name), func(t *testing.T) { 288 err := tt.af.AddFile(tt.src, tt.dest) 289 if err != nil && !strings.Contains(err.Error(), tt.errContains) { 290 t.Errorf("Error is %v, does not contain %v", err, tt.errContains) 291 } 292 if err == nil && len(tt.errContains) > 0 { 293 t.Errorf("Got no error, want %v", tt.errContains) 294 } 295 296 if tt.result != nil && !reflect.DeepEqual(tt.af, tt.result) { 297 t.Errorf("got %v, want %v", tt.af, tt.result) 298 } 299 }) 300 } 301 } 302 303 func TestFilesAddRecord(t *testing.T) { 304 for i, tt := range []struct { 305 af *Files 306 record cpio.Record 307 308 result *Files 309 errContains string 310 }{ 311 { 312 af: NewFiles(), 313 record: cpio.Symlink("bar/foo", ""), 314 result: &Files{ 315 Files: map[string]string{}, 316 Records: map[string]cpio.Record{ 317 "bar/foo": cpio.Symlink("bar/foo", ""), 318 }, 319 }, 320 }, 321 { 322 af: &Files{ 323 Files: map[string]string{ 324 "bar/foo": "/some/other/place", 325 }, 326 }, 327 record: cpio.Symlink("bar/foo", ""), 328 result: &Files{ 329 Files: map[string]string{ 330 "bar/foo": "/some/other/place", 331 }, 332 }, 333 errContains: "already exists in archive", 334 }, 335 { 336 af: &Files{ 337 Records: map[string]cpio.Record{ 338 "bar/foo": cpio.Symlink("bar/foo", "/some/other/place"), 339 }, 340 }, 341 record: cpio.Symlink("bar/foo", ""), 342 result: &Files{ 343 Records: map[string]cpio.Record{ 344 "bar/foo": cpio.Symlink("bar/foo", "/some/other/place"), 345 }, 346 }, 347 errContains: "already exists in archive", 348 }, 349 { 350 af: &Files{ 351 Records: map[string]cpio.Record{ 352 "bar/foo": cpio.Symlink("bar/foo", "/some/other/place"), 353 }, 354 }, 355 record: cpio.Symlink("bar/foo", "/some/other/place"), 356 result: &Files{ 357 Records: map[string]cpio.Record{ 358 "bar/foo": cpio.Symlink("bar/foo", "/some/other/place"), 359 }, 360 }, 361 }, 362 { 363 record: cpio.Symlink("/bar/foo", ""), 364 errContains: "must not be absolute", 365 }, 366 } { 367 t.Run(fmt.Sprintf("Test %02d", i), func(t *testing.T) { 368 err := tt.af.AddRecord(tt.record) 369 if err != nil && !strings.Contains(err.Error(), tt.errContains) { 370 t.Errorf("Error is %v, does not contain %v", err, tt.errContains) 371 } 372 if err == nil && len(tt.errContains) > 0 { 373 t.Errorf("Got no error, want %v", tt.errContains) 374 } 375 376 if !reflect.DeepEqual(tt.af, tt.result) { 377 t.Errorf("got %v, want %v", tt.af, tt.result) 378 } 379 }) 380 } 381 } 382 383 func TestFilesfillInParent(t *testing.T) { 384 for i, tt := range []struct { 385 af *Files 386 result *Files 387 }{ 388 { 389 af: &Files{ 390 Records: map[string]cpio.Record{ 391 "foo/bar": cpio.Directory("foo/bar", 0777), 392 }, 393 }, 394 result: &Files{ 395 Records: map[string]cpio.Record{ 396 "foo/bar": cpio.Directory("foo/bar", 0777), 397 "foo": cpio.Directory("foo", 0755), 398 }, 399 }, 400 }, 401 { 402 af: &Files{ 403 Files: map[string]string{ 404 "baz/baz/baz": "/somewhere", 405 }, 406 Records: map[string]cpio.Record{ 407 "foo/bar": cpio.Directory("foo/bar", 0777), 408 }, 409 }, 410 result: &Files{ 411 Files: map[string]string{ 412 "baz/baz/baz": "/somewhere", 413 }, 414 Records: map[string]cpio.Record{ 415 "foo/bar": cpio.Directory("foo/bar", 0777), 416 "foo": cpio.Directory("foo", 0755), 417 "baz": cpio.Directory("baz", 0755), 418 "baz/baz": cpio.Directory("baz/baz", 0755), 419 }, 420 }, 421 }, 422 { 423 af: &Files{}, 424 result: &Files{}, 425 }, 426 } { 427 t.Run(fmt.Sprintf("Test %02d", i), func(t *testing.T) { 428 tt.af.fillInParents() 429 if !reflect.DeepEqual(tt.af, tt.result) { 430 t.Errorf("got %v, want %v", tt.af, tt.result) 431 } 432 }) 433 } 434 } 435 436 type MockArchiver struct { 437 Records Records 438 FinishCalled bool 439 BaseArchive []cpio.Record 440 } 441 442 func (ma *MockArchiver) WriteRecord(r cpio.Record) error { 443 if _, ok := ma.Records[r.Name]; ok { 444 return fmt.Errorf("file exists") 445 } 446 ma.Records[r.Name] = r 447 return nil 448 } 449 450 func (ma *MockArchiver) Finish() error { 451 ma.FinishCalled = true 452 return nil 453 } 454 455 func (ma *MockArchiver) ReadRecord() (cpio.Record, error) { 456 if len(ma.BaseArchive) > 0 { 457 next := ma.BaseArchive[0] 458 ma.BaseArchive = ma.BaseArchive[1:] 459 return next, nil 460 } 461 return cpio.Record{}, io.EOF 462 } 463 464 type Records map[string]cpio.Record 465 466 func RecordsEqual(r1, r2 Records, recordEqual func(cpio.Record, cpio.Record) bool) bool { 467 for name, s1 := range r1 { 468 s2, ok := r2[name] 469 if !ok { 470 return false 471 } 472 if !recordEqual(s1, s2) { 473 return false 474 } 475 } 476 for name := range r2 { 477 if _, ok := r1[name]; !ok { 478 return false 479 } 480 } 481 return true 482 } 483 484 func sameNameModeContent(r1 cpio.Record, r2 cpio.Record) bool { 485 if r1.Name != r2.Name || r1.Mode != r2.Mode { 486 return false 487 } 488 return uio.ReaderAtEqual(r1.ReaderAt, r2.ReaderAt) 489 } 490 491 func TestOptsWrite(t *testing.T) { 492 for i, tt := range []struct { 493 desc string 494 opts *Opts 495 ma *MockArchiver 496 want Records 497 err error 498 }{ 499 { 500 desc: "no conflicts, just records", 501 opts: &Opts{ 502 Files: &Files{ 503 Records: map[string]cpio.Record{ 504 "foo": cpio.Symlink("foo", "elsewhere"), 505 }, 506 }, 507 }, 508 ma: &MockArchiver{ 509 Records: make(Records), 510 BaseArchive: []cpio.Record{ 511 cpio.Directory("etc", 0777), 512 cpio.Directory("etc/nginx", 0777), 513 }, 514 }, 515 want: Records{ 516 "foo": cpio.Symlink("foo", "elsewhere"), 517 "etc": cpio.Directory("etc", 0777), 518 "etc/nginx": cpio.Directory("etc/nginx", 0777), 519 }, 520 }, 521 { 522 desc: "default already exists", 523 opts: &Opts{ 524 Files: &Files{ 525 Records: map[string]cpio.Record{ 526 "etc": cpio.Symlink("etc", "whatever"), 527 }, 528 }, 529 }, 530 ma: &MockArchiver{ 531 Records: make(Records), 532 BaseArchive: []cpio.Record{ 533 cpio.Directory("etc", 0777), 534 }, 535 }, 536 want: Records{ 537 "etc": cpio.Symlink("etc", "whatever"), 538 }, 539 }, 540 { 541 desc: "no conflicts, missing parent automatically created", 542 opts: &Opts{ 543 Files: &Files{ 544 Records: map[string]cpio.Record{ 545 "foo/bar/baz": cpio.Symlink("foo/bar/baz", "elsewhere"), 546 }, 547 }, 548 }, 549 ma: &MockArchiver{ 550 Records: make(Records), 551 }, 552 want: Records{ 553 "foo": cpio.Directory("foo", 0755), 554 "foo/bar": cpio.Directory("foo/bar", 0755), 555 "foo/bar/baz": cpio.Symlink("foo/bar/baz", "elsewhere"), 556 }, 557 }, 558 { 559 desc: "parent only automatically created if not already exists", 560 opts: &Opts{ 561 Files: &Files{ 562 Records: map[string]cpio.Record{ 563 "foo/bar": cpio.Directory("foo/bar", 0444), 564 "foo/bar/baz": cpio.Symlink("foo/bar/baz", "elsewhere"), 565 }, 566 }, 567 }, 568 ma: &MockArchiver{ 569 Records: make(Records), 570 }, 571 want: Records{ 572 "foo": cpio.Directory("foo", 0755), 573 "foo/bar": cpio.Directory("foo/bar", 0444), 574 "foo/bar/baz": cpio.Symlink("foo/bar/baz", "elsewhere"), 575 }, 576 }, 577 { 578 desc: "base archive", 579 opts: &Opts{ 580 Files: &Files{ 581 Records: map[string]cpio.Record{ 582 "foo/bar": cpio.Symlink("foo/bar", "elsewhere"), 583 "exists": cpio.Directory("exists", 0777), 584 }, 585 }, 586 }, 587 ma: &MockArchiver{ 588 Records: make(Records), 589 BaseArchive: []cpio.Record{ 590 cpio.Directory("etc", 0755), 591 cpio.Directory("foo", 0444), 592 cpio.Directory("exists", 0), 593 }, 594 }, 595 want: Records{ 596 "etc": cpio.Directory("etc", 0755), 597 "exists": cpio.Directory("exists", 0777), 598 "foo": cpio.Directory("foo", 0444), 599 "foo/bar": cpio.Symlink("foo/bar", "elsewhere"), 600 }, 601 }, 602 { 603 desc: "base archive with init, no user init", 604 opts: &Opts{ 605 Files: &Files{ 606 Records: map[string]cpio.Record{}, 607 }, 608 }, 609 ma: &MockArchiver{ 610 Records: make(Records), 611 BaseArchive: []cpio.Record{ 612 cpio.StaticFile("init", "boo", 0555), 613 }, 614 }, 615 want: Records{ 616 "init": cpio.StaticFile("init", "boo", 0555), 617 }, 618 }, 619 { 620 desc: "base archive with init and user init", 621 opts: &Opts{ 622 Files: &Files{ 623 Records: map[string]cpio.Record{ 624 "init": cpio.StaticFile("init", "bar", 0444), 625 }, 626 }, 627 }, 628 ma: &MockArchiver{ 629 Records: make(Records), 630 BaseArchive: []cpio.Record{ 631 cpio.StaticFile("init", "boo", 0555), 632 }, 633 }, 634 want: Records{ 635 "init": cpio.StaticFile("init", "bar", 0444), 636 "inito": cpio.StaticFile("inito", "boo", 0555), 637 }, 638 }, 639 { 640 desc: "base archive with init, use existing init", 641 opts: &Opts{ 642 Files: &Files{ 643 Records: map[string]cpio.Record{}, 644 }, 645 UseExistingInit: true, 646 }, 647 ma: &MockArchiver{ 648 Records: make(Records), 649 BaseArchive: []cpio.Record{ 650 cpio.StaticFile("init", "boo", 0555), 651 }, 652 }, 653 want: Records{ 654 "init": cpio.StaticFile("init", "boo", 0555), 655 }, 656 }, 657 { 658 desc: "base archive with init and user init, use existing init", 659 opts: &Opts{ 660 Files: &Files{ 661 Records: map[string]cpio.Record{ 662 "init": cpio.StaticFile("init", "huh", 0111), 663 }, 664 }, 665 UseExistingInit: true, 666 }, 667 ma: &MockArchiver{ 668 Records: make(Records), 669 BaseArchive: []cpio.Record{ 670 cpio.StaticFile("init", "boo", 0555), 671 }, 672 }, 673 want: Records{ 674 "init": cpio.StaticFile("init", "boo", 0555), 675 "inito": cpio.StaticFile("inito", "huh", 0111), 676 }, 677 }, 678 } { 679 t.Run(fmt.Sprintf("Test %02d (%s)", i, tt.desc), func(t *testing.T) { 680 tt.opts.BaseArchive = tt.ma 681 tt.opts.OutputFile = tt.ma 682 683 if err := Write(tt.opts); err != tt.err { 684 t.Errorf("Write() = %v, want %v", err, tt.err) 685 } else if err == nil && !tt.ma.FinishCalled { 686 t.Errorf("Finish wasn't called on archive") 687 } 688 689 if !RecordsEqual(tt.ma.Records, tt.want, sameNameModeContent) { 690 t.Errorf("Write() = %v, want %v", tt.ma.Records, tt.want) 691 } 692 }) 693 } 694 }