Annotation of src/sys/fs/tmpfs/tmpfs.h, Revision 1.33.2.2
1.33.2.2! wrstuden 1: /* $NetBSD: tmpfs.h,v 1.33.2.1 2008/06/23 04:31:49 wrstuden Exp $ */
1.1 jmmv 2:
3: /*
1.30 ad 4: * Copyright (c) 2005, 2006, 2007 The NetBSD Foundation, Inc.
1.1 jmmv 5: * All rights reserved.
6: *
7: * This code is derived from software contributed to The NetBSD Foundation
1.6 jmmv 8: * by Julio M. Merino Vidal, developed as part of Google's Summer of Code
9: * 2005 program.
1.1 jmmv 10: *
11: * Redistribution and use in source and binary forms, with or without
12: * modification, are permitted provided that the following conditions
13: * are met:
14: * 1. Redistributions of source code must retain the above copyright
15: * notice, this list of conditions and the following disclaimer.
16: * 2. Redistributions in binary form must reproduce the above copyright
17: * notice, this list of conditions and the following disclaimer in the
18: * documentation and/or other materials provided with the distribution.
19: *
20: * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
21: * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22: * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
24: * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30: * POSSIBILITY OF SUCH DAMAGE.
31: */
32:
1.10 christos 33: #ifndef _FS_TMPFS_TMPFS_H_
34: #define _FS_TMPFS_TMPFS_H_
1.1 jmmv 35:
36: #include <sys/dirent.h>
37: #include <sys/mount.h>
38: #include <sys/queue.h>
39: #include <sys/vnode.h>
40:
1.33.2.2! wrstuden 41: /* --------------------------------------------------------------------- */
! 42: /* For the kernel and anyone who likes peeking into kernel memory */
! 43: /* --------------------------------------------------------------------- */
! 44:
1.20 yamt 45: #if defined(_KERNEL)
1.1 jmmv 46: #include <fs/tmpfs/tmpfs_pool.h>
1.20 yamt 47: #endif /* defined(_KERNEL) */
1.1 jmmv 48:
49: /* --------------------------------------------------------------------- */
50:
51: /*
1.6 jmmv 52: * Internal representation of a tmpfs directory entry.
1.1 jmmv 53: */
54: struct tmpfs_dirent {
55: TAILQ_ENTRY(tmpfs_dirent) td_entries;
1.6 jmmv 56:
57: /* Length of the name stored in this directory entry. This avoids
58: * the need to recalculate it every time the name is used. */
1.1 jmmv 59: uint16_t td_namelen;
1.6 jmmv 60:
61: /* The name of the entry, allocated from a string pool. This
62: * string is not required to be zero-terminated; therefore, the
63: * td_namelen field must always be used when accessing its value. */
1.1 jmmv 64: char * td_name;
1.6 jmmv 65:
66: /* Pointer to the node this entry refers to. */
1.1 jmmv 67: struct tmpfs_node * td_node;
68: };
1.6 jmmv 69:
70: /* A directory in tmpfs holds a sorted list of directory entries, which in
71: * turn point to other files (which can be directories themselves).
72: *
73: * In tmpfs, this list is managed by a tail queue, whose head is defined by
74: * the struct tmpfs_dir type.
75: *
76: * It is imporant to notice that directories do not have entries for . and
77: * .. as other file systems do. These can be generated when requested
78: * based on information available by other means, such as the pointer to
79: * the node itself in the former case or the pointer to the parent directory
80: * in the latter case. This is done to simplify tmpfs's code and, more
81: * importantly, to remove redundancy. */
1.1 jmmv 82: TAILQ_HEAD(tmpfs_dir, tmpfs_dirent);
83:
1.22 jmmv 84: /* Each entry in a directory has a cookie that identifies it. Cookies
85: * supersede offsets within directories because, given how tmpfs stores
86: * directories in memory, there is no such thing as an offset. (Emulating
87: * a real offset could be very difficult.)
1.31 jmmv 88: *
1.22 jmmv 89: * The '.', '..' and the end of directory markers have fixed cookies which
90: * cannot collide with the cookies generated by other entries. The cookies
91: * fot the other entries are generated based on the memory address on which
92: * stores their information is stored.
93: *
94: * Ideally, using the entry's memory pointer as the cookie would be enough
95: * to represent it and it wouldn't cause collisions in any system.
96: * Unfortunately, this results in "offsets" with very large values which
97: * later raise problems in the Linux compatibility layer (and maybe in other
98: * places) as described in PR kern/32034. Hence we need to workaround this
99: * with a rather ugly hack.
100: *
101: * Linux 32-bit binaries, unless built with _FILE_OFFSET_BITS=64, have off_t
102: * set to 'long', which is a 32-bit *signed* long integer. Regardless of
103: * the macro value, GLIBC (2.3 at least) always uses the getdents64
104: * system call (when calling readdir) which internally returns off64_t
105: * offsets. In order to make 32-bit binaries work, *GLIBC* converts the
106: * 64-bit values returned by the kernel to 32-bit ones and aborts with
107: * EOVERFLOW if the conversion results in values that won't fit in 32-bit
108: * integers (which it assumes is because the directory is extremely large).
109: * This wouldn't cause problems if we were dealing with unsigned integers,
110: * but as we have signed integers, this check fails due to sign expansion.
111: *
112: * For example, consider that the kernel returns the 0xc1234567 cookie to
113: * userspace in a off64_t integer. Later on, GLIBC casts this value to
114: * off_t (remember, signed) with code similar to:
115: * system call returns the offset in kernel_value;
116: * off_t casted_value = kernel_value;
117: * if (sizeof(off_t) != sizeof(off64_t) &&
118: * kernel_value != casted_value)
119: * error!
120: * In this case, casted_value still has 0xc1234567, but when it is compared
121: * for equality against kernel_value, it is promoted to a 64-bit integer and
122: * becomes 0xffffffffc1234567, which is different than 0x00000000c1234567.
123: * Then, GLIBC assumes this is because the directory is very large.
124: *
125: * Given that all the above happens in user-space, we have no control over
126: * it; therefore we must workaround the issue here. We do this by
127: * truncating the pointer value to a 32-bit integer and hope that there
128: * won't be collisions. In fact, this will not cause any problems in
129: * 32-bit platforms but some might arise in 64-bit machines (I'm not sure
130: * if they can happen at all in practice).
131: *
132: * XXX A nicer solution shall be attempted. */
1.23 jmmv 133: #if defined(_KERNEL)
1.4 yamt 134: #define TMPFS_DIRCOOKIE_DOT 0
135: #define TMPFS_DIRCOOKIE_DOTDOT 1
136: #define TMPFS_DIRCOOKIE_EOF 2
1.22 jmmv 137: static __inline
138: off_t
139: tmpfs_dircookie(struct tmpfs_dirent *de)
140: {
141: off_t cookie;
142:
143: cookie = ((off_t)(uintptr_t)de >> 1) & 0x7FFFFFFF;
144: KASSERT(cookie != TMPFS_DIRCOOKIE_DOT);
145: KASSERT(cookie != TMPFS_DIRCOOKIE_DOTDOT);
146: KASSERT(cookie != TMPFS_DIRCOOKIE_EOF);
147:
148: return cookie;
149: }
1.23 jmmv 150: #endif /* defined(_KERNEL) */
1.4 yamt 151:
1.1 jmmv 152: /* --------------------------------------------------------------------- */
153:
154: /*
1.6 jmmv 155: * Internal representation of a tmpfs file system node.
156: *
157: * This structure is splitted in two parts: one holds attributes common
158: * to all file types and the other holds data that is only applicable to
159: * a particular type. The code must be careful to only access those
160: * attributes that are actually allowed by the node's type.
1.1 jmmv 161: */
162: struct tmpfs_node {
1.6 jmmv 163: /* Doubly-linked list entry which links all existing nodes for a
164: * single file system. This is provided to ease the removal of
165: * all nodes during the unmount operation. */
1.1 jmmv 166: LIST_ENTRY(tmpfs_node) tn_entries;
167:
1.6 jmmv 168: /* The node's type. Any of 'VBLK', 'VCHR', 'VDIR', 'VFIFO',
169: * 'VLNK', 'VREG' and 'VSOCK' is allowed. The usage of vnode
170: * types instead of a custom enumeration is to make things simpler
171: * and faster, as we do not need to convert between two types. */
1.1 jmmv 172: enum vtype tn_type;
1.6 jmmv 173:
174: /* Node identifier. */
1.1 jmmv 175: ino_t tn_id;
176:
1.6 jmmv 177: /* Node's internal status. This is used by several file system
178: * operations to do modifications to the node in a delayed
179: * fashion. */
180: int tn_status;
1.1 jmmv 181: #define TMPFS_NODE_ACCESSED (1 << 1)
182: #define TMPFS_NODE_MODIFIED (1 << 2)
183: #define TMPFS_NODE_CHANGED (1 << 3)
184:
1.6 jmmv 185: /* The node size. It does not necessarily match the real amount
186: * of memory consumed by it. */
1.1 jmmv 187: off_t tn_size;
188:
1.6 jmmv 189: /* Generic node attributes. */
1.1 jmmv 190: uid_t tn_uid;
191: gid_t tn_gid;
192: mode_t tn_mode;
193: int tn_flags;
194: nlink_t tn_links;
195: struct timespec tn_atime;
196: struct timespec tn_mtime;
197: struct timespec tn_ctime;
198: struct timespec tn_birthtime;
199: unsigned long tn_gen;
200:
1.8 jmmv 201: /* Head of byte-level lock list (used by tmpfs_advlock). */
202: struct lockf * tn_lockf;
203:
1.6 jmmv 204: /* As there is a single vnode for each active file within the
205: * system, care has to be taken to avoid allocating more than one
206: * vnode per file. In order to do this, a bidirectional association
207: * is kept between vnodes and nodes.
208: *
209: * Whenever a vnode is allocated, its v_data field is updated to
210: * point to the node it references. At the same time, the node's
211: * tn_vnode field is modified to point to the new vnode representing
212: * it. Further attempts to allocate a vnode for this same node will
213: * result in returning a new reference to the value stored in
214: * tn_vnode.
215: *
216: * May be NULL when the node is unused (that is, no vnode has been
217: * allocated for it or it has been reclaimed). */
1.30 ad 218: kmutex_t tn_vlock;
1.1 jmmv 219: struct vnode * tn_vnode;
220:
221: union {
222: /* Valid when tn_type == VBLK || tn_type == VCHR. */
223: struct {
224: dev_t tn_rdev;
1.15 jmmv 225: } tn_dev;
1.1 jmmv 226:
227: /* Valid when tn_type == VDIR. */
228: struct {
1.6 jmmv 229: /* Pointer to the parent directory. The root
230: * directory has a pointer to itself in this field;
231: * this property identifies the root node. */
1.1 jmmv 232: struct tmpfs_node * tn_parent;
1.6 jmmv 233:
234: /* Head of a tail-queue that links the contents of
235: * the directory together. See above for a
236: * description of its contents. */
1.1 jmmv 237: struct tmpfs_dir tn_dir;
238:
1.6 jmmv 239: /* Number and pointer of the first directory entry
240: * returned by the readdir operation if it were
241: * called again to continue reading data from the
242: * same directory as before. This is used to speed
243: * up reads of long directories, assuming that no
244: * more than one read is in progress at a given time.
245: * Otherwise, these values are discarded and a linear
246: * scan is performed from the beginning up to the
247: * point where readdir starts returning values. */
1.4 yamt 248: off_t tn_readdir_lastn;
1.1 jmmv 249: struct tmpfs_dirent * tn_readdir_lastp;
1.15 jmmv 250: } tn_dir;
1.1 jmmv 251:
252: /* Valid when tn_type == VLNK. */
1.15 jmmv 253: struct tn_lnk {
1.6 jmmv 254: /* The link's target, allocated from a string pool. */
1.1 jmmv 255: char * tn_link;
1.15 jmmv 256: } tn_lnk;
1.1 jmmv 257:
258: /* Valid when tn_type == VREG. */
1.15 jmmv 259: struct tn_reg {
1.6 jmmv 260: /* The contents of regular files stored in a tmpfs
261: * file system are represented by a single anonymous
262: * memory object (aobj, for short). The aobj provides
263: * direct access to any position within the file,
264: * because its contents are always mapped in a
265: * contiguous region of virtual memory. It is a task
266: * of the memory management subsystem (see uvm(9)) to
267: * issue the required page ins or page outs whenever
268: * a position within the file is accessed. */
1.1 jmmv 269: struct uvm_object * tn_aobj;
270: size_t tn_aobj_pages;
1.15 jmmv 271: } tn_reg;
272: } tn_spec;
1.1 jmmv 273: };
1.20 yamt 274:
275: #if defined(_KERNEL)
1.1 jmmv 276: LIST_HEAD(tmpfs_node_list, tmpfs_node);
277:
278: /* --------------------------------------------------------------------- */
279:
280: /*
1.6 jmmv 281: * Internal representation of a tmpfs mount point.
1.1 jmmv 282: */
283: struct tmpfs_mount {
1.6 jmmv 284: /* Maximum number of memory pages available for use by the file
285: * system, set during mount time. This variable must never be
1.24 jmmv 286: * used directly as it may be bigger than the current amount of
1.6 jmmv 287: * free memory; in the extreme case, it will hold the SIZE_MAX
288: * value. Instead, use the TMPFS_PAGES_MAX macro. */
1.32 jmmv 289: unsigned int tm_pages_max;
1.6 jmmv 290:
291: /* Number of pages in use by the file system. Cannot be bigger
292: * than the value returned by TMPFS_PAGES_MAX in any case. */
1.32 jmmv 293: unsigned int tm_pages_used;
1.1 jmmv 294:
1.6 jmmv 295: /* Pointer to the node representing the root directory of this
296: * file system. */
1.1 jmmv 297: struct tmpfs_node * tm_root;
298:
1.6 jmmv 299: /* Maximum number of possible nodes for this file system; set
300: * during mount time. We need a hard limit on the maximum number
301: * of nodes to avoid allocating too much of them; their objects
302: * cannot be released until the file system is unmounted.
303: * Otherwise, we could easily run out of memory by creating lots
304: * of empty files and then simply removing them. */
1.32 jmmv 305: unsigned int tm_nodes_max;
1.6 jmmv 306:
307: /* Number of nodes currently allocated. This number only grows.
308: * When it reaches tm_nodes_max, no more new nodes can be allocated.
309: * Of course, the old, unused ones can be reused. */
1.32 jmmv 310: unsigned int tm_nodes_cnt;
1.6 jmmv 311:
1.30 ad 312: /* Node list. */
313: kmutex_t tm_lock;
314: struct tmpfs_node_list tm_nodes;
1.1 jmmv 315:
1.6 jmmv 316: /* Pools used to store file system meta data. These are not shared
317: * across several instances of tmpfs for the reasons described in
318: * tmpfs_pool.c. */
1.1 jmmv 319: struct tmpfs_pool tm_dirent_pool;
320: struct tmpfs_pool tm_node_pool;
321: struct tmpfs_str_pool tm_str_pool;
322: };
323:
324: /* --------------------------------------------------------------------- */
325:
326: /*
327: * This structure maps a file identifier to a tmpfs node. Used by the
328: * NFS code.
329: */
330: struct tmpfs_fid {
331: uint16_t tf_len;
332: uint16_t tf_pad;
1.18 riz 333: uint32_t tf_gen;
1.1 jmmv 334: ino_t tf_id;
335: };
336:
337: /* --------------------------------------------------------------------- */
338:
339: /*
340: * Prototypes for tmpfs_subr.c.
341: */
342:
343: int tmpfs_alloc_node(struct tmpfs_mount *, enum vtype,
344: uid_t uid, gid_t gid, mode_t mode, struct tmpfs_node *,
1.29 pooka 345: char *, dev_t, struct tmpfs_node **);
1.1 jmmv 346: void tmpfs_free_node(struct tmpfs_mount *, struct tmpfs_node *);
347: int tmpfs_alloc_dirent(struct tmpfs_mount *, struct tmpfs_node *,
348: const char *, uint16_t, struct tmpfs_dirent **);
349: void tmpfs_free_dirent(struct tmpfs_mount *, struct tmpfs_dirent *,
1.25 thorpej 350: bool);
1.1 jmmv 351: int tmpfs_alloc_vp(struct mount *, struct tmpfs_node *, struct vnode **);
352: void tmpfs_free_vp(struct vnode *);
353: int tmpfs_alloc_file(struct vnode *, struct vnode **, struct vattr *,
354: struct componentname *, char *);
355: void tmpfs_dir_attach(struct vnode *, struct tmpfs_dirent *);
356: void tmpfs_dir_detach(struct vnode *, struct tmpfs_dirent *);
357: struct tmpfs_dirent * tmpfs_dir_lookup(struct tmpfs_node *node,
358: struct componentname *cnp);
359: int tmpfs_dir_getdotdent(struct tmpfs_node *, struct uio *);
360: int tmpfs_dir_getdotdotdent(struct tmpfs_node *, struct uio *);
1.4 yamt 361: struct tmpfs_dirent * tmpfs_dir_lookupbycookie(struct tmpfs_node *, off_t);
362: int tmpfs_dir_getdents(struct tmpfs_node *, struct uio *, off_t *);
1.1 jmmv 363: int tmpfs_reg_resize(struct vnode *, off_t);
1.25 thorpej 364: size_t tmpfs_mem_info(bool);
1.21 ad 365: int tmpfs_chflags(struct vnode *, int, kauth_cred_t, struct lwp *);
366: int tmpfs_chmod(struct vnode *, mode_t, kauth_cred_t, struct lwp *);
367: int tmpfs_chown(struct vnode *, uid_t, gid_t, kauth_cred_t, struct lwp *);
368: int tmpfs_chsize(struct vnode *, u_quad_t, kauth_cred_t, struct lwp *);
1.33.2.1 wrstuden 369: int tmpfs_chtimes(struct vnode *, const struct timespec *,
370: const struct timespec *, const struct timespec *, int, kauth_cred_t,
371: struct lwp *);
1.7 yamt 372: void tmpfs_itimes(struct vnode *, const struct timespec *,
1.33.2.1 wrstuden 373: const struct timespec *, const struct timespec *);
1.1 jmmv 374:
1.9 yamt 375: void tmpfs_update(struct vnode *, const struct timespec *,
1.33.2.1 wrstuden 376: const struct timespec *, const struct timespec *, int);
1.9 yamt 377: int tmpfs_truncate(struct vnode *, off_t);
378:
1.1 jmmv 379: /* --------------------------------------------------------------------- */
380:
381: /*
382: * Convenience macros to simplify some logical expressions.
383: */
384: #define IMPLIES(a, b) (!(a) || (b))
385: #define IFF(a, b) (IMPLIES(a, b) && IMPLIES(b, a))
386:
387: /* --------------------------------------------------------------------- */
388:
389: /*
390: * Checks that the directory entry pointed by 'de' matches the name 'name'
391: * with a length of 'len'.
392: */
393: #define TMPFS_DIRENT_MATCHES(de, name, len) \
394: (de->td_namelen == (uint16_t)len && \
395: memcmp((de)->td_name, (name), (de)->td_namelen) == 0)
396:
397: /* --------------------------------------------------------------------- */
398:
399: /*
400: * Ensures that the node pointed by 'node' is a directory and that its
401: * contents are consistent with respect to directories.
402: */
403: #define TMPFS_VALIDATE_DIR(node) \
404: KASSERT((node)->tn_type == VDIR); \
1.4 yamt 405: KASSERT((node)->tn_size % sizeof(struct tmpfs_dirent) == 0); \
1.15 jmmv 406: KASSERT((node)->tn_spec.tn_dir.tn_readdir_lastp == NULL || \
1.22 jmmv 407: tmpfs_dircookie((node)->tn_spec.tn_dir.tn_readdir_lastp) == \
1.15 jmmv 408: (node)->tn_spec.tn_dir.tn_readdir_lastn);
1.1 jmmv 409:
410: /* --------------------------------------------------------------------- */
411:
412: /*
413: * Memory management stuff.
414: */
415:
416: /* Amount of memory pages to reserve for the system (e.g., to not use by
417: * tmpfs).
418: * XXX: Should this be tunable through sysctl, for instance? */
419: #define TMPFS_PAGES_RESERVED (4 * 1024 * 1024 / PAGE_SIZE)
420:
1.6 jmmv 421: /* Returns the maximum size allowed for a tmpfs file system. This macro
422: * must be used instead of directly retrieving the value from tm_pages_max.
423: * The reason is that the size of a tmpfs file system is dynamic: it lets
424: * the user store files as long as there is enough free memory (including
425: * physical memory and swap space). Therefore, the amount of memory to be
426: * used is either the limit imposed by the user during mount time or the
427: * amount of available memory, whichever is lower. To avoid consuming all
428: * the memory for a given mount point, the system will always reserve a
429: * minimum of TMPFS_PAGES_RESERVED pages, which is also taken into account
430: * by this macro (see above). */
1.16 perry 431: static __inline size_t
1.1 jmmv 432: TMPFS_PAGES_MAX(struct tmpfs_mount *tmp)
433: {
434: size_t freepages;
435:
1.26 thorpej 436: freepages = tmpfs_mem_info(false);
1.1 jmmv 437: if (freepages < TMPFS_PAGES_RESERVED)
438: freepages = 0;
439: else
440: freepages -= TMPFS_PAGES_RESERVED;
441:
442: return MIN(tmp->tm_pages_max, freepages + tmp->tm_pages_used);
443: }
444:
1.6 jmmv 445: /* Returns the available space for the given file system. */
1.30 ad 446: #define TMPFS_PAGES_AVAIL(tmp) \
447: ((ssize_t)(TMPFS_PAGES_MAX(tmp) - (tmp)->tm_pages_used))
1.1 jmmv 448:
449: /* --------------------------------------------------------------------- */
450:
451: /*
452: * Macros/functions to convert from generic data structures to tmpfs
453: * specific ones.
454: */
455:
1.16 perry 456: static __inline
1.1 jmmv 457: struct tmpfs_mount *
458: VFS_TO_TMPFS(struct mount *mp)
459: {
460: struct tmpfs_mount *tmp;
461:
1.14 christos 462: #ifdef KASSERT
1.1 jmmv 463: KASSERT((mp) != NULL && (mp)->mnt_data != NULL);
1.14 christos 464: #endif
1.1 jmmv 465: tmp = (struct tmpfs_mount *)(mp)->mnt_data;
466: return tmp;
467: }
468:
1.20 yamt 469: #endif /* defined(_KERNEL) */
470:
1.16 perry 471: static __inline
1.1 jmmv 472: struct tmpfs_node *
473: VP_TO_TMPFS_NODE(struct vnode *vp)
474: {
475: struct tmpfs_node *node;
476:
1.14 christos 477: #ifdef KASSERT
1.1 jmmv 478: KASSERT((vp) != NULL && (vp)->v_data != NULL);
1.14 christos 479: #endif
1.1 jmmv 480: node = (struct tmpfs_node *)vp->v_data;
481: return node;
482: }
483:
1.20 yamt 484: #if defined(_KERNEL)
485:
1.16 perry 486: static __inline
1.1 jmmv 487: struct tmpfs_node *
488: VP_TO_TMPFS_DIR(struct vnode *vp)
489: {
490: struct tmpfs_node *node;
491:
492: node = VP_TO_TMPFS_NODE(vp);
1.14 christos 493: #ifdef KASSERT
1.1 jmmv 494: TMPFS_VALIDATE_DIR(node);
1.14 christos 495: #endif
1.1 jmmv 496: return node;
497: }
498:
1.20 yamt 499: #endif /* defined(_KERNEL) */
1.10 christos 500: #endif /* _FS_TMPFS_TMPFS_H_ */
CVSweb <webmaster@jp.NetBSD.org>