|
1 | 1 | package sender |
2 | 2 |
|
3 | 3 | import ( |
4 | | - "fmt" |
5 | 4 | "io/fs" |
6 | 5 | "os" |
7 | 6 | "os/user" |
8 | 7 | "path/filepath" |
9 | | - "slices" |
10 | 8 | "strconv" |
11 | 9 | "strings" |
12 | 10 | "sync" |
@@ -58,82 +56,50 @@ var ( |
58 | 56 | lookupGroupOnce sync.Once |
59 | 57 | ) |
60 | 58 |
|
61 | | -func getRootStrip(requested, localDir string) (string, string) { |
62 | | - root := filepath.Clean(filepath.Join(localDir, requested)) |
63 | | - |
| 59 | +func getStrip(requested string) string { |
64 | 60 | sep := string(os.PathSeparator) |
65 | | - strip := filepath.Dir(filepath.Clean(root)) + sep |
| 61 | + if requested == sep { |
| 62 | + return "" |
| 63 | + } |
66 | 64 | if strings.HasSuffix(requested, sep) { |
67 | | - strip = filepath.Clean(root) + sep |
| 65 | + return strings.TrimPrefix(filepath.Clean(requested), "/") + sep |
68 | 66 | } |
69 | | - return root, strip |
| 67 | + return "" |
70 | 68 | } |
71 | 69 |
|
72 | 70 | type scopedWalker struct { |
73 | | - st *Transfer |
74 | | - ioError func(err error) |
75 | | - conn *rsyncwire.Conn |
76 | | - fec *rsyncwire.Buffer |
77 | | - excl *filterRuleList |
78 | | - uidMap map[int32]string |
79 | | - gidMap map[int32]string |
80 | | - fileList *fileList |
81 | | - prefix string |
82 | | - rootPath string |
83 | | - root *os.Root |
| 71 | + st *Transfer |
| 72 | + ioError func(err error) |
| 73 | + conn *rsyncwire.Conn |
| 74 | + fec *rsyncwire.Buffer |
| 75 | + excl *filterRuleList |
| 76 | + uidMap map[int32]string |
| 77 | + gidMap map[int32]string |
| 78 | + fileList *fileList |
| 79 | + root *os.Root |
| 80 | + localDir string |
| 81 | + requested string |
| 82 | + strip string |
84 | 83 | } |
85 | 84 |
|
86 | 85 | func (s *scopedWalker) walk() error { |
87 | | - root, err := os.OpenRoot(s.rootPath) |
| 86 | + root, err := os.OpenRoot(s.localDir) |
88 | 87 | if err != nil { |
89 | | - if st, err := os.Stat(s.rootPath); err == nil && !st.IsDir() { |
90 | | - // Not a directory? Open the parent directory, stat the file and |
91 | | - // call the WalkFn directly for this entry. |
92 | | - return s.walkFile() |
93 | | - } |
| 88 | + s.st.Logger.Printf(" OpenRoot(localDir=%q): %v", s.localDir, err) |
94 | 89 | // File does not exist or we cannot open the file? |
95 | 90 | // Set the I/O error flag, but keep going. |
96 | 91 | s.ioError(err) |
97 | 92 | return nil |
98 | 93 | } |
99 | 94 | s.fileList.Roots = append(s.fileList.Roots, root) |
100 | 95 | s.root = root |
101 | | - if err := fs.WalkDir(root.FS(), ".", s.walkFn); err != nil { |
102 | | - return err |
103 | | - } |
104 | | - return nil |
105 | | -} |
106 | | - |
107 | | -func (s *scopedWalker) walkFile() error { |
108 | | - dir, file := filepath.Split(s.rootPath) |
109 | | - if s.st.Opts.DebugGTE(rsyncopts.DEBUG_FLIST, 1) { |
110 | | - s.st.Logger.Printf("treating rootPath=%q as dir=%q + file=%q", s.rootPath, dir, file) |
111 | | - } |
112 | | - s.prefix = "" |
113 | | - root, err := os.OpenRoot(dir) |
114 | | - if err != nil { |
115 | | - // set the I/O error flag, but keep going |
116 | | - s.ioError(err) |
117 | | - return nil |
118 | | - } |
119 | | - f, err := root.Open(".") |
120 | | - if err != nil { |
121 | | - return err |
122 | | - } |
123 | | - defer f.Close() |
124 | | - dirents, err := f.ReadDir(-1) |
125 | | - if err != nil { |
126 | | - return err |
| 96 | + rootname := s.requested |
| 97 | + // fs.WalkDir(root.FS(), …) does not accept absolute paths, |
| 98 | + // so make them relative by prepending a . |
| 99 | + if strings.HasPrefix(rootname, "/") { |
| 100 | + rootname = "." + rootname |
127 | 101 | } |
128 | | - idx := slices.IndexFunc(dirents, func(dirent os.DirEntry) bool { |
129 | | - return dirent.Name() == file |
130 | | - }) |
131 | | - if idx == -1 { |
132 | | - return fmt.Errorf("file %s not found in %s", file, dir) |
133 | | - } |
134 | | - s.fileList.Roots = append(s.fileList.Roots, root) |
135 | | - s.root = root |
136 | | - if err := s.walkFn(file, dirents[idx], nil); err != nil { |
| 102 | + if err := fs.WalkDir(root.FS(), filepath.Clean(rootname), s.walkFn); err != nil { |
137 | 103 | return err |
138 | 104 | } |
139 | 105 | return nil |
@@ -166,15 +132,14 @@ func (s *scopedWalker) walkFn(path string, d fs.DirEntry, err error) error { |
166 | 132 | // Only ever transmit long names, like openrsync |
167 | 133 | flags := byte(rsync.XMIT_LONG_NAME) |
168 | 134 |
|
169 | | - name := s.prefix + path |
| 135 | + name := path |
| 136 | + if s.strip != "" { |
| 137 | + name = strings.TrimPrefix(name, s.strip) |
| 138 | + } |
170 | 139 | if opts.DebugGTE(rsyncopts.DEBUG_FLIST, 1) { |
171 | 140 | logger.Printf("Trim(path=%q) = %q", path, name) |
172 | 141 | } |
173 | 142 | if path == "." { |
174 | | - name = s.prefix |
175 | | - if s.prefix == "" { |
176 | | - name = "." |
177 | | - } |
178 | 143 | flags |= rsync.XMIT_TOP_DIR |
179 | 144 | } |
180 | 145 | // st.logger.Printf("flags for %q: %v", name, flags) |
@@ -372,33 +337,41 @@ func (st *Transfer) SendFileList(localDir string, paths []string, excl *filterRu |
372 | 337 | } |
373 | 338 |
|
374 | 339 | for _, requested := range paths { |
| 340 | + local := localDir |
| 341 | + if local == "/" { |
| 342 | + // Implicit module (/) and absolute requested path (/tmp/foo/), |
| 343 | + // turn the path into the local directory and request /. |
| 344 | + local = requested |
| 345 | + if strings.HasSuffix(requested, string(os.PathSeparator)) { |
| 346 | + requested = "/" |
| 347 | + } else { |
| 348 | + local = filepath.Dir(requested) |
| 349 | + requested = filepath.Base(requested) |
| 350 | + } |
| 351 | + } |
| 352 | + |
375 | 353 | if st.Opts.DebugGTE(rsyncopts.DEBUG_FLIST, 1) { |
376 | | - st.Logger.Printf(" path %q (local dir %q)", requested, localDir) |
| 354 | + st.Logger.Printf(" path %q (local dir %q)", requested, local) |
377 | 355 | } |
378 | 356 | // st.Logger.Printf("getRootStrip(requested=%q, localDir=%q", requested, localDir) |
379 | | - rootPath, strip := getRootStrip(requested, localDir) |
| 357 | + strip := getStrip(requested) |
380 | 358 | // st.Logger.Printf("root=%q, strip=%q", root, strip) |
381 | | - prefix := strings.TrimPrefix(rootPath, filepath.Clean(strip)) |
382 | | - if prefix != "" { |
383 | | - prefix = strings.TrimPrefix(prefix, "/") |
384 | | - prefix += "/" |
385 | | - } |
386 | 359 | if st.Opts.DebugGTE(rsyncopts.DEBUG_FLIST, 1) { |
387 | | - st.Logger.Printf(" filepath.Walk(%q), strip=%q", rootPath, strip) |
388 | | - st.Logger.Printf(" prefix=%q", prefix) |
| 360 | + st.Logger.Printf(" fs.Walk(%q, %q), strip=%q", local, requested) |
389 | 361 | } |
390 | 362 |
|
391 | 363 | sw := &scopedWalker{ |
392 | | - st: st, |
393 | | - conn: st.Conn, |
394 | | - fec: fec, |
395 | | - excl: excl, |
396 | | - uidMap: uidMap, |
397 | | - gidMap: gidMap, |
398 | | - fileList: &fileList, |
399 | | - prefix: prefix, |
400 | | - ioError: ioError, |
401 | | - rootPath: rootPath, |
| 364 | + st: st, |
| 365 | + conn: st.Conn, |
| 366 | + fec: fec, |
| 367 | + excl: excl, |
| 368 | + uidMap: uidMap, |
| 369 | + gidMap: gidMap, |
| 370 | + fileList: &fileList, |
| 371 | + ioError: ioError, |
| 372 | + localDir: local, |
| 373 | + requested: requested, |
| 374 | + strip: strip, |
402 | 375 | } |
403 | 376 | if err := sw.walk(); err != nil { |
404 | 377 | return nil, err |
|
0 commit comments