summaryrefslogtreecommitdiffhomepage
path: root/test/fuse/linux/fuse_base.cc
blob: 32221e7b6aed0c94579a495f8be275287e4cae94 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
// 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 "fuse_base.h"

#include <fcntl.h>
#include <linux/fuse.h>
#include <stdio.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <unistd.h>

#include <iostream>

#include "absl/strings/str_format.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "test/util/posix_error.h"
#include "test/util/test_util.h"

namespace gvisor {
namespace testing {

void FuseTest::SetUp() {
  MountFuse();
  SetUpFuseServer();
}

void FuseTest::TearDown() { UnmountFuse(); }

// Since CompareRequest is running in background thread, gTest assertions and
// expectations won't directly reflect the test result. However, the FUSE
// background server still connects to the same standard I/O as testing main
// thread. So EXPECT_XX can still be used to show different results. To
// ensure failed testing result is observable, return false and the result
// will be sent to test main thread via pipe.
bool FuseTest::CompareRequest(void* expected_mem, size_t expected_len,
                              void* real_mem, size_t real_len) {
  if (expected_len != real_len) return false;
  return memcmp(expected_mem, real_mem, expected_len) == 0;
}

// SetExpected is called by the testing main thread to set expected request-
// response pair of a single FUSE operation.
void FuseTest::SetExpected(struct iovec* iov_in, int iov_in_cnt,
                           struct iovec* iov_out, int iov_out_cnt) {
  EXPECT_THAT(RetryEINTR(writev)(set_expected_[1], iov_in, iov_in_cnt),
              SyscallSucceedsWithValue(::testing::Gt(0)));
  WaitCompleted();

  EXPECT_THAT(RetryEINTR(writev)(set_expected_[1], iov_out, iov_out_cnt),
              SyscallSucceedsWithValue(::testing::Gt(0)));
  WaitCompleted();
}

// WaitCompleted waits for the FUSE server to finish its job and check if it
// completes without errors.
void FuseTest::WaitCompleted() {
  char success;
  EXPECT_THAT(RetryEINTR(read)(done_[0], &success, sizeof(success)),
              SyscallSucceedsWithValue(1));
}

void FuseTest::MountFuse() {
  EXPECT_THAT(dev_fd_ = open("/dev/fuse", O_RDWR), SyscallSucceeds());

  std::string mount_opts = absl::StrFormat("fd=%d,%s", dev_fd_, kMountOpts);
  EXPECT_THAT(mount("fuse", kMountPoint, "fuse", MS_NODEV | MS_NOSUID,
                    mount_opts.c_str()),
              SyscallSucceedsWithValue(0));
}

void FuseTest::UnmountFuse() {
  EXPECT_THAT(umount(kMountPoint), SyscallSucceeds());
  // TODO(gvisor.dev/issue/3330): ensure the process is terminated successfully.
}

// ConsumeFuseInit consumes the first FUSE request and returns the
// corresponding PosixError.
PosixError FuseTest::ConsumeFuseInit() {
  RETURN_ERROR_IF_SYSCALL_FAIL(
      RetryEINTR(read)(dev_fd_, buf_.data(), buf_.size()));

  struct iovec iov_out[2];
  struct fuse_out_header out_header = {
      .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_init_out),
      .error = 0,
      .unique = 2,
  };
  // Returns a fake fuse_init_out with 7.0 version to avoid ECONNREFUSED
  // error in the initialization of FUSE connection.
  struct fuse_init_out out_payload = {
      .major = 7,
  };
  iov_out[0].iov_len = sizeof(out_header);
  iov_out[0].iov_base = &out_header;
  iov_out[1].iov_len = sizeof(out_payload);
  iov_out[1].iov_base = &out_payload;

  RETURN_ERROR_IF_SYSCALL_FAIL(RetryEINTR(writev)(dev_fd_, iov_out, 2));
  return NoError();
}

// ReceiveExpected reads 1 pair of expected fuse request-response `iovec`s
// from pipe and save them into member variables of this testing instance.
void FuseTest::ReceiveExpected() {
  // Set expected fuse_in request.
  EXPECT_THAT(len_in_ = RetryEINTR(read)(set_expected_[0], mem_in_.data(),
                                         mem_in_.size()),
              SyscallSucceedsWithValue(::testing::Gt(0)));
  MarkDone(len_in_ > 0);

  // Set expected fuse_out response.
  EXPECT_THAT(len_out_ = RetryEINTR(read)(set_expected_[0], mem_out_.data(),
                                          mem_out_.size()),
              SyscallSucceedsWithValue(::testing::Gt(0)));
  MarkDone(len_out_ > 0);
}

// MarkDone writes 1 byte of success indicator through pipe.
void FuseTest::MarkDone(bool success) {
  char data = success ? 1 : 0;
  EXPECT_THAT(RetryEINTR(write)(done_[1], &data, sizeof(data)),
              SyscallSucceedsWithValue(1));
}

// FuseLoop is the implementation of the fake FUSE server. Read from /dev/fuse,
// compare the request by CompareRequest (use derived function if specified),
// and write the expected response to /dev/fuse.
void FuseTest::FuseLoop() {
  bool success = true;
  ssize_t len = 0;
  while (true) {
    ReceiveExpected();

    EXPECT_THAT(len = RetryEINTR(read)(dev_fd_, buf_.data(), buf_.size()),
                SyscallSucceedsWithValue(len_in_));
    if (len != len_in_) success = false;

    if (!CompareRequest(buf_.data(), len_in_, mem_in_.data(), len_in_)) {
      std::cerr << "the FUSE request is not expected" << std::endl;
      success = false;
    }

    EXPECT_THAT(len = RetryEINTR(write)(dev_fd_, mem_out_.data(), len_out_),
                SyscallSucceedsWithValue(len_out_));
    if (len != len_out_) success = false;
    MarkDone(success);
  }
}

// SetUpFuseServer creates 2 pipes. First is for testing client to send the
// expected request-response pair, and the other acts as a checkpoint for the
// FUSE server to notify the client that it can proceed.
void FuseTest::SetUpFuseServer() {
  ASSERT_THAT(pipe(set_expected_), SyscallSucceedsWithValue(0));
  ASSERT_THAT(pipe(done_), SyscallSucceedsWithValue(0));

  switch (fork()) {
    case -1:
      GTEST_FAIL();
      return;
    case 0:
      break;
    default:
      ASSERT_THAT(close(set_expected_[0]), SyscallSucceedsWithValue(0));
      ASSERT_THAT(close(done_[1]), SyscallSucceedsWithValue(0));
      WaitCompleted();
      return;
  }

  ASSERT_THAT(close(set_expected_[1]), SyscallSucceedsWithValue(0));
  ASSERT_THAT(close(done_[0]), SyscallSucceedsWithValue(0));

  MarkDone(ConsumeFuseInit().ok());

  FuseLoop();
  _exit(0);
}

// GetPayloadSize is a helper function to get the number of bytes of a
// specific FUSE operation struct.
size_t FuseTest::GetPayloadSize(uint32_t opcode, bool in) {
  switch (opcode) {
    case FUSE_INIT:
      return in ? sizeof(struct fuse_init_in) : sizeof(struct fuse_init_out);
    default:
      break;
  }
  return 0;
}

}  // namespace testing
}  // namespace gvisor