# HG changeset patch # User Paul Boddie # Date 1628371160 -7200 # Node ID d39c8916cc3d0fa8bce55b5b6f388c4cff5aafca # Parent 6b456edd075b3b90d872708006d317b2c7155291 Introduced support for pipe closure detection during directory listing production. This requires the reader endpoint to be propagated from the server to the client, with the capability being discarded in the server after having been sent using an operation completion function in the opener context. Simplified various reading and writing operations, removing loops that should be unnecessary: either a blocking read or write completes successfully or it runs short, indicating the closure of the pipe. Reorganised the directory reading test so that listing completion and early termination situations can be more easily tested. diff -r 6b456edd075b -r d39c8916cc3d libfsserver/lib/files/ext2_file_opener.cc --- a/libfsserver/lib/files/ext2_file_opener.cc Sat Aug 07 22:55:39 2021 +0200 +++ b/libfsserver/lib/files/ext2_file_opener.cc Sat Aug 07 23:19:20 2021 +0200 @@ -81,7 +81,7 @@ { /* Subscribe to space and closure notifications on the pipe. */ - long err = client_set_blocking(writer, NOTIFY_SPACE_AVAILABLE); + long err = client_set_blocking(writer, NOTIFY_SPACE_AVAILABLE | NOTIFY_PEER_CLOSED); if (err) { @@ -135,16 +135,20 @@ *size = reader->size; *cap = reader->ref; - /* Discard the reader structure but do not close the reader itself. */ - - delete reader; - /* Spawn a independent thread for reading the directory details and writing them to the pipe. */ std::thread(_read_directory, this, fileid, writer).detach(); - return L4_EOK; + /* Discard the reader structure but preserve the capability. */ + + reader->ref = L4_INVALID_CAP; + file_close(reader); + + /* Return an indication that the capability will be propagated and not + retained. This is explicitly supported by the opener context. */ + + return IPC_MESSAGE_SENT; } /* Thread payload helper method. */ @@ -195,10 +199,12 @@ /* Write the structure to the pipe. */ - offset_t nwritten = 0; + offset_t nwritten = client_write(dir->writer, (const void *) dirent, reclen); - while (nwritten < reclen) - nwritten += client_write(dir->writer, (const void *) (dirent + nwritten), reclen - nwritten); + /* Stop writing if the pipe is closed. */ + + if (nwritten < reclen) + return DIRENT_ABORT; return 0; } diff -r 6b456edd075b -r d39c8916cc3d libfsserver/lib/files/host_file_opener.cc --- a/libfsserver/lib/files/host_file_opener.cc Sat Aug 07 22:55:39 2021 +0200 +++ b/libfsserver/lib/files/host_file_opener.cc Sat Aug 07 23:19:20 2021 +0200 @@ -40,7 +40,7 @@ /* Subscribe to space and closure notifications on the pipe. */ - long err = client_set_blocking(writer, NOTIFY_SPACE_AVAILABLE); + long err = client_set_blocking(writer, NOTIFY_SPACE_AVAILABLE | NOTIFY_PEER_CLOSED); if (err) { @@ -52,10 +52,12 @@ while ((dirent = readdir(dir)) != NULL) { - offset_t nwritten = 0; + offset_t nwritten = client_write(writer, (const void *) dirent, dirent->d_reclen); - while (nwritten < dirent->d_reclen) - nwritten += client_write(writer, (const void *) (dirent + nwritten), dirent->d_reclen - nwritten); + /* Stop writing if the pipe is closed. */ + + if (nwritten < dirent->d_reclen) + break; } client_close(writer); @@ -104,9 +106,10 @@ *size = reader->size; *cap = reader->ref; - /* Discard the reader structure but do not close the reader itself. */ + /* Discard the reader structure but preserve the capability. */ - delete reader; + reader->ref = L4_INVALID_CAP; + file_close(reader); /* Spawn a independent thread for reading the directory details and writing them to the pipe. */ diff -r 6b456edd075b -r d39c8916cc3d libfsserver/lib/files/opener_context_resource.cc --- a/libfsserver/lib/files/opener_context_resource.cc Sat Aug 07 22:55:39 2021 +0200 +++ b/libfsserver/lib/files/opener_context_resource.cc Sat Aug 07 23:19:20 2021 +0200 @@ -19,6 +19,8 @@ * Boston, MA 02110-1301, USA */ +#include + #include "opener_context_resource.h" #include "opener_context_object_server.h" #include "opener_resource.h" @@ -72,7 +74,19 @@ if (path == NULL) return -L4_EINVAL; - return _opener->open(path, flags, size, file); + long err = _opener->open(path, flags, size, file); + + /* Handle propagated capabilities. By indicating the special status, the + operation is first completed and then the capability is discarded. */ + + if (err == IPC_MESSAGE_SENT) + { + complete_OpenerContext_open(*size, *file); + ipc_cap_free_um(*file); + return IPC_MESSAGE_SENT; + } + + return err; } // vim: tabstop=4 expandtab shiftwidth=4 diff -r 6b456edd075b -r d39c8916cc3d tests/dstest_file_readdir.cc --- a/tests/dstest_file_readdir.cc Sat Aug 07 22:55:39 2021 +0200 +++ b/tests/dstest_file_readdir.cc Sat Aug 07 23:19:20 2021 +0200 @@ -34,6 +34,94 @@ #define DIRENT_CORE_SIZE (sizeof(struct dirent) - sizeof(((struct dirent *) 0)->d_name)) + + +/* Return a directory entry. This must be freed by the caller after use. */ + +static struct dirent *read_directory_entry(file_t *file) +{ + char buffer[DIRENT_CORE_SIZE]; + offset_t nread = client_read(file, buffer, DIRENT_CORE_SIZE); + + /* Stop if no new structure can be successfully read. */ + + if (nread != DIRENT_CORE_SIZE) + return NULL; + + struct dirent *dirent = (struct dirent *) buffer; + offset_t remaining = dirent->d_reclen - DIRENT_CORE_SIZE; + + /* Allocate a buffer for the complete structure. */ + + char *entry = (char *) calloc(DIRENT_CORE_SIZE + remaining, sizeof(char)); + + if (entry == NULL) + return NULL; + + /* Copy the start of the entry into a new buffer. */ + + memcpy(entry, buffer, DIRENT_CORE_SIZE); + + /* Append to the entry buffer. */ + + char *current = entry + DIRENT_CORE_SIZE; + + nread = client_read(file, current, remaining); + + /* Stop if no complete structure can be successfully read. */ + + if (nread != remaining) + { + free(entry); + return NULL; + } + + return (struct dirent *) entry; +} + + + +static file_t *open_file(char *filename, bool have_uid, sys_uid_t uid) +{ + /* With a user, open a user-specific file opener. */ + + if (have_uid) + { + l4_cap_idx_t opener = client_open_for_user((user_t) {uid, uid, 0022}); + + if (l4_is_invalid_cap(opener)) + { + printf("Could not obtain opener for file.\n"); + return NULL; + } + + /* Invoke the open method to receive the file reference. */ + + return client_open_using(filename, O_DIRECTORY, opener); + } + else + { + return client_open(filename, O_DIRECTORY); + } +} + + + +static long open_directory(file_t *file) +{ + // NOTE: To be replaced by a proper mechanism identifying the nature of each + // NOTE: obtained object. + + file->can_mmap = 0; + file->has_size = 0; + + /* Register the reader for notification. */ + + return client_set_blocking(file, NOTIFY_CONTENT_AVAILABLE | NOTIFY_PEER_CLOSED); +} + + + int main(int argc, char *argv[]) { if (argc < 2) @@ -45,28 +133,7 @@ char *filename = argv[1]; bool have_uid = (argc > 2) && strlen(argv[2]); sys_uid_t uid = have_uid ? atoi(argv[2]) : 0; - file_t *file; - - /* With a user, open a user-specific file opener. */ - - if (have_uid) - { - l4_cap_idx_t opener = client_open_for_user((user_t) {uid, uid, 0022}); - - if (l4_is_invalid_cap(opener)) - { - printf("Could not obtain opener for file.\n"); - return 1; - } - - /* Invoke the open method to receive the file reference. */ - - file = client_open_using(filename, O_DIRECTORY, opener); - } - else - { - file = client_open(filename, O_DIRECTORY); - } + file_t *file = open_file(filename, have_uid, uid); if (file == NULL) { @@ -74,15 +141,7 @@ return 1; } - // NOTE: To be replaced by a proper mechanism identifying the nature of each - // NOTE: obtained object. - - file->can_mmap = 0; - file->has_size = 0; - - /* Register the reader for notification. */ - - long err = client_set_blocking(file, NOTIFY_CONTENT_AVAILABLE | NOTIFY_PEER_CLOSED); + long err = open_directory(file); if (err) { @@ -90,48 +149,44 @@ return 1; } - char buffer[DIRENT_CORE_SIZE]; - offset_t nread = client_read(file, buffer, DIRENT_CORE_SIZE); - offset_t total = 0; - - while (1) - { - if (!nread && (file->notifications & NOTIFY_PEER_CLOSED)) - break; - - total += nread; - - if (total == DIRENT_CORE_SIZE) - { - struct dirent *dirent = (struct dirent *) buffer; - int remaining = dirent->d_reclen - DIRENT_CORE_SIZE; - char entry[DIRENT_CORE_SIZE + remaining], *current; + struct dirent *dirent; - memcpy(entry, buffer, DIRENT_CORE_SIZE); - current = entry + DIRENT_CORE_SIZE; - - do - { - nread = client_read(file, current, remaining); - remaining -= nread; - current += nread; - } - while (nread && remaining); - - if (remaining) - break; - - dirent = (struct dirent *) entry; - printf("> %s\n", dirent->d_name); - - total = 0; - } - - nread = client_read(file, buffer, DIRENT_CORE_SIZE); + while ((dirent = read_directory_entry(file)) != NULL) + { + printf("> %s\n", dirent->d_name); + free(dirent); } printf("Directory shown.\n"); + /* Open again, reading a single entry only. */ + + file = open_file(filename, have_uid, uid); + + if (file == NULL) + { + printf("Could not obtain directory.\n"); + return 1; + } + + err = open_directory(file); + + if (err) + { + printf("Could not subscribe to notifications: %s\n", l4sys_errtostr(err)); + return 1; + } + + dirent = read_directory_entry(file); + + if (dirent != NULL) + { + printf("> %s\n", dirent->d_name); + free(dirent); + } + + printf("Entry shown.\n"); + return 0; }