Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve handling of dangling symlinks when creating file list #27

Open
prologic opened this issue Dec 30, 2022 · 4 comments
Open

Improve handling of dangling symlinks when creating file list #27

prologic opened this issue Dec 30, 2022 · 4 comments
Labels
enhancement New feature or request

Comments

@prologic
Copy link

Hey @stapelberg 👋

Toying around with your tool/library here and ran into a small problem I'm a bit befuddled by:
(I'm trying to see if I can build a container backup solution using rsync as the primary driver)

I'm an invoking an rsync(1) client invoking the gokr-rsync over a shell like so:

$ rsync --rsh="docker run -i --rm -v data:/data prologic/docker-rsync" --list-only :
2022/12/30 23:44:31 remote protocol: 31
2022/12/30 23:44:31 exclusion list read
2022/12/30 23:44:31 sendFileList(module="implicit")
2022/12/30 23:44:31   path "." (module root "/")
2022/12/30 23:44:31 lstat /proc/1/fd/3: no such file or directory
gokr-rsync [sender]: lstat /proc/1/fd/3: no such file or directory
rsync: connection unexpectedly closed (71 bytes received so far) [Receiver]
rsync error: error in rsync protocol data stream (code 12) at io.c(231) [Receiver=3.2.7]

I'm not sure why /proc/1/fd/3 is being opened? I poked around in the container that is spawned and there is no file descriptor 3, only 0 1 2 as you'd expect (stdin, stdout, stderr).

My Dockerfile is mostly similar to yours:

FROM golang:alpine AS build

RUN go install github.com/gokrazy/rsync/cmd/gokr-rsyncd@latest

FROM alpine AS runtime

COPY --from=build /go/bin/gokr-rsyncd /usr/local/bin

#USER nobody:nobody

VOLUME /data

COPY entrypoint.sh /entrypoint

ENTRYPOINT ["/entrypoint"]

And the entrypoint.sh:

#!/bin/sh

cd /data || exit 1
# exec gokr-rsyncd --daemon --gokr.listen=0.0.0.0:8730 --gokr.modulemap=pwd=$PWD
exec gokr-rsyncd --server --sender . .
@prologic
Copy link
Author

Just wanted to say that I got something working in a slightly different setup:

Dockerfile:

# Build
FROM golang:alpine AS build

RUN go install github.com/gokrazy/rsync/cmd/gokr-rsyncd@latest

# Runtime
FROM alpine:latest

RUN apk --no-cache -U add su-exec shadow

ENV PUID=1000
ENV PGID=1000

RUN addgroup -g "${PGID}" nonroot && \
    adduser -D -H -G nonroot -h /var/empty -u "${PUID}" nonroot && \
    mkdir -p /data && chown -R nonroot:nonroot /data

EXPOSE 8730

VOLUME /data

WORKDIR /

# force cgo resolver
ENV GODEBUG=netdns=cgo

COPY --from=build /go/bin/gokr-rsyncd /usr/local/bin/rsyncd

COPY entrypoint.sh /init

ENTRYPOINT ["/init"]
CMD ["rsyncd", "--daemon", "--gokr.listen=0.0.0.0:8730", "--gokr.modulemap=data=/data"]

entrypoint.sh:

#!/bin/sh

[ -n "${PUID}" ] && usermod -u "${PUID}" nonroot
[ -n "${PGID}" ] && groupmod -g "${PGID}" nonroot

printf "Switching UID=%s and GID=%s\n" "${PUID}" "${PGID}"
exec su-exec nonroot:nonroot "$@"

I was successfully able to rsync the contents of a Docker named volume (even while a container was running/attached to it) 👌 -- That's backup done.

Now for restore 😅

@prologic
Copy link
Author

My plan was to (btw):

  • Spin up a backup container like: docker run --rm -p 8730:8730 -v data:/data:ro prologic/docker-rsync for any given named volume.
  • Perform an initial backup using like: rsync --archive --delete --verbose --port 8730 rsync://localhost/data/ .
  • Temporarily stop the any attached running containers.
  • Re-run the backup (in case any files changed)
  • Restart the affected containers.

@stapelberg stapelberg changed the title lstat /proc/1/fd/3: no such file or directory Improve handling of dangling symlinks when creating file list Dec 31, 2022
@stapelberg stapelberg added the enhancement New feature or request label Dec 31, 2022
@stapelberg
Copy link
Contributor

stapelberg commented Dec 31, 2022

Hey! The problem you ran into is that you were trying to serve /, which includes the /proc pseudo file system, which in turn contains a bunch of dangling symlinks (by design).

Obviously the current behavior of gokr-rsyncd isn’t great when encountering dangling symlinks. I haven’t checked what the original rsync does yet.

You can work around the issue by changing the code to ignore ENOENT for now:

--- i/rsyncd/flist.go
+++ w/rsyncd/flist.go
@@ -45,6 +45,12 @@ func (st *sendTransfer) sendFileList(mod Module, opts *Opts, paths []string) (*f
 		err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
 			// st.logger.Printf("filepath.WalkFn(path=%s)", path)
 			if err != nil {
+				if os.IsNotExist(err) {
+					// We encounter -ENOENT when walking over a dangling symlink
+					// (e.g. /proc/<pid>/fd/3). Don’t stop file list creation,
+					// just ignore the affected file.
+					return nil
+				}
 				return err
 			}

@prologic
Copy link
Author

@stapelberg Oh! 🤦‍♂️ This is because despite the modulemap I configured, I had set the working directory of the image to / 🤔

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants