aboutsummaryrefslogtreecommitdiffstats
path: root/amd/amfs_generic.c
diff options
context:
space:
mode:
Diffstat (limited to 'amd/amfs_generic.c')
-rw-r--r--amd/amfs_generic.c243
1 files changed, 118 insertions, 125 deletions
diff --git a/amd/amfs_generic.c b/amd/amfs_generic.c
index 0d5996969cf8..25d734bbde00 100644
--- a/amd/amfs_generic.c
+++ b/amd/amfs_generic.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997-2006 Erez Zadok
+ * Copyright (c) 1997-2014 Erez Zadok
* Copyright (c) 1990 Jan-Simon Pendry
* Copyright (c) 1990 Imperial College of Science, Technology & Medicine
* Copyright (c) 1990 The Regents of the University of California.
@@ -16,11 +16,7 @@
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
- * 3. All advertising materials mentioning features or use of this software
- * must display the following acknowledgment:
- * This product includes software developed by the University of
- * California, Berkeley and its contributors.
- * 4. Neither the name of the University nor the names of its contributors
+ * 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
@@ -55,7 +51,7 @@
/****************************************************************************
*** MACROS ***
****************************************************************************/
-#define IN_PROGRESS(cp) ((cp)->mp->am_mnt->mf_flags & MFF_MOUNTING)
+#define IN_PROGRESS(cp) ((cp)->mp->am_al->al_mnt->mf_flags & MFF_MOUNTING)
/****************************************************************************
@@ -80,7 +76,7 @@ struct continuation {
int retry; /* Try again? */
time_t start; /* Time we started this mount */
int callout; /* Callout identifier */
- mntfs **mf; /* Current mntfs */
+ am_loc **al; /* Current location */
};
@@ -88,9 +84,9 @@ struct continuation {
*** FORWARD DEFINITIONS ***
****************************************************************************/
static am_node *amfs_lookup_node(am_node *mp, char *fname, int *error_return);
-static mntfs *amfs_lookup_one_mntfs(am_node *new_mp, mntfs *mf, char *ivec,
+static am_loc *amfs_lookup_one_location(am_node *new_mp, mntfs *mf, char *ivec,
char *def_opts, char *pfname);
-static mntfs **amfs_lookup_mntfs(am_node *new_mp, int *error_return);
+static am_loc **amfs_lookup_loc(am_node *new_mp, int *error_return);
static void amfs_cont(int rc, int term, opaque_t arg);
static void amfs_retry(int rc, int term, opaque_t arg);
static void free_continuation(struct continuation *cp);
@@ -108,7 +104,7 @@ amfs_lookup_node(am_node *mp, char *fname, int *error_return)
int error = 0; /* Error so far */
int in_progress = 0; /* # of (un)mount in progress */
mntfs *mf;
- char *expanded_fname = 0;
+ char *expanded_fname = NULL;
dlog("in amfs_lookup_node");
@@ -118,7 +114,7 @@ amfs_lookup_node(am_node *mp, char *fname, int *error_return)
* about the mount point.
*/
if (amd_state == Finishing) {
- if (mp->am_mnt == 0 || mp->am_mnt->mf_fsflags & FS_DIRECT) {
+ if (mp->am_al == NULL || mp->am_al->al_mnt == NULL || mp->am_al->al_mnt->mf_fsflags & FS_DIRECT) {
dlog("%s mount ignored - going down", fname);
} else {
dlog("%s/%s mount ignored - going down", mp->am_path, fname);
@@ -170,7 +166,7 @@ amfs_lookup_node(am_node *mp, char *fname, int *error_return)
* If the error code is undefined then it must be
* in progress.
*/
- mf = new_mp->am_mnt;
+ mf = new_mp->am_al->al_mnt;
if (mf->mf_error < 0)
goto in_progrss;
@@ -240,7 +236,7 @@ amfs_lookup_node(am_node *mp, char *fname, int *error_return)
*/
new_mp = get_ap_child(mp, expanded_fname);
XFREE(expanded_fname);
- if (new_mp == 0)
+ if (new_mp == NULL)
ereturn(ENOSPC);
*error_return = -1;
@@ -249,19 +245,25 @@ amfs_lookup_node(am_node *mp, char *fname, int *error_return)
-static mntfs *
-amfs_lookup_one_mntfs(am_node *new_mp, mntfs *mf, char *ivec,
- char *def_opts, char *pfname)
+static am_loc *
+amfs_lookup_one_location(am_node *new_mp, mntfs *mf, char *ivec,
+ char *def_opts, char *pfname)
{
am_ops *p;
am_opts *fs_opts;
+ am_loc *new_al;
mntfs *new_mf;
- char *mp_dir = 0;
+ char *mp_dir = NULL;
#ifdef HAVE_FS_AUTOFS
int on_autofs = 1;
#endif /* HAVE_FS_AUTOFS */
/* match the operators */
+ /*
+ * although we alloc the fs_opts here, the pointer is 'owned' by the am_loc and will
+ * be free'd on destruction of the am_loc. If we don't allocate a loc, then we need
+ * to free this.
+ */
fs_opts = CALLOC(am_opts);
p = ops_match(fs_opts, ivec, def_opts, new_mp->am_path,
pfname, mf->mf_info);
@@ -269,7 +271,7 @@ amfs_lookup_one_mntfs(am_node *new_mp, mntfs *mf, char *ivec,
/* XXX: this should be factored out into an autofs-specific function */
if (new_mp->am_flags & AMF_AUTOFS) {
/* ignore user-provided fs if we're using autofs */
- if (fs_opts->opt_sublink) {
+ if (fs_opts->opt_sublink && fs_opts->opt_sublink[0]) {
/*
* For sublinks we need to use a hack with autofs:
* mount the filesystem on the original opt_fs (which is NOT an
@@ -292,6 +294,9 @@ amfs_lookup_one_mntfs(am_node *new_mp, mntfs *mf, char *ivec,
/*
* Find or allocate a filesystem for this node.
+ * we search for a matching backend share, since
+ * we will construct our own al_loc to handle
+ * any customisations for this usage.
*/
new_mf = find_mntfs(p, fs_opts,
mp_dir,
@@ -300,6 +305,7 @@ amfs_lookup_one_mntfs(am_node *new_mp, mntfs *mf, char *ivec,
fs_opts->opt_opts,
fs_opts->opt_remopts);
+
/*
* See whether this is a real filesystem
*/
@@ -307,10 +313,16 @@ amfs_lookup_one_mntfs(am_node *new_mp, mntfs *mf, char *ivec,
if (p == &amfs_error_ops) {
plog(XLOG_MAP, "Map entry %s for %s did not match", ivec, new_mp->am_path);
free_mntfs(new_mf);
+ free_opts(fs_opts);
+ XFREE(fs_opts);
return NULL;
}
dlog("Got a hit with %s", p->fs_type);
+ new_al = new_loc();
+ free_mntfs(new_al->al_mnt);
+ new_al->al_mnt = new_mf;
+ new_al->al_fo = fs_opts; /* now the loc is in charge of free'ing this mem */
#ifdef HAVE_FS_AUTOFS
if (new_mp->am_flags & AMF_AUTOFS && on_autofs) {
@@ -332,12 +344,12 @@ amfs_lookup_one_mntfs(am_node *new_mp, mntfs *mf, char *ivec,
new_mf->mf_flags |= MFF_IS_AUTOFS;
#endif /* HAVE_FS_AUTOFS */
- return new_mf;
+ return new_al;
}
-static mntfs **
-amfs_lookup_mntfs(am_node *new_mp, int *error_return)
+static am_loc **
+amfs_lookup_loc(am_node *new_mp, int *error_return)
{
am_node *mp;
char *info; /* Mount info - where to get the file system */
@@ -348,10 +360,11 @@ amfs_lookup_mntfs(am_node *new_mp, int *error_return)
int error = 0; /* Error so far */
char path_name[MAXPATHLEN]; /* General path name buffer */
char *pfname; /* Path for database lookup */
- mntfs *mf, **mf_array;
+ mntfs* mf; /* The mntfs for the map of our parent */
+ am_loc **al_array; /* the generated list of locations */
int count;
- dlog("in amfs_lookup_mntfs");
+ dlog("in amfs_lookup_loc");
mp = new_mp->am_parent;
@@ -369,7 +382,7 @@ amfs_lookup_mntfs(am_node *new_mp, int *error_return)
pfname = new_mp->am_name;
}
- mf = mp->am_mnt;
+ mf = mp->am_al->al_mnt;
dlog("will search map info in %s to find %s", mf->mf_info, pfname);
/*
@@ -404,8 +417,8 @@ amfs_lookup_mntfs(am_node *new_mp, int *error_return)
else
def_opts = "";
- orig_def_opts = amfs_parse_defaults(mp, mf, strdup(def_opts));
- def_opts = strdup(orig_def_opts);
+ orig_def_opts = amfs_parse_defaults(mp, mf, xstrdup(def_opts));
+ def_opts = xstrdup(orig_def_opts);
/* first build our defaults */
num_ivecs = 0;
@@ -423,11 +436,11 @@ amfs_lookup_mntfs(am_node *new_mp, int *error_return)
num_ivecs++;
}
- mf_array = calloc(num_ivecs + 1, sizeof(mntfs *));
+ al_array = calloc(num_ivecs + 1, sizeof(am_loc *));
- /* construct the array of struct mntfs for this mount point */
+ /* construct the array of struct locations for this key */
for (count = 0, cur_ivec = ivecs; *cur_ivec; cur_ivec++) {
- mntfs *new_mf;
+ am_loc *new_al;
if (**cur_ivec == '-') {
XFREE(def_opts);
@@ -436,18 +449,18 @@ amfs_lookup_mntfs(am_node *new_mp, int *error_return)
* If we have a single dash '-' than we need to reset the
* default options.
*/
- def_opts = strdup(orig_def_opts);
+ def_opts = xstrdup(orig_def_opts);
dlog("Resetting the default options, a single dash '-' was found.");
} else {
/* append options to /default options */
- def_opts = str3cat((char *) 0, orig_def_opts, ";", *cur_ivec + 1);
+ def_opts = str3cat((char *) NULL, orig_def_opts, ";", *cur_ivec + 1);
dlog("Resetting def_opts to \"%s\"", def_opts);
}
continue;
}
/*
- * If a mntfs has already been found, and we find
+ * If a loc has already been found, and we find
* a cut then don't try any more locations.
*
* XXX: we do not know when the "/" was added as an equivalent for "||".
@@ -455,16 +468,16 @@ amfs_lookup_mntfs(am_node *new_mp, int *error_return)
*/
if (STREQ(*cur_ivec, "/") || STREQ(*cur_ivec, "||")) {
if (count > 0) {
- dlog("Cut: not trying any more locations for %s", mp->am_path);
+ dlog("Cut: not trying any more locations for %s", pfname);
break;
}
continue;
}
- new_mf = amfs_lookup_one_mntfs(new_mp, mf, *cur_ivec, def_opts, pfname);
- if (new_mf == NULL)
+ new_al = amfs_lookup_one_location(new_mp, mf, *cur_ivec, def_opts, pfname);
+ if (new_al == NULL)
continue;
- mf_array[count++] = new_mf;
+ al_array[count++] = new_al;
}
/* We're done with ivecs */
@@ -473,11 +486,11 @@ amfs_lookup_mntfs(am_node *new_mp, int *error_return)
XFREE(orig_def_opts);
XFREE(def_opts);
if (count == 0) { /* no match */
- XFREE(mf_array);
+ XFREE(al_array);
ereturn(ENOENT);
}
- return mf_array;
+ return al_array;
}
@@ -491,7 +504,7 @@ amfs_cont(int rc, int term, opaque_t arg)
{
struct continuation *cp = (struct continuation *) arg;
am_node *mp = cp->mp;
- mntfs *mf = mp->am_mnt;
+ mntfs *mf = mp->am_al->al_mnt;
dlog("amfs_cont: '%s'", mp->am_path);
@@ -556,7 +569,7 @@ amfs_cont(int rc, int term, opaque_t arg)
mf->mf_error = rc;
mf->mf_flags |= MFF_ERROR;
errno = rc; /* XXX */
- if (!STREQ(mp->am_mnt->mf_ops->fs_type, "linkx"))
+ if (!STREQ(mp->am_al->al_mnt->mf_ops->fs_type, "linkx"))
plog(XLOG_ERROR, "%s: mount (amfs_cont): %m", mp->am_path);
}
}
@@ -568,7 +581,7 @@ amfs_cont(int rc, int term, opaque_t arg)
* call the background mount routine again
*/
amd_stats.d_merr++;
- cp->mf++;
+ cp->al++;
}
amfs_bgmount(cp);
if (mp->am_error > 0)
@@ -608,13 +621,21 @@ amfs_retry(int rc, int term, opaque_t arg)
*/
plog(XLOG_INFO, "mount of \"%s\" has timed out", mp->am_path);
error = ETIMEDOUT;
- while (*cp->mf)
- cp->mf++;
+ while (*cp->al)
+ cp->al++;
/* explicitly forbid further retries after timeout */
cp->retry = FALSE;
}
if (error || !IN_PROGRESS(cp))
error = amfs_bgmount(cp);
+ else
+ /* Normally it's amfs_bgmount() which frees the continuation. However, if
+ * the mount is already in progress and we're in amfs_retry() for another
+ * node we don't try mounting the filesystem once again. Still, we have
+ * to free the continuation as we won't get called again and thus would
+ * leak the continuation structure and our am_loc references.
+ */
+ free_continuation(cp);
reschedule_timeout_mp();
}
@@ -626,7 +647,7 @@ amfs_retry(int rc, int term, opaque_t arg)
static void
free_continuation(struct continuation *cp)
{
- mntfs **mfp;
+ am_loc **alp;
dlog("free_continuation");
if (cp->callout)
@@ -634,13 +655,12 @@ free_continuation(struct continuation *cp)
/*
* we must free the mntfs's in the list.
* so free all of them if there was an error,
- * or free all but the used one, if the mount succeeded.
*/
- for (mfp = cp->mp->am_mfarray; *mfp; mfp++) {
- free_mntfs(*mfp);
+ for (alp = cp->mp->am_alarray; *alp; alp++) {
+ free_loc(*alp);
}
- XFREE(cp->mp->am_mfarray);
- cp->mp->am_mfarray = 0;
+ XFREE(cp->mp->am_alarray);
+ cp->mp->am_alarray = 0;
XFREE(cp);
}
@@ -690,12 +710,13 @@ static int
amfs_bgmount(struct continuation *cp)
{
am_node *mp = cp->mp;
- mntfs *mf; /* Current mntfs */
+ am_loc *loc;
+ mntfs *mf;
int this_error = -1; /* Per-mount error */
int hard_error = -1; /* Cumulative per-node error */
- if (mp->am_mnt)
- free_mntfs(mp->am_mnt);
+ if (mp->am_al)
+ free_loc(mp->am_al);
/*
* Try to mount each location.
@@ -704,10 +725,11 @@ amfs_bgmount(struct continuation *cp)
* hard_error > 0 indicates everything failed with a hard error
* hard_error < 0 indicates nothing could be mounted now
*/
- for (mp->am_mnt = *cp->mf; *cp->mf; cp->mf++, mp->am_mnt = *cp->mf) {
+ for (mp->am_al = *cp->al; *cp->al; cp->al++, mp->am_al = *cp->al) {
am_ops *p;
- mf = dup_mntfs(mp->am_mnt);
+ loc = dup_loc(mp->am_al);
+ mf = loc->al_mnt;
p = mf->mf_ops;
if (hard_error < 0)
@@ -737,12 +759,11 @@ amfs_bgmount(struct continuation *cp)
goto failed;
}
- if (mp->am_link) {
- XFREE(mp->am_link);
- mp->am_link = NULL;
- }
- if (mf->mf_fo && mf->mf_fo->opt_sublink)
- mp->am_link = strdup(mf->mf_fo->opt_sublink);
+ XFREE(mp->am_link);
+ mp->am_link = NULL;
+
+ if (loc->al_fo && loc->al_fo->opt_sublink && loc->al_fo->opt_sublink[0])
+ mp->am_link = xstrdup(loc->al_fo->opt_sublink);
/*
* Will usually need to play around with the mount nodes
@@ -784,13 +805,13 @@ amfs_bgmount(struct continuation *cp)
if (this_error < 0)
goto retry;
- if (mf->mf_fo && mf->mf_fo->opt_delay) {
+ if (loc->al_fo && loc->al_fo->opt_delay) {
/*
- * If there is a delay timer on the mount
+ * If there is a delay timer on the location
* then don't try to mount if the timer
* has not expired.
*/
- int i = atoi(mf->mf_fo->opt_delay);
+ int i = atoi(loc->al_fo->opt_delay);
time_t now = clocktime(NULL);
if (i > 0 && now < (cp->start + i)) {
dlog("Mount of %s delayed by %lds", mf->mf_mount, (long) (i - now + cp->start));
@@ -866,22 +887,25 @@ amfs_bgmount(struct continuation *cp)
return -1;
failed:
- amd_stats.d_merr++;
- mf->mf_error = this_error;
- mf->mf_flags |= MFF_ERROR;
+ if (!FSRV_ISDOWN(mf->mf_server)) {
+ /* mark the mount as failed unless the server is down */
+ amd_stats.d_merr++;
+ mf->mf_error = this_error;
+ mf->mf_flags |= MFF_ERROR;
#ifdef HAVE_FS_AUTOFS
- if (mp->am_autofs_fh)
- autofs_release_fh(mp);
+ if (mp->am_autofs_fh)
+ autofs_release_fh(mp);
#endif /* HAVE_FS_AUTOFS */
- if (mf->mf_flags & MFF_MKMNT) {
- rmdirs(mf->mf_mount);
- mf->mf_flags &= ~MFF_MKMNT;
+ if (mf->mf_flags & MFF_MKMNT) {
+ rmdirs(mf->mf_mount);
+ mf->mf_flags &= ~MFF_MKMNT;
+ }
}
/*
* Wakeup anything waiting for this mount
*/
wakeup(get_mntfs_wchan(mf));
- free_mntfs(mf);
+ free_loc(loc);
/* continue */
}
@@ -890,7 +914,10 @@ amfs_bgmount(struct continuation *cp)
* there is no more mount information available.
*/
if (this_error) {
- mp->am_mnt = mf = new_mntfs();
+ if (mp->am_al)
+ free_loc(mp->am_al);
+ mp->am_al = loc = new_loc();
+ mf = loc->al_mnt;
#ifdef HAVE_FS_AUTOFS
if (mp->am_flags & AMF_AUTOFS)
@@ -920,7 +947,7 @@ amfs_bgmount(struct continuation *cp)
}
new_ttl(mp);
} else {
- mf = mp->am_mnt;
+ mf = loc->al_mnt;
/*
* Wakeup anything waiting for this mount
*/
@@ -959,19 +986,10 @@ amfs_parse_defaults(am_node *mp, mntfs *mf, char *def_opts)
/*
* Find out if amd.conf overrode any map-specific /defaults.
- *
- * HACK ALERT: there's no easy way to find out what the map mount point is
- * at this point, so I am forced to initialize the mnt_map->cfm field here
- * for the first time, upon the very first search for a /defaults entry in
- * this map. This initialization is much better done in mapc_create(),
- * but it's impossible to do that there with the current code structure.
*/
- if (mm->cfm == NULL) { /* then initialize it for first time */
- mm->cfm = find_cf_map(mf->mf_mount);
- }
if (mm->cfm && mm->cfm->cfm_defaults) {
dlog("map %s map_defaults override: %s", mf->mf_mount, mm->cfm->cfm_defaults);
- dflts = strdup(mm->cfm->cfm_defaults);
+ dflts = xstrdup(mm->cfm->cfm_defaults);
} else if (mapc_search(mm, "/defaults", &dflts) == 0) {
dlog("/defaults gave %s", dflts);
} else {
@@ -1009,7 +1027,7 @@ amfs_parse_defaults(am_node *mp, mntfs *mf, char *def_opts)
* get expanded to "/defaults"
*/
pt = ops_match(&ap, *sp, "", mp->am_path, "/defaults",
- mp->am_parent->am_mnt->mf_info);
+ mp->am_parent->am_al->al_mnt->mf_info);
free_opts(&ap); /* don't leak */
if (pt == &amfs_error_ops) {
plog(XLOG_MAP, "did not match defaults for \"%s\"", *sp);
@@ -1076,8 +1094,9 @@ amfs_generic_mount_child(am_node *new_mp, int *error_return)
*error_return = error = 0; /* Error so far */
/* we have an errorfs attached to the am_node, free it */
- free_mntfs(new_mp->am_mnt);
- new_mp->am_mnt = 0;
+ if (new_mp->am_al)
+ free_loc(new_mp->am_al);
+ new_mp->am_al = NULL;
/*
* Construct a continuation
@@ -1087,7 +1106,7 @@ amfs_generic_mount_child(am_node *new_mp, int *error_return)
cp->mp = new_mp;
cp->retry = TRUE;
cp->start = clocktime(NULL);
- cp->mf = new_mp->am_mfarray;
+ cp->al = new_mp->am_alarray;
/*
* Try and mount the file system. If this succeeds immediately (possible
@@ -1101,7 +1120,7 @@ amfs_generic_mount_child(am_node *new_mp, int *error_return)
/*
* Code for quick reply. If current_transp is set, then it's the
- * transp that's been passed down from nfs_program_2() or from
+ * transp that's been passed down from nfs_dispatcher() or from
* autofs_program_[123]().
* If new_mp->am_transp is not already set, set it by copying in
* current_transp. Once am_transp is set, nfs_quick_reply() and
@@ -1113,7 +1132,8 @@ amfs_generic_mount_child(am_node *new_mp, int *error_return)
new_mp->am_transp = (SVCXPRT *) xmalloc(sizeof(SVCXPRT));
*(new_mp->am_transp) = *current_transp;
}
- if (error && new_mp->am_mnt && (new_mp->am_mnt->mf_ops == &amfs_error_ops))
+ if (error && new_mp->am_al && new_mp->am_al->al_mnt &&
+ (new_mp->am_al->al_mnt->mf_ops == &amfs_error_ops))
new_mp->am_error = error;
if (new_mp->am_error > 0)
@@ -1132,7 +1152,7 @@ am_node *
amfs_generic_lookup_child(am_node *mp, char *fname, int *error_return, int op)
{
am_node *new_mp;
- mntfs **mf_array;
+ am_loc **al_array;
int mp_error;
dlog("in amfs_generic_lookup_child");
@@ -1145,7 +1165,7 @@ amfs_generic_lookup_child(am_node *mp, char *fname, int *error_return, int op)
return new_mp;
/* also return if it's already mounted and known to be up */
- if (*error_return == 0 && FSRV_ISUP(new_mp->am_mnt->mf_server))
+ if (*error_return == 0 && FSRV_ISUP(new_mp->am_al->al_mnt->mf_server))
return new_mp;
switch (op) {
@@ -1161,42 +1181,15 @@ amfs_generic_lookup_child(am_node *mp, char *fname, int *error_return, int op)
/* save error_return */
mp_error = *error_return;
- mf_array = amfs_lookup_mntfs(new_mp, error_return);
- if (!mf_array) {
- new_mp->am_error = new_mp->am_mnt->mf_error = *error_return;
+ al_array = amfs_lookup_loc(new_mp, error_return);
+ if (!al_array) {
+ new_mp->am_error = new_mp->am_al->al_mnt->mf_error = *error_return;
free_map(new_mp);
return NULL;
}
- /*
- * Already mounted but known to be down:
- * check if we have any alternatives to mount
- */
- if (mp_error == 0) {
- mntfs **mfp;
- for (mfp = mf_array; *mfp; mfp++)
- if (*mfp != new_mp->am_mnt)
- break;
- if (*mfp != NULL) {
- /*
- * we found an alternative, so try mounting again.
- */
- *error_return = -1;
- } else {
- for (mfp = mf_array; *mfp; mfp++)
- free_mntfs(*mfp);
- XFREE(mf_array);
- if (new_mp->am_flags & AMF_SOFTLOOKUP) {
- ereturn(EIO);
- } else {
- *error_return = 0;
- return new_mp;
- }
- }
- }
-
/* store the array inside the am_node */
- new_mp->am_mfarray = mf_array;
+ new_mp->am_alarray = al_array;
/*
* Note: while it might seem like a good idea to prioritize
@@ -1258,5 +1251,5 @@ amfs_generic_match(am_opts *fo)
/*
* mtab entry turns out to be the name of the mount map
*/
- return strdup(fo->opt_rfs ? fo->opt_rfs : ".");
+ return xstrdup(fo->opt_rfs ? fo->opt_rfs : ".");
}