aboutsummaryrefslogtreecommitdiff
path: root/fs/ext4/inode-test.c
blob: d62d802c9c126564b6d6dc689e4dba6edfe1a63c (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
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
// SPDX-License-Identifier: GPL-2.0
/*
 * KUnit test of ext4 inode that verify the seconds part of [a/c/m]
 * timestamps in ext4 inode structs are decoded correctly.
 */

#include <kunit/test.h>
#include <linux/kernel.h>
#include <linux/time64.h>

#include "ext4.h"

/*
 * For constructing the nonnegative timestamp lower bound value.
 * binary: 00000000 00000000 00000000 00000000
 */
#define LOWER_MSB_0 0L
/*
 * For constructing the nonnegative timestamp upper bound value.
 * binary: 01111111 11111111 11111111 11111111
 *
 */
#define UPPER_MSB_0 0x7fffffffL
/*
 * For constructing the negative timestamp lower bound value.
 * binary: 10000000 00000000 00000000 00000000
 */
#define LOWER_MSB_1 (-(UPPER_MSB_0) - 1L)  /* avoid overflow */
/*
 * For constructing the negative timestamp upper bound value.
 * binary: 11111111 11111111 11111111 11111111
 */
#define UPPER_MSB_1 (-1L)
/*
 * Upper bound for nanoseconds value supported by the encoding.
 * binary: 00111111 11111111 11111111 11111111
 */
#define MAX_NANOSECONDS ((1L << 30) - 1)

#define CASE_NAME_FORMAT "%s: msb:%x lower_bound:%x extra_bits: %x"

#define LOWER_BOUND_NEG_NO_EXTRA_BITS_CASE\
	"1901-12-13 Lower bound of 32bit < 0 timestamp, no extra bits"
#define UPPER_BOUND_NEG_NO_EXTRA_BITS_CASE\
	"1969-12-31 Upper bound of 32bit < 0 timestamp, no extra bits"
#define LOWER_BOUND_NONNEG_NO_EXTRA_BITS_CASE\
	"1970-01-01 Lower bound of 32bit >=0 timestamp, no extra bits"
#define UPPER_BOUND_NONNEG_NO_EXTRA_BITS_CASE\
	"2038-01-19 Upper bound of 32bit >=0 timestamp, no extra bits"
#define LOWER_BOUND_NEG_LO_1_CASE\
	"2038-01-19 Lower bound of 32bit <0 timestamp, lo extra sec bit on"
#define UPPER_BOUND_NEG_LO_1_CASE\
	"2106-02-07 Upper bound of 32bit <0 timestamp, lo extra sec bit on"
#define LOWER_BOUND_NONNEG_LO_1_CASE\
	"2106-02-07 Lower bound of 32bit >=0 timestamp, lo extra sec bit on"
#define UPPER_BOUND_NONNEG_LO_1_CASE\
	"2174-02-25 Upper bound of 32bit >=0 timestamp, lo extra sec bit on"
#define LOWER_BOUND_NEG_HI_1_CASE\
	"2174-02-25 Lower bound of 32bit <0 timestamp, hi extra sec bit on"
#define UPPER_BOUND_NEG_HI_1_CASE\
	"2242-03-16 Upper bound of 32bit <0 timestamp, hi extra sec bit on"
#define LOWER_BOUND_NONNEG_HI_1_CASE\
	"2242-03-16 Lower bound of 32bit >=0 timestamp, hi extra sec bit on"
#define UPPER_BOUND_NONNEG_HI_1_CASE\
	"2310-04-04 Upper bound of 32bit >=0 timestamp, hi extra sec bit on"
#define UPPER_BOUND_NONNEG_HI_1_NS_1_CASE\
	"2310-04-04 Upper bound of 32bit>=0 timestamp, hi extra sec bit 1. 1 ns"
#define LOWER_BOUND_NONNEG_HI_1_NS_MAX_CASE\
	"2378-04-22 Lower bound of 32bit>= timestamp. Extra sec bits 1. Max ns"
#define LOWER_BOUND_NONNEG_EXTRA_BITS_1_CASE\
	"2378-04-22 Lower bound of 32bit >=0 timestamp. All extra sec bits on"
#define UPPER_BOUND_NONNEG_EXTRA_BITS_1_CASE\
	"2446-05-10 Upper bound of 32bit >=0 timestamp. All extra sec bits on"

struct timestamp_expectation {
	const char *test_case_name;
	struct timespec64 expected;
	u32 extra_bits;
	bool msb_set;
	bool lower_bound;
};

static time64_t get_32bit_time(const struct timestamp_expectation * const test)
{
	if (test->msb_set) {
		if (test->lower_bound)
			return LOWER_MSB_1;

		return UPPER_MSB_1;
	}

	if (test->lower_bound)
		return LOWER_MSB_0;
	return UPPER_MSB_0;
}


/*
 *  Test data is derived from the table in the Inode Timestamps section of
 *  Documentation/filesystems/ext4/inodes.rst.
 */
static void inode_test_xtimestamp_decoding(struct kunit *test)
{
	const struct timestamp_expectation test_data[] = {
		{
			.test_case_name = LOWER_BOUND_NEG_NO_EXTRA_BITS_CASE,
			.msb_set = true,
			.lower_bound = true,
			.extra_bits = 0,
			.expected = {.tv_sec = -0x80000000LL, .tv_nsec = 0L},
		},

		{
			.test_case_name = UPPER_BOUND_NEG_NO_EXTRA_BITS_CASE,
			.msb_set = true,
			.lower_bound = false,
			.extra_bits = 0,
			.expected = {.tv_sec = -1LL, .tv_nsec = 0L},
		},

		{
			.test_case_name = LOWER_BOUND_NONNEG_NO_EXTRA_BITS_CASE,
			.msb_set = false,
			.lower_bound = true,
			.extra_bits = 0,
			.expected = {0LL, 0L},
		},

		{
			.test_case_name = UPPER_BOUND_NONNEG_NO_EXTRA_BITS_CASE,
			.msb_set = false,
			.lower_bound = false,
			.extra_bits = 0,
			.expected = {.tv_sec = 0x7fffffffLL, .tv_nsec = 0L},
		},

		{
			.test_case_name = LOWER_BOUND_NEG_LO_1_CASE,
			.msb_set = true,
			.lower_bound = true,
			.extra_bits = 1,
			.expected = {.tv_sec = 0x80000000LL, .tv_nsec = 0L},
		},

		{
			.test_case_name = UPPER_BOUND_NEG_LO_1_CASE,
			.msb_set = true,
			.lower_bound = false,
			.extra_bits = 1,
			.expected = {.tv_sec = 0xffffffffLL, .tv_nsec = 0L},
		},

		{
			.test_case_name = LOWER_BOUND_NONNEG_LO_1_CASE,
			.msb_set = false,
			.lower_bound = true,
			.extra_bits = 1,
			.expected = {.tv_sec = 0x100000000LL, .tv_nsec = 0L},
		},

		{
			.test_case_name = UPPER_BOUND_NONNEG_LO_1_CASE,
			.msb_set = false,
			.lower_bound = false,
			.extra_bits = 1,
			.expected = {.tv_sec = 0x17fffffffLL, .tv_nsec = 0L},
		},

		{
			.test_case_name = LOWER_BOUND_NEG_HI_1_CASE,
			.msb_set = true,
			.lower_bound = true,
			.extra_bits =  2,
			.expected = {.tv_sec = 0x180000000LL, .tv_nsec = 0L},
		},

		{
			.test_case_name = UPPER_BOUND_NEG_HI_1_CASE,
			.msb_set = true,
			.lower_bound = false,
			.extra_bits = 2,
			.expected = {.tv_sec = 0x1ffffffffLL, .tv_nsec = 0L},
		},

		{
			.test_case_name = LOWER_BOUND_NONNEG_HI_1_CASE,
			.msb_set = false,
			.lower_bound = true,
			.extra_bits = 2,
			.expected = {.tv_sec = 0x200000000LL, .tv_nsec = 0L},
		},

		{
			.test_case_name = UPPER_BOUND_NONNEG_HI_1_CASE,
			.msb_set = false,
			.lower_bound = false,
			.extra_bits = 2,
			.expected = {.tv_sec = 0x27fffffffLL, .tv_nsec = 0L},
		},

		{
			.test_case_name = UPPER_BOUND_NONNEG_HI_1_NS_1_CASE,
			.msb_set = false,
			.lower_bound = false,
			.extra_bits = 6,
			.expected = {.tv_sec = 0x27fffffffLL, .tv_nsec = 1L},
		},

		{
			.test_case_name = LOWER_BOUND_NONNEG_HI_1_NS_MAX_CASE,
			.msb_set = false,
			.lower_bound = true,
			.extra_bits = 0xFFFFFFFF,
			.expected = {.tv_sec = 0x300000000LL,
				     .tv_nsec = MAX_NANOSECONDS},
		},

		{
			.test_case_name = LOWER_BOUND_NONNEG_EXTRA_BITS_1_CASE,
			.msb_set = false,
			.lower_bound = true,
			.extra_bits = 3,
			.expected = {.tv_sec = 0x300000000LL, .tv_nsec = 0L},
		},

		{
			.test_case_name = UPPER_BOUND_NONNEG_EXTRA_BITS_1_CASE,
			.msb_set = false,
			.lower_bound = false,
			.extra_bits = 3,
			.expected = {.tv_sec = 0x37fffffffLL, .tv_nsec = 0L},
		}
	};

	struct timespec64 timestamp;
	int i;

	for (i = 0; i < ARRAY_SIZE(test_data); ++i) {
		timestamp.tv_sec = get_32bit_time(&test_data[i]);
		ext4_decode_extra_time(&timestamp,
				       cpu_to_le32(test_data[i].extra_bits));

		KUNIT_EXPECT_EQ_MSG(test,
				    test_data[i].expected.tv_sec,
				    timestamp.tv_sec,
				    CASE_NAME_FORMAT,
				    test_data[i].test_case_name,
				    test_data[i].msb_set,
				    test_data[i].lower_bound,
				    test_data[i].extra_bits);
		KUNIT_EXPECT_EQ_MSG(test,
				    test_data[i].expected.tv_nsec,
				    timestamp.tv_nsec,
				    CASE_NAME_FORMAT,
				    test_data[i].test_case_name,
				    test_data[i].msb_set,
				    test_data[i].lower_bound,
				    test_data[i].extra_bits);
	}
}

static struct kunit_case ext4_inode_test_cases[] = {
	KUNIT_CASE(inode_test_xtimestamp_decoding),
	{}
};

static struct kunit_suite ext4_inode_test_suite = {
	.name = "ext4_inode_test",
	.test_cases = ext4_inode_test_cases,
};

kunit_test_suites(&ext4_inode_test_suite);

MODULE_LICENSE("GPL v2");