L4Re/departure

libfsclient/lib/src/client.cc

150:73649ab3a61d
2021-08-01 Paul Boddie Added filesystem factory interface provision and simplified the existing opening operation for filesystems to use a user structure.
     1 /*     2  * Filesystem client functions.     3  *     4  * Copyright (C) 2018, 2019, 2020, 2021 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     27 #include "client.h"    28     29     30     31 /* Default size of pipe regions. */    32     33 const offset_t DEFAULT_PIPE_SIZE = 4096;    34     35     36     37 /* Access the given position and synchronise state with the file object. */    38     39 static long _access(file_t *file, offset_t position)    40 {    41   long err;    42     43   if (file->can_mmap)    44   {    45     /* Where the position is outside the current region, re-map. */    46     47     if ((position < file->start_pos) || (position >= file->end_pos))    48     {    49       if (file_mmap(file, position, file_span(file)))    50         return -L4_EIO;    51     }    52     53     /* Otherwise, flush any written data in the current region and update the    54        file size details. */    55     56     else    57     {    58       err = client_flush(file);    59       if (err)    60         return err;    61     }    62   }    63   else    64   {    65     /* Strict conditions for region navigation in pipes. */    66     67     if ((position < file->start_pos) || (position > file->end_pos))    68     {    69       return -L4_EIO;    70     }    71     72     /* The next region is only available at the end of the mapped memory. */    73     74     else if (position == file->end_pos)    75     {    76       err = client_next_region(file);    77       if (err)    78         return err;    79     80       file->data_current = 0;    81       return L4_EOK;    82     }    83     84     /* Within the current pipe region, synchronise with the pipe object. */    85     86     else    87     {    88       err = client_current_region(file);    89       if (err)    90         return err;    91     }    92   }    93     94   /* Update the current data offset. */    95     96   file->data_current = position - file->start_pos;    97     98   return L4_EOK;    99 }   100    101    102    103 /* Return whether an access could occur, blocking if necessary. */   104    105 static int _access_blocking(file_t *file, offset_t position)   106 {   107   long err;   108    109   while ((err = _access(file, position)))   110   {   111     /* Exit if blocking is not configured or suitable. */   112    113     if ((err != -L4_EBUSY) || !file->can_block)   114       return 0;   115    116     /* Handle an inability to access by blocking, exiting if waiting failed. */   117    118     if (client_wait_file(file))   119       return 0;   120   }   121    122   return 1;   123 }   124    125    126    127 /* Ensure that memory is mapped for accessing the given file, using the   128    indicated count as a region size hint. */   129    130 static void *_map_memory(file_t *file, offset_t count)   131 {   132   if (file->memory == NULL)   133   {   134     if (file->can_mmap)   135       return client_mmap(file, client_tell(file), count);   136     else if (pipe_current(file))   137       return NULL;   138   }   139    140   return file->memory;   141 }   142    143    144    145 /* Open a file opening object. */   146    147 l4_cap_idx_t client_open_for_user(user_t user)   148 {   149   l4_cap_idx_t server = l4re_env_get_cap("server");   150    151   return client_open_for_user_using(user, server);   152 }   153    154 /* Open a file opening object via a named capability. */   155    156 l4_cap_idx_t client_open_for_user_using(user_t user, l4_cap_idx_t server)   157 {   158   if (l4_is_invalid_cap(server))   159     return L4_INVALID_CAP;   160    161   l4_cap_idx_t opener;   162   long err = file_open_for_user(user, server, &opener);   163    164   if (err)   165     return L4_INVALID_CAP;   166    167   return opener;   168 }   169    170    171    172 /* Close a filesystem object. */   173    174 void client_close(file_t *file)   175 {   176   if (file == NULL)   177     return;   178    179   file_close(file);   180   free(file);   181 }   182    183    184    185 /* Open a filesystem object. */   186    187 file_t *client_open(const char *name, flags_t flags)   188 {   189   l4_cap_idx_t server = l4re_env_get_cap("server");   190    191   return client_open_using(name, flags, server);   192 }   193    194 /* Open a filesystem object via a named capability. */   195    196 file_t *client_open_using(const char *name, flags_t flags, l4_cap_idx_t server)   197 {   198   if (l4_is_invalid_cap(server))   199     return NULL;   200    201   file_t *file = (file_t *) malloc(sizeof(file_t));   202    203   if (file == NULL)   204     return NULL;   205    206   if (file_open(file, name, flags, server))   207   {   208     free(file);   209     return NULL;   210   }   211    212   return file;   213 }   214    215    216    217 /* Open a pipe object. */   218    219 long client_pipe(file_t **reader, file_t **writer)   220 {   221   l4_cap_idx_t server = l4re_env_get_cap("pipes");   222    223   return client_pipe_using(reader, writer, server);   224 }   225    226 long client_pipe_using(file_t **reader, file_t **writer, l4_cap_idx_t server)   227 {   228   if (l4_is_invalid_cap(server))   229     return -L4_EINVAL;   230    231   *reader = (file_t *) malloc(sizeof(file_t));   232    233   if (*reader == NULL)   234     return -L4_ENOMEM;   235    236   *writer = (file_t *) malloc(sizeof(file_t));   237    238   if (*writer == NULL)   239   {   240     free(*reader);   241     return -L4_ENOMEM;   242   }   243    244   long err = pipe_open(DEFAULT_PIPE_SIZE, *reader, *writer, server);   245    246   if (err)   247   {   248     free(*reader);   249     free(*writer);   250   }   251    252   return err;   253 }   254    255    256    257 /* Flush data explicitly to the filesystem object. */   258    259 long client_flush(file_t *file)   260 {   261   if (file == NULL)   262     return -L4_EINVAL;   263    264   /* Flush and retain most buffer settings. */   265    266   return file_flush(file);   267 }   268    269    270    271 /* Map a memory region to a file. */   272    273 void *client_mmap(file_t *file, offset_t position, offset_t length)   274 {   275   if ((file == NULL) || (file_mmap(file, position, length)))   276     return NULL;   277    278   return file->memory;   279 }   280    281    282    283 /* Obtain the current region of a pipe. */   284    285 long client_current_region(file_t *file)   286 {   287   if (file == NULL)   288     return -L4_EINVAL;   289    290   return pipe_current(file);   291 }   292    293    294    295 /* Obtain the next region of a pipe. */   296    297 long client_next_region(file_t *file)   298 {   299   if (file == NULL)   300     return -L4_EINVAL;   301    302   return pipe_next(file);   303 }   304    305    306    307 /* Read from the filesystem object into the buffer provided. */   308    309 offset_t client_read(file_t *file, void *buf, offset_t count)   310 {   311   if (file == NULL)   312     return 0;   313    314   /* Map memory if none has been mapped so far. */   315    316   if (_map_memory(file, count) == NULL)   317     return 0;   318    319   /* Amount available in the descriptor buffer already. */   320    321   offset_t available = file_data_available(file);   322   offset_t to_transfer, total = 0;   323    324   while (count > 0)   325   {   326     /* If there is no data, try and obtain more data. */   327    328     if (!available)   329     {   330       /* Flush any unwritten data, preparing to read from the file position at   331          the end of the data, and returning if no new data is available. */   332    333       if (!_access_blocking(file, file_data_end_position(file)))   334         break;   335    336       available = file_data_available(file);   337    338       if (!available)   339         break;   340     }   341    342     /* Transfer data into the supplied buffer. */   343    344     to_transfer = available <= count ? available : count;   345    346     file_data_read(file, (char *) buf, to_transfer);   347    348     /* Update counters. */   349    350     available -= to_transfer;   351    352     count -= to_transfer;   353     total += to_transfer;   354    355     buf = ((char *) buf + to_transfer);   356   }   357    358   return total;   359 }   360    361    362    363 /* Ensure that the buffer can provide the needed data. */   364    365 offset_t client_seek(file_t *file, offset_t offset, int whence)   366 {   367   if (file == NULL)   368     return 0;   369    370   offset_t position, current = file_data_current_position(file), change;   371    372   switch (whence)   373   {   374     case SEEK_SET:   375       position = offset;   376       break;   377    378     case SEEK_CUR:   379       position = current + offset;   380       break;   381    382     case SEEK_END:   383       position = file->size + offset;   384       break;   385    386     default:   387       /* NOTE: Set errno to EINVAL. */   388       return current;   389   }   390    391   /* Retain the current position if unchanged. */   392    393   if (position == current)   394     return position;   395    396   /* Move forward in the file. */   397    398   if (position > current)   399   {   400     change = position - current;   401    402     /* Move towards the end of available data.   403        Request new data if not enough is available. */   404    405     if (change <= file_data_available(file))   406     {   407       file->data_current += change;   408       return position;   409     }   410   }   411    412   /* Move backward in the file. */   413    414   else   415   {   416     change = current - position;   417    418     /* Move towards the start of available data.   419        Request new data if moving beyond the start of the data. */   420    421     if (change <= file->data_current)   422     {   423       file->data_current -= change;   424       return position;   425     }   426   }   427    428   /* Handle unwritten data and reset the buffer for reading. */   429    430   if (_access(file, position))   431     return current;   432    433   return position;   434 }   435    436    437    438 /* Set or unset blocking access for a file. */   439    440 long client_set_blocking(file_t *file, notify_flags_t flags)   441 {   442   long err;   443    444   if (file->can_block == flags)   445     return L4_EOK;   446    447   // NOTE: Set appropriate flags.   448    449   if (flags)   450     err = client_subscribe(file, flags);   451   else   452     err = client_unsubscribe(file);   453    454   if (err)   455     return err;   456    457   file->can_block = flags;   458   return L4_EOK;   459 }   460    461    462    463 /* Subscribe from events concerning a file. */   464    465 long client_subscribe(file_t *file, notify_flags_t flags)   466 {   467   if (file == NULL)   468     return -L4_EINVAL;   469    470   return file_notify_subscribe(file, flags);   471 }   472    473    474    475 /* Return the current position in the file. */   476    477 long client_tell(file_t *file)   478 {   479   if (file == NULL)   480     return -L4_EINVAL;   481    482   return file_data_current_position(file);   483 }   484    485    486    487 /* Unsubscribe from events concerning a file. */   488    489 long client_unsubscribe(file_t *file)   490 {   491   if (file == NULL)   492     return -L4_EINVAL;   493    494   return file_notify_unsubscribe(file);   495 }   496    497    498    499 /* Wait for events involving a specific file. */   500    501 long client_wait_file(file_t *file)   502 {   503   if (file == NULL)   504     return -L4_EINVAL;   505    506   return file_notify_wait_file(file);   507 }   508    509 /* Wait for events concerning files, referencing a file object if an event is   510    delivered. */   511    512 long client_wait_files(file_t **file)   513 {   514   return file_notify_wait_files(file);   515 }   516    517    518    519 /* Write to the filesystem object from the buffer provided. */   520    521 offset_t client_write(file_t *file, const void *buf, offset_t count)   522 {   523   if (file == NULL)   524     return 0;   525    526   /* Map memory if none has been mapped so far. */   527    528   if (_map_memory(file, count) == NULL)   529     return 0;   530    531   /* Attempt to ensure that the file can accept the amount of data to be   532      written. This may not resize to the needed amount if a file has a fixed   533      size, but data will still be written to any available space. */   534    535   offset_t needed_size = file_data_current_position(file) + count;   536    537   if (file->has_size)   538   {   539     if (file->size < needed_size)   540     {   541       file_resize(file, needed_size);   542    543       if (file->size < needed_size)   544         count = file->size - file_data_current_position(file);   545     }   546   }   547    548   /* Space remaining in the descriptor buffer. */   549    550   offset_t space = file_data_space(file);   551   offset_t to_transfer, total = 0;   552    553   while (count > 0)   554   {   555     /* If no space is available, try and send data, reset the buffer. */   556    557     if (!space)   558     {   559       /* Flush any unwritten data and continue writing from the current data   560          position. */   561    562       if (!_access_blocking(file, file_data_current_position(file)))   563         break;   564    565       space = file_data_space(file);   566     }   567    568     /* Transfer data into the supplied buffer. */   569    570     to_transfer = space <= count ? space : count;   571    572     file_data_write(file, (char *) buf, to_transfer);   573    574     /* Update counters. */   575    576     space -= to_transfer;   577    578     count -= to_transfer;   579     total += to_transfer;   580    581     buf = ((char *) buf + to_transfer);   582   }   583    584   return total;   585 }   586    587 // vim: tabstop=2 expandtab shiftwidth=2