1 // SPDX-License-Identifier: GPL-3.0-or-later
2 // Copyright (C) 2021-2024 Simon Ruderich
15 "ruderich.org/simon/safcm"
16 ft "ruderich.org/simon/safcm/remote/sync/filetest"
17 "ruderich.org/simon/safcm/testutil"
20 var randFilesRegexp = regexp.MustCompile(`\d+"$`)
22 func TestSyncFiles(t *testing.T) {
23 cwd, err := os.Getwd()
27 defer os.Chdir(cwd) //nolint:errcheck
29 err = os.RemoveAll("testdata")
33 err = os.Mkdir("testdata", 0700)
40 Mode: fs.ModeDir | 0700,
42 user, uid, group, gid := ft.CurrentUserAndGroup()
44 skipUnlessCiRun := len(os.Getenv("SAFCM_CI_RUN")) == 0
46 tmpTestFilePath := "/tmp/safcm-sync-files-test-file"
55 expResp safcm.MsgSyncResp
60 // NOTE: Also update MsgSyncResp in safcm test cases when
61 // changing the MsgSyncResp struct!
63 // See TestSyncFile() for most file related tests. This
64 // function only tests the overall results and triggers.
70 Files: map[string]*safcm.File{
74 Mode: fs.ModeDir | 0700,
81 Mode: fs.ModeDir | 0755,
91 Data: []byte("content\n"),
101 Mode: fs.ModeDir | 0755,
106 Data: []byte("content\n"),
110 FileChanges: []safcm.FileChange{
114 New: safcm.FileChangeInfo{
115 Mode: fs.ModeDir | 0755,
125 New: safcm.FileChangeInfo{
136 `4: files: "." (group): unchanged`,
137 `4: files: "dir" (group): will create`,
138 `3: files: "dir" (group): creating`,
139 `4: files: "dir" (group): creating directory`,
140 `4: files: "dir" (group): chmodding drwxr-xr-x`,
141 fmt.Sprintf(`4: files: "dir" (group): chowning %d/%d`, uid, gid),
142 `4: files: "dir/file" (group): will create`,
143 `3: files: "dir/file" (group): creating`,
144 `4: files: "dir/file" (group): creating temporary file "dir/.file*"`,
145 `4: files: "dir/file" (group): renaming "dir/.fileRND"`,
154 Files: map[string]*safcm.File{
158 Mode: fs.ModeDir | 0700,
165 Mode: fs.ModeDir | 0755,
175 Data: []byte("content\n"),
180 ft.CreateDirectory("dir", 0755)
181 ft.CreateFile("dir/file", "content\n", 0644)
188 Mode: fs.ModeDir | 0755,
193 Data: []byte("content\n"),
198 `4: files: "." (group): unchanged`,
199 `4: files: "dir" (group): unchanged`,
200 `4: files: "dir/file" (group): unchanged`,
206 "invalid File: user",
209 Files: map[string]*safcm.File{
213 Mode: fs.ModeDir | 0700,
227 fmt.Errorf("\".\": cannot set both User (\"user\") and Uid (1)"),
230 "invalid File: group",
233 Files: map[string]*safcm.File{
237 Mode: fs.ModeDir | 0700,
251 fmt.Errorf("\".\": cannot set both Group (\"group\") and Gid (1)"),
255 // We use relative paths for most tests because we
256 // don't want to modify the running system. Use this
257 // test (and the one below for triggers) as a basic
258 // check that absolute paths work.
260 // Use numeric IDs as not all systems use root/root;
261 // for example BSDs use root/wheel.
262 "absolute paths: no change",
265 Files: map[string]*safcm.File{
269 Mode: fs.ModeDir | 0755,
276 Mode: fs.ModeDir | 0755,
283 Mode: fs.ModeDir | 0777 | fs.ModeSticky,
296 `4: files: "/" (group): unchanged`,
297 `4: files: "/etc" (group): unchanged`,
298 `4: files: "/tmp" (group): unchanged`,
304 "triggers: no change",
307 Files: map[string]*safcm.File{
311 Mode: fs.ModeDir | 0700,
314 TriggerCommands: []string{
321 Mode: fs.ModeDir | 0755,
324 TriggerCommands: []string{
334 Data: []byte("content\n"),
335 TriggerCommands: []string{
336 "echo trigger dir/file",
342 ft.CreateDirectory("dir", 0755)
343 ft.CreateFile("dir/file", "content\n", 0644)
350 Mode: fs.ModeDir | 0755,
355 Data: []byte("content\n"),
360 `4: files: "." (group): unchanged`,
361 `4: files: "dir" (group): unchanged`,
362 `4: files: "dir/file" (group): unchanged`,
368 "triggers: change root",
371 Files: map[string]*safcm.File{
375 Mode: fs.ModeDir | 0700,
378 TriggerCommands: []string{
385 Mode: fs.ModeDir | 0755,
388 TriggerCommands: []string{
398 Data: []byte("content\n"),
399 TriggerCommands: []string{
400 "echo trigger dir/file",
406 ft.CreateDirectoryExists(".", 0750)
407 ft.CreateDirectory("dir", 0755)
408 ft.CreateFile("dir/file", "content\n", 0644)
417 Mode: fs.ModeDir | 0755,
422 Data: []byte("content\n"),
426 FileChanges: []safcm.FileChange{
429 Old: safcm.FileChangeInfo{
430 Mode: fs.ModeDir | 0750,
436 New: safcm.FileChangeInfo{
437 Mode: fs.ModeDir | 0700,
447 `4: files: "." (group): permission differs drwxr-x--- -> drwx------`,
448 `3: files: "." (group): updating`,
449 `4: files: "." (group): chmodding drwx------`,
450 `3: files: ".": queuing trigger on "."`,
451 `4: files: "dir" (group): unchanged`,
452 `4: files: "dir/file" (group): unchanged`,
458 "triggers: change middle",
461 Files: map[string]*safcm.File{
465 Mode: fs.ModeDir | 0700,
468 TriggerCommands: []string{
475 Mode: fs.ModeDir | 0755,
478 TriggerCommands: []string{
488 Data: []byte("content\n"),
489 TriggerCommands: []string{
490 "echo trigger dir/file",
496 ft.CreateDirectory("dir", 0750)
497 ft.CreateFile("dir/file", "content\n", 0644)
507 Mode: fs.ModeDir | 0755,
512 Data: []byte("content\n"),
516 FileChanges: []safcm.FileChange{
519 Old: safcm.FileChangeInfo{
520 Mode: fs.ModeDir | 0750,
526 New: safcm.FileChangeInfo{
527 Mode: fs.ModeDir | 0755,
537 `4: files: "." (group): unchanged`,
538 `4: files: "dir" (group): permission differs drwxr-x--- -> drwxr-xr-x`,
539 `3: files: "dir" (group): updating`,
540 `4: files: "dir" (group): chmodding drwxr-xr-x`,
541 `3: files: "dir": queuing trigger on "."`,
542 `3: files: "dir": queuing trigger on "dir"`,
543 `4: files: "dir/file" (group): unchanged`,
549 "triggers: change leaf",
552 Files: map[string]*safcm.File{
556 Mode: fs.ModeDir | 0700,
559 TriggerCommands: []string{
566 Mode: fs.ModeDir | 0755,
569 TriggerCommands: []string{
579 Data: []byte("content\n"),
580 TriggerCommands: []string{
581 "echo trigger dir/file",
587 ft.CreateDirectory("dir", 0755)
598 Mode: fs.ModeDir | 0755,
603 Data: []byte("content\n"),
607 FileChanges: []safcm.FileChange{
611 New: safcm.FileChangeInfo{
622 `4: files: "." (group): unchanged`,
623 `4: files: "dir" (group): unchanged`,
624 `4: files: "dir/file" (group): will create`,
625 `3: files: "dir/file" (group): creating`,
626 `4: files: "dir/file" (group): creating temporary file "dir/.file*"`,
627 `4: files: "dir/file" (group): renaming "dir/.fileRND"`,
628 `3: files: "dir/file": queuing trigger on "."`,
629 `3: files: "dir/file": queuing trigger on "dir"`,
630 `3: files: "dir/file": queuing trigger on "dir/file"`,
636 "triggers: multiple changes",
639 Files: map[string]*safcm.File{
643 Mode: fs.ModeDir | 0700,
646 TriggerCommands: []string{
653 Mode: fs.ModeDir | 0755,
656 TriggerCommands: []string{
666 Data: []byte("content\n"),
667 TriggerCommands: []string{
668 "echo trigger dir/file",
683 Mode: fs.ModeDir | 0755,
688 Data: []byte("content\n"),
692 FileChanges: []safcm.FileChange{
696 New: safcm.FileChangeInfo{
697 Mode: fs.ModeDir | 0755,
707 New: safcm.FileChangeInfo{
718 `4: files: "." (group): unchanged`,
719 `4: files: "dir" (group): will create`,
720 `3: files: "dir" (group): creating`,
721 `4: files: "dir" (group): creating directory`,
722 `4: files: "dir" (group): chmodding drwxr-xr-x`,
723 fmt.Sprintf(`4: files: "dir" (group): chowning %d/%d`, uid, gid),
724 `3: files: "dir": queuing trigger on "."`,
725 `3: files: "dir": queuing trigger on "dir"`,
726 `4: files: "dir/file" (group): will create`,
727 `3: files: "dir/file" (group): creating`,
728 `4: files: "dir/file" (group): creating temporary file "dir/.file*"`,
729 `4: files: "dir/file" (group): renaming "dir/.fileRND"`,
730 `4: files: "dir/file": skipping trigger on ".", already active`,
731 `4: files: "dir/file": skipping trigger on "dir", already active`,
732 `3: files: "dir/file": queuing trigger on "dir/file"`,
738 "triggers: absolute paths",
741 Files: map[string]*safcm.File{
745 Mode: fs.ModeDir | 0755,
748 TriggerCommands: []string{
755 Mode: fs.ModeDir | 0777 | fs.ModeSticky,
758 TriggerCommands: []string{
764 Path: tmpTestFilePath,
768 TriggerCommands: []string{
769 "echo trigger /tmp/file",
778 // Don't use variable for more robust test
779 "/tmp/safcm-sync-files-test-file",
785 FileChanges: []safcm.FileChange{
787 Path: "/tmp/safcm-sync-files-test-file",
789 New: safcm.FileChangeInfo{
800 `4: files: "/" (group): unchanged`,
801 `4: files: "/tmp" (group): unchanged`,
802 `4: files: "/tmp/safcm-sync-files-test-file" (group): will create`,
803 `3: files: "/tmp/safcm-sync-files-test-file" (group): creating`,
804 `4: files: "/tmp/safcm-sync-files-test-file" (group): creating temporary file "/tmp/.safcm-sync-files-test-file*"`,
805 `4: files: "/tmp/safcm-sync-files-test-file" (group): renaming "/tmp/.safcm-sync-files-test-fileRND"`,
806 `3: files: "/tmp/safcm-sync-files-test-file": queuing trigger on "/"`,
807 `3: files: "/tmp/safcm-sync-files-test-file": queuing trigger on "/tmp"`,
808 `3: files: "/tmp/safcm-sync-files-test-file": queuing trigger on "/tmp/safcm-sync-files-test-file"`,
814 for _, tc := range tests {
815 t.Run(tc.name, func(t *testing.T) {
820 // Create separate test directory for each test case
821 path := filepath.Join(cwd, "testdata", "files-"+tc.name)
822 err := os.Mkdir(path, 0700)
831 if tc.prepare != nil {
835 s, res := prepareSync(tc.req, &testRunner{
838 err = s.setDefaults()
844 testutil.AssertErrorEqual(t, "err", err, tc.expErr)
846 // Remove random file names from result
847 for i, x := range dbg {
848 dbg[i] = randFilesRegexp.ReplaceAllString(x, `RND"`)
850 testutil.AssertEqual(t, "dbg", dbg, tc.expDbg)
852 files, err := ft.WalkDir(path)
856 testutil.AssertEqual(t, "files", files, tc.expFiles)
858 testutil.AssertEqual(t, "resp", s.resp, tc.expResp)
859 testutil.AssertEqual(t, "triggers",
860 s.triggers, tc.expTriggers)
864 os.Remove(tmpTestFilePath)
866 err = os.RemoveAll(filepath.Join(cwd, "testdata"))
873 func TestSyncFile(t *testing.T) {
874 cwd, err := os.Getwd()
878 defer os.Chdir(cwd) //nolint:errcheck
880 err = os.RemoveAll("testdata")
884 err = os.Mkdir("testdata", 0700)
891 Mode: fs.ModeDir | 0700,
893 user, uid, group, gid := ft.CurrentUserAndGroup()
902 expResp safcm.MsgSyncResp
907 // NOTE: Also update MsgSyncResp in safcm test cases when
908 // changing the MsgSyncResp struct!
910 // TODO: Add tests for chown and run them only as root
922 Data: []byte("content\n"),
932 Data: []byte("content\n"),
936 FileChanges: []safcm.FileChange{
940 New: safcm.FileChangeInfo{
951 `4: files: "file" (group): will create`,
952 `3: files: "file" (group): creating`,
953 `4: files: "file" (group): creating temporary file ".file*"`,
954 `4: files: "file" (group): renaming ".fileRND"`,
959 "file: create (dry-run)",
968 Data: []byte("content\n"),
975 FileChanges: []safcm.FileChange{
979 New: safcm.FileChangeInfo{
990 `4: files: "file" (group): will create`,
991 `3: files: "file" (group): creating`,
992 `4: files: "file" (group): dry-run, skipping changes`,
998 "file: create, missing parent (dry-run)",
1003 Path: "does-not-exist/file",
1007 Data: []byte("content\n"),
1014 FileChanges: []safcm.FileChange{
1016 Path: "does-not-exist/file",
1018 New: safcm.FileChangeInfo{
1029 `4: files: "does-not-exist/file" (group): will create (parent missing)`,
1030 `4: files: "does-not-exist/file" (group): dry-run, skipping changes`,
1043 Data: []byte("content\n"),
1047 ft.CreateFile("file", "content\n", 0644)
1055 Data: []byte("content\n"),
1058 safcm.MsgSyncResp{},
1060 `4: files: "file" (group): unchanged`,
1066 "file: unchanged (non-default user-group)",
1075 Data: []byte("content\n"),
1079 ft.CreateFile("file", "content\n", 0644)
1087 Data: []byte("content\n"),
1090 safcm.MsgSyncResp{},
1092 `4: files: "file" (group): unchanged`,
1102 Mode: 0755 | fs.ModeSetuid,
1105 Data: []byte("content\n"),
1109 ft.CreateFile("file", "content\n", 0755)
1116 Mode: 0755 | fs.ModeSetuid,
1117 Data: []byte("content\n"),
1121 FileChanges: []safcm.FileChange{
1124 Old: safcm.FileChangeInfo{
1131 New: safcm.FileChangeInfo{
1132 Mode: 0755 | fs.ModeSetuid,
1142 `4: files: "file" (group): permission differs -rwxr-xr-x -> urwxr-xr-x`,
1143 `3: files: "file" (group): updating`,
1144 `4: files: "file" (group): creating temporary file ".file*"`,
1145 `4: files: "file" (group): renaming ".fileRND"`,
1158 Data: []byte("content\n"),
1162 ft.CreateFile("file", "old content\n", 0644)
1170 Data: []byte("content\n"),
1174 FileChanges: []safcm.FileChange{
1177 Old: safcm.FileChangeInfo{
1184 New: safcm.FileChangeInfo{
1191 DataDiff: `@@ -1,2 +1,2 @@
1200 `4: files: "file" (group): content differs`,
1201 `3: files: "file" (group): updating`,
1202 `4: files: "file" (group): creating temporary file ".file*"`,
1203 `4: files: "file" (group): renaming ".fileRND"`,
1215 Mode: fs.ModeSymlink | 0777,
1218 Data: []byte("target"),
1227 Mode: fs.ModeSymlink | 0777,
1228 Data: []byte("target"),
1232 FileChanges: []safcm.FileChange{
1236 New: safcm.FileChangeInfo{
1237 Mode: fs.ModeSymlink | 0777,
1247 `4: files: "link" (group): will create`,
1248 `3: files: "link" (group): creating`,
1249 `4: files: "link" (group): creating temporary symlink ".linkRND"`,
1250 `4: files: "link" (group): renaming ".linkRND"`,
1255 "symlink: create (conflict)",
1259 Mode: fs.ModeSymlink | 0777,
1262 Data: []byte("target"),
1266 ft.CreateFile(".link8717895732742165505", "", 0600)
1272 Path: ".link8717895732742165505",
1278 Mode: fs.ModeSymlink | 0777,
1279 Data: []byte("target"),
1283 FileChanges: []safcm.FileChange{
1287 New: safcm.FileChangeInfo{
1288 Mode: fs.ModeSymlink | 0777,
1298 `4: files: "link" (group): will create`,
1299 `3: files: "link" (group): creating`,
1300 `4: files: "link" (group): creating temporary symlink ".linkRND"`,
1301 `4: files: "link" (group): creating temporary symlink ".linkRND"`,
1302 `4: files: "link" (group): renaming ".linkRND"`,
1307 "symlink: create (dry-run)",
1313 Mode: fs.ModeSymlink | 0777,
1316 Data: []byte("target"),
1323 FileChanges: []safcm.FileChange{
1327 New: safcm.FileChangeInfo{
1328 Mode: fs.ModeSymlink | 0777,
1338 `4: files: "link" (group): will create`,
1339 `3: files: "link" (group): creating`,
1340 `4: files: "link" (group): dry-run, skipping changes`,
1346 "symlink: unchanged",
1350 Mode: fs.ModeSymlink | 0777,
1353 Data: []byte("target"),
1357 ft.CreateSymlink("link", "target")
1364 Mode: fs.ModeSymlink | 0777,
1365 Data: []byte("target"),
1368 safcm.MsgSyncResp{},
1370 `4: files: "link" (group): unchanged`,
1380 Mode: fs.ModeSymlink | 0777,
1383 Data: []byte("target"),
1387 ft.CreateSymlink("link", "old-target")
1394 Mode: fs.ModeSymlink | 0777,
1395 Data: []byte("target"),
1399 FileChanges: []safcm.FileChange{
1402 Old: safcm.FileChangeInfo{
1403 Mode: fs.ModeSymlink | 0777,
1409 New: safcm.FileChangeInfo{
1410 Mode: fs.ModeSymlink | 0777,
1416 DataDiff: `@@ -1 +1 @@
1424 `4: files: "link" (group): content differs`,
1425 `3: files: "link" (group): updating`,
1426 `4: files: "link" (group): creating temporary symlink ".linkRND"`,
1427 `4: files: "link" (group): renaming ".linkRND"`,
1435 "directory: create",
1439 Mode: fs.ModeDir | 0705,
1450 Mode: fs.ModeDir | 0705,
1454 FileChanges: []safcm.FileChange{
1458 New: safcm.FileChangeInfo{
1459 Mode: fs.ModeDir | 0705,
1469 `4: files: "dir" (group): will create`,
1470 `3: files: "dir" (group): creating`,
1471 `4: files: "dir" (group): creating directory`,
1472 `4: files: "dir" (group): chmodding drwx---r-x`,
1473 fmt.Sprintf(`4: files: "dir" (group): chowning %d/%d`, uid, gid),
1478 "directory: create (dry-run)",
1484 Mode: fs.ModeDir | 0644,
1493 FileChanges: []safcm.FileChange{
1497 New: safcm.FileChangeInfo{
1498 Mode: fs.ModeDir | 0644,
1508 `4: files: "dir" (group): will create`,
1509 `3: files: "dir" (group): creating`,
1510 `4: files: "dir" (group): dry-run, skipping changes`,
1516 "directory: create, missing parent (dry-run)",
1521 Path: "does-not-exist/dir",
1522 Mode: fs.ModeDir | 0755,
1531 FileChanges: []safcm.FileChange{
1533 Path: "does-not-exist/dir",
1535 New: safcm.FileChangeInfo{
1536 Mode: fs.ModeDir | 0755,
1546 `4: files: "does-not-exist/dir" (group): will create (parent missing)`,
1547 `4: files: "does-not-exist/dir" (group): dry-run, skipping changes`,
1553 "directory: unchanged",
1557 Mode: fs.ModeDir | 0755,
1563 ft.CreateDirectory("dir", 0755)
1570 Mode: fs.ModeDir | 0755,
1573 safcm.MsgSyncResp{},
1575 `4: files: "dir" (group): unchanged`,
1581 "directory: permission",
1585 Mode: fs.ModeDir | 0755 | fs.ModeSetgid,
1591 ft.CreateDirectory("dir", 0500|fs.ModeSticky)
1598 Mode: fs.ModeDir | 0755 | fs.ModeSetgid,
1602 FileChanges: []safcm.FileChange{
1605 Old: safcm.FileChangeInfo{
1606 Mode: fs.ModeDir | 0500 | fs.ModeSticky,
1612 New: safcm.FileChangeInfo{
1613 Mode: fs.ModeDir | 0755 | fs.ModeSetgid,
1623 `4: files: "dir" (group): permission differs dtr-x------ -> dgrwxr-xr-x`,
1624 `3: files: "dir" (group): updating`,
1625 `4: files: "dir" (group): chmodding dgrwxr-xr-x`,
1633 "change: file to directory",
1637 Mode: fs.ModeDir | 0751,
1643 ft.CreateFile("path", "content\n", 0644)
1650 Mode: fs.ModeDir | 0751,
1654 FileChanges: []safcm.FileChange{
1657 Old: safcm.FileChangeInfo{
1664 New: safcm.FileChangeInfo{
1665 Mode: fs.ModeDir | 0751,
1671 DataDiff: `@@ -1,2 +1 @@
1679 `4: files: "path" (group): type differs ---------- -> d---------`,
1680 `3: files: "path" (group): updating`,
1681 `4: files: "path" (group): removing (due to type change)`,
1682 `4: files: "path" (group): creating directory`,
1683 `4: files: "path" (group): chmodding drwxr-x--x`,
1684 fmt.Sprintf(`4: files: "path" (group): chowning %d/%d`, uid, gid),
1690 "change: file to symlink",
1694 Mode: fs.ModeSymlink | 0777,
1698 Data: []byte("target"),
1701 ft.CreateFile("path", "content\n", 0644)
1708 Mode: fs.ModeSymlink | 0777,
1709 Data: []byte("target"),
1713 FileChanges: []safcm.FileChange{
1716 Old: safcm.FileChangeInfo{
1723 New: safcm.FileChangeInfo{
1724 Mode: fs.ModeSymlink | 0777,
1730 DataDiff: `@@ -1,2 +1 @@
1739 `4: files: "path" (group): type differs ---------- -> L---------`,
1740 `3: files: "path" (group): updating`,
1741 `4: files: "path" (group): creating temporary symlink ".pathRND"`,
1742 `4: files: "path" (group): renaming ".pathRND"`,
1748 "change: symlink to file",
1756 Data: []byte("content\n"),
1759 ft.CreateSymlink("path", "target")
1767 Data: []byte("content\n"),
1771 FileChanges: []safcm.FileChange{
1774 Old: safcm.FileChangeInfo{
1775 Mode: fs.ModeSymlink | 0777,
1781 New: safcm.FileChangeInfo{
1788 DataDiff: `@@ -1 +1,2 @@
1797 `4: files: "path" (group): type differs L--------- -> ----------`,
1798 `3: files: "path" (group): updating`,
1799 `4: files: "path" (group): creating temporary file ".path*"`,
1800 `4: files: "path" (group): renaming ".pathRND"`,
1806 "change: symlink to directory",
1810 Mode: fs.ModeDir | 0751,
1816 ft.CreateSymlink("path", "target")
1823 Mode: fs.ModeDir | 0751,
1827 FileChanges: []safcm.FileChange{
1830 Old: safcm.FileChangeInfo{
1831 Mode: fs.ModeSymlink | 0777,
1837 New: safcm.FileChangeInfo{
1838 Mode: fs.ModeDir | 0751,
1844 DataDiff: `@@ -1 +1 @@
1852 `4: files: "path" (group): type differs L--------- -> d---------`,
1853 `3: files: "path" (group): updating`,
1854 `4: files: "path" (group): removing (due to type change)`,
1855 `4: files: "path" (group): creating directory`,
1856 `4: files: "path" (group): chmodding drwxr-x--x`,
1857 fmt.Sprintf(`4: files: "path" (group): chowning %d/%d`, uid, gid),
1863 "change: directory to file",
1871 Data: []byte("content\n"),
1874 ft.CreateDirectory("path", 0777)
1882 Data: []byte("content\n"),
1886 FileChanges: []safcm.FileChange{
1889 Old: safcm.FileChangeInfo{
1890 Mode: fs.ModeDir | 0777,
1896 New: safcm.FileChangeInfo{
1907 `4: files: "path" (group): type differs d--------- -> ----------`,
1908 `3: files: "path" (group): updating`,
1909 `4: files: "path" (group): removing (due to type change)`,
1910 `4: files: "path" (group): creating temporary file ".path*"`,
1911 `4: files: "path" (group): renaming ".pathRND"`,
1916 "change: directory to file (non-empty)",
1924 Data: []byte("content\n"),
1927 ft.CreateDirectory("path", 0777)
1928 ft.CreateFile("path/file", "content\n", 0644)
1935 Mode: fs.ModeDir | 0777,
1940 Data: []byte("content\n"),
1944 FileChanges: []safcm.FileChange{
1947 Old: safcm.FileChangeInfo{
1948 Mode: fs.ModeDir | 0777,
1954 New: safcm.FileChangeInfo{
1965 `4: files: "path" (group): type differs d--------- -> ----------`,
1966 `3: files: "path" (group): updating`,
1967 `4: files: "path" (group): removing (due to type change)`,
1969 fmt.Errorf("will not replace non-empty directory, please remove manually"),
1973 "change: directory to symlink",
1977 Mode: fs.ModeSymlink | 0777,
1981 Data: []byte("target"),
1984 ft.CreateDirectory("path", 0777)
1991 Mode: fs.ModeSymlink | 0777,
1992 Data: []byte("target"),
1996 FileChanges: []safcm.FileChange{
1999 Old: safcm.FileChangeInfo{
2000 Mode: fs.ModeDir | 0777,
2006 New: safcm.FileChangeInfo{
2007 Mode: fs.ModeSymlink | 0777,
2017 `4: files: "path" (group): type differs d--------- -> L---------`,
2018 `3: files: "path" (group): updating`,
2019 `4: files: "path" (group): removing (due to type change)`,
2020 `4: files: "path" (group): creating temporary symlink ".pathRND"`,
2021 `4: files: "path" (group): renaming ".pathRND"`,
2027 "change: other to file",
2035 Data: []byte("content\n"),
2038 ft.CreateFifo("path", 0666)
2046 Data: []byte("content\n"),
2050 FileChanges: []safcm.FileChange{
2053 Old: safcm.FileChangeInfo{
2054 Mode: fs.ModeNamedPipe | 0666,
2060 New: safcm.FileChangeInfo{
2071 `4: files: "path" (group): type differs p--------- -> ----------`,
2072 `3: files: "path" (group): updating`,
2073 `4: files: "path" (group): creating temporary file ".path*"`,
2074 `4: files: "path" (group): renaming ".pathRND"`,
2080 "change: other to symlink",
2084 Mode: fs.ModeSymlink | 0777,
2088 Data: []byte("target"),
2091 ft.CreateFifo("path", 0666)
2098 Mode: fs.ModeSymlink | 0777,
2099 Data: []byte("target"),
2103 FileChanges: []safcm.FileChange{
2106 Old: safcm.FileChangeInfo{
2107 Mode: fs.ModeNamedPipe | 0666,
2113 New: safcm.FileChangeInfo{
2114 Mode: fs.ModeSymlink | 0777,
2124 `4: files: "path" (group): type differs p--------- -> L---------`,
2125 `3: files: "path" (group): updating`,
2126 `4: files: "path" (group): creating temporary symlink ".pathRND"`,
2127 `4: files: "path" (group): renaming ".pathRND"`,
2133 "change: other to directory",
2137 Mode: fs.ModeDir | 0751,
2143 ft.CreateFifo("path", 0666)
2150 Mode: fs.ModeDir | 0751,
2154 FileChanges: []safcm.FileChange{
2157 Old: safcm.FileChangeInfo{
2158 Mode: fs.ModeNamedPipe | 0666,
2164 New: safcm.FileChangeInfo{
2165 Mode: fs.ModeDir | 0751,
2175 `4: files: "path" (group): type differs p--------- -> d---------`,
2176 `3: files: "path" (group): updating`,
2177 `4: files: "path" (group): removing (due to type change)`,
2178 `4: files: "path" (group): creating directory`,
2179 `4: files: "path" (group): chmodding drwxr-x--x`,
2180 fmt.Sprintf(`4: files: "path" (group): chowning %d/%d`, uid, gid),
2186 "change: file to symlink (same content)",
2190 Mode: fs.ModeSymlink | 0777,
2194 Data: []byte("target"),
2197 ft.CreateFile("path", "target", 0644)
2204 Mode: fs.ModeSymlink | 0777,
2205 Data: []byte("target"),
2209 FileChanges: []safcm.FileChange{
2212 Old: safcm.FileChangeInfo{
2219 New: safcm.FileChangeInfo{
2220 Mode: fs.ModeSymlink | 0777,
2230 `4: files: "path" (group): type differs ---------- -> L---------`,
2231 `3: files: "path" (group): updating`,
2232 `4: files: "path" (group): creating temporary symlink ".pathRND"`,
2233 `4: files: "path" (group): renaming ".pathRND"`,
2238 // Symlink "attacks"
2241 "symlink in earlier path component",
2249 Data: []byte("content"),
2252 ft.CreateDirectory("tmp", 0755)
2253 ft.CreateSymlink("dir", "tmp")
2260 Mode: fs.ModeSymlink | 0777,
2261 Data: []byte("tmp"),
2265 Mode: fs.ModeDir | 0755,
2268 safcm.MsgSyncResp{},
2270 fmt.Errorf("symlink not permitted in path: \"dir\""),
2276 "relative path with leading dot",
2284 Data: []byte("content"),
2287 ft.CreateDirectory("dir", 0755)
2294 Mode: fs.ModeDir | 0755,
2299 Data: []byte("content"),
2303 FileChanges: []safcm.FileChange{
2307 New: safcm.FileChangeInfo{
2318 `4: files: "./dir/file" (group): will create`,
2319 `3: files: "./dir/file" (group): creating`,
2320 `4: files: "./dir/file" (group): creating temporary file "dir/.file*"`,
2321 `4: files: "./dir/file" (group): renaming "dir/.fileRND"`,
2348 ft.CreateFile("file", `this
2368 FileChanges: []safcm.FileChange{
2371 Old: safcm.FileChangeInfo{
2378 New: safcm.FileChangeInfo{
2385 DataDiff: `@@ -1,5 +1,7 @@
2399 `4: files: "file" (group): content differs`,
2400 `3: files: "file" (group): updating`,
2401 `4: files: "file" (group): dry-run, skipping changes`,
2407 "diff: binary both",
2416 Data: []byte("\x00\x01\x02\x03"),
2420 ft.CreateFile("file", "\x00\x01\x02", 0644)
2428 Data: []byte("\x00\x01\x02"),
2432 FileChanges: []safcm.FileChange{
2435 Old: safcm.FileChangeInfo{
2442 New: safcm.FileChangeInfo{
2449 DataDiff: "Binary files differ (3 -> 4 bytes), cannot show diff",
2454 `4: files: "file" (group): content differs`,
2455 `3: files: "file" (group): updating`,
2456 `4: files: "file" (group): dry-run, skipping changes`,
2471 Data: []byte("content\n"),
2475 ft.CreateFile("file", "\x00\x01\x02", 0644)
2483 Data: []byte("\x00\x01\x02"),
2487 FileChanges: []safcm.FileChange{
2490 Old: safcm.FileChangeInfo{
2497 New: safcm.FileChangeInfo{
2504 DataDiff: `@@ -1,2 +1,2 @@
2505 -<binary content, 3 bytes>
2513 `4: files: "file" (group): content differs`,
2514 `3: files: "file" (group): updating`,
2515 `4: files: "file" (group): dry-run, skipping changes`,
2530 Data: []byte("\x00\x01\x02\x03"),
2534 ft.CreateFile("file", "content\n", 0644)
2542 Data: []byte("content\n"),
2546 FileChanges: []safcm.FileChange{
2549 Old: safcm.FileChangeInfo{
2556 New: safcm.FileChangeInfo{
2563 DataDiff: `@@ -1,2 +1,2 @@
2565 +<binary content, 4 bytes>
2572 `4: files: "file" (group): content differs`,
2573 `3: files: "file" (group): updating`,
2574 `4: files: "file" (group): dry-run, skipping changes`,
2580 for _, tc := range tests {
2581 t.Run(tc.name, func(t *testing.T) {
2582 // Create separate test directory for each test case
2583 path := filepath.Join(cwd, "testdata", "file-"+tc.name)
2584 err := os.Mkdir(path, 0700)
2588 err = os.Chdir(path)
2593 if tc.prepare != nil {
2597 s, res := prepareSync(tc.req, &testRunner{
2600 err = s.setDefaults()
2605 // Deterministic temporary symlink names
2609 err = s.syncFile(tc.file, &changed)
2610 testutil.AssertErrorEqual(t, "err", err, tc.expErr)
2612 // Remove random file names from result
2613 for i, x := range dbg {
2614 dbg[i] = randFilesRegexp.ReplaceAllString(x, `RND"`)
2616 testutil.AssertEqual(t, "dbg", dbg, tc.expDbg)
2618 files, err := ft.WalkDir(path)
2622 testutil.AssertEqual(t, "files", files, tc.expFiles)
2624 testutil.AssertEqual(t, "changed", changed, tc.expChanged)
2625 testutil.AssertEqual(t, "resp", s.resp, tc.expResp)
2630 err = os.RemoveAll(filepath.Join(cwd, "testdata"))