blob: 9e2dbc589c19f7fbb6e03ba7a4a5b3246b43d96c [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fbl/algorithm.h>
#include "host.h"
#include "misc.h"
#define TRY(func) ({\
int ret = (func); \
if (ret < 0) { \
printf("%s:%d:error: %s -> %d\n", __FILE__, __LINE__, #func, ret); \
exit(1); \
} \
ret; })
#define EXPECT_FAIL(func) ({\
int ret = (func); \
if (ret >= 0) { \
printf("%s:%d:expected error from: %s -> %d\n", __FILE__, __LINE__, #func, ret); \
exit(1); \
} \
ret; })
#define FAIL -1
#define BUSY 0
#define DONE 1
#define FBUFSIZE 65536
static_assert(FBUFSIZE == ((FBUFSIZE/sizeof(uint64_t)) * sizeof(uint64_t)),
"FBUFSIZE not multiple of uint64_t");
typedef struct worker worker_t;
struct worker {
worker_t* next;
int (*work)(worker_t* w);
rand64_t rdata;
rand32_t rops;
int fd;
int status;
uint32_t flags;
uint32_t size;
uint32_t pos;
union {
uint8_t u8[FBUFSIZE];
uint64_t u64[FBUFSIZE / sizeof(uint64_t)];
};
char name[256];
};
static worker_t* all_workers;
#define F_RAND_IOSIZE 1
int worker_rw(worker_t* w, bool do_read) {
if (w->pos == w->size) {
return DONE;
}
// offset into buffer
uint32_t off = w->pos % FBUFSIZE;
// fill our content buffer if it's empty
if (off == 0) {
for (unsigned n = 0; n < (FBUFSIZE / sizeof(uint64_t)); n++) {
w->u64[n] = rand64(&w->rdata);
}
}
// data in buffer available to write
uint32_t xfer = FBUFSIZE - off;
// do not exceed our desired size
if (xfer > (w->size - w->pos)) {
xfer = w->size - w->pos;
}
if ((w->flags & F_RAND_IOSIZE) && (xfer > 3000)) {
xfer = 3000 + (rand32(&w->rops) % (xfer - 3000));
}
int r;
if (do_read) {
uint8_t buffer[FBUFSIZE];
if ((r = emu_read(w->fd, buffer, xfer)) < 0) {
fprintf(stderr, "worker('%s') emu_read failed @%u: %s\n",
w->name, w->pos, strerror(errno));
return FAIL;
}
if (memcmp(buffer, w->u8 + off, r)) {
fprintf(stderr, "worker('%s) verify failed @%u\n",
w->name, w->pos);
return FAIL;
}
} else {
if ((r = emu_write(w->fd, w->u8 + off, xfer)) < 0) {
fprintf(stderr, "worker('%s') write failed @%u: %s\n",
w->name, w->pos, strerror(errno));
return FAIL;
}
}
// advance
w->pos += r;
return BUSY;
}
int worker_verify(worker_t* w) {
int r = worker_rw(w, true);
if (r == DONE) {
emu_close(w->fd);
}
return r;
}
int worker_writer(worker_t* w) {
int r = worker_rw(w, false);
if (r == DONE) {
if (emu_lseek(w->fd, 0, SEEK_SET) != 0) {
fprintf(stderr, "worker('%s') seek failed: %s\n",
w->name, strerror(errno));
return FAIL;
}
// start at 0 and reset our data generator seed
srand64(&w->rdata, w->name);
w->pos = 0;
w->work = worker_verify;
return BUSY;
}
return r;
}
int worker_new(const char* where, const char* fn,
int (*work)(worker_t* w), uint32_t size, uint32_t flags) {
worker_t* w;
if ((w = (worker_t*)calloc(1, sizeof(worker_t))) == nullptr) {
return -1;
}
snprintf(w->name, sizeof(w->name), "%s%s", where, fn);
srand64(&w->rdata, w->name);
srand32(&w->rops, w->name);
w->size = size;
w->work = work;
w->flags = flags;
if ((w->fd = emu_open(w->name, O_RDWR | O_CREAT | O_EXCL, 0644)) < 0) {
fprintf(stderr, "worker('%s') cannot create file\n", w->name);
free(w);
return -1;
}
if (all_workers) {
w->next = all_workers;
}
all_workers = w;
return 0;
}
int do_work() {
uint32_t busy_count = 0;
for (worker_t* w = all_workers; w != nullptr; w = w->next) {
if (w->status == BUSY) {
busy_count++;
if ((w->status = w->work(w)) == FAIL) {
return FAIL;
}
if (w->status == DONE) {
fprintf(stderr, "worker('%s') finished\n", w->name);
}
}
}
return busy_count ? BUSY : DONE;
}
int do_all_work() {
for (;;) {
int r = do_work();
if (r == FAIL) {
return -1;
}
if (r == DONE) {
return 0;
}
}
}
#define KB(n) ((n) * 1024)
#define MB(n) ((n) * 1024 * 1024)
struct {
int (*work)(worker_t*);
const char* name;
uint32_t size;
uint32_t flags;
} WORK[] = {
{ worker_writer, "file0000", MB(8), F_RAND_IOSIZE, },
{ worker_writer, "file0001", MB(8), F_RAND_IOSIZE, },
{ worker_writer, "file0002", MB(8), F_RAND_IOSIZE, },
{ worker_writer, "file0003", MB(8), F_RAND_IOSIZE, },
{ worker_writer, "file0004", MB(8), 0, },
{ worker_writer, "file0005", MB(8), 0, },
{ worker_writer, "file0006", MB(8), 0, },
{ worker_writer, "file0007", MB(8), 0, },
};
int test_rw1() {
const char* where = "::";
for (unsigned n = 0; n < fbl::count_of(WORK); n++) {
if (worker_new(where, WORK[n].name, WORK[n].work, WORK[n].size, WORK[n].flags) < 0) {
return -1;
}
}
emu_unlink("::file0007");
return do_all_work();
}
int test_maxfile() {
int fd = TRY(emu_open("::bigfile", O_CREAT | O_WRONLY, 0644));
if (fd < 0) {
return -1;
}
char data[128*1024];
memset(data, 0xee, sizeof(data));
ssize_t sz = 0;
ssize_t r;
for (;;) {
if ((r = TRY(emu_write(fd, data, sizeof(data)))) < 0) {
return -1;
}
sz += r;
if (r < sizeof(data)) {
break;
}
}
emu_close(fd);
emu_unlink("::bigfile");
fprintf(stderr, "wrote %d bytes\n", (int) sz);
return (r < 0) ? -1 : 0;
}
int test_basic() {
TRY(emu_mkdir("::alpha", 0755));
TRY(emu_mkdir("::alpha/bravo", 0755));
TRY(emu_mkdir("::alpha/bravo/charlie", 0755));
TRY(emu_mkdir("::alpha/bravo/charlie/delta", 0755));
TRY(emu_mkdir("::alpha/bravo/charlie/delta/echo", 0755));
int fd1 = TRY(emu_open("::alpha/bravo/charlie/delta/echo/foxtrot", O_RDWR | O_CREAT, 0644));
int fd2 = TRY(emu_open("::alpha/bravo/charlie/delta/echo/foxtrot", O_RDWR, 0644));
TRY(emu_write(fd1, "Hello, World!\n", 14));
emu_close(fd1);
emu_close(fd2);
fd1 = TRY(emu_open("::file.txt", O_CREAT | O_RDWR, 0644));
emu_close(fd1);
TRY(emu_unlink("::file.txt"));
TRY(emu_mkdir("::emptydir", 0755));
fd1 = TRY(emu_open("::emptydir", O_RDWR, 0644));
EXPECT_FAIL(emu_unlink("::emptydir"));
emu_close(fd1);
TRY(emu_unlink("::emptydir"));
return 0;
}
int test_rename() {
EXPECT_FAIL(emu_rename("::alpha", "::bravo")); // Cannot rename when src does not exist
TRY(emu_mkdir("::alpha", 0755));
EXPECT_FAIL(emu_rename("::alpha", "::alpha")); // Cannot rename to self
int fd = TRY(emu_open("::bravo", O_RDWR | O_CREAT | O_EXCL, 0644));
emu_close(fd);
EXPECT_FAIL(emu_rename("::alpha", "::bravo")); // Cannot rename dir to file
TRY(emu_unlink("::bravo"));
TRY(emu_rename("::alpha", "::bravo")); // Rename dir (dst does not exist)
TRY(emu_mkdir("::alpha", 0755));
TRY(emu_rename("::bravo", "::alpha")); // Rename dir (dst does exist)
fd = TRY(emu_open("::alpha/charlie", O_RDWR | O_CREAT | O_EXCL, 0644));
TRY(emu_rename("::alpha/charlie", "::alpha/delta")); // Rename file (dst does not exist)
emu_close(fd);
fd = TRY(emu_open("::alpha/charlie", O_RDWR | O_CREAT | O_EXCL, 0644));
TRY(emu_rename("::alpha/delta", "::alpha/charlie")); // Rename file (dst does not exist)
EXPECT_FAIL(emu_rename("::alpha/charlie", "::charlie")); // Cannot rename outside current directory
emu_close(fd);
TRY(emu_unlink("::alpha/charlie"));
TRY(emu_unlink("::alpha"));
return 0;
}
int run_fs_tests(int argc, char** argv) {
fprintf(stderr, "--- fs tests ---\n");
if (argc > 0) {
if (!strcmp(argv[0], "maxfile")) {
return test_maxfile();
}
if (!strcmp(argv[0], "rw1")) {
return test_rw1();
}
if (!strcmp(argv[0], "basic")) {
return test_basic();
}
if (!strcmp(argv[0], "rename")) {
return test_rename();
}
fprintf(stderr, "unknown test: %s\n", argv[0]);
return -1;
}
return 0;
}