1// Copyright 2016 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <assert.h>
6#include <errno.h>
7#include <lib/zircon-internal/xorshiftrand.h>
8
9#include "util.h"
10
11#define FAIL -1
12#define BUSY 0
13#define DONE 1
14
15#define FBUFSIZE 65536
16
17static_assert(FBUFSIZE == ((FBUFSIZE / sizeof(uint64_t)) * sizeof(uint64_t)),
18              "FBUFSIZE not multiple of uint64_t");
19
20typedef struct worker worker_t;
21
22struct worker {
23    worker_t* next;
24    int (*work)(worker_t* w);
25
26    rand64_t rdata;
27    rand32_t rops;
28
29    int fd;
30    int status;
31    uint32_t flags;
32    uint32_t size;
33    uint32_t pos;
34
35    union {
36        uint8_t u8[FBUFSIZE];
37        uint64_t u64[FBUFSIZE / sizeof(uint64_t)];
38    };
39
40    char name[256];
41};
42
43static worker_t* all_workers;
44
45bool worker_new(const char* where, const char* fn,
46                int (*work)(worker_t* w), uint32_t size, uint32_t flags);
47int worker_writer(worker_t* w);
48static bool init_environment();
49
50#define F_RAND_IOSIZE 1
51#define KB(n) ((n)*1024)
52#define MB(n) ((n)*1024 * 1024)
53
54struct {
55    int (*work)(worker_t*);
56    const char* name;
57    uint32_t size;
58    uint32_t flags;
59} WORK[] = {
60    { worker_writer, "file0000", KB(512), F_RAND_IOSIZE, },
61    { worker_writer, "file0001", MB(10),  F_RAND_IOSIZE, },
62    { worker_writer, "file0002", KB(512), F_RAND_IOSIZE, },
63    { worker_writer, "file0003", KB(512), F_RAND_IOSIZE, },
64    { worker_writer, "file0004", KB(512), 0,             },
65    { worker_writer, "file0005", MB(20),  0,             },
66    { worker_writer, "file0006", KB(512), 0,             },
67    { worker_writer, "file0007", KB(512), 0,             },
68};
69
70int worker_rw(worker_t* w, bool do_read) {
71    if (w->pos == w->size) {
72        return DONE;
73    }
74
75    // offset into buffer
76    uint32_t off = w->pos % FBUFSIZE;
77
78    // fill our content buffer if it's empty
79    if (off == 0) {
80        for (unsigned n = 0; n < (FBUFSIZE / sizeof(uint64_t)); n++) {
81            w->u64[n] = rand64(&w->rdata);
82        }
83    }
84
85    // data in buffer available to write
86    uint32_t xfer = FBUFSIZE - off;
87
88    // do not exceed our desired size
89    if (xfer > (w->size - w->pos)) {
90        xfer = w->size - w->pos;
91    }
92
93    if ((w->flags & F_RAND_IOSIZE) && (xfer > 3000)) {
94        xfer = 3000 + (rand32(&w->rops) % (xfer - 3000));
95    }
96
97    int r;
98    if (do_read) {
99        uint8_t buffer[FBUFSIZE];
100        if ((r = emu_read(w->fd, buffer, xfer)) < 0) {
101            fprintf(stderr, "worker('%s') read failed @%u: %d\n",
102                    w->name, w->pos, errno);
103            return FAIL;
104        }
105
106        if (memcmp(buffer, w->u8 + off, r)) {
107            fprintf(stderr, "worker('%s) verify failed @%u\n",
108                    w->name, w->pos);
109            return FAIL;
110        }
111    } else {
112        if ((r = emu_write(w->fd, w->u8 + off, xfer)) < 0) {
113            fprintf(stderr, "worker('%s') write failed @%u: %d\n",
114                    w->name, w->pos, errno);
115            return FAIL;
116        }
117    }
118
119    // advance
120    w->pos += r;
121    return BUSY;
122}
123
124int worker_verify(worker_t* w) {
125    int r = worker_rw(w, true);
126    if (r == DONE) {
127        emu_close(w->fd);
128    }
129    return r;
130}
131
132int worker_writer(worker_t* w) {
133    int r = worker_rw(w, false);
134    if (r == DONE) {
135        if (emu_lseek(w->fd, 0, SEEK_SET) != 0) {
136            fprintf(stderr, "worker('%s') seek failed: %s\n",
137                    w->name, strerror(errno));
138            return FAIL;
139        }
140        // start at 0 and reset our data generator seed
141        srand64(&w->rdata, w->name);
142        w->pos = 0;
143        w->work = worker_verify;
144        return BUSY;
145    }
146    return r;
147}
148
149bool worker_new(const char* where, const char* fn,
150               int (*work)(worker_t* w), uint32_t size, uint32_t flags) {
151    worker_t* w = (worker_t*)calloc(1, sizeof(worker_t));
152    ASSERT_NE(w, nullptr);
153
154    snprintf(w->name, sizeof(w->name), "%s%s", where, fn);
155    srand64(&w->rdata, w->name);
156    srand32(&w->rops, w->name);
157    w->size = size;
158    w->work = work;
159    w->flags = flags;
160
161    if ((w->fd = emu_open(w->name, O_RDWR | O_CREAT | O_EXCL, 0644)) < 0) {
162        fprintf(stderr, "worker('%s') cannot create file\n", w->name);
163        free(w);
164        return false;
165    }
166
167    if (all_workers) {
168        w->next = all_workers;
169    }
170
171    all_workers = w;
172
173    return true;
174}
175
176int do_work() {
177    uint32_t busy_count = 0;
178    for (worker_t* w = all_workers; w != nullptr; w = w->next) {
179        if (w->status == BUSY) {
180            busy_count++;
181            if ((w->status = w->work(w)) == FAIL) {
182                return FAIL;
183            }
184            if (w->status == DONE) {
185                fprintf(stderr, "worker('%s') finished\n", w->name);
186            }
187        }
188    }
189    return busy_count ? BUSY : DONE;
190}
191
192bool do_all_work() {
193    BEGIN_HELPER;
194    for (;;) {
195        int r = do_work();
196        ASSERT_NE(r, FAIL);
197        if (r == DONE) {
198            break;
199        }
200        ASSERT_EQ(run_fsck(), 0);
201    }
202    END_HELPER;
203}
204
205static bool init_environment() {
206    all_workers = nullptr;
207
208    // assemble workers
209    const char* where = "::";
210    for (unsigned n = 0; n < fbl::count_of(WORK); n++) {
211        ASSERT_TRUE(worker_new(where, WORK[n].name, WORK[n].work,
212                               WORK[n].size, WORK[n].flags));
213    }
214    return true;
215}
216
217bool test_work_single_thread(void) {
218    BEGIN_TEST;
219
220    ASSERT_TRUE(init_environment());
221    ASSERT_TRUE(do_all_work());
222    worker_t* w = all_workers;
223    worker_t* next;
224    while (w != NULL) {
225        next = w->next;
226        free(w);
227        w = next;
228    }
229
230    END_TEST;
231}
232
233RUN_MINFS_TESTS(rw_workers_test,
234    RUN_TEST_MEDIUM(test_work_single_thread)
235)
236