source: mainline/uspace/lib/c/generic/vfs/vfs.c@ 7907cf9

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 7907cf9 was 96b02eb9, checked in by Martin Decky <martin@…>, 15 years ago

more unification of basic types

  • use sysarg_t and native_t (unsigned and signed variant) in both kernel and uspace
  • remove ipcarg_t in favour of sysarg_t

(no change in functionality)

  • Property mode set to 100644
File size: 16.3 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 libc
30 * @{
31 */
32/** @file
33 */
34
35#include <vfs/vfs.h>
36#include <vfs/canonify.h>
37#include <macros.h>
38#include <stdlib.h>
39#include <unistd.h>
40#include <dirent.h>
41#include <fcntl.h>
42#include <stdio.h>
43#include <sys/stat.h>
44#include <sys/types.h>
45#include <ipc/ipc.h>
46#include <ipc/services.h>
47#include <async.h>
48#include <atomic.h>
49#include <futex.h>
50#include <errno.h>
51#include <str.h>
52#include <devmap.h>
53#include <ipc/vfs.h>
54#include <ipc/devmap.h>
55
56static int vfs_phone = -1;
57static futex_t vfs_phone_futex = FUTEX_INITIALIZER;
58static futex_t cwd_futex = FUTEX_INITIALIZER;
59
60static int cwd_fd = -1;
61static char *cwd_path = NULL;
62static size_t cwd_size = 0;
63
64char *absolutize(const char *path, size_t *retlen)
65{
66 char *ncwd_path;
67 char *ncwd_path_nc;
68
69 futex_down(&cwd_futex);
70 size_t size = str_size(path);
71 if (*path != '/') {
72 if (!cwd_path) {
73 futex_up(&cwd_futex);
74 return NULL;
75 }
76 ncwd_path_nc = malloc(cwd_size + 1 + size + 1);
77 if (!ncwd_path_nc) {
78 futex_up(&cwd_futex);
79 return NULL;
80 }
81 str_cpy(ncwd_path_nc, cwd_size + 1 + size + 1, cwd_path);
82 ncwd_path_nc[cwd_size] = '/';
83 ncwd_path_nc[cwd_size + 1] = '\0';
84 } else {
85 ncwd_path_nc = malloc(size + 1);
86 if (!ncwd_path_nc) {
87 futex_up(&cwd_futex);
88 return NULL;
89 }
90 ncwd_path_nc[0] = '\0';
91 }
92 str_append(ncwd_path_nc, cwd_size + 1 + size + 1, path);
93 ncwd_path = canonify(ncwd_path_nc, retlen);
94 if (!ncwd_path) {
95 futex_up(&cwd_futex);
96 free(ncwd_path_nc);
97 return NULL;
98 }
99 /*
100 * We need to clone ncwd_path because canonify() works in-place and thus
101 * the address in ncwd_path need not be the same as ncwd_path_nc, even
102 * though they both point into the same dynamically allocated buffer.
103 */
104 ncwd_path = str_dup(ncwd_path);
105 free(ncwd_path_nc);
106 if (!ncwd_path) {
107 futex_up(&cwd_futex);
108 return NULL;
109 }
110 futex_up(&cwd_futex);
111 return ncwd_path;
112}
113
114static void vfs_connect(void)
115{
116 while (vfs_phone < 0)
117 vfs_phone = ipc_connect_me_to_blocking(PHONE_NS, SERVICE_VFS, 0, 0);
118}
119
120int mount(const char *fs_name, const char *mp, const char *fqdn,
121 const char *opts, unsigned int flags)
122{
123 int null_id = -1;
124 char null[DEVMAP_NAME_MAXLEN];
125
126 if (str_cmp(fqdn, "") == 0) {
127 /* No device specified, create a fresh
128 null/%d device instead */
129 null_id = devmap_null_create();
130
131 if (null_id == -1)
132 return ENOMEM;
133
134 snprintf(null, DEVMAP_NAME_MAXLEN, "null/%d", null_id);
135 fqdn = null;
136 }
137
138 devmap_handle_t devmap_handle;
139 int res = devmap_device_get_handle(fqdn, &devmap_handle, flags);
140 if (res != EOK) {
141 if (null_id != -1)
142 devmap_null_destroy(null_id);
143
144 return res;
145 }
146
147 size_t mpa_size;
148 char *mpa = absolutize(mp, &mpa_size);
149 if (!mpa) {
150 if (null_id != -1)
151 devmap_null_destroy(null_id);
152
153 return ENOMEM;
154 }
155
156 futex_down(&vfs_phone_futex);
157 async_serialize_start();
158 vfs_connect();
159
160 sysarg_t rc_orig;
161 aid_t req = async_send_2(vfs_phone, VFS_IN_MOUNT, devmap_handle, flags, NULL);
162 sysarg_t rc = async_data_write_start(vfs_phone, (void *) mpa, mpa_size);
163 if (rc != EOK) {
164 async_wait_for(req, &rc_orig);
165 async_serialize_end();
166 futex_up(&vfs_phone_futex);
167 free(mpa);
168
169 if (null_id != -1)
170 devmap_null_destroy(null_id);
171
172 if (rc_orig == EOK)
173 return (int) rc;
174 else
175 return (int) rc_orig;
176 }
177
178 rc = async_data_write_start(vfs_phone, (void *) opts, str_size(opts));
179 if (rc != EOK) {
180 async_wait_for(req, &rc_orig);
181 async_serialize_end();
182 futex_up(&vfs_phone_futex);
183 free(mpa);
184
185 if (null_id != -1)
186 devmap_null_destroy(null_id);
187
188 if (rc_orig == EOK)
189 return (int) rc;
190 else
191 return (int) rc_orig;
192 }
193
194 rc = async_data_write_start(vfs_phone, (void *) fs_name, str_size(fs_name));
195 if (rc != EOK) {
196 async_wait_for(req, &rc_orig);
197 async_serialize_end();
198 futex_up(&vfs_phone_futex);
199 free(mpa);
200
201 if (null_id != -1)
202 devmap_null_destroy(null_id);
203
204 if (rc_orig == EOK)
205 return (int) rc;
206 else
207 return (int) rc_orig;
208 }
209
210 /* Ask VFS whether it likes fs_name. */
211 rc = async_req_0_0(vfs_phone, IPC_M_PING);
212 if (rc != EOK) {
213 async_wait_for(req, &rc_orig);
214 async_serialize_end();
215 futex_up(&vfs_phone_futex);
216 free(mpa);
217
218 if (null_id != -1)
219 devmap_null_destroy(null_id);
220
221 if (rc_orig == EOK)
222 return (int) rc;
223 else
224 return (int) rc_orig;
225 }
226
227 async_wait_for(req, &rc);
228 async_serialize_end();
229 futex_up(&vfs_phone_futex);
230 free(mpa);
231
232 if ((rc != EOK) && (null_id != -1))
233 devmap_null_destroy(null_id);
234
235 return (int) rc;
236}
237
238int unmount(const char *mp)
239{
240 sysarg_t rc;
241 sysarg_t rc_orig;
242 aid_t req;
243 size_t mpa_size;
244 char *mpa;
245
246 mpa = absolutize(mp, &mpa_size);
247 if (!mpa)
248 return ENOMEM;
249
250 futex_down(&vfs_phone_futex);
251 async_serialize_start();
252 vfs_connect();
253
254 req = async_send_0(vfs_phone, VFS_IN_UNMOUNT, NULL);
255 rc = async_data_write_start(vfs_phone, (void *) mpa, mpa_size);
256 if (rc != EOK) {
257 async_wait_for(req, &rc_orig);
258 async_serialize_end();
259 futex_up(&vfs_phone_futex);
260 free(mpa);
261 if (rc_orig == EOK)
262 return (int) rc;
263 else
264 return (int) rc_orig;
265 }
266
267
268 async_wait_for(req, &rc);
269 async_serialize_end();
270 futex_up(&vfs_phone_futex);
271 free(mpa);
272
273 return (int) rc;
274}
275
276static int open_internal(const char *abs, size_t abs_size, int lflag, int oflag)
277{
278 futex_down(&vfs_phone_futex);
279 async_serialize_start();
280 vfs_connect();
281
282 ipc_call_t answer;
283 aid_t req = async_send_3(vfs_phone, VFS_IN_OPEN, lflag, oflag, 0, &answer);
284 sysarg_t rc = async_data_write_start(vfs_phone, abs, abs_size);
285
286 if (rc != EOK) {
287 sysarg_t rc_orig;
288 async_wait_for(req, &rc_orig);
289
290 async_serialize_end();
291 futex_up(&vfs_phone_futex);
292
293 if (rc_orig == EOK)
294 return (int) rc;
295 else
296 return (int) rc_orig;
297 }
298
299 async_wait_for(req, &rc);
300 async_serialize_end();
301 futex_up(&vfs_phone_futex);
302
303 if (rc != EOK)
304 return (int) rc;
305
306 return (int) IPC_GET_ARG1(answer);
307}
308
309int open(const char *path, int oflag, ...)
310{
311 size_t abs_size;
312 char *abs = absolutize(path, &abs_size);
313 if (!abs)
314 return ENOMEM;
315
316 int ret = open_internal(abs, abs_size, L_FILE, oflag);
317 free(abs);
318
319 return ret;
320}
321
322int open_node(fdi_node_t *node, int oflag)
323{
324 futex_down(&vfs_phone_futex);
325 async_serialize_start();
326 vfs_connect();
327
328 ipc_call_t answer;
329 aid_t req = async_send_4(vfs_phone, VFS_IN_OPEN_NODE, node->fs_handle,
330 node->devmap_handle, node->index, oflag, &answer);
331
332 sysarg_t rc;
333 async_wait_for(req, &rc);
334 async_serialize_end();
335 futex_up(&vfs_phone_futex);
336
337 if (rc != EOK)
338 return (int) rc;
339
340 return (int) IPC_GET_ARG1(answer);
341}
342
343int close(int fildes)
344{
345 sysarg_t rc;
346
347 futex_down(&vfs_phone_futex);
348 async_serialize_start();
349 vfs_connect();
350
351 rc = async_req_1_0(vfs_phone, VFS_IN_CLOSE, fildes);
352
353 async_serialize_end();
354 futex_up(&vfs_phone_futex);
355
356 return (int)rc;
357}
358
359ssize_t read(int fildes, void *buf, size_t nbyte)
360{
361 sysarg_t rc;
362 ipc_call_t answer;
363 aid_t req;
364
365 futex_down(&vfs_phone_futex);
366 async_serialize_start();
367 vfs_connect();
368
369 req = async_send_1(vfs_phone, VFS_IN_READ, fildes, &answer);
370 rc = async_data_read_start(vfs_phone, (void *)buf, nbyte);
371 if (rc != EOK) {
372 sysarg_t rc_orig;
373
374 async_wait_for(req, &rc_orig);
375 async_serialize_end();
376 futex_up(&vfs_phone_futex);
377 if (rc_orig == EOK)
378 return (ssize_t) rc;
379 else
380 return (ssize_t) rc_orig;
381 }
382 async_wait_for(req, &rc);
383 async_serialize_end();
384 futex_up(&vfs_phone_futex);
385 if (rc == EOK)
386 return (ssize_t) IPC_GET_ARG1(answer);
387 else
388 return rc;
389}
390
391ssize_t write(int fildes, const void *buf, size_t nbyte)
392{
393 sysarg_t rc;
394 ipc_call_t answer;
395 aid_t req;
396
397 futex_down(&vfs_phone_futex);
398 async_serialize_start();
399 vfs_connect();
400
401 req = async_send_1(vfs_phone, VFS_IN_WRITE, fildes, &answer);
402 rc = async_data_write_start(vfs_phone, (void *)buf, nbyte);
403 if (rc != EOK) {
404 sysarg_t rc_orig;
405
406 async_wait_for(req, &rc_orig);
407 async_serialize_end();
408 futex_up(&vfs_phone_futex);
409 if (rc_orig == EOK)
410 return (ssize_t) rc;
411 else
412 return (ssize_t) rc_orig;
413 }
414 async_wait_for(req, &rc);
415 async_serialize_end();
416 futex_up(&vfs_phone_futex);
417 if (rc == EOK)
418 return (ssize_t) IPC_GET_ARG1(answer);
419 else
420 return -1;
421}
422
423int fsync(int fildes)
424{
425 futex_down(&vfs_phone_futex);
426 async_serialize_start();
427 vfs_connect();
428
429 sysarg_t rc = async_req_1_0(vfs_phone, VFS_IN_SYNC, fildes);
430
431 async_serialize_end();
432 futex_up(&vfs_phone_futex);
433
434 return (int) rc;
435}
436
437off64_t lseek(int fildes, off64_t offset, int whence)
438{
439 futex_down(&vfs_phone_futex);
440 async_serialize_start();
441 vfs_connect();
442
443 sysarg_t newoff_lo;
444 sysarg_t newoff_hi;
445 sysarg_t rc = async_req_4_2(vfs_phone, VFS_IN_SEEK, fildes,
446 LOWER32(offset), UPPER32(offset), whence,
447 &newoff_lo, &newoff_hi);
448
449 async_serialize_end();
450 futex_up(&vfs_phone_futex);
451
452 if (rc != EOK)
453 return (off64_t) -1;
454
455 return (off64_t) MERGE_LOUP32(newoff_lo, newoff_hi);
456}
457
458int ftruncate(int fildes, aoff64_t length)
459{
460 sysarg_t rc;
461
462 futex_down(&vfs_phone_futex);
463 async_serialize_start();
464 vfs_connect();
465
466 rc = async_req_3_0(vfs_phone, VFS_IN_TRUNCATE, fildes,
467 LOWER32(length), UPPER32(length));
468 async_serialize_end();
469 futex_up(&vfs_phone_futex);
470
471 return (int) rc;
472}
473
474int fstat(int fildes, struct stat *stat)
475{
476 sysarg_t rc;
477 aid_t req;
478
479 futex_down(&vfs_phone_futex);
480 async_serialize_start();
481 vfs_connect();
482
483 req = async_send_1(vfs_phone, VFS_IN_FSTAT, fildes, NULL);
484 rc = async_data_read_start(vfs_phone, (void *) stat, sizeof(struct stat));
485 if (rc != EOK) {
486 sysarg_t rc_orig;
487
488 async_wait_for(req, &rc_orig);
489 async_serialize_end();
490 futex_up(&vfs_phone_futex);
491 if (rc_orig == EOK)
492 return (ssize_t) rc;
493 else
494 return (ssize_t) rc_orig;
495 }
496 async_wait_for(req, &rc);
497 async_serialize_end();
498 futex_up(&vfs_phone_futex);
499
500 return rc;
501}
502
503int stat(const char *path, struct stat *stat)
504{
505 sysarg_t rc;
506 sysarg_t rc_orig;
507 aid_t req;
508
509 size_t pa_size;
510 char *pa = absolutize(path, &pa_size);
511 if (!pa)
512 return ENOMEM;
513
514 futex_down(&vfs_phone_futex);
515 async_serialize_start();
516 vfs_connect();
517
518 req = async_send_0(vfs_phone, VFS_IN_STAT, NULL);
519 rc = async_data_write_start(vfs_phone, pa, pa_size);
520 if (rc != EOK) {
521 async_wait_for(req, &rc_orig);
522 async_serialize_end();
523 futex_up(&vfs_phone_futex);
524 free(pa);
525 if (rc_orig == EOK)
526 return (int) rc;
527 else
528 return (int) rc_orig;
529 }
530 rc = async_data_read_start(vfs_phone, stat, sizeof(struct stat));
531 if (rc != EOK) {
532 async_wait_for(req, &rc_orig);
533 async_serialize_end();
534 futex_up(&vfs_phone_futex);
535 free(pa);
536 if (rc_orig == EOK)
537 return (int) rc;
538 else
539 return (int) rc_orig;
540 }
541 async_wait_for(req, &rc);
542 async_serialize_end();
543 futex_up(&vfs_phone_futex);
544 free(pa);
545 return rc;
546}
547
548DIR *opendir(const char *dirname)
549{
550 DIR *dirp = malloc(sizeof(DIR));
551 if (!dirp)
552 return NULL;
553
554 size_t abs_size;
555 char *abs = absolutize(dirname, &abs_size);
556 if (!abs) {
557 free(dirp);
558 return NULL;
559 }
560
561 int ret = open_internal(abs, abs_size, L_DIRECTORY, 0);
562 free(abs);
563
564 if (ret < 0) {
565 free(dirp);
566 return NULL;
567 }
568
569 dirp->fd = ret;
570 return dirp;
571}
572
573struct dirent *readdir(DIR *dirp)
574{
575 ssize_t len = read(dirp->fd, &dirp->res.d_name[0], NAME_MAX + 1);
576 if (len <= 0)
577 return NULL;
578 return &dirp->res;
579}
580
581void rewinddir(DIR *dirp)
582{
583 (void) lseek(dirp->fd, 0, SEEK_SET);
584}
585
586int closedir(DIR *dirp)
587{
588 (void) close(dirp->fd);
589 free(dirp);
590 return 0;
591}
592
593int mkdir(const char *path, mode_t mode)
594{
595 sysarg_t rc;
596 aid_t req;
597
598 size_t pa_size;
599 char *pa = absolutize(path, &pa_size);
600 if (!pa)
601 return ENOMEM;
602
603 futex_down(&vfs_phone_futex);
604 async_serialize_start();
605 vfs_connect();
606
607 req = async_send_1(vfs_phone, VFS_IN_MKDIR, mode, NULL);
608 rc = async_data_write_start(vfs_phone, pa, pa_size);
609 if (rc != EOK) {
610 sysarg_t rc_orig;
611
612 async_wait_for(req, &rc_orig);
613 async_serialize_end();
614 futex_up(&vfs_phone_futex);
615 free(pa);
616 if (rc_orig == EOK)
617 return (int) rc;
618 else
619 return (int) rc_orig;
620 }
621 async_wait_for(req, &rc);
622 async_serialize_end();
623 futex_up(&vfs_phone_futex);
624 free(pa);
625 return rc;
626}
627
628static int _unlink(const char *path, int lflag)
629{
630 sysarg_t rc;
631 aid_t req;
632
633 size_t pa_size;
634 char *pa = absolutize(path, &pa_size);
635 if (!pa)
636 return ENOMEM;
637
638 futex_down(&vfs_phone_futex);
639 async_serialize_start();
640 vfs_connect();
641
642 req = async_send_0(vfs_phone, VFS_IN_UNLINK, NULL);
643 rc = async_data_write_start(vfs_phone, pa, pa_size);
644 if (rc != EOK) {
645 sysarg_t rc_orig;
646
647 async_wait_for(req, &rc_orig);
648 async_serialize_end();
649 futex_up(&vfs_phone_futex);
650 free(pa);
651 if (rc_orig == EOK)
652 return (int) rc;
653 else
654 return (int) rc_orig;
655 }
656 async_wait_for(req, &rc);
657 async_serialize_end();
658 futex_up(&vfs_phone_futex);
659 free(pa);
660 return rc;
661}
662
663int unlink(const char *path)
664{
665 return _unlink(path, L_NONE);
666}
667
668int rmdir(const char *path)
669{
670 return _unlink(path, L_DIRECTORY);
671}
672
673int rename(const char *old, const char *new)
674{
675 sysarg_t rc;
676 sysarg_t rc_orig;
677 aid_t req;
678
679 size_t olda_size;
680 char *olda = absolutize(old, &olda_size);
681 if (!olda)
682 return ENOMEM;
683
684 size_t newa_size;
685 char *newa = absolutize(new, &newa_size);
686 if (!newa) {
687 free(olda);
688 return ENOMEM;
689 }
690
691 futex_down(&vfs_phone_futex);
692 async_serialize_start();
693 vfs_connect();
694
695 req = async_send_0(vfs_phone, VFS_IN_RENAME, NULL);
696 rc = async_data_write_start(vfs_phone, olda, olda_size);
697 if (rc != EOK) {
698 async_wait_for(req, &rc_orig);
699 async_serialize_end();
700 futex_up(&vfs_phone_futex);
701 free(olda);
702 free(newa);
703 if (rc_orig == EOK)
704 return (int) rc;
705 else
706 return (int) rc_orig;
707 }
708 rc = async_data_write_start(vfs_phone, newa, newa_size);
709 if (rc != EOK) {
710 async_wait_for(req, &rc_orig);
711 async_serialize_end();
712 futex_up(&vfs_phone_futex);
713 free(olda);
714 free(newa);
715 if (rc_orig == EOK)
716 return (int) rc;
717 else
718 return (int) rc_orig;
719 }
720 async_wait_for(req, &rc);
721 async_serialize_end();
722 futex_up(&vfs_phone_futex);
723 free(olda);
724 free(newa);
725 return rc;
726}
727
728int chdir(const char *path)
729{
730 size_t abs_size;
731 char *abs = absolutize(path, &abs_size);
732 if (!abs)
733 return ENOMEM;
734
735 int fd = open_internal(abs, abs_size, L_DIRECTORY, O_DESC);
736
737 if (fd < 0) {
738 free(abs);
739 return ENOENT;
740 }
741
742 futex_down(&cwd_futex);
743
744 if (cwd_fd >= 0)
745 close(cwd_fd);
746
747
748 if (cwd_path)
749 free(cwd_path);
750
751 cwd_fd = fd;
752 cwd_path = abs;
753 cwd_size = abs_size;
754
755 futex_up(&cwd_futex);
756 return EOK;
757}
758
759char *getcwd(char *buf, size_t size)
760{
761 if (size == 0)
762 return NULL;
763
764 futex_down(&cwd_futex);
765
766 if ((cwd_size == 0) || (size < cwd_size + 1)) {
767 futex_up(&cwd_futex);
768 return NULL;
769 }
770
771 str_cpy(buf, size, cwd_path);
772 futex_up(&cwd_futex);
773
774 return buf;
775}
776
777int fd_phone(int fildes)
778{
779 struct stat stat;
780 int rc;
781
782 rc = fstat(fildes, &stat);
783
784 if (!stat.device)
785 return -1;
786
787 return devmap_device_connect(stat.device, 0);
788}
789
790int fd_node(int fildes, fdi_node_t *node)
791{
792 struct stat stat;
793 int rc;
794
795 rc = fstat(fildes, &stat);
796
797 if (rc == EOK) {
798 node->fs_handle = stat.fs_handle;
799 node->devmap_handle = stat.devmap_handle;
800 node->index = stat.index;
801 }
802
803 return rc;
804}
805
806int dup2(int oldfd, int newfd)
807{
808 futex_down(&vfs_phone_futex);
809 async_serialize_start();
810 vfs_connect();
811
812 sysarg_t ret;
813 sysarg_t rc = async_req_2_1(vfs_phone, VFS_IN_DUP, oldfd, newfd, &ret);
814
815 async_serialize_end();
816 futex_up(&vfs_phone_futex);
817
818 if (rc == EOK)
819 return (int) ret;
820
821 return (int) rc;
822}
823
824/** @}
825 */
Note: See TracBrowser for help on using the repository browser.