1 // SPDX-License-Identifier: CDDL-1.0
2 /*
3 * This file and its contents are supplied under the terms of the
4 * Common Development and Distribution License ("CDDL"), version 1.0.
5 * You may only use this file in accordance with the terms of version
6 * 1.0 of the CDDL.
7 *
8 * A full copy of the text of the CDDL should have accompanied this
9 * source. A copy of the CDDL is also available via the Internet at
10 * https://opensource.org/license/CDDL-1.0.
11 */
12
13 /*
14 * Copyright 2015 Nexenta Systems, Inc. All rights reserved.
15 * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
16 * Copyright (c) 2014, 2021 by Delphix. All rights reserved.
17 * Copyright 2016 Igor Kozhukhov <ikozhukhov@gmail.com>
18 * Copyright 2017 RackTop Systems.
19 * Copyright (c) 2018 Datto Inc.
20 * Copyright 2018 OmniOS Community Edition (OmniOSce) Association.
21 */
22
23 #include <dirent.h>
24 #include <dlfcn.h>
25 #include <errno.h>
26 #include <fcntl.h>
27 #include <libgen.h>
28 #include <libintl.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <unistd.h>
33 #include <zone.h>
34 #include <sys/mntent.h>
35 #include <sys/mount.h>
36 #include <sys/stat.h>
37 #include <sys/vfs.h>
38 #include <sys/dsl_crypt.h>
39 #include <libzfs.h>
40
41 #include "../../libzfs_impl.h"
42
43 #define ZS_COMMENT 0x00000000 /* comment */
44 #define ZS_ZFSUTIL 0x00000001 /* caller is zfs(8) */
45
46 typedef struct option_map {
47 const char *name;
48 unsigned long mntmask;
49 unsigned long zfsmask;
50 } option_map_t;
51
52 static const option_map_t option_map[] = {
53 /* Canonicalized filesystem independent options from mount(8) */
54 { MNTOPT_NOAUTO, MS_COMMENT, ZS_COMMENT },
55 { MNTOPT_DEFAULTS, MS_COMMENT, ZS_COMMENT },
56 { MNTOPT_NODEVICES, MS_NODEV, ZS_COMMENT },
57 { MNTOPT_DEVICES, MS_COMMENT, ZS_COMMENT },
58 { MNTOPT_DIRSYNC, MS_DIRSYNC, ZS_COMMENT },
59 { MNTOPT_NOEXEC, MS_NOEXEC, ZS_COMMENT },
60 { MNTOPT_EXEC, MS_COMMENT, ZS_COMMENT },
61 { MNTOPT_GROUP, MS_GROUP, ZS_COMMENT },
62 { MNTOPT_NETDEV, MS_COMMENT, ZS_COMMENT },
63 { MNTOPT_NOFAIL, MS_COMMENT, ZS_COMMENT },
64 { MNTOPT_NOSUID, MS_NOSUID, ZS_COMMENT },
65 { MNTOPT_SUID, MS_COMMENT, ZS_COMMENT },
66 { MNTOPT_OWNER, MS_OWNER, ZS_COMMENT },
67 { MNTOPT_REMOUNT, MS_REMOUNT, ZS_COMMENT },
68 { MNTOPT_RO, MS_RDONLY, ZS_COMMENT },
69 { MNTOPT_RW, MS_COMMENT, ZS_COMMENT },
70 { MNTOPT_SYNC, MS_SYNCHRONOUS, ZS_COMMENT },
71 { MNTOPT_USER, MS_USERS, ZS_COMMENT },
72 { MNTOPT_USERS, MS_USERS, ZS_COMMENT },
73 /* acl flags passed with util-linux-2.24 mount command */
74 { MNTOPT_ACL, MS_POSIXACL, ZS_COMMENT },
75 { MNTOPT_NOACL, MS_COMMENT, ZS_COMMENT },
76 { MNTOPT_POSIXACL, MS_POSIXACL, ZS_COMMENT },
77 /*
78 * Case sensitive options are just listed here to silently
79 * ignore the error if passed with zfs mount command.
80 */
81 { MNTOPT_CASESENSITIVE, MS_COMMENT, ZS_COMMENT },
82 { MNTOPT_CASEINSENSITIVE, MS_COMMENT, ZS_COMMENT },
83 { MNTOPT_CASEMIXED, MS_COMMENT, ZS_COMMENT },
84 #ifdef MS_NOATIME
85 { MNTOPT_NOATIME, MS_NOATIME, ZS_COMMENT },
86 { MNTOPT_ATIME, MS_COMMENT, ZS_COMMENT },
87 #endif
88 #ifdef MS_NODIRATIME
89 { MNTOPT_NODIRATIME, MS_NODIRATIME, ZS_COMMENT },
90 { MNTOPT_DIRATIME, MS_COMMENT, ZS_COMMENT },
91 #endif
92 #ifdef MS_RELATIME
93 { MNTOPT_RELATIME, MS_RELATIME, ZS_COMMENT },
94 { MNTOPT_NORELATIME, MS_COMMENT, ZS_COMMENT },
95 #endif
96 #ifdef MS_STRICTATIME
97 { MNTOPT_STRICTATIME, MS_STRICTATIME, ZS_COMMENT },
98 { MNTOPT_NOSTRICTATIME, MS_COMMENT, ZS_COMMENT },
99 #endif
100 #ifdef MS_LAZYTIME
101 { MNTOPT_LAZYTIME, MS_LAZYTIME, ZS_COMMENT },
102 #endif
103 { MNTOPT_CONTEXT, MS_COMMENT, ZS_COMMENT },
104 { MNTOPT_FSCONTEXT, MS_COMMENT, ZS_COMMENT },
105 { MNTOPT_DEFCONTEXT, MS_COMMENT, ZS_COMMENT },
106 { MNTOPT_ROOTCONTEXT, MS_COMMENT, ZS_COMMENT },
107 #ifdef MS_I_VERSION
108 { MNTOPT_IVERSION, MS_I_VERSION, ZS_COMMENT },
109 #endif
110 #ifdef MS_MANDLOCK
111 { MNTOPT_NBMAND, MS_MANDLOCK, ZS_COMMENT },
112 { MNTOPT_NONBMAND, MS_COMMENT, ZS_COMMENT },
113 #endif
114 /* Valid options not found in mount(8) */
115 { MNTOPT_BIND, MS_BIND, ZS_COMMENT },
116 #ifdef MS_REC
117 { MNTOPT_RBIND, MS_BIND|MS_REC, ZS_COMMENT },
118 #endif
119 { MNTOPT_COMMENT, MS_COMMENT, ZS_COMMENT },
120 #ifdef MS_NOSUB
121 { MNTOPT_NOSUB, MS_NOSUB, ZS_COMMENT },
122 #endif
123 #ifdef MS_SILENT
124 { MNTOPT_QUIET, MS_SILENT, ZS_COMMENT },
125 #endif
126 /* Custom zfs options */
127 { MNTOPT_XATTR, MS_COMMENT, ZS_COMMENT },
128 { MNTOPT_NOXATTR, MS_COMMENT, ZS_COMMENT },
129 { MNTOPT_ZFSUTIL, MS_COMMENT, ZS_ZFSUTIL },
130 { NULL, 0, 0 } };
131
132 /*
133 * Break the mount option in to a name/value pair. The name is
134 * validated against the option map and mount flags set accordingly.
135 */
136 static int
parse_option(char * mntopt,unsigned long * mntflags,unsigned long * zfsflags,int sloppy)137 parse_option(char *mntopt, unsigned long *mntflags,
138 unsigned long *zfsflags, int sloppy)
139 {
140 const option_map_t *opt;
141 char *ptr, *name, *value = NULL;
142 int error = 0;
143
144 name = strdup(mntopt);
145 if (name == NULL)
146 return (ENOMEM);
147
148 for (ptr = name; ptr && *ptr; ptr++) {
149 if (*ptr == '=') {
150 *ptr = '\0';
151 value = ptr+1;
152 VERIFY3P(value, !=, NULL);
153 break;
154 }
155 }
156
157 for (opt = option_map; opt->name != NULL; opt++) {
158 if (strncmp(name, opt->name, strlen(name)) == 0) {
159 *mntflags |= opt->mntmask;
160 *zfsflags |= opt->zfsmask;
161 error = 0;
162 goto out;
163 }
164 }
165
166 if (!sloppy)
167 error = ENOENT;
168 out:
169 /* If required further process on the value may be done here */
170 free(name);
171 return (error);
172 }
173
174 /*
175 * Translate the mount option string in to MS_* mount flags for the
176 * kernel vfs. When sloppy is non-zero unknown options will be ignored
177 * otherwise they are considered fatal are copied in to badopt.
178 */
179 int
zfs_parse_mount_options(const char * mntopts,unsigned long * mntflags,unsigned long * zfsflags,int sloppy,char * badopt,char * mtabopt)180 zfs_parse_mount_options(const char *mntopts, unsigned long *mntflags,
181 unsigned long *zfsflags, int sloppy, char *badopt, char *mtabopt)
182 {
183 int error = 0, quote = 0, flag = 0, count = 0;
184 char *ptr, *opt, *opts;
185
186 opts = strdup(mntopts);
187 if (opts == NULL)
188 return (ENOMEM);
189
190 *mntflags = 0;
191 opt = NULL;
192
193 /*
194 * Scan through all mount options which must be comma delimited.
195 * We must be careful to notice regions which are double quoted
196 * and skip commas in these regions. Each option is then checked
197 * to determine if it is a known option.
198 */
199 for (ptr = opts; ptr && !flag; ptr++) {
200 if (opt == NULL)
201 opt = ptr;
202
203 if (*ptr == '"')
204 quote = !quote;
205
206 if (quote)
207 continue;
208
209 if (*ptr == '\0')
210 flag = 1;
211
212 if ((*ptr == ',') || (*ptr == '\0')) {
213 *ptr = '\0';
214
215 error = parse_option(opt, mntflags, zfsflags, sloppy);
216 if (error) {
217 strcpy(badopt, opt);
218 goto out;
219
220 }
221
222 if (!(*mntflags & MS_REMOUNT) &&
223 !(*zfsflags & ZS_ZFSUTIL) &&
224 mtabopt != NULL) {
225 if (count > 0)
226 strlcat(mtabopt, ",", MNT_LINE_MAX);
227
228 strlcat(mtabopt, opt, MNT_LINE_MAX);
229 count++;
230 }
231
232 opt = NULL;
233 }
234 }
235
236 out:
237 free(opts);
238 return (error);
239 }
240
241 static void
append_mntopt(const char * name,const char * val,char * mntopts,char * mtabopt,boolean_t quote)242 append_mntopt(const char *name, const char *val, char *mntopts,
243 char *mtabopt, boolean_t quote)
244 {
245 char tmp[MNT_LINE_MAX];
246
247 snprintf(tmp, MNT_LINE_MAX, quote ? ",%s=\"%s\"" : ",%s=%s", name, val);
248
249 if (mntopts)
250 strlcat(mntopts, tmp, MNT_LINE_MAX);
251
252 if (mtabopt)
253 strlcat(mtabopt, tmp, MNT_LINE_MAX);
254 }
255
256 static void
zfs_selinux_setcontext(zfs_handle_t * zhp,zfs_prop_t zpt,const char * name,char * mntopts,char * mtabopt)257 zfs_selinux_setcontext(zfs_handle_t *zhp, zfs_prop_t zpt, const char *name,
258 char *mntopts, char *mtabopt)
259 {
260 char context[ZFS_MAXPROPLEN];
261
262 if (zfs_prop_get(zhp, zpt, context, sizeof (context),
263 NULL, NULL, 0, B_FALSE) == 0) {
264 if (strcmp(context, "none") != 0)
265 append_mntopt(name, context, mntopts, mtabopt, B_TRUE);
266 }
267 }
268
269 void
zfs_adjust_mount_options(zfs_handle_t * zhp,const char * mntpoint,char * mntopts,char * mtabopt)270 zfs_adjust_mount_options(zfs_handle_t *zhp, const char *mntpoint,
271 char *mntopts, char *mtabopt)
272 {
273 char prop[ZFS_MAXPROPLEN];
274
275 /*
276 * Checks to see if the ZFS_PROP_SELINUX_CONTEXT exists
277 * if it does, create a tmp variable in case it's needed
278 * checks to see if the selinux context is set to the default
279 * if it is, allow the setting of the other context properties
280 * this is needed because the 'context' property overrides others
281 * if it is not the default, set the 'context' property
282 */
283 if (zfs_prop_get(zhp, ZFS_PROP_SELINUX_CONTEXT, prop, sizeof (prop),
284 NULL, NULL, 0, B_FALSE) == 0) {
285 if (strcmp(prop, "none") == 0) {
286 zfs_selinux_setcontext(zhp, ZFS_PROP_SELINUX_FSCONTEXT,
287 MNTOPT_FSCONTEXT, mntopts, mtabopt);
288 zfs_selinux_setcontext(zhp, ZFS_PROP_SELINUX_DEFCONTEXT,
289 MNTOPT_DEFCONTEXT, mntopts, mtabopt);
290 zfs_selinux_setcontext(zhp,
291 ZFS_PROP_SELINUX_ROOTCONTEXT, MNTOPT_ROOTCONTEXT,
292 mntopts, mtabopt);
293 } else {
294 append_mntopt(MNTOPT_CONTEXT, prop,
295 mntopts, mtabopt, B_TRUE);
296 }
297 }
298
299 /* A hint used to determine an auto-mounted snapshot mount point */
300 append_mntopt(MNTOPT_MNTPOINT, mntpoint, mntopts, NULL, B_FALSE);
301 }
302
303 /*
304 * By default the filesystem by preparing the mount options (i.e. parsing
305 * some flags from the "opts" parameter into the "flags" parameter) and then
306 * directly calling the system call mount(2). We don't need the mount utility
307 * or update /etc/mtab, because this is a symlink on all modern systems.
308 *
309 * If the environment variable ZFS_MOUNT_HELPER is set, we fall back to the
310 * previous behavior:
311 * The filesystem is mounted by invoking the system mount utility rather
312 * than by the system call mount(2). This ensures that the /etc/mtab
313 * file is correctly locked for the update. Performing our own locking
314 * and /etc/mtab update requires making an unsafe assumption about how
315 * the mount utility performs its locking. Unfortunately, this also means
316 * in the case of a mount failure we do not have the exact errno. We must
317 * make due with return value from the mount process.
318 */
319 int
do_mount(zfs_handle_t * zhp,const char * mntpt,const char * opts,int flags)320 do_mount(zfs_handle_t *zhp, const char *mntpt, const char *opts, int flags)
321 {
322 const char *src = zfs_get_name(zhp);
323 int error = 0;
324
325 if (!libzfs_envvar_is_set("ZFS_MOUNT_HELPER")) {
326 char badopt[MNT_LINE_MAX] = {0};
327 unsigned long mntflags = flags, zfsflags = 0;
328 char myopts[MNT_LINE_MAX] = {0};
329
330 if (zfs_parse_mount_options(opts, &mntflags,
331 &zfsflags, 0, badopt, NULL)) {
332 return (EINVAL);
333 }
334 strlcat(myopts, opts, MNT_LINE_MAX);
335 zfs_adjust_mount_options(zhp, mntpt, myopts, NULL);
336 if (mount(src, mntpt, MNTTYPE_ZFS, mntflags, myopts)) {
337 return (errno);
338 }
339 } else {
340 char *argv[9] = {
341 (char *)"/bin/mount",
342 (char *)"--no-canonicalize",
343 (char *)"-t", (char *)MNTTYPE_ZFS,
344 (char *)"-o", (char *)opts,
345 (char *)src,
346 (char *)mntpt,
347 (char *)NULL };
348
349 /* Return only the most critical mount error */
350 error = libzfs_run_process(argv[0], argv,
351 STDOUT_VERBOSE|STDERR_VERBOSE);
352 if (error) {
353 if (error & MOUNT_FILEIO) {
354 error = EIO;
355 } else if (error & MOUNT_USER) {
356 error = EINTR;
357 } else if (error & MOUNT_SOFTWARE) {
358 error = EPIPE;
359 } else if (error & MOUNT_BUSY) {
360 error = EBUSY;
361 } else if (error & MOUNT_SYSERR) {
362 error = EAGAIN;
363 } else if (error & MOUNT_USAGE) {
364 error = EINVAL;
365 } else
366 error = ENXIO; /* Generic error */
367 }
368 }
369
370 return (error);
371 }
372
373 int
do_unmount(zfs_handle_t * zhp,const char * mntpt,int flags)374 do_unmount(zfs_handle_t *zhp, const char *mntpt, int flags)
375 {
376 (void) zhp;
377
378 /*
379 * MS_CRYPT and MS_OVERLAY are libzfs-internal flags; strip them so they
380 * are never passed to umount2(2), which rejects unknown flag bits with
381 * EINVAL. MS_FORCE/MS_DETACH are real umount2(2) flags and kept.
382 */
383 flags &= ~(MS_CRYPT | MS_OVERLAY);
384
385 if (!libzfs_envvar_is_set("ZFS_MOUNT_HELPER")) {
386 int rv = umount2(mntpt, flags);
387
388 return (rv < 0 ? errno : 0);
389 }
390
391 char *argv[7] = {
392 (char *)"/bin/umount",
393 (char *)"-t", (char *)MNTTYPE_ZFS,
394 NULL, NULL, NULL, NULL };
395 int rc, count = 3;
396
397 if (flags & MS_FORCE)
398 argv[count++] = (char *)"-f";
399
400 if (flags & MS_DETACH)
401 argv[count++] = (char *)"-l";
402
403 argv[count] = (char *)mntpt;
404 rc = libzfs_run_process(argv[0], argv, STDOUT_VERBOSE|STDERR_VERBOSE);
405
406 if (rc == 0)
407 return (0);
408 if (rc > 0 && geteuid() != 0)
409 return (EPERM);
410 return (EINVAL);
411 }
412
413 #ifdef HAVE_MOUNT_SETATTR
414 /*
415 * Build a struct mount_attr for the changed namespace properties.
416 * Parallel to zfs_add_options() but produces mount_setattr(2) input
417 * instead of a mount options string.
418 */
419 static void
zfs_add_options_setattr(zfs_handle_t * zhp,struct mount_attr * attr,uint32_t nspflags)420 zfs_add_options_setattr(zfs_handle_t *zhp, struct mount_attr *attr,
421 uint32_t nspflags)
422 {
423 const char *source;
424
425 if (nspflags & ZFS_MNT_PROP_READONLY) {
426 if (getprop_uint64(zhp, ZFS_PROP_READONLY, &source))
427 attr->attr_set |= MOUNT_ATTR_RDONLY;
428 else
429 attr->attr_clr |= MOUNT_ATTR_RDONLY;
430 }
431 if (nspflags & ZFS_MNT_PROP_EXEC) {
432 if (getprop_uint64(zhp, ZFS_PROP_EXEC, &source))
433 attr->attr_clr |= MOUNT_ATTR_NOEXEC;
434 else
435 attr->attr_set |= MOUNT_ATTR_NOEXEC;
436 }
437 if (nspflags & ZFS_MNT_PROP_SETUID) {
438 if (getprop_uint64(zhp, ZFS_PROP_SETUID, &source))
439 attr->attr_clr |= MOUNT_ATTR_NOSUID;
440 else
441 attr->attr_set |= MOUNT_ATTR_NOSUID;
442 }
443 if (nspflags & ZFS_MNT_PROP_DEVICES) {
444 if (getprop_uint64(zhp, ZFS_PROP_DEVICES, &source))
445 attr->attr_clr |= MOUNT_ATTR_NODEV;
446 else
447 attr->attr_set |= MOUNT_ATTR_NODEV;
448 }
449 if (nspflags & (ZFS_MNT_PROP_ATIME | ZFS_MNT_PROP_RELATIME)) {
450 uint64_t atime = getprop_uint64(zhp, ZFS_PROP_ATIME, &source);
451 uint64_t relatime = getprop_uint64(zhp,
452 ZFS_PROP_RELATIME, &source);
453
454 attr->attr_clr |= MOUNT_ATTR__ATIME;
455 if (!atime)
456 attr->attr_set |= MOUNT_ATTR_NOATIME;
457 else if (relatime)
458 attr->attr_set |= MOUNT_ATTR_RELATIME;
459 else
460 attr->attr_set |= MOUNT_ATTR_STRICTATIME;
461 }
462 }
463 #endif /* HAVE_MOUNT_SETATTR */
464
465 /*
466 * Selectively update per-mount VFS flags for the changed namespace
467 * properties using mount_setattr(2). Unlike a full remount via mount(2),
468 * this only modifies the specified flags without resetting others --
469 * avoiding clobbering temporary mount flags set by the administrator.
470 *
471 * For non-legacy datasets, the single known mountpoint is used.
472 * For legacy datasets, /proc/mounts is iterated since legacy datasets
473 * can be mounted at multiple locations.
474 *
475 * Falls back to a full remount via zfs_mount() when mount_setattr(2)
476 * is not available (ENOSYS), except for legacy mounts where a full
477 * remount would clobber temporary flags.
478 */
479 int
zfs_mount_setattr(zfs_handle_t * zhp,uint32_t nspflags)480 zfs_mount_setattr(zfs_handle_t *zhp, uint32_t nspflags)
481 {
482 #ifdef HAVE_MOUNT_SETATTR
483 struct mount_attr attr = { 0 };
484 char mntpt_prop[ZFS_MAXPROPLEN];
485 boolean_t legacy = B_FALSE;
486 libzfs_handle_t *hdl = zhp->zfs_hdl;
487 FILE *mnttab;
488 struct mnttab entry;
489 int ret = 0;
490
491 if (!zfs_is_mountable_internal(zhp))
492 return (0);
493
494 zfs_add_options_setattr(zhp, &attr, nspflags);
495 if (attr.attr_set == 0 && attr.attr_clr == 0)
496 return (0);
497
498 if (zfs_prop_valid_for_type(ZFS_PROP_MOUNTPOINT, zhp->zfs_type,
499 B_FALSE)) {
500 verify(zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, mntpt_prop,
501 sizeof (mntpt_prop), NULL, NULL, 0, B_FALSE) == 0);
502 legacy = (strcmp(mntpt_prop, ZFS_MOUNTPOINT_LEGACY) == 0);
503 }
504
505 if (!legacy) {
506 char *mntpt = NULL;
507
508 if (!zfs_is_mounted(zhp, &mntpt))
509 return (0);
510
511 ret = mount_setattr(AT_FDCWD, mntpt, 0,
512 &attr, sizeof (attr));
513 free(mntpt);
514 if (ret != 0) {
515 if (errno == ENOSYS)
516 return (zfs_mount(zhp, MNTOPT_REMOUNT, 0));
517 return (zfs_error_fmt(hdl, EZFS_MOUNTFAILED,
518 dgettext(TEXT_DOMAIN, "cannot set mount "
519 "attributes for '%s'"), zhp->zfs_name));
520 }
521 return (0);
522 }
523
524 /* Legacy: iterate /proc/mounts for all mountpoints. */
525 if ((mnttab = fopen(MNTTAB, "re")) == NULL)
526 return (0);
527
528 while (getmntent(mnttab, &entry) == 0) {
529 if (strcmp(entry.mnt_fstype, MNTTYPE_ZFS) != 0)
530 continue;
531 if (strcmp(entry.mnt_special, zhp->zfs_name) != 0)
532 continue;
533
534 ret = mount_setattr(AT_FDCWD, entry.mnt_mountp, 0,
535 &attr, sizeof (attr));
536 if (ret != 0) {
537 if (errno == ENOSYS) {
538 ret = 0;
539 break;
540 }
541 ret = zfs_error_fmt(hdl, EZFS_MOUNTFAILED,
542 dgettext(TEXT_DOMAIN, "cannot set mount "
543 "attributes for '%s'"), zhp->zfs_name);
544 break;
545 }
546 }
547
548 (void) fclose(mnttab);
549 return (ret);
550 #else
551 (void) nspflags;
552 return (zfs_mount(zhp, MNTOPT_REMOUNT, 0));
553 #endif
554 }
555
556 int
zfs_mount_delegation_check(void)557 zfs_mount_delegation_check(void)
558 {
559 return ((geteuid() != 0) ? EACCES : 0);
560 }
561
562 /* Called from the tail end of zpool_disable_datasets() */
563 void
zpool_disable_datasets_os(zpool_handle_t * zhp,boolean_t force)564 zpool_disable_datasets_os(zpool_handle_t *zhp, boolean_t force)
565 {
566 (void) zhp, (void) force;
567 }
568
569 /* Called from the tail end of zfs_unmount() */
570 void
zpool_disable_volume_os(const char * name)571 zpool_disable_volume_os(const char *name)
572 {
573 (void) name;
574 }
575