UNIXworkcode

1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 3 * 4 * Copyright 2022 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 "vfs.h" 30 #include "config.h" 31 32 #include <inttypes.h> 33 34 #include "../../util/util.h" 35 #include "../../util/pblock.h" 36 37 static VFS pg_vfs_class = { 38 pg_vfs_open, 39 pg_vfs_stat, 40 pg_vfs_fstat, 41 pg_vfs_opendir, 42 pg_vfs_fdopendir, 43 pg_vfs_mkdir, 44 pg_vfs_unlink, 45 pg_vfs_rmdir 46 }; 47 48 static VFS_IO pg_vfs_io_class = { 49 pg_vfs_io_read, 50 pg_vfs_io_write, 51 pg_vfs_io_pread, 52 pg_vfs_io_pwrite, 53 pg_vfs_io_seek, 54 pg_vfs_io_close, 55 NULL, // no pg aio implementation yet 56 NULL, 57 pg_vfs_io_getetag 58 }; 59 60 static VFS_DIRIO pg_vfs_dirio_class = { 61 pg_vfs_dirio_readdir, 62 pg_vfs_dirio_close 63 }; 64 65 66 /* 67 * SQL Queries 68 */ 69 70 // Resolves a path into resource_id and parent_id 71 // params: $1: path string 72 static const char *sql_resolve_path = 73 "with recursive resolvepath as (\n\ 74 select\n\ 75 resource_id,\n\ 76 parent_id,\n\ 77 '''' as fullpath,\n\ 78 resoid,\n\ 79 iscollection,\n\ 80 lastmodified,\n\ 81 creationdate,\n\ 82 contentlength,\n\ 83 etag,\n\ 84 regexp_split_to_array($1, ''/'') as pathelm,\n\ 85 1 as pathdepth\n\ 86 from Resource\n\ 87 where resource_id = $2\n\ 88 union\n\ 89 select\n\ 90 r.resource_id,\n\ 91 r.parent_id,\n\ 92 p.fullpath || ''/'' || r.nodename,\n\ 93 r.resoid,\n\ 94 r.iscollection,\n\ 95 r.lastmodified,\n\ 96 r.creationdate,\n\ 97 r.contentlength,\n\ 98 r.etag,\n\ 99 p.pathelm,\n\ 100 p.pathdepth + 1\n\ 101 from Resource r\n\ 102 inner join resolvepath p on r.parent_id = p.resource_id\n\ 103 where p.pathelm[p.pathdepth+1] = r.nodename\n\ 104 )\n\ 105 select resource_id, parent_id, fullpath, resoid, iscollection, lastmodified, creationdate, contentlength, etag from resolvepath\n\ 106 where fullpath = $1 ;"; 107 108 // Same as sql_resolve_path, but it returns the root collection 109 // params: $1: path string (should be '/') 110 static const char *sql_get_root = "select resource_id, parent_id, $1 as fullpath, resoid, true as iscollection, lastmodified, creationdate, contentlength, etag from Resource where resource_id = $2;"; 111 112 // Get all children of a specific collection 113 // params: $1: parent resource_id 114 static const char *sql_get_children = "select resource_id, nodename, iscollection, lastmodified, creationdate, contentlength, etag from Resource where parent_id = $1;"; 115 116 // Get resource 117 // params: $1: resource_id 118 static const char *sql_get_resource = "select resource_id, nodename, iscollection, lastmodified, creationdate, contentlength, etag from Resource where resource_id = $1;"; 119 120 // Create resource 121 // params: $1: parent_id 122 // $2: node name 123 static const char *sql_create_resource = 124 "insert into Resource (parent_id, nodename, iscollection, lastmodified, creationdate, contentlength, resoid) values\n\ 125 ($1, $2, false, now(), now(), 0, lo_creat(-1))\n\ 126 returning resource_id, resoid, lastmodified, creationdate;"; 127 128 // Create collection 129 // params: $1: parent_id 130 // $2: node name 131 static const char *sql_create_collection = 132 "insert into Resource (parent_id, nodename, iscollection, lastmodified, creationdate, contentlength) values\n\ 133 ($1, $2, true, now(), now(), 0)\n\ 134 returning resource_id, lastmodified, creationdate;"; 135 136 // Update resource metadata 137 // params: $1: resource_id 138 // $2: contentlength 139 static const char *sql_update_resource = "update Resource set contentlength = $2, lastmodified = now(), etag = gen_random_uuid() where resource_id = $1;"; 140 141 // Delete a resource 142 // params: $1: resource_id 143 static const char *sql_delete_res = "delete from Resource where parent_id is not null and resource_id = $1;"; 144 145 146 void* pg_vfs_init(ServerConfiguration *cfg, pool_handle_t *pool, WSConfigNode *config) { 147 return pg_init_repo(cfg, pool, config); 148 } 149 150 VFS* pg_vfs_create(Session *sn, Request *rq, pblock *pb, void *initData) { 151 PgRepository *repo = initData; 152 153 char *resource_pool; 154 if(repo) { 155 resource_pool = repo->resourcepool.ptr; 156 } else { 157 // resourcepool is required 158 resource_pool = pblock_findval("resourcepool", pb); 159 if(!resource_pool) { 160 log_ereport(LOG_MISCONFIG, "pg_vfs_create: missing resourcepool parameter"); 161 return NULL; 162 } 163 } 164 165 // get the resource first (most likely failure reason is misconfig) 166 ResourceData *resdata = resourcepool_lookup(sn, rq, resource_pool, 0); 167 if(!resdata) { 168 log_ereport(LOG_MISCONFIG, "postgresql vfs: resource pool %s not found", resource_pool); 169 return NULL; 170 } 171 // resdata will be freed automatically when the request is finished 172 173 return pg_vfs_create_from_resourcedata(sn, rq, repo, resdata); 174 } 175 176 VFS* pg_vfs_create_from_resourcedata(Session *sn, Request *rq, PgRepository *repo, ResourceData *resdata) { 177 // Create a new VFS object and a separate instance object 178 // VFS contains fptrs that can be copied from pg_vfs_class 179 // instance contains request specific data (PGconn) 180 VFS *vfs = pool_malloc(sn->pool, sizeof(VFS)); 181 if(!vfs) { 182 return NULL; 183 } 184 185 PgVFS *vfs_priv = pool_malloc(sn->pool, sizeof(PgVFS)); 186 if(!vfs_priv) { 187 pool_free(sn->pool, vfs); 188 return NULL; 189 } 190 vfs_priv->connection = resdata->data; 191 vfs_priv->pg_resource = resdata; 192 vfs_priv->root_resource_id = repo->root_resource_id; 193 snprintf(vfs_priv->root_resource_id_str, 32, "%" PRId64, repo->root_resource_id); 194 195 memcpy(vfs, &pg_vfs_class, sizeof(VFS)); 196 vfs->flags = 0; 197 vfs->instance = vfs_priv; 198 199 return vfs; 200 } 201 202 203 int pg_resolve_path( 204 PGconn *connection, 205 const char *path, 206 const char *root_id, 207 int64_t *parent_id, 208 int64_t *resource_id, 209 Oid *oid, 210 const char **resource_name, 211 WSBool *iscollection, 212 struct stat *s, 213 char *etag, 214 int *res_errno) 215 { 216 // basic path validation 217 if(!path) return 1; 218 size_t pathlen = strlen(path); 219 if(pathlen == 0) return 1; 220 if(path[0] != '/') { 221 return 1; 222 } 223 224 char *pathf = NULL; 225 if(pathlen > 1 && path[pathlen-1] == '/') { 226 pathf = malloc(pathlen); 227 memcpy(pathf, path, pathlen); 228 pathf[pathlen-1] = 0; // remove trailing '/' 229 path = pathf; 230 } 231 232 // get last node of path 233 *resource_name = util_resource_name(path); 234 235 const char *sql = pathlen == 1 ? sql_get_root : sql_resolve_path; 236 const char* params[2] = { path, root_id }; 237 PGresult *result = PQexecParams( 238 connection, 239 sql, 240 2, // number of parameters 241 NULL, 242 params, // parameter value 243 NULL, 244 NULL, 245 0); // 0: result in text format 246 247 if(pathf) { 248 free(pathf); 249 } 250 251 if(!result) return 1; 252 253 int ret = 1; 254 //int nfields = PQnfields(result); 255 int nrows = PQntuples(result); 256 if(nrows == 1) { 257 char *resource_id_str = PQgetvalue(result, 0, 0); 258 char *parent_id_str = PQgetvalue(result, 0, 1); 259 char *iscol = PQgetvalue(result, 0, 4); 260 char *lastmodified = PQgetvalue(result, 0, 5); 261 char *creationdate = PQgetvalue(result, 0, 6); 262 char *contentlength = PQgetvalue(result, 0, 7); 263 char *res_etag = PQgetvalue(result, 0, 8); 264 if(resource_id_str && parent_id_str) { 265 if(util_strtoint(resource_id_str, resource_id)) { 266 ret = 0; // success 267 } 268 // optionally get parent_id 269 util_strtoint(parent_id_str, parent_id); 270 } 271 272 if(oid) { 273 char *resoid = PQgetvalue(result, 0, 3); 274 int64_t roid; 275 if(resoid && util_strtoint(resoid, &roid)) { 276 *oid = roid; 277 } 278 } 279 280 if(iscollection && iscol) { 281 *iscollection = iscol[0] == 't' ? TRUE : FALSE; 282 } 283 284 if(s) { 285 pg_set_stat(s, iscol, lastmodified, creationdate, contentlength); 286 } 287 288 if(etag) { 289 size_t etag_len = strlen(res_etag); 290 if(etag_len < PG_ETAG_MAXLEN) 291 memcpy(etag, res_etag, etag_len+1); 292 } 293 } else if(res_errno) { 294 *res_errno = ENOENT; 295 } 296 297 PQclear(result); 298 299 return ret; 300 } 301 302 303 void pg_set_stat( 304 struct stat *s, 305 const char *iscollection, 306 const char *lastmodified, 307 const char *creationdate, 308 const char *contentlength) 309 { 310 memset(s, 0, sizeof(struct stat)); 311 if(iscollection) { 312 WSBool iscol = iscollection[0] == 't' ? TRUE : FALSE; 313 if(iscol) { 314 s->st_mode |= 0x4000; 315 } 316 } 317 s->st_mtime = pg_convert_timestamp(lastmodified); 318 319 if(contentlength) { 320 int64_t len; 321 if(util_strtoint(contentlength, &len)) { 322 s->st_size = len; 323 } 324 } 325 } 326 327 static int pg_create_res( 328 PgVFS *pg, 329 const char *resparentid_str, 330 const char *nodename, 331 int64_t *new_resource_id, 332 Oid *oid, 333 const char **resource_name, 334 struct stat *s) 335 { 336 const char* params[2] = { resparentid_str, nodename }; 337 PGresult *result = PQexecParams( 338 pg->connection, 339 sql_create_resource, 340 2, // number of parameters 341 NULL, 342 params, // parameter value 343 NULL, 344 NULL, 345 0); // 0: result in text format 346 347 if(!result) return 1; 348 349 int ret = 1; 350 if(PQntuples(result) == 1) { 351 // sql insert succesful 352 ret = 0; 353 354 char *id_str = PQgetvalue(result, 0, 0); 355 char *oid_str = PQgetvalue(result, 0, 1); 356 char *lastmodified = PQgetvalue(result, 0, 2); 357 char *creationdate = PQgetvalue(result, 0, 3); 358 359 if(new_resource_id) { 360 if(!id_str || !util_strtoint(id_str, new_resource_id)) { 361 ret = 1; // shouldn't happen 362 log_ereport(LOG_FAILURE, "Postgresql VFS: sql_create_resource: Could not convert resource_id to int"); 363 } 364 } 365 if(oid) { 366 int64_t i; 367 if(!oid_str || !util_strtoint(oid_str, &i)) { 368 ret = 1; // shouldn't happen 369 log_ereport(LOG_FAILURE, "Postgresql VFS: sql_create_resource: Could not convert oid to int"); 370 } else { 371 *oid = i; 372 } 373 } 374 if(resource_name) { 375 *resource_name = nodename; 376 } 377 if(s) { 378 pg_set_stat(s, 0, lastmodified, creationdate, NULL); 379 } 380 } 381 382 PQclear(result); 383 384 return ret; 385 } 386 387 388 static int pg_create_col( 389 PgVFS *pg, 390 const char *resparentid_str, 391 const char *nodename, 392 int64_t *new_resource_id, 393 const char **resource_name, 394 struct stat *s) 395 { 396 const char* params[2] = { resparentid_str, nodename }; 397 PGresult *result = PQexecParams( 398 pg->connection, 399 sql_create_collection, 400 2, // number of parameters 401 NULL, 402 params, // parameter value 403 NULL, 404 NULL, 405 0); // 0: result in text format 406 407 if(!result) return 1; 408 409 int ret = 1; 410 if(PQntuples(result) == 1) { 411 // sql insert succesful 412 ret = 0; 413 414 char *id_str = PQgetvalue(result, 0, 0); 415 char *lastmodified = PQgetvalue(result, 0, 1); 416 char *creationdate = PQgetvalue(result, 0, 2); 417 418 if(new_resource_id) { 419 if(!id_str || !util_strtoint(id_str, new_resource_id)) { 420 ret = 1; // shouldn't happen 421 log_ereport(LOG_FAILURE, "Postgresql VFS: sql_create_collection: Could not convert resource_id to int"); 422 } 423 } 424 if(resource_name) { 425 *resource_name = nodename; 426 } 427 if(s) { 428 pg_set_stat(s, 0, lastmodified, creationdate, NULL); 429 } 430 } 431 432 PQclear(result); 433 434 return ret; 435 } 436 437 int pg_create_file( 438 VFSContext *ctx, 439 PgVFS *pg, 440 const char *path, 441 int64_t *new_resource_id, 442 int64_t *res_parent_id, 443 Oid *oid, 444 const char **resource_name, 445 struct stat *s, 446 WSBool collection) 447 { 448 char *parent_path = util_parent_path(path); 449 if(!parent_path) return 1; 450 451 size_t pathlen = strlen(path); 452 char *pathf = NULL; 453 if(pathlen > 1 && path[pathlen-1] == '/') { 454 pathf = malloc(pathlen); 455 memcpy(pathf, path, pathlen); 456 pathf[pathlen-1] = 0; // remove trailing '/' 457 path = pathf; 458 } 459 460 const char *nodename = util_resource_name(path); 461 462 // resolve the parent path 463 // if the parent path can't be resolved, we are done 464 const char *resname; 465 int64_t resource_id, parent_id; 466 resource_id = -1; 467 parent_id = -1; 468 WSBool iscollection; 469 Oid unused_oid = 0; 470 471 int err = pg_resolve_path( 472 pg->connection, 473 parent_path, 474 pg->root_resource_id_str, 475 &parent_id, 476 &resource_id, 477 &unused_oid, 478 &resname, 479 &iscollection, 480 NULL, 481 NULL, 482 &ctx->vfs_errno); 483 FREE(parent_path); 484 if(err) { 485 ctx->vfs_errno = ENOENT; 486 if(pathf) free(pathf); 487 return 1; 488 } 489 490 // parent path exists, check if it is a collection 491 if(!iscollection) { 492 if(pathf) free(pathf); 493 return 1; 494 } 495 496 // create new Resource 497 char resid_str[32]; 498 snprintf(resid_str, 32, "%" PRId64, resource_id); // convert parent resource_id to string 499 500 int ret; 501 if(collection) { 502 ret = pg_create_col(pg, resid_str, nodename, new_resource_id, resource_name, s); 503 } else { 504 ret = pg_create_res(pg, resid_str, nodename, new_resource_id, oid, resource_name, s); 505 } 506 507 if(pathf) free(pathf); 508 509 if(res_parent_id) { 510 // resource_id is still the id of the parent from previous pg_resolve_path call 511 *res_parent_id = resource_id; 512 } 513 514 return ret; 515 } 516 517 int pg_remove_res( 518 VFSContext *ctx, 519 PgVFS *pg, 520 int64_t resource_id, 521 Oid oid) 522 { 523 // create transaction savepoint 524 PGresult *result = PQexec(pg->connection, "savepoint del_res;"); 525 ExecStatusType execStatus = PQresultStatus(result); 526 PQclear(result); 527 if(execStatus != PGRES_COMMAND_OK) { 528 return 1; 529 } 530 531 if(oid > 0) { 532 if(lo_unlink(pg->connection, oid) != 1) { 533 // restore savepoint 534 result = PQexec(pg->connection, "rollback to savepoint del_res;"); 535 PQclear(result); 536 return 1; // error 537 } 538 } 539 540 char resid_str[32]; 541 snprintf(resid_str, 32, "%" PRId64, resource_id); 542 543 const char* params[1] = { resid_str }; 544 result = PQexecParams( 545 pg->connection, 546 sql_delete_res, 547 1, // number of parameters 548 NULL, 549 params, // parameter value 550 NULL, 551 NULL, 552 0); // 0: result in text format 553 554 execStatus = PQresultStatus(result); 555 PQclear(result); 556 int ret = 0; 557 558 if(execStatus != PGRES_COMMAND_OK) { 559 ret = 1; 560 // restore savepoint 561 result = PQexec(pg->connection, "rollback to savepoint del_res;"); 562 PQclear(result); 563 } else { 564 // we don't need the savepoint anymore 565 result = PQexec(pg->connection, "release savepoint del_res;"); 566 PQclear(result); 567 } 568 569 return ret; 570 } 571 572 int pg_update_resource(PgVFS *pg, int64_t resource_id, int64_t contentlength) { 573 char resid_str[32]; 574 char ctlen_str[32]; 575 snprintf(resid_str, 32, "%" PRId64, resource_id); 576 snprintf(ctlen_str, 32, "%" PRId64, contentlength); 577 578 const char* params[2] = { resid_str, ctlen_str }; 579 PGresult *result = PQexecParams( 580 pg->connection, 581 sql_update_resource, 582 2, // number of parameters 583 NULL, 584 params, // parameter value 585 NULL, 586 NULL, 587 0); // 0: result in text format 588 589 int ret = PQresultStatus(result) == PGRES_COMMAND_OK ? 0 : 1; 590 PQclear(result); 591 return ret; 592 } 593 594 /* -------------------------- VFS functions -------------------------- */ 595 596 SYS_FILE pg_vfs_open(VFSContext *ctx, const char *path, int oflags) { 597 VFS *vfs = ctx->vfs; 598 PgVFS *pg = vfs->instance; 599 600 const char *resname; 601 int64_t resource_id, parent_id; 602 resource_id = -1; 603 parent_id = -1; 604 WSBool iscollection; 605 struct stat s; 606 char etag[PG_ETAG_MAXLEN]; 607 Oid oid = 0; 608 if(pg_resolve_path(pg->connection, path, pg->root_resource_id_str, &parent_id, &resource_id, &oid, &resname, &iscollection, &s, etag, &ctx->vfs_errno)) { 609 if((oflags & O_CREAT) == O_CREAT) { 610 if(pg_create_file(ctx, pg, path, &resource_id, &parent_id, &oid, &resname, &s, FALSE)) { 611 return NULL; 612 } 613 iscollection = 0; 614 } else { 615 return NULL; 616 } 617 } 618 619 // store the resource_id in rq->vars 620 if(ctx->rq) { 621 char *rq_path = pblock_findkeyval(pb_key_path, ctx->rq->vars); 622 if(rq_path && !strcmp(rq_path, path)) { 623 char *res_id_str = pblock_findval("resource_id", ctx->rq->vars); 624 if(!res_id_str) { 625 char resource_id_str[32]; 626 snprintf(resource_id_str, 32, "%" PRId64, resource_id); 627 pblock_nvinsert("resource_id",resource_id_str, ctx->rq->vars); 628 } 629 } 630 } 631 632 VFSFile *file = pool_malloc(ctx->pool, sizeof(VFSFile)); 633 if(!file) { 634 return NULL; 635 } 636 PgFile *pgfile = pool_malloc(ctx->pool, sizeof(PgFile)); 637 if(!pgfile) { 638 pool_free(ctx->pool, file); 639 return NULL; 640 } 641 642 int fd = -1; 643 if(!iscollection) { 644 if (PQstatus(pg->connection) != CONNECTION_OK) { 645 fd = -2; 646 } 647 648 int lo_mode = INV_READ; 649 if((oflags & O_RDWR) == O_RDWR) { 650 lo_mode = INV_READ|INV_WRITE; 651 } else if((oflags & O_WRONLY) == O_WRONLY) { 652 lo_mode = INV_WRITE; 653 } 654 fd = lo_open(pg->connection, oid, lo_mode); 655 int err = 0; 656 if(fd < 0) { 657 err = 1; 658 } else if((oflags & O_TRUNC) == O_TRUNC) { 659 if(lo_truncate(pg->connection, fd, 0)) { 660 lo_close(pg->connection, fd); 661 err = 1; 662 } 663 } 664 665 if(err) { 666 pool_free(ctx->pool, file); 667 pool_free(ctx->pool, pgfile); 668 return NULL; 669 } 670 } 671 672 pgfile->iscollection = iscollection; 673 pgfile->resource_id = resource_id; 674 pgfile->parent_id = parent_id; 675 pgfile->oid = oid; 676 pgfile->fd = fd; 677 pgfile->oflags = oflags; 678 pgfile->s = s; 679 memcpy(pgfile->etag, etag, PG_ETAG_MAXLEN); 680 681 file->ctx = ctx; 682 file->io = iscollection ? &pg_vfs_io_class : &pg_vfs_io_class; 683 file->fd = -1; 684 file->data = pgfile; 685 686 return file; 687 } 688 689 int pg_vfs_stat(VFSContext *ctx, const char *path, struct stat *buf) { 690 VFS *vfs = ctx->vfs; 691 PgVFS *pg = vfs->instance; 692 693 int64_t parent_id, resource_id; 694 const char *resname; 695 WSBool iscollection; 696 return pg_resolve_path(pg->connection, path, pg->root_resource_id_str, &parent_id, &resource_id, NULL, &resname, &iscollection, buf, NULL, &ctx->vfs_errno); 697 } 698 699 int pg_vfs_fstat(VFSContext *ctx, SYS_FILE fd, struct stat *buf) { 700 PgFile *pgfile = fd->data; 701 memcpy(buf, &pgfile->s, sizeof(struct stat)); 702 return 0; 703 } 704 705 VFS_DIR pg_vfs_opendir(VFSContext *ctx, const char *path) { 706 VFSFile *file = pg_vfs_open(ctx, path, O_RDONLY); 707 if(!file) return NULL; 708 return pg_vfs_fdopendir(ctx, file); 709 } 710 711 VFS_DIR pg_vfs_fdopendir(VFSContext *ctx, SYS_FILE fd) { 712 PgFile *pg = fd->data; 713 if(!pg->iscollection) { 714 ctx->vfs_errno = ENOTDIR; 715 return NULL; 716 } 717 718 VFSDir *dir = pool_malloc(ctx->pool, sizeof(VFSDir)); 719 if(!dir) { 720 fd->io->close(fd); 721 ctx->vfs_errno = ENOMEM; 722 return NULL; 723 } 724 725 PgDir *pgdir = pool_malloc(ctx->pool, sizeof(PgDir)); 726 if(!pgdir) { 727 fd->io->close(fd); 728 pool_free(ctx->pool, dir); 729 ctx->vfs_errno = ENOMEM; 730 return NULL; 731 } 732 memset(pgdir, 0, sizeof(PgDir)); 733 pgdir->file = fd; 734 735 dir->ctx = ctx; 736 dir->io = &pg_vfs_dirio_class; 737 dir->data = pgdir; 738 dir->fd = -1; 739 740 return dir; 741 } 742 743 int pg_vfs_mkdir(VFSContext *ctx, const char *path) { 744 VFS *vfs = ctx->vfs; 745 PgVFS *pg = vfs->instance; 746 747 const char *resname; 748 int64_t resource_id, parent_id; 749 resource_id = -1; 750 parent_id = -1; 751 WSBool iscollection; 752 struct stat s; 753 Oid oid = 0; 754 if(!pg_resolve_path(pg->connection, path, pg->root_resource_id_str, &parent_id, &resource_id, &oid, &resname, &iscollection, &s, NULL, &ctx->vfs_errno)) { 755 ctx->vfs_errno = EEXIST; 756 return 1; 757 } 758 759 if(pg_create_file(ctx, pg, path, NULL, NULL, NULL, NULL, NULL, TRUE)) { 760 return 1; 761 } 762 763 return 0; 764 } 765 766 int pg_vfs_unlink(VFSContext *ctx, const char *path) { 767 VFS *vfs = ctx->vfs; 768 PgVFS *pg = vfs->instance; 769 770 const char *resname; 771 int64_t resource_id, parent_id; 772 resource_id = -1; 773 parent_id = -1; 774 WSBool iscollection; 775 Oid oid = 0; 776 if(pg_resolve_path(pg->connection, path, pg->root_resource_id_str, &parent_id, &resource_id, &oid, &resname, &iscollection, NULL, NULL, &ctx->vfs_errno)) { 777 return 1; 778 } 779 780 if(iscollection) { 781 ctx->vfs_errno = EISDIR; 782 return 1; 783 } 784 785 return pg_remove_res(ctx, pg, resource_id, oid); 786 } 787 788 int pg_vfs_rmdir(VFSContext *ctx, const char *path) { 789 VFS *vfs = ctx->vfs; 790 PgVFS *pg = vfs->instance; 791 792 const char *resname; 793 int64_t resource_id, parent_id; 794 resource_id = -1; 795 parent_id = -1; 796 WSBool iscollection; 797 if(pg_resolve_path(pg->connection, path, pg->root_resource_id_str, &parent_id, &resource_id, NULL, &resname, &iscollection, NULL, NULL, &ctx->vfs_errno)) { 798 return 1; 799 } 800 801 if(!iscollection) { 802 ctx->vfs_errno = ENOTDIR; 803 return 1; 804 } 805 806 return pg_remove_res(ctx, pg, resource_id, 0); 807 } 808 809 810 /* -------------------------- VFS_IO functions -------------------------- */ 811 812 ssize_t pg_vfs_io_read(SYS_FILE fd, void *buf, size_t nbyte) { 813 PgVFS *pgvfs = fd->ctx->vfs->instance; 814 PgFile *pg = fd->data; 815 if(pg->fd < 0) return-1; 816 return lo_read(pgvfs->connection, pg->fd, buf, nbyte); 817 } 818 819 ssize_t pg_vfs_io_write(SYS_FILE fd, const void *buf, size_t nbyte) { 820 PgVFS *pgvfs = fd->ctx->vfs->instance; 821 PgFile *pg = fd->data; 822 if(pg->fd < 0) return-1; 823 return lo_write(pgvfs->connection, pg->fd, buf, nbyte); 824 } 825 826 ssize_t pg_vfs_io_pread(SYS_FILE fd, void *buf, size_t nbyte, off_t offset) { 827 PgVFS *pgvfs = fd->ctx->vfs->instance; 828 PgFile *pg = fd->data; 829 if(pg->fd < 0) return-1; 830 if(lo_lseek64(pgvfs->connection, pg->fd, offset, SEEK_SET) == -1) { 831 return -1; 832 } 833 return lo_read(pgvfs->connection, pg->fd, buf, nbyte); 834 } 835 836 ssize_t pg_vfs_io_pwrite(SYS_FILE fd, const void *buf, size_t nbyte, off_t offset) { 837 PgVFS *pgvfs = fd->ctx->vfs->instance; 838 PgFile *pg = fd->data; 839 if(pg->fd < 0) return-1; 840 if(lo_lseek64(pgvfs->connection, pg->fd, offset, SEEK_SET) == -1) { 841 return -1; 842 } 843 return lo_write(pgvfs->connection, pg->fd, buf, nbyte); 844 } 845 846 off_t pg_vfs_io_seek(SYS_FILE fd, off_t offset, int whence) { 847 PgVFS *pgvfs = fd->ctx->vfs->instance; 848 PgFile *pg = fd->data; 849 if(pg->fd < 0) return-1; 850 return lo_lseek64(pgvfs->connection, pg->fd, offset, whence); 851 } 852 853 off_t pg_vfs_io_tell(SYS_FILE fd) { 854 PgVFS *pgvfs = fd->ctx->vfs->instance; 855 PgFile *pg = fd->data; 856 if(pg->fd < 0) return-1; 857 return lo_tell64(pgvfs->connection, pg->fd); 858 } 859 860 void pg_vfs_io_close(SYS_FILE fd) { 861 pool_handle_t *pool = fd->ctx->pool; 862 PgVFS *pgvfs = fd->ctx->vfs->instance; 863 PgFile *pg = fd->data; 864 865 if(pg->fd >= 0) { 866 if((pg->oflags & (O_WRONLY|O_RDWR)) > 0) { 867 // file modified, update length and lastmodified 868 off_t len = pg_vfs_io_seek(fd, 0, SEEK_END); 869 if(len < 0) len = 0; 870 871 pg_update_resource(pgvfs, pg->resource_id, len); 872 } 873 874 PgVFS *pgvfs = fd->ctx->vfs->instance; 875 lo_close(pgvfs->connection, pg->fd); 876 } 877 878 pool_free(pool, pg); 879 pool_free(pool, fd); 880 } 881 882 const char *pg_vfs_io_getetag(SYS_FILE fd) { 883 PgFile *pg = fd->data; 884 return pg->etag; 885 } 886 887 /* -------------------------- VFS_DIRIO functions -------------------------- */ 888 889 static int load_dir(VFSDir *dir, PgDir *pg) { 890 VFS *vfs = dir->ctx->vfs; 891 PgVFS *pgvfs = vfs->instance; 892 PgFile *pgfd = pg->file->data; 893 PgDir *pgdir = dir->data; 894 895 char resid_param[32]; 896 snprintf(resid_param, 32, "%" PRId64, pgfd->resource_id); 897 898 const char *param = resid_param; 899 900 PGresult *result = PQexecParams( 901 pgvfs->connection, 902 sql_get_children, 903 1, // number of parameters 904 NULL, 905 &param, // param: parent resource_id 906 NULL, 907 NULL, 908 0); // 0: result in text format 909 if(!result) return 1; 910 911 pgdir->result = result; 912 pgdir->nrows = PQntuples(result); 913 return 0; 914 } 915 916 int pg_vfs_dirio_readdir(VFS_DIR dir, VFS_ENTRY *entry, int getstat) { 917 PgDir *pg = dir->data; 918 if(!pg->result) { 919 if(load_dir(dir, pg)) { 920 return 0; 921 } 922 } 923 924 if(pg->row >= pg->nrows) { 925 return 0; // EOF 926 } 927 928 entry->name = PQgetvalue(pg->result, pg->row, 1); 929 entry->stat_errno = 0; 930 entry->stat_extra = NULL; 931 932 if(getstat) { 933 memset(&entry->stat, 0, sizeof(struct stat)); 934 935 char *iscollection = PQgetvalue(pg->result, pg->row, 2); 936 char *lastmodified = PQgetvalue(pg->result, pg->row, 3); 937 char *creationdate = PQgetvalue(pg->result, pg->row, 4); 938 char *contentlength = PQgetvalue(pg->result, pg->row, 5); 939 pg_set_stat(&entry->stat, iscollection, lastmodified, creationdate, contentlength); 940 } 941 942 pg->row++; 943 return 1; 944 } 945 946 void pg_vfs_dirio_close(VFS_DIR dir) { 947 pool_handle_t *pool = dir->ctx->pool; 948 PgDir *pg = dir->data; 949 if(pg->result) { 950 PQclear(pg->result); 951 } 952 PgFile *pgfile = pg->file->data; 953 954 pool_free(pool, pgfile); 955 pool_free(pool, pg->file); 956 pool_free(pool, pg); 957 pool_free(pool, dir); 958 } 959 960 time_t pg_convert_timestamp(const char *timestamp) { 961 struct tm tm; 962 if(pg_convert_timestamp_tm(timestamp, &tm)) { 963 return 0; 964 } 965 #ifdef __FreeBSD__ 966 return timelocal(&tm); 967 #else 968 return mktime(&tm) - timezone; 969 #endif 970 } 971 972 int pg_convert_timestamp_tm(const char *timestamp, struct tm *tm) { 973 // TODO: this is a very basic implementation that needs some work 974 // format yyyy-mm-dd HH:MM:SS 975 976 memset(tm, 0, sizeof(struct tm)); 977 size_t len = timestamp ? strlen(timestamp) : 0; 978 if(len < 19) { 979 return 1; 980 } else if(len > 63) { 981 return 1; 982 } 983 984 char buf[64]; 985 memcpy(buf, timestamp, len); 986 987 char *year_str = buf; 988 year_str[4] = '\0'; 989 990 char *month_str = buf + 5; 991 month_str[2] = '\0'; 992 993 char *day_str = buf + 8; 994 day_str[2] = '\0'; 995 996 char *hour_str = buf + 11; 997 hour_str[2] = '\0'; 998 999 char *minute_str = buf + 14; 1000 minute_str[2] = '\0'; 1001 1002 char *second_str = buf + 17; 1003 second_str[2] = '\0'; 1004 1005 tm->tm_year = atoi(year_str) - 1900; 1006 tm->tm_mon = atoi(month_str) - 1; 1007 tm->tm_mday = atoi(day_str); 1008 tm->tm_hour = atoi(hour_str); 1009 tm->tm_min = atoi(minute_str); 1010 tm->tm_sec = atoi(second_str); 1011 1012 return 0; 1013 } 1014