1: /*-
2: * Copyright (c) 1980, 1989, 1993, 1994
3: * The Regents of the University of California. 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: * 1. Redistributions of source code must retain the above copyright
9: * notice, this list of conditions and the following disclaimer.
10: * 2. Redistributions in binary form must reproduce the above copyright
11: * notice, this list of conditions and the following disclaimer in the
12: * documentation and/or other materials provided with the distribution.
13: * 3. All advertising materials mentioning features or use of this software
14: * must display the following acknowledgement:
15: * This product includes software developed by the University of
16: * California, Berkeley and its contributors.
17: * 4. Neither the name of the University nor the names of its contributors
18: * may be used to endorse or promote products derived from this software
19: * without specific prior written permission.
20: *
21: * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22: * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24: * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25: * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27: * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28: * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29: * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30: * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31: * SUCH DAMAGE.
32: *
33: * @(#) Copyright (c) 1980, 1989, 1993, 1994 The Regents of the University of California. All rights reserved.
34: * @(#)mount.c 8.25 (Berkeley) 5/8/95
35: * $FreeBSD: src/sbin/mount/mount.c,v 1.39.2.3 2001/08/01 08:26:23 obrien Exp $
36: * $DragonFly: src/sbin/mount/mount.c,v 1.6 2004/02/06 22:11:48 joerg Exp $
37: */
38:
39: #include <sys/param.h>
40: #include <sys/mount.h>
41: #include <sys/stat.h>
42: #include <sys/wait.h>
43:
44: #include <err.h>
45: #include <errno.h>
46: #include <fstab.h>
47: #include <pwd.h>
48: #include <signal.h>
49: #include <stdio.h>
50: #include <stdlib.h>
51: #include <string.h>
52: #include <unistd.h>
53:
54: #include "extern.h"
55: #include "mntopts.h"
56: #include "pathnames.h"
57:
58: /* `meta' options */
59: #define MOUNT_META_OPTION_FSTAB "fstab"
60: #define MOUNT_META_OPTION_CURRENT "current"
61:
62: int debug, fstab_style, verbose;
63:
64: char *catopt(char *, const char *);
65: struct statfs
66: *getmntpt(const char *);
67: int hasopt(const char *, const char *);
68: int ismounted(struct fstab *, struct statfs *, int);
69: int isremountable(const char *);
70: void mangle(char *, int *, const char **);
71: char *update_options(char *, char *, int);
72: int mountfs(const char *, const char *, const char *,
73: int, const char *, const char *);
74: void remopt(char *, const char *);
75: void prmount(struct statfs *);
76: void putfsent(const struct statfs *);
77: void usage(void);
78: char *flags2opts(int);
79:
80: /* Map from mount options to printable formats. */
81: static struct opt {
82: int o_opt;
83: const char *o_name;
84: } optnames[] = {
85: { MNT_ASYNC, "asynchronous" },
86: { MNT_EXPORTED, "NFS exported" },
87: { MNT_LOCAL, "local" },
88: { MNT_NOATIME, "noatime" },
89: { MNT_NODEV, "nodev" },
90: { MNT_NOEXEC, "noexec" },
91: { MNT_NOSUID, "nosuid" },
92: { MNT_NOSYMFOLLOW, "nosymfollow" },
93: { MNT_QUOTA, "with quotas" },
94: { MNT_RDONLY, "read-only" },
95: { MNT_SYNCHRONOUS, "synchronous" },
96: { MNT_UNION, "union" },
97: { MNT_NOCLUSTERR, "noclusterr" },
98: { MNT_NOCLUSTERW, "noclusterw" },
99: { MNT_SUIDDIR, "suiddir" },
100: { MNT_SOFTDEP, "soft-updates" },
101: { 0, NULL }
102: };
103:
104: /*
105: * List of VFS types that can be remounted without becoming mounted on top
106: * of each other.
107: * XXX Is this list correct?
108: */
109: static const char *
110: remountable_fs_names[] = {
111: "ufs", "ffs", "ext2fs",
112: 0
113: };
114:
115: int
116: main(int argc, char **argv)
117: {
118: const char *mntfromname, **vfslist, *vfstype;
119: struct fstab *fs;
120: struct statfs *mntbuf;
121: FILE *mountdfp;
122: pid_t pid;
123: int all, ch, i, init_flags, mntsize, rval, have_fstab;
124: char *options;
125:
126: all = init_flags = 0;
127: options = NULL;
128: vfslist = NULL;
129: vfstype = "ufs";
130: while ((ch = getopt(argc, argv, "adfo:prwt:uv")) != -1)
131: switch (ch) {
132: case 'a':
133: all = 1;
134: break;
135: case 'd':
136: debug = 1;
137: break;
138: case 'f':
139: init_flags |= MNT_FORCE;
140: break;
141: case 'o':
142: if (*optarg)
143: options = catopt(options, optarg);
144: break;
145: case 'p':
146: fstab_style = 1;
147: verbose = 1;
148: break;
149: case 'r':
150: options = catopt(options, "ro");
151: break;
152: case 't':
153: if (vfslist != NULL)
154: errx(1, "only one -t option may be specified");
155: vfslist = makevfslist(optarg);
156: vfstype = optarg;
157: break;
158: case 'u':
159: init_flags |= MNT_UPDATE;
160: break;
161: case 'v':
162: verbose = 1;
163: break;
164: case 'w':
165: options = catopt(options, "noro");
166: break;
167: case '?':
168: default:
169: usage();
170: /* NOTREACHED */
171: }
172: argc -= optind;
173: argv += optind;
174:
175: #define BADTYPE(type) \
176: (strcmp(type, FSTAB_RO) && \
177: strcmp(type, FSTAB_RW) && strcmp(type, FSTAB_RQ))
178:
179: rval = 0;
180: switch (argc) {
181: case 0:
182: if ((mntsize = getmntinfo(&mntbuf, MNT_NOWAIT)) == 0)
183: err(1, "getmntinfo");
184: if (all) {
185: while ((fs = getfsent()) != NULL) {
186: if (BADTYPE(fs->fs_type))
187: continue;
188: if (checkvfsname(fs->fs_vfstype, vfslist))
189: continue;
190: if (hasopt(fs->fs_mntops, "noauto"))
191: continue;
192: if (!(init_flags & MNT_UPDATE) &&
193: ismounted(fs, mntbuf, mntsize))
194: continue;
195: options = update_options(options,
196: fs->fs_mntops, mntbuf->f_flags);
197: if (mountfs(fs->fs_vfstype, fs->fs_spec,
198: fs->fs_file, init_flags, options,
199: fs->fs_mntops))
200: rval = 1;
201: }
202: } else if (fstab_style) {
203: for (i = 0; i < mntsize; i++) {
204: if (checkvfsname(mntbuf[i].f_fstypename, vfslist))
205: continue;
206: putfsent(&mntbuf[i]);
207: }
208: } else {
209: for (i = 0; i < mntsize; i++) {
210: if (checkvfsname(mntbuf[i].f_fstypename,
211: vfslist))
212: continue;
213: prmount(&mntbuf[i]);
214: }
215: }
216: exit(rval);
217: case 1:
218: if (vfslist != NULL)
219: usage();
220:
221: if (init_flags & MNT_UPDATE) {
222: mntfromname = NULL;
223: have_fstab = 0;
224: if ((mntbuf = getmntpt(*argv)) == NULL)
225: errx(1, "not currently mounted %s", *argv);
226: /*
227: * Only get the mntflags from fstab if both mntpoint
228: * and mntspec are identical. Also handle the special
229: * case where just '/' is mounted and 'spec' is not
230: * identical with the one from fstab ('/dev' is missing
231: * in the spec-string at boot-time).
232: */
233: if ((fs = getfsfile(mntbuf->f_mntonname)) != NULL) {
234: if (strcmp(fs->fs_spec,
235: mntbuf->f_mntfromname) == 0 &&
236: strcmp(fs->fs_file,
237: mntbuf->f_mntonname) == 0) {
238: have_fstab = 1;
239: mntfromname = mntbuf->f_mntfromname;
240: } else if (argv[0][0] == '/' &&
241: argv[0][1] == '\0') {
242: fs = getfsfile("/");
243: have_fstab = 1;
244: mntfromname = fs->fs_spec;
245: }
246: }
247: if (have_fstab) {
248: options = update_options(options, fs->fs_mntops,
249: mntbuf->f_flags);
250: } else {
251: mntfromname = mntbuf->f_mntfromname;
252: options = update_options(options, NULL,
253: mntbuf->f_flags);
254: }
255: rval = mountfs(mntbuf->f_fstypename, mntfromname,
256: mntbuf->f_mntonname, init_flags, options, 0);
257: break;
258: }
259: rmslashes(*argv, *argv);
260: if ((fs = getfsfile(*argv)) == NULL &&
261: (fs = getfsspec(*argv)) == NULL)
262: errx(1, "%s: unknown special file or file system",
263: *argv);
264: if (BADTYPE(fs->fs_type))
265: errx(1, "%s has unknown file system type",
266: *argv);
267: rval = mountfs(fs->fs_vfstype, fs->fs_spec, fs->fs_file,
268: init_flags, options, fs->fs_mntops);
269: break;
270: case 2:
271: /*
272: * If -t flag has not been specified, the path cannot be
273: * found, spec contains either a ':' or a '@', and the
274: * spec is not a file with those characters, then assume
275: * that an NFS filesystem is being specified ala Sun.
276: */
277: if (vfslist == NULL && strpbrk(argv[0], ":@") != NULL &&
278: access(argv[0], 0) == -1)
279: vfstype = "nfs";
280: rval = mountfs(vfstype,
281: argv[0], argv[1], init_flags, options, NULL);
282: break;
283: default:
284: usage();
285: /* NOTREACHED */
286: }
287:
288: /*
289: * If the mount was successfully, and done by root, tell mountd the
290: * good news. Pid checks are probably unnecessary, but don't hurt.
291: */
292: if (rval == 0 && getuid() == 0 &&
293: (mountdfp = fopen(_PATH_MOUNTDPID, "r")) != NULL) {
294: if (fscanf(mountdfp, "%d", &pid) == 1 &&
295: pid > 0 && kill(pid, SIGHUP) == -1 && errno != ESRCH)
296: err(1, "signal mountd");
297: (void)fclose(mountdfp);
298: }
299:
300: exit(rval);
301: }
302:
303: int
304: ismounted(struct fstab *fs, struct statfs *mntbuf, int mntsize)
305: {
306: int i;
307:
308: if (fs->fs_file[0] == '/' && fs->fs_file[1] == '\0')
309: /* the root file system can always be remounted */
310: return (0);
311:
312: for (i = mntsize - 1; i >= 0; --i)
313: if (strcmp(fs->fs_file, mntbuf[i].f_mntonname) == 0 &&
314: (!isremountable(fs->fs_vfstype) ||
315: strcmp(fs->fs_spec, mntbuf[i].f_mntfromname) == 0))
316: return (1);
317: return (0);
318: }
319:
320: int
321: isremountable(const char *vfsname)
322: {
323: const char **cp;
324:
325: for (cp = remountable_fs_names; *cp; cp++)
326: if (strcmp(*cp, vfsname) == 0)
327: return (1);
328: return (0);
329: }
330:
331: int
332: hasopt(const char *mntopts, const char *option)
333: {
334: int negative, found;
335: char *opt, *optbuf;
336:
337: if (option[0] == 'n' && option[1] == 'o') {
338: negative = 1;
339: option += 2;
340: } else
341: negative = 0;
342: optbuf = strdup(mntopts);
343: found = 0;
344: for (opt = optbuf; (opt = strtok(opt, ",")) != NULL; opt = NULL) {
345: if (opt[0] == 'n' && opt[1] == 'o') {
346: if (!strcasecmp(opt + 2, option))
347: found = negative;
348: } else if (!strcasecmp(opt, option))
349: found = !negative;
350: }
351: free(optbuf);
352: return (found);
353: }
354:
355: int
356: mountfs(const char *vfstype, const char *spec, const char *name, int flags,
357: const char *options, const char *mntopts)
358: {
359: /* List of directories containing mount_xxx subcommands. */
360: static const char *edirs[] = {
361: _PATH_SBIN,
362: _PATH_USRSBIN,
363: NULL
364: };
365: const char *argv[100], **edir;
366: struct statfs sf;
367: pid_t pid;
368: int argc, i, status;
369: char *optbuf, execname[MAXPATHLEN + 1], mntpath[MAXPATHLEN];
370:
371: #if __GNUC__
372: (void)&optbuf;
373: (void)&name;
374: #endif
375:
376: /* resolve the mountpoint with realpath(3) */
377: (void)checkpath(name, mntpath);
378: name = mntpath;
379:
380: if (mntopts == NULL)
381: mntopts = "";
382: if (options == NULL) {
383: if (*mntopts == '\0') {
384: options = "rw";
385: } else {
386: options = mntopts;
387: mntopts = "";
388: }
389: }
390: optbuf = catopt(strdup(mntopts), options);
391:
392: if (strcmp(name, "/") == 0)
393: flags |= MNT_UPDATE;
394: if (flags & MNT_FORCE)
395: optbuf = catopt(optbuf, "force");
396: if (flags & MNT_RDONLY)
397: optbuf = catopt(optbuf, "ro");
398: /*
399: * XXX
400: * The mount_mfs (newfs) command uses -o to select the
401: * optimization mode. We don't pass the default "-o rw"
402: * for that reason.
403: */
404: if (flags & MNT_UPDATE)
405: optbuf = catopt(optbuf, "update");
406:
407: argc = 0;
408: argv[argc++] = vfstype;
409: mangle(optbuf, &argc, argv);
410: argv[argc++] = spec;
411: argv[argc++] = name;
412: argv[argc] = NULL;
413:
414: if (debug) {
415: (void)printf("exec: mount_%s", vfstype);
416: for (i = 1; i < argc; i++)
417: (void)printf(" %s", argv[i]);
418: (void)printf("\n");
419: return (0);
420: }
421:
422: switch (pid = fork()) {
423: case -1: /* Error. */
424: warn("fork");
425: free(optbuf);
426: return (1);
427: case 0: /* Child. */
428: if (strcmp(vfstype, "ufs") == 0)
429: exit(mount_ufs(argc, (char * const *) argv));
430:
431: /* Go find an executable. */
432: for (edir = edirs; *edir; edir++) {
433: (void)snprintf(execname,
434: sizeof(execname), "%s/mount_%s", *edir, vfstype);
435: execv(execname, (char * const *)argv);
436: }
437: if (errno == ENOENT) {
438: int len = 0;
439: char *cp;
440: for (edir = edirs; *edir; edir++)
441: len += strlen(*edir) + 2; /* ", " */
442: if ((cp = malloc(len)) == NULL)
443: errx(1, "malloc failed");
444: cp[0] = '\0';
445: for (edir = edirs; *edir; edir++) {
446: strcat(cp, *edir);
447: if (edir[1] != NULL)
448: strcat(cp, ", ");
449: }
450: warn("exec mount_%s not found in %s", vfstype, cp);
451: }
452: exit(1);
453: /* NOTREACHED */
454: default: /* Parent. */
455: free(optbuf);
456:
457: if (waitpid(pid, &status, 0) < 0) {
458: warn("waitpid");
459: return (1);
460: }
461:
462: if (WIFEXITED(status)) {
463: if (WEXITSTATUS(status) != 0)
464: return (WEXITSTATUS(status));
465: } else if (WIFSIGNALED(status)) {
466: warnx("%s: %s", name, sys_siglist[WTERMSIG(status)]);
467: return (1);
468: }
469:
470: if (verbose) {
471: if (statfs(name, &sf) < 0) {
472: warn("statfs %s", name);
473: return (1);
474: }
475: if (fstab_style)
476: putfsent(&sf);
477: else
478: prmount(&sf);
479: }
480: break;
481: }
482:
483: return (0);
484: }
485:
486: void
487: prmount(struct statfs *sfp)
488: {
489: int flags;
490: struct opt *o;
491: struct passwd *pw;
492:
493: (void)printf("%s on %s (%s", sfp->f_mntfromname, sfp->f_mntonname,
494: sfp->f_fstypename);
495:
496: flags = sfp->f_flags & MNT_VISFLAGMASK;
497: for (o = optnames; flags && o->o_opt; o++)
498: if (flags & o->o_opt) {
499: (void)printf(", %s", o->o_name);
500: flags &= ~o->o_opt;
501: }
502: if (sfp->f_owner) {
503: (void)printf(", mounted by ");
504: if ((pw = getpwuid(sfp->f_owner)) != NULL)
505: (void)printf("%s", pw->pw_name);
506: else
507: (void)printf("%d", sfp->f_owner);
508: }
509: if (verbose) {
510: if (sfp->f_syncwrites != 0 || sfp->f_asyncwrites != 0)
511: (void)printf(", writes: sync %ld async %ld",
512: sfp->f_syncwrites, sfp->f_asyncwrites);
513: if (sfp->f_syncreads != 0 || sfp->f_asyncreads != 0)
514: (void)printf(", reads: sync %ld async %ld",
515: sfp->f_syncreads, sfp->f_asyncreads);
516: }
517: (void)printf(")\n");
518: }
519:
520: struct statfs *
521: getmntpt(const char *name)
522: {
523: struct statfs *mntbuf;
524: int i, mntsize;
525:
526: mntsize = getmntinfo(&mntbuf, MNT_NOWAIT);
527: for (i = mntsize - 1; i >= 0; i--) {
528: if (strcmp(mntbuf[i].f_mntfromname, name) == 0 ||
529: strcmp(mntbuf[i].f_mntonname, name) == 0)
530: return (&mntbuf[i]);
531: }
532: return (NULL);
533: }
534:
535: char *
536: catopt(char *s0, const char *s1)
537: {
538: size_t i;
539: char *cp;
540:
541: if (s1 == NULL || *s1 == '\0')
542: return s0;
543:
544: if (s0 && *s0) {
545: i = strlen(s0) + strlen(s1) + 1 + 1;
546: if ((cp = malloc(i)) == NULL)
547: errx(1, "malloc failed");
548: (void)snprintf(cp, i, "%s,%s", s0, s1);
549: } else
550: cp = strdup(s1);
551:
552: if (s0)
553: free(s0);
554: return (cp);
555: }
556:
557: void
558: mangle(char *options, int *argcp, const char **argv)
559: {
560: char *p, *s;
561: int argc;
562:
563: argc = *argcp;
564: for (s = options; (p = strsep(&s, ",")) != NULL;)
565: if (*p != '\0') {
566: if (*p == '-') {
567: argv[argc++] = p;
568: p = strchr(p, '=');
569: if (p) {
570: *p = '\0';
571: argv[argc++] = p+1;
572: }
573: } else if (strcmp(p, "rw") != 0) {
574: argv[argc++] = "-o";
575: argv[argc++] = p;
576: }
577: }
578:
579: *argcp = argc;
580: }
581:
582:
583: char *
584: update_options(char *opts, char *fstab, int curflags)
585: {
586: char *o, *p;
587: char *cur;
588: char *expopt, *newopt, *tmpopt;
589:
590: if (opts == NULL)
591: return strdup("");
592:
593: /* remove meta options from list */
594: remopt(fstab, MOUNT_META_OPTION_FSTAB);
595: remopt(fstab, MOUNT_META_OPTION_CURRENT);
596: cur = flags2opts(curflags);
597:
598: /*
599: * Expand all meta-options passed to us first.
600: */
601: expopt = NULL;
602: for (p = opts; (o = strsep(&p, ",")) != NULL;) {
603: if (strcmp(MOUNT_META_OPTION_FSTAB, o) == 0)
604: expopt = catopt(expopt, fstab);
605: else if (strcmp(MOUNT_META_OPTION_CURRENT, o) == 0)
606: expopt = catopt(expopt, cur);
607: else
608: expopt = catopt(expopt, o);
609: }
610: free(cur);
611: free(opts);
612:
613: /*
614: * Remove previous contradictory arguments. Given option "foo" we
615: * remove all the "nofoo" options. Given "nofoo" we remove "nonofoo"
616: * and "foo" - so we can deal with possible options like "notice".
617: */
618: newopt = NULL;
619: for (p = expopt; (o = strsep(&p, ",")) != NULL;) {
620: if ((tmpopt = malloc( strlen(o) + 2 + 1 )) == NULL)
621: errx(1, "malloc failed");
622:
623: strcpy(tmpopt, "no");
624: strcat(tmpopt, o);
625: remopt(newopt, tmpopt);
626: free(tmpopt);
627:
628: if (strncmp("no", o, 2) == 0)
629: remopt(newopt, o+2);
630:
631: newopt = catopt(newopt, o);
632: }
633: free(expopt);
634:
635: return newopt;
636: }
637:
638: void
639: remopt(char *string, const char *opt)
640: {
641: char *o, *p, *r;
642:
643: if (string == NULL || *string == '\0' || opt == NULL || *opt == '\0')
644: return;
645:
646: r = string;
647:
648: for (p = string; (o = strsep(&p, ",")) != NULL;) {
649: if (strcmp(opt, o) != 0) {
650: if (*r == ',' && *o != '\0')
651: r++;
652: while ((*r++ = *o++) != '\0')
653: ;
654: *--r = ',';
655: }
656: }
657: *r = '\0';
658: }
659:
660: void
661: usage(void)
662: {
663:
664: (void)fprintf(stderr, "%s\n%s\n%s\n",
665: "usage: mount [-dfpruvw] [-o options] [-t ufs | external_type] special node",
666: " mount [-adfpruvw] [-o options] [-t ufs | external_type]",
667: " mount [-dfpruvw] special | node");
668: exit(1);
669: }
670:
671: void
672: putfsent(const struct statfs *ent)
673: {
674: struct fstab *fst;
675: char *opts;
676:
677: opts = flags2opts(ent->f_flags);
678: printf("%s\t%s\t%s %s", ent->f_mntfromname, ent->f_mntonname,
679: ent->f_fstypename, opts);
680: free(opts);
681:
682: if ((fst = getfsspec(ent->f_mntfromname)))
683: printf("\t%u %u\n", fst->fs_freq, fst->fs_passno);
684: else if ((fst = getfsfile(ent->f_mntonname)))
685: printf("\t%u %u\n", fst->fs_freq, fst->fs_passno);
686: else if (strcmp(ent->f_fstypename, "ufs") == 0) {
687: if (strcmp(ent->f_mntonname, "/") == 0)
688: printf("\t1 1\n");
689: else
690: printf("\t2 2\n");
691: } else
692: printf("\t0 0\n");
693: }
694:
695:
696: char *
697: flags2opts(int flags)
698: {
699: char *res;
700:
701: res = NULL;
702:
703: res = catopt(res, (flags & MNT_RDONLY) ? "ro" : "rw");
704:
705: if (flags & MNT_SYNCHRONOUS) res = catopt(res, "sync");
706: if (flags & MNT_NOEXEC) res = catopt(res, "noexec");
707: if (flags & MNT_NOSUID) res = catopt(res, "nosuid");
708: if (flags & MNT_NODEV) res = catopt(res, "nodev");
709: if (flags & MNT_UNION) res = catopt(res, "union");
710: if (flags & MNT_ASYNC) res = catopt(res, "async");
711: if (flags & MNT_NOATIME) res = catopt(res, "noatime");
712: if (flags & MNT_NOCLUSTERR) res = catopt(res, "noclusterr");
713: if (flags & MNT_NOCLUSTERW) res = catopt(res, "noclusterw");
714: if (flags & MNT_NOSYMFOLLOW) res = catopt(res, "nosymfollow");
715: if (flags & MNT_SUIDDIR) res = catopt(res, "suiddir");
716:
717: return res;
718: }