src/server/plugins/postgresql/vfs.c

changeset 385
a1f4cb076d2f
parent 382
9e2289c77b04
child 387
f5caf41b4db6
equal deleted inserted replaced
210:21274e5950af 385:a1f4cb076d2f
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 to fail due to 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(&tparts) - 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 }

mercurial