| 
									
										
										
										
											2018-07-21 20:56:24 +02:00
										 |  |  | package restorer | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"bytes" | 
					
						
							|  |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2024-02-22 17:55:50 -07:00
										 |  |  | 	"encoding/json" | 
					
						
							| 
									
										
										
										
											2022-12-02 19:36:43 +01:00
										 |  |  | 	"io" | 
					
						
							| 
									
										
										
										
											2022-09-04 11:23:31 +02:00
										 |  |  | 	"math" | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 	"os" | 
					
						
							|  |  |  | 	"path/filepath" | 
					
						
							| 
									
										
										
										
											2024-06-29 18:58:17 +02:00
										 |  |  | 	"reflect" | 
					
						
							| 
									
										
										
										
											2020-08-28 23:29:33 +02:00
										 |  |  | 	"runtime" | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2024-05-31 18:15:50 +02:00
										 |  |  | 	"syscall" | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 	"testing" | 
					
						
							|  |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-04 11:23:31 +02:00
										 |  |  | 	"github.com/restic/restic/internal/archiver" | 
					
						
							| 
									
										
										
										
											2024-05-31 20:57:28 +02:00
										 |  |  | 	"github.com/restic/restic/internal/errors" | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 	"github.com/restic/restic/internal/fs" | 
					
						
							|  |  |  | 	"github.com/restic/restic/internal/repository" | 
					
						
							|  |  |  | 	"github.com/restic/restic/internal/restic" | 
					
						
							|  |  |  | 	rtest "github.com/restic/restic/internal/test" | 
					
						
							| 
									
										
										
										
											2021-08-07 22:52:05 +02:00
										 |  |  | 	"golang.org/x/sync/errgroup" | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type Node interface{} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type Snapshot struct { | 
					
						
							| 
									
										
										
										
											2020-03-06 23:48:22 +01:00
										 |  |  | 	Nodes map[string]Node | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type File struct { | 
					
						
							| 
									
										
										
										
											2024-02-22 17:55:50 -07:00
										 |  |  | 	Data       string | 
					
						
							|  |  |  | 	Links      uint64 | 
					
						
							|  |  |  | 	Inode      uint64 | 
					
						
							|  |  |  | 	Mode       os.FileMode | 
					
						
							|  |  |  | 	ModTime    time.Time | 
					
						
							|  |  |  | 	attributes *FileAttributes | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-07 22:44:47 +02:00
										 |  |  | type Symlink struct { | 
					
						
							|  |  |  | 	Target  string | 
					
						
							|  |  |  | 	ModTime time.Time | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | type Dir struct { | 
					
						
							| 
									
										
										
										
											2024-02-22 17:55:50 -07:00
										 |  |  | 	Nodes      map[string]Node | 
					
						
							|  |  |  | 	Mode       os.FileMode | 
					
						
							|  |  |  | 	ModTime    time.Time | 
					
						
							|  |  |  | 	attributes *FileAttributes | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type FileAttributes struct { | 
					
						
							|  |  |  | 	ReadOnly  bool | 
					
						
							|  |  |  | 	Hidden    bool | 
					
						
							|  |  |  | 	System    bool | 
					
						
							|  |  |  | 	Archive   bool | 
					
						
							|  |  |  | 	Encrypted bool | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-19 22:44:50 +01:00
										 |  |  | func saveFile(t testing.TB, repo restic.BlobSaver, node File) restic.ID { | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 	ctx, cancel := context.WithCancel(context.Background()) | 
					
						
							|  |  |  | 	defer cancel() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-01 14:26:57 +02:00
										 |  |  | 	id, _, _, err := repo.SaveBlob(ctx, restic.DataBlob, []byte(node.Data), restic.ID{}, false) | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return id | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-22 17:55:50 -07:00
										 |  |  | func saveDir(t testing.TB, repo restic.BlobSaver, nodes map[string]Node, inode uint64, getGenericAttributes func(attr *FileAttributes, isDir bool) (genericAttributes map[restic.GenericAttributeType]json.RawMessage)) restic.ID { | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 	ctx, cancel := context.WithCancel(context.Background()) | 
					
						
							|  |  |  | 	defer cancel() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tree := &restic.Tree{} | 
					
						
							|  |  |  | 	for name, n := range nodes { | 
					
						
							| 
									
										
										
										
											2018-04-07 20:32:19 -04:00
										 |  |  | 		inode++ | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 		switch node := n.(type) { | 
					
						
							|  |  |  | 		case File: | 
					
						
							| 
									
										
										
										
											2018-09-27 08:59:33 -04:00
										 |  |  | 			fi := n.(File).Inode | 
					
						
							|  |  |  | 			if fi == 0 { | 
					
						
							|  |  |  | 				fi = inode | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			lc := n.(File).Links | 
					
						
							|  |  |  | 			if lc == 0 { | 
					
						
							|  |  |  | 				lc = 1 | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			fc := []restic.ID{} | 
					
						
							|  |  |  | 			if len(n.(File).Data) > 0 { | 
					
						
							|  |  |  | 				fc = append(fc, saveFile(t, repo, node)) | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2020-08-28 23:29:33 +02:00
										 |  |  | 			mode := node.Mode | 
					
						
							|  |  |  | 			if mode == 0 { | 
					
						
							|  |  |  | 				mode = 0644 | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2021-01-30 19:35:46 +01:00
										 |  |  | 			err := tree.Insert(&restic.Node{ | 
					
						
							| 
									
										
										
										
											2024-02-22 17:55:50 -07:00
										 |  |  | 				Type:              "file", | 
					
						
							|  |  |  | 				Mode:              mode, | 
					
						
							|  |  |  | 				ModTime:           node.ModTime, | 
					
						
							|  |  |  | 				Name:              name, | 
					
						
							|  |  |  | 				UID:               uint32(os.Getuid()), | 
					
						
							|  |  |  | 				GID:               uint32(os.Getgid()), | 
					
						
							|  |  |  | 				Content:           fc, | 
					
						
							|  |  |  | 				Size:              uint64(len(n.(File).Data)), | 
					
						
							|  |  |  | 				Inode:             fi, | 
					
						
							|  |  |  | 				Links:             lc, | 
					
						
							|  |  |  | 				GenericAttributes: getGenericAttributes(node.attributes, false), | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 			}) | 
					
						
							| 
									
										
										
										
											2021-01-30 19:35:46 +01:00
										 |  |  | 			rtest.OK(t, err) | 
					
						
							| 
									
										
										
										
											2024-06-07 22:44:47 +02:00
										 |  |  | 		case Symlink: | 
					
						
							|  |  |  | 			symlink := n.(Symlink) | 
					
						
							|  |  |  | 			err := tree.Insert(&restic.Node{ | 
					
						
							|  |  |  | 				Type:       "symlink", | 
					
						
							|  |  |  | 				Mode:       os.ModeSymlink | 0o777, | 
					
						
							|  |  |  | 				ModTime:    symlink.ModTime, | 
					
						
							|  |  |  | 				Name:       name, | 
					
						
							|  |  |  | 				UID:        uint32(os.Getuid()), | 
					
						
							|  |  |  | 				GID:        uint32(os.Getgid()), | 
					
						
							|  |  |  | 				LinkTarget: symlink.Target, | 
					
						
							|  |  |  | 				Inode:      inode, | 
					
						
							|  |  |  | 				Links:      1, | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 			rtest.OK(t, err) | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 		case Dir: | 
					
						
							| 
									
										
										
										
											2024-02-22 17:55:50 -07:00
										 |  |  | 			id := saveDir(t, repo, node.Nodes, inode, getGenericAttributes) | 
					
						
							| 
									
										
										
										
											2018-01-07 14:50:47 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			mode := node.Mode | 
					
						
							|  |  |  | 			if mode == 0 { | 
					
						
							|  |  |  | 				mode = 0755 | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-30 19:35:46 +01:00
										 |  |  | 			err := tree.Insert(&restic.Node{ | 
					
						
							| 
									
										
										
										
											2024-02-22 17:55:50 -07:00
										 |  |  | 				Type:              "dir", | 
					
						
							|  |  |  | 				Mode:              mode, | 
					
						
							|  |  |  | 				ModTime:           node.ModTime, | 
					
						
							|  |  |  | 				Name:              name, | 
					
						
							|  |  |  | 				UID:               uint32(os.Getuid()), | 
					
						
							|  |  |  | 				GID:               uint32(os.Getgid()), | 
					
						
							|  |  |  | 				Subtree:           &id, | 
					
						
							|  |  |  | 				GenericAttributes: getGenericAttributes(node.attributes, false), | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 			}) | 
					
						
							| 
									
										
										
										
											2021-01-30 19:35:46 +01:00
										 |  |  | 			rtest.OK(t, err) | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 		default: | 
					
						
							|  |  |  | 			t.Fatalf("unknown node type %T", node) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-12 14:38:19 +02:00
										 |  |  | 	id, err := restic.SaveTree(ctx, repo, tree) | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return id | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-22 17:55:50 -07:00
										 |  |  | func saveSnapshot(t testing.TB, repo restic.Repository, snapshot Snapshot, getGenericAttributes func(attr *FileAttributes, isDir bool) (genericAttributes map[restic.GenericAttributeType]json.RawMessage)) (*restic.Snapshot, restic.ID) { | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 	ctx, cancel := context.WithCancel(context.Background()) | 
					
						
							|  |  |  | 	defer cancel() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-07 22:52:05 +02:00
										 |  |  | 	wg, wgCtx := errgroup.WithContext(ctx) | 
					
						
							|  |  |  | 	repo.StartPackUploader(wgCtx, wg) | 
					
						
							| 
									
										
										
										
											2024-02-22 17:55:50 -07:00
										 |  |  | 	treeID := saveDir(t, repo, snapshot.Nodes, 1000, getGenericAttributes) | 
					
						
							| 
									
										
										
										
											2017-12-03 07:32:50 -05:00
										 |  |  | 	err := repo.Flush(ctx) | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	sn, err := restic.NewSnapshot([]string{"test"}, nil, "", time.Now()) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	sn.Tree = &treeID | 
					
						
							| 
									
										
										
										
											2022-06-12 14:38:19 +02:00
										 |  |  | 	id, err := restic.SaveSnapshot(ctx, repo, sn) | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-21 20:56:38 +02:00
										 |  |  | 	return sn, id | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-22 17:55:50 -07:00
										 |  |  | var noopGetGenericAttributes = func(attr *FileAttributes, isDir bool) (genericAttributes map[restic.GenericAttributeType]json.RawMessage) { | 
					
						
							|  |  |  | 	// No-op | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | func TestRestorer(t *testing.T) { | 
					
						
							|  |  |  | 	var tests = []struct { | 
					
						
							|  |  |  | 		Snapshot | 
					
						
							|  |  |  | 		Files      map[string]string | 
					
						
							| 
									
										
										
										
											2018-09-14 20:55:30 -04:00
										 |  |  | 		ErrorsMust map[string]map[string]struct{} | 
					
						
							|  |  |  | 		ErrorsMay  map[string]map[string]struct{} | 
					
						
							| 
									
										
										
										
											2024-06-29 19:23:09 +02:00
										 |  |  | 		Select     func(item string, isDir bool) (selectForRestore bool, childMayBeSelected bool) | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 	}{ | 
					
						
							|  |  |  | 		// valid test cases | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			Snapshot: Snapshot{ | 
					
						
							|  |  |  | 				Nodes: map[string]Node{ | 
					
						
							| 
									
										
										
										
											2018-09-27 08:59:33 -04:00
										 |  |  | 					"foo": File{Data: "content: foo\n"}, | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 					"dirtest": Dir{ | 
					
						
							|  |  |  | 						Nodes: map[string]Node{ | 
					
						
							| 
									
										
										
										
											2018-09-27 08:59:33 -04:00
										 |  |  | 							"file": File{Data: "content: file\n"}, | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 						}, | 
					
						
							|  |  |  | 					}, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			Files: map[string]string{ | 
					
						
							|  |  |  | 				"foo":          "content: foo\n", | 
					
						
							|  |  |  | 				"dirtest/file": "content: file\n", | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			Snapshot: Snapshot{ | 
					
						
							|  |  |  | 				Nodes: map[string]Node{ | 
					
						
							| 
									
										
										
										
											2018-09-27 08:59:33 -04:00
										 |  |  | 					"top": File{Data: "toplevel file"}, | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 					"dir": Dir{ | 
					
						
							|  |  |  | 						Nodes: map[string]Node{ | 
					
						
							| 
									
										
										
										
											2018-09-27 08:59:33 -04:00
										 |  |  | 							"file": File{Data: "file in dir"}, | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 							"subdir": Dir{ | 
					
						
							|  |  |  | 								Nodes: map[string]Node{ | 
					
						
							| 
									
										
										
										
											2018-09-27 08:59:33 -04:00
										 |  |  | 									"file": File{Data: "file in subdir"}, | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 								}, | 
					
						
							|  |  |  | 							}, | 
					
						
							|  |  |  | 						}, | 
					
						
							|  |  |  | 					}, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			Files: map[string]string{ | 
					
						
							|  |  |  | 				"top":             "toplevel file", | 
					
						
							|  |  |  | 				"dir/file":        "file in dir", | 
					
						
							|  |  |  | 				"dir/subdir/file": "file in subdir", | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2018-01-07 14:50:47 +01:00
										 |  |  | 		{ | 
					
						
							|  |  |  | 			Snapshot: Snapshot{ | 
					
						
							|  |  |  | 				Nodes: map[string]Node{ | 
					
						
							|  |  |  | 					"dir": Dir{ | 
					
						
							|  |  |  | 						Mode: 0444, | 
					
						
							| 
									
										
										
										
											2018-01-07 15:13:24 +01:00
										 |  |  | 					}, | 
					
						
							| 
									
										
										
										
											2018-09-27 08:59:33 -04:00
										 |  |  | 					"file": File{Data: "top-level file"}, | 
					
						
							| 
									
										
										
										
											2018-01-07 15:13:24 +01:00
										 |  |  | 				}, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			Files: map[string]string{ | 
					
						
							|  |  |  | 				"file": "top-level file", | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			Snapshot: Snapshot{ | 
					
						
							|  |  |  | 				Nodes: map[string]Node{ | 
					
						
							|  |  |  | 					"dir": Dir{ | 
					
						
							|  |  |  | 						Mode: 0555, | 
					
						
							| 
									
										
										
										
											2018-01-07 14:50:47 +01:00
										 |  |  | 						Nodes: map[string]Node{ | 
					
						
							| 
									
										
										
										
											2018-09-27 08:59:33 -04:00
										 |  |  | 							"file": File{Data: "file in dir"}, | 
					
						
							| 
									
										
										
										
											2018-01-07 14:50:47 +01:00
										 |  |  | 						}, | 
					
						
							|  |  |  | 					}, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			Files: map[string]string{ | 
					
						
							|  |  |  | 				"dir/file": "file in dir", | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2018-07-14 21:19:42 +02:00
										 |  |  | 		{ | 
					
						
							|  |  |  | 			Snapshot: Snapshot{ | 
					
						
							|  |  |  | 				Nodes: map[string]Node{ | 
					
						
							| 
									
										
										
										
											2018-09-27 08:59:33 -04:00
										 |  |  | 					"topfile": File{Data: "top-level file"}, | 
					
						
							| 
									
										
										
										
											2018-07-14 21:19:42 +02:00
										 |  |  | 				}, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			Files: map[string]string{ | 
					
						
							|  |  |  | 				"topfile": "top-level file", | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2018-07-21 14:06:19 +02:00
										 |  |  | 		{ | 
					
						
							|  |  |  | 			Snapshot: Snapshot{ | 
					
						
							|  |  |  | 				Nodes: map[string]Node{ | 
					
						
							|  |  |  | 					"dir": Dir{ | 
					
						
							|  |  |  | 						Nodes: map[string]Node{ | 
					
						
							| 
									
										
										
										
											2018-09-27 08:59:33 -04:00
										 |  |  | 							"file": File{Data: "content: file\n"}, | 
					
						
							| 
									
										
										
										
											2018-07-21 14:06:19 +02:00
										 |  |  | 						}, | 
					
						
							|  |  |  | 					}, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			Files: map[string]string{ | 
					
						
							|  |  |  | 				"dir/file": "content: file\n", | 
					
						
							|  |  |  | 			}, | 
					
						
							| 
									
										
										
										
											2024-06-29 19:23:09 +02:00
										 |  |  | 			Select: func(item string, isDir bool) (selectedForRestore bool, childMayBeSelected bool) { | 
					
						
							| 
									
										
										
										
											2018-07-21 14:06:19 +02:00
										 |  |  | 				switch item { | 
					
						
							|  |  |  | 				case filepath.FromSlash("/dir"): | 
					
						
							|  |  |  | 					childMayBeSelected = true | 
					
						
							|  |  |  | 				case filepath.FromSlash("/dir/file"): | 
					
						
							|  |  |  | 					selectedForRestore = true | 
					
						
							|  |  |  | 					childMayBeSelected = true | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				return selectedForRestore, childMayBeSelected | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// test cases with invalid/constructed names | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			Snapshot: Snapshot{ | 
					
						
							|  |  |  | 				Nodes: map[string]Node{ | 
					
						
							| 
									
										
										
										
											2018-09-27 08:59:33 -04:00
										 |  |  | 					`..\test`:                      File{Data: "foo\n"}, | 
					
						
							|  |  |  | 					`..\..\foo\..\bar\..\xx\test2`: File{Data: "test2\n"}, | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 				}, | 
					
						
							|  |  |  | 			}, | 
					
						
							| 
									
										
										
										
											2018-09-14 20:55:30 -04:00
										 |  |  | 			ErrorsMay: map[string]map[string]struct{}{ | 
					
						
							|  |  |  | 				`/`: { | 
					
						
							|  |  |  | 					`invalid child node name ..\test`:                      struct{}{}, | 
					
						
							|  |  |  | 					`invalid child node name ..\..\foo\..\bar\..\xx\test2`: struct{}{}, | 
					
						
							|  |  |  | 				}, | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			Snapshot: Snapshot{ | 
					
						
							|  |  |  | 				Nodes: map[string]Node{ | 
					
						
							| 
									
										
										
										
											2018-09-27 08:59:33 -04:00
										 |  |  | 					`../test`:                      File{Data: "foo\n"}, | 
					
						
							|  |  |  | 					`../../foo/../bar/../xx/test2`: File{Data: "test2\n"}, | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 				}, | 
					
						
							|  |  |  | 			}, | 
					
						
							| 
									
										
										
										
											2018-09-14 20:55:30 -04:00
										 |  |  | 			ErrorsMay: map[string]map[string]struct{}{ | 
					
						
							|  |  |  | 				`/`: { | 
					
						
							|  |  |  | 					`invalid child node name ../test`:                      struct{}{}, | 
					
						
							|  |  |  | 					`invalid child node name ../../foo/../bar/../xx/test2`: struct{}{}, | 
					
						
							|  |  |  | 				}, | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			Snapshot: Snapshot{ | 
					
						
							|  |  |  | 				Nodes: map[string]Node{ | 
					
						
							| 
									
										
										
										
											2018-09-27 08:59:33 -04:00
										 |  |  | 					"top": File{Data: "toplevel file"}, | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 					"x": Dir{ | 
					
						
							|  |  |  | 						Nodes: map[string]Node{ | 
					
						
							| 
									
										
										
										
											2018-09-27 08:59:33 -04:00
										 |  |  | 							"file1": File{Data: "file1"}, | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 							"..": Dir{ | 
					
						
							|  |  |  | 								Nodes: map[string]Node{ | 
					
						
							| 
									
										
										
										
											2018-09-27 08:59:33 -04:00
										 |  |  | 									"file2": File{Data: "file2"}, | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 									"..": Dir{ | 
					
						
							|  |  |  | 										Nodes: map[string]Node{ | 
					
						
							| 
									
										
										
										
											2018-09-27 08:59:33 -04:00
										 |  |  | 											"file2": File{Data: "file2"}, | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 										}, | 
					
						
							|  |  |  | 									}, | 
					
						
							|  |  |  | 								}, | 
					
						
							|  |  |  | 							}, | 
					
						
							|  |  |  | 						}, | 
					
						
							|  |  |  | 					}, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			Files: map[string]string{ | 
					
						
							|  |  |  | 				"top": "toplevel file", | 
					
						
							|  |  |  | 			}, | 
					
						
							| 
									
										
										
										
											2018-09-14 20:55:30 -04:00
										 |  |  | 			ErrorsMust: map[string]map[string]struct{}{ | 
					
						
							|  |  |  | 				`/x`: { | 
					
						
							|  |  |  | 					`invalid child node name ..`: struct{}{}, | 
					
						
							|  |  |  | 				}, | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, test := range tests { | 
					
						
							|  |  |  | 		t.Run("", func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2022-12-11 10:41:22 +01:00
										 |  |  | 			repo := repository.TestRepository(t) | 
					
						
							| 
									
										
										
										
											2024-02-22 17:55:50 -07:00
										 |  |  | 			sn, id := saveSnapshot(t, repo, test.Snapshot, noopGetGenericAttributes) | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 			t.Logf("snapshot saved as %v", id.Str()) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-31 11:42:25 +02:00
										 |  |  | 			res := NewRestorer(repo, sn, Options{}) | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-09 13:42:33 +01:00
										 |  |  | 			tempdir := rtest.TempDir(t) | 
					
						
							| 
									
										
										
										
											2018-07-14 21:19:42 +02:00
										 |  |  | 			// make sure we're creating a new subdir of the tempdir | 
					
						
							|  |  |  | 			tempdir = filepath.Join(tempdir, "target") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-29 19:23:09 +02:00
										 |  |  | 			res.SelectFilter = func(item string, isDir bool) (selectedForRestore bool, childMayBeSelected bool) { | 
					
						
							|  |  |  | 				t.Logf("restore %v", item) | 
					
						
							| 
									
										
										
										
											2018-07-21 14:06:19 +02:00
										 |  |  | 				if test.Select != nil { | 
					
						
							| 
									
										
										
										
											2024-06-29 19:23:09 +02:00
										 |  |  | 					return test.Select(item, isDir) | 
					
						
							| 
									
										
										
										
											2018-07-21 14:06:19 +02:00
										 |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 				return true, true | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-14 20:55:30 -04:00
										 |  |  | 			errors := make(map[string]map[string]struct{}) | 
					
						
							|  |  |  | 			res.Error = func(location string, err error) error { | 
					
						
							| 
									
										
										
										
											2020-12-11 09:41:59 +01:00
										 |  |  | 				location = filepath.ToSlash(location) | 
					
						
							| 
									
										
										
										
											2018-09-14 20:55:30 -04:00
										 |  |  | 				t.Logf("restore returned error for %q: %v", location, err) | 
					
						
							|  |  |  | 				if errors[location] == nil { | 
					
						
							|  |  |  | 					errors[location] = make(map[string]struct{}) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				errors[location][err.Error()] = struct{}{} | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 				return nil | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			ctx, cancel := context.WithCancel(context.Background()) | 
					
						
							|  |  |  | 			defer cancel() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-03 14:48:14 +02:00
										 |  |  | 			err := res.RestoreTo(ctx, tempdir) | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				t.Fatal(err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-20 11:56:33 +01:00
										 |  |  | 			if len(test.ErrorsMust)+len(test.ErrorsMay) == 0 { | 
					
						
							|  |  |  | 				_, err = res.VerifyFiles(ctx, tempdir) | 
					
						
							|  |  |  | 				rtest.OK(t, err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-14 20:55:30 -04:00
										 |  |  | 			for location, expectedErrors := range test.ErrorsMust { | 
					
						
							|  |  |  | 				actualErrors, ok := errors[location] | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 				if !ok { | 
					
						
							| 
									
										
										
										
											2018-09-14 20:55:30 -04:00
										 |  |  | 					t.Errorf("expected error(s) for %v, found none", location) | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 					continue | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-14 20:55:30 -04:00
										 |  |  | 				rtest.Equals(t, expectedErrors, actualErrors) | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-14 20:55:30 -04:00
										 |  |  | 				delete(errors, location) | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-14 20:55:30 -04:00
										 |  |  | 			for location, expectedErrors := range test.ErrorsMay { | 
					
						
							|  |  |  | 				actualErrors, ok := errors[location] | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 				if !ok { | 
					
						
							|  |  |  | 					continue | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-14 20:55:30 -04:00
										 |  |  | 				rtest.Equals(t, expectedErrors, actualErrors) | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-14 20:55:30 -04:00
										 |  |  | 				delete(errors, location) | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			for filename, err := range errors { | 
					
						
							|  |  |  | 				t.Errorf("unexpected error for %v found: %v", filename, err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			for filename, content := range test.Files { | 
					
						
							| 
									
										
										
										
											2022-12-02 19:36:43 +01:00
										 |  |  | 				data, err := os.ReadFile(filepath.Join(tempdir, filepath.FromSlash(filename))) | 
					
						
							| 
									
										
										
										
											2017-11-25 20:36:32 +01:00
										 |  |  | 				if err != nil { | 
					
						
							|  |  |  | 					t.Errorf("unable to read file %v: %v", filename, err) | 
					
						
							|  |  |  | 					continue | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				if !bytes.Equal(data, []byte(content)) { | 
					
						
							|  |  |  | 					t.Errorf("file %v has wrong content: want %q, got %q", filename, content, data) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2017-11-26 18:36:48 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | func TestRestorerRelative(t *testing.T) { | 
					
						
							|  |  |  | 	var tests = []struct { | 
					
						
							|  |  |  | 		Snapshot | 
					
						
							|  |  |  | 		Files map[string]string | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			Snapshot: Snapshot{ | 
					
						
							|  |  |  | 				Nodes: map[string]Node{ | 
					
						
							| 
									
										
										
										
											2018-09-27 08:59:33 -04:00
										 |  |  | 					"foo": File{Data: "content: foo\n"}, | 
					
						
							| 
									
										
										
										
											2017-11-26 18:36:48 +01:00
										 |  |  | 					"dirtest": Dir{ | 
					
						
							|  |  |  | 						Nodes: map[string]Node{ | 
					
						
							| 
									
										
										
										
											2018-09-27 08:59:33 -04:00
										 |  |  | 							"file": File{Data: "content: file\n"}, | 
					
						
							| 
									
										
										
										
											2017-11-26 18:36:48 +01:00
										 |  |  | 						}, | 
					
						
							|  |  |  | 					}, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			Files: map[string]string{ | 
					
						
							|  |  |  | 				"foo":          "content: foo\n", | 
					
						
							|  |  |  | 				"dirtest/file": "content: file\n", | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, test := range tests { | 
					
						
							|  |  |  | 		t.Run("", func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2022-12-11 10:41:22 +01:00
										 |  |  | 			repo := repository.TestRepository(t) | 
					
						
							| 
									
										
										
										
											2017-11-26 18:36:48 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-22 17:55:50 -07:00
										 |  |  | 			sn, id := saveSnapshot(t, repo, test.Snapshot, noopGetGenericAttributes) | 
					
						
							| 
									
										
										
										
											2017-11-26 18:36:48 +01:00
										 |  |  | 			t.Logf("snapshot saved as %v", id.Str()) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-31 11:42:25 +02:00
										 |  |  | 			res := NewRestorer(repo, sn, Options{}) | 
					
						
							| 
									
										
										
										
											2017-11-26 18:36:48 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-09 13:42:33 +01:00
										 |  |  | 			tempdir := rtest.TempDir(t) | 
					
						
							| 
									
										
										
										
											2022-12-11 10:41:22 +01:00
										 |  |  | 			cleanup := rtest.Chdir(t, tempdir) | 
					
						
							| 
									
										
										
										
											2017-11-26 18:36:48 +01:00
										 |  |  | 			defer cleanup() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			errors := make(map[string]string) | 
					
						
							| 
									
										
										
										
											2018-09-14 20:55:30 -04:00
										 |  |  | 			res.Error = func(location string, err error) error { | 
					
						
							|  |  |  | 				t.Logf("restore returned error for %q: %v", location, err) | 
					
						
							|  |  |  | 				errors[location] = err.Error() | 
					
						
							| 
									
										
										
										
											2017-11-26 18:36:48 +01:00
										 |  |  | 				return nil | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			ctx, cancel := context.WithCancel(context.Background()) | 
					
						
							|  |  |  | 			defer cancel() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-03 14:48:14 +02:00
										 |  |  | 			err := res.RestoreTo(ctx, "restore") | 
					
						
							| 
									
										
										
										
											2017-11-26 18:36:48 +01:00
										 |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				t.Fatal(err) | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2020-02-20 11:56:33 +01:00
										 |  |  | 			nverified, err := res.VerifyFiles(ctx, "restore") | 
					
						
							|  |  |  | 			rtest.OK(t, err) | 
					
						
							|  |  |  | 			rtest.Equals(t, len(test.Files), nverified) | 
					
						
							| 
									
										
										
										
											2017-11-26 18:36:48 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			for filename, err := range errors { | 
					
						
							|  |  |  | 				t.Errorf("unexpected error for %v found: %v", filename, err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			for filename, content := range test.Files { | 
					
						
							| 
									
										
										
										
											2022-12-02 19:36:43 +01:00
										 |  |  | 				data, err := os.ReadFile(filepath.Join(tempdir, "restore", filepath.FromSlash(filename))) | 
					
						
							| 
									
										
										
										
											2017-11-26 18:36:48 +01:00
										 |  |  | 				if err != nil { | 
					
						
							|  |  |  | 					t.Errorf("unable to read file %v: %v", filename, err) | 
					
						
							|  |  |  | 					continue | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				if !bytes.Equal(data, []byte(content)) { | 
					
						
							|  |  |  | 					t.Errorf("file %v has wrong content: want %q, got %q", filename, content, data) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2018-07-21 21:55:57 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | type TraverseTreeCheck func(testing.TB) treeVisitor | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type TreeVisit struct { | 
					
						
							| 
									
										
										
										
											2024-06-29 18:58:17 +02:00
										 |  |  | 	funcName string   // name of the function | 
					
						
							|  |  |  | 	location string   // location passed to the function | 
					
						
							|  |  |  | 	files    []string // file list passed to the function | 
					
						
							| 
									
										
										
										
											2018-07-21 21:55:57 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func checkVisitOrder(list []TreeVisit) TraverseTreeCheck { | 
					
						
							|  |  |  | 	var pos int | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return func(t testing.TB) treeVisitor { | 
					
						
							| 
									
										
										
										
											2024-06-29 18:58:17 +02:00
										 |  |  | 		check := func(funcName string) func(*restic.Node, string, string, []string) error { | 
					
						
							|  |  |  | 			return func(node *restic.Node, target, location string, expectedFilenames []string) error { | 
					
						
							| 
									
										
										
										
											2018-07-21 21:55:57 +02:00
										 |  |  | 				if pos >= len(list) { | 
					
						
							|  |  |  | 					t.Errorf("step %v, %v(%v): expected no more than %d function calls", pos, funcName, location, len(list)) | 
					
						
							|  |  |  | 					pos++ | 
					
						
							|  |  |  | 					return nil | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				v := list[pos] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				if v.funcName != funcName { | 
					
						
							|  |  |  | 					t.Errorf("step %v, location %v: want function %v, but %v was called", | 
					
						
							|  |  |  | 						pos, location, v.funcName, funcName) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				if location != filepath.FromSlash(v.location) { | 
					
						
							|  |  |  | 					t.Errorf("step %v: want location %v, got %v", pos, list[pos].location, location) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-29 18:58:17 +02:00
										 |  |  | 				if !reflect.DeepEqual(expectedFilenames, v.files) { | 
					
						
							|  |  |  | 					t.Errorf("step %v: want files %v, got %v", pos, list[pos].files, expectedFilenames) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-21 21:55:57 +02:00
										 |  |  | 				pos++ | 
					
						
							|  |  |  | 				return nil | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-06-29 18:58:17 +02:00
										 |  |  | 		checkNoFilename := func(funcName string) func(*restic.Node, string, string) error { | 
					
						
							|  |  |  | 			f := check(funcName) | 
					
						
							|  |  |  | 			return func(node *restic.Node, target, location string) error { | 
					
						
							|  |  |  | 				return f(node, target, location, nil) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2018-07-21 21:55:57 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		return treeVisitor{ | 
					
						
							| 
									
										
										
										
											2024-06-29 18:58:17 +02:00
										 |  |  | 			enterDir:  checkNoFilename("enterDir"), | 
					
						
							|  |  |  | 			visitNode: checkNoFilename("visitNode"), | 
					
						
							| 
									
										
										
										
											2018-07-21 21:55:57 +02:00
										 |  |  | 			leaveDir:  check("leaveDir"), | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestRestorerTraverseTree(t *testing.T) { | 
					
						
							|  |  |  | 	var tests = []struct { | 
					
						
							|  |  |  | 		Snapshot | 
					
						
							| 
									
										
										
										
											2024-06-29 19:23:09 +02:00
										 |  |  | 		Select  func(item string, isDir bool) (selectForRestore bool, childMayBeSelected bool) | 
					
						
							| 
									
										
										
										
											2018-07-21 21:55:57 +02:00
										 |  |  | 		Visitor TraverseTreeCheck | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			// select everything | 
					
						
							|  |  |  | 			Snapshot: Snapshot{ | 
					
						
							|  |  |  | 				Nodes: map[string]Node{ | 
					
						
							|  |  |  | 					"dir": Dir{Nodes: map[string]Node{ | 
					
						
							| 
									
										
										
										
											2018-09-27 08:59:33 -04:00
										 |  |  | 						"otherfile": File{Data: "x"}, | 
					
						
							| 
									
										
										
										
											2018-07-21 21:55:57 +02:00
										 |  |  | 						"subdir": Dir{Nodes: map[string]Node{ | 
					
						
							| 
									
										
										
										
											2018-09-27 08:59:33 -04:00
										 |  |  | 							"file": File{Data: "content: file\n"}, | 
					
						
							| 
									
										
										
										
											2018-07-21 21:55:57 +02:00
										 |  |  | 						}}, | 
					
						
							|  |  |  | 					}}, | 
					
						
							| 
									
										
										
										
											2018-09-27 08:59:33 -04:00
										 |  |  | 					"foo": File{Data: "content: foo\n"}, | 
					
						
							| 
									
										
										
										
											2018-07-21 21:55:57 +02:00
										 |  |  | 				}, | 
					
						
							|  |  |  | 			}, | 
					
						
							| 
									
										
										
										
											2024-06-29 19:23:09 +02:00
										 |  |  | 			Select: func(item string, isDir bool) (selectForRestore bool, childMayBeSelected bool) { | 
					
						
							| 
									
										
										
										
											2018-07-21 21:55:57 +02:00
										 |  |  | 				return true, true | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			Visitor: checkVisitOrder([]TreeVisit{ | 
					
						
							| 
									
										
										
										
											2024-06-29 18:58:17 +02:00
										 |  |  | 				{"enterDir", "/", nil}, | 
					
						
							|  |  |  | 				{"enterDir", "/dir", nil}, | 
					
						
							|  |  |  | 				{"visitNode", "/dir/otherfile", nil}, | 
					
						
							|  |  |  | 				{"enterDir", "/dir/subdir", nil}, | 
					
						
							|  |  |  | 				{"visitNode", "/dir/subdir/file", nil}, | 
					
						
							|  |  |  | 				{"leaveDir", "/dir/subdir", []string{"file"}}, | 
					
						
							|  |  |  | 				{"leaveDir", "/dir", []string{"otherfile", "subdir"}}, | 
					
						
							|  |  |  | 				{"visitNode", "/foo", nil}, | 
					
						
							|  |  |  | 				{"leaveDir", "/", []string{"dir", "foo"}}, | 
					
						
							| 
									
										
										
										
											2018-07-21 21:55:57 +02:00
										 |  |  | 			}), | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// select only the top-level file | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			Snapshot: Snapshot{ | 
					
						
							|  |  |  | 				Nodes: map[string]Node{ | 
					
						
							|  |  |  | 					"dir": Dir{Nodes: map[string]Node{ | 
					
						
							| 
									
										
										
										
											2018-09-27 08:59:33 -04:00
										 |  |  | 						"otherfile": File{Data: "x"}, | 
					
						
							| 
									
										
										
										
											2018-07-21 21:55:57 +02:00
										 |  |  | 						"subdir": Dir{Nodes: map[string]Node{ | 
					
						
							| 
									
										
										
										
											2018-09-27 08:59:33 -04:00
										 |  |  | 							"file": File{Data: "content: file\n"}, | 
					
						
							| 
									
										
										
										
											2018-07-21 21:55:57 +02:00
										 |  |  | 						}}, | 
					
						
							|  |  |  | 					}}, | 
					
						
							| 
									
										
										
										
											2018-09-27 08:59:33 -04:00
										 |  |  | 					"foo": File{Data: "content: foo\n"}, | 
					
						
							| 
									
										
										
										
											2018-07-21 21:55:57 +02:00
										 |  |  | 				}, | 
					
						
							|  |  |  | 			}, | 
					
						
							| 
									
										
										
										
											2024-06-29 19:23:09 +02:00
										 |  |  | 			Select: func(item string, isDir bool) (selectForRestore bool, childMayBeSelected bool) { | 
					
						
							| 
									
										
										
										
											2018-07-21 21:55:57 +02:00
										 |  |  | 				if item == "/foo" { | 
					
						
							|  |  |  | 					return true, false | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				return false, false | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			Visitor: checkVisitOrder([]TreeVisit{ | 
					
						
							| 
									
										
										
										
											2024-06-29 18:58:17 +02:00
										 |  |  | 				{"enterDir", "/", nil}, | 
					
						
							|  |  |  | 				{"visitNode", "/foo", nil}, | 
					
						
							|  |  |  | 				{"leaveDir", "/", []string{"dir", "foo"}}, | 
					
						
							| 
									
										
										
										
											2018-07-21 21:55:57 +02:00
										 |  |  | 			}), | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			Snapshot: Snapshot{ | 
					
						
							|  |  |  | 				Nodes: map[string]Node{ | 
					
						
							| 
									
										
										
										
											2018-09-27 08:59:33 -04:00
										 |  |  | 					"aaa": File{Data: "content: foo\n"}, | 
					
						
							| 
									
										
										
										
											2018-07-21 21:55:57 +02:00
										 |  |  | 					"dir": Dir{Nodes: map[string]Node{ | 
					
						
							| 
									
										
										
										
											2018-09-27 08:59:33 -04:00
										 |  |  | 						"otherfile": File{Data: "x"}, | 
					
						
							| 
									
										
										
										
											2018-07-21 21:55:57 +02:00
										 |  |  | 						"subdir": Dir{Nodes: map[string]Node{ | 
					
						
							| 
									
										
										
										
											2018-09-27 08:59:33 -04:00
										 |  |  | 							"file": File{Data: "content: file\n"}, | 
					
						
							| 
									
										
										
										
											2018-07-21 21:55:57 +02:00
										 |  |  | 						}}, | 
					
						
							|  |  |  | 					}}, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			}, | 
					
						
							| 
									
										
										
										
											2024-06-29 19:23:09 +02:00
										 |  |  | 			Select: func(item string, isDir bool) (selectForRestore bool, childMayBeSelected bool) { | 
					
						
							| 
									
										
										
										
											2018-07-21 21:55:57 +02:00
										 |  |  | 				if item == "/aaa" { | 
					
						
							|  |  |  | 					return true, false | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				return false, false | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			Visitor: checkVisitOrder([]TreeVisit{ | 
					
						
							| 
									
										
										
										
											2024-06-29 18:58:17 +02:00
										 |  |  | 				{"enterDir", "/", nil}, | 
					
						
							|  |  |  | 				{"visitNode", "/aaa", nil}, | 
					
						
							|  |  |  | 				{"leaveDir", "/", []string{"aaa", "dir"}}, | 
					
						
							| 
									
										
										
										
											2018-07-21 21:55:57 +02:00
										 |  |  | 			}), | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// select dir/ | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			Snapshot: Snapshot{ | 
					
						
							|  |  |  | 				Nodes: map[string]Node{ | 
					
						
							|  |  |  | 					"dir": Dir{Nodes: map[string]Node{ | 
					
						
							| 
									
										
										
										
											2018-09-27 08:59:33 -04:00
										 |  |  | 						"otherfile": File{Data: "x"}, | 
					
						
							| 
									
										
										
										
											2018-07-21 21:55:57 +02:00
										 |  |  | 						"subdir": Dir{Nodes: map[string]Node{ | 
					
						
							| 
									
										
										
										
											2018-09-27 08:59:33 -04:00
										 |  |  | 							"file": File{Data: "content: file\n"}, | 
					
						
							| 
									
										
										
										
											2018-07-21 21:55:57 +02:00
										 |  |  | 						}}, | 
					
						
							|  |  |  | 					}}, | 
					
						
							| 
									
										
										
										
											2018-09-27 08:59:33 -04:00
										 |  |  | 					"foo": File{Data: "content: foo\n"}, | 
					
						
							| 
									
										
										
										
											2018-07-21 21:55:57 +02:00
										 |  |  | 				}, | 
					
						
							|  |  |  | 			}, | 
					
						
							| 
									
										
										
										
											2024-06-29 19:23:09 +02:00
										 |  |  | 			Select: func(item string, isDir bool) (selectForRestore bool, childMayBeSelected bool) { | 
					
						
							| 
									
										
										
										
											2018-07-21 21:55:57 +02:00
										 |  |  | 				if strings.HasPrefix(item, "/dir") { | 
					
						
							|  |  |  | 					return true, true | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				return false, false | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			Visitor: checkVisitOrder([]TreeVisit{ | 
					
						
							| 
									
										
										
										
											2024-06-29 18:58:17 +02:00
										 |  |  | 				{"enterDir", "/", nil}, | 
					
						
							|  |  |  | 				{"enterDir", "/dir", nil}, | 
					
						
							|  |  |  | 				{"visitNode", "/dir/otherfile", nil}, | 
					
						
							|  |  |  | 				{"enterDir", "/dir/subdir", nil}, | 
					
						
							|  |  |  | 				{"visitNode", "/dir/subdir/file", nil}, | 
					
						
							|  |  |  | 				{"leaveDir", "/dir/subdir", []string{"file"}}, | 
					
						
							|  |  |  | 				{"leaveDir", "/dir", []string{"otherfile", "subdir"}}, | 
					
						
							|  |  |  | 				{"leaveDir", "/", []string{"dir", "foo"}}, | 
					
						
							| 
									
										
										
										
											2018-07-21 21:55:57 +02:00
										 |  |  | 			}), | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// select only dir/otherfile | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			Snapshot: Snapshot{ | 
					
						
							|  |  |  | 				Nodes: map[string]Node{ | 
					
						
							|  |  |  | 					"dir": Dir{Nodes: map[string]Node{ | 
					
						
							| 
									
										
										
										
											2018-09-27 08:59:33 -04:00
										 |  |  | 						"otherfile": File{Data: "x"}, | 
					
						
							| 
									
										
										
										
											2018-07-21 21:55:57 +02:00
										 |  |  | 						"subdir": Dir{Nodes: map[string]Node{ | 
					
						
							| 
									
										
										
										
											2018-09-27 08:59:33 -04:00
										 |  |  | 							"file": File{Data: "content: file\n"}, | 
					
						
							| 
									
										
										
										
											2018-07-21 21:55:57 +02:00
										 |  |  | 						}}, | 
					
						
							|  |  |  | 					}}, | 
					
						
							| 
									
										
										
										
											2018-09-27 08:59:33 -04:00
										 |  |  | 					"foo": File{Data: "content: foo\n"}, | 
					
						
							| 
									
										
										
										
											2018-07-21 21:55:57 +02:00
										 |  |  | 				}, | 
					
						
							|  |  |  | 			}, | 
					
						
							| 
									
										
										
										
											2024-06-29 19:23:09 +02:00
										 |  |  | 			Select: func(item string, isDir bool) (selectForRestore bool, childMayBeSelected bool) { | 
					
						
							| 
									
										
										
										
											2018-07-21 21:55:57 +02:00
										 |  |  | 				switch item { | 
					
						
							|  |  |  | 				case "/dir": | 
					
						
							|  |  |  | 					return false, true | 
					
						
							|  |  |  | 				case "/dir/otherfile": | 
					
						
							|  |  |  | 					return true, false | 
					
						
							|  |  |  | 				default: | 
					
						
							|  |  |  | 					return false, false | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			Visitor: checkVisitOrder([]TreeVisit{ | 
					
						
							| 
									
										
										
										
											2024-06-29 18:58:17 +02:00
										 |  |  | 				{"enterDir", "/", nil}, | 
					
						
							|  |  |  | 				{"visitNode", "/dir/otherfile", nil}, | 
					
						
							|  |  |  | 				{"leaveDir", "/dir", []string{"otherfile", "subdir"}}, | 
					
						
							|  |  |  | 				{"leaveDir", "/", []string{"dir", "foo"}}, | 
					
						
							| 
									
										
										
										
											2018-07-21 21:55:57 +02:00
										 |  |  | 			}), | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, test := range tests { | 
					
						
							|  |  |  | 		t.Run("", func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2022-12-11 10:41:22 +01:00
										 |  |  | 			repo := repository.TestRepository(t) | 
					
						
							| 
									
										
										
										
											2024-02-22 17:55:50 -07:00
										 |  |  | 			sn, _ := saveSnapshot(t, repo, test.Snapshot, noopGetGenericAttributes) | 
					
						
							| 
									
										
										
										
											2018-07-21 21:55:57 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-29 18:58:17 +02:00
										 |  |  | 			// set Delete option to enable tracking filenames in a directory | 
					
						
							|  |  |  | 			res := NewRestorer(repo, sn, Options{Delete: true}) | 
					
						
							| 
									
										
										
										
											2018-07-21 21:55:57 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			res.SelectFilter = test.Select | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-09 13:42:33 +01:00
										 |  |  | 			tempdir := rtest.TempDir(t) | 
					
						
							| 
									
										
										
										
											2018-07-21 21:55:57 +02:00
										 |  |  | 			ctx, cancel := context.WithCancel(context.Background()) | 
					
						
							|  |  |  | 			defer cancel() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// make sure we're creating a new subdir of the tempdir | 
					
						
							|  |  |  | 			target := filepath.Join(tempdir, "target") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-29 18:58:17 +02:00
										 |  |  | 			err := res.traverseTree(ctx, target, *sn.Tree, test.Visitor(t)) | 
					
						
							| 
									
										
										
										
											2018-07-21 21:55:57 +02:00
										 |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				t.Fatal(err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2020-08-28 23:29:33 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | func normalizeFileMode(mode os.FileMode) os.FileMode { | 
					
						
							|  |  |  | 	if runtime.GOOS == "windows" { | 
					
						
							|  |  |  | 		if mode.IsDir() { | 
					
						
							|  |  |  | 			return 0555 | os.ModeDir | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return os.FileMode(0444) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return mode | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func checkConsistentInfo(t testing.TB, file string, fi os.FileInfo, modtime time.Time, mode os.FileMode) { | 
					
						
							|  |  |  | 	if fi.Mode() != mode { | 
					
						
							|  |  |  | 		t.Errorf("checking %q, Mode() returned wrong value, want 0%o, got 0%o", file, mode, fi.Mode()) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if !fi.ModTime().Equal(modtime) { | 
					
						
							|  |  |  | 		t.Errorf("checking %s, ModTime() returned wrong value, want %v, got %v", file, modtime, fi.ModTime()) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // test inspired from test case https://github.com/restic/restic/issues/1212 | 
					
						
							|  |  |  | func TestRestorerConsistentTimestampsAndPermissions(t *testing.T) { | 
					
						
							|  |  |  | 	timeForTest := time.Date(2019, time.January, 9, 1, 46, 40, 0, time.UTC) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-11 10:41:22 +01:00
										 |  |  | 	repo := repository.TestRepository(t) | 
					
						
							| 
									
										
										
										
											2020-08-28 23:29:33 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-03 14:48:14 +02:00
										 |  |  | 	sn, _ := saveSnapshot(t, repo, Snapshot{ | 
					
						
							| 
									
										
										
										
											2020-08-28 23:29:33 +02:00
										 |  |  | 		Nodes: map[string]Node{ | 
					
						
							|  |  |  | 			"dir": Dir{ | 
					
						
							|  |  |  | 				Mode:    normalizeFileMode(0750 | os.ModeDir), | 
					
						
							|  |  |  | 				ModTime: timeForTest, | 
					
						
							|  |  |  | 				Nodes: map[string]Node{ | 
					
						
							|  |  |  | 					"file1": File{ | 
					
						
							|  |  |  | 						Mode:    normalizeFileMode(os.FileMode(0700)), | 
					
						
							|  |  |  | 						ModTime: timeForTest, | 
					
						
							|  |  |  | 						Data:    "content: file\n", | 
					
						
							|  |  |  | 					}, | 
					
						
							|  |  |  | 					"anotherfile": File{ | 
					
						
							|  |  |  | 						Data: "content: file\n", | 
					
						
							|  |  |  | 					}, | 
					
						
							|  |  |  | 					"subdir": Dir{ | 
					
						
							|  |  |  | 						Mode:    normalizeFileMode(0700 | os.ModeDir), | 
					
						
							|  |  |  | 						ModTime: timeForTest, | 
					
						
							|  |  |  | 						Nodes: map[string]Node{ | 
					
						
							|  |  |  | 							"file2": File{ | 
					
						
							|  |  |  | 								Mode:    normalizeFileMode(os.FileMode(0666)), | 
					
						
							|  |  |  | 								ModTime: timeForTest, | 
					
						
							|  |  |  | 								Links:   2, | 
					
						
							|  |  |  | 								Inode:   1, | 
					
						
							|  |  |  | 							}, | 
					
						
							|  |  |  | 						}, | 
					
						
							|  |  |  | 					}, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2024-02-22 17:55:50 -07:00
										 |  |  | 	}, noopGetGenericAttributes) | 
					
						
							| 
									
										
										
										
											2020-08-28 23:29:33 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-31 11:42:25 +02:00
										 |  |  | 	res := NewRestorer(repo, sn, Options{}) | 
					
						
							| 
									
										
										
										
											2020-08-28 23:29:33 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-29 19:23:09 +02:00
										 |  |  | 	res.SelectFilter = func(item string, isDir bool) (selectedForRestore bool, childMayBeSelected bool) { | 
					
						
							| 
									
										
										
										
											2020-08-28 23:29:33 +02:00
										 |  |  | 		switch filepath.ToSlash(item) { | 
					
						
							|  |  |  | 		case "/dir": | 
					
						
							|  |  |  | 			childMayBeSelected = true | 
					
						
							|  |  |  | 		case "/dir/file1": | 
					
						
							|  |  |  | 			selectedForRestore = true | 
					
						
							|  |  |  | 			childMayBeSelected = false | 
					
						
							|  |  |  | 		case "/dir/subdir": | 
					
						
							|  |  |  | 			selectedForRestore = true | 
					
						
							|  |  |  | 			childMayBeSelected = true | 
					
						
							|  |  |  | 		case "/dir/subdir/file2": | 
					
						
							|  |  |  | 			selectedForRestore = true | 
					
						
							|  |  |  | 			childMayBeSelected = false | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return selectedForRestore, childMayBeSelected | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-09 13:42:33 +01:00
										 |  |  | 	tempdir := rtest.TempDir(t) | 
					
						
							| 
									
										
										
										
											2020-08-28 23:29:33 +02:00
										 |  |  | 	ctx, cancel := context.WithCancel(context.Background()) | 
					
						
							|  |  |  | 	defer cancel() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-03 14:48:14 +02:00
										 |  |  | 	err := res.RestoreTo(ctx, tempdir) | 
					
						
							| 
									
										
										
										
											2020-08-28 23:29:33 +02:00
										 |  |  | 	rtest.OK(t, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var testPatterns = []struct { | 
					
						
							|  |  |  | 		path    string | 
					
						
							|  |  |  | 		modtime time.Time | 
					
						
							|  |  |  | 		mode    os.FileMode | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		{"dir", timeForTest, normalizeFileMode(0750 | os.ModeDir)}, | 
					
						
							|  |  |  | 		{filepath.Join("dir", "file1"), timeForTest, normalizeFileMode(os.FileMode(0700))}, | 
					
						
							|  |  |  | 		{filepath.Join("dir", "subdir"), timeForTest, normalizeFileMode(0700 | os.ModeDir)}, | 
					
						
							|  |  |  | 		{filepath.Join("dir", "subdir", "file2"), timeForTest, normalizeFileMode(os.FileMode(0666))}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, test := range testPatterns { | 
					
						
							|  |  |  | 		f, err := os.Stat(filepath.Join(tempdir, test.path)) | 
					
						
							|  |  |  | 		rtest.OK(t, err) | 
					
						
							|  |  |  | 		checkConsistentInfo(t, test.path, f, test.modtime, test.mode) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2020-03-16 10:05:59 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-06 13:11:55 +01:00
										 |  |  | // VerifyFiles must not report cancellation of its context through res.Error. | 
					
						
							| 
									
										
										
										
											2020-03-16 10:05:59 +01:00
										 |  |  | func TestVerifyCancel(t *testing.T) { | 
					
						
							|  |  |  | 	snapshot := Snapshot{ | 
					
						
							|  |  |  | 		Nodes: map[string]Node{ | 
					
						
							|  |  |  | 			"foo": File{Data: "content: foo\n"}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-11 10:41:22 +01:00
										 |  |  | 	repo := repository.TestRepository(t) | 
					
						
							| 
									
										
										
										
											2024-02-22 17:55:50 -07:00
										 |  |  | 	sn, _ := saveSnapshot(t, repo, snapshot, noopGetGenericAttributes) | 
					
						
							| 
									
										
										
										
											2020-03-16 10:05:59 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-31 11:42:25 +02:00
										 |  |  | 	res := NewRestorer(repo, sn, Options{}) | 
					
						
							| 
									
										
										
										
											2020-03-16 10:05:59 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-09 13:42:33 +01:00
										 |  |  | 	tempdir := rtest.TempDir(t) | 
					
						
							| 
									
										
										
										
											2020-03-16 10:05:59 +01:00
										 |  |  | 	ctx, cancel := context.WithCancel(context.Background()) | 
					
						
							|  |  |  | 	defer cancel() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	rtest.OK(t, res.RestoreTo(ctx, tempdir)) | 
					
						
							| 
									
										
										
										
											2022-12-02 19:36:43 +01:00
										 |  |  | 	err := os.WriteFile(filepath.Join(tempdir, "foo"), []byte("bar"), 0644) | 
					
						
							| 
									
										
										
										
											2020-03-16 10:05:59 +01:00
										 |  |  | 	rtest.OK(t, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var errs []error | 
					
						
							|  |  |  | 	res.Error = func(filename string, err error) error { | 
					
						
							|  |  |  | 		errs = append(errs, err) | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	nverified, err := res.VerifyFiles(ctx, tempdir) | 
					
						
							|  |  |  | 	rtest.Equals(t, 0, nverified) | 
					
						
							|  |  |  | 	rtest.Assert(t, err != nil, "nil error from VerifyFiles") | 
					
						
							| 
									
										
										
										
											2021-09-19 13:21:57 +02:00
										 |  |  | 	rtest.Equals(t, 1, len(errs)) | 
					
						
							|  |  |  | 	rtest.Assert(t, strings.Contains(errs[0].Error(), "Invalid file size for"), "wrong error %q", errs[0].Error()) | 
					
						
							| 
									
										
										
										
											2020-03-16 10:05:59 +01:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2022-09-04 11:23:31 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | func TestRestorerSparseFiles(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2022-12-11 10:41:22 +01:00
										 |  |  | 	repo := repository.TestRepository(t) | 
					
						
							| 
									
										
										
										
											2022-09-04 11:23:31 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	var zeros [1<<20 + 13]byte | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	target := &fs.Reader{ | 
					
						
							|  |  |  | 		Mode:       0600, | 
					
						
							|  |  |  | 		Name:       "/zeros", | 
					
						
							| 
									
										
										
										
											2022-12-02 19:36:43 +01:00
										 |  |  | 		ReadCloser: io.NopCloser(bytes.NewReader(zeros[:])), | 
					
						
							| 
									
										
										
										
											2022-09-04 11:23:31 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	sc := archiver.NewScanner(target) | 
					
						
							|  |  |  | 	err := sc.Scan(context.TODO(), []string{"/zeros"}) | 
					
						
							|  |  |  | 	rtest.OK(t, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	arch := archiver.New(repo, target, archiver.Options{}) | 
					
						
							| 
									
										
										
										
											2024-02-22 22:14:48 +01:00
										 |  |  | 	sn, _, _, err := arch.Snapshot(context.Background(), []string{"/zeros"}, | 
					
						
							| 
									
										
										
										
											2022-09-04 11:23:31 +02:00
										 |  |  | 		archiver.SnapshotOptions{}) | 
					
						
							|  |  |  | 	rtest.OK(t, err) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-31 11:42:25 +02:00
										 |  |  | 	res := NewRestorer(repo, sn, Options{Sparse: true}) | 
					
						
							| 
									
										
										
										
											2022-09-04 11:23:31 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-09 13:42:33 +01:00
										 |  |  | 	tempdir := rtest.TempDir(t) | 
					
						
							| 
									
										
										
										
											2022-09-04 11:23:31 +02:00
										 |  |  | 	ctx, cancel := context.WithCancel(context.Background()) | 
					
						
							|  |  |  | 	defer cancel() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	err = res.RestoreTo(ctx, tempdir) | 
					
						
							|  |  |  | 	rtest.OK(t, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	filename := filepath.Join(tempdir, "zeros") | 
					
						
							| 
									
										
										
										
											2022-12-02 19:36:43 +01:00
										 |  |  | 	content, err := os.ReadFile(filename) | 
					
						
							| 
									
										
										
										
											2022-09-04 11:23:31 +02:00
										 |  |  | 	rtest.OK(t, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	rtest.Equals(t, len(zeros[:]), len(content)) | 
					
						
							|  |  |  | 	rtest.Equals(t, zeros[:], content) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	blocks := getBlockCount(t, filename) | 
					
						
							|  |  |  | 	if blocks < 0 { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// st.Blocks is the size in 512-byte blocks. | 
					
						
							|  |  |  | 	denseBlocks := math.Ceil(float64(len(zeros)) / 512) | 
					
						
							|  |  |  | 	sparsity := 1 - float64(blocks)/denseBlocks | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// This should report 100% sparse. We don't assert that, | 
					
						
							|  |  |  | 	// as the behavior of sparse writes depends on the underlying | 
					
						
							|  |  |  | 	// file system as well as the OS. | 
					
						
							|  |  |  | 	t.Logf("wrote %d zeros as %d blocks, %.1f%% sparse", | 
					
						
							|  |  |  | 		len(zeros), blocks, 100*sparsity) | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2024-05-31 15:30:30 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-07 22:43:31 +02:00
										 |  |  | func saveSnapshotsAndOverwrite(t *testing.T, baseSnapshot Snapshot, overwriteSnapshot Snapshot, options Options) string { | 
					
						
							|  |  |  | 	repo := repository.TestRepository(t) | 
					
						
							|  |  |  | 	tempdir := filepath.Join(rtest.TempDir(t), "target") | 
					
						
							|  |  |  | 	ctx, cancel := context.WithCancel(context.Background()) | 
					
						
							|  |  |  | 	defer cancel() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// base snapshot | 
					
						
							|  |  |  | 	sn, id := saveSnapshot(t, repo, baseSnapshot, noopGetGenericAttributes) | 
					
						
							|  |  |  | 	t.Logf("base snapshot saved as %v", id.Str()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	res := NewRestorer(repo, sn, options) | 
					
						
							|  |  |  | 	rtest.OK(t, res.RestoreTo(ctx, tempdir)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// overwrite snapshot | 
					
						
							|  |  |  | 	sn, id = saveSnapshot(t, repo, overwriteSnapshot, noopGetGenericAttributes) | 
					
						
							|  |  |  | 	t.Logf("overwrite snapshot saved as %v", id.Str()) | 
					
						
							|  |  |  | 	res = NewRestorer(repo, sn, options) | 
					
						
							|  |  |  | 	rtest.OK(t, res.RestoreTo(ctx, tempdir)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	_, err := res.VerifyFiles(ctx, tempdir) | 
					
						
							|  |  |  | 	rtest.OK(t, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return tempdir | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-05 22:01:55 +02:00
										 |  |  | func TestRestorerSparseOverwrite(t *testing.T) { | 
					
						
							|  |  |  | 	baseSnapshot := Snapshot{ | 
					
						
							|  |  |  | 		Nodes: map[string]Node{ | 
					
						
							|  |  |  | 			"foo": File{Data: "content: new\n"}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	var zero [14]byte | 
					
						
							|  |  |  | 	sparseSnapshot := Snapshot{ | 
					
						
							|  |  |  | 		Nodes: map[string]Node{ | 
					
						
							|  |  |  | 			"foo": File{Data: string(zero[:])}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-07 22:43:31 +02:00
										 |  |  | 	saveSnapshotsAndOverwrite(t, baseSnapshot, sparseSnapshot, Options{Sparse: true, Overwrite: OverwriteAlways}) | 
					
						
							| 
									
										
										
										
											2024-06-05 22:01:55 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-31 15:30:30 +02:00
										 |  |  | func TestRestorerOverwriteBehavior(t *testing.T) { | 
					
						
							|  |  |  | 	baseTime := time.Now() | 
					
						
							|  |  |  | 	baseSnapshot := Snapshot{ | 
					
						
							|  |  |  | 		Nodes: map[string]Node{ | 
					
						
							|  |  |  | 			"foo": File{Data: "content: foo\n", ModTime: baseTime}, | 
					
						
							|  |  |  | 			"dirtest": Dir{ | 
					
						
							|  |  |  | 				Nodes: map[string]Node{ | 
					
						
							|  |  |  | 					"file": File{Data: "content: file\n", ModTime: baseTime}, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 				ModTime: baseTime, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	overwriteSnapshot := Snapshot{ | 
					
						
							|  |  |  | 		Nodes: map[string]Node{ | 
					
						
							|  |  |  | 			"foo": File{Data: "content: new\n", ModTime: baseTime.Add(time.Second)}, | 
					
						
							|  |  |  | 			"dirtest": Dir{ | 
					
						
							|  |  |  | 				Nodes: map[string]Node{ | 
					
						
							|  |  |  | 					"file": File{Data: "content: file2\n", ModTime: baseTime.Add(-time.Second)}, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var tests = []struct { | 
					
						
							|  |  |  | 		Overwrite OverwriteBehavior | 
					
						
							|  |  |  | 		Files     map[string]string | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			Overwrite: OverwriteAlways, | 
					
						
							|  |  |  | 			Files: map[string]string{ | 
					
						
							|  |  |  | 				"foo":          "content: new\n", | 
					
						
							|  |  |  | 				"dirtest/file": "content: file2\n", | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2024-05-31 18:15:50 +02:00
										 |  |  | 		{ | 
					
						
							|  |  |  | 			Overwrite: OverwriteIfChanged, | 
					
						
							|  |  |  | 			Files: map[string]string{ | 
					
						
							|  |  |  | 				"foo":          "content: new\n", | 
					
						
							|  |  |  | 				"dirtest/file": "content: file2\n", | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2024-05-31 15:30:30 +02:00
										 |  |  | 		{ | 
					
						
							|  |  |  | 			Overwrite: OverwriteIfNewer, | 
					
						
							|  |  |  | 			Files: map[string]string{ | 
					
						
							|  |  |  | 				"foo":          "content: new\n", | 
					
						
							|  |  |  | 				"dirtest/file": "content: file\n", | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			Overwrite: OverwriteNever, | 
					
						
							|  |  |  | 			Files: map[string]string{ | 
					
						
							|  |  |  | 				"foo":          "content: foo\n", | 
					
						
							|  |  |  | 				"dirtest/file": "content: file\n", | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, test := range tests { | 
					
						
							|  |  |  | 		t.Run("", func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2024-06-07 22:43:31 +02:00
										 |  |  | 			tempdir := saveSnapshotsAndOverwrite(t, baseSnapshot, overwriteSnapshot, Options{Overwrite: test.Overwrite}) | 
					
						
							| 
									
										
										
										
											2024-05-31 15:30:30 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			for filename, content := range test.Files { | 
					
						
							|  |  |  | 				data, err := os.ReadFile(filepath.Join(tempdir, filepath.FromSlash(filename))) | 
					
						
							|  |  |  | 				if err != nil { | 
					
						
							|  |  |  | 					t.Errorf("unable to read file %v: %v", filename, err) | 
					
						
							|  |  |  | 					continue | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				if !bytes.Equal(data, []byte(content)) { | 
					
						
							|  |  |  | 					t.Errorf("file %v has wrong content: want %q, got %q", filename, content, data) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2024-05-31 18:15:50 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-07 22:44:47 +02:00
										 |  |  | func TestRestorerOverwriteSpecial(t *testing.T) { | 
					
						
							|  |  |  | 	baseTime := time.Now() | 
					
						
							|  |  |  | 	baseSnapshot := Snapshot{ | 
					
						
							|  |  |  | 		Nodes: map[string]Node{ | 
					
						
							|  |  |  | 			"dirtest":  Dir{ModTime: baseTime}, | 
					
						
							|  |  |  | 			"link":     Symlink{Target: "foo", ModTime: baseTime}, | 
					
						
							|  |  |  | 			"file":     File{Data: "content: file\n", Inode: 42, Links: 2, ModTime: baseTime}, | 
					
						
							|  |  |  | 			"hardlink": File{Data: "content: file\n", Inode: 42, Links: 2, ModTime: baseTime}, | 
					
						
							| 
									
										
										
										
											2024-06-07 23:02:46 +02:00
										 |  |  | 			"newdir":   File{Data: "content: dir\n", ModTime: baseTime}, | 
					
						
							| 
									
										
										
										
											2024-06-07 22:44:47 +02:00
										 |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	overwriteSnapshot := Snapshot{ | 
					
						
							|  |  |  | 		Nodes: map[string]Node{ | 
					
						
							|  |  |  | 			"dirtest":  Symlink{Target: "foo", ModTime: baseTime}, | 
					
						
							|  |  |  | 			"link":     File{Data: "content: link\n", Inode: 42, Links: 2, ModTime: baseTime.Add(time.Second)}, | 
					
						
							|  |  |  | 			"file":     Symlink{Target: "foo2", ModTime: baseTime}, | 
					
						
							|  |  |  | 			"hardlink": File{Data: "content: link\n", Inode: 42, Links: 2, ModTime: baseTime.Add(time.Second)}, | 
					
						
							| 
									
										
										
										
											2024-06-07 23:02:46 +02:00
										 |  |  | 			"newdir":   Dir{ModTime: baseTime}, | 
					
						
							| 
									
										
										
										
											2024-06-07 22:44:47 +02:00
										 |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	files := map[string]string{ | 
					
						
							|  |  |  | 		"link":     "content: link\n", | 
					
						
							|  |  |  | 		"hardlink": "content: link\n", | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	links := map[string]string{ | 
					
						
							|  |  |  | 		"dirtest": "foo", | 
					
						
							|  |  |  | 		"file":    "foo2", | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tempdir := saveSnapshotsAndOverwrite(t, baseSnapshot, overwriteSnapshot, Options{Overwrite: OverwriteAlways}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for filename, content := range files { | 
					
						
							|  |  |  | 		data, err := os.ReadFile(filepath.Join(tempdir, filepath.FromSlash(filename))) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			t.Errorf("unable to read file %v: %v", filename, err) | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if !bytes.Equal(data, []byte(content)) { | 
					
						
							|  |  |  | 			t.Errorf("file %v has wrong content: want %q, got %q", filename, content, data) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	for filename, target := range links { | 
					
						
							|  |  |  | 		link, err := fs.Readlink(filepath.Join(tempdir, filepath.FromSlash(filename))) | 
					
						
							|  |  |  | 		rtest.OK(t, err) | 
					
						
							|  |  |  | 		rtest.Equals(t, link, target, "wrong symlink target") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-31 18:15:50 +02:00
										 |  |  | func TestRestoreModified(t *testing.T) { | 
					
						
							|  |  |  | 	// overwrite files between snapshots and also change their filesize | 
					
						
							|  |  |  | 	snapshots := []Snapshot{ | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			Nodes: map[string]Node{ | 
					
						
							|  |  |  | 				"foo": File{Data: "content: foo\n", ModTime: time.Now()}, | 
					
						
							|  |  |  | 				"bar": File{Data: "content: a\n", ModTime: time.Now()}, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			Nodes: map[string]Node{ | 
					
						
							|  |  |  | 				"foo": File{Data: "content: a\n", ModTime: time.Now()}, | 
					
						
							|  |  |  | 				"bar": File{Data: "content: bar\n", ModTime: time.Now()}, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	repo := repository.TestRepository(t) | 
					
						
							|  |  |  | 	tempdir := filepath.Join(rtest.TempDir(t), "target") | 
					
						
							|  |  |  | 	ctx, cancel := context.WithCancel(context.Background()) | 
					
						
							|  |  |  | 	defer cancel() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, snapshot := range snapshots { | 
					
						
							|  |  |  | 		sn, id := saveSnapshot(t, repo, snapshot, noopGetGenericAttributes) | 
					
						
							|  |  |  | 		t.Logf("snapshot saved as %v", id.Str()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		res := NewRestorer(repo, sn, Options{Overwrite: OverwriteIfChanged}) | 
					
						
							|  |  |  | 		rtest.OK(t, res.RestoreTo(ctx, tempdir)) | 
					
						
							|  |  |  | 		n, err := res.VerifyFiles(ctx, tempdir) | 
					
						
							|  |  |  | 		rtest.OK(t, err) | 
					
						
							|  |  |  | 		rtest.Equals(t, 2, n, "unexpected number of verified files") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestRestoreIfChanged(t *testing.T) { | 
					
						
							|  |  |  | 	origData := "content: foo\n" | 
					
						
							|  |  |  | 	modData := "content: bar\n" | 
					
						
							|  |  |  | 	rtest.Equals(t, len(modData), len(origData), "broken testcase") | 
					
						
							|  |  |  | 	snapshot := Snapshot{ | 
					
						
							|  |  |  | 		Nodes: map[string]Node{ | 
					
						
							|  |  |  | 			"foo": File{Data: origData, ModTime: time.Now()}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	repo := repository.TestRepository(t) | 
					
						
							|  |  |  | 	tempdir := filepath.Join(rtest.TempDir(t), "target") | 
					
						
							|  |  |  | 	ctx, cancel := context.WithCancel(context.Background()) | 
					
						
							|  |  |  | 	defer cancel() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	sn, id := saveSnapshot(t, repo, snapshot, noopGetGenericAttributes) | 
					
						
							|  |  |  | 	t.Logf("snapshot saved as %v", id.Str()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	res := NewRestorer(repo, sn, Options{}) | 
					
						
							|  |  |  | 	rtest.OK(t, res.RestoreTo(ctx, tempdir)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// modify file but maintain size and timestamp | 
					
						
							|  |  |  | 	path := filepath.Join(tempdir, "foo") | 
					
						
							|  |  |  | 	f, err := os.OpenFile(path, os.O_RDWR, 0) | 
					
						
							|  |  |  | 	rtest.OK(t, err) | 
					
						
							|  |  |  | 	fi, err := f.Stat() | 
					
						
							|  |  |  | 	rtest.OK(t, err) | 
					
						
							|  |  |  | 	_, err = f.Write([]byte(modData)) | 
					
						
							|  |  |  | 	rtest.OK(t, err) | 
					
						
							|  |  |  | 	rtest.OK(t, f.Close()) | 
					
						
							|  |  |  | 	var utimes = [...]syscall.Timespec{ | 
					
						
							|  |  |  | 		syscall.NsecToTimespec(fi.ModTime().UnixNano()), | 
					
						
							|  |  |  | 		syscall.NsecToTimespec(fi.ModTime().UnixNano()), | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	rtest.OK(t, syscall.UtimesNano(path, utimes[:])) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, overwrite := range []OverwriteBehavior{OverwriteIfChanged, OverwriteAlways} { | 
					
						
							|  |  |  | 		res = NewRestorer(repo, sn, Options{Overwrite: overwrite}) | 
					
						
							|  |  |  | 		rtest.OK(t, res.RestoreTo(ctx, tempdir)) | 
					
						
							|  |  |  | 		data, err := os.ReadFile(path) | 
					
						
							|  |  |  | 		rtest.OK(t, err) | 
					
						
							|  |  |  | 		if overwrite == OverwriteAlways { | 
					
						
							|  |  |  | 			// restore should notice the changed file content | 
					
						
							|  |  |  | 			rtest.Equals(t, origData, string(data), "expected original file content") | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			// restore should not have noticed the changed file content | 
					
						
							| 
									
										
										
										
											2024-07-01 22:45:59 +00:00
										 |  |  | 			rtest.Equals(t, modData, string(data), "expected modified file content") | 
					
						
							| 
									
										
										
										
											2024-05-31 18:15:50 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2024-05-31 20:57:28 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | func TestRestoreDryRun(t *testing.T) { | 
					
						
							|  |  |  | 	snapshot := Snapshot{ | 
					
						
							|  |  |  | 		Nodes: map[string]Node{ | 
					
						
							|  |  |  | 			"foo":  File{Data: "content: foo\n", Links: 2, Inode: 42}, | 
					
						
							|  |  |  | 			"foo2": File{Data: "content: foo\n", Links: 2, Inode: 42}, | 
					
						
							|  |  |  | 			"dirtest": Dir{ | 
					
						
							|  |  |  | 				Nodes: map[string]Node{ | 
					
						
							|  |  |  | 					"file": File{Data: "content: file\n"}, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			"link": Symlink{Target: "foo"}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	repo := repository.TestRepository(t) | 
					
						
							|  |  |  | 	tempdir := filepath.Join(rtest.TempDir(t), "target") | 
					
						
							|  |  |  | 	ctx, cancel := context.WithCancel(context.Background()) | 
					
						
							|  |  |  | 	defer cancel() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	sn, id := saveSnapshot(t, repo, snapshot, noopGetGenericAttributes) | 
					
						
							|  |  |  | 	t.Logf("snapshot saved as %v", id.Str()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	res := NewRestorer(repo, sn, Options{DryRun: true}) | 
					
						
							|  |  |  | 	rtest.OK(t, res.RestoreTo(ctx, tempdir)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	_, err := os.Stat(tempdir) | 
					
						
							|  |  |  | 	rtest.Assert(t, errors.Is(err, os.ErrNotExist), "expected no file to be created, got %v", err) | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2024-06-29 19:02:57 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | func TestRestoreDelete(t *testing.T) { | 
					
						
							|  |  |  | 	repo := repository.TestRepository(t) | 
					
						
							|  |  |  | 	tempdir := rtest.TempDir(t) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	sn, _ := saveSnapshot(t, repo, Snapshot{ | 
					
						
							|  |  |  | 		Nodes: map[string]Node{ | 
					
						
							|  |  |  | 			"dir": Dir{ | 
					
						
							|  |  |  | 				Mode: normalizeFileMode(0755 | os.ModeDir), | 
					
						
							|  |  |  | 				Nodes: map[string]Node{ | 
					
						
							|  |  |  | 					"file1":       File{Data: "content: file\n"}, | 
					
						
							|  |  |  | 					"anotherfile": File{Data: "content: file\n"}, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			"dir2": Dir{ | 
					
						
							|  |  |  | 				Mode: normalizeFileMode(0755 | os.ModeDir), | 
					
						
							|  |  |  | 				Nodes: map[string]Node{ | 
					
						
							|  |  |  | 					"anotherfile": File{Data: "content: file\n"}, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			"anotherfile": File{Data: "content: file\n"}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	}, noopGetGenericAttributes) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// should delete files that no longer exist in the snapshot | 
					
						
							|  |  |  | 	deleteSn, _ := saveSnapshot(t, repo, Snapshot{ | 
					
						
							|  |  |  | 		Nodes: map[string]Node{ | 
					
						
							|  |  |  | 			"dir": Dir{ | 
					
						
							|  |  |  | 				Mode: normalizeFileMode(0755 | os.ModeDir), | 
					
						
							|  |  |  | 				Nodes: map[string]Node{ | 
					
						
							|  |  |  | 					"file1": File{Data: "content: file\n"}, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	}, noopGetGenericAttributes) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tests := []struct { | 
					
						
							| 
									
										
										
										
											2024-06-29 19:23:09 +02:00
										 |  |  | 		selectFilter func(item string, isDir bool) (selectedForRestore bool, childMayBeSelected bool) | 
					
						
							| 
									
										
										
										
											2024-06-29 19:02:57 +02:00
										 |  |  | 		fileState    map[string]bool | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			selectFilter: nil, | 
					
						
							|  |  |  | 			fileState: map[string]bool{ | 
					
						
							|  |  |  | 				"dir":                                true, | 
					
						
							|  |  |  | 				filepath.Join("dir", "anotherfile"):  false, | 
					
						
							|  |  |  | 				filepath.Join("dir", "file1"):        true, | 
					
						
							|  |  |  | 				"dir2":                               false, | 
					
						
							|  |  |  | 				filepath.Join("dir2", "anotherfile"): false, | 
					
						
							|  |  |  | 				"anotherfile":                        false, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							| 
									
										
										
										
											2024-06-29 19:23:09 +02:00
										 |  |  | 			selectFilter: func(item string, isDir bool) (selectedForRestore bool, childMayBeSelected bool) { | 
					
						
							| 
									
										
										
										
											2024-06-29 19:02:57 +02:00
										 |  |  | 				return false, false | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			fileState: map[string]bool{ | 
					
						
							|  |  |  | 				"dir":                                true, | 
					
						
							|  |  |  | 				filepath.Join("dir", "anotherfile"):  true, | 
					
						
							|  |  |  | 				filepath.Join("dir", "file1"):        true, | 
					
						
							|  |  |  | 				"dir2":                               true, | 
					
						
							|  |  |  | 				filepath.Join("dir2", "anotherfile"): true, | 
					
						
							|  |  |  | 				"anotherfile":                        true, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							| 
									
										
										
										
											2024-06-29 19:23:09 +02:00
										 |  |  | 			selectFilter: func(item string, isDir bool) (selectedForRestore bool, childMayBeSelected bool) { | 
					
						
							| 
									
										
										
										
											2024-06-29 19:02:57 +02:00
										 |  |  | 				switch item { | 
					
						
							|  |  |  | 				case filepath.FromSlash("/dir"): | 
					
						
							|  |  |  | 					selectedForRestore = true | 
					
						
							|  |  |  | 				case filepath.FromSlash("/dir2"): | 
					
						
							|  |  |  | 					selectedForRestore = true | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			fileState: map[string]bool{ | 
					
						
							|  |  |  | 				"dir":                                true, | 
					
						
							|  |  |  | 				filepath.Join("dir", "anotherfile"):  true, | 
					
						
							|  |  |  | 				filepath.Join("dir", "file1"):        true, | 
					
						
							|  |  |  | 				"dir2":                               false, | 
					
						
							|  |  |  | 				filepath.Join("dir2", "anotherfile"): false, | 
					
						
							|  |  |  | 				"anotherfile":                        true, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, test := range tests { | 
					
						
							|  |  |  | 		t.Run("", func(t *testing.T) { | 
					
						
							|  |  |  | 			res := NewRestorer(repo, sn, Options{}) | 
					
						
							|  |  |  | 			ctx, cancel := context.WithCancel(context.Background()) | 
					
						
							|  |  |  | 			defer cancel() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			err := res.RestoreTo(ctx, tempdir) | 
					
						
							|  |  |  | 			rtest.OK(t, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			res = NewRestorer(repo, deleteSn, Options{Delete: true}) | 
					
						
							|  |  |  | 			if test.selectFilter != nil { | 
					
						
							|  |  |  | 				res.SelectFilter = test.selectFilter | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			err = res.RestoreTo(ctx, tempdir) | 
					
						
							|  |  |  | 			rtest.OK(t, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			for fn, shouldExist := range test.fileState { | 
					
						
							|  |  |  | 				_, err := os.Stat(filepath.Join(tempdir, fn)) | 
					
						
							|  |  |  | 				if shouldExist { | 
					
						
							|  |  |  | 					rtest.OK(t, err) | 
					
						
							|  |  |  | 				} else { | 
					
						
							|  |  |  | 					rtest.Assert(t, errors.Is(err, os.ErrNotExist), "file %v: unexpected error got %v, expected ErrNotExist", fn, err) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } |