aboutsummaryrefslogtreecommitdiff
path: root/user/usr/bin/tests/s5fstest.c
blob: e2441ac4b02a29f9c245ed3deadc6ad3c0f5c2ed (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
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
//
// Tests some edge cases of s5fs
// Ported to user mode by twd in 7/2018
//

#ifdef __KERNEL__

#include "config.h"
#include "errno.h"
#include "globals.h"
#include "limits.h"

#include "test/usertest.h"

#include "util/debug.h"
#include "util/printf.h"
#include "util/string.h"

#include "fs/fcntl.h"
#include "fs/lseek.h"
#include "fs/s5fs/s5fs.h"
#include "fs/vfs_syscall.h"

#else

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <test/test.h>
#include <unistd.h>

#define do_write write
#define do_read read
#define do_lseek lseek
#define do_open(x, y) open((x), (y), 0777)
#define do_link link
#define do_unlink unlink
#define do_mknod mknod
#define do_close close
#define do_mkdir(x) mkdir((x), 0777)
#define do_chdir chdir
#define do_rmdir rmdir

#define S5_BLOCK_SIZE 4096
#define S5_MAX_FILE_BLOCKS 1052

#define KASSERT(x) test_assert(x, NULL)
#define dbg(code, fmt, args...) printf(fmt, ##args)

#endif

#define BUFSIZE 256
#define BIG_BUFSIZE 2056

#define S5_MAX_FILE_SIZE S5_BLOCK_SIZE *S5_MAX_FILE_BLOCKS

static void get_file_name(char *buf, size_t sz, int fileno)
{
    snprintf(buf, sz, "file%d", fileno);
}

// Write to a fail forever until it is either filled up or we get an error.
static int write_until_fail(int fd)
{
    size_t total_written = 0;
    char buf[BIG_BUFSIZE] = {42};
    while (total_written < S5_MAX_FILE_SIZE)
    {
        int res = do_write(fd, buf, BIG_BUFSIZE);
        if (res < 0)
        {
            return res;
        }
        total_written += res;
    }
    KASSERT(total_written == S5_MAX_FILE_SIZE);
    KASSERT(do_lseek(fd, 0, SEEK_END) == S5_MAX_FILE_SIZE);

    return 0;
}

// Read n bytes from the file, and check they're all 0
// We do this in increments of big_bufsize because we might want to read
// like a million bytes from the file
static int is_first_n_bytes_zero(int fd, int n)
{
    int total_read = 0;
    while (total_read < n)
    {
        int amt_to_read = MIN(BIG_BUFSIZE, n - total_read);
        char buf[BIG_BUFSIZE] = {1};
        int res = do_read(fd, buf, amt_to_read);
        if (res != amt_to_read)
        {
            dbg(DBG_TESTFAIL, "do_read result was %d\n", res);
            return 0;
            // KASSERT("Could not read from file" && 0);
        }
        total_read += res;

        // Check everything that we read is indeed 0
        for (int i = 0; i < amt_to_read; ++i)
        {
            if (buf[i] != 0)
            {
                dbg(DBG_TESTFAIL, "buf contains char %d\n", (int)buf[i]);
                return 0;
            }
        }
    }

    return 1;
}

static void test_running_out_of_inodes()
{
    // Open a ton of files until we get an error
    int res = -1;
    int fileno = 0;
    char filename[BUFSIZE];

    // open files til we get an error
    while (1)
    {
        get_file_name(filename, BUFSIZE, fileno);
        res = do_open(filename, O_RDONLY | O_CREAT);
        if (res >= 0)
        {
            fileno++;
            test_assert(do_close(res) == 0, "couldn't close");
        }
        else
        {
            break;
        }
    }
#ifdef __KERNEL__
    test_assert(res == -ENOSPC, "Did not get ENOSPC error");
#else
    test_assert(errno == ENOSPC, "Did not get ENOSPC error");
#endif

    // make sure mkdir fails now that we're out of inodes
    test_assert(do_mkdir("directory") < 0, "do_mkdir worked!?");
#ifdef __KERNEL__
    test_assert(res == -ENOSPC, "unexpected error");
#else
    test_assert(errno == ENOSPC, "unexpected error");
#endif

#ifdef __KERNEL__
    test_assert(do_mknod("nod", S_IFCHR, 123) != 0, "mknod worked!?");
    test_assert(res == -ENOSPC, "wrong error code");
#endif

    // the last file we tried to open failed
    fileno--;

    do
    {
        get_file_name(filename, BUFSIZE, fileno);
        res = do_unlink(filename);
        test_assert(res == 0, "couldnt unlink");
        fileno--;
    } while (fileno >= 0);

    // Now we've freed all the files, try to create another file
    int fd = do_open("file", O_RDONLY | O_CREAT);
    test_assert(fd >= 0, "Still cannot create files");
    test_assert(do_close(fd) == 0, "Could not do_close fd");
    test_assert(do_unlink("file") == 0, "Could not remove file");
}

static void test_filling_file()
{
    int res = 0;
    int fd = do_open("hugefile", O_RDWR | O_CREAT);
    KASSERT(fd >= 0);

    res = write_until_fail(fd);
    test_assert(res == 0, "Did not write to entire file");

    // make sure all other writes are unsuccessful/dont complete
    char buf[BIG_BUFSIZE] = {0};
    res = do_write(fd, buf, sizeof(buf));
    test_assert(res < 0, "Able to write although the file is full");
#ifdef __KERNEL__
    test_assert(res == -EFBIG || res == -EINVAL, "Wrong error code");
#else
    test_assert(errno == EFBIG || errno == EINVAL, "Wrong error code");
#endif

    test_assert(do_close(fd) == 0, "couldnt close hugefile");
    test_assert(do_unlink("hugefile") == 0, "couldnt unlink hugefile");
}

// Fill up the disk. Apparently to do this, we should need to fill up one
// entire file, then start to fill up another. We should eventually get
// the ENOSPC error
static void test_running_out_of_blocks()
{
    int res = 0;

    int fd1 = do_open("fullfile", O_RDWR | O_CREAT);

    res = write_until_fail(fd1);
    test_assert(res == 0, "Ran out of space quicker than we expected");

    int fd2 = do_open("partiallyfullfile", O_RDWR | O_CREAT);
    res = write_until_fail(fd2);
#ifdef __KERNEL__
    test_assert(res == -ENOSPC, "Did not get nospc error");
#else
    test_assert(errno == ENOSPC, "Did not get nospc error");
#endif

    test_assert(do_close(fd1) == 0, "could not close");
    test_assert(do_close(fd2) == 0, "could not close");

    test_assert(do_unlink("fullfile") == 0, "couldnt do_unlink file");
    test_assert(do_unlink("partiallyfullfile") == 0, "couldnt do_unlink file");
}

// Open a new file, write to some random address in the file,
// and make sure everything up to that is all 0s.
static int test_sparseness_direct_blocks()
{
    const char *filename = "sparsefile";
    int fd = do_open(filename, O_RDWR | O_CREAT);

    // Now write to some random address that'll be in a direct block
    const int addr = 10000;
    const char *b = "iboros";
    const int sz = strlen(b);

    test_assert(do_lseek(fd, addr, SEEK_SET) == addr, "couldnt seek");
    test_assert(do_write(fd, b, sz) == sz, "couldnt write to random address");

    test_assert(do_lseek(fd, 0, SEEK_SET) == 0, "couldnt seek back to begin");
    test_assert(is_first_n_bytes_zero(fd, addr) == 1, "sparseness don't work");

    // Get rid of this file
    test_assert(do_close(fd) == 0, "couldn't close file");
    test_assert(do_unlink(filename) == 0, "couldnt unlink file");

    return 0;
}

/*
 * Fixed by twd to do a better test in 7/2018
 */
static int test_sparseness_indirect_blocks()
{
    const char *filename = "bigsparsefile";
    int fd = do_open(filename, O_RDWR | O_CREAT);

    // first partially fill the first block of the file
    char randomgarbage[4050];
    test_assert(do_write(fd, randomgarbage, 4050) == 4050,
                "couldn't write to first block");

    // Now write to some random address that'll be in an indirect block
    const int addr = 1000000;
    const char *b = "iboros";
    const int sz = strlen(b);

    test_assert(do_lseek(fd, addr, SEEK_SET) == addr, "couldnt seek");
    test_assert(do_write(fd, b, sz) == sz, "couldnt write to random address");

    test_assert(do_lseek(fd, 4050, SEEK_SET) == 4050,
                "couldnt seek back to begin");
    test_assert(is_first_n_bytes_zero(fd, addr - 4050) == 1,
                "sparseness don't work");

    // Get rid of this file
    test_assert(do_close(fd) == 0, "couldn't close file");
    test_assert(do_unlink(filename) == 0, "couldnt unlink file");

    return 0;
}

#ifdef __KERNEL__
extern uint64_t jiffies;
#endif

static void seed_randomness()
{
#ifdef __KERNEL__
    srand(jiffies);
#else
    srand(time(NULL));
#endif
    rand();
}

#ifdef __KERNEL__
int s5fstest_main()
#else

int main()
#endif
{
    dbg(DBG_TEST, "Starting S5FS test\n");

    test_init();
    seed_randomness();

    KASSERT(do_mkdir("s5fstest") == 0);
    KASSERT(do_chdir("s5fstest") == 0);
    dbg(DBG_TEST, "Test dir initialized\n");

    dbg(DBG_TEST, "Testing sparseness for direct blocks\n");
    test_sparseness_direct_blocks();
    dbg(DBG_TEST, "Testing sparseness for indirect blocks\n");
    test_sparseness_indirect_blocks();

    dbg(DBG_TEST, "Testing running out of inodes\n");
    test_running_out_of_inodes();
    dbg(DBG_TEST, "Testing filling a file to max capacity\n");
    test_filling_file();
    dbg(DBG_TEST, "Testing using all available blocks on disk\n");
    test_running_out_of_blocks();

    test_assert(do_chdir("..") == 0, "");
    test_assert(do_rmdir("s5fstest") == 0, "");

    test_fini();

    return 0;
}