diff --git a/drivers/file_transfer/file_transfer_backend_localfs.c b/drivers/file_transfer/file_transfer_backend_localfs.c index 1b5d190c3..e15498297 100644 --- a/drivers/file_transfer/file_transfer_backend_localfs.c +++ b/drivers/file_transfer/file_transfer_backend_localfs.c @@ -458,8 +458,8 @@ buildLocalPath(const LocalFileSystemContext *ctx, const UA_String relPath, } static UA_StatusCode -localFsOpen(UA_FileTransferFileBackend *b, const UA_String path, - UA_Byte mode, UA_UInt32 *handle) { +localFsOpen(UA_FileTransferFileBackend *b, const UA_String path, UA_Byte mode, + const UA_FileTransferOpenContext *context, UA_UInt32 *handle) { LocalFileSystemContext *ctx = (LocalFileSystemContext*)b->context; char localPath[FT_PATH_MAX]; UA_StatusCode res = buildLocalPath(ctx, path, localPath); diff --git a/drivers/file_transfer/file_transfer_directory.c b/drivers/file_transfer/file_transfer_directory.c index 1bfec4c83..82d0a1ed4 100644 --- a/drivers/file_transfer/file_transfer_directory.c +++ b/drivers/file_transfer/file_transfer_directory.c @@ -249,17 +249,26 @@ deleteBackendTree(UA_FileTransferBackend *b, const UA_String path, /* Copy a single file. Source and target may live in different backends. Uses * the backend fast-path only within one backend, otherwise streams the content - * through read/write loops. */ + * through read/write loops. The files are opened for the caller of the copy. */ static UA_StatusCode copyBackendFile(UA_FileTransferBackend *srcB, const UA_String fromPath, - UA_FileTransferBackend *dstB, const UA_String toPath) { + UA_FileTransferBackend *dstB, const UA_String toPath, + const FTTreeAccess *access) { if(srcB == dstB && dstB->copy) return dstB->copy(dstB, fromPath, toPath); + UA_FileTransferOpenContext caller; + const UA_FileTransferOpenContext *context = NULL; + if(access) { + caller.server = access->server; + caller.sessionId = access->sessionId; + caller.sessionContext = access->sessionContext; + context = &caller; + } UA_FileTransferFileBackend *srcF = &srcB->file; UA_FileTransferFileBackend *dstF = &dstB->file; UA_UInt32 src = 0; - UA_StatusCode res = srcF->open(srcF, fromPath, UA_OPENFILEMODE_READ, &src); + UA_StatusCode res = srcF->open(srcF, fromPath, UA_OPENFILEMODE_READ, context, &src); if(res != UA_STATUSCODE_GOOD) return res; res = createBackendEntry(dstB, toPath, false); @@ -268,7 +277,7 @@ copyBackendFile(UA_FileTransferBackend *srcB, const UA_String fromPath, return res; } UA_UInt32 dst = 0; - res = dstF->open(dstF, toPath, UA_OPENFILEMODE_WRITE, &dst); + res = dstF->open(dstF, toPath, UA_OPENFILEMODE_WRITE, context, &dst); if(res != UA_STATUSCODE_GOOD) { /* Remove the empty file just created so a failed open does not leave * a stale entry behind in the destination backend. */ @@ -311,7 +320,7 @@ copyBackendTree(UA_FileTransferBackend *srcB, const UA_String fromPath, if(res != UA_STATUSCODE_GOOD) return res; if(!isDir) - return copyBackendFile(srcB, fromPath, dstB, toPath); + return copyBackendFile(srcB, fromPath, dstB, toPath, access); res = createBackendEntry(dstB, toPath, true); if(res != UA_STATUSCODE_GOOD) @@ -679,11 +688,10 @@ createFileMethodCallback(UA_Server *server, const UA_NodeId *sessionId, /* Optionally open the new file for reading and writing. A failed open * removes the file again. */ - UA_UInt32 handle = 0; + FTHandle *h = NULL; if(requestFileOpen) { res = openFileHandle(server, newNode, sessionId, sessionContext, - UA_OPENFILEMODE_READ | UA_OPENFILEMODE_WRITE, - &handle); + UA_OPENFILEMODE_READ | UA_OPENFILEMODE_WRITE, &h); if(res != UA_STATUSCODE_GOOD) { ftd->backend.remove(&ftd->backend, newNode->path); removeSubtree(server, newNode); @@ -692,16 +700,14 @@ createFileMethodCallback(UA_Server *server, const UA_NodeId *sessionId, } /* The client cannot close a handle it does not receive */ + UA_UInt32 handle = h ? h->handle : 0; res = UA_Variant_setScalarCopy(&output[0], &newNode->nodeId, &UA_TYPES[UA_TYPES_NODEID]); if(res == UA_STATUSCODE_GOOD) res = UA_Variant_setScalarCopy(&output[1], &handle, &UA_TYPES[UA_TYPES_UINT32]); - if(res != UA_STATUSCODE_GOOD && requestFileOpen) { - FTHandle *h = findFTHandle(ftd, sessionId, handle); - if(h) - closeFTHandle(server, h); - } + if(res != UA_STATUSCODE_GOOD && h) + closeFTHandle(server, h); return res; } diff --git a/drivers/file_transfer/file_transfer_file.c b/drivers/file_transfer/file_transfer_file.c index 30564b30a..eb6160c23 100644 --- a/drivers/file_transfer/file_transfer_file.c +++ b/drivers/file_transfer/file_transfer_file.c @@ -251,7 +251,7 @@ resolveHandle(FileTransferDriver *ftd, const UA_NodeId *sessionId, UA_StatusCode openFileHandle(UA_Server *server, FTEntry *node, const UA_NodeId *sessionId, void *sessionContext, UA_Byte mode, - UA_UInt32 *outHandle) { + FTHandle **outHandle) { FileTransferDriver *ftd = node->driver; /* Validate the mode bit combination */ if((mode & ~UA_FILETRANSFER_OPENMODE_ALLBITS) || @@ -287,8 +287,9 @@ openFileHandle(UA_Server *server, FTEntry *node, return UA_STATUSCODE_BADRESOURCEUNAVAILABLE; UA_FileTransferFileBackend *b = &node->driver->backend.file; + UA_FileTransferOpenContext context = {server, sessionId, sessionContext}; UA_UInt32 backendHandle = 0; - res = b->open(b, node->path, mode, &backendHandle); + res = b->open(b, node->path, mode, &context, &backendHandle); if(res != UA_STATUSCODE_GOOD) return res; @@ -316,7 +317,7 @@ openFileHandle(UA_Server *server, FTEntry *node, if(writeBit) node->openForWrite = true; - *outHandle = h->handle; + *outHandle = h; return UA_STATUSCODE_GOOD; } @@ -329,7 +330,6 @@ openMethodCallback(UA_Server *server, const UA_NodeId *sessionId, FTEntry *node = resolveFTEntry(server, objectId, objectContext); if(!node) return UA_STATUSCODE_BADNOTSUPPORTED; - FileTransferDriver *ftd = node->driver; if(node->isDirectory || node->zombie) return UA_STATUSCODE_BADNOTFOUND; @@ -341,19 +341,16 @@ openMethodCallback(UA_Server *server, const UA_NodeId *sessionId, /* Open takes a Byte bit mask, not the generated 32-bit UA_OpenFileMode. */ UA_Byte mode = *(UA_Byte*)input[0].data; - UA_UInt32 handle = 0; - res = openFileHandle(server, node, sessionId, sessionContext, mode, &handle); + FTHandle *h = NULL; + res = openFileHandle(server, node, sessionId, sessionContext, mode, &h); if(res != UA_STATUSCODE_GOOD) return res; /* The client cannot close a handle it does not receive */ - res = UA_Variant_setScalarCopy(&output[0], &handle, + res = UA_Variant_setScalarCopy(&output[0], &h->handle, &UA_TYPES[UA_TYPES_UINT32]); - if(res != UA_STATUSCODE_GOOD) { - FTHandle *h = findFTHandle(ftd, sessionId, handle); - if(h) - closeFTHandle(server, h); - } + if(res != UA_STATUSCODE_GOOD) + closeFTHandle(server, h); return res; } diff --git a/drivers/file_transfer/file_transfer_internal.h b/drivers/file_transfer/file_transfer_internal.h index c731e9097..e21bf345f 100644 --- a/drivers/file_transfer/file_transfer_internal.h +++ b/drivers/file_transfer/file_transfer_internal.h @@ -192,7 +192,7 @@ FTHandle *findFTHandle(FileTransferDriver *ftd, const UA_NodeId *sessionId, /* mode combines UA_OPENFILEMODE_* flags in the protocol's Byte representation. */ UA_StatusCode openFileHandle(UA_Server *server, FTEntry *node, const UA_NodeId *sessionId, void *sessionContext, - UA_Byte mode, UA_UInt32 *outHandle); + UA_Byte mode, FTHandle **outHandle); UA_StatusCode closeFTHandle(UA_Server *server, FTHandle *handle); void closeDriverHandles(UA_Server *server, FileTransferDriver *ftd); void closeSessionHandles(UA_Server *server, FileTransferDriver *ftd, diff --git a/examples/filetransfer/server_filetransfer_custom_backend.c b/examples/filetransfer/server_filetransfer_custom_backend.c index 23d5a168f..822a1f8e7 100644 --- a/examples/filetransfer/server_filetransfer_custom_backend.c +++ b/examples/filetransfer/server_filetransfer_custom_backend.c @@ -53,7 +53,7 @@ memFsHandle(UA_FileTransferFileBackend *b, UA_UInt32 handle) { static UA_StatusCode memFsOpen(UA_FileTransferFileBackend *b, const UA_String path, UA_Byte mode, - UA_UInt32 *handle) { + const UA_FileTransferOpenContext *context, UA_UInt32 *handle) { MemFs *fs = (MemFs*)b->context; MemFile *file = memFsFind(fs, path); if(!file) diff --git a/include/open62541/driver/file_transfer.h b/include/open62541/driver/file_transfer.h index 826c9c112..7ea26563e 100644 --- a/include/open62541/driver/file_transfer.h +++ b/include/open62541/driver/file_transfer.h @@ -78,6 +78,15 @@ typedef struct { * leaves an existing one unchanged. */ } UA_FileTransferFileInfo; +/* The caller of an open. The driver passes the Session of the Method call for + * Open, CreateFile and the copies of MoveOrCopy. Direct calls of the + * application pass NULL. */ +typedef struct { + UA_Server *server; + const UA_NodeId *sessionId; + void *sessionContext; +} UA_FileTransferOpenContext; + typedef struct UA_FileTransferFileBackend UA_FileTransferFileBackend; struct UA_FileTransferFileBackend { /* Backend-owned storage and handle state */ @@ -85,9 +94,14 @@ struct UA_FileTransferFileBackend { /* Open a file. mode is a UA_Byte bit mask of UA_OPENFILEMODE_* flags, * validated by the driver. Append starts at EOF; otherwise start at zero. - * EraseExisting truncates the file. Return an independent handle. */ + * EraseExisting truncates the file. Return an independent handle. + * context is the caller or NULL. For a Method call, the driver has checked + * the mode, the locking (Part 20, 4.2.2), the access rights and the handle + * limits before, and the result is returned to the client, e.g. + * Bad_NotSupported for a mode the file does not offer. */ UA_StatusCode (*open)(UA_FileTransferFileBackend *b, const UA_String path, - UA_Byte mode, UA_UInt32 *handle); + UA_Byte mode, const UA_FileTransferOpenContext *context, + UA_UInt32 *handle); /* Close the handle and flush buffered writes. Report flush errors here; * a destination close error also fails a driver-managed copy. */ diff --git a/tests/client/check_client_filetransfer.c b/tests/client/check_client_filetransfer.c index 58bd70c07..cd7c2ce98 100644 --- a/tests/client/check_client_filetransfer.c +++ b/tests/client/check_client_filetransfer.c @@ -53,7 +53,7 @@ sfPosition(UA_UInt32 handle) { static UA_StatusCode sfOpen(UA_FileTransferFileBackend *b, const UA_String path, UA_Byte mode, - UA_UInt32 *handle) { + const UA_FileTransferOpenContext *context, UA_UInt32 *handle) { for(size_t i = 0; i < SF_MAXOPEN; i++) { if(sfFiles[i].used) continue; diff --git a/tests/server/check_server_filetransfer.c b/tests/server/check_server_filetransfer.c index 82ea2e58f..8ff9dbb28 100644 --- a/tests/server/check_server_filetransfer.c +++ b/tests/server/check_server_filetransfer.c @@ -329,8 +329,8 @@ memHandle(UA_FileTransferFileBackend *b, UA_UInt32 handle) { } static UA_StatusCode -memOpen(UA_FileTransferFileBackend *b, const UA_String path, - UA_Byte mode, UA_UInt32 *handle) { +memOpen(UA_FileTransferFileBackend *b, const UA_String path, UA_Byte mode, + const UA_FileTransferOpenContext *context, UA_UInt32 *handle) { MemBackendContext *ctx = (MemBackendContext*)b->context; MemEntry *e = memFind(ctx, path); if(!e || e->isDir) @@ -683,7 +683,7 @@ runBackendContract(UA_FileTransferBackend *b) { UA_STATUSCODE_BADBROWSENAMEDUPLICATED); /* Write content */ - res = f->open(f, UA_STRING("hello.txt"), UA_OPENFILEMODE_WRITE, &fc); + res = f->open(f, UA_STRING("hello.txt"), UA_OPENFILEMODE_WRITE, NULL, &fc); ck_assert_uint_eq(res, UA_STATUSCODE_GOOD); ck_assert_uint_eq(f->write(f, fc, UA_BYTESTRING("Hello World")), UA_STATUSCODE_GOOD); @@ -704,7 +704,7 @@ runBackendContract(UA_FileTransferBackend *b) { UA_STATUSCODE_BADNOTFOUND); /* Read in chunks until the end of the file */ - res = f->open(f, UA_STRING("hello.txt"), UA_OPENFILEMODE_READ, &fc); + res = f->open(f, UA_STRING("hello.txt"), UA_OPENFILEMODE_READ, NULL, &fc); ck_assert_uint_eq(res, UA_STATUSCODE_GOOD); ck_assert_uint_eq(f->read(f, fc, 5, &out), UA_STATUSCODE_GOOD); ck_assert_uint_eq(out.length, 5); @@ -733,7 +733,7 @@ runBackendContract(UA_FileTransferBackend *b) { /* EraseExisting truncates */ res = f->open(f, UA_STRING("hello.txt"), - UA_OPENFILEMODE_WRITE | UA_OPENFILEMODE_ERASEEXISTING, &fc); + UA_OPENFILEMODE_WRITE | UA_OPENFILEMODE_ERASEEXISTING, NULL, &fc); ck_assert_uint_eq(res, UA_STATUSCODE_GOOD); ck_assert_uint_eq(f->write(f, fc, UA_BYTESTRING("Hi")), UA_STATUSCODE_GOOD); ck_assert_uint_eq(f->close(f, fc), UA_STATUSCODE_GOOD); @@ -743,7 +743,7 @@ runBackendContract(UA_FileTransferBackend *b) { /* Append positions at the end */ res = f->open(f, UA_STRING("hello.txt"), - UA_OPENFILEMODE_WRITE | UA_OPENFILEMODE_APPEND, &fc); + UA_OPENFILEMODE_WRITE | UA_OPENFILEMODE_APPEND, NULL, &fc); ck_assert_uint_eq(res, UA_STATUSCODE_GOOD); ck_assert_uint_eq(f->getPosition(f, fc, &pos), UA_STATUSCODE_GOOD); ck_assert_uint_eq(pos, 2); @@ -754,19 +754,19 @@ runBackendContract(UA_FileTransferBackend *b) { ck_assert_uint_eq(info.size, 3); /* Empty write is a no-op */ - res = f->open(f, UA_STRING("hello.txt"), UA_OPENFILEMODE_WRITE, &fc); + res = f->open(f, UA_STRING("hello.txt"), UA_OPENFILEMODE_WRITE, NULL, &fc); ck_assert_uint_eq(res, UA_STATUSCODE_GOOD); ck_assert_uint_eq(f->write(f, fc, UA_BYTESTRING_NULL), UA_STATUSCODE_GOOD); ck_assert_uint_eq(f->close(f, fc), UA_STATUSCODE_GOOD); /* Invalid open modes and missing files */ - ck_assert_uint_eq(f->open(f, UA_STRING("hello.txt"), 0, &fc), + ck_assert_uint_eq(f->open(f, UA_STRING("hello.txt"), 0, NULL, &fc), UA_STATUSCODE_BADINVALIDARGUMENT); ck_assert_uint_eq(f->open(f, UA_STRING("missing"), - UA_OPENFILEMODE_READ, &fc), + UA_OPENFILEMODE_READ, NULL, &fc), UA_STATUSCODE_BADNOTFOUND); ck_assert_uint_eq(f->open(f, UA_STRING("missing"), - UA_OPENFILEMODE_WRITE, &fc), + UA_OPENFILEMODE_WRITE, NULL, &fc), UA_STATUSCODE_BADNOTFOUND); /* Listing */ @@ -971,7 +971,7 @@ START_TEST(localDirectoryBackendSandbox) { UA_UInt32 fc = 0; for(size_t i = 0; i < 8; i++) { UA_String p = UA_STRING((char*)(uintptr_t)badPaths[i]); - ck_assert_uint_eq(b.file.open(&b.file, p, UA_OPENFILEMODE_READ, &fc), + ck_assert_uint_eq(b.file.open(&b.file, p, UA_OPENFILEMODE_READ, NULL, &fc), UA_STATUSCODE_BADINVALIDARGUMENT); ck_assert_uint_eq(createEntry(&b, p, false), UA_STATUSCODE_BADINVALIDARGUMENT); ck_assert_uint_eq(createEntry(&b, p, true), @@ -1015,7 +1015,7 @@ START_TEST(localDirectoryUtf8Names) { UA_STATUSCODE_GOOD); ck_assert_uint_eq(createEntry(&b, path, false), UA_STATUSCODE_GOOD); UA_UInt32 fc = 0; - ck_assert_uint_eq(b.file.open(&b.file, path, UA_OPENFILEMODE_WRITE, &fc), + ck_assert_uint_eq(b.file.open(&b.file, path, UA_OPENFILEMODE_WRITE, NULL, &fc), UA_STATUSCODE_GOOD); ck_assert_uint_eq(b.file.write(&b.file, fc, UA_BYTESTRING("utf8")), UA_STATUSCODE_GOOD); @@ -1060,7 +1060,7 @@ START_TEST(localDirectoryUtf8Names) { ck_assert_str_eq(info.name, name); ck_assert_str_eq(info.mimeType, "text/plain"); ck_assert_uint_eq(info.size, 4); - ck_assert_uint_eq(file.open(&file, UA_STRING_NULL, UA_OPENFILEMODE_READ, &fc), + ck_assert_uint_eq(file.open(&file, UA_STRING_NULL, UA_OPENFILEMODE_READ, NULL, &fc), UA_STATUSCODE_GOOD); UA_ByteString content; ck_assert_uint_eq(file.read(&file, fc, 10, &content), UA_STATUSCODE_GOOD); @@ -1181,7 +1181,7 @@ memBackendWithFile(const char *name, const char *content) { ck_assert_uint_eq(createEntry(&b, path, false), UA_STATUSCODE_GOOD); if(content && strlen(content) > 0) { UA_UInt32 fc = 0; - ck_assert_uint_eq(b.file.open(&b.file, path, UA_OPENFILEMODE_WRITE, &fc), + ck_assert_uint_eq(b.file.open(&b.file, path, UA_OPENFILEMODE_WRITE, NULL, &fc), UA_STATUSCODE_GOOD); UA_ByteString data = UA_BYTESTRING((char*)(uintptr_t)content); ck_assert_uint_eq(b.file.write(&b.file, fc, data), UA_STATUSCODE_GOOD); @@ -1750,7 +1750,7 @@ memBackendWithTree(void) { ck_assert_uint_eq(createEntry(&b, UA_STRING("docs/sub/b.txt"), false), UA_STATUSCODE_GOOD); UA_UInt32 fc = 0; ck_assert_uint_eq(b.file.open(&b.file, UA_STRING("docs/a.txt"), - UA_OPENFILEMODE_WRITE, &fc), + UA_OPENFILEMODE_WRITE, NULL, &fc), UA_STATUSCODE_GOOD); ck_assert_uint_eq(b.file.write(&b.file, fc, UA_BYTESTRING("content-a")), UA_STATUSCODE_GOOD); @@ -2088,7 +2088,7 @@ writeMemFile(UA_FileTransferBackend *b, const char *name, const char *content) { UA_String path = UA_STRING((char*)(uintptr_t)name); ck_assert_uint_eq(createEntry(b, path, false), UA_STATUSCODE_GOOD); UA_UInt32 fc = 0; - ck_assert_uint_eq(b->file.open(&b->file, path, UA_OPENFILEMODE_WRITE, &fc), + ck_assert_uint_eq(b->file.open(&b->file, path, UA_OPENFILEMODE_WRITE, NULL, &fc), UA_STATUSCODE_GOOD); ck_assert_uint_eq(b->file.write(&b->file, fc, UA_BYTESTRING((char*)(uintptr_t)content)), @@ -2446,7 +2446,7 @@ START_TEST(localFileMount) { UA_FileTransferFileInfo info; for(size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); i++) { UA_String path = UA_STRING((char*)(uintptr_t)paths[i]); - ck_assert_uint_eq(backend.open(&backend, path, UA_OPENFILEMODE_READ, &handle), + ck_assert_uint_eq(backend.open(&backend, path, UA_OPENFILEMODE_READ, NULL, &handle), UA_STATUSCODE_BADINVALIDARGUMENT); ck_assert_uint_eq(backend.getInfo(&backend, path, &info), UA_STATUSCODE_BADINVALIDARGUMENT); } @@ -2702,8 +2702,8 @@ static struct { } minimalFiles[MINIMAL_MAXOPEN]; static UA_StatusCode -minimalOpen(UA_FileTransferFileBackend *b, const UA_String path, - UA_Byte mode, UA_UInt32 *handle) { +minimalOpen(UA_FileTransferFileBackend *b, const UA_String path, UA_Byte mode, + const UA_FileTransferOpenContext *context, UA_UInt32 *handle) { if(mode & UA_OPENFILEMODE_WRITE) return UA_STATUSCODE_BADNOTWRITABLE; for(size_t i = 0; i < MINIMAL_MAXOPEN; i++) { @@ -3112,6 +3112,7 @@ static UA_StatusCode (*memCreateFn)(UA_FileTransferBackend *b, const UA_String p const UA_FileTransferFileInfo *info); static UA_StatusCode (*memOpenFn)(UA_FileTransferFileBackend *b, const UA_String path, UA_Byte mode, + const UA_FileTransferOpenContext *context, UA_UInt32 *handle); static UA_Boolean @@ -3135,9 +3136,9 @@ failingCreate(UA_FileTransferBackend *b, const UA_String path, static UA_StatusCode failingOpen(UA_FileTransferFileBackend *b, const UA_String path, UA_Byte mode, - UA_UInt32 *handle) { + const UA_FileTransferOpenContext *context, UA_UInt32 *handle) { return isFailingPath(path) ? UA_STATUSCODE_BADUSERACCESSDENIED : - memOpenFn(b, path, mode, handle); + memOpenFn(b, path, mode, context, handle); } /* A move to another mount that cannot delete the source keeps the data. A @@ -3649,15 +3650,15 @@ START_TEST(localWriteOnlyFile) { ck_assert_uint_eq(info.accessRights, UA_FILEACCESS_WRITE); UA_UInt32 h = 0; - ck_assert_uint_eq(b.file.open(&b.file, name, UA_OPENFILEMODE_READ, &h), + ck_assert_uint_eq(b.file.open(&b.file, name, UA_OPENFILEMODE_READ, NULL, &h), UA_STATUSCODE_BADUSERACCESSDENIED); - ck_assert_uint_eq(b.file.open(&b.file, name, UA_OPENFILEMODE_WRITE, &h), + ck_assert_uint_eq(b.file.open(&b.file, name, UA_OPENFILEMODE_WRITE, NULL, &h), UA_STATUSCODE_GOOD); ck_assert_uint_eq(b.file.write(&b.file, h, UA_BYTESTRING("XY")), UA_STATUSCODE_GOOD); ck_assert_uint_eq(b.file.close(&b.file, h), UA_STATUSCODE_GOOD); /* Append starts at the end; SetPosition still moves the next write */ ck_assert_uint_eq(b.file.open(&b.file, name, UA_OPENFILEMODE_WRITE | - UA_OPENFILEMODE_APPEND, &h), UA_STATUSCODE_GOOD); + UA_OPENFILEMODE_APPEND, NULL, &h), UA_STATUSCODE_GOOD); ck_assert_uint_eq(b.file.write(&b.file, h, UA_BYTESTRING("Z")), UA_STATUSCODE_GOOD); ck_assert_uint_eq(b.file.setPosition(&b.file, h, 0), UA_STATUSCODE_GOOD); ck_assert_uint_eq(b.file.write(&b.file, h, UA_BYTESTRING("Q")), UA_STATUSCODE_GOOD); @@ -3666,7 +3667,7 @@ START_TEST(localWriteOnlyFile) { checkLocalFile(path, "QYcZ"); ck_assert_int_eq(fchmod(fileno(permissionsFile), 0200), 0); ck_assert_uint_eq(b.file.open(&b.file, name, UA_OPENFILEMODE_WRITE | - UA_OPENFILEMODE_ERASEEXISTING, &h), UA_STATUSCODE_GOOD); + UA_OPENFILEMODE_ERASEEXISTING, NULL, &h), UA_STATUSCODE_GOOD); ck_assert_uint_eq(b.file.close(&b.file, h), UA_STATUSCODE_GOOD); ck_assert_int_eq(fchmod(fileno(permissionsFile), 0600), 0); checkLocalFile(path, ""); @@ -4110,6 +4111,129 @@ accessBackend(UA_FileTransferBackend backend, AccessPolicy *policy) { return backend; } +/* Records the opens of the backend and forwards them to the memory backend + * unless a result is preset */ +static struct { + size_t calls; + UA_Boolean hasContext; + UA_NodeId sessionId; + void *sessionContext; + UA_String path; + UA_Byte mode; + UA_StatusCode result; +} recordedOpen; + +static UA_StatusCode +recordingOpen(UA_FileTransferFileBackend *b, const UA_String path, UA_Byte mode, + const UA_FileTransferOpenContext *context, UA_UInt32 *handle) { + recordedOpen.calls++; + recordedOpen.hasContext = (context != NULL); + UA_NodeId_clear(&recordedOpen.sessionId); + if(context) { + ck_assert_ptr_eq(context->server, server_ft); + ck_assert_uint_eq(UA_NodeId_copy(context->sessionId, &recordedOpen.sessionId), + UA_STATUSCODE_GOOD); + recordedOpen.sessionContext = context->sessionContext; + } + UA_String_clear(&recordedOpen.path); + ck_assert_uint_eq(UA_String_copy(&path, &recordedOpen.path), UA_STATUSCODE_GOOD); + recordedOpen.mode = mode; + if(recordedOpen.result != UA_STATUSCODE_GOOD) + return recordedOpen.result; + return memOpen(b, path, mode, context, handle); +} + +static void +resetRecordedOpen(void) { + UA_NodeId_clear(&recordedOpen.sessionId); + UA_String_clear(&recordedOpen.path); + memset(&recordedOpen, 0, sizeof(recordedOpen)); +} + +START_TEST(openReceivesCaller) { + resetRecordedOpen(); + UA_FileTransferBackend backend = memBackendWithFile("f.bin", "content"); + backend.file.open = recordingOpen; + server_ft->adminSession.context = &accessSessionContext; + UA_NodeId fileId = addTestFileBackend(backend, "SessionOpen", NULL); + + UA_UInt32 h = callOpen(fileId, UA_OPENFILEMODE_READ, UA_STATUSCODE_GOOD); + ck_assert_uint_eq(recordedOpen.calls, 1); + ck_assert(recordedOpen.hasContext); + ck_assert(UA_NodeId_equal(&recordedOpen.sessionId, + &server_ft->adminSession.sessionId)); + ck_assert_ptr_eq(recordedOpen.sessionContext, &accessSessionContext); + UA_String path = UA_STRING("f.bin"); + ck_assert(UA_String_equal(&recordedOpen.path, &path)); + ck_assert_uint_eq(recordedOpen.mode, UA_OPENFILEMODE_READ); + UA_ByteString data = callRead(fileId, h, 100, UA_STATUSCODE_GOOD); + UA_ByteString expected = UA_BYTESTRING("content"); + ck_assert(UA_ByteString_equal(&data, &expected)); + UA_ByteString_clear(&data); + callClose(fileId, h, UA_STATUSCODE_GOOD); + + /* The result of the backend reaches the client */ + recordedOpen.result = UA_STATUSCODE_BADTRANSACTIONPENDING; + callOpen(fileId, UA_OPENFILEMODE_WRITE | UA_OPENFILEMODE_ERASEEXISTING, + UA_STATUSCODE_BADTRANSACTIONPENDING); + ck_assert_uint_eq(readOpenCount(fileId), 0); + + /* The mode and the locking are checked before */ + recordedOpen.result = UA_STATUSCODE_GOOD; + size_t calls = recordedOpen.calls; + callOpen(fileId, UA_OPENFILEMODE_ERASEEXISTING, UA_STATUSCODE_BADINVALIDARGUMENT); + h = callOpen(fileId, UA_OPENFILEMODE_WRITE, UA_STATUSCODE_GOOD); + callOpen(fileId, UA_OPENFILEMODE_READ, UA_STATUSCODE_BADNOTREADABLE); + ck_assert_uint_eq(recordedOpen.calls, calls + 1); + callClose(fileId, h, UA_STATUSCODE_GOOD); + resetRecordedOpen(); +} END_TEST + +/* CreateFile and the copies of MoveOrCopy open for the caller, direct calls + * of the application pass no context */ +START_TEST(openInDirectories) { + resetRecordedOpen(); + UA_FileTransferBackend backend = memBackendWithTree(); + backend.file.open = recordingOpen; + UA_NodeId fsId = UA_NODEID_NULL; + ck_assert_uint_eq(testAddDirectory(UA_NODEID_NULL, UA_NS0ID(OBJECTSFOLDER), + UA_QUALIFIEDNAME(0, "FileSystem"), backendArg(backend), + NULL, &fsId), UA_STATUSCODE_GOOD); + UA_UInt32 h = 0; + UA_NodeId fileId = callCreateFile(fsId, "new.bin", true, &h, UA_STATUSCODE_GOOD); + ck_assert_uint_eq(recordedOpen.calls, 1); + ck_assert(recordedOpen.hasContext); + ck_assert(UA_NodeId_equal(&recordedOpen.sessionId, + &server_ft->adminSession.sessionId)); + ck_assert_uint_eq(recordedOpen.mode, UA_OPENFILEMODE_READ | UA_OPENFILEMODE_WRITE); + UA_String path = UA_STRING("new.bin"); + ck_assert(UA_String_equal(&recordedOpen.path, &path)); + callClose(fileId, h, UA_STATUSCODE_GOOD); + UA_NodeId_clear(&fileId); + + UA_NodeId docsId = resolveChild(server_ft, fsId, "docs"); + UA_NodeId aId = resolveChild(server_ft, docsId, "a.txt"); + UA_NodeId copyId = callMoveOrCopy(docsId, aId, fsId, true, "copy.txt", + UA_STATUSCODE_GOOD); + ck_assert_uint_eq(recordedOpen.calls, 3); + ck_assert(recordedOpen.hasContext); + ck_assert(UA_NodeId_equal(&recordedOpen.sessionId, + &server_ft->adminSession.sessionId)); + ck_assert_uint_eq(recordedOpen.mode, UA_OPENFILEMODE_WRITE); + path = UA_STRING("copy.txt"); + ck_assert(UA_String_equal(&recordedOpen.path, &path)); + + UA_UInt32 fc = 0; + ck_assert_uint_eq(backend.file.open(&backend.file, path, UA_OPENFILEMODE_READ, + NULL, &fc), UA_STATUSCODE_GOOD); + ck_assert(!recordedOpen.hasContext); + ck_assert_uint_eq(backend.file.close(&backend.file, fc), UA_STATUSCODE_GOOD); + UA_NodeId_clear(©Id); + UA_NodeId_clear(&aId); + UA_NodeId_clear(&docsId); + resetRecordedOpen(); +} END_TEST + START_TEST(fileAccessRightsEnforced) { AccessPolicy policy; UA_FileTransferBackend backend = @@ -5953,13 +6077,16 @@ START_TEST(indexedDirectoryAndSessionCleanup) { lockServer(server_ft); for(size_t s = 0; s < 2; s++) { for(size_t i = 0; i < 8; i++) { + FTHandle *opened = NULL; ck_assert_uint_eq(openFileHandle(server_ft, file, &sessions[s], NULL, - UA_OPENFILEMODE_READ, &handles[s][i]), UA_STATUSCODE_GOOD); + UA_OPENFILEMODE_READ, &opened), UA_STATUSCODE_GOOD); + handles[s][i] = opened->handle; ck_assert_ptr_null(findFTHandle(ftd, &sessions[1 - s], handles[s][i])); } - UA_UInt32 rejected; + FTHandle *rejected = NULL; ck_assert_uint_eq(openFileHandle(server_ft, file, &sessions[s], NULL, UA_OPENFILEMODE_READ, &rejected), UA_STATUSCODE_BADRESOURCEUNAVAILABLE); + ck_assert_ptr_null(rejected); } unlockServer(server_ft); ck_assert_uint_eq(readOpenCount(files[0]), 16); @@ -6090,6 +6217,7 @@ int main(void) { tcase_add_test(tc_file, readOnlyBackendNeedsNoWriteCallbacks); tcase_add_test(tc_file, userWritableFollowsStorage); tcase_add_test(tc_file, fileAccessRightsEnforced); + tcase_add_test(tc_file, openReceivesCaller); #ifndef _WIN32 tcase_add_test(tc_file, localDirectoryAccessRightsFollowPermissions); #endif @@ -6152,6 +6280,7 @@ int main(void) { tcase_add_test(tc_dir, localDirectoryListingErrors); #endif tcase_add_checked_fixture(tc_dir, setup, teardown); + tcase_add_test(tc_dir, openInDirectories); suite_add_tcase(s, tc_dir); TCase *tc_backend = tcase_create("Storage Backends");