Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/tests/sys/fs/fusefs/interrupt.cc
39537 views
1
/*-
2
* SPDX-License-Identifier: BSD-2-Clause
3
*
4
* Copyright (c) 2019 The FreeBSD Foundation
5
*
6
* This software was developed by BFF Storage Systems, LLC under sponsorship
7
* from the FreeBSD Foundation.
8
*
9
* Redistribution and use in source and binary forms, with or without
10
* modification, are permitted provided that the following conditions
11
* are met:
12
* 1. Redistributions of source code must retain the above copyright
13
* notice, this list of conditions and the following disclaimer.
14
* 2. Redistributions in binary form must reproduce the above copyright
15
* notice, this list of conditions and the following disclaimer in the
16
* documentation and/or other materials provided with the distribution.
17
*
18
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28
* SUCH DAMAGE.
29
*/
30
31
extern "C" {
32
#include <sys/types.h>
33
#include <sys/extattr.h>
34
#include <sys/mman.h>
35
#include <sys/wait.h>
36
#include <fcntl.h>
37
#include <pthread.h>
38
#include <semaphore.h>
39
#include <signal.h>
40
}
41
42
#include "mockfs.hh"
43
#include "utils.hh"
44
45
using namespace testing;
46
47
/* Initial size of files used by these tests */
48
const off_t FILESIZE = 1000;
49
/* Access mode used by all directories in these tests */
50
const mode_t MODE = 0755;
51
const char FULLDIRPATH0[] = "mountpoint/some_dir";
52
const char RELDIRPATH0[] = "some_dir";
53
const char FULLDIRPATH1[] = "mountpoint/other_dir";
54
const char RELDIRPATH1[] = "other_dir";
55
56
static sem_t *blocked_semaphore;
57
static sem_t *signaled_semaphore;
58
59
static bool killer_should_sleep = false;
60
61
/* Don't do anything; all we care about is that the syscall gets interrupted */
62
void sigusr2_handler(int __unused sig) {
63
if (verbosity > 1) {
64
printf("Signaled! thread %p\n", pthread_self());
65
}
66
67
}
68
69
void* killer(void* target) {
70
/* Wait until the main thread is blocked in fdisp_wait_answ */
71
if (killer_should_sleep)
72
nap();
73
else
74
sem_wait(blocked_semaphore);
75
if (verbosity > 1)
76
printf("Signalling! thread %p\n", target);
77
pthread_kill((pthread_t)target, SIGUSR2);
78
if (signaled_semaphore != NULL)
79
sem_post(signaled_semaphore);
80
81
return(NULL);
82
}
83
84
class Interrupt: public FuseTest {
85
public:
86
pthread_t m_child;
87
88
Interrupt(): m_child(NULL) {};
89
90
void expect_lookup(const char *relpath, uint64_t ino)
91
{
92
FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, FILESIZE, 1);
93
}
94
95
/*
96
* Expect a FUSE_MKDIR but don't reply. Instead, just record the unique value
97
* to the provided pointer
98
*/
99
void expect_mkdir(uint64_t *mkdir_unique)
100
{
101
EXPECT_CALL(*m_mock, process(
102
ResultOf([=](auto in) {
103
return (in.header.opcode == FUSE_MKDIR);
104
}, Eq(true)),
105
_)
106
).WillOnce(Invoke([=](auto in, auto &out __unused) {
107
*mkdir_unique = in.header.unique;
108
sem_post(blocked_semaphore);
109
}));
110
}
111
112
/*
113
* Expect a FUSE_READ but don't reply. Instead, just record the unique value
114
* to the provided pointer
115
*/
116
void expect_read(uint64_t ino, uint64_t *read_unique)
117
{
118
EXPECT_CALL(*m_mock, process(
119
ResultOf([=](auto in) {
120
return (in.header.opcode == FUSE_READ &&
121
in.header.nodeid == ino);
122
}, Eq(true)),
123
_)
124
).WillOnce(Invoke([=](auto in, auto &out __unused) {
125
*read_unique = in.header.unique;
126
sem_post(blocked_semaphore);
127
}));
128
}
129
130
/*
131
* Expect a FUSE_WRITE but don't reply. Instead, just record the unique value
132
* to the provided pointer
133
*/
134
void expect_write(uint64_t ino, uint64_t *write_unique)
135
{
136
EXPECT_CALL(*m_mock, process(
137
ResultOf([=](auto in) {
138
return (in.header.opcode == FUSE_WRITE &&
139
in.header.nodeid == ino);
140
}, Eq(true)),
141
_)
142
).WillOnce(Invoke([=](auto in, auto &out __unused) {
143
*write_unique = in.header.unique;
144
sem_post(blocked_semaphore);
145
}));
146
}
147
148
void setup_interruptor(pthread_t target, bool sleep = false)
149
{
150
ASSERT_NE(SIG_ERR, signal(SIGUSR2, sigusr2_handler)) << strerror(errno);
151
killer_should_sleep = sleep;
152
ASSERT_EQ(0, pthread_create(&m_child, NULL, killer, (void*)target))
153
<< strerror(errno);
154
}
155
156
void SetUp() {
157
const int mprot = PROT_READ | PROT_WRITE;
158
const int mflags = MAP_ANON | MAP_SHARED;
159
160
signaled_semaphore = NULL;
161
162
blocked_semaphore = (sem_t*)mmap(NULL, sizeof(*blocked_semaphore),
163
mprot, mflags, -1, 0);
164
ASSERT_NE(MAP_FAILED, blocked_semaphore) << strerror(errno);
165
ASSERT_EQ(0, sem_init(blocked_semaphore, 1, 0)) << strerror(errno);
166
ASSERT_EQ(0, siginterrupt(SIGUSR2, 1));
167
168
FuseTest::SetUp();
169
}
170
171
void TearDown() {
172
struct sigaction sa;
173
174
if (m_child != NULL) {
175
pthread_join(m_child, NULL);
176
}
177
bzero(&sa, sizeof(sa));
178
sa.sa_handler = SIG_DFL;
179
sigaction(SIGUSR2, &sa, NULL);
180
181
sem_destroy(blocked_semaphore);
182
munmap(blocked_semaphore, sizeof(*blocked_semaphore));
183
184
FuseTest::TearDown();
185
}
186
};
187
188
class Intr: public Interrupt {};
189
190
class Nointr: public Interrupt {
191
void SetUp() {
192
m_nointr = true;
193
Interrupt::SetUp();
194
}
195
};
196
197
static void* mkdir0(void* arg __unused) {
198
ssize_t r;
199
200
r = mkdir(FULLDIRPATH0, MODE);
201
if (r >= 0)
202
return 0;
203
else
204
return (void*)(intptr_t)errno;
205
}
206
207
static void* read1(void* arg) {
208
const size_t bufsize = FILESIZE;
209
char buf[bufsize];
210
int fd = (int)(intptr_t)arg;
211
ssize_t r;
212
213
r = read(fd, buf, bufsize);
214
if (r >= 0)
215
return 0;
216
else
217
return (void*)(intptr_t)errno;
218
}
219
220
/*
221
* An interrupt operation that gets received after the original command is
222
* complete should generate an EAGAIN response.
223
*/
224
/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
225
TEST_F(Intr, already_complete)
226
{
227
uint64_t ino = 42;
228
pthread_t self;
229
uint64_t mkdir_unique = 0;
230
Sequence seq;
231
232
self = pthread_self();
233
234
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
235
.InSequence(seq)
236
.WillOnce(Invoke(ReturnErrno(ENOENT)));
237
expect_mkdir(&mkdir_unique);
238
EXPECT_CALL(*m_mock, process(
239
ResultOf([&](auto in) {
240
return (in.header.opcode == FUSE_INTERRUPT &&
241
in.body.interrupt.unique == mkdir_unique);
242
}, Eq(true)),
243
_)
244
).WillOnce(Invoke([&](auto in, auto &out) {
245
// First complete the mkdir request
246
std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
247
out0->header.unique = mkdir_unique;
248
SET_OUT_HEADER_LEN(*out0, entry);
249
out0->body.create.entry.attr.mode = S_IFDIR | MODE;
250
out0->body.create.entry.nodeid = ino;
251
out.push_back(std::move(out0));
252
253
// Then, respond EAGAIN to the interrupt request
254
std::unique_ptr<mockfs_buf_out> out1(new mockfs_buf_out);
255
out1->header.unique = in.header.unique;
256
out1->header.error = -EAGAIN;
257
out1->header.len = sizeof(out1->header);
258
out.push_back(std::move(out1));
259
}));
260
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
261
.InSequence(seq)
262
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
263
SET_OUT_HEADER_LEN(out, entry);
264
out.body.entry.attr.mode = S_IFDIR | MODE;
265
out.body.entry.nodeid = ino;
266
out.body.entry.attr.nlink = 2;
267
})));
268
269
setup_interruptor(self);
270
EXPECT_EQ(0, mkdir(FULLDIRPATH0, MODE)) << strerror(errno);
271
/*
272
* The final syscall simply ensures that the test's main thread doesn't
273
* end before the daemon finishes responding to the FUSE_INTERRUPT.
274
*/
275
EXPECT_EQ(0, access(FULLDIRPATH0, F_OK)) << strerror(errno);
276
}
277
278
/*
279
* If a FUSE file system returns ENOSYS for a FUSE_INTERRUPT operation, the
280
* kernel should not attempt to interrupt any other operations on that mount
281
* point.
282
*/
283
TEST_F(Intr, enosys)
284
{
285
uint64_t ino0 = 42, ino1 = 43;;
286
uint64_t mkdir_unique;
287
pthread_t self, th0;
288
sem_t sem0, sem1;
289
void *thr0_value;
290
Sequence seq;
291
292
self = pthread_self();
293
ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
294
ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno);
295
296
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH1)
297
.WillOnce(Invoke(ReturnErrno(ENOENT)));
298
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
299
.WillOnce(Invoke(ReturnErrno(ENOENT)));
300
expect_mkdir(&mkdir_unique);
301
EXPECT_CALL(*m_mock, process(
302
ResultOf([&](auto in) {
303
return (in.header.opcode == FUSE_INTERRUPT &&
304
in.body.interrupt.unique == mkdir_unique);
305
}, Eq(true)),
306
_)
307
).InSequence(seq)
308
.WillOnce(Invoke([&](auto in, auto &out) {
309
// reject FUSE_INTERRUPT and respond to the FUSE_MKDIR
310
std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
311
std::unique_ptr<mockfs_buf_out> out1(new mockfs_buf_out);
312
313
out0->header.unique = in.header.unique;
314
out0->header.error = -ENOSYS;
315
out0->header.len = sizeof(out0->header);
316
out.push_back(std::move(out0));
317
318
SET_OUT_HEADER_LEN(*out1, entry);
319
out1->body.create.entry.attr.mode = S_IFDIR | MODE;
320
out1->body.create.entry.nodeid = ino1;
321
out1->header.unique = mkdir_unique;
322
out.push_back(std::move(out1));
323
}));
324
EXPECT_CALL(*m_mock, process(
325
ResultOf([&](auto in) {
326
return (in.header.opcode == FUSE_MKDIR);
327
}, Eq(true)),
328
_)
329
).InSequence(seq)
330
.WillOnce(Invoke([&](auto in, auto &out) {
331
std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
332
333
sem_post(&sem0);
334
sem_wait(&sem1);
335
336
SET_OUT_HEADER_LEN(*out0, entry);
337
out0->body.create.entry.attr.mode = S_IFDIR | MODE;
338
out0->body.create.entry.nodeid = ino0;
339
out0->header.unique = in.header.unique;
340
out.push_back(std::move(out0));
341
}));
342
343
setup_interruptor(self);
344
/* First mkdir operation should finish synchronously */
345
ASSERT_EQ(0, mkdir(FULLDIRPATH1, MODE)) << strerror(errno);
346
347
ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
348
<< strerror(errno);
349
350
sem_wait(&sem0);
351
/*
352
* th0 should be blocked waiting for the fuse daemon thread.
353
* Signal it. No FUSE_INTERRUPT should result
354
*/
355
pthread_kill(th0, SIGUSR1);
356
/* Allow the daemon thread to proceed */
357
sem_post(&sem1);
358
pthread_join(th0, &thr0_value);
359
/* Second mkdir should've finished without error */
360
EXPECT_EQ(0, (intptr_t)thr0_value);
361
}
362
363
/*
364
* A FUSE filesystem is legally allowed to ignore INTERRUPT operations, and
365
* complete the original operation whenever it damn well pleases.
366
*/
367
/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
368
TEST_F(Intr, ignore)
369
{
370
uint64_t ino = 42;
371
pthread_t self;
372
uint64_t mkdir_unique;
373
374
self = pthread_self();
375
376
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
377
.WillOnce(Invoke(ReturnErrno(ENOENT)));
378
expect_mkdir(&mkdir_unique);
379
EXPECT_CALL(*m_mock, process(
380
ResultOf([&](auto in) {
381
return (in.header.opcode == FUSE_INTERRUPT &&
382
in.body.interrupt.unique == mkdir_unique);
383
}, Eq(true)),
384
_)
385
).WillOnce(Invoke([&](auto in __unused, auto &out) {
386
// Ignore FUSE_INTERRUPT; respond to the FUSE_MKDIR
387
std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
388
out0->header.unique = mkdir_unique;
389
SET_OUT_HEADER_LEN(*out0, entry);
390
out0->body.create.entry.attr.mode = S_IFDIR | MODE;
391
out0->body.create.entry.nodeid = ino;
392
out.push_back(std::move(out0));
393
}));
394
395
setup_interruptor(self);
396
ASSERT_EQ(0, mkdir(FULLDIRPATH0, MODE)) << strerror(errno);
397
}
398
399
/*
400
* A restartable operation (basically, anything except write or setextattr)
401
* that hasn't yet been sent to userland can be interrupted without sending
402
* FUSE_INTERRUPT, and will be automatically restarted.
403
*/
404
TEST_F(Intr, in_kernel_restartable)
405
{
406
const char FULLPATH1[] = "mountpoint/other_file.txt";
407
const char RELPATH1[] = "other_file.txt";
408
uint64_t ino0 = 42, ino1 = 43;
409
int fd1;
410
pthread_t self, th0, th1;
411
sem_t sem0, sem1;
412
void *thr0_value, *thr1_value;
413
414
ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
415
ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno);
416
self = pthread_self();
417
418
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
419
.WillOnce(Invoke(ReturnErrno(ENOENT)));
420
expect_lookup(RELPATH1, ino1);
421
expect_open(ino1, 0, 1);
422
EXPECT_CALL(*m_mock, process(
423
ResultOf([=](auto in) {
424
return (in.header.opcode == FUSE_MKDIR);
425
}, Eq(true)),
426
_)
427
).WillOnce(Invoke(ReturnImmediate([&](auto in __unused, auto& out) {
428
/* Let the next write proceed */
429
sem_post(&sem1);
430
/* Pause the daemon thread so it won't read the next op */
431
sem_wait(&sem0);
432
433
SET_OUT_HEADER_LEN(out, entry);
434
out.body.create.entry.attr.mode = S_IFDIR | MODE;
435
out.body.create.entry.nodeid = ino0;
436
})));
437
FuseTest::expect_read(ino1, 0, FILESIZE, 0, NULL);
438
439
fd1 = open(FULLPATH1, O_RDONLY);
440
ASSERT_LE(0, fd1) << strerror(errno);
441
442
/* Use a separate thread for each operation */
443
ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
444
<< strerror(errno);
445
446
sem_wait(&sem1); /* Sequence the two operations */
447
448
ASSERT_EQ(0, pthread_create(&th1, NULL, read1, (void*)(intptr_t)fd1))
449
<< strerror(errno);
450
451
setup_interruptor(self, true);
452
453
pause(); /* Wait for signal */
454
455
/* Unstick the daemon */
456
ASSERT_EQ(0, sem_post(&sem0)) << strerror(errno);
457
458
/* Wait awhile to make sure the signal generates no FUSE_INTERRUPT */
459
nap();
460
461
pthread_join(th1, &thr1_value);
462
pthread_join(th0, &thr0_value);
463
EXPECT_EQ(0, (intptr_t)thr1_value);
464
EXPECT_EQ(0, (intptr_t)thr0_value);
465
sem_destroy(&sem1);
466
sem_destroy(&sem0);
467
468
leak(fd1);
469
}
470
471
/*
472
* An operation that hasn't yet been sent to userland can be interrupted
473
* without sending FUSE_INTERRUPT. If it's a non-restartable operation (write
474
* or setextattr) it will return EINTR.
475
*/
476
TEST_F(Intr, in_kernel_nonrestartable)
477
{
478
const char FULLPATH1[] = "mountpoint/other_file.txt";
479
const char RELPATH1[] = "other_file.txt";
480
const char value[] = "whatever";
481
ssize_t value_len = strlen(value) + 1;
482
uint64_t ino0 = 42, ino1 = 43;
483
int ns = EXTATTR_NAMESPACE_USER;
484
int fd1;
485
pthread_t self, th0;
486
sem_t sem0, sem1;
487
void *thr0_value;
488
ssize_t r;
489
490
ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
491
ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno);
492
self = pthread_self();
493
494
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
495
.WillOnce(Invoke(ReturnErrno(ENOENT)));
496
expect_lookup(RELPATH1, ino1);
497
expect_open(ino1, 0, 1);
498
EXPECT_CALL(*m_mock, process(
499
ResultOf([=](auto in) {
500
return (in.header.opcode == FUSE_MKDIR);
501
}, Eq(true)),
502
_)
503
).WillOnce(Invoke(ReturnImmediate([&](auto in __unused, auto& out) {
504
/* Let the next write proceed */
505
sem_post(&sem1);
506
/* Pause the daemon thread so it won't read the next op */
507
sem_wait(&sem0);
508
509
SET_OUT_HEADER_LEN(out, entry);
510
out.body.create.entry.attr.mode = S_IFDIR | MODE;
511
out.body.create.entry.nodeid = ino0;
512
})));
513
514
fd1 = open(FULLPATH1, O_WRONLY);
515
ASSERT_LE(0, fd1) << strerror(errno);
516
517
/* Use a separate thread for the first write */
518
ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
519
<< strerror(errno);
520
521
sem_wait(&sem1); /* Sequence the two operations */
522
523
setup_interruptor(self, true);
524
525
r = extattr_set_fd(fd1, ns, "foo", (const void*)value, value_len);
526
EXPECT_NE(0, r);
527
EXPECT_EQ(EINTR, errno);
528
529
/* Unstick the daemon */
530
ASSERT_EQ(0, sem_post(&sem0)) << strerror(errno);
531
532
/* Wait awhile to make sure the signal generates no FUSE_INTERRUPT */
533
nap();
534
535
pthread_join(th0, &thr0_value);
536
EXPECT_EQ(0, (intptr_t)thr0_value);
537
sem_destroy(&sem1);
538
sem_destroy(&sem0);
539
540
leak(fd1);
541
}
542
543
/*
544
* A syscall that gets interrupted while blocking on FUSE I/O should send a
545
* FUSE_INTERRUPT command to the fuse filesystem, which should then send EINTR
546
* in response to the _original_ operation. The kernel should ultimately
547
* return EINTR to userspace
548
*/
549
/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
550
TEST_F(Intr, in_progress)
551
{
552
pthread_t self;
553
uint64_t mkdir_unique;
554
555
self = pthread_self();
556
557
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
558
.WillOnce(Invoke(ReturnErrno(ENOENT)));
559
expect_mkdir(&mkdir_unique);
560
EXPECT_CALL(*m_mock, process(
561
ResultOf([&](auto in) {
562
return (in.header.opcode == FUSE_INTERRUPT &&
563
in.body.interrupt.unique == mkdir_unique);
564
}, Eq(true)),
565
_)
566
).WillOnce(Invoke([&](auto in __unused, auto &out) {
567
std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
568
out0->header.error = -EINTR;
569
out0->header.unique = mkdir_unique;
570
out0->header.len = sizeof(out0->header);
571
out.push_back(std::move(out0));
572
}));
573
574
setup_interruptor(self);
575
ASSERT_EQ(-1, mkdir(FULLDIRPATH0, MODE));
576
EXPECT_EQ(EINTR, errno);
577
}
578
579
/* Reads should also be interruptible */
580
TEST_F(Intr, in_progress_read)
581
{
582
const char FULLPATH[] = "mountpoint/some_file.txt";
583
const char RELPATH[] = "some_file.txt";
584
const size_t bufsize = 80;
585
char buf[bufsize];
586
uint64_t ino = 42;
587
int fd;
588
pthread_t self;
589
uint64_t read_unique;
590
591
self = pthread_self();
592
593
expect_lookup(RELPATH, ino);
594
expect_open(ino, 0, 1);
595
expect_read(ino, &read_unique);
596
EXPECT_CALL(*m_mock, process(
597
ResultOf([&](auto in) {
598
return (in.header.opcode == FUSE_INTERRUPT &&
599
in.body.interrupt.unique == read_unique);
600
}, Eq(true)),
601
_)
602
).WillOnce(Invoke([&](auto in __unused, auto &out) {
603
std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
604
out0->header.error = -EINTR;
605
out0->header.unique = read_unique;
606
out0->header.len = sizeof(out0->header);
607
out.push_back(std::move(out0));
608
}));
609
610
fd = open(FULLPATH, O_RDONLY);
611
ASSERT_LE(0, fd) << strerror(errno);
612
613
setup_interruptor(self);
614
ASSERT_EQ(-1, read(fd, buf, bufsize));
615
EXPECT_EQ(EINTR, errno);
616
617
leak(fd);
618
}
619
620
/*
621
* When mounted with -o nointr, fusefs will block signals while waiting for the
622
* server.
623
*/
624
TEST_F(Nointr, block)
625
{
626
uint64_t ino = 42;
627
pthread_t self;
628
sem_t sem0;
629
630
ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
631
signaled_semaphore = &sem0;
632
self = pthread_self();
633
634
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
635
.WillOnce(Invoke(ReturnErrno(ENOENT)));
636
EXPECT_CALL(*m_mock, process(
637
ResultOf([=](auto in) {
638
return (in.header.opcode == FUSE_MKDIR);
639
}, Eq(true)),
640
_)
641
).WillOnce(Invoke(ReturnImmediate([&](auto in __unused, auto& out) {
642
/* Let the killer proceed */
643
sem_post(blocked_semaphore);
644
645
/* Wait until after the signal has been sent */
646
sem_wait(signaled_semaphore);
647
/* Allow time for the mkdir thread to receive the signal */
648
nap();
649
650
/* Finally, complete the original op */
651
SET_OUT_HEADER_LEN(out, entry);
652
out.body.create.entry.attr.mode = S_IFDIR | MODE;
653
out.body.create.entry.nodeid = ino;
654
})));
655
EXPECT_CALL(*m_mock, process(
656
ResultOf([&](auto in) {
657
return (in.header.opcode == FUSE_INTERRUPT);
658
}, Eq(true)),
659
_)
660
).Times(0);
661
662
setup_interruptor(self);
663
ASSERT_EQ(0, mkdir(FULLDIRPATH0, MODE)) << strerror(errno);
664
665
sem_destroy(&sem0);
666
}
667
668
/* FUSE_INTERRUPT operations should take priority over other pending ops */
669
TEST_F(Intr, priority)
670
{
671
Sequence seq;
672
uint64_t ino1 = 43;
673
uint64_t mkdir_unique;
674
pthread_t th0;
675
sem_t sem0, sem1;
676
677
ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
678
ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno);
679
680
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
681
.WillOnce(Invoke(ReturnErrno(ENOENT)));
682
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH1)
683
.WillOnce(Invoke(ReturnErrno(ENOENT)));
684
EXPECT_CALL(*m_mock, process(
685
ResultOf([=](auto in) {
686
return (in.header.opcode == FUSE_MKDIR);
687
}, Eq(true)),
688
_)
689
).InSequence(seq)
690
.WillOnce(Invoke(ReturnImmediate([&](auto in, auto& out) {
691
mkdir_unique = in.header.unique;
692
693
/* Let the next mkdir proceed */
694
sem_post(&sem1);
695
696
/* Pause the daemon thread so it won't read the next op */
697
sem_wait(&sem0);
698
699
/* Finally, interrupt the original op */
700
out.header.error = -EINTR;
701
out.header.unique = mkdir_unique;
702
out.header.len = sizeof(out.header);
703
})));
704
/*
705
* FUSE_INTERRUPT should be received before the second FUSE_MKDIR,
706
* even though it was generated later
707
*/
708
EXPECT_CALL(*m_mock, process(
709
ResultOf([&](auto in) {
710
return (in.header.opcode == FUSE_INTERRUPT &&
711
in.body.interrupt.unique == mkdir_unique);
712
}, Eq(true)),
713
_)
714
).InSequence(seq)
715
.WillOnce(Invoke(ReturnErrno(EAGAIN)));
716
EXPECT_CALL(*m_mock, process(
717
ResultOf([&](auto in) {
718
return (in.header.opcode == FUSE_MKDIR);
719
}, Eq(true)),
720
_)
721
).InSequence(seq)
722
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
723
SET_OUT_HEADER_LEN(out, entry);
724
out.body.create.entry.attr.mode = S_IFDIR | MODE;
725
out.body.create.entry.nodeid = ino1;
726
})));
727
728
/* Use a separate thread for the first mkdir */
729
ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
730
<< strerror(errno);
731
732
signaled_semaphore = &sem0;
733
734
sem_wait(&sem1); /* Sequence the two mkdirs */
735
setup_interruptor(th0, true);
736
ASSERT_EQ(0, mkdir(FULLDIRPATH1, MODE)) << strerror(errno);
737
738
pthread_join(th0, NULL);
739
sem_destroy(&sem1);
740
sem_destroy(&sem0);
741
}
742
743
/*
744
* If the FUSE filesystem receives the FUSE_INTERRUPT operation before
745
* processing the original, then it should wait for "some timeout" for the
746
* original operation to arrive. If not, it should send EAGAIN to the
747
* INTERRUPT operation, and the kernel should requeue the INTERRUPT.
748
*
749
* In this test, we'll pretend that the INTERRUPT arrives too soon, gets
750
* EAGAINed, then the kernel requeues it, and the second time around it
751
* successfully interrupts the original
752
*/
753
/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
754
TEST_F(Intr, too_soon)
755
{
756
Sequence seq;
757
pthread_t self;
758
uint64_t mkdir_unique;
759
760
self = pthread_self();
761
762
EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
763
.WillOnce(Invoke(ReturnErrno(ENOENT)));
764
expect_mkdir(&mkdir_unique);
765
766
EXPECT_CALL(*m_mock, process(
767
ResultOf([&](auto in) {
768
return (in.header.opcode == FUSE_INTERRUPT &&
769
in.body.interrupt.unique == mkdir_unique);
770
}, Eq(true)),
771
_)
772
).InSequence(seq)
773
.WillOnce(Invoke(ReturnErrno(EAGAIN)));
774
775
EXPECT_CALL(*m_mock, process(
776
ResultOf([&](auto in) {
777
return (in.header.opcode == FUSE_INTERRUPT &&
778
in.body.interrupt.unique == mkdir_unique);
779
}, Eq(true)),
780
_)
781
).InSequence(seq)
782
.WillOnce(Invoke([&](auto in __unused, auto &out __unused) {
783
std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
784
out0->header.error = -EINTR;
785
out0->header.unique = mkdir_unique;
786
out0->header.len = sizeof(out0->header);
787
out.push_back(std::move(out0));
788
}));
789
790
setup_interruptor(self);
791
ASSERT_EQ(-1, mkdir(FULLDIRPATH0, MODE));
792
EXPECT_EQ(EINTR, errno);
793
}
794
795
796
// TODO: add a test where write returns EWOULDBLOCK
797
798