mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
@@ -483,3 +483,14 @@ func (r *FUSESymLinkIn) MarshalUnsafe(buf []byte) {
|
||||
func (r *FUSESymLinkIn) SizeBytes() int {
|
||||
return len(r.Name) + len(r.Target) + 2
|
||||
}
|
||||
|
||||
// FUSEEmptyIn is used by operations without request body.
|
||||
type FUSEEmptyIn struct{ marshal.StubMarshallable }
|
||||
|
||||
// MarshalUnsafe do nothing for marshal.
|
||||
func (r *FUSEEmptyIn) MarshalUnsafe(buf []byte) {}
|
||||
|
||||
// SizeBytes is 0 for empty request.
|
||||
func (r *FUSEEmptyIn) SizeBytes() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -225,6 +225,9 @@ type inode struct {
|
||||
|
||||
// version of the inode.
|
||||
version uint64
|
||||
|
||||
// link is result of following a symbolic link.
|
||||
link string
|
||||
}
|
||||
|
||||
func (fs *filesystem) newRootInode(creds *auth.Credentials, mode linux.FileMode) *kernfs.Dentry {
|
||||
@@ -406,6 +409,33 @@ func (i *inode) newEntry(ctx context.Context, name string, fileType linux.FileMo
|
||||
return child.VFSDentry(), nil
|
||||
}
|
||||
|
||||
// Readlink implements kernfs.Inode.Readlink.
|
||||
func (i *inode) Readlink(ctx context.Context, mnt *vfs.Mount) (string, error) {
|
||||
if i.Mode().FileType()&linux.S_IFLNK == 0 {
|
||||
return "", syserror.EINVAL
|
||||
}
|
||||
if i.link == "" {
|
||||
kernelTask := kernel.TaskFromContext(ctx)
|
||||
if kernelTask == nil {
|
||||
log.Warningf("fusefs.Inode.Readlink: couldn't get kernel task from context")
|
||||
return "", syserror.EINVAL
|
||||
}
|
||||
req, err := i.fs.conn.NewRequest(auth.CredentialsFromContext(ctx), uint32(kernelTask.ThreadID()), i.NodeID, linux.FUSE_READLINK, &linux.FUSEEmptyIn{})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
res, err := i.fs.conn.Call(kernelTask, req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
i.link = string(res.data[res.hdr.SizeBytes():])
|
||||
if !mnt.Options().ReadOnly {
|
||||
i.attributeTime = 0
|
||||
}
|
||||
}
|
||||
return i.link, nil
|
||||
}
|
||||
|
||||
// statFromFUSEAttr makes attributes from linux.FUSEAttr to linux.Statx. The
|
||||
// opts.Sync attribute is ignored since the synchronization is handled by the
|
||||
// FUSE server.
|
||||
|
||||
@@ -548,7 +548,7 @@ func (fs *Filesystem) ReadlinkAt(ctx context.Context, rp *vfs.ResolvingPath) (st
|
||||
if !d.Impl().(*Dentry).isSymlink() {
|
||||
return "", syserror.EINVAL
|
||||
}
|
||||
return inode.Readlink(ctx)
|
||||
return inode.Readlink(ctx, rp.Mount())
|
||||
}
|
||||
|
||||
// RenameAt implements vfs.FilesystemImpl.RenameAt.
|
||||
|
||||
@@ -172,7 +172,7 @@ func (InodeNoDynamicLookup) Valid(ctx context.Context) bool {
|
||||
type InodeNotSymlink struct{}
|
||||
|
||||
// Readlink implements Inode.Readlink.
|
||||
func (InodeNotSymlink) Readlink(context.Context) (string, error) {
|
||||
func (InodeNotSymlink) Readlink(context.Context, *vfs.Mount) (string, error) {
|
||||
return "", syserror.EINVAL
|
||||
}
|
||||
|
||||
|
||||
@@ -437,7 +437,7 @@ type inodeDynamicLookup interface {
|
||||
type inodeSymlink interface {
|
||||
// Readlink returns the target of a symbolic link. If an inode is not a
|
||||
// symlink, the implementation should return EINVAL.
|
||||
Readlink(ctx context.Context) (string, error)
|
||||
Readlink(ctx context.Context, mnt *vfs.Mount) (string, error)
|
||||
|
||||
// Getlink returns the target of a symbolic link, as used by path
|
||||
// resolution:
|
||||
|
||||
@@ -52,7 +52,7 @@ func (s *StaticSymlink) Init(creds *auth.Credentials, devMajor uint32, devMinor
|
||||
}
|
||||
|
||||
// Readlink implements Inode.
|
||||
func (s *StaticSymlink) Readlink(_ context.Context) (string, error) {
|
||||
func (s *StaticSymlink) Readlink(_ context.Context, _ *vfs.Mount) (string, error) {
|
||||
return s.target, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -214,7 +214,7 @@ func (fs *filesystem) newFDSymlink(task *kernel.Task, fd int32, ino uint64) *ker
|
||||
return d
|
||||
}
|
||||
|
||||
func (s *fdSymlink) Readlink(ctx context.Context) (string, error) {
|
||||
func (s *fdSymlink) Readlink(ctx context.Context, _ *vfs.Mount) (string, error) {
|
||||
file, _ := getTaskFD(s.task, s.fd)
|
||||
if file == nil {
|
||||
return "", syserror.ENOENT
|
||||
|
||||
@@ -668,7 +668,7 @@ func (fs *filesystem) newExeSymlink(task *kernel.Task, ino uint64) *kernfs.Dentr
|
||||
}
|
||||
|
||||
// Readlink implements kernfs.Inode.
|
||||
func (s *exeSymlink) Readlink(ctx context.Context) (string, error) {
|
||||
func (s *exeSymlink) Readlink(ctx context.Context, _ *vfs.Mount) (string, error) {
|
||||
if !kernel.ContextCanTrace(ctx, s.task, false) {
|
||||
return "", syserror.EACCES
|
||||
}
|
||||
@@ -808,11 +808,11 @@ func (fs *filesystem) newNamespaceSymlink(task *kernel.Task, ino uint64, ns stri
|
||||
}
|
||||
|
||||
// Readlink implements Inode.
|
||||
func (s *namespaceSymlink) Readlink(ctx context.Context) (string, error) {
|
||||
func (s *namespaceSymlink) Readlink(ctx context.Context, mnt *vfs.Mount) (string, error) {
|
||||
if err := checkTaskState(s.task); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return s.StaticSymlink.Readlink(ctx)
|
||||
return s.StaticSymlink.Readlink(ctx, mnt)
|
||||
}
|
||||
|
||||
// Getlink implements Inode.Getlink.
|
||||
|
||||
@@ -51,7 +51,7 @@ func (fs *filesystem) newSelfSymlink(creds *auth.Credentials, ino uint64, pidns
|
||||
return d
|
||||
}
|
||||
|
||||
func (s *selfSymlink) Readlink(ctx context.Context) (string, error) {
|
||||
func (s *selfSymlink) Readlink(ctx context.Context, _ *vfs.Mount) (string, error) {
|
||||
t := kernel.TaskFromContext(ctx)
|
||||
if t == nil {
|
||||
// Who is reading this link?
|
||||
@@ -64,8 +64,8 @@ func (s *selfSymlink) Readlink(ctx context.Context) (string, error) {
|
||||
return strconv.FormatUint(uint64(tgid), 10), nil
|
||||
}
|
||||
|
||||
func (s *selfSymlink) Getlink(ctx context.Context, _ *vfs.Mount) (vfs.VirtualDentry, string, error) {
|
||||
target, err := s.Readlink(ctx)
|
||||
func (s *selfSymlink) Getlink(ctx context.Context, mnt *vfs.Mount) (vfs.VirtualDentry, string, error) {
|
||||
target, err := s.Readlink(ctx, mnt)
|
||||
return vfs.VirtualDentry{}, target, err
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ func (fs *filesystem) newThreadSelfSymlink(creds *auth.Credentials, ino uint64,
|
||||
return d
|
||||
}
|
||||
|
||||
func (s *threadSelfSymlink) Readlink(ctx context.Context) (string, error) {
|
||||
func (s *threadSelfSymlink) Readlink(ctx context.Context, _ *vfs.Mount) (string, error) {
|
||||
t := kernel.TaskFromContext(ctx)
|
||||
if t == nil {
|
||||
// Who is reading this link?
|
||||
@@ -108,8 +108,8 @@ func (s *threadSelfSymlink) Readlink(ctx context.Context) (string, error) {
|
||||
return fmt.Sprintf("%d/task/%d", tgid, tid), nil
|
||||
}
|
||||
|
||||
func (s *threadSelfSymlink) Getlink(ctx context.Context, _ *vfs.Mount) (vfs.VirtualDentry, string, error) {
|
||||
target, err := s.Readlink(ctx)
|
||||
func (s *threadSelfSymlink) Getlink(ctx context.Context, mnt *vfs.Mount) (vfs.VirtualDentry, string, error) {
|
||||
target, err := s.Readlink(ctx, mnt)
|
||||
return vfs.VirtualDentry{}, target, err
|
||||
}
|
||||
|
||||
|
||||
@@ -26,3 +26,8 @@ syscall_test(
|
||||
fuse = "True",
|
||||
test = "//test/fuse/linux:symlink_test",
|
||||
)
|
||||
|
||||
syscall_test(
|
||||
fuse = "True",
|
||||
test = "//test/fuse/linux:readlink_test",
|
||||
)
|
||||
|
||||
@@ -71,6 +71,19 @@ cc_binary(
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "readlink_test",
|
||||
testonly = 1,
|
||||
srcs = ["readlink_test.cc"],
|
||||
deps = [
|
||||
gtest,
|
||||
":fuse_base",
|
||||
"//test/util:fuse_util",
|
||||
"//test/util:test_main",
|
||||
"//test/util:test_util",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "fuse_base",
|
||||
testonly = 1,
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
// Copyright 2020 The gVisor 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.
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <linux/fuse.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/statfs.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "test/fuse/linux/fuse_base.h"
|
||||
#include "test/util/fuse_util.h"
|
||||
#include "test/util/test_util.h"
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
namespace {
|
||||
|
||||
class ReadlinkTest : public FuseTest {
|
||||
protected:
|
||||
const std::string test_file_ = "test_file_";
|
||||
const mode_t perms_ = S_IRWXU | S_IRWXG | S_IRWXO;
|
||||
};
|
||||
|
||||
TEST_F(ReadlinkTest, ReadSymLink) {
|
||||
const std::string symlink_path =
|
||||
JoinPath(mount_point_.path().c_str(), test_file_);
|
||||
SetServerInodeLookup(test_file_, S_IFLNK | perms_);
|
||||
|
||||
struct fuse_out_header out_header = {
|
||||
.len = static_cast<uint32_t>(sizeof(struct fuse_out_header)) +
|
||||
static_cast<uint32_t>(test_file_.length()) + 1,
|
||||
};
|
||||
std::string link = test_file_;
|
||||
auto iov_out = FuseGenerateIovecs(out_header, link);
|
||||
SetServerResponse(FUSE_READLINK, iov_out);
|
||||
const std::string actual_link =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(ReadLink(symlink_path));
|
||||
|
||||
struct fuse_in_header in_header;
|
||||
auto iov_in = FuseGenerateIovecs(in_header);
|
||||
GetServerActualRequest(iov_in);
|
||||
|
||||
EXPECT_EQ(in_header.len, sizeof(in_header));
|
||||
EXPECT_EQ(in_header.opcode, FUSE_READLINK);
|
||||
EXPECT_EQ(0, memcmp(actual_link.c_str(), link.data(), link.size()));
|
||||
|
||||
// next readlink should have link cached, so shouldn't have new request to
|
||||
// server.
|
||||
uint32_t recieved_before = GetServerTotalReceivedBytes();
|
||||
ASSERT_NO_ERRNO(ReadLink(symlink_path));
|
||||
EXPECT_EQ(GetServerTotalReceivedBytes(), recieved_before);
|
||||
}
|
||||
|
||||
TEST_F(ReadlinkTest, NotSymlink) {
|
||||
const std::string test_file_path =
|
||||
JoinPath(mount_point_.path().c_str(), test_file_);
|
||||
SetServerInodeLookup(test_file_, S_IFREG | perms_);
|
||||
|
||||
std::vector<char> buf(PATH_MAX + 1);
|
||||
ASSERT_THAT(readlink(test_file_path.c_str(), buf.data(), PATH_MAX),
|
||||
SyscallFailsWithErrno(EINVAL));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
Reference in New Issue
Block a user