1 // Copyright (C) 2021 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()
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{
85 Mode: fs.ModeDir | 0700,
92 Mode: fs.ModeDir | 0755,
102 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{
169 Mode: fs.ModeDir | 0700,
176 Mode: fs.ModeDir | 0755,
186 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{
224 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{
248 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{
280 Mode: fs.ModeDir | 0755,
287 Mode: fs.ModeDir | 0755,
294 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{
322 Mode: fs.ModeDir | 0700,
326 TriggerCommands: []string{
332 Mode: fs.ModeDir | 0755,
336 TriggerCommands: []string{
345 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{
386 Mode: fs.ModeDir | 0700,
390 TriggerCommands: []string{
396 Mode: fs.ModeDir | 0755,
400 TriggerCommands: []string{
409 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{
476 Mode: fs.ModeDir | 0700,
480 TriggerCommands: []string{
486 Mode: fs.ModeDir | 0755,
490 TriggerCommands: []string{
499 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{
567 Mode: fs.ModeDir | 0700,
571 TriggerCommands: []string{
577 Mode: fs.ModeDir | 0755,
581 TriggerCommands: []string{
590 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{
654 Mode: fs.ModeDir | 0700,
658 TriggerCommands: []string{
664 Mode: fs.ModeDir | 0755,
668 TriggerCommands: []string{
677 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{
756 Mode: fs.ModeDir | 0755,
760 TriggerCommands: []string{
766 Mode: fs.ModeDir | 0777 | fs.ModeSticky,
770 TriggerCommands: []string{
775 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()
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`,
1017 Data: []byte("content\n"),
1021 ft.CreateFile("file", "content\n", 0644)
1029 Data: []byte("content\n"),
1032 safcm.MsgSyncResp{},
1034 `4: files: "file" (group): unchanged`,
1040 "file: unchanged (non-default user-group)",
1049 Data: []byte("content\n"),
1053 ft.CreateFile("file", "content\n", 0644)
1061 Data: []byte("content\n"),
1064 safcm.MsgSyncResp{},
1066 `4: files: "file" (group): unchanged`,
1076 Mode: 0755 | fs.ModeSetuid,
1079 Data: []byte("content\n"),
1083 ft.CreateFile("file", "content\n", 0755)
1090 Mode: 0755 | fs.ModeSetuid,
1091 Data: []byte("content\n"),
1095 FileChanges: []safcm.FileChange{
1098 Old: safcm.FileChangeInfo{
1105 New: safcm.FileChangeInfo{
1106 Mode: 0755 | fs.ModeSetuid,
1116 `4: files: "file" (group): permission differs -rwxr-xr-x -> urwxr-xr-x`,
1117 `3: files: "file" (group): updating`,
1118 `4: files: "file" (group): creating temporary file ".file*"`,
1119 `4: files: "file" (group): renaming ".fileRND"`,
1132 Data: []byte("content\n"),
1136 ft.CreateFile("file", "old content\n", 0644)
1144 Data: []byte("content\n"),
1148 FileChanges: []safcm.FileChange{
1151 Old: safcm.FileChangeInfo{
1158 New: safcm.FileChangeInfo{
1165 DataDiff: `@@ -1,2 +1,2 @@
1174 `4: files: "file" (group): content differs`,
1175 `3: files: "file" (group): updating`,
1176 `4: files: "file" (group): creating temporary file ".file*"`,
1177 `4: files: "file" (group): renaming ".fileRND"`,
1189 Mode: fs.ModeSymlink | 0777,
1192 Data: []byte("target"),
1201 Mode: fs.ModeSymlink | 0777,
1202 Data: []byte("target"),
1206 FileChanges: []safcm.FileChange{
1210 New: safcm.FileChangeInfo{
1211 Mode: fs.ModeSymlink | 0777,
1221 `4: files: "link" (group): will create`,
1222 `3: files: "link" (group): creating`,
1223 `4: files: "link" (group): creating temporary symlink ".linkRND"`,
1224 `4: files: "link" (group): renaming ".linkRND"`,
1229 "symlink: create (conflict)",
1233 Mode: fs.ModeSymlink | 0777,
1236 Data: []byte("target"),
1240 ft.CreateFile(".link8717895732742165505", "", 0600)
1246 Path: ".link8717895732742165505",
1252 Mode: fs.ModeSymlink | 0777,
1253 Data: []byte("target"),
1257 FileChanges: []safcm.FileChange{
1261 New: safcm.FileChangeInfo{
1262 Mode: fs.ModeSymlink | 0777,
1272 `4: files: "link" (group): will create`,
1273 `3: files: "link" (group): creating`,
1274 `4: files: "link" (group): creating temporary symlink ".linkRND"`,
1275 `4: files: "link" (group): creating temporary symlink ".linkRND"`,
1276 `4: files: "link" (group): renaming ".linkRND"`,
1281 "symlink: create (dry-run)",
1287 Mode: fs.ModeSymlink | 0777,
1290 Data: []byte("target"),
1297 FileChanges: []safcm.FileChange{
1301 New: safcm.FileChangeInfo{
1302 Mode: fs.ModeSymlink | 0777,
1312 `4: files: "link" (group): will create`,
1313 `3: files: "link" (group): creating`,
1314 `4: files: "link" (group): dry-run, skipping changes`,
1320 "symlink: unchanged",
1324 Mode: fs.ModeSymlink | 0777,
1327 Data: []byte("target"),
1331 ft.CreateSymlink("link", "target")
1338 Mode: fs.ModeSymlink | 0777,
1339 Data: []byte("target"),
1342 safcm.MsgSyncResp{},
1344 `4: files: "link" (group): unchanged`,
1354 Mode: fs.ModeSymlink | 0777,
1357 Data: []byte("target"),
1361 ft.CreateSymlink("link", "old-target")
1368 Mode: fs.ModeSymlink | 0777,
1369 Data: []byte("target"),
1373 FileChanges: []safcm.FileChange{
1376 Old: safcm.FileChangeInfo{
1377 Mode: fs.ModeSymlink | 0777,
1383 New: safcm.FileChangeInfo{
1384 Mode: fs.ModeSymlink | 0777,
1390 DataDiff: `@@ -1 +1 @@
1398 `4: files: "link" (group): content differs`,
1399 `3: files: "link" (group): updating`,
1400 `4: files: "link" (group): creating temporary symlink ".linkRND"`,
1401 `4: files: "link" (group): renaming ".linkRND"`,
1409 "directory: create",
1413 Mode: fs.ModeDir | 0705,
1424 Mode: fs.ModeDir | 0705,
1428 FileChanges: []safcm.FileChange{
1432 New: safcm.FileChangeInfo{
1433 Mode: fs.ModeDir | 0705,
1443 `4: files: "dir" (group): will create`,
1444 `3: files: "dir" (group): creating`,
1445 `4: files: "dir" (group): creating directory`,
1446 `4: files: "dir" (group): chmodding drwx---r-x`,
1447 fmt.Sprintf(`4: files: "dir" (group): chowning %d/%d`, uid, gid),
1452 "directory: create (dry-run)",
1458 Mode: fs.ModeDir | 0644,
1467 FileChanges: []safcm.FileChange{
1471 New: safcm.FileChangeInfo{
1472 Mode: fs.ModeDir | 0644,
1482 `4: files: "dir" (group): will create`,
1483 `3: files: "dir" (group): creating`,
1484 `4: files: "dir" (group): dry-run, skipping changes`,
1490 "directory: unchanged",
1494 Mode: fs.ModeDir | 0755,
1500 ft.CreateDirectory("dir", 0755)
1507 Mode: fs.ModeDir | 0755,
1510 safcm.MsgSyncResp{},
1512 `4: files: "dir" (group): unchanged`,
1518 "directory: permission",
1522 Mode: fs.ModeDir | 0755 | fs.ModeSetgid,
1528 ft.CreateDirectory("dir", 0500|fs.ModeSticky)
1535 Mode: fs.ModeDir | 0755 | fs.ModeSetgid,
1539 FileChanges: []safcm.FileChange{
1542 Old: safcm.FileChangeInfo{
1543 Mode: fs.ModeDir | 0500 | fs.ModeSticky,
1549 New: safcm.FileChangeInfo{
1550 Mode: fs.ModeDir | 0755 | fs.ModeSetgid,
1560 `4: files: "dir" (group): permission differs dtr-x------ -> dgrwxr-xr-x`,
1561 `3: files: "dir" (group): updating`,
1562 `4: files: "dir" (group): chmodding dgrwxr-xr-x`,
1570 "change: file to directory",
1574 Mode: fs.ModeDir | 0751,
1580 ft.CreateFile("path", "content\n", 0644)
1587 Mode: fs.ModeDir | 0751,
1591 FileChanges: []safcm.FileChange{
1594 Old: safcm.FileChangeInfo{
1601 New: safcm.FileChangeInfo{
1602 Mode: fs.ModeDir | 0751,
1608 DataDiff: `@@ -1,2 +1 @@
1616 `4: files: "path" (group): type differs ---------- -> d---------`,
1617 `3: files: "path" (group): updating`,
1618 `4: files: "path" (group): removing (due to type change)`,
1619 `4: files: "path" (group): creating directory`,
1620 `4: files: "path" (group): chmodding drwxr-x--x`,
1621 fmt.Sprintf(`4: files: "path" (group): chowning %d/%d`, uid, gid),
1627 "change: file to symlink",
1631 Mode: fs.ModeSymlink | 0777,
1635 Data: []byte("target"),
1638 ft.CreateFile("path", "content\n", 0644)
1645 Mode: fs.ModeSymlink | 0777,
1646 Data: []byte("target"),
1650 FileChanges: []safcm.FileChange{
1653 Old: safcm.FileChangeInfo{
1660 New: safcm.FileChangeInfo{
1661 Mode: fs.ModeSymlink | 0777,
1667 DataDiff: `@@ -1,2 +1 @@
1676 `4: files: "path" (group): type differs ---------- -> L---------`,
1677 `3: files: "path" (group): updating`,
1678 `4: files: "path" (group): creating temporary symlink ".pathRND"`,
1679 `4: files: "path" (group): renaming ".pathRND"`,
1685 "change: symlink to file",
1693 Data: []byte("content\n"),
1696 ft.CreateSymlink("path", "target")
1704 Data: []byte("content\n"),
1708 FileChanges: []safcm.FileChange{
1711 Old: safcm.FileChangeInfo{
1712 Mode: fs.ModeSymlink | 0777,
1718 New: safcm.FileChangeInfo{
1725 DataDiff: `@@ -1 +1,2 @@
1734 `4: files: "path" (group): type differs L--------- -> ----------`,
1735 `3: files: "path" (group): updating`,
1736 `4: files: "path" (group): creating temporary file ".path*"`,
1737 `4: files: "path" (group): renaming ".pathRND"`,
1743 "change: symlink to directory",
1747 Mode: fs.ModeDir | 0751,
1753 ft.CreateSymlink("path", "target")
1760 Mode: fs.ModeDir | 0751,
1764 FileChanges: []safcm.FileChange{
1767 Old: safcm.FileChangeInfo{
1768 Mode: fs.ModeSymlink | 0777,
1774 New: safcm.FileChangeInfo{
1775 Mode: fs.ModeDir | 0751,
1781 DataDiff: `@@ -1 +1 @@
1789 `4: files: "path" (group): type differs L--------- -> d---------`,
1790 `3: files: "path" (group): updating`,
1791 `4: files: "path" (group): removing (due to type change)`,
1792 `4: files: "path" (group): creating directory`,
1793 `4: files: "path" (group): chmodding drwxr-x--x`,
1794 fmt.Sprintf(`4: files: "path" (group): chowning %d/%d`, uid, gid),
1800 "change: directory to file",
1808 Data: []byte("content\n"),
1811 ft.CreateDirectory("path", 0777)
1819 Data: []byte("content\n"),
1823 FileChanges: []safcm.FileChange{
1826 Old: safcm.FileChangeInfo{
1827 Mode: fs.ModeDir | 0777,
1833 New: safcm.FileChangeInfo{
1844 `4: files: "path" (group): type differs d--------- -> ----------`,
1845 `3: files: "path" (group): updating`,
1846 `4: files: "path" (group): removing (due to type change)`,
1847 `4: files: "path" (group): creating temporary file ".path*"`,
1848 `4: files: "path" (group): renaming ".pathRND"`,
1853 "change: directory to file (non-empty)",
1861 Data: []byte("content\n"),
1864 ft.CreateDirectory("path", 0777)
1865 ft.CreateFile("path/file", "content\n", 0644)
1872 Mode: fs.ModeDir | 0777,
1877 Data: []byte("content\n"),
1881 FileChanges: []safcm.FileChange{
1884 Old: safcm.FileChangeInfo{
1885 Mode: fs.ModeDir | 0777,
1891 New: safcm.FileChangeInfo{
1902 `4: files: "path" (group): type differs d--------- -> ----------`,
1903 `3: files: "path" (group): updating`,
1904 `4: files: "path" (group): removing (due to type change)`,
1906 fmt.Errorf("will not replace non-empty directory, please remove manually"),
1910 "change: directory to symlink",
1914 Mode: fs.ModeSymlink | 0777,
1918 Data: []byte("target"),
1921 ft.CreateDirectory("path", 0777)
1928 Mode: fs.ModeSymlink | 0777,
1929 Data: []byte("target"),
1933 FileChanges: []safcm.FileChange{
1936 Old: safcm.FileChangeInfo{
1937 Mode: fs.ModeDir | 0777,
1943 New: safcm.FileChangeInfo{
1944 Mode: fs.ModeSymlink | 0777,
1954 `4: files: "path" (group): type differs d--------- -> L---------`,
1955 `3: files: "path" (group): updating`,
1956 `4: files: "path" (group): removing (due to type change)`,
1957 `4: files: "path" (group): creating temporary symlink ".pathRND"`,
1958 `4: files: "path" (group): renaming ".pathRND"`,
1964 "change: other to file",
1972 Data: []byte("content\n"),
1975 ft.CreateFifo("path", 0666)
1983 Data: []byte("content\n"),
1987 FileChanges: []safcm.FileChange{
1990 Old: safcm.FileChangeInfo{
1991 Mode: fs.ModeNamedPipe | 0666,
1997 New: safcm.FileChangeInfo{
2008 `4: files: "path" (group): type differs p--------- -> ----------`,
2009 `3: files: "path" (group): updating`,
2010 `4: files: "path" (group): creating temporary file ".path*"`,
2011 `4: files: "path" (group): renaming ".pathRND"`,
2017 "change: other to symlink",
2021 Mode: fs.ModeSymlink | 0777,
2025 Data: []byte("target"),
2028 ft.CreateFifo("path", 0666)
2035 Mode: fs.ModeSymlink | 0777,
2036 Data: []byte("target"),
2040 FileChanges: []safcm.FileChange{
2043 Old: safcm.FileChangeInfo{
2044 Mode: fs.ModeNamedPipe | 0666,
2050 New: safcm.FileChangeInfo{
2051 Mode: fs.ModeSymlink | 0777,
2061 `4: files: "path" (group): type differs p--------- -> L---------`,
2062 `3: files: "path" (group): updating`,
2063 `4: files: "path" (group): creating temporary symlink ".pathRND"`,
2064 `4: files: "path" (group): renaming ".pathRND"`,
2070 "change: other to directory",
2074 Mode: fs.ModeDir | 0751,
2080 ft.CreateFifo("path", 0666)
2087 Mode: fs.ModeDir | 0751,
2091 FileChanges: []safcm.FileChange{
2094 Old: safcm.FileChangeInfo{
2095 Mode: fs.ModeNamedPipe | 0666,
2101 New: safcm.FileChangeInfo{
2102 Mode: fs.ModeDir | 0751,
2112 `4: files: "path" (group): type differs p--------- -> d---------`,
2113 `3: files: "path" (group): updating`,
2114 `4: files: "path" (group): removing (due to type change)`,
2115 `4: files: "path" (group): creating directory`,
2116 `4: files: "path" (group): chmodding drwxr-x--x`,
2117 fmt.Sprintf(`4: files: "path" (group): chowning %d/%d`, uid, gid),
2123 "change: file to symlink (same content)",
2127 Mode: fs.ModeSymlink | 0777,
2131 Data: []byte("target"),
2134 ft.CreateFile("path", "target", 0644)
2141 Mode: fs.ModeSymlink | 0777,
2142 Data: []byte("target"),
2146 FileChanges: []safcm.FileChange{
2149 Old: safcm.FileChangeInfo{
2156 New: safcm.FileChangeInfo{
2157 Mode: fs.ModeSymlink | 0777,
2167 `4: files: "path" (group): type differs ---------- -> L---------`,
2168 `3: files: "path" (group): updating`,
2169 `4: files: "path" (group): creating temporary symlink ".pathRND"`,
2170 `4: files: "path" (group): renaming ".pathRND"`,
2175 // Symlink "attacks"
2178 "symlink in earlier path component",
2186 Data: []byte("content"),
2189 ft.CreateDirectory("tmp", 0755)
2190 ft.CreateSymlink("dir", "tmp")
2197 Mode: fs.ModeSymlink | 0777,
2198 Data: []byte("tmp"),
2202 Mode: fs.ModeDir | 0755,
2205 safcm.MsgSyncResp{},
2207 fmt.Errorf("symlink not permitted in path: \"dir\""),
2213 "relative path with leading dot",
2221 Data: []byte("content"),
2224 ft.CreateDirectory("dir", 0755)
2231 Mode: fs.ModeDir | 0755,
2236 Data: []byte("content"),
2240 FileChanges: []safcm.FileChange{
2244 New: safcm.FileChangeInfo{
2255 `4: files: "./dir/file" (group): will create`,
2256 `3: files: "./dir/file" (group): creating`,
2257 `4: files: "./dir/file" (group): creating temporary file "dir/.file*"`,
2258 `4: files: "./dir/file" (group): renaming "dir/.fileRND"`,
2285 ft.CreateFile("file", `this
2305 FileChanges: []safcm.FileChange{
2308 Old: safcm.FileChangeInfo{
2315 New: safcm.FileChangeInfo{
2322 DataDiff: `@@ -1,5 +1,7 @@
2336 `4: files: "file" (group): content differs`,
2337 `3: files: "file" (group): updating`,
2338 `4: files: "file" (group): dry-run, skipping changes`,
2344 "diff: binary both",
2353 Data: []byte("\x00\x01\x02\x03"),
2357 ft.CreateFile("file", "\x00\x01\x02", 0644)
2365 Data: []byte("\x00\x01\x02"),
2369 FileChanges: []safcm.FileChange{
2372 Old: safcm.FileChangeInfo{
2379 New: safcm.FileChangeInfo{
2386 DataDiff: "Binary files differ (3 -> 4 bytes), cannot show diff",
2391 `4: files: "file" (group): content differs`,
2392 `3: files: "file" (group): updating`,
2393 `4: files: "file" (group): dry-run, skipping changes`,
2408 Data: []byte("content\n"),
2412 ft.CreateFile("file", "\x00\x01\x02", 0644)
2420 Data: []byte("\x00\x01\x02"),
2424 FileChanges: []safcm.FileChange{
2427 Old: safcm.FileChangeInfo{
2434 New: safcm.FileChangeInfo{
2441 DataDiff: `@@ -1,2 +1,2 @@
2442 -<binary content, 3 bytes>
2450 `4: files: "file" (group): content differs`,
2451 `3: files: "file" (group): updating`,
2452 `4: files: "file" (group): dry-run, skipping changes`,
2467 Data: []byte("\x00\x01\x02\x03"),
2471 ft.CreateFile("file", "content\n", 0644)
2479 Data: []byte("content\n"),
2483 FileChanges: []safcm.FileChange{
2486 Old: safcm.FileChangeInfo{
2493 New: safcm.FileChangeInfo{
2500 DataDiff: `@@ -1,2 +1,2 @@
2502 +<binary content, 4 bytes>
2509 `4: files: "file" (group): content differs`,
2510 `3: files: "file" (group): updating`,
2511 `4: files: "file" (group): dry-run, skipping changes`,
2517 for _, tc := range tests {
2518 t.Run(tc.name, func(t *testing.T) {
2519 // Create separate test directory for each test case
2520 path := filepath.Join(cwd, "testdata", "file-"+tc.name)
2521 err := os.Mkdir(path, 0700)
2525 err = os.Chdir(path)
2530 if tc.prepare != nil {
2534 s, res := prepareSync(tc.req, &testRunner{
2537 err = s.setDefaults()
2542 // Deterministic temporary symlink names
2546 err = s.syncFile(tc.file, &changed)
2547 testutil.AssertErrorEqual(t, "err", err, tc.expErr)
2549 // Remove random file names from result
2550 for i, x := range dbg {
2551 dbg[i] = randFilesRegexp.ReplaceAllString(x, `RND"`)
2553 testutil.AssertEqual(t, "dbg", dbg, tc.expDbg)
2555 files, err := ft.WalkDir(path)
2559 testutil.AssertEqual(t, "files", files, tc.expFiles)
2561 testutil.AssertEqual(t, "changed", changed, tc.expChanged)
2562 testutil.AssertEqual(t, "resp", s.resp, tc.expResp)
2567 err = os.RemoveAll(filepath.Join(cwd, "testdata"))