UNIXworkcode

1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 3 * 4 * Copyright 2019 Olaf Wintermann. All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include <stdio.h> 30 #include <stdlib.h> 31 #include <errno.h> 32 33 #include <cx/string.h> 34 #include <cx/list.h> 35 #include <cx/map.h> 36 37 #include "../daemon/session.h" 38 39 #include "testutils.h" 40 41 #include "vfs.h" 42 43 typedef struct TestVFS { 44 CxMap *files; 45 int count_unlink; 46 int count_rmdir; 47 } TestVFS; 48 49 typedef struct TestVFSFile { 50 VFSFile file; 51 cxmutstr path; 52 int isdir; 53 CxBuffer content; 54 } TestVFSFile; 55 56 typedef struct TestVFSDir { 57 VFSDir dir; 58 TestVFSFile *file; 59 CxIterator i; 60 cxmutstr name; 61 } TestVFSDir; 62 63 /* dir io */ 64 65 static const char* test_resource_name(char *url) { 66 cxstring urlstr = cx_str(url); 67 if(urlstr.ptr[urlstr.length-1] == '/') { 68 urlstr.length--; 69 } 70 cxstring resname = cx_strrchr(urlstr, '/'); 71 if(resname.length > 1) { 72 return resname.ptr+1; 73 } else { 74 return url; 75 } 76 } 77 78 int testvfs_readdir(VFS_DIR dir, VFS_ENTRY *entry, int getstat) { 79 TestVFS *vfs = dir->ctx->vfs->instance; 80 TestVFSDir *vfsdir = (TestVFSDir*)dir; 81 82 cxmutstr prefix = cx_strcat(2, vfsdir->file->path, cx_str("/")); 83 84 // not the most efficient file system implementation ever, but it is only 85 // for testing 86 // check every entry in vfs->files and return, if the parent path 87 // matches the open directory 88 TestVFSFile *file = NULL; 89 cx_foreach(TestVFSFile *, entry, vfsdir->i) { 90 if(file) break; 91 cxmutstr file_path = cx_strcat( 92 2, 93 prefix, 94 cx_str(test_resource_name(entry->path.ptr))); 95 file = cxMapGet(vfs->files, cx_hash_key(file_path.ptr, file_path.length)); 96 free(file_path.ptr); 97 } 98 free(prefix.ptr); 99 100 if(file) { 101 vfsdir->name = cx_strdup_a( 102 pool_allocator(dir->ctx->sn->pool), 103 cx_str(test_resource_name(file->path.ptr))); 104 ZERO(entry, sizeof(VFS_ENTRY)); 105 entry->name = vfsdir->name.ptr; 106 107 if(getstat) { 108 ZERO(&entry->stat, sizeof(struct stat)); 109 if(file->isdir) { 110 entry->stat.st_mode = S_IFDIR; 111 } 112 } 113 114 return 1; 115 } else { 116 return 0; 117 } 118 } 119 120 void testvfs_dir_close(VFS_DIR dir) { 121 TestVFSDir *testdir = (TestVFSDir*)dir; 122 pool_free(testdir->dir.ctx->sn->pool, dir); 123 124 } 125 126 ssize_t testvfs_read(SYS_FILE fd, void *buf, size_t nbyte) { 127 TestVFSFile *file = (TestVFSFile*)fd; 128 return (ssize_t)cxBufferRead(buf, 1, nbyte, &file->content); 129 } 130 131 ssize_t testvfs_write(SYS_FILE fd, const void *buf, size_t nbyte) { 132 TestVFSFile *file = (TestVFSFile*)fd; 133 return (ssize_t)cxBufferWrite(buf, 1, nbyte, &file->content); 134 } 135 136 ssize_t testvfs_pread(SYS_FILE fd, void *buf, size_t nbyte, off_t offset) { 137 TestVFSFile *file = (TestVFSFile*)fd; 138 file->content.pos = (size_t)offset; 139 return testvfs_read(fd, buf, nbyte); 140 } 141 142 ssize_t testvfs_pwrite(SYS_FILE fd, const void *buf, size_t nbyte, off_t offset) { 143 TestVFSFile *file = (TestVFSFile*)fd; 144 file->content.pos = (size_t)offset; 145 return testvfs_write(fd, buf, nbyte); 146 } 147 148 off_t testvfs_seek(SYS_FILE fd, off_t offset, int whence) { 149 TestVFSFile *file = (TestVFSFile*)fd; 150 cxBufferSeek(&file->content, offset, whence); 151 return (off_t)file->content.pos; 152 } 153 154 void testvfs_close(SYS_FILE fd) { 155 TestVFSFile *file = (TestVFSFile*)fd; 156 file->content.pos = 0; 157 } 158 159 VFS_IO test_file_io = { 160 testvfs_read, 161 testvfs_write, 162 testvfs_pread, 163 testvfs_pwrite, 164 testvfs_seek, 165 testvfs_close, 166 NULL, /* aio_read */ 167 NULL /* aio_write */ 168 }; 169 170 VFS_DIRIO test_dir_io = { 171 testvfs_readdir, 172 testvfs_dir_close 173 }; 174 175 176 /* vfs funcs */ 177 178 SYS_FILE testvfs_open(VFSContext *ctx, const char *path, int oflags) { 179 TestVFS *vfs = ctx->vfs->instance; 180 TestVFSFile *file = NULL; 181 182 cxstring s_path = cx_str((char*)path); 183 if(cx_strsuffix(s_path, cx_str("/"))) { 184 s_path.length--; 185 } 186 187 file = cxMapGet(vfs->files, cx_hash_key_bytes((const unsigned char*)s_path.ptr, s_path.length)); 188 if(!file) { 189 if((oflags & O_CREAT) == O_CREAT) { 190 file = pool_malloc(ctx->sn->pool, sizeof(TestVFSFile)); 191 ZERO(file, sizeof(TestVFSFile)); 192 file->file.ctx = ctx; 193 file->path = cx_strdup_a(pool_allocator(ctx->sn->pool), s_path); 194 file->file.io = &test_file_io; 195 196 cxBufferInit(&file->content, pool_malloc(ctx->sn->pool, 2048), 2048, pool_allocator(ctx->sn->pool), 0); 197 198 cxMapPut(vfs->files, cx_hash_key((void*)s_path.ptr, s_path.length), file); 199 } else { 200 ctx->vfs_errno = ENOENT; 201 } 202 } 203 204 return (SYS_FILE)file; 205 } 206 207 int testvfs_stat(VFSContext *ctx, const char *path, struct stat *buf) { 208 TestVFS *vfs = ctx->vfs->instance; 209 TestVFSFile *file = NULL; 210 211 cxstring s_path = cx_str((char*)path); 212 if(cx_strsuffix(s_path, cx_str("/"))) { 213 s_path.length--; 214 } 215 216 file = cxMapGet(vfs->files, cx_hash_key((void*)s_path.ptr, s_path.length)); 217 if(!file) { 218 ctx->vfs_errno = ENOENT; 219 return 1; 220 } 221 222 ZERO(buf, sizeof(struct stat)); 223 if(file->isdir) { 224 buf->st_mode = S_IFDIR; 225 } 226 227 return 0; 228 } 229 230 int testvfs_fstat(VFSContext *ctx, SYS_FILE fd, struct stat *buf) { 231 return 0; 232 } 233 234 VFS_DIR testvfs_opendir(VFSContext *ctx, const char *path) { 235 TestVFS *vfs = ctx->vfs->instance; 236 TestVFSFile *file = NULL; 237 238 cxstring s_path = cx_str((char*)path); 239 if(cx_strsuffix(s_path, cx_str("/"))) { 240 s_path.length--; 241 } 242 243 file = cxMapGet(vfs->files, cx_hash_key((void*)s_path.ptr, s_path.length)); 244 if(!file) { 245 ctx->vfs_errno = ENOENT; 246 return NULL; 247 } 248 249 if(!file->isdir) { 250 return NULL; 251 } 252 253 TestVFSDir *dir = pool_malloc(ctx->sn->pool, sizeof(TestVFSDir)); 254 ZERO(dir, sizeof(TestVFSDir)); 255 dir->file = file; 256 dir->i = cxMapIteratorValues(vfs->files); 257 258 dir->dir.ctx = ctx; 259 dir->dir.io = &test_dir_io; 260 261 return (VFS_DIR)dir; 262 } 263 264 VFS_DIR testvfs_fdopendir(VFSContext *ctx, SYS_FILE fd) { 265 TestVFS *vfs = ctx->vfs->instance; 266 TestVFSFile *file = (TestVFSFile*)fd; 267 if(!file->isdir) { 268 return NULL; 269 } 270 271 TestVFSDir *dir = pool_malloc(ctx->sn->pool, sizeof(TestVFSDir)); 272 ZERO(dir, sizeof(TestVFSDir)); 273 dir->file = file; 274 dir->i = cxMapIteratorValues(vfs->files); 275 276 dir->dir.ctx = ctx; 277 dir->dir.io = &test_dir_io; 278 279 return (VFS_DIR)dir; 280 } 281 282 int testvfs_mkdir(VFSContext *ctx, const char *path) { 283 SYS_FILE fd = testvfs_open(ctx, path, O_CREAT); 284 if(!fd) { 285 return 1; 286 } 287 288 TestVFSFile *file = (TestVFSFile*)fd; 289 file->isdir = 1; 290 291 return 0; 292 } 293 294 int testvfs_unlink(VFSContext *ctx, const char *path) { 295 TestVFS *vfs = ctx->vfs->instance; 296 CxHashKey path_key = cx_hash_key_str(path); 297 TestVFSFile *file = cxMapGet(vfs->files, path_key); 298 if(!file) { 299 return 1; 300 } 301 302 if(file->isdir) { 303 return 1; 304 } 305 306 cxMapRemove(vfs->files, path_key); 307 vfs->count_unlink++; 308 return 0; 309 } 310 311 int testvfs_rmdir(VFSContext *ctx, const char *path) { 312 TestVFS *vfs = ctx->vfs->instance; 313 CxHashKey path_key = cx_hash_key_str(path); 314 TestVFSFile *dir = cxMapGet(vfs->files, path_key); 315 if(!dir) { 316 ctx->vfs_errno = ENOENT; 317 return 1; 318 } 319 320 if(!dir->isdir) { 321 return 1; 322 } 323 324 CxIterator i = cxMapIteratorValues(vfs->files); 325 cx_foreach(TestVFSFile *, f, i) { 326 if(f->path.length > dir->path.length && cx_strprefix(cx_strcast(f->path), cx_strcast(dir->path))){ 327 return 1; // dir not empty 328 } 329 } 330 331 cxMapRemove(vfs->files, path_key); 332 vfs->count_rmdir++; 333 return 0; 334 } 335 336 static VFS testVFSClass = { 337 testvfs_open, 338 testvfs_stat, 339 testvfs_fstat, 340 testvfs_opendir, 341 testvfs_fdopendir, 342 testvfs_mkdir, 343 testvfs_unlink, 344 testvfs_rmdir, 345 0, 346 NULL 347 }; 348 349 350 VFS* testvfs_create(Session *sn) { 351 TestVFS *vfs = pool_malloc(sn->pool, sizeof(TestVFS)); 352 vfs->count_unlink = 0; 353 vfs->count_rmdir = 0; 354 vfs->files = cxHashMapCreate(pool_allocator(sn->pool), CX_STORE_POINTERS, 64); 355 356 testVFSClass.instance = vfs; 357 return &testVFSClass; 358 } 359 360 361 /* ------------------------------------------------------------------------- */ 362 // 363 // VFS Tests 364 // 365 /* ------------------------------------------------------------------------- */ 366 367 UCX_TEST(test_vfs_open) { 368 Session *sn = testutil_session(); 369 Request *rq = testutil_request(sn->pool, "PUT", "/"); 370 rq->vfs = testvfs_create(sn); 371 372 VFSContext *vfs = vfs_request_context(sn, rq); 373 374 UCX_TEST_BEGIN; 375 376 UCX_TEST_ASSERT(vfs, "vfs is NULL"); 377 378 SYS_FILE f1 = vfs_open(vfs, "/file1", O_CREAT); 379 UCX_TEST_ASSERT(f1, "f1 not opened"); 380 381 SYS_FILE f2 = vfs_open(vfs, "/file1", 0); 382 UCX_TEST_ASSERT(f2, "f2 not opened"); 383 384 UCX_TEST_END; 385 386 testutil_destroy_session(sn); 387 } 388 389 UCX_TEST(test_vfs_mkdir) { 390 Session *sn = testutil_session(); 391 Request *rq = testutil_request(sn->pool, "PUT", "/"); 392 rq->vfs = testvfs_create(sn); 393 394 VFSContext *vfs = vfs_request_context(sn, rq); 395 396 UCX_TEST_BEGIN; 397 398 int err = vfs_mkdir(vfs, "/dir"); 399 UCX_TEST_ASSERT(err == 0, "error not 0"); 400 401 SYS_FILE fd = vfs_open(vfs, "/dir", 0); 402 UCX_TEST_ASSERT(fd, "no fd"); 403 404 UCX_TEST_END; 405 406 testutil_destroy_session(sn); 407 } 408 409 UCX_TEST(test_vfs_opendir) { 410 Session *sn = testutil_session(); 411 Request *rq = testutil_request(sn->pool, "PUT", "/"); 412 rq->vfs = testvfs_create(sn); 413 414 VFSContext *vfs = vfs_request_context(sn, rq); 415 416 UCX_TEST_BEGIN; 417 418 int err = vfs_mkdir(vfs, "/dir"); 419 UCX_TEST_ASSERT(err == 0, "error not 0"); 420 421 VFSDir *dir = vfs_opendir(vfs, "/dir"); 422 UCX_TEST_ASSERT(dir, "no dir"); 423 424 UCX_TEST_END; 425 426 testutil_destroy_session(sn); 427 } 428 429 UCX_TEST(test_vfs_readdir) { 430 Session *sn = testutil_session(); 431 Request *rq = testutil_request(sn->pool, "PUT", "/"); 432 rq->vfs = testvfs_create(sn); 433 434 VFSContext *vfs = vfs_request_context(sn, rq); 435 436 UCX_TEST_BEGIN; 437 438 int err = vfs_mkdir(vfs, "/dir"); 439 UCX_TEST_ASSERT(err == 0, "error not 0"); 440 441 // add some test file to /dir 442 UCX_TEST_ASSERT(vfs_open(vfs, "/dir/file1", O_CREAT), "creation of file1 failed"); 443 UCX_TEST_ASSERT(vfs_open(vfs, "/dir/file2", O_CREAT), "creation of file2 failed"); 444 UCX_TEST_ASSERT(vfs_open(vfs, "/dir/file3", O_CREAT), "creation of file3 failed"); 445 UCX_TEST_ASSERT(vfs_open(vfs, "/dir/file4", O_CREAT), "creation of file4 failed"); 446 447 VFSDir *dir = vfs_opendir(vfs, "/dir"); 448 UCX_TEST_ASSERT(dir, "dir not opened"); 449 450 CxMap *files = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 8); 451 452 VFSEntry entry; 453 while(vfs_readdir(dir, &entry)) { 454 cxMapPut(files, cx_hash_key_str(entry.name), dir); 455 } 456 457 UCX_TEST_ASSERT(files->size == 4, "wrong files count"); 458 UCX_TEST_ASSERT(cxMapGet(files, cx_hash_key_str("file1")), "file1 missing"); 459 UCX_TEST_ASSERT(cxMapGet(files, cx_hash_key_str("file2")), "file2 missing"); 460 UCX_TEST_ASSERT(cxMapGet(files, cx_hash_key_str("file3")), "file3 missing"); 461 UCX_TEST_ASSERT(cxMapGet(files, cx_hash_key_str("file4")), "file4 missing"); 462 463 cxMapDestroy(files); 464 465 UCX_TEST_END; 466 467 testutil_destroy_session(sn); 468 } 469 470 UCX_TEST(test_vfs_unlink) { 471 Session *sn = testutil_session(); 472 Request *rq = testutil_request(sn->pool, "PUT", "/"); 473 rq->vfs = testvfs_create(sn); 474 475 VFSContext *vfs = vfs_request_context(sn, rq); 476 477 UCX_TEST_BEGIN; 478 // prepare test 479 int err; 480 err = vfs_mkdir(vfs, "/dir1"); 481 UCX_TEST_ASSERT(err == 0, "mkdir 1: error not 0"); 482 err = vfs_mkdir(vfs, "/dir2"); 483 UCX_TEST_ASSERT(err == 0, "mkdir 1: error not 0"); 484 485 SYS_FILE f1 = vfs_open(vfs, "/file1", O_CREAT); 486 UCX_TEST_ASSERT(f1, "f1 not opened"); 487 488 SYS_FILE f2 = vfs_open(vfs, "/file2", O_CREAT); 489 UCX_TEST_ASSERT(f1, "f2 not opened"); 490 491 SYS_FILE f3 = vfs_open(vfs, "/dir1/file3", O_CREAT); 492 UCX_TEST_ASSERT(f1, "f3 not opened"); 493 494 // test unlink 495 err = vfs_unlink(vfs, "/file1"); 496 UCX_TEST_ASSERT(err == 0, "unlink /file1 failed"); 497 err = vfs_unlink(vfs, "/dir1/file3"); 498 UCX_TEST_ASSERT(err == 0, "unlink /dir1/file3 failed"); 499 500 err = vfs_unlink(vfs, "/filex"); 501 UCX_TEST_ASSERT(err != 0, "unlink /filex should fail"); 502 503 // check if files were removed 504 SYS_FILE o1 = vfs_open(vfs, "/file1", O_RDONLY); 505 UCX_TEST_ASSERT(o1 == NULL, "/file1 not deleted"); 506 SYS_FILE o3 = vfs_open(vfs, "/dir1/file3", O_RDONLY); 507 UCX_TEST_ASSERT(o1 == NULL, "/dir1/file3 not deleted"); 508 509 // file2 should still be there 510 SYS_FILE o2 = vfs_open(vfs, "/file2", O_RDONLY); 511 UCX_TEST_ASSERT(o2, "/file2 deleted"); 512 513 // check if dir unlink fails 514 err = vfs_unlink(vfs, "/dir1"); 515 UCX_TEST_ASSERT(err != 0, "unlink dir1 should fail"); 516 517 UCX_TEST_END; 518 519 testutil_destroy_session(sn); 520 } 521 522 UCX_TEST(test_vfs_rmdir) { 523 Session *sn = testutil_session(); 524 Request *rq = testutil_request(sn->pool, "PUT", "/"); 525 rq->vfs = testvfs_create(sn); 526 527 VFSContext *vfs = vfs_request_context(sn, rq); 528 529 UCX_TEST_BEGIN; 530 // prepare test 531 int err; 532 err = vfs_mkdir(vfs, "/dir1"); 533 UCX_TEST_ASSERT(err == 0, "mkdir 1: error not 0"); 534 err = vfs_mkdir(vfs, "/dir2"); 535 UCX_TEST_ASSERT(err == 0, "mkdir 1: error not 0"); 536 537 SYS_FILE f1 = vfs_open(vfs, "/dir1/file1", O_CREAT); 538 UCX_TEST_ASSERT(f1, "f1 not opened"); 539 540 err = vfs_rmdir(vfs, "/dir1"); 541 UCX_TEST_ASSERT(err != 0, "rmdir /dir1 should fail"); 542 err = vfs_rmdir(vfs, "/dir2"); 543 UCX_TEST_ASSERT(err == 0, "rmdir /dir2 failed"); 544 545 err = vfs_unlink(vfs, "/dir1/file1"); 546 UCX_TEST_ASSERT(err == 0, "unlink failed"); 547 err = vfs_rmdir(vfs, "/dir1"); 548 UCX_TEST_ASSERT(err == 0, "rmdir /dir1 (2) failed"); 549 550 UCX_TEST_END; 551 552 testutil_destroy_session(sn); 553 } 554