From 01331e2004c751b6cf4c42ffbebb1ec8754f9f7c Mon Sep 17 00:00:00 2001 From: blacknon Date: Wed, 14 Aug 2024 00:00:57 +0900 Subject: [PATCH] update. --- README.md | 4 +- cmd/lscp/args.go | 2 +- cmd/lsftp/args.go | 2 +- cmd/lssh/args.go | 2 +- go.mod | 18 +- go.sum | 66 +- .../blacknon/go-nfs-sshlib/CONTRIBUTING.md | 11 + .../github.com/blacknon/go-nfs-sshlib/LICENSE | 202 ++++ .../blacknon/go-nfs-sshlib/README.md | 96 ++ .../blacknon/go-nfs-sshlib/SECURITY.md | 11 + .../github.com/blacknon/go-nfs-sshlib/conn.go | 328 +++++++ .../blacknon/go-nfs-sshlib/errors.go | 230 +++++ .../github.com/blacknon/go-nfs-sshlib/file.go | 377 +++++++ .../blacknon/go-nfs-sshlib/file/file.go | 25 + .../blacknon/go-nfs-sshlib/file/file_unix.go | 24 + .../go-nfs-sshlib/file/file_windows.go | 12 + .../blacknon/go-nfs-sshlib/filesystem.go | 15 + .../blacknon/go-nfs-sshlib/handler.go | 52 + .../go-nfs-sshlib/helpers/cachinghandler.go | 199 ++++ .../go-nfs-sshlib/helpers/nullauthhandler.go | 59 ++ .../github.com/blacknon/go-nfs-sshlib/log.go | 216 +++++ .../blacknon/go-nfs-sshlib/mount.go | 58 ++ .../blacknon/go-nfs-sshlib/mountinterface.go | 90 ++ .../github.com/blacknon/go-nfs-sshlib/nfs.go | 38 + .../blacknon/go-nfs-sshlib/nfs_onaccess.go | 45 + .../blacknon/go-nfs-sshlib/nfs_oncommit.go | 50 + .../blacknon/go-nfs-sshlib/nfs_oncreate.go | 124 +++ .../blacknon/go-nfs-sshlib/nfs_onfsinfo.go | 87 ++ .../blacknon/go-nfs-sshlib/nfs_onfsstat.go | 58 ++ .../blacknon/go-nfs-sshlib/nfs_ongetattr.go | 44 + .../blacknon/go-nfs-sshlib/nfs_onlink.go | 94 ++ .../blacknon/go-nfs-sshlib/nfs_onlookup.go | 87 ++ .../blacknon/go-nfs-sshlib/nfs_onmkdir.go | 94 ++ .../blacknon/go-nfs-sshlib/nfs_onmknod.go | 156 +++ .../blacknon/go-nfs-sshlib/nfs_onpathconf.go | 55 ++ .../blacknon/go-nfs-sshlib/nfs_onread.go | 94 ++ .../blacknon/go-nfs-sshlib/nfs_onreaddir.go | 191 ++++ .../go-nfs-sshlib/nfs_onreaddirplus.go | 153 +++ .../blacknon/go-nfs-sshlib/nfs_onreadlink.go | 51 + .../blacknon/go-nfs-sshlib/nfs_onremove.go | 78 ++ .../blacknon/go-nfs-sshlib/nfs_onrename.go | 110 +++ .../blacknon/go-nfs-sshlib/nfs_onrmdir.go | 9 + .../blacknon/go-nfs-sshlib/nfs_onsetattr.go | 76 ++ .../blacknon/go-nfs-sshlib/nfs_onsymlink.go | 88 ++ .../blacknon/go-nfs-sshlib/nfs_onwrite.go | 112 +++ .../blacknon/go-nfs-sshlib/nfsinterface.go | 188 ++++ .../blacknon/go-nfs-sshlib/server.go | 107 ++ .../github.com/blacknon/go-nfs-sshlib/time.go | 32 + .../blacknon/go-sshlib/auth_pkcs11.go | 8 + vendor/github.com/blacknon/go-sshlib/cmd.go | 1 - .../github.com/blacknon/go-sshlib/connect.go | 62 +- .../github.com/blacknon/go-sshlib/nfs_cos.go | 42 + .../blacknon/go-sshlib/nfs_cos_unix.go | 30 + .../blacknon/go-sshlib/nfs_forward.go | 60 ++ .../blacknon/go-sshlib/nfs_sftpfs.go | 185 ++++ vendor/github.com/blacknon/go-sshlib/proxy.go | 61 +- .../cyphar/filepath-securejoin/LICENSE | 28 + .../cyphar/filepath-securejoin/README.md | 79 ++ .../cyphar/filepath-securejoin/VERSION | 1 + .../cyphar/filepath-securejoin/join.go | 125 +++ .../cyphar/filepath-securejoin/vfs.go | 41 + vendor/github.com/davecgh/go-spew/LICENSE | 2 +- .../github.com/davecgh/go-spew/spew/bypass.go | 187 ++-- .../davecgh/go-spew/spew/bypasssafe.go | 2 +- .../github.com/davecgh/go-spew/spew/common.go | 2 +- .../github.com/davecgh/go-spew/spew/dump.go | 10 +- .../github.com/davecgh/go-spew/spew/format.go | 4 +- .../github.com/go-git/go-billy/v5/.gitignore | 4 + vendor/github.com/go-git/go-billy/v5/LICENSE | 201 ++++ vendor/github.com/go-git/go-billy/v5/Makefile | 11 + .../github.com/go-git/go-billy/v5/README.md | 73 ++ vendor/github.com/go-git/go-billy/v5/fs.go | 202 ++++ .../go-billy/v5/helper/chroot/chroot.go | 242 +++++ .../go-billy/v5/helper/polyfill/polyfill.go | 105 ++ .../go-billy/v5/helper/temporal/temporal.go | 30 + .../go-git/go-billy/v5/memfs/memory.go | 410 ++++++++ .../go-git/go-billy/v5/memfs/storage.go | 238 +++++ .../github.com/go-git/go-billy/v5/osfs/os.go | 127 +++ .../go-git/go-billy/v5/osfs/os_bound.go | 261 +++++ .../go-git/go-billy/v5/osfs/os_chroot.go | 112 +++ .../go-git/go-billy/v5/osfs/os_js.go | 25 + .../go-git/go-billy/v5/osfs/os_options.go | 3 + .../go-git/go-billy/v5/osfs/os_plan9.go | 91 ++ .../go-git/go-billy/v5/osfs/os_posix.go | 38 + .../go-git/go-billy/v5/osfs/os_windows.go | 58 ++ .../go-git/go-billy/v5/util/glob.go | 111 +++ .../go-git/go-billy/v5/util/util.go | 282 ++++++ .../go-git/go-billy/v5/util/walk.go | 72 ++ vendor/github.com/google/uuid/CHANGELOG.md | 41 + vendor/github.com/google/uuid/CONTRIBUTING.md | 26 + vendor/github.com/google/uuid/CONTRIBUTORS | 9 + vendor/github.com/google/uuid/LICENSE | 27 + vendor/github.com/google/uuid/README.md | 21 + vendor/github.com/google/uuid/dce.go | 80 ++ vendor/github.com/google/uuid/doc.go | 12 + vendor/github.com/google/uuid/hash.go | 59 ++ vendor/github.com/google/uuid/marshal.go | 38 + vendor/github.com/google/uuid/node.go | 90 ++ vendor/github.com/google/uuid/node_js.go | 12 + vendor/github.com/google/uuid/node_net.go | 33 + vendor/github.com/google/uuid/null.go | 118 +++ vendor/github.com/google/uuid/sql.go | 59 ++ vendor/github.com/google/uuid/time.go | 134 +++ vendor/github.com/google/uuid/util.go | 43 + vendor/github.com/google/uuid/uuid.go | 365 +++++++ vendor/github.com/google/uuid/version1.go | 44 + vendor/github.com/google/uuid/version4.go | 76 ++ vendor/github.com/google/uuid/version6.go | 56 ++ vendor/github.com/google/uuid/version7.go | 104 ++ .../hashicorp/golang-lru/v2/.gitignore | 23 + .../hashicorp/golang-lru/v2/.golangci.yml | 46 + .../github.com/hashicorp/golang-lru/v2/2q.go | 267 +++++ .../hashicorp/golang-lru/v2/LICENSE | 364 +++++++ .../hashicorp/golang-lru/v2/README.md | 79 ++ .../github.com/hashicorp/golang-lru/v2/doc.go | 24 + .../hashicorp/golang-lru/v2/internal/list.go | 142 +++ .../github.com/hashicorp/golang-lru/v2/lru.go | 250 +++++ .../golang-lru/v2/simplelru/LICENSE_list | 29 + .../hashicorp/golang-lru/v2/simplelru/lru.go | 177 ++++ .../golang-lru/v2/simplelru/lru_interface.go | 46 + vendor/github.com/pkg/sftp/attrs.go | 33 +- vendor/github.com/pkg/sftp/attrs_stubs.go | 1 + vendor/github.com/pkg/sftp/attrs_unix.go | 1 + vendor/github.com/pkg/sftp/client.go | 166 +++- vendor/github.com/pkg/sftp/conn.go | 12 +- vendor/github.com/pkg/sftp/debug.go | 1 + vendor/github.com/pkg/sftp/fuzz.go | 1 + .../internal/encoding/ssh/filexfer/attrs.go | 118 +-- .../internal/encoding/ssh/filexfer/buffer.go | 153 ++- .../encoding/ssh/filexfer/extended_packets.go | 7 +- .../encoding/ssh/filexfer/extensions.go | 13 +- .../encoding/ssh/filexfer/filexfer.go | 4 +- .../sftp/internal/encoding/ssh/filexfer/fx.go | 18 +- .../internal/encoding/ssh/filexfer/fxp.go | 57 +- .../encoding/ssh/filexfer/handle_packets.go | 67 +- .../encoding/ssh/filexfer/init_packets.go | 12 +- .../encoding/ssh/filexfer/open_packets.go | 17 +- .../internal/encoding/ssh/filexfer/packets.go | 80 +- .../encoding/ssh/filexfer/path_packets.go | 72 +- .../encoding/ssh/filexfer/permissions.go | 2 +- .../encoding/ssh/filexfer/response_packets.go | 57 +- vendor/github.com/pkg/sftp/ls_formatting.go | 7 + vendor/github.com/pkg/sftp/ls_plan9.go | 1 + vendor/github.com/pkg/sftp/ls_stub.go | 1 + vendor/github.com/pkg/sftp/ls_unix.go | 1 + vendor/github.com/pkg/sftp/packet-manager.go | 4 +- vendor/github.com/pkg/sftp/packet-typing.go | 2 +- vendor/github.com/pkg/sftp/packet.go | 25 +- vendor/github.com/pkg/sftp/release.go | 1 + vendor/github.com/pkg/sftp/request-example.go | 41 +- .../github.com/pkg/sftp/request-interfaces.go | 48 +- vendor/github.com/pkg/sftp/request-plan9.go | 20 +- vendor/github.com/pkg/sftp/request-readme.md | 2 +- vendor/github.com/pkg/sftp/request-server.go | 57 +- vendor/github.com/pkg/sftp/request-unix.go | 5 +- vendor/github.com/pkg/sftp/request.go | 39 +- vendor/github.com/pkg/sftp/request_windows.go | 31 - vendor/github.com/pkg/sftp/server.go | 62 +- vendor/github.com/pkg/sftp/server_plan9.go | 27 + .../pkg/sftp/server_statvfs_impl.go | 1 + .../pkg/sftp/server_statvfs_linux.go | 1 + .../pkg/sftp/server_statvfs_stubs.go | 1 + vendor/github.com/pkg/sftp/server_unix.go | 16 + vendor/github.com/pkg/sftp/server_windows.go | 39 + vendor/github.com/pkg/sftp/sftp.go | 2 +- vendor/github.com/pkg/sftp/stat_posix.go | 3 +- vendor/github.com/pkg/sftp/syscall_fixed.go | 1 + vendor/github.com/pkg/sftp/syscall_good.go | 4 +- vendor/github.com/rasky/go-xdr/LICENSE | 13 + vendor/github.com/rasky/go-xdr/xdr2/decode.go | 918 ++++++++++++++++++ vendor/github.com/rasky/go-xdr/xdr2/doc.go | 206 ++++ vendor/github.com/rasky/go-xdr/xdr2/encode.go | 718 ++++++++++++++ vendor/github.com/rasky/go-xdr/xdr2/error.go | 187 ++++ vendor/github.com/rasky/go-xdr/xdr2/tag.go | 78 ++ .../testify/assert/assertion_compare.go | 24 +- .../assert/assertion_compare_can_convert.go | 2 +- .../testify/assert/assertion_format.go | 10 + .../testify/assert/assertion_forward.go | 20 + .../stretchr/testify/assert/assertions.go | 78 +- .../willscott/go-nfs-client/LICENSE_BSD-2.txt | 16 + .../willscott/go-nfs-client/NOTICE.txt | 8 + .../willscott/go-nfs-client/nfs/rpc/client.go | 305 ++++++ .../go-nfs-client/nfs/rpc/portmap.go | 80 ++ .../willscott/go-nfs-client/nfs/rpc/rpc.go | 48 + .../willscott/go-nfs-client/nfs/rpc/tcp.go | 59 ++ .../willscott/go-nfs-client/nfs/util/log.go | 69 ++ .../willscott/go-nfs-client/nfs/xdr/decode.go | 56 ++ .../willscott/go-nfs-client/nfs/xdr/encode.go | 15 + vendor/modules.txt | 41 +- 189 files changed, 14377 insertions(+), 748 deletions(-) create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/CONTRIBUTING.md create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/LICENSE create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/README.md create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/SECURITY.md create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/conn.go create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/errors.go create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/file.go create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/file/file.go create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/file/file_unix.go create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/file/file_windows.go create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/filesystem.go create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/handler.go create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/helpers/cachinghandler.go create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/helpers/nullauthhandler.go create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/log.go create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/mount.go create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/mountinterface.go create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/nfs.go create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/nfs_onaccess.go create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/nfs_oncommit.go create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/nfs_oncreate.go create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/nfs_onfsinfo.go create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/nfs_onfsstat.go create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/nfs_ongetattr.go create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/nfs_onlink.go create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/nfs_onlookup.go create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/nfs_onmkdir.go create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/nfs_onmknod.go create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/nfs_onpathconf.go create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/nfs_onread.go create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/nfs_onreaddir.go create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/nfs_onreaddirplus.go create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/nfs_onreadlink.go create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/nfs_onremove.go create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/nfs_onrename.go create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/nfs_onrmdir.go create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/nfs_onsetattr.go create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/nfs_onsymlink.go create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/nfs_onwrite.go create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/nfsinterface.go create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/server.go create mode 100644 vendor/github.com/blacknon/go-nfs-sshlib/time.go create mode 100644 vendor/github.com/blacknon/go-sshlib/nfs_cos.go create mode 100644 vendor/github.com/blacknon/go-sshlib/nfs_cos_unix.go create mode 100644 vendor/github.com/blacknon/go-sshlib/nfs_forward.go create mode 100644 vendor/github.com/blacknon/go-sshlib/nfs_sftpfs.go create mode 100644 vendor/github.com/cyphar/filepath-securejoin/LICENSE create mode 100644 vendor/github.com/cyphar/filepath-securejoin/README.md create mode 100644 vendor/github.com/cyphar/filepath-securejoin/VERSION create mode 100644 vendor/github.com/cyphar/filepath-securejoin/join.go create mode 100644 vendor/github.com/cyphar/filepath-securejoin/vfs.go create mode 100644 vendor/github.com/go-git/go-billy/v5/.gitignore create mode 100644 vendor/github.com/go-git/go-billy/v5/LICENSE create mode 100644 vendor/github.com/go-git/go-billy/v5/Makefile create mode 100644 vendor/github.com/go-git/go-billy/v5/README.md create mode 100644 vendor/github.com/go-git/go-billy/v5/fs.go create mode 100644 vendor/github.com/go-git/go-billy/v5/helper/chroot/chroot.go create mode 100644 vendor/github.com/go-git/go-billy/v5/helper/polyfill/polyfill.go create mode 100644 vendor/github.com/go-git/go-billy/v5/helper/temporal/temporal.go create mode 100644 vendor/github.com/go-git/go-billy/v5/memfs/memory.go create mode 100644 vendor/github.com/go-git/go-billy/v5/memfs/storage.go create mode 100644 vendor/github.com/go-git/go-billy/v5/osfs/os.go create mode 100644 vendor/github.com/go-git/go-billy/v5/osfs/os_bound.go create mode 100644 vendor/github.com/go-git/go-billy/v5/osfs/os_chroot.go create mode 100644 vendor/github.com/go-git/go-billy/v5/osfs/os_js.go create mode 100644 vendor/github.com/go-git/go-billy/v5/osfs/os_options.go create mode 100644 vendor/github.com/go-git/go-billy/v5/osfs/os_plan9.go create mode 100644 vendor/github.com/go-git/go-billy/v5/osfs/os_posix.go create mode 100644 vendor/github.com/go-git/go-billy/v5/osfs/os_windows.go create mode 100644 vendor/github.com/go-git/go-billy/v5/util/glob.go create mode 100644 vendor/github.com/go-git/go-billy/v5/util/util.go create mode 100644 vendor/github.com/go-git/go-billy/v5/util/walk.go create mode 100644 vendor/github.com/google/uuid/CHANGELOG.md create mode 100644 vendor/github.com/google/uuid/CONTRIBUTING.md create mode 100644 vendor/github.com/google/uuid/CONTRIBUTORS create mode 100644 vendor/github.com/google/uuid/LICENSE create mode 100644 vendor/github.com/google/uuid/README.md create mode 100644 vendor/github.com/google/uuid/dce.go create mode 100644 vendor/github.com/google/uuid/doc.go create mode 100644 vendor/github.com/google/uuid/hash.go create mode 100644 vendor/github.com/google/uuid/marshal.go create mode 100644 vendor/github.com/google/uuid/node.go create mode 100644 vendor/github.com/google/uuid/node_js.go create mode 100644 vendor/github.com/google/uuid/node_net.go create mode 100644 vendor/github.com/google/uuid/null.go create mode 100644 vendor/github.com/google/uuid/sql.go create mode 100644 vendor/github.com/google/uuid/time.go create mode 100644 vendor/github.com/google/uuid/util.go create mode 100644 vendor/github.com/google/uuid/uuid.go create mode 100644 vendor/github.com/google/uuid/version1.go create mode 100644 vendor/github.com/google/uuid/version4.go create mode 100644 vendor/github.com/google/uuid/version6.go create mode 100644 vendor/github.com/google/uuid/version7.go create mode 100644 vendor/github.com/hashicorp/golang-lru/v2/.gitignore create mode 100644 vendor/github.com/hashicorp/golang-lru/v2/.golangci.yml create mode 100644 vendor/github.com/hashicorp/golang-lru/v2/2q.go create mode 100644 vendor/github.com/hashicorp/golang-lru/v2/LICENSE create mode 100644 vendor/github.com/hashicorp/golang-lru/v2/README.md create mode 100644 vendor/github.com/hashicorp/golang-lru/v2/doc.go create mode 100644 vendor/github.com/hashicorp/golang-lru/v2/internal/list.go create mode 100644 vendor/github.com/hashicorp/golang-lru/v2/lru.go create mode 100644 vendor/github.com/hashicorp/golang-lru/v2/simplelru/LICENSE_list create mode 100644 vendor/github.com/hashicorp/golang-lru/v2/simplelru/lru.go create mode 100644 vendor/github.com/hashicorp/golang-lru/v2/simplelru/lru_interface.go create mode 100644 vendor/github.com/pkg/sftp/server_plan9.go create mode 100644 vendor/github.com/pkg/sftp/server_unix.go create mode 100644 vendor/github.com/pkg/sftp/server_windows.go create mode 100644 vendor/github.com/rasky/go-xdr/LICENSE create mode 100644 vendor/github.com/rasky/go-xdr/xdr2/decode.go create mode 100644 vendor/github.com/rasky/go-xdr/xdr2/doc.go create mode 100644 vendor/github.com/rasky/go-xdr/xdr2/encode.go create mode 100644 vendor/github.com/rasky/go-xdr/xdr2/error.go create mode 100644 vendor/github.com/rasky/go-xdr/xdr2/tag.go create mode 100644 vendor/github.com/willscott/go-nfs-client/LICENSE_BSD-2.txt create mode 100644 vendor/github.com/willscott/go-nfs-client/NOTICE.txt create mode 100644 vendor/github.com/willscott/go-nfs-client/nfs/rpc/client.go create mode 100644 vendor/github.com/willscott/go-nfs-client/nfs/rpc/portmap.go create mode 100644 vendor/github.com/willscott/go-nfs-client/nfs/rpc/rpc.go create mode 100644 vendor/github.com/willscott/go-nfs-client/nfs/rpc/tcp.go create mode 100644 vendor/github.com/willscott/go-nfs-client/nfs/util/log.go create mode 100644 vendor/github.com/willscott/go-nfs-client/nfs/xdr/decode.go create mode 100644 vendor/github.com/willscott/go-nfs-client/nfs/xdr/encode.go diff --git a/README.md b/README.md index 0341cb1e..84c0eb16 100644 --- a/README.md +++ b/README.md @@ -641,8 +641,8 @@ If OpenSsh config is loaded, it will be loaded as it is. ## Related projects -- [go-sshlib](github.com/blacknon/go-sshlib) -- [lsshell](github.com/blacknon/lsshell) +- [go-sshlib](https://github.com/blacknon/go-sshlib) +- [lsshell](https://github.com/blacknon/lsshell) ## Licence diff --git a/cmd/lscp/args.go b/cmd/lscp/args.go index 3d7a5cfc..2e09c9a8 100644 --- a/cmd/lscp/args.go +++ b/cmd/lscp/args.go @@ -58,7 +58,7 @@ USAGE: app.Name = "lscp" app.Usage = "TUI list select and parallel scp client command." app.Copyright = "blacknon(blacknon@orebibou.com)" - app.Version = "0.6.11" + app.Version = "0.6.12" // options // TODO(blacknon): オプションの追加(0.7.0) diff --git a/cmd/lsftp/args.go b/cmd/lsftp/args.go index fff8ac3f..2c8af55a 100644 --- a/cmd/lsftp/args.go +++ b/cmd/lsftp/args.go @@ -50,7 +50,7 @@ USAGE: app.Name = "lsftp" app.Usage = "TUI list select and parallel sftp client command." app.Copyright = "blacknon(blacknon@orebibou.com)" - app.Version = "0.6.11" + app.Version = "0.6.12" app.Flags = []cli.Flag{ cli.StringFlag{Name: "file,F", Value: defConf, Usage: "config file path"}, diff --git a/cmd/lssh/args.go b/cmd/lssh/args.go index 4254d828..f3a1983a 100644 --- a/cmd/lssh/args.go +++ b/cmd/lssh/args.go @@ -59,7 +59,7 @@ USAGE: app.Name = "lssh" app.Usage = "TUI list select and parallel ssh client command." app.Copyright = "blacknon(blacknon@orebibou.com)" - app.Version = "0.6.11" + app.Version = "0.6.12" // TODO(blacknon): オプションの追加 // -m ... NFSマウントで、リモートホストの特定ディレクトリをローカルにマウント可能にする (v0.7.0) diff --git a/go.mod b/go.mod index 9c319360..abd029e2 100644 --- a/go.mod +++ b/go.mod @@ -13,11 +13,11 @@ require ( github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 // indirect github.com/blacknon/crypto11 v1.2.7 // indirect - github.com/blacknon/go-sshlib v0.1.15 + github.com/blacknon/go-sshlib v0.1.16 github.com/blacknon/go-x11auth v0.1.0 // indirect github.com/blacknon/textcol v0.0.1 github.com/c-bata/go-prompt v0.2.6 - github.com/davecgh/go-spew v1.1.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a // indirect github.com/disiqueira/gotree v1.0.0 github.com/dustin/go-humanize v1.0.0 @@ -34,12 +34,12 @@ require ( github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect github.com/nsf/termbox-go v1.1.1 github.com/pkg/errors v0.9.1 // indirect - github.com/pkg/sftp v1.13.4 + github.com/pkg/sftp v1.13.6 github.com/pkg/term v1.2.0-beta.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/sevlyar/go-daemon v0.1.5 - github.com/stretchr/testify v1.7.1 + github.com/stretchr/testify v1.8.0 github.com/thales-e-security/pool v0.0.2 // indirect github.com/urfave/cli v1.21.0 github.com/vbauerster/mpb v3.4.0+incompatible @@ -50,5 +50,15 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect ) +require ( + github.com/blacknon/go-nfs-sshlib v0.0.3 // indirect + github.com/cyphar/filepath-securejoin v0.2.4 // indirect + github.com/go-git/go-billy/v5 v5.5.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/rasky/go-xdr v0.0.0-20170124162913-1a41d1a06c93 // indirect + github.com/willscott/go-nfs-client v0.0.0-20240104095149-b44639837b00 // indirect +) + // replace replace github.com/c-bata/go-prompt v0.2.6 => github.com/blacknon/go-prompt v0.2.7 diff --git a/go.sum b/go.sum index 39c5f18c..88d4ef5a 100644 --- a/go.sum +++ b/go.sum @@ -12,25 +12,38 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/blacknon/crypto11 v1.2.7 h1:LKrnCeXAk4jQmpJTeg/tVvNjqEgKHIE+tYSNXQj8Fco= github.com/blacknon/crypto11 v1.2.7/go.mod h1:Z24sFD9pMX3NdUFzX52ggcV7HurAYAGvxud73BrHudA= +github.com/blacknon/go-nfs-sshlib v0.0.3 h1:tq83kTZibrr99/GCn0pqkJhmBTDMupL/eMI3ZhSM0G4= +github.com/blacknon/go-nfs-sshlib v0.0.3/go.mod h1:jaCmHgFoj8j08rGrBnhJ4nFO7nUWs4xFrUd2vEPwyx8= github.com/blacknon/go-prompt v0.2.7 h1:dVdTqVplKvpT/k4bB9BlbcBYl/k6amYX5tvjYBmuKkI= github.com/blacknon/go-prompt v0.2.7/go.mod h1:zNBmC/BPAyr+3ey1oRhPxuXJS9zz1lEmJpwaoQroe3w= -github.com/blacknon/go-sshlib v0.1.15 h1:3yd50Y+/DsMOkCpm4szomcZg0yS9DebXIsfnCXhiXfI= -github.com/blacknon/go-sshlib v0.1.15/go.mod h1:5oa1CR8KJjlgvv+gP9JJC5oHOfXXKpIFkRsM4Ccm22A= +github.com/blacknon/go-sshlib v0.1.16 h1:de5KXEYalMzknPWwSbKEr/Z3CxgOfMyiQC5+sfzYwLg= +github.com/blacknon/go-sshlib v0.1.16/go.mod h1:upfnjVHf/Lh7ysT3dU1ziHKtzRQ1J62JyrShZ7FlNSI= github.com/blacknon/go-x11auth v0.1.0 h1:SnljCPWcvglWeGAlKc1RAPMHnOfMpM9+GrTGEUQ1lqQ= github.com/blacknon/go-x11auth v0.1.0/go.mod h1:SKOCa19LluXHyB+OaLYobquzceE0SWxVW7e/qU5xGBM= github.com/blacknon/textcol v0.0.1 h1:x9h7yLPGyr8Pdz12XJ30h7Iz5mJlKd0CzfGYxhrmnk8= github.com/blacknon/textcol v0.0.1/go.mod h1:1x1tHA4cEgiQ8BsKysc60OALSZMG9WjmbjmJvPqIInQ= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= +github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a h1:saTgr5tMLFnmy/yg3qDTft4rE5DY2uJ/cCxCe3q0XTU= github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a/go.mod h1:Bw9BbhOJVNR+t0jCqx2GC6zv0TGBsShs56Y3gfSCvl0= github.com/disiqueira/gotree v1.0.0 h1:en5wk87n7/Jyk6gVME3cx3xN9KmUCstJ1IjHr4Se4To= github.com/disiqueira/gotree v1.0.0/go.mod h1:7CwL+VWsWAU95DovkdRZAtA7YbtHwGk+tLV/kNi8niU= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= +github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= @@ -39,6 +52,10 @@ github.com/kevinburke/ssh_config v0.0.0-20190724205821-6cfae18c12b8 h1:AUkD9wwFc github.com/kevinburke/ssh_config v0.0.0-20190724205821-6cfae18c12b8/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lunixbochs/vtclean v1.0.0 h1:xu2sLAri4lGiovBDQKxl5mrXyESr3gUr5m5SM5+LVb8= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -60,43 +77,59 @@ github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQB github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= github.com/nsf/termbox-go v1.1.1 h1:nksUPLCb73Q++DwbYUBEglYBRPZyoXJdrj5L+TkjyZY= github.com/nsf/termbox-go v1.1.1/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.13.4 h1:Lb0RYJCmgUcBgZosfoi9Y9sbl6+LJgOIgk/2Y4YjMFg= -github.com/pkg/sftp v1.13.4/go.mod h1:LzqnAvaD5TWeNBsZpfKxSYn1MbjWwOsCIAFFJbpIsK8= +github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo= +github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk= github.com/pkg/term v1.2.0-beta.2 h1:L3y/h2jkuBVFdWiJvNfYfKmzcCnILw7mJWm2JQuMppw= github.com/pkg/term v1.2.0-beta.2/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rasky/go-xdr v0.0.0-20170124162913-1a41d1a06c93 h1:UVArwN/wkKjMVhh2EQGC0tEc1+FqiLlvYXY5mQ2f8Wg= +github.com/rasky/go-xdr v0.0.0-20170124162913-1a41d1a06c93/go.mod h1:Nfe4efndBz4TibWycNE+lqyJZiMX4ycx+QKV8Ta0f/o= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/sevlyar/go-daemon v0.1.5 h1:Zy/6jLbM8CfqJ4x4RPr7MJlSKt90f00kNM1D401C+Qk= github.com/sevlyar/go-daemon v0.1.5/go.mod h1:6dJpPatBT9eUwM5VCw9Bt6CdX9Tk6UWvhW3MebLDRKE= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg= github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU= github.com/urfave/cli v1.21.0 h1:wYSSj06510qPIzGSua9ZqsncMmWE3Zr55KBERygyrxE= github.com/urfave/cli v1.21.0/go.mod h1:lxDj6qX9Q6lWQxIrbrT0nwecwUtRnhVZAJjJZrVUZZQ= github.com/vbauerster/mpb v3.4.0+incompatible h1:mfiiYw87ARaeRW6x5gWwYRUawxaW1tLAD8IceomUCNw= github.com/vbauerster/mpb v3.4.0+incompatible/go.mod h1:zAHG26FUhVKETRu+MWqYXcI70POlC6N8up9p1dID7SU= +github.com/willscott/go-nfs-client v0.0.0-20240104095149-b44639837b00 h1:U0DnHRZFzoIV1oFEZczg5XyPut9yxk9jjtax/9Bxr/o= +github.com/willscott/go-nfs-client v0.0.0-20240104095149-b44639837b00/go.mod h1:Tq++Lr/FgiS3X48q5FETemXiSLGuYMQT2sPjYNPJSwA= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -107,20 +140,33 @@ golang.org/x/sys v0.0.0-20200219091948-cb0a6d8edb6c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/CONTRIBUTING.md b/vendor/github.com/blacknon/go-nfs-sshlib/CONTRIBUTING.md new file mode 100644 index 00000000..f1f3f112 --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/CONTRIBUTING.md @@ -0,0 +1,11 @@ +# Contributing Guidelines + +We appreciate your interest in improving go-nfs! + +## Looking for ways to contribute? + +There are several ways you can contribute: +- Start contributing immediately via the [opened](https://github.com/willscott/go-nfs/issues) issues on GitHub. + Defined issues provide an excellent starting point. +- Reporting issues, bugs, mistakes, or inconsistencies. + As many open source projects, we are short-staffed, we thus kindly ask you to be open to contribute a fix for discovered issues. diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/LICENSE b/vendor/github.com/blacknon/go-nfs-sshlib/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/README.md b/vendor/github.com/blacknon/go-nfs-sshlib/README.md new file mode 100644 index 00000000..a92f706a --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/README.md @@ -0,0 +1,96 @@ +Golang Network File Server +=== + +NFSv3 protocol implementation in pure Golang. + +Current Status: +* Minimally tested +* Mounts, read-only and read-write support + +Usage +=== + +The most interesting demo is currently in `example/osview`. + +Start the server +`go run ./example/osview .`. + +The local folder at `.` will be the initial view in the mount. mutations to metadata or contents +will be stored purely in memory and not written back to the OS. When run, this +demo will print the port it is listening on. + +The mount can be accessed using a command similar to +`mount -o port=,mountport= -t nfs localhost:/mount ` (For Mac users) + +or + +`mount -o port=,mountport=,nfsvers=3,noacl,tcp -t nfs localhost:/mount ` (For Linux users) + +API +=== + +The NFS server runs on a `net.Listener` to export a file system to NFS clients. +Usage is structured similarly to many other golang network servers. + +```golang +package main + +import ( + "fmt" + "log" + "net" + + "github.com/go-git/go-billy/v5/memfs" + nfs "github.com/willscott/go-nfs" + nfshelper "github.com/willscott/go-nfs/helpers" +) + +func main() { + listener, err := net.Listen("tcp", ":0") + panicOnErr(err, "starting TCP listener") + fmt.Printf("Server running at %s\n", listener.Addr()) + mem := memfs.New() + f, err := mem.Create("hello.txt") + panicOnErr(err, "creating file") + _, err = f.Write([]byte("hello world")) + panicOnErr(err, "writing data") + f.Close() + handler := nfshelper.NewNullAuthHandler(mem) + cacheHelper := nfshelper.NewCachingHandler(handler, 1) + panicOnErr(nfs.Serve(listener, cacheHelper), "serving nfs") +} + +func panicOnErr(err error, desc ...interface{}) { + if err == nil { + return + } + log.Println(desc...) + log.Panicln(err) +} +``` + +Notes +--- + +* Ports are typically determined through portmap. The need for running portmap +(which is the only part that needs a privileged listening port) can be avoided +through specific mount options. e.g. +`mount -o port=n,mountport=n -t nfs host:/mount /localmount` + +* This server currently uses [billy](https://github.com/go-git/go-billy/) to +provide a file system abstraction layer. There are some edges of the NFS protocol +which do not translate to this abstraction. + * NFS expects access to an `inode` or equivalent unique identifier to reference + files in a file system. These are considered opaque identifiers here, which + means they will not work as expected in cases of hard linking. + * The billy abstraction layer does not extend to exposing `uid` and `gid` + ownership of files. If ownership is important to your file system, you + will need to ensure that the `os.FileInfo` meets additional constraints. + In particular, the `Sys()` escape hatch is queried by this library, and + if your file system populates a [`syscall.Stat_t`](https://golang.org/pkg/syscall/#Stat_t) + concrete struct, the ownership specified in that object will be used. + +* Relevant RFCS: +[5531 - RPC protocol](https://tools.ietf.org/html/rfc5531), +[1813 - NFSv3](https://tools.ietf.org/html/rfc1813), +[1094 - NFS](https://tools.ietf.org/html/rfc1094) diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/SECURITY.md b/vendor/github.com/blacknon/go-nfs-sshlib/SECURITY.md new file mode 100644 index 00000000..5f079c19 --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/SECURITY.md @@ -0,0 +1,11 @@ +# Security Policy + +## Supported Versions + +The latest release reflects the current best recommendation / supported version at this time. + +## Reporting a Vulnerability + +Please email Will (the git commit author) if you need to report issues privately. +I will endeavor to respond within a day, but if I am offline, responses may be delayed longer than that. +If you need a stronger SLA to have confidence in using this code, feel free to reach out. diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/conn.go b/vendor/github.com/blacknon/go-nfs-sshlib/conn.go new file mode 100644 index 00000000..6ed6d039 --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/conn.go @@ -0,0 +1,328 @@ +package nfs + +import ( + "bufio" + "bytes" + "context" + "encoding/binary" + "errors" + "fmt" + "io" + "net" + + xdr2 "github.com/rasky/go-xdr/xdr2" + "github.com/willscott/go-nfs-client/nfs/rpc" + "github.com/willscott/go-nfs-client/nfs/xdr" +) + +var ( + // ErrInputInvalid is returned when input cannot be parsed + ErrInputInvalid = errors.New("invalid input") + // ErrAlreadySent is returned when writing a header/status multiple times + ErrAlreadySent = errors.New("response already started") +) + +// ResponseCode is a combination of accept_stat and reject_stat. +type ResponseCode uint32 + +// ResponseCode Codes +const ( + ResponseCodeSuccess ResponseCode = iota + ResponseCodeProgUnavailable + ResponseCodeProcUnavailable + ResponseCodeGarbageArgs + ResponseCodeSystemErr + ResponseCodeRPCMismatch + ResponseCodeAuthError +) + +type conn struct { + *Server + writeSerializer chan []byte + net.Conn +} + +func (c *conn) serve(ctx context.Context) { + connCtx, cancel := context.WithCancel(ctx) + defer cancel() + c.writeSerializer = make(chan []byte, 1) + go c.serializeWrites(connCtx) + + bio := bufio.NewReader(c.Conn) + for { + w, err := c.readRequestHeader(connCtx, bio) + if err != nil { + if err == io.EOF { + // Clean close. + c.Close() + return + } + return + } + Log.Tracef("request: %v", w.req) + err = c.handle(connCtx, w) + respErr := w.finish(connCtx) + if err != nil { + Log.Errorf("error handling req: %v", err) + // failure to handle at a level needing to close the connection. + c.Close() + return + } + if respErr != nil { + Log.Errorf("error sending response: %v", respErr) + c.Close() + return + } + } +} + +func (c *conn) serializeWrites(ctx context.Context) { + // todo: maybe don't need the extra buffer + writer := bufio.NewWriter(c.Conn) + var fragmentBuf [4]byte + var fragmentInt uint32 + for { + select { + case <-ctx.Done(): + return + case msg, ok := <-c.writeSerializer: + if !ok { + return + } + // prepend the fragmentation header + fragmentInt = uint32(len(msg)) + fragmentInt |= (1 << 31) + binary.BigEndian.PutUint32(fragmentBuf[:], fragmentInt) + n, err := writer.Write(fragmentBuf[:]) + if n < 4 || err != nil { + return + } + n, err = writer.Write(msg) + if err != nil { + return + } + if n < len(msg) { + panic("todo: ensure writes complete fully.") + } + if err = writer.Flush(); err != nil { + return + } + } + } +} + +// Handle a request. errors from this method indicate a failure to read or +// write on the network stream, and trigger a disconnection of the connection. +func (c *conn) handle(ctx context.Context, w *response) error { + handler := c.Server.handlerFor(w.req.Header.Prog, w.req.Header.Proc) + if handler == nil { + Log.Errorf("No handler for %d.%d", w.req.Header.Prog, w.req.Header.Proc) + if err := w.drain(ctx); err != nil { + return err + } + return c.err(ctx, w, &ResponseCodeProcUnavailableError{}) + } + appError := handler(ctx, w, c.Server.Handler) + if drainErr := w.drain(ctx); drainErr != nil { + return drainErr + } + if appError != nil && !w.responded { + if err := c.err(ctx, w, appError); err != nil { + return err + } + } + if !w.responded { + Log.Errorf("Handler did not indicate response status via writing or erroring") + if err := c.err(ctx, w, &ResponseCodeSystemError{}); err != nil { + return err + } + } + return nil +} + +func (c *conn) err(ctx context.Context, w *response, err error) error { + select { + case <-ctx.Done(): + return nil + default: + } + + if w.err == nil { + w.err = err + } + + if w.responded { + return nil + } + + rpcErr := w.errorFmt(err) + if writeErr := w.writeHeader(rpcErr.Code()); writeErr != nil { + return writeErr + } + + body, _ := rpcErr.MarshalBinary() + return w.Write(body) +} + +type request struct { + xid uint32 + rpc.Header + Body io.Reader +} + +func (r *request) String() string { + if r.Header.Prog == nfsServiceID { + return fmt.Sprintf("RPC #%d (nfs.%s)", r.xid, NFSProcedure(r.Header.Proc)) + } else if r.Header.Prog == mountServiceID { + return fmt.Sprintf("RPC #%d (mount.%s)", r.xid, MountProcedure(r.Header.Proc)) + } + return fmt.Sprintf("RPC #%d (%d.%d)", r.xid, r.Header.Prog, r.Header.Proc) +} + +type response struct { + *conn + writer *bytes.Buffer + responded bool + err error + errorFmt func(error) RPCError + req *request +} + +func (w *response) writeXdrHeader() error { + err := xdr.Write(w.writer, &w.req.xid) + if err != nil { + return err + } + respType := uint32(1) + err = xdr.Write(w.writer, &respType) + if err != nil { + return err + } + return nil +} + +func (w *response) writeHeader(code ResponseCode) error { + if w.responded { + return ErrAlreadySent + } + w.responded = true + if err := w.writeXdrHeader(); err != nil { + return err + } + + status := rpc.MsgAccepted + if code == ResponseCodeAuthError || code == ResponseCodeRPCMismatch { + status = rpc.MsgDenied + } + + err := xdr.Write(w.writer, &status) + if err != nil { + return err + } + + if status == rpc.MsgAccepted { + // Write opaque_auth header. + err = xdr.Write(w.writer, &rpc.AuthNull) + if err != nil { + return err + } + } + + return xdr.Write(w.writer, &code) +} + +// Write a response to an xdr message +func (w *response) Write(dat []byte) error { + if !w.responded { + if err := w.writeHeader(ResponseCodeSuccess); err != nil { + return err + } + } + + acc := 0 + for acc < len(dat) { + n, err := w.writer.Write(dat[acc:]) + if err != nil { + return err + } + acc += n + } + return nil +} + +// drain reads the rest of the request frame if not consumed by the handler. +func (w *response) drain(ctx context.Context) error { + if reader, ok := w.req.Body.(*io.LimitedReader); ok { + if reader.N == 0 { + return nil + } + // todo: wrap body in a context reader. + _, err := io.CopyN(io.Discard, w.req.Body, reader.N) + if err == nil || err == io.EOF { + return nil + } + return err + } + return io.ErrUnexpectedEOF +} + +func (w *response) finish(ctx context.Context) error { + select { + case w.conn.writeSerializer <- w.writer.Bytes(): + return nil + case <-ctx.Done(): + return ctx.Err() + } +} + +func (c *conn) readRequestHeader(ctx context.Context, reader *bufio.Reader) (w *response, err error) { + fragment, err := xdr.ReadUint32(reader) + if err != nil { + if xdrErr, ok := err.(*xdr2.UnmarshalError); ok { + if xdrErr.Err == io.EOF { + return nil, io.EOF + } + } + return nil, err + } + if fragment&(1<<31) == 0 { + Log.Warnf("Warning: haven't implemented fragment reconstruction.\n") + return nil, ErrInputInvalid + } + reqLen := fragment - uint32(1<<31) + if reqLen < 40 { + return nil, ErrInputInvalid + } + + r := io.LimitedReader{R: reader, N: int64(reqLen)} + + xid, err := xdr.ReadUint32(&r) + if err != nil { + return nil, err + } + reqType, err := xdr.ReadUint32(&r) + if err != nil { + return nil, err + } + if reqType != 0 { // 0 = request, 1 = response + return nil, ErrInputInvalid + } + + req := request{ + xid, + rpc.Header{}, + &r, + } + if err = xdr.Read(&r, &req.Header); err != nil { + return nil, err + } + + w = &response{ + conn: c, + req: &req, + errorFmt: basicErrorFormatter, + // TODO: use a pool for these. + writer: bytes.NewBuffer([]byte{}), + } + return w, nil +} diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/errors.go b/vendor/github.com/blacknon/go-nfs-sshlib/errors.go new file mode 100644 index 00000000..af08be6a --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/errors.go @@ -0,0 +1,230 @@ +package nfs + +import ( + "encoding" + "encoding/binary" + "errors" + "fmt" +) + +// RPCError provides the error interface for errors thrown by +// procedures to be transmitted over the XDR RPC channel +type RPCError interface { + // An RPCError is an `error` with this method + Error() string + // Code is the RPC Response code to send + Code() ResponseCode + // BinaryMarshaler is the on-wire representation of this error + encoding.BinaryMarshaler +} + +// AuthStat is an enumeration of why authentication ahs failed +type AuthStat uint32 + +// AuthStat Codes +const ( + AuthStatOK AuthStat = iota + AuthStatBadCred + AuthStatRejectedCred + AuthStatBadVerifier + AuthStatRejectedVerfier + AuthStatTooWeak + AuthStatInvalidResponse + AuthStatFailed + AuthStatKerbGeneric + AuthStatTimeExpire + AuthStatTktFile + AuthStatDecode + AuthStatNetAddr + AuthStatRPCGSSCredProblem + AuthStatRPCGSSCTXProblem +) + +// AuthError is an RPCError +type AuthError struct { + AuthStat +} + +// Code for AuthErrors is ResponseCodeAuthError +func (a *AuthError) Code() ResponseCode { + return ResponseCodeAuthError +} + +// Error is a textual representaiton of the auth error. From the RFC +func (a *AuthError) Error() string { + switch a.AuthStat { + case AuthStatOK: + return "Auth Status: OK" + case AuthStatBadCred: + return "Auth Status: bad credential" + case AuthStatRejectedCred: + return "Auth Status: client must begin new session" + case AuthStatBadVerifier: + return "Auth Status: bad verifier" + case AuthStatRejectedVerfier: + return "Auth Status: verifier expired or replayed" + case AuthStatTooWeak: + return "Auth Status: rejected for security reasons" + case AuthStatInvalidResponse: + return "Auth Status: bogus response verifier" + case AuthStatFailed: + return "Auth Status: reason unknown" + case AuthStatKerbGeneric: + return "Auth Status: kerberos generic error" + case AuthStatTimeExpire: + return "Auth Status: time of credential expired" + case AuthStatTktFile: + return "Auth Status: problem with ticket file" + case AuthStatDecode: + return "Auth Status: can't decode authenticator" + case AuthStatNetAddr: + return "Auth Status: wrong net address in ticket" + case AuthStatRPCGSSCredProblem: + return "Auth Status: no credentials for user" + case AuthStatRPCGSSCTXProblem: + return "Auth Status: problem with context" + } + return "Auth Status: Unknown" +} + +// MarshalBinary sends the specific auth status +func (a *AuthError) MarshalBinary() (data []byte, err error) { + var resp [4]byte + binary.LittleEndian.PutUint32(resp[:], uint32(a.AuthStat)) + return resp[:], nil +} + +// RPCMismatchError is an RPCError +type RPCMismatchError struct { + Low uint32 + High uint32 +} + +// Code for RPCMismatchError is ResponseCodeRPCMismatch +func (r *RPCMismatchError) Code() ResponseCode { + return ResponseCodeRPCMismatch +} + +func (r *RPCMismatchError) Error() string { + return fmt.Sprintf("RPC Mismatch: Expected version between %d and %d.", r.Low, r.High) +} + +// MarshalBinary sends the specific rpc mismatch range +func (r *RPCMismatchError) MarshalBinary() (data []byte, err error) { + var resp [8]byte + binary.LittleEndian.PutUint32(resp[0:4], uint32(r.Low)) + binary.LittleEndian.PutUint32(resp[4:8], uint32(r.High)) + return resp[:], nil +} + +// ResponseCodeProcUnavailableError is an RPCError +type ResponseCodeProcUnavailableError struct { +} + +// Code for ResponseCodeProcUnavailableError +func (r *ResponseCodeProcUnavailableError) Code() ResponseCode { + return ResponseCodeProcUnavailable +} + +func (r *ResponseCodeProcUnavailableError) Error() string { + return "The requested procedure is unexported" +} + +// MarshalBinary - this error has no associated body +func (r *ResponseCodeProcUnavailableError) MarshalBinary() (data []byte, err error) { + return []byte{}, nil +} + +// ResponseCodeSystemError is an RPCError +type ResponseCodeSystemError struct { +} + +// Code for ResponseCodeSystemError +func (r *ResponseCodeSystemError) Code() ResponseCode { + return ResponseCodeSystemErr +} + +func (r *ResponseCodeSystemError) Error() string { + return "memory allocation failure" +} + +// MarshalBinary - this error has no associated body +func (r *ResponseCodeSystemError) MarshalBinary() (data []byte, err error) { + return []byte{}, nil +} + +// basicErrorFormatter is the default error handler for response errors. +// if the error is already formatted, it is directly written. Otherwise, +// ResponseCodeSystemError is sent to the client. +func basicErrorFormatter(err error) RPCError { + var rpcErr RPCError + if errors.As(err, &rpcErr) { + return rpcErr + } + return &ResponseCodeSystemError{} +} + +// NFSStatusError represents an error at the NFS level. +type NFSStatusError struct { + NFSStatus + WrappedErr error +} + +// Error is The wrapped error +func (s *NFSStatusError) Error() string { + message := s.NFSStatus.String() + if s.WrappedErr != nil { + message = fmt.Sprintf("%s: %v", message, s.WrappedErr) + } + return message +} + +// Code for NFS issues are successful RPC responses +func (s *NFSStatusError) Code() ResponseCode { + return ResponseCodeSuccess +} + +// MarshalBinary - The binary form of the code. +func (s *NFSStatusError) MarshalBinary() (data []byte, err error) { + var resp [4]byte + binary.BigEndian.PutUint32(resp[0:4], uint32(s.NFSStatus)) + return resp[:], nil +} + +// Unwrap unpacks wrapped errors +func (s *NFSStatusError) Unwrap() error { + return s.WrappedErr +} + +// StatusErrorWithBody is an NFS error with a payload. +type StatusErrorWithBody struct { + NFSStatusError + Body []byte +} + +// MarshalBinary provides the wire format of the error response +func (s *StatusErrorWithBody) MarshalBinary() (data []byte, err error) { + head, err := s.NFSStatusError.MarshalBinary() + return append(head, s.Body...), err +} + +// errFormatterWithBody appends a provided body to errors +func errFormatterWithBody(body []byte) func(err error) RPCError { + return func(err error) RPCError { + if nerr, ok := err.(*NFSStatusError); ok { + return &StatusErrorWithBody{*nerr, body[:]} + } + var rErr RPCError + if errors.As(err, &rErr) { + return rErr + } + return &ResponseCodeSystemError{} + } +} + +var ( + opAttrErrorBody = [4]byte{} + opAttrErrorFormatter = errFormatterWithBody(opAttrErrorBody[:]) + wccDataErrorBody = [8]byte{} + wccDataErrorFormatter = errFormatterWithBody(wccDataErrorBody[:]) +) diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/file.go b/vendor/github.com/blacknon/go-nfs-sshlib/file.go new file mode 100644 index 00000000..801aec0f --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/file.go @@ -0,0 +1,377 @@ +package nfs + +import ( + "errors" + "hash/fnv" + "io" + "math" + "os" + "time" + + "github.com/blacknon/go-nfs-sshlib/file" + "github.com/go-git/go-billy/v5" + "github.com/willscott/go-nfs-client/nfs/xdr" +) + +// FileAttribute holds metadata about a filesystem object +type FileAttribute struct { + Type FileType + FileMode uint32 + Nlink uint32 + UID uint32 + GID uint32 + Filesize uint64 + Used uint64 + SpecData [2]uint32 + FSID uint64 + Fileid uint64 + Atime, Mtime, Ctime FileTime +} + +// FileType represents a NFS File Type +type FileType uint32 + +// Enumeration of NFS FileTypes +const ( + FileTypeRegular FileType = iota + 1 + FileTypeDirectory + FileTypeBlock + FileTypeCharacter + FileTypeLink + FileTypeSocket + FileTypeFIFO +) + +func (f FileType) String() string { + switch f { + case FileTypeRegular: + return "Regular" + case FileTypeDirectory: + return "Directory" + case FileTypeBlock: + return "Block Device" + case FileTypeCharacter: + return "Character Device" + case FileTypeLink: + return "Symbolic Link" + case FileTypeSocket: + return "Socket" + case FileTypeFIFO: + return "FIFO" + default: + return "Unknown" + } +} + +// Mode provides the OS interpreted mode of the file attributes +func (f *FileAttribute) Mode() os.FileMode { + return os.FileMode(f.FileMode) +} + +// FileCacheAttribute is the subset of FileAttribute used by +// wcc_attr +type FileCacheAttribute struct { + Filesize uint64 + Mtime, Ctime FileTime +} + +// AsCache provides the wcc view of the file attributes +func (f FileAttribute) AsCache() *FileCacheAttribute { + wcc := FileCacheAttribute{ + Filesize: f.Filesize, + Mtime: f.Mtime, + Ctime: f.Ctime, + } + return &wcc +} + +// ToFileAttribute creates an NFS fattr3 struct from an OS.FileInfo +func ToFileAttribute(info os.FileInfo, filePath string) *FileAttribute { + f := FileAttribute{} + + m := info.Mode() + f.FileMode = uint32(m) + if info.IsDir() { + f.Type = FileTypeDirectory + } else if m&os.ModeSymlink != 0 { + f.Type = FileTypeLink + } else if m&os.ModeCharDevice != 0 { + f.Type = FileTypeCharacter + } else if m&os.ModeDevice != 0 { + f.Type = FileTypeBlock + } else if m&os.ModeSocket != 0 { + f.Type = FileTypeSocket + } else if m&os.ModeNamedPipe != 0 { + f.Type = FileTypeFIFO + } else { + f.Type = FileTypeRegular + } + // The number of hard links to the file. + f.Nlink = 1 + + if a := file.GetInfo(info); a != nil { + f.Nlink = a.Nlink + f.UID = a.UID + f.GID = a.GID + f.SpecData = [2]uint32{a.Major, a.Minor} + f.Fileid = a.Fileid + } else { + hasher := fnv.New64() + _, _ = hasher.Write([]byte(filePath)) + f.Fileid = hasher.Sum64() + } + + f.Filesize = uint64(info.Size()) + f.Used = uint64(info.Size()) + f.Atime = ToNFSTime(info.ModTime()) + f.Mtime = f.Atime + f.Ctime = f.Atime + return &f +} + +// tryStat attempts to create a FileAttribute from a path. +func tryStat(fs billy.Filesystem, path []string) *FileAttribute { + fullPath := fs.Join(path...) + attrs, err := fs.Lstat(fullPath) + if err != nil || attrs == nil { + Log.Errorf("err loading attrs for %s: %v", fs.Join(path...), err) + return nil + } + return ToFileAttribute(attrs, fullPath) +} + +// WriteWcc writes the `wcc_data` representation of an object. +func WriteWcc(writer io.Writer, pre *FileCacheAttribute, post *FileAttribute) error { + if pre == nil { + if err := xdr.Write(writer, uint32(0)); err != nil { + return err + } + } else { + if err := xdr.Write(writer, uint32(1)); err != nil { + return err + } + if err := xdr.Write(writer, *pre); err != nil { + return err + } + } + if post == nil { + if err := xdr.Write(writer, uint32(0)); err != nil { + return err + } + } else { + if err := xdr.Write(writer, uint32(1)); err != nil { + return err + } + if err := xdr.Write(writer, *post); err != nil { + return err + } + } + return nil +} + +// WritePostOpAttrs writes the `post_op_attr` representation of a files attributes +func WritePostOpAttrs(writer io.Writer, post *FileAttribute) error { + if post == nil { + if err := xdr.Write(writer, uint32(0)); err != nil { + return err + } + } else { + if err := xdr.Write(writer, uint32(1)); err != nil { + return err + } + if err := xdr.Write(writer, *post); err != nil { + return err + } + } + return nil +} + +// SetFileAttributes represents a command to update some metadata +// about a file. +type SetFileAttributes struct { + SetMode *uint32 + SetUID *uint32 + SetGID *uint32 + SetSize *uint64 + SetAtime *time.Time + SetMtime *time.Time +} + +// Apply uses a `Change` implementation to set defined attributes on a +// provided file. +func (s *SetFileAttributes) Apply(changer billy.Change, fs billy.Filesystem, file string) error { + curOS, err := fs.Lstat(file) + if errors.Is(err, os.ErrNotExist) { + return &NFSStatusError{NFSStatusNoEnt, os.ErrNotExist} + } else if errors.Is(err, os.ErrPermission) { + return &NFSStatusError{NFSStatusAccess, os.ErrPermission} + } else if err != nil { + return nil + } + curr := ToFileAttribute(curOS, file) + + if s.SetMode != nil { + mode := os.FileMode(*s.SetMode) & os.ModePerm + if mode != curr.Mode().Perm() { + if changer == nil { + return &NFSStatusError{NFSStatusNotSupp, os.ErrPermission} + } + if err := changer.Chmod(file, mode); err != nil { + if errors.Is(err, os.ErrPermission) { + return &NFSStatusError{NFSStatusAccess, os.ErrPermission} + } + return err + } + } + } + if s.SetUID != nil || s.SetGID != nil { + euid := curr.UID + if s.SetUID != nil { + euid = *s.SetUID + } + egid := curr.GID + if s.SetGID != nil { + egid = *s.SetGID + } + if euid != curr.UID || egid != curr.GID { + if changer == nil { + return &NFSStatusError{NFSStatusNotSupp, os.ErrPermission} + } + if err := changer.Lchown(file, int(euid), int(egid)); err != nil { + if errors.Is(err, os.ErrPermission) { + return &NFSStatusError{NFSStatusAccess, os.ErrPermission} + } + return err + } + } + } + if s.SetSize != nil { + if curr.Mode()&os.ModeSymlink != 0 { + return &NFSStatusError{NFSStatusNotSupp, os.ErrInvalid} + } + fp, err := fs.OpenFile(file, os.O_WRONLY|os.O_EXCL, 0) + if errors.Is(err, os.ErrPermission) { + return &NFSStatusError{NFSStatusAccess, err} + } else if err != nil { + return err + } + if *s.SetSize > math.MaxInt64 { + return &NFSStatusError{NFSStatusInval, os.ErrInvalid} + } + if err := fp.Truncate(int64(*s.SetSize)); err != nil { + return err + } + if err := fp.Close(); err != nil { + return err + } + } + + if s.SetAtime != nil || s.SetMtime != nil { + atime := curr.Atime.Native() + if s.SetAtime != nil { + atime = s.SetAtime + } + mtime := curr.Mtime.Native() + if s.SetMtime != nil { + mtime = s.SetMtime + } + if atime != curr.Atime.Native() || mtime != curr.Mtime.Native() { + if changer == nil { + return &NFSStatusError{NFSStatusNotSupp, os.ErrPermission} + } + if err := changer.Chtimes(file, *atime, *mtime); err != nil { + if errors.Is(err, os.ErrPermission) { + return &NFSStatusError{NFSStatusAccess, err} + } + return err + } + } + } + return nil +} + +// Mode returns a mode if specified or the provided default mode. +func (s *SetFileAttributes) Mode(def os.FileMode) os.FileMode { + if s.SetMode != nil { + return os.FileMode(*s.SetMode) & os.ModePerm + } + return def +} + +// ReadSetFileAttributes reads an sattr3 xdr stream into a go struct. +func ReadSetFileAttributes(r io.Reader) (*SetFileAttributes, error) { + attrs := SetFileAttributes{} + hasMode, err := xdr.ReadUint32(r) + if err != nil { + return nil, err + } + if hasMode != 0 { + mode, err := xdr.ReadUint32(r) + if err != nil { + return nil, err + } + attrs.SetMode = &mode + } + hasUID, err := xdr.ReadUint32(r) + if err != nil { + return nil, err + } + if hasUID != 0 { + uid, err := xdr.ReadUint32(r) + if err != nil { + return nil, err + } + attrs.SetUID = &uid + } + hasGID, err := xdr.ReadUint32(r) + if err != nil { + return nil, err + } + if hasGID != 0 { + gid, err := xdr.ReadUint32(r) + if err != nil { + return nil, err + } + attrs.SetGID = &gid + } + hasSize, err := xdr.ReadUint32(r) + if err != nil { + return nil, err + } + if hasSize != 0 { + var size uint64 + attrs.SetSize = &size + if err := xdr.Read(r, &size); err != nil { + return nil, err + } + } + aTime, err := xdr.ReadUint32(r) + if err != nil { + return nil, err + } + if aTime == 1 { + now := time.Now() + attrs.SetAtime = &now + } else if aTime == 2 { + t := FileTime{} + if err := xdr.Read(r, &t); err != nil { + return nil, err + } + attrs.SetAtime = t.Native() + } + mTime, err := xdr.ReadUint32(r) + if err != nil { + return nil, err + } + if mTime == 1 { + now := time.Now() + attrs.SetMtime = &now + } else if mTime == 2 { + t := FileTime{} + if err := xdr.Read(r, &t); err != nil { + return nil, err + } + attrs.SetMtime = t.Native() + } + return &attrs, nil +} diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/file/file.go b/vendor/github.com/blacknon/go-nfs-sshlib/file/file.go new file mode 100644 index 00000000..cdaad267 --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/file/file.go @@ -0,0 +1,25 @@ +package file + +import "os" + +type FileInfo struct { + Nlink uint32 + UID uint32 + GID uint32 + Major uint32 + Minor uint32 + Fileid uint64 +} + +// GetInfo extracts some non-standardized items from the result of a Stat call. +func GetInfo(fi os.FileInfo) *FileInfo { + sys := fi.Sys() + switch v := sys.(type) { + case FileInfo: + return &v + case *FileInfo: + return v + default: + return getOSFileInfo(fi) + } +} diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/file/file_unix.go b/vendor/github.com/blacknon/go-nfs-sshlib/file/file_unix.go new file mode 100644 index 00000000..148c23f7 --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/file/file_unix.go @@ -0,0 +1,24 @@ +//go:build darwin || dragonfly || freebsd || linux || nacl || netbsd || openbsd || solaris + +package file + +import ( + "os" + "syscall" + + "golang.org/x/sys/unix" +) + +func getOSFileInfo(info os.FileInfo) *FileInfo { + fi := &FileInfo{} + if s, ok := info.Sys().(*syscall.Stat_t); ok { + fi.Nlink = uint32(s.Nlink) + fi.UID = s.Uid + fi.GID = s.Gid + fi.Major = unix.Major(uint64(s.Rdev)) + fi.Minor = unix.Minor(uint64(s.Rdev)) + fi.Fileid = s.Ino + return fi + } + return nil +} diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/file/file_windows.go b/vendor/github.com/blacknon/go-nfs-sshlib/file/file_windows.go new file mode 100644 index 00000000..093cf253 --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/file/file_windows.go @@ -0,0 +1,12 @@ +//go:build windows + +package file + +import "os" + +func getOSFileInfo(info os.FileInfo) *FileInfo { + // https://godoc.org/golang.org/x/sys/windows#GetFileInformationByHandle + // can be potentially used to populate Nlink + + return nil +} diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/filesystem.go b/vendor/github.com/blacknon/go-nfs-sshlib/filesystem.go new file mode 100644 index 00000000..b2f8cb1f --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/filesystem.go @@ -0,0 +1,15 @@ +package nfs + +import "time" + +// FSStat returns metadata about a file system +type FSStat struct { + TotalSize uint64 + FreeSize uint64 + AvailableSize uint64 + TotalFiles uint64 + FreeFiles uint64 + AvailableFiles uint64 + // CacheHint is called "invarsec" in the nfs standard + CacheHint time.Duration +} diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/handler.go b/vendor/github.com/blacknon/go-nfs-sshlib/handler.go new file mode 100644 index 00000000..13a0eaf8 --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/handler.go @@ -0,0 +1,52 @@ +package nfs + +import ( + "context" + "io/fs" + "net" + + billy "github.com/go-git/go-billy/v5" +) + +// Handler represents the interface of the file system / vfs being exposed over NFS +type Handler interface { + // Required methods + + Mount(context.Context, net.Conn, MountRequest) (MountStatus, billy.Filesystem, []AuthFlavor) + + // Change can return 'nil' if filesystem is read-only + // If the returned value can be cast to `UnixChange`, mknod and link RPCs will be available. + Change(billy.Filesystem) billy.Change + + // Optional methods - generic helpers or trivial implementations can be sufficient depending on use case. + + // Fill in information about a file system's free space. + FSStat(context.Context, billy.Filesystem, *FSStat) error + + // represent file objects as opaque references + // Can be safely implemented via helpers/cachinghandler. + ToHandle(fs billy.Filesystem, path []string) []byte + FromHandle(fh []byte) (billy.Filesystem, []string, error) + InvalidateHandle(billy.Filesystem, []byte) error + + // How many handles can be safely maintained by the handler. + HandleLimit() int +} + +// UnixChange extends the billy `Change` interface with support for special files. +type UnixChange interface { + billy.Change + Mknod(path string, mode uint32, major uint32, minor uint32) error + Mkfifo(path string, mode uint32) error + Socket(path string) error + Link(path string, link string) error +} + +// CachingHandler represents the optional caching work that a user may wish to over-ride with +// their own implementations, but which can be otherwise provided through defaults. +type CachingHandler interface { + VerifierFor(path string, contents []fs.FileInfo) uint64 + + // fs.FileInfo needs to be sorted by Name(), nil in case of a cache-miss + DataForVerifier(path string, verifier uint64) []fs.FileInfo +} diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/helpers/cachinghandler.go b/vendor/github.com/blacknon/go-nfs-sshlib/helpers/cachinghandler.go new file mode 100644 index 00000000..a9881d5a --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/helpers/cachinghandler.go @@ -0,0 +1,199 @@ +package helpers + +import ( + "crypto/sha256" + "encoding/binary" + "io/fs" + "reflect" + + "github.com/blacknon/go-nfs-sshlib" + + "github.com/go-git/go-billy/v5" + "github.com/google/uuid" + lru "github.com/hashicorp/golang-lru/v2" +) + +// NewCachingHandler wraps a handler to provide a basic to/from-file handle cache. +func NewCachingHandler(h nfs.Handler, limit int) nfs.Handler { + return NewCachingHandlerWithVerifierLimit(h, limit, limit) +} + +// NewCachingHandlerWithVerifierLimit provides a basic to/from-file handle cache that can be tuned with a smaller cache of active directory listings. +func NewCachingHandlerWithVerifierLimit(h nfs.Handler, limit int, verifierLimit int) nfs.Handler { + if limit < 2 || verifierLimit < 2 { + nfs.Log.Warnf("Caching handler created with insufficient cache to support directory listing", "size", limit, "verifiers", verifierLimit) + } + cache, _ := lru.New[uuid.UUID, entry](limit) + reverseCache := make(map[string][]uuid.UUID) + verifiers, _ := lru.New[uint64, verifier](verifierLimit) + return &CachingHandler{ + Handler: h, + activeHandles: cache, + reverseHandles: reverseCache, + activeVerifiers: verifiers, + cacheLimit: limit, + } +} + +// CachingHandler implements to/from handle via an LRU cache. +type CachingHandler struct { + nfs.Handler + activeHandles *lru.Cache[uuid.UUID, entry] + reverseHandles map[string][]uuid.UUID + activeVerifiers *lru.Cache[uint64, verifier] + cacheLimit int +} + +type entry struct { + f billy.Filesystem + p []string +} + +// ToHandle takes a file and represents it with an opaque handle to reference it. +// In stateless nfs (when it's serving a unix fs) this can be the device + inode +// but we can generalize with a stateful local cache of handed out IDs. +func (c *CachingHandler) ToHandle(f billy.Filesystem, path []string) []byte { + joinedPath := f.Join(path...) + + if handle := c.searchReverseCache(f, joinedPath); handle != nil { + return handle + } + + id := uuid.New() + + newPath := make([]string, len(path)) + + copy(newPath, path) + evictedKey, evictedPath, ok := c.activeHandles.GetOldest() + if evicted := c.activeHandles.Add(id, entry{f, newPath}); evicted && ok { + rk := evictedPath.f.Join(evictedPath.p...) + c.evictReverseCache(rk, evictedKey) + } + + if _, ok := c.reverseHandles[joinedPath]; !ok { + c.reverseHandles[joinedPath] = []uuid.UUID{} + } + c.reverseHandles[joinedPath] = append(c.reverseHandles[joinedPath], id) + b, _ := id.MarshalBinary() + + return b +} + +// FromHandle converts from an opaque handle to the file it represents +func (c *CachingHandler) FromHandle(fh []byte) (billy.Filesystem, []string, error) { + id, err := uuid.FromBytes(fh) + if err != nil { + return nil, []string{}, err + } + + if f, ok := c.activeHandles.Get(id); ok { + for _, k := range c.activeHandles.Keys() { + candidate, _ := c.activeHandles.Peek(k) + if hasPrefix(f.p, candidate.p) { + _, _ = c.activeHandles.Get(k) + } + } + if ok { + newP := make([]string, len(f.p)) + copy(newP, f.p) + return f.f, newP, nil + } + } + return nil, []string{}, &nfs.NFSStatusError{NFSStatus: nfs.NFSStatusStale} +} + +func (c *CachingHandler) searchReverseCache(f billy.Filesystem, path string) []byte { + uuids, exists := c.reverseHandles[path] + + if !exists { + return nil + } + + for _, id := range uuids { + if candidate, ok := c.activeHandles.Get(id); ok { + if reflect.DeepEqual(candidate.f, f) { + return id[:] + } + } + } + + return nil +} + +func (c *CachingHandler) evictReverseCache(path string, handle uuid.UUID) { + uuids, exists := c.reverseHandles[path] + + if !exists { + return + } + for i, u := range uuids { + if u == handle { + uuids = append(uuids[:i], uuids[i+1:]...) + c.reverseHandles[path] = uuids + return + } + } +} + +func (c *CachingHandler) InvalidateHandle(fs billy.Filesystem, handle []byte) error { + //Remove from cache + id, _ := uuid.FromBytes(handle) + entry, ok := c.activeHandles.Get(id) + if ok { + rk := entry.f.Join(entry.p...) + c.evictReverseCache(rk, id) + } + c.activeHandles.Remove(id) + return nil +} + +// HandleLimit exports how many file handles can be safely stored by this cache. +func (c *CachingHandler) HandleLimit() int { + return c.cacheLimit +} + +func hasPrefix(path, prefix []string) bool { + if len(prefix) > len(path) { + return false + } + for i, e := range prefix { + if path[i] != e { + return false + } + } + return true +} + +type verifier struct { + path string + contents []fs.FileInfo +} + +func hashPathAndContents(path string, contents []fs.FileInfo) uint64 { + //calculate a cookie-verifier. + vHash := sha256.New() + + // Add the path to avoid collisions of directories with the same content + vHash.Write(binary.BigEndian.AppendUint64([]byte{}, uint64(len(path)))) + vHash.Write([]byte(path)) + + for _, c := range contents { + vHash.Write([]byte(c.Name())) // Never fails according to the docs + } + + verify := vHash.Sum(nil)[0:8] + return binary.BigEndian.Uint64(verify) +} + +func (c *CachingHandler) VerifierFor(path string, contents []fs.FileInfo) uint64 { + id := hashPathAndContents(path, contents) + c.activeVerifiers.Add(id, verifier{path, contents}) + return id +} + +func (c *CachingHandler) DataForVerifier(path string, id uint64) []fs.FileInfo { + if cache, ok := c.activeVerifiers.Get(id); ok { + return cache.contents + } + return nil +} diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/helpers/nullauthhandler.go b/vendor/github.com/blacknon/go-nfs-sshlib/helpers/nullauthhandler.go new file mode 100644 index 00000000..fcb03a2d --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/helpers/nullauthhandler.go @@ -0,0 +1,59 @@ +package helpers + +import ( + "context" + "net" + + "github.com/blacknon/go-nfs-sshlib" + "github.com/go-git/go-billy/v5" +) + +// NewNullAuthHandler creates a handler for the provided filesystem +func NewNullAuthHandler(fs billy.Filesystem) nfs.Handler { + return &NullAuthHandler{fs} +} + +// NullAuthHandler returns a NFS backing that exposes a given file system in response to all mount requests. +type NullAuthHandler struct { + fs billy.Filesystem +} + +// Mount backs Mount RPC Requests, allowing for access control policies. +func (h *NullAuthHandler) Mount(ctx context.Context, conn net.Conn, req nfs.MountRequest) (status nfs.MountStatus, hndl billy.Filesystem, auths []nfs.AuthFlavor) { + status = nfs.MountStatusOk + hndl = h.fs + auths = []nfs.AuthFlavor{nfs.AuthFlavorNull} + return +} + +// Change provides an interface for updating file attributes. +func (h *NullAuthHandler) Change(fs billy.Filesystem) billy.Change { + if c, ok := h.fs.(billy.Change); ok { + return c + } + return nil +} + +// FSStat provides information about a filesystem. +func (h *NullAuthHandler) FSStat(ctx context.Context, f billy.Filesystem, s *nfs.FSStat) error { + return nil +} + +// ToHandle handled by CachingHandler +func (h *NullAuthHandler) ToHandle(f billy.Filesystem, s []string) []byte { + return []byte{} +} + +// FromHandle handled by CachingHandler +func (h *NullAuthHandler) FromHandle([]byte) (billy.Filesystem, []string, error) { + return nil, []string{}, nil +} + +func (c *NullAuthHandler) InvalidateHandle(billy.Filesystem, []byte) error { + return nil +} + +// HandleLImit handled by cachingHandler +func (h *NullAuthHandler) HandleLimit() int { + return -1 +} diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/log.go b/vendor/github.com/blacknon/go-nfs-sshlib/log.go new file mode 100644 index 00000000..db594d38 --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/log.go @@ -0,0 +1,216 @@ +package nfs + +import ( + "fmt" + "log" + "os" +) + +var ( + Log Logger = &DefaultLogger{} +) + +type LogLevel int + +const ( + PanicLevel LogLevel = iota + FatalLevel + ErrorLevel + WarnLevel + InfoLevel + DebugLevel + TraceLevel + + panicLevelStr string = "[PANIC] " + fatalLevelStr string = "[FATAL] " + errorLevelStr string = "[ERROR] " + warnLevelStr string = "[WARN] " + infoLevelStr string = "[INFO] " + debugLevelStr string = "[DEBUG] " + traceLevelStr string = "[TRACE] " +) + +type Logger interface { + SetLevel(level LogLevel) + GetLevel() LogLevel + ParseLevel(level string) (LogLevel, error) + + Panic(args ...interface{}) + Fatal(args ...interface{}) + Error(args ...interface{}) + Warn(args ...interface{}) + Info(args ...interface{}) + Debug(args ...interface{}) + Trace(args ...interface{}) + Print(args ...interface{}) + + Panicf(format string, args ...interface{}) + Fatalf(format string, args ...interface{}) + Errorf(format string, args ...interface{}) + Warnf(format string, args ...interface{}) + Infof(format string, args ...interface{}) + Debugf(format string, args ...interface{}) + Tracef(format string, args ...interface{}) + Printf(format string, args ...interface{}) +} + +type DefaultLogger struct { + Level LogLevel +} + +func SetLogger(logger Logger) { + Log = logger +} + +func init() { + if os.Getenv("LOG_LEVEL") != "" { + if level, err := Log.ParseLevel(os.Getenv("LOG_LEVEL")); err == nil { + Log.SetLevel(level) + } + } else { + // set default log level to info + Log.SetLevel(InfoLevel) + } +} + +func (l *DefaultLogger) GetLevel() LogLevel { + return l.Level +} + +func (l *DefaultLogger) SetLevel(level LogLevel) { + l.Level = level +} + +func (l *DefaultLogger) ParseLevel(level string) (LogLevel, error) { + switch level { + case "panic": + return PanicLevel, nil + case "fatal": + return FatalLevel, nil + case "error": + return ErrorLevel, nil + case "warn": + return WarnLevel, nil + case "info": + return InfoLevel, nil + case "debug": + return DebugLevel, nil + case "trace": + return TraceLevel, nil + } + var ll LogLevel + return ll, fmt.Errorf("invalid log level %q", level) +} + +func (l *DefaultLogger) Panic(args ...interface{}) { + if l.Level < PanicLevel { + return + } + args = append([]interface{}{panicLevelStr}, args...) + log.Print(args...) +} + +func (l *DefaultLogger) Panicf(format string, args ...interface{}) { + if l.Level < PanicLevel { + return + } + log.Printf(panicLevelStr+format, args...) +} + +func (l *DefaultLogger) Fatal(args ...interface{}) { + if l.Level < FatalLevel { + return + } + args = append([]interface{}{fatalLevelStr}, args...) + log.Print(args...) +} + +func (l *DefaultLogger) Fatalf(format string, args ...interface{}) { + if l.Level < FatalLevel { + return + } + log.Printf(fatalLevelStr+format, args...) +} + +func (l *DefaultLogger) Error(args ...interface{}) { + if l.Level < ErrorLevel { + return + } + args = append([]interface{}{errorLevelStr}, args...) + log.Print(args...) +} + +func (l *DefaultLogger) Errorf(format string, args ...interface{}) { + if l.Level < ErrorLevel { + return + } + log.Printf(errorLevelStr+format, args...) +} + +func (l *DefaultLogger) Warn(args ...interface{}) { + if l.Level < WarnLevel { + return + } + args = append([]interface{}{warnLevelStr}, args...) + log.Print(args...) +} + +func (l *DefaultLogger) Warnf(format string, args ...interface{}) { + if l.Level < WarnLevel { + return + } + log.Printf(warnLevelStr+format, args...) +} + +func (l *DefaultLogger) Info(args ...interface{}) { + if l.Level < InfoLevel { + return + } + args = append([]interface{}{infoLevelStr}, args...) + log.Print(args...) +} + +func (l *DefaultLogger) Infof(format string, args ...interface{}) { + if l.Level < InfoLevel { + return + } + log.Printf(infoLevelStr+format, args...) +} + +func (l *DefaultLogger) Debug(args ...interface{}) { + if l.Level < DebugLevel { + return + } + args = append([]interface{}{debugLevelStr}, args...) + log.Print(args...) +} + +func (l *DefaultLogger) Debugf(format string, args ...interface{}) { + if l.Level < DebugLevel { + return + } + log.Printf(debugLevelStr+format, args...) +} + +func (l *DefaultLogger) Trace(args ...interface{}) { + if l.Level < TraceLevel { + return + } + args = append([]interface{}{traceLevelStr}, args...) + log.Print(args...) +} + +func (l *DefaultLogger) Tracef(format string, args ...interface{}) { + if l.Level < TraceLevel { + return + } + log.Printf(traceLevelStr+format, args...) +} + +func (l *DefaultLogger) Print(args ...interface{}) { + log.Print(args...) +} + +func (l *DefaultLogger) Printf(format string, args ...interface{}) { + log.Printf(format, args...) +} diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/mount.go b/vendor/github.com/blacknon/go-nfs-sshlib/mount.go new file mode 100644 index 00000000..e95d098c --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/mount.go @@ -0,0 +1,58 @@ +package nfs + +import ( + "bytes" + "context" + + "github.com/willscott/go-nfs-client/nfs/xdr" +) + +const ( + mountServiceID = 100005 +) + +func init() { + _ = RegisterMessageHandler(mountServiceID, uint32(MountProcNull), onMountNull) + _ = RegisterMessageHandler(mountServiceID, uint32(MountProcMount), onMount) + _ = RegisterMessageHandler(mountServiceID, uint32(MountProcUmnt), onUMount) +} + +func onMountNull(ctx context.Context, w *response, userHandle Handler) error { + return w.writeHeader(ResponseCodeSuccess) +} + +func onMount(ctx context.Context, w *response, userHandle Handler) error { + // TODO: auth check. + dirpath, err := xdr.ReadOpaque(w.req.Body) + if err != nil { + return err + } + mountReq := MountRequest{Header: w.req.Header, Dirpath: dirpath} + status, handle, flavors := userHandle.Mount(ctx, w.conn, mountReq) + + if err := w.writeHeader(ResponseCodeSuccess); err != nil { + return err + } + + writer := bytes.NewBuffer([]byte{}) + if err := xdr.Write(writer, uint32(status)); err != nil { + return err + } + + rootHndl := userHandle.ToHandle(handle, []string{}) + + if status == MountStatusOk { + _ = xdr.Write(writer, rootHndl) + _ = xdr.Write(writer, flavors) + } + return w.Write(writer.Bytes()) +} + +func onUMount(ctx context.Context, w *response, userHandle Handler) error { + _, err := xdr.ReadOpaque(w.req.Body) + if err != nil { + return err + } + + return w.writeHeader(ResponseCodeSuccess) +} diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/mountinterface.go b/vendor/github.com/blacknon/go-nfs-sshlib/mountinterface.go new file mode 100644 index 00000000..1dc39ee7 --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/mountinterface.go @@ -0,0 +1,90 @@ +package nfs + +import ( + "github.com/willscott/go-nfs-client/nfs/rpc" +) + +// FHSize is the maximum size of a FileHandle +const FHSize = 64 + +// MNTNameLen is the maximum size of a mount name +const MNTNameLen = 255 + +// MntPathLen is the maximum size of a mount path +const MntPathLen = 1024 + +// FileHandle maps to a fhandle3 +type FileHandle []byte + +// MountStatus defines the response to the Mount Procedure +type MountStatus uint32 + +// MountStatus Codes +const ( + MountStatusOk MountStatus = 0 + MountStatusErrPerm MountStatus = 1 + MountStatusErrNoEnt MountStatus = 2 + MountStatusErrIO MountStatus = 5 + MountStatusErrAcces MountStatus = 13 + MountStatusErrNotDir MountStatus = 20 + MountStatusErrInval MountStatus = 22 + MountStatusErrNameTooLong MountStatus = 63 + MountStatusErrNotSupp MountStatus = 10004 + MountStatusErrServerFault MountStatus = 10006 +) + +// MountProcedure is the valid RPC calls for the mount service. +type MountProcedure uint32 + +// MountProcedure Codes +const ( + MountProcNull MountProcedure = iota + MountProcMount + MountProcDump + MountProcUmnt + MountProcUmntAll + MountProcExport +) + +func (m MountProcedure) String() string { + switch m { + case MountProcNull: + return "Null" + case MountProcMount: + return "Mount" + case MountProcDump: + return "Dump" + case MountProcUmnt: + return "Umnt" + case MountProcUmntAll: + return "UmntAll" + case MountProcExport: + return "Export" + default: + return "Unknown" + } +} + +// AuthFlavor is a form of authentication, per rfc1057 section 7.2 +type AuthFlavor uint32 + +// AuthFlavor Codes +const ( + AuthFlavorNull AuthFlavor = 0 + AuthFlavorUnix AuthFlavor = 1 + AuthFlavorShort AuthFlavor = 2 + AuthFlavorDES AuthFlavor = 3 +) + +// MountRequest contains the format of a client request to open a mount. +type MountRequest struct { + rpc.Header + Dirpath []byte +} + +// MountResponse is the server's response with status `MountStatusOk` +type MountResponse struct { + rpc.Header + FileHandle + AuthFlavors []int +} diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/nfs.go b/vendor/github.com/blacknon/go-nfs-sshlib/nfs.go new file mode 100644 index 00000000..bf85e0a2 --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/nfs.go @@ -0,0 +1,38 @@ +package nfs + +import ( + "context" +) + +const ( + nfsServiceID = 100003 +) + +func init() { + _ = RegisterMessageHandler(nfsServiceID, uint32(NFSProcedureNull), onNull) // 0 + _ = RegisterMessageHandler(nfsServiceID, uint32(NFSProcedureGetAttr), onGetAttr) // 1 + _ = RegisterMessageHandler(nfsServiceID, uint32(NFSProcedureSetAttr), onSetAttr) // 2 + _ = RegisterMessageHandler(nfsServiceID, uint32(NFSProcedureLookup), onLookup) // 3 + _ = RegisterMessageHandler(nfsServiceID, uint32(NFSProcedureAccess), onAccess) // 4 + _ = RegisterMessageHandler(nfsServiceID, uint32(NFSProcedureReadlink), onReadLink) // 5 + _ = RegisterMessageHandler(nfsServiceID, uint32(NFSProcedureRead), onRead) // 6 + _ = RegisterMessageHandler(nfsServiceID, uint32(NFSProcedureWrite), onWrite) // 7 + _ = RegisterMessageHandler(nfsServiceID, uint32(NFSProcedureCreate), onCreate) // 8 + _ = RegisterMessageHandler(nfsServiceID, uint32(NFSProcedureMkDir), onMkdir) // 9 + _ = RegisterMessageHandler(nfsServiceID, uint32(NFSProcedureSymlink), onSymlink) // 10 + _ = RegisterMessageHandler(nfsServiceID, uint32(NFSProcedureMkNod), onMknod) // 11 + _ = RegisterMessageHandler(nfsServiceID, uint32(NFSProcedureRemove), onRemove) // 12 + _ = RegisterMessageHandler(nfsServiceID, uint32(NFSProcedureRmDir), onRmDir) // 13 + _ = RegisterMessageHandler(nfsServiceID, uint32(NFSProcedureRename), onRename) // 14 + _ = RegisterMessageHandler(nfsServiceID, uint32(NFSProcedureLink), onLink) // 15 + _ = RegisterMessageHandler(nfsServiceID, uint32(NFSProcedureReadDir), onReadDir) // 16 + _ = RegisterMessageHandler(nfsServiceID, uint32(NFSProcedureReadDirPlus), onReadDirPlus) // 17 + _ = RegisterMessageHandler(nfsServiceID, uint32(NFSProcedureFSStat), onFSStat) // 18 + _ = RegisterMessageHandler(nfsServiceID, uint32(NFSProcedureFSInfo), onFSInfo) // 19 + _ = RegisterMessageHandler(nfsServiceID, uint32(NFSProcedurePathConf), onPathConf) // 20 + _ = RegisterMessageHandler(nfsServiceID, uint32(NFSProcedureCommit), onCommit) // 21 +} + +func onNull(ctx context.Context, w *response, userHandle Handler) error { + return w.Write([]byte{}) +} diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onaccess.go b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onaccess.go new file mode 100644 index 00000000..b29d62e3 --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onaccess.go @@ -0,0 +1,45 @@ +package nfs + +import ( + "bytes" + "context" + + "github.com/go-git/go-billy/v5" + "github.com/willscott/go-nfs-client/nfs/xdr" +) + +func onAccess(ctx context.Context, w *response, userHandle Handler) error { + w.errorFmt = opAttrErrorFormatter + roothandle, err := xdr.ReadOpaque(w.req.Body) + if err != nil { + return &NFSStatusError{NFSStatusInval, err} + } + fs, path, err := userHandle.FromHandle(roothandle) + if err != nil { + return &NFSStatusError{NFSStatusStale, err} + } + mask, err := xdr.ReadUint32(w.req.Body) + if err != nil { + return &NFSStatusError{NFSStatusInval, err} + } + + writer := bytes.NewBuffer([]byte{}) + if err := xdr.Write(writer, uint32(NFSStatusOk)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + if err := WritePostOpAttrs(writer, tryStat(fs, path)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + + if !billy.CapabilityCheck(fs, billy.WriteCapability) { + mask = mask & (1 | 2 | 0x20) + } + + if err := xdr.Write(writer, mask); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + if err := w.Write(writer.Bytes()); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + return nil +} diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/nfs_oncommit.go b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_oncommit.go new file mode 100644 index 00000000..0d66837a --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_oncommit.go @@ -0,0 +1,50 @@ +package nfs + +import ( + "bytes" + "context" + "os" + + "github.com/go-git/go-billy/v5" + "github.com/willscott/go-nfs-client/nfs/xdr" +) + +// onCommit - note this is a no-op, as we always push writes to the backing store. +func onCommit(ctx context.Context, w *response, userHandle Handler) error { + w.errorFmt = wccDataErrorFormatter + handle, err := xdr.ReadOpaque(w.req.Body) + if err != nil { + return &NFSStatusError{NFSStatusInval, err} + } + // The conn will drain the unread offset and count arguments. + + fs, path, err := userHandle.FromHandle(handle) + if err != nil { + return &NFSStatusError{NFSStatusStale, err} + } + if !billy.CapabilityCheck(fs, billy.WriteCapability) { + return &NFSStatusError{NFSStatusServerFault, os.ErrPermission} + } + + writer := bytes.NewBuffer([]byte{}) + if err := xdr.Write(writer, uint32(NFSStatusOk)); err != nil { + return err + } + + // no pre-op cache data. + if err := xdr.Write(writer, uint32(0)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + if err := WritePostOpAttrs(writer, tryStat(fs, path)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + // write the 8 bytes of write verification. + if err := xdr.Write(writer, w.Server.ID); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + + if err := w.Write(writer.Bytes()); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + return nil +} diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/nfs_oncreate.go b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_oncreate.go new file mode 100644 index 00000000..63d7901f --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_oncreate.go @@ -0,0 +1,124 @@ +package nfs + +import ( + "bytes" + "context" + "os" + + "github.com/go-git/go-billy/v5" + "github.com/willscott/go-nfs-client/nfs/xdr" +) + +const ( + createModeUnchecked = 0 + createModeGuarded = 1 + createModeExclusive = 2 +) + +func onCreate(ctx context.Context, w *response, userHandle Handler) error { + w.errorFmt = wccDataErrorFormatter + obj := DirOpArg{} + err := xdr.Read(w.req.Body, &obj) + if err != nil { + return &NFSStatusError{NFSStatusInval, err} + } + how, err := xdr.ReadUint32(w.req.Body) + if err != nil { + return &NFSStatusError{NFSStatusInval, err} + } + var attrs *SetFileAttributes + if how == createModeUnchecked || how == createModeGuarded { + sattr, err := ReadSetFileAttributes(w.req.Body) + if err != nil { + return &NFSStatusError{NFSStatusInval, err} + } + attrs = sattr + } else if how == createModeExclusive { + // read createverf3 + var verf [8]byte + if err := xdr.Read(w.req.Body, &verf); err != nil { + return &NFSStatusError{NFSStatusInval, err} + } + Log.Errorf("failing create to indicate lack of support for 'exclusive' mode.") + // TODO: support 'exclusive' mode. + return &NFSStatusError{NFSStatusNotSupp, os.ErrPermission} + } else { + // invalid + return &NFSStatusError{NFSStatusNotSupp, os.ErrInvalid} + } + + fs, path, err := userHandle.FromHandle(obj.Handle) + if err != nil { + return &NFSStatusError{NFSStatusStale, err} + } + if !billy.CapabilityCheck(fs, billy.WriteCapability) { + return &NFSStatusError{NFSStatusROFS, os.ErrPermission} + } + + if len(string(obj.Filename)) > PathNameMax { + return &NFSStatusError{NFSStatusNameTooLong, nil} + } + + newFile := append(path, string(obj.Filename)) + newFilePath := fs.Join(newFile...) + if s, err := fs.Stat(newFilePath); err == nil { + if s.IsDir() { + return &NFSStatusError{NFSStatusExist, nil} + } + if how == createModeGuarded { + return &NFSStatusError{NFSStatusExist, os.ErrPermission} + } + } else { + if s, err := fs.Stat(fs.Join(path...)); err != nil { + return &NFSStatusError{NFSStatusAccess, err} + } else if !s.IsDir() { + return &NFSStatusError{NFSStatusNotDir, nil} + } + } + + file, err := fs.Create(newFilePath) + if err != nil { + Log.Errorf("Error Creating: %v", err) + return &NFSStatusError{NFSStatusAccess, err} + } + if err := file.Close(); err != nil { + Log.Errorf("Error Creating: %v", err) + return &NFSStatusError{NFSStatusAccess, err} + } + + fp := userHandle.ToHandle(fs, newFile) + changer := userHandle.Change(fs) + if err := attrs.Apply(changer, fs, newFilePath); err != nil { + Log.Errorf("Error applying attributes: %v\n", err) + return &NFSStatusError{NFSStatusIO, err} + } + + writer := bytes.NewBuffer([]byte{}) + if err := xdr.Write(writer, uint32(NFSStatusOk)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + + // "handle follows" + if err := xdr.Write(writer, uint32(1)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + if err := xdr.Write(writer, fp); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + if err := WritePostOpAttrs(writer, tryStat(fs, []string{file.Name()})); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + + // dir_wcc (we don't include pre_op_attr) + if err := xdr.Write(writer, uint32(0)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + if err := WritePostOpAttrs(writer, tryStat(fs, path)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + + if err := w.Write(writer.Bytes()); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + return nil +} diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onfsinfo.go b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onfsinfo.go new file mode 100644 index 00000000..d9821149 --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onfsinfo.go @@ -0,0 +1,87 @@ +package nfs + +import ( + "bytes" + "context" + + "github.com/go-git/go-billy/v5" + "github.com/willscott/go-nfs-client/nfs/xdr" +) + +const ( + // FSInfoPropertyLink does the FS support hard links? + FSInfoPropertyLink = 0x0001 + // FSInfoPropertySymlink does the FS support soft links? + FSInfoPropertySymlink = 0x0002 + // FSInfoPropertyHomogeneous does the FS need PATHCONF calls for each file + FSInfoPropertyHomogeneous = 0x0008 + // FSInfoPropertyCanSetTime can the FS support setting access/mod times? + FSInfoPropertyCanSetTime = 0x0010 +) + +func onFSInfo(ctx context.Context, w *response, userHandle Handler) error { + roothandle, err := xdr.ReadOpaque(w.req.Body) + if err != nil { + return &NFSStatusError{NFSStatusInval, err} + } + fs, path, err := userHandle.FromHandle(roothandle) + if err != nil { + return &NFSStatusError{NFSStatusStale, err} + } + + writer := bytes.NewBuffer([]byte{}) + if err := xdr.Write(writer, uint32(NFSStatusOk)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + if err := WritePostOpAttrs(writer, tryStat(fs, path)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + + type fsinfores struct { + Rtmax uint32 + Rtpref uint32 + Rtmult uint32 + Wtmax uint32 + Wtpref uint32 + Wtmult uint32 + Dtpref uint32 + Maxfilesize uint64 + TimeDelta uint64 + Properties uint32 + } + + res := fsinfores{ + Rtmax: 1 << 30, + Rtpref: 1 << 30, + Rtmult: 4096, + Wtmax: 1 << 30, + Wtpref: 1 << 30, + Wtmult: 4096, + Dtpref: 8192, + Maxfilesize: 1 << 62, // wild guess. this seems big. + TimeDelta: 1, // nanosecond precision. + Properties: 0, + } + + // TODO: these aren't great indications of support, really. + if _, ok := fs.(billy.Symlink); ok { + res.Properties |= FSInfoPropertyLink + res.Properties |= FSInfoPropertySymlink + } + // TODO: if the nfs share spans multiple virtual mounts, may need + // to support granular PATHINFO responses. + res.Properties |= FSInfoPropertyHomogeneous + // TODO: not a perfect indicator + if billy.CapabilityCheck(fs, billy.WriteCapability) { + res.Properties |= FSInfoPropertyCanSetTime + } + // TODO: this whole struct should be specifiable by the userhandler. + + if err := xdr.Write(writer, res); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + if err := w.Write(writer.Bytes()); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + return nil +} diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onfsstat.go b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onfsstat.go new file mode 100644 index 00000000..9e85e408 --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onfsstat.go @@ -0,0 +1,58 @@ +package nfs + +import ( + "bytes" + "context" + + "github.com/go-git/go-billy/v5" + "github.com/willscott/go-nfs-client/nfs/xdr" +) + +func onFSStat(ctx context.Context, w *response, userHandle Handler) error { + roothandle, err := xdr.ReadOpaque(w.req.Body) + if err != nil { + return &NFSStatusError{NFSStatusInval, err} + } + fs, path, err := userHandle.FromHandle(roothandle) + if err != nil { + return &NFSStatusError{NFSStatusStale, err} + } + + defaults := FSStat{ + TotalSize: 1 << 62, + FreeSize: 1 << 62, + AvailableSize: 1 << 62, + TotalFiles: 1 << 62, + FreeFiles: 1 << 62, + AvailableFiles: 1 << 62, + CacheHint: 0, + } + if !billy.CapabilityCheck(fs, billy.WriteCapability) { + defaults.AvailableFiles = 0 + defaults.AvailableSize = 0 + } + + err = userHandle.FSStat(ctx, fs, &defaults) + if err != nil { + if _, ok := err.(*NFSStatusError); ok { + return err + } + return &NFSStatusError{NFSStatusServerFault, err} + } + + writer := bytes.NewBuffer([]byte{}) + if err := xdr.Write(writer, uint32(NFSStatusOk)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + if err := WritePostOpAttrs(writer, tryStat(fs, path)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + + if err := xdr.Write(writer, defaults); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + if err := w.Write(writer.Bytes()); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + return nil +} diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/nfs_ongetattr.go b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_ongetattr.go new file mode 100644 index 00000000..5ecc2401 --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_ongetattr.go @@ -0,0 +1,44 @@ +package nfs + +import ( + "bytes" + "context" + "os" + + "github.com/willscott/go-nfs-client/nfs/xdr" +) + +func onGetAttr(ctx context.Context, w *response, userHandle Handler) error { + handle, err := xdr.ReadOpaque(w.req.Body) + if err != nil { + return &NFSStatusError{NFSStatusInval, err} + } + + fs, path, err := userHandle.FromHandle(handle) + if err != nil { + return &NFSStatusError{NFSStatusStale, err} + } + + fullPath := fs.Join(path...) + info, err := fs.Lstat(fullPath) + if err != nil { + if os.IsNotExist(err) { + return &NFSStatusError{NFSStatusNoEnt, err} + } + return &NFSStatusError{NFSStatusIO, err} + } + attr := ToFileAttribute(info, fullPath) + + writer := bytes.NewBuffer([]byte{}) + if err := xdr.Write(writer, uint32(NFSStatusOk)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + if err := xdr.Write(writer, attr); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + + if err := w.Write(writer.Bytes()); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + return nil +} diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onlink.go b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onlink.go new file mode 100644 index 00000000..ed52df5f --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onlink.go @@ -0,0 +1,94 @@ +package nfs + +import ( + "bytes" + "context" + "os" + + "github.com/go-git/go-billy/v5" + "github.com/willscott/go-nfs-client/nfs/xdr" +) + +// Backing billy.FS doesn't support hard links +func onLink(ctx context.Context, w *response, userHandle Handler) error { + w.errorFmt = wccDataErrorFormatter + obj := DirOpArg{} + err := xdr.Read(w.req.Body, &obj) + if err != nil { + return &NFSStatusError{NFSStatusInval, err} + } + attrs, err := ReadSetFileAttributes(w.req.Body) + if err != nil { + return &NFSStatusError{NFSStatusInval, err} + } + + target, err := xdr.ReadOpaque(w.req.Body) + if err != nil { + return &NFSStatusError{NFSStatusInval, err} + } + + fs, path, err := userHandle.FromHandle(obj.Handle) + if err != nil { + return &NFSStatusError{NFSStatusStale, err} + } + if !billy.CapabilityCheck(fs, billy.WriteCapability) { + return &NFSStatusError{NFSStatusROFS, os.ErrPermission} + } + + if len(string(obj.Filename)) > PathNameMax { + return &NFSStatusError{NFSStatusNameTooLong, os.ErrInvalid} + } + + newFilePath := fs.Join(append(path, string(obj.Filename))...) + if _, err := fs.Stat(newFilePath); err == nil { + return &NFSStatusError{NFSStatusExist, os.ErrExist} + } + if s, err := fs.Stat(fs.Join(path...)); err != nil { + return &NFSStatusError{NFSStatusAccess, err} + } else if !s.IsDir() { + return &NFSStatusError{NFSStatusNotDir, nil} + } + + fp := userHandle.ToHandle(fs, append(path, string(obj.Filename))) + changer := userHandle.Change(fs) + if changer == nil { + return &NFSStatusError{NFSStatusAccess, err} + } + cos, ok := changer.(UnixChange) + if !ok { + return &NFSStatusError{NFSStatusAccess, err} + } + + err = cos.Link(string(target), newFilePath) + if err != nil { + return &NFSStatusError{NFSStatusAccess, err} + } + if err := attrs.Apply(changer, fs, newFilePath); err != nil { + return &NFSStatusError{NFSStatusIO, err} + } + + writer := bytes.NewBuffer([]byte{}) + if err := xdr.Write(writer, uint32(NFSStatusOk)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + + // "handle follows" + if err := xdr.Write(writer, uint32(1)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + if err := xdr.Write(writer, fp); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + if err := WritePostOpAttrs(writer, tryStat(fs, append(path, string(obj.Filename)))); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + + if err := WriteWcc(writer, nil, tryStat(fs, path)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + + if err := w.Write(writer.Bytes()); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + return nil +} diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onlookup.go b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onlookup.go new file mode 100644 index 00000000..66545881 --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onlookup.go @@ -0,0 +1,87 @@ +package nfs + +import ( + "bytes" + "context" + "os" + + "github.com/go-git/go-billy/v5" + "github.com/willscott/go-nfs-client/nfs/xdr" +) + +func lookupSuccessResponse(handle []byte, entPath, dirPath []string, fs billy.Filesystem) ([]byte, error) { + writer := bytes.NewBuffer([]byte{}) + if err := xdr.Write(writer, uint32(NFSStatusOk)); err != nil { + return nil, err + } + if err := xdr.Write(writer, handle); err != nil { + return nil, err + } + if err := WritePostOpAttrs(writer, tryStat(fs, entPath)); err != nil { + return nil, err + } + if err := WritePostOpAttrs(writer, tryStat(fs, dirPath)); err != nil { + return nil, err + } + return writer.Bytes(), nil +} + +func onLookup(ctx context.Context, w *response, userHandle Handler) error { + w.errorFmt = opAttrErrorFormatter + obj := DirOpArg{} + err := xdr.Read(w.req.Body, &obj) + if err != nil { + return &NFSStatusError{NFSStatusInval, err} + } + + fs, p, err := userHandle.FromHandle(obj.Handle) + if err != nil { + return &NFSStatusError{NFSStatusStale, err} + } + dirInfo, err := fs.Lstat(fs.Join(p...)) + if err != nil || !dirInfo.IsDir() { + return &NFSStatusError{NFSStatusNotDir, err} + } + + // Special cases for "." and ".." + if bytes.Equal(obj.Filename, []byte(".")) { + resp, err := lookupSuccessResponse(obj.Handle, p, p, fs) + if err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + if err := w.Write(resp); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + return nil + } + if bytes.Equal(obj.Filename, []byte("..")) { + if len(p) == 0 { + return &NFSStatusError{NFSStatusAccess, os.ErrPermission} + } + pPath := p[0 : len(p)-1] + pHandle := userHandle.ToHandle(fs, pPath) + resp, err := lookupSuccessResponse(pHandle, pPath, p, fs) + if err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + if err := w.Write(resp); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + return nil + } + + reqPath := append(p, string(obj.Filename)) + if _, err = fs.Lstat(fs.Join(reqPath...)); err != nil { + return &NFSStatusError{NFSStatusNoEnt, os.ErrNotExist} + } + + newHandle := userHandle.ToHandle(fs, reqPath) + resp, err := lookupSuccessResponse(newHandle, reqPath, p, fs) + if err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + if err := w.Write(resp); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + return nil +} diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onmkdir.go b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onmkdir.go new file mode 100644 index 00000000..35059b27 --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onmkdir.go @@ -0,0 +1,94 @@ +package nfs + +import ( + "bytes" + "context" + "os" + + "github.com/go-git/go-billy/v5" + "github.com/willscott/go-nfs-client/nfs/xdr" +) + +const ( + mkdirDefaultMode = 755 +) + +func onMkdir(ctx context.Context, w *response, userHandle Handler) error { + w.errorFmt = wccDataErrorFormatter + obj := DirOpArg{} + err := xdr.Read(w.req.Body, &obj) + if err != nil { + return &NFSStatusError{NFSStatusInval, err} + } + + attrs, err := ReadSetFileAttributes(w.req.Body) + if err != nil { + return &NFSStatusError{NFSStatusInval, err} + } + + fs, path, err := userHandle.FromHandle(obj.Handle) + if err != nil { + return &NFSStatusError{NFSStatusStale, err} + } + if !billy.CapabilityCheck(fs, billy.WriteCapability) { + return &NFSStatusError{NFSStatusROFS, os.ErrPermission} + } + + if len(string(obj.Filename)) > PathNameMax { + return &NFSStatusError{NFSStatusNameTooLong, os.ErrInvalid} + } + if string(obj.Filename) == "." || string(obj.Filename) == ".." { + return &NFSStatusError{NFSStatusExist, os.ErrExist} + } + + newFolder := append(path, string(obj.Filename)) + newFolderPath := fs.Join(newFolder...) + if s, err := fs.Stat(newFolderPath); err == nil { + if s.IsDir() { + return &NFSStatusError{NFSStatusExist, nil} + } + } else { + if s, err := fs.Stat(fs.Join(path...)); err != nil { + return &NFSStatusError{NFSStatusAccess, err} + } else if !s.IsDir() { + return &NFSStatusError{NFSStatusNotDir, nil} + } + } + + if err := fs.MkdirAll(newFolderPath, attrs.Mode(mkdirDefaultMode)); err != nil { + return &NFSStatusError{NFSStatusAccess, err} + } + + fp := userHandle.ToHandle(fs, newFolder) + changer := userHandle.Change(fs) + if changer != nil { + if err := attrs.Apply(changer, fs, newFolderPath); err != nil { + return &NFSStatusError{NFSStatusIO, err} + } + } + + writer := bytes.NewBuffer([]byte{}) + if err := xdr.Write(writer, uint32(NFSStatusOk)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + + // "handle follows" + if err := xdr.Write(writer, uint32(1)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + if err := xdr.Write(writer, fp); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + if err := WritePostOpAttrs(writer, tryStat(fs, newFolder)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + + if err := WriteWcc(writer, nil, tryStat(fs, path)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + + if err := w.Write(writer.Bytes()); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + return nil +} diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onmknod.go b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onmknod.go new file mode 100644 index 00000000..b93855e4 --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onmknod.go @@ -0,0 +1,156 @@ +package nfs + +import ( + "bytes" + "context" + "os" + + "github.com/go-git/go-billy/v5" + "github.com/willscott/go-nfs-client/nfs/xdr" +) + +type nfs_ftype int32 + +const ( + FTYPE_NF3REG nfs_ftype = 1 + FTYPE_NF3DIR nfs_ftype = 2 + FTYPE_NF3BLK nfs_ftype = 3 + FTYPE_NF3CHR nfs_ftype = 4 + FTYPE_NF3LNK nfs_ftype = 5 + FTYPE_NF3SOCK nfs_ftype = 6 + FTYPE_NF3FIFO nfs_ftype = 7 +) + +// Backing billy.FS doesn't support creation of +// char, block, socket, or fifo pipe nodes +func onMknod(ctx context.Context, w *response, userHandle Handler) error { + w.errorFmt = wccDataErrorFormatter + obj := DirOpArg{} + err := xdr.Read(w.req.Body, &obj) + if err != nil { + return &NFSStatusError{NFSStatusInval, err} + } + + ftype, err := xdr.ReadUint32(w.req.Body) + if err != nil { + return &NFSStatusError{NFSStatusInval, err} + } + + // see if the filesystem supports mknod + fs, path, err := userHandle.FromHandle(obj.Handle) + if err != nil { + return &NFSStatusError{NFSStatusStale, err} + } + if !billy.CapabilityCheck(fs, billy.WriteCapability) { + return &NFSStatusError{NFSStatusROFS, os.ErrPermission} + } + c := userHandle.Change(fs) + if c == nil { + return &NFSStatusError{NFSStatusAccess, os.ErrPermission} + } + cu, ok := c.(UnixChange) + if !ok { + return &NFSStatusError{NFSStatusAccess, os.ErrPermission} + } + + if len(string(obj.Filename)) > PathNameMax { + return &NFSStatusError{NFSStatusNameTooLong, os.ErrInvalid} + } + + newFilePath := fs.Join(append(path, string(obj.Filename))...) + if _, err := fs.Stat(newFilePath); err == nil { + return &NFSStatusError{NFSStatusExist, os.ErrExist} + } + parent, err := fs.Stat(fs.Join(path...)) + if err != nil { + return &NFSStatusError{NFSStatusAccess, err} + } else if !parent.IsDir() { + return &NFSStatusError{NFSStatusNotDir, nil} + } + fp := userHandle.ToHandle(fs, append(path, string(obj.Filename))) + + switch nfs_ftype(ftype) { + case FTYPE_NF3CHR: + case FTYPE_NF3BLK: + // read devicedata3 = {sattr3, specdata3} + attrs, err := ReadSetFileAttributes(w.req.Body) + if err != nil { + return &NFSStatusError{NFSStatusInval, err} + } + specData1, err := xdr.ReadUint32(w.req.Body) + if err != nil { + return &NFSStatusError{NFSStatusInval, err} + } + specData2, err := xdr.ReadUint32(w.req.Body) + if err != nil { + return &NFSStatusError{NFSStatusInval, err} + } + + err = cu.Mknod(newFilePath, uint32(attrs.Mode(parent.Mode())), specData1, specData2) + if err != nil { + return &NFSStatusError{NFSStatusAccess, err} + } + if err = attrs.Apply(cu, fs, newFilePath); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + + case FTYPE_NF3SOCK: + // read sattr3 + attrs, err := ReadSetFileAttributes(w.req.Body) + if err != nil { + return &NFSStatusError{NFSStatusInval, err} + } + if err := cu.Socket(newFilePath); err != nil { + return &NFSStatusError{NFSStatusAccess, err} + } + if err = attrs.Apply(cu, fs, newFilePath); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + + case FTYPE_NF3FIFO: + // read sattr3 + attrs, err := ReadSetFileAttributes(w.req.Body) + if err != nil { + return &NFSStatusError{NFSStatusInval, err} + } + err = cu.Mkfifo(newFilePath, uint32(attrs.Mode(parent.Mode()))) + if err != nil { + return &NFSStatusError{NFSStatusAccess, err} + } + if err = attrs.Apply(cu, fs, newFilePath); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + + default: + return &NFSStatusError{NFSStatusBadType, os.ErrInvalid} + // end of input. + } + + writer := bytes.NewBuffer([]byte{}) + if err := xdr.Write(writer, uint32(NFSStatusOk)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + + // "handle follows" + if err := xdr.Write(writer, uint32(1)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + // fh3 + if err := xdr.Write(writer, fp); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + // attr + if err := WritePostOpAttrs(writer, tryStat(fs, append(path, string(obj.Filename)))); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + // wcc + if err := WriteWcc(writer, nil, tryStat(fs, path)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + + if err := w.Write(writer.Bytes()); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + + return nil +} diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onpathconf.go b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onpathconf.go new file mode 100644 index 00000000..68973dd7 --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onpathconf.go @@ -0,0 +1,55 @@ +package nfs + +import ( + "bytes" + "context" + + "github.com/willscott/go-nfs-client/nfs/xdr" +) + +// PathNameMax is the maximum length for a file name +const PathNameMax = 255 + +func onPathConf(ctx context.Context, w *response, userHandle Handler) error { + roothandle, err := xdr.ReadOpaque(w.req.Body) + if err != nil { + return &NFSStatusError{NFSStatusInval, err} + } + fs, path, err := userHandle.FromHandle(roothandle) + if err != nil { + return &NFSStatusError{NFSStatusStale, err} + } + + writer := bytes.NewBuffer([]byte{}) + if err := xdr.Write(writer, uint32(NFSStatusOk)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + if err := WritePostOpAttrs(writer, tryStat(fs, path)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + + type PathConf struct { + LinkMax uint32 + NameMax uint32 + NoTrunc uint32 + ChownRestricted uint32 + CaseInsensitive uint32 + CasePreserving uint32 + } + + defaults := PathConf{ + LinkMax: 1, + NameMax: PathNameMax, + NoTrunc: 1, + ChownRestricted: 0, + CaseInsensitive: 0, + CasePreserving: 1, + } + if err := xdr.Write(writer, defaults); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + if err := w.Write(writer.Bytes()); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + return nil +} diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onread.go b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onread.go new file mode 100644 index 00000000..23b3e424 --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onread.go @@ -0,0 +1,94 @@ +package nfs + +import ( + "bytes" + "context" + "errors" + "io" + "os" + + "github.com/willscott/go-nfs-client/nfs/xdr" +) + +type nfsReadArgs struct { + Handle []byte + Offset uint64 + Count uint32 +} + +type nfsReadResponse struct { + Count uint32 + EOF uint32 + Data []byte +} + +// MaxRead is the advertised largest buffer the server is willing to read +const MaxRead = 1 << 24 + +// CheckRead is a size where - if a request to read is larger than this, +// the server will stat the file to learn it's actual size before allocating +// a buffer to read into. +const CheckRead = 1 << 15 + +func onRead(ctx context.Context, w *response, userHandle Handler) error { + w.errorFmt = opAttrErrorFormatter + var obj nfsReadArgs + err := xdr.Read(w.req.Body, &obj) + if err != nil { + return &NFSStatusError{NFSStatusInval, err} + } + fs, path, err := userHandle.FromHandle(obj.Handle) + if err != nil { + return &NFSStatusError{NFSStatusStale, err} + } + + fh, err := fs.Open(fs.Join(path...)) + if err != nil { + if os.IsNotExist(err) { + return &NFSStatusError{NFSStatusNoEnt, err} + } + return &NFSStatusError{NFSStatusAccess, err} + } + + resp := nfsReadResponse{} + + if obj.Count > CheckRead { + info, err := fs.Stat(fs.Join(path...)) + if err != nil { + return &NFSStatusError{NFSStatusAccess, err} + } + if info.Size()-int64(obj.Offset) < int64(obj.Count) { + obj.Count = uint32(uint64(info.Size()) - obj.Offset) + } + } + if obj.Count > MaxRead { + obj.Count = MaxRead + } + resp.Data = make([]byte, obj.Count) + // todo: multiple reads if size isn't full + cnt, err := fh.ReadAt(resp.Data, int64(obj.Offset)) + if err != nil && !errors.Is(err, io.EOF) { + return &NFSStatusError{NFSStatusIO, err} + } + resp.Count = uint32(cnt) + resp.Data = resp.Data[:resp.Count] + if errors.Is(err, io.EOF) { + resp.EOF = 1 + } + + writer := bytes.NewBuffer([]byte{}) + if err := xdr.Write(writer, uint32(NFSStatusOk)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + if err := WritePostOpAttrs(writer, tryStat(fs, path)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + + if err := xdr.Write(writer, resp); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + if err := w.Write(writer.Bytes()); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + return nil +} diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onreaddir.go b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onreaddir.go new file mode 100644 index 00000000..b59239e9 --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onreaddir.go @@ -0,0 +1,191 @@ +package nfs + +import ( + "bytes" + "context" + "crypto/sha256" + "encoding/binary" + "io" + "io/fs" + "os" + "path" + "sort" + + "github.com/willscott/go-nfs-client/nfs/xdr" +) + +type readDirArgs struct { + Handle []byte + Cookie uint64 + CookieVerif uint64 + Count uint32 +} + +type readDirEntity struct { + FileID uint64 + Name []byte + Cookie uint64 + Next bool +} + +func onReadDir(ctx context.Context, w *response, userHandle Handler) error { + w.errorFmt = opAttrErrorFormatter + obj := readDirArgs{} + err := xdr.Read(w.req.Body, &obj) + if err != nil { + return &NFSStatusError{NFSStatusInval, err} + } + + if obj.Count < 1024 { + return &NFSStatusError{NFSStatusTooSmall, io.ErrShortBuffer} + } + + fs, p, err := userHandle.FromHandle(obj.Handle) + if err != nil { + return &NFSStatusError{NFSStatusStale, err} + } + + contents, verifier, err := getDirListingWithVerifier(userHandle, obj.Handle, obj.CookieVerif) + if err != nil { + return err + } + if obj.Cookie > 0 && obj.CookieVerif > 0 && verifier != obj.CookieVerif { + return &NFSStatusError{NFSStatusBadCookie, nil} + } + + entities := make([]readDirEntity, 0) + maxBytes := uint32(100) // conservative overhead measure + + started := obj.Cookie == 0 + if started { + // add '.' and '..' to entities + dotdotFileID := uint64(0) + if len(p) > 0 { + dda := tryStat(fs, p[0:len(p)-1]) + if dda != nil { + dotdotFileID = dda.Fileid + } + } + dotFileID := uint64(0) + da := tryStat(fs, p) + if da != nil { + dotFileID = da.Fileid + } + entities = append(entities, + readDirEntity{Name: []byte("."), Cookie: 0, Next: true, FileID: dotFileID}, + readDirEntity{Name: []byte(".."), Cookie: 1, Next: true, FileID: dotdotFileID}, + ) + } + + eof := true + maxEntities := userHandle.HandleLimit() / 2 + for i, c := range contents { + // cookie equates to index within contents + 2 (for '.' and '..') + cookie := uint64(i + 2) + if started { + maxBytes += 512 // TODO: better estimation. + if maxBytes > obj.Count || len(entities) > maxEntities { + eof = false + break + } + + attrs := ToFileAttribute(c, path.Join(append(p, c.Name())...)) + entities = append(entities, readDirEntity{ + FileID: attrs.Fileid, + Name: []byte(c.Name()), + Cookie: cookie, + Next: true, + }) + } else if cookie == obj.Cookie { + started = true + } + } + + writer := bytes.NewBuffer([]byte{}) + if err := xdr.Write(writer, uint32(NFSStatusOk)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + if err := WritePostOpAttrs(writer, tryStat(fs, p)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + + if err := xdr.Write(writer, verifier); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + + if err := xdr.Write(writer, len(entities) > 0); err != nil { // next + return &NFSStatusError{NFSStatusServerFault, err} + } + if len(entities) > 0 { + entities[len(entities)-1].Next = false + // no next for last entity + + for _, e := range entities { + if err := xdr.Write(writer, e); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + } + } + if err := xdr.Write(writer, eof); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + // TODO: track writer size at this point to validate maxcount estimation and stop early if needed. + + if err := w.Write(writer.Bytes()); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + return nil +} + +func getDirListingWithVerifier(userHandle Handler, fsHandle []byte, verifier uint64) ([]fs.FileInfo, uint64, error) { + // figure out what directory it is. + fs, p, err := userHandle.FromHandle(fsHandle) + if err != nil { + return nil, 0, &NFSStatusError{NFSStatusStale, err} + } + + path := fs.Join(p...) + // see if the verifier has this dir cached: + if vh, ok := userHandle.(CachingHandler); verifier != 0 && ok { + entries := vh.DataForVerifier(path, verifier) + if entries != nil { + return entries, verifier, nil + } + } + // load the entries. + contents, err := fs.ReadDir(path) + if err != nil { + if os.IsPermission(err) { + return nil, 0, &NFSStatusError{NFSStatusAccess, err} + } + return nil, 0, &NFSStatusError{NFSStatusNotDir, err} + } + + sort.Slice(contents, func(i, j int) bool { + return contents[i].Name() < contents[j].Name() + }) + + if vh, ok := userHandle.(CachingHandler); ok { + // let the user handler make a verifier if it can. + v := vh.VerifierFor(path, contents) + return contents, v, nil + } + + id := hashPathAndContents(path, contents) + return contents, id, nil +} + +func hashPathAndContents(path string, contents []fs.FileInfo) uint64 { + //calculate a cookie-verifier. + vHash := sha256.New() + + // Add the path to avoid collisions of directories with the same content + vHash.Write([]byte(path)) + + for _, c := range contents { + vHash.Write([]byte(c.Name())) // Never fails according to the docs + } + + verify := vHash.Sum(nil)[0:8] + return binary.BigEndian.Uint64(verify) +} diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onreaddirplus.go b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onreaddirplus.go new file mode 100644 index 00000000..e6796def --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onreaddirplus.go @@ -0,0 +1,153 @@ +package nfs + +import ( + "bytes" + "context" + "path" + + "github.com/willscott/go-nfs-client/nfs/xdr" +) + +type readDirPlusArgs struct { + Handle []byte + Cookie uint64 + CookieVerif uint64 + DirCount uint32 + MaxCount uint32 +} + +type readDirPlusEntity struct { + FileID uint64 + Name []byte + Cookie uint64 + Attributes *FileAttribute `xdr:"optional"` + Handle *[]byte `xdr:"optional"` + Next bool +} + +func joinPath(parent []string, elements ...string) []string { + joinedPath := make([]string, 0, len(parent)+len(elements)) + joinedPath = append(joinedPath, parent...) + joinedPath = append(joinedPath, elements...) + return joinedPath +} + +func onReadDirPlus(ctx context.Context, w *response, userHandle Handler) error { + w.errorFmt = opAttrErrorFormatter + obj := readDirPlusArgs{} + if err := xdr.Read(w.req.Body, &obj); err != nil { + return &NFSStatusError{NFSStatusInval, err} + } + + // in case of test, nfs-client send: + // DirCount = 512 + // MaxCount = 4096 + if obj.DirCount < 512 || obj.MaxCount < 4096 { + return &NFSStatusError{NFSStatusTooSmall, nil} + } + + fs, p, err := userHandle.FromHandle(obj.Handle) + if err != nil { + return &NFSStatusError{NFSStatusStale, err} + } + + contents, verifier, err := getDirListingWithVerifier(userHandle, obj.Handle, obj.CookieVerif) + if err != nil { + return err + } + if obj.Cookie > 0 && obj.CookieVerif > 0 && verifier != obj.CookieVerif { + return &NFSStatusError{NFSStatusBadCookie, nil} + } + + entities := make([]readDirPlusEntity, 0) + dirBytes := uint32(0) + maxBytes := uint32(100) // conservative overhead measure + + started := obj.Cookie == 0 + if started { + // add '.' and '..' to entities + dotdotFileID := uint64(0) + if len(p) > 0 { + dda := tryStat(fs, p[0:len(p)-1]) + if dda != nil { + dotdotFileID = dda.Fileid + } + } + dotFileID := uint64(0) + da := tryStat(fs, p) + if da != nil { + dotFileID = da.Fileid + } + entities = append(entities, + readDirPlusEntity{Name: []byte("."), Cookie: 0, Next: true, FileID: dotFileID, Attributes: da}, + readDirPlusEntity{Name: []byte(".."), Cookie: 1, Next: true, FileID: dotdotFileID}, + ) + } + + eof := true + maxEntities := userHandle.HandleLimit() / 2 + fb := 0 + fss := 0 + for i, c := range contents { + // cookie equates to index within contents + 2 (for '.' and '..') + cookie := uint64(i + 2) + fb++ + if started { + fss++ + dirBytes += uint32(len(c.Name()) + 20) + maxBytes += 512 // TODO: better estimation. + if dirBytes > obj.DirCount || maxBytes > obj.MaxCount || len(entities) > maxEntities { + eof = false + break + } + + filePath := joinPath(p, c.Name()) + handle := userHandle.ToHandle(fs, filePath) + attrs := ToFileAttribute(c, path.Join(filePath...)) + entities = append(entities, readDirPlusEntity{ + FileID: attrs.Fileid, + Name: []byte(c.Name()), + Cookie: cookie, + Attributes: attrs, + Handle: &handle, + Next: true, + }) + } else if cookie == obj.Cookie { + started = true + } + } + + writer := bytes.NewBuffer([]byte{}) + if err := xdr.Write(writer, uint32(NFSStatusOk)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + if err := WritePostOpAttrs(writer, tryStat(fs, p)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + if err := xdr.Write(writer, verifier); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + + if err := xdr.Write(writer, len(entities) > 0); err != nil { // next + return &NFSStatusError{NFSStatusServerFault, err} + } + if len(entities) > 0 { + entities[len(entities)-1].Next = false + // no next for last entity + + for _, e := range entities { + if err := xdr.Write(writer, e); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + } + } + if err := xdr.Write(writer, eof); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + // TODO: track writer size at this point to validate maxcount estimation and stop early if needed. + + if err := w.Write(writer.Bytes()); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + return nil +} diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onreadlink.go b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onreadlink.go new file mode 100644 index 00000000..d1379a17 --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onreadlink.go @@ -0,0 +1,51 @@ +package nfs + +import ( + "bytes" + "context" + "os" + + "github.com/willscott/go-nfs-client/nfs/xdr" +) + +func onReadLink(ctx context.Context, w *response, userHandle Handler) error { + w.errorFmt = opAttrErrorFormatter + handle, err := xdr.ReadOpaque(w.req.Body) + if err != nil { + return &NFSStatusError{NFSStatusInval, err} + } + fs, path, err := userHandle.FromHandle(handle) + if err != nil { + return &NFSStatusError{NFSStatusStale, err} + } + + out, err := fs.Readlink(fs.Join(path...)) + if err != nil { + if info, err := fs.Stat(fs.Join(path...)); err == nil { + if info.Mode()&os.ModeSymlink == 0 { + return &NFSStatusError{NFSStatusInval, err} + } + } + if os.IsNotExist(err) { + return &NFSStatusError{NFSStatusNoEnt, err} + } + + return &NFSStatusError{NFSStatusAccess, err} + } + + writer := bytes.NewBuffer([]byte{}) + if err := xdr.Write(writer, uint32(NFSStatusOk)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + if err := WritePostOpAttrs(writer, tryStat(fs, path)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + + if err := xdr.Write(writer, out); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + if err := w.Write(writer.Bytes()); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + return nil +} diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onremove.go b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onremove.go new file mode 100644 index 00000000..53a7ca7b --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onremove.go @@ -0,0 +1,78 @@ +package nfs + +import ( + "bytes" + "context" + "os" + + "github.com/go-git/go-billy/v5" + "github.com/willscott/go-nfs-client/nfs/xdr" +) + +func onRemove(ctx context.Context, w *response, userHandle Handler) error { + w.errorFmt = wccDataErrorFormatter + obj := DirOpArg{} + if err := xdr.Read(w.req.Body, &obj); err != nil { + return &NFSStatusError{NFSStatusInval, err} + } + fs, path, err := userHandle.FromHandle(obj.Handle) + if err != nil { + return &NFSStatusError{NFSStatusStale, err} + } + + if !billy.CapabilityCheck(fs, billy.WriteCapability) { + return &NFSStatusError{NFSStatusROFS, os.ErrPermission} + } + + if len(string(obj.Filename)) > PathNameMax { + return &NFSStatusError{NFSStatusNameTooLong, nil} + } + + fullPath := fs.Join(path...) + dirInfo, err := fs.Stat(fullPath) + if err != nil { + if os.IsNotExist(err) { + return &NFSStatusError{NFSStatusNoEnt, err} + } + if os.IsPermission(err) { + return &NFSStatusError{NFSStatusAccess, err} + } + return &NFSStatusError{NFSStatusIO, err} + } + if !dirInfo.IsDir() { + return &NFSStatusError{NFSStatusNotDir, nil} + } + preCacheData := ToFileAttribute(dirInfo, fullPath).AsCache() + + toDelete := fs.Join(append(path, string(obj.Filename))...) + toDeleteHandle := userHandle.ToHandle(fs, append(path, string(obj.Filename))) + + err = fs.Remove(toDelete) + if err != nil { + if os.IsNotExist(err) { + return &NFSStatusError{NFSStatusNoEnt, err} + } + if os.IsPermission(err) { + return &NFSStatusError{NFSStatusAccess, err} + } + return &NFSStatusError{NFSStatusIO, err} + } + + if err := userHandle.InvalidateHandle(fs, toDeleteHandle); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + + writer := bytes.NewBuffer([]byte{}) + if err := xdr.Write(writer, uint32(NFSStatusOk)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + + if err := WriteWcc(writer, preCacheData, tryStat(fs, path)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + + if err := w.Write(writer.Bytes()); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + return nil +} diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onrename.go b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onrename.go new file mode 100644 index 00000000..2cba4fee --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onrename.go @@ -0,0 +1,110 @@ +package nfs + +import ( + "bytes" + "context" + "os" + "reflect" + + "github.com/go-git/go-billy/v5" + "github.com/willscott/go-nfs-client/nfs/xdr" +) + +var doubleWccErrorBody = [16]byte{} + +func onRename(ctx context.Context, w *response, userHandle Handler) error { + w.errorFmt = errFormatterWithBody(doubleWccErrorBody[:]) + from := DirOpArg{} + err := xdr.Read(w.req.Body, &from) + if err != nil { + return &NFSStatusError{NFSStatusInval, err} + } + fs, fromPath, err := userHandle.FromHandle(from.Handle) + if err != nil { + return &NFSStatusError{NFSStatusStale, err} + } + + to := DirOpArg{} + if err = xdr.Read(w.req.Body, &to); err != nil { + return &NFSStatusError{NFSStatusInval, err} + } + fs2, toPath, err := userHandle.FromHandle(to.Handle) + if err != nil { + return &NFSStatusError{NFSStatusStale, err} + } + // check the two fs are the same + if !reflect.DeepEqual(fs, fs2) { + return &NFSStatusError{NFSStatusNotSupp, os.ErrPermission} + } + + if !billy.CapabilityCheck(fs, billy.WriteCapability) { + return &NFSStatusError{NFSStatusROFS, os.ErrPermission} + } + + if len(string(from.Filename)) > PathNameMax || len(string(to.Filename)) > PathNameMax { + return &NFSStatusError{NFSStatusNameTooLong, os.ErrInvalid} + } + + fromDirPath := fs.Join(fromPath...) + fromDirInfo, err := fs.Stat(fromDirPath) + if err != nil { + if os.IsNotExist(err) { + return &NFSStatusError{NFSStatusNoEnt, err} + } + return &NFSStatusError{NFSStatusIO, err} + } + if !fromDirInfo.IsDir() { + return &NFSStatusError{NFSStatusNotDir, nil} + } + preCacheData := ToFileAttribute(fromDirInfo, fromDirPath).AsCache() + + toDirPath := fs.Join(toPath...) + toDirInfo, err := fs.Stat(toDirPath) + if err != nil { + if os.IsNotExist(err) { + return &NFSStatusError{NFSStatusNoEnt, err} + } + return &NFSStatusError{NFSStatusIO, err} + } + if !toDirInfo.IsDir() { + return &NFSStatusError{NFSStatusNotDir, nil} + } + preDestData := ToFileAttribute(toDirInfo, toDirPath).AsCache() + + oldHandle := userHandle.ToHandle(fs, append(fromPath, string(from.Filename))) + + fromLoc := fs.Join(append(fromPath, string(from.Filename))...) + toLoc := fs.Join(append(toPath, string(to.Filename))...) + + err = fs.Rename(fromLoc, toLoc) + if err != nil { + if os.IsNotExist(err) { + return &NFSStatusError{NFSStatusNoEnt, err} + } + if os.IsPermission(err) { + return &NFSStatusError{NFSStatusAccess, err} + } + return &NFSStatusError{NFSStatusIO, err} + } + + if err := userHandle.InvalidateHandle(fs, oldHandle); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + + writer := bytes.NewBuffer([]byte{}) + if err := xdr.Write(writer, uint32(NFSStatusOk)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + + if err := WriteWcc(writer, preCacheData, tryStat(fs, fromPath)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + if err := WriteWcc(writer, preDestData, tryStat(fs, toPath)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + + if err := w.Write(writer.Bytes()); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + return nil +} diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onrmdir.go b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onrmdir.go new file mode 100644 index 00000000..12e35718 --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onrmdir.go @@ -0,0 +1,9 @@ +package nfs + +import ( + "context" +) + +func onRmDir(ctx context.Context, w *response, userHandle Handler) error { + return onRemove(ctx, w, userHandle) +} diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onsetattr.go b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onsetattr.go new file mode 100644 index 00000000..910ba3a6 --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onsetattr.go @@ -0,0 +1,76 @@ +package nfs + +import ( + "bytes" + "context" + "os" + + "github.com/go-git/go-billy/v5" + "github.com/willscott/go-nfs-client/nfs/xdr" +) + +func onSetAttr(ctx context.Context, w *response, userHandle Handler) error { + w.errorFmt = wccDataErrorFormatter + handle, err := xdr.ReadOpaque(w.req.Body) + if err != nil { + return &NFSStatusError{NFSStatusInval, err} + } + + fs, path, err := userHandle.FromHandle(handle) + if err != nil { + return &NFSStatusError{NFSStatusStale, err} + } + attrs, err := ReadSetFileAttributes(w.req.Body) + if err != nil { + return &NFSStatusError{NFSStatusInval, err} + } + + fullPath := fs.Join(path...) + info, err := fs.Lstat(fullPath) + if err != nil { + if os.IsNotExist(err) { + return &NFSStatusError{NFSStatusNoEnt, err} + } + return &NFSStatusError{NFSStatusAccess, err} + } + + // see if there's a "guard" + if guard, err := xdr.ReadUint32(w.req.Body); err != nil { + return &NFSStatusError{NFSStatusInval, err} + } else if guard != 0 { + // read the ctime. + t := FileTime{} + if err := xdr.Read(w.req.Body, &t); err != nil { + return &NFSStatusError{NFSStatusInval, err} + } + attr := ToFileAttribute(info, fullPath) + if t != attr.Ctime { + return &NFSStatusError{NFSStatusNotSync, nil} + } + } + + if !billy.CapabilityCheck(fs, billy.WriteCapability) { + return &NFSStatusError{NFSStatusROFS, os.ErrPermission} + } + + changer := userHandle.Change(fs) + if err := attrs.Apply(changer, fs, fs.Join(path...)); err != nil { + // Already an nfsstatuserror + return err + } + + preAttr := ToFileAttribute(info, fullPath).AsCache() + + writer := bytes.NewBuffer([]byte{}) + if err := xdr.Write(writer, uint32(NFSStatusOk)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + if err := WriteWcc(writer, preAttr, tryStat(fs, path)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + + if err := w.Write(writer.Bytes()); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + return nil +} diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onsymlink.go b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onsymlink.go new file mode 100644 index 00000000..a58c9bcd --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onsymlink.go @@ -0,0 +1,88 @@ +package nfs + +import ( + "bytes" + "context" + "os" + + "github.com/go-git/go-billy/v5" + "github.com/willscott/go-nfs-client/nfs/xdr" +) + +func onSymlink(ctx context.Context, w *response, userHandle Handler) error { + w.errorFmt = wccDataErrorFormatter + obj := DirOpArg{} + err := xdr.Read(w.req.Body, &obj) + if err != nil { + return &NFSStatusError{NFSStatusInval, err} + } + attrs, err := ReadSetFileAttributes(w.req.Body) + if err != nil { + return &NFSStatusError{NFSStatusInval, err} + } + + target, err := xdr.ReadOpaque(w.req.Body) + if err != nil { + return &NFSStatusError{NFSStatusInval, err} + } + + fs, path, err := userHandle.FromHandle(obj.Handle) + if err != nil { + return &NFSStatusError{NFSStatusStale, err} + } + if !billy.CapabilityCheck(fs, billy.WriteCapability) { + return &NFSStatusError{NFSStatusROFS, os.ErrPermission} + } + + if len(string(obj.Filename)) > PathNameMax { + return &NFSStatusError{NFSStatusNameTooLong, os.ErrInvalid} + } + + newFilePath := fs.Join(append(path, string(obj.Filename))...) + if _, err := fs.Stat(newFilePath); err == nil { + return &NFSStatusError{NFSStatusExist, os.ErrExist} + } + if s, err := fs.Stat(fs.Join(path...)); err != nil { + return &NFSStatusError{NFSStatusAccess, err} + } else if !s.IsDir() { + return &NFSStatusError{NFSStatusNotDir, nil} + } + + err = fs.Symlink(string(target), newFilePath) + if err != nil { + return &NFSStatusError{NFSStatusAccess, err} + } + + fp := userHandle.ToHandle(fs, append(path, string(obj.Filename))) + changer := userHandle.Change(fs) + if changer != nil { + if err := attrs.Apply(changer, fs, newFilePath); err != nil { + return &NFSStatusError{NFSStatusIO, err} + } + } + + writer := bytes.NewBuffer([]byte{}) + if err := xdr.Write(writer, uint32(NFSStatusOk)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + + // "handle follows" + if err := xdr.Write(writer, uint32(1)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + if err := xdr.Write(writer, fp); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + if err := WritePostOpAttrs(writer, tryStat(fs, append(path, string(obj.Filename)))); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + + if err := WriteWcc(writer, nil, tryStat(fs, path)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + + if err := w.Write(writer.Bytes()); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + return nil +} diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onwrite.go b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onwrite.go new file mode 100644 index 00000000..6631cc71 --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/nfs_onwrite.go @@ -0,0 +1,112 @@ +package nfs + +import ( + "bytes" + "context" + "io" + "math" + "os" + + "github.com/go-git/go-billy/v5" + "github.com/willscott/go-nfs-client/nfs/xdr" +) + +// writeStability is the level of durability requested with the write +type writeStability uint32 + +const ( + unstable writeStability = 0 + dataSync writeStability = 1 + fileSync writeStability = 2 +) + +type writeArgs struct { + Handle []byte + Offset uint64 + Count uint32 + How uint32 + Data []byte +} + +func onWrite(ctx context.Context, w *response, userHandle Handler) error { + w.errorFmt = wccDataErrorFormatter + var req writeArgs + if err := xdr.Read(w.req.Body, &req); err != nil { + return &NFSStatusError{NFSStatusInval, err} + } + + fs, path, err := userHandle.FromHandle(req.Handle) + if err != nil { + return &NFSStatusError{NFSStatusStale, err} + } + if !billy.CapabilityCheck(fs, billy.WriteCapability) { + return &NFSStatusError{NFSStatusROFS, os.ErrPermission} + } + if len(req.Data) > math.MaxInt32 || req.Count > math.MaxInt32 { + return &NFSStatusError{NFSStatusFBig, os.ErrInvalid} + } + if req.How != uint32(unstable) && req.How != uint32(dataSync) && req.How != uint32(fileSync) { + return &NFSStatusError{NFSStatusInval, os.ErrInvalid} + } + + // stat first for pre-op wcc. + fullPath := fs.Join(path...) + info, err := fs.Stat(fullPath) + if err != nil { + if os.IsNotExist(err) { + return &NFSStatusError{NFSStatusNoEnt, err} + } + return &NFSStatusError{NFSStatusAccess, err} + } + if !info.Mode().IsRegular() { + return &NFSStatusError{NFSStatusInval, os.ErrInvalid} + } + preOpCache := ToFileAttribute(info, fullPath).AsCache() + + // now the actual op. + file, err := fs.OpenFile(fs.Join(path...), os.O_RDWR, info.Mode().Perm()) + if err != nil { + return &NFSStatusError{NFSStatusAccess, err} + } + if req.Offset > 0 { + if _, err := file.Seek(int64(req.Offset), io.SeekStart); err != nil { + return &NFSStatusError{NFSStatusIO, err} + } + } + end := req.Count + if len(req.Data) < int(end) { + end = uint32(len(req.Data)) + } + writtenCount, err := file.Write(req.Data[:end]) + if err != nil { + Log.Errorf("Error writing: %v", err) + return &NFSStatusError{NFSStatusIO, err} + } + if err := file.Close(); err != nil { + Log.Errorf("error closing: %v", err) + return &NFSStatusError{NFSStatusIO, err} + } + + writer := bytes.NewBuffer([]byte{}) + if err := xdr.Write(writer, uint32(NFSStatusOk)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + + if err := WriteWcc(writer, preOpCache, tryStat(fs, path)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + if err := xdr.Write(writer, uint32(writtenCount)); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + if err := xdr.Write(writer, fileSync); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + if err := xdr.Write(writer, w.Server.ID); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + + if err := w.Write(writer.Bytes()); err != nil { + return &NFSStatusError{NFSStatusServerFault, err} + } + return nil +} diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/nfsinterface.go b/vendor/github.com/blacknon/go-nfs-sshlib/nfsinterface.go new file mode 100644 index 00000000..cf439afc --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/nfsinterface.go @@ -0,0 +1,188 @@ +package nfs + +// NFSProcedure is the valid RPC calls for the nfs service. +type NFSProcedure uint32 + +// NfsProcedure Codes +const ( + NFSProcedureNull NFSProcedure = iota + NFSProcedureGetAttr + NFSProcedureSetAttr + NFSProcedureLookup + NFSProcedureAccess + NFSProcedureReadlink + NFSProcedureRead + NFSProcedureWrite + NFSProcedureCreate + NFSProcedureMkDir + NFSProcedureSymlink + NFSProcedureMkNod + NFSProcedureRemove + NFSProcedureRmDir + NFSProcedureRename + NFSProcedureLink + NFSProcedureReadDir + NFSProcedureReadDirPlus + NFSProcedureFSStat + NFSProcedureFSInfo + NFSProcedurePathConf + NFSProcedureCommit +) + +func (n NFSProcedure) String() string { + switch n { + case NFSProcedureNull: + return "Null" + case NFSProcedureGetAttr: + return "GetAttr" + case NFSProcedureSetAttr: + return "SetAttr" + case NFSProcedureLookup: + return "Lookup" + case NFSProcedureAccess: + return "Access" + case NFSProcedureReadlink: + return "ReadLink" + case NFSProcedureRead: + return "Read" + case NFSProcedureWrite: + return "Write" + case NFSProcedureCreate: + return "Create" + case NFSProcedureMkDir: + return "Mkdir" + case NFSProcedureSymlink: + return "Symlink" + case NFSProcedureMkNod: + return "Mknod" + case NFSProcedureRemove: + return "Remove" + case NFSProcedureRmDir: + return "Rmdir" + case NFSProcedureRename: + return "Rename" + case NFSProcedureLink: + return "Link" + case NFSProcedureReadDir: + return "ReadDir" + case NFSProcedureReadDirPlus: + return "ReadDirPlus" + case NFSProcedureFSStat: + return "FSStat" + case NFSProcedureFSInfo: + return "FSInfo" + case NFSProcedurePathConf: + return "PathConf" + case NFSProcedureCommit: + return "Commit" + default: + return "Unknown" + } +} + +// NFSStatus (nfsstat3) is a result code for nfs rpc calls +type NFSStatus uint32 + +// NFSStatus codes +const ( + NFSStatusOk NFSStatus = 0 + NFSStatusPerm NFSStatus = 1 + NFSStatusNoEnt NFSStatus = 2 + NFSStatusIO NFSStatus = 5 + NFSStatusNXIO NFSStatus = 6 + NFSStatusAccess NFSStatus = 13 + NFSStatusExist NFSStatus = 17 + NFSStatusXDev NFSStatus = 18 + NFSStatusNoDev NFSStatus = 19 + NFSStatusNotDir NFSStatus = 20 + NFSStatusIsDir NFSStatus = 21 + NFSStatusInval NFSStatus = 22 + NFSStatusFBig NFSStatus = 27 + NFSStatusNoSPC NFSStatus = 28 + NFSStatusROFS NFSStatus = 30 + NFSStatusMlink NFSStatus = 31 + NFSStatusNameTooLong NFSStatus = 63 + NFSStatusNotEmpty NFSStatus = 66 + NFSStatusDQuot NFSStatus = 69 + NFSStatusStale NFSStatus = 70 + NFSStatusRemote NFSStatus = 71 + NFSStatusBadHandle NFSStatus = 10001 + NFSStatusNotSync NFSStatus = 10002 + NFSStatusBadCookie NFSStatus = 10003 + NFSStatusNotSupp NFSStatus = 10004 + NFSStatusTooSmall NFSStatus = 10005 + NFSStatusServerFault NFSStatus = 10006 + NFSStatusBadType NFSStatus = 10007 + NFSStatusJukebox NFSStatus = 10008 +) + +func (s NFSStatus) String() string { + switch s { + case NFSStatusOk: + return "Call Completed Successfull" + case NFSStatusPerm: + return "Not Owner" + case NFSStatusNoEnt: + return "No such file or directory" + case NFSStatusIO: + return "I/O error" + case NFSStatusNXIO: + return "I/O error: No such device" + case NFSStatusAccess: + return "Permission denied" + case NFSStatusExist: + return "File exists" + case NFSStatusXDev: + return "Attempt to do a cross device hard link" + case NFSStatusNoDev: + return "No such device" + case NFSStatusNotDir: + return "Not a directory" + case NFSStatusIsDir: + return "Is a directory" + case NFSStatusInval: + return "Invalid argument" + case NFSStatusFBig: + return "File too large" + case NFSStatusNoSPC: + return "No space left on device" + case NFSStatusROFS: + return "Read only file system" + case NFSStatusMlink: + return "Too many hard links" + case NFSStatusNameTooLong: + return "Name too long" + case NFSStatusNotEmpty: + return "Not empty" + case NFSStatusDQuot: + return "Resource quota exceeded" + case NFSStatusStale: + return "Invalid file handle" + case NFSStatusRemote: + return "Too many levels of remote in path" + case NFSStatusBadHandle: + return "Illegal NFS file handle" + case NFSStatusNotSync: + return "Synchronization mismatch" + case NFSStatusBadCookie: + return "Cookie is Stale" + case NFSStatusNotSupp: + return "Operation not supported" + case NFSStatusTooSmall: + return "Buffer or request too small" + case NFSStatusServerFault: + return "Unmapped error (EIO)" + case NFSStatusBadType: + return "Type not supported" + case NFSStatusJukebox: + return "Initiated, but too slow. Try again with new txn" + default: + return "unknown" + } +} + +// DirOpArg is a common serialization used for referencing an object in a directory +type DirOpArg struct { + Handle []byte + Filename []byte +} diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/server.go b/vendor/github.com/blacknon/go-nfs-sshlib/server.go new file mode 100644 index 00000000..f3c4db24 --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/server.go @@ -0,0 +1,107 @@ +package nfs + +import ( + "bytes" + "context" + "crypto/rand" + "errors" + "net" + "time" +) + +// Server is a handle to the listening NFS server. +type Server struct { + Handler + ID [8]byte + context.Context +} + +// RegisterMessageHandler registers a handler for a specific +// XDR procedure. +func RegisterMessageHandler(protocol uint32, proc uint32, handler HandleFunc) error { + if registeredHandlers == nil { + registeredHandlers = make(map[registeredHandlerID]HandleFunc) + } + for k := range registeredHandlers { + if k.protocol == protocol && k.proc == proc { + return errors.New("already registered") + } + } + id := registeredHandlerID{protocol, proc} + registeredHandlers[id] = handler + return nil +} + +// HandleFunc represents a handler for a specific protocol message. +type HandleFunc func(ctx context.Context, w *response, userHandler Handler) error + +// TODO: store directly as a uint64 for more efficient lookups +type registeredHandlerID struct { + protocol uint32 + proc uint32 +} + +var registeredHandlers map[registeredHandlerID]HandleFunc + +// Serve listens on the provided listener port for incoming client requests. +func (s *Server) Serve(l net.Listener) error { + defer l.Close() + baseCtx := context.Background() + if s.Context != nil { + baseCtx = s.Context + } + if bytes.Equal(s.ID[:], []byte{0, 0, 0, 0, 0, 0, 0, 0}) { + if _, err := rand.Reader.Read(s.ID[:]); err != nil { + return err + } + } + + var tempDelay time.Duration + + for { + conn, err := l.Accept() + if err != nil { + if ne, ok := err.(net.Error); ok && ne.Timeout() { + if tempDelay == 0 { + tempDelay = 5 * time.Millisecond + } else { + tempDelay *= 2 + } + if max := 1 * time.Second; tempDelay > max { + tempDelay = max + } + time.Sleep(tempDelay) + continue + } + return err + } + tempDelay = 0 + c := s.newConn(conn) + go c.serve(baseCtx) + } +} + +func (s *Server) newConn(nc net.Conn) *conn { + c := &conn{ + Server: s, + Conn: nc, + } + return c +} + +// TODO: keep an immutable map for each server instance to have less +// chance of races. +func (s *Server) handlerFor(prog uint32, proc uint32) HandleFunc { + for k, v := range registeredHandlers { + if k.protocol == prog && k.proc == proc { + return v + } + } + return nil +} + +// Serve is a singleton listener paralleling http.Serve +func Serve(l net.Listener, handler Handler) error { + srv := &Server{Handler: handler} + return srv.Serve(l) +} diff --git a/vendor/github.com/blacknon/go-nfs-sshlib/time.go b/vendor/github.com/blacknon/go-nfs-sshlib/time.go new file mode 100644 index 00000000..266dd27f --- /dev/null +++ b/vendor/github.com/blacknon/go-nfs-sshlib/time.go @@ -0,0 +1,32 @@ +package nfs + +import ( + "time" +) + +// FileTime is the NFS wire time format +// This is equivalent to go-nfs-client/nfs.NFS3Time +type FileTime struct { + Seconds uint32 + Nseconds uint32 +} + +// ToNFSTime generates the nfs 64bit time format from a golang time. +func ToNFSTime(t time.Time) FileTime { + return FileTime{ + Seconds: uint32(t.Unix()), + Nseconds: uint32(t.UnixNano() % int64(time.Second)), + } +} + +// Native generates a golang time from an nfs time spec +func (t FileTime) Native() *time.Time { + ts := time.Unix(int64(t.Seconds), int64(t.Nseconds)) + return &ts +} + +// EqualTimespec returns if this time is equal to a local time spec +func (t FileTime) EqualTimespec(sec int64, nsec int64) bool { + // TODO: bounds check on sec/nsec overflow + return t.Nseconds == uint32(nsec) && t.Seconds == uint32(sec) +} diff --git a/vendor/github.com/blacknon/go-sshlib/auth_pkcs11.go b/vendor/github.com/blacknon/go-sshlib/auth_pkcs11.go index f664610a..9b3e86ff 100644 --- a/vendor/github.com/blacknon/go-sshlib/auth_pkcs11.go +++ b/vendor/github.com/blacknon/go-sshlib/auth_pkcs11.go @@ -7,6 +7,9 @@ package sshlib import ( + "errors" + "os" + "github.com/miekg/pkcs11" "golang.org/x/crypto/ssh" ) @@ -37,6 +40,11 @@ func CreateSignerPKCS11(provider, pin string) (signers []ssh.Signer, err error) // get absolute path provider = getAbsPath(provider) + // Check exist provider + if _, err = os.Stat(provider); errors.Is(err, os.ErrNotExist) { + return + } + ctx := pkcs11.New(provider) err = ctx.Initialize() if err != nil { diff --git a/vendor/github.com/blacknon/go-sshlib/cmd.go b/vendor/github.com/blacknon/go-sshlib/cmd.go index 3f1fac00..89e5a681 100644 --- a/vendor/github.com/blacknon/go-sshlib/cmd.go +++ b/vendor/github.com/blacknon/go-sshlib/cmd.go @@ -59,7 +59,6 @@ func (c *Connect) Command(command string) (err error) { return } -// func (c *Connect) setOption(session *ssh.Session) (err error) { // Request tty if c.TTY { diff --git a/vendor/github.com/blacknon/go-sshlib/connect.go b/vendor/github.com/blacknon/go-sshlib/connect.go index d077cb6e..2d6d2e51 100644 --- a/vendor/github.com/blacknon/go-sshlib/connect.go +++ b/vendor/github.com/blacknon/go-sshlib/connect.go @@ -117,15 +117,15 @@ func (c *Connect) CreateClient(host, port, user string, authMethods []ssh.AuthMe uri := net.JoinHostPort(host, port) timeout := 20 - if c.ConnectTimeout > 0 { - timeout = c.ConnectTimeout + if c.ConnectTimeout == 0 { + c.ConnectTimeout = timeout } // Create new ssh.ClientConfig{} config := &ssh.ClientConfig{ User: user, Auth: authMethods, - Timeout: time.Duration(timeout) * time.Second, + Timeout: time.Duration(c.ConnectTimeout) * time.Second, } if c.HostKeyCallback != nil { @@ -147,21 +147,27 @@ func (c *Connect) CreateClient(host, port, user string, authMethods []ssh.AuthMe c.ProxyDialer = proxy.Direct } - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(c.ConnectTimeout)*time.Second) defer cancel() // Dial to host:port - netConn, err := c.ProxyDialer.DialContext(ctx, "tcp", uri) - if err != nil { - return + netConn, cerr := c.ProxyDialer.DialContext(ctx, "tcp", uri) + if cerr != nil { + return cerr } + // Set deadline + netConn.SetDeadline(time.Now().Add(time.Duration(c.ConnectTimeout) * time.Second)) + // Create new ssh connect - sshCon, channel, req, err := ssh.NewClientConn(netConn, uri, config) - if err != nil { - return + sshCon, channel, req, cerr := ssh.NewClientConn(netConn, uri, config) + if cerr != nil { + return cerr } + // Reet deadline + netConn.SetDeadline(time.Time{}) + // Create *ssh.Client c.Client = ssh.NewClient(sshCon, channel, req) @@ -172,7 +178,6 @@ func (c *Connect) CreateClient(host, port, user string, authMethods []ssh.AuthMe func (c *Connect) CreateSession() (session *ssh.Session, err error) { // Create session session, err = c.Client.NewSession() - return } @@ -180,32 +185,29 @@ func (c *Connect) CreateSession() (session *ssh.Session, err error) { // TODO(blacknon): Interval及びMaxを設定できるようにする(v0.1.1) func (c *Connect) SendKeepAlive(session *ssh.Session) { // keep alive interval (default 30 sec) - interval := 30 + interval := 1 if c.SendKeepAliveInterval > 0 { interval = c.SendKeepAliveInterval } - // keep alive max (default 5) - max := 5 - if c.SendKeepAliveMax > 0 { - max = c.SendKeepAliveMax - } - - // keep alive counter - i := 0 for { - // Send keep alive packet - _, err := session.SendRequest("keepalive", true, nil) - // _, _, err := c.Client.SendRequest("keepalive", true, nil) - if err == nil { - i = 0 - } else { - i += 1 - } + // timeout channel + tc := make(chan bool, 1) + + go func() { + // Send keep alive packet + _, err := session.SendRequest("keepalive", true, nil) + if err == nil { + tc <- true + } + }() - // check counter - if max <= i { + select { + case <-tc: + case <-time.After(time.Duration(c.ConnectTimeout) * time.Second): session.Close() + c.Client.Close() + log.Println("keepalive timeout") return } diff --git a/vendor/github.com/blacknon/go-sshlib/nfs_cos.go b/vendor/github.com/blacknon/go-sshlib/nfs_cos.go new file mode 100644 index 00000000..c9328d4a --- /dev/null +++ b/vendor/github.com/blacknon/go-sshlib/nfs_cos.go @@ -0,0 +1,42 @@ +// Copyright (c) 2024 Blacknon. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +package sshlib + +import ( + "os" + "time" + + "github.com/go-git/go-billy/v5" +) + +// NewChangeOSFS wraps billy osfs to add the change interface +func NewChangeOSFS(fs billy.Filesystem) billy.Filesystem { + return COS{fs} +} + +// COS or OSFS + Change wraps a billy.FS to not fail the `Change` interface. +type COS struct { + billy.Filesystem +} + +// Chmod changes mode +func (fs COS) Chmod(name string, mode os.FileMode) error { + return os.Chmod(fs.Join(fs.Root(), name), mode) +} + +// Lchown changes ownership +func (fs COS) Lchown(name string, uid, gid int) error { + return os.Lchown(fs.Join(fs.Root(), name), uid, gid) +} + +// Chown changes ownership +func (fs COS) Chown(name string, uid, gid int) error { + return os.Chown(fs.Join(fs.Root(), name), uid, gid) +} + +// Chtimes changes access time +func (fs COS) Chtimes(name string, atime time.Time, mtime time.Time) error { + return os.Chtimes(fs.Join(fs.Root(), name), atime, mtime) +} diff --git a/vendor/github.com/blacknon/go-sshlib/nfs_cos_unix.go b/vendor/github.com/blacknon/go-sshlib/nfs_cos_unix.go new file mode 100644 index 00000000..b9d6755a --- /dev/null +++ b/vendor/github.com/blacknon/go-sshlib/nfs_cos_unix.go @@ -0,0 +1,30 @@ +//go:build darwin || dragonfly || freebsd || linux || nacl || netbsd || openbsd || solaris + +// Copyright (c) 2024 Blacknon. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +package sshlib + +import "golang.org/x/sys/unix" + +func (fs COS) Mknod(path string, mode uint32, major uint32, minor uint32) error { + dev := unix.Mkdev(major, minor) + return unix.Mknod(fs.Join(fs.Root(), path), mode, int(dev)) +} + +func (fs COS) Mkfifo(path string, mode uint32) error { + return unix.Mkfifo(fs.Join(fs.Root(), path), mode) +} + +func (fs COS) Link(path string, link string) error { + return unix.Link(fs.Join(fs.Root(), path), link) +} + +func (fs COS) Socket(path string) error { + fd, err := unix.Socket(unix.AF_UNIX, unix.SOCK_STREAM, 0) + if err != nil { + return err + } + return unix.Bind(fd, &unix.SockaddrUnix{Name: fs.Join(fs.Root(), path)}) +} diff --git a/vendor/github.com/blacknon/go-sshlib/nfs_forward.go b/vendor/github.com/blacknon/go-sshlib/nfs_forward.go new file mode 100644 index 00000000..54e4fd55 --- /dev/null +++ b/vendor/github.com/blacknon/go-sshlib/nfs_forward.go @@ -0,0 +1,60 @@ +// Copyright (c) 2024 Blacknon. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +package sshlib + +import ( + "net" + + nfs "github.com/blacknon/go-nfs-sshlib" + nfshelper "github.com/blacknon/go-nfs-sshlib/helpers" + osfs "github.com/go-git/go-billy/v5/osfs" + "github.com/pkg/sftp" +) + +func (c *Connect) NFSForward(address, port, basepoint string) (err error) { + // create listener + listener, err := net.Listen("tcp", net.JoinHostPort(address, port)) + if err != nil { + return + } + defer listener.Close() + + client, err := sftp.NewClient(c.Client) + if err != nil { + return + } + + sftpfsPlusChange := NewChangeSFTPFS(client, basepoint) + + handler := nfshelper.NewNullAuthHandler(sftpfsPlusChange) + cacheHelper := nfshelper.NewCachingHandler(handler, 1024) + + // listen + err = nfs.Serve(listener, cacheHelper) + + return +} + +// NFSReverseForward is Start NFS Server and forward port to remote server. +// This port is forawrd GO-NFS Server. +func (c *Connect) NFSReverseForward(address, port, sharepoint string) (err error) { + // create listener + listener, err := c.Client.Listen("tcp", net.JoinHostPort(address, port)) + if err != nil { + return + } + defer listener.Close() + + bfs := osfs.New(sharepoint) + bfsPlusChange := NewChangeOSFS(bfs) + + handler := nfshelper.NewNullAuthHandler(bfsPlusChange) + cacheHelper := nfshelper.NewCachingHandler(handler, 1024) + + // listen + err = nfs.Serve(listener, cacheHelper) + + return +} diff --git a/vendor/github.com/blacknon/go-sshlib/nfs_sftpfs.go b/vendor/github.com/blacknon/go-sshlib/nfs_sftpfs.go new file mode 100644 index 00000000..45ae926a --- /dev/null +++ b/vendor/github.com/blacknon/go-sshlib/nfs_sftpfs.go @@ -0,0 +1,185 @@ +// Copyright (c) 2024 Blacknon. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +package sshlib + +import ( + "os" + "path/filepath" + "strconv" + "sync" + "time" + + "github.com/go-git/go-billy/v5" + "github.com/go-git/go-billy/v5/helper/chroot" + "github.com/go-git/go-billy/v5/helper/temporal" + "github.com/pkg/sftp" +) + +// sftpFile is a wrapper around sftp.File that implements the billy.File interface +type sftpFile struct { + *sftp.File + mx sync.Mutex +} + +func (f *sftpFile) Lock() error { + f.mx.Lock() + return nil +} + +func (f *sftpFile) Unlock() error { + f.mx.Unlock() + return nil +} + +func NewChangeSFTPFS(client *sftp.Client, base string) billy.Filesystem { + return temporal.New( + chroot.New(&SFTPFS{Client: client}, base), + "", + ) +} + +type SFTPFS struct { + billy.Filesystem + Client *sftp.Client +} + +// Create +func (fs *SFTPFS) Create(filename string) (billy.File, error) { + _, err := fs.Stat(filename) + if err == nil { + return nil, os.ErrExist + } + + dir := filepath.Dir(filename) + err = fs.MkdirAll(dir, os.ModeDir) + if err != nil { + return nil, err + } + + f, err := fs.Client.Create(filename) + if err != nil { + return nil, err + } + return &sftpFile{File: f}, nil +} + +// OpenFile +func (fs *SFTPFS) OpenFile(filename string, flag int, perm os.FileMode) (billy.File, error) { + // TODO: create dirをする + // https://github.com/src-d/go-billy/blob/master/osfs/os.go#L31-L54 + + f, err := fs.Client.OpenFile(filename, flag) + if err != nil { + return nil, err + } + + return &sftpFile{File: f}, nil +} + +func (fs *SFTPFS) createDir(fullpath string) error { + dir := filepath.Dir(fullpath) + if dir != "." { + if err := fs.Client.MkdirAll(dir); err != nil { + return err + } + } + + return nil +} + +// ReadDir +func (fs *SFTPFS) ReadDir(path string) ([]os.FileInfo, error) { + l, err := fs.Client.ReadDir(path) + if err != nil { + return nil, err + } + + var s = make([]os.FileInfo, len(l)) + for i, f := range l { + s[i] = f + } + + return s, nil +} + +// Rename +func (fs *SFTPFS) Rename(from, to string) error { + return fs.Client.Rename(from, to) +} + +// MkdirAll +func (fs *SFTPFS) MkdirAll(filename string, perm os.FileMode) error { + err := fs.Client.MkdirAll(filename) + if err != nil { + return err + } + + return nil +} + +// Open +func (fs *SFTPFS) Open(filename string) (billy.File, error) { + f, err := fs.Client.Open(filename) + if err != nil { + return nil, err + } + return &sftpFile{File: f}, nil +} + +// Stat +func (fs *SFTPFS) Stat(filename string) (os.FileInfo, error) { + return fs.Client.Stat(filepath.Clean(filename)) +} + +// Remove +func (fs *SFTPFS) Remove(filename string) error { + return fs.Client.Remove(filename) +} + +// TempFile +func (fs *SFTPFS) TempFile(dir, prefix string) (billy.File, error) { + if err := fs.createDir(dir + string(os.PathSeparator)); err != nil { + return nil, err + } + + tempFileName := prefix + strconv.FormatInt(time.Now().UnixNano(), 10) + tempFilePath := filepath.Join(dir, tempFileName) + + f, err := fs.Create(tempFilePath) + if err != nil { + return nil, err + } + + return f, nil +} + +func (fs *SFTPFS) Join(elem ...string) string { + return filepath.Join(elem...) +} + +// RemoveAll +func (fs *SFTPFS) RemoveAll(filename string) error { + return fs.Client.RemoveAll(filename) +} + +// Lstat +func (fs *SFTPFS) Lstat(filename string) (os.FileInfo, error) { + return fs.Client.Lstat(filepath.Clean(filename)) +} + +// Symlink +func (fs *SFTPFS) Symlink(target, link string) error { + return fs.Client.Symlink(target, link) +} + +// Readlink +func (fs *SFTPFS) Readlink(link string) (string, error) { + return fs.Client.ReadLink(link) +} + +// Capabilities +func (fs *SFTPFS) Capabilities() billy.Capability { + return billy.DefaultCapabilities +} diff --git a/vendor/github.com/blacknon/go-sshlib/proxy.go b/vendor/github.com/blacknon/go-sshlib/proxy.go index b4aa3273..59eb0a17 100644 --- a/vendor/github.com/blacknon/go-sshlib/proxy.go +++ b/vendor/github.com/blacknon/go-sshlib/proxy.go @@ -8,10 +8,10 @@ import ( "bufio" "context" "fmt" + "log" "net" "net/http" "net/url" - "os" "os/exec" "golang.org/x/net/proxy" @@ -23,19 +23,31 @@ type ProxyDialer interface { } type ContextDialer struct { - dialer proxy.Dialer + Dialer proxy.Dialer +} + +func (c *ContextDialer) GetDialer() proxy.Dialer { + return c.Dialer } func (c *ContextDialer) Dial(network, addr string) (net.Conn, error) { - return c.dialer.Dial(network, addr) + return c.Dialer.Dial(network, addr) } func (c *ContextDialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) { + // Simply call the DialContext method if supported + if dialerCtx, ok := c.Dialer.(interface { + DialContext(context.Context, string, string) (net.Conn, error) + }); ok { + return dialerCtx.DialContext(ctx, network, addr) + } + + // Fallback if DialContext is not supported connChan := make(chan net.Conn, 1) errChan := make(chan error, 1) go func() { - conn, err := c.dialer.Dial(network, addr) + conn, err := c.Dialer.Dial(network, addr) if err != nil { errChan <- err return @@ -96,7 +108,7 @@ func (p *Proxy) CreateProxyDialer() (proxyContextDialer ProxyDialer, err error) proxyDialer, err = p.CreateProxyCommandProxyDialer() } - proxyContextDialer = &ContextDialer{dialer: proxyDialer} + proxyContextDialer = &ContextDialer{Dialer: proxyDialer} return } @@ -158,6 +170,8 @@ func (p *Proxy) CreateProxyCommandProxyDialer() (proxyDialer proxy.Dialer, err e type NetPipe struct { Command string + ctx context.Context + Cmd *exec.Cmd } func (n *NetPipe) Dial(network, addr string) (con net.Conn, err error) { @@ -167,15 +181,15 @@ func (n *NetPipe) Dial(network, addr string) (con net.Conn, err error) { // Create net.Pipe(), and set proxyCommand con, srv := net.Pipe() - cmd := exec.Command("sh", "-c", n.Command) + n.Cmd = exec.Command("sh", "-c", n.Command) // setup FD - cmd.Stdin = srv - cmd.Stdout = srv - cmd.Stderr = os.Stderr + n.Cmd.Stdin = srv + n.Cmd.Stdout = srv + n.Cmd.Stderr = log.Writer() - // run proxyCommand - err = cmd.Start() + // Start the command + err = n.Cmd.Start() return } @@ -190,6 +204,7 @@ func (n *NetPipe) DialContext(ctx context.Context, network, addr string) (con ne errChan <- err return } + connChan <- conn }() @@ -199,6 +214,7 @@ func (n *NetPipe) DialContext(ctx context.Context, network, addr string) (con ne case err := <-errChan: return nil, err case <-ctx.Done(): + n.Cmd.Process.Kill() return nil, ctx.Err() } } @@ -258,29 +274,6 @@ func (s *httpProxy) Dial(network, addr string) (net.Conn, error) { return c, nil } -func (s *httpProxy) DialContext(ctx context.Context, network, addr string) (con net.Conn, err error) { - connChan := make(chan net.Conn, 1) - errChan := make(chan error, 1) - - go func() { - conn, err := s.Dial(network, addr) - if err != nil { - errChan <- err - return - } - connChan <- conn - }() - - select { - case conn := <-connChan: - return conn, nil - case err := <-errChan: - return nil, err - case <-ctx.Done(): - return nil, ctx.Err() - } -} - // newHttpProxy func newHttpProxy(uri *url.URL, forward proxy.Dialer) (proxy.Dialer, error) { s := new(httpProxy) diff --git a/vendor/github.com/cyphar/filepath-securejoin/LICENSE b/vendor/github.com/cyphar/filepath-securejoin/LICENSE new file mode 100644 index 00000000..bec842f2 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/LICENSE @@ -0,0 +1,28 @@ +Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved. +Copyright (C) 2017 SUSE LLC. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/cyphar/filepath-securejoin/README.md b/vendor/github.com/cyphar/filepath-securejoin/README.md new file mode 100644 index 00000000..4eca0f23 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/README.md @@ -0,0 +1,79 @@ +## `filepath-securejoin` ## + +[![Build Status](https://github.com/cyphar/filepath-securejoin/actions/workflows/ci.yml/badge.svg)](https://github.com/cyphar/filepath-securejoin/actions/workflows/ci.yml) + +An implementation of `SecureJoin`, a [candidate for inclusion in the Go +standard library][go#20126]. The purpose of this function is to be a "secure" +alternative to `filepath.Join`, and in particular it provides certain +guarantees that are not provided by `filepath.Join`. + +> **NOTE**: This code is *only* safe if you are not at risk of other processes +> modifying path components after you've used `SecureJoin`. If it is possible +> for a malicious process to modify path components of the resolved path, then +> you will be vulnerable to some fairly trivial TOCTOU race conditions. [There +> are some Linux kernel patches I'm working on which might allow for a better +> solution.][lwn-obeneath] +> +> In addition, with a slightly modified API it might be possible to use +> `O_PATH` and verify that the opened path is actually the resolved one -- but +> I have not done that yet. I might add it in the future as a helper function +> to help users verify the path (we can't just return `/proc/self/fd/` +> because that doesn't always work transparently for all users). + +This is the function prototype: + +```go +func SecureJoin(root, unsafePath string) (string, error) +``` + +This library **guarantees** the following: + +* If no error is set, the resulting string **must** be a child path of + `root` and will not contain any symlink path components (they will all be + expanded). + +* When expanding symlinks, all symlink path components **must** be resolved + relative to the provided root. In particular, this can be considered a + userspace implementation of how `chroot(2)` operates on file paths. Note that + these symlinks will **not** be expanded lexically (`filepath.Clean` is not + called on the input before processing). + +* Non-existent path components are unaffected by `SecureJoin` (similar to + `filepath.EvalSymlinks`'s semantics). + +* The returned path will always be `filepath.Clean`ed and thus not contain any + `..` components. + +A (trivial) implementation of this function on GNU/Linux systems could be done +with the following (note that this requires root privileges and is far more +opaque than the implementation in this library, and also requires that +`readlink` is inside the `root` path): + +```go +package securejoin + +import ( + "os/exec" + "path/filepath" +) + +func SecureJoin(root, unsafePath string) (string, error) { + unsafePath = string(filepath.Separator) + unsafePath + cmd := exec.Command("chroot", root, + "readlink", "--canonicalize-missing", "--no-newline", unsafePath) + output, err := cmd.CombinedOutput() + if err != nil { + return "", err + } + expanded := string(output) + return filepath.Join(root, expanded), nil +} +``` + +[lwn-obeneath]: https://lwn.net/Articles/767547/ +[go#20126]: https://github.com/golang/go/issues/20126 + +### License ### + +The license of this project is the same as Go, which is a BSD 3-clause license +available in the `LICENSE` file. diff --git a/vendor/github.com/cyphar/filepath-securejoin/VERSION b/vendor/github.com/cyphar/filepath-securejoin/VERSION new file mode 100644 index 00000000..abd41058 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/VERSION @@ -0,0 +1 @@ +0.2.4 diff --git a/vendor/github.com/cyphar/filepath-securejoin/join.go b/vendor/github.com/cyphar/filepath-securejoin/join.go new file mode 100644 index 00000000..aa32b85f --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/join.go @@ -0,0 +1,125 @@ +// Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved. +// Copyright (C) 2017 SUSE LLC. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package securejoin is an implementation of the hopefully-soon-to-be-included +// SecureJoin helper that is meant to be part of the "path/filepath" package. +// The purpose of this project is to provide a PoC implementation to make the +// SecureJoin proposal (https://github.com/golang/go/issues/20126) more +// tangible. +package securejoin + +import ( + "bytes" + "errors" + "os" + "path/filepath" + "strings" + "syscall" +) + +// IsNotExist tells you if err is an error that implies that either the path +// accessed does not exist (or path components don't exist). This is +// effectively a more broad version of os.IsNotExist. +func IsNotExist(err error) bool { + // Check that it's not actually an ENOTDIR, which in some cases is a more + // convoluted case of ENOENT (usually involving weird paths). + return errors.Is(err, os.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) || errors.Is(err, syscall.ENOENT) +} + +// SecureJoinVFS joins the two given path components (similar to Join) except +// that the returned path is guaranteed to be scoped inside the provided root +// path (when evaluated). Any symbolic links in the path are evaluated with the +// given root treated as the root of the filesystem, similar to a chroot. The +// filesystem state is evaluated through the given VFS interface (if nil, the +// standard os.* family of functions are used). +// +// Note that the guarantees provided by this function only apply if the path +// components in the returned string are not modified (in other words are not +// replaced with symlinks on the filesystem) after this function has returned. +// Such a symlink race is necessarily out-of-scope of SecureJoin. +// +// Volume names in unsafePath are always discarded, regardless if they are +// provided via direct input or when evaluating symlinks. Therefore: +// +// "C:\Temp" + "D:\path\to\file.txt" results in "C:\Temp\path\to\file.txt" +func SecureJoinVFS(root, unsafePath string, vfs VFS) (string, error) { + // Use the os.* VFS implementation if none was specified. + if vfs == nil { + vfs = osVFS{} + } + + unsafePath = filepath.FromSlash(unsafePath) + var path bytes.Buffer + n := 0 + for unsafePath != "" { + if n > 255 { + return "", &os.PathError{Op: "SecureJoin", Path: root + string(filepath.Separator) + unsafePath, Err: syscall.ELOOP} + } + + if v := filepath.VolumeName(unsafePath); v != "" { + unsafePath = unsafePath[len(v):] + } + + // Next path component, p. + i := strings.IndexRune(unsafePath, filepath.Separator) + var p string + if i == -1 { + p, unsafePath = unsafePath, "" + } else { + p, unsafePath = unsafePath[:i], unsafePath[i+1:] + } + + // Create a cleaned path, using the lexical semantics of /../a, to + // create a "scoped" path component which can safely be joined to fullP + // for evaluation. At this point, path.String() doesn't contain any + // symlink components. + cleanP := filepath.Clean(string(filepath.Separator) + path.String() + p) + if cleanP == string(filepath.Separator) { + path.Reset() + continue + } + fullP := filepath.Clean(root + cleanP) + + // Figure out whether the path is a symlink. + fi, err := vfs.Lstat(fullP) + if err != nil && !IsNotExist(err) { + return "", err + } + // Treat non-existent path components the same as non-symlinks (we + // can't do any better here). + if IsNotExist(err) || fi.Mode()&os.ModeSymlink == 0 { + path.WriteString(p) + path.WriteRune(filepath.Separator) + continue + } + + // Only increment when we actually dereference a link. + n++ + + // It's a symlink, expand it by prepending it to the yet-unparsed path. + dest, err := vfs.Readlink(fullP) + if err != nil { + return "", err + } + // Absolute symlinks reset any work we've already done. + if filepath.IsAbs(dest) { + path.Reset() + } + unsafePath = dest + string(filepath.Separator) + unsafePath + } + + // We have to clean path.String() here because it may contain '..' + // components that are entirely lexical, but would be misleading otherwise. + // And finally do a final clean to ensure that root is also lexically + // clean. + fullP := filepath.Clean(string(filepath.Separator) + path.String()) + return filepath.Clean(root + fullP), nil +} + +// SecureJoin is a wrapper around SecureJoinVFS that just uses the os.* library +// of functions as the VFS. If in doubt, use this function over SecureJoinVFS. +func SecureJoin(root, unsafePath string) (string, error) { + return SecureJoinVFS(root, unsafePath, nil) +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/vfs.go b/vendor/github.com/cyphar/filepath-securejoin/vfs.go new file mode 100644 index 00000000..a82a5eae --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/vfs.go @@ -0,0 +1,41 @@ +// Copyright (C) 2017 SUSE LLC. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package securejoin + +import "os" + +// In future this should be moved into a separate package, because now there +// are several projects (umoci and go-mtree) that are using this sort of +// interface. + +// VFS is the minimal interface necessary to use SecureJoinVFS. A nil VFS is +// equivalent to using the standard os.* family of functions. This is mainly +// used for the purposes of mock testing, but also can be used to otherwise use +// SecureJoin with VFS-like system. +type VFS interface { + // Lstat returns a FileInfo describing the named file. If the file is a + // symbolic link, the returned FileInfo describes the symbolic link. Lstat + // makes no attempt to follow the link. These semantics are identical to + // os.Lstat. + Lstat(name string) (os.FileInfo, error) + + // Readlink returns the destination of the named symbolic link. These + // semantics are identical to os.Readlink. + Readlink(name string) (string, error) +} + +// osVFS is the "nil" VFS, in that it just passes everything through to the os +// module. +type osVFS struct{} + +// Lstat returns a FileInfo describing the named file. If the file is a +// symbolic link, the returned FileInfo describes the symbolic link. Lstat +// makes no attempt to follow the link. These semantics are identical to +// os.Lstat. +func (o osVFS) Lstat(name string) (os.FileInfo, error) { return os.Lstat(name) } + +// Readlink returns the destination of the named symbolic link. These +// semantics are identical to os.Readlink. +func (o osVFS) Readlink(name string) (string, error) { return os.Readlink(name) } diff --git a/vendor/github.com/davecgh/go-spew/LICENSE b/vendor/github.com/davecgh/go-spew/LICENSE index c8364161..bc52e96f 100644 --- a/vendor/github.com/davecgh/go-spew/LICENSE +++ b/vendor/github.com/davecgh/go-spew/LICENSE @@ -2,7 +2,7 @@ ISC License Copyright (c) 2012-2016 Dave Collins -Permission to use, copy, modify, and distribute this software for any +Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. diff --git a/vendor/github.com/davecgh/go-spew/spew/bypass.go b/vendor/github.com/davecgh/go-spew/spew/bypass.go index 8a4a6589..79299478 100644 --- a/vendor/github.com/davecgh/go-spew/spew/bypass.go +++ b/vendor/github.com/davecgh/go-spew/spew/bypass.go @@ -16,7 +16,9 @@ // when the code is not running on Google App Engine, compiled by GopherJS, and // "-tags safe" is not added to the go build command line. The "disableunsafe" // tag is deprecated and thus should not be used. -// +build !js,!appengine,!safe,!disableunsafe +// Go versions prior to 1.4 are disabled because they use a different layout +// for interfaces which make the implementation of unsafeReflectValue more complex. +// +build !js,!appengine,!safe,!disableunsafe,go1.4 package spew @@ -34,80 +36,49 @@ const ( ptrSize = unsafe.Sizeof((*byte)(nil)) ) +type flag uintptr + var ( - // offsetPtr, offsetScalar, and offsetFlag are the offsets for the - // internal reflect.Value fields. These values are valid before golang - // commit ecccf07e7f9d which changed the format. The are also valid - // after commit 82f48826c6c7 which changed the format again to mirror - // the original format. Code in the init function updates these offsets - // as necessary. - offsetPtr = uintptr(ptrSize) - offsetScalar = uintptr(0) - offsetFlag = uintptr(ptrSize * 2) - - // flagKindWidth and flagKindShift indicate various bits that the - // reflect package uses internally to track kind information. - // - // flagRO indicates whether or not the value field of a reflect.Value is - // read-only. - // - // flagIndir indicates whether the value field of a reflect.Value is - // the actual data or a pointer to the data. - // - // These values are valid before golang commit 90a7c3c86944 which - // changed their positions. Code in the init function updates these - // flags as necessary. - flagKindWidth = uintptr(5) - flagKindShift = uintptr(flagKindWidth - 1) - flagRO = uintptr(1 << 0) - flagIndir = uintptr(1 << 1) + // flagRO indicates whether the value field of a reflect.Value + // is read-only. + flagRO flag + + // flagAddr indicates whether the address of the reflect.Value's + // value may be taken. + flagAddr flag ) -func init() { - // Older versions of reflect.Value stored small integers directly in the - // ptr field (which is named val in the older versions). Versions - // between commits ecccf07e7f9d and 82f48826c6c7 added a new field named - // scalar for this purpose which unfortunately came before the flag - // field, so the offset of the flag field is different for those - // versions. - // - // This code constructs a new reflect.Value from a known small integer - // and checks if the size of the reflect.Value struct indicates it has - // the scalar field. When it does, the offsets are updated accordingly. - vv := reflect.ValueOf(0xf00) - if unsafe.Sizeof(vv) == (ptrSize * 4) { - offsetScalar = ptrSize * 2 - offsetFlag = ptrSize * 3 - } +// flagKindMask holds the bits that make up the kind +// part of the flags field. In all the supported versions, +// it is in the lower 5 bits. +const flagKindMask = flag(0x1f) - // Commit 90a7c3c86944 changed the flag positions such that the low - // order bits are the kind. This code extracts the kind from the flags - // field and ensures it's the correct type. When it's not, the flag - // order has been changed to the newer format, so the flags are updated - // accordingly. - upf := unsafe.Pointer(uintptr(unsafe.Pointer(&vv)) + offsetFlag) - upfv := *(*uintptr)(upf) - flagKindMask := uintptr((1<>flagKindShift != uintptr(reflect.Int) { - flagKindShift = 0 - flagRO = 1 << 5 - flagIndir = 1 << 6 - - // Commit adf9b30e5594 modified the flags to separate the - // flagRO flag into two bits which specifies whether or not the - // field is embedded. This causes flagIndir to move over a bit - // and means that flagRO is the combination of either of the - // original flagRO bit and the new bit. - // - // This code detects the change by extracting what used to be - // the indirect bit to ensure it's set. When it's not, the flag - // order has been changed to the newer format, so the flags are - // updated accordingly. - if upfv&flagIndir == 0 { - flagRO = 3 << 5 - flagIndir = 1 << 7 - } +// Different versions of Go have used different +// bit layouts for the flags type. This table +// records the known combinations. +var okFlags = []struct { + ro, addr flag +}{{ + // From Go 1.4 to 1.5 + ro: 1 << 5, + addr: 1 << 7, +}, { + // Up to Go tip. + ro: 1<<5 | 1<<6, + addr: 1 << 8, +}} + +var flagValOffset = func() uintptr { + field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag") + if !ok { + panic("reflect.Value has no flag field") } + return field.Offset +}() + +// flagField returns a pointer to the flag field of a reflect.Value. +func flagField(v *reflect.Value) *flag { + return (*flag)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + flagValOffset)) } // unsafeReflectValue converts the passed reflect.Value into a one that bypasses @@ -119,34 +90,56 @@ func init() { // This allows us to check for implementations of the Stringer and error // interfaces to be used for pretty printing ordinarily unaddressable and // inaccessible values such as unexported struct fields. -func unsafeReflectValue(v reflect.Value) (rv reflect.Value) { - indirects := 1 - vt := v.Type() - upv := unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetPtr) - rvf := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetFlag)) - if rvf&flagIndir != 0 { - vt = reflect.PtrTo(v.Type()) - indirects++ - } else if offsetScalar != 0 { - // The value is in the scalar field when it's not one of the - // reference types. - switch vt.Kind() { - case reflect.Uintptr: - case reflect.Chan: - case reflect.Func: - case reflect.Map: - case reflect.Ptr: - case reflect.UnsafePointer: - default: - upv = unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + - offsetScalar) - } +func unsafeReflectValue(v reflect.Value) reflect.Value { + if !v.IsValid() || (v.CanInterface() && v.CanAddr()) { + return v } + flagFieldPtr := flagField(&v) + *flagFieldPtr &^= flagRO + *flagFieldPtr |= flagAddr + return v +} - pv := reflect.NewAt(vt, upv) - rv = pv - for i := 0; i < indirects; i++ { - rv = rv.Elem() +// Sanity checks against future reflect package changes +// to the type or semantics of the Value.flag field. +func init() { + field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag") + if !ok { + panic("reflect.Value has no flag field") + } + if field.Type.Kind() != reflect.TypeOf(flag(0)).Kind() { + panic("reflect.Value flag field has changed kind") + } + type t0 int + var t struct { + A t0 + // t0 will have flagEmbedRO set. + t0 + // a will have flagStickyRO set + a t0 + } + vA := reflect.ValueOf(t).FieldByName("A") + va := reflect.ValueOf(t).FieldByName("a") + vt0 := reflect.ValueOf(t).FieldByName("t0") + + // Infer flagRO from the difference between the flags + // for the (otherwise identical) fields in t. + flagPublic := *flagField(&vA) + flagWithRO := *flagField(&va) | *flagField(&vt0) + flagRO = flagPublic ^ flagWithRO + + // Infer flagAddr from the difference between a value + // taken from a pointer and not. + vPtrA := reflect.ValueOf(&t).Elem().FieldByName("A") + flagNoPtr := *flagField(&vA) + flagPtr := *flagField(&vPtrA) + flagAddr = flagNoPtr ^ flagPtr + + // Check that the inferred flags tally with one of the known versions. + for _, f := range okFlags { + if flagRO == f.ro && flagAddr == f.addr { + return + } } - return rv + panic("reflect.Value read-only flag has changed semantics") } diff --git a/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go b/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go index 1fe3cf3d..205c28d6 100644 --- a/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go +++ b/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go @@ -16,7 +16,7 @@ // when the code is running on Google App Engine, compiled by GopherJS, or // "-tags safe" is added to the go build command line. The "disableunsafe" // tag is deprecated and thus should not be used. -// +build js appengine safe disableunsafe +// +build js appengine safe disableunsafe !go1.4 package spew diff --git a/vendor/github.com/davecgh/go-spew/spew/common.go b/vendor/github.com/davecgh/go-spew/spew/common.go index 7c519ff4..1be8ce94 100644 --- a/vendor/github.com/davecgh/go-spew/spew/common.go +++ b/vendor/github.com/davecgh/go-spew/spew/common.go @@ -180,7 +180,7 @@ func printComplex(w io.Writer, c complex128, floatPrecision int) { w.Write(closeParenBytes) } -// printHexPtr outputs a uintptr formatted as hexidecimal with a leading '0x' +// printHexPtr outputs a uintptr formatted as hexadecimal with a leading '0x' // prefix to Writer w. func printHexPtr(w io.Writer, p uintptr) { // Null pointer. diff --git a/vendor/github.com/davecgh/go-spew/spew/dump.go b/vendor/github.com/davecgh/go-spew/spew/dump.go index df1d582a..f78d89fc 100644 --- a/vendor/github.com/davecgh/go-spew/spew/dump.go +++ b/vendor/github.com/davecgh/go-spew/spew/dump.go @@ -35,16 +35,16 @@ var ( // cCharRE is a regular expression that matches a cgo char. // It is used to detect character arrays to hexdump them. - cCharRE = regexp.MustCompile("^.*\\._Ctype_char$") + cCharRE = regexp.MustCompile(`^.*\._Ctype_char$`) // cUnsignedCharRE is a regular expression that matches a cgo unsigned // char. It is used to detect unsigned character arrays to hexdump // them. - cUnsignedCharRE = regexp.MustCompile("^.*\\._Ctype_unsignedchar$") + cUnsignedCharRE = regexp.MustCompile(`^.*\._Ctype_unsignedchar$`) // cUint8tCharRE is a regular expression that matches a cgo uint8_t. // It is used to detect uint8_t arrays to hexdump them. - cUint8tCharRE = regexp.MustCompile("^.*\\._Ctype_uint8_t$") + cUint8tCharRE = regexp.MustCompile(`^.*\._Ctype_uint8_t$`) ) // dumpState contains information about the state of a dump operation. @@ -143,10 +143,10 @@ func (d *dumpState) dumpPtr(v reflect.Value) { // Display dereferenced value. d.w.Write(openParenBytes) switch { - case nilFound == true: + case nilFound: d.w.Write(nilAngleBytes) - case cycleFound == true: + case cycleFound: d.w.Write(circularBytes) default: diff --git a/vendor/github.com/davecgh/go-spew/spew/format.go b/vendor/github.com/davecgh/go-spew/spew/format.go index c49875ba..b04edb7d 100644 --- a/vendor/github.com/davecgh/go-spew/spew/format.go +++ b/vendor/github.com/davecgh/go-spew/spew/format.go @@ -182,10 +182,10 @@ func (f *formatState) formatPtr(v reflect.Value) { // Display dereferenced value. switch { - case nilFound == true: + case nilFound: f.fs.Write(nilAngleBytes) - case cycleFound == true: + case cycleFound: f.fs.Write(circularShortBytes) default: diff --git a/vendor/github.com/go-git/go-billy/v5/.gitignore b/vendor/github.com/go-git/go-billy/v5/.gitignore new file mode 100644 index 00000000..7aeb4669 --- /dev/null +++ b/vendor/github.com/go-git/go-billy/v5/.gitignore @@ -0,0 +1,4 @@ +/coverage.txt +/vendor +Gopkg.lock +Gopkg.toml diff --git a/vendor/github.com/go-git/go-billy/v5/LICENSE b/vendor/github.com/go-git/go-billy/v5/LICENSE new file mode 100644 index 00000000..9d607568 --- /dev/null +++ b/vendor/github.com/go-git/go-billy/v5/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2017 Sourced Technologies S.L. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/go-git/go-billy/v5/Makefile b/vendor/github.com/go-git/go-billy/v5/Makefile new file mode 100644 index 00000000..74dad8b4 --- /dev/null +++ b/vendor/github.com/go-git/go-billy/v5/Makefile @@ -0,0 +1,11 @@ +# Go parameters +GOCMD = go +GOTEST = $(GOCMD) test + +.PHONY: test +test: + $(GOTEST) -race ./... + +test-coverage: + echo "" > $(COVERAGE_REPORT); \ + $(GOTEST) -coverprofile=$(COVERAGE_REPORT) -coverpkg=./... -covermode=$(COVERAGE_MODE) ./... diff --git a/vendor/github.com/go-git/go-billy/v5/README.md b/vendor/github.com/go-git/go-billy/v5/README.md new file mode 100644 index 00000000..da5c0747 --- /dev/null +++ b/vendor/github.com/go-git/go-billy/v5/README.md @@ -0,0 +1,73 @@ +# go-billy [![GoDoc](https://godoc.org/gopkg.in/go-git/go-billy.v5?status.svg)](https://pkg.go.dev/github.com/go-git/go-billy/v5) [![Test](https://github.com/go-git/go-billy/workflows/Test/badge.svg)](https://github.com/go-git/go-billy/actions?query=workflow%3ATest) + +The missing interface filesystem abstraction for Go. +Billy implements an interface based on the `os` standard library, allowing to develop applications without dependency on the underlying storage. Makes it virtually free to implement mocks and testing over filesystem operations. + +Billy was born as part of [go-git/go-git](https://github.com/go-git/go-git) project. + +## Installation + +```go +import "github.com/go-git/go-billy/v5" // with go modules enabled (GO111MODULE=on or outside GOPATH) +import "github.com/go-git/go-billy" // with go modules disabled +``` + +## Usage + +Billy exposes filesystems using the +[`Filesystem` interface](https://pkg.go.dev/github.com/go-git/go-billy/v5?tab=doc#Filesystem). +Each filesystem implementation gives you a `New` method, whose arguments depend on +the implementation itself, that returns a new `Filesystem`. + +The following example caches in memory all readable files in a directory from any +billy's filesystem implementation. + +```go +func LoadToMemory(origin billy.Filesystem, path string) (*memory.Memory, error) { + memory := memory.New() + + files, err := origin.ReadDir("/") + if err != nil { + return nil, err + } + + for _, file := range files { + if file.IsDir() { + continue + } + + src, err := origin.Open(file.Name()) + if err != nil { + return nil, err + } + + dst, err := memory.Create(file.Name()) + if err != nil { + return nil, err + } + + if _, err = io.Copy(dst, src); err != nil { + return nil, err + } + + if err := dst.Close(); err != nil { + return nil, err + } + + if err := src.Close(); err != nil { + return nil, err + } + } + + return memory, nil +} +``` + +## Why billy? + +The library billy deals with storage systems and Billy is the name of a well-known, IKEA +bookcase. That's it. + +## License + +Apache License Version 2.0, see [LICENSE](LICENSE) diff --git a/vendor/github.com/go-git/go-billy/v5/fs.go b/vendor/github.com/go-git/go-billy/v5/fs.go new file mode 100644 index 00000000..a9efccde --- /dev/null +++ b/vendor/github.com/go-git/go-billy/v5/fs.go @@ -0,0 +1,202 @@ +package billy + +import ( + "errors" + "io" + "os" + "time" +) + +var ( + ErrReadOnly = errors.New("read-only filesystem") + ErrNotSupported = errors.New("feature not supported") + ErrCrossedBoundary = errors.New("chroot boundary crossed") +) + +// Capability holds the supported features of a billy filesystem. This does +// not mean that the capability has to be supported by the underlying storage. +// For example, a billy filesystem may support WriteCapability but the +// storage be mounted in read only mode. +type Capability uint64 + +const ( + // WriteCapability means that the fs is writable. + WriteCapability Capability = 1 << iota + // ReadCapability means that the fs is readable. + ReadCapability + // ReadAndWriteCapability is the ability to open a file in read and write mode. + ReadAndWriteCapability + // SeekCapability means it is able to move position inside the file. + SeekCapability + // TruncateCapability means that a file can be truncated. + TruncateCapability + // LockCapability is the ability to lock a file. + LockCapability + + // DefaultCapabilities lists all capable features supported by filesystems + // without Capability interface. This list should not be changed until a + // major version is released. + DefaultCapabilities Capability = WriteCapability | ReadCapability | + ReadAndWriteCapability | SeekCapability | TruncateCapability | + LockCapability + + // AllCapabilities lists all capable features. + AllCapabilities Capability = WriteCapability | ReadCapability | + ReadAndWriteCapability | SeekCapability | TruncateCapability | + LockCapability +) + +// Filesystem abstract the operations in a storage-agnostic interface. +// Each method implementation mimics the behavior of the equivalent functions +// at the os package from the standard library. +type Filesystem interface { + Basic + TempFile + Dir + Symlink + Chroot +} + +// Basic abstract the basic operations in a storage-agnostic interface as +// an extension to the Basic interface. +type Basic interface { + // Create creates the named file with mode 0666 (before umask), truncating + // it if it already exists. If successful, methods on the returned File can + // be used for I/O; the associated file descriptor has mode O_RDWR. + Create(filename string) (File, error) + // Open opens the named file for reading. If successful, methods on the + // returned file can be used for reading; the associated file descriptor has + // mode O_RDONLY. + Open(filename string) (File, error) + // OpenFile is the generalized open call; most users will use Open or Create + // instead. It opens the named file with specified flag (O_RDONLY etc.) and + // perm, (0666 etc.) if applicable. If successful, methods on the returned + // File can be used for I/O. + OpenFile(filename string, flag int, perm os.FileMode) (File, error) + // Stat returns a FileInfo describing the named file. + Stat(filename string) (os.FileInfo, error) + // Rename renames (moves) oldpath to newpath. If newpath already exists and + // is not a directory, Rename replaces it. OS-specific restrictions may + // apply when oldpath and newpath are in different directories. + Rename(oldpath, newpath string) error + // Remove removes the named file or directory. + Remove(filename string) error + // Join joins any number of path elements into a single path, adding a + // Separator if necessary. Join calls filepath.Clean on the result; in + // particular, all empty strings are ignored. On Windows, the result is a + // UNC path if and only if the first path element is a UNC path. + Join(elem ...string) string +} + +type TempFile interface { + // TempFile creates a new temporary file in the directory dir with a name + // beginning with prefix, opens the file for reading and writing, and + // returns the resulting *os.File. If dir is the empty string, TempFile + // uses the default directory for temporary files (see os.TempDir). + // Multiple programs calling TempFile simultaneously will not choose the + // same file. The caller can use f.Name() to find the pathname of the file. + // It is the caller's responsibility to remove the file when no longer + // needed. + TempFile(dir, prefix string) (File, error) +} + +// Dir abstract the dir related operations in a storage-agnostic interface as +// an extension to the Basic interface. +type Dir interface { + // ReadDir reads the directory named by dirname and returns a list of + // directory entries sorted by filename. + ReadDir(path string) ([]os.FileInfo, error) + // MkdirAll creates a directory named path, along with any necessary + // parents, and returns nil, or else returns an error. The permission bits + // perm are used for all directories that MkdirAll creates. If path is/ + // already a directory, MkdirAll does nothing and returns nil. + MkdirAll(filename string, perm os.FileMode) error +} + +// Symlink abstract the symlink related operations in a storage-agnostic +// interface as an extension to the Basic interface. +type Symlink interface { + // Lstat returns a FileInfo describing the named file. If the file is a + // symbolic link, the returned FileInfo describes the symbolic link. Lstat + // makes no attempt to follow the link. + Lstat(filename string) (os.FileInfo, error) + // Symlink creates a symbolic-link from link to target. target may be an + // absolute or relative path, and need not refer to an existing node. + // Parent directories of link are created as necessary. + Symlink(target, link string) error + // Readlink returns the target path of link. + Readlink(link string) (string, error) +} + +// Change abstract the FileInfo change related operations in a storage-agnostic +// interface as an extension to the Basic interface +type Change interface { + // Chmod changes the mode of the named file to mode. If the file is a + // symbolic link, it changes the mode of the link's target. + Chmod(name string, mode os.FileMode) error + // Lchown changes the numeric uid and gid of the named file. If the file is + // a symbolic link, it changes the uid and gid of the link itself. + Lchown(name string, uid, gid int) error + // Chown changes the numeric uid and gid of the named file. If the file is a + // symbolic link, it changes the uid and gid of the link's target. + Chown(name string, uid, gid int) error + // Chtimes changes the access and modification times of the named file, + // similar to the Unix utime() or utimes() functions. + // + // The underlying filesystem may truncate or round the values to a less + // precise time unit. + Chtimes(name string, atime time.Time, mtime time.Time) error +} + +// Chroot abstract the chroot related operations in a storage-agnostic interface +// as an extension to the Basic interface. +type Chroot interface { + // Chroot returns a new filesystem from the same type where the new root is + // the given path. Files outside of the designated directory tree cannot be + // accessed. + Chroot(path string) (Filesystem, error) + // Root returns the root path of the filesystem. + Root() string +} + +// File represent a file, being a subset of the os.File +type File interface { + // Name returns the name of the file as presented to Open. + Name() string + io.Writer + io.Reader + io.ReaderAt + io.Seeker + io.Closer + // Lock locks the file like e.g. flock. It protects against access from + // other processes. + Lock() error + // Unlock unlocks the file. + Unlock() error + // Truncate the file. + Truncate(size int64) error +} + +// Capable interface can return the available features of a filesystem. +type Capable interface { + // Capabilities returns the capabilities of a filesystem in bit flags. + Capabilities() Capability +} + +// Capabilities returns the features supported by a filesystem. If the FS +// does not implement Capable interface it returns all features. +func Capabilities(fs Basic) Capability { + capable, ok := fs.(Capable) + if !ok { + return DefaultCapabilities + } + + return capable.Capabilities() +} + +// CapabilityCheck tests the filesystem for the provided capabilities and +// returns true in case it supports all of them. +func CapabilityCheck(fs Basic, capabilities Capability) bool { + fsCaps := Capabilities(fs) + return fsCaps&capabilities == capabilities +} diff --git a/vendor/github.com/go-git/go-billy/v5/helper/chroot/chroot.go b/vendor/github.com/go-git/go-billy/v5/helper/chroot/chroot.go new file mode 100644 index 00000000..8b44e784 --- /dev/null +++ b/vendor/github.com/go-git/go-billy/v5/helper/chroot/chroot.go @@ -0,0 +1,242 @@ +package chroot + +import ( + "os" + "path/filepath" + "strings" + + "github.com/go-git/go-billy/v5" + "github.com/go-git/go-billy/v5/helper/polyfill" +) + +// ChrootHelper is a helper to implement billy.Chroot. +type ChrootHelper struct { + underlying billy.Filesystem + base string +} + +// New creates a new filesystem wrapping up the given 'fs'. +// The created filesystem has its base in the given ChrootHelperectory of the +// underlying filesystem. +func New(fs billy.Basic, base string) billy.Filesystem { + return &ChrootHelper{ + underlying: polyfill.New(fs), + base: base, + } +} + +func (fs *ChrootHelper) underlyingPath(filename string) (string, error) { + if isCrossBoundaries(filename) { + return "", billy.ErrCrossedBoundary + } + + return fs.Join(fs.Root(), filename), nil +} + +func isCrossBoundaries(path string) bool { + path = filepath.ToSlash(path) + path = filepath.Clean(path) + + return strings.HasPrefix(path, ".."+string(filepath.Separator)) +} + +func (fs *ChrootHelper) Create(filename string) (billy.File, error) { + fullpath, err := fs.underlyingPath(filename) + if err != nil { + return nil, err + } + + f, err := fs.underlying.Create(fullpath) + if err != nil { + return nil, err + } + + return newFile(fs, f, filename), nil +} + +func (fs *ChrootHelper) Open(filename string) (billy.File, error) { + fullpath, err := fs.underlyingPath(filename) + if err != nil { + return nil, err + } + + f, err := fs.underlying.Open(fullpath) + if err != nil { + return nil, err + } + + return newFile(fs, f, filename), nil +} + +func (fs *ChrootHelper) OpenFile(filename string, flag int, mode os.FileMode) (billy.File, error) { + fullpath, err := fs.underlyingPath(filename) + if err != nil { + return nil, err + } + + f, err := fs.underlying.OpenFile(fullpath, flag, mode) + if err != nil { + return nil, err + } + + return newFile(fs, f, filename), nil +} + +func (fs *ChrootHelper) Stat(filename string) (os.FileInfo, error) { + fullpath, err := fs.underlyingPath(filename) + if err != nil { + return nil, err + } + + return fs.underlying.Stat(fullpath) +} + +func (fs *ChrootHelper) Rename(from, to string) error { + var err error + from, err = fs.underlyingPath(from) + if err != nil { + return err + } + + to, err = fs.underlyingPath(to) + if err != nil { + return err + } + + return fs.underlying.Rename(from, to) +} + +func (fs *ChrootHelper) Remove(path string) error { + fullpath, err := fs.underlyingPath(path) + if err != nil { + return err + } + + return fs.underlying.Remove(fullpath) +} + +func (fs *ChrootHelper) Join(elem ...string) string { + return fs.underlying.Join(elem...) +} + +func (fs *ChrootHelper) TempFile(dir, prefix string) (billy.File, error) { + fullpath, err := fs.underlyingPath(dir) + if err != nil { + return nil, err + } + + f, err := fs.underlying.(billy.TempFile).TempFile(fullpath, prefix) + if err != nil { + return nil, err + } + + return newFile(fs, f, fs.Join(dir, filepath.Base(f.Name()))), nil +} + +func (fs *ChrootHelper) ReadDir(path string) ([]os.FileInfo, error) { + fullpath, err := fs.underlyingPath(path) + if err != nil { + return nil, err + } + + return fs.underlying.(billy.Dir).ReadDir(fullpath) +} + +func (fs *ChrootHelper) MkdirAll(filename string, perm os.FileMode) error { + fullpath, err := fs.underlyingPath(filename) + if err != nil { + return err + } + + return fs.underlying.(billy.Dir).MkdirAll(fullpath, perm) +} + +func (fs *ChrootHelper) Lstat(filename string) (os.FileInfo, error) { + fullpath, err := fs.underlyingPath(filename) + if err != nil { + return nil, err + } + + return fs.underlying.(billy.Symlink).Lstat(fullpath) +} + +func (fs *ChrootHelper) Symlink(target, link string) error { + target = filepath.FromSlash(target) + + // only rewrite target if it's already absolute + if filepath.IsAbs(target) || strings.HasPrefix(target, string(filepath.Separator)) { + target = fs.Join(fs.Root(), target) + target = filepath.Clean(filepath.FromSlash(target)) + } + + link, err := fs.underlyingPath(link) + if err != nil { + return err + } + + return fs.underlying.(billy.Symlink).Symlink(target, link) +} + +func (fs *ChrootHelper) Readlink(link string) (string, error) { + fullpath, err := fs.underlyingPath(link) + if err != nil { + return "", err + } + + target, err := fs.underlying.(billy.Symlink).Readlink(fullpath) + if err != nil { + return "", err + } + + if !filepath.IsAbs(target) && !strings.HasPrefix(target, string(filepath.Separator)) { + return target, nil + } + + target, err = filepath.Rel(fs.base, target) + if err != nil { + return "", err + } + + return string(os.PathSeparator) + target, nil +} + +func (fs *ChrootHelper) Chroot(path string) (billy.Filesystem, error) { + fullpath, err := fs.underlyingPath(path) + if err != nil { + return nil, err + } + + return New(fs.underlying, fullpath), nil +} + +func (fs *ChrootHelper) Root() string { + return fs.base +} + +func (fs *ChrootHelper) Underlying() billy.Basic { + return fs.underlying +} + +// Capabilities implements the Capable interface. +func (fs *ChrootHelper) Capabilities() billy.Capability { + return billy.Capabilities(fs.underlying) +} + +type file struct { + billy.File + name string +} + +func newFile(fs billy.Filesystem, f billy.File, filename string) billy.File { + filename = fs.Join(fs.Root(), filename) + filename, _ = filepath.Rel(fs.Root(), filename) + + return &file{ + File: f, + name: filename, + } +} + +func (f *file) Name() string { + return f.name +} diff --git a/vendor/github.com/go-git/go-billy/v5/helper/polyfill/polyfill.go b/vendor/github.com/go-git/go-billy/v5/helper/polyfill/polyfill.go new file mode 100644 index 00000000..1efce0e7 --- /dev/null +++ b/vendor/github.com/go-git/go-billy/v5/helper/polyfill/polyfill.go @@ -0,0 +1,105 @@ +package polyfill + +import ( + "os" + "path/filepath" + + "github.com/go-git/go-billy/v5" +) + +// Polyfill is a helper that implements all missing method from billy.Filesystem. +type Polyfill struct { + billy.Basic + c capabilities +} + +type capabilities struct{ tempfile, dir, symlink, chroot bool } + +// New creates a new filesystem wrapping up 'fs' the intercepts all the calls +// made and errors if fs doesn't implement any of the billy interfaces. +func New(fs billy.Basic) billy.Filesystem { + if original, ok := fs.(billy.Filesystem); ok { + return original + } + + h := &Polyfill{Basic: fs} + + _, h.c.tempfile = h.Basic.(billy.TempFile) + _, h.c.dir = h.Basic.(billy.Dir) + _, h.c.symlink = h.Basic.(billy.Symlink) + _, h.c.chroot = h.Basic.(billy.Chroot) + return h +} + +func (h *Polyfill) TempFile(dir, prefix string) (billy.File, error) { + if !h.c.tempfile { + return nil, billy.ErrNotSupported + } + + return h.Basic.(billy.TempFile).TempFile(dir, prefix) +} + +func (h *Polyfill) ReadDir(path string) ([]os.FileInfo, error) { + if !h.c.dir { + return nil, billy.ErrNotSupported + } + + return h.Basic.(billy.Dir).ReadDir(path) +} + +func (h *Polyfill) MkdirAll(filename string, perm os.FileMode) error { + if !h.c.dir { + return billy.ErrNotSupported + } + + return h.Basic.(billy.Dir).MkdirAll(filename, perm) +} + +func (h *Polyfill) Symlink(target, link string) error { + if !h.c.symlink { + return billy.ErrNotSupported + } + + return h.Basic.(billy.Symlink).Symlink(target, link) +} + +func (h *Polyfill) Readlink(link string) (string, error) { + if !h.c.symlink { + return "", billy.ErrNotSupported + } + + return h.Basic.(billy.Symlink).Readlink(link) +} + +func (h *Polyfill) Lstat(path string) (os.FileInfo, error) { + if !h.c.symlink { + return nil, billy.ErrNotSupported + } + + return h.Basic.(billy.Symlink).Lstat(path) +} + +func (h *Polyfill) Chroot(path string) (billy.Filesystem, error) { + if !h.c.chroot { + return nil, billy.ErrNotSupported + } + + return h.Basic.(billy.Chroot).Chroot(path) +} + +func (h *Polyfill) Root() string { + if !h.c.chroot { + return string(filepath.Separator) + } + + return h.Basic.(billy.Chroot).Root() +} + +func (h *Polyfill) Underlying() billy.Basic { + return h.Basic +} + +// Capabilities implements the Capable interface. +func (h *Polyfill) Capabilities() billy.Capability { + return billy.Capabilities(h.Basic) +} diff --git a/vendor/github.com/go-git/go-billy/v5/helper/temporal/temporal.go b/vendor/github.com/go-git/go-billy/v5/helper/temporal/temporal.go new file mode 100644 index 00000000..393dac13 --- /dev/null +++ b/vendor/github.com/go-git/go-billy/v5/helper/temporal/temporal.go @@ -0,0 +1,30 @@ +package temporal + +import ( + "github.com/go-git/go-billy/v5" + "github.com/go-git/go-billy/v5/util" +) + +// Temporal is a helper that implements billy.TempFile over any filesystem. +type Temporal struct { + billy.Filesystem + defaultDir string +} + +// New creates a new filesystem wrapping up 'fs' the intercepts the calls to +// the TempFile method. The param defaultDir is used as default directory were +// the tempfiles are created. +func New(fs billy.Filesystem, defaultDir string) billy.Filesystem { + return &Temporal{ + Filesystem: fs, + defaultDir: defaultDir, + } +} + +func (h *Temporal) TempFile(dir, prefix string) (billy.File, error) { + if dir == "" { + dir = h.defaultDir + } + + return util.TempFile(h.Filesystem, dir, prefix) +} diff --git a/vendor/github.com/go-git/go-billy/v5/memfs/memory.go b/vendor/github.com/go-git/go-billy/v5/memfs/memory.go new file mode 100644 index 00000000..dab73968 --- /dev/null +++ b/vendor/github.com/go-git/go-billy/v5/memfs/memory.go @@ -0,0 +1,410 @@ +// Package memfs provides a billy filesystem base on memory. +package memfs // import "github.com/go-git/go-billy/v5/memfs" + +import ( + "errors" + "fmt" + "io" + "os" + "path/filepath" + "sort" + "strings" + "time" + + "github.com/go-git/go-billy/v5" + "github.com/go-git/go-billy/v5/helper/chroot" + "github.com/go-git/go-billy/v5/util" +) + +const separator = filepath.Separator + +// Memory a very convenient filesystem based on memory files +type Memory struct { + s *storage + + tempCount int +} + +//New returns a new Memory filesystem. +func New() billy.Filesystem { + fs := &Memory{s: newStorage()} + return chroot.New(fs, string(separator)) +} + +func (fs *Memory) Create(filename string) (billy.File, error) { + return fs.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) +} + +func (fs *Memory) Open(filename string) (billy.File, error) { + return fs.OpenFile(filename, os.O_RDONLY, 0) +} + +func (fs *Memory) OpenFile(filename string, flag int, perm os.FileMode) (billy.File, error) { + f, has := fs.s.Get(filename) + if !has { + if !isCreate(flag) { + return nil, os.ErrNotExist + } + + var err error + f, err = fs.s.New(filename, perm, flag) + if err != nil { + return nil, err + } + } else { + if isExclusive(flag) { + return nil, os.ErrExist + } + + if target, isLink := fs.resolveLink(filename, f); isLink { + return fs.OpenFile(target, flag, perm) + } + } + + if f.mode.IsDir() { + return nil, fmt.Errorf("cannot open directory: %s", filename) + } + + return f.Duplicate(filename, perm, flag), nil +} + +var errNotLink = errors.New("not a link") + +func (fs *Memory) resolveLink(fullpath string, f *file) (target string, isLink bool) { + if !isSymlink(f.mode) { + return fullpath, false + } + + target = string(f.content.bytes) + if !isAbs(target) { + target = fs.Join(filepath.Dir(fullpath), target) + } + + return target, true +} + +// On Windows OS, IsAbs validates if a path is valid based on if stars with a +// unit (eg.: `C:\`) to assert that is absolute, but in this mem implementation +// any path starting by `separator` is also considered absolute. +func isAbs(path string) bool { + return filepath.IsAbs(path) || strings.HasPrefix(path, string(separator)) +} + +func (fs *Memory) Stat(filename string) (os.FileInfo, error) { + f, has := fs.s.Get(filename) + if !has { + return nil, os.ErrNotExist + } + + fi, _ := f.Stat() + + var err error + if target, isLink := fs.resolveLink(filename, f); isLink { + fi, err = fs.Stat(target) + if err != nil { + return nil, err + } + } + + // the name of the file should always the name of the stated file, so we + // overwrite the Stat returned from the storage with it, since the + // filename may belong to a link. + fi.(*fileInfo).name = filepath.Base(filename) + return fi, nil +} + +func (fs *Memory) Lstat(filename string) (os.FileInfo, error) { + f, has := fs.s.Get(filename) + if !has { + return nil, os.ErrNotExist + } + + return f.Stat() +} + +type ByName []os.FileInfo + +func (a ByName) Len() int { return len(a) } +func (a ByName) Less(i, j int) bool { return a[i].Name() < a[j].Name() } +func (a ByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +func (fs *Memory) ReadDir(path string) ([]os.FileInfo, error) { + if f, has := fs.s.Get(path); has { + if target, isLink := fs.resolveLink(path, f); isLink { + return fs.ReadDir(target) + } + } + + var entries []os.FileInfo + for _, f := range fs.s.Children(path) { + fi, _ := f.Stat() + entries = append(entries, fi) + } + + sort.Sort(ByName(entries)) + + return entries, nil +} + +func (fs *Memory) MkdirAll(path string, perm os.FileMode) error { + _, err := fs.s.New(path, perm|os.ModeDir, 0) + return err +} + +func (fs *Memory) TempFile(dir, prefix string) (billy.File, error) { + return util.TempFile(fs, dir, prefix) +} + +func (fs *Memory) getTempFilename(dir, prefix string) string { + fs.tempCount++ + filename := fmt.Sprintf("%s_%d_%d", prefix, fs.tempCount, time.Now().UnixNano()) + return fs.Join(dir, filename) +} + +func (fs *Memory) Rename(from, to string) error { + return fs.s.Rename(from, to) +} + +func (fs *Memory) Remove(filename string) error { + return fs.s.Remove(filename) +} + +func (fs *Memory) Join(elem ...string) string { + return filepath.Join(elem...) +} + +func (fs *Memory) Symlink(target, link string) error { + _, err := fs.Stat(link) + if err == nil { + return os.ErrExist + } + + if !os.IsNotExist(err) { + return err + } + + return util.WriteFile(fs, link, []byte(target), 0777|os.ModeSymlink) +} + +func (fs *Memory) Readlink(link string) (string, error) { + f, has := fs.s.Get(link) + if !has { + return "", os.ErrNotExist + } + + if !isSymlink(f.mode) { + return "", &os.PathError{ + Op: "readlink", + Path: link, + Err: fmt.Errorf("not a symlink"), + } + } + + return string(f.content.bytes), nil +} + +// Capabilities implements the Capable interface. +func (fs *Memory) Capabilities() billy.Capability { + return billy.WriteCapability | + billy.ReadCapability | + billy.ReadAndWriteCapability | + billy.SeekCapability | + billy.TruncateCapability +} + +type file struct { + name string + content *content + position int64 + flag int + mode os.FileMode + + isClosed bool +} + +func (f *file) Name() string { + return f.name +} + +func (f *file) Read(b []byte) (int, error) { + n, err := f.ReadAt(b, f.position) + f.position += int64(n) + + if err == io.EOF && n != 0 { + err = nil + } + + return n, err +} + +func (f *file) ReadAt(b []byte, off int64) (int, error) { + if f.isClosed { + return 0, os.ErrClosed + } + + if !isReadAndWrite(f.flag) && !isReadOnly(f.flag) { + return 0, errors.New("read not supported") + } + + n, err := f.content.ReadAt(b, off) + + return n, err +} + +func (f *file) Seek(offset int64, whence int) (int64, error) { + if f.isClosed { + return 0, os.ErrClosed + } + + switch whence { + case io.SeekCurrent: + f.position += offset + case io.SeekStart: + f.position = offset + case io.SeekEnd: + f.position = int64(f.content.Len()) + offset + } + + return f.position, nil +} + +func (f *file) Write(p []byte) (int, error) { + if f.isClosed { + return 0, os.ErrClosed + } + + if !isReadAndWrite(f.flag) && !isWriteOnly(f.flag) { + return 0, errors.New("write not supported") + } + + n, err := f.content.WriteAt(p, f.position) + f.position += int64(n) + + return n, err +} + +func (f *file) Close() error { + if f.isClosed { + return os.ErrClosed + } + + f.isClosed = true + return nil +} + +func (f *file) Truncate(size int64) error { + if size < int64(len(f.content.bytes)) { + f.content.bytes = f.content.bytes[:size] + } else if more := int(size) - len(f.content.bytes); more > 0 { + f.content.bytes = append(f.content.bytes, make([]byte, more)...) + } + + return nil +} + +func (f *file) Duplicate(filename string, mode os.FileMode, flag int) billy.File { + new := &file{ + name: filename, + content: f.content, + mode: mode, + flag: flag, + } + + if isTruncate(flag) { + new.content.Truncate() + } + + if isAppend(flag) { + new.position = int64(new.content.Len()) + } + + return new +} + +func (f *file) Stat() (os.FileInfo, error) { + return &fileInfo{ + name: f.Name(), + mode: f.mode, + size: f.content.Len(), + }, nil +} + +// Lock is a no-op in memfs. +func (f *file) Lock() error { + return nil +} + +// Unlock is a no-op in memfs. +func (f *file) Unlock() error { + return nil +} + +type fileInfo struct { + name string + size int + mode os.FileMode +} + +func (fi *fileInfo) Name() string { + return fi.name +} + +func (fi *fileInfo) Size() int64 { + return int64(fi.size) +} + +func (fi *fileInfo) Mode() os.FileMode { + return fi.mode +} + +func (*fileInfo) ModTime() time.Time { + return time.Now() +} + +func (fi *fileInfo) IsDir() bool { + return fi.mode.IsDir() +} + +func (*fileInfo) Sys() interface{} { + return nil +} + +func (c *content) Truncate() { + c.bytes = make([]byte, 0) +} + +func (c *content) Len() int { + return len(c.bytes) +} + +func isCreate(flag int) bool { + return flag&os.O_CREATE != 0 +} + +func isExclusive(flag int) bool { + return flag&os.O_EXCL != 0 +} + +func isAppend(flag int) bool { + return flag&os.O_APPEND != 0 +} + +func isTruncate(flag int) bool { + return flag&os.O_TRUNC != 0 +} + +func isReadAndWrite(flag int) bool { + return flag&os.O_RDWR != 0 +} + +func isReadOnly(flag int) bool { + return flag == os.O_RDONLY +} + +func isWriteOnly(flag int) bool { + return flag&os.O_WRONLY != 0 +} + +func isSymlink(m os.FileMode) bool { + return m&os.ModeSymlink != 0 +} diff --git a/vendor/github.com/go-git/go-billy/v5/memfs/storage.go b/vendor/github.com/go-git/go-billy/v5/memfs/storage.go new file mode 100644 index 00000000..e3c4e38b --- /dev/null +++ b/vendor/github.com/go-git/go-billy/v5/memfs/storage.go @@ -0,0 +1,238 @@ +package memfs + +import ( + "errors" + "fmt" + "io" + "os" + "path/filepath" + "sync" +) + +type storage struct { + files map[string]*file + children map[string]map[string]*file +} + +func newStorage() *storage { + return &storage{ + files: make(map[string]*file, 0), + children: make(map[string]map[string]*file, 0), + } +} + +func (s *storage) Has(path string) bool { + path = clean(path) + + _, ok := s.files[path] + return ok +} + +func (s *storage) New(path string, mode os.FileMode, flag int) (*file, error) { + path = clean(path) + if s.Has(path) { + if !s.MustGet(path).mode.IsDir() { + return nil, fmt.Errorf("file already exists %q", path) + } + + return nil, nil + } + + name := filepath.Base(path) + + f := &file{ + name: name, + content: &content{name: name}, + mode: mode, + flag: flag, + } + + s.files[path] = f + s.createParent(path, mode, f) + return f, nil +} + +func (s *storage) createParent(path string, mode os.FileMode, f *file) error { + base := filepath.Dir(path) + base = clean(base) + if f.Name() == string(separator) { + return nil + } + + if _, err := s.New(base, mode.Perm()|os.ModeDir, 0); err != nil { + return err + } + + if _, ok := s.children[base]; !ok { + s.children[base] = make(map[string]*file, 0) + } + + s.children[base][f.Name()] = f + return nil +} + +func (s *storage) Children(path string) []*file { + path = clean(path) + + l := make([]*file, 0) + for _, f := range s.children[path] { + l = append(l, f) + } + + return l +} + +func (s *storage) MustGet(path string) *file { + f, ok := s.Get(path) + if !ok { + panic(fmt.Errorf("couldn't find %q", path)) + } + + return f +} + +func (s *storage) Get(path string) (*file, bool) { + path = clean(path) + if !s.Has(path) { + return nil, false + } + + file, ok := s.files[path] + return file, ok +} + +func (s *storage) Rename(from, to string) error { + from = clean(from) + to = clean(to) + + if !s.Has(from) { + return os.ErrNotExist + } + + move := [][2]string{{from, to}} + + for pathFrom := range s.files { + if pathFrom == from || !filepath.HasPrefix(pathFrom, from) { + continue + } + + rel, _ := filepath.Rel(from, pathFrom) + pathTo := filepath.Join(to, rel) + + move = append(move, [2]string{pathFrom, pathTo}) + } + + for _, ops := range move { + from := ops[0] + to := ops[1] + + if err := s.move(from, to); err != nil { + return err + } + } + + return nil +} + +func (s *storage) move(from, to string) error { + s.files[to] = s.files[from] + s.files[to].name = filepath.Base(to) + s.children[to] = s.children[from] + + defer func() { + delete(s.children, from) + delete(s.files, from) + delete(s.children[filepath.Dir(from)], filepath.Base(from)) + }() + + return s.createParent(to, 0644, s.files[to]) +} + +func (s *storage) Remove(path string) error { + path = clean(path) + + f, has := s.Get(path) + if !has { + return os.ErrNotExist + } + + if f.mode.IsDir() && len(s.children[path]) != 0 { + return fmt.Errorf("dir: %s contains files", path) + } + + base, file := filepath.Split(path) + base = filepath.Clean(base) + + delete(s.children[base], file) + delete(s.files, path) + return nil +} + +func clean(path string) string { + return filepath.Clean(filepath.FromSlash(path)) +} + +type content struct { + name string + bytes []byte + + m sync.RWMutex +} + +func (c *content) WriteAt(p []byte, off int64) (int, error) { + if off < 0 { + return 0, &os.PathError{ + Op: "writeat", + Path: c.name, + Err: errors.New("negative offset"), + } + } + + c.m.Lock() + prev := len(c.bytes) + + diff := int(off) - prev + if diff > 0 { + c.bytes = append(c.bytes, make([]byte, diff)...) + } + + c.bytes = append(c.bytes[:off], p...) + if len(c.bytes) < prev { + c.bytes = c.bytes[:prev] + } + c.m.Unlock() + + return len(p), nil +} + +func (c *content) ReadAt(b []byte, off int64) (n int, err error) { + if off < 0 { + return 0, &os.PathError{ + Op: "readat", + Path: c.name, + Err: errors.New("negative offset"), + } + } + + c.m.RLock() + size := int64(len(c.bytes)) + if off >= size { + c.m.RUnlock() + return 0, io.EOF + } + + l := int64(len(b)) + if off+l > size { + l = size - off + } + + btr := c.bytes[off : off+l] + n = copy(b, btr) + + if len(btr) < len(b) { + err = io.EOF + } + c.m.RUnlock() + + return +} diff --git a/vendor/github.com/go-git/go-billy/v5/osfs/os.go b/vendor/github.com/go-git/go-billy/v5/osfs/os.go new file mode 100644 index 00000000..a7fe79f2 --- /dev/null +++ b/vendor/github.com/go-git/go-billy/v5/osfs/os.go @@ -0,0 +1,127 @@ +//go:build !js +// +build !js + +// Package osfs provides a billy filesystem for the OS. +package osfs + +import ( + "fmt" + "io/fs" + "os" + "sync" + + "github.com/go-git/go-billy/v5" +) + +const ( + defaultDirectoryMode = 0o755 + defaultCreateMode = 0o666 +) + +// Default Filesystem representing the root of the os filesystem. +var Default = &ChrootOS{} + +// New returns a new OS filesystem. +// By default paths are deduplicated, but still enforced +// under baseDir. For more info refer to WithDeduplicatePath. +func New(baseDir string, opts ...Option) billy.Filesystem { + o := &options{ + deduplicatePath: true, + } + for _, opt := range opts { + opt(o) + } + + if o.Type == BoundOSFS { + return newBoundOS(baseDir, o.deduplicatePath) + } + + return newChrootOS(baseDir) +} + +// WithBoundOS returns the option of using a Bound filesystem OS. +func WithBoundOS() Option { + return func(o *options) { + o.Type = BoundOSFS + } +} + +// WithChrootOS returns the option of using a Chroot filesystem OS. +func WithChrootOS() Option { + return func(o *options) { + o.Type = ChrootOSFS + } +} + +// WithDeduplicatePath toggles the deduplication of the base dir in the path. +// This occurs when absolute links are being used. +// Assuming base dir /base/dir and an absolute symlink /base/dir/target: +// +// With DeduplicatePath (default): /base/dir/target +// Without DeduplicatePath: /base/dir/base/dir/target +// +// This option is only used by the BoundOS OS type. +func WithDeduplicatePath(enabled bool) Option { + return func(o *options) { + o.deduplicatePath = enabled + } +} + +type options struct { + Type + deduplicatePath bool +} + +type Type int + +const ( + ChrootOSFS Type = iota + BoundOSFS +) + +func readDir(dir string) ([]os.FileInfo, error) { + entries, err := os.ReadDir(dir) + if err != nil { + return nil, err + } + infos := make([]fs.FileInfo, 0, len(entries)) + for _, entry := range entries { + fi, err := entry.Info() + if err != nil { + return nil, err + } + infos = append(infos, fi) + } + return infos, nil +} + +func tempFile(dir, prefix string) (billy.File, error) { + f, err := os.CreateTemp(dir, prefix) + if err != nil { + return nil, err + } + return &file{File: f}, nil +} + +func openFile(fn string, flag int, perm os.FileMode, createDir func(string) error) (billy.File, error) { + if flag&os.O_CREATE != 0 { + if createDir == nil { + return nil, fmt.Errorf("createDir func cannot be nil if file needs to be opened in create mode") + } + if err := createDir(fn); err != nil { + return nil, err + } + } + + f, err := os.OpenFile(fn, flag, perm) + if err != nil { + return nil, err + } + return &file{File: f}, err +} + +// file is a wrapper for an os.File which adds support for file locking. +type file struct { + *os.File + m sync.Mutex +} diff --git a/vendor/github.com/go-git/go-billy/v5/osfs/os_bound.go b/vendor/github.com/go-git/go-billy/v5/osfs/os_bound.go new file mode 100644 index 00000000..b4b6dbc0 --- /dev/null +++ b/vendor/github.com/go-git/go-billy/v5/osfs/os_bound.go @@ -0,0 +1,261 @@ +//go:build !js +// +build !js + +/* + Copyright 2022 The Flux authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package osfs + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + securejoin "github.com/cyphar/filepath-securejoin" + "github.com/go-git/go-billy/v5" +) + +// BoundOS is a fs implementation based on the OS filesystem which is bound to +// a base dir. +// Prefer this fs implementation over ChrootOS. +// +// Behaviours of note: +// 1. Read and write operations can only be directed to files which descends +// from the base dir. +// 2. Symlinks don't have their targets modified, and therefore can point +// to locations outside the base dir or to non-existent paths. +// 3. Readlink and Lstat ensures that the link file is located within the base +// dir, evaluating any symlinks that file or base dir may contain. +type BoundOS struct { + baseDir string + deduplicatePath bool +} + +func newBoundOS(d string, deduplicatePath bool) billy.Filesystem { + return &BoundOS{baseDir: d, deduplicatePath: deduplicatePath} +} + +func (fs *BoundOS) Create(filename string) (billy.File, error) { + return fs.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, defaultCreateMode) +} + +func (fs *BoundOS) OpenFile(filename string, flag int, perm os.FileMode) (billy.File, error) { + fn, err := fs.abs(filename) + if err != nil { + return nil, err + } + return openFile(fn, flag, perm, fs.createDir) +} + +func (fs *BoundOS) ReadDir(path string) ([]os.FileInfo, error) { + dir, err := fs.abs(path) + if err != nil { + return nil, err + } + + return readDir(dir) +} + +func (fs *BoundOS) Rename(from, to string) error { + f, err := fs.abs(from) + if err != nil { + return err + } + t, err := fs.abs(to) + if err != nil { + return err + } + + // MkdirAll for target name. + if err := fs.createDir(t); err != nil { + return err + } + + return os.Rename(f, t) +} + +func (fs *BoundOS) MkdirAll(path string, perm os.FileMode) error { + dir, err := fs.abs(path) + if err != nil { + return err + } + return os.MkdirAll(dir, perm) +} + +func (fs *BoundOS) Open(filename string) (billy.File, error) { + return fs.OpenFile(filename, os.O_RDONLY, 0) +} + +func (fs *BoundOS) Stat(filename string) (os.FileInfo, error) { + filename, err := fs.abs(filename) + if err != nil { + return nil, err + } + return os.Stat(filename) +} + +func (fs *BoundOS) Remove(filename string) error { + fn, err := fs.abs(filename) + if err != nil { + return err + } + return os.Remove(fn) +} + +// TempFile creates a temporary file. If dir is empty, the file +// will be created within the OS Temporary dir. If dir is provided +// it must descend from the current base dir. +func (fs *BoundOS) TempFile(dir, prefix string) (billy.File, error) { + if dir != "" { + var err error + dir, err = fs.abs(dir) + if err != nil { + return nil, err + } + } + + return tempFile(dir, prefix) +} + +func (fs *BoundOS) Join(elem ...string) string { + return filepath.Join(elem...) +} + +func (fs *BoundOS) RemoveAll(path string) error { + dir, err := fs.abs(path) + if err != nil { + return err + } + return os.RemoveAll(dir) +} + +func (fs *BoundOS) Symlink(target, link string) error { + ln, err := fs.abs(link) + if err != nil { + return err + } + // MkdirAll for containing dir. + if err := fs.createDir(ln); err != nil { + return err + } + return os.Symlink(target, ln) +} + +func (fs *BoundOS) Lstat(filename string) (os.FileInfo, error) { + filename = filepath.Clean(filename) + if !filepath.IsAbs(filename) { + filename = filepath.Join(fs.baseDir, filename) + } + if ok, err := fs.insideBaseDirEval(filename); !ok { + return nil, err + } + return os.Lstat(filename) +} + +func (fs *BoundOS) Readlink(link string) (string, error) { + if !filepath.IsAbs(link) { + link = filepath.Clean(filepath.Join(fs.baseDir, link)) + } + if ok, err := fs.insideBaseDirEval(link); !ok { + return "", err + } + return os.Readlink(link) +} + +// Chroot returns a new OS filesystem, with the base dir set to the +// result of joining the provided path with the underlying base dir. +func (fs *BoundOS) Chroot(path string) (billy.Filesystem, error) { + joined, err := securejoin.SecureJoin(fs.baseDir, path) + if err != nil { + return nil, err + } + return New(joined), nil +} + +// Root returns the current base dir of the billy.Filesystem. +// This is required in order for this implementation to be a drop-in +// replacement for other upstream implementations (e.g. memory and osfs). +func (fs *BoundOS) Root() string { + return fs.baseDir +} + +func (fs *BoundOS) createDir(fullpath string) error { + dir := filepath.Dir(fullpath) + if dir != "." { + if err := os.MkdirAll(dir, defaultDirectoryMode); err != nil { + return err + } + } + + return nil +} + +// abs transforms filename to an absolute path, taking into account the base dir. +// Relative paths won't be allowed to ascend the base dir, so `../file` will become +// `/working-dir/file`. +// +// Note that if filename is a symlink, the returned address will be the target of the +// symlink. +func (fs *BoundOS) abs(filename string) (string, error) { + if filename == fs.baseDir { + filename = string(filepath.Separator) + } + + path, err := securejoin.SecureJoin(fs.baseDir, filename) + if err != nil { + return "", nil + } + + if fs.deduplicatePath { + vol := filepath.VolumeName(fs.baseDir) + dup := filepath.Join(fs.baseDir, fs.baseDir[len(vol):]) + if strings.HasPrefix(path, dup+string(filepath.Separator)) { + return fs.abs(path[len(dup):]) + } + } + return path, nil +} + +// insideBaseDir checks whether filename is located within +// the fs.baseDir. +func (fs *BoundOS) insideBaseDir(filename string) (bool, error) { + if filename == fs.baseDir { + return true, nil + } + if !strings.HasPrefix(filename, fs.baseDir+string(filepath.Separator)) { + return false, fmt.Errorf("path outside base dir") + } + return true, nil +} + +// insideBaseDirEval checks whether filename is contained within +// a dir that is within the fs.baseDir, by first evaluating any symlinks +// that either filename or fs.baseDir may contain. +func (fs *BoundOS) insideBaseDirEval(filename string) (bool, error) { + dir, err := filepath.EvalSymlinks(filepath.Dir(filename)) + if dir == "" || os.IsNotExist(err) { + dir = filepath.Dir(filename) + } + wd, err := filepath.EvalSymlinks(fs.baseDir) + if wd == "" || os.IsNotExist(err) { + wd = fs.baseDir + } + if filename != wd && dir != wd && !strings.HasPrefix(dir, wd+string(filepath.Separator)) { + return false, fmt.Errorf("path outside base dir") + } + return true, nil +} diff --git a/vendor/github.com/go-git/go-billy/v5/osfs/os_chroot.go b/vendor/github.com/go-git/go-billy/v5/osfs/os_chroot.go new file mode 100644 index 00000000..fd65e773 --- /dev/null +++ b/vendor/github.com/go-git/go-billy/v5/osfs/os_chroot.go @@ -0,0 +1,112 @@ +//go:build !js +// +build !js + +package osfs + +import ( + "os" + "path/filepath" + + "github.com/go-git/go-billy/v5" + "github.com/go-git/go-billy/v5/helper/chroot" +) + +// ChrootOS is a legacy filesystem based on a "soft chroot" of the os filesystem. +// Although this is still the default os filesystem, consider using BoundOS instead. +// +// Behaviours of note: +// 1. A "soft chroot" translates the base dir to "/" for the purposes of the +// fs abstraction. +// 2. Symlinks targets may be modified to be kept within the chroot bounds. +// 3. Some file modes does not pass-through the fs abstraction. +// 4. The combination of 1 and 2 may cause go-git to think that a Git repository +// is dirty, when in fact it isn't. +type ChrootOS struct{} + +func newChrootOS(baseDir string) billy.Filesystem { + return chroot.New(&ChrootOS{}, baseDir) +} + +func (fs *ChrootOS) Create(filename string) (billy.File, error) { + return fs.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, defaultCreateMode) +} + +func (fs *ChrootOS) OpenFile(filename string, flag int, perm os.FileMode) (billy.File, error) { + return openFile(filename, flag, perm, fs.createDir) +} + +func (fs *ChrootOS) createDir(fullpath string) error { + dir := filepath.Dir(fullpath) + if dir != "." { + if err := os.MkdirAll(dir, defaultDirectoryMode); err != nil { + return err + } + } + + return nil +} + +func (fs *ChrootOS) ReadDir(dir string) ([]os.FileInfo, error) { + return readDir(dir) +} + +func (fs *ChrootOS) Rename(from, to string) error { + if err := fs.createDir(to); err != nil { + return err + } + + return rename(from, to) +} + +func (fs *ChrootOS) MkdirAll(path string, perm os.FileMode) error { + return os.MkdirAll(path, defaultDirectoryMode) +} + +func (fs *ChrootOS) Open(filename string) (billy.File, error) { + return fs.OpenFile(filename, os.O_RDONLY, 0) +} + +func (fs *ChrootOS) Stat(filename string) (os.FileInfo, error) { + return os.Stat(filename) +} + +func (fs *ChrootOS) Remove(filename string) error { + return os.Remove(filename) +} + +func (fs *ChrootOS) TempFile(dir, prefix string) (billy.File, error) { + if err := fs.createDir(dir + string(os.PathSeparator)); err != nil { + return nil, err + } + + return tempFile(dir, prefix) +} + +func (fs *ChrootOS) Join(elem ...string) string { + return filepath.Join(elem...) +} + +func (fs *ChrootOS) RemoveAll(path string) error { + return os.RemoveAll(filepath.Clean(path)) +} + +func (fs *ChrootOS) Lstat(filename string) (os.FileInfo, error) { + return os.Lstat(filepath.Clean(filename)) +} + +func (fs *ChrootOS) Symlink(target, link string) error { + if err := fs.createDir(link); err != nil { + return err + } + + return os.Symlink(target, link) +} + +func (fs *ChrootOS) Readlink(link string) (string, error) { + return os.Readlink(link) +} + +// Capabilities implements the Capable interface. +func (fs *ChrootOS) Capabilities() billy.Capability { + return billy.DefaultCapabilities +} diff --git a/vendor/github.com/go-git/go-billy/v5/osfs/os_js.go b/vendor/github.com/go-git/go-billy/v5/osfs/os_js.go new file mode 100644 index 00000000..2e58aa5c --- /dev/null +++ b/vendor/github.com/go-git/go-billy/v5/osfs/os_js.go @@ -0,0 +1,25 @@ +//go:build js +// +build js + +package osfs + +import ( + "github.com/go-git/go-billy/v5" + "github.com/go-git/go-billy/v5/helper/chroot" + "github.com/go-git/go-billy/v5/memfs" +) + +// globalMemFs is the global memory fs +var globalMemFs = memfs.New() + +// Default Filesystem representing the root of in-memory filesystem for a +// js/wasm environment. +var Default = memfs.New() + +// New returns a new OS filesystem. +func New(baseDir string, _ ...Option) billy.Filesystem { + return chroot.New(Default, Default.Join("/", baseDir)) +} + +type options struct { +} diff --git a/vendor/github.com/go-git/go-billy/v5/osfs/os_options.go b/vendor/github.com/go-git/go-billy/v5/osfs/os_options.go new file mode 100644 index 00000000..2f235c6d --- /dev/null +++ b/vendor/github.com/go-git/go-billy/v5/osfs/os_options.go @@ -0,0 +1,3 @@ +package osfs + +type Option func(*options) diff --git a/vendor/github.com/go-git/go-billy/v5/osfs/os_plan9.go b/vendor/github.com/go-git/go-billy/v5/osfs/os_plan9.go new file mode 100644 index 00000000..84020b52 --- /dev/null +++ b/vendor/github.com/go-git/go-billy/v5/osfs/os_plan9.go @@ -0,0 +1,91 @@ +//go:build plan9 +// +build plan9 + +package osfs + +import ( + "io" + "os" + "path/filepath" + "syscall" +) + +func (f *file) Lock() error { + // Plan 9 uses a mode bit instead of explicit lock/unlock syscalls. + // + // Per http://man.cat-v.org/plan_9/5/stat: “Exclusive use files may be open + // for I/O by only one fid at a time across all clients of the server. If a + // second open is attempted, it draws an error.” + // + // There is no obvious way to implement this function using the exclusive use bit. + // See https://golang.org/src/cmd/go/internal/lockedfile/lockedfile_plan9.go + // for how file locking is done by the go tool on Plan 9. + return nil +} + +func (f *file) Unlock() error { + return nil +} + +func rename(from, to string) error { + // If from and to are in different directories, copy the file + // since Plan 9 does not support cross-directory rename. + if filepath.Dir(from) != filepath.Dir(to) { + fi, err := os.Stat(from) + if err != nil { + return &os.LinkError{"rename", from, to, err} + } + if fi.Mode().IsDir() { + return &os.LinkError{"rename", from, to, syscall.EISDIR} + } + fromFile, err := os.Open(from) + if err != nil { + return &os.LinkError{"rename", from, to, err} + } + toFile, err := os.OpenFile(to, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fi.Mode()) + if err != nil { + return &os.LinkError{"rename", from, to, err} + } + _, err = io.Copy(toFile, fromFile) + if err != nil { + return &os.LinkError{"rename", from, to, err} + } + + // Copy mtime and mode from original file. + // We need only one syscall if we avoid os.Chmod and os.Chtimes. + dir := fi.Sys().(*syscall.Dir) + var d syscall.Dir + d.Null() + d.Mtime = dir.Mtime + d.Mode = dir.Mode + if err = dirwstat(to, &d); err != nil { + return &os.LinkError{"rename", from, to, err} + } + + // Remove original file. + err = os.Remove(from) + if err != nil { + return &os.LinkError{"rename", from, to, err} + } + return nil + } + return os.Rename(from, to) +} + +func dirwstat(name string, d *syscall.Dir) error { + var buf [syscall.STATFIXLEN]byte + + n, err := d.Marshal(buf[:]) + if err != nil { + return &os.PathError{"dirwstat", name, err} + } + if err = syscall.Wstat(name, buf[:n]); err != nil { + return &os.PathError{"dirwstat", name, err} + } + return nil +} + +func umask(new int) func() { + return func() { + } +} diff --git a/vendor/github.com/go-git/go-billy/v5/osfs/os_posix.go b/vendor/github.com/go-git/go-billy/v5/osfs/os_posix.go new file mode 100644 index 00000000..d834a114 --- /dev/null +++ b/vendor/github.com/go-git/go-billy/v5/osfs/os_posix.go @@ -0,0 +1,38 @@ +//go:build !plan9 && !windows && !js +// +build !plan9,!windows,!js + +package osfs + +import ( + "os" + "syscall" + + "golang.org/x/sys/unix" +) + +func (f *file) Lock() error { + f.m.Lock() + defer f.m.Unlock() + + return unix.Flock(int(f.File.Fd()), unix.LOCK_EX) +} + +func (f *file) Unlock() error { + f.m.Lock() + defer f.m.Unlock() + + return unix.Flock(int(f.File.Fd()), unix.LOCK_UN) +} + +func rename(from, to string) error { + return os.Rename(from, to) +} + +// umask sets umask to a new value, and returns a func which allows the +// caller to reset it back to what it was originally. +func umask(new int) func() { + old := syscall.Umask(new) + return func() { + syscall.Umask(old) + } +} diff --git a/vendor/github.com/go-git/go-billy/v5/osfs/os_windows.go b/vendor/github.com/go-git/go-billy/v5/osfs/os_windows.go new file mode 100644 index 00000000..e54df748 --- /dev/null +++ b/vendor/github.com/go-git/go-billy/v5/osfs/os_windows.go @@ -0,0 +1,58 @@ +//go:build windows +// +build windows + +package osfs + +import ( + "os" + "runtime" + "unsafe" + + "golang.org/x/sys/windows" +) + +var ( + kernel32DLL = windows.NewLazySystemDLL("kernel32.dll") + lockFileExProc = kernel32DLL.NewProc("LockFileEx") + unlockFileProc = kernel32DLL.NewProc("UnlockFile") +) + +const ( + lockfileExclusiveLock = 0x2 +) + +func (f *file) Lock() error { + f.m.Lock() + defer f.m.Unlock() + + var overlapped windows.Overlapped + // err is always non-nil as per sys/windows semantics. + ret, _, err := lockFileExProc.Call(f.File.Fd(), lockfileExclusiveLock, 0, 0xFFFFFFFF, 0, + uintptr(unsafe.Pointer(&overlapped))) + runtime.KeepAlive(&overlapped) + if ret == 0 { + return err + } + return nil +} + +func (f *file) Unlock() error { + f.m.Lock() + defer f.m.Unlock() + + // err is always non-nil as per sys/windows semantics. + ret, _, err := unlockFileProc.Call(f.File.Fd(), 0, 0, 0xFFFFFFFF, 0) + if ret == 0 { + return err + } + return nil +} + +func rename(from, to string) error { + return os.Rename(from, to) +} + +func umask(new int) func() { + return func() { + } +} diff --git a/vendor/github.com/go-git/go-billy/v5/util/glob.go b/vendor/github.com/go-git/go-billy/v5/util/glob.go new file mode 100644 index 00000000..f7cb1de8 --- /dev/null +++ b/vendor/github.com/go-git/go-billy/v5/util/glob.go @@ -0,0 +1,111 @@ +package util + +import ( + "path/filepath" + "sort" + "strings" + + "github.com/go-git/go-billy/v5" +) + +// Glob returns the names of all files matching pattern or nil +// if there is no matching file. The syntax of patterns is the same +// as in Match. The pattern may describe hierarchical names such as +// /usr/*/bin/ed (assuming the Separator is '/'). +// +// Glob ignores file system errors such as I/O errors reading directories. +// The only possible returned error is ErrBadPattern, when pattern +// is malformed. +// +// Function originally from https://golang.org/src/path/filepath/match_test.go +func Glob(fs billy.Filesystem, pattern string) (matches []string, err error) { + if !hasMeta(pattern) { + if _, err = fs.Lstat(pattern); err != nil { + return nil, nil + } + return []string{pattern}, nil + } + + dir, file := filepath.Split(pattern) + // Prevent infinite recursion. See issue 15879. + if dir == pattern { + return nil, filepath.ErrBadPattern + } + + var m []string + m, err = Glob(fs, cleanGlobPath(dir)) + if err != nil { + return + } + for _, d := range m { + matches, err = glob(fs, d, file, matches) + if err != nil { + return + } + } + return +} + +// cleanGlobPath prepares path for glob matching. +func cleanGlobPath(path string) string { + switch path { + case "": + return "." + case string(filepath.Separator): + // do nothing to the path + return path + default: + return path[0 : len(path)-1] // chop off trailing separator + } +} + +// glob searches for files matching pattern in the directory dir +// and appends them to matches. If the directory cannot be +// opened, it returns the existing matches. New matches are +// added in lexicographical order. +func glob(fs billy.Filesystem, dir, pattern string, matches []string) (m []string, e error) { + m = matches + fi, err := fs.Stat(dir) + if err != nil { + return + } + + if !fi.IsDir() { + return + } + + names, _ := readdirnames(fs, dir) + sort.Strings(names) + + for _, n := range names { + matched, err := filepath.Match(pattern, n) + if err != nil { + return m, err + } + if matched { + m = append(m, filepath.Join(dir, n)) + } + } + return +} + +// hasMeta reports whether path contains any of the magic characters +// recognized by Match. +func hasMeta(path string) bool { + // TODO(niemeyer): Should other magic characters be added here? + return strings.ContainsAny(path, "*?[") +} + +func readdirnames(fs billy.Filesystem, dir string) ([]string, error) { + files, err := fs.ReadDir(dir) + if err != nil { + return nil, err + } + + var names []string + for _, file := range files { + names = append(names, file.Name()) + } + + return names, nil +} diff --git a/vendor/github.com/go-git/go-billy/v5/util/util.go b/vendor/github.com/go-git/go-billy/v5/util/util.go new file mode 100644 index 00000000..5c77128c --- /dev/null +++ b/vendor/github.com/go-git/go-billy/v5/util/util.go @@ -0,0 +1,282 @@ +package util + +import ( + "io" + "os" + "path/filepath" + "strconv" + "sync" + "time" + + "github.com/go-git/go-billy/v5" +) + +// RemoveAll removes path and any children it contains. It removes everything it +// can but returns the first error it encounters. If the path does not exist, +// RemoveAll returns nil (no error). +func RemoveAll(fs billy.Basic, path string) error { + fs, path = getUnderlyingAndPath(fs, path) + + if r, ok := fs.(removerAll); ok { + return r.RemoveAll(path) + } + + return removeAll(fs, path) +} + +type removerAll interface { + RemoveAll(string) error +} + +func removeAll(fs billy.Basic, path string) error { + // This implementation is adapted from os.RemoveAll. + + // Simple case: if Remove works, we're done. + err := fs.Remove(path) + if err == nil || os.IsNotExist(err) { + return nil + } + + // Otherwise, is this a directory we need to recurse into? + dir, serr := fs.Stat(path) + if serr != nil { + if os.IsNotExist(serr) { + return nil + } + + return serr + } + + if !dir.IsDir() { + // Not a directory; return the error from Remove. + return err + } + + dirfs, ok := fs.(billy.Dir) + if !ok { + return billy.ErrNotSupported + } + + // Directory. + fis, err := dirfs.ReadDir(path) + if err != nil { + if os.IsNotExist(err) { + // Race. It was deleted between the Lstat and Open. + // Return nil per RemoveAll's docs. + return nil + } + + return err + } + + // Remove contents & return first error. + err = nil + for _, fi := range fis { + cpath := fs.Join(path, fi.Name()) + err1 := removeAll(fs, cpath) + if err == nil { + err = err1 + } + } + + // Remove directory. + err1 := fs.Remove(path) + if err1 == nil || os.IsNotExist(err1) { + return nil + } + + if err == nil { + err = err1 + } + + return err + +} + +// WriteFile writes data to a file named by filename in the given filesystem. +// If the file does not exist, WriteFile creates it with permissions perm; +// otherwise WriteFile truncates it before writing. +func WriteFile(fs billy.Basic, filename string, data []byte, perm os.FileMode) error { + f, err := fs.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm) + if err != nil { + return err + } + + n, err := f.Write(data) + if err == nil && n < len(data) { + err = io.ErrShortWrite + } + + if err1 := f.Close(); err == nil { + err = err1 + } + + return err +} + +// Random number state. +// We generate random temporary file names so that there's a good +// chance the file doesn't exist yet - keeps the number of tries in +// TempFile to a minimum. +var rand uint32 +var randmu sync.Mutex + +func reseed() uint32 { + return uint32(time.Now().UnixNano() + int64(os.Getpid())) +} + +func nextSuffix() string { + randmu.Lock() + r := rand + if r == 0 { + r = reseed() + } + r = r*1664525 + 1013904223 // constants from Numerical Recipes + rand = r + randmu.Unlock() + return strconv.Itoa(int(1e9 + r%1e9))[1:] +} + +// TempFile creates a new temporary file in the directory dir with a name +// beginning with prefix, opens the file for reading and writing, and returns +// the resulting *os.File. If dir is the empty string, TempFile uses the default +// directory for temporary files (see os.TempDir). Multiple programs calling +// TempFile simultaneously will not choose the same file. The caller can use +// f.Name() to find the pathname of the file. It is the caller's responsibility +// to remove the file when no longer needed. +func TempFile(fs billy.Basic, dir, prefix string) (f billy.File, err error) { + // This implementation is based on stdlib ioutil.TempFile. + if dir == "" { + dir = getTempDir(fs) + } + + nconflict := 0 + for i := 0; i < 10000; i++ { + name := filepath.Join(dir, prefix+nextSuffix()) + f, err = fs.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) + if os.IsExist(err) { + if nconflict++; nconflict > 10 { + randmu.Lock() + rand = reseed() + randmu.Unlock() + } + continue + } + break + } + return +} + +// TempDir creates a new temporary directory in the directory dir +// with a name beginning with prefix and returns the path of the +// new directory. If dir is the empty string, TempDir uses the +// default directory for temporary files (see os.TempDir). +// Multiple programs calling TempDir simultaneously +// will not choose the same directory. It is the caller's responsibility +// to remove the directory when no longer needed. +func TempDir(fs billy.Dir, dir, prefix string) (name string, err error) { + // This implementation is based on stdlib ioutil.TempDir + + if dir == "" { + dir = getTempDir(fs.(billy.Basic)) + } + + nconflict := 0 + for i := 0; i < 10000; i++ { + try := filepath.Join(dir, prefix+nextSuffix()) + err = fs.MkdirAll(try, 0700) + if os.IsExist(err) { + if nconflict++; nconflict > 10 { + randmu.Lock() + rand = reseed() + randmu.Unlock() + } + continue + } + if os.IsNotExist(err) { + if _, err := os.Stat(dir); os.IsNotExist(err) { + return "", err + } + } + if err == nil { + name = try + } + break + } + return +} + +func getTempDir(fs billy.Basic) string { + ch, ok := fs.(billy.Chroot) + if !ok || ch.Root() == "" || ch.Root() == "/" || ch.Root() == string(filepath.Separator) { + return os.TempDir() + } + + return ".tmp" +} + +type underlying interface { + Underlying() billy.Basic +} + +func getUnderlyingAndPath(fs billy.Basic, path string) (billy.Basic, string) { + u, ok := fs.(underlying) + if !ok { + return fs, path + } + if ch, ok := fs.(billy.Chroot); ok { + path = fs.Join(ch.Root(), path) + } + + return u.Underlying(), path +} + +// ReadFile reads the named file and returns the contents from the given filesystem. +// A successful call returns err == nil, not err == EOF. +// Because ReadFile reads the whole file, it does not treat an EOF from Read +// as an error to be reported. +func ReadFile(fs billy.Basic, name string) ([]byte, error) { + f, err := fs.Open(name) + if err != nil { + return nil, err + } + + defer f.Close() + + var size int + if info, err := fs.Stat(name); err == nil { + size64 := info.Size() + if int64(int(size64)) == size64 { + size = int(size64) + } + } + + size++ // one byte for final read at EOF + // If a file claims a small size, read at least 512 bytes. + // In particular, files in Linux's /proc claim size 0 but + // then do not work right if read in small pieces, + // so an initial read of 1 byte would not work correctly. + + if size < 512 { + size = 512 + } + + data := make([]byte, 0, size) + for { + if len(data) >= cap(data) { + d := append(data[:cap(data)], 0) + data = d[:len(data)] + } + + n, err := f.Read(data[len(data):cap(data)]) + data = data[:len(data)+n] + + if err != nil { + if err == io.EOF { + err = nil + } + + return data, err + } + } +} diff --git a/vendor/github.com/go-git/go-billy/v5/util/walk.go b/vendor/github.com/go-git/go-billy/v5/util/walk.go new file mode 100644 index 00000000..1531bcaa --- /dev/null +++ b/vendor/github.com/go-git/go-billy/v5/util/walk.go @@ -0,0 +1,72 @@ +package util + +import ( + "os" + "path/filepath" + + "github.com/go-git/go-billy/v5" +) + +// walk recursively descends path, calling walkFn +// adapted from https://golang.org/src/path/filepath/path.go +func walk(fs billy.Filesystem, path string, info os.FileInfo, walkFn filepath.WalkFunc) error { + if !info.IsDir() { + return walkFn(path, info, nil) + } + + names, err := readdirnames(fs, path) + err1 := walkFn(path, info, err) + // If err != nil, walk can't walk into this directory. + // err1 != nil means walkFn want walk to skip this directory or stop walking. + // Therefore, if one of err and err1 isn't nil, walk will return. + if err != nil || err1 != nil { + // The caller's behavior is controlled by the return value, which is decided + // by walkFn. walkFn may ignore err and return nil. + // If walkFn returns SkipDir, it will be handled by the caller. + // So walk should return whatever walkFn returns. + return err1 + } + + for _, name := range names { + filename := filepath.Join(path, name) + fileInfo, err := fs.Lstat(filename) + if err != nil { + if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir { + return err + } + } else { + err = walk(fs, filename, fileInfo, walkFn) + if err != nil { + if !fileInfo.IsDir() || err != filepath.SkipDir { + return err + } + } + } + } + return nil +} + +// Walk walks the file tree rooted at root, calling fn for each file or +// directory in the tree, including root. All errors that arise visiting files +// and directories are filtered by fn: see the WalkFunc documentation for +// details. +// +// The files are walked in lexical order, which makes the output deterministic +// but requires Walk to read an entire directory into memory before proceeding +// to walk that directory. Walk does not follow symbolic links. +// +// Function adapted from https://github.com/golang/go/blob/3b770f2ccb1fa6fecc22ea822a19447b10b70c5c/src/path/filepath/path.go#L500 +func Walk(fs billy.Filesystem, root string, walkFn filepath.WalkFunc) error { + info, err := fs.Lstat(root) + if err != nil { + err = walkFn(root, nil, err) + } else { + err = walk(fs, root, info, walkFn) + } + + if err == filepath.SkipDir { + return nil + } + + return err +} diff --git a/vendor/github.com/google/uuid/CHANGELOG.md b/vendor/github.com/google/uuid/CHANGELOG.md new file mode 100644 index 00000000..7ec5ac7e --- /dev/null +++ b/vendor/github.com/google/uuid/CHANGELOG.md @@ -0,0 +1,41 @@ +# Changelog + +## [1.6.0](https://github.com/google/uuid/compare/v1.5.0...v1.6.0) (2024-01-16) + + +### Features + +* add Max UUID constant ([#149](https://github.com/google/uuid/issues/149)) ([c58770e](https://github.com/google/uuid/commit/c58770eb495f55fe2ced6284f93c5158a62e53e3)) + + +### Bug Fixes + +* fix typo in version 7 uuid documentation ([#153](https://github.com/google/uuid/issues/153)) ([016b199](https://github.com/google/uuid/commit/016b199544692f745ffc8867b914129ecb47ef06)) +* Monotonicity in UUIDv7 ([#150](https://github.com/google/uuid/issues/150)) ([a2b2b32](https://github.com/google/uuid/commit/a2b2b32373ff0b1a312b7fdf6d38a977099698a6)) + +## [1.5.0](https://github.com/google/uuid/compare/v1.4.0...v1.5.0) (2023-12-12) + + +### Features + +* Validate UUID without creating new UUID ([#141](https://github.com/google/uuid/issues/141)) ([9ee7366](https://github.com/google/uuid/commit/9ee7366e66c9ad96bab89139418a713dc584ae29)) + +## [1.4.0](https://github.com/google/uuid/compare/v1.3.1...v1.4.0) (2023-10-26) + + +### Features + +* UUIDs slice type with Strings() convenience method ([#133](https://github.com/google/uuid/issues/133)) ([cd5fbbd](https://github.com/google/uuid/commit/cd5fbbdd02f3e3467ac18940e07e062be1f864b4)) + +### Fixes + +* Clarify that Parse's job is to parse but not necessarily validate strings. (Documents current behavior) + +## [1.3.1](https://github.com/google/uuid/compare/v1.3.0...v1.3.1) (2023-08-18) + + +### Bug Fixes + +* Use .EqualFold() to parse urn prefixed UUIDs ([#118](https://github.com/google/uuid/issues/118)) ([574e687](https://github.com/google/uuid/commit/574e6874943741fb99d41764c705173ada5293f0)) + +## Changelog diff --git a/vendor/github.com/google/uuid/CONTRIBUTING.md b/vendor/github.com/google/uuid/CONTRIBUTING.md new file mode 100644 index 00000000..a502fdc5 --- /dev/null +++ b/vendor/github.com/google/uuid/CONTRIBUTING.md @@ -0,0 +1,26 @@ +# How to contribute + +We definitely welcome patches and contribution to this project! + +### Tips + +Commits must be formatted according to the [Conventional Commits Specification](https://www.conventionalcommits.org). + +Always try to include a test case! If it is not possible or not necessary, +please explain why in the pull request description. + +### Releasing + +Commits that would precipitate a SemVer change, as described in the Conventional +Commits Specification, will trigger [`release-please`](https://github.com/google-github-actions/release-please-action) +to create a release candidate pull request. Once submitted, `release-please` +will create a release. + +For tips on how to work with `release-please`, see its documentation. + +### Legal requirements + +In order to protect both you and ourselves, you will need to sign the +[Contributor License Agreement](https://cla.developers.google.com/clas). + +You may have already signed it for other Google projects. diff --git a/vendor/github.com/google/uuid/CONTRIBUTORS b/vendor/github.com/google/uuid/CONTRIBUTORS new file mode 100644 index 00000000..b4bb97f6 --- /dev/null +++ b/vendor/github.com/google/uuid/CONTRIBUTORS @@ -0,0 +1,9 @@ +Paul Borman +bmatsuo +shawnps +theory +jboverfelt +dsymonds +cd1 +wallclockbuilder +dansouza diff --git a/vendor/github.com/google/uuid/LICENSE b/vendor/github.com/google/uuid/LICENSE new file mode 100644 index 00000000..5dc68268 --- /dev/null +++ b/vendor/github.com/google/uuid/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009,2014 Google Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/google/uuid/README.md b/vendor/github.com/google/uuid/README.md new file mode 100644 index 00000000..3e9a6188 --- /dev/null +++ b/vendor/github.com/google/uuid/README.md @@ -0,0 +1,21 @@ +# uuid +The uuid package generates and inspects UUIDs based on +[RFC 4122](https://datatracker.ietf.org/doc/html/rfc4122) +and DCE 1.1: Authentication and Security Services. + +This package is based on the github.com/pborman/uuid package (previously named +code.google.com/p/go-uuid). It differs from these earlier packages in that +a UUID is a 16 byte array rather than a byte slice. One loss due to this +change is the ability to represent an invalid UUID (vs a NIL UUID). + +###### Install +```sh +go get github.com/google/uuid +``` + +###### Documentation +[![Go Reference](https://pkg.go.dev/badge/github.com/google/uuid.svg)](https://pkg.go.dev/github.com/google/uuid) + +Full `go doc` style documentation for the package can be viewed online without +installing this package by using the GoDoc site here: +http://pkg.go.dev/github.com/google/uuid diff --git a/vendor/github.com/google/uuid/dce.go b/vendor/github.com/google/uuid/dce.go new file mode 100644 index 00000000..fa820b9d --- /dev/null +++ b/vendor/github.com/google/uuid/dce.go @@ -0,0 +1,80 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "encoding/binary" + "fmt" + "os" +) + +// A Domain represents a Version 2 domain +type Domain byte + +// Domain constants for DCE Security (Version 2) UUIDs. +const ( + Person = Domain(0) + Group = Domain(1) + Org = Domain(2) +) + +// NewDCESecurity returns a DCE Security (Version 2) UUID. +// +// The domain should be one of Person, Group or Org. +// On a POSIX system the id should be the users UID for the Person +// domain and the users GID for the Group. The meaning of id for +// the domain Org or on non-POSIX systems is site defined. +// +// For a given domain/id pair the same token may be returned for up to +// 7 minutes and 10 seconds. +func NewDCESecurity(domain Domain, id uint32) (UUID, error) { + uuid, err := NewUUID() + if err == nil { + uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2 + uuid[9] = byte(domain) + binary.BigEndian.PutUint32(uuid[0:], id) + } + return uuid, err +} + +// NewDCEPerson returns a DCE Security (Version 2) UUID in the person +// domain with the id returned by os.Getuid. +// +// NewDCESecurity(Person, uint32(os.Getuid())) +func NewDCEPerson() (UUID, error) { + return NewDCESecurity(Person, uint32(os.Getuid())) +} + +// NewDCEGroup returns a DCE Security (Version 2) UUID in the group +// domain with the id returned by os.Getgid. +// +// NewDCESecurity(Group, uint32(os.Getgid())) +func NewDCEGroup() (UUID, error) { + return NewDCESecurity(Group, uint32(os.Getgid())) +} + +// Domain returns the domain for a Version 2 UUID. Domains are only defined +// for Version 2 UUIDs. +func (uuid UUID) Domain() Domain { + return Domain(uuid[9]) +} + +// ID returns the id for a Version 2 UUID. IDs are only defined for Version 2 +// UUIDs. +func (uuid UUID) ID() uint32 { + return binary.BigEndian.Uint32(uuid[0:4]) +} + +func (d Domain) String() string { + switch d { + case Person: + return "Person" + case Group: + return "Group" + case Org: + return "Org" + } + return fmt.Sprintf("Domain%d", int(d)) +} diff --git a/vendor/github.com/google/uuid/doc.go b/vendor/github.com/google/uuid/doc.go new file mode 100644 index 00000000..5b8a4b9a --- /dev/null +++ b/vendor/github.com/google/uuid/doc.go @@ -0,0 +1,12 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package uuid generates and inspects UUIDs. +// +// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security +// Services. +// +// A UUID is a 16 byte (128 bit) array. UUIDs may be used as keys to +// maps or compared directly. +package uuid diff --git a/vendor/github.com/google/uuid/hash.go b/vendor/github.com/google/uuid/hash.go new file mode 100644 index 00000000..dc60082d --- /dev/null +++ b/vendor/github.com/google/uuid/hash.go @@ -0,0 +1,59 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "crypto/md5" + "crypto/sha1" + "hash" +) + +// Well known namespace IDs and UUIDs +var ( + NameSpaceDNS = Must(Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8")) + NameSpaceURL = Must(Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8")) + NameSpaceOID = Must(Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8")) + NameSpaceX500 = Must(Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8")) + Nil UUID // empty UUID, all zeros + + // The Max UUID is special form of UUID that is specified to have all 128 bits set to 1. + Max = UUID{ + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + } +) + +// NewHash returns a new UUID derived from the hash of space concatenated with +// data generated by h. The hash should be at least 16 byte in length. The +// first 16 bytes of the hash are used to form the UUID. The version of the +// UUID will be the lower 4 bits of version. NewHash is used to implement +// NewMD5 and NewSHA1. +func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID { + h.Reset() + h.Write(space[:]) //nolint:errcheck + h.Write(data) //nolint:errcheck + s := h.Sum(nil) + var uuid UUID + copy(uuid[:], s) + uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4) + uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant + return uuid +} + +// NewMD5 returns a new MD5 (Version 3) UUID based on the +// supplied name space and data. It is the same as calling: +// +// NewHash(md5.New(), space, data, 3) +func NewMD5(space UUID, data []byte) UUID { + return NewHash(md5.New(), space, data, 3) +} + +// NewSHA1 returns a new SHA1 (Version 5) UUID based on the +// supplied name space and data. It is the same as calling: +// +// NewHash(sha1.New(), space, data, 5) +func NewSHA1(space UUID, data []byte) UUID { + return NewHash(sha1.New(), space, data, 5) +} diff --git a/vendor/github.com/google/uuid/marshal.go b/vendor/github.com/google/uuid/marshal.go new file mode 100644 index 00000000..14bd3407 --- /dev/null +++ b/vendor/github.com/google/uuid/marshal.go @@ -0,0 +1,38 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import "fmt" + +// MarshalText implements encoding.TextMarshaler. +func (uuid UUID) MarshalText() ([]byte, error) { + var js [36]byte + encodeHex(js[:], uuid) + return js[:], nil +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (uuid *UUID) UnmarshalText(data []byte) error { + id, err := ParseBytes(data) + if err != nil { + return err + } + *uuid = id + return nil +} + +// MarshalBinary implements encoding.BinaryMarshaler. +func (uuid UUID) MarshalBinary() ([]byte, error) { + return uuid[:], nil +} + +// UnmarshalBinary implements encoding.BinaryUnmarshaler. +func (uuid *UUID) UnmarshalBinary(data []byte) error { + if len(data) != 16 { + return fmt.Errorf("invalid UUID (got %d bytes)", len(data)) + } + copy(uuid[:], data) + return nil +} diff --git a/vendor/github.com/google/uuid/node.go b/vendor/github.com/google/uuid/node.go new file mode 100644 index 00000000..d651a2b0 --- /dev/null +++ b/vendor/github.com/google/uuid/node.go @@ -0,0 +1,90 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "sync" +) + +var ( + nodeMu sync.Mutex + ifname string // name of interface being used + nodeID [6]byte // hardware for version 1 UUIDs + zeroID [6]byte // nodeID with only 0's +) + +// NodeInterface returns the name of the interface from which the NodeID was +// derived. The interface "user" is returned if the NodeID was set by +// SetNodeID. +func NodeInterface() string { + defer nodeMu.Unlock() + nodeMu.Lock() + return ifname +} + +// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs. +// If name is "" then the first usable interface found will be used or a random +// Node ID will be generated. If a named interface cannot be found then false +// is returned. +// +// SetNodeInterface never fails when name is "". +func SetNodeInterface(name string) bool { + defer nodeMu.Unlock() + nodeMu.Lock() + return setNodeInterface(name) +} + +func setNodeInterface(name string) bool { + iname, addr := getHardwareInterface(name) // null implementation for js + if iname != "" && addr != nil { + ifname = iname + copy(nodeID[:], addr) + return true + } + + // We found no interfaces with a valid hardware address. If name + // does not specify a specific interface generate a random Node ID + // (section 4.1.6) + if name == "" { + ifname = "random" + randomBits(nodeID[:]) + return true + } + return false +} + +// NodeID returns a slice of a copy of the current Node ID, setting the Node ID +// if not already set. +func NodeID() []byte { + defer nodeMu.Unlock() + nodeMu.Lock() + if nodeID == zeroID { + setNodeInterface("") + } + nid := nodeID + return nid[:] +} + +// SetNodeID sets the Node ID to be used for Version 1 UUIDs. The first 6 bytes +// of id are used. If id is less than 6 bytes then false is returned and the +// Node ID is not set. +func SetNodeID(id []byte) bool { + if len(id) < 6 { + return false + } + defer nodeMu.Unlock() + nodeMu.Lock() + copy(nodeID[:], id) + ifname = "user" + return true +} + +// NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is +// not valid. The NodeID is only well defined for version 1 and 2 UUIDs. +func (uuid UUID) NodeID() []byte { + var node [6]byte + copy(node[:], uuid[10:]) + return node[:] +} diff --git a/vendor/github.com/google/uuid/node_js.go b/vendor/github.com/google/uuid/node_js.go new file mode 100644 index 00000000..b2a0bc87 --- /dev/null +++ b/vendor/github.com/google/uuid/node_js.go @@ -0,0 +1,12 @@ +// Copyright 2017 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build js + +package uuid + +// getHardwareInterface returns nil values for the JS version of the code. +// This removes the "net" dependency, because it is not used in the browser. +// Using the "net" library inflates the size of the transpiled JS code by 673k bytes. +func getHardwareInterface(name string) (string, []byte) { return "", nil } diff --git a/vendor/github.com/google/uuid/node_net.go b/vendor/github.com/google/uuid/node_net.go new file mode 100644 index 00000000..0cbbcddb --- /dev/null +++ b/vendor/github.com/google/uuid/node_net.go @@ -0,0 +1,33 @@ +// Copyright 2017 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !js + +package uuid + +import "net" + +var interfaces []net.Interface // cached list of interfaces + +// getHardwareInterface returns the name and hardware address of interface name. +// If name is "" then the name and hardware address of one of the system's +// interfaces is returned. If no interfaces are found (name does not exist or +// there are no interfaces) then "", nil is returned. +// +// Only addresses of at least 6 bytes are returned. +func getHardwareInterface(name string) (string, []byte) { + if interfaces == nil { + var err error + interfaces, err = net.Interfaces() + if err != nil { + return "", nil + } + } + for _, ifs := range interfaces { + if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) { + return ifs.Name, ifs.HardwareAddr + } + } + return "", nil +} diff --git a/vendor/github.com/google/uuid/null.go b/vendor/github.com/google/uuid/null.go new file mode 100644 index 00000000..d7fcbf28 --- /dev/null +++ b/vendor/github.com/google/uuid/null.go @@ -0,0 +1,118 @@ +// Copyright 2021 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "bytes" + "database/sql/driver" + "encoding/json" + "fmt" +) + +var jsonNull = []byte("null") + +// NullUUID represents a UUID that may be null. +// NullUUID implements the SQL driver.Scanner interface so +// it can be used as a scan destination: +// +// var u uuid.NullUUID +// err := db.QueryRow("SELECT name FROM foo WHERE id=?", id).Scan(&u) +// ... +// if u.Valid { +// // use u.UUID +// } else { +// // NULL value +// } +// +type NullUUID struct { + UUID UUID + Valid bool // Valid is true if UUID is not NULL +} + +// Scan implements the SQL driver.Scanner interface. +func (nu *NullUUID) Scan(value interface{}) error { + if value == nil { + nu.UUID, nu.Valid = Nil, false + return nil + } + + err := nu.UUID.Scan(value) + if err != nil { + nu.Valid = false + return err + } + + nu.Valid = true + return nil +} + +// Value implements the driver Valuer interface. +func (nu NullUUID) Value() (driver.Value, error) { + if !nu.Valid { + return nil, nil + } + // Delegate to UUID Value function + return nu.UUID.Value() +} + +// MarshalBinary implements encoding.BinaryMarshaler. +func (nu NullUUID) MarshalBinary() ([]byte, error) { + if nu.Valid { + return nu.UUID[:], nil + } + + return []byte(nil), nil +} + +// UnmarshalBinary implements encoding.BinaryUnmarshaler. +func (nu *NullUUID) UnmarshalBinary(data []byte) error { + if len(data) != 16 { + return fmt.Errorf("invalid UUID (got %d bytes)", len(data)) + } + copy(nu.UUID[:], data) + nu.Valid = true + return nil +} + +// MarshalText implements encoding.TextMarshaler. +func (nu NullUUID) MarshalText() ([]byte, error) { + if nu.Valid { + return nu.UUID.MarshalText() + } + + return jsonNull, nil +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (nu *NullUUID) UnmarshalText(data []byte) error { + id, err := ParseBytes(data) + if err != nil { + nu.Valid = false + return err + } + nu.UUID = id + nu.Valid = true + return nil +} + +// MarshalJSON implements json.Marshaler. +func (nu NullUUID) MarshalJSON() ([]byte, error) { + if nu.Valid { + return json.Marshal(nu.UUID) + } + + return jsonNull, nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (nu *NullUUID) UnmarshalJSON(data []byte) error { + if bytes.Equal(data, jsonNull) { + *nu = NullUUID{} + return nil // valid null UUID + } + err := json.Unmarshal(data, &nu.UUID) + nu.Valid = err == nil + return err +} diff --git a/vendor/github.com/google/uuid/sql.go b/vendor/github.com/google/uuid/sql.go new file mode 100644 index 00000000..2e02ec06 --- /dev/null +++ b/vendor/github.com/google/uuid/sql.go @@ -0,0 +1,59 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "database/sql/driver" + "fmt" +) + +// Scan implements sql.Scanner so UUIDs can be read from databases transparently. +// Currently, database types that map to string and []byte are supported. Please +// consult database-specific driver documentation for matching types. +func (uuid *UUID) Scan(src interface{}) error { + switch src := src.(type) { + case nil: + return nil + + case string: + // if an empty UUID comes from a table, we return a null UUID + if src == "" { + return nil + } + + // see Parse for required string format + u, err := Parse(src) + if err != nil { + return fmt.Errorf("Scan: %v", err) + } + + *uuid = u + + case []byte: + // if an empty UUID comes from a table, we return a null UUID + if len(src) == 0 { + return nil + } + + // assumes a simple slice of bytes if 16 bytes + // otherwise attempts to parse + if len(src) != 16 { + return uuid.Scan(string(src)) + } + copy((*uuid)[:], src) + + default: + return fmt.Errorf("Scan: unable to scan type %T into UUID", src) + } + + return nil +} + +// Value implements sql.Valuer so that UUIDs can be written to databases +// transparently. Currently, UUIDs map to strings. Please consult +// database-specific driver documentation for matching types. +func (uuid UUID) Value() (driver.Value, error) { + return uuid.String(), nil +} diff --git a/vendor/github.com/google/uuid/time.go b/vendor/github.com/google/uuid/time.go new file mode 100644 index 00000000..c3511292 --- /dev/null +++ b/vendor/github.com/google/uuid/time.go @@ -0,0 +1,134 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "encoding/binary" + "sync" + "time" +) + +// A Time represents a time as the number of 100's of nanoseconds since 15 Oct +// 1582. +type Time int64 + +const ( + lillian = 2299160 // Julian day of 15 Oct 1582 + unix = 2440587 // Julian day of 1 Jan 1970 + epoch = unix - lillian // Days between epochs + g1582 = epoch * 86400 // seconds between epochs + g1582ns100 = g1582 * 10000000 // 100s of a nanoseconds between epochs +) + +var ( + timeMu sync.Mutex + lasttime uint64 // last time we returned + clockSeq uint16 // clock sequence for this run + + timeNow = time.Now // for testing +) + +// UnixTime converts t the number of seconds and nanoseconds using the Unix +// epoch of 1 Jan 1970. +func (t Time) UnixTime() (sec, nsec int64) { + sec = int64(t - g1582ns100) + nsec = (sec % 10000000) * 100 + sec /= 10000000 + return sec, nsec +} + +// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and +// clock sequence as well as adjusting the clock sequence as needed. An error +// is returned if the current time cannot be determined. +func GetTime() (Time, uint16, error) { + defer timeMu.Unlock() + timeMu.Lock() + return getTime() +} + +func getTime() (Time, uint16, error) { + t := timeNow() + + // If we don't have a clock sequence already, set one. + if clockSeq == 0 { + setClockSequence(-1) + } + now := uint64(t.UnixNano()/100) + g1582ns100 + + // If time has gone backwards with this clock sequence then we + // increment the clock sequence + if now <= lasttime { + clockSeq = ((clockSeq + 1) & 0x3fff) | 0x8000 + } + lasttime = now + return Time(now), clockSeq, nil +} + +// ClockSequence returns the current clock sequence, generating one if not +// already set. The clock sequence is only used for Version 1 UUIDs. +// +// The uuid package does not use global static storage for the clock sequence or +// the last time a UUID was generated. Unless SetClockSequence is used, a new +// random clock sequence is generated the first time a clock sequence is +// requested by ClockSequence, GetTime, or NewUUID. (section 4.2.1.1) +func ClockSequence() int { + defer timeMu.Unlock() + timeMu.Lock() + return clockSequence() +} + +func clockSequence() int { + if clockSeq == 0 { + setClockSequence(-1) + } + return int(clockSeq & 0x3fff) +} + +// SetClockSequence sets the clock sequence to the lower 14 bits of seq. Setting to +// -1 causes a new sequence to be generated. +func SetClockSequence(seq int) { + defer timeMu.Unlock() + timeMu.Lock() + setClockSequence(seq) +} + +func setClockSequence(seq int) { + if seq == -1 { + var b [2]byte + randomBits(b[:]) // clock sequence + seq = int(b[0])<<8 | int(b[1]) + } + oldSeq := clockSeq + clockSeq = uint16(seq&0x3fff) | 0x8000 // Set our variant + if oldSeq != clockSeq { + lasttime = 0 + } +} + +// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in +// uuid. The time is only defined for version 1, 2, 6 and 7 UUIDs. +func (uuid UUID) Time() Time { + var t Time + switch uuid.Version() { + case 6: + time := binary.BigEndian.Uint64(uuid[:8]) // Ignore uuid[6] version b0110 + t = Time(time) + case 7: + time := binary.BigEndian.Uint64(uuid[:8]) + t = Time((time>>16)*10000 + g1582ns100) + default: // forward compatible + time := int64(binary.BigEndian.Uint32(uuid[0:4])) + time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32 + time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48 + t = Time(time) + } + return t +} + +// ClockSequence returns the clock sequence encoded in uuid. +// The clock sequence is only well defined for version 1 and 2 UUIDs. +func (uuid UUID) ClockSequence() int { + return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff +} diff --git a/vendor/github.com/google/uuid/util.go b/vendor/github.com/google/uuid/util.go new file mode 100644 index 00000000..5ea6c737 --- /dev/null +++ b/vendor/github.com/google/uuid/util.go @@ -0,0 +1,43 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "io" +) + +// randomBits completely fills slice b with random data. +func randomBits(b []byte) { + if _, err := io.ReadFull(rander, b); err != nil { + panic(err.Error()) // rand should never fail + } +} + +// xvalues returns the value of a byte as a hexadecimal digit or 255. +var xvalues = [256]byte{ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255, + 255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, +} + +// xtob converts hex characters x1 and x2 into a byte. +func xtob(x1, x2 byte) (byte, bool) { + b1 := xvalues[x1] + b2 := xvalues[x2] + return (b1 << 4) | b2, b1 != 255 && b2 != 255 +} diff --git a/vendor/github.com/google/uuid/uuid.go b/vendor/github.com/google/uuid/uuid.go new file mode 100644 index 00000000..5232b486 --- /dev/null +++ b/vendor/github.com/google/uuid/uuid.go @@ -0,0 +1,365 @@ +// Copyright 2018 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "bytes" + "crypto/rand" + "encoding/hex" + "errors" + "fmt" + "io" + "strings" + "sync" +) + +// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC +// 4122. +type UUID [16]byte + +// A Version represents a UUID's version. +type Version byte + +// A Variant represents a UUID's variant. +type Variant byte + +// Constants returned by Variant. +const ( + Invalid = Variant(iota) // Invalid UUID + RFC4122 // The variant specified in RFC4122 + Reserved // Reserved, NCS backward compatibility. + Microsoft // Reserved, Microsoft Corporation backward compatibility. + Future // Reserved for future definition. +) + +const randPoolSize = 16 * 16 + +var ( + rander = rand.Reader // random function + poolEnabled = false + poolMu sync.Mutex + poolPos = randPoolSize // protected with poolMu + pool [randPoolSize]byte // protected with poolMu +) + +type invalidLengthError struct{ len int } + +func (err invalidLengthError) Error() string { + return fmt.Sprintf("invalid UUID length: %d", err.len) +} + +// IsInvalidLengthError is matcher function for custom error invalidLengthError +func IsInvalidLengthError(err error) bool { + _, ok := err.(invalidLengthError) + return ok +} + +// Parse decodes s into a UUID or returns an error if it cannot be parsed. Both +// the standard UUID forms defined in RFC 4122 +// (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and +// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) are decoded. In addition, +// Parse accepts non-standard strings such as the raw hex encoding +// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx and 38 byte "Microsoft style" encodings, +// e.g. {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}. Only the middle 36 bytes are +// examined in the latter case. Parse should not be used to validate strings as +// it parses non-standard encodings as indicated above. +func Parse(s string) (UUID, error) { + var uuid UUID + switch len(s) { + // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + case 36: + + // urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + case 36 + 9: + if !strings.EqualFold(s[:9], "urn:uuid:") { + return uuid, fmt.Errorf("invalid urn prefix: %q", s[:9]) + } + s = s[9:] + + // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} + case 36 + 2: + s = s[1:] + + // xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + case 32: + var ok bool + for i := range uuid { + uuid[i], ok = xtob(s[i*2], s[i*2+1]) + if !ok { + return uuid, errors.New("invalid UUID format") + } + } + return uuid, nil + default: + return uuid, invalidLengthError{len(s)} + } + // s is now at least 36 bytes long + // it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' { + return uuid, errors.New("invalid UUID format") + } + for i, x := range [16]int{ + 0, 2, 4, 6, + 9, 11, + 14, 16, + 19, 21, + 24, 26, 28, 30, 32, 34, + } { + v, ok := xtob(s[x], s[x+1]) + if !ok { + return uuid, errors.New("invalid UUID format") + } + uuid[i] = v + } + return uuid, nil +} + +// ParseBytes is like Parse, except it parses a byte slice instead of a string. +func ParseBytes(b []byte) (UUID, error) { + var uuid UUID + switch len(b) { + case 36: // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + case 36 + 9: // urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + if !bytes.EqualFold(b[:9], []byte("urn:uuid:")) { + return uuid, fmt.Errorf("invalid urn prefix: %q", b[:9]) + } + b = b[9:] + case 36 + 2: // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} + b = b[1:] + case 32: // xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + var ok bool + for i := 0; i < 32; i += 2 { + uuid[i/2], ok = xtob(b[i], b[i+1]) + if !ok { + return uuid, errors.New("invalid UUID format") + } + } + return uuid, nil + default: + return uuid, invalidLengthError{len(b)} + } + // s is now at least 36 bytes long + // it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + if b[8] != '-' || b[13] != '-' || b[18] != '-' || b[23] != '-' { + return uuid, errors.New("invalid UUID format") + } + for i, x := range [16]int{ + 0, 2, 4, 6, + 9, 11, + 14, 16, + 19, 21, + 24, 26, 28, 30, 32, 34, + } { + v, ok := xtob(b[x], b[x+1]) + if !ok { + return uuid, errors.New("invalid UUID format") + } + uuid[i] = v + } + return uuid, nil +} + +// MustParse is like Parse but panics if the string cannot be parsed. +// It simplifies safe initialization of global variables holding compiled UUIDs. +func MustParse(s string) UUID { + uuid, err := Parse(s) + if err != nil { + panic(`uuid: Parse(` + s + `): ` + err.Error()) + } + return uuid +} + +// FromBytes creates a new UUID from a byte slice. Returns an error if the slice +// does not have a length of 16. The bytes are copied from the slice. +func FromBytes(b []byte) (uuid UUID, err error) { + err = uuid.UnmarshalBinary(b) + return uuid, err +} + +// Must returns uuid if err is nil and panics otherwise. +func Must(uuid UUID, err error) UUID { + if err != nil { + panic(err) + } + return uuid +} + +// Validate returns an error if s is not a properly formatted UUID in one of the following formats: +// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx +// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx +// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +// {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} +// It returns an error if the format is invalid, otherwise nil. +func Validate(s string) error { + switch len(s) { + // Standard UUID format + case 36: + + // UUID with "urn:uuid:" prefix + case 36 + 9: + if !strings.EqualFold(s[:9], "urn:uuid:") { + return fmt.Errorf("invalid urn prefix: %q", s[:9]) + } + s = s[9:] + + // UUID enclosed in braces + case 36 + 2: + if s[0] != '{' || s[len(s)-1] != '}' { + return fmt.Errorf("invalid bracketed UUID format") + } + s = s[1 : len(s)-1] + + // UUID without hyphens + case 32: + for i := 0; i < len(s); i += 2 { + _, ok := xtob(s[i], s[i+1]) + if !ok { + return errors.New("invalid UUID format") + } + } + + default: + return invalidLengthError{len(s)} + } + + // Check for standard UUID format + if len(s) == 36 { + if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' { + return errors.New("invalid UUID format") + } + for _, x := range []int{0, 2, 4, 6, 9, 11, 14, 16, 19, 21, 24, 26, 28, 30, 32, 34} { + if _, ok := xtob(s[x], s[x+1]); !ok { + return errors.New("invalid UUID format") + } + } + } + + return nil +} + +// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx +// , or "" if uuid is invalid. +func (uuid UUID) String() string { + var buf [36]byte + encodeHex(buf[:], uuid) + return string(buf[:]) +} + +// URN returns the RFC 2141 URN form of uuid, +// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, or "" if uuid is invalid. +func (uuid UUID) URN() string { + var buf [36 + 9]byte + copy(buf[:], "urn:uuid:") + encodeHex(buf[9:], uuid) + return string(buf[:]) +} + +func encodeHex(dst []byte, uuid UUID) { + hex.Encode(dst, uuid[:4]) + dst[8] = '-' + hex.Encode(dst[9:13], uuid[4:6]) + dst[13] = '-' + hex.Encode(dst[14:18], uuid[6:8]) + dst[18] = '-' + hex.Encode(dst[19:23], uuid[8:10]) + dst[23] = '-' + hex.Encode(dst[24:], uuid[10:]) +} + +// Variant returns the variant encoded in uuid. +func (uuid UUID) Variant() Variant { + switch { + case (uuid[8] & 0xc0) == 0x80: + return RFC4122 + case (uuid[8] & 0xe0) == 0xc0: + return Microsoft + case (uuid[8] & 0xe0) == 0xe0: + return Future + default: + return Reserved + } +} + +// Version returns the version of uuid. +func (uuid UUID) Version() Version { + return Version(uuid[6] >> 4) +} + +func (v Version) String() string { + if v > 15 { + return fmt.Sprintf("BAD_VERSION_%d", v) + } + return fmt.Sprintf("VERSION_%d", v) +} + +func (v Variant) String() string { + switch v { + case RFC4122: + return "RFC4122" + case Reserved: + return "Reserved" + case Microsoft: + return "Microsoft" + case Future: + return "Future" + case Invalid: + return "Invalid" + } + return fmt.Sprintf("BadVariant%d", int(v)) +} + +// SetRand sets the random number generator to r, which implements io.Reader. +// If r.Read returns an error when the package requests random data then +// a panic will be issued. +// +// Calling SetRand with nil sets the random number generator to the default +// generator. +func SetRand(r io.Reader) { + if r == nil { + rander = rand.Reader + return + } + rander = r +} + +// EnableRandPool enables internal randomness pool used for Random +// (Version 4) UUID generation. The pool contains random bytes read from +// the random number generator on demand in batches. Enabling the pool +// may improve the UUID generation throughput significantly. +// +// Since the pool is stored on the Go heap, this feature may be a bad fit +// for security sensitive applications. +// +// Both EnableRandPool and DisableRandPool are not thread-safe and should +// only be called when there is no possibility that New or any other +// UUID Version 4 generation function will be called concurrently. +func EnableRandPool() { + poolEnabled = true +} + +// DisableRandPool disables the randomness pool if it was previously +// enabled with EnableRandPool. +// +// Both EnableRandPool and DisableRandPool are not thread-safe and should +// only be called when there is no possibility that New or any other +// UUID Version 4 generation function will be called concurrently. +func DisableRandPool() { + poolEnabled = false + defer poolMu.Unlock() + poolMu.Lock() + poolPos = randPoolSize +} + +// UUIDs is a slice of UUID types. +type UUIDs []UUID + +// Strings returns a string slice containing the string form of each UUID in uuids. +func (uuids UUIDs) Strings() []string { + var uuidStrs = make([]string, len(uuids)) + for i, uuid := range uuids { + uuidStrs[i] = uuid.String() + } + return uuidStrs +} diff --git a/vendor/github.com/google/uuid/version1.go b/vendor/github.com/google/uuid/version1.go new file mode 100644 index 00000000..46310962 --- /dev/null +++ b/vendor/github.com/google/uuid/version1.go @@ -0,0 +1,44 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "encoding/binary" +) + +// NewUUID returns a Version 1 UUID based on the current NodeID and clock +// sequence, and the current time. If the NodeID has not been set by SetNodeID +// or SetNodeInterface then it will be set automatically. If the NodeID cannot +// be set NewUUID returns nil. If clock sequence has not been set by +// SetClockSequence then it will be set automatically. If GetTime fails to +// return the current NewUUID returns nil and an error. +// +// In most cases, New should be used. +func NewUUID() (UUID, error) { + var uuid UUID + now, seq, err := GetTime() + if err != nil { + return uuid, err + } + + timeLow := uint32(now & 0xffffffff) + timeMid := uint16((now >> 32) & 0xffff) + timeHi := uint16((now >> 48) & 0x0fff) + timeHi |= 0x1000 // Version 1 + + binary.BigEndian.PutUint32(uuid[0:], timeLow) + binary.BigEndian.PutUint16(uuid[4:], timeMid) + binary.BigEndian.PutUint16(uuid[6:], timeHi) + binary.BigEndian.PutUint16(uuid[8:], seq) + + nodeMu.Lock() + if nodeID == zeroID { + setNodeInterface("") + } + copy(uuid[10:], nodeID[:]) + nodeMu.Unlock() + + return uuid, nil +} diff --git a/vendor/github.com/google/uuid/version4.go b/vendor/github.com/google/uuid/version4.go new file mode 100644 index 00000000..7697802e --- /dev/null +++ b/vendor/github.com/google/uuid/version4.go @@ -0,0 +1,76 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import "io" + +// New creates a new random UUID or panics. New is equivalent to +// the expression +// +// uuid.Must(uuid.NewRandom()) +func New() UUID { + return Must(NewRandom()) +} + +// NewString creates a new random UUID and returns it as a string or panics. +// NewString is equivalent to the expression +// +// uuid.New().String() +func NewString() string { + return Must(NewRandom()).String() +} + +// NewRandom returns a Random (Version 4) UUID. +// +// The strength of the UUIDs is based on the strength of the crypto/rand +// package. +// +// Uses the randomness pool if it was enabled with EnableRandPool. +// +// A note about uniqueness derived from the UUID Wikipedia entry: +// +// Randomly generated UUIDs have 122 random bits. One's annual risk of being +// hit by a meteorite is estimated to be one chance in 17 billion, that +// means the probability is about 0.00000000006 (6 × 10−11), +// equivalent to the odds of creating a few tens of trillions of UUIDs in a +// year and having one duplicate. +func NewRandom() (UUID, error) { + if !poolEnabled { + return NewRandomFromReader(rander) + } + return newRandomFromPool() +} + +// NewRandomFromReader returns a UUID based on bytes read from a given io.Reader. +func NewRandomFromReader(r io.Reader) (UUID, error) { + var uuid UUID + _, err := io.ReadFull(r, uuid[:]) + if err != nil { + return Nil, err + } + uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4 + uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10 + return uuid, nil +} + +func newRandomFromPool() (UUID, error) { + var uuid UUID + poolMu.Lock() + if poolPos == randPoolSize { + _, err := io.ReadFull(rander, pool[:]) + if err != nil { + poolMu.Unlock() + return Nil, err + } + poolPos = 0 + } + copy(uuid[:], pool[poolPos:(poolPos+16)]) + poolPos += 16 + poolMu.Unlock() + + uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4 + uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10 + return uuid, nil +} diff --git a/vendor/github.com/google/uuid/version6.go b/vendor/github.com/google/uuid/version6.go new file mode 100644 index 00000000..339a959a --- /dev/null +++ b/vendor/github.com/google/uuid/version6.go @@ -0,0 +1,56 @@ +// Copyright 2023 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import "encoding/binary" + +// UUID version 6 is a field-compatible version of UUIDv1, reordered for improved DB locality. +// It is expected that UUIDv6 will primarily be used in contexts where there are existing v1 UUIDs. +// Systems that do not involve legacy UUIDv1 SHOULD consider using UUIDv7 instead. +// +// see https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-03#uuidv6 +// +// NewV6 returns a Version 6 UUID based on the current NodeID and clock +// sequence, and the current time. If the NodeID has not been set by SetNodeID +// or SetNodeInterface then it will be set automatically. If the NodeID cannot +// be set NewV6 set NodeID is random bits automatically . If clock sequence has not been set by +// SetClockSequence then it will be set automatically. If GetTime fails to +// return the current NewV6 returns Nil and an error. +func NewV6() (UUID, error) { + var uuid UUID + now, seq, err := GetTime() + if err != nil { + return uuid, err + } + + /* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | time_high | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | time_mid | time_low_and_version | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |clk_seq_hi_res | clk_seq_low | node (0-1) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | node (2-5) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + + binary.BigEndian.PutUint64(uuid[0:], uint64(now)) + binary.BigEndian.PutUint16(uuid[8:], seq) + + uuid[6] = 0x60 | (uuid[6] & 0x0F) + uuid[8] = 0x80 | (uuid[8] & 0x3F) + + nodeMu.Lock() + if nodeID == zeroID { + setNodeInterface("") + } + copy(uuid[10:], nodeID[:]) + nodeMu.Unlock() + + return uuid, nil +} diff --git a/vendor/github.com/google/uuid/version7.go b/vendor/github.com/google/uuid/version7.go new file mode 100644 index 00000000..3167b643 --- /dev/null +++ b/vendor/github.com/google/uuid/version7.go @@ -0,0 +1,104 @@ +// Copyright 2023 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "io" +) + +// UUID version 7 features a time-ordered value field derived from the widely +// implemented and well known Unix Epoch timestamp source, +// the number of milliseconds seconds since midnight 1 Jan 1970 UTC, leap seconds excluded. +// As well as improved entropy characteristics over versions 1 or 6. +// +// see https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-03#name-uuid-version-7 +// +// Implementations SHOULD utilize UUID version 7 over UUID version 1 and 6 if possible. +// +// NewV7 returns a Version 7 UUID based on the current time(Unix Epoch). +// Uses the randomness pool if it was enabled with EnableRandPool. +// On error, NewV7 returns Nil and an error +func NewV7() (UUID, error) { + uuid, err := NewRandom() + if err != nil { + return uuid, err + } + makeV7(uuid[:]) + return uuid, nil +} + +// NewV7FromReader returns a Version 7 UUID based on the current time(Unix Epoch). +// it use NewRandomFromReader fill random bits. +// On error, NewV7FromReader returns Nil and an error. +func NewV7FromReader(r io.Reader) (UUID, error) { + uuid, err := NewRandomFromReader(r) + if err != nil { + return uuid, err + } + + makeV7(uuid[:]) + return uuid, nil +} + +// makeV7 fill 48 bits time (uuid[0] - uuid[5]), set version b0111 (uuid[6]) +// uuid[8] already has the right version number (Variant is 10) +// see function NewV7 and NewV7FromReader +func makeV7(uuid []byte) { + /* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | unix_ts_ms | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | unix_ts_ms | ver | rand_a (12 bit seq) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |var| rand_b | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | rand_b | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + _ = uuid[15] // bounds check + + t, s := getV7Time() + + uuid[0] = byte(t >> 40) + uuid[1] = byte(t >> 32) + uuid[2] = byte(t >> 24) + uuid[3] = byte(t >> 16) + uuid[4] = byte(t >> 8) + uuid[5] = byte(t) + + uuid[6] = 0x70 | (0x0F & byte(s>>8)) + uuid[7] = byte(s) +} + +// lastV7time is the last time we returned stored as: +// +// 52 bits of time in milliseconds since epoch +// 12 bits of (fractional nanoseconds) >> 8 +var lastV7time int64 + +const nanoPerMilli = 1000000 + +// getV7Time returns the time in milliseconds and nanoseconds / 256. +// The returned (milli << 12 + seq) is guarenteed to be greater than +// (milli << 12 + seq) returned by any previous call to getV7Time. +func getV7Time() (milli, seq int64) { + timeMu.Lock() + defer timeMu.Unlock() + + nano := timeNow().UnixNano() + milli = nano / nanoPerMilli + // Sequence number is between 0 and 3906 (nanoPerMilli>>8) + seq = (nano - milli*nanoPerMilli) >> 8 + now := milli<<12 + seq + if now <= lastV7time { + now = lastV7time + 1 + milli = now >> 12 + seq = now & 0xfff + } + lastV7time = now + return milli, seq +} diff --git a/vendor/github.com/hashicorp/golang-lru/v2/.gitignore b/vendor/github.com/hashicorp/golang-lru/v2/.gitignore new file mode 100644 index 00000000..83656241 --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/v2/.gitignore @@ -0,0 +1,23 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test diff --git a/vendor/github.com/hashicorp/golang-lru/v2/.golangci.yml b/vendor/github.com/hashicorp/golang-lru/v2/.golangci.yml new file mode 100644 index 00000000..7e7b8a96 --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/v2/.golangci.yml @@ -0,0 +1,46 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +linters: + fast: false + disable-all: true + enable: + - revive + - megacheck + - govet + - unconvert + - gas + - gocyclo + - dupl + - misspell + - unparam + - unused + - typecheck + - ineffassign + # - stylecheck + - exportloopref + - gocritic + - nakedret + - gosimple + - prealloc + +# golangci-lint configuration file +linters-settings: + revive: + ignore-generated-header: true + severity: warning + rules: + - name: package-comments + severity: warning + disabled: true + - name: exported + severity: warning + disabled: false + arguments: ["checkPrivateReceivers", "disableStutteringCheck"] + +issues: + exclude-use-default: false + exclude-rules: + - path: _test\.go + linters: + - dupl diff --git a/vendor/github.com/hashicorp/golang-lru/v2/2q.go b/vendor/github.com/hashicorp/golang-lru/v2/2q.go new file mode 100644 index 00000000..8c95252b --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/v2/2q.go @@ -0,0 +1,267 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package lru + +import ( + "errors" + "sync" + + "github.com/hashicorp/golang-lru/v2/simplelru" +) + +const ( + // Default2QRecentRatio is the ratio of the 2Q cache dedicated + // to recently added entries that have only been accessed once. + Default2QRecentRatio = 0.25 + + // Default2QGhostEntries is the default ratio of ghost + // entries kept to track entries recently evicted + Default2QGhostEntries = 0.50 +) + +// TwoQueueCache is a thread-safe fixed size 2Q cache. +// 2Q is an enhancement over the standard LRU cache +// in that it tracks both frequently and recently used +// entries separately. This avoids a burst in access to new +// entries from evicting frequently used entries. It adds some +// additional tracking overhead to the standard LRU cache, and is +// computationally about 2x the cost, and adds some metadata over +// head. The ARCCache is similar, but does not require setting any +// parameters. +type TwoQueueCache[K comparable, V any] struct { + size int + recentSize int + recentRatio float64 + ghostRatio float64 + + recent simplelru.LRUCache[K, V] + frequent simplelru.LRUCache[K, V] + recentEvict simplelru.LRUCache[K, struct{}] + lock sync.RWMutex +} + +// New2Q creates a new TwoQueueCache using the default +// values for the parameters. +func New2Q[K comparable, V any](size int) (*TwoQueueCache[K, V], error) { + return New2QParams[K, V](size, Default2QRecentRatio, Default2QGhostEntries) +} + +// New2QParams creates a new TwoQueueCache using the provided +// parameter values. +func New2QParams[K comparable, V any](size int, recentRatio, ghostRatio float64) (*TwoQueueCache[K, V], error) { + if size <= 0 { + return nil, errors.New("invalid size") + } + if recentRatio < 0.0 || recentRatio > 1.0 { + return nil, errors.New("invalid recent ratio") + } + if ghostRatio < 0.0 || ghostRatio > 1.0 { + return nil, errors.New("invalid ghost ratio") + } + + // Determine the sub-sizes + recentSize := int(float64(size) * recentRatio) + evictSize := int(float64(size) * ghostRatio) + + // Allocate the LRUs + recent, err := simplelru.NewLRU[K, V](size, nil) + if err != nil { + return nil, err + } + frequent, err := simplelru.NewLRU[K, V](size, nil) + if err != nil { + return nil, err + } + recentEvict, err := simplelru.NewLRU[K, struct{}](evictSize, nil) + if err != nil { + return nil, err + } + + // Initialize the cache + c := &TwoQueueCache[K, V]{ + size: size, + recentSize: recentSize, + recentRatio: recentRatio, + ghostRatio: ghostRatio, + recent: recent, + frequent: frequent, + recentEvict: recentEvict, + } + return c, nil +} + +// Get looks up a key's value from the cache. +func (c *TwoQueueCache[K, V]) Get(key K) (value V, ok bool) { + c.lock.Lock() + defer c.lock.Unlock() + + // Check if this is a frequent value + if val, ok := c.frequent.Get(key); ok { + return val, ok + } + + // If the value is contained in recent, then we + // promote it to frequent + if val, ok := c.recent.Peek(key); ok { + c.recent.Remove(key) + c.frequent.Add(key, val) + return val, ok + } + + // No hit + return +} + +// Add adds a value to the cache. +func (c *TwoQueueCache[K, V]) Add(key K, value V) { + c.lock.Lock() + defer c.lock.Unlock() + + // Check if the value is frequently used already, + // and just update the value + if c.frequent.Contains(key) { + c.frequent.Add(key, value) + return + } + + // Check if the value is recently used, and promote + // the value into the frequent list + if c.recent.Contains(key) { + c.recent.Remove(key) + c.frequent.Add(key, value) + return + } + + // If the value was recently evicted, add it to the + // frequently used list + if c.recentEvict.Contains(key) { + c.ensureSpace(true) + c.recentEvict.Remove(key) + c.frequent.Add(key, value) + return + } + + // Add to the recently seen list + c.ensureSpace(false) + c.recent.Add(key, value) +} + +// ensureSpace is used to ensure we have space in the cache +func (c *TwoQueueCache[K, V]) ensureSpace(recentEvict bool) { + // If we have space, nothing to do + recentLen := c.recent.Len() + freqLen := c.frequent.Len() + if recentLen+freqLen < c.size { + return + } + + // If the recent buffer is larger than + // the target, evict from there + if recentLen > 0 && (recentLen > c.recentSize || (recentLen == c.recentSize && !recentEvict)) { + k, _, _ := c.recent.RemoveOldest() + c.recentEvict.Add(k, struct{}{}) + return + } + + // Remove from the frequent list otherwise + c.frequent.RemoveOldest() +} + +// Len returns the number of items in the cache. +func (c *TwoQueueCache[K, V]) Len() int { + c.lock.RLock() + defer c.lock.RUnlock() + return c.recent.Len() + c.frequent.Len() +} + +// Resize changes the cache size. +func (c *TwoQueueCache[K, V]) Resize(size int) (evicted int) { + c.lock.Lock() + defer c.lock.Unlock() + + // Recalculate the sub-sizes + recentSize := int(float64(size) * c.recentRatio) + evictSize := int(float64(size) * c.ghostRatio) + c.size = size + c.recentSize = recentSize + + // ensureSpace + diff := c.recent.Len() + c.frequent.Len() - size + if diff < 0 { + diff = 0 + } + for i := 0; i < diff; i++ { + c.ensureSpace(true) + } + + // Reallocate the LRUs + c.recent.Resize(size) + c.frequent.Resize(size) + c.recentEvict.Resize(evictSize) + + return diff +} + +// Keys returns a slice of the keys in the cache. +// The frequently used keys are first in the returned slice. +func (c *TwoQueueCache[K, V]) Keys() []K { + c.lock.RLock() + defer c.lock.RUnlock() + k1 := c.frequent.Keys() + k2 := c.recent.Keys() + return append(k1, k2...) +} + +// Values returns a slice of the values in the cache. +// The frequently used values are first in the returned slice. +func (c *TwoQueueCache[K, V]) Values() []V { + c.lock.RLock() + defer c.lock.RUnlock() + v1 := c.frequent.Values() + v2 := c.recent.Values() + return append(v1, v2...) +} + +// Remove removes the provided key from the cache. +func (c *TwoQueueCache[K, V]) Remove(key K) { + c.lock.Lock() + defer c.lock.Unlock() + if c.frequent.Remove(key) { + return + } + if c.recent.Remove(key) { + return + } + if c.recentEvict.Remove(key) { + return + } +} + +// Purge is used to completely clear the cache. +func (c *TwoQueueCache[K, V]) Purge() { + c.lock.Lock() + defer c.lock.Unlock() + c.recent.Purge() + c.frequent.Purge() + c.recentEvict.Purge() +} + +// Contains is used to check if the cache contains a key +// without updating recency or frequency. +func (c *TwoQueueCache[K, V]) Contains(key K) bool { + c.lock.RLock() + defer c.lock.RUnlock() + return c.frequent.Contains(key) || c.recent.Contains(key) +} + +// Peek is used to inspect the cache value of a key +// without updating recency or frequency. +func (c *TwoQueueCache[K, V]) Peek(key K) (value V, ok bool) { + c.lock.RLock() + defer c.lock.RUnlock() + if val, ok := c.frequent.Peek(key); ok { + return val, ok + } + return c.recent.Peek(key) +} diff --git a/vendor/github.com/hashicorp/golang-lru/v2/LICENSE b/vendor/github.com/hashicorp/golang-lru/v2/LICENSE new file mode 100644 index 00000000..0e5d580e --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/v2/LICENSE @@ -0,0 +1,364 @@ +Copyright (c) 2014 HashiCorp, Inc. + +Mozilla Public License, version 2.0 + +1. Definitions + +1.1. "Contributor" + + means each individual or legal entity that creates, contributes to the + creation of, or owns Covered Software. + +1.2. "Contributor Version" + + means the combination of the Contributions of others (if any) used by a + Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + + means Source Code Form to which the initial Contributor has attached the + notice in Exhibit A, the Executable Form of such Source Code Form, and + Modifications of such Source Code Form, in each case including portions + thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + a. that the initial Contributor has attached the notice described in + Exhibit B to the Covered Software; or + + b. that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the terms of + a Secondary License. + +1.6. "Executable Form" + + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + + means a work that combines Covered Software with other material, in a + separate file or files, that is not Covered Software. + +1.8. "License" + + means this document. + +1.9. "Licensable" + + means having the right to grant, to the maximum extent possible, whether + at the time of the initial grant or subsequently, any and all of the + rights conveyed by this License. + +1.10. "Modifications" + + means any of the following: + + a. any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered Software; or + + b. any new file in Source Code Form that contains any Covered Software. + +1.11. "Patent Claims" of a Contributor + + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the License, + by the making, using, selling, offering for sale, having made, import, + or transfer of either its Contributions or its Contributor Version. + +1.12. "Secondary License" + + means either the GNU General Public License, Version 2.0, the GNU Lesser + General Public License, Version 2.1, the GNU Affero General Public + License, Version 3.0, or any later versions of those licenses. + +1.13. "Source Code Form" + + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that controls, is + controlled by, or is under common control with You. For purposes of this + definition, "control" means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by contract or + otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. + + +2. License Grants and Conditions + +2.1. Grants + + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + a. under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + + b. under Patent Claims of such Contributor to make, use, sell, offer for + sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + + The licenses granted in Section 2.1 with respect to any Contribution + become effective for each Contribution on the date the Contributor first + distributes such Contribution. + +2.3. Limitations on Grant Scope + + The licenses granted in this Section 2 are the only rights granted under + this License. No additional rights or licenses will be implied from the + distribution or licensing of Covered Software under this License. + Notwithstanding Section 2.1(b) above, no patent license is granted by a + Contributor: + + a. for any code that a Contributor has removed from Covered Software; or + + b. for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + + c. under Patent Claims infringed by Covered Software in the absence of + its Contributions. + + This License does not grant any rights in the trademarks, service marks, + or logos of any Contributor (except as may be necessary to comply with + the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this + License (see Section 10.2) or under the terms of a Secondary License (if + permitted under the terms of Section 3.3). + +2.5. Representation + + Each Contributor represents that the Contributor believes its + Contributions are its original creation(s) or it has sufficient rights to + grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + + This License is not intended to limit any rights You have under + applicable copyright doctrines of fair use, fair dealing, or other + equivalents. + +2.7. Conditions + + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in + Section 2.1. + + +3. Responsibilities + +3.1. Distribution of Source Form + + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under + the terms of this License. You must inform recipients that the Source + Code Form of the Covered Software is governed by the terms of this + License, and how they can obtain a copy of this License. You may not + attempt to alter or restrict the recipients' rights in the Source Code + Form. + +3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + + a. such Covered Software must also be made available in Source Code Form, + as described in Section 3.1, and You must inform recipients of the + Executable Form how they can obtain a copy of such Source Code Form by + reasonable means in a timely manner, at a charge no more than the cost + of distribution to the recipient; and + + b. You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter the + recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for + the Covered Software. If the Larger Work is a combination of Covered + Software with a work governed by one or more Secondary Licenses, and the + Covered Software is not Incompatible With Secondary Licenses, this + License permits You to additionally distribute such Covered Software + under the terms of such Secondary License(s), so that the recipient of + the Larger Work may, at their option, further distribute the Covered + Software under the terms of either this License or such Secondary + License(s). + +3.4. Notices + + You may not remove or alter the substance of any license notices + (including copyright notices, patent notices, disclaimers of warranty, or + limitations of liability) contained within the Source Code Form of the + Covered Software, except that You may alter any license notices to the + extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on + behalf of any Contributor. You must make it absolutely clear that any + such warranty, support, indemnity, or liability obligation is offered by + You alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + + If it is impossible for You to comply with any of the terms of this License + with respect to some or all of the Covered Software due to statute, + judicial order, or regulation then You must: (a) comply with the terms of + this License to the maximum extent possible; and (b) describe the + limitations and the code they affect. Such description must be placed in a + text file included with all distributions of the Covered Software under + this License. Except to the extent prohibited by statute or regulation, + such description must be sufficiently detailed for a recipient of ordinary + skill to be able to understand it. + +5. Termination + +5.1. The rights granted under this License will terminate automatically if You + fail to comply with any of its terms. However, if You become compliant, + then the rights granted under this License from a particular Contributor + are reinstated (a) provisionally, unless and until such Contributor + explicitly and finally terminates Your grants, and (b) on an ongoing + basis, if such Contributor fails to notify You of the non-compliance by + some reasonable means prior to 60 days after You have come back into + compliance. Moreover, Your grants from a particular Contributor are + reinstated on an ongoing basis if such Contributor notifies You of the + non-compliance by some reasonable means, this is the first time You have + received notice of non-compliance with this License from such + Contributor, and You become compliant prior to 30 days after Your receipt + of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, + counter-claims, and cross-claims) alleging that a Contributor Version + directly or indirectly infringes any patent, then the rights granted to + You by any and all Contributors for the Covered Software under Section + 2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user + license agreements (excluding distributors and resellers) which have been + validly granted by You or Your distributors under this License prior to + termination shall survive termination. + +6. Disclaimer of Warranty + + Covered Software is provided under this License on an "as is" basis, + without warranty of any kind, either expressed, implied, or statutory, + including, without limitation, warranties that the Covered Software is free + of defects, merchantable, fit for a particular purpose or non-infringing. + The entire risk as to the quality and performance of the Covered Software + is with You. Should any Covered Software prove defective in any respect, + You (not any Contributor) assume the cost of any necessary servicing, + repair, or correction. This disclaimer of warranty constitutes an essential + part of this License. No use of any Covered Software is authorized under + this License except under this disclaimer. + +7. Limitation of Liability + + Under no circumstances and under no legal theory, whether tort (including + negligence), contract, or otherwise, shall any Contributor, or anyone who + distributes Covered Software as permitted above, be liable to You for any + direct, indirect, special, incidental, or consequential damages of any + character including, without limitation, damages for lost profits, loss of + goodwill, work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses, even if such party shall have been + informed of the possibility of such damages. This limitation of liability + shall not apply to liability for death or personal injury resulting from + such party's negligence to the extent applicable law prohibits such + limitation. Some jurisdictions do not allow the exclusion or limitation of + incidental or consequential damages, so this exclusion and limitation may + not apply to You. + +8. Litigation + + Any litigation relating to this License may be brought only in the courts + of a jurisdiction where the defendant maintains its principal place of + business and such litigation shall be governed by laws of that + jurisdiction, without reference to its conflict-of-law provisions. Nothing + in this Section shall prevent a party's ability to bring cross-claims or + counter-claims. + +9. Miscellaneous + + This License represents the complete agreement concerning the subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. Any law or regulation which provides that + the language of a contract shall be construed against the drafter shall not + be used to construe this License against a Contributor. + + +10. Versions of the License + +10.1. New Versions + + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. + +10.2. Effect of New Versions + + You may distribute the Covered Software under the terms of the version + of the License under which You originally received the Covered Software, + or under the terms of any subsequent version published by the license + steward. + +10.3. Modified Versions + + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a + modified version of this License if you rename the license and remove + any references to the name of the license steward (except to note that + such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary + Licenses If You choose to distribute Source Code Form that is + Incompatible With Secondary Licenses under the terms of this version of + the License, the notice described in Exhibit B of this License must be + attached. + +Exhibit A - Source Code Form License Notice + + This Source Code Form is subject to the + terms of the Mozilla Public License, v. + 2.0. If a copy of the MPL was not + distributed with this file, You can + obtain one at + http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, +then You may include the notice in a location (such as a LICENSE file in a +relevant directory) where a recipient would be likely to look for such a +notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice + + This Source Code Form is "Incompatible + With Secondary Licenses", as defined by + the Mozilla Public License, v. 2.0. diff --git a/vendor/github.com/hashicorp/golang-lru/v2/README.md b/vendor/github.com/hashicorp/golang-lru/v2/README.md new file mode 100644 index 00000000..a942eb53 --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/v2/README.md @@ -0,0 +1,79 @@ +golang-lru +========== + +This provides the `lru` package which implements a fixed-size +thread safe LRU cache. It is based on the cache in Groupcache. + +Documentation +============= + +Full docs are available on [Go Packages](https://pkg.go.dev/github.com/hashicorp/golang-lru/v2) + +LRU cache example +================= + +```go +package main + +import ( + "fmt" + "github.com/hashicorp/golang-lru/v2" +) + +func main() { + l, _ := lru.New[int, any](128) + for i := 0; i < 256; i++ { + l.Add(i, nil) + } + if l.Len() != 128 { + panic(fmt.Sprintf("bad len: %v", l.Len())) + } +} +``` + +Expirable LRU cache example +=========================== + +```go +package main + +import ( + "fmt" + "time" + + "github.com/hashicorp/golang-lru/v2/expirable" +) + +func main() { + // make cache with 10ms TTL and 5 max keys + cache := expirable.NewLRU[string, string](5, nil, time.Millisecond*10) + + + // set value under key1. + cache.Add("key1", "val1") + + // get value under key1 + r, ok := cache.Get("key1") + + // check for OK value + if ok { + fmt.Printf("value before expiration is found: %v, value: %q\n", ok, r) + } + + // wait for cache to expire + time.Sleep(time.Millisecond * 12) + + // get value under key1 after key expiration + r, ok = cache.Get("key1") + fmt.Printf("value after expiration is found: %v, value: %q\n", ok, r) + + // set value under key2, would evict old entry because it is already expired. + cache.Add("key2", "val2") + + fmt.Printf("Cache len: %d\n", cache.Len()) + // Output: + // value before expiration is found: true, value: "val1" + // value after expiration is found: false, value: "" + // Cache len: 1 +} +``` diff --git a/vendor/github.com/hashicorp/golang-lru/v2/doc.go b/vendor/github.com/hashicorp/golang-lru/v2/doc.go new file mode 100644 index 00000000..24107ee0 --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/v2/doc.go @@ -0,0 +1,24 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Package lru provides three different LRU caches of varying sophistication. +// +// Cache is a simple LRU cache. It is based on the LRU implementation in +// groupcache: https://github.com/golang/groupcache/tree/master/lru +// +// TwoQueueCache tracks frequently used and recently used entries separately. +// This avoids a burst of accesses from taking out frequently used entries, at +// the cost of about 2x computational overhead and some extra bookkeeping. +// +// ARCCache is an adaptive replacement cache. It tracks recent evictions as well +// as recent usage in both the frequent and recent caches. Its computational +// overhead is comparable to TwoQueueCache, but the memory overhead is linear +// with the size of the cache. +// +// ARC has been patented by IBM, so do not use it if that is problematic for +// your program. For this reason, it is in a separate go module contained within +// this repository. +// +// All caches in this package take locks while operating, and are therefore +// thread-safe for consumers. +package lru diff --git a/vendor/github.com/hashicorp/golang-lru/v2/internal/list.go b/vendor/github.com/hashicorp/golang-lru/v2/internal/list.go new file mode 100644 index 00000000..5cd74a03 --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/v2/internal/list.go @@ -0,0 +1,142 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE_list file. + +package internal + +import "time" + +// Entry is an LRU Entry +type Entry[K comparable, V any] struct { + // Next and previous pointers in the doubly-linked list of elements. + // To simplify the implementation, internally a list l is implemented + // as a ring, such that &l.root is both the next element of the last + // list element (l.Back()) and the previous element of the first list + // element (l.Front()). + next, prev *Entry[K, V] + + // The list to which this element belongs. + list *LruList[K, V] + + // The LRU Key of this element. + Key K + + // The Value stored with this element. + Value V + + // The time this element would be cleaned up, optional + ExpiresAt time.Time + + // The expiry bucket item was put in, optional + ExpireBucket uint8 +} + +// PrevEntry returns the previous list element or nil. +func (e *Entry[K, V]) PrevEntry() *Entry[K, V] { + if p := e.prev; e.list != nil && p != &e.list.root { + return p + } + return nil +} + +// LruList represents a doubly linked list. +// The zero Value for LruList is an empty list ready to use. +type LruList[K comparable, V any] struct { + root Entry[K, V] // sentinel list element, only &root, root.prev, and root.next are used + len int // current list Length excluding (this) sentinel element +} + +// Init initializes or clears list l. +func (l *LruList[K, V]) Init() *LruList[K, V] { + l.root.next = &l.root + l.root.prev = &l.root + l.len = 0 + return l +} + +// NewList returns an initialized list. +func NewList[K comparable, V any]() *LruList[K, V] { return new(LruList[K, V]).Init() } + +// Length returns the number of elements of list l. +// The complexity is O(1). +func (l *LruList[K, V]) Length() int { return l.len } + +// Back returns the last element of list l or nil if the list is empty. +func (l *LruList[K, V]) Back() *Entry[K, V] { + if l.len == 0 { + return nil + } + return l.root.prev +} + +// lazyInit lazily initializes a zero List Value. +func (l *LruList[K, V]) lazyInit() { + if l.root.next == nil { + l.Init() + } +} + +// insert inserts e after at, increments l.len, and returns e. +func (l *LruList[K, V]) insert(e, at *Entry[K, V]) *Entry[K, V] { + e.prev = at + e.next = at.next + e.prev.next = e + e.next.prev = e + e.list = l + l.len++ + return e +} + +// insertValue is a convenience wrapper for insert(&Entry{Value: v, ExpiresAt: ExpiresAt}, at). +func (l *LruList[K, V]) insertValue(k K, v V, expiresAt time.Time, at *Entry[K, V]) *Entry[K, V] { + return l.insert(&Entry[K, V]{Value: v, Key: k, ExpiresAt: expiresAt}, at) +} + +// Remove removes e from its list, decrements l.len +func (l *LruList[K, V]) Remove(e *Entry[K, V]) V { + e.prev.next = e.next + e.next.prev = e.prev + e.next = nil // avoid memory leaks + e.prev = nil // avoid memory leaks + e.list = nil + l.len-- + + return e.Value +} + +// move moves e to next to at. +func (l *LruList[K, V]) move(e, at *Entry[K, V]) { + if e == at { + return + } + e.prev.next = e.next + e.next.prev = e.prev + + e.prev = at + e.next = at.next + e.prev.next = e + e.next.prev = e +} + +// PushFront inserts a new element e with value v at the front of list l and returns e. +func (l *LruList[K, V]) PushFront(k K, v V) *Entry[K, V] { + l.lazyInit() + return l.insertValue(k, v, time.Time{}, &l.root) +} + +// PushFrontExpirable inserts a new expirable element e with Value v at the front of list l and returns e. +func (l *LruList[K, V]) PushFrontExpirable(k K, v V, expiresAt time.Time) *Entry[K, V] { + l.lazyInit() + return l.insertValue(k, v, expiresAt, &l.root) +} + +// MoveToFront moves element e to the front of list l. +// If e is not an element of l, the list is not modified. +// The element must not be nil. +func (l *LruList[K, V]) MoveToFront(e *Entry[K, V]) { + if e.list != l || l.root.next == e { + return + } + // see comment in List.Remove about initialization of l + l.move(e, &l.root) +} diff --git a/vendor/github.com/hashicorp/golang-lru/v2/lru.go b/vendor/github.com/hashicorp/golang-lru/v2/lru.go new file mode 100644 index 00000000..a2655f1f --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/v2/lru.go @@ -0,0 +1,250 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package lru + +import ( + "sync" + + "github.com/hashicorp/golang-lru/v2/simplelru" +) + +const ( + // DefaultEvictedBufferSize defines the default buffer size to store evicted key/val + DefaultEvictedBufferSize = 16 +) + +// Cache is a thread-safe fixed size LRU cache. +type Cache[K comparable, V any] struct { + lru *simplelru.LRU[K, V] + evictedKeys []K + evictedVals []V + onEvictedCB func(k K, v V) + lock sync.RWMutex +} + +// New creates an LRU of the given size. +func New[K comparable, V any](size int) (*Cache[K, V], error) { + return NewWithEvict[K, V](size, nil) +} + +// NewWithEvict constructs a fixed size cache with the given eviction +// callback. +func NewWithEvict[K comparable, V any](size int, onEvicted func(key K, value V)) (c *Cache[K, V], err error) { + // create a cache with default settings + c = &Cache[K, V]{ + onEvictedCB: onEvicted, + } + if onEvicted != nil { + c.initEvictBuffers() + onEvicted = c.onEvicted + } + c.lru, err = simplelru.NewLRU(size, onEvicted) + return +} + +func (c *Cache[K, V]) initEvictBuffers() { + c.evictedKeys = make([]K, 0, DefaultEvictedBufferSize) + c.evictedVals = make([]V, 0, DefaultEvictedBufferSize) +} + +// onEvicted save evicted key/val and sent in externally registered callback +// outside of critical section +func (c *Cache[K, V]) onEvicted(k K, v V) { + c.evictedKeys = append(c.evictedKeys, k) + c.evictedVals = append(c.evictedVals, v) +} + +// Purge is used to completely clear the cache. +func (c *Cache[K, V]) Purge() { + var ks []K + var vs []V + c.lock.Lock() + c.lru.Purge() + if c.onEvictedCB != nil && len(c.evictedKeys) > 0 { + ks, vs = c.evictedKeys, c.evictedVals + c.initEvictBuffers() + } + c.lock.Unlock() + // invoke callback outside of critical section + if c.onEvictedCB != nil { + for i := 0; i < len(ks); i++ { + c.onEvictedCB(ks[i], vs[i]) + } + } +} + +// Add adds a value to the cache. Returns true if an eviction occurred. +func (c *Cache[K, V]) Add(key K, value V) (evicted bool) { + var k K + var v V + c.lock.Lock() + evicted = c.lru.Add(key, value) + if c.onEvictedCB != nil && evicted { + k, v = c.evictedKeys[0], c.evictedVals[0] + c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0] + } + c.lock.Unlock() + if c.onEvictedCB != nil && evicted { + c.onEvictedCB(k, v) + } + return +} + +// Get looks up a key's value from the cache. +func (c *Cache[K, V]) Get(key K) (value V, ok bool) { + c.lock.Lock() + value, ok = c.lru.Get(key) + c.lock.Unlock() + return value, ok +} + +// Contains checks if a key is in the cache, without updating the +// recent-ness or deleting it for being stale. +func (c *Cache[K, V]) Contains(key K) bool { + c.lock.RLock() + containKey := c.lru.Contains(key) + c.lock.RUnlock() + return containKey +} + +// Peek returns the key value (or undefined if not found) without updating +// the "recently used"-ness of the key. +func (c *Cache[K, V]) Peek(key K) (value V, ok bool) { + c.lock.RLock() + value, ok = c.lru.Peek(key) + c.lock.RUnlock() + return value, ok +} + +// ContainsOrAdd checks if a key is in the cache without updating the +// recent-ness or deleting it for being stale, and if not, adds the value. +// Returns whether found and whether an eviction occurred. +func (c *Cache[K, V]) ContainsOrAdd(key K, value V) (ok, evicted bool) { + var k K + var v V + c.lock.Lock() + if c.lru.Contains(key) { + c.lock.Unlock() + return true, false + } + evicted = c.lru.Add(key, value) + if c.onEvictedCB != nil && evicted { + k, v = c.evictedKeys[0], c.evictedVals[0] + c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0] + } + c.lock.Unlock() + if c.onEvictedCB != nil && evicted { + c.onEvictedCB(k, v) + } + return false, evicted +} + +// PeekOrAdd checks if a key is in the cache without updating the +// recent-ness or deleting it for being stale, and if not, adds the value. +// Returns whether found and whether an eviction occurred. +func (c *Cache[K, V]) PeekOrAdd(key K, value V) (previous V, ok, evicted bool) { + var k K + var v V + c.lock.Lock() + previous, ok = c.lru.Peek(key) + if ok { + c.lock.Unlock() + return previous, true, false + } + evicted = c.lru.Add(key, value) + if c.onEvictedCB != nil && evicted { + k, v = c.evictedKeys[0], c.evictedVals[0] + c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0] + } + c.lock.Unlock() + if c.onEvictedCB != nil && evicted { + c.onEvictedCB(k, v) + } + return +} + +// Remove removes the provided key from the cache. +func (c *Cache[K, V]) Remove(key K) (present bool) { + var k K + var v V + c.lock.Lock() + present = c.lru.Remove(key) + if c.onEvictedCB != nil && present { + k, v = c.evictedKeys[0], c.evictedVals[0] + c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0] + } + c.lock.Unlock() + if c.onEvictedCB != nil && present { + c.onEvictedCB(k, v) + } + return +} + +// Resize changes the cache size. +func (c *Cache[K, V]) Resize(size int) (evicted int) { + var ks []K + var vs []V + c.lock.Lock() + evicted = c.lru.Resize(size) + if c.onEvictedCB != nil && evicted > 0 { + ks, vs = c.evictedKeys, c.evictedVals + c.initEvictBuffers() + } + c.lock.Unlock() + if c.onEvictedCB != nil && evicted > 0 { + for i := 0; i < len(ks); i++ { + c.onEvictedCB(ks[i], vs[i]) + } + } + return evicted +} + +// RemoveOldest removes the oldest item from the cache. +func (c *Cache[K, V]) RemoveOldest() (key K, value V, ok bool) { + var k K + var v V + c.lock.Lock() + key, value, ok = c.lru.RemoveOldest() + if c.onEvictedCB != nil && ok { + k, v = c.evictedKeys[0], c.evictedVals[0] + c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0] + } + c.lock.Unlock() + if c.onEvictedCB != nil && ok { + c.onEvictedCB(k, v) + } + return +} + +// GetOldest returns the oldest entry +func (c *Cache[K, V]) GetOldest() (key K, value V, ok bool) { + c.lock.RLock() + key, value, ok = c.lru.GetOldest() + c.lock.RUnlock() + return +} + +// Keys returns a slice of the keys in the cache, from oldest to newest. +func (c *Cache[K, V]) Keys() []K { + c.lock.RLock() + keys := c.lru.Keys() + c.lock.RUnlock() + return keys +} + +// Values returns a slice of the values in the cache, from oldest to newest. +func (c *Cache[K, V]) Values() []V { + c.lock.RLock() + values := c.lru.Values() + c.lock.RUnlock() + return values +} + +// Len returns the number of items in the cache. +func (c *Cache[K, V]) Len() int { + c.lock.RLock() + length := c.lru.Len() + c.lock.RUnlock() + return length +} diff --git a/vendor/github.com/hashicorp/golang-lru/v2/simplelru/LICENSE_list b/vendor/github.com/hashicorp/golang-lru/v2/simplelru/LICENSE_list new file mode 100644 index 00000000..c4764e6b --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/v2/simplelru/LICENSE_list @@ -0,0 +1,29 @@ +This license applies to simplelru/list.go + +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/hashicorp/golang-lru/v2/simplelru/lru.go b/vendor/github.com/hashicorp/golang-lru/v2/simplelru/lru.go new file mode 100644 index 00000000..f6979238 --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/v2/simplelru/lru.go @@ -0,0 +1,177 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package simplelru + +import ( + "errors" + + "github.com/hashicorp/golang-lru/v2/internal" +) + +// EvictCallback is used to get a callback when a cache entry is evicted +type EvictCallback[K comparable, V any] func(key K, value V) + +// LRU implements a non-thread safe fixed size LRU cache +type LRU[K comparable, V any] struct { + size int + evictList *internal.LruList[K, V] + items map[K]*internal.Entry[K, V] + onEvict EvictCallback[K, V] +} + +// NewLRU constructs an LRU of the given size +func NewLRU[K comparable, V any](size int, onEvict EvictCallback[K, V]) (*LRU[K, V], error) { + if size <= 0 { + return nil, errors.New("must provide a positive size") + } + + c := &LRU[K, V]{ + size: size, + evictList: internal.NewList[K, V](), + items: make(map[K]*internal.Entry[K, V]), + onEvict: onEvict, + } + return c, nil +} + +// Purge is used to completely clear the cache. +func (c *LRU[K, V]) Purge() { + for k, v := range c.items { + if c.onEvict != nil { + c.onEvict(k, v.Value) + } + delete(c.items, k) + } + c.evictList.Init() +} + +// Add adds a value to the cache. Returns true if an eviction occurred. +func (c *LRU[K, V]) Add(key K, value V) (evicted bool) { + // Check for existing item + if ent, ok := c.items[key]; ok { + c.evictList.MoveToFront(ent) + ent.Value = value + return false + } + + // Add new item + ent := c.evictList.PushFront(key, value) + c.items[key] = ent + + evict := c.evictList.Length() > c.size + // Verify size not exceeded + if evict { + c.removeOldest() + } + return evict +} + +// Get looks up a key's value from the cache. +func (c *LRU[K, V]) Get(key K) (value V, ok bool) { + if ent, ok := c.items[key]; ok { + c.evictList.MoveToFront(ent) + return ent.Value, true + } + return +} + +// Contains checks if a key is in the cache, without updating the recent-ness +// or deleting it for being stale. +func (c *LRU[K, V]) Contains(key K) (ok bool) { + _, ok = c.items[key] + return ok +} + +// Peek returns the key value (or undefined if not found) without updating +// the "recently used"-ness of the key. +func (c *LRU[K, V]) Peek(key K) (value V, ok bool) { + var ent *internal.Entry[K, V] + if ent, ok = c.items[key]; ok { + return ent.Value, true + } + return +} + +// Remove removes the provided key from the cache, returning if the +// key was contained. +func (c *LRU[K, V]) Remove(key K) (present bool) { + if ent, ok := c.items[key]; ok { + c.removeElement(ent) + return true + } + return false +} + +// RemoveOldest removes the oldest item from the cache. +func (c *LRU[K, V]) RemoveOldest() (key K, value V, ok bool) { + if ent := c.evictList.Back(); ent != nil { + c.removeElement(ent) + return ent.Key, ent.Value, true + } + return +} + +// GetOldest returns the oldest entry +func (c *LRU[K, V]) GetOldest() (key K, value V, ok bool) { + if ent := c.evictList.Back(); ent != nil { + return ent.Key, ent.Value, true + } + return +} + +// Keys returns a slice of the keys in the cache, from oldest to newest. +func (c *LRU[K, V]) Keys() []K { + keys := make([]K, c.evictList.Length()) + i := 0 + for ent := c.evictList.Back(); ent != nil; ent = ent.PrevEntry() { + keys[i] = ent.Key + i++ + } + return keys +} + +// Values returns a slice of the values in the cache, from oldest to newest. +func (c *LRU[K, V]) Values() []V { + values := make([]V, len(c.items)) + i := 0 + for ent := c.evictList.Back(); ent != nil; ent = ent.PrevEntry() { + values[i] = ent.Value + i++ + } + return values +} + +// Len returns the number of items in the cache. +func (c *LRU[K, V]) Len() int { + return c.evictList.Length() +} + +// Resize changes the cache size. +func (c *LRU[K, V]) Resize(size int) (evicted int) { + diff := c.Len() - size + if diff < 0 { + diff = 0 + } + for i := 0; i < diff; i++ { + c.removeOldest() + } + c.size = size + return diff +} + +// removeOldest removes the oldest item from the cache. +func (c *LRU[K, V]) removeOldest() { + if ent := c.evictList.Back(); ent != nil { + c.removeElement(ent) + } +} + +// removeElement is used to remove a given list element from the cache +func (c *LRU[K, V]) removeElement(e *internal.Entry[K, V]) { + c.evictList.Remove(e) + delete(c.items, e.Key) + if c.onEvict != nil { + c.onEvict(e.Key, e.Value) + } +} diff --git a/vendor/github.com/hashicorp/golang-lru/v2/simplelru/lru_interface.go b/vendor/github.com/hashicorp/golang-lru/v2/simplelru/lru_interface.go new file mode 100644 index 00000000..043b8bcc --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/v2/simplelru/lru_interface.go @@ -0,0 +1,46 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Package simplelru provides simple LRU implementation based on build-in container/list. +package simplelru + +// LRUCache is the interface for simple LRU cache. +type LRUCache[K comparable, V any] interface { + // Adds a value to the cache, returns true if an eviction occurred and + // updates the "recently used"-ness of the key. + Add(key K, value V) bool + + // Returns key's value from the cache and + // updates the "recently used"-ness of the key. #value, isFound + Get(key K) (value V, ok bool) + + // Checks if a key exists in cache without updating the recent-ness. + Contains(key K) (ok bool) + + // Returns key's value without updating the "recently used"-ness of the key. + Peek(key K) (value V, ok bool) + + // Removes a key from the cache. + Remove(key K) bool + + // Removes the oldest entry from cache. + RemoveOldest() (K, V, bool) + + // Returns the oldest entry from the cache. #key, value, isFound + GetOldest() (K, V, bool) + + // Returns a slice of the keys in the cache, from oldest to newest. + Keys() []K + + // Values returns a slice of the values in the cache, from oldest to newest. + Values() []V + + // Returns the number of items in the cache. + Len() int + + // Clears all cache entries. + Purge() + + // Resizes cache, returning number evicted + Resize(int) int +} diff --git a/vendor/github.com/pkg/sftp/attrs.go b/vendor/github.com/pkg/sftp/attrs.go index 2bb2d576..758cd4ff 100644 --- a/vendor/github.com/pkg/sftp/attrs.go +++ b/vendor/github.com/pkg/sftp/attrs.go @@ -1,7 +1,7 @@ package sftp // ssh_FXP_ATTRS support -// see http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-5 +// see https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt#section-5 import ( "os" @@ -69,6 +69,20 @@ func fileInfoFromStat(stat *FileStat, name string) os.FileInfo { } } +// FileInfoUidGid extends os.FileInfo and adds callbacks for Uid and Gid retrieval, +// as an alternative to *syscall.Stat_t objects on unix systems. +type FileInfoUidGid interface { + os.FileInfo + Uid() uint32 + Gid() uint32 +} + +// FileInfoUidGid extends os.FileInfo and adds a callbacks for extended data retrieval. +type FileInfoExtendedData interface { + os.FileInfo + Extended() []StatExtended +} + func fileStatFromInfo(fi os.FileInfo) (uint32, *FileStat) { mtime := fi.ModTime().Unix() atime := mtime @@ -86,5 +100,22 @@ func fileStatFromInfo(fi os.FileInfo) (uint32, *FileStat) { // os specific file stat decoding fileStatFromInfoOs(fi, &flags, fileStat) + // The call above will include the sshFileXferAttrUIDGID in case + // the os.FileInfo can be casted to *syscall.Stat_t on unix. + // If fi implements FileInfoUidGid, retrieve Uid, Gid from it instead. + if fiExt, ok := fi.(FileInfoUidGid); ok { + flags |= sshFileXferAttrUIDGID + fileStat.UID = fiExt.Uid() + fileStat.GID = fiExt.Gid() + } + + // if fi implements FileInfoExtendedData, retrieve extended data from it + if fiExt, ok := fi.(FileInfoExtendedData); ok { + fileStat.Extended = fiExt.Extended() + if len(fileStat.Extended) > 0 { + flags |= sshFileXferAttrExtended + } + } + return flags, fileStat } diff --git a/vendor/github.com/pkg/sftp/attrs_stubs.go b/vendor/github.com/pkg/sftp/attrs_stubs.go index c01f3367..d20348c1 100644 --- a/vendor/github.com/pkg/sftp/attrs_stubs.go +++ b/vendor/github.com/pkg/sftp/attrs_stubs.go @@ -1,3 +1,4 @@ +//go:build plan9 || windows || android // +build plan9 windows android package sftp diff --git a/vendor/github.com/pkg/sftp/attrs_unix.go b/vendor/github.com/pkg/sftp/attrs_unix.go index d1f44524..371ae9b9 100644 --- a/vendor/github.com/pkg/sftp/attrs_unix.go +++ b/vendor/github.com/pkg/sftp/attrs_unix.go @@ -1,3 +1,4 @@ +//go:build darwin || dragonfly || freebsd || (!android && linux) || netbsd || openbsd || solaris || aix || js // +build darwin dragonfly freebsd !android,linux netbsd openbsd solaris aix js package sftp diff --git a/vendor/github.com/pkg/sftp/client.go b/vendor/github.com/pkg/sftp/client.go index ce62286f..0df125e1 100644 --- a/vendor/github.com/pkg/sftp/client.go +++ b/vendor/github.com/pkg/sftp/client.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/binary" "errors" + "fmt" "io" "math" "os" @@ -226,15 +227,22 @@ func NewClientPipe(rd io.Reader, wr io.WriteCloser, opts ...ClientOption) (*Clie if err := sftp.sendInit(); err != nil { wr.Close() - return nil, err + return nil, fmt.Errorf("error sending init packet to server: %w", err) } + if err := sftp.recvVersion(); err != nil { wr.Close() - return nil, err + return nil, fmt.Errorf("error receiving version packet from server: %w", err) } sftp.clientConn.wg.Add(1) - go sftp.loop() + go func() { + defer sftp.clientConn.wg.Done() + + if err := sftp.clientConn.recv(); err != nil { + sftp.clientConn.broadcastErr(err) + } + }() return sftp, nil } @@ -251,11 +259,11 @@ func (c *Client) Create(path string) (*File, error) { return c.open(path, flags(os.O_RDWR|os.O_CREATE|os.O_TRUNC)) } -const sftpProtocolVersion = 3 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02 +const sftpProtocolVersion = 3 // https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt func (c *Client) sendInit() error { return c.clientConn.conn.sendPacket(&sshFxInitPacket{ - Version: sftpProtocolVersion, // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02 + Version: sftpProtocolVersion, // https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt }) } @@ -267,8 +275,13 @@ func (c *Client) nextID() uint32 { func (c *Client) recvVersion() error { typ, data, err := c.recvPacket(0) if err != nil { + if err == io.EOF { + return fmt.Errorf("server unexpectedly closed connection: %w", io.ErrUnexpectedEOF) + } + return err } + if typ != sshFxpVersion { return &unexpectedPacketErr{sshFxpVersion, typ} } @@ -277,6 +290,7 @@ func (c *Client) recvVersion() error { if err != nil { return err } + if version != sftpProtocolVersion { return &unexpectedVersionErr{sftpProtocolVersion, version} } @@ -910,6 +924,45 @@ func (c *Client) MkdirAll(path string) error { return nil } +// RemoveAll delete files recursively in the directory and Recursively delete subdirectories. +// An error will be returned if no file or directory with the specified path exists +func (c *Client) RemoveAll(path string) error { + + // Get the file/directory information + fi, err := c.Stat(path) + if err != nil { + return err + } + + if fi.IsDir() { + // Delete files recursively in the directory + files, err := c.ReadDir(path) + if err != nil { + return err + } + + for _, file := range files { + if file.IsDir() { + // Recursively delete subdirectories + err = c.RemoveAll(path + "/" + file.Name()) + if err != nil { + return err + } + } else { + // Delete individual files + err = c.Remove(path + "/" + file.Name()) + if err != nil { + return err + } + } + } + + } + + return c.Remove(path) + +} + // File represents a remote file. type File struct { c *Client @@ -999,9 +1052,6 @@ func (f *File) readAtSequential(b []byte, off int64) (read int, err error) { read += n } if err != nil { - if errors.Is(err, io.EOF) { - return read, nil // return nil explicitly. - } return read, err } } @@ -1179,11 +1229,11 @@ func (f *File) writeToSequential(w io.Writer) (written int64, err error) { if n > 0 { f.offset += int64(n) - m, err2 := w.Write(b[:n]) + m, err := w.Write(b[:n]) written += int64(m) - if err == nil { - err = err2 + if err != nil { + return written, err } } @@ -1461,11 +1511,20 @@ func (f *File) writeAtConcurrent(b []byte, off int64) (int, error) { cancel := make(chan struct{}) type work struct { - b []byte + id uint32 + res chan result + off int64 } workCh := make(chan work) + concurrency := len(b)/f.c.maxPacket + 1 + if concurrency > f.c.maxConcurrentRequests || concurrency < 1 { + concurrency = f.c.maxConcurrentRequests + } + + pool := newResChanPool(concurrency) + // Slice: cut up the Read into any number of buffers of length <= f.c.maxPacket, and at appropriate offsets. go func() { defer close(workCh) @@ -1479,8 +1538,20 @@ func (f *File) writeAtConcurrent(b []byte, off int64) (int, error) { wb = wb[:chunkSize] } + id := f.c.nextID() + res := pool.Get() + off := off + int64(read) + + f.c.dispatchRequest(res, &sshFxpWritePacket{ + ID: id, + Handle: f.handle, + Offset: uint64(off), + Length: uint32(len(wb)), + Data: wb, + }) + select { - case workCh <- work{wb, off + int64(read)}: + case workCh <- work{id, res, off}: case <-cancel: return } @@ -1495,11 +1566,6 @@ func (f *File) writeAtConcurrent(b []byte, off int64) (int, error) { } errCh := make(chan wErr) - concurrency := len(b)/f.c.maxPacket + 1 - if concurrency > f.c.maxConcurrentRequests || concurrency < 1 { - concurrency = f.c.maxConcurrentRequests - } - var wg sync.WaitGroup wg.Add(concurrency) for i := 0; i < concurrency; i++ { @@ -1507,13 +1573,22 @@ func (f *File) writeAtConcurrent(b []byte, off int64) (int, error) { go func() { defer wg.Done() - ch := make(chan result, 1) // reusable channel per mapper. + for work := range workCh { + s := <-work.res + pool.Put(work.res) + + err := s.err + if err == nil { + switch s.typ { + case sshFxpStatus: + err = normaliseError(unmarshalStatus(work.id, s.data)) + default: + err = unimplementedPacketErr(s.typ) + } + } - for packet := range workCh { - n, err := f.writeChunkAt(ch, packet.b, packet.off) if err != nil { - // return the offset as the start + how much we wrote before the error. - errCh <- wErr{packet.off + int64(n), err} + errCh <- wErr{work.off, err} } } }() @@ -1598,8 +1673,9 @@ func (f *File) ReadFromWithConcurrency(r io.Reader, concurrency int) (read int64 cancel := make(chan struct{}) type work struct { - b []byte - n int + id uint32 + res chan result + off int64 } workCh := make(chan work) @@ -1614,24 +1690,34 @@ func (f *File) ReadFromWithConcurrency(r io.Reader, concurrency int) (read int64 concurrency = f.c.maxConcurrentRequests } - pool := newBufPool(concurrency, f.c.maxPacket) + pool := newResChanPool(concurrency) // Slice: cut up the Read into any number of buffers of length <= f.c.maxPacket, and at appropriate offsets. go func() { defer close(workCh) + b := make([]byte, f.c.maxPacket) off := f.offset for { - b := pool.Get() - n, err := r.Read(b) + if n > 0 { read += int64(n) + id := f.c.nextID() + res := pool.Get() + + f.c.dispatchRequest(res, &sshFxpWritePacket{ + ID: id, + Handle: f.handle, + Offset: uint64(off), + Length: uint32(n), + Data: b[:n], + }) + select { - case workCh <- work{b, n, off}: - // We need the pool.Put(b) to put the whole slice, not just trunced. + case workCh <- work{id, res, off}: case <-cancel: return } @@ -1655,15 +1741,23 @@ func (f *File) ReadFromWithConcurrency(r io.Reader, concurrency int) (read int64 go func() { defer wg.Done() - ch := make(chan result, 1) // reusable channel per mapper. + for work := range workCh { + s := <-work.res + pool.Put(work.res) + + err := s.err + if err == nil { + switch s.typ { + case sshFxpStatus: + err = normaliseError(unmarshalStatus(work.id, s.data)) + default: + err = unimplementedPacketErr(s.typ) + } + } - for packet := range workCh { - n, err := f.writeChunkAt(ch, packet.b[:packet.n], packet.off) if err != nil { - // return the offset as the start + how much we wrote before the error. - errCh <- rwErr{packet.off + int64(n), err} + errCh <- rwErr{work.off, err} } - pool.Put(packet.b) } }() } diff --git a/vendor/github.com/pkg/sftp/conn.go b/vendor/github.com/pkg/sftp/conn.go index 7d951423..3bb2ba15 100644 --- a/vendor/github.com/pkg/sftp/conn.go +++ b/vendor/github.com/pkg/sftp/conn.go @@ -18,7 +18,9 @@ type conn struct { } // the orderID is used in server mode if the allocator is enabled. -// For the client mode just pass 0 +// For the client mode just pass 0. +// It returns io.EOF if the connection is closed and +// there are no more packets to read. func (c *conn) recvPacket(orderID uint32) (uint8, []byte, error) { return recvPacket(c, c.alloc, orderID) } @@ -61,14 +63,6 @@ func (c *clientConn) Close() error { return c.conn.Close() } -func (c *clientConn) loop() { - defer c.wg.Done() - err := c.recv() - if err != nil { - c.broadcastErr(err) - } -} - // recv continuously reads from the server and forwards responses to the // appropriate channel. func (c *clientConn) recv() error { diff --git a/vendor/github.com/pkg/sftp/debug.go b/vendor/github.com/pkg/sftp/debug.go index 3e264abe..f0db14d3 100644 --- a/vendor/github.com/pkg/sftp/debug.go +++ b/vendor/github.com/pkg/sftp/debug.go @@ -1,3 +1,4 @@ +//go:build debug // +build debug package sftp diff --git a/vendor/github.com/pkg/sftp/fuzz.go b/vendor/github.com/pkg/sftp/fuzz.go index 169aebc2..f2f1fc31 100644 --- a/vendor/github.com/pkg/sftp/fuzz.go +++ b/vendor/github.com/pkg/sftp/fuzz.go @@ -1,3 +1,4 @@ +//go:build gofuzz // +build gofuzz package sftp diff --git a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/attrs.go b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/attrs.go index 1d4bb799..3aec937f 100644 --- a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/attrs.go +++ b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/attrs.go @@ -1,4 +1,4 @@ -package filexfer +package sshfx // Attributes related flags. const ( @@ -12,7 +12,7 @@ const ( // Attributes defines the file attributes type defined in draft-ietf-secsh-filexfer-02 // -// Defined in: https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-5 +// Defined in: https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt#section-5 type Attributes struct { Flags uint32 @@ -74,7 +74,6 @@ func (a *Attributes) SetPermissions(perms FileMode) { // GetACModTime returns the ATime and MTime fields and a bool that is true if and only if the values are valid/defined. func (a *Attributes) GetACModTime() (atime, mtime uint32, ok bool) { return a.ATime, a.MTime, a.Flags&AttrACModTime != 0 - return a.ATime, a.MTime, a.Flags&AttrACModTime != 0 } // SetACModTime is a convenience function that sets the ATime and MTime fields, @@ -117,32 +116,32 @@ func (a *Attributes) Len() int { } // MarshalInto marshals e onto the end of the given Buffer. -func (a *Attributes) MarshalInto(b *Buffer) { - b.AppendUint32(a.Flags) +func (a *Attributes) MarshalInto(buf *Buffer) { + buf.AppendUint32(a.Flags) if a.Flags&AttrSize != 0 { - b.AppendUint64(a.Size) + buf.AppendUint64(a.Size) } if a.Flags&AttrUIDGID != 0 { - b.AppendUint32(a.UID) - b.AppendUint32(a.GID) + buf.AppendUint32(a.UID) + buf.AppendUint32(a.GID) } if a.Flags&AttrPermissions != 0 { - b.AppendUint32(uint32(a.Permissions)) + buf.AppendUint32(uint32(a.Permissions)) } if a.Flags&AttrACModTime != 0 { - b.AppendUint32(a.ATime) - b.AppendUint32(a.MTime) + buf.AppendUint32(a.ATime) + buf.AppendUint32(a.MTime) } if a.Flags&AttrExtended != 0 { - b.AppendUint32(uint32(len(a.ExtendedAttributes))) + buf.AppendUint32(uint32(len(a.ExtendedAttributes))) for _, ext := range a.ExtendedAttributes { - ext.MarshalInto(b) + ext.MarshalInto(buf) } } } @@ -157,74 +156,51 @@ func (a *Attributes) MarshalBinary() ([]byte, error) { // UnmarshalFrom unmarshals an Attributes from the given Buffer into e. // // NOTE: The values of fields not covered in the a.Flags are explicitly undefined. -func (a *Attributes) UnmarshalFrom(b *Buffer) (err error) { - flags, err := b.ConsumeUint32() - if err != nil { - return err - } +func (a *Attributes) UnmarshalFrom(buf *Buffer) (err error) { + flags := buf.ConsumeUint32() - return a.XXX_UnmarshalByFlags(flags, b) + return a.XXX_UnmarshalByFlags(flags, buf) } // XXX_UnmarshalByFlags uses the pre-existing a.Flags field to determine which fields to decode. // DO NOT USE THIS: it is an anti-corruption function to implement existing internal usage in pkg/sftp. // This function is not a part of any compatibility promise. -func (a *Attributes) XXX_UnmarshalByFlags(flags uint32, b *Buffer) (err error) { +func (a *Attributes) XXX_UnmarshalByFlags(flags uint32, buf *Buffer) (err error) { a.Flags = flags // Short-circuit dummy attributes. if a.Flags == 0 { - return nil + return buf.Err } if a.Flags&AttrSize != 0 { - if a.Size, err = b.ConsumeUint64(); err != nil { - return err - } + a.Size = buf.ConsumeUint64() } if a.Flags&AttrUIDGID != 0 { - if a.UID, err = b.ConsumeUint32(); err != nil { - return err - } - - if a.GID, err = b.ConsumeUint32(); err != nil { - return err - } + a.UID = buf.ConsumeUint32() + a.GID = buf.ConsumeUint32() } if a.Flags&AttrPermissions != 0 { - m, err := b.ConsumeUint32() - if err != nil { - return err - } - - a.Permissions = FileMode(m) + a.Permissions = FileMode(buf.ConsumeUint32()) } if a.Flags&AttrACModTime != 0 { - if a.ATime, err = b.ConsumeUint32(); err != nil { - return err - } - - if a.MTime, err = b.ConsumeUint32(); err != nil { - return err - } + a.ATime = buf.ConsumeUint32() + a.MTime = buf.ConsumeUint32() } if a.Flags&AttrExtended != 0 { - count, err := b.ConsumeUint32() - if err != nil { - return err - } + count := buf.ConsumeCount() a.ExtendedAttributes = make([]ExtendedAttribute, count) for i := range a.ExtendedAttributes { - a.ExtendedAttributes[i].UnmarshalFrom(b) + a.ExtendedAttributes[i].UnmarshalFrom(buf) } } - return nil + return buf.Err } // UnmarshalBinary decodes the binary encoding of Attributes into e. @@ -234,7 +210,7 @@ func (a *Attributes) UnmarshalBinary(data []byte) error { // ExtendedAttribute defines the extended file attribute type defined in draft-ietf-secsh-filexfer-02 // -// Defined in: https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-5 +// Defined in: https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt#section-5 type ExtendedAttribute struct { Type string Data string @@ -246,9 +222,9 @@ func (e *ExtendedAttribute) Len() int { } // MarshalInto marshals e onto the end of the given Buffer. -func (e *ExtendedAttribute) MarshalInto(b *Buffer) { - b.AppendString(e.Type) - b.AppendString(e.Data) +func (e *ExtendedAttribute) MarshalInto(buf *Buffer) { + buf.AppendString(e.Type) + buf.AppendString(e.Data) } // MarshalBinary returns e as the binary encoding of e. @@ -259,16 +235,13 @@ func (e *ExtendedAttribute) MarshalBinary() ([]byte, error) { } // UnmarshalFrom unmarshals an ExtendedAattribute from the given Buffer into e. -func (e *ExtendedAttribute) UnmarshalFrom(b *Buffer) (err error) { - if e.Type, err = b.ConsumeString(); err != nil { - return err +func (e *ExtendedAttribute) UnmarshalFrom(buf *Buffer) (err error) { + *e = ExtendedAttribute{ + Type: buf.ConsumeString(), + Data: buf.ConsumeString(), } - if e.Data, err = b.ConsumeString(); err != nil { - return err - } - - return nil + return buf.Err } // UnmarshalBinary decodes the binary encoding of ExtendedAttribute into e. @@ -291,11 +264,11 @@ func (e *NameEntry) Len() int { } // MarshalInto marshals e onto the end of the given Buffer. -func (e *NameEntry) MarshalInto(b *Buffer) { - b.AppendString(e.Filename) - b.AppendString(e.Longname) +func (e *NameEntry) MarshalInto(buf *Buffer) { + buf.AppendString(e.Filename) + buf.AppendString(e.Longname) - e.Attrs.MarshalInto(b) + e.Attrs.MarshalInto(buf) } // MarshalBinary returns e as the binary encoding of e. @@ -308,16 +281,13 @@ func (e *NameEntry) MarshalBinary() ([]byte, error) { // UnmarshalFrom unmarshals an NameEntry from the given Buffer into e. // // NOTE: The values of fields not covered in the a.Flags are explicitly undefined. -func (e *NameEntry) UnmarshalFrom(b *Buffer) (err error) { - if e.Filename, err = b.ConsumeString(); err != nil { - return err - } - - if e.Longname, err = b.ConsumeString(); err != nil { - return err +func (e *NameEntry) UnmarshalFrom(buf *Buffer) (err error) { + *e = NameEntry{ + Filename: buf.ConsumeString(), + Longname: buf.ConsumeString(), } - return e.Attrs.UnmarshalFrom(b) + return e.Attrs.UnmarshalFrom(buf) } // UnmarshalBinary decodes the binary encoding of NameEntry into e. diff --git a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/buffer.go b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/buffer.go index a6086036..bd4783bb 100644 --- a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/buffer.go +++ b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/buffer.go @@ -1,4 +1,4 @@ -package filexfer +package sshfx import ( "encoding/binary" @@ -17,6 +17,7 @@ var ( type Buffer struct { b []byte off int + Err error } // NewBuffer creates and initializes a new buffer using buf as its initial contents. @@ -51,14 +52,17 @@ func (b *Buffer) Cap() int { return cap(b.b) } // Reset resets the buffer to be empty, but it retains the underlying storage for use by future Appends. func (b *Buffer) Reset() { - b.b = b.b[:0] - b.off = 0 + *b = Buffer{ + b: b.b[:0], + } } // StartPacket resets and initializes the buffer to be ready to start marshaling a packet into. // It truncates the buffer, reserves space for uint32(length), then appends the given packetType and requestID. func (b *Buffer) StartPacket(packetType PacketType, requestID uint32) { - b.b, b.off = append(b.b[:0], make([]byte, 4)...), 0 + *b = Buffer{ + b: append(b.b[:0], make([]byte, 4)...), + } b.AppendUint8(uint8(packetType)) b.AppendUint32(requestID) @@ -81,15 +85,21 @@ func (b *Buffer) Packet(payload []byte) (header, payloadPassThru []byte, err err } // ConsumeUint8 consumes a single byte from the buffer. -// If the buffer does not have enough data, it will return ErrShortPacket. -func (b *Buffer) ConsumeUint8() (uint8, error) { +// If the buffer does not have enough data, it will set Err to ErrShortPacket. +func (b *Buffer) ConsumeUint8() uint8 { + if b.Err != nil { + return 0 + } + if b.Len() < 1 { - return 0, ErrShortPacket + b.off = len(b.b) + b.Err = ErrShortPacket + return 0 } var v uint8 v, b.off = b.b[b.off], b.off+1 - return v, nil + return v } // AppendUint8 appends a single byte into the buffer. @@ -98,14 +108,9 @@ func (b *Buffer) AppendUint8(v uint8) { } // ConsumeBool consumes a single byte from the buffer, and returns true if that byte is non-zero. -// If the buffer does not have enough data, it will return ErrShortPacket. -func (b *Buffer) ConsumeBool() (bool, error) { - v, err := b.ConsumeUint8() - if err != nil { - return false, err - } - - return v != 0, nil +// If the buffer does not have enough data, it will set Err to ErrShortPacket. +func (b *Buffer) ConsumeBool() bool { + return b.ConsumeUint8() != 0 } // AppendBool appends a single bool into the buffer. @@ -119,15 +124,21 @@ func (b *Buffer) AppendBool(v bool) { } // ConsumeUint16 consumes a single uint16 from the buffer, in network byte order (big-endian). -// If the buffer does not have enough data, it will return ErrShortPacket. -func (b *Buffer) ConsumeUint16() (uint16, error) { +// If the buffer does not have enough data, it will set Err to ErrShortPacket. +func (b *Buffer) ConsumeUint16() uint16 { + if b.Err != nil { + return 0 + } + if b.Len() < 2 { - return 0, ErrShortPacket + b.off = len(b.b) + b.Err = ErrShortPacket + return 0 } v := binary.BigEndian.Uint16(b.b[b.off:]) b.off += 2 - return v, nil + return v } // AppendUint16 appends single uint16 into the buffer, in network byte order (big-endian). @@ -146,15 +157,21 @@ func unmarshalUint32(b []byte) uint32 { } // ConsumeUint32 consumes a single uint32 from the buffer, in network byte order (big-endian). -// If the buffer does not have enough data, it will return ErrShortPacket. -func (b *Buffer) ConsumeUint32() (uint32, error) { +// If the buffer does not have enough data, it will set Err to ErrShortPacket. +func (b *Buffer) ConsumeUint32() uint32 { + if b.Err != nil { + return 0 + } + if b.Len() < 4 { - return 0, ErrShortPacket + b.off = len(b.b) + b.Err = ErrShortPacket + return 0 } v := binary.BigEndian.Uint32(b.b[b.off:]) b.off += 4 - return v, nil + return v } // AppendUint32 appends a single uint32 into the buffer, in network byte order (big-endian). @@ -167,16 +184,33 @@ func (b *Buffer) AppendUint32(v uint32) { ) } +// ConsumeCount consumes a single uint32 count from the buffer, in network byte order (big-endian) as an int. +// If the buffer does not have enough data, it will set Err to ErrShortPacket. +func (b *Buffer) ConsumeCount() int { + return int(b.ConsumeUint32()) +} + +// AppendCount appends a single int length as a uint32 into the buffer, in network byte order (big-endian). +func (b *Buffer) AppendCount(v int) { + b.AppendUint32(uint32(v)) +} + // ConsumeUint64 consumes a single uint64 from the buffer, in network byte order (big-endian). -// If the buffer does not have enough data, it will return ErrShortPacket. -func (b *Buffer) ConsumeUint64() (uint64, error) { +// If the buffer does not have enough data, it will set Err to ErrShortPacket. +func (b *Buffer) ConsumeUint64() uint64 { + if b.Err != nil { + return 0 + } + if b.Len() < 8 { - return 0, ErrShortPacket + b.off = len(b.b) + b.Err = ErrShortPacket + return 0 } v := binary.BigEndian.Uint64(b.b[b.off:]) b.off += 8 - return v, nil + return v } // AppendUint64 appends a single uint64 into the buffer, in network byte order (big-endian). @@ -194,14 +228,9 @@ func (b *Buffer) AppendUint64(v uint64) { } // ConsumeInt64 consumes a single int64 from the buffer, in network byte order (big-endian) with two’s complement. -// If the buffer does not have enough data, it will return ErrShortPacket. -func (b *Buffer) ConsumeInt64() (int64, error) { - u, err := b.ConsumeUint64() - if err != nil { - return 0, err - } - - return int64(u), err +// If the buffer does not have enough data, it will set Err to ErrShortPacket. +func (b *Buffer) ConsumeInt64() int64 { + return int64(b.ConsumeUint64()) } // AppendInt64 appends a single int64 into the buffer, in network byte order (big-endian) with two’s complement. @@ -211,29 +240,52 @@ func (b *Buffer) AppendInt64(v int64) { // ConsumeByteSlice consumes a single string of raw binary data from the buffer. // A string is a uint32 length, followed by that number of raw bytes. -// If the buffer does not have enough data, or defines a length larger than available, it will return ErrShortPacket. +// If the buffer does not have enough data, or defines a length larger than available, it will set Err to ErrShortPacket. // // The returned slice aliases the buffer contents, and is valid only as long as the buffer is not reused // (that is, only until the next call to Reset, PutLength, StartPacket, or UnmarshalBinary). // // In no case will any Consume calls return overlapping slice aliases, // and Append calls are guaranteed to not disturb this slice alias. -func (b *Buffer) ConsumeByteSlice() ([]byte, error) { - length, err := b.ConsumeUint32() - if err != nil { - return nil, err +func (b *Buffer) ConsumeByteSlice() []byte { + length := int(b.ConsumeUint32()) + if b.Err != nil { + return nil } - if b.Len() < int(length) { - return nil, ErrShortPacket + if b.Len() < length || length < 0 { + b.off = len(b.b) + b.Err = ErrShortPacket + return nil } v := b.b[b.off:] - if len(v) > int(length) { + if len(v) > length || cap(v) > length { v = v[:length:length] } b.off += int(length) - return v, nil + return v +} + +// ConsumeByteSliceCopy consumes a single string of raw binary data as a copy from the buffer. +// A string is a uint32 length, followed by that number of raw bytes. +// If the buffer does not have enough data, or defines a length larger than available, it will set Err to ErrShortPacket. +// +// The returned slice does not alias any buffer contents, +// and will therefore be valid even if the buffer is later reused. +// +// If hint has sufficient capacity to hold the data, it will be reused and overwritten, +// otherwise a new backing slice will be allocated and returned. +func (b *Buffer) ConsumeByteSliceCopy(hint []byte) []byte { + data := b.ConsumeByteSlice() + + if grow := len(data) - len(hint); grow > 0 { + hint = append(hint, make([]byte, grow)...) + } + + n := copy(hint, data) + hint = hint[:n] + return hint } // AppendByteSlice appends a single string of raw binary data into the buffer. @@ -245,17 +297,12 @@ func (b *Buffer) AppendByteSlice(v []byte) { // ConsumeString consumes a single string of binary data from the buffer. // A string is a uint32 length, followed by that number of raw bytes. -// If the buffer does not have enough data, or defines a length larger than available, it will return ErrShortPacket. +// If the buffer does not have enough data, or defines a length larger than available, it will set Err to ErrShortPacket. // // NOTE: Go implicitly assumes that strings contain UTF-8 encoded data. // All caveats on using arbitrary binary data in Go strings applies. -func (b *Buffer) ConsumeString() (string, error) { - v, err := b.ConsumeByteSlice() - if err != nil { - return "", err - } - - return string(v), nil +func (b *Buffer) ConsumeString() string { + return string(b.ConsumeByteSlice()) } // AppendString appends a single string of binary data into the buffer. diff --git a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/extended_packets.go b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/extended_packets.go index 6b7b2cef..f7174253 100644 --- a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/extended_packets.go +++ b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/extended_packets.go @@ -1,4 +1,4 @@ -package filexfer +package sshfx import ( "encoding" @@ -86,8 +86,9 @@ func (p *ExtendedPacket) MarshalPacket(reqid uint32, b []byte) (header, payload // If the extension has not been registered, then a new Buffer will be allocated. // Then the request-specific-data will be unmarshaled from the rest of the buffer. func (p *ExtendedPacket) UnmarshalPacketBody(buf *Buffer) (err error) { - if p.ExtendedRequest, err = buf.ConsumeString(); err != nil { - return err + p.ExtendedRequest = buf.ConsumeString() + if buf.Err != nil { + return buf.Err } if p.Data == nil { diff --git a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/extensions.go b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/extensions.go index 11c0b99c..c425780c 100644 --- a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/extensions.go +++ b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/extensions.go @@ -1,4 +1,4 @@ -package filexfer +package sshfx // ExtensionPair defines the extension-pair type defined in draft-ietf-secsh-filexfer-13. // This type is backwards-compatible with how draft-ietf-secsh-filexfer-02 defines extensions. @@ -29,15 +29,12 @@ func (e *ExtensionPair) MarshalBinary() ([]byte, error) { // UnmarshalFrom unmarshals an ExtensionPair from the given Buffer into e. func (e *ExtensionPair) UnmarshalFrom(buf *Buffer) (err error) { - if e.Name, err = buf.ConsumeString(); err != nil { - return err + *e = ExtensionPair{ + Name: buf.ConsumeString(), + Data: buf.ConsumeString(), } - if e.Data, err = buf.ConsumeString(); err != nil { - return err - } - - return nil + return buf.Err } // UnmarshalBinary decodes the binary encoding of ExtensionPair into e. diff --git a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/filexfer.go b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/filexfer.go index 1e5abf74..d3009994 100644 --- a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/filexfer.go +++ b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/filexfer.go @@ -1,5 +1,5 @@ -// Package filexfer implements the wire encoding for secsh-filexfer as described in https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02 -package filexfer +// Package sshfx implements the wire encoding for secsh-filexfer as described in https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt +package sshfx // PacketMarshaller narrowly defines packets that will only be transmitted. // diff --git a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/fx.go b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/fx.go index 48f86986..9abcbafc 100644 --- a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/fx.go +++ b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/fx.go @@ -1,4 +1,4 @@ -package filexfer +package sshfx import ( "fmt" @@ -10,7 +10,7 @@ type Status uint32 // Defines the various SSH_FX_* values. const ( // see draft-ietf-secsh-filexfer-02 - // https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-7 + // https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt#section-7 StatusOK = Status(iota) StatusEOF StatusNoSuchFile @@ -21,28 +21,28 @@ const ( StatusConnectionLost StatusOPUnsupported - // https://tools.ietf.org/html/draft-ietf-secsh-filexfer-03#section-7 + // https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-03.txt#section-7 StatusV4InvalidHandle StatusV4NoSuchPath StatusV4FileAlreadyExists StatusV4WriteProtect - // https://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-7 + // https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-04.txt#section-7 StatusV4NoMedia - // https://tools.ietf.org/html/draft-ietf-secsh-filexfer-05#section-7 + // https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-05.txt#section-7 StatusV5NoSpaceOnFilesystem StatusV5QuotaExceeded StatusV5UnknownPrincipal StatusV5LockConflict - // https://tools.ietf.org/html/draft-ietf-secsh-filexfer-06#section-8 + // https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-06.txt#section-8 StatusV6DirNotEmpty StatusV6NotADirectory StatusV6InvalidFilename StatusV6LinkLoop - // https://tools.ietf.org/html/draft-ietf-secsh-filexfer-07#section-8 + // https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-07.txt#section-8 StatusV6CannotDelete StatusV6InvalidParameter StatusV6FileIsADirectory @@ -50,10 +50,10 @@ const ( StatusV6ByteRangeLockRefused StatusV6DeletePending - // https://tools.ietf.org/html/draft-ietf-secsh-filexfer-08#section-8.1 + // https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-08.txt#section-8.1 StatusV6FileCorrupt - // https://tools.ietf.org/html/draft-ietf-secsh-filexfer-10#section-9.1 + // https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-10.txt#section-9.1 StatusV6OwnerInvalid StatusV6GroupInvalid diff --git a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/fxp.go b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/fxp.go index 15caf6d2..78080021 100644 --- a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/fxp.go +++ b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/fxp.go @@ -1,4 +1,4 @@ -package filexfer +package sshfx import ( "fmt" @@ -9,7 +9,7 @@ type PacketType uint8 // Request packet types. const ( - // https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-3 + // https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt#section-3 PacketTypeInit = PacketType(iota + 1) PacketTypeVersion PacketTypeOpen @@ -31,17 +31,17 @@ const ( PacketTypeReadLink PacketTypeSymlink - // https://tools.ietf.org/html/draft-ietf-secsh-filexfer-07#section-3.3 + // https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-07.txt#section-3.3 PacketTypeV6Link - // https://tools.ietf.org/html/draft-ietf-secsh-filexfer-08#section-3.3 + // https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-08.txt#section-3.3 PacketTypeV6Block PacketTypeV6Unblock ) // Response packet types. const ( - // https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-3 + // https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt#section-3 PacketTypeStatus = PacketType(iota + 101) PacketTypeHandle PacketTypeData @@ -51,7 +51,7 @@ const ( // Extended packet types. const ( - // https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-3 + // https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt#section-3 PacketTypeExtended = PacketType(iota + 200) PacketTypeExtendedReply ) @@ -122,3 +122,48 @@ func (f PacketType) String() string { return fmt.Sprintf("SSH_FXP_UNKNOWN(%d)", f) } } + +func newPacketFromType(typ PacketType) (Packet, error) { + switch typ { + case PacketTypeOpen: + return new(OpenPacket), nil + case PacketTypeClose: + return new(ClosePacket), nil + case PacketTypeRead: + return new(ReadPacket), nil + case PacketTypeWrite: + return new(WritePacket), nil + case PacketTypeLStat: + return new(LStatPacket), nil + case PacketTypeFStat: + return new(FStatPacket), nil + case PacketTypeSetstat: + return new(SetstatPacket), nil + case PacketTypeFSetstat: + return new(FSetstatPacket), nil + case PacketTypeOpenDir: + return new(OpenDirPacket), nil + case PacketTypeReadDir: + return new(ReadDirPacket), nil + case PacketTypeRemove: + return new(RemovePacket), nil + case PacketTypeMkdir: + return new(MkdirPacket), nil + case PacketTypeRmdir: + return new(RmdirPacket), nil + case PacketTypeRealPath: + return new(RealPathPacket), nil + case PacketTypeStat: + return new(StatPacket), nil + case PacketTypeRename: + return new(RenamePacket), nil + case PacketTypeReadLink: + return new(ReadLinkPacket), nil + case PacketTypeSymlink: + return new(SymlinkPacket), nil + case PacketTypeExtended: + return new(ExtendedPacket), nil + default: + return nil, fmt.Errorf("unexpected request packet type: %v", typ) + } +} diff --git a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/handle_packets.go b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/handle_packets.go index a1427712..44594acf 100644 --- a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/handle_packets.go +++ b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/handle_packets.go @@ -1,4 +1,4 @@ -package filexfer +package sshfx // ClosePacket defines the SSH_FXP_CLOSE packet. type ClosePacket struct { @@ -27,18 +27,18 @@ func (p *ClosePacket) MarshalPacket(reqid uint32, b []byte) (header, payload []b // UnmarshalPacketBody unmarshals the packet body from the given Buffer. // It is assumed that the uint32(request-id) has already been consumed. func (p *ClosePacket) UnmarshalPacketBody(buf *Buffer) (err error) { - if p.Handle, err = buf.ConsumeString(); err != nil { - return err + *p = ClosePacket{ + Handle: buf.ConsumeString(), } - return nil + return buf.Err } // ReadPacket defines the SSH_FXP_READ packet. type ReadPacket struct { Handle string Offset uint64 - Len uint32 + Length uint32 } // Type returns the SSH_FXP_xy value associated with this packet type. @@ -58,7 +58,7 @@ func (p *ReadPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []by buf.StartPacket(PacketTypeRead, reqid) buf.AppendString(p.Handle) buf.AppendUint64(p.Offset) - buf.AppendUint32(p.Len) + buf.AppendUint32(p.Length) return buf.Packet(payload) } @@ -66,19 +66,13 @@ func (p *ReadPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []by // UnmarshalPacketBody unmarshals the packet body from the given Buffer. // It is assumed that the uint32(request-id) has already been consumed. func (p *ReadPacket) UnmarshalPacketBody(buf *Buffer) (err error) { - if p.Handle, err = buf.ConsumeString(); err != nil { - return err + *p = ReadPacket{ + Handle: buf.ConsumeString(), + Offset: buf.ConsumeUint64(), + Length: buf.ConsumeUint32(), } - if p.Offset, err = buf.ConsumeUint64(); err != nil { - return err - } - - if p.Len, err = buf.ConsumeUint32(); err != nil { - return err - } - - return nil + return buf.Err } // WritePacket defines the SSH_FXP_WRITE packet. @@ -121,26 +115,13 @@ func (p *WritePacket) MarshalPacket(reqid uint32, b []byte) (header, payload []b // // This means this _does not_ alias any of the data buffer that is passed in. func (p *WritePacket) UnmarshalPacketBody(buf *Buffer) (err error) { - if p.Handle, err = buf.ConsumeString(); err != nil { - return err - } - - if p.Offset, err = buf.ConsumeUint64(); err != nil { - return err - } - - data, err := buf.ConsumeByteSlice() - if err != nil { - return err - } - - if len(p.Data) < len(data) { - p.Data = make([]byte, len(data)) + *p = WritePacket{ + Handle: buf.ConsumeString(), + Offset: buf.ConsumeUint64(), + Data: buf.ConsumeByteSliceCopy(p.Data), } - n := copy(p.Data, data) - p.Data = p.Data[:n] - return nil + return buf.Err } // FStatPacket defines the SSH_FXP_FSTAT packet. @@ -170,11 +151,11 @@ func (p *FStatPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []b // UnmarshalPacketBody unmarshals the packet body from the given Buffer. // It is assumed that the uint32(request-id) has already been consumed. func (p *FStatPacket) UnmarshalPacketBody(buf *Buffer) (err error) { - if p.Handle, err = buf.ConsumeString(); err != nil { - return err + *p = FStatPacket{ + Handle: buf.ConsumeString(), } - return nil + return buf.Err } // FSetstatPacket defines the SSH_FXP_FSETSTAT packet. @@ -207,8 +188,8 @@ func (p *FSetstatPacket) MarshalPacket(reqid uint32, b []byte) (header, payload // UnmarshalPacketBody unmarshals the packet body from the given Buffer. // It is assumed that the uint32(request-id) has already been consumed. func (p *FSetstatPacket) UnmarshalPacketBody(buf *Buffer) (err error) { - if p.Handle, err = buf.ConsumeString(); err != nil { - return err + *p = FSetstatPacket{ + Handle: buf.ConsumeString(), } return p.Attrs.UnmarshalFrom(buf) @@ -241,9 +222,9 @@ func (p *ReadDirPacket) MarshalPacket(reqid uint32, b []byte) (header, payload [ // UnmarshalPacketBody unmarshals the packet body from the given Buffer. // It is assumed that the uint32(request-id) has already been consumed. func (p *ReadDirPacket) UnmarshalPacketBody(buf *Buffer) (err error) { - if p.Handle, err = buf.ConsumeString(); err != nil { - return err + *p = ReadDirPacket{ + Handle: buf.ConsumeString(), } - return nil + return buf.Err } diff --git a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/init_packets.go b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/init_packets.go index b0bc6f50..c553ee2e 100644 --- a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/init_packets.go +++ b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/init_packets.go @@ -1,4 +1,4 @@ -package filexfer +package sshfx // InitPacket defines the SSH_FXP_INIT packet. type InitPacket struct { @@ -33,8 +33,8 @@ func (p *InitPacket) MarshalBinary() ([]byte, error) { func (p *InitPacket) UnmarshalBinary(data []byte) (err error) { buf := NewBuffer(data) - if p.Version, err = buf.ConsumeUint32(); err != nil { - return err + *p = InitPacket{ + Version: buf.ConsumeUint32(), } for buf.Len() > 0 { @@ -46,7 +46,7 @@ func (p *InitPacket) UnmarshalBinary(data []byte) (err error) { p.Extensions = append(p.Extensions, &ext) } - return nil + return buf.Err } // VersionPacket defines the SSH_FXP_VERSION packet. @@ -82,8 +82,8 @@ func (p *VersionPacket) MarshalBinary() ([]byte, error) { func (p *VersionPacket) UnmarshalBinary(data []byte) (err error) { buf := NewBuffer(data) - if p.Version, err = buf.ConsumeUint32(); err != nil { - return err + *p = VersionPacket{ + Version: buf.ConsumeUint32(), } for buf.Len() > 0 { diff --git a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/open_packets.go b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/open_packets.go index 13587114..896ba16e 100644 --- a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/open_packets.go +++ b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/open_packets.go @@ -1,4 +1,4 @@ -package filexfer +package sshfx // SSH_FXF_* flags. const ( @@ -43,12 +43,9 @@ func (p *OpenPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []by // UnmarshalPacketBody unmarshals the packet body from the given Buffer. // It is assumed that the uint32(request-id) has already been consumed. func (p *OpenPacket) UnmarshalPacketBody(buf *Buffer) (err error) { - if p.Filename, err = buf.ConsumeString(); err != nil { - return err - } - - if p.PFlags, err = buf.ConsumeUint32(); err != nil { - return err + *p = OpenPacket{ + Filename: buf.ConsumeString(), + PFlags: buf.ConsumeUint32(), } return p.Attrs.UnmarshalFrom(buf) @@ -81,9 +78,9 @@ func (p *OpenDirPacket) MarshalPacket(reqid uint32, b []byte) (header, payload [ // UnmarshalPacketBody unmarshals the packet body from the given Buffer. // It is assumed that the uint32(request-id) has already been consumed. func (p *OpenDirPacket) UnmarshalPacketBody(buf *Buffer) (err error) { - if p.Path, err = buf.ConsumeString(); err != nil { - return err + *p = OpenDirPacket{ + Path: buf.ConsumeString(), } - return nil + return buf.Err } diff --git a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/packets.go b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/packets.go index 3f24e9c2..fdf65d05 100644 --- a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/packets.go +++ b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/packets.go @@ -1,59 +1,13 @@ -package filexfer +package sshfx import ( "errors" - "fmt" "io" ) // smallBufferSize is an initial allocation minimal capacity. const smallBufferSize = 64 -func newPacketFromType(typ PacketType) (Packet, error) { - switch typ { - case PacketTypeOpen: - return new(OpenPacket), nil - case PacketTypeClose: - return new(ClosePacket), nil - case PacketTypeRead: - return new(ReadPacket), nil - case PacketTypeWrite: - return new(WritePacket), nil - case PacketTypeLStat: - return new(LStatPacket), nil - case PacketTypeFStat: - return new(FStatPacket), nil - case PacketTypeSetstat: - return new(SetstatPacket), nil - case PacketTypeFSetstat: - return new(FSetstatPacket), nil - case PacketTypeOpenDir: - return new(OpenDirPacket), nil - case PacketTypeReadDir: - return new(ReadDirPacket), nil - case PacketTypeRemove: - return new(RemovePacket), nil - case PacketTypeMkdir: - return new(MkdirPacket), nil - case PacketTypeRmdir: - return new(RmdirPacket), nil - case PacketTypeRealPath: - return new(RealPathPacket), nil - case PacketTypeStat: - return new(StatPacket), nil - case PacketTypeRename: - return new(RenamePacket), nil - case PacketTypeReadLink: - return new(ReadLinkPacket), nil - case PacketTypeSymlink: - return new(SymlinkPacket), nil - case PacketTypeExtended: - return new(ExtendedPacket), nil - default: - return nil, fmt.Errorf("unexpected request packet type: %v", typ) - } -} - // RawPacket implements the general packet format from draft-ietf-secsh-filexfer-02 // // RawPacket is intended for use in clients receiving responses, @@ -63,7 +17,7 @@ func newPacketFromType(typ PacketType) (Packet, error) { // For servers expecting to receive arbitrary request packet types, // use RequestPacket. // -// Defined in https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-3 +// Defined in https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt#section-3 type RawPacket struct { PacketType PacketType RequestID uint32 @@ -110,19 +64,14 @@ func (p *RawPacket) MarshalBinary() ([]byte, error) { // The Data field will alias the passed in Buffer, // so the buffer passed in should not be reused before RawPacket.Reset(). func (p *RawPacket) UnmarshalFrom(buf *Buffer) error { - typ, err := buf.ConsumeUint8() - if err != nil { - return err - } - - p.PacketType = PacketType(typ) - - if p.RequestID, err = buf.ConsumeUint32(); err != nil { - return err + *p = RawPacket{ + PacketType: PacketType(buf.ConsumeUint8()), + RequestID: buf.ConsumeUint32(), } p.Data = *buf - return nil + + return buf.Err } // UnmarshalBinary decodes a full raw packet out of the given data. @@ -225,7 +174,7 @@ func (p *RawPacket) ReadFrom(r io.Reader, b []byte, maxPacketLength uint32) erro // where automatic unmarshaling of the packet body does not make sense, // use RawPacket. // -// Defined in https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-3 +// Defined in https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt#section-3 type RequestPacket struct { RequestID uint32 @@ -268,18 +217,19 @@ func (p *RequestPacket) MarshalBinary() ([]byte, error) { // The Request field may alias the passed in Buffer, (e.g. SSH_FXP_WRITE), // so the buffer passed in should not be reused before RequestPacket.Reset(). func (p *RequestPacket) UnmarshalFrom(buf *Buffer) error { - typ, err := buf.ConsumeUint8() - if err != nil { - return err + typ := PacketType(buf.ConsumeUint8()) + if buf.Err != nil { + return buf.Err } - p.Request, err = newPacketFromType(PacketType(typ)) + req, err := newPacketFromType(typ) if err != nil { return err } - if p.RequestID, err = buf.ConsumeUint32(); err != nil { - return err + *p = RequestPacket{ + RequestID: buf.ConsumeUint32(), + Request: req, } return p.Request.UnmarshalPacketBody(buf) diff --git a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/path_packets.go b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/path_packets.go index e6f692d9..0180326f 100644 --- a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/path_packets.go +++ b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/path_packets.go @@ -1,4 +1,4 @@ -package filexfer +package sshfx // LStatPacket defines the SSH_FXP_LSTAT packet. type LStatPacket struct { @@ -27,11 +27,11 @@ func (p *LStatPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []b // UnmarshalPacketBody unmarshals the packet body from the given Buffer. // It is assumed that the uint32(request-id) has already been consumed. func (p *LStatPacket) UnmarshalPacketBody(buf *Buffer) (err error) { - if p.Path, err = buf.ConsumeString(); err != nil { - return err + *p = LStatPacket{ + Path: buf.ConsumeString(), } - return nil + return buf.Err } // SetstatPacket defines the SSH_FXP_SETSTAT packet. @@ -64,8 +64,8 @@ func (p *SetstatPacket) MarshalPacket(reqid uint32, b []byte) (header, payload [ // UnmarshalPacketBody unmarshals the packet body from the given Buffer. // It is assumed that the uint32(request-id) has already been consumed. func (p *SetstatPacket) UnmarshalPacketBody(buf *Buffer) (err error) { - if p.Path, err = buf.ConsumeString(); err != nil { - return err + *p = SetstatPacket{ + Path: buf.ConsumeString(), } return p.Attrs.UnmarshalFrom(buf) @@ -98,11 +98,11 @@ func (p *RemovePacket) MarshalPacket(reqid uint32, b []byte) (header, payload [] // UnmarshalPacketBody unmarshals the packet body from the given Buffer. // It is assumed that the uint32(request-id) has already been consumed. func (p *RemovePacket) UnmarshalPacketBody(buf *Buffer) (err error) { - if p.Path, err = buf.ConsumeString(); err != nil { - return err + *p = RemovePacket{ + Path: buf.ConsumeString(), } - return nil + return buf.Err } // MkdirPacket defines the SSH_FXP_MKDIR packet. @@ -135,8 +135,8 @@ func (p *MkdirPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []b // UnmarshalPacketBody unmarshals the packet body from the given Buffer. // It is assumed that the uint32(request-id) has already been consumed. func (p *MkdirPacket) UnmarshalPacketBody(buf *Buffer) (err error) { - if p.Path, err = buf.ConsumeString(); err != nil { - return err + *p = MkdirPacket{ + Path: buf.ConsumeString(), } return p.Attrs.UnmarshalFrom(buf) @@ -169,11 +169,11 @@ func (p *RmdirPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []b // UnmarshalPacketBody unmarshals the packet body from the given Buffer. // It is assumed that the uint32(request-id) has already been consumed. func (p *RmdirPacket) UnmarshalPacketBody(buf *Buffer) (err error) { - if p.Path, err = buf.ConsumeString(); err != nil { - return err + *p = RmdirPacket{ + Path: buf.ConsumeString(), } - return nil + return buf.Err } // RealPathPacket defines the SSH_FXP_REALPATH packet. @@ -203,11 +203,11 @@ func (p *RealPathPacket) MarshalPacket(reqid uint32, b []byte) (header, payload // UnmarshalPacketBody unmarshals the packet body from the given Buffer. // It is assumed that the uint32(request-id) has already been consumed. func (p *RealPathPacket) UnmarshalPacketBody(buf *Buffer) (err error) { - if p.Path, err = buf.ConsumeString(); err != nil { - return err + *p = RealPathPacket{ + Path: buf.ConsumeString(), } - return nil + return buf.Err } // StatPacket defines the SSH_FXP_STAT packet. @@ -237,11 +237,11 @@ func (p *StatPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []by // UnmarshalPacketBody unmarshals the packet body from the given Buffer. // It is assumed that the uint32(request-id) has already been consumed. func (p *StatPacket) UnmarshalPacketBody(buf *Buffer) (err error) { - if p.Path, err = buf.ConsumeString(); err != nil { - return err + *p = StatPacket{ + Path: buf.ConsumeString(), } - return nil + return buf.Err } // RenamePacket defines the SSH_FXP_RENAME packet. @@ -274,15 +274,12 @@ func (p *RenamePacket) MarshalPacket(reqid uint32, b []byte) (header, payload [] // UnmarshalPacketBody unmarshals the packet body from the given Buffer. // It is assumed that the uint32(request-id) has already been consumed. func (p *RenamePacket) UnmarshalPacketBody(buf *Buffer) (err error) { - if p.OldPath, err = buf.ConsumeString(); err != nil { - return err + *p = RenamePacket{ + OldPath: buf.ConsumeString(), + NewPath: buf.ConsumeString(), } - if p.NewPath, err = buf.ConsumeString(); err != nil { - return err - } - - return nil + return buf.Err } // ReadLinkPacket defines the SSH_FXP_READLINK packet. @@ -312,18 +309,18 @@ func (p *ReadLinkPacket) MarshalPacket(reqid uint32, b []byte) (header, payload // UnmarshalPacketBody unmarshals the packet body from the given Buffer. // It is assumed that the uint32(request-id) has already been consumed. func (p *ReadLinkPacket) UnmarshalPacketBody(buf *Buffer) (err error) { - if p.Path, err = buf.ConsumeString(); err != nil { - return err + *p = ReadLinkPacket{ + Path: buf.ConsumeString(), } - return nil + return buf.Err } // SymlinkPacket defines the SSH_FXP_SYMLINK packet. // // The order of the arguments to the SSH_FXP_SYMLINK method was inadvertently reversed. // Unfortunately, the reversal was not noticed until the server was widely deployed. -// Covered in Section 3.1 of https://github.com/openssh/openssh-portable/blob/master/PROTOCOL +// Covered in Section 4.1 of https://github.com/openssh/openssh-portable/blob/master/PROTOCOL type SymlinkPacket struct { LinkPath string TargetPath string @@ -355,14 +352,11 @@ func (p *SymlinkPacket) MarshalPacket(reqid uint32, b []byte) (header, payload [ // UnmarshalPacketBody unmarshals the packet body from the given Buffer. // It is assumed that the uint32(request-id) has already been consumed. func (p *SymlinkPacket) UnmarshalPacketBody(buf *Buffer) (err error) { - // Arguments were inadvertently reversed. - if p.TargetPath, err = buf.ConsumeString(); err != nil { - return err - } - - if p.LinkPath, err = buf.ConsumeString(); err != nil { - return err + *p = SymlinkPacket{ + // Arguments were inadvertently reversed. + TargetPath: buf.ConsumeString(), + LinkPath: buf.ConsumeString(), } - return nil + return buf.Err } diff --git a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/permissions.go b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/permissions.go index 2fe63d59..0143ec0c 100644 --- a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/permissions.go +++ b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/permissions.go @@ -1,4 +1,4 @@ -package filexfer +package sshfx // FileMode represents a file’s mode and permission bits. // The bits are defined according to POSIX standards, diff --git a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/response_packets.go b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/response_packets.go index 7a9b3eae..311708ff 100644 --- a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/response_packets.go +++ b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/response_packets.go @@ -1,4 +1,4 @@ -package filexfer +package sshfx import ( "fmt" @@ -6,7 +6,7 @@ import ( // StatusPacket defines the SSH_FXP_STATUS packet. // -// Specified in https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-7 +// Specified in https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt#section-7 type StatusPacket struct { StatusCode Status ErrorMessage string @@ -19,7 +19,7 @@ func (p *StatusPacket) Error() string { return "sftp: " + p.StatusCode.String() } - return fmt.Sprintf("sftp: %q (%s)", p.ErrorMessage, p.StatusCode) + return fmt.Sprintf("sftp: %s: %q", p.StatusCode, p.ErrorMessage) } // Is returns true if target is a StatusPacket with the same StatusCode, @@ -57,21 +57,13 @@ func (p *StatusPacket) MarshalPacket(reqid uint32, b []byte) (header, payload [] // UnmarshalPacketBody unmarshals the packet body from the given Buffer. // It is assumed that the uint32(request-id) has already been consumed. func (p *StatusPacket) UnmarshalPacketBody(buf *Buffer) (err error) { - statusCode, err := buf.ConsumeUint32() - if err != nil { - return err + *p = StatusPacket{ + StatusCode: Status(buf.ConsumeUint32()), + ErrorMessage: buf.ConsumeString(), + LanguageTag: buf.ConsumeString(), } - p.StatusCode = Status(statusCode) - if p.ErrorMessage, err = buf.ConsumeString(); err != nil { - return err - } - - if p.LanguageTag, err = buf.ConsumeString(); err != nil { - return err - } - - return nil + return buf.Err } // HandlePacket defines the SSH_FXP_HANDLE packet. @@ -101,11 +93,11 @@ func (p *HandlePacket) MarshalPacket(reqid uint32, b []byte) (header, payload [] // UnmarshalPacketBody unmarshals the packet body from the given Buffer. // It is assumed that the uint32(request-id) has already been consumed. func (p *HandlePacket) UnmarshalPacketBody(buf *Buffer) (err error) { - if p.Handle, err = buf.ConsumeString(); err != nil { - return err + *p = HandlePacket{ + Handle: buf.ConsumeString(), } - return nil + return buf.Err } // DataPacket defines the SSH_FXP_DATA packet. @@ -143,18 +135,11 @@ func (p *DataPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []by // // This means this _does not_ alias any of the data buffer that is passed in. func (p *DataPacket) UnmarshalPacketBody(buf *Buffer) (err error) { - data, err := buf.ConsumeByteSlice() - if err != nil { - return err + *p = DataPacket{ + Data: buf.ConsumeByteSliceCopy(p.Data), } - if len(p.Data) < len(data) { - p.Data = make([]byte, len(data)) - } - - n := copy(p.Data, data) - p.Data = p.Data[:n] - return nil + return buf.Err } // NamePacket defines the SSH_FXP_NAME packet. @@ -193,14 +178,16 @@ func (p *NamePacket) MarshalPacket(reqid uint32, b []byte) (header, payload []by // UnmarshalPacketBody unmarshals the packet body from the given Buffer. // It is assumed that the uint32(request-id) has already been consumed. func (p *NamePacket) UnmarshalPacketBody(buf *Buffer) (err error) { - count, err := buf.ConsumeUint32() - if err != nil { - return err + count := buf.ConsumeCount() + if buf.Err != nil { + return buf.Err } - p.Entries = make([]*NameEntry, 0, count) + *p = NamePacket{ + Entries: make([]*NameEntry, 0, count), + } - for i := uint32(0); i < count; i++ { + for i := 0; i < count; i++ { var e NameEntry if err := e.UnmarshalFrom(buf); err != nil { return err @@ -209,7 +196,7 @@ func (p *NamePacket) UnmarshalPacketBody(buf *Buffer) (err error) { p.Entries = append(p.Entries, &e) } - return nil + return buf.Err } // AttrsPacket defines the SSH_FXP_ATTRS packet. diff --git a/vendor/github.com/pkg/sftp/ls_formatting.go b/vendor/github.com/pkg/sftp/ls_formatting.go index e083e22a..19271ad7 100644 --- a/vendor/github.com/pkg/sftp/ls_formatting.go +++ b/vendor/github.com/pkg/sftp/ls_formatting.go @@ -60,6 +60,13 @@ func runLs(idLookup NameLookupFileLister, dirent os.FileInfo) string { uid = lsFormatID(sys.UID) gid = lsFormatID(sys.GID) default: + if fiExt, ok := dirent.(FileInfoUidGid); ok { + uid = lsFormatID(fiExt.Uid()) + gid = lsFormatID(fiExt.Gid()) + + break + } + numLinks, uid, gid = lsLinksUIDGID(dirent) } diff --git a/vendor/github.com/pkg/sftp/ls_plan9.go b/vendor/github.com/pkg/sftp/ls_plan9.go index a16a3ea0..b70b2942 100644 --- a/vendor/github.com/pkg/sftp/ls_plan9.go +++ b/vendor/github.com/pkg/sftp/ls_plan9.go @@ -1,3 +1,4 @@ +//go:build plan9 // +build plan9 package sftp diff --git a/vendor/github.com/pkg/sftp/ls_stub.go b/vendor/github.com/pkg/sftp/ls_stub.go index 6dec3937..f58abf78 100644 --- a/vendor/github.com/pkg/sftp/ls_stub.go +++ b/vendor/github.com/pkg/sftp/ls_stub.go @@ -1,3 +1,4 @@ +//go:build windows || android // +build windows android package sftp diff --git a/vendor/github.com/pkg/sftp/ls_unix.go b/vendor/github.com/pkg/sftp/ls_unix.go index 59ccffde..0beba32b 100644 --- a/vendor/github.com/pkg/sftp/ls_unix.go +++ b/vendor/github.com/pkg/sftp/ls_unix.go @@ -1,3 +1,4 @@ +//go:build aix || darwin || dragonfly || freebsd || (!android && linux) || netbsd || openbsd || solaris || js // +build aix darwin dragonfly freebsd !android,linux netbsd openbsd solaris js package sftp diff --git a/vendor/github.com/pkg/sftp/packet-manager.go b/vendor/github.com/pkg/sftp/packet-manager.go index c740c4c8..647836ba 100644 --- a/vendor/github.com/pkg/sftp/packet-manager.go +++ b/vendor/github.com/pkg/sftp/packet-manager.go @@ -40,7 +40,7 @@ func newPktMgr(sender packetSender) *packetManager { return s } -//// packet ordering +// // packet ordering func (s *packetManager) newOrderID() uint32 { s.packetCount++ return s.packetCount @@ -89,7 +89,7 @@ func (o orderedPackets) Sort() { }) } -//// packet registry +// // packet registry // register incoming packets to be handled func (s *packetManager) incomingPacket(pkt orderedRequest) { s.working.Add(1) diff --git a/vendor/github.com/pkg/sftp/packet-typing.go b/vendor/github.com/pkg/sftp/packet-typing.go index f4f90529..fec88e7b 100644 --- a/vendor/github.com/pkg/sftp/packet-typing.go +++ b/vendor/github.com/pkg/sftp/packet-typing.go @@ -31,7 +31,7 @@ type notReadOnly interface { notReadOnly() } -//// define types by adding methods +// // define types by adding methods // hasPath func (p *sshFxpLstatPacket) getPath() string { return p.Path } func (p *sshFxpStatPacket) getPath() string { return p.Path } diff --git a/vendor/github.com/pkg/sftp/packet.go b/vendor/github.com/pkg/sftp/packet.go index 50ca069d..1232ff1e 100644 --- a/vendor/github.com/pkg/sftp/packet.go +++ b/vendor/github.com/pkg/sftp/packet.go @@ -71,6 +71,15 @@ func marshalFileInfo(b []byte, fi os.FileInfo) []byte { b = marshalUint32(b, fileStat.Mtime) } + if flags&sshFileXferAttrExtended != 0 { + b = marshalUint32(b, uint32(len(fileStat.Extended))) + + for _, attr := range fileStat.Extended { + b = marshalString(b, attr.ExtType) + b = marshalString(b, attr.ExtData) + } + } + return b } @@ -281,6 +290,11 @@ func recvPacket(r io.Reader, alloc *allocator, orderID uint32) (uint8, []byte, e b = make([]byte, length) } if _, err := io.ReadFull(r, b[:length]); err != nil { + // ReadFull only returns EOF if it has read no bytes. + // In this case, that means a partial packet, and thus unexpected. + if err == io.EOF { + err = io.ErrUnexpectedEOF + } debug("recv packet %d bytes: err %v", length, err) return 0, nil, err } @@ -522,7 +536,12 @@ func (p *sshFxpRmdirPacket) UnmarshalBinary(b []byte) error { } type sshFxpSymlinkPacket struct { - ID uint32 + ID uint32 + + // The order of the arguments to the SSH_FXP_SYMLINK method was inadvertently reversed. + // Unfortunately, the reversal was not noticed until the server was widely deployed. + // Covered in Section 4.1 of https://github.com/openssh/openssh-portable/blob/master/PROTOCOL + Targetpath string Linkpath string } @@ -1242,7 +1261,7 @@ func (p *sshFxpExtendedPacketPosixRename) UnmarshalBinary(b []byte) error { } func (p *sshFxpExtendedPacketPosixRename) respond(s *Server) responsePacket { - err := os.Rename(p.Oldpath, p.Newpath) + err := os.Rename(s.toLocalPath(p.Oldpath), s.toLocalPath(p.Newpath)) return statusFromError(p.ID, err) } @@ -1271,6 +1290,6 @@ func (p *sshFxpExtendedPacketHardlink) UnmarshalBinary(b []byte) error { } func (p *sshFxpExtendedPacketHardlink) respond(s *Server) responsePacket { - err := os.Link(p.Oldpath, p.Newpath) + err := os.Link(s.toLocalPath(p.Oldpath), s.toLocalPath(p.Newpath)) return statusFromError(p.ID, err) } diff --git a/vendor/github.com/pkg/sftp/release.go b/vendor/github.com/pkg/sftp/release.go index b695528f..9ecedc44 100644 --- a/vendor/github.com/pkg/sftp/release.go +++ b/vendor/github.com/pkg/sftp/release.go @@ -1,3 +1,4 @@ +//go:build !debug // +build !debug package sftp diff --git a/vendor/github.com/pkg/sftp/request-example.go b/vendor/github.com/pkg/sftp/request-example.go index ba22bcd0..519b3b76 100644 --- a/vendor/github.com/pkg/sftp/request-example.go +++ b/vendor/github.com/pkg/sftp/request-example.go @@ -391,21 +391,6 @@ func (fs *root) Filelist(r *Request) (ListerAt, error) { return nil, err } return listerat{file}, nil - - case "Readlink": - symlink, err := fs.readlink(r.Filepath) - if err != nil { - return nil, err - } - - // SFTP-v2: The server will respond with a SSH_FXP_NAME packet containing only - // one name and a dummy attributes value. - return listerat{ - &memFile{ - name: symlink, - err: os.ErrNotExist, // prevent accidental use as a reader/writer. - }, - }, nil } return nil, errors.New("unsupported") @@ -434,7 +419,7 @@ func (fs *root) readdir(pathname string) ([]os.FileInfo, error) { return files, nil } -func (fs *root) readlink(pathname string) (string, error) { +func (fs *root) Readlink(pathname string) (string, error) { file, err := fs.lfetch(pathname) if err != nil { return "", err @@ -464,19 +449,10 @@ func (fs *root) Lstat(r *Request) (ListerAt, error) { return listerat{file}, nil } -// implements RealpathFileLister interface -func (fs *root) Realpath(p string) string { - if fs.startDirectory == "" || fs.startDirectory == "/" { - return cleanPath(p) - } - return cleanPathWithBase(fs.startDirectory, p) -} - // In memory file-system-y thing that the Hanlders live on type root struct { - rootFile *memFile - mockErr error - startDirectory string + rootFile *memFile + mockErr error mu sync.Mutex files map[string]*memFile @@ -534,8 +510,8 @@ func (fs *root) exists(path string) bool { return err != os.ErrNotExist } -func (fs *root) fetch(path string) (*memFile, error) { - file, err := fs.lfetch(path) +func (fs *root) fetch(pathname string) (*memFile, error) { + file, err := fs.lfetch(pathname) if err != nil { return nil, err } @@ -546,7 +522,12 @@ func (fs *root) fetch(path string) (*memFile, error) { return nil, errTooManySymlinks } - file, err = fs.lfetch(file.symlink) + linkTarget := file.symlink + if !path.IsAbs(linkTarget) { + linkTarget = path.Join(path.Dir(file.name), linkTarget) + } + + file, err = fs.lfetch(linkTarget) if err != nil { return nil, err } diff --git a/vendor/github.com/pkg/sftp/request-interfaces.go b/vendor/github.com/pkg/sftp/request-interfaces.go index c8c424c9..2090e316 100644 --- a/vendor/github.com/pkg/sftp/request-interfaces.go +++ b/vendor/github.com/pkg/sftp/request-interfaces.go @@ -74,6 +74,11 @@ type StatVFSFileCmder interface { // FileLister should return an object that fulfils the ListerAt interface // Note in cases of an error, the error text will be sent to the client. // Called for Methods: List, Stat, Readlink +// +// Since Filelist returns an os.FileInfo, this can make it non-ideal for implementing Readlink. +// This is because the Name receiver method defined by that interface defines that it should only return the base name. +// However, Readlink is required to be capable of returning essentially any arbitrary valid path relative or absolute. +// In order to implement this more expressive requirement, implement [ReadlinkFileLister] which will then be used instead. type FileLister interface { Filelist(*Request) (ListerAt, error) } @@ -87,10 +92,33 @@ type LstatFileLister interface { } // RealPathFileLister is a FileLister that implements the Realpath method. -// We use "/" as start directory for relative paths, implementing this -// interface you can customize the start directory. +// The built-in RealPath implementation does not resolve symbolic links. +// By implementing this interface you can customize the returned path +// and, for example, resolve symbolinc links if needed for your use case. // You have to return an absolute POSIX path. +// +// Up to v1.13.5 the signature for the RealPath method was: +// +// # RealPath(string) string +// +// we have added a legacyRealPathFileLister that implements the old method +// to ensure that your code does not break. +// You should use the new method signature to avoid future issues type RealPathFileLister interface { + FileLister + RealPath(string) (string, error) +} + +// ReadlinkFileLister is a FileLister that implements the Readlink method. +// By implementing the Readlink method, it is possible to return any arbitrary valid path relative or absolute. +// This allows giving a better response than via the default FileLister (which is limited to os.FileInfo, whose Name method should only return the base name of a file) +type ReadlinkFileLister interface { + FileLister + Readlink(string) (string, error) +} + +// This interface is here for backward compatibility only +type legacyRealPathFileLister interface { FileLister RealPath(string) string } @@ -103,11 +131,19 @@ type NameLookupFileLister interface { LookupGroupName(string) string } -// ListerAt does for file lists what io.ReaderAt does for files. -// ListAt should return the number of entries copied and an io.EOF -// error if at end of list. This is testable by comparing how many you -// copied to how many could be copied (eg. n < len(ls) below). +// ListerAt does for file lists what io.ReaderAt does for files, i.e. a []os.FileInfo buffer is passed to the ListAt function +// and the entries that are populated in the buffer will be passed to the client. +// +// ListAt should return the number of entries copied and an io.EOF error if at end of list. +// This is testable by comparing how many you copied to how many could be copied (eg. n < len(ls) below). // The copy() builtin is best for the copying. +// +// Uid and gid information will on unix systems be retrieved from [os.FileInfo.Sys] +// if this function returns a [syscall.Stat_t] when called on a populated entry. +// Alternatively, if the entry implements [FileInfoUidGid], it will be used for uid and gid information. +// +// If a populated entry implements [FileInfoExtendedData], extended attributes will also be returned to the client. +// // Note in cases of an error, the error text will be sent to the client. type ListerAt interface { ListAt([]os.FileInfo, int64) (int, error) diff --git a/vendor/github.com/pkg/sftp/request-plan9.go b/vendor/github.com/pkg/sftp/request-plan9.go index 2444da59..38f91bcd 100644 --- a/vendor/github.com/pkg/sftp/request-plan9.go +++ b/vendor/github.com/pkg/sftp/request-plan9.go @@ -1,10 +1,9 @@ +//go:build plan9 // +build plan9 package sftp import ( - "path" - "path/filepath" "syscall" ) @@ -15,20 +14,3 @@ func fakeFileInfoSys() interface{} { func testOsSys(sys interface{}) error { return nil } - -func toLocalPath(p string) string { - lp := filepath.FromSlash(p) - - if path.IsAbs(p) { - tmp := lp[1:] - - if filepath.IsAbs(tmp) { - // If the FromSlash without any starting slashes is absolute, - // then we have a filepath encoded with a prefix '/'. - // e.g. "/#s/boot" to "#s/boot" - return tmp - } - } - - return lp -} diff --git a/vendor/github.com/pkg/sftp/request-readme.md b/vendor/github.com/pkg/sftp/request-readme.md index f887274d..f8b81f3a 100644 --- a/vendor/github.com/pkg/sftp/request-readme.md +++ b/vendor/github.com/pkg/sftp/request-readme.md @@ -28,7 +28,7 @@ then sends to the client. Handler for "Put" method and returns an io.Writer for the file which the server then writes the uploaded file to. The file opening "pflags" are currently preserved in the Request.Flags field as a 32bit bitmask value. See the [SFTP -spec](https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-6.3) for +spec](https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt#section-6.3) for details. ### Filecmd(*Request) error diff --git a/vendor/github.com/pkg/sftp/request-server.go b/vendor/github.com/pkg/sftp/request-server.go index 5fa828bb..7a99db64 100644 --- a/vendor/github.com/pkg/sftp/request-server.go +++ b/vendor/github.com/pkg/sftp/request-server.go @@ -27,6 +27,8 @@ type RequestServer struct { *serverConn pktMgr *packetManager + startDirectory string + mu sync.RWMutex handleCount int openRequests map[string]*Request @@ -47,6 +49,14 @@ func WithRSAllocator() RequestServerOption { } } +// WithStartDirectory sets a start directory to use as base for relative paths. +// If unset the default is "/" +func WithStartDirectory(startDirectory string) RequestServerOption { + return func(rs *RequestServer) { + rs.startDirectory = cleanPath(startDirectory) + } +} + // NewRequestServer creates/allocates/returns new RequestServer. // Normally there will be one server per user-session. func NewRequestServer(rwc io.ReadWriteCloser, h Handlers, options ...RequestServerOption) *RequestServer { @@ -62,6 +72,8 @@ func NewRequestServer(rwc io.ReadWriteCloser, h Handlers, options ...RequestServ serverConn: svrConn, pktMgr: newPktMgr(svrConn), + startDirectory: "/", + openRequests: make(map[string]*Request), } @@ -207,14 +219,23 @@ func (rs *RequestServer) packetWorker(ctx context.Context, pktChan chan orderedR rpkt = statusFromError(pkt.ID, rs.closeRequest(handle)) case *sshFxpRealpathPacket: var realPath string - if realPather, ok := rs.Handlers.FileList.(RealPathFileLister); ok { - realPath = realPather.RealPath(pkt.getPath()) + var err error + + switch pather := rs.Handlers.FileList.(type) { + case RealPathFileLister: + realPath, err = pather.RealPath(pkt.getPath()) + case legacyRealPathFileLister: + realPath = pather.RealPath(pkt.getPath()) + default: + realPath = cleanPathWithBase(rs.startDirectory, pkt.getPath()) + } + if err != nil { + rpkt = statusFromError(pkt.ID, err) } else { - realPath = cleanPath(pkt.getPath()) + rpkt = cleanPacketPath(pkt, realPath) } - rpkt = cleanPacketPath(pkt, realPath) case *sshFxpOpendirPacket: - request := requestFromPacket(ctx, pkt) + request := requestFromPacket(ctx, pkt, rs.startDirectory) handle := rs.nextRequest(request) rpkt = request.opendir(rs.Handlers, pkt) if _, ok := rpkt.(*sshFxpHandlePacket); !ok { @@ -222,7 +243,7 @@ func (rs *RequestServer) packetWorker(ctx context.Context, pktChan chan orderedR rs.closeRequest(handle) } case *sshFxpOpenPacket: - request := requestFromPacket(ctx, pkt) + request := requestFromPacket(ctx, pkt, rs.startDirectory) handle := rs.nextRequest(request) rpkt = request.open(rs.Handlers, pkt) if _, ok := rpkt.(*sshFxpHandlePacket); !ok { @@ -235,7 +256,10 @@ func (rs *RequestServer) packetWorker(ctx context.Context, pktChan chan orderedR if !ok { rpkt = statusFromError(pkt.ID, EBADF) } else { - request = NewRequest("Stat", request.Filepath) + request = &Request{ + Method: "Stat", + Filepath: cleanPathWithBase(rs.startDirectory, request.Filepath), + } rpkt = request.call(rs.Handlers, pkt, rs.pktMgr.alloc, orderID) } case *sshFxpFsetstatPacket: @@ -244,15 +268,24 @@ func (rs *RequestServer) packetWorker(ctx context.Context, pktChan chan orderedR if !ok { rpkt = statusFromError(pkt.ID, EBADF) } else { - request = NewRequest("Setstat", request.Filepath) + request = &Request{ + Method: "Setstat", + Filepath: cleanPathWithBase(rs.startDirectory, request.Filepath), + } rpkt = request.call(rs.Handlers, pkt, rs.pktMgr.alloc, orderID) } case *sshFxpExtendedPacketPosixRename: - request := NewRequest("PosixRename", pkt.Oldpath) - request.Target = pkt.Newpath + request := &Request{ + Method: "PosixRename", + Filepath: cleanPathWithBase(rs.startDirectory, pkt.Oldpath), + Target: cleanPathWithBase(rs.startDirectory, pkt.Newpath), + } rpkt = request.call(rs.Handlers, pkt, rs.pktMgr.alloc, orderID) case *sshFxpExtendedPacketStatVFS: - request := NewRequest("StatVFS", pkt.Path) + request := &Request{ + Method: "StatVFS", + Filepath: cleanPathWithBase(rs.startDirectory, pkt.Path), + } rpkt = request.call(rs.Handlers, pkt, rs.pktMgr.alloc, orderID) case hasHandle: handle := pkt.getHandle() @@ -263,7 +296,7 @@ func (rs *RequestServer) packetWorker(ctx context.Context, pktChan chan orderedR rpkt = request.call(rs.Handlers, pkt, rs.pktMgr.alloc, orderID) } case hasPath: - request := requestFromPacket(ctx, pkt) + request := requestFromPacket(ctx, pkt, rs.startDirectory) rpkt = request.call(rs.Handlers, pkt, rs.pktMgr.alloc, orderID) request.close() default: diff --git a/vendor/github.com/pkg/sftp/request-unix.go b/vendor/github.com/pkg/sftp/request-unix.go index 50b08a38..e3e037d6 100644 --- a/vendor/github.com/pkg/sftp/request-unix.go +++ b/vendor/github.com/pkg/sftp/request-unix.go @@ -1,3 +1,4 @@ +//go:build !windows && !plan9 // +build !windows,!plan9 package sftp @@ -21,7 +22,3 @@ func testOsSys(sys interface{}) error { } return nil } - -func toLocalPath(p string) string { - return p -} diff --git a/vendor/github.com/pkg/sftp/request.go b/vendor/github.com/pkg/sftp/request.go index c6da4b60..57d788df 100644 --- a/vendor/github.com/pkg/sftp/request.go +++ b/vendor/github.com/pkg/sftp/request.go @@ -168,9 +168,11 @@ func (r *Request) copy() *Request { } // New Request initialized based on packet data -func requestFromPacket(ctx context.Context, pkt hasPath) *Request { - method := requestMethod(pkt) - request := NewRequest(method, pkt.getPath()) +func requestFromPacket(ctx context.Context, pkt hasPath, baseDir string) *Request { + request := &Request{ + Method: requestMethod(pkt), + Filepath: cleanPathWithBase(baseDir, pkt.getPath()), + } request.ctx, request.cancelCtx = context.WithCancel(ctx) switch p := pkt.(type) { @@ -180,13 +182,14 @@ func requestFromPacket(ctx context.Context, pkt hasPath) *Request { request.Flags = p.Flags request.Attrs = p.Attrs.([]byte) case *sshFxpRenamePacket: - request.Target = cleanPath(p.Newpath) + request.Target = cleanPathWithBase(baseDir, p.Newpath) case *sshFxpSymlinkPacket: // NOTE: given a POSIX compliant signature: symlink(target, linkpath string) // this makes Request.Target the linkpath, and Request.Filepath the target. - request.Target = cleanPath(p.Linkpath) + request.Target = cleanPathWithBase(baseDir, p.Linkpath) + request.Filepath = p.Targetpath case *sshFxpExtendedPacketHardlink: - request.Target = cleanPath(p.Newpath) + request.Target = cleanPathWithBase(baseDir, p.Newpath) } return request } @@ -292,7 +295,12 @@ func (r *Request) call(handlers Handlers, pkt requestPacket, alloc *allocator, o return filecmd(handlers.FileCmd, r, pkt) case "List": return filelist(handlers.FileList, r, pkt) - case "Stat", "Lstat", "Readlink": + case "Stat", "Lstat": + return filestat(handlers.FileList, r, pkt) + case "Readlink": + if readlinkFileLister, ok := handlers.FileList.(ReadlinkFileLister); ok { + return readlink(readlinkFileLister, r, pkt) + } return filestat(handlers.FileList, r, pkt) default: return statusFromError(pkt.id(), fmt.Errorf("unexpected method: %s", r.Method)) @@ -596,6 +604,23 @@ func filestat(h FileLister, r *Request, pkt requestPacket) responsePacket { } } +func readlink(readlinkFileLister ReadlinkFileLister, r *Request, pkt requestPacket) responsePacket { + resolved, err := readlinkFileLister.Readlink(r.Filepath) + if err != nil { + return statusFromError(pkt.id(), err) + } + return &sshFxpNamePacket{ + ID: pkt.id(), + NameAttrs: []*sshFxpNameAttr{ + { + Name: resolved, + LongName: resolved, + Attrs: emptyFileStat, + }, + }, + } +} + // init attributes of request object from packet data func requestMethod(p requestPacket) (method string) { switch p.(type) { diff --git a/vendor/github.com/pkg/sftp/request_windows.go b/vendor/github.com/pkg/sftp/request_windows.go index 1f6d3df1..bd1d6864 100644 --- a/vendor/github.com/pkg/sftp/request_windows.go +++ b/vendor/github.com/pkg/sftp/request_windows.go @@ -1,8 +1,6 @@ package sftp import ( - "path" - "path/filepath" "syscall" ) @@ -13,32 +11,3 @@ func fakeFileInfoSys() interface{} { func testOsSys(sys interface{}) error { return nil } - -func toLocalPath(p string) string { - lp := filepath.FromSlash(p) - - if path.IsAbs(p) { - tmp := lp - for len(tmp) > 0 && tmp[0] == '\\' { - tmp = tmp[1:] - } - - if filepath.IsAbs(tmp) { - // If the FromSlash without any starting slashes is absolute, - // then we have a filepath encoded with a prefix '/'. - // e.g. "/C:/Windows" to "C:\\Windows" - return tmp - } - - tmp += "\\" - - if filepath.IsAbs(tmp) { - // If the FromSlash without any starting slashes but with extra end slash is absolute, - // then we have a filepath encoded with a prefix '/' and a dropped '/' at the end. - // e.g. "/C:" to "C:\\" - return tmp - } - } - - return lp -} diff --git a/vendor/github.com/pkg/sftp/server.go b/vendor/github.com/pkg/sftp/server.go index 529052b4..2e419f59 100644 --- a/vendor/github.com/pkg/sftp/server.go +++ b/vendor/github.com/pkg/sftp/server.go @@ -24,7 +24,7 @@ const ( // Server is an SSH File Transfer Protocol (sftp) server. // This is intended to provide the sftp subsystem to an ssh server daemon. // This implementation currently supports most of sftp server protocol version 3, -// as specified at http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02 +// as specified at https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt type Server struct { *serverConn debugStream io.Writer @@ -33,6 +33,7 @@ type Server struct { openFiles map[string]*os.File openFilesLock sync.RWMutex handleCount int + workDir string } func (svr *Server) nextHandle(f *os.File) string { @@ -128,6 +129,16 @@ func WithAllocator() ServerOption { } } +// WithServerWorkingDirectory sets a working directory to use as base +// for relative paths. +// If unset the default is current working directory (os.Getwd). +func WithServerWorkingDirectory(workDir string) ServerOption { + return func(s *Server) error { + s.workDir = cleanPath(workDir) + return nil + } +} + type rxPacket struct { pktType fxp pktBytes []byte @@ -174,7 +185,7 @@ func handlePacket(s *Server, p orderedRequest) error { } case *sshFxpStatPacket: // stat the requested file - info, err := os.Stat(toLocalPath(p.Path)) + info, err := os.Stat(s.toLocalPath(p.Path)) rpkt = &sshFxpStatResponse{ ID: p.ID, info: info, @@ -184,7 +195,7 @@ func handlePacket(s *Server, p orderedRequest) error { } case *sshFxpLstatPacket: // stat the requested file - info, err := os.Lstat(toLocalPath(p.Path)) + info, err := os.Lstat(s.toLocalPath(p.Path)) rpkt = &sshFxpStatResponse{ ID: p.ID, info: info, @@ -208,24 +219,24 @@ func handlePacket(s *Server, p orderedRequest) error { } case *sshFxpMkdirPacket: // TODO FIXME: ignore flags field - err := os.Mkdir(toLocalPath(p.Path), 0755) + err := os.Mkdir(s.toLocalPath(p.Path), 0o755) rpkt = statusFromError(p.ID, err) case *sshFxpRmdirPacket: - err := os.Remove(toLocalPath(p.Path)) + err := os.Remove(s.toLocalPath(p.Path)) rpkt = statusFromError(p.ID, err) case *sshFxpRemovePacket: - err := os.Remove(toLocalPath(p.Filename)) + err := os.Remove(s.toLocalPath(p.Filename)) rpkt = statusFromError(p.ID, err) case *sshFxpRenamePacket: - err := os.Rename(toLocalPath(p.Oldpath), toLocalPath(p.Newpath)) + err := os.Rename(s.toLocalPath(p.Oldpath), s.toLocalPath(p.Newpath)) rpkt = statusFromError(p.ID, err) case *sshFxpSymlinkPacket: - err := os.Symlink(toLocalPath(p.Targetpath), toLocalPath(p.Linkpath)) + err := os.Symlink(s.toLocalPath(p.Targetpath), s.toLocalPath(p.Linkpath)) rpkt = statusFromError(p.ID, err) case *sshFxpClosePacket: rpkt = statusFromError(p.ID, s.closeHandle(p.Handle)) case *sshFxpReadlinkPacket: - f, err := os.Readlink(toLocalPath(p.Path)) + f, err := os.Readlink(s.toLocalPath(p.Path)) rpkt = &sshFxpNamePacket{ ID: p.ID, NameAttrs: []*sshFxpNameAttr{ @@ -240,7 +251,7 @@ func handlePacket(s *Server, p orderedRequest) error { rpkt = statusFromError(p.ID, err) } case *sshFxpRealpathPacket: - f, err := filepath.Abs(toLocalPath(p.Path)) + f, err := filepath.Abs(s.toLocalPath(p.Path)) f = cleanPath(f) rpkt = &sshFxpNamePacket{ ID: p.ID, @@ -256,13 +267,14 @@ func handlePacket(s *Server, p orderedRequest) error { rpkt = statusFromError(p.ID, err) } case *sshFxpOpendirPacket: - p.Path = toLocalPath(p.Path) + lp := s.toLocalPath(p.Path) - if stat, err := os.Stat(p.Path); err != nil { + if stat, err := os.Stat(lp); err != nil { rpkt = statusFromError(p.ID, err) } else if !stat.IsDir() { rpkt = statusFromError(p.ID, &os.PathError{ - Path: p.Path, Err: syscall.ENOTDIR}) + Path: lp, Err: syscall.ENOTDIR, + }) } else { rpkt = (&sshFxpOpenPacket{ ID: p.ID, @@ -315,7 +327,7 @@ func handlePacket(s *Server, p orderedRequest) error { } // Serve serves SFTP connections until the streams stop or the SFTP subsystem -// is stopped. +// is stopped. It returns nil if the server exits cleanly. func (svr *Server) Serve() error { defer func() { if svr.pktMgr.alloc != nil { @@ -341,6 +353,10 @@ func (svr *Server) Serve() error { for { pktType, pktBytes, err = svr.serverConn.recvPacket(svr.pktMgr.getNextOrderID()) if err != nil { + // Check whether the connection terminated cleanly in-between packets. + if err == io.EOF { + err = nil + } // we don't care about releasing allocated pages here, the server will quit and the allocator freed break } @@ -446,7 +462,7 @@ func (p *sshFxpOpenPacket) respond(svr *Server) responsePacket { osFlags |= os.O_EXCL } - f, err := os.OpenFile(toLocalPath(p.Path), osFlags, 0644) + f, err := os.OpenFile(svr.toLocalPath(p.Path), osFlags, 0o644) if err != nil { return statusFromError(p.ID, err) } @@ -484,7 +500,7 @@ func (p *sshFxpSetstatPacket) respond(svr *Server) responsePacket { b := p.Attrs.([]byte) var err error - p.Path = toLocalPath(p.Path) + p.Path = svr.toLocalPath(p.Path) debug("setstat name \"%s\"", p.Path) if (p.Flags & sshFileXferAttrSize) != 0 { @@ -603,13 +619,15 @@ func statusFromError(id uint32, err error) *sshFxpStatusPacket { return ret } - switch e := err.(type) { - case fxerr: + if errors.Is(err, io.EOF) { + ret.StatusError.Code = sshFxEOF + return ret + } + + var e fxerr + if errors.As(err, &e) { ret.StatusError.Code = uint32(e) - default: - if e == io.EOF { - ret.StatusError.Code = sshFxEOF - } + return ret } return ret diff --git a/vendor/github.com/pkg/sftp/server_plan9.go b/vendor/github.com/pkg/sftp/server_plan9.go new file mode 100644 index 00000000..4e8ed067 --- /dev/null +++ b/vendor/github.com/pkg/sftp/server_plan9.go @@ -0,0 +1,27 @@ +package sftp + +import ( + "path" + "path/filepath" +) + +func (s *Server) toLocalPath(p string) string { + if s.workDir != "" && !path.IsAbs(p) { + p = path.Join(s.workDir, p) + } + + lp := filepath.FromSlash(p) + + if path.IsAbs(p) { + tmp := lp[1:] + + if filepath.IsAbs(tmp) { + // If the FromSlash without any starting slashes is absolute, + // then we have a filepath encoded with a prefix '/'. + // e.g. "/#s/boot" to "#s/boot" + return tmp + } + } + + return lp +} diff --git a/vendor/github.com/pkg/sftp/server_statvfs_impl.go b/vendor/github.com/pkg/sftp/server_statvfs_impl.go index 94b6d832..a5470798 100644 --- a/vendor/github.com/pkg/sftp/server_statvfs_impl.go +++ b/vendor/github.com/pkg/sftp/server_statvfs_impl.go @@ -1,3 +1,4 @@ +//go:build darwin || linux // +build darwin linux // fill in statvfs structure with OS specific values diff --git a/vendor/github.com/pkg/sftp/server_statvfs_linux.go b/vendor/github.com/pkg/sftp/server_statvfs_linux.go index 1d180d47..615c4157 100644 --- a/vendor/github.com/pkg/sftp/server_statvfs_linux.go +++ b/vendor/github.com/pkg/sftp/server_statvfs_linux.go @@ -1,3 +1,4 @@ +//go:build linux // +build linux package sftp diff --git a/vendor/github.com/pkg/sftp/server_statvfs_stubs.go b/vendor/github.com/pkg/sftp/server_statvfs_stubs.go index fbf49068..dd4705bb 100644 --- a/vendor/github.com/pkg/sftp/server_statvfs_stubs.go +++ b/vendor/github.com/pkg/sftp/server_statvfs_stubs.go @@ -1,3 +1,4 @@ +//go:build !darwin && !linux && !plan9 // +build !darwin,!linux,!plan9 package sftp diff --git a/vendor/github.com/pkg/sftp/server_unix.go b/vendor/github.com/pkg/sftp/server_unix.go new file mode 100644 index 00000000..495b397c --- /dev/null +++ b/vendor/github.com/pkg/sftp/server_unix.go @@ -0,0 +1,16 @@ +//go:build !windows && !plan9 +// +build !windows,!plan9 + +package sftp + +import ( + "path" +) + +func (s *Server) toLocalPath(p string) string { + if s.workDir != "" && !path.IsAbs(p) { + p = path.Join(s.workDir, p) + } + + return p +} diff --git a/vendor/github.com/pkg/sftp/server_windows.go b/vendor/github.com/pkg/sftp/server_windows.go new file mode 100644 index 00000000..b35be730 --- /dev/null +++ b/vendor/github.com/pkg/sftp/server_windows.go @@ -0,0 +1,39 @@ +package sftp + +import ( + "path" + "path/filepath" +) + +func (s *Server) toLocalPath(p string) string { + if s.workDir != "" && !path.IsAbs(p) { + p = path.Join(s.workDir, p) + } + + lp := filepath.FromSlash(p) + + if path.IsAbs(p) { + tmp := lp + for len(tmp) > 0 && tmp[0] == '\\' { + tmp = tmp[1:] + } + + if filepath.IsAbs(tmp) { + // If the FromSlash without any starting slashes is absolute, + // then we have a filepath encoded with a prefix '/'. + // e.g. "/C:/Windows" to "C:\\Windows" + return tmp + } + + tmp += "\\" + + if filepath.IsAbs(tmp) { + // If the FromSlash without any starting slashes but with extra end slash is absolute, + // then we have a filepath encoded with a prefix '/' and a dropped '/' at the end. + // e.g. "/C:" to "C:\\" + return tmp + } + } + + return lp +} diff --git a/vendor/github.com/pkg/sftp/sftp.go b/vendor/github.com/pkg/sftp/sftp.go index 9a63c39d..778c8f3d 100644 --- a/vendor/github.com/pkg/sftp/sftp.go +++ b/vendor/github.com/pkg/sftp/sftp.go @@ -1,5 +1,5 @@ // Package sftp implements the SSH File Transfer Protocol as described in -// https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02 +// https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt package sftp import ( diff --git a/vendor/github.com/pkg/sftp/stat_posix.go b/vendor/github.com/pkg/sftp/stat_posix.go index 92f76e2f..5b870e23 100644 --- a/vendor/github.com/pkg/sftp/stat_posix.go +++ b/vendor/github.com/pkg/sftp/stat_posix.go @@ -1,3 +1,4 @@ +//go:build !plan9 // +build !plan9 package sftp @@ -23,7 +24,7 @@ func translateErrno(errno syscall.Errno) uint32 { return sshFxOk case syscall.ENOENT: return sshFxNoSuchFile - case syscall.EPERM: + case syscall.EACCES, syscall.EPERM: return sshFxPermissionDenied } diff --git a/vendor/github.com/pkg/sftp/syscall_fixed.go b/vendor/github.com/pkg/sftp/syscall_fixed.go index d4045777..e8443083 100644 --- a/vendor/github.com/pkg/sftp/syscall_fixed.go +++ b/vendor/github.com/pkg/sftp/syscall_fixed.go @@ -1,3 +1,4 @@ +//go:build plan9 || windows || (js && wasm) // +build plan9 windows js,wasm // Go defines S_IFMT on windows, plan9 and js/wasm as 0x1f000 instead of diff --git a/vendor/github.com/pkg/sftp/syscall_good.go b/vendor/github.com/pkg/sftp/syscall_good.go index 4c2b240c..50052189 100644 --- a/vendor/github.com/pkg/sftp/syscall_good.go +++ b/vendor/github.com/pkg/sftp/syscall_good.go @@ -1,4 +1,6 @@ -// +build !plan9,!windows +//go:build !plan9 && !windows && (!js || !wasm) +// +build !plan9 +// +build !windows // +build !js !wasm package sftp diff --git a/vendor/github.com/rasky/go-xdr/LICENSE b/vendor/github.com/rasky/go-xdr/LICENSE new file mode 100644 index 00000000..0cc3543c --- /dev/null +++ b/vendor/github.com/rasky/go-xdr/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2012-2014 Dave Collins + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. \ No newline at end of file diff --git a/vendor/github.com/rasky/go-xdr/xdr2/decode.go b/vendor/github.com/rasky/go-xdr/xdr2/decode.go new file mode 100644 index 00000000..6f37f76b --- /dev/null +++ b/vendor/github.com/rasky/go-xdr/xdr2/decode.go @@ -0,0 +1,918 @@ +/* + * Copyright (c) 2012-2014 Dave Collins + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package xdr + +import ( + "fmt" + "io" + "math" + "reflect" + "strconv" + "time" +) + +var ( + errMaxSlice = "data exceeds max slice limit" + errIODecode = "%s while decoding %d bytes" +) + +/* +Unmarshal parses XDR-encoded data into the value pointed to by v reading from +reader r and returning the total number of bytes read. An addressable pointer +must be provided since Unmarshal needs to both store the result of the decode as +well as obtain target type information. Unmarhsal traverses v recursively and +automatically indirects pointers through arbitrary depth, allocating them as +necessary, to decode the data into the underlying value pointed to. + +Unmarshal uses reflection to determine the type of the concrete value contained +by v and performs a mapping of underlying XDR types to Go types as follows: + + Go Type <- XDR Type + -------------------- + int8, int16, int32, int <- XDR Integer + uint8, uint16, uint32, uint <- XDR Unsigned Integer + int64 <- XDR Hyper Integer + uint64 <- XDR Unsigned Hyper Integer + bool <- XDR Boolean + float32 <- XDR Floating-Point + float64 <- XDR Double-Precision Floating-Point + string <- XDR String + byte <- XDR Integer + []byte <- XDR Variable-Length Opaque Data + [#]byte <- XDR Fixed-Length Opaque Data + [] <- XDR Variable-Length Array + [#] <- XDR Fixed-Length Array + struct <- XDR Structure + map <- XDR Variable-Length Array of two-element XDR Structures + time.Time <- XDR String encoded with RFC3339 nanosecond precision + +Notes and Limitations: + + * Automatic unmarshalling of variable and fixed-length arrays of uint8s + requires a special struct tag `xdropaque:"false"` since byte slices + and byte arrays are assumed to be opaque data and byte is a Go alias + for uint8 thus indistinguishable under reflection + * Cyclic data structures are not supported and will result in infinite + loops + +If any issues are encountered during the unmarshalling process, an +UnmarshalError is returned with a human readable description as well as +an ErrorCode value for further inspection from sophisticated callers. Some +potential issues are unsupported Go types, attempting to decode a value which is +too large to fit into a specified Go type, and exceeding max slice limitations. +*/ +func Unmarshal(r io.Reader, v interface{}) (int, error) { + d := Decoder{r: r} + return d.Decode(v) +} + +// UnmarshalLimited is identical to Unmarshal but it sets maxReadSize in order +// to cap reads. +func UnmarshalLimited(r io.Reader, v interface{}, maxSize uint) (int, error) { + d := Decoder{r: r, maxReadSize: maxSize} + return d.Decode(v) +} + +// A Decoder wraps an io.Reader that is expected to provide an XDR-encoded byte +// stream and provides several exposed methods to manually decode various XDR +// primitives without relying on reflection. The NewDecoder function can be +// used to get a new Decoder directly. +// +// Typically, Unmarshal should be used instead of manual decoding. A Decoder +// is exposed so it is possible to perform manual decoding should it be +// necessary in complex scenarios where automatic reflection-based decoding +// won't work. +type Decoder struct { + r io.Reader + + // maxReadSize is the default maximum bytes an element can contain. 0 + // is unlimited and provides backwards compatability. Setting it to a + // non-zero value caps reads. + maxReadSize uint +} + +// DecodeInt treats the next 4 bytes as an XDR encoded integer and returns the +// result as an int32 along with the number of bytes actually read. +// +// An UnmarshalError is returned if there are insufficient bytes remaining. +// +// Reference: +// RFC Section 4.1 - Integer +// 32-bit big-endian signed integer in range [-2147483648, 2147483647] +func (d *Decoder) DecodeInt() (int32, int, error) { + var buf [4]byte + n, err := io.ReadFull(d.r, buf[:]) + if err != nil { + msg := fmt.Sprintf(errIODecode, err.Error(), 4) + err := unmarshalError("DecodeInt", ErrIO, msg, buf[:n], err) + return 0, n, err + } + + rv := int32(buf[3]) | int32(buf[2])<<8 | + int32(buf[1])<<16 | int32(buf[0])<<24 + return rv, n, nil +} + +// DecodeUint treats the next 4 bytes as an XDR encoded unsigned integer and +// returns the result as a uint32 along with the number of bytes actually read. +// +// An UnmarshalError is returned if there are insufficient bytes remaining. +// +// Reference: +// RFC Section 4.2 - Unsigned Integer +// 32-bit big-endian unsigned integer in range [0, 4294967295] +func (d *Decoder) DecodeUint() (uint32, int, error) { + var buf [4]byte + n, err := io.ReadFull(d.r, buf[:]) + if err != nil { + msg := fmt.Sprintf(errIODecode, err.Error(), 4) + err := unmarshalError("DecodeUint", ErrIO, msg, buf[:n], err) + return 0, n, err + } + + rv := uint32(buf[3]) | uint32(buf[2])<<8 | + uint32(buf[1])<<16 | uint32(buf[0])<<24 + return rv, n, nil +} + +// DecodeEnum treats the next 4 bytes as an XDR encoded enumeration value and +// returns the result as an int32 after verifying that the value is in the +// provided map of valid values. It also returns the number of bytes actually +// read. +// +// An UnmarshalError is returned if there are insufficient bytes remaining or +// the parsed enumeration value is not one of the provided valid values. +// +// Reference: +// RFC Section 4.3 - Enumeration +// Represented as an XDR encoded signed integer +func (d *Decoder) DecodeEnum(validEnums map[int32]bool) (int32, int, error) { + val, n, err := d.DecodeInt() + if err != nil { + return 0, n, err + } + + if !validEnums[val] { + err := unmarshalError("DecodeEnum", ErrBadEnumValue, + "invalid enum", val, nil) + return 0, n, err + } + return val, n, nil +} + +// DecodeBool treats the next 4 bytes as an XDR encoded boolean value and +// returns the result as a bool along with the number of bytes actually read. +// +// An UnmarshalError is returned if there are insufficient bytes remaining or +// the parsed value is not a 0 or 1. +// +// Reference: +// RFC Section 4.4 - Boolean +// Represented as an XDR encoded enumeration where 0 is false and 1 is true +func (d *Decoder) DecodeBool() (bool, int, error) { + val, n, err := d.DecodeInt() + if err != nil { + return false, n, err + } + switch val { + case 0: + return false, n, nil + case 1: + return true, n, nil + } + + err = unmarshalError("DecodeBool", ErrBadEnumValue, "bool not 0 or 1", + val, nil) + return false, n, err +} + +// DecodeHyper treats the next 8 bytes as an XDR encoded hyper value and +// returns the result as an int64 along with the number of bytes actually read. +// +// An UnmarshalError is returned if there are insufficient bytes remaining. +// +// Reference: +// RFC Section 4.5 - Hyper Integer +// 64-bit big-endian signed integer in range [-9223372036854775808, 9223372036854775807] +func (d *Decoder) DecodeHyper() (int64, int, error) { + var buf [8]byte + n, err := io.ReadFull(d.r, buf[:]) + if err != nil { + msg := fmt.Sprintf(errIODecode, err.Error(), 8) + err := unmarshalError("DecodeHyper", ErrIO, msg, buf[:n], err) + return 0, n, err + } + + rv := int64(buf[7]) | int64(buf[6])<<8 | + int64(buf[5])<<16 | int64(buf[4])<<24 | + int64(buf[3])<<32 | int64(buf[2])<<40 | + int64(buf[1])<<48 | int64(buf[0])<<56 + return rv, n, err +} + +// DecodeUhyper treats the next 8 bytes as an XDR encoded unsigned hyper value +// and returns the result as a uint64 along with the number of bytes actually +// read. +// +// An UnmarshalError is returned if there are insufficient bytes remaining. +// +// Reference: +// RFC Section 4.5 - Unsigned Hyper Integer +// 64-bit big-endian unsigned integer in range [0, 18446744073709551615] +func (d *Decoder) DecodeUhyper() (uint64, int, error) { + var buf [8]byte + n, err := io.ReadFull(d.r, buf[:]) + if err != nil { + msg := fmt.Sprintf(errIODecode, err.Error(), 8) + err := unmarshalError("DecodeUhyper", ErrIO, msg, buf[:n], err) + return 0, n, err + } + + rv := uint64(buf[7]) | uint64(buf[6])<<8 | + uint64(buf[5])<<16 | uint64(buf[4])<<24 | + uint64(buf[3])<<32 | uint64(buf[2])<<40 | + uint64(buf[1])<<48 | uint64(buf[0])<<56 + return rv, n, nil +} + +// DecodeFloat treats the next 4 bytes as an XDR encoded floating point and +// returns the result as a float32 along with the number of bytes actually read. +// +// An UnmarshalError is returned if there are insufficient bytes remaining. +// +// Reference: +// RFC Section 4.6 - Floating Point +// 32-bit single-precision IEEE 754 floating point +func (d *Decoder) DecodeFloat() (float32, int, error) { + var buf [4]byte + n, err := io.ReadFull(d.r, buf[:]) + if err != nil { + msg := fmt.Sprintf(errIODecode, err.Error(), 4) + err := unmarshalError("DecodeFloat", ErrIO, msg, buf[:n], err) + return 0, n, err + } + + val := uint32(buf[3]) | uint32(buf[2])<<8 | + uint32(buf[1])<<16 | uint32(buf[0])<<24 + return math.Float32frombits(val), n, nil +} + +// DecodeDouble treats the next 8 bytes as an XDR encoded double-precision +// floating point and returns the result as a float64 along with the number of +// bytes actually read. +// +// An UnmarshalError is returned if there are insufficient bytes remaining. +// +// Reference: +// RFC Section 4.7 - Double-Precision Floating Point +// 64-bit double-precision IEEE 754 floating point +func (d *Decoder) DecodeDouble() (float64, int, error) { + var buf [8]byte + n, err := io.ReadFull(d.r, buf[:]) + if err != nil { + msg := fmt.Sprintf(errIODecode, err.Error(), 8) + err := unmarshalError("DecodeDouble", ErrIO, msg, buf[:n], err) + return 0, n, err + } + + val := uint64(buf[7]) | uint64(buf[6])<<8 | + uint64(buf[5])<<16 | uint64(buf[4])<<24 | + uint64(buf[3])<<32 | uint64(buf[2])<<40 | + uint64(buf[1])<<48 | uint64(buf[0])<<56 + return math.Float64frombits(val), n, nil +} + +// RFC Section 4.8 - Quadruple-Precision Floating Point +// 128-bit quadruple-precision floating point +// Not Implemented + +// DecodeFixedOpaque treats the next 'size' bytes as XDR encoded opaque data and +// returns the result as a byte slice along with the number of bytes actually +// read. +// +// An UnmarshalError is returned if there are insufficient bytes remaining to +// satisfy the passed size, including the necessary padding to make it a +// multiple of 4. +// +// Reference: +// RFC Section 4.9 - Fixed-Length Opaque Data +// Fixed-length uninterpreted data zero-padded to a multiple of four +func (d *Decoder) DecodeFixedOpaque(size int32) ([]byte, int, error) { + // Nothing to do if size is 0. + if size == 0 { + return nil, 0, nil + } + + pad := (4 - (size % 4)) % 4 + paddedSize := size + pad + if uint(paddedSize) > uint(math.MaxInt32) { + err := unmarshalError("DecodeFixedOpaque", ErrOverflow, + errMaxSlice, paddedSize, nil) + return nil, 0, err + } + + buf := make([]byte, paddedSize) + n, err := io.ReadFull(d.r, buf) + if err != nil { + msg := fmt.Sprintf(errIODecode, err.Error(), paddedSize) + err := unmarshalError("DecodeFixedOpaque", ErrIO, msg, buf[:n], + err) + return nil, n, err + } + return buf[0:size], n, nil +} + +// DecodeOpaque treats the next bytes as variable length XDR encoded opaque +// data and returns the result as a byte slice along with the number of bytes +// actually read. +// +// An UnmarshalError is returned if there are insufficient bytes remaining or +// the opaque data is larger than the max length of a Go slice. +// +// Reference: +// RFC Section 4.10 - Variable-Length Opaque Data +// Unsigned integer length followed by fixed opaque data of that length +func (d *Decoder) DecodeOpaque() ([]byte, int, error) { + dataLen, n, err := d.DecodeUint() + if err != nil { + return nil, n, err + } + if uint(dataLen) > uint(math.MaxInt32) || + (d.maxReadSize != 0 && uint(dataLen) > d.maxReadSize) { + err := unmarshalError("DecodeOpaque", ErrOverflow, errMaxSlice, + dataLen, nil) + return nil, n, err + } + + rv, n2, err := d.DecodeFixedOpaque(int32(dataLen)) + n += n2 + if err != nil { + return nil, n, err + } + return rv, n, nil +} + +// DecodeString treats the next bytes as a variable length XDR encoded string +// and returns the result as a string along with the number of bytes actually +// read. Character encoding is assumed to be UTF-8 and therefore ASCII +// compatible. If the underlying character encoding is not compatibile with +// this assumption, the data can instead be read as variable-length opaque data +// (DecodeOpaque) and manually converted as needed. +// +// An UnmarshalError is returned if there are insufficient bytes remaining or +// the string data is larger than the max length of a Go slice. +// +// Reference: +// RFC Section 4.11 - String +// Unsigned integer length followed by bytes zero-padded to a multiple of +// four +func (d *Decoder) DecodeString() (string, int, error) { + dataLen, n, err := d.DecodeUint() + if err != nil { + return "", n, err + } + if uint(dataLen) > uint(math.MaxInt32) || + (d.maxReadSize != 0 && uint(dataLen) > d.maxReadSize) { + err = unmarshalError("DecodeString", ErrOverflow, errMaxSlice, + dataLen, nil) + return "", n, err + } + + opaque, n2, err := d.DecodeFixedOpaque(int32(dataLen)) + n += n2 + if err != nil { + return "", n, err + } + return string(opaque), n, nil +} + +// decodeFixedArray treats the next bytes as a series of XDR encoded elements +// of the same type as the array represented by the reflection value and decodes +// each element into the passed array. The ignoreOpaque flag controls whether +// or not uint8 (byte) elements should be decoded individually or as a fixed +// sequence of opaque data. It returns the the number of bytes actually read. +// +// An UnmarshalError is returned if any issues are encountered while decoding +// the array elements. +// +// Reference: +// RFC Section 4.12 - Fixed-Length Array +// Individually XDR encoded array elements +func (d *Decoder) decodeFixedArray(v reflect.Value, ignoreOpaque bool) (int, error) { + // Treat [#]byte (byte is alias for uint8) as opaque data unless + // ignored. + if !ignoreOpaque && v.Type().Elem().Kind() == reflect.Uint8 { + data, n, err := d.DecodeFixedOpaque(int32(v.Len())) + if err != nil { + return n, err + } + reflect.Copy(v, reflect.ValueOf(data)) + return n, nil + } + + // Decode each array element. + var n int + for i := 0; i < v.Len(); i++ { + n2, err := d.decode(v.Index(i)) + n += n2 + if err != nil { + return n, err + } + } + return n, nil +} + +// decodeArray treats the next bytes as a variable length series of XDR encoded +// elements of the same type as the array represented by the reflection value. +// The number of elements is obtained by first decoding the unsigned integer +// element count. Then each element is decoded into the passed array. The +// ignoreOpaque flag controls whether or not uint8 (byte) elements should be +// decoded individually or as a variable sequence of opaque data. It returns +// the number of bytes actually read. +// +// An UnmarshalError is returned if any issues are encountered while decoding +// the array elements. +// +// Reference: +// RFC Section 4.13 - Variable-Length Array +// Unsigned integer length followed by individually XDR encoded array +// elements +func (d *Decoder) decodeArray(v reflect.Value, ignoreOpaque bool) (int, error) { + dataLen, n, err := d.DecodeUint() + if err != nil { + return n, err + } + if uint(dataLen) > uint(math.MaxInt32) || + (d.maxReadSize != 0 && uint(dataLen) > d.maxReadSize) { + err := unmarshalError("decodeArray", ErrOverflow, errMaxSlice, + dataLen, nil) + return n, err + } + + // Allocate storage for the slice elements (the underlying array) if + // existing slice does not have enough capacity. + sliceLen := int(dataLen) + if v.Cap() < sliceLen { + v.Set(reflect.MakeSlice(v.Type(), sliceLen, sliceLen)) + } + if v.Len() < sliceLen { + v.SetLen(sliceLen) + } + + // Treat []byte (byte is alias for uint8) as opaque data unless ignored. + if !ignoreOpaque && v.Type().Elem().Kind() == reflect.Uint8 { + data, n2, err := d.DecodeFixedOpaque(int32(sliceLen)) + n += n2 + if err != nil { + return n, err + } + v.SetBytes(data) + return n, nil + } + + // Decode each slice element. + for i := 0; i < sliceLen; i++ { + n2, err := d.decode(v.Index(i)) + n += n2 + if err != nil { + return n, err + } + } + return n, nil +} + +// decodeStruct treats the next bytes as a series of XDR encoded elements +// of the same type as the exported fields of the struct represented by the +// passed reflection value. Pointers are automatically indirected and +// allocated as necessary. It returns the the number of bytes actually read. +// +// An UnmarshalError is returned if any issues are encountered while decoding +// the elements. +// +// Reference: +// RFC Section 4.14 - Structure +// XDR encoded elements in the order of their declaration in the struct +func (d *Decoder) decodeStruct(v reflect.Value) (int, error) { + var n int + var union string + vt := v.Type() + for i := 0; i < v.NumField(); i++ { + // Skip unexported fields. + vtf := vt.Field(i) + if vtf.PkgPath != "" { + continue + } + + vf := v.Field(i) + tag := parseTag(vtf.Tag) + + // RFC Section 4.19 - Optional data + if tag.Get("optional") == "true" { + if vf.Type().Kind() != reflect.Ptr { + msg := fmt.Sprintf("optional must be a pointer, not '%v'", + vf.Type().String()) + err := unmarshalError("decodeStruct", ErrBadOptional, + msg, nil, nil) + return n, err + } + + hasopt, n2, err := d.DecodeBool() + n += n2 + if err != nil { + return n, err + } + if !hasopt { + continue + } + } + + // Indirect through pointers allocating them as needed and + // ensure the field is settable. + vf, err := d.indirect(vf) + if err != nil { + return n, err + } + + if !vf.CanSet() { + msg := fmt.Sprintf("can't decode to unsettable '%v'", + vf.Type().String()) + err := unmarshalError("decodeStruct", ErrNotSettable, + msg, nil, nil) + return n, err + } + + // Handle non-opaque data to []uint8 and [#]uint8 based on + // struct tag. + if tag.Get("opaque") == "false" { + switch vf.Kind() { + case reflect.Slice: + n2, err := d.decodeArray(vf, true) + n += n2 + if err != nil { + return n, err + } + continue + + case reflect.Array: + n2, err := d.decodeFixedArray(vf, true) + n += n2 + if err != nil { + return n, err + } + continue + } + } + + if union != "" { + ucase := tag.Get("unioncase") + if ucase != "" && ucase != union { + continue + } + } + + // Decode each struct field. + n2, err := d.decode(vf) + n += n2 + if err != nil { + return n, err + } + + if tag.Get("union") == "true" { + if vf.Type().ConvertibleTo(reflect.TypeOf(0)) { + union = strconv.Itoa(int(vf.Convert(reflect.TypeOf(0)).Int())) + } else if vf.Kind() == reflect.Bool { + if vf.Bool() { + union = "1" + } else { + union = "0" + } + } else { + msg := fmt.Sprintf("type '%s' is not valid", vf.Kind().String()) + return n, unmarshalError("decodeStruct", ErrBadDiscriminant, msg, nil, nil) + } + } + + } + + return n, nil +} + +// RFC Section 4.16 - Void +// RFC Section 4.17 - Constant +// RFC Section 4.18 - Typedef +// RFC Section 4.19 - Optional data +// RFC Sections 4.15 though 4.19 only apply to the data specification language +// which is not implemented by this package. + +// decodeMap treats the next bytes as an XDR encoded variable array of 2-element +// structures whose fields are of the same type as the map keys and elements +// represented by the passed reflection value. Pointers are automatically +// indirected and allocated as necessary. It returns the the number of bytes +// actually read. +// +// An UnmarshalError is returned if any issues are encountered while decoding +// the elements. +func (d *Decoder) decodeMap(v reflect.Value) (int, error) { + dataLen, n, err := d.DecodeUint() + if err != nil { + return n, err + } + + // Allocate storage for the underlying map if needed. + vt := v.Type() + if v.IsNil() { + v.Set(reflect.MakeMap(vt)) + } + + // Decode each key and value according to their type. + keyType := vt.Key() + elemType := vt.Elem() + for i := uint32(0); i < dataLen; i++ { + key := reflect.New(keyType).Elem() + n2, err := d.decode(key) + n += n2 + if err != nil { + return n, err + } + + val := reflect.New(elemType).Elem() + n2, err = d.decode(val) + n += n2 + if err != nil { + return n, err + } + v.SetMapIndex(key, val) + } + return n, nil +} + +// decodeInterface examines the interface represented by the passed reflection +// value to detect whether it is an interface that can be decoded into and +// if it is, extracts the underlying value to pass back into the decode function +// for decoding according to its type. It returns the the number of bytes +// actually read. +// +// An UnmarshalError is returned if any issues are encountered while decoding +// the interface. +func (d *Decoder) decodeInterface(v reflect.Value) (int, error) { + if v.IsNil() || !v.CanInterface() { + msg := fmt.Sprintf("can't decode to nil interface") + err := unmarshalError("decodeInterface", ErrNilInterface, msg, + nil, nil) + return 0, err + } + + // Extract underlying value from the interface and indirect through + // pointers allocating them as needed. + ve := reflect.ValueOf(v.Interface()) + ve, err := d.indirect(ve) + if err != nil { + return 0, err + } + if !ve.CanSet() { + msg := fmt.Sprintf("can't decode to unsettable '%v'", + ve.Type().String()) + err := unmarshalError("decodeInterface", ErrNotSettable, msg, + nil, nil) + return 0, err + } + return d.decode(ve) +} + +// decode is the main workhorse for unmarshalling via reflection. It uses +// the passed reflection value to choose the XDR primitives to decode from +// the encapsulated reader. It is a recursive function, +// so cyclic data structures are not supported and will result in an infinite +// loop. It returns the the number of bytes actually read. +func (d *Decoder) decode(v reflect.Value) (int, error) { + if !v.IsValid() { + msg := fmt.Sprintf("type '%s' is not valid", v.Kind().String()) + err := unmarshalError("decode", ErrUnsupportedType, msg, nil, nil) + return 0, err + } + + // Indirect through pointers allocating them as needed. + ve, err := d.indirect(v) + if err != nil { + return 0, err + } + + // Handle time.Time values by decoding them as an RFC3339 formatted + // string with nanosecond precision. Check the type string rather + // than doing a full blown conversion to interface and type assertion + // since checking a string is much quicker. + if ve.Type().String() == "time.Time" { + // Read the value as a string and parse it. + timeString, n, err := d.DecodeString() + if err != nil { + return n, err + } + ttv, err := time.Parse(time.RFC3339, timeString) + if err != nil { + err := unmarshalError("decode", ErrParseTime, + err.Error(), timeString, err) + return n, err + } + ve.Set(reflect.ValueOf(ttv)) + return n, nil + } + + // Handle native Go types. + switch ve.Kind() { + case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int: + i, n, err := d.DecodeInt() + if err != nil { + return n, err + } + if ve.OverflowInt(int64(i)) { + msg := fmt.Sprintf("signed integer too large to fit '%s'", + ve.Kind().String()) + err = unmarshalError("decode", ErrOverflow, msg, i, nil) + return n, err + } + ve.SetInt(int64(i)) + return n, nil + + case reflect.Int64: + i, n, err := d.DecodeHyper() + if err != nil { + return n, err + } + ve.SetInt(i) + return n, nil + + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint: + ui, n, err := d.DecodeUint() + if err != nil { + return n, err + } + if ve.OverflowUint(uint64(ui)) { + msg := fmt.Sprintf("unsigned integer too large to fit '%s'", + ve.Kind().String()) + err = unmarshalError("decode", ErrOverflow, msg, ui, nil) + return n, err + } + ve.SetUint(uint64(ui)) + return n, nil + + case reflect.Uint64: + ui, n, err := d.DecodeUhyper() + if err != nil { + return n, err + } + ve.SetUint(ui) + return n, nil + + case reflect.Bool: + b, n, err := d.DecodeBool() + if err != nil { + return n, err + } + ve.SetBool(b) + return n, nil + + case reflect.Float32: + f, n, err := d.DecodeFloat() + if err != nil { + return n, err + } + ve.SetFloat(float64(f)) + return n, nil + + case reflect.Float64: + f, n, err := d.DecodeDouble() + if err != nil { + return n, err + } + ve.SetFloat(f) + return n, nil + + case reflect.String: + s, n, err := d.DecodeString() + if err != nil { + return n, err + } + ve.SetString(s) + return n, nil + + case reflect.Array: + n, err := d.decodeFixedArray(ve, false) + if err != nil { + return n, err + } + return n, nil + + case reflect.Slice: + n, err := d.decodeArray(ve, false) + if err != nil { + return n, err + } + return n, nil + + case reflect.Struct: + n, err := d.decodeStruct(ve) + if err != nil { + return n, err + } + return n, nil + + case reflect.Map: + n, err := d.decodeMap(ve) + if err != nil { + return n, err + } + return n, nil + + case reflect.Interface: + n, err := d.decodeInterface(ve) + if err != nil { + return n, err + } + return n, nil + } + + // The only unhandled types left are unsupported. At the time of this + // writing the only remaining unsupported types that exist are + // reflect.Uintptr and reflect.UnsafePointer. + msg := fmt.Sprintf("unsupported Go type '%s'", ve.Kind().String()) + err = unmarshalError("decode", ErrUnsupportedType, msg, nil, nil) + return 0, err +} + +// indirect dereferences pointers allocating them as needed until it reaches +// a non-pointer. This allows transparent decoding through arbitrary levels +// of indirection. +func (d *Decoder) indirect(v reflect.Value) (reflect.Value, error) { + rv := v + for rv.Kind() == reflect.Ptr { + // Allocate pointer if needed. + isNil := rv.IsNil() + if isNil && !rv.CanSet() { + msg := fmt.Sprintf("unable to allocate pointer for '%v'", + rv.Type().String()) + err := unmarshalError("indirect", ErrNotSettable, msg, + nil, nil) + return rv, err + } + if isNil { + rv.Set(reflect.New(rv.Type().Elem())) + } + rv = rv.Elem() + } + return rv, nil +} + +// Decode operates identically to the Unmarshal function with the exception of +// using the reader associated with the Decoder as the source of XDR-encoded +// data instead of a user-supplied reader. See the Unmarhsal documentation for +// specifics. +func (d *Decoder) Decode(v interface{}) (int, error) { + if v == nil { + msg := "can't unmarshal to nil interface" + return 0, unmarshalError("Unmarshal", ErrNilInterface, msg, nil, + nil) + } + + vv := reflect.ValueOf(v) + if vv.Kind() != reflect.Ptr { + msg := fmt.Sprintf("can't unmarshal to non-pointer '%v' - use "+ + "& operator", vv.Type().String()) + err := unmarshalError("Unmarshal", ErrBadArguments, msg, nil, nil) + return 0, err + } + if vv.IsNil() && !vv.CanSet() { + msg := fmt.Sprintf("can't unmarshal to unsettable '%v' - use "+ + "& operator", vv.Type().String()) + err := unmarshalError("Unmarshal", ErrNotSettable, msg, nil, nil) + return 0, err + } + + return d.decode(vv) +} + +// NewDecoder returns a Decoder that can be used to manually decode XDR data +// from a provided reader. Typically, Unmarshal should be used instead of +// manually creating a Decoder. +func NewDecoder(r io.Reader) *Decoder { + return &Decoder{r: r} +} + +// NewDecoderLimited is identical to NewDecoder but it sets maxReadSize in +// order to cap reads. +func NewDecoderLimited(r io.Reader, maxSize uint) *Decoder { + return &Decoder{r: r, maxReadSize: maxSize} +} diff --git a/vendor/github.com/rasky/go-xdr/xdr2/doc.go b/vendor/github.com/rasky/go-xdr/xdr2/doc.go new file mode 100644 index 00000000..1904d2b8 --- /dev/null +++ b/vendor/github.com/rasky/go-xdr/xdr2/doc.go @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2012-2014 Dave Collins + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* +Package xdr implements the data representation portion of the External Data +Representation (XDR) standard protocol as specified in RFC 4506 (obsoletes +RFC 1832 and RFC 1014). + +The XDR RFC defines both a data specification language and a data +representation standard. This package implements methods to encode and decode +XDR data per the data representation standard with the exception of 128-bit +quadruple-precision floating points. It does not currently implement parsing of +the data specification language. In other words, the ability to automatically +generate Go code by parsing an XDR data specification file (typically .x +extension) is not supported. In practice, this limitation of the package is +fairly minor since it is largely unnecessary due to the reflection capabilities +of Go as described below. + +This package provides two approaches for encoding and decoding XDR data: + + 1) Marshal/Unmarshal functions which automatically map between XDR and Go types + 2) Individual Encoder/Decoder objects to manually work with XDR primitives + +For the Marshal/Unmarshal functions, Go reflection capabilities are used to +choose the type of the underlying XDR data based upon the Go type to encode or +the target Go type to decode into. A description of how each type is mapped is +provided below, however one important type worth reviewing is Go structs. In +the case of structs, each exported field (first letter capitalized) is reflected +and mapped in order. As a result, this means a Go struct with exported fields +of the appropriate types listed in the expected order can be used to +automatically encode / decode the XDR data thereby eliminating the need to write +a lot of boilerplate code to encode/decode and error check each piece of XDR +data as is typically required with C based XDR libraries. + +Go Type to XDR Type Mappings + +The following chart shows an overview of how Go types are mapped to XDR types +for automatic marshalling and unmarshalling. The documentation for the Marshal +and Unmarshal functions has specific details of how the mapping proceeds. + + Go Type <-> XDR Type + -------------------- + int8, int16, int32, int <-> XDR Integer + uint8, uint16, uint32, uint <-> XDR Unsigned Integer + int64 <-> XDR Hyper Integer + uint64 <-> XDR Unsigned Hyper Integer + bool <-> XDR Boolean + float32 <-> XDR Floating-Point + float64 <-> XDR Double-Precision Floating-Point + string <-> XDR String + byte <-> XDR Integer + []byte <-> XDR Variable-Length Opaque Data + [#]byte <-> XDR Fixed-Length Opaque Data + [] <-> XDR Variable-Length Array + [#] <-> XDR Fixed-Length Array + * <-> XDR Optional data (when marked with struct tag `xdr:"optional"`) + struct <-> XDR Structure or Discriminated Unions + map <-> XDR Variable-Length Array of two-element XDR Structures + time.Time <-> XDR String encoded with RFC3339 nanosecond precision + +Notes and Limitations: + + * Automatic marshalling and unmarshalling of variable and fixed-length + arrays of uint8s require a special struct tag `xdr:"opaque=false"` + since byte slices and byte arrays are assumed to be opaque data and + byte is a Go alias for uint8 thus indistinguishable under reflection + * Channel, complex, and function types cannot be encoded + * Interfaces without a concrete value cannot be encoded + * Cyclic data structures are not supported and will result in infinite + loops + * Strings are marshalled and unmarshalled with UTF-8 character encoding + which differs from the XDR specification of ASCII, however UTF-8 is + backwards compatible with ASCII so this should rarely cause issues + + +Encoding + +To encode XDR data, use the Marshal function. + func Marshal(w io.Writer, v interface{}) (int, error) + +For example, given the following code snippet: + + type ImageHeader struct { + Signature [3]byte + Version uint32 + IsGrayscale bool + NumSections uint32 + } + h := ImageHeader{[3]byte{0xAB, 0xCD, 0xEF}, 2, true, 10} + + var w bytes.Buffer + bytesWritten, err := xdr.Marshal(&w, &h) + // Error check elided + +The result, encodedData, will then contain the following XDR encoded byte +sequence: + + 0xAB, 0xCD, 0xEF, 0x00, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x0A + + +In addition, while the automatic marshalling discussed above will work for the +vast majority of cases, an Encoder object is provided that can be used to +manually encode XDR primitives for complex scenarios where automatic +reflection-based encoding won't work. The included examples provide a sample of +manual usage via an Encoder. + + +Decoding + +To decode XDR data, use the Unmarshal function. + func Unmarshal(r io.Reader, v interface{}) (int, error) + +For example, given the following code snippet: + + type ImageHeader struct { + Signature [3]byte + Version uint32 + IsGrayscale bool + NumSections uint32 + } + + // Using output from the Encoding section above. + encodedData := []byte{ + 0xAB, 0xCD, 0xEF, 0x00, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x0A, + } + + var h ImageHeader + bytesRead, err := xdr.Unmarshal(bytes.NewReader(encodedData), &h) + // Error check elided + +The struct instance, h, will then contain the following values: + + h.Signature = [3]byte{0xAB, 0xCD, 0xEF} + h.Version = 2 + h.IsGrayscale = true + h.NumSections = 10 + +In addition, while the automatic unmarshalling discussed above will work for the +vast majority of cases, a Decoder object is provided that can be used to +manually decode XDR primitives for complex scenarios where automatic +reflection-based decoding won't work. The included examples provide a sample of +manual usage via a Decoder. + + +Discriminated Unions + +Discriminated unions are marshalled via Go structs, using special struct tags +to mark the discriminant and the different cases. For instance: + + type ReturnValue struct { + Status int `xdr:"union"` + StatusOk struct { + Width int + Height int + } `xdr:"unioncase=0"` + StatusError struct { + ErrMsg string + } `xdr:"unioncase=-1"` + } + +The Status field is the discriminant of the union, and is always serialized; +if its value is 0, the StatusOK struct is serialized while the StatusErr struct +is ignored; if its value is -1, the opposite happens. If the value is different +from both 0 and -1, only the Status field is serialized. Any additional field +not marked with unioncase is always serialized as normal. + +You are not forced to use sub-structures; for instance, the following is also +valid: + + type ReturnValue struct { + Status int `xdr:"union"` + Width int `xdr:"unioncase=0"` + Height int `xdr:"unioncase=0"` + ErrMsg string `xdr:"unioncase=-1"` + } + + +Errors + +All errors are either of type UnmarshalError or MarshalError. Both provide +human-readable output as well as an ErrorCode field which can be inspected by +sophisticated callers if necessary. + +See the documentation of UnmarshalError, MarshalError, and ErrorCode for further +details. +*/ +package xdr diff --git a/vendor/github.com/rasky/go-xdr/xdr2/encode.go b/vendor/github.com/rasky/go-xdr/xdr2/encode.go new file mode 100644 index 00000000..73a2ff00 --- /dev/null +++ b/vendor/github.com/rasky/go-xdr/xdr2/encode.go @@ -0,0 +1,718 @@ +/* + * Copyright (c) 2012-2014 Dave Collins + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package xdr + +import ( + "fmt" + "io" + "math" + "reflect" + "strconv" + "time" +) + +var errIOEncode = "%s while encoding %d bytes" + +/* +Marshal writes the XDR encoding of v to writer w and returns the number of bytes +written. It traverses v recursively and automatically indirects pointers +through arbitrary depth to encode the actual value pointed to. + +Marshal uses reflection to determine the type of the concrete value contained by +v and performs a mapping of Go types to the underlying XDR types as follows: + + Go Type -> XDR Type + -------------------- + int8, int16, int32, int -> XDR Integer + uint8, uint16, uint32, uint -> XDR Unsigned Integer + int64 -> XDR Hyper Integer + uint64 -> XDR Unsigned Hyper Integer + bool -> XDR Boolean + float32 -> XDR Floating-Point + float64 -> XDR Double-Precision Floating-Point + string -> XDR String + byte -> XDR Integer + []byte -> XDR Variable-Length Opaque Data + [#]byte -> XDR Fixed-Length Opaque Data + [] -> XDR Variable-Length Array + [#] -> XDR Fixed-Length Array + struct -> XDR Structure + map -> XDR Variable-Length Array of two-element XDR Structures + time.Time -> XDR String encoded with RFC3339 nanosecond precision + +Notes and Limitations: + + * Automatic marshalling of variable and fixed-length arrays of uint8s + requires a special struct tag `xdropaque:"false"` since byte slices and + byte arrays are assumed to be opaque data and byte is a Go alias for uint8 + thus indistinguishable under reflection + * Channel, complex, and function types cannot be encoded + * Interfaces without a concrete value cannot be encoded + * Cyclic data structures are not supported and will result in infinite loops + * Strings are marshalled with UTF-8 character encoding which differs from + the XDR specification of ASCII, however UTF-8 is backwards compatible with + ASCII so this should rarely cause issues + +If any issues are encountered during the marshalling process, a MarshalError is +returned with a human readable description as well as an ErrorCode value for +further inspection from sophisticated callers. Some potential issues are +unsupported Go types, attempting to encode more opaque data than can be +represented by a single opaque XDR entry, and exceeding max slice limitations. +*/ +func Marshal(w io.Writer, v interface{}) (int, error) { + enc := Encoder{w: w} + return enc.Encode(v) +} + +// An Encoder wraps an io.Writer that will receive the XDR encoded byte stream. +// See NewEncoder. +type Encoder struct { + w io.Writer +} + +// EncodeInt writes the XDR encoded representation of the passed 32-bit signed +// integer to the encapsulated writer and returns the number of bytes written. +// +// A MarshalError with an error code of ErrIO is returned if writing the data +// fails. +// +// Reference: +// RFC Section 4.1 - Integer +// 32-bit big-endian signed integer in range [-2147483648, 2147483647] +func (enc *Encoder) EncodeInt(v int32) (int, error) { + var b [4]byte + b[0] = byte(v >> 24) + b[1] = byte(v >> 16) + b[2] = byte(v >> 8) + b[3] = byte(v) + + n, err := enc.w.Write(b[:]) + if err != nil { + msg := fmt.Sprintf(errIOEncode, err.Error(), 4) + err := marshalError("EncodeInt", ErrIO, msg, b[:n], err) + return n, err + } + + return n, nil +} + +// EncodeUint writes the XDR encoded representation of the passed 32-bit +// unsigned integer to the encapsulated writer and returns the number of bytes +// written. +// +// A MarshalError with an error code of ErrIO is returned if writing the data +// fails. +// +// Reference: +// RFC Section 4.2 - Unsigned Integer +// 32-bit big-endian unsigned integer in range [0, 4294967295] +func (enc *Encoder) EncodeUint(v uint32) (int, error) { + var b [4]byte + b[0] = byte(v >> 24) + b[1] = byte(v >> 16) + b[2] = byte(v >> 8) + b[3] = byte(v) + + n, err := enc.w.Write(b[:]) + if err != nil { + msg := fmt.Sprintf(errIOEncode, err.Error(), 4) + err := marshalError("EncodeUint", ErrIO, msg, b[:n], err) + return n, err + } + + return n, nil +} + +// EncodeEnum treats the passed 32-bit signed integer as an enumeration value +// and, if it is in the list of passed valid enumeration values, writes the XDR +// encoded representation of it to the encapsulated writer. It returns the +// number of bytes written. +// +// A MarshalError is returned if the enumeration value is not one of the +// provided valid values or if writing the data fails. +// +// Reference: +// RFC Section 4.3 - Enumeration +// Represented as an XDR encoded signed integer +func (enc *Encoder) EncodeEnum(v int32, validEnums map[int32]bool) (int, error) { + if !validEnums[v] { + err := marshalError("EncodeEnum", ErrBadEnumValue, + "invalid enum", v, nil) + return 0, err + } + return enc.EncodeInt(v) +} + +// EncodeBool writes the XDR encoded representation of the passed boolean to the +// encapsulated writer and returns the number of bytes written. +// +// A MarshalError with an error code of ErrIO is returned if writing the data +// fails. +// +// Reference: +// RFC Section 4.4 - Boolean +// Represented as an XDR encoded enumeration where 0 is false and 1 is true +func (enc *Encoder) EncodeBool(v bool) (int, error) { + i := int32(0) + if v == true { + i = 1 + } + return enc.EncodeInt(i) +} + +// EncodeHyper writes the XDR encoded representation of the passed 64-bit +// signed integer to the encapsulated writer and returns the number of bytes +// written. +// +// A MarshalError with an error code of ErrIO is returned if writing the data +// fails. +// +// Reference: +// RFC Section 4.5 - Hyper Integer +// 64-bit big-endian signed integer in range [-9223372036854775808, 9223372036854775807] +func (enc *Encoder) EncodeHyper(v int64) (int, error) { + var b [8]byte + b[0] = byte(v >> 56) + b[1] = byte(v >> 48) + b[2] = byte(v >> 40) + b[3] = byte(v >> 32) + b[4] = byte(v >> 24) + b[5] = byte(v >> 16) + b[6] = byte(v >> 8) + b[7] = byte(v) + + n, err := enc.w.Write(b[:]) + if err != nil { + msg := fmt.Sprintf(errIOEncode, err.Error(), 8) + err := marshalError("EncodeHyper", ErrIO, msg, b[:n], err) + return n, err + } + + return n, nil +} + +// EncodeUhyper writes the XDR encoded representation of the passed 64-bit +// unsigned integer to the encapsulated writer and returns the number of bytes +// written. +// +// A MarshalError with an error code of ErrIO is returned if writing the data +// fails. +// +// Reference: +// RFC Section 4.5 - Unsigned Hyper Integer +// 64-bit big-endian unsigned integer in range [0, 18446744073709551615] +func (enc *Encoder) EncodeUhyper(v uint64) (int, error) { + var b [8]byte + b[0] = byte(v >> 56) + b[1] = byte(v >> 48) + b[2] = byte(v >> 40) + b[3] = byte(v >> 32) + b[4] = byte(v >> 24) + b[5] = byte(v >> 16) + b[6] = byte(v >> 8) + b[7] = byte(v) + + n, err := enc.w.Write(b[:]) + if err != nil { + msg := fmt.Sprintf(errIOEncode, err.Error(), 8) + err := marshalError("EncodeUhyper", ErrIO, msg, b[:n], err) + return n, err + } + + return n, nil +} + +// EncodeFloat writes the XDR encoded representation of the passed 32-bit +// (single-precision) floating point to the encapsulated writer and returns the +// number of bytes written. +// +// A MarshalError with an error code of ErrIO is returned if writing the data +// fails. +// +// Reference: +// RFC Section 4.6 - Floating Point +// 32-bit single-precision IEEE 754 floating point +func (enc *Encoder) EncodeFloat(v float32) (int, error) { + ui := math.Float32bits(v) + return enc.EncodeUint(ui) +} + +// EncodeDouble writes the XDR encoded representation of the passed 64-bit +// (double-precision) floating point to the encapsulated writer and returns the +// number of bytes written. +// +// A MarshalError with an error code of ErrIO is returned if writing the data +// fails. +// +// Reference: +// RFC Section 4.7 - Double-Precision Floating Point +// 64-bit double-precision IEEE 754 floating point +func (enc *Encoder) EncodeDouble(v float64) (int, error) { + ui := math.Float64bits(v) + return enc.EncodeUhyper(ui) +} + +// RFC Section 4.8 - Quadruple-Precision Floating Point +// 128-bit quadruple-precision floating point +// Not Implemented + +// EncodeFixedOpaque treats the passed byte slice as opaque data of a fixed +// size and writes the XDR encoded representation of it to the encapsulated +// writer. It returns the number of bytes written. +// +// A MarshalError with an error code of ErrIO is returned if writing the data +// fails. +// +// Reference: +// RFC Section 4.9 - Fixed-Length Opaque Data +// Fixed-length uninterpreted data zero-padded to a multiple of four +func (enc *Encoder) EncodeFixedOpaque(v []byte) (int, error) { + l := len(v) + pad := (4 - (l % 4)) % 4 + + // Write the actual bytes. + n, err := enc.w.Write(v) + if err != nil { + msg := fmt.Sprintf(errIOEncode, err.Error(), len(v)) + err := marshalError("EncodeFixedOpaque", ErrIO, msg, v[:n], err) + return n, err + } + + // Write any padding if needed. + if pad > 0 { + b := make([]byte, pad) + n2, err := enc.w.Write(b) + n += n2 + if err != nil { + written := make([]byte, l+n2) + copy(written, v) + copy(written[l:], b[:n2]) + msg := fmt.Sprintf(errIOEncode, err.Error(), l+pad) + err := marshalError("EncodeFixedOpaque", ErrIO, msg, + written, err) + return n, err + } + } + + return n, nil +} + +// EncodeOpaque treats the passed byte slice as opaque data of a variable +// size and writes the XDR encoded representation of it to the encapsulated +// writer. It returns the number of bytes written. +// +// A MarshalError with an error code of ErrIO is returned if writing the data +// fails. +// +// Reference: +// RFC Section 4.10 - Variable-Length Opaque Data +// Unsigned integer length followed by fixed opaque data of that length +func (enc *Encoder) EncodeOpaque(v []byte) (int, error) { + // Length of opaque data. + n, err := enc.EncodeUint(uint32(len(v))) + if err != nil { + return n, err + } + + n2, err := enc.EncodeFixedOpaque(v) + n += n2 + return n, err +} + +// EncodeString writes the XDR encoded representation of the passed string +// to the encapsulated writer and returns the number of bytes written. +// Character encoding is assumed to be UTF-8 and therefore ASCII compatible. If +// the underlying character encoding is not compatible with this assumption, the +// data can instead be written as variable-length opaque data (EncodeOpaque) and +// manually converted as needed. +// +// A MarshalError with an error code of ErrIO is returned if writing the data +// fails. +// +// Reference: +// RFC Section 4.11 - String +// Unsigned integer length followed by bytes zero-padded to a multiple of four +func (enc *Encoder) EncodeString(v string) (int, error) { + // Length of string. + n, err := enc.EncodeUint(uint32(len(v))) + if err != nil { + return n, err + } + + n2, err := enc.EncodeFixedOpaque([]byte(v)) + n += n2 + return n, err +} + +// encodeFixedArray writes the XDR encoded representation of each element +// in the passed array represented by the reflection value to the encapsulated +// writer and returns the number of bytes written. The ignoreOpaque flag +// controls whether or not uint8 (byte) elements should be encoded individually +// or as a fixed sequence of opaque data. +// +// A MarshalError is returned if any issues are encountered while encoding +// the array elements. +// +// Reference: +// RFC Section 4.12 - Fixed-Length Array +// Individually XDR encoded array elements +func (enc *Encoder) encodeFixedArray(v reflect.Value, ignoreOpaque bool) (int, error) { + // Treat [#]byte (byte is alias for uint8) as opaque data unless ignored. + if !ignoreOpaque && v.Type().Elem().Kind() == reflect.Uint8 { + // Create a slice of the underlying array for better efficiency + // when possible. Can't create a slice of an unaddressable + // value. + if v.CanAddr() { + return enc.EncodeFixedOpaque(v.Slice(0, v.Len()).Bytes()) + } + + // When the underlying array isn't addressable fall back to + // copying the array into a new slice. This is rather ugly, but + // the inability to create a constant slice from an + // unaddressable array is a limitation of Go. + slice := make([]byte, v.Len(), v.Len()) + reflect.Copy(reflect.ValueOf(slice), v) + return enc.EncodeFixedOpaque(slice) + } + + // Encode each array element. + var n int + for i := 0; i < v.Len(); i++ { + n2, err := enc.encode(v.Index(i)) + n += n2 + if err != nil { + return n, err + } + } + + return n, nil +} + +// encodeArray writes an XDR encoded integer representing the number of +// elements in the passed slice represented by the reflection value followed by +// the XDR encoded representation of each element in slice to the encapsulated +// writer and returns the number of bytes written. The ignoreOpaque flag +// controls whether or not uint8 (byte) elements should be encoded individually +// or as a variable sequence of opaque data. +// +// A MarshalError is returned if any issues are encountered while encoding +// the array elements. +// +// Reference: +// RFC Section 4.13 - Variable-Length Array +// Unsigned integer length followed by individually XDR encoded array elements +func (enc *Encoder) encodeArray(v reflect.Value, ignoreOpaque bool) (int, error) { + numItems := uint32(v.Len()) + n, err := enc.EncodeUint(numItems) + if err != nil { + return n, err + } + + n2, err := enc.encodeFixedArray(v, ignoreOpaque) + n += n2 + return n, err +} + +// encodeStruct writes an XDR encoded representation of each value in the +// exported fields of the struct represented by the passed reflection value to +// the encapsulated writer and returns the number of bytes written. Pointers +// are automatically indirected through arbitrary depth to encode the actual +// value pointed to. +// +// A MarshalError is returned if any issues are encountered while encoding +// the elements. +// +// Reference: +// RFC Section 4.14 - Structure +// XDR encoded elements in the order of their declaration in the struct +func (enc *Encoder) encodeStruct(v reflect.Value) (int, error) { + var n int + var union string + vt := v.Type() + for i := 0; i < v.NumField(); i++ { + // Skip unexported fields and indirect through pointers. + vtf := vt.Field(i) + if vtf.PkgPath != "" { + continue + } + + vf := v.Field(i) + tag := parseTag(vtf.Tag) + + // RFC Section 4.19 - Optional data + if tag.Get("optional") == "true" { + if vf.Type().Kind() != reflect.Ptr { + msg := fmt.Sprintf("optional must be a pointer, not '%v'", + vf.Type().String()) + err := marshalError("encodeStruct", ErrBadOptional, + msg, nil, nil) + return n, err + } + + hasopt := !vf.IsNil() + n2, err := enc.EncodeBool(hasopt) + n += n2 + if err != nil { + return n, err + } + if !hasopt { + continue + } + } + + vf = enc.indirect(vf) + + // Handle non-opaque data to []uint8 and [#]uint8 based on struct tag. + if tag.Get("opaque") == "false" { + switch vf.Kind() { + case reflect.Slice: + n2, err := enc.encodeArray(vf, true) + n += n2 + if err != nil { + return n, err + } + continue + + case reflect.Array: + n2, err := enc.encodeFixedArray(vf, true) + n += n2 + if err != nil { + return n, err + } + continue + } + } + + // RFC Section 4.15 - Discriminated Union + // The tag option "union" marks the discriminant in the struct; the tag + // option "unioncase=N" marks a struct field that is only serialized + // when the discriminant has the specified value. + if tag.Get("union") == "true" { + if vf.Type().ConvertibleTo(reflect.TypeOf(0)) { + union = strconv.Itoa(int(vf.Convert(reflect.TypeOf(0)).Int())) + } else if vf.Kind() == reflect.Bool { + if vf.Bool() { + union = "1" + } else { + union = "0" + } + } else { + msg := fmt.Sprintf("type '%s' is not valid", vf.Kind().String()) + return n, marshalError("encodeStruct", ErrBadDiscriminant, msg, nil, nil) + } + } + + if union != "" { + ucase := tag.Get("unioncase") + if ucase != "" && ucase != union { + continue + } + } + + // Encode each struct field. + n2, err := enc.encode(vf) + n += n2 + if err != nil { + return n, err + } + } + + return n, nil +} + +// RFC Section 4.16 - Void +// RFC Section 4.17 - Constant +// RFC Section 4.18 - Typedef +// RFC Section 4.19 - Optional data +// RFC Sections 4.16 though 4.19 only apply to the data specification language +// which is not implemented by this package. + +// encodeMap treats the map represented by the passed reflection value as a +// variable-length array of 2-element structures whose fields are of the same +// type as the map keys and elements and writes its XDR encoded representation +// to the encapsulated writer. It returns the number of bytes written. +// +// A MarshalError is returned if any issues are encountered while encoding +// the elements. +func (enc *Encoder) encodeMap(v reflect.Value) (int, error) { + // Number of elements. + n, err := enc.EncodeUint(uint32(v.Len())) + if err != nil { + return n, err + } + + // Encode each key and value according to their type. + for _, key := range v.MapKeys() { + n2, err := enc.encode(key) + n += n2 + if err != nil { + return n, err + } + + n2, err = enc.encode(v.MapIndex(key)) + n += n2 + if err != nil { + return n, err + } + } + + return n, nil +} + +// encodeInterface examines the interface represented by the passed reflection +// value to detect whether it is an interface that can be encoded if it is, +// extracts the underlying value to pass back into the encode function for +// encoding according to its type. +// +// A MarshalError is returned if any issues are encountered while encoding +// the interface. +func (enc *Encoder) encodeInterface(v reflect.Value) (int, error) { + if v.IsNil() || !v.CanInterface() { + msg := fmt.Sprintf("can't encode nil interface") + err := marshalError("encodeInterface", ErrNilInterface, msg, + nil, nil) + return 0, err + } + + // Extract underlying value from the interface and indirect through pointers. + ve := reflect.ValueOf(v.Interface()) + ve = enc.indirect(ve) + return enc.encode(ve) +} + +// encode is the main workhorse for marshalling via reflection. It uses +// the passed reflection value to choose the XDR primitives to encode into +// the encapsulated writer and returns the number of bytes written. It is a +// recursive function, so cyclic data structures are not supported and will +// result in an infinite loop. +func (enc *Encoder) encode(v reflect.Value) (int, error) { + if !v.IsValid() { + msg := fmt.Sprintf("type '%s' is not valid", v.Kind().String()) + err := marshalError("encode", ErrUnsupportedType, msg, nil, nil) + return 0, err + } + + // Indirect through pointers to get at the concrete value. + ve := enc.indirect(v) + + // Handle time.Time values by encoding them as an RFC3339 formatted + // string with nanosecond precision. Check the type string before + // doing a full blown conversion to interface and type assertion since + // checking a string is much quicker. + if ve.Type().String() == "time.Time" && ve.CanInterface() { + viface := ve.Interface() + if tv, ok := viface.(time.Time); ok { + return enc.EncodeString(tv.Format(time.RFC3339Nano)) + } + } + + // Handle native Go types. + switch ve.Kind() { + case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int: + return enc.EncodeInt(int32(ve.Int())) + + case reflect.Int64: + return enc.EncodeHyper(ve.Int()) + + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint: + return enc.EncodeUint(uint32(ve.Uint())) + + case reflect.Uint64: + return enc.EncodeUhyper(ve.Uint()) + + case reflect.Bool: + return enc.EncodeBool(ve.Bool()) + + case reflect.Float32: + return enc.EncodeFloat(float32(ve.Float())) + + case reflect.Float64: + return enc.EncodeDouble(ve.Float()) + + case reflect.String: + return enc.EncodeString(ve.String()) + + case reflect.Array: + return enc.encodeFixedArray(ve, false) + + case reflect.Slice: + return enc.encodeArray(ve, false) + + case reflect.Struct: + return enc.encodeStruct(ve) + + case reflect.Map: + return enc.encodeMap(ve) + + case reflect.Interface: + return enc.encodeInterface(ve) + } + + // The only unhandled types left are unsupported. At the time of this + // writing the only remaining unsupported types that exist are + // reflect.Uintptr and reflect.UnsafePointer. + msg := fmt.Sprintf("unsupported Go type '%s'", ve.Kind().String()) + err := marshalError("encode", ErrUnsupportedType, msg, nil, nil) + return 0, err +} + +// indirect dereferences pointers until it reaches a non-pointer. This allows +// transparent encoding through arbitrary levels of indirection. +func (enc *Encoder) indirect(v reflect.Value) reflect.Value { + rv := v + for rv.Kind() == reflect.Ptr { + rv = rv.Elem() + } + return rv +} + +// Encode operates identically to the Marshal function with the exception of +// using the writer associated with the Encoder for the destination of the +// XDR-encoded data instead of a user-supplied writer. See the Marshal +// documentation for specifics. +func (enc *Encoder) Encode(v interface{}) (int, error) { + if v == nil { + msg := "can't marshal nil interface" + err := marshalError("Marshal", ErrNilInterface, msg, nil, nil) + return 0, err + } + + vv := reflect.ValueOf(v) + vve := vv + for vve.Kind() == reflect.Ptr { + if vve.IsNil() { + msg := fmt.Sprintf("can't marshal nil pointer '%v'", + vv.Type().String()) + err := marshalError("Marshal", ErrBadArguments, msg, + nil, nil) + return 0, err + } + vve = vve.Elem() + } + + return enc.encode(vve) +} + +// NewEncoder returns an object that can be used to manually choose fields to +// XDR encode to the passed writer w. Typically, Marshal should be used instead +// of manually creating an Encoder. An Encoder, along with several of its +// methods to encode XDR primitives, is exposed so it is possible to perform +// manual encoding of data without relying on reflection should it be necessary +// in complex scenarios where automatic reflection-based encoding won't work. +func NewEncoder(w io.Writer) *Encoder { + return &Encoder{w: w} +} diff --git a/vendor/github.com/rasky/go-xdr/xdr2/error.go b/vendor/github.com/rasky/go-xdr/xdr2/error.go new file mode 100644 index 00000000..8c1f3aec --- /dev/null +++ b/vendor/github.com/rasky/go-xdr/xdr2/error.go @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2012-2014 Dave Collins + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package xdr + +import "fmt" + +// ErrorCode identifies a kind of error. +type ErrorCode int + +const ( + // ErrBadArguments indicates arguments passed to the function are not + // what was expected. + ErrBadArguments ErrorCode = iota + + // ErrUnsupportedType indicates the Go type is not a supported type for + // marshalling and unmarshalling XDR data. + ErrUnsupportedType + + // ErrBadEnumValue indicates an enumeration value is not in the list of + // valid values. + ErrBadEnumValue + + // ErrNotSettable indicates an interface value cannot be written to. + // This usually means the interface value was not passed with the & + // operator, but it can also happen if automatic pointer allocation + // fails. + ErrNotSettable + + // ErrOverflow indicates that the data in question is too large to fit + // into the corresponding Go or XDR data type. For example, an integer + // decoded from XDR that is too large to fit into a target type of int8, + // or opaque data that exceeds the max length of a Go slice. + ErrOverflow + + // ErrNilInterface indicates an interface with no concrete type + // information was encountered. Type information is necessary to + // perform mapping between XDR and Go types. + ErrNilInterface + + // ErrIO indicates an error was encountered while reading or writing to + // an io.Reader or io.Writer, respectively. The actual underlying error + // will be available via the Err field of the MarshalError or + // UnmarshalError struct. + ErrIO + + // ErrParseTime indicates an error was encountered while parsing an + // RFC3339 formatted time value. The actual underlying error will be + // available via the Err field of the UnmarshalError struct. + ErrParseTime + + // ErrBadDiscriminant indicates that a non-integer field of a struct + // was marked as a union discriminant through a struct tag. + ErrBadDiscriminant + + // ErrBadOptional indicates that a non-pointer field of a struct + // was marked as an optional-data. + ErrBadOptional +) + +// Map of ErrorCode values back to their constant names for pretty printing. +var errorCodeStrings = map[ErrorCode]string{ + ErrBadArguments: "ErrBadArguments", + ErrUnsupportedType: "ErrUnsupportedType", + ErrBadEnumValue: "ErrBadEnumValue", + ErrNotSettable: "ErrNotSettable", + ErrOverflow: "ErrOverflow", + ErrNilInterface: "ErrNilInterface", + ErrIO: "ErrIO", + ErrParseTime: "ErrParseTime", + ErrBadDiscriminant: "ErrBadDiscriminant", + ErrBadOptional: "ErrBadOptional", +} + +// String returns the ErrorCode as a human-readable name. +func (e ErrorCode) String() string { + if s := errorCodeStrings[e]; s != "" { + return s + } + return fmt.Sprintf("Unknown ErrorCode (%d)", e) +} + +// UnmarshalError describes a problem encountered while unmarshaling data. +// Some potential issues are unsupported Go types, attempting to decode a value +// which is too large to fit into a specified Go type, and exceeding max slice +// limitations. +type UnmarshalError struct { + ErrorCode ErrorCode // Describes the kind of error + Func string // Function name + Value interface{} // Value actually parsed where appropriate + Description string // Human readable description of the issue + Err error // The underlying error for IO errors +} + +// Error satisfies the error interface and prints human-readable errors. +func (e *UnmarshalError) Error() string { + switch e.ErrorCode { + case ErrBadEnumValue, ErrOverflow, ErrIO, ErrParseTime: + return fmt.Sprintf("xdr:%s: %s - read: '%v'", e.Func, + e.Description, e.Value) + } + return fmt.Sprintf("xdr:%s: %s", e.Func, e.Description) +} + +// unmarshalError creates an error given a set of arguments and will copy byte +// slices into the Value field since they might otherwise be changed from from +// the original value. +func unmarshalError(f string, c ErrorCode, desc string, v interface{}, err error) *UnmarshalError { + e := &UnmarshalError{ErrorCode: c, Func: f, Description: desc, Err: err} + switch t := v.(type) { + case []byte: + slice := make([]byte, len(t)) + copy(slice, t) + e.Value = slice + default: + e.Value = v + } + + return e +} + +// IsIO returns a boolean indicating whether the error is known to report that +// the underlying reader or writer encountered an ErrIO. +func IsIO(err error) bool { + switch e := err.(type) { + case *UnmarshalError: + return e.ErrorCode == ErrIO + case *MarshalError: + return e.ErrorCode == ErrIO + } + return false +} + +// MarshalError describes a problem encountered while marshaling data. +// Some potential issues are unsupported Go types, attempting to encode more +// opaque data than can be represented by a single opaque XDR entry, and +// exceeding max slice limitations. +type MarshalError struct { + ErrorCode ErrorCode // Describes the kind of error + Func string // Function name + Value interface{} // Value actually parsed where appropriate + Description string // Human readable description of the issue + Err error // The underlying error for IO errors +} + +// Error satisfies the error interface and prints human-readable errors. +func (e *MarshalError) Error() string { + switch e.ErrorCode { + case ErrIO: + return fmt.Sprintf("xdr:%s: %s - wrote: '%v'", e.Func, + e.Description, e.Value) + case ErrBadEnumValue: + return fmt.Sprintf("xdr:%s: %s - value: '%v'", e.Func, + e.Description, e.Value) + } + return fmt.Sprintf("xdr:%s: %s", e.Func, e.Description) +} + +// marshalError creates an error given a set of arguments and will copy byte +// slices into the Value field since they might otherwise be changed from from +// the original value. +func marshalError(f string, c ErrorCode, desc string, v interface{}, err error) *MarshalError { + e := &MarshalError{ErrorCode: c, Func: f, Description: desc, Err: err} + switch t := v.(type) { + case []byte: + slice := make([]byte, len(t)) + copy(slice, t) + e.Value = slice + default: + e.Value = v + } + + return e +} diff --git a/vendor/github.com/rasky/go-xdr/xdr2/tag.go b/vendor/github.com/rasky/go-xdr/xdr2/tag.go new file mode 100644 index 00000000..170e7762 --- /dev/null +++ b/vendor/github.com/rasky/go-xdr/xdr2/tag.go @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2012-2014 Dave Collins + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package xdr + +import ( + "reflect" + "strings" +) + +// xdrtag represents a XDR struct tag, identified by the name "xdr:". +// The value of the tag is a string that is parsed as a comma-separated +// list of =-separated key-value options. If an option has no value, +// "true" is assumed to be the default value. +// +// For instance: +// +// `xdr:"foo,bar=2,baz=false" +// +// After parsing this tag, Get("foo") will return "true", Get("bar") +// will return "2", and Get("baz") will return "false". +type xdrtag string + +// parseTag extracts a xdrtag from the original reflect.StructTag as found in +// in the struct field. If the tag was not specified, an empty strtag is +// returned. +func parseTag(tag reflect.StructTag) xdrtag { + t := tag.Get("xdr") + // Handle backward compatibility with the previous "xdropaque" + // tag which is now deprecated. + if tag.Get("xdropaque") == "false" { + if t == "" { + t = "," + } + t += ",opaque=false" + } + return xdrtag(t) +} + +// Get returns the value for the specified option. If the option is not +// present in the tag, an empty string is returned. If the option is +// present but has no value, the string "true" is returned as default value. +func (t xdrtag) Get(opt string) string { + tag := string(t) + for tag != "" { + var next string + i := strings.Index(tag, ",") + if i >= 0 { + tag, next = tag[:i], tag[i+1:] + } + if tag == opt { + return "true" + } + if len(tag) > len(opt) && tag[:len(opt)] == opt && tag[len(opt)] == '=' { + val := tag[len(opt)+1:] + i = strings.Index(val, ",") + if i >= 0 { + val = val[i:] + } + return val + } + tag = next + } + return "" +} diff --git a/vendor/github.com/stretchr/testify/assert/assertion_compare.go b/vendor/github.com/stretchr/testify/assert/assertion_compare.go index 3bb22a97..95d8e59d 100644 --- a/vendor/github.com/stretchr/testify/assert/assertion_compare.go +++ b/vendor/github.com/stretchr/testify/assert/assertion_compare.go @@ -1,6 +1,7 @@ package assert import ( + "bytes" "fmt" "reflect" "time" @@ -32,7 +33,8 @@ var ( stringType = reflect.TypeOf("") - timeType = reflect.TypeOf(time.Time{}) + timeType = reflect.TypeOf(time.Time{}) + bytesType = reflect.TypeOf([]byte{}) ) func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { @@ -323,6 +325,26 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { return compare(timeObj1.UnixNano(), timeObj2.UnixNano(), reflect.Int64) } + case reflect.Slice: + { + // We only care about the []byte type. + if !canConvert(obj1Value, bytesType) { + break + } + + // []byte can be compared! + bytesObj1, ok := obj1.([]byte) + if !ok { + bytesObj1 = obj1Value.Convert(bytesType).Interface().([]byte) + + } + bytesObj2, ok := obj2.([]byte) + if !ok { + bytesObj2 = obj2Value.Convert(bytesType).Interface().([]byte) + } + + return CompareType(bytes.Compare(bytesObj1, bytesObj2)), true + } } return compareEqual, false diff --git a/vendor/github.com/stretchr/testify/assert/assertion_compare_can_convert.go b/vendor/github.com/stretchr/testify/assert/assertion_compare_can_convert.go index df22c47f..da867903 100644 --- a/vendor/github.com/stretchr/testify/assert/assertion_compare_can_convert.go +++ b/vendor/github.com/stretchr/testify/assert/assertion_compare_can_convert.go @@ -9,7 +9,7 @@ package assert import "reflect" -// Wrapper around reflect.Value.CanConvert, for compatability +// Wrapper around reflect.Value.CanConvert, for compatibility // reasons. func canConvert(value reflect.Value, to reflect.Type) bool { return value.CanConvert(to) diff --git a/vendor/github.com/stretchr/testify/assert/assertion_format.go b/vendor/github.com/stretchr/testify/assert/assertion_format.go index 27e2420e..7880b8f9 100644 --- a/vendor/github.com/stretchr/testify/assert/assertion_format.go +++ b/vendor/github.com/stretchr/testify/assert/assertion_format.go @@ -736,6 +736,16 @@ func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta tim return WithinDuration(t, expected, actual, delta, append([]interface{}{msg}, args...)...) } +// WithinRangef asserts that a time is within a time range (inclusive). +// +// assert.WithinRangef(t, time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second), "error message %s", "formatted") +func WithinRangef(t TestingT, actual time.Time, start time.Time, end time.Time, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return WithinRange(t, actual, start, end, append([]interface{}{msg}, args...)...) +} + // YAMLEqf asserts that two YAML strings are equivalent. func YAMLEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { diff --git a/vendor/github.com/stretchr/testify/assert/assertion_forward.go b/vendor/github.com/stretchr/testify/assert/assertion_forward.go index d9ea368d..339515b8 100644 --- a/vendor/github.com/stretchr/testify/assert/assertion_forward.go +++ b/vendor/github.com/stretchr/testify/assert/assertion_forward.go @@ -1461,6 +1461,26 @@ func (a *Assertions) WithinDurationf(expected time.Time, actual time.Time, delta return WithinDurationf(a.t, expected, actual, delta, msg, args...) } +// WithinRange asserts that a time is within a time range (inclusive). +// +// a.WithinRange(time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second)) +func (a *Assertions) WithinRange(actual time.Time, start time.Time, end time.Time, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return WithinRange(a.t, actual, start, end, msgAndArgs...) +} + +// WithinRangef asserts that a time is within a time range (inclusive). +// +// a.WithinRangef(time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second), "error message %s", "formatted") +func (a *Assertions) WithinRangef(actual time.Time, start time.Time, end time.Time, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return WithinRangef(a.t, actual, start, end, msg, args...) +} + // YAMLEq asserts that two YAML strings are equivalent. func (a *Assertions) YAMLEq(expected string, actual string, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { diff --git a/vendor/github.com/stretchr/testify/assert/assertions.go b/vendor/github.com/stretchr/testify/assert/assertions.go index 0357b223..fa1245b1 100644 --- a/vendor/github.com/stretchr/testify/assert/assertions.go +++ b/vendor/github.com/stretchr/testify/assert/assertions.go @@ -8,6 +8,7 @@ import ( "fmt" "math" "os" + "path/filepath" "reflect" "regexp" "runtime" @@ -144,7 +145,8 @@ func CallerInfo() []string { if len(parts) > 1 { dir := parts[len(parts)-2] if (dir != "assert" && dir != "mock" && dir != "require") || file == "mock_test.go" { - callers = append(callers, fmt.Sprintf("%s:%d", file, line)) + path, _ := filepath.Abs(file) + callers = append(callers, fmt.Sprintf("%s:%d", path, line)) } } @@ -563,16 +565,17 @@ func isEmpty(object interface{}) bool { switch objValue.Kind() { // collection types are empty when they have no element - case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice: + case reflect.Chan, reflect.Map, reflect.Slice: return objValue.Len() == 0 - // pointers are empty if nil or if the value they point to is empty + // pointers are empty if nil or if the value they point to is empty case reflect.Ptr: if objValue.IsNil() { return true } deref := objValue.Elem().Interface() return isEmpty(deref) - // for all other types, compare against the zero value + // for all other types, compare against the zero value + // array types are empty when they match their zero-initialized state default: zero := reflect.Zero(objValue.Type()) return reflect.DeepEqual(object, zero.Interface()) @@ -815,7 +818,6 @@ func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok return true // we consider nil to be equal to the nil set } - subsetValue := reflect.ValueOf(subset) defer func() { if e := recover(); e != nil { ok = false @@ -825,14 +827,32 @@ func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok listKind := reflect.TypeOf(list).Kind() subsetKind := reflect.TypeOf(subset).Kind() - if listKind != reflect.Array && listKind != reflect.Slice { + if listKind != reflect.Array && listKind != reflect.Slice && listKind != reflect.Map { return Fail(t, fmt.Sprintf("%q has an unsupported type %s", list, listKind), msgAndArgs...) } - if subsetKind != reflect.Array && subsetKind != reflect.Slice { + if subsetKind != reflect.Array && subsetKind != reflect.Slice && listKind != reflect.Map { return Fail(t, fmt.Sprintf("%q has an unsupported type %s", subset, subsetKind), msgAndArgs...) } + subsetValue := reflect.ValueOf(subset) + if subsetKind == reflect.Map && listKind == reflect.Map { + listValue := reflect.ValueOf(list) + subsetKeys := subsetValue.MapKeys() + + for i := 0; i < len(subsetKeys); i++ { + subsetKey := subsetKeys[i] + subsetElement := subsetValue.MapIndex(subsetKey).Interface() + listElement := listValue.MapIndex(subsetKey).Interface() + + if !ObjectsAreEqual(subsetElement, listElement) { + return Fail(t, fmt.Sprintf("\"%s\" does not contain \"%s\"", list, subsetElement), msgAndArgs...) + } + } + + return true + } + for i := 0; i < subsetValue.Len(); i++ { element := subsetValue.Index(i).Interface() ok, found := containsElement(list, element) @@ -859,7 +879,6 @@ func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) return Fail(t, "nil is the empty set which is a subset of every set", msgAndArgs...) } - subsetValue := reflect.ValueOf(subset) defer func() { if e := recover(); e != nil { ok = false @@ -869,14 +888,32 @@ func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) listKind := reflect.TypeOf(list).Kind() subsetKind := reflect.TypeOf(subset).Kind() - if listKind != reflect.Array && listKind != reflect.Slice { + if listKind != reflect.Array && listKind != reflect.Slice && listKind != reflect.Map { return Fail(t, fmt.Sprintf("%q has an unsupported type %s", list, listKind), msgAndArgs...) } - if subsetKind != reflect.Array && subsetKind != reflect.Slice { + if subsetKind != reflect.Array && subsetKind != reflect.Slice && listKind != reflect.Map { return Fail(t, fmt.Sprintf("%q has an unsupported type %s", subset, subsetKind), msgAndArgs...) } + subsetValue := reflect.ValueOf(subset) + if subsetKind == reflect.Map && listKind == reflect.Map { + listValue := reflect.ValueOf(list) + subsetKeys := subsetValue.MapKeys() + + for i := 0; i < len(subsetKeys); i++ { + subsetKey := subsetKeys[i] + subsetElement := subsetValue.MapIndex(subsetKey).Interface() + listElement := listValue.MapIndex(subsetKey).Interface() + + if !ObjectsAreEqual(subsetElement, listElement) { + return true + } + } + + return Fail(t, fmt.Sprintf("%q is a subset of %q", subset, list), msgAndArgs...) + } + for i := 0; i < subsetValue.Len(); i++ { element := subsetValue.Index(i).Interface() ok, found := containsElement(list, element) @@ -1109,6 +1146,27 @@ func WithinDuration(t TestingT, expected, actual time.Time, delta time.Duration, return true } +// WithinRange asserts that a time is within a time range (inclusive). +// +// assert.WithinRange(t, time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second)) +func WithinRange(t TestingT, actual, start, end time.Time, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + if end.Before(start) { + return Fail(t, "Start should be before end", msgAndArgs...) + } + + if actual.Before(start) { + return Fail(t, fmt.Sprintf("Time %v expected to be in time range %v to %v, but is before the range", actual, start, end), msgAndArgs...) + } else if actual.After(end) { + return Fail(t, fmt.Sprintf("Time %v expected to be in time range %v to %v, but is after the range", actual, start, end), msgAndArgs...) + } + + return true +} + func toFloat(x interface{}) (float64, bool) { var xf float64 xok := true diff --git a/vendor/github.com/willscott/go-nfs-client/LICENSE_BSD-2.txt b/vendor/github.com/willscott/go-nfs-client/LICENSE_BSD-2.txt new file mode 100644 index 00000000..e245edaf --- /dev/null +++ b/vendor/github.com/willscott/go-nfs-client/LICENSE_BSD-2.txt @@ -0,0 +1,16 @@ +Go-nfs-client version 0.1 + +Copyright 2017 VMware, Inc. All rights reserved + +The BSD-2 license (the License) set forth below applies to all parts of the Go-nfs-client +project. You may not use this file except in compliance with the License. + +BSD-2 License + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + diff --git a/vendor/github.com/willscott/go-nfs-client/NOTICE.txt b/vendor/github.com/willscott/go-nfs-client/NOTICE.txt new file mode 100644 index 00000000..3898e99f --- /dev/null +++ b/vendor/github.com/willscott/go-nfs-client/NOTICE.txt @@ -0,0 +1,8 @@ +Go-nfs-client version 0.1 + +Copyright (c) 2017 VMware, Inc. All Rights Reserved. + +This product is licensed to you under the BSD-2 license (the "License"). You may not use this product except in compliance with the BSD-2 License. + +This product may include a number of subcomponents with separate copyright notices and license terms. Your use of these subcomponents is subject to the terms and conditions of the subcomponent's license, as noted in the LICENSE file. + diff --git a/vendor/github.com/willscott/go-nfs-client/nfs/rpc/client.go b/vendor/github.com/willscott/go-nfs-client/nfs/rpc/client.go new file mode 100644 index 00000000..c70e5e18 --- /dev/null +++ b/vendor/github.com/willscott/go-nfs-client/nfs/rpc/client.go @@ -0,0 +1,305 @@ +// Copyright © 2017 VMware, Inc. All Rights Reserved. +// SPDX-License-Identifier: BSD-2-Clause +package rpc + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "io" + "math/rand" + "net" + "os" + "sync" + "sync/atomic" + "syscall" + "time" + + "github.com/willscott/go-nfs-client/nfs/util" + "github.com/willscott/go-nfs-client/nfs/xdr" +) + +const ( + MsgAccepted = iota + MsgDenied +) + +const ( + Success = iota + ProgUnavail + ProgMismatch + ProcUnavail + GarbageArgs + SystemErr +) + +const ( + RpcMismatch = iota +) + +var xid uint32 + +func init() { + // seed the XID (which is set by the client) + xid = rand.New(rand.NewSource(time.Now().UnixNano())).Uint32() +} + +var DefaultReadTimeout = time.Second * 5 + +type Client struct { + *tcpTransport + sync.Mutex + network string + addr string + privileged bool + + closed bool + replies map[uint32]chan io.ReadSeeker +} + +func isAddrInUse(err error) bool { + if er, ok := (err.(*net.OpError)); ok { + if syser, ok := er.Err.(*os.SyscallError); ok { + return syser.Err == syscall.EADDRINUSE + } + } + return false +} + +func DialTCP(network string, addr string, privileged bool) (*Client, error) { + c := &Client{ + network: network, + addr: addr, + privileged: privileged, + replies: make(map[uint32]chan io.ReadSeeker), + } + if t, err := c.connect(); err != nil { + return nil, err + } else { + c.tcpTransport = t + } + go c.receive() + return c, nil +} + +func (c *Client) pickLdr() *net.TCPAddr { + if c.privileged { + r1 := rand.New(rand.NewSource(time.Now().UnixNano())) + p := r1.Intn(1023) + 1 + return &net.TCPAddr{Port: p} + } + + r1 := rand.New(rand.NewSource(time.Now().UnixNano())) + p := r1.Intn(16383) + 49152 + return &net.TCPAddr{Port: p} +} + +type message struct { + Xid uint32 + Msgtype uint32 + Body interface{} +} + +func (c *Client) receive() { + for { + c.Lock() + if c.closed { + c.Unlock() + break + } + t := c.tcpTransport + c.Unlock() + if t == nil { + var err error + t, err = c.connect() + if err != nil { + time.Sleep(time.Millisecond * 100) + continue + } + c.Lock() + c.tcpTransport = t + c.Unlock() + } + res, err := t.recv() + if err != nil { + util.Debugf("nfs rpc: recv got error: %s", err) + c.disconnect() + continue + } + xid, err := xdr.ReadUint32(res) + if err != nil { + c.disconnect() + continue + } + + c.Lock() + r, ok := c.replies[xid] + c.Unlock() + if ok { + r <- res + } else { + util.Errorf("received unexpected response with xid: %x", xid) + } + } +} + +func (c *Client) connect() (*tcpTransport, error) { + a, err := net.ResolveTCPAddr(c.network, c.addr) + if err != nil { + return nil, err + } + conn, err := net.DialTCP(a.Network(), c.pickLdr(), a) + for err != nil && isAddrInUse(err) && c.privileged { + // bind error, pick a new port + conn, err = net.DialTCP(a.Network(), c.pickLdr(), a) + } + if err != nil { + return nil, err + } + util.Debugf("connected with local %s -> remote %s", conn.LocalAddr(), c.addr) + return &tcpTransport{ + r: bufio.NewReader(conn), + wc: conn, + }, nil +} + +func (c *Client) disconnect() { + c.Lock() + defer c.Unlock() + if c.tcpTransport != nil { + c.tcpTransport.Close() + c.tcpTransport = nil + } + for _, r := range c.replies { + close(r) + } +} + +func (c *Client) Close() { + c.Lock() + c.closed = true + c.Unlock() + c.disconnect() +} + +func (c *Client) Call(call interface{}) (io.ReadSeeker, error) { + msg := &message{ + Xid: atomic.AddUint32(&xid, 1), + Body: call, + } + w := new(bytes.Buffer) + if err := xdr.Write(w, msg); err != nil { + return nil, err + } + + retries := 0 + garbage := false +retry: + retries++ + if retries > 100 { + return nil, errors.New("disconnected") + } + + c.Lock() + if c.tcpTransport == nil { + c.Unlock() + time.Sleep(time.Millisecond * 100) + goto retry + } + if _, err := c.Write(w.Bytes()); err != nil { + c.Unlock() + c.disconnect() + goto retry + } + reply := make(chan io.ReadSeeker) + c.replies[msg.Xid] = reply + c.Unlock() + + var res io.ReadSeeker + select { + case res = <-reply: + case <-time.After(DefaultReadTimeout): + } + + c.Lock() + delete(c.replies, msg.Xid) + c.Unlock() + + if res == nil { + goto retry + } + + mtype, err := xdr.ReadUint32(res) + if err != nil { + return nil, err + } + if mtype != 1 { + return nil, fmt.Errorf("message as not a reply: %d", mtype) + } + + status, err := xdr.ReadUint32(res) + if err != nil { + return nil, err + } + + switch status { + case MsgAccepted: + + // padding + _, err = xdr.ReadUint32(res) + if err != nil { + panic(err.Error()) + } + + opaque_len, err := xdr.ReadUint32(res) + if err != nil { + panic(err.Error()) + } + + _, err = res.Seek(int64(opaque_len), io.SeekCurrent) + if err != nil { + panic(err.Error()) + } + + acceptStatus, _ := xdr.ReadUint32(res) + + switch acceptStatus { + case Success: + return res, nil + case ProgUnavail: + return nil, fmt.Errorf("rpc: PROG_UNAVAIL - server does not recognize the program number") + case ProgMismatch: + return nil, fmt.Errorf("rpc: PROG_MISMATCH - program version does not exist on the server") + case ProcUnavail: + return nil, fmt.Errorf("rpc: PROC_UNAVAIL - unrecognized procedure number") + case GarbageArgs: + // emulate Linux behaviour for GARBAGE_ARGS + if !garbage { + util.Debugf("Retrying on GARBAGE_ARGS per linux semantics") + garbage = true + goto retry + } + + return nil, fmt.Errorf("rpc: GARBAGE_ARGS - rpc arguments cannot be XDR decoded") + case SystemErr: + return nil, fmt.Errorf("rpc: SYSTEM_ERR - unknown error on server") + default: + return nil, fmt.Errorf("rpc: unknown accepted status error: %d", acceptStatus) + } + + case MsgDenied: + rejectStatus, _ := xdr.ReadUint32(res) + switch rejectStatus { + case RpcMismatch: + + default: + return nil, fmt.Errorf("rejectedStatus was not valid: %d", rejectStatus) + } + + default: + return nil, fmt.Errorf("rejectedStatus was not valid: %d", status) + } + + panic("unreachable") +} diff --git a/vendor/github.com/willscott/go-nfs-client/nfs/rpc/portmap.go b/vendor/github.com/willscott/go-nfs-client/nfs/rpc/portmap.go new file mode 100644 index 00000000..ce2b52c7 --- /dev/null +++ b/vendor/github.com/willscott/go-nfs-client/nfs/rpc/portmap.go @@ -0,0 +1,80 @@ +// Copyright © 2017 VMware, Inc. All Rights Reserved. +// SPDX-License-Identifier: BSD-2-Clause +// +package rpc + +import ( + "fmt" + + "github.com/willscott/go-nfs-client/nfs/xdr" +) + +// PORTMAP +// RFC 1057 Section A.1 + +const ( + PmapPort = 111 + PmapProg = 100000 + PmapVers = 2 + + PmapProcGetPort = 3 + + IPProtoTCP = 6 + IPProtoUDP = 17 +) + +type Header struct { + Rpcvers uint32 + Prog uint32 + Vers uint32 + Proc uint32 + Cred Auth + Verf Auth +} + +type Mapping struct { + Prog uint32 + Vers uint32 + Prot uint32 + Port uint32 +} + +type Portmapper struct { + *Client + host string +} + +func (p *Portmapper) Getport(mapping Mapping) (int, error) { + type getport struct { + Header + Mapping + } + msg := &getport{ + Header{ + Rpcvers: 2, + Prog: PmapProg, + Vers: PmapVers, + Proc: PmapProcGetPort, + Cred: AuthNull, + Verf: AuthNull, + }, + mapping, + } + res, err := p.Call(msg) + if err != nil { + return 0, err + } + port, err := xdr.ReadUint32(res) + if err != nil { + return int(port), err + } + return int(port), nil +} + +func DialPortmapper(net, host string) (*Portmapper, error) { + client, err := DialTCP(net, fmt.Sprintf("%s:%d", host, PmapPort), false) + if err != nil { + return nil, err + } + return &Portmapper{client, host}, nil +} diff --git a/vendor/github.com/willscott/go-nfs-client/nfs/rpc/rpc.go b/vendor/github.com/willscott/go-nfs-client/nfs/rpc/rpc.go new file mode 100644 index 00000000..55cf7c8b --- /dev/null +++ b/vendor/github.com/willscott/go-nfs-client/nfs/rpc/rpc.go @@ -0,0 +1,48 @@ +// Copyright © 2017 VMware, Inc. All Rights Reserved. +// SPDX-License-Identifier: BSD-2-Clause +// +package rpc + +import ( + "bytes" + "math/rand" + "time" + + "github.com/willscott/go-nfs-client/nfs/xdr" +) + +type Auth struct { + Flavor uint32 + Body []byte +} + +var AuthNull Auth + +type AuthUnix struct { + Stamp uint32 + Machinename string + Uid uint32 + Gid uint32 + GidLen uint32 + Gids uint32 +} + +func NewAuthUnix(machinename string, uid, gid uint32) *AuthUnix { + return &AuthUnix{ + Stamp: rand.New(rand.NewSource(time.Now().UnixNano())).Uint32(), + Machinename: machinename, + Uid: uid, + Gid: gid, + GidLen: 1, + } +} + +// Auth converts a into an Auth opaque struct +func (a AuthUnix) Auth() Auth { + w := new(bytes.Buffer) + xdr.Write(w, a) + return Auth{ + 1, + w.Bytes(), + } +} diff --git a/vendor/github.com/willscott/go-nfs-client/nfs/rpc/tcp.go b/vendor/github.com/willscott/go-nfs-client/nfs/rpc/tcp.go new file mode 100644 index 00000000..98b0f05b --- /dev/null +++ b/vendor/github.com/willscott/go-nfs-client/nfs/rpc/tcp.go @@ -0,0 +1,59 @@ +// Copyright © 2017 VMware, Inc. All Rights Reserved. +// SPDX-License-Identifier: BSD-2-Clause +// +package rpc + +import ( + "bytes" + "encoding/binary" + "io" + "net" + "time" +) + +type tcpTransport struct { + r io.Reader + wc net.Conn + timeout time.Duration +} + +// Get the response from the conn, buffer the contents, and return a reader to +// it. +func (t *tcpTransport) recv() (io.ReadSeeker, error) { + var hdr uint32 + if err := binary.Read(t.r, binary.BigEndian, &hdr); err != nil { + return nil, err + } + + buf := make([]byte, hdr&0x7fffffff) + if _, err := io.ReadFull(t.r, buf); err != nil { + return nil, err + } + + return bytes.NewReader(buf), nil +} + +func (t *tcpTransport) Write(buf []byte) (int, error) { + var hdr uint32 = uint32(len(buf)) | 0x80000000 + b := make([]byte, 4) + binary.BigEndian.PutUint32(b, hdr) + if t.timeout != 0 { + deadline := time.Now().Add(t.timeout) + t.wc.SetWriteDeadline(deadline) + } + n, err := t.wc.Write(append(b, buf...)) + + return n, err +} + +func (t *tcpTransport) Close() error { + return t.wc.Close() +} + +func (t *tcpTransport) SetTimeout(d time.Duration) { + t.timeout = d + if d == 0 { + var zeroTime time.Time + t.wc.SetDeadline(zeroTime) + } +} diff --git a/vendor/github.com/willscott/go-nfs-client/nfs/util/log.go b/vendor/github.com/willscott/go-nfs-client/nfs/util/log.go new file mode 100644 index 00000000..67e3dbaf --- /dev/null +++ b/vendor/github.com/willscott/go-nfs-client/nfs/util/log.go @@ -0,0 +1,69 @@ +// Copyright © 2017 VMware, Inc. All Rights Reserved. +// SPDX-License-Identifier: BSD-2-Clause +// +// Copyright 2016 VMware, Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package util + +import "log" + +var DefaultLogger Logger + +type Logger interface { + SetDebug(bool) + Errorf(format string, args ...interface{}) + Debugf(format string, args ...interface{}) + Infof(format string, args ...interface{}) +} + +func init() { + DefaultLogger = &logger{} +} + +type logger struct { + DebugLevel bool +} + +func (l *logger) SetDebug(enable bool) { + l.DebugLevel = enable +} + +func (l *logger) Errorf(format string, args ...interface{}) { + log.Printf(format, args...) +} + +func (l *logger) Debugf(format string, args ...interface{}) { + if !l.DebugLevel { + return + } + + log.Printf(format, args...) +} + +func (l *logger) Infof(format string, args ...interface{}) { + log.Printf(format, args...) +} + +func Errorf(format string, args ...interface{}) { + DefaultLogger.Errorf(format, args...) +} + +func Debugf(format string, args ...interface{}) { + DefaultLogger.Debugf(format, args...) +} + +func Infof(format string, args ...interface{}) { + DefaultLogger.Infof(format, args...) +} diff --git a/vendor/github.com/willscott/go-nfs-client/nfs/xdr/decode.go b/vendor/github.com/willscott/go-nfs-client/nfs/xdr/decode.go new file mode 100644 index 00000000..9754798e --- /dev/null +++ b/vendor/github.com/willscott/go-nfs-client/nfs/xdr/decode.go @@ -0,0 +1,56 @@ +// Copyright © 2017 VMware, Inc. All Rights Reserved. +// SPDX-License-Identifier: BSD-2-Clause +// +package xdr + +import ( + "io" + + xdr "github.com/rasky/go-xdr/xdr2" +) + +func Read(r io.Reader, val interface{}) error { + _, err := xdr.Unmarshal(r, val) + return err +} + +func ReadUint32(r io.Reader) (uint32, error) { + var n uint32 + if err := Read(r, &n); err != nil { + return n, err + } + + return n, nil +} + +func ReadOpaque(r io.Reader) ([]byte, error) { + length, err := ReadUint32(r) + if err != nil { + return nil, err + } + + buf := make([]byte, length) + if _, err = r.Read(buf); err != nil { + return nil, err + } + + return buf, nil +} + +func ReadUint32List(r io.Reader) ([]uint32, error) { + length, err := ReadUint32(r) + if err != nil { + return nil, err + } + + buf := make([]uint32, length) + + for i := 0; i < int(length); i++ { + buf[i], err = ReadUint32(r) + if err != nil { + return nil, err + } + } + + return buf, nil +} diff --git a/vendor/github.com/willscott/go-nfs-client/nfs/xdr/encode.go b/vendor/github.com/willscott/go-nfs-client/nfs/xdr/encode.go new file mode 100644 index 00000000..59854f03 --- /dev/null +++ b/vendor/github.com/willscott/go-nfs-client/nfs/xdr/encode.go @@ -0,0 +1,15 @@ +// Copyright © 2017 VMware, Inc. All Rights Reserved. +// SPDX-License-Identifier: BSD-2-Clause +// +package xdr + +import ( + "io" + + xdr "github.com/rasky/go-xdr/xdr2" +) + +func Write(w io.Writer, val interface{}) error { + _, err := xdr.Marshal(w, val) + return err +} diff --git a/vendor/modules.txt b/vendor/modules.txt index db93b8b5..3fc60ccb 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -20,7 +20,12 @@ github.com/armon/go-socks5 # github.com/blacknon/crypto11 v1.2.7 ## explicit; go 1.22 github.com/blacknon/crypto11 -# github.com/blacknon/go-sshlib v0.1.15 +# github.com/blacknon/go-nfs-sshlib v0.0.3 +## explicit; go 1.19 +github.com/blacknon/go-nfs-sshlib +github.com/blacknon/go-nfs-sshlib/file +github.com/blacknon/go-nfs-sshlib/helpers +# github.com/blacknon/go-sshlib v0.1.16 ## explicit; go 1.22.4 github.com/blacknon/go-sshlib # github.com/blacknon/go-x11auth v0.1.0 @@ -36,7 +41,10 @@ github.com/c-bata/go-prompt/internal/bisect github.com/c-bata/go-prompt/internal/debug github.com/c-bata/go-prompt/internal/strings github.com/c-bata/go-prompt/internal/term -# github.com/davecgh/go-spew v1.1.0 +# github.com/cyphar/filepath-securejoin v0.2.4 +## explicit; go 1.13 +github.com/cyphar/filepath-securejoin +# github.com/davecgh/go-spew v1.1.1 ## explicit github.com/davecgh/go-spew/spew # github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a @@ -48,6 +56,23 @@ github.com/disiqueira/gotree # github.com/dustin/go-humanize v1.0.0 ## explicit github.com/dustin/go-humanize +# github.com/go-git/go-billy/v5 v5.5.0 +## explicit; go 1.19 +github.com/go-git/go-billy/v5 +github.com/go-git/go-billy/v5/helper/chroot +github.com/go-git/go-billy/v5/helper/polyfill +github.com/go-git/go-billy/v5/helper/temporal +github.com/go-git/go-billy/v5/memfs +github.com/go-git/go-billy/v5/osfs +github.com/go-git/go-billy/v5/util +# github.com/google/uuid v1.6.0 +## explicit +github.com/google/uuid +# github.com/hashicorp/golang-lru/v2 v2.0.7 +## explicit; go 1.18 +github.com/hashicorp/golang-lru/v2 +github.com/hashicorp/golang-lru/v2/internal +github.com/hashicorp/golang-lru/v2/simplelru # github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 ## explicit github.com/kardianos/osext @@ -87,7 +112,7 @@ github.com/nsf/termbox-go # github.com/pkg/errors v0.9.1 ## explicit github.com/pkg/errors -# github.com/pkg/sftp v1.13.4 +# github.com/pkg/sftp v1.13.6 ## explicit; go 1.15 github.com/pkg/sftp github.com/pkg/sftp/internal/encoding/ssh/filexfer @@ -97,13 +122,16 @@ github.com/pkg/term/termios # github.com/pmezard/go-difflib v1.0.0 ## explicit github.com/pmezard/go-difflib/difflib +# github.com/rasky/go-xdr v0.0.0-20170124162913-1a41d1a06c93 +## explicit +github.com/rasky/go-xdr/xdr2 # github.com/rivo/uniseg v0.2.0 ## explicit; go 1.12 github.com/rivo/uniseg # github.com/sevlyar/go-daemon v0.1.5 ## explicit github.com/sevlyar/go-daemon -# github.com/stretchr/testify v1.7.1 +# github.com/stretchr/testify v1.8.0 ## explicit; go 1.13 github.com/stretchr/testify/assert # github.com/thales-e-security/pool v0.0.2 @@ -118,6 +146,11 @@ github.com/vbauerster/mpb github.com/vbauerster/mpb/cwriter github.com/vbauerster/mpb/decor github.com/vbauerster/mpb/internal +# github.com/willscott/go-nfs-client v0.0.0-20240104095149-b44639837b00 +## explicit; go 1.19 +github.com/willscott/go-nfs-client/nfs/rpc +github.com/willscott/go-nfs-client/nfs/util +github.com/willscott/go-nfs-client/nfs/xdr # golang.org/x/crypto v0.26.0 ## explicit; go 1.20 golang.org/x/crypto/blowfish