L4Re/departure

libfsclient/lib/src/client.cc

693:9d03d878b5ab
5 months ago Paul Boddie Used the convenience function to generate notification flags. libc_newlib
     1 /*     2  * Filesystem client functions.     3  *     4  * Copyright (C) 2018-2024 Paul Boddie <paul@boddie.org.uk>     5  *     6  * This program is free software; you can redistribute it and/or     7  * modify it under the terms of the GNU General Public License as     8  * published by the Free Software Foundation; either version 2 of     9  * the License, or (at your option) any later version.    10  *    11  * This program is distributed in the hope that it will be useful,    12  * but WITHOUT ANY WARRANTY; without even the implied warranty of    13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    14  * GNU General Public License for more details.    15  *    16  * You should have received a copy of the GNU General Public License    17  * along with this program; if not, write to the Free Software    18  * Foundation, Inc., 51 Franklin Street, Fifth Floor,    19  * Boston, MA  02110-1301, USA    20  */    21     22 #include <l4/re/env.h>    23     24 #include <stdio.h>    25 #include <stdlib.h>    26 #include <string.h>    27     28 #include <mem/memory_utils.h>    29 #include <systypes/env.h>    30 #include <systypes/fcntl.h>    31     32 #include "client.h"    33     34     35     36 /* Default size of pipe regions. */    37     38 static const offset_t DEFAULT_PIPE_SIZE = 4096;    39     40 /* Size of the core member region of a directory entry structure. */    41     42 static const offset_t DIRENT_CORE_SIZE = (sizeof(struct dirent) - sizeof(((struct dirent *) 0)->d_name));    43     44     45     46 /* Merging of region flags from protection and access flags. */    47     48 static rm_flags_t _combine_region_flags(rm_flags_t region_flags, flags_t flags)    49 {    50   return region_flags & (file_region_flags(flags) | L4RE_RM_F_X);    51 }    52     53 /* Conversion of protection and access flags to region flags. */    54     55 rm_flags_t client_region_flags(prot_t prot, flags_t flags)    56 {    57   rm_flags_t rm_flags = 0;    58     59   if (prot & PROT_READ)    60     rm_flags |= L4RE_RM_F_R;    61   if (prot & PROT_WRITE)    62     rm_flags |= L4RE_RM_F_W;    63   if (prot & PROT_EXEC)    64     rm_flags |= L4RE_RM_F_X;    65     66   return _combine_region_flags(rm_flags, flags);    67 }    68     69     70     71 /* Access the given position and synchronise state with the file object. Pipe    72    objects may return busy conditions indicating that the desired access cannot    73    yet be fulfilled. */    74     75 static long _access(file_t *file, offset_t position)    76 {    77   long err;    78     79   if (file->object_flags & OBJECT_SUPPORTS_MMAP)    80   {    81     offset_t adjusted_position = position;    82     83     /* Where the position is outside the current region, re-map. */    84     85     if ((position < file->start_pos) || (position >= file->end_pos))    86     {    87       offset_t length = file_span(file);    88     89       if (!length)    90         length = PAGE_SIZE;    91     92       /* Avoid growth of the mapped region when the end of the region is    93          calculated to be position + span, which is then rounded up, whereas the    94          start of the region is rounded down. */    95     96       adjusted_position = trunc(position, PAGE_SIZE);    97     98       if (file_mmap(file, adjusted_position, length, 0, 0,    99                     file_region_flags(file->flags)))   100         return -L4_EIO;   101     }   102    103     /* Otherwise, flush any written data in the current region and update the   104        file size details. */   105    106     else   107     {   108       err = client_flush(file);   109       if (err)   110         return err;   111     }   112    113     /* Update the current data offset. */   114    115     file->data_current = position - file->start_pos;   116    117     return L4_EOK;   118   }   119   else   120   {   121     bool initial = (file->memory == NULL);   122    123     /* Handle the initial condition with no current region. */   124    125     if (initial)   126     {   127       err = client_current_region(file);   128       if (err)   129         return err;   130     }   131    132     /* Strict conditions for region navigation in pipes. */   133    134     if ((position < file->start_pos) || (position > file->end_pos))   135     {   136       return -L4_EIO;   137     }   138    139     /* The next region is only available at the end of the mapped memory. */   140    141     else if (position == file->end_pos)   142     {   143       return client_next_region(file);   144     }   145    146     /* Within the current pipe region, synchronise with the pipe object. */   147    148     else   149     {   150       if (initial)   151         return L4_EOK;   152       else   153         return client_current_region(file);   154     }   155   }   156 }   157    158    159    160 /* Return whether an operation on file should block for more content or more   161    space. A file must be configured for blocking, not be closed, and must either   162    be lacking content (if reading) or space (if writing). */   163    164 static int _operation_blocking(file_t *file, int reading)   165 {   166   return (file->can_block && !(file->notifiable.notifications & NOTIFY_PEER_CLOSED) && (   167           (reading && !file_data_available(file)) ||   168           (!reading && !file_data_space(file))));   169 }   170    171    172    173 /* Return whether an access could occur, blocking if necessary. */   174    175 static int _access_blocking(file_t *file, offset_t position, int reading)   176 {   177   long err;   178    179   /* Attempt to access the position, handling an error condition or a blocking   180      condition. */   181    182   while ((err = _access(file, position)) || _operation_blocking(file, reading))   183   {   184     position = file->data_current;   185    186     /* Exit if blocking is not configured or suitable. */   187    188     if ((err && (err != -L4_EBUSY)) || !file->can_block)   189       return 0;   190    191     /* Handle an inability to access by blocking, exiting if waiting failed. */   192    193     if (client_wait_file(file, client_notifier_task()))   194       return 0;   195   }   196    197   return 1;   198 }   199    200    201    202 /* Ensure that memory is mapped for accessing the given file, using the   203    indicated count as a region size hint. */   204    205 static void *_map_memory(file_t *file, offset_t count)   206 {   207   if (file->memory == NULL)   208   {   209     if (file->object_flags & OBJECT_SUPPORTS_MMAP)   210       return client_mmap(file, client_tell(file), count, 0, 0,   211                          file_region_flags(file->flags));   212     else if (pipe_current(file))   213       return NULL;   214   }   215    216   return file->memory;   217 }   218    219    220    221 /* Open a file opening object. */   222    223 l4_cap_idx_t client_open_for_user(user_t user)   224 {   225   l4_cap_idx_t server = l4re_env_get_cap(ENV_FILESYSTEM_SERVER_NAME);   226    227   return client_open_for_user_using(user, server);   228 }   229    230 /* Open a file opening object via a named capability. */   231    232 l4_cap_idx_t client_open_for_user_using(user_t user, l4_cap_idx_t server)   233 {   234   if (l4_is_invalid_cap(server))   235     return L4_INVALID_CAP;   236    237   l4_cap_idx_t opener;   238   long err = file_open_for_user(user, server, &opener);   239    240   if (err)   241     return L4_INVALID_CAP;   242    243   return opener;   244 }   245    246    247    248 /* Close a filesystem object. */   249    250 void client_close(file_t *file)   251 {   252   if (file == NULL)   253     return;   254    255   if (file->metadata != NULL)   256     client_close(file->metadata);   257    258   file_flush(file);   259   file_close(file);   260   free(file);   261 }   262    263    264    265 /* Obtain a stream from the environment. */   266    267 file_t *client_get_stream(const char *name, flags_t flags)   268 {   269   l4_cap_idx_t ref = l4re_env_get_cap(name);   270   file_t *stream;   271    272   if (l4_is_invalid_cap(ref))   273     return NULL;   274    275   stream = (file_t *) malloc(sizeof(file_t));   276    277   file_init(stream);   278   stream->flags = flags;   279   stream->ref = ref;   280    281   /* Restore the state of the stream. */   282    283   client_sync_stream(stream);   284    285   /* Enforce blocking if necessary.   286      NOTE: Ignoring any event subscription error. */   287    288   if (!(flags & O_NONBLOCK))   289     client_set_blocking(stream, file_notify_flags(flags));   290    291   return stream;   292 }   293    294 /* Initialise the stream data position to the end of any existing data for an   295    output stream. For an input stream, initialise the position to the end of   296    consumed data, preserving any unconsumed data. */   297    298 long client_sync_stream(file_t *file)   299 {   300   if (!client_opened(file))   301     return -L4_EINVAL;   302    303   return file_refresh(file);   304 }   305    306    307    308 /* Open a filesystem object. */   309    310 file_t *client_open(const char *name, flags_t flags)   311 {   312   l4_cap_idx_t server = l4re_env_get_cap(ENV_FILESYSTEM_SERVER_NAME);   313    314   return client_open_using(name, flags, server);   315 }   316    317 /* Open a filesystem object via a named capability. */   318    319 file_t *client_open_using(const char *name, flags_t flags, l4_cap_idx_t server)   320 {   321   if (l4_is_invalid_cap(server))   322     return NULL;   323    324   file_t *file = (file_t *) malloc(sizeof(file_t));   325    326   if (file == NULL)   327     return NULL;   328    329   /* Return any allocated structure even if an error occurs. */   330    331   file->error = file_open(file, name, flags, server);   332   return file;   333 }   334    335    336    337 /* Open a directory listing stream via the given named directory. */   338    339 file_t *client_opendir(const char *name)   340 {   341   l4_cap_idx_t server = l4re_env_get_cap(ENV_FILESYSTEM_SERVER_NAME);   342    343   return client_opendir_using(name, server);   344 }   345    346 /* Open a directory listing stream via the given named directory and a named   347    capability, returning the directory object with a metadata file object   348    reference to the directory listing stream. */   349    350 file_t *client_opendir_using(const char *name, l4_cap_idx_t server)   351 {   352   file_t *file = client_open_using(name, O_DIRECTORY, server);   353    354   /* Return the directory structure itself for error handling. */   355    356   if (!client_opened(file))   357     return file;   358    359   file->metadata = client_opendir_reader(file);   360    361   /* Return the directory even if an error occurs. */   362    363   return file;   364 }   365    366    367    368 /* Open a directory listing stream via the given directory. */   369    370 file_t *client_opendir_reader(file_t *file)   371 {   372   file_t *reader = (file_t *) malloc(sizeof(file_t));   373    374   if (reader == NULL)   375     return NULL;   376    377   /* Return any allocated structure even if an error occurs. */   378    379   reader->error = directory_opendir(file, reader);   380    381   /* Set blocking read mode to be able to conveniently read directory entries   382      from the stream. If this fails, the error is set on the structure, but the   383      stream will be open. */   384    385   if (!reader->error)   386     reader->error = client_set_blocking(reader, NOTIFY_CONTENT_AVAILABLE | NOTIFY_PEER_CLOSED);   387    388   return reader;   389 }   390    391    392    393 /* Open another instance of an opened filesystem object. */   394    395 file_t *client_reopen(file_t *file, flags_t flags)   396 {   397   if (file == NULL)   398     return NULL;   399    400   file_t *new_file = (file_t *) malloc(sizeof(file_t));   401    402   if (new_file == NULL)   403     return NULL;   404    405   /* Return any allocated structure even if an error occurs. */   406    407   new_file->error = file_reopen(file, new_file, flags);   408   return new_file;   409 }   410    411    412    413 /* Open a pipe object, returning any error condition. */   414    415 long client_pipe(file_t **reader, file_t **writer, flags_t flags)   416 {   417   l4_cap_idx_t server = l4re_env_get_cap(ENV_PIPE_SERVER_NAME);   418    419   return client_pipe_using(reader, writer, flags, server);   420 }   421    422 long client_pipe_using(file_t **reader, file_t **writer, flags_t flags, l4_cap_idx_t server)   423 {   424   *reader = NULL;   425   *writer = NULL;   426    427   if (l4_is_invalid_cap(server))   428     return -L4_EINVAL;   429    430   *reader = (file_t *) malloc(sizeof(file_t));   431    432   if (*reader == NULL)   433     return -L4_ENOMEM;   434    435   *writer = (file_t *) malloc(sizeof(file_t));   436    437   if (*writer == NULL)   438   {   439     free(*reader);   440     return -L4_ENOMEM;   441   }   442    443   long err = pipe_open(DEFAULT_PIPE_SIZE, *reader, *writer, flags, server);   444    445   /* Set blocking if successful and non-blocking is not indicated. */   446    447   if (!err && !(flags & O_NONBLOCK))   448   {   449     err = client_set_blocking(*reader, NOTIFY_CONTENT_AVAILABLE | NOTIFY_PEER_CLOSED);   450     if (!err)   451       err = client_set_blocking(*writer, NOTIFY_SPACE_AVAILABLE | NOTIFY_PEER_CLOSED);   452   }   453    454   if (err)   455   {   456     free(*reader);   457     free(*writer);   458   }   459    460   return err;   461 }   462    463    464    465 /* Determine whether a file has been successfully opened. */   466    467 int client_opened(file_t *file)   468 {   469   return (file != NULL) && !file->error;   470 }   471    472    473    474 /* Make a directory in the filesystem. */   475    476 long client_mkdir(const char *path, mode_t mode)   477 {   478   l4_cap_idx_t server = l4re_env_get_cap(ENV_FILESYSTEM_SERVER_NAME);   479    480   return client_mkdir_using(path, mode, server);   481 }   482    483 /* Make a directory in the filesystem via a named capability. */   484    485 long client_mkdir_using(const char *path, mode_t mode, l4_cap_idx_t server)   486 {   487   return file_mkdir(path, mode, server);   488 }   489    490    491    492 /* Remove a file from the filesystem. */   493    494 long client_remove(const char *path)   495 {   496   l4_cap_idx_t server = l4re_env_get_cap(ENV_FILESYSTEM_SERVER_NAME);   497    498   return client_remove_using(path, server);   499 }   500    501 /* Remove a file from the filesystem via a named capability. */   502    503 long client_remove_using(const char *path, l4_cap_idx_t server)   504 {   505   return file_remove(path, server);   506 }   507    508    509    510 /* Rename a file in the filesystem. */   511    512 long client_rename(const char *source, const char *target)   513 {   514   l4_cap_idx_t server = l4re_env_get_cap(ENV_FILESYSTEM_SERVER_NAME);   515    516   return client_rename_using(source, target, server);   517 }   518    519 /* Rename a file in the filesystem via a named capability. */   520    521 long client_rename_using(const char *source, const char *target, l4_cap_idx_t server)   522 {   523   return file_rename(source, target, server);   524 }   525    526    527    528 /* Obtain filesystem object statistics. */   529    530 long client_stat(const char *path, struct stat *st)   531 {   532   l4_cap_idx_t server = l4re_env_get_cap(ENV_FILESYSTEM_SERVER_NAME);   533    534   return client_stat_using(path, st, server);   535 }   536    537 /* Obtain object statistics from the filesystem via a named capability. */   538    539 long client_stat_using(const char *path, struct stat *st, l4_cap_idx_t server)   540 {   541   return file_stat(path, st, server);   542 }   543    544    545    546 /* Obtain the current region of a pipe, updating the extent of populated data   547    and only changing the current position if no populated data exists. */   548    549 long client_current_region(file_t *file)   550 {   551   if (!client_opened(file))   552     return -L4_EINVAL;   553    554   return pipe_current(file);   555 }   556    557    558    559 /* Flush data explicitly to the filesystem object. */   560    561 long client_flush(file_t *file)   562 {   563   if (!client_opened(file))   564     return -L4_EINVAL;   565    566   /* Flush and retain most buffer settings. */   567    568   return file_flush(file);   569 }   570    571    572    573 /* Map a memory region to a file. */   574    575 void *client_mmap(file_t *file, offset_t position, offset_t length,   576                   offset_t start_visible, offset_t end_visible,   577                   rm_flags_t region_flags)   578 {   579   if (!client_opened(file) || file_mmap(file, position, length, start_visible,   580                                         end_visible, region_flags))   581     return NULL;   582    583   return file->memory;   584 }   585    586    587    588 /* Obtain the next region of a pipe. */   589    590 long client_next_region(file_t *file)   591 {   592   if (!client_opened(file))   593     return -L4_EINVAL;   594    595   return pipe_next(file);   596 }   597    598    599    600 /* Close a notifier object. */   601    602 void client_notifier_close(notifier_t *notifier)   603 {   604   notify_close(notifier);   605 }   606    607 /* Obtain a local notifier object. */   608    609 notifier_t *client_notifier_local()   610 {   611   return notify_get_local();   612 }   613    614 /* Obtain a task-wide notifier object. */   615    616 notifier_t *client_notifier_task()   617 {   618   return notify_get_task();   619 }   620    621    622    623 /* Read a directory entry. This must be freed by the caller after use. */   624    625 struct dirent *client_readdir(file_t *file)   626 {   627   if ((file == NULL) || (file->metadata == NULL))   628     return NULL;   629    630   char buffer[DIRENT_CORE_SIZE];   631   offset_t nread = client_read(file->metadata, buffer, DIRENT_CORE_SIZE);   632    633   /* Stop if no new structure can be successfully read. */   634    635   if (nread != DIRENT_CORE_SIZE)   636     return NULL;   637    638   struct dirent *dirent = (struct dirent *) buffer;   639   offset_t remaining = dirent->d_reclen - DIRENT_CORE_SIZE;   640    641   /* Allocate a buffer for the complete structure. */   642    643   char *entry = (char *) calloc(DIRENT_CORE_SIZE + remaining, sizeof(char));   644    645   if (entry == NULL)   646     return NULL;   647    648   /* Copy the start of the entry into a new buffer. */   649    650   memcpy(entry, buffer, DIRENT_CORE_SIZE);   651    652   /* Append to the entry buffer. */   653    654   char *current = entry + DIRENT_CORE_SIZE;   655    656   nread = client_read(file->metadata, current, remaining);   657    658   /* Stop if no complete structure can be successfully read. */   659    660   if (nread != remaining)   661   {   662     free(entry);   663     return NULL;   664   }   665    666   return (struct dirent *) entry;   667 }   668    669 /* Rewind directory reading. */   670    671 void client_rewinddir(file_t *file)   672 {   673   if ((file == NULL) || (file->metadata == NULL))   674     return;   675    676   client_close(file->metadata);   677    678   file->metadata = client_opendir_reader(file);   679 }   680    681    682    683 /* Read from the filesystem object into the buffer provided. */   684    685 offset_t client_read(file_t *file, void *buf, offset_t count)   686 {   687   if (!client_opened(file))   688     return 0;   689    690   /* Map memory if none has been mapped so far. */   691    692   if (_map_memory(file, count) == NULL)   693     return 0;   694    695   /* Amount available in the descriptor buffer already. */   696    697   offset_t available = file_data_available(file);   698   offset_t to_transfer, total = 0;   699    700   while (count > 0)   701   {   702     /* If there is no data, try and obtain more data. */   703    704     if (!available)   705     {   706       /* Flush any unwritten data, preparing to read from the file position at   707          the end of the data, and returning if no new data is available. */   708    709       if (!_access_blocking(file, file_data_end_position(file), 1))   710         break;   711    712       available = file_data_available(file);   713    714       if (!available)   715         break;   716     }   717    718     /* Transfer data into the supplied buffer. */   719    720     to_transfer = available <= count ? available : count;   721    722     file_data_read(file, (char *) buf, to_transfer);   723    724     /* Update counters. */   725    726     available -= to_transfer;   727    728     count -= to_transfer;   729     total += to_transfer;   730    731     buf = ((char *) buf + to_transfer);   732   }   733    734   return total;   735 }   736    737    738    739 /* Ensure that the buffer can provide the needed data. */   740    741 offset_t client_seek(file_t *file, offset_t offset, int whence)   742 {   743   if (!client_opened(file))   744     return 0;   745    746   offset_t position, current = file_data_current_position(file), change;   747    748   switch (whence)   749   {   750     case SEEK_SET:   751       position = offset;   752       break;   753    754     case SEEK_CUR:   755       position = current + offset;   756       break;   757    758     case SEEK_END:   759       position = file->size + offset;   760       break;   761    762     default:   763       /* NOTE: Set errno to EINVAL. */   764       return current;   765   }   766    767   /* Retain the current position if unchanged. */   768    769   if (position == current)   770     return position;   771    772   /* Move forward in the file. */   773    774   if (position > current)   775   {   776     change = position - current;   777    778     /* Move towards the end of available data.   779        Request new data if not enough is available. */   780    781     if (change <= file_data_available(file))   782     {   783       file->data_current += change;   784       return position;   785     }   786   }   787    788   /* Move backward in the file. */   789    790   else   791   {   792     change = current - position;   793    794     /* Move towards the start of available data.   795        Request new data if moving beyond the start of the data. */   796    797     if (change <= file->data_current)   798     {   799       file->data_current -= change;   800       return position;   801     }   802   }   803    804   /* Handle unwritten data and reset the buffer for reading. */   805    806   if (_access(file, position))   807     return current;   808    809   return position;   810 }   811    812    813    814 /* Set or unset blocking access for a file. */   815    816 long client_set_blocking(file_t *file, notify_flags_t flags)   817 {   818   long err;   819    820   if (file->can_block == flags)   821     return L4_EOK;   822    823   /* Since blocking access is used with specific file notifications, the   824      per-task notifier is used. */   825    826   notifier_t *notifier = client_notifier_task();   827    828   if (flags)   829     err = client_subscribe(file, flags, notifier);   830   else   831     err = client_unsubscribe(file, notifier);   832    833   if (err)   834     return err;   835    836   file->can_block = flags;   837   return L4_EOK;   838 }   839    840    841    842 /* Subscribe to events concerning a file. */   843    844 long client_subscribe(file_t *file, notify_flags_t flags, notifier_t *notifier)   845 {   846   if (!client_opened(file))   847     return -L4_EINVAL;   848    849   return notify_subscribe(file_notifiable(file), flags, notifier);   850 }   851    852    853    854 /* Return the current position in the file. */   855    856 offset_t client_tell(file_t *file)   857 {   858   if (!client_opened(file))   859     return -L4_EINVAL;   860    861   return file_data_current_position(file);   862 }   863    864    865    866 /* Unsubscribe from events concerning a file. */   867    868 long client_unsubscribe(file_t *file, notifier_t *notifier)   869 {   870   if (!client_opened(file))   871     return -L4_EINVAL;   872    873   return notify_unsubscribe(file_notifiable(file), notifier);   874 }   875    876    877    878 /* Wait for events involving a specific file. */   879    880 long client_wait_file(file_t *file, notifier_t *notifier)   881 {   882   if (!client_opened(file))   883     return -L4_EINVAL;   884    885   return file_notify_wait_file(file, notifier);   886 }   887    888 /* Wait for events concerning files, referencing a file object if an event is   889    delivered. */   890    891 long client_wait_files(file_t **file, notifier_t *notifier)   892 {   893   return file_notify_wait_files(file, notifier);   894 }   895    896    897    898 /* Write to the filesystem object from the buffer provided. */   899    900 offset_t client_write(file_t *file, const void *buf, offset_t count)   901 {   902   if (!client_opened(file))   903     return 0;   904    905   /* Map memory if none has been mapped so far. */   906    907   if (_map_memory(file, count) == NULL)   908     return 0;   909    910   /* Attempt to ensure that the file can accept the amount of data to be   911      written. This may not resize to the needed amount if a file has a fixed   912      size, but data will still be written to any available space. */   913    914   offset_t needed_size = file_data_current_position(file) + count;   915    916   if (file->object_flags & OBJECT_HAS_SIZE)   917   {   918     if (file->size < needed_size)   919     {   920       file_resize(file, needed_size);   921    922       if (file->size < needed_size)   923         count = file->size - file_data_current_position(file);   924     }   925   }   926    927   /* Space remaining in the descriptor buffer. */   928    929   offset_t space = file_data_space(file);   930   offset_t to_transfer, total = 0;   931    932   while (count > 0)   933   {   934     /* If no space is available, try and send data, reset the buffer. */   935    936     if (!space)   937     {   938       /* Flush any unwritten data and continue writing from the current data   939          position. */   940    941       if (!_access_blocking(file, file_data_current_position(file), 0))   942         break;   943    944       space = file_data_space(file);   945     }   946    947     /* Transfer data into the supplied buffer. */   948    949     to_transfer = space <= count ? space : count;   950    951     file_data_write(file, (char *) buf, to_transfer);   952    953     /* Update counters. */   954    955     space -= to_transfer;   956    957     count -= to_transfer;   958     total += to_transfer;   959    960     buf = ((char *) buf + to_transfer);   961   }   962    963   return total;   964 }   965    966 // vim: tabstop=2 expandtab shiftwidth=2