feat(driver): open files with the Session of the caller (Part 20 4.2.2)

The backend open gets the caller as a context: the Session of Open, CreateFile
and the copies of MoveOrCopy, NULL for direct calls. Its result reaches the
client.
This commit is contained in:
Andreas Ebner
2026-10-14 14:41:56 +02:00
committed by Andreas Ebner
parent 3350689aed
commit 0503c8fd97
8 changed files with 205 additions and 59 deletions
@@ -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);
+19 -13
View File
@@ -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;
}
+9 -12
View File
@@ -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;
}
@@ -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,
@@ -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)
+16 -2
View File
@@ -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. */
+1 -1
View File
@@ -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;
+156 -27
View File
@@ -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(&copyId);
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");