1// SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Copyright (c) International Business Machines  Corp., 2002
4  * Copyright (c) 2015 Cyril Hrubis <chrubis@suse.cz>
5  * Copyright (c) 2019 FUJITSU LIMITED. All rights reserved.
6  *
7  * Robbie Williamson <robbiew@us.ibm.com>
8  * Roy Lee <roylee@andestech.com>
9  */
10/*
11 * Test Description:
12 *
13 * Tests truncate and mandatory record locking.
14 *
15 * Parent creates a file, child locks a region and sleeps.
16 *
17 * Parent checks that ftruncate before the locked region and inside the region
18 * fails while ftruncate after the region succeds.
19 *
20 * Parent wakes up child, child exits, lock is unlocked.
21 *
22 * Parent checks that ftruncate now works in all cases.
23 *
24 */
25
26#include <stdio.h>
27#include <errno.h>
28#include <sys/types.h>
29#include <sys/stat.h>
30#include <sys/mount.h>
31#include <unistd.h>
32#include <stdlib.h>
33#include <sys/statvfs.h>
34
35#include "tst_test.h"
36
37#define RECLEN	100
38#define MNTPOINT	"mntpoint"
39#define TESTFILE	MNTPOINT"/testfile"
40
41static int len = 8 * 1024;
42static int recstart, reclen;
43
44static void ftruncate_expect_fail(int fd, off_t offset, const char *msg)
45{
46	TEST(ftruncate(fd, offset));
47
48	if (TST_RET == 0) {
49		tst_res(TFAIL, "ftruncate() %s succeeded unexpectedly", msg);
50		return;
51	}
52
53	if (TST_ERR != EAGAIN) {
54		tst_res(TFAIL | TTERRNO,
55			"ftruncate() %s failed unexpectedly, expected EAGAIN",
56			msg);
57		return;
58	}
59
60	tst_res(TPASS, "ftruncate() %s failed with EAGAIN", msg);
61}
62
63static void ftruncate_expect_success(int fd, off_t offset, const char *msg)
64{
65	struct stat sb;
66
67	TEST(ftruncate(fd, offset));
68
69	if (TST_RET != 0) {
70		tst_res(TFAIL | TTERRNO,
71			"ftruncate() %s failed unexpectedly", msg);
72		return;
73	}
74
75	SAFE_FSTAT(fd, &sb);
76
77	if (sb.st_size != offset) {
78		tst_res(TFAIL,
79			"ftruncate() to %li bytes succeded but fstat() reports size %li",
80			(long)offset, (long)sb.st_size);
81		return;
82	}
83
84	tst_res(TPASS, "ftruncate() %s succeded", msg);
85}
86
87static void doparent(void)
88{
89	int fd;
90
91	TST_CHECKPOINT_WAIT(0);
92
93	fd = SAFE_OPEN(TESTFILE, O_RDWR | O_NONBLOCK);
94
95	ftruncate_expect_fail(fd, RECLEN, "offset before lock");
96	ftruncate_expect_fail(fd, recstart + RECLEN/2, "offset in lock");
97	ftruncate_expect_success(fd, recstart + RECLEN, "offset after lock");
98
99	TST_CHECKPOINT_WAKE(0);
100	SAFE_WAIT(NULL);
101
102	ftruncate_expect_success(fd, recstart + RECLEN/2, "offset in lock");
103	ftruncate_expect_success(fd, recstart, "offset before lock");
104	ftruncate_expect_success(fd, recstart + RECLEN, "offset after lock");
105
106	SAFE_CLOSE(fd);
107}
108
109void dochild(void)
110{
111	int fd;
112	struct flock flocks;
113
114	fd = SAFE_OPEN(TESTFILE, O_RDWR);
115
116	tst_res(TINFO, "Child locks file");
117
118	flocks.l_type = F_WRLCK;
119	flocks.l_whence = SEEK_CUR;
120	flocks.l_start = recstart;
121	flocks.l_len = reclen;
122
123	SAFE_FCNTL(fd, F_SETLKW, &flocks);
124
125	TST_CHECKPOINT_WAKE_AND_WAIT(0);
126
127	tst_res(TINFO, "Child unlocks file");
128
129	exit(0);
130}
131
132static void verify_ftruncate(void)
133{
134	int pid;
135
136	if (tst_fill_file(TESTFILE, 0, 1024, 8))
137		tst_brk(TBROK, "Failed to create test file");
138
139	SAFE_CHMOD(TESTFILE, 02666);
140
141	reclen = RECLEN;
142	recstart = RECLEN + rand() % (len - 3 * RECLEN);
143
144	pid = SAFE_FORK();
145
146	if (pid == 0)
147		dochild();
148
149	doparent();
150}
151
152static void setup(void)
153{
154	 /*
155	  * Kernel returns EPERM when CONFIG_MANDATORY_FILE_LOCKING is not
156	  * supported - to avoid false negatives, mount the fs first without
157	  * flags and then remount it as MS_MANDLOCK
158	  */
159	if (mount(NULL, MNTPOINT, NULL, MS_REMOUNT|MS_MANDLOCK, NULL) == -1) {
160		if (errno == EPERM) {
161			tst_brk(TCONF,
162				"Mandatory lock not supported by this system");
163		} else {
164			tst_brk(TBROK | TTERRNO,
165				"Remount with MS_MANDLOCK failed");
166		}
167	}
168}
169
170static struct tst_test test = {
171	.needs_kconfigs = (const char *[]) {
172		"CONFIG_MANDATORY_FILE_LOCKING=y",
173		NULL
174	},
175	.test_all = verify_ftruncate,
176	.setup = setup,
177	.needs_checkpoints = 1,
178	.forks_child = 1,
179	.mount_device = 1,
180	.needs_root = 1,
181	.mntpoint = MNTPOINT,
182};
183