1 // Copyright (C) 2021-2023 Simon Ruderich
3 // This program is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, either version 3 of the License, or
6 // (at your option) any later version.
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
13 // You should have received a copy of the GNU General Public License
14 // along with this program. If not, see <http://www.gnu.org/licenses/>.
27 "ruderich.org/simon/safcm"
28 ft "ruderich.org/simon/safcm/remote/sync/filetest"
29 "ruderich.org/simon/safcm/testutil"
32 var randFilesRegexp = regexp.MustCompile(`\d+"$`)
34 func TestSyncFiles(t *testing.T) {
35 cwd, err := os.Getwd()
39 defer os.Chdir(cwd) //nolint:errcheck
41 err = os.RemoveAll("testdata")
45 err = os.Mkdir("testdata", 0700)
52 Mode: fs.ModeDir | 0700,
54 user, uid, group, gid := ft.CurrentUserAndGroup()
56 skipUnlessCiRun := len(os.Getenv("SAFCM_CI_RUN")) == 0
58 tmpTestFilePath := "/tmp/safcm-sync-files-test-file"
67 expResp safcm.MsgSyncResp
72 // NOTE: Also update MsgSyncResp in safcm test cases when
73 // changing the MsgSyncResp struct!
75 // See TestSyncFile() for most file related tests. This
76 // function only tests the overall results and triggers.
82 Files: map[string]*safcm.File{
86 Mode: fs.ModeDir | 0700,
93 Mode: fs.ModeDir | 0755,
103 Data: []byte("content\n"),
113 Mode: fs.ModeDir | 0755,
118 Data: []byte("content\n"),
122 FileChanges: []safcm.FileChange{
126 New: safcm.FileChangeInfo{
127 Mode: fs.ModeDir | 0755,
137 New: safcm.FileChangeInfo{
148 `4: files: "." (group): unchanged`,
149 `4: files: "dir" (group): will create`,
150 `3: files: "dir" (group): creating`,
151 `4: files: "dir" (group): creating directory`,
152 `4: files: "dir" (group): chmodding drwxr-xr-x`,
153 fmt.Sprintf(`4: files: "dir" (group): chowning %d/%d`, uid, gid),
154 `4: files: "dir/file" (group): will create`,
155 `3: files: "dir/file" (group): creating`,
156 `4: files: "dir/file" (group): creating temporary file "dir/.file*"`,
157 `4: files: "dir/file" (group): renaming "dir/.fileRND"`,
166 Files: map[string]*safcm.File{
170 Mode: fs.ModeDir | 0700,
177 Mode: fs.ModeDir | 0755,
187 Data: []byte("content\n"),
192 ft.CreateDirectory("dir", 0755)
193 ft.CreateFile("dir/file", "content\n", 0644)
200 Mode: fs.ModeDir | 0755,
205 Data: []byte("content\n"),
210 `4: files: "." (group): unchanged`,
211 `4: files: "dir" (group): unchanged`,
212 `4: files: "dir/file" (group): unchanged`,
218 "invalid File: user",
221 Files: map[string]*safcm.File{
225 Mode: fs.ModeDir | 0700,
239 fmt.Errorf("\".\": cannot set both User (\"user\") and Uid (1)"),
242 "invalid File: group",
245 Files: map[string]*safcm.File{
249 Mode: fs.ModeDir | 0700,
263 fmt.Errorf("\".\": cannot set both Group (\"group\") and Gid (1)"),
267 // We use relative paths for most tests because we
268 // don't want to modify the running system. Use this
269 // test (and the one below for triggers) as a basic
270 // check that absolute paths work.
272 // Use numeric IDs as not all systems use root/root;
273 // for example BSDs use root/wheel.
274 "absolute paths: no change",
277 Files: map[string]*safcm.File{
281 Mode: fs.ModeDir | 0755,
288 Mode: fs.ModeDir | 0755,
295 Mode: fs.ModeDir | 0777 | fs.ModeSticky,
308 `4: files: "/" (group): unchanged`,
309 `4: files: "/etc" (group): unchanged`,
310 `4: files: "/tmp" (group): unchanged`,
316 "triggers: no change",
319 Files: map[string]*safcm.File{
323 Mode: fs.ModeDir | 0700,
326 TriggerCommands: []string{
333 Mode: fs.ModeDir | 0755,
336 TriggerCommands: []string{
346 Data: []byte("content\n"),
347 TriggerCommands: []string{
348 "echo trigger dir/file",
354 ft.CreateDirectory("dir", 0755)
355 ft.CreateFile("dir/file", "content\n", 0644)
362 Mode: fs.ModeDir | 0755,
367 Data: []byte("content\n"),
372 `4: files: "." (group): unchanged`,
373 `4: files: "dir" (group): unchanged`,
374 `4: files: "dir/file" (group): unchanged`,
380 "triggers: change root",
383 Files: map[string]*safcm.File{
387 Mode: fs.ModeDir | 0700,
390 TriggerCommands: []string{
397 Mode: fs.ModeDir | 0755,
400 TriggerCommands: []string{
410 Data: []byte("content\n"),
411 TriggerCommands: []string{
412 "echo trigger dir/file",
418 ft.CreateDirectoryExists(".", 0750)
419 ft.CreateDirectory("dir", 0755)
420 ft.CreateFile("dir/file", "content\n", 0644)
429 Mode: fs.ModeDir | 0755,
434 Data: []byte("content\n"),
438 FileChanges: []safcm.FileChange{
441 Old: safcm.FileChangeInfo{
442 Mode: fs.ModeDir | 0750,
448 New: safcm.FileChangeInfo{
449 Mode: fs.ModeDir | 0700,
459 `4: files: "." (group): permission differs drwxr-x--- -> drwx------`,
460 `3: files: "." (group): updating`,
461 `4: files: "." (group): chmodding drwx------`,
462 `3: files: ".": queuing trigger on "."`,
463 `4: files: "dir" (group): unchanged`,
464 `4: files: "dir/file" (group): unchanged`,
470 "triggers: change middle",
473 Files: map[string]*safcm.File{
477 Mode: fs.ModeDir | 0700,
480 TriggerCommands: []string{
487 Mode: fs.ModeDir | 0755,
490 TriggerCommands: []string{
500 Data: []byte("content\n"),
501 TriggerCommands: []string{
502 "echo trigger dir/file",
508 ft.CreateDirectory("dir", 0750)
509 ft.CreateFile("dir/file", "content\n", 0644)
519 Mode: fs.ModeDir | 0755,
524 Data: []byte("content\n"),
528 FileChanges: []safcm.FileChange{
531 Old: safcm.FileChangeInfo{
532 Mode: fs.ModeDir | 0750,
538 New: safcm.FileChangeInfo{
539 Mode: fs.ModeDir | 0755,
549 `4: files: "." (group): unchanged`,
550 `4: files: "dir" (group): permission differs drwxr-x--- -> drwxr-xr-x`,
551 `3: files: "dir" (group): updating`,
552 `4: files: "dir" (group): chmodding drwxr-xr-x`,
553 `3: files: "dir": queuing trigger on "."`,
554 `3: files: "dir": queuing trigger on "dir"`,
555 `4: files: "dir/file" (group): unchanged`,
561 "triggers: change leaf",
564 Files: map[string]*safcm.File{
568 Mode: fs.ModeDir | 0700,
571 TriggerCommands: []string{
578 Mode: fs.ModeDir | 0755,
581 TriggerCommands: []string{
591 Data: []byte("content\n"),
592 TriggerCommands: []string{
593 "echo trigger dir/file",
599 ft.CreateDirectory("dir", 0755)
610 Mode: fs.ModeDir | 0755,
615 Data: []byte("content\n"),
619 FileChanges: []safcm.FileChange{
623 New: safcm.FileChangeInfo{
634 `4: files: "." (group): unchanged`,
635 `4: files: "dir" (group): unchanged`,
636 `4: files: "dir/file" (group): will create`,
637 `3: files: "dir/file" (group): creating`,
638 `4: files: "dir/file" (group): creating temporary file "dir/.file*"`,
639 `4: files: "dir/file" (group): renaming "dir/.fileRND"`,
640 `3: files: "dir/file": queuing trigger on "."`,
641 `3: files: "dir/file": queuing trigger on "dir"`,
642 `3: files: "dir/file": queuing trigger on "dir/file"`,
648 "triggers: multiple changes",
651 Files: map[string]*safcm.File{
655 Mode: fs.ModeDir | 0700,
658 TriggerCommands: []string{
665 Mode: fs.ModeDir | 0755,
668 TriggerCommands: []string{
678 Data: []byte("content\n"),
679 TriggerCommands: []string{
680 "echo trigger dir/file",
695 Mode: fs.ModeDir | 0755,
700 Data: []byte("content\n"),
704 FileChanges: []safcm.FileChange{
708 New: safcm.FileChangeInfo{
709 Mode: fs.ModeDir | 0755,
719 New: safcm.FileChangeInfo{
730 `4: files: "." (group): unchanged`,
731 `4: files: "dir" (group): will create`,
732 `3: files: "dir" (group): creating`,
733 `4: files: "dir" (group): creating directory`,
734 `4: files: "dir" (group): chmodding drwxr-xr-x`,
735 fmt.Sprintf(`4: files: "dir" (group): chowning %d/%d`, uid, gid),
736 `3: files: "dir": queuing trigger on "."`,
737 `3: files: "dir": queuing trigger on "dir"`,
738 `4: files: "dir/file" (group): will create`,
739 `3: files: "dir/file" (group): creating`,
740 `4: files: "dir/file" (group): creating temporary file "dir/.file*"`,
741 `4: files: "dir/file" (group): renaming "dir/.fileRND"`,
742 `4: files: "dir/file": skipping trigger on ".", already active`,
743 `4: files: "dir/file": skipping trigger on "dir", already active`,
744 `3: files: "dir/file": queuing trigger on "dir/file"`,
750 "triggers: absolute paths",
753 Files: map[string]*safcm.File{
757 Mode: fs.ModeDir | 0755,
760 TriggerCommands: []string{
767 Mode: fs.ModeDir | 0777 | fs.ModeSticky,
770 TriggerCommands: []string{
776 Path: tmpTestFilePath,
780 TriggerCommands: []string{
781 "echo trigger /tmp/file",
790 // Don't use variable for more robust test
791 "/tmp/safcm-sync-files-test-file",
797 FileChanges: []safcm.FileChange{
799 Path: "/tmp/safcm-sync-files-test-file",
801 New: safcm.FileChangeInfo{
812 `4: files: "/" (group): unchanged`,
813 `4: files: "/tmp" (group): unchanged`,
814 `4: files: "/tmp/safcm-sync-files-test-file" (group): will create`,
815 `3: files: "/tmp/safcm-sync-files-test-file" (group): creating`,
816 `4: files: "/tmp/safcm-sync-files-test-file" (group): creating temporary file "/tmp/.safcm-sync-files-test-file*"`,
817 `4: files: "/tmp/safcm-sync-files-test-file" (group): renaming "/tmp/.safcm-sync-files-test-fileRND"`,
818 `3: files: "/tmp/safcm-sync-files-test-file": queuing trigger on "/"`,
819 `3: files: "/tmp/safcm-sync-files-test-file": queuing trigger on "/tmp"`,
820 `3: files: "/tmp/safcm-sync-files-test-file": queuing trigger on "/tmp/safcm-sync-files-test-file"`,
826 for _, tc := range tests {
827 t.Run(tc.name, func(t *testing.T) {
832 // Create separate test directory for each test case
833 path := filepath.Join(cwd, "testdata", "files-"+tc.name)
834 err := os.Mkdir(path, 0700)
843 if tc.prepare != nil {
847 s, res := prepareSync(tc.req, &testRunner{
850 err = s.setDefaults()
856 testutil.AssertErrorEqual(t, "err", err, tc.expErr)
858 // Remove random file names from result
859 for i, x := range dbg {
860 dbg[i] = randFilesRegexp.ReplaceAllString(x, `RND"`)
862 testutil.AssertEqual(t, "dbg", dbg, tc.expDbg)
864 files, err := ft.WalkDir(path)
868 testutil.AssertEqual(t, "files", files, tc.expFiles)
870 testutil.AssertEqual(t, "resp", s.resp, tc.expResp)
871 testutil.AssertEqual(t, "triggers",
872 s.triggers, tc.expTriggers)
876 os.Remove(tmpTestFilePath)
878 err = os.RemoveAll(filepath.Join(cwd, "testdata"))
885 func TestSyncFile(t *testing.T) {
886 cwd, err := os.Getwd()
890 defer os.Chdir(cwd) //nolint:errcheck
892 err = os.RemoveAll("testdata")
896 err = os.Mkdir("testdata", 0700)
903 Mode: fs.ModeDir | 0700,
905 user, uid, group, gid := ft.CurrentUserAndGroup()
914 expResp safcm.MsgSyncResp
919 // NOTE: Also update MsgSyncResp in safcm test cases when
920 // changing the MsgSyncResp struct!
922 // TODO: Add tests for chown and run them only as root
934 Data: []byte("content\n"),
944 Data: []byte("content\n"),
948 FileChanges: []safcm.FileChange{
952 New: safcm.FileChangeInfo{
963 `4: files: "file" (group): will create`,
964 `3: files: "file" (group): creating`,
965 `4: files: "file" (group): creating temporary file ".file*"`,
966 `4: files: "file" (group): renaming ".fileRND"`,
971 "file: create (dry-run)",
980 Data: []byte("content\n"),
987 FileChanges: []safcm.FileChange{
991 New: safcm.FileChangeInfo{
1002 `4: files: "file" (group): will create`,
1003 `3: files: "file" (group): creating`,
1004 `4: files: "file" (group): dry-run, skipping changes`,
1010 "file: create, missing parent (dry-run)",
1015 Path: "does-not-exist/file",
1019 Data: []byte("content\n"),
1026 FileChanges: []safcm.FileChange{
1028 Path: "does-not-exist/file",
1030 New: safcm.FileChangeInfo{
1041 `4: files: "does-not-exist/file" (group): will create (parent missing)`,
1042 `4: files: "does-not-exist/file" (group): dry-run, skipping changes`,
1055 Data: []byte("content\n"),
1059 ft.CreateFile("file", "content\n", 0644)
1067 Data: []byte("content\n"),
1070 safcm.MsgSyncResp{},
1072 `4: files: "file" (group): unchanged`,
1078 "file: unchanged (non-default user-group)",
1087 Data: []byte("content\n"),
1091 ft.CreateFile("file", "content\n", 0644)
1099 Data: []byte("content\n"),
1102 safcm.MsgSyncResp{},
1104 `4: files: "file" (group): unchanged`,
1114 Mode: 0755 | fs.ModeSetuid,
1117 Data: []byte("content\n"),
1121 ft.CreateFile("file", "content\n", 0755)
1128 Mode: 0755 | fs.ModeSetuid,
1129 Data: []byte("content\n"),
1133 FileChanges: []safcm.FileChange{
1136 Old: safcm.FileChangeInfo{
1143 New: safcm.FileChangeInfo{
1144 Mode: 0755 | fs.ModeSetuid,
1154 `4: files: "file" (group): permission differs -rwxr-xr-x -> urwxr-xr-x`,
1155 `3: files: "file" (group): updating`,
1156 `4: files: "file" (group): creating temporary file ".file*"`,
1157 `4: files: "file" (group): renaming ".fileRND"`,
1170 Data: []byte("content\n"),
1174 ft.CreateFile("file", "old content\n", 0644)
1182 Data: []byte("content\n"),
1186 FileChanges: []safcm.FileChange{
1189 Old: safcm.FileChangeInfo{
1196 New: safcm.FileChangeInfo{
1203 DataDiff: `@@ -1,2 +1,2 @@
1212 `4: files: "file" (group): content differs`,
1213 `3: files: "file" (group): updating`,
1214 `4: files: "file" (group): creating temporary file ".file*"`,
1215 `4: files: "file" (group): renaming ".fileRND"`,
1227 Mode: fs.ModeSymlink | 0777,
1230 Data: []byte("target"),
1239 Mode: fs.ModeSymlink | 0777,
1240 Data: []byte("target"),
1244 FileChanges: []safcm.FileChange{
1248 New: safcm.FileChangeInfo{
1249 Mode: fs.ModeSymlink | 0777,
1259 `4: files: "link" (group): will create`,
1260 `3: files: "link" (group): creating`,
1261 `4: files: "link" (group): creating temporary symlink ".linkRND"`,
1262 `4: files: "link" (group): renaming ".linkRND"`,
1267 "symlink: create (conflict)",
1271 Mode: fs.ModeSymlink | 0777,
1274 Data: []byte("target"),
1278 ft.CreateFile(".link8717895732742165505", "", 0600)
1284 Path: ".link8717895732742165505",
1290 Mode: fs.ModeSymlink | 0777,
1291 Data: []byte("target"),
1295 FileChanges: []safcm.FileChange{
1299 New: safcm.FileChangeInfo{
1300 Mode: fs.ModeSymlink | 0777,
1310 `4: files: "link" (group): will create`,
1311 `3: files: "link" (group): creating`,
1312 `4: files: "link" (group): creating temporary symlink ".linkRND"`,
1313 `4: files: "link" (group): creating temporary symlink ".linkRND"`,
1314 `4: files: "link" (group): renaming ".linkRND"`,
1319 "symlink: create (dry-run)",
1325 Mode: fs.ModeSymlink | 0777,
1328 Data: []byte("target"),
1335 FileChanges: []safcm.FileChange{
1339 New: safcm.FileChangeInfo{
1340 Mode: fs.ModeSymlink | 0777,
1350 `4: files: "link" (group): will create`,
1351 `3: files: "link" (group): creating`,
1352 `4: files: "link" (group): dry-run, skipping changes`,
1358 "symlink: unchanged",
1362 Mode: fs.ModeSymlink | 0777,
1365 Data: []byte("target"),
1369 ft.CreateSymlink("link", "target")
1376 Mode: fs.ModeSymlink | 0777,
1377 Data: []byte("target"),
1380 safcm.MsgSyncResp{},
1382 `4: files: "link" (group): unchanged`,
1392 Mode: fs.ModeSymlink | 0777,
1395 Data: []byte("target"),
1399 ft.CreateSymlink("link", "old-target")
1406 Mode: fs.ModeSymlink | 0777,
1407 Data: []byte("target"),
1411 FileChanges: []safcm.FileChange{
1414 Old: safcm.FileChangeInfo{
1415 Mode: fs.ModeSymlink | 0777,
1421 New: safcm.FileChangeInfo{
1422 Mode: fs.ModeSymlink | 0777,
1428 DataDiff: `@@ -1 +1 @@
1436 `4: files: "link" (group): content differs`,
1437 `3: files: "link" (group): updating`,
1438 `4: files: "link" (group): creating temporary symlink ".linkRND"`,
1439 `4: files: "link" (group): renaming ".linkRND"`,
1447 "directory: create",
1451 Mode: fs.ModeDir | 0705,
1462 Mode: fs.ModeDir | 0705,
1466 FileChanges: []safcm.FileChange{
1470 New: safcm.FileChangeInfo{
1471 Mode: fs.ModeDir | 0705,
1481 `4: files: "dir" (group): will create`,
1482 `3: files: "dir" (group): creating`,
1483 `4: files: "dir" (group): creating directory`,
1484 `4: files: "dir" (group): chmodding drwx---r-x`,
1485 fmt.Sprintf(`4: files: "dir" (group): chowning %d/%d`, uid, gid),
1490 "directory: create (dry-run)",
1496 Mode: fs.ModeDir | 0644,
1505 FileChanges: []safcm.FileChange{
1509 New: safcm.FileChangeInfo{
1510 Mode: fs.ModeDir | 0644,
1520 `4: files: "dir" (group): will create`,
1521 `3: files: "dir" (group): creating`,
1522 `4: files: "dir" (group): dry-run, skipping changes`,
1528 "directory: create, missing parent (dry-run)",
1533 Path: "does-not-exist/dir",
1534 Mode: fs.ModeDir | 0755,
1543 FileChanges: []safcm.FileChange{
1545 Path: "does-not-exist/dir",
1547 New: safcm.FileChangeInfo{
1548 Mode: fs.ModeDir | 0755,
1558 `4: files: "does-not-exist/dir" (group): will create (parent missing)`,
1559 `4: files: "does-not-exist/dir" (group): dry-run, skipping changes`,
1565 "directory: unchanged",
1569 Mode: fs.ModeDir | 0755,
1575 ft.CreateDirectory("dir", 0755)
1582 Mode: fs.ModeDir | 0755,
1585 safcm.MsgSyncResp{},
1587 `4: files: "dir" (group): unchanged`,
1593 "directory: permission",
1597 Mode: fs.ModeDir | 0755 | fs.ModeSetgid,
1603 ft.CreateDirectory("dir", 0500|fs.ModeSticky)
1610 Mode: fs.ModeDir | 0755 | fs.ModeSetgid,
1614 FileChanges: []safcm.FileChange{
1617 Old: safcm.FileChangeInfo{
1618 Mode: fs.ModeDir | 0500 | fs.ModeSticky,
1624 New: safcm.FileChangeInfo{
1625 Mode: fs.ModeDir | 0755 | fs.ModeSetgid,
1635 `4: files: "dir" (group): permission differs dtr-x------ -> dgrwxr-xr-x`,
1636 `3: files: "dir" (group): updating`,
1637 `4: files: "dir" (group): chmodding dgrwxr-xr-x`,
1645 "change: file to directory",
1649 Mode: fs.ModeDir | 0751,
1655 ft.CreateFile("path", "content\n", 0644)
1662 Mode: fs.ModeDir | 0751,
1666 FileChanges: []safcm.FileChange{
1669 Old: safcm.FileChangeInfo{
1676 New: safcm.FileChangeInfo{
1677 Mode: fs.ModeDir | 0751,
1683 DataDiff: `@@ -1,2 +1 @@
1691 `4: files: "path" (group): type differs ---------- -> d---------`,
1692 `3: files: "path" (group): updating`,
1693 `4: files: "path" (group): removing (due to type change)`,
1694 `4: files: "path" (group): creating directory`,
1695 `4: files: "path" (group): chmodding drwxr-x--x`,
1696 fmt.Sprintf(`4: files: "path" (group): chowning %d/%d`, uid, gid),
1702 "change: file to symlink",
1706 Mode: fs.ModeSymlink | 0777,
1710 Data: []byte("target"),
1713 ft.CreateFile("path", "content\n", 0644)
1720 Mode: fs.ModeSymlink | 0777,
1721 Data: []byte("target"),
1725 FileChanges: []safcm.FileChange{
1728 Old: safcm.FileChangeInfo{
1735 New: safcm.FileChangeInfo{
1736 Mode: fs.ModeSymlink | 0777,
1742 DataDiff: `@@ -1,2 +1 @@
1751 `4: files: "path" (group): type differs ---------- -> L---------`,
1752 `3: files: "path" (group): updating`,
1753 `4: files: "path" (group): creating temporary symlink ".pathRND"`,
1754 `4: files: "path" (group): renaming ".pathRND"`,
1760 "change: symlink to file",
1768 Data: []byte("content\n"),
1771 ft.CreateSymlink("path", "target")
1779 Data: []byte("content\n"),
1783 FileChanges: []safcm.FileChange{
1786 Old: safcm.FileChangeInfo{
1787 Mode: fs.ModeSymlink | 0777,
1793 New: safcm.FileChangeInfo{
1800 DataDiff: `@@ -1 +1,2 @@
1809 `4: files: "path" (group): type differs L--------- -> ----------`,
1810 `3: files: "path" (group): updating`,
1811 `4: files: "path" (group): creating temporary file ".path*"`,
1812 `4: files: "path" (group): renaming ".pathRND"`,
1818 "change: symlink to directory",
1822 Mode: fs.ModeDir | 0751,
1828 ft.CreateSymlink("path", "target")
1835 Mode: fs.ModeDir | 0751,
1839 FileChanges: []safcm.FileChange{
1842 Old: safcm.FileChangeInfo{
1843 Mode: fs.ModeSymlink | 0777,
1849 New: safcm.FileChangeInfo{
1850 Mode: fs.ModeDir | 0751,
1856 DataDiff: `@@ -1 +1 @@
1864 `4: files: "path" (group): type differs L--------- -> d---------`,
1865 `3: files: "path" (group): updating`,
1866 `4: files: "path" (group): removing (due to type change)`,
1867 `4: files: "path" (group): creating directory`,
1868 `4: files: "path" (group): chmodding drwxr-x--x`,
1869 fmt.Sprintf(`4: files: "path" (group): chowning %d/%d`, uid, gid),
1875 "change: directory to file",
1883 Data: []byte("content\n"),
1886 ft.CreateDirectory("path", 0777)
1894 Data: []byte("content\n"),
1898 FileChanges: []safcm.FileChange{
1901 Old: safcm.FileChangeInfo{
1902 Mode: fs.ModeDir | 0777,
1908 New: safcm.FileChangeInfo{
1919 `4: files: "path" (group): type differs d--------- -> ----------`,
1920 `3: files: "path" (group): updating`,
1921 `4: files: "path" (group): removing (due to type change)`,
1922 `4: files: "path" (group): creating temporary file ".path*"`,
1923 `4: files: "path" (group): renaming ".pathRND"`,
1928 "change: directory to file (non-empty)",
1936 Data: []byte("content\n"),
1939 ft.CreateDirectory("path", 0777)
1940 ft.CreateFile("path/file", "content\n", 0644)
1947 Mode: fs.ModeDir | 0777,
1952 Data: []byte("content\n"),
1956 FileChanges: []safcm.FileChange{
1959 Old: safcm.FileChangeInfo{
1960 Mode: fs.ModeDir | 0777,
1966 New: safcm.FileChangeInfo{
1977 `4: files: "path" (group): type differs d--------- -> ----------`,
1978 `3: files: "path" (group): updating`,
1979 `4: files: "path" (group): removing (due to type change)`,
1981 fmt.Errorf("will not replace non-empty directory, please remove manually"),
1985 "change: directory to symlink",
1989 Mode: fs.ModeSymlink | 0777,
1993 Data: []byte("target"),
1996 ft.CreateDirectory("path", 0777)
2003 Mode: fs.ModeSymlink | 0777,
2004 Data: []byte("target"),
2008 FileChanges: []safcm.FileChange{
2011 Old: safcm.FileChangeInfo{
2012 Mode: fs.ModeDir | 0777,
2018 New: safcm.FileChangeInfo{
2019 Mode: fs.ModeSymlink | 0777,
2029 `4: files: "path" (group): type differs d--------- -> L---------`,
2030 `3: files: "path" (group): updating`,
2031 `4: files: "path" (group): removing (due to type change)`,
2032 `4: files: "path" (group): creating temporary symlink ".pathRND"`,
2033 `4: files: "path" (group): renaming ".pathRND"`,
2039 "change: other to file",
2047 Data: []byte("content\n"),
2050 ft.CreateFifo("path", 0666)
2058 Data: []byte("content\n"),
2062 FileChanges: []safcm.FileChange{
2065 Old: safcm.FileChangeInfo{
2066 Mode: fs.ModeNamedPipe | 0666,
2072 New: safcm.FileChangeInfo{
2083 `4: files: "path" (group): type differs p--------- -> ----------`,
2084 `3: files: "path" (group): updating`,
2085 `4: files: "path" (group): creating temporary file ".path*"`,
2086 `4: files: "path" (group): renaming ".pathRND"`,
2092 "change: other to symlink",
2096 Mode: fs.ModeSymlink | 0777,
2100 Data: []byte("target"),
2103 ft.CreateFifo("path", 0666)
2110 Mode: fs.ModeSymlink | 0777,
2111 Data: []byte("target"),
2115 FileChanges: []safcm.FileChange{
2118 Old: safcm.FileChangeInfo{
2119 Mode: fs.ModeNamedPipe | 0666,
2125 New: safcm.FileChangeInfo{
2126 Mode: fs.ModeSymlink | 0777,
2136 `4: files: "path" (group): type differs p--------- -> L---------`,
2137 `3: files: "path" (group): updating`,
2138 `4: files: "path" (group): creating temporary symlink ".pathRND"`,
2139 `4: files: "path" (group): renaming ".pathRND"`,
2145 "change: other to directory",
2149 Mode: fs.ModeDir | 0751,
2155 ft.CreateFifo("path", 0666)
2162 Mode: fs.ModeDir | 0751,
2166 FileChanges: []safcm.FileChange{
2169 Old: safcm.FileChangeInfo{
2170 Mode: fs.ModeNamedPipe | 0666,
2176 New: safcm.FileChangeInfo{
2177 Mode: fs.ModeDir | 0751,
2187 `4: files: "path" (group): type differs p--------- -> d---------`,
2188 `3: files: "path" (group): updating`,
2189 `4: files: "path" (group): removing (due to type change)`,
2190 `4: files: "path" (group): creating directory`,
2191 `4: files: "path" (group): chmodding drwxr-x--x`,
2192 fmt.Sprintf(`4: files: "path" (group): chowning %d/%d`, uid, gid),
2198 "change: file to symlink (same content)",
2202 Mode: fs.ModeSymlink | 0777,
2206 Data: []byte("target"),
2209 ft.CreateFile("path", "target", 0644)
2216 Mode: fs.ModeSymlink | 0777,
2217 Data: []byte("target"),
2221 FileChanges: []safcm.FileChange{
2224 Old: safcm.FileChangeInfo{
2231 New: safcm.FileChangeInfo{
2232 Mode: fs.ModeSymlink | 0777,
2242 `4: files: "path" (group): type differs ---------- -> L---------`,
2243 `3: files: "path" (group): updating`,
2244 `4: files: "path" (group): creating temporary symlink ".pathRND"`,
2245 `4: files: "path" (group): renaming ".pathRND"`,
2250 // Symlink "attacks"
2253 "symlink in earlier path component",
2261 Data: []byte("content"),
2264 ft.CreateDirectory("tmp", 0755)
2265 ft.CreateSymlink("dir", "tmp")
2272 Mode: fs.ModeSymlink | 0777,
2273 Data: []byte("tmp"),
2277 Mode: fs.ModeDir | 0755,
2280 safcm.MsgSyncResp{},
2282 fmt.Errorf("symlink not permitted in path: \"dir\""),
2288 "relative path with leading dot",
2296 Data: []byte("content"),
2299 ft.CreateDirectory("dir", 0755)
2306 Mode: fs.ModeDir | 0755,
2311 Data: []byte("content"),
2315 FileChanges: []safcm.FileChange{
2319 New: safcm.FileChangeInfo{
2330 `4: files: "./dir/file" (group): will create`,
2331 `3: files: "./dir/file" (group): creating`,
2332 `4: files: "./dir/file" (group): creating temporary file "dir/.file*"`,
2333 `4: files: "./dir/file" (group): renaming "dir/.fileRND"`,
2360 ft.CreateFile("file", `this
2380 FileChanges: []safcm.FileChange{
2383 Old: safcm.FileChangeInfo{
2390 New: safcm.FileChangeInfo{
2397 DataDiff: `@@ -1,5 +1,7 @@
2411 `4: files: "file" (group): content differs`,
2412 `3: files: "file" (group): updating`,
2413 `4: files: "file" (group): dry-run, skipping changes`,
2419 "diff: binary both",
2428 Data: []byte("\x00\x01\x02\x03"),
2432 ft.CreateFile("file", "\x00\x01\x02", 0644)
2440 Data: []byte("\x00\x01\x02"),
2444 FileChanges: []safcm.FileChange{
2447 Old: safcm.FileChangeInfo{
2454 New: safcm.FileChangeInfo{
2461 DataDiff: "Binary files differ (3 -> 4 bytes), cannot show diff",
2466 `4: files: "file" (group): content differs`,
2467 `3: files: "file" (group): updating`,
2468 `4: files: "file" (group): dry-run, skipping changes`,
2483 Data: []byte("content\n"),
2487 ft.CreateFile("file", "\x00\x01\x02", 0644)
2495 Data: []byte("\x00\x01\x02"),
2499 FileChanges: []safcm.FileChange{
2502 Old: safcm.FileChangeInfo{
2509 New: safcm.FileChangeInfo{
2516 DataDiff: `@@ -1,2 +1,2 @@
2517 -<binary content, 3 bytes>
2525 `4: files: "file" (group): content differs`,
2526 `3: files: "file" (group): updating`,
2527 `4: files: "file" (group): dry-run, skipping changes`,
2542 Data: []byte("\x00\x01\x02\x03"),
2546 ft.CreateFile("file", "content\n", 0644)
2554 Data: []byte("content\n"),
2558 FileChanges: []safcm.FileChange{
2561 Old: safcm.FileChangeInfo{
2568 New: safcm.FileChangeInfo{
2575 DataDiff: `@@ -1,2 +1,2 @@
2577 +<binary content, 4 bytes>
2584 `4: files: "file" (group): content differs`,
2585 `3: files: "file" (group): updating`,
2586 `4: files: "file" (group): dry-run, skipping changes`,
2592 for _, tc := range tests {
2593 t.Run(tc.name, func(t *testing.T) {
2594 // Create separate test directory for each test case
2595 path := filepath.Join(cwd, "testdata", "file-"+tc.name)
2596 err := os.Mkdir(path, 0700)
2600 err = os.Chdir(path)
2605 if tc.prepare != nil {
2609 s, res := prepareSync(tc.req, &testRunner{
2612 err = s.setDefaults()
2617 // Deterministic temporary symlink names
2621 err = s.syncFile(tc.file, &changed)
2622 testutil.AssertErrorEqual(t, "err", err, tc.expErr)
2624 // Remove random file names from result
2625 for i, x := range dbg {
2626 dbg[i] = randFilesRegexp.ReplaceAllString(x, `RND"`)
2628 testutil.AssertEqual(t, "dbg", dbg, tc.expDbg)
2630 files, err := ft.WalkDir(path)
2634 testutil.AssertEqual(t, "files", files, tc.expFiles)
2636 testutil.AssertEqual(t, "changed", changed, tc.expChanged)
2637 testutil.AssertEqual(t, "resp", s.resp, tc.expResp)
2642 err = os.RemoveAll(filepath.Join(cwd, "testdata"))