github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/test/fuse/linux/readdir_test.cc (about)

     1  // Copyright 2020 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  #include <dirent.h>
    16  #include <errno.h>
    17  #include <fcntl.h>
    18  #include <linux/fuse.h>
    19  #include <linux/unistd.h>
    20  #include <sys/stat.h>
    21  #include <sys/statfs.h>
    22  #include <sys/types.h>
    23  #include <unistd.h>
    24  
    25  #include <string>
    26  
    27  #include "gtest/gtest.h"
    28  #include "test/fuse/linux/fuse_base.h"
    29  #include "test/util/fuse_util.h"
    30  #include "test/util/test_util.h"
    31  
    32  #define FUSE_NAME_OFFSET offsetof(struct fuse_dirent, name)
    33  #define FUSE_DIRENT_ALIGN(x) \
    34    (((x) + sizeof(uint64_t) - 1) & ~(sizeof(uint64_t) - 1))
    35  #define FUSE_DIRENT_SIZE(d) FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET + (d)->namelen)
    36  
    37  namespace gvisor {
    38  namespace testing {
    39  
    40  namespace {
    41  
    42  class ReaddirTest : public FuseTest {
    43   public:
    44    void fill_fuse_dirent(char *buf, const char *name, uint64_t ino) {
    45      size_t namelen = strlen(name);
    46      size_t entlen = FUSE_NAME_OFFSET + namelen;
    47      size_t entlen_padded = FUSE_DIRENT_ALIGN(entlen);
    48      struct fuse_dirent *dirent;
    49  
    50      dirent = reinterpret_cast<struct fuse_dirent *>(buf);
    51      dirent->ino = ino;
    52      dirent->namelen = namelen;
    53      memcpy(dirent->name, name, namelen);
    54      memset(dirent->name + namelen, 0, entlen_padded - entlen);
    55    }
    56  
    57   protected:
    58    const std::string test_dir_name_ = "test_dir";
    59  };
    60  
    61  TEST_F(ReaddirTest, SingleEntry) {
    62    const std::string test_dir_path =
    63        JoinPath(mount_point_.path().c_str(), test_dir_name_);
    64  
    65    const uint64_t ino_dir = 1024;
    66    // We need to make sure the test dir is a directory that can be found.
    67    mode_t expected_mode =
    68        S_IFDIR | S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
    69    struct fuse_attr dir_attr = {
    70        .ino = ino_dir,
    71        .size = 512,
    72        .blocks = 4,
    73        .mode = expected_mode,
    74        .blksize = 4096,
    75    };
    76  
    77    // We need to make sure the test dir is a directory that can be found.
    78    struct fuse_out_header lookup_header = {
    79        .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_entry_out),
    80    };
    81    struct fuse_entry_out lookup_payload = {
    82        .nodeid = 1,
    83        .entry_valid = true,
    84        .attr_valid = true,
    85        .attr = dir_attr,
    86    };
    87  
    88    struct fuse_out_header open_header = {
    89        .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_open_out),
    90    };
    91    struct fuse_open_out open_payload = {
    92        .fh = 1,
    93    };
    94    auto iov_out = FuseGenerateIovecs(lookup_header, lookup_payload);
    95    SetServerResponse(FUSE_LOOKUP, iov_out);
    96  
    97    iov_out = FuseGenerateIovecs(open_header, open_payload);
    98    SetServerResponse(FUSE_OPENDIR, iov_out);
    99  
   100    FileDescriptor fd =
   101        ASSERT_NO_ERRNO_AND_VALUE(Open(test_dir_path.c_str(), O_RDONLY));
   102  
   103    // The open command makes two syscalls. Lookup the dir file and open.
   104    // We don't need to inspect those headers in this test.
   105    SkipServerActualRequest();  // LOOKUP.
   106    SkipServerActualRequest();  // OPENDIR.
   107  
   108    // Readdir test code.
   109    std::string dot = ".";
   110    std::string dot_dot = "..";
   111    std::string test_file = "testFile";
   112  
   113    // Figure out how many dirents to send over and allocate them appropriately.
   114    // Each dirent has a dynamic name and a static metadata part. The dirent size
   115    // is aligned to being a multiple of 8.
   116    size_t dot_file_dirent_size =
   117        FUSE_DIRENT_ALIGN(dot.length() + FUSE_NAME_OFFSET);
   118    size_t dot_dot_file_dirent_size =
   119        FUSE_DIRENT_ALIGN(dot_dot.length() + FUSE_NAME_OFFSET);
   120    size_t test_file_dirent_size =
   121        FUSE_DIRENT_ALIGN(test_file.length() + FUSE_NAME_OFFSET);
   122  
   123    // Create an appropriately sized payload.
   124    size_t readdir_payload_size =
   125        test_file_dirent_size + dot_file_dirent_size + dot_dot_file_dirent_size;
   126    std::vector<char> readdir_payload_vec(readdir_payload_size);
   127    char *readdir_payload = readdir_payload_vec.data();
   128  
   129    // Use fake ino for other directories.
   130    fill_fuse_dirent(readdir_payload, dot.c_str(), ino_dir - 2);
   131    fill_fuse_dirent(readdir_payload + dot_file_dirent_size, dot_dot.c_str(),
   132                     ino_dir - 1);
   133    fill_fuse_dirent(
   134        readdir_payload + dot_file_dirent_size + dot_dot_file_dirent_size,
   135        test_file.c_str(), ino_dir);
   136  
   137    struct fuse_out_header readdir_header = {
   138        .len = uint32_t(sizeof(struct fuse_out_header) + readdir_payload_size),
   139    };
   140    struct fuse_out_header readdir_header_break = {
   141        .len = uint32_t(sizeof(struct fuse_out_header)),
   142    };
   143  
   144    iov_out = FuseGenerateIovecs(readdir_header, readdir_payload_vec);
   145    SetServerResponse(FUSE_READDIR, iov_out);
   146  
   147    iov_out = FuseGenerateIovecs(readdir_header_break);
   148    SetServerResponse(FUSE_READDIR, iov_out);
   149  
   150    std::vector<char> buf(4090, 0);
   151    int nread, off = 0, i = 0;
   152    EXPECT_THAT(
   153        nread = syscall(__NR_getdents64, fd.get(), buf.data(), buf.size()),
   154        SyscallSucceeds());
   155    for (; off < nread;) {
   156      struct dirent64 *ent = (struct dirent64 *)(buf.data() + off);
   157      off += ent->d_reclen;
   158      switch (i++) {
   159        case 0:
   160          EXPECT_EQ(std::string(ent->d_name), dot);
   161          break;
   162        case 1:
   163          EXPECT_EQ(std::string(ent->d_name), dot_dot);
   164          break;
   165        case 2:
   166          EXPECT_EQ(std::string(ent->d_name), test_file);
   167          break;
   168      }
   169    }
   170  
   171    EXPECT_THAT(
   172        nread = syscall(__NR_getdents64, fd.get(), buf.data(), buf.size()),
   173        SyscallSucceedsWithValue(0));
   174  
   175    SkipServerActualRequest();  // READDIR.
   176    SkipServerActualRequest();  // READDIR with no data.
   177  
   178    // Clean up.
   179    fd.reset(-1);
   180  
   181    struct fuse_in_header in_header;
   182    struct fuse_release_in in_payload;
   183  
   184    auto iov_in = FuseGenerateIovecs(in_header, in_payload);
   185    GetServerActualRequest(iov_in);
   186    EXPECT_EQ(in_header.len, sizeof(in_header) + sizeof(in_payload));
   187    EXPECT_EQ(in_header.opcode, FUSE_RELEASEDIR);
   188  }
   189  
   190  }  // namespace
   191  
   192  }  // namespace testing
   193  }  // namespace gvisor