source: mainline/uspace/srv/vfs/vfs_ops.c@ df908b3

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since df908b3 was df908b3, checked in by Jakub Jermar <jakub@…>, 15 years ago

Use async relations introduced in the previous changeset to implement
vfs_grab_phone() and vfs_release_phone().

With this change, VFS will not be pointlessly connecting and disconnecting
phones on each VFS request.

Judging from the output of the top utility, this reduces the share of VFS on
kernel time by about 7% (from 25% down to 18%) and DEVFS kernel time by about 5%
(from 28% to 23%). This also makes CONSOLE the biggest consumer of kernel time,
while it used to be third after DEVFS and VFS.

  • Property mode set to 100644
File size: 34.4 KB
Line 
1/*
2 * Copyright (c) 2008 Jakub Jermar
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 fs
30 * @{
31 */
32
33/**
34 * @file vfs_ops.c
35 * @brief Operations that VFS offers to its clients.
36 */
37
38#include "vfs.h"
39#include <ipc/ipc.h>
40#include <macros.h>
41#include <stdint.h>
42#include <async.h>
43#include <errno.h>
44#include <stdio.h>
45#include <stdlib.h>
46#include <str.h>
47#include <bool.h>
48#include <fibril_synch.h>
49#include <adt/list.h>
50#include <unistd.h>
51#include <ctype.h>
52#include <fcntl.h>
53#include <assert.h>
54#include <vfs/canonify.h>
55
56/* Forward declarations of static functions. */
57static int vfs_truncate_internal(fs_handle_t, dev_handle_t, fs_index_t, aoff64_t);
58
59/**
60 * This rwlock prevents the race between a triplet-to-VFS-node resolution and a
61 * concurrent VFS operation which modifies the file system namespace.
62 */
63FIBRIL_RWLOCK_INITIALIZE(namespace_rwlock);
64
65vfs_pair_t rootfs = {
66 .fs_handle = 0,
67 .dev_handle = 0
68};
69
70static void vfs_mount_internal(ipc_callid_t rid, dev_handle_t dev_handle,
71 fs_handle_t fs_handle, char *mp, char *opts)
72{
73 vfs_lookup_res_t mp_res;
74 vfs_lookup_res_t mr_res;
75 vfs_node_t *mp_node = NULL;
76 vfs_node_t *mr_node;
77 fs_index_t rindex;
78 size_t rsize;
79 unsigned rlnkcnt;
80 ipcarg_t rc;
81 int phone;
82 aid_t msg;
83 ipc_call_t answer;
84
85 /* Resolve the path to the mountpoint. */
86 fibril_rwlock_write_lock(&namespace_rwlock);
87 if (rootfs.fs_handle) {
88 /* We already have the root FS. */
89 if (str_cmp(mp, "/") == 0) {
90 /* Trying to mount root FS over root FS */
91 fibril_rwlock_write_unlock(&namespace_rwlock);
92 ipc_answer_0(rid, EBUSY);
93 return;
94 }
95
96 rc = vfs_lookup_internal(mp, L_MP, &mp_res, NULL);
97 if (rc != EOK) {
98 /* The lookup failed for some reason. */
99 fibril_rwlock_write_unlock(&namespace_rwlock);
100 ipc_answer_0(rid, rc);
101 return;
102 }
103
104 mp_node = vfs_node_get(&mp_res);
105 if (!mp_node) {
106 fibril_rwlock_write_unlock(&namespace_rwlock);
107 ipc_answer_0(rid, ENOMEM);
108 return;
109 }
110
111 /*
112 * Now we hold a reference to mp_node.
113 * It will be dropped upon the corresponding VFS_IN_UNMOUNT.
114 * This prevents the mount point from being deleted.
115 */
116 } else {
117 /* We still don't have the root file system mounted. */
118 if (str_cmp(mp, "/") == 0) {
119 /*
120 * For this simple, but important case,
121 * we are almost done.
122 */
123
124 /* Tell the mountee that it is being mounted. */
125 phone = vfs_grab_phone(fs_handle);
126 msg = async_send_1(phone, VFS_OUT_MOUNTED,
127 (ipcarg_t) dev_handle, &answer);
128 /* send the mount options */
129 rc = async_data_write_start(phone, (void *)opts,
130 str_size(opts));
131 if (rc != EOK) {
132 async_wait_for(msg, NULL);
133 vfs_release_phone(fs_handle, phone);
134 fibril_rwlock_write_unlock(&namespace_rwlock);
135 ipc_answer_0(rid, rc);
136 return;
137 }
138 async_wait_for(msg, &rc);
139 vfs_release_phone(fs_handle, phone);
140
141 if (rc != EOK) {
142 fibril_rwlock_write_unlock(&namespace_rwlock);
143 ipc_answer_0(rid, rc);
144 return;
145 }
146
147 rindex = (fs_index_t) IPC_GET_ARG1(answer);
148 rsize = (size_t) IPC_GET_ARG2(answer);
149 rlnkcnt = (unsigned) IPC_GET_ARG3(answer);
150
151 mr_res.triplet.fs_handle = fs_handle;
152 mr_res.triplet.dev_handle = dev_handle;
153 mr_res.triplet.index = rindex;
154 mr_res.size = rsize;
155 mr_res.lnkcnt = rlnkcnt;
156 mr_res.type = VFS_NODE_DIRECTORY;
157
158 rootfs.fs_handle = fs_handle;
159 rootfs.dev_handle = dev_handle;
160
161 /* Add reference to the mounted root. */
162 mr_node = vfs_node_get(&mr_res);
163 assert(mr_node);
164
165 fibril_rwlock_write_unlock(&namespace_rwlock);
166 ipc_answer_0(rid, rc);
167 return;
168 } else {
169 /*
170 * We can't resolve this without the root filesystem
171 * being mounted first.
172 */
173 fibril_rwlock_write_unlock(&namespace_rwlock);
174 ipc_answer_0(rid, ENOENT);
175 return;
176 }
177 }
178
179 /*
180 * At this point, we have all necessary pieces: file system and device
181 * handles, and we know the mount point VFS node.
182 */
183
184 int mountee_phone = vfs_grab_phone(fs_handle);
185 assert(mountee_phone >= 0);
186
187 phone = vfs_grab_phone(mp_res.triplet.fs_handle);
188 msg = async_send_4(phone, VFS_OUT_MOUNT,
189 (ipcarg_t) mp_res.triplet.dev_handle,
190 (ipcarg_t) mp_res.triplet.index,
191 (ipcarg_t) fs_handle,
192 (ipcarg_t) dev_handle, &answer);
193
194 /* send connection */
195 rc = async_req_1_0(phone, IPC_M_CONNECTION_CLONE, mountee_phone);
196 if (rc != EOK) {
197 async_wait_for(msg, NULL);
198 vfs_release_phone(fs_handle, mountee_phone);
199 vfs_release_phone(mp_res.triplet.fs_handle, phone);
200 /* Mount failed, drop reference to mp_node. */
201 if (mp_node)
202 vfs_node_put(mp_node);
203 ipc_answer_0(rid, rc);
204 fibril_rwlock_write_unlock(&namespace_rwlock);
205 return;
206 }
207
208 vfs_release_phone(fs_handle, mountee_phone);
209
210 /* send the mount options */
211 rc = async_data_write_start(phone, (void *)opts, str_size(opts));
212 if (rc != EOK) {
213 async_wait_for(msg, NULL);
214 vfs_release_phone(mp_res.triplet.fs_handle, phone);
215 /* Mount failed, drop reference to mp_node. */
216 if (mp_node)
217 vfs_node_put(mp_node);
218 fibril_rwlock_write_unlock(&namespace_rwlock);
219 ipc_answer_0(rid, rc);
220 return;
221 }
222 async_wait_for(msg, &rc);
223 vfs_release_phone(mp_res.triplet.fs_handle, phone);
224
225 if (rc == EOK) {
226 rindex = (fs_index_t) IPC_GET_ARG1(answer);
227 rsize = (size_t) IPC_GET_ARG2(answer);
228 rlnkcnt = (unsigned) IPC_GET_ARG3(answer);
229
230 mr_res.triplet.fs_handle = fs_handle;
231 mr_res.triplet.dev_handle = dev_handle;
232 mr_res.triplet.index = rindex;
233 mr_res.size = rsize;
234 mr_res.lnkcnt = rlnkcnt;
235 mr_res.type = VFS_NODE_DIRECTORY;
236
237 /* Add reference to the mounted root. */
238 mr_node = vfs_node_get(&mr_res);
239 assert(mr_node);
240 } else {
241 /* Mount failed, drop reference to mp_node. */
242 if (mp_node)
243 vfs_node_put(mp_node);
244 }
245
246 ipc_answer_0(rid, rc);
247 fibril_rwlock_write_unlock(&namespace_rwlock);
248}
249
250void vfs_mount(ipc_callid_t rid, ipc_call_t *request)
251{
252 /*
253 * We expect the library to do the device-name to device-handle
254 * translation for us, thus the device handle will arrive as ARG1
255 * in the request.
256 */
257 dev_handle_t dev_handle = (dev_handle_t) IPC_GET_ARG1(*request);
258
259 /*
260 * Mount flags are passed as ARG2.
261 */
262 unsigned int flags = (unsigned int) IPC_GET_ARG2(*request);
263
264 /*
265 * For now, don't make use of ARG3, but it can be used to
266 * carry mount options in the future.
267 */
268
269 /* We want the client to send us the mount point. */
270 char *mp;
271 int rc = async_data_write_accept((void **) &mp, true, 0, MAX_PATH_LEN,
272 0, NULL);
273 if (rc != EOK) {
274 ipc_answer_0(rid, rc);
275 return;
276 }
277
278 /* Now we expect to receive the mount options. */
279 char *opts;
280 rc = async_data_write_accept((void **) &opts, true, 0, MAX_MNTOPTS_LEN,
281 0, NULL);
282 if (rc != EOK) {
283 free(mp);
284 ipc_answer_0(rid, rc);
285 return;
286 }
287
288 /*
289 * Now, we expect the client to send us data with the name of the file
290 * system.
291 */
292 char *fs_name;
293 rc = async_data_write_accept((void **) &fs_name, true, 0, FS_NAME_MAXLEN,
294 0, NULL);
295 if (rc != EOK) {
296 free(mp);
297 free(opts);
298 ipc_answer_0(rid, rc);
299 return;
300 }
301
302 /*
303 * Wait for IPC_M_PING so that we can return an error if we don't know
304 * fs_name.
305 */
306 ipc_call_t data;
307 ipc_callid_t callid = async_get_call(&data);
308 if (IPC_GET_METHOD(data) != IPC_M_PING) {
309 ipc_answer_0(callid, ENOTSUP);
310 ipc_answer_0(rid, ENOTSUP);
311 free(mp);
312 free(opts);
313 free(fs_name);
314 return;
315 }
316
317 /*
318 * Check if we know a file system with the same name as is in fs_name.
319 * This will also give us its file system handle.
320 */
321 fibril_mutex_lock(&fs_head_lock);
322 fs_handle_t fs_handle;
323recheck:
324 fs_handle = fs_name_to_handle(fs_name, false);
325 if (!fs_handle) {
326 if (flags & IPC_FLAG_BLOCKING) {
327 fibril_condvar_wait(&fs_head_cv, &fs_head_lock);
328 goto recheck;
329 }
330
331 fibril_mutex_unlock(&fs_head_lock);
332 ipc_answer_0(callid, ENOENT);
333 ipc_answer_0(rid, ENOENT);
334 free(mp);
335 free(fs_name);
336 free(opts);
337 return;
338 }
339 fibril_mutex_unlock(&fs_head_lock);
340
341 /* Acknowledge that we know fs_name. */
342 ipc_answer_0(callid, EOK);
343
344 /* Do the mount */
345 vfs_mount_internal(rid, dev_handle, fs_handle, mp, opts);
346 free(mp);
347 free(fs_name);
348 free(opts);
349}
350
351void vfs_unmount(ipc_callid_t rid, ipc_call_t *request)
352{
353 int rc;
354 char *mp;
355 vfs_lookup_res_t mp_res;
356 vfs_lookup_res_t mr_res;
357 vfs_node_t *mr_node;
358 int phone;
359
360 /*
361 * Receive the mount point path.
362 */
363 rc = async_data_write_accept((void **) &mp, true, 0, MAX_PATH_LEN,
364 0, NULL);
365 if (rc != EOK)
366 ipc_answer_0(rid, rc);
367
368 /*
369 * Taking the namespace lock will do two things for us. First, it will
370 * prevent races with other lookup operations. Second, it will stop new
371 * references to already existing VFS nodes and creation of new VFS
372 * nodes. This is because new references are added as a result of some
373 * lookup operation or at least of some operation which is protected by
374 * the namespace lock.
375 */
376 fibril_rwlock_write_lock(&namespace_rwlock);
377
378 /*
379 * Lookup the mounted root and instantiate it.
380 */
381 rc = vfs_lookup_internal(mp, L_ROOT, &mr_res, NULL);
382 if (rc != EOK) {
383 fibril_rwlock_write_unlock(&namespace_rwlock);
384 free(mp);
385 ipc_answer_0(rid, rc);
386 return;
387 }
388 mr_node = vfs_node_get(&mr_res);
389 if (!mr_node) {
390 fibril_rwlock_write_unlock(&namespace_rwlock);
391 free(mp);
392 ipc_answer_0(rid, ENOMEM);
393 return;
394 }
395
396 /*
397 * Count the total number of references for the mounted file system. We
398 * are expecting at least two. One which we got above and one which we
399 * got when the file system was mounted. If we find more, it means that
400 * the file system cannot be gracefully unmounted at the moment because
401 * someone is working with it.
402 */
403 if (vfs_nodes_refcount_sum_get(mr_node->fs_handle,
404 mr_node->dev_handle) != 2) {
405 fibril_rwlock_write_unlock(&namespace_rwlock);
406 vfs_node_put(mr_node);
407 free(mp);
408 ipc_answer_0(rid, EBUSY);
409 return;
410 }
411
412 if (str_cmp(mp, "/") == 0) {
413
414 /*
415 * Unmounting the root file system.
416 *
417 * In this case, there is no mount point node and we send
418 * VFS_OUT_UNMOUNTED directly to the mounted file system.
419 */
420
421 free(mp);
422 phone = vfs_grab_phone(mr_node->fs_handle);
423 rc = async_req_1_0(phone, VFS_OUT_UNMOUNTED,
424 mr_node->dev_handle);
425 vfs_release_phone(mr_node->fs_handle, phone);
426 if (rc != EOK) {
427 fibril_rwlock_write_unlock(&namespace_rwlock);
428 vfs_node_put(mr_node);
429 ipc_answer_0(rid, rc);
430 return;
431 }
432 rootfs.fs_handle = 0;
433 rootfs.dev_handle = 0;
434 } else {
435
436 /*
437 * Unmounting a non-root file system.
438 *
439 * We have a regular mount point node representing the parent
440 * file system, so we delegate the operation to it.
441 */
442
443 rc = vfs_lookup_internal(mp, L_MP, &mp_res, NULL);
444 free(mp);
445 if (rc != EOK) {
446 fibril_rwlock_write_unlock(&namespace_rwlock);
447 vfs_node_put(mr_node);
448 ipc_answer_0(rid, rc);
449 return;
450 }
451 vfs_node_t *mp_node = vfs_node_get(&mp_res);
452 if (!mp_node) {
453 fibril_rwlock_write_unlock(&namespace_rwlock);
454 vfs_node_put(mr_node);
455 ipc_answer_0(rid, ENOMEM);
456 return;
457 }
458
459 phone = vfs_grab_phone(mp_node->fs_handle);
460 rc = async_req_2_0(phone, VFS_OUT_UNMOUNT, mp_node->dev_handle,
461 mp_node->index);
462 vfs_release_phone(mp_node->fs_handle, phone);
463 if (rc != EOK) {
464 fibril_rwlock_write_unlock(&namespace_rwlock);
465 vfs_node_put(mp_node);
466 vfs_node_put(mr_node);
467 ipc_answer_0(rid, rc);
468 return;
469 }
470
471 /* Drop the reference we got above. */
472 vfs_node_put(mp_node);
473 /* Drop the reference from when the file system was mounted. */
474 vfs_node_put(mp_node);
475 }
476
477
478 /*
479 * All went well, the mounted file system was successfully unmounted.
480 * The only thing left is to forget the unmounted root VFS node.
481 */
482 vfs_node_forget(mr_node);
483
484 fibril_rwlock_write_unlock(&namespace_rwlock);
485 ipc_answer_0(rid, EOK);
486}
487
488void vfs_open(ipc_callid_t rid, ipc_call_t *request)
489{
490 if (!vfs_files_init()) {
491 ipc_answer_0(rid, ENOMEM);
492 return;
493 }
494
495 /*
496 * The POSIX interface is open(path, oflag, mode).
497 * We can receive oflags and mode along with the VFS_IN_OPEN call;
498 * the path will need to arrive in another call.
499 *
500 * We also receive one private, non-POSIX set of flags called lflag
501 * used to pass information to vfs_lookup_internal().
502 */
503 int lflag = IPC_GET_ARG1(*request);
504 int oflag = IPC_GET_ARG2(*request);
505 int mode = IPC_GET_ARG3(*request);
506
507 /* Ignore mode for now. */
508 (void) mode;
509
510 /*
511 * Make sure that we are called with exactly one of L_FILE and
512 * L_DIRECTORY. Make sure that the user does not pass L_OPEN,
513 * L_ROOT or L_MP.
514 */
515 if (((lflag & (L_FILE | L_DIRECTORY)) == 0) ||
516 ((lflag & (L_FILE | L_DIRECTORY)) == (L_FILE | L_DIRECTORY)) ||
517 (lflag & (L_OPEN | L_ROOT | L_MP))) {
518 ipc_answer_0(rid, EINVAL);
519 return;
520 }
521
522 if (oflag & O_CREAT)
523 lflag |= L_CREATE;
524 if (oflag & O_EXCL)
525 lflag |= L_EXCLUSIVE;
526
527 char *path;
528 int rc = async_data_write_accept((void **) &path, true, 0, 0, 0, NULL);
529 if (rc != EOK) {
530 ipc_answer_0(rid, rc);
531 return;
532 }
533
534 /*
535 * Avoid the race condition in which the file can be deleted before we
536 * find/create-and-lock the VFS node corresponding to the looked-up
537 * triplet.
538 */
539 if (lflag & L_CREATE)
540 fibril_rwlock_write_lock(&namespace_rwlock);
541 else
542 fibril_rwlock_read_lock(&namespace_rwlock);
543
544 /* The path is now populated and we can call vfs_lookup_internal(). */
545 vfs_lookup_res_t lr;
546 rc = vfs_lookup_internal(path, lflag | L_OPEN, &lr, NULL);
547 if (rc != EOK) {
548 if (lflag & L_CREATE)
549 fibril_rwlock_write_unlock(&namespace_rwlock);
550 else
551 fibril_rwlock_read_unlock(&namespace_rwlock);
552 ipc_answer_0(rid, rc);
553 free(path);
554 return;
555 }
556
557 /* Path is no longer needed. */
558 free(path);
559
560 vfs_node_t *node = vfs_node_get(&lr);
561 if (lflag & L_CREATE)
562 fibril_rwlock_write_unlock(&namespace_rwlock);
563 else
564 fibril_rwlock_read_unlock(&namespace_rwlock);
565
566 /* Truncate the file if requested and if necessary. */
567 if (oflag & O_TRUNC) {
568 fibril_rwlock_write_lock(&node->contents_rwlock);
569 if (node->size) {
570 rc = vfs_truncate_internal(node->fs_handle,
571 node->dev_handle, node->index, 0);
572 if (rc) {
573 fibril_rwlock_write_unlock(&node->contents_rwlock);
574 vfs_node_put(node);
575 ipc_answer_0(rid, rc);
576 return;
577 }
578 node->size = 0;
579 }
580 fibril_rwlock_write_unlock(&node->contents_rwlock);
581 }
582
583 /*
584 * Get ourselves a file descriptor and the corresponding vfs_file_t
585 * structure.
586 */
587 int fd = vfs_fd_alloc((oflag & O_DESC) != 0);
588 if (fd < 0) {
589 vfs_node_put(node);
590 ipc_answer_0(rid, fd);
591 return;
592 }
593 vfs_file_t *file = vfs_file_get(fd);
594 assert(file);
595 file->node = node;
596 if (oflag & O_APPEND)
597 file->append = true;
598
599 /*
600 * The following increase in reference count is for the fact that the
601 * file is being opened and that a file structure is pointing to it.
602 * It is necessary so that the file will not disappear when
603 * vfs_node_put() is called. The reference will be dropped by the
604 * respective VFS_IN_CLOSE.
605 */
606 vfs_node_addref(node);
607 vfs_node_put(node);
608
609 /* Success! Return the new file descriptor to the client. */
610 ipc_answer_1(rid, EOK, fd);
611}
612
613void vfs_open_node(ipc_callid_t rid, ipc_call_t *request)
614{
615 // FIXME: check for sanity of the supplied fs, dev and index
616
617 if (!vfs_files_init()) {
618 ipc_answer_0(rid, ENOMEM);
619 return;
620 }
621
622 /*
623 * The interface is open_node(fs, dev, index, oflag).
624 */
625 vfs_lookup_res_t lr;
626
627 lr.triplet.fs_handle = IPC_GET_ARG1(*request);
628 lr.triplet.dev_handle = IPC_GET_ARG2(*request);
629 lr.triplet.index = IPC_GET_ARG3(*request);
630 int oflag = IPC_GET_ARG4(*request);
631
632 fibril_rwlock_read_lock(&namespace_rwlock);
633
634 int rc = vfs_open_node_internal(&lr);
635 if (rc != EOK) {
636 fibril_rwlock_read_unlock(&namespace_rwlock);
637 ipc_answer_0(rid, rc);
638 return;
639 }
640
641 vfs_node_t *node = vfs_node_get(&lr);
642 fibril_rwlock_read_unlock(&namespace_rwlock);
643
644 /* Truncate the file if requested and if necessary. */
645 if (oflag & O_TRUNC) {
646 fibril_rwlock_write_lock(&node->contents_rwlock);
647 if (node->size) {
648 rc = vfs_truncate_internal(node->fs_handle,
649 node->dev_handle, node->index, 0);
650 if (rc) {
651 fibril_rwlock_write_unlock(&node->contents_rwlock);
652 vfs_node_put(node);
653 ipc_answer_0(rid, rc);
654 return;
655 }
656 node->size = 0;
657 }
658 fibril_rwlock_write_unlock(&node->contents_rwlock);
659 }
660
661 /*
662 * Get ourselves a file descriptor and the corresponding vfs_file_t
663 * structure.
664 */
665 int fd = vfs_fd_alloc((oflag & O_DESC) != 0);
666 if (fd < 0) {
667 vfs_node_put(node);
668 ipc_answer_0(rid, fd);
669 return;
670 }
671 vfs_file_t *file = vfs_file_get(fd);
672 file->node = node;
673 if (oflag & O_APPEND)
674 file->append = true;
675
676 /*
677 * The following increase in reference count is for the fact that the
678 * file is being opened and that a file structure is pointing to it.
679 * It is necessary so that the file will not disappear when
680 * vfs_node_put() is called. The reference will be dropped by the
681 * respective VFS_IN_CLOSE.
682 */
683 vfs_node_addref(node);
684 vfs_node_put(node);
685
686 /* Success! Return the new file descriptor to the client. */
687 ipc_answer_1(rid, EOK, fd);
688}
689
690void vfs_sync(ipc_callid_t rid, ipc_call_t *request)
691{
692 int fd = IPC_GET_ARG1(*request);
693
694 /* Lookup the file structure corresponding to the file descriptor. */
695 vfs_file_t *file = vfs_file_get(fd);
696 if (!file) {
697 ipc_answer_0(rid, ENOENT);
698 return;
699 }
700
701 /*
702 * Lock the open file structure so that no other thread can manipulate
703 * the same open file at a time.
704 */
705 fibril_mutex_lock(&file->lock);
706 int fs_phone = vfs_grab_phone(file->node->fs_handle);
707
708 /* Make a VFS_OUT_SYMC request at the destination FS server. */
709 aid_t msg;
710 ipc_call_t answer;
711 msg = async_send_2(fs_phone, VFS_OUT_SYNC, file->node->dev_handle,
712 file->node->index, &answer);
713
714 /* Wait for reply from the FS server. */
715 ipcarg_t rc;
716 async_wait_for(msg, &rc);
717
718 vfs_release_phone(file->node->fs_handle, fs_phone);
719 fibril_mutex_unlock(&file->lock);
720
721 ipc_answer_0(rid, rc);
722}
723
724int vfs_close_internal(vfs_file_t *file)
725{
726 /*
727 * Lock the open file structure so that no other thread can manipulate
728 * the same open file at a time.
729 */
730 fibril_mutex_lock(&file->lock);
731
732 if (file->refcnt <= 1) {
733 /* Only close the file on the destination FS server
734 if there are no more file descriptors (except the
735 present one) pointing to this file. */
736
737 int fs_phone = vfs_grab_phone(file->node->fs_handle);
738
739 /* Make a VFS_OUT_CLOSE request at the destination FS server. */
740 aid_t msg;
741 ipc_call_t answer;
742 msg = async_send_2(fs_phone, VFS_OUT_CLOSE, file->node->dev_handle,
743 file->node->index, &answer);
744
745 /* Wait for reply from the FS server. */
746 ipcarg_t rc;
747 async_wait_for(msg, &rc);
748
749 vfs_release_phone(file->node->fs_handle, fs_phone);
750 fibril_mutex_unlock(&file->lock);
751
752 return IPC_GET_ARG1(answer);
753 }
754
755 fibril_mutex_unlock(&file->lock);
756 return EOK;
757}
758
759void vfs_close(ipc_callid_t rid, ipc_call_t *request)
760{
761 int fd = IPC_GET_ARG1(*request);
762
763 /* Lookup the file structure corresponding to the file descriptor. */
764 vfs_file_t *file = vfs_file_get(fd);
765 if (!file) {
766 ipc_answer_0(rid, ENOENT);
767 return;
768 }
769
770 int ret = vfs_close_internal(file);
771 if (ret != EOK)
772 ipc_answer_0(rid, ret);
773
774 ret = vfs_fd_free(fd);
775 ipc_answer_0(rid, ret);
776}
777
778static void vfs_rdwr(ipc_callid_t rid, ipc_call_t *request, bool read)
779{
780
781 /*
782 * The following code strongly depends on the fact that the files data
783 * structure can be only accessed by a single fibril and all file
784 * operations are serialized (i.e. the reads and writes cannot
785 * interleave and a file cannot be closed while it is being read).
786 *
787 * Additional synchronization needs to be added once the table of
788 * open files supports parallel access!
789 */
790
791 int fd = IPC_GET_ARG1(*request);
792
793 /* Lookup the file structure corresponding to the file descriptor. */
794 vfs_file_t *file = vfs_file_get(fd);
795 if (!file) {
796 ipc_answer_0(rid, ENOENT);
797 return;
798 }
799
800 /*
801 * Lock the open file structure so that no other thread can manipulate
802 * the same open file at a time.
803 */
804 fibril_mutex_lock(&file->lock);
805
806 /*
807 * Lock the file's node so that no other client can read/write to it at
808 * the same time.
809 */
810 if (read)
811 fibril_rwlock_read_lock(&file->node->contents_rwlock);
812 else
813 fibril_rwlock_write_lock(&file->node->contents_rwlock);
814
815 if (file->node->type == VFS_NODE_DIRECTORY) {
816 /*
817 * Make sure that no one is modifying the namespace
818 * while we are in readdir().
819 */
820 assert(read);
821 fibril_rwlock_read_lock(&namespace_rwlock);
822 }
823
824 int fs_phone = vfs_grab_phone(file->node->fs_handle);
825
826 /*
827 * Make a VFS_READ/VFS_WRITE request at the destination FS server
828 * and forward the IPC_M_DATA_READ/IPC_M_DATA_WRITE request to the
829 * destination FS server. The call will be routed as if sent by
830 * ourselves. Note that call arguments are immutable in this case so we
831 * don't have to bother.
832 */
833 ipcarg_t rc;
834 ipc_call_t answer;
835 if (read) {
836 if (file->append)
837 file->pos = file->node->size;
838
839 rc = async_data_read_forward_3_1(fs_phone, VFS_OUT_READ,
840 file->node->dev_handle, file->node->index, file->pos,
841 &answer);
842 } else {
843 rc = async_data_write_forward_3_1(fs_phone, VFS_OUT_WRITE,
844 file->node->dev_handle, file->node->index, file->pos,
845 &answer);
846 }
847
848 vfs_release_phone(file->node->fs_handle, fs_phone);
849
850 size_t bytes = IPC_GET_ARG1(answer);
851
852 if (file->node->type == VFS_NODE_DIRECTORY)
853 fibril_rwlock_read_unlock(&namespace_rwlock);
854
855 /* Unlock the VFS node. */
856 if (read)
857 fibril_rwlock_read_unlock(&file->node->contents_rwlock);
858 else {
859 /* Update the cached version of node's size. */
860 if (rc == EOK)
861 file->node->size = IPC_GET_ARG2(answer);
862 fibril_rwlock_write_unlock(&file->node->contents_rwlock);
863 }
864
865 /* Update the position pointer and unlock the open file. */
866 if (rc == EOK)
867 file->pos += bytes;
868 fibril_mutex_unlock(&file->lock);
869
870 /*
871 * FS server's reply is the final result of the whole operation we
872 * return to the client.
873 */
874 ipc_answer_1(rid, rc, bytes);
875}
876
877void vfs_read(ipc_callid_t rid, ipc_call_t *request)
878{
879 vfs_rdwr(rid, request, true);
880}
881
882void vfs_write(ipc_callid_t rid, ipc_call_t *request)
883{
884 vfs_rdwr(rid, request, false);
885}
886
887void vfs_seek(ipc_callid_t rid, ipc_call_t *request)
888{
889 int fd = (int) IPC_GET_ARG1(*request);
890 off64_t off =
891 (off64_t) MERGE_LOUP32(IPC_GET_ARG2(*request), IPC_GET_ARG3(*request));
892 int whence = (int) IPC_GET_ARG4(*request);
893
894 /* Lookup the file structure corresponding to the file descriptor. */
895 vfs_file_t *file = vfs_file_get(fd);
896 if (!file) {
897 ipc_answer_0(rid, ENOENT);
898 return;
899 }
900
901 fibril_mutex_lock(&file->lock);
902
903 off64_t newoff;
904 switch (whence) {
905 case SEEK_SET:
906 if (off >= 0) {
907 file->pos = (aoff64_t) off;
908 fibril_mutex_unlock(&file->lock);
909 ipc_answer_1(rid, EOK, off);
910 return;
911 }
912 break;
913 case SEEK_CUR:
914 if ((off >= 0) && (file->pos + off < file->pos)) {
915 fibril_mutex_unlock(&file->lock);
916 ipc_answer_0(rid, EOVERFLOW);
917 return;
918 }
919
920 if ((off < 0) && (file->pos < (aoff64_t) -off)) {
921 fibril_mutex_unlock(&file->lock);
922 ipc_answer_0(rid, EOVERFLOW);
923 return;
924 }
925
926 file->pos += off;
927 newoff = (file->pos > OFF64_MAX) ? OFF64_MAX : file->pos;
928
929 fibril_mutex_unlock(&file->lock);
930 ipc_answer_2(rid, EOK, LOWER32(newoff), UPPER32(newoff));
931 return;
932 case SEEK_END:
933 fibril_rwlock_read_lock(&file->node->contents_rwlock);
934 aoff64_t size = file->node->size;
935
936 if ((off >= 0) && (size + off < size)) {
937 fibril_rwlock_read_unlock(&file->node->contents_rwlock);
938 fibril_mutex_unlock(&file->lock);
939 ipc_answer_0(rid, EOVERFLOW);
940 return;
941 }
942
943 if ((off < 0) && (size < (aoff64_t) -off)) {
944 fibril_rwlock_read_unlock(&file->node->contents_rwlock);
945 fibril_mutex_unlock(&file->lock);
946 ipc_answer_0(rid, EOVERFLOW);
947 return;
948 }
949
950 file->pos = size + off;
951 newoff = (file->pos > OFF64_MAX) ? OFF64_MAX : file->pos;
952
953 fibril_rwlock_read_unlock(&file->node->contents_rwlock);
954 fibril_mutex_unlock(&file->lock);
955 ipc_answer_2(rid, EOK, LOWER32(newoff), UPPER32(newoff));
956 return;
957 }
958
959 fibril_mutex_unlock(&file->lock);
960 ipc_answer_0(rid, EINVAL);
961}
962
963int vfs_truncate_internal(fs_handle_t fs_handle, dev_handle_t dev_handle,
964 fs_index_t index, aoff64_t size)
965{
966 ipcarg_t rc;
967 int fs_phone;
968
969 fs_phone = vfs_grab_phone(fs_handle);
970 rc = async_req_4_0(fs_phone, VFS_OUT_TRUNCATE, (ipcarg_t) dev_handle,
971 (ipcarg_t) index, LOWER32(size), UPPER32(size));
972 vfs_release_phone(fs_handle, fs_phone);
973 return (int)rc;
974}
975
976void vfs_truncate(ipc_callid_t rid, ipc_call_t *request)
977{
978 int fd = IPC_GET_ARG1(*request);
979 aoff64_t size =
980 (aoff64_t) MERGE_LOUP32(IPC_GET_ARG2(*request), IPC_GET_ARG3(*request));
981 int rc;
982
983 vfs_file_t *file = vfs_file_get(fd);
984 if (!file) {
985 ipc_answer_0(rid, ENOENT);
986 return;
987 }
988 fibril_mutex_lock(&file->lock);
989
990 fibril_rwlock_write_lock(&file->node->contents_rwlock);
991 rc = vfs_truncate_internal(file->node->fs_handle,
992 file->node->dev_handle, file->node->index, size);
993 if (rc == EOK)
994 file->node->size = size;
995 fibril_rwlock_write_unlock(&file->node->contents_rwlock);
996
997 fibril_mutex_unlock(&file->lock);
998 ipc_answer_0(rid, (ipcarg_t)rc);
999}
1000
1001void vfs_fstat(ipc_callid_t rid, ipc_call_t *request)
1002{
1003 int fd = IPC_GET_ARG1(*request);
1004 ipcarg_t rc;
1005
1006 vfs_file_t *file = vfs_file_get(fd);
1007 if (!file) {
1008 ipc_answer_0(rid, ENOENT);
1009 return;
1010 }
1011
1012 ipc_callid_t callid;
1013 if (!async_data_read_receive(&callid, NULL)) {
1014 ipc_answer_0(callid, EINVAL);
1015 ipc_answer_0(rid, EINVAL);
1016 return;
1017 }
1018
1019 fibril_mutex_lock(&file->lock);
1020
1021 int fs_phone = vfs_grab_phone(file->node->fs_handle);
1022
1023 aid_t msg;
1024 msg = async_send_3(fs_phone, VFS_OUT_STAT, file->node->dev_handle,
1025 file->node->index, true, NULL);
1026 ipc_forward_fast(callid, fs_phone, 0, 0, 0, IPC_FF_ROUTE_FROM_ME);
1027 async_wait_for(msg, &rc);
1028 vfs_release_phone(file->node->fs_handle, fs_phone);
1029
1030 fibril_mutex_unlock(&file->lock);
1031 ipc_answer_0(rid, rc);
1032}
1033
1034void vfs_stat(ipc_callid_t rid, ipc_call_t *request)
1035{
1036 char *path;
1037 int rc = async_data_write_accept((void **) &path, true, 0, 0, 0, NULL);
1038 if (rc != EOK) {
1039 ipc_answer_0(rid, rc);
1040 return;
1041 }
1042
1043 ipc_callid_t callid;
1044 if (!async_data_read_receive(&callid, NULL)) {
1045 free(path);
1046 ipc_answer_0(callid, EINVAL);
1047 ipc_answer_0(rid, EINVAL);
1048 return;
1049 }
1050
1051 vfs_lookup_res_t lr;
1052 fibril_rwlock_read_lock(&namespace_rwlock);
1053 rc = vfs_lookup_internal(path, L_NONE, &lr, NULL);
1054 free(path);
1055 if (rc != EOK) {
1056 fibril_rwlock_read_unlock(&namespace_rwlock);
1057 ipc_answer_0(callid, rc);
1058 ipc_answer_0(rid, rc);
1059 return;
1060 }
1061 vfs_node_t *node = vfs_node_get(&lr);
1062 if (!node) {
1063 fibril_rwlock_read_unlock(&namespace_rwlock);
1064 ipc_answer_0(callid, ENOMEM);
1065 ipc_answer_0(rid, ENOMEM);
1066 return;
1067 }
1068
1069 fibril_rwlock_read_unlock(&namespace_rwlock);
1070
1071 int fs_phone = vfs_grab_phone(node->fs_handle);
1072 aid_t msg;
1073 msg = async_send_3(fs_phone, VFS_OUT_STAT, node->dev_handle,
1074 node->index, false, NULL);
1075 ipc_forward_fast(callid, fs_phone, 0, 0, 0, IPC_FF_ROUTE_FROM_ME);
1076
1077 ipcarg_t rv;
1078 async_wait_for(msg, &rv);
1079 vfs_release_phone(node->fs_handle, fs_phone);
1080
1081 ipc_answer_0(rid, rv);
1082
1083 vfs_node_put(node);
1084}
1085
1086void vfs_mkdir(ipc_callid_t rid, ipc_call_t *request)
1087{
1088 int mode = IPC_GET_ARG1(*request);
1089
1090 char *path;
1091 int rc = async_data_write_accept((void **) &path, true, 0, 0, 0, NULL);
1092 if (rc != EOK) {
1093 ipc_answer_0(rid, rc);
1094 return;
1095 }
1096
1097 /* Ignore mode for now. */
1098 (void) mode;
1099
1100 fibril_rwlock_write_lock(&namespace_rwlock);
1101 int lflag = L_DIRECTORY | L_CREATE | L_EXCLUSIVE;
1102 rc = vfs_lookup_internal(path, lflag, NULL, NULL);
1103 fibril_rwlock_write_unlock(&namespace_rwlock);
1104 free(path);
1105 ipc_answer_0(rid, rc);
1106}
1107
1108void vfs_unlink(ipc_callid_t rid, ipc_call_t *request)
1109{
1110 int lflag = IPC_GET_ARG1(*request);
1111
1112 char *path;
1113 int rc = async_data_write_accept((void **) &path, true, 0, 0, 0, NULL);
1114 if (rc != EOK) {
1115 ipc_answer_0(rid, rc);
1116 return;
1117 }
1118
1119 fibril_rwlock_write_lock(&namespace_rwlock);
1120 lflag &= L_DIRECTORY; /* sanitize lflag */
1121 vfs_lookup_res_t lr;
1122 rc = vfs_lookup_internal(path, lflag | L_UNLINK, &lr, NULL);
1123 free(path);
1124 if (rc != EOK) {
1125 fibril_rwlock_write_unlock(&namespace_rwlock);
1126 ipc_answer_0(rid, rc);
1127 return;
1128 }
1129
1130 /*
1131 * The name has already been unlinked by vfs_lookup_internal().
1132 * We have to get and put the VFS node to ensure that it is
1133 * VFS_OUT_DESTROY'ed after the last reference to it is dropped.
1134 */
1135 vfs_node_t *node = vfs_node_get(&lr);
1136 fibril_mutex_lock(&nodes_mutex);
1137 node->lnkcnt--;
1138 fibril_mutex_unlock(&nodes_mutex);
1139 fibril_rwlock_write_unlock(&namespace_rwlock);
1140 vfs_node_put(node);
1141 ipc_answer_0(rid, EOK);
1142}
1143
1144void vfs_rename(ipc_callid_t rid, ipc_call_t *request)
1145{
1146 /* Retrieve the old path. */
1147 char *old;
1148 int rc = async_data_write_accept((void **) &old, true, 0, 0, 0, NULL);
1149 if (rc != EOK) {
1150 ipc_answer_0(rid, rc);
1151 return;
1152 }
1153
1154 /* Retrieve the new path. */
1155 char *new;
1156 rc = async_data_write_accept((void **) &new, true, 0, 0, 0, NULL);
1157 if (rc != EOK) {
1158 free(old);
1159 ipc_answer_0(rid, rc);
1160 return;
1161 }
1162
1163 size_t olen;
1164 size_t nlen;
1165 char *oldc = canonify(old, &olen);
1166 char *newc = canonify(new, &nlen);
1167
1168 if ((!oldc) || (!newc)) {
1169 ipc_answer_0(rid, EINVAL);
1170 free(old);
1171 free(new);
1172 return;
1173 }
1174
1175 oldc[olen] = '\0';
1176 newc[nlen] = '\0';
1177
1178 if ((!str_lcmp(newc, oldc, str_length(oldc))) &&
1179 ((newc[str_length(oldc)] == '/') ||
1180 (str_length(oldc) == 1) ||
1181 (str_length(oldc) == str_length(newc)))) {
1182 /*
1183 * oldc is a prefix of newc and either
1184 * - newc continues with a / where oldc ends, or
1185 * - oldc was / itself, or
1186 * - oldc and newc are equal.
1187 */
1188 ipc_answer_0(rid, EINVAL);
1189 free(old);
1190 free(new);
1191 return;
1192 }
1193
1194 vfs_lookup_res_t old_lr;
1195 vfs_lookup_res_t new_lr;
1196 vfs_lookup_res_t new_par_lr;
1197 fibril_rwlock_write_lock(&namespace_rwlock);
1198
1199 /* Lookup the node belonging to the old file name. */
1200 rc = vfs_lookup_internal(oldc, L_NONE, &old_lr, NULL);
1201 if (rc != EOK) {
1202 fibril_rwlock_write_unlock(&namespace_rwlock);
1203 ipc_answer_0(rid, rc);
1204 free(old);
1205 free(new);
1206 return;
1207 }
1208
1209 vfs_node_t *old_node = vfs_node_get(&old_lr);
1210 if (!old_node) {
1211 fibril_rwlock_write_unlock(&namespace_rwlock);
1212 ipc_answer_0(rid, ENOMEM);
1213 free(old);
1214 free(new);
1215 return;
1216 }
1217
1218 /* Determine the path to the parent of the node with the new name. */
1219 char *parentc = str_dup(newc);
1220 if (!parentc) {
1221 fibril_rwlock_write_unlock(&namespace_rwlock);
1222 ipc_answer_0(rid, rc);
1223 free(old);
1224 free(new);
1225 return;
1226 }
1227
1228 char *lastsl = str_rchr(parentc + 1, '/');
1229 if (lastsl)
1230 *lastsl = '\0';
1231 else
1232 parentc[1] = '\0';
1233
1234 /* Lookup parent of the new file name. */
1235 rc = vfs_lookup_internal(parentc, L_NONE, &new_par_lr, NULL);
1236 free(parentc); /* not needed anymore */
1237 if (rc != EOK) {
1238 fibril_rwlock_write_unlock(&namespace_rwlock);
1239 ipc_answer_0(rid, rc);
1240 free(old);
1241 free(new);
1242 return;
1243 }
1244
1245 /* Check whether linking to the same file system instance. */
1246 if ((old_node->fs_handle != new_par_lr.triplet.fs_handle) ||
1247 (old_node->dev_handle != new_par_lr.triplet.dev_handle)) {
1248 fibril_rwlock_write_unlock(&namespace_rwlock);
1249 ipc_answer_0(rid, EXDEV); /* different file systems */
1250 free(old);
1251 free(new);
1252 return;
1253 }
1254
1255 /* Destroy the old link for the new name. */
1256 vfs_node_t *new_node = NULL;
1257 rc = vfs_lookup_internal(newc, L_UNLINK, &new_lr, NULL);
1258
1259 switch (rc) {
1260 case ENOENT:
1261 /* simply not in our way */
1262 break;
1263 case EOK:
1264 new_node = vfs_node_get(&new_lr);
1265 if (!new_node) {
1266 fibril_rwlock_write_unlock(&namespace_rwlock);
1267 ipc_answer_0(rid, ENOMEM);
1268 free(old);
1269 free(new);
1270 return;
1271 }
1272 fibril_mutex_lock(&nodes_mutex);
1273 new_node->lnkcnt--;
1274 fibril_mutex_unlock(&nodes_mutex);
1275 break;
1276 default:
1277 fibril_rwlock_write_unlock(&namespace_rwlock);
1278 ipc_answer_0(rid, ENOTEMPTY);
1279 free(old);
1280 free(new);
1281 return;
1282 }
1283
1284 /* Create the new link for the new name. */
1285 rc = vfs_lookup_internal(newc, L_LINK, NULL, NULL, old_node->index);
1286 if (rc != EOK) {
1287 fibril_rwlock_write_unlock(&namespace_rwlock);
1288 if (new_node)
1289 vfs_node_put(new_node);
1290 ipc_answer_0(rid, rc);
1291 free(old);
1292 free(new);
1293 return;
1294 }
1295
1296 fibril_mutex_lock(&nodes_mutex);
1297 old_node->lnkcnt++;
1298 fibril_mutex_unlock(&nodes_mutex);
1299
1300 /* Destroy the link for the old name. */
1301 rc = vfs_lookup_internal(oldc, L_UNLINK, NULL, NULL);
1302 if (rc != EOK) {
1303 fibril_rwlock_write_unlock(&namespace_rwlock);
1304 vfs_node_put(old_node);
1305 if (new_node)
1306 vfs_node_put(new_node);
1307 ipc_answer_0(rid, rc);
1308 free(old);
1309 free(new);
1310 return;
1311 }
1312
1313 fibril_mutex_lock(&nodes_mutex);
1314 old_node->lnkcnt--;
1315 fibril_mutex_unlock(&nodes_mutex);
1316 fibril_rwlock_write_unlock(&namespace_rwlock);
1317 vfs_node_put(old_node);
1318
1319 if (new_node)
1320 vfs_node_put(new_node);
1321
1322 free(old);
1323 free(new);
1324 ipc_answer_0(rid, EOK);
1325}
1326
1327void vfs_dup(ipc_callid_t rid, ipc_call_t *request)
1328{
1329 int oldfd = IPC_GET_ARG1(*request);
1330 int newfd = IPC_GET_ARG2(*request);
1331
1332 /* Lookup the file structure corresponding to oldfd. */
1333 vfs_file_t *oldfile = vfs_file_get(oldfd);
1334 if (!oldfile) {
1335 ipc_answer_0(rid, EBADF);
1336 return;
1337 }
1338
1339 /* If the file descriptors are the same, do nothing. */
1340 if (oldfd == newfd) {
1341 ipc_answer_1(rid, EOK, newfd);
1342 return;
1343 }
1344
1345 /*
1346 * Lock the open file structure so that no other thread can manipulate
1347 * the same open file at a time.
1348 */
1349 fibril_mutex_lock(&oldfile->lock);
1350
1351 /* Lookup an open file structure possibly corresponding to newfd. */
1352 vfs_file_t *newfile = vfs_file_get(newfd);
1353 if (newfile) {
1354 /* Close the originally opened file. */
1355 int ret = vfs_close_internal(newfile);
1356 if (ret != EOK) {
1357 ipc_answer_0(rid, ret);
1358 return;
1359 }
1360
1361 ret = vfs_fd_free(newfd);
1362 if (ret != EOK) {
1363 ipc_answer_0(rid, ret);
1364 return;
1365 }
1366 }
1367
1368 /* Assign the old file to newfd. */
1369 int ret = vfs_fd_assign(oldfile, newfd);
1370 fibril_mutex_unlock(&oldfile->lock);
1371
1372 if (ret != EOK)
1373 ipc_answer_0(rid, ret);
1374 else
1375 ipc_answer_1(rid, EOK, newfd);
1376}
1377
1378/**
1379 * @}
1380 */
Note: See TracBrowser for help on using the repository browser.