Skip to content

Commit 9056c4d

Browse files
committed
rsyncd: descend into subdirectory (if specified)
fixes #40
1 parent f2037a0 commit 9056c4d

File tree

2 files changed

+126
-2
lines changed

2 files changed

+126
-2
lines changed

integration/receiver/receiver_daemon_test.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,98 @@ func TestDaemonReceiverSync(t *testing.T) {
5454
}
5555
}
5656

57+
// like TestDaemonReceiverSync, but specifying a destination path that is a subdirectory
58+
// within the module.
59+
func TestDaemonReceiverSyncSubdir(t *testing.T) {
60+
t.Parallel()
61+
62+
rsyncBin := rsynctest.TridgeOrGTFO(t, "TODO: reason")
63+
64+
tmp := t.TempDir()
65+
source := filepath.Join(tmp, "source")
66+
dest := filepath.Join(tmp, "dest")
67+
destLarge := filepath.Join(dest, "destsubdir", "large-data-file")
68+
69+
headPattern := []byte{0x11}
70+
bodyPattern := []byte{0xbb}
71+
endPattern := []byte{0xee}
72+
rsynctest.WriteLargeDataFile(t, source, headPattern, bodyPattern, endPattern)
73+
74+
// start a server which receives data
75+
srv := rsynctest.New(t, rsynctest.WritableInteropModule(dest))
76+
77+
rsync := exec.Command(rsyncBin,
78+
// "--debug=all4",
79+
"--archive",
80+
// A verbosity level of 3 is enough, any higher than that and rsync
81+
// will start listing individual chunk matches.
82+
"-v", "-v", "-v", // "-v",
83+
"--port="+srv.Port,
84+
source+"/", // copy contents of source
85+
"rsync://localhost/interop/destsubdir/")
86+
rsync.Env = append(os.Environ(),
87+
// Ensure rsync does not localize decimal separators and fractional
88+
// points based on the current locale:
89+
"LANG=C.UTF-8")
90+
rsync.Stdout = testlogger.New(t)
91+
rsync.Stderr = testlogger.New(t)
92+
if err := rsync.Run(); err != nil {
93+
t.Fatalf("%v: %v", rsync.Args, err)
94+
}
95+
96+
if err := rsynctest.DataFileMatches(destLarge, headPattern, bodyPattern, endPattern); err != nil {
97+
t.Fatal(err)
98+
}
99+
}
100+
101+
// like TestDaemonReceiverSync, but specifying a destination path that is a subdirectory
102+
// within the module.
103+
func TestDaemonReceiverSyncSubdirTraversal(t *testing.T) {
104+
t.Parallel()
105+
106+
rsyncBin := rsynctest.TridgeOrGTFO(t, "TODO: reason")
107+
108+
tmp := t.TempDir()
109+
source := filepath.Join(tmp, "source")
110+
dest := filepath.Join(tmp, "dest")
111+
destLarge := filepath.Join(dest, "destsubdir", "large-data-file")
112+
113+
headPattern := []byte{0x11}
114+
bodyPattern := []byte{0xbb}
115+
endPattern := []byte{0xee}
116+
rsynctest.WriteLargeDataFile(t, source, headPattern, bodyPattern, endPattern)
117+
118+
// start a server which receives data
119+
srv := rsynctest.New(t, rsynctest.WritableInteropModule(dest))
120+
121+
rsync := exec.Command(rsyncBin,
122+
// "--debug=all4",
123+
"--archive",
124+
// A verbosity level of 3 is enough, any higher than that and rsync
125+
// will start listing individual chunk matches.
126+
"-v", "-v", "-v", // "-v",
127+
"--port="+srv.Port,
128+
source+"/", // copy contents of source
129+
"rsync://localhost/interop/../")
130+
rsync.Env = append(os.Environ(),
131+
// Ensure rsync does not localize decimal separators and fractional
132+
// points based on the current locale:
133+
"LANG=C.UTF-8")
134+
var buf bytes.Buffer
135+
rsync.Stdout = &buf
136+
rsync.Stderr = &buf
137+
if err := rsync.Run(); err != nil {
138+
if strings.Contains(buf.String(), "path escapes from parent") {
139+
return
140+
}
141+
t.Fatalf("%v: %v", rsync.Args, err)
142+
}
143+
144+
if err := rsynctest.DataFileMatches(destLarge, headPattern, bodyPattern, endPattern); err != nil {
145+
t.Fatal(err)
146+
}
147+
}
148+
57149
func TestDaemonReceiverDelete(t *testing.T) {
58150
t.Parallel()
59151

rsyncd/rsyncd.go

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"io"
1313
"net"
1414
"os"
15+
"path/filepath"
1516
"strings"
1617
"time"
1718

@@ -392,7 +393,9 @@ func (s *Server) handleConn(ctx context.Context, conn *Conn, module *Module, pc
392393

393394
// handleConnReceiver is equivalent to rsync/main.c:do_server_recv
394395
func (s *Server) handleConnReceiver(module *Module, crd *rsyncwire.CountingReader, cwr *rsyncwire.CountingWriter, paths []string, opts *rsyncopts.Options, negotiate bool, c *rsyncwire.Conn, sessionChecksumSeed int32) (err error) {
395-
if module == nil {
396+
var destPath string
397+
implicitModule := module == nil
398+
if implicitModule {
396399
if len(paths) != 1 {
397400
return fmt.Errorf("precisely one destination path required, got %q", paths)
398401
}
@@ -401,9 +404,10 @@ func (s *Server) handleConnReceiver(module *Module, crd *rsyncwire.CountingReade
401404
Path: paths[0],
402405
Writable: true,
403406
}
407+
destPath = module.Path
404408
}
405409
if opts.Verbose() {
406-
s.logger.Printf("handleConnReceiver(module=%+v)", module)
410+
s.logger.Printf("handleConnReceiver(module=%+v, destPath=%q)", module, destPath)
407411
}
408412

409413
if !module.Writable {
@@ -442,6 +446,34 @@ func (s *Server) handleConnReceiver(module *Module, crd *rsyncwire.CountingReade
442446
}
443447
defer rt.DestRoot.Close()
444448

449+
if !implicitModule {
450+
if len(paths) > 1 {
451+
return fmt.Errorf("module is available, and at most one destination path is allowed, got %q", paths)
452+
}
453+
// Descend into subdirectory (if requested),
454+
// using the os.OpenRoot traversal-safe API.
455+
if len(paths) == 1 && paths[0] != "/" {
456+
subdir := strings.TrimPrefix(paths[0], "/")
457+
subRoot, err := rt.DestRoot.OpenRoot(subdir)
458+
if err != nil {
459+
if os.IsNotExist(err) {
460+
if err := rt.DestRoot.Mkdir(subdir, 0755); err != nil {
461+
return fmt.Errorf("Mkdir(%s): %v", subdir, err)
462+
}
463+
subRoot, err = rt.DestRoot.OpenRoot(subdir)
464+
}
465+
if err != nil {
466+
return fmt.Errorf("OpenRoot(%s): %v", subdir, err)
467+
}
468+
}
469+
rt.Dest = filepath.Join(rt.Dest, subRoot.Name())
470+
rt.DestRoot = subRoot
471+
if opts.Verbose() {
472+
s.logger.Printf("opened subdirectory %q", rt.Dest)
473+
}
474+
}
475+
}
476+
445477
if opts.PreserveHardLinks() {
446478
return fmt.Errorf("support for hard links not yet implemented")
447479
}

0 commit comments

Comments
 (0)