]> ruderich.org/simon Gitweb - safcm/safcm.git/blob - remote/sync/files_test.go
Use SPDX license identifiers
[safcm/safcm.git] / remote / sync / files_test.go
1 // SPDX-License-Identifier: GPL-3.0-or-later
2 // Copyright (C) 2021-2024  Simon Ruderich
3
4 package sync
5
6 import (
7         "fmt"
8         "io/fs"
9         "math/rand"
10         "os"
11         "path/filepath"
12         "regexp"
13         "testing"
14
15         "ruderich.org/simon/safcm"
16         ft "ruderich.org/simon/safcm/remote/sync/filetest"
17         "ruderich.org/simon/safcm/testutil"
18 )
19
20 var randFilesRegexp = regexp.MustCompile(`\d+"$`)
21
22 func TestSyncFiles(t *testing.T) {
23         cwd, err := os.Getwd()
24         if err != nil {
25                 t.Fatal(err)
26         }
27         defer os.Chdir(cwd) //nolint:errcheck
28
29         err = os.RemoveAll("testdata")
30         if err != nil {
31                 t.Fatal(err)
32         }
33         err = os.Mkdir("testdata", 0700)
34         if err != nil {
35                 t.Fatal(err)
36         }
37
38         root := ft.File{
39                 Path: ".",
40                 Mode: fs.ModeDir | 0700,
41         }
42         user, uid, group, gid := ft.CurrentUserAndGroup()
43
44         skipUnlessCiRun := len(os.Getenv("SAFCM_CI_RUN")) == 0
45
46         tmpTestFilePath := "/tmp/safcm-sync-files-test-file"
47
48         tests := []struct {
49                 name        string
50                 skip        bool
51                 req         safcm.MsgSyncReq
52                 prepare     func()
53                 expTriggers []string
54                 expFiles    []ft.File
55                 expResp     safcm.MsgSyncResp
56                 expDbg      []string
57                 expErr      error
58         }{
59
60                 // NOTE: Also update MsgSyncResp in safcm test cases when
61                 // changing the MsgSyncResp struct!
62
63                 // See TestSyncFile() for most file related tests. This
64                 // function only tests the overall results and triggers.
65
66                 {
67                         "basic: create",
68                         false,
69                         safcm.MsgSyncReq{
70                                 Files: map[string]*safcm.File{
71                                         ".": {
72                                                 OrigGroup: "group",
73                                                 Path:      ".",
74                                                 Mode:      fs.ModeDir | 0700,
75                                                 Uid:       -1,
76                                                 Gid:       -1,
77                                         },
78                                         "dir": {
79                                                 OrigGroup: "group",
80                                                 Path:      "dir",
81                                                 Mode:      fs.ModeDir | 0755,
82                                                 Uid:       -1,
83                                                 Gid:       -1,
84                                         },
85                                         "dir/file": {
86                                                 OrigGroup: "group",
87                                                 Path:      "dir/file",
88                                                 Mode:      0644,
89                                                 Uid:       -1,
90                                                 Gid:       -1,
91                                                 Data:      []byte("content\n"),
92                                         },
93                                 },
94                         },
95                         nil,
96                         nil,
97                         []ft.File{
98                                 root,
99                                 {
100                                         Path: "dir",
101                                         Mode: fs.ModeDir | 0755,
102                                 },
103                                 {
104                                         Path: "dir/file",
105                                         Mode: 0644,
106                                         Data: []byte("content\n"),
107                                 },
108                         },
109                         safcm.MsgSyncResp{
110                                 FileChanges: []safcm.FileChange{
111                                         {
112                                                 Path:    "dir",
113                                                 Created: true,
114                                                 New: safcm.FileChangeInfo{
115                                                         Mode:  fs.ModeDir | 0755,
116                                                         User:  user,
117                                                         Uid:   uid,
118                                                         Group: group,
119                                                         Gid:   gid,
120                                                 },
121                                         },
122                                         {
123                                                 Path:    "dir/file",
124                                                 Created: true,
125                                                 New: safcm.FileChangeInfo{
126                                                         Mode:  0644,
127                                                         User:  user,
128                                                         Uid:   uid,
129                                                         Group: group,
130                                                         Gid:   gid,
131                                                 },
132                                         },
133                                 },
134                         },
135                         []string{
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"`,
146                         },
147                         nil,
148                 },
149
150                 {
151                         "basic: no change",
152                         false,
153                         safcm.MsgSyncReq{
154                                 Files: map[string]*safcm.File{
155                                         ".": {
156                                                 OrigGroup: "group",
157                                                 Path:      ".",
158                                                 Mode:      fs.ModeDir | 0700,
159                                                 Uid:       -1,
160                                                 Gid:       -1,
161                                         },
162                                         "dir": {
163                                                 OrigGroup: "group",
164                                                 Path:      "dir",
165                                                 Mode:      fs.ModeDir | 0755,
166                                                 Uid:       -1,
167                                                 Gid:       -1,
168                                         },
169                                         "dir/file": {
170                                                 OrigGroup: "group",
171                                                 Path:      "dir/file",
172                                                 Mode:      0644,
173                                                 Uid:       -1,
174                                                 Gid:       -1,
175                                                 Data:      []byte("content\n"),
176                                         },
177                                 },
178                         },
179                         func() {
180                                 ft.CreateDirectory("dir", 0755)
181                                 ft.CreateFile("dir/file", "content\n", 0644)
182                         },
183                         nil,
184                         []ft.File{
185                                 root,
186                                 {
187                                         Path: "dir",
188                                         Mode: fs.ModeDir | 0755,
189                                 },
190                                 {
191                                         Path: "dir/file",
192                                         Mode: 0644,
193                                         Data: []byte("content\n"),
194                                 },
195                         },
196                         safcm.MsgSyncResp{},
197                         []string{
198                                 `4: files: "." (group): unchanged`,
199                                 `4: files: "dir" (group): unchanged`,
200                                 `4: files: "dir/file" (group): unchanged`,
201                         },
202                         nil,
203                 },
204
205                 {
206                         "invalid File: user",
207                         false,
208                         safcm.MsgSyncReq{
209                                 Files: map[string]*safcm.File{
210                                         ".": {
211                                                 OrigGroup: "group",
212                                                 Path:      ".",
213                                                 Mode:      fs.ModeDir | 0700,
214                                                 User:      "user",
215                                                 Uid:       1,
216                                                 Gid:       -1,
217                                         },
218                                 },
219                         },
220                         nil,
221                         nil,
222                         []ft.File{
223                                 root,
224                         },
225                         safcm.MsgSyncResp{},
226                         nil,
227                         fmt.Errorf("\".\": cannot set both User (\"user\") and Uid (1)"),
228                 },
229                 {
230                         "invalid File: group",
231                         false,
232                         safcm.MsgSyncReq{
233                                 Files: map[string]*safcm.File{
234                                         ".": {
235                                                 OrigGroup: "group",
236                                                 Path:      ".",
237                                                 Mode:      fs.ModeDir | 0700,
238                                                 Uid:       -1,
239                                                 Group:     "group",
240                                                 Gid:       1,
241                                         },
242                                 },
243                         },
244                         nil,
245                         nil,
246                         []ft.File{
247                                 root,
248                         },
249                         safcm.MsgSyncResp{},
250                         nil,
251                         fmt.Errorf("\".\": cannot set both Group (\"group\") and Gid (1)"),
252                 },
253
254                 {
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.
259                         //
260                         // Use numeric IDs as not all systems use root/root;
261                         // for example BSDs use root/wheel.
262                         "absolute paths: no change",
263                         skipUnlessCiRun,
264                         safcm.MsgSyncReq{
265                                 Files: map[string]*safcm.File{
266                                         "/": {
267                                                 OrigGroup: "group",
268                                                 Path:      "/",
269                                                 Mode:      fs.ModeDir | 0755,
270                                                 Uid:       0,
271                                                 Gid:       0,
272                                         },
273                                         "/etc": {
274                                                 OrigGroup: "group",
275                                                 Path:      "/etc",
276                                                 Mode:      fs.ModeDir | 0755,
277                                                 Uid:       0,
278                                                 Gid:       0,
279                                         },
280                                         "/tmp": {
281                                                 OrigGroup: "group",
282                                                 Path:      "/tmp",
283                                                 Mode:      fs.ModeDir | 0777 | fs.ModeSticky,
284                                                 Uid:       0,
285                                                 Gid:       0,
286                                         },
287                                 },
288                         },
289                         nil,
290                         nil,
291                         []ft.File{
292                                 root,
293                         },
294                         safcm.MsgSyncResp{},
295                         []string{
296                                 `4: files: "/" (group): unchanged`,
297                                 `4: files: "/etc" (group): unchanged`,
298                                 `4: files: "/tmp" (group): unchanged`,
299                         },
300                         nil,
301                 },
302
303                 {
304                         "triggers: no change",
305                         false,
306                         safcm.MsgSyncReq{
307                                 Files: map[string]*safcm.File{
308                                         ".": {
309                                                 OrigGroup: "group",
310                                                 Path:      ".",
311                                                 Mode:      fs.ModeDir | 0700,
312                                                 Uid:       -1,
313                                                 Gid:       -1,
314                                                 TriggerCommands: []string{
315                                                         "echo trigger .",
316                                                 },
317                                         },
318                                         "dir": {
319                                                 OrigGroup: "group",
320                                                 Path:      "dir",
321                                                 Mode:      fs.ModeDir | 0755,
322                                                 Uid:       -1,
323                                                 Gid:       -1,
324                                                 TriggerCommands: []string{
325                                                         "echo trigger dir",
326                                                 },
327                                         },
328                                         "dir/file": {
329                                                 OrigGroup: "group",
330                                                 Path:      "dir/file",
331                                                 Mode:      0644,
332                                                 Uid:       -1,
333                                                 Gid:       -1,
334                                                 Data:      []byte("content\n"),
335                                                 TriggerCommands: []string{
336                                                         "echo trigger dir/file",
337                                                 },
338                                         },
339                                 },
340                         },
341                         func() {
342                                 ft.CreateDirectory("dir", 0755)
343                                 ft.CreateFile("dir/file", "content\n", 0644)
344                         },
345                         nil,
346                         []ft.File{
347                                 root,
348                                 {
349                                         Path: "dir",
350                                         Mode: fs.ModeDir | 0755,
351                                 },
352                                 {
353                                         Path: "dir/file",
354                                         Mode: 0644,
355                                         Data: []byte("content\n"),
356                                 },
357                         },
358                         safcm.MsgSyncResp{},
359                         []string{
360                                 `4: files: "." (group): unchanged`,
361                                 `4: files: "dir" (group): unchanged`,
362                                 `4: files: "dir/file" (group): unchanged`,
363                         },
364                         nil,
365                 },
366
367                 {
368                         "triggers: change root",
369                         false,
370                         safcm.MsgSyncReq{
371                                 Files: map[string]*safcm.File{
372                                         ".": {
373                                                 OrigGroup: "group",
374                                                 Path:      ".",
375                                                 Mode:      fs.ModeDir | 0700,
376                                                 Uid:       -1,
377                                                 Gid:       -1,
378                                                 TriggerCommands: []string{
379                                                         "echo trigger .",
380                                                 },
381                                         },
382                                         "dir": {
383                                                 OrigGroup: "group",
384                                                 Path:      "dir",
385                                                 Mode:      fs.ModeDir | 0755,
386                                                 Uid:       -1,
387                                                 Gid:       -1,
388                                                 TriggerCommands: []string{
389                                                         "echo trigger dir",
390                                                 },
391                                         },
392                                         "dir/file": {
393                                                 OrigGroup: "group",
394                                                 Path:      "dir/file",
395                                                 Mode:      0644,
396                                                 Uid:       -1,
397                                                 Gid:       -1,
398                                                 Data:      []byte("content\n"),
399                                                 TriggerCommands: []string{
400                                                         "echo trigger dir/file",
401                                                 },
402                                         },
403                                 },
404                         },
405                         func() {
406                                 ft.CreateDirectoryExists(".", 0750)
407                                 ft.CreateDirectory("dir", 0755)
408                                 ft.CreateFile("dir/file", "content\n", 0644)
409                         },
410                         []string{
411                                 ".",
412                         },
413                         []ft.File{
414                                 root,
415                                 {
416                                         Path: "dir",
417                                         Mode: fs.ModeDir | 0755,
418                                 },
419                                 {
420                                         Path: "dir/file",
421                                         Mode: 0644,
422                                         Data: []byte("content\n"),
423                                 },
424                         },
425                         safcm.MsgSyncResp{
426                                 FileChanges: []safcm.FileChange{
427                                         {
428                                                 Path: ".",
429                                                 Old: safcm.FileChangeInfo{
430                                                         Mode:  fs.ModeDir | 0750,
431                                                         User:  user,
432                                                         Uid:   uid,
433                                                         Group: group,
434                                                         Gid:   gid,
435                                                 },
436                                                 New: safcm.FileChangeInfo{
437                                                         Mode:  fs.ModeDir | 0700,
438                                                         User:  user,
439                                                         Uid:   uid,
440                                                         Group: group,
441                                                         Gid:   gid,
442                                                 },
443                                         },
444                                 },
445                         },
446                         []string{
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`,
453                         },
454                         nil,
455                 },
456
457                 {
458                         "triggers: change middle",
459                         false,
460                         safcm.MsgSyncReq{
461                                 Files: map[string]*safcm.File{
462                                         ".": {
463                                                 OrigGroup: "group",
464                                                 Path:      ".",
465                                                 Mode:      fs.ModeDir | 0700,
466                                                 Uid:       -1,
467                                                 Gid:       -1,
468                                                 TriggerCommands: []string{
469                                                         "echo trigger .",
470                                                 },
471                                         },
472                                         "dir": {
473                                                 OrigGroup: "group",
474                                                 Path:      "dir",
475                                                 Mode:      fs.ModeDir | 0755,
476                                                 Uid:       -1,
477                                                 Gid:       -1,
478                                                 TriggerCommands: []string{
479                                                         "echo trigger dir",
480                                                 },
481                                         },
482                                         "dir/file": {
483                                                 OrigGroup: "group",
484                                                 Path:      "dir/file",
485                                                 Mode:      0644,
486                                                 Uid:       -1,
487                                                 Gid:       -1,
488                                                 Data:      []byte("content\n"),
489                                                 TriggerCommands: []string{
490                                                         "echo trigger dir/file",
491                                                 },
492                                         },
493                                 },
494                         },
495                         func() {
496                                 ft.CreateDirectory("dir", 0750)
497                                 ft.CreateFile("dir/file", "content\n", 0644)
498                         },
499                         []string{
500                                 ".",
501                                 "dir",
502                         },
503                         []ft.File{
504                                 root,
505                                 {
506                                         Path: "dir",
507                                         Mode: fs.ModeDir | 0755,
508                                 },
509                                 {
510                                         Path: "dir/file",
511                                         Mode: 0644,
512                                         Data: []byte("content\n"),
513                                 },
514                         },
515                         safcm.MsgSyncResp{
516                                 FileChanges: []safcm.FileChange{
517                                         {
518                                                 Path: "dir",
519                                                 Old: safcm.FileChangeInfo{
520                                                         Mode:  fs.ModeDir | 0750,
521                                                         User:  user,
522                                                         Uid:   uid,
523                                                         Group: group,
524                                                         Gid:   gid,
525                                                 },
526                                                 New: safcm.FileChangeInfo{
527                                                         Mode:  fs.ModeDir | 0755,
528                                                         User:  user,
529                                                         Uid:   uid,
530                                                         Group: group,
531                                                         Gid:   gid,
532                                                 },
533                                         },
534                                 },
535                         },
536                         []string{
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`,
544                         },
545                         nil,
546                 },
547
548                 {
549                         "triggers: change leaf",
550                         false,
551                         safcm.MsgSyncReq{
552                                 Files: map[string]*safcm.File{
553                                         ".": {
554                                                 OrigGroup: "group",
555                                                 Path:      ".",
556                                                 Mode:      fs.ModeDir | 0700,
557                                                 Uid:       -1,
558                                                 Gid:       -1,
559                                                 TriggerCommands: []string{
560                                                         "echo trigger .",
561                                                 },
562                                         },
563                                         "dir": {
564                                                 OrigGroup: "group",
565                                                 Path:      "dir",
566                                                 Mode:      fs.ModeDir | 0755,
567                                                 Uid:       -1,
568                                                 Gid:       -1,
569                                                 TriggerCommands: []string{
570                                                         "echo trigger dir",
571                                                 },
572                                         },
573                                         "dir/file": {
574                                                 OrigGroup: "group",
575                                                 Path:      "dir/file",
576                                                 Mode:      0644,
577                                                 Uid:       -1,
578                                                 Gid:       -1,
579                                                 Data:      []byte("content\n"),
580                                                 TriggerCommands: []string{
581                                                         "echo trigger dir/file",
582                                                 },
583                                         },
584                                 },
585                         },
586                         func() {
587                                 ft.CreateDirectory("dir", 0755)
588                         },
589                         []string{
590                                 ".",
591                                 "dir",
592                                 "dir/file",
593                         },
594                         []ft.File{
595                                 root,
596                                 {
597                                         Path: "dir",
598                                         Mode: fs.ModeDir | 0755,
599                                 },
600                                 {
601                                         Path: "dir/file",
602                                         Mode: 0644,
603                                         Data: []byte("content\n"),
604                                 },
605                         },
606                         safcm.MsgSyncResp{
607                                 FileChanges: []safcm.FileChange{
608                                         {
609                                                 Path:    "dir/file",
610                                                 Created: true,
611                                                 New: safcm.FileChangeInfo{
612                                                         Mode:  0644,
613                                                         User:  user,
614                                                         Uid:   uid,
615                                                         Group: group,
616                                                         Gid:   gid,
617                                                 },
618                                         },
619                                 },
620                         },
621                         []string{
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"`,
631                         },
632                         nil,
633                 },
634
635                 {
636                         "triggers: multiple changes",
637                         false,
638                         safcm.MsgSyncReq{
639                                 Files: map[string]*safcm.File{
640                                         ".": {
641                                                 OrigGroup: "group",
642                                                 Path:      ".",
643                                                 Mode:      fs.ModeDir | 0700,
644                                                 Uid:       -1,
645                                                 Gid:       -1,
646                                                 TriggerCommands: []string{
647                                                         "echo trigger .",
648                                                 },
649                                         },
650                                         "dir": {
651                                                 OrigGroup: "group",
652                                                 Path:      "dir",
653                                                 Mode:      fs.ModeDir | 0755,
654                                                 Uid:       -1,
655                                                 Gid:       -1,
656                                                 TriggerCommands: []string{
657                                                         "echo trigger dir",
658                                                 },
659                                         },
660                                         "dir/file": {
661                                                 OrigGroup: "group",
662                                                 Path:      "dir/file",
663                                                 Mode:      0644,
664                                                 Uid:       -1,
665                                                 Gid:       -1,
666                                                 Data:      []byte("content\n"),
667                                                 TriggerCommands: []string{
668                                                         "echo trigger dir/file",
669                                                 },
670                                         },
671                                 },
672                         },
673                         nil,
674                         []string{
675                                 ".",
676                                 "dir",
677                                 "dir/file",
678                         },
679                         []ft.File{
680                                 root,
681                                 {
682                                         Path: "dir",
683                                         Mode: fs.ModeDir | 0755,
684                                 },
685                                 {
686                                         Path: "dir/file",
687                                         Mode: 0644,
688                                         Data: []byte("content\n"),
689                                 },
690                         },
691                         safcm.MsgSyncResp{
692                                 FileChanges: []safcm.FileChange{
693                                         {
694                                                 Path:    "dir",
695                                                 Created: true,
696                                                 New: safcm.FileChangeInfo{
697                                                         Mode:  fs.ModeDir | 0755,
698                                                         User:  user,
699                                                         Uid:   uid,
700                                                         Group: group,
701                                                         Gid:   gid,
702                                                 },
703                                         },
704                                         {
705                                                 Path:    "dir/file",
706                                                 Created: true,
707                                                 New: safcm.FileChangeInfo{
708                                                         Mode:  0644,
709                                                         User:  user,
710                                                         Uid:   uid,
711                                                         Group: group,
712                                                         Gid:   gid,
713                                                 },
714                                         },
715                                 },
716                         },
717                         []string{
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"`,
733                         },
734                         nil,
735                 },
736
737                 {
738                         "triggers: absolute paths",
739                         skipUnlessCiRun,
740                         safcm.MsgSyncReq{
741                                 Files: map[string]*safcm.File{
742                                         "/": {
743                                                 OrigGroup: "group",
744                                                 Path:      "/",
745                                                 Mode:      fs.ModeDir | 0755,
746                                                 Uid:       0,
747                                                 Gid:       0,
748                                                 TriggerCommands: []string{
749                                                         "echo trigger /",
750                                                 },
751                                         },
752                                         "/tmp": {
753                                                 OrigGroup: "group",
754                                                 Path:      "/tmp",
755                                                 Mode:      fs.ModeDir | 0777 | fs.ModeSticky,
756                                                 Uid:       0,
757                                                 Gid:       0,
758                                                 TriggerCommands: []string{
759                                                         "echo trigger /tmp",
760                                                 },
761                                         },
762                                         tmpTestFilePath: {
763                                                 OrigGroup: "group",
764                                                 Path:      tmpTestFilePath,
765                                                 Mode:      0600,
766                                                 Uid:       -1,
767                                                 Gid:       -1,
768                                                 TriggerCommands: []string{
769                                                         "echo trigger /tmp/file",
770                                                 },
771                                         },
772                                 },
773                         },
774                         nil,
775                         []string{
776                                 "/",
777                                 "/tmp",
778                                 // Don't use variable for more robust test
779                                 "/tmp/safcm-sync-files-test-file",
780                         },
781                         []ft.File{
782                                 root,
783                         },
784                         safcm.MsgSyncResp{
785                                 FileChanges: []safcm.FileChange{
786                                         {
787                                                 Path:    "/tmp/safcm-sync-files-test-file",
788                                                 Created: true,
789                                                 New: safcm.FileChangeInfo{
790                                                         Mode:  0600,
791                                                         User:  user,
792                                                         Uid:   uid,
793                                                         Group: group,
794                                                         Gid:   gid,
795                                                 },
796                                         },
797                                 },
798                         },
799                         []string{
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"`,
809                         },
810                         nil,
811                 },
812         }
813
814         for _, tc := range tests {
815                 t.Run(tc.name, func(t *testing.T) {
816                         if tc.skip {
817                                 t.SkipNow()
818                         }
819
820                         // Create separate test directory for each test case
821                         path := filepath.Join(cwd, "testdata", "files-"+tc.name)
822                         err := os.Mkdir(path, 0700)
823                         if err != nil {
824                                 t.Fatal(err)
825                         }
826                         err = os.Chdir(path)
827                         if err != nil {
828                                 t.Fatal(err)
829                         }
830
831                         if tc.prepare != nil {
832                                 tc.prepare()
833                         }
834
835                         s, res := prepareSync(tc.req, &testRunner{
836                                 t: t,
837                         })
838                         err = s.setDefaults()
839                         if err != nil {
840                                 t.Fatal(err)
841                         }
842
843                         err = s.syncFiles()
844                         testutil.AssertErrorEqual(t, "err", err, tc.expErr)
845                         dbg := res.Wait()
846                         // Remove random file names from result
847                         for i, x := range dbg {
848                                 dbg[i] = randFilesRegexp.ReplaceAllString(x, `RND"`)
849                         }
850                         testutil.AssertEqual(t, "dbg", dbg, tc.expDbg)
851
852                         files, err := ft.WalkDir(path)
853                         if err != nil {
854                                 t.Fatal(err)
855                         }
856                         testutil.AssertEqual(t, "files", files, tc.expFiles)
857
858                         testutil.AssertEqual(t, "resp", s.resp, tc.expResp)
859                         testutil.AssertEqual(t, "triggers",
860                                 s.triggers, tc.expTriggers)
861                 })
862         }
863
864         os.Remove(tmpTestFilePath)
865         if !t.Failed() {
866                 err = os.RemoveAll(filepath.Join(cwd, "testdata"))
867                 if err != nil {
868                         t.Fatal(err)
869                 }
870         }
871 }
872
873 func TestSyncFile(t *testing.T) {
874         cwd, err := os.Getwd()
875         if err != nil {
876                 t.Fatal(err)
877         }
878         defer os.Chdir(cwd) //nolint:errcheck
879
880         err = os.RemoveAll("testdata")
881         if err != nil {
882                 t.Fatal(err)
883         }
884         err = os.Mkdir("testdata", 0700)
885         if err != nil {
886                 t.Fatal(err)
887         }
888
889         root := ft.File{
890                 Path: ".",
891                 Mode: fs.ModeDir | 0700,
892         }
893         user, uid, group, gid := ft.CurrentUserAndGroup()
894
895         tests := []struct {
896                 name       string
897                 req        safcm.MsgSyncReq
898                 file       *safcm.File
899                 prepare    func()
900                 expChanged bool
901                 expFiles   []ft.File
902                 expResp    safcm.MsgSyncResp
903                 expDbg     []string
904                 expErr     error
905         }{
906
907                 // NOTE: Also update MsgSyncResp in safcm test cases when
908                 // changing the MsgSyncResp struct!
909
910                 // TODO: Add tests for chown and run them only as root
911
912                 // Regular file
913
914                 {
915                         "file: create",
916                         safcm.MsgSyncReq{},
917                         &safcm.File{
918                                 Path:      "file",
919                                 Mode:      0644,
920                                 Uid:       -1,
921                                 Gid:       -1,
922                                 Data:      []byte("content\n"),
923                                 OrigGroup: "group",
924                         },
925                         nil,
926                         true,
927                         []ft.File{
928                                 root,
929                                 {
930                                         Path: "file",
931                                         Mode: 0644,
932                                         Data: []byte("content\n"),
933                                 },
934                         },
935                         safcm.MsgSyncResp{
936                                 FileChanges: []safcm.FileChange{
937                                         {
938                                                 Path:    "file",
939                                                 Created: true,
940                                                 New: safcm.FileChangeInfo{
941                                                         Mode:  0644,
942                                                         User:  user,
943                                                         Uid:   uid,
944                                                         Group: group,
945                                                         Gid:   gid,
946                                                 },
947                                         },
948                                 },
949                         },
950                         []string{
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"`,
955                         },
956                         nil,
957                 },
958                 {
959                         "file: create (dry-run)",
960                         safcm.MsgSyncReq{
961                                 DryRun: true,
962                         },
963                         &safcm.File{
964                                 Path:      "file",
965                                 Mode:      0644,
966                                 Uid:       -1,
967                                 Gid:       -1,
968                                 Data:      []byte("content\n"),
969                                 OrigGroup: "group",
970                         },
971                         nil,
972                         true,
973                         []ft.File{root},
974                         safcm.MsgSyncResp{
975                                 FileChanges: []safcm.FileChange{
976                                         {
977                                                 Path:    "file",
978                                                 Created: true,
979                                                 New: safcm.FileChangeInfo{
980                                                         Mode:  0644,
981                                                         User:  user,
982                                                         Uid:   uid,
983                                                         Group: group,
984                                                         Gid:   gid,
985                                                 },
986                                         },
987                                 },
988                         },
989                         []string{
990                                 `4: files: "file" (group): will create`,
991                                 `3: files: "file" (group): creating`,
992                                 `4: files: "file" (group): dry-run, skipping changes`,
993                         },
994                         nil,
995                 },
996
997                 {
998                         "file: create, missing parent (dry-run)",
999                         safcm.MsgSyncReq{
1000                                 DryRun: true,
1001                         },
1002                         &safcm.File{
1003                                 Path:      "does-not-exist/file",
1004                                 Mode:      0644,
1005                                 Uid:       -1,
1006                                 Gid:       -1,
1007                                 Data:      []byte("content\n"),
1008                                 OrigGroup: "group",
1009                         },
1010                         nil,
1011                         true,
1012                         []ft.File{root},
1013                         safcm.MsgSyncResp{
1014                                 FileChanges: []safcm.FileChange{
1015                                         {
1016                                                 Path:    "does-not-exist/file",
1017                                                 Created: true,
1018                                                 New: safcm.FileChangeInfo{
1019                                                         Mode:  0644,
1020                                                         User:  user,
1021                                                         Uid:   uid,
1022                                                         Group: group,
1023                                                         Gid:   gid,
1024                                                 },
1025                                         },
1026                                 },
1027                         },
1028                         []string{
1029                                 `4: files: "does-not-exist/file" (group): will create (parent missing)`,
1030                                 `4: files: "does-not-exist/file" (group): dry-run, skipping changes`,
1031                         },
1032                         nil,
1033                 },
1034
1035                 {
1036                         "file: unchanged",
1037                         safcm.MsgSyncReq{},
1038                         &safcm.File{
1039                                 Path:      "file",
1040                                 Mode:      0644,
1041                                 Uid:       -1,
1042                                 Gid:       -1,
1043                                 Data:      []byte("content\n"),
1044                                 OrigGroup: "group",
1045                         },
1046                         func() {
1047                                 ft.CreateFile("file", "content\n", 0644)
1048                         },
1049                         false,
1050                         []ft.File{
1051                                 root,
1052                                 {
1053                                         Path: "file",
1054                                         Mode: 0644,
1055                                         Data: []byte("content\n"),
1056                                 },
1057                         },
1058                         safcm.MsgSyncResp{},
1059                         []string{
1060                                 `4: files: "file" (group): unchanged`,
1061                         },
1062                         nil,
1063                 },
1064
1065                 {
1066                         "file: unchanged (non-default user-group)",
1067                         safcm.MsgSyncReq{},
1068                         &safcm.File{
1069                                 Path:      "file",
1070                                 Mode:      0644,
1071                                 User:      user,
1072                                 Uid:       -1,
1073                                 Group:     group,
1074                                 Gid:       -1,
1075                                 Data:      []byte("content\n"),
1076                                 OrigGroup: "group",
1077                         },
1078                         func() {
1079                                 ft.CreateFile("file", "content\n", 0644)
1080                         },
1081                         false,
1082                         []ft.File{
1083                                 root,
1084                                 {
1085                                         Path: "file",
1086                                         Mode: 0644,
1087                                         Data: []byte("content\n"),
1088                                 },
1089                         },
1090                         safcm.MsgSyncResp{},
1091                         []string{
1092                                 `4: files: "file" (group): unchanged`,
1093                         },
1094                         nil,
1095                 },
1096
1097                 {
1098                         "file: permission",
1099                         safcm.MsgSyncReq{},
1100                         &safcm.File{
1101                                 Path:      "file",
1102                                 Mode:      0755 | fs.ModeSetuid,
1103                                 Uid:       -1,
1104                                 Gid:       -1,
1105                                 Data:      []byte("content\n"),
1106                                 OrigGroup: "group",
1107                         },
1108                         func() {
1109                                 ft.CreateFile("file", "content\n", 0755)
1110                         },
1111                         true,
1112                         []ft.File{
1113                                 root,
1114                                 {
1115                                         Path: "file",
1116                                         Mode: 0755 | fs.ModeSetuid,
1117                                         Data: []byte("content\n"),
1118                                 },
1119                         },
1120                         safcm.MsgSyncResp{
1121                                 FileChanges: []safcm.FileChange{
1122                                         {
1123                                                 Path: "file",
1124                                                 Old: safcm.FileChangeInfo{
1125                                                         Mode:  0755,
1126                                                         User:  user,
1127                                                         Uid:   uid,
1128                                                         Group: group,
1129                                                         Gid:   gid,
1130                                                 },
1131                                                 New: safcm.FileChangeInfo{
1132                                                         Mode:  0755 | fs.ModeSetuid,
1133                                                         User:  user,
1134                                                         Uid:   uid,
1135                                                         Group: group,
1136                                                         Gid:   gid,
1137                                                 },
1138                                         },
1139                                 },
1140                         },
1141                         []string{
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"`,
1146                         },
1147                         nil,
1148                 },
1149
1150                 {
1151                         "file: content",
1152                         safcm.MsgSyncReq{},
1153                         &safcm.File{
1154                                 Path:      "file",
1155                                 Mode:      0644,
1156                                 Uid:       -1,
1157                                 Gid:       -1,
1158                                 Data:      []byte("content\n"),
1159                                 OrigGroup: "group",
1160                         },
1161                         func() {
1162                                 ft.CreateFile("file", "old content\n", 0644)
1163                         },
1164                         true,
1165                         []ft.File{
1166                                 root,
1167                                 {
1168                                         Path: "file",
1169                                         Mode: 0644,
1170                                         Data: []byte("content\n"),
1171                                 },
1172                         },
1173                         safcm.MsgSyncResp{
1174                                 FileChanges: []safcm.FileChange{
1175                                         {
1176                                                 Path: "file",
1177                                                 Old: safcm.FileChangeInfo{
1178                                                         Mode:  0644,
1179                                                         User:  user,
1180                                                         Uid:   uid,
1181                                                         Group: group,
1182                                                         Gid:   gid,
1183                                                 },
1184                                                 New: safcm.FileChangeInfo{
1185                                                         Mode:  0644,
1186                                                         User:  user,
1187                                                         Uid:   uid,
1188                                                         Group: group,
1189                                                         Gid:   gid,
1190                                                 },
1191                                                 DataDiff: `@@ -1,2 +1,2 @@
1192 -old content
1193 +content
1194  
1195 `,
1196                                         },
1197                                 },
1198                         },
1199                         []string{
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"`,
1204                         },
1205                         nil,
1206                 },
1207
1208                 // Symbolic link
1209
1210                 {
1211                         "symlink: create",
1212                         safcm.MsgSyncReq{},
1213                         &safcm.File{
1214                                 Path:      "link",
1215                                 Mode:      fs.ModeSymlink | 0777,
1216                                 Uid:       -1,
1217                                 Gid:       -1,
1218                                 Data:      []byte("target"),
1219                                 OrigGroup: "group",
1220                         },
1221                         nil,
1222                         true,
1223                         []ft.File{
1224                                 root,
1225                                 {
1226                                         Path: "link",
1227                                         Mode: fs.ModeSymlink | 0777,
1228                                         Data: []byte("target"),
1229                                 },
1230                         },
1231                         safcm.MsgSyncResp{
1232                                 FileChanges: []safcm.FileChange{
1233                                         {
1234                                                 Path:    "link",
1235                                                 Created: true,
1236                                                 New: safcm.FileChangeInfo{
1237                                                         Mode:  fs.ModeSymlink | 0777,
1238                                                         User:  user,
1239                                                         Uid:   uid,
1240                                                         Group: group,
1241                                                         Gid:   gid,
1242                                                 },
1243                                         },
1244                                 },
1245                         },
1246                         []string{
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"`,
1251                         },
1252                         nil,
1253                 },
1254                 {
1255                         "symlink: create (conflict)",
1256                         safcm.MsgSyncReq{},
1257                         &safcm.File{
1258                                 Path:      "link",
1259                                 Mode:      fs.ModeSymlink | 0777,
1260                                 Uid:       -1,
1261                                 Gid:       -1,
1262                                 Data:      []byte("target"),
1263                                 OrigGroup: "group",
1264                         },
1265                         func() {
1266                                 ft.CreateFile(".link8717895732742165505", "", 0600)
1267                         },
1268                         true,
1269                         []ft.File{
1270                                 root,
1271                                 {
1272                                         Path: ".link8717895732742165505",
1273                                         Mode: 0600,
1274                                         Data: []byte(""),
1275                                 },
1276                                 {
1277                                         Path: "link",
1278                                         Mode: fs.ModeSymlink | 0777,
1279                                         Data: []byte("target"),
1280                                 },
1281                         },
1282                         safcm.MsgSyncResp{
1283                                 FileChanges: []safcm.FileChange{
1284                                         {
1285                                                 Path:    "link",
1286                                                 Created: true,
1287                                                 New: safcm.FileChangeInfo{
1288                                                         Mode:  fs.ModeSymlink | 0777,
1289                                                         User:  user,
1290                                                         Uid:   uid,
1291                                                         Group: group,
1292                                                         Gid:   gid,
1293                                                 },
1294                                         },
1295                                 },
1296                         },
1297                         []string{
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"`,
1303                         },
1304                         nil,
1305                 },
1306                 {
1307                         "symlink: create (dry-run)",
1308                         safcm.MsgSyncReq{
1309                                 DryRun: true,
1310                         },
1311                         &safcm.File{
1312                                 Path:      "link",
1313                                 Mode:      fs.ModeSymlink | 0777,
1314                                 Uid:       -1,
1315                                 Gid:       -1,
1316                                 Data:      []byte("target"),
1317                                 OrigGroup: "group",
1318                         },
1319                         nil,
1320                         true,
1321                         []ft.File{root},
1322                         safcm.MsgSyncResp{
1323                                 FileChanges: []safcm.FileChange{
1324                                         {
1325                                                 Path:    "link",
1326                                                 Created: true,
1327                                                 New: safcm.FileChangeInfo{
1328                                                         Mode:  fs.ModeSymlink | 0777,
1329                                                         User:  user,
1330                                                         Uid:   uid,
1331                                                         Group: group,
1332                                                         Gid:   gid,
1333                                                 },
1334                                         },
1335                                 },
1336                         },
1337                         []string{
1338                                 `4: files: "link" (group): will create`,
1339                                 `3: files: "link" (group): creating`,
1340                                 `4: files: "link" (group): dry-run, skipping changes`,
1341                         },
1342                         nil,
1343                 },
1344
1345                 {
1346                         "symlink: unchanged",
1347                         safcm.MsgSyncReq{},
1348                         &safcm.File{
1349                                 Path:      "link",
1350                                 Mode:      fs.ModeSymlink | 0777,
1351                                 Uid:       -1,
1352                                 Gid:       -1,
1353                                 Data:      []byte("target"),
1354                                 OrigGroup: "group",
1355                         },
1356                         func() {
1357                                 ft.CreateSymlink("link", "target")
1358                         },
1359                         false,
1360                         []ft.File{
1361                                 root,
1362                                 {
1363                                         Path: "link",
1364                                         Mode: fs.ModeSymlink | 0777,
1365                                         Data: []byte("target"),
1366                                 },
1367                         },
1368                         safcm.MsgSyncResp{},
1369                         []string{
1370                                 `4: files: "link" (group): unchanged`,
1371                         },
1372                         nil,
1373                 },
1374
1375                 {
1376                         "symlink: content",
1377                         safcm.MsgSyncReq{},
1378                         &safcm.File{
1379                                 Path:      "link",
1380                                 Mode:      fs.ModeSymlink | 0777,
1381                                 Uid:       -1,
1382                                 Gid:       -1,
1383                                 Data:      []byte("target"),
1384                                 OrigGroup: "group",
1385                         },
1386                         func() {
1387                                 ft.CreateSymlink("link", "old-target")
1388                         },
1389                         true,
1390                         []ft.File{
1391                                 root,
1392                                 {
1393                                         Path: "link",
1394                                         Mode: fs.ModeSymlink | 0777,
1395                                         Data: []byte("target"),
1396                                 },
1397                         },
1398                         safcm.MsgSyncResp{
1399                                 FileChanges: []safcm.FileChange{
1400                                         {
1401                                                 Path: "link",
1402                                                 Old: safcm.FileChangeInfo{
1403                                                         Mode:  fs.ModeSymlink | 0777,
1404                                                         User:  user,
1405                                                         Uid:   uid,
1406                                                         Group: group,
1407                                                         Gid:   gid,
1408                                                 },
1409                                                 New: safcm.FileChangeInfo{
1410                                                         Mode:  fs.ModeSymlink | 0777,
1411                                                         User:  user,
1412                                                         Uid:   uid,
1413                                                         Group: group,
1414                                                         Gid:   gid,
1415                                                 },
1416                                                 DataDiff: `@@ -1 +1 @@
1417 -old-target
1418 +target
1419 `,
1420                                         },
1421                                 },
1422                         },
1423                         []string{
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"`,
1428                         },
1429                         nil,
1430                 },
1431
1432                 // Directory
1433
1434                 {
1435                         "directory: create",
1436                         safcm.MsgSyncReq{},
1437                         &safcm.File{
1438                                 Path:      "dir",
1439                                 Mode:      fs.ModeDir | 0705,
1440                                 Uid:       -1,
1441                                 Gid:       -1,
1442                                 OrigGroup: "group",
1443                         },
1444                         nil,
1445                         true,
1446                         []ft.File{
1447                                 root,
1448                                 {
1449                                         Path: "dir",
1450                                         Mode: fs.ModeDir | 0705,
1451                                 },
1452                         },
1453                         safcm.MsgSyncResp{
1454                                 FileChanges: []safcm.FileChange{
1455                                         {
1456                                                 Path:    "dir",
1457                                                 Created: true,
1458                                                 New: safcm.FileChangeInfo{
1459                                                         Mode:  fs.ModeDir | 0705,
1460                                                         User:  user,
1461                                                         Uid:   uid,
1462                                                         Group: group,
1463                                                         Gid:   gid,
1464                                                 },
1465                                         },
1466                                 },
1467                         },
1468                         []string{
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),
1474                         },
1475                         nil,
1476                 },
1477                 {
1478                         "directory: create (dry-run)",
1479                         safcm.MsgSyncReq{
1480                                 DryRun: true,
1481                         },
1482                         &safcm.File{
1483                                 Path:      "dir",
1484                                 Mode:      fs.ModeDir | 0644,
1485                                 Uid:       -1,
1486                                 Gid:       -1,
1487                                 OrigGroup: "group",
1488                         },
1489                         nil,
1490                         true,
1491                         []ft.File{root},
1492                         safcm.MsgSyncResp{
1493                                 FileChanges: []safcm.FileChange{
1494                                         {
1495                                                 Path:    "dir",
1496                                                 Created: true,
1497                                                 New: safcm.FileChangeInfo{
1498                                                         Mode:  fs.ModeDir | 0644,
1499                                                         User:  user,
1500                                                         Uid:   uid,
1501                                                         Group: group,
1502                                                         Gid:   gid,
1503                                                 },
1504                                         },
1505                                 },
1506                         },
1507                         []string{
1508                                 `4: files: "dir" (group): will create`,
1509                                 `3: files: "dir" (group): creating`,
1510                                 `4: files: "dir" (group): dry-run, skipping changes`,
1511                         },
1512                         nil,
1513                 },
1514
1515                 {
1516                         "directory: create, missing parent (dry-run)",
1517                         safcm.MsgSyncReq{
1518                                 DryRun: true,
1519                         },
1520                         &safcm.File{
1521                                 Path:      "does-not-exist/dir",
1522                                 Mode:      fs.ModeDir | 0755,
1523                                 Uid:       -1,
1524                                 Gid:       -1,
1525                                 OrigGroup: "group",
1526                         },
1527                         nil,
1528                         true,
1529                         []ft.File{root},
1530                         safcm.MsgSyncResp{
1531                                 FileChanges: []safcm.FileChange{
1532                                         {
1533                                                 Path:    "does-not-exist/dir",
1534                                                 Created: true,
1535                                                 New: safcm.FileChangeInfo{
1536                                                         Mode:  fs.ModeDir | 0755,
1537                                                         User:  user,
1538                                                         Uid:   uid,
1539                                                         Group: group,
1540                                                         Gid:   gid,
1541                                                 },
1542                                         },
1543                                 },
1544                         },
1545                         []string{
1546                                 `4: files: "does-not-exist/dir" (group): will create (parent missing)`,
1547                                 `4: files: "does-not-exist/dir" (group): dry-run, skipping changes`,
1548                         },
1549                         nil,
1550                 },
1551
1552                 {
1553                         "directory: unchanged",
1554                         safcm.MsgSyncReq{},
1555                         &safcm.File{
1556                                 Path:      "dir",
1557                                 Mode:      fs.ModeDir | 0755,
1558                                 Uid:       -1,
1559                                 Gid:       -1,
1560                                 OrigGroup: "group",
1561                         },
1562                         func() {
1563                                 ft.CreateDirectory("dir", 0755)
1564                         },
1565                         false,
1566                         []ft.File{
1567                                 root,
1568                                 {
1569                                         Path: "dir",
1570                                         Mode: fs.ModeDir | 0755,
1571                                 },
1572                         },
1573                         safcm.MsgSyncResp{},
1574                         []string{
1575                                 `4: files: "dir" (group): unchanged`,
1576                         },
1577                         nil,
1578                 },
1579
1580                 {
1581                         "directory: permission",
1582                         safcm.MsgSyncReq{},
1583                         &safcm.File{
1584                                 Path:      "dir",
1585                                 Mode:      fs.ModeDir | 0755 | fs.ModeSetgid,
1586                                 Uid:       -1,
1587                                 Gid:       -1,
1588                                 OrigGroup: "group",
1589                         },
1590                         func() {
1591                                 ft.CreateDirectory("dir", 0500|fs.ModeSticky)
1592                         },
1593                         true,
1594                         []ft.File{
1595                                 root,
1596                                 {
1597                                         Path: "dir",
1598                                         Mode: fs.ModeDir | 0755 | fs.ModeSetgid,
1599                                 },
1600                         },
1601                         safcm.MsgSyncResp{
1602                                 FileChanges: []safcm.FileChange{
1603                                         {
1604                                                 Path: "dir",
1605                                                 Old: safcm.FileChangeInfo{
1606                                                         Mode:  fs.ModeDir | 0500 | fs.ModeSticky,
1607                                                         User:  user,
1608                                                         Uid:   uid,
1609                                                         Group: group,
1610                                                         Gid:   gid,
1611                                                 },
1612                                                 New: safcm.FileChangeInfo{
1613                                                         Mode:  fs.ModeDir | 0755 | fs.ModeSetgid,
1614                                                         User:  user,
1615                                                         Uid:   uid,
1616                                                         Group: group,
1617                                                         Gid:   gid,
1618                                                 },
1619                                         },
1620                                 },
1621                         },
1622                         []string{
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`,
1626                         },
1627                         nil,
1628                 },
1629
1630                 // Type changes
1631
1632                 {
1633                         "change: file to directory",
1634                         safcm.MsgSyncReq{},
1635                         &safcm.File{
1636                                 Path:      "path",
1637                                 Mode:      fs.ModeDir | 0751,
1638                                 Uid:       -1,
1639                                 Gid:       -1,
1640                                 OrigGroup: "group",
1641                         },
1642                         func() {
1643                                 ft.CreateFile("path", "content\n", 0644)
1644                         },
1645                         true,
1646                         []ft.File{
1647                                 root,
1648                                 {
1649                                         Path: "path",
1650                                         Mode: fs.ModeDir | 0751,
1651                                 },
1652                         },
1653                         safcm.MsgSyncResp{
1654                                 FileChanges: []safcm.FileChange{
1655                                         {
1656                                                 Path: "path",
1657                                                 Old: safcm.FileChangeInfo{
1658                                                         Mode:  0644,
1659                                                         User:  user,
1660                                                         Uid:   uid,
1661                                                         Group: group,
1662                                                         Gid:   gid,
1663                                                 },
1664                                                 New: safcm.FileChangeInfo{
1665                                                         Mode:  fs.ModeDir | 0751,
1666                                                         User:  user,
1667                                                         Uid:   uid,
1668                                                         Group: group,
1669                                                         Gid:   gid,
1670                                                 },
1671                                                 DataDiff: `@@ -1,2 +1 @@
1672 -content
1673  
1674 `,
1675                                         },
1676                                 },
1677                         },
1678                         []string{
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),
1685                         },
1686                         nil,
1687                 },
1688
1689                 {
1690                         "change: file to symlink",
1691                         safcm.MsgSyncReq{},
1692                         &safcm.File{
1693                                 Path:      "path",
1694                                 Mode:      fs.ModeSymlink | 0777,
1695                                 Uid:       -1,
1696                                 Gid:       -1,
1697                                 OrigGroup: "group",
1698                                 Data:      []byte("target"),
1699                         },
1700                         func() {
1701                                 ft.CreateFile("path", "content\n", 0644)
1702                         },
1703                         true,
1704                         []ft.File{
1705                                 root,
1706                                 {
1707                                         Path: "path",
1708                                         Mode: fs.ModeSymlink | 0777,
1709                                         Data: []byte("target"),
1710                                 },
1711                         },
1712                         safcm.MsgSyncResp{
1713                                 FileChanges: []safcm.FileChange{
1714                                         {
1715                                                 Path: "path",
1716                                                 Old: safcm.FileChangeInfo{
1717                                                         Mode:  0644,
1718                                                         User:  user,
1719                                                         Uid:   uid,
1720                                                         Group: group,
1721                                                         Gid:   gid,
1722                                                 },
1723                                                 New: safcm.FileChangeInfo{
1724                                                         Mode:  fs.ModeSymlink | 0777,
1725                                                         User:  user,
1726                                                         Uid:   uid,
1727                                                         Group: group,
1728                                                         Gid:   gid,
1729                                                 },
1730                                                 DataDiff: `@@ -1,2 +1 @@
1731 -content
1732 -
1733 +target
1734 `,
1735                                         },
1736                                 },
1737                         },
1738                         []string{
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"`,
1743                         },
1744                         nil,
1745                 },
1746
1747                 {
1748                         "change: symlink to file",
1749                         safcm.MsgSyncReq{},
1750                         &safcm.File{
1751                                 Path:      "path",
1752                                 Mode:      0640,
1753                                 Uid:       -1,
1754                                 Gid:       -1,
1755                                 OrigGroup: "group",
1756                                 Data:      []byte("content\n"),
1757                         },
1758                         func() {
1759                                 ft.CreateSymlink("path", "target")
1760                         },
1761                         true,
1762                         []ft.File{
1763                                 root,
1764                                 {
1765                                         Path: "path",
1766                                         Mode: 0640,
1767                                         Data: []byte("content\n"),
1768                                 },
1769                         },
1770                         safcm.MsgSyncResp{
1771                                 FileChanges: []safcm.FileChange{
1772                                         {
1773                                                 Path: "path",
1774                                                 Old: safcm.FileChangeInfo{
1775                                                         Mode:  fs.ModeSymlink | 0777,
1776                                                         User:  user,
1777                                                         Uid:   uid,
1778                                                         Group: group,
1779                                                         Gid:   gid,
1780                                                 },
1781                                                 New: safcm.FileChangeInfo{
1782                                                         Mode:  0640,
1783                                                         User:  user,
1784                                                         Uid:   uid,
1785                                                         Group: group,
1786                                                         Gid:   gid,
1787                                                 },
1788                                                 DataDiff: `@@ -1 +1,2 @@
1789 -target
1790 +content
1791 +
1792 `,
1793                                         },
1794                                 },
1795                         },
1796                         []string{
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"`,
1801                         },
1802                         nil,
1803                 },
1804
1805                 {
1806                         "change: symlink to directory",
1807                         safcm.MsgSyncReq{},
1808                         &safcm.File{
1809                                 Path:      "path",
1810                                 Mode:      fs.ModeDir | 0751,
1811                                 Uid:       -1,
1812                                 Gid:       -1,
1813                                 OrigGroup: "group",
1814                         },
1815                         func() {
1816                                 ft.CreateSymlink("path", "target")
1817                         },
1818                         true,
1819                         []ft.File{
1820                                 root,
1821                                 {
1822                                         Path: "path",
1823                                         Mode: fs.ModeDir | 0751,
1824                                 },
1825                         },
1826                         safcm.MsgSyncResp{
1827                                 FileChanges: []safcm.FileChange{
1828                                         {
1829                                                 Path: "path",
1830                                                 Old: safcm.FileChangeInfo{
1831                                                         Mode:  fs.ModeSymlink | 0777,
1832                                                         User:  user,
1833                                                         Uid:   uid,
1834                                                         Group: group,
1835                                                         Gid:   gid,
1836                                                 },
1837                                                 New: safcm.FileChangeInfo{
1838                                                         Mode:  fs.ModeDir | 0751,
1839                                                         User:  user,
1840                                                         Uid:   uid,
1841                                                         Group: group,
1842                                                         Gid:   gid,
1843                                                 },
1844                                                 DataDiff: `@@ -1 +1 @@
1845 -target
1846 +
1847 `,
1848                                         },
1849                                 },
1850                         },
1851                         []string{
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),
1858                         },
1859                         nil,
1860                 },
1861
1862                 {
1863                         "change: directory to file",
1864                         safcm.MsgSyncReq{},
1865                         &safcm.File{
1866                                 Path:      "path",
1867                                 Mode:      0666,
1868                                 Uid:       -1,
1869                                 Gid:       -1,
1870                                 OrigGroup: "group",
1871                                 Data:      []byte("content\n"),
1872                         },
1873                         func() {
1874                                 ft.CreateDirectory("path", 0777)
1875                         },
1876                         true,
1877                         []ft.File{
1878                                 root,
1879                                 {
1880                                         Path: "path",
1881                                         Mode: 0666,
1882                                         Data: []byte("content\n"),
1883                                 },
1884                         },
1885                         safcm.MsgSyncResp{
1886                                 FileChanges: []safcm.FileChange{
1887                                         {
1888                                                 Path: "path",
1889                                                 Old: safcm.FileChangeInfo{
1890                                                         Mode:  fs.ModeDir | 0777,
1891                                                         User:  user,
1892                                                         Uid:   uid,
1893                                                         Group: group,
1894                                                         Gid:   gid,
1895                                                 },
1896                                                 New: safcm.FileChangeInfo{
1897                                                         Mode:  0666,
1898                                                         User:  user,
1899                                                         Uid:   uid,
1900                                                         Group: group,
1901                                                         Gid:   gid,
1902                                                 },
1903                                         },
1904                                 },
1905                         },
1906                         []string{
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"`,
1912                         },
1913                         nil,
1914                 },
1915                 {
1916                         "change: directory to file (non-empty)",
1917                         safcm.MsgSyncReq{},
1918                         &safcm.File{
1919                                 Path:      "path",
1920                                 Mode:      0666,
1921                                 Uid:       -1,
1922                                 Gid:       -1,
1923                                 OrigGroup: "group",
1924                                 Data:      []byte("content\n"),
1925                         },
1926                         func() {
1927                                 ft.CreateDirectory("path", 0777)
1928                                 ft.CreateFile("path/file", "content\n", 0644)
1929                         },
1930                         true,
1931                         []ft.File{
1932                                 root,
1933                                 {
1934                                         Path: "path",
1935                                         Mode: fs.ModeDir | 0777,
1936                                 },
1937                                 {
1938                                         Path: "path/file",
1939                                         Mode: 0644,
1940                                         Data: []byte("content\n"),
1941                                 },
1942                         },
1943                         safcm.MsgSyncResp{
1944                                 FileChanges: []safcm.FileChange{
1945                                         {
1946                                                 Path: "path",
1947                                                 Old: safcm.FileChangeInfo{
1948                                                         Mode:  fs.ModeDir | 0777,
1949                                                         User:  user,
1950                                                         Uid:   uid,
1951                                                         Group: group,
1952                                                         Gid:   gid,
1953                                                 },
1954                                                 New: safcm.FileChangeInfo{
1955                                                         Mode:  0666,
1956                                                         User:  user,
1957                                                         Uid:   uid,
1958                                                         Group: group,
1959                                                         Gid:   gid,
1960                                                 },
1961                                         },
1962                                 },
1963                         },
1964                         []string{
1965                                 `4: files: "path" (group): type differs d--------- -> ----------`,
1966                                 `3: files: "path" (group): updating`,
1967                                 `4: files: "path" (group): removing (due to type change)`,
1968                         },
1969                         fmt.Errorf("will not replace non-empty directory, please remove manually"),
1970                 },
1971
1972                 {
1973                         "change: directory to symlink",
1974                         safcm.MsgSyncReq{},
1975                         &safcm.File{
1976                                 Path:      "path",
1977                                 Mode:      fs.ModeSymlink | 0777,
1978                                 Uid:       -1,
1979                                 Gid:       -1,
1980                                 OrigGroup: "group",
1981                                 Data:      []byte("target"),
1982                         },
1983                         func() {
1984                                 ft.CreateDirectory("path", 0777)
1985                         },
1986                         true,
1987                         []ft.File{
1988                                 root,
1989                                 {
1990                                         Path: "path",
1991                                         Mode: fs.ModeSymlink | 0777,
1992                                         Data: []byte("target"),
1993                                 },
1994                         },
1995                         safcm.MsgSyncResp{
1996                                 FileChanges: []safcm.FileChange{
1997                                         {
1998                                                 Path: "path",
1999                                                 Old: safcm.FileChangeInfo{
2000                                                         Mode:  fs.ModeDir | 0777,
2001                                                         User:  user,
2002                                                         Uid:   uid,
2003                                                         Group: group,
2004                                                         Gid:   gid,
2005                                                 },
2006                                                 New: safcm.FileChangeInfo{
2007                                                         Mode:  fs.ModeSymlink | 0777,
2008                                                         User:  user,
2009                                                         Uid:   uid,
2010                                                         Group: group,
2011                                                         Gid:   gid,
2012                                                 },
2013                                         },
2014                                 },
2015                         },
2016                         []string{
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"`,
2022                         },
2023                         nil,
2024                 },
2025
2026                 {
2027                         "change: other to file",
2028                         safcm.MsgSyncReq{},
2029                         &safcm.File{
2030                                 Path:      "path",
2031                                 Mode:      0640,
2032                                 Uid:       -1,
2033                                 Gid:       -1,
2034                                 OrigGroup: "group",
2035                                 Data:      []byte("content\n"),
2036                         },
2037                         func() {
2038                                 ft.CreateFifo("path", 0666)
2039                         },
2040                         true,
2041                         []ft.File{
2042                                 root,
2043                                 {
2044                                         Path: "path",
2045                                         Mode: 0640,
2046                                         Data: []byte("content\n"),
2047                                 },
2048                         },
2049                         safcm.MsgSyncResp{
2050                                 FileChanges: []safcm.FileChange{
2051                                         {
2052                                                 Path: "path",
2053                                                 Old: safcm.FileChangeInfo{
2054                                                         Mode:  fs.ModeNamedPipe | 0666,
2055                                                         User:  user,
2056                                                         Uid:   uid,
2057                                                         Group: group,
2058                                                         Gid:   gid,
2059                                                 },
2060                                                 New: safcm.FileChangeInfo{
2061                                                         Mode:  0640,
2062                                                         User:  user,
2063                                                         Uid:   uid,
2064                                                         Group: group,
2065                                                         Gid:   gid,
2066                                                 },
2067                                         },
2068                                 },
2069                         },
2070                         []string{
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"`,
2075                         },
2076                         nil,
2077                 },
2078
2079                 {
2080                         "change: other to symlink",
2081                         safcm.MsgSyncReq{},
2082                         &safcm.File{
2083                                 Path:      "path",
2084                                 Mode:      fs.ModeSymlink | 0777,
2085                                 Uid:       -1,
2086                                 Gid:       -1,
2087                                 OrigGroup: "group",
2088                                 Data:      []byte("target"),
2089                         },
2090                         func() {
2091                                 ft.CreateFifo("path", 0666)
2092                         },
2093                         true,
2094                         []ft.File{
2095                                 root,
2096                                 {
2097                                         Path: "path",
2098                                         Mode: fs.ModeSymlink | 0777,
2099                                         Data: []byte("target"),
2100                                 },
2101                         },
2102                         safcm.MsgSyncResp{
2103                                 FileChanges: []safcm.FileChange{
2104                                         {
2105                                                 Path: "path",
2106                                                 Old: safcm.FileChangeInfo{
2107                                                         Mode:  fs.ModeNamedPipe | 0666,
2108                                                         User:  user,
2109                                                         Uid:   uid,
2110                                                         Group: group,
2111                                                         Gid:   gid,
2112                                                 },
2113                                                 New: safcm.FileChangeInfo{
2114                                                         Mode:  fs.ModeSymlink | 0777,
2115                                                         User:  user,
2116                                                         Uid:   uid,
2117                                                         Group: group,
2118                                                         Gid:   gid,
2119                                                 },
2120                                         },
2121                                 },
2122                         },
2123                         []string{
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"`,
2128                         },
2129                         nil,
2130                 },
2131
2132                 {
2133                         "change: other to directory",
2134                         safcm.MsgSyncReq{},
2135                         &safcm.File{
2136                                 Path:      "path",
2137                                 Mode:      fs.ModeDir | 0751,
2138                                 Uid:       -1,
2139                                 Gid:       -1,
2140                                 OrigGroup: "group",
2141                         },
2142                         func() {
2143                                 ft.CreateFifo("path", 0666)
2144                         },
2145                         true,
2146                         []ft.File{
2147                                 root,
2148                                 {
2149                                         Path: "path",
2150                                         Mode: fs.ModeDir | 0751,
2151                                 },
2152                         },
2153                         safcm.MsgSyncResp{
2154                                 FileChanges: []safcm.FileChange{
2155                                         {
2156                                                 Path: "path",
2157                                                 Old: safcm.FileChangeInfo{
2158                                                         Mode:  fs.ModeNamedPipe | 0666,
2159                                                         User:  user,
2160                                                         Uid:   uid,
2161                                                         Group: group,
2162                                                         Gid:   gid,
2163                                                 },
2164                                                 New: safcm.FileChangeInfo{
2165                                                         Mode:  fs.ModeDir | 0751,
2166                                                         User:  user,
2167                                                         Uid:   uid,
2168                                                         Group: group,
2169                                                         Gid:   gid,
2170                                                 },
2171                                         },
2172                                 },
2173                         },
2174                         []string{
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),
2181                         },
2182                         nil,
2183                 },
2184
2185                 {
2186                         "change: file to symlink (same content)",
2187                         safcm.MsgSyncReq{},
2188                         &safcm.File{
2189                                 Path:      "path",
2190                                 Mode:      fs.ModeSymlink | 0777,
2191                                 Uid:       -1,
2192                                 Gid:       -1,
2193                                 OrigGroup: "group",
2194                                 Data:      []byte("target"),
2195                         },
2196                         func() {
2197                                 ft.CreateFile("path", "target", 0644)
2198                         },
2199                         true,
2200                         []ft.File{
2201                                 root,
2202                                 {
2203                                         Path: "path",
2204                                         Mode: fs.ModeSymlink | 0777,
2205                                         Data: []byte("target"),
2206                                 },
2207                         },
2208                         safcm.MsgSyncResp{
2209                                 FileChanges: []safcm.FileChange{
2210                                         {
2211                                                 Path: "path",
2212                                                 Old: safcm.FileChangeInfo{
2213                                                         Mode:  0644,
2214                                                         User:  user,
2215                                                         Uid:   uid,
2216                                                         Group: group,
2217                                                         Gid:   gid,
2218                                                 },
2219                                                 New: safcm.FileChangeInfo{
2220                                                         Mode:  fs.ModeSymlink | 0777,
2221                                                         User:  user,
2222                                                         Uid:   uid,
2223                                                         Group: group,
2224                                                         Gid:   gid,
2225                                                 },
2226                                         },
2227                                 },
2228                         },
2229                         []string{
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"`,
2234                         },
2235                         nil,
2236                 },
2237
2238                 // Symlink "attacks"
2239
2240                 {
2241                         "symlink in earlier path component",
2242                         safcm.MsgSyncReq{},
2243                         &safcm.File{
2244                                 Path:      "dir/file",
2245                                 Mode:      0644,
2246                                 Uid:       -1,
2247                                 Gid:       -1,
2248                                 OrigGroup: "group",
2249                                 Data:      []byte("content"),
2250                         },
2251                         func() {
2252                                 ft.CreateDirectory("tmp", 0755)
2253                                 ft.CreateSymlink("dir", "tmp")
2254                         },
2255                         false,
2256                         []ft.File{
2257                                 root,
2258                                 {
2259                                         Path: "dir",
2260                                         Mode: fs.ModeSymlink | 0777,
2261                                         Data: []byte("tmp"),
2262                                 },
2263                                 {
2264                                         Path: "tmp",
2265                                         Mode: fs.ModeDir | 0755,
2266                                 },
2267                         },
2268                         safcm.MsgSyncResp{},
2269                         nil,
2270                         fmt.Errorf("symlink not permitted in path: \"dir\""),
2271                 },
2272
2273                 // Border cases
2274
2275                 {
2276                         "relative path with leading dot",
2277                         safcm.MsgSyncReq{},
2278                         &safcm.File{
2279                                 Path:      "./dir/file",
2280                                 Mode:      0644,
2281                                 Uid:       -1,
2282                                 Gid:       -1,
2283                                 OrigGroup: "group",
2284                                 Data:      []byte("content"),
2285                         },
2286                         func() {
2287                                 ft.CreateDirectory("dir", 0755)
2288                         },
2289                         true,
2290                         []ft.File{
2291                                 root,
2292                                 {
2293                                         Path: "dir",
2294                                         Mode: fs.ModeDir | 0755,
2295                                 },
2296                                 {
2297                                         Path: "dir/file",
2298                                         Mode: 0644,
2299                                         Data: []byte("content"),
2300                                 },
2301                         },
2302                         safcm.MsgSyncResp{
2303                                 FileChanges: []safcm.FileChange{
2304                                         {
2305                                                 Path:    "./dir/file",
2306                                                 Created: true,
2307                                                 New: safcm.FileChangeInfo{
2308                                                         Mode:  0644,
2309                                                         User:  user,
2310                                                         Uid:   uid,
2311                                                         Group: group,
2312                                                         Gid:   gid,
2313                                                 },
2314                                         },
2315                                 },
2316                         },
2317                         []string{
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"`,
2322                         },
2323                         nil,
2324                 },
2325
2326                 // Diffs
2327
2328                 {
2329                         "diff: textual",
2330                         safcm.MsgSyncReq{
2331                                 DryRun: true,
2332                         },
2333                         &safcm.File{
2334                                 Path: "file",
2335                                 Mode: 0644,
2336                                 Uid:  -1,
2337                                 Gid:  -1,
2338                                 Data: []byte(`
2339 this
2340 is
2341 a
2342 simple
2343 file
2344 `),
2345                                 OrigGroup: "group",
2346                         },
2347                         func() {
2348                                 ft.CreateFile("file", `this
2349 is
2350 file
2351 !
2352 `, 0644)
2353                         },
2354                         true,
2355                         []ft.File{
2356                                 root,
2357                                 {
2358                                         Path: "file",
2359                                         Mode: 0644,
2360                                         Data: []byte(`this
2361 is
2362 file
2363 !
2364 `),
2365                                 },
2366                         },
2367                         safcm.MsgSyncResp{
2368                                 FileChanges: []safcm.FileChange{
2369                                         {
2370                                                 Path: "file",
2371                                                 Old: safcm.FileChangeInfo{
2372                                                         Mode:  0644,
2373                                                         User:  user,
2374                                                         Uid:   uid,
2375                                                         Group: group,
2376                                                         Gid:   gid,
2377                                                 },
2378                                                 New: safcm.FileChangeInfo{
2379                                                         Mode:  0644,
2380                                                         User:  user,
2381                                                         Uid:   uid,
2382                                                         Group: group,
2383                                                         Gid:   gid,
2384                                                 },
2385                                                 DataDiff: `@@ -1,5 +1,7 @@
2386 +
2387  this
2388  is
2389 +a
2390 +simple
2391  file
2392 -!
2393  
2394 `,
2395                                         },
2396                                 },
2397                         },
2398                         []string{
2399                                 `4: files: "file" (group): content differs`,
2400                                 `3: files: "file" (group): updating`,
2401                                 `4: files: "file" (group): dry-run, skipping changes`,
2402                         },
2403                         nil,
2404                 },
2405
2406                 {
2407                         "diff: binary both",
2408                         safcm.MsgSyncReq{
2409                                 DryRun: true,
2410                         },
2411                         &safcm.File{
2412                                 Path:      "file",
2413                                 Mode:      0644,
2414                                 Uid:       -1,
2415                                 Gid:       -1,
2416                                 Data:      []byte("\x00\x01\x02\x03"),
2417                                 OrigGroup: "group",
2418                         },
2419                         func() {
2420                                 ft.CreateFile("file", "\x00\x01\x02", 0644)
2421                         },
2422                         true,
2423                         []ft.File{
2424                                 root,
2425                                 {
2426                                         Path: "file",
2427                                         Mode: 0644,
2428                                         Data: []byte("\x00\x01\x02"),
2429                                 },
2430                         },
2431                         safcm.MsgSyncResp{
2432                                 FileChanges: []safcm.FileChange{
2433                                         {
2434                                                 Path: "file",
2435                                                 Old: safcm.FileChangeInfo{
2436                                                         Mode:  0644,
2437                                                         User:  user,
2438                                                         Uid:   uid,
2439                                                         Group: group,
2440                                                         Gid:   gid,
2441                                                 },
2442                                                 New: safcm.FileChangeInfo{
2443                                                         Mode:  0644,
2444                                                         User:  user,
2445                                                         Uid:   uid,
2446                                                         Group: group,
2447                                                         Gid:   gid,
2448                                                 },
2449                                                 DataDiff: "Binary files differ (3 -> 4 bytes), cannot show diff",
2450                                         },
2451                                 },
2452                         },
2453                         []string{
2454                                 `4: files: "file" (group): content differs`,
2455                                 `3: files: "file" (group): updating`,
2456                                 `4: files: "file" (group): dry-run, skipping changes`,
2457                         },
2458                         nil,
2459                 },
2460
2461                 {
2462                         "diff: binary old",
2463                         safcm.MsgSyncReq{
2464                                 DryRun: true,
2465                         },
2466                         &safcm.File{
2467                                 Path:      "file",
2468                                 Mode:      0644,
2469                                 Uid:       -1,
2470                                 Gid:       -1,
2471                                 Data:      []byte("content\n"),
2472                                 OrigGroup: "group",
2473                         },
2474                         func() {
2475                                 ft.CreateFile("file", "\x00\x01\x02", 0644)
2476                         },
2477                         true,
2478                         []ft.File{
2479                                 root,
2480                                 {
2481                                         Path: "file",
2482                                         Mode: 0644,
2483                                         Data: []byte("\x00\x01\x02"),
2484                                 },
2485                         },
2486                         safcm.MsgSyncResp{
2487                                 FileChanges: []safcm.FileChange{
2488                                         {
2489                                                 Path: "file",
2490                                                 Old: safcm.FileChangeInfo{
2491                                                         Mode:  0644,
2492                                                         User:  user,
2493                                                         Uid:   uid,
2494                                                         Group: group,
2495                                                         Gid:   gid,
2496                                                 },
2497                                                 New: safcm.FileChangeInfo{
2498                                                         Mode:  0644,
2499                                                         User:  user,
2500                                                         Uid:   uid,
2501                                                         Group: group,
2502                                                         Gid:   gid,
2503                                                 },
2504                                                 DataDiff: `@@ -1,2 +1,2 @@
2505 -<binary content, 3 bytes>
2506 +content
2507  
2508 `,
2509                                         },
2510                                 },
2511                         },
2512                         []string{
2513                                 `4: files: "file" (group): content differs`,
2514                                 `3: files: "file" (group): updating`,
2515                                 `4: files: "file" (group): dry-run, skipping changes`,
2516                         },
2517                         nil,
2518                 },
2519
2520                 {
2521                         "diff: binary new",
2522                         safcm.MsgSyncReq{
2523                                 DryRun: true,
2524                         },
2525                         &safcm.File{
2526                                 Path:      "file",
2527                                 Mode:      0644,
2528                                 Uid:       -1,
2529                                 Gid:       -1,
2530                                 Data:      []byte("\x00\x01\x02\x03"),
2531                                 OrigGroup: "group",
2532                         },
2533                         func() {
2534                                 ft.CreateFile("file", "content\n", 0644)
2535                         },
2536                         true,
2537                         []ft.File{
2538                                 root,
2539                                 {
2540                                         Path: "file",
2541                                         Mode: 0644,
2542                                         Data: []byte("content\n"),
2543                                 },
2544                         },
2545                         safcm.MsgSyncResp{
2546                                 FileChanges: []safcm.FileChange{
2547                                         {
2548                                                 Path: "file",
2549                                                 Old: safcm.FileChangeInfo{
2550                                                         Mode:  0644,
2551                                                         User:  user,
2552                                                         Uid:   uid,
2553                                                         Group: group,
2554                                                         Gid:   gid,
2555                                                 },
2556                                                 New: safcm.FileChangeInfo{
2557                                                         Mode:  0644,
2558                                                         User:  user,
2559                                                         Uid:   uid,
2560                                                         Group: group,
2561                                                         Gid:   gid,
2562                                                 },
2563                                                 DataDiff: `@@ -1,2 +1,2 @@
2564 -content
2565 +<binary content, 4 bytes>
2566  
2567 `,
2568                                         },
2569                                 },
2570                         },
2571                         []string{
2572                                 `4: files: "file" (group): content differs`,
2573                                 `3: files: "file" (group): updating`,
2574                                 `4: files: "file" (group): dry-run, skipping changes`,
2575                         },
2576                         nil,
2577                 },
2578         }
2579
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)
2585                         if err != nil {
2586                                 t.Fatal(err)
2587                         }
2588                         err = os.Chdir(path)
2589                         if err != nil {
2590                                 t.Fatal(err)
2591                         }
2592
2593                         if tc.prepare != nil {
2594                                 tc.prepare()
2595                         }
2596
2597                         s, res := prepareSync(tc.req, &testRunner{
2598                                 t: t,
2599                         })
2600                         err = s.setDefaults()
2601                         if err != nil {
2602                                 t.Fatal(err)
2603                         }
2604
2605                         // Deterministic temporary symlink names
2606                         rand.Seed(0)
2607
2608                         var changed bool
2609                         err = s.syncFile(tc.file, &changed)
2610                         testutil.AssertErrorEqual(t, "err", err, tc.expErr)
2611                         dbg := res.Wait()
2612                         // Remove random file names from result
2613                         for i, x := range dbg {
2614                                 dbg[i] = randFilesRegexp.ReplaceAllString(x, `RND"`)
2615                         }
2616                         testutil.AssertEqual(t, "dbg", dbg, tc.expDbg)
2617
2618                         files, err := ft.WalkDir(path)
2619                         if err != nil {
2620                                 t.Fatal(err)
2621                         }
2622                         testutil.AssertEqual(t, "files", files, tc.expFiles)
2623
2624                         testutil.AssertEqual(t, "changed", changed, tc.expChanged)
2625                         testutil.AssertEqual(t, "resp", s.resp, tc.expResp)
2626                 })
2627         }
2628
2629         if !t.Failed() {
2630                 err = os.RemoveAll(filepath.Join(cwd, "testdata"))
2631                 if err != nil {
2632                         t.Fatal(err)
2633                 }
2634         }
2635 }