source: mainline/uspace/lib/c/generic/vfs/vfs.c@ bde5c04

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since bde5c04 was 8e3498b, checked in by Jiri Svoboda <jiri@…>, 8 years ago

vfs_read/write() should return error code separately from number of bytes transferred.

  • Property mode set to 100644
File size: 32.3 KB
RevLine 
[2f02aa17]1/*
[64d2b10]2 * Copyright (c) 2008 Jakub Jermar
[2f02aa17]3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup libc
30 * @{
31 */
32/** @file
33 */
[19b28b0]34
[79ae36dd]35#include <vfs/vfs.h>
[438f355]36#include <vfs/canonify.h>
[8ffedd8]37#include <vfs/vfs_mtab.h>
[79ae36dd]38#include <vfs/vfs_sess.h>
[ed903174]39#include <macros.h>
[d0dc74ae]40#include <stdlib.h>
[8d2dd7f2]41#include <stddef.h>
42#include <stdint.h>
[2f02aa17]43#include <ipc/services.h>
[79ae36dd]44#include <ns.h>
[2f02aa17]45#include <async.h>
[a28ab12]46#include <fibril_synch.h>
[2f02aa17]47#include <errno.h>
[a28ab12]48#include <assert.h>
[19f857a]49#include <str.h>
[15f3c3f]50#include <loc.h>
[2595dab]51#include <ipc/vfs.h>
[15f3c3f]52#include <ipc/loc.h>
[2f02aa17]53
[ca7506f]54/*
55 * This file contains the implementation of the native HelenOS file system API.
56 *
57 * The API supports client-side file system roots, client-side IO cursors and
58 * uses file handles as a primary means to refer to files. In order to call the
59 * API functions, one just includes vfs/vfs.h.
60 *
61 * The API functions come in two main flavors:
62 *
63 * - functions that operate on integer file handles, such as:
64 * vfs_walk(), vfs_open(), vfs_read(), vfs_link(), ...
65 *
66 * - functions that operate on paths, such as:
67 * vfs_lookup(), vfs_link_path(), vfs_unlink_path(), vfs_rename_path(), ...
68 *
69 * There is usually a corresponding path function for each file handle function
70 * that exists mostly as a convenience wrapper, except for cases when only a
71 * path version exists due to file system consistency considerations (see
72 * vfs_rename_path()). Sometimes one of the versions does not make sense, in
73 * which case it is also omitted.
74 *
75 * Besides of that, the API provides some convenience wrappers for frequently
76 * performed pairs of operations, for example there is a combo API for
77 * vfs_lookup() and vfs_open(): vfs_lookup_open().
78 *
79 * Some of the functions here return a file handle that can be passed to other
80 * functions. Note that a file handle does not automatically represent a file
81 * from which one can read or to which one can write. In order to do so, the
82 * file handle must be opened first for reading/writing using vfs_open().
83 *
84 * All file handles, no matter whether opened or not, must be eventually
85 * returned to the system using vfs_put(). Non-returned file handles are in use
86 * and consume system resources.
87 *
[8e3498b]88 * Functions that return int return an error code on error and do not
[ca7506f]89 * set errno. Depending on function, success is signalled by returning either
90 * EOK or a non-negative file handle.
91 *
92 * An example life-cycle of a file handle is as follows:
93 *
94 * #include <vfs/vfs.h>
95 *
96 * int file = vfs_lookup("/foo/bar/foobar", WALK_REGULAR);
97 * if (file < 0)
98 * return file;
99 * int rc = vfs_open(file, MODE_READ);
100 * if (rc != EOK) {
101 * (void) vfs_put(file);
102 * return rc;
103 * }
104 * aoff64_t pos = 42;
105 * char buf[512];
[8e3498b]106 * size_t nread;
107 * rc = vfs_read(file, &pos, buf, sizeof(buf), &nread);
108 * if (rc != EOK) {
[ca7506f]109 * vfs_put(file);
[8e3498b]110 * return rc;
[ca7506f]111 * }
112 *
[8e3498b]113 * // buf is now filled with nread bytes from file
[ca7506f]114 *
115 * vfs_put(file);
116 */
117
[79ae36dd]118static FIBRIL_MUTEX_INITIALIZE(vfs_mutex);
119static async_sess_t *vfs_sess = NULL;
[a28ab12]120
121static FIBRIL_MUTEX_INITIALIZE(cwd_mutex);
[5fec355]122
[2b88074b]123static int cwd_fd = -1;
124static char *cwd_path = NULL;
125static size_t cwd_size = 0;
[5fec355]126
[5126f80]127static FIBRIL_MUTEX_INITIALIZE(root_mutex);
128static int root_fd = -1;
129
[3ba431a]130static int get_parent_and_child(const char *path, char **child)
[eca9fd0]131{
132 size_t size;
[3ba431a]133 char *apath = vfs_absolutize(path, &size);
134 if (!apath)
[eca9fd0]135 return ENOMEM;
[b19e892]136
[3ba431a]137 char *slash = str_rchr(apath, L'/');
138 int parent;
139 if (slash == apath) {
140 parent = vfs_root();
141 *child = apath;
142 } else {
143 *slash = '\0';
144 parent = vfs_lookup(apath, WALK_DIRECTORY);
145 if (parent < 0) {
146 free(apath);
147 return parent;
148 }
149 *slash = '/';
150 *child = str_dup(slash);
151 free(apath);
152 if (!*child) {
153 vfs_put(parent);
154 return ENOMEM;
155 }
[b19e892]156 }
[3ba431a]157
158 return parent;
[0b18364]159}
160
[ca7506f]161/** Make a potentially relative path absolute
162 *
163 * This function coverts a current-working-directory-relative path into a
164 * well-formed, absolute path. The caller is responsible for deallocating the
165 * returned buffer.
166 *
167 * @param[in] path Path to be absolutized
168 * @param[out] retlen Length of the absolutized path
169 *
170 * @return New buffer holding the absolutized path or NULL
171 */
[6afc9d7]172char *vfs_absolutize(const char *path, size_t *retlen)
[5fec355]173{
174 char *ncwd_path;
[34a74ab]175 char *ncwd_path_nc;
[5fec355]176
[a28ab12]177 fibril_mutex_lock(&cwd_mutex);
[9eb3623]178 size_t size = str_size(path);
[5fec355]179 if (*path != '/') {
[6afc9d7]180 if (cwd_path == NULL) {
[a28ab12]181 fibril_mutex_unlock(&cwd_mutex);
[5fec355]182 return NULL;
183 }
[79ae36dd]184 ncwd_path_nc = malloc(cwd_size + 1 + size + 1);
[6afc9d7]185 if (ncwd_path_nc == NULL) {
[a28ab12]186 fibril_mutex_unlock(&cwd_mutex);
[5fec355]187 return NULL;
188 }
[79ae36dd]189 str_cpy(ncwd_path_nc, cwd_size + 1 + size + 1, cwd_path);
[9eb3623]190 ncwd_path_nc[cwd_size] = '/';
191 ncwd_path_nc[cwd_size + 1] = '\0';
[5fec355]192 } else {
[79ae36dd]193 ncwd_path_nc = malloc(size + 1);
[6afc9d7]194 if (ncwd_path_nc == NULL) {
[a28ab12]195 fibril_mutex_unlock(&cwd_mutex);
[5fec355]196 return NULL;
197 }
[34a74ab]198 ncwd_path_nc[0] = '\0';
[5fec355]199 }
[79ae36dd]200 str_append(ncwd_path_nc, cwd_size + 1 + size + 1, path);
[34a74ab]201 ncwd_path = canonify(ncwd_path_nc, retlen);
[6afc9d7]202 if (ncwd_path == NULL) {
[a28ab12]203 fibril_mutex_unlock(&cwd_mutex);
[34a74ab]204 free(ncwd_path_nc);
205 return NULL;
206 }
207 /*
208 * We need to clone ncwd_path because canonify() works in-place and thus
209 * the address in ncwd_path need not be the same as ncwd_path_nc, even
210 * though they both point into the same dynamically allocated buffer.
211 */
[095003a8]212 ncwd_path = str_dup(ncwd_path);
[34a74ab]213 free(ncwd_path_nc);
[6afc9d7]214 if (ncwd_path == NULL) {
[a28ab12]215 fibril_mutex_unlock(&cwd_mutex);
[923c39e]216 return NULL;
217 }
[a28ab12]218 fibril_mutex_unlock(&cwd_mutex);
[5fec355]219 return ncwd_path;
220}
[2f02aa17]221
[3ba431a]222/** Clone a file handle
[ca7506f]223 *
[3ba431a]224 * The caller can choose whether to clone an existing file handle into another
225 * already existing file handle (in which case it is first closed) or to a new
226 * file handle allocated either from low or high indices.
[ca7506f]227 *
[3ba431a]228 * @param file_from Source file handle
229 * @param file_to Destination file handle or -1
230 * @param high If file_to is -1, high controls whether the new file
231 * handle will be allocated from high indices
232 *
233 * @return New file handle on success or a negative error code
[ca7506f]234 */
[3ba431a]235int vfs_clone(int file_from, int file_to, bool high)
[5126f80]236{
[3ba431a]237 async_exch_t *vfs_exch = vfs_exchange_begin();
238 int rc = async_req_3_0(vfs_exch, VFS_IN_CLONE, (sysarg_t) file_from,
239 (sysarg_t) file_to, (sysarg_t) high);
240 vfs_exchange_end(vfs_exch);
241 return rc;
242}
[5126f80]243
[3ba431a]244/** Get current working directory path
245 *
246 * @param[out] buf Buffer
247 * @param size Size of @a buf
248 *
249 * @return EOK on success or a non-negative error code
250 */
251int vfs_cwd_get(char *buf, size_t size)
252{
253 fibril_mutex_lock(&cwd_mutex);
254
255 if ((cwd_size == 0) || (size < cwd_size + 1)) {
256 fibril_mutex_unlock(&cwd_mutex);
257 return ERANGE;
[5126f80]258 }
259
[3ba431a]260 str_cpy(buf, size, cwd_path);
261 fibril_mutex_unlock(&cwd_mutex);
262
263 return EOK;
[5126f80]264}
265
[3ba431a]266/** Change working directory
[ca7506f]267 *
[3ba431a]268 * @param path Path of the new working directory
[ca7506f]269 *
270 * @return EOK on success or a negative error code
271 */
[3ba431a]272int vfs_cwd_set(const char *path)
[5126f80]273{
[3ba431a]274 size_t abs_size;
275 char *abs = vfs_absolutize(path, &abs_size);
276 if (!abs)
277 return ENOMEM;
278
279 int fd = vfs_lookup(abs, WALK_DIRECTORY);
280 if (fd < 0) {
281 free(abs);
282 return fd;
283 }
284
285 fibril_mutex_lock(&cwd_mutex);
286
287 if (cwd_fd >= 0)
288 vfs_put(cwd_fd);
289
290 if (cwd_path)
291 free(cwd_path);
292
293 cwd_fd = fd;
294 cwd_path = abs;
295 cwd_size = abs_size;
296
297 fibril_mutex_unlock(&cwd_mutex);
298 return EOK;
299}
300
301/** Start an async exchange on the VFS session
302 *
303 * @return New exchange
304 */
305async_exch_t *vfs_exchange_begin(void)
306{
307 fibril_mutex_lock(&vfs_mutex);
308
309 while (vfs_sess == NULL) {
310 vfs_sess = service_connect_blocking(SERVICE_VFS, INTERFACE_VFS,
311 0);
312 }
313
314 fibril_mutex_unlock(&vfs_mutex);
315
316 return async_exchange_begin(vfs_sess);
317}
318
319/** Finish an async exchange on the VFS session
320 *
321 * @param exch Exchange to be finished
322 */
323void vfs_exchange_end(async_exch_t *exch)
324{
325 async_exchange_end(exch);
326}
327
328/** Open session to service represented by a special file
329 *
330 * Given that the file referred to by @a file represents a service,
331 * open a session to that service.
332 *
333 * @param file File handle representing a service
334 * @param iface Interface to connect to (XXX Should be automatic)
335 *
336 * @return Session pointer on success.
337 * @return @c NULL or error.
338 */
339async_sess_t *vfs_fd_session(int file, iface_t iface)
340{
341 struct stat stat;
342 int rc = vfs_stat(file, &stat);
343 if (rc != 0)
344 return NULL;
345
346 if (stat.service == 0)
347 return NULL;
348
349 return loc_service_connect(stat.service, iface, 0);
350}
351
[d2c8533]352/** Determine if a device contains the specified file system type. If so,
353 * return identification information.
354 *
355 * @param fs_name File system name
356 * @param serv Service representing the mountee
357 * @param info Place to store volume identification information
358 *
359 * @return EOK on success or a negative error code
360 */
361int vfs_fsprobe(const char *fs_name, service_id_t serv,
362 vfs_fs_probe_info_t *info)
363{
364 sysarg_t rc;
365
366 ipc_call_t answer;
367 async_exch_t *exch = vfs_exchange_begin();
368 aid_t req = async_send_1(exch, VFS_IN_FSPROBE, serv, &answer);
369
370 rc = async_data_write_start(exch, (void *) fs_name,
371 str_size(fs_name));
372
373 async_wait_for(req, &rc);
374
375 if (rc != EOK) {
376 vfs_exchange_end(exch);
377 return rc;
378 }
379
380 rc = async_data_read_start(exch, info, sizeof(*info));
381 vfs_exchange_end(exch);
382
383 return rc;
384}
385
386
[b14d9f9]387/** Return a list of currently available file system types
388 *
389 * @param fstypes Points to structure where list of filesystem types is
390 * stored. It is read as a null-terminated list of strings
391 * fstypes->fstypes[0..]. To free the list use vfs_fstypes_free().
392 *
393 * @return EOK on success or a negative error code
394 */
395int vfs_fstypes(vfs_fstypes_t *fstypes)
396{
397 sysarg_t size;
398 char *buf;
399 char dummybuf[1];
400 size_t count, i;
401
402 async_exch_t *exch = vfs_exchange_begin();
403 int rc = async_req_0_1(exch, VFS_IN_FSTYPES, &size);
404
405 if (rc != EOK) {
406 vfs_exchange_end(exch);
407 return rc;
408 }
409
410 buf = malloc(size);
411 if (buf == NULL) {
412 buf = dummybuf;
413 size = 1;
414 }
415
416 rc = async_data_read_start(exch, buf, size);
417 vfs_exchange_end(exch);
418
419 if (buf == dummybuf)
420 return ENOMEM;
421
422 /*
423 * Buffer should contain a number of null-terminated strings.
424 * Count them so that we can allocate an index
425 */
426 count = 0;
427 i = 0;
428 while (i < size) {
429 if (buf[i] == '\0')
430 ++count;
431 ++i;
432 }
433
434 if (count == 0) {
435 free(buf);
436 return EIO;
437 }
438
439 fstypes->fstypes = calloc(sizeof(char *), count + 1);
440 if (fstypes->fstypes == NULL) {
441 free(buf);
442 return ENOMEM;
443 }
444
445 /* Now fill the index */
446 if (buf[0] != '\0')
447 fstypes->fstypes[0] = &buf[0];
448 count = 0;
449 i = 0;
450 while (i < size) {
451 if (buf[i] == '\0')
452 fstypes->fstypes[++count] = &buf[i + 1];
453 ++i;
454 }
455 fstypes->fstypes[count] = NULL;
456 fstypes->buf = buf;
457 fstypes->size = size;
458
459 return rc;
460}
461
462/** Free list of file system types.
463 *
464 * @param fstypes List of file system types
465 */
466void vfs_fstypes_free(vfs_fstypes_t *fstypes)
467{
468 free(fstypes->buf);
469 fstypes->buf = NULL;
470 free(fstypes->fstypes);
471 fstypes->fstypes = NULL;
472 fstypes->size = 0;
473}
474
[3ba431a]475/** Link a file or directory
476 *
477 * Create a new name and an empty file or an empty directory in a parent
478 * directory. If child with the same name already exists, the function returns
479 * a failure, the existing file remains untouched and no file system object
480 * is created.
481 *
482 * @param parent File handle of the parent directory node
483 * @param child New name to be linked
484 * @param kind Kind of the object to be created: KIND_FILE or
485 * KIND_DIRECTORY
[a6fc88a]486 * @param[out] linkedfd If not NULL, will receive a file handle to the linked
487 * child
[3ba431a]488 * @return EOK on success or a negative error code
489 */
[a6fc88a]490int vfs_link(int parent, const char *child, vfs_file_kind_t kind, int *linkedfd)
[3ba431a]491{
492 int flags = (kind == KIND_DIRECTORY) ? WALK_DIRECTORY : WALK_REGULAR;
493 int file = vfs_walk(parent, child, WALK_MUST_CREATE | flags);
494
495 if (file < 0)
496 return file;
497
[a6fc88a]498 if (linkedfd)
499 *linkedfd = file;
500 else
501 vfs_put(file);
[3ba431a]502
503 return EOK;
504}
505
506/** Link a file or directory
507 *
508 * Create a new name and an empty file or an empty directory at given path.
509 * If a link with the same name already exists, the function returns
510 * a failure, the existing file remains untouched and no file system object
511 * is created.
512 *
513 * @param path New path to be linked
514 * @param kind Kind of the object to be created: KIND_FILE or
515 * KIND_DIRECTORY
[a6fc88a]516 * @param[out] linkedfd If not NULL, will receive a file handle to the linked
517 * child
[3ba431a]518 * @return EOK on success or a negative error code
519 */
[a6fc88a]520int vfs_link_path(const char *path, vfs_file_kind_t kind, int *linkedfd)
[3ba431a]521{
522 char *child;
523 int parent = get_parent_and_child(path, &child);
524 if (parent < 0)
525 return parent;
526
[a6fc88a]527 int rc = vfs_link(parent, child, kind, linkedfd);
[3ba431a]528
529 free(child);
530 vfs_put(parent);
531 return rc;
532}
533
534/** Lookup a path relative to the local root
535 *
536 * @param path Path to be looked up
537 * @param flags Walk flags
538 *
539 * @return File handle representing the result on success or a negative
540 * error code on error
541 */
542int vfs_lookup(const char *path, int flags)
543{
544 size_t size;
545 char *p = vfs_absolutize(path, &size);
546 if (!p)
547 return ENOMEM;
548 int root = vfs_root();
549 if (root < 0) {
550 free(p);
551 return ENOENT;
552 }
553 int rc = vfs_walk(root, p, flags);
554 vfs_put(root);
555 free(p);
556 return rc;
557}
558
559/** Lookup a path relative to the local root and open the result
560 *
561 * This function is a convenience combo for vfs_lookup() and vfs_open().
562 *
563 * @param path Path to be looked up
564 * @param flags Walk flags
565 * @param mode Mode in which to open file in
566 *
567 * @return EOK on success or a negative error code
568 */
569int vfs_lookup_open(const char *path, int flags, int mode)
570{
571 int file = vfs_lookup(path, flags);
572 if (file < 0)
573 return file;
574
575 int rc = vfs_open(file, mode);
576 if (rc != EOK) {
577 vfs_put(file);
578 return rc;
579 }
580
581 return file;
582}
583
584/** Mount a file system
585 *
586 * @param[in] mp File handle representing the mount-point
587 * @param[in] fs_name File system name
588 * @param[in] serv Service representing the mountee
589 * @param[in] opts Mount options for the endpoint file system
590 * @param[in] flags Mount flags
591 * @param[in] instance Instance number of the file system server
592 * @param[out] mountedfd File handle of the mounted root if not NULL
593 *
594 * @return EOK on success or a negative error code
595 */
596int vfs_mount(int mp, const char *fs_name, service_id_t serv, const char *opts,
597 unsigned int flags, unsigned int instance, int *mountedfd)
598{
599 sysarg_t rc, rc1;
600
601 if (!mountedfd)
602 flags |= VFS_MOUNT_NO_REF;
603 if (mp < 0)
604 flags |= VFS_MOUNT_CONNECT_ONLY;
605
606 ipc_call_t answer;
607 async_exch_t *exch = vfs_exchange_begin();
608 aid_t req = async_send_4(exch, VFS_IN_MOUNT, mp, serv, flags, instance,
609 &answer);
610
611 rc1 = async_data_write_start(exch, (void *) opts, str_size(opts));
612 if (rc1 == EOK) {
613 rc1 = async_data_write_start(exch, (void *) fs_name,
614 str_size(fs_name));
615 }
616
617 vfs_exchange_end(exch);
618
619 async_wait_for(req, &rc);
620
621 if (mountedfd)
622 *mountedfd = (int) IPC_GET_ARG1(answer);
623
624 if (rc != EOK)
625 return rc;
626 return rc1;
627}
[5126f80]628
[ca7506f]629/** Mount a file system
630 *
631 * @param[in] mp Path representing the mount-point
632 * @param[in] fs_name File system name
633 * @param[in] fqsn Fully qualified service name of the mountee
634 * @param[in] opts Mount options for the endpoint file system
635 * @param[in] flags Mount flags
636 * @param[in] instance Instance number of the file system server
637 *
638 * @return EOK on success or a negative error code
639 */
[80743a1]640int vfs_mount_path(const char *mp, const char *fs_name, const char *fqsn,
[4979403]641 const char *opts, unsigned int flags, unsigned int instance)
[2f02aa17]642{
[210e50a]643 int null_id = -1;
[15f3c3f]644 char null[LOC_NAME_MAXLEN];
[210e50a]645
[15f3c3f]646 if (str_cmp(fqsn, "") == 0) {
[61042de]647 /*
648 * No device specified, create a fresh null/%d device instead.
649 */
[15f3c3f]650 null_id = loc_null_create();
[210e50a]651
652 if (null_id == -1)
653 return ENOMEM;
654
[15f3c3f]655 snprintf(null, LOC_NAME_MAXLEN, "null/%d", null_id);
656 fqsn = null;
[210e50a]657 }
[82405266]658
[61042de]659 if (flags & IPC_FLAG_BLOCKING)
[5126f80]660 flags = VFS_MOUNT_BLOCKING;
[61042de]661 else
[5126f80]662 flags = 0;
663
[15f3c3f]664 service_id_t service_id;
665 int res = loc_service_get_id(fqsn, &service_id, flags);
[210e50a]666 if (res != EOK) {
667 if (null_id != -1)
[15f3c3f]668 loc_null_destroy(null_id);
[210e50a]669
[82405266]670 return res;
[210e50a]671 }
[82405266]672
[9eb3623]673 size_t mpa_size;
[6afc9d7]674 char *mpa = vfs_absolutize(mp, &mpa_size);
675 if (mpa == NULL) {
[210e50a]676 if (null_id != -1)
[15f3c3f]677 loc_null_destroy(null_id);
[210e50a]678
[5fec355]679 return ENOMEM;
[210e50a]680 }
[19b28b0]681
[5126f80]682 fibril_mutex_lock(&root_mutex);
[19b28b0]683
[5126f80]684 int rc;
[210e50a]685
[5126f80]686 if (str_cmp(mpa, "/") == 0) {
687 /* Mounting root. */
[210e50a]688
[5126f80]689 if (root_fd >= 0) {
690 fibril_mutex_unlock(&root_mutex);
[61042de]691 if (null_id != -1)
[5126f80]692 loc_null_destroy(null_id);
693 return EBUSY;
694 }
[210e50a]695
[5126f80]696 int root;
[61042de]697 rc = vfs_mount(-1, fs_name, service_id, opts, flags, instance,
698 &root);
699 if (rc == EOK)
[5126f80]700 root_fd = root;
701 } else {
702 if (root_fd < 0) {
703 fibril_mutex_unlock(&root_mutex);
[61042de]704 if (null_id != -1)
[5126f80]705 loc_null_destroy(null_id);
706 return EINVAL;
707 }
708
[151f1cc]709 int mpfd = vfs_walk(root_fd, mpa, WALK_DIRECTORY);
[5126f80]710 if (mpfd >= 0) {
[61042de]711 rc = vfs_mount(mpfd, fs_name, service_id, opts, flags,
712 instance, NULL);
[9c4cf0d]713 vfs_put(mpfd);
[5126f80]714 } else {
715 rc = mpfd;
716 }
[2f02aa17]717 }
[210e50a]718
[5126f80]719 fibril_mutex_unlock(&root_mutex);
[19b28b0]720
[210e50a]721 if ((rc != EOK) && (null_id != -1))
[15f3c3f]722 loc_null_destroy(null_id);
[210e50a]723
[2f02aa17]724 return (int) rc;
725}
726
[3ba431a]727
728/** Open a file handle for I/O
[ca7506f]729 *
[3ba431a]730 * @param file File handle to enable I/O on
731 * @param mode Mode in which to open file in
[ca7506f]732 *
733 * @return EOK on success or a negative error code
734 */
[3ba431a]735int vfs_open(int file, int mode)
[21f32ee1]736{
[3ba431a]737 async_exch_t *exch = vfs_exchange_begin();
738 int rc = async_req_2_0(exch, VFS_IN_OPEN, file, mode);
739 vfs_exchange_end(exch);
[b9067dfa]740
[5126f80]741 return rc;
[21f32ee1]742}
743
[3ba431a]744/** Pass a file handle to another VFS client
745 *
746 * @param vfs_exch Donor's VFS exchange
747 * @param file Donor's file handle to pass
748 * @param exch Exchange to the acceptor
749 *
750 * @return EOK on success or a negative error code
751 */
752int vfs_pass_handle(async_exch_t *vfs_exch, int file, async_exch_t *exch)
753{
754 return async_state_change_start(exch, VFS_PASS_HANDLE, (sysarg_t) file,
755 0, vfs_exch);
756}
757
[ca7506f]758/** Stop working with a file handle
759 *
760 * @param file File handle to put
[6afc9d7]761 *
[ca7506f]762 * @return EOK on success or a negative error code
[6afc9d7]763 */
[ca7506f]764int vfs_put(int file)
[72bde81]765{
[79ae36dd]766 async_exch_t *exch = vfs_exchange_begin();
[ca7506f]767 int rc = async_req_1_0(exch, VFS_IN_PUT, file);
[79ae36dd]768 vfs_exchange_end(exch);
[19b28b0]769
[ca7506f]770 return rc;
[72bde81]771}
772
[3ba431a]773/** Receive a file handle from another VFS client
[6afc9d7]774 *
[3ba431a]775 * @param high If true, the received file handle will be allocated from high
776 * indices
[6afc9d7]777 *
[3ba431a]778 * @return EOK on success or a negative error code
[6afc9d7]779 */
[3ba431a]780int vfs_receive_handle(bool high)
[2f02aa17]781{
[3ba431a]782 ipc_callid_t callid;
783 if (!async_state_change_receive(&callid, NULL, NULL, NULL)) {
784 async_answer_0(callid, EINVAL);
785 return EINVAL;
786 }
[354b642]787
[3ba431a]788 async_exch_t *vfs_exch = vfs_exchange_begin();
[2f02aa17]789
[3ba431a]790 async_state_change_finalize(callid, vfs_exch);
791
792 sysarg_t ret;
793 sysarg_t rc = async_req_1_1(vfs_exch, VFS_IN_WAIT_HANDLE, high, &ret);
794
795 async_exchange_end(vfs_exch);
[354b642]796
[6afc9d7]797 if (rc != EOK)
798 return rc;
[3ba431a]799 return ret;
[449c246]800}
[222e57c]801
[ca7506f]802/** Read data
[8fd04ba9]803 *
[6afc9d7]804 * Read up to @a nbytes bytes from file if available. This function always reads
805 * all the available bytes up to @a nbytes.
[8fd04ba9]806 *
[ca7506f]807 * @param file File handle to read from
808 * @param[inout] pos Position to read from, updated by the actual bytes read
[8fd04ba9]809 * @param buf Buffer, @a nbytes bytes long
810 * @param nbytes Number of bytes to read
[8e3498b]811 * @param nread Place to store number of bytes actually read
[8fd04ba9]812 *
[8e3498b]813 * @return On success, EOK and @a *nread is filled with number
814 * of bytes actually read.
815 * @return On failure, an error code
[8fd04ba9]816 */
[8e3498b]817int vfs_read(int file, aoff64_t *pos, void *buf, size_t nbyte, size_t *nread)
[8fd04ba9]818{
819 ssize_t cnt = 0;
[8e3498b]820 size_t nr = 0;
[8fd04ba9]821 uint8_t *bp = (uint8_t *) buf;
[6afc9d7]822 int rc;
823
[8fd04ba9]824 do {
825 bp += cnt;
[8e3498b]826 nr += cnt;
[58898d1d]827 *pos += cnt;
[8e3498b]828 rc = vfs_read_short(file, *pos, bp, nbyte - nr, &cnt);
829 } while (rc == EOK && cnt > 0 && (nbyte - nr - cnt) > 0);
[6afc9d7]830
[8e3498b]831 if (rc != EOK) {
832 *nread = nr;
[ce04ea44]833 return rc;
[8e3498b]834 }
[6afc9d7]835
[8e3498b]836 nr += cnt;
[58898d1d]837 *pos += cnt;
[8e3498b]838 *nread = nr;
839 return EOK;
[8fd04ba9]840}
841
[3ba431a]842/** Read bytes from a file
[8fd04ba9]843 *
[3ba431a]844 * Read up to @a nbyte bytes from file. The actual number of bytes read
845 * may be lower, but greater than zero if there are any bytes available.
846 * If there are no bytes available for reading, then the function will
847 * return success with zero bytes read.
[8fd04ba9]848 *
[3ba431a]849 * @param file File handle to read from
850 * @param[in] pos Position to read from
851 * @param buf Buffer to read from
852 * @param nbyte Maximum number of bytes to read
853 * @param[out] nread Actual number of bytes read (0 or more)
[8fd04ba9]854 *
[3ba431a]855 * @return EOK on success or a negative error code
[8fd04ba9]856 */
[3ba431a]857int vfs_read_short(int file, aoff64_t pos, void *buf, size_t nbyte,
858 ssize_t *nread)
[8fd04ba9]859{
[3ba431a]860 sysarg_t rc;
861 ipc_call_t answer;
862 aid_t req;
863
864 if (nbyte > DATA_XFER_LIMIT)
865 nbyte = DATA_XFER_LIMIT;
866
867 async_exch_t *exch = vfs_exchange_begin();
868
869 req = async_send_3(exch, VFS_IN_READ, file, LOWER32(pos),
870 UPPER32(pos), &answer);
871 rc = async_data_read_start(exch, (void *) buf, nbyte);
[8fd04ba9]872
[3ba431a]873 vfs_exchange_end(exch);
874
875 if (rc == EOK)
876 async_wait_for(req, &rc);
877 else
878 async_forget(req);
879
[ce04ea44]880 if (rc != EOK)
881 return rc;
[3ba431a]882
883 *nread = (ssize_t) IPC_GET_ARG1(answer);
884 return EOK;
[8fd04ba9]885}
886
[3ba431a]887/** Rename a file or directory
[6afc9d7]888 *
[3ba431a]889 * There is no file-handle-based variant to disallow attempts to introduce loops
890 * and breakage in the directory tree when relinking eg. a node under its own
891 * descendant. The path-based variant is not susceptible because the VFS can
892 * prevent this lexically by comparing the paths.
893 *
894 * @param old Old path
895 * @param new New path
[ca7506f]896 *
897 * @return EOK on success or a negative error code
[6afc9d7]898 */
[3ba431a]899int vfs_rename_path(const char *old, const char *new)
[2595dab]900{
[3ba431a]901 sysarg_t rc;
902 sysarg_t rc_orig;
903 aid_t req;
904
905 size_t olda_size;
906 char *olda = vfs_absolutize(old, &olda_size);
907 if (olda == NULL)
908 return ENOMEM;
909
910 size_t newa_size;
911 char *newa = vfs_absolutize(new, &newa_size);
912 if (newa == NULL) {
913 free(olda);
914 return ENOMEM;
915 }
916
[79ae36dd]917 async_exch_t *exch = vfs_exchange_begin();
[3ba431a]918 int root = vfs_root();
919 if (root < 0) {
920 free(olda);
921 free(newa);
922 return ENOENT;
923 }
[2595dab]924
[3ba431a]925 req = async_send_1(exch, VFS_IN_RENAME, root, NULL);
926 rc = async_data_write_start(exch, olda, olda_size);
927 if (rc != EOK) {
928 vfs_exchange_end(exch);
929 free(olda);
930 free(newa);
931 vfs_put(root);
932 async_wait_for(req, &rc_orig);
933 if (rc_orig != EOK)
934 rc = rc_orig;
935 return rc;
936 }
937 rc = async_data_write_start(exch, newa, newa_size);
938 if (rc != EOK) {
939 vfs_exchange_end(exch);
940 free(olda);
941 free(newa);
942 vfs_put(root);
943 async_wait_for(req, &rc_orig);
944 if (rc_orig != EOK)
945 rc = rc_orig;
946 return rc;
947 }
948 vfs_exchange_end(exch);
949 free(olda);
950 free(newa);
951 vfs_put(root);
952 async_wait_for(req, &rc);
953
[a56cef9]954 return rc;
[2595dab]955}
956
[ca7506f]957/** Resize file to a specified length
[6afc9d7]958 *
[ca7506f]959 * Resize file so that its size is exactly @a length.
[6afc9d7]960 *
[ca7506f]961 * @param file File handle to resize
962 * @param length New length
[6afc9d7]963 *
[ca7506f]964 * @return EOK on success or a negative error code
[6afc9d7]965 */
[67e881c]966int vfs_resize(int file, aoff64_t length)
[0ee4322]967{
[79ae36dd]968 async_exch_t *exch = vfs_exchange_begin();
[ca7506f]969 int rc = async_req_3_0(exch, VFS_IN_RESIZE, file, LOWER32(length),
[67e881c]970 UPPER32(length));
[79ae36dd]971 vfs_exchange_end(exch);
[ed903174]972
[67e881c]973 return rc;
[0ee4322]974}
975
[3ba431a]976/** Return a new file handle representing the local root
977 *
978 * @return A clone of the local root file handle or a negative error code
979 */
980int vfs_root(void)
981{
982 fibril_mutex_lock(&root_mutex);
983 int r;
984 if (root_fd < 0)
985 r = ENOENT;
986 else
987 r = vfs_clone(root_fd, -1, true);
988 fibril_mutex_unlock(&root_mutex);
989 return r;
990}
991
992/** Set a new local root
993 *
994 * Note that it is still possible to have file handles for other roots and pass
995 * them to the API functions. Functions like vfs_root() and vfs_lookup() will
996 * however consider the file set by this function to be the root.
997 *
998 * @param nroot The new local root file handle
999 */
1000void vfs_root_set(int nroot)
1001{
1002 fibril_mutex_lock(&root_mutex);
1003 if (root_fd >= 0)
1004 vfs_put(root_fd);
1005 root_fd = vfs_clone(nroot, -1, true);
1006 fibril_mutex_unlock(&root_mutex);
1007}
1008
[ca7506f]1009/** Get file information
[6afc9d7]1010 *
[ca7506f]1011 * @param file File handle to get information about
1012 * @param[out] stat Place to store file information
[6afc9d7]1013 *
[ca7506f]1014 * @return EOK on success or a negative error code
[6afc9d7]1015 */
[23a0368]1016int vfs_stat(int file, struct stat *stat)
[852b801]1017{
[96b02eb9]1018 sysarg_t rc;
[852b801]1019 aid_t req;
1020
[79ae36dd]1021 async_exch_t *exch = vfs_exchange_begin();
1022
[23a0368]1023 req = async_send_1(exch, VFS_IN_STAT, file, NULL);
[79ae36dd]1024 rc = async_data_read_start(exch, (void *) stat, sizeof(struct stat));
[852b801]1025 if (rc != EOK) {
[79ae36dd]1026 vfs_exchange_end(exch);
[6afc9d7]1027
[96b02eb9]1028 sysarg_t rc_orig;
[3734106]1029 async_wait_for(req, &rc_orig);
[6afc9d7]1030
1031 if (rc_orig != EOK)
1032 rc = rc_orig;
1033
[23a0368]1034 return rc;
[852b801]1035 }
[6afc9d7]1036
[79ae36dd]1037 vfs_exchange_end(exch);
[852b801]1038 async_wait_for(req, &rc);
[6afc9d7]1039
[23a0368]1040 return rc;
[852b801]1041}
1042
[ca7506f]1043/** Get file information
[6afc9d7]1044 *
[ca7506f]1045 * @param path File path to get information about
1046 * @param[out] stat Place to store file information
[6afc9d7]1047 *
[ca7506f]1048 * @return EOK on success or a negative error code
[6afc9d7]1049 */
[23a0368]1050int vfs_stat_path(const char *path, struct stat *stat)
[415c7e0d]1051{
[23a0368]1052 int file = vfs_lookup(path, 0);
1053 if (file < 0)
1054 return file;
[0ed9cb6]1055
[23a0368]1056 int rc = vfs_stat(file, stat);
1057
[9c4cf0d]1058 vfs_put(file);
[0b97336]1059
[415c7e0d]1060 return rc;
1061}
1062
[3ba431a]1063/** Get filesystem statistics
1064 *
1065 * @param file File located on the queried file system
1066 * @param[out] st Buffer for storing information
1067 *
1068 * @return EOK on success or a negative error code
1069 */
1070int vfs_statfs(int file, struct statfs *st)
1071{
1072 sysarg_t rc, ret;
1073 aid_t req;
[1e2e5795]1074
[3ba431a]1075 async_exch_t *exch = vfs_exchange_begin();
[1e2e5795]1076
[3ba431a]1077 req = async_send_1(exch, VFS_IN_STATFS, file, NULL);
1078 rc = async_data_read_start(exch, (void *) st, sizeof(*st));
1079
1080 vfs_exchange_end(exch);
1081 async_wait_for(req, &ret);
1082
1083 rc = (ret != EOK ? ret : rc);
1084
1085 return rc;
[1e2e5795]1086}
1087
[3ba431a]1088/** Get filesystem statistics
[ca7506f]1089 *
[3ba431a]1090 * @param file Path pointing to the queried file system
1091 * @param[out] st Buffer for storing information
[ca7506f]1092 *
1093 * @return EOK on success or a negative error code
1094 */
[3ba431a]1095int vfs_statfs_path(const char *path, struct statfs *st)
[6e5562a]1096{
[3ba431a]1097 int file = vfs_lookup(path, 0);
[6e5562a]1098 if (file < 0)
1099 return file;
[3ba431a]1100
1101 int rc = vfs_statfs(file, st);
[6e5562a]1102
[9c4cf0d]1103 vfs_put(file);
[6e5562a]1104
[3ba431a]1105 return rc;
[6e5562a]1106}
1107
[3ba431a]1108/** Synchronize file
[6afc9d7]1109 *
[3ba431a]1110 * @param file File handle to synchronize
[ca7506f]1111 *
[3ba431a]1112 * @return EOK on success or a negative error code
[6afc9d7]1113 */
[3ba431a]1114int vfs_sync(int file)
[d0dc74ae]1115{
[3ba431a]1116 async_exch_t *exch = vfs_exchange_begin();
1117 int rc = async_req_1_0(exch, VFS_IN_SYNC, file);
1118 vfs_exchange_end(exch);
1119
[6e5562a]1120 return rc;
[3ba431a]1121}
[d0dc74ae]1122
[ca7506f]1123/** Unlink a file or directory
1124 *
1125 * Unlink a name from a parent directory. The caller can supply the file handle
1126 * of the unlinked child in order to detect a possible race with vfs_link() and
1127 * avoid unlinking a wrong file. If the last link for a file or directory is
1128 * removed, the FS implementation will deallocate its resources.
1129 *
1130 * @param parent File handle of the parent directory node
1131 * @param child Old name to be unlinked
1132 * @param expect File handle of the unlinked child
1133 *
1134 * @return EOK on success or a negative error code
1135 */
[79ea5af]1136int vfs_unlink(int parent, const char *child, int expect)
[f15cf1a6]1137{
[96b02eb9]1138 sysarg_t rc;
[f15cf1a6]1139 aid_t req;
1140
[79ae36dd]1141 async_exch_t *exch = vfs_exchange_begin();
1142
[79ea5af]1143 req = async_send_2(exch, VFS_IN_UNLINK, parent, expect, NULL);
1144 rc = async_data_write_start(exch, child, str_size(child));
[d18c404]1145
[79ae36dd]1146 vfs_exchange_end(exch);
[d18c404]1147
1148 sysarg_t rc_orig;
1149 async_wait_for(req, &rc_orig);
1150
[61042de]1151 if (rc_orig != EOK)
[d18c404]1152 return (int) rc_orig;
[2595dab]1153 return rc;
[f15cf1a6]1154}
1155
[ca7506f]1156/** Unlink a file or directory
[6afc9d7]1157 *
[ca7506f]1158 * Unlink a path. If the last link for a file or directory is removed, the FS
1159 * implementation will deallocate its resources.
1160 *
1161 * @param path Old path to be unlinked
1162 *
1163 * @return EOK on success or a negative error code
[6afc9d7]1164 */
[79ea5af]1165int vfs_unlink_path(const char *path)
[f15cf1a6]1166{
[79ea5af]1167 int expect = vfs_lookup(path, 0);
[1e2e5795]1168 if (expect < 0)
[79ea5af]1169 return expect;
1170
[1e2e5795]1171 char *child;
1172 int parent = get_parent_and_child(path, &child);
[79ea5af]1173 if (parent < 0) {
[9c4cf0d]1174 vfs_put(expect);
[79ea5af]1175 return parent;
[0b97336]1176 }
[79ea5af]1177
[1e2e5795]1178 int rc = vfs_unlink(parent, child, expect);
[5126f80]1179
[1e2e5795]1180 free(child);
[9c4cf0d]1181 vfs_put(parent);
1182 vfs_put(expect);
[5126f80]1183 return rc;
[f15cf1a6]1184}
1185
[3ba431a]1186/** Unmount a file system
[6afc9d7]1187 *
[3ba431a]1188 * @param mp File handle representing the mount-point
[6afc9d7]1189 *
[ca7506f]1190 * @return EOK on success or a negative error code
[6afc9d7]1191 */
[3ba431a]1192int vfs_unmount(int mp)
[a8e9ab8d]1193{
[79ae36dd]1194 async_exch_t *exch = vfs_exchange_begin();
[3ba431a]1195 int rc = async_req_1_0(exch, VFS_IN_UNMOUNT, mp);
[79ae36dd]1196 vfs_exchange_end(exch);
[163fc09]1197 return rc;
[a8e9ab8d]1198}
1199
[3ba431a]1200/** Unmount a file system
[6afc9d7]1201 *
[3ba431a]1202 * @param mpp Mount-point path
[ca7506f]1203 *
1204 * @return EOK on success or a negative error code
[6afc9d7]1205 */
[3ba431a]1206int vfs_unmount_path(const char *mpp)
[5fec355]1207{
[3ba431a]1208 int mp = vfs_lookup(mpp, WALK_MOUNT_POINT | WALK_DIRECTORY);
1209 if (mp < 0)
1210 return mp;
[2b88074b]1211
[3ba431a]1212 int rc = vfs_unmount(mp);
1213 vfs_put(mp);
1214 return rc;
[5fec355]1215}
1216
[3ba431a]1217/** Walk a path starting in a parent node
[ca7506f]1218 *
[3ba431a]1219 * @param parent File handle of the parent node where the walk starts
1220 * @param path Parent-relative path to be walked
1221 * @param flags Flags influencing the walk
[6afc9d7]1222 *
[3ba431a]1223 * @retrun File handle representing the result on success or
1224 * a negative error code on error
[6afc9d7]1225 */
[3ba431a]1226int vfs_walk(int parent, const char *path, int flags)
[5fec355]1227{
[3ba431a]1228 async_exch_t *exch = vfs_exchange_begin();
[2b88074b]1229
[3ba431a]1230 ipc_call_t answer;
1231 aid_t req = async_send_2(exch, VFS_IN_WALK, parent, flags, &answer);
1232 sysarg_t rc = async_data_write_start(exch, path, str_size(path));
1233 vfs_exchange_end(exch);
1234
1235 sysarg_t rc_orig;
1236 async_wait_for(req, &rc_orig);
[5fec355]1237
[3ba431a]1238 if (rc_orig != EOK)
1239 return (int) rc_orig;
1240
1241 if (rc != EOK)
1242 return (int) rc;
[852b801]1243
[3ba431a]1244 return (int) IPC_GET_ARG1(answer);
[852b801]1245}
1246
[3ba431a]1247/** Write data
[6afc9d7]1248 *
[3ba431a]1249 * This function fails if it cannot write exactly @a len bytes to the file.
[ca7506f]1250 *
[3ba431a]1251 * @param file File handle to write to
1252 * @param[inout] pos Position to write to, updated by the actual bytes
1253 * written
1254 * @param buf Data, @a nbytes bytes long
1255 * @param nbytes Number of bytes to write
[8e3498b]1256 * @param nwritten Place to store number of bytes written
[3ba431a]1257 *
[8e3498b]1258 * @return On success, EOK, @a *nwr is filled with number
1259 * of bytes written
1260 * @return On failure, an error code
[6afc9d7]1261 */
[8e3498b]1262int vfs_write(int file, aoff64_t *pos, const void *buf, size_t nbyte,
1263 size_t *nwritten)
[66366470]1264{
[3ba431a]1265 ssize_t cnt = 0;
[8e3498b]1266 ssize_t nwr = 0;
[3ba431a]1267 const uint8_t *bp = (uint8_t *) buf;
1268 int rc;
[73fbcbb]1269
[3ba431a]1270 do {
1271 bp += cnt;
[8e3498b]1272 nwr += cnt;
[3ba431a]1273 *pos += cnt;
[8e3498b]1274 rc = vfs_write_short(file, *pos, bp, nbyte - nwr, &cnt);
1275 } while (rc == EOK && ((ssize_t )nbyte - nwr - cnt) > 0);
[6afc9d7]1276
[8e3498b]1277 if (rc != EOK) {
1278 *nwritten = nwr;
[3ba431a]1279 return rc;
[8e3498b]1280 }
[6afc9d7]1281
[8e3498b]1282 nwr += cnt;
[3ba431a]1283 *pos += cnt;
[8e3498b]1284 *nwritten = nwr;
1285 return EOK;
[b5b5d84]1286}
[ca7506f]1287
[3ba431a]1288/** Write bytes to a file
[ca7506f]1289 *
[3ba431a]1290 * Write up to @a nbyte bytes from file. The actual number of bytes written
1291 * may be lower, but greater than zero.
[ca7506f]1292 *
[3ba431a]1293 * @param file File handle to write to
1294 * @param[in] pos Position to write to
1295 * @param buf Buffer to write to
1296 * @param nbyte Maximum number of bytes to write
1297 * @param[out] nread Actual number of bytes written (0 or more)
[ca7506f]1298 *
1299 * @return EOK on success or a negative error code
1300 */
[3ba431a]1301int vfs_write_short(int file, aoff64_t pos, const void *buf, size_t nbyte,
1302 ssize_t *nwritten)
[354b642]1303{
[3ba431a]1304 sysarg_t rc;
1305 ipc_call_t answer;
1306 aid_t req;
1307
1308 if (nbyte > DATA_XFER_LIMIT)
1309 nbyte = DATA_XFER_LIMIT;
1310
1311 async_exch_t *exch = vfs_exchange_begin();
1312
1313 req = async_send_3(exch, VFS_IN_WRITE, file, LOWER32(pos),
1314 UPPER32(pos), &answer);
1315 rc = async_data_write_start(exch, (void *) buf, nbyte);
1316
1317 vfs_exchange_end(exch);
1318
1319 if (rc == EOK)
1320 async_wait_for(req, &rc);
1321 else
1322 async_forget(req);
[354b642]1323
[61042de]1324 if (rc != EOK)
[354b642]1325 return rc;
[3ba431a]1326
1327 *nwritten = (ssize_t) IPC_GET_ARG1(answer);
1328 return EOK;
[354b642]1329}
1330
[2f02aa17]1331/** @}
1332 */
Note: See TracBrowser for help on using the repository browser.