#include "am.h"
#include <unistd.h>
static unsigned int am_gen = 2;
#define new_gen() (am_gen++)
am_node **exported_ap = (am_node **) 0;
int exported_ap_size = 0;
int first_free_map = 0;
int last_used_map = -1;
static int timeout_mp_id;
static struct fattr gen_fattr = {
NFLNK,
NFSMODE_LNK | 0777,
1,
0,
0,
0,
4096,
0,
1,
0,
0,
{ 0, 0 },
{ 0, 0 },
{ 0, 0 },
};
static int
exported_ap_realloc_map(int nsize)
{
if (nsize < 0 || nsize == exported_ap_size)
return 0;
exported_ap = xreallocarray(exported_ap, nsize, sizeof *exported_ap);
if (nsize > exported_ap_size)
bzero(exported_ap+exported_ap_size,
(nsize - exported_ap_size) * sizeof(am_node*));
exported_ap_size = nsize;
return 1;
}
am_node *root_node;
am_node *exported_ap_alloc(void)
{
am_node *mp, **mpp;
if (first_free_map >= exported_ap_size)
if (!exported_ap_realloc_map(exported_ap_size + NEXP_AP))
return 0;
mpp = exported_ap + first_free_map;
mp = *mpp = ALLOC(am_node);
bzero(mp, sizeof(*mp));
mp->am_mapno = first_free_map++;
while (first_free_map < exported_ap_size && exported_ap[first_free_map])
first_free_map++;
if (first_free_map > last_used_map)
last_used_map = first_free_map - 1;
if (last_used_map < exported_ap_size - (NEXP_AP + NEXP_AP_MARGIN))
exported_ap_realloc_map(exported_ap_size - NEXP_AP);
#ifdef DEBUG
#endif
return mp;
}
static void
exported_ap_free(am_node *mp)
{
if (!mp)
return;
exported_ap[mp->am_mapno] = 0;
if (mp->am_mapno == last_used_map)
while (last_used_map >= 0 && exported_ap[last_used_map] == 0)
--last_used_map;
if (first_free_map > mp->am_mapno)
first_free_map = mp->am_mapno;
#ifdef DEBUG
#endif
free(mp);
}
void
insert_am(am_node *mp, am_node *p_mp)
{
if (p_mp == root_node)
mp->am_flags |= AMF_ROOT;
mp->am_parent = p_mp;
mp->am_osib = p_mp->am_child;
if (mp->am_osib)
mp->am_osib->am_ysib = mp;
p_mp->am_child = mp;
}
static void
remove_am(am_node *mp)
{
if (mp->am_child && mp->am_parent) {
plog(XLOG_WARNING, "children of \"%s\" still exist - deleting anyway", mp->am_path);
}
if (mp->am_parent && mp->am_parent->am_child == mp)
mp->am_parent->am_child = mp->am_osib;
if (mp->am_ysib)
mp->am_ysib->am_osib = mp->am_osib;
if (mp->am_osib)
mp->am_osib->am_ysib = mp->am_ysib;
}
void
new_ttl(am_node *mp)
{
mp->am_timeo_w = 0;
mp->am_ttl = clocktime();
mp->am_fattr.atime.seconds = mp->am_ttl;
mp->am_ttl += mp->am_timeo;
}
void
mk_fattr(am_node *mp, int vntype)
{
switch (vntype) {
case NFDIR:
mp->am_fattr.type = NFDIR;
mp->am_fattr.mode = NFSMODE_DIR | 0555;
mp->am_fattr.nlink = 2;
mp->am_fattr.size = 512;
break;
case NFLNK:
mp->am_fattr.type = NFLNK;
mp->am_fattr.mode = NFSMODE_LNK | 0777;
mp->am_fattr.nlink = 1;
mp->am_fattr.size = 0;
break;
default:
plog(XLOG_FATAL, "Unknown fattr type %d - ignored", vntype);
break;
}
}
void
init_map(am_node *mp, char *dir)
{
mp->am_mnt = new_mntfs();
mp->am_name = strdup(dir);
mp->am_path = strdup(dir);
mp->am_gen = new_gen();
mp->am_timeo = am_timeo;
mp->am_attr.status = NFS_OK;
mp->am_fattr = gen_fattr;
mp->am_fattr.fsid = 42;
mp->am_fattr.fileid = 0;
mp->am_fattr.atime.seconds = clocktime();
mp->am_fattr.atime.useconds = 0;
mp->am_fattr.mtime = mp->am_fattr.ctime = mp->am_fattr.atime;
new_ttl(mp);
mp->am_stats.s_mtime = mp->am_fattr.atime.seconds;
}
void
free_map(am_node *mp)
{
remove_am(mp);
free(mp->am_link);
free(mp->am_name);
free(mp->am_path);
free(mp->am_pref);
if (mp->am_mnt)
free_mntfs(mp->am_mnt);
exported_ap_free(mp);
}
am_node *
fh_to_mp3(nfs_fh *fhp, int *rp, int c_or_d)
{
struct am_fh *fp = (struct am_fh *) fhp;
am_node *ap = 0;
if (fp->fhh_pid != mypid)
goto drop;
if (fp->fhh_id < 0 || fp->fhh_id >= exported_ap_size)
goto drop;
ap = exported_ap[fp->fhh_id];
if (ap) {
if (ap->am_gen != fp->fhh_gen) {
ap = 0;
goto drop;
}
if (ap->am_mnt && FSRV_ISDOWN(ap->am_mnt->mf_server) && ap->am_parent) {
int error;
am_node *orig_ap = ap;
#ifdef DEBUG
dlog("fh_to_mp3: %s (%s) is hung:- call lookup",
orig_ap->am_path, orig_ap->am_mnt->mf_info);
#endif
orig_ap->am_fattr.mtime.seconds = clocktime();
if (c_or_d == VLOOK_CREATE) {
ap = (*orig_ap->am_parent->am_mnt->mf_ops->lookuppn)(orig_ap->am_parent,
orig_ap->am_name, &error, c_or_d);
} else {
ap = 0;
error = ESTALE;
}
if (ap == 0) {
if (error < 0 && amd_state == Finishing)
error = ENOENT;
*rp = error;
return 0;
}
new_ttl(orig_ap);
}
if (ap->am_mnt && (ap->am_mnt->mf_flags & MFF_UNMOUNTING) &&
!(ap->am_flags & AMF_ROOT)) {
if (amd_state == Finishing)
*rp = ENOENT;
else
*rp = -1;
return 0;
}
new_ttl(ap);
}
drop:
if (!ap || !ap->am_mnt) {
if (amd_state == Finishing)
*rp = ENOENT;
else
*rp = ESTALE;
amd_stats.d_stale++;
}
return ap;
}
am_node *
fh_to_mp(nfs_fh *fhp)
{
int dummy;
return fh_to_mp2(fhp, &dummy);
}
void
mp_to_fh(am_node *mp, struct nfs_fh *fhp)
{
struct am_fh *fp = (struct am_fh *) fhp;
fp->fhh_pid = mypid;
fp->fhh_id = mp->am_mapno;
fp->fhh_gen = mp->am_gen;
}
static am_node *
find_ap2(char *dir, am_node *mp)
{
if (mp) {
am_node *mp2;
if (strcmp(mp->am_path, dir) == 0)
return mp;
if ((mp->am_mnt->mf_flags & MFF_MOUNTED) &&
strcmp(mp->am_mnt->mf_mount, dir) == 0)
return mp;
mp2 = find_ap2(dir, mp->am_osib);
if (mp2)
return mp2;
return find_ap2(dir, mp->am_child);
}
return 0;
}
am_node *
find_ap(char *dir)
{
int i;
for (i = last_used_map; i >= 0; --i) {
am_node *mp = exported_ap[i];
if (mp && (mp->am_flags & AMF_ROOT)) {
mp = find_ap2(dir, exported_ap[i]);
if (mp)
return mp;
}
}
return 0;
}
am_node *
find_mf(mntfs *mf)
{
int i;
for (i = last_used_map; i >= 0; --i) {
am_node *mp = exported_ap[i];
if (mp && mp->am_mnt == mf)
return mp;
}
return 0;
}
nfs_fh *
root_fh(char *dir)
{
static nfs_fh nfh;
am_node *mp = root_ap(dir, TRUE);
if (mp) {
mp_to_fh(mp, &nfh);
if (!foreground) {
pid_t pid = getppid();
((struct am_fh *) &nfh)->fhh_pid = pid;
#ifdef DEBUG
dlog("root_fh substitutes pid %d", (int)pid);
#endif
}
return &nfh;
}
plog(XLOG_ERROR, "Can't find root filehandle for %s", dir);
return 0;
}
am_node *
root_ap(char *dir, int path)
{
am_node *mp = find_ap(dir);
if (mp && mp->am_parent == root_node)
return mp;
return 0;
}
void
map_flush_srvr(fserver *fs)
{
int i;
int done = 0;
for (i = last_used_map; i >= 0; --i) {
am_node *mp = exported_ap[i];
if (mp && mp->am_mnt && mp->am_mnt->mf_server == fs) {
plog(XLOG_INFO, "Flushed %s; dependent on %s", mp->am_path, fs->fs_host);
mp->am_ttl = clocktime();
done = 1;
}
}
if (done)
reschedule_timeout_mp();
}
int
mount_auto_node(char *dir, void *arg)
{
int error = 0;
(void) afs_ops.lookuppn((am_node *) arg, dir, &error, VLOOK_CREATE);
if (error > 0) {
errno = error;
plog(XLOG_ERROR, "Could not mount %s: %m", dir);
}
return error;
}
int
mount_exported(void)
{
return root_keyiter((void (*)(char *, void *)) mount_auto_node, root_node);
}
void
make_root_node(void)
{
mntfs *root_mnt;
char *rootmap = ROOT_MAP;
root_node = exported_ap_alloc();
init_map(root_node, "");
root_mnt = find_mntfs(&root_ops, (am_opts *) 0, "", rootmap, "", "", "");
free_mntfs(root_node->am_mnt);
root_node->am_mnt = root_mnt;
if (root_mnt->mf_ops->fs_init)
(*root_mnt->mf_ops->fs_init)(root_mnt);
root_mnt->mf_error = (*root_mnt->mf_ops->mount_fs)(root_node);
}
void
umount_exported(void)
{
int i;
for (i = last_used_map; i >= 0; --i) {
am_node *mp = exported_ap[i];
if (mp) {
mntfs *mf = mp->am_mnt;
if (mf->mf_flags & MFF_UNMOUNTING) {
continue;
}
if (mf && !(mf->mf_ops->fs_flags & FS_DIRECTORY)) {
mk_fattr(mp, NFDIR);
}
if ((--immediate_abort < 0 && !(mp->am_flags & AMF_ROOT) && mp->am_parent) ||
(mf->mf_flags & MFF_RESTART)) {
if (mf->mf_server &&
(mf->mf_server->fs_flags & (FSF_DOWN|FSF_VALID)) != FSF_VALID)
mf->mf_flags &= ~MFF_MKMNT;
am_unmounted(mp);
} else {
mp->am_flags &= ~AMF_NOTIMEOUT;
mp->am_mnt->mf_flags &= ~MFF_RSTKEEP;
mp->am_ttl = 0;
mp->am_timeo = 1;
mp->am_timeo_w = 0;
}
}
}
}
static int
unmount_node(am_node *mp)
{
mntfs *mf = mp->am_mnt;
int error;
if ((mf->mf_flags & MFF_ERROR) || mf->mf_refc > 1) {
#ifdef DEBUG
if (mf->mf_flags & MFF_ERROR)
dlog("No-op unmount of error node %s", mf->mf_info);
#endif
error = 0;
} else {
#ifdef DEBUG
dlog("Unmounting %s (%s)", mf->mf_mount, mf->mf_info);
#endif
error = (*mf->mf_ops->umount_fs)(mp);
}
if (error) {
#ifdef DEBUG
errno = error;
dlog("%s: unmount: %m", mf->mf_mount);
#endif
}
return error;
}
#ifdef FLUSH_KERNEL_NAME_CACHE
static void
flush_kernel_name_cache(am_node *mp)
{
int islink = (mp->am_mnt->mf_fattr.type == NFLNK);
int isdir = (mp->am_mnt->mf_fattr.type == NFDIR);
int elog = 0;
if (islink) {
if (unlink(mp->am_path) < 0)
elog = 1;
} else if (isdir) {
if (rmdir(mp->am_path) < 0)
elog = 1;
}
if (elog)
plog(XLOG_WARNING, "failed to clear \"%s\" from dnlc: %m", mp->am_path);
}
#endif
static int
unmount_node_wrap(void *vp)
{
#ifndef FLUSH_KERNEL_NAME_CACHE
return unmount_node((am_node*) vp);
#else
am_node *mp = (am_node *) vp;
int isauto = mp->am_parent && (mp->am_parent->am_mnt->mf_fattr.type == NFDIR);
int error = unmount_node(mp);
if (error)
return error;
if (isauto && (int)amd_state < (int)Finishing)
flush_kernel_name_cache(mp);
return 0;
#endif
}
static void
free_map_if_success(int rc, int term, void *closure)
{
am_node *mp = (am_node *) closure;
mntfs *mf = mp->am_mnt;
mf->mf_flags &= ~MFF_UNMOUNTING;
if (mf->mf_flags & MFF_WANTTIMO) {
mf->mf_flags &= ~MFF_WANTTIMO;
reschedule_timeout_mp();
}
if (term) {
plog(XLOG_ERROR, "unmount for %s got signal %d", mp->am_path, term);
#if defined(DEBUG) && defined(SIGTRAP)
if (term == SIGTRAP) {
am_unmounted(mp);
}
#endif
amd_stats.d_uerr++;
} else if (rc) {
if (rc == EBUSY) {
plog(XLOG_STATS, "\"%s\" on %s still active", mp->am_path, mf->mf_mount);
} else {
errno = rc;
plog(XLOG_ERROR, "%s: unmount: %m", mp->am_path);
}
amd_stats.d_uerr++;
} else {
am_unmounted(mp);
}
wakeup(mf);
}
static int
unmount_mp(am_node *mp)
{
int was_backgrounded = 0;
mntfs *mf = mp->am_mnt;
#ifdef notdef
plog(XLOG_INFO, "\"%s\" on %s timed out", mp->am_path, mp->am_mnt->mf_mount);
#endif
if ((mf->mf_ops->fs_flags & FS_UBACKGROUND) &&
(mf->mf_flags & MFF_MOUNTED)) {
if (mf->mf_refc == 1 && !FSRV_ISUP(mf->mf_server)) {
if (!(mf->mf_flags & MFF_LOGDOWN)) {
plog(XLOG_STATS, "file server %s is down - timeout of \"%s\" ignored", mf->mf_server->fs_host, mp->am_path);
mf->mf_flags |= MFF_LOGDOWN;
}
} else {
mf->mf_flags &= ~MFF_LOGDOWN;
#ifdef DEBUG
dlog("\"%s\" on %s timed out", mp->am_path, mp->am_mnt->mf_mount);
#endif
mf->mf_flags |= MFF_UNMOUNTING;
run_task(unmount_node_wrap, mp,
free_map_if_success, mp);
was_backgrounded = 1;
#ifdef DEBUG
dlog("unmount attempt backgrounded");
#endif
}
} else {
#ifdef DEBUG
dlog("\"%s\" on %s timed out", mp->am_path, mp->am_mnt->mf_mount);
dlog("Trying unmount in foreground");
#endif
mf->mf_flags |= MFF_UNMOUNTING;
free_map_if_success(unmount_node(mp), 0, mp);
#ifdef DEBUG
dlog("unmount attempt done");
#endif
}
return was_backgrounded;
}
static void
timeout_mp(void *arg)
{
#define NEVER (time_t) 0
#define smallest_t(t1, t2) \
(t1 != NEVER ? (t2 != NEVER ? (t1 < t2 ? t1 : t2) : t1) : t2)
#define IGNORE_FLAGS (MFF_MOUNTING|MFF_UNMOUNTING|MFF_RESTART)
int i;
time_t t = NEVER;
time_t now = clocktime();
int backoff = 0;
#ifdef DEBUG
dlog("Timing out automount points...");
#endif
for (i = last_used_map; i >= 0; --i) {
am_node *mp = exported_ap[i];
mntfs *mf;
if (!mp || (mp->am_flags & AMF_NOTIMEOUT))
continue;
mf = mp->am_mnt;
if (!mf)
continue;
if ((mf->mf_flags & MFF_RSTKEEP) && mf->mf_refc == 1)
continue;
if (!(mf->mf_flags & IGNORE_FLAGS)) {
int expired = 0;
mf->mf_flags &= ~MFF_WANTTIMO;
#ifdef DEBUG
#endif
if (now >= mp->am_ttl) {
if (!backoff) {
expired = 1;
if (mp->am_timeo_w < 4 * am_timeo_w)
mp->am_timeo_w += am_timeo_w;
mp->am_ttl = now + mp->am_timeo_w;
} else {
mp->am_ttl = now + backoff + 1;
}
}
t = smallest_t(t, mp->am_ttl);
#ifdef DEBUG
#endif
if (!mp->am_child && mf->mf_error >= 0 && expired) {
if (unmount_mp(mp)) {
backoff = 2;
#ifdef DEBUG
#endif
}
}
} else if (mf->mf_flags & MFF_UNMOUNTING) {
mf->mf_flags |= MFF_WANTTIMO;
}
}
if (t == NEVER) {
#ifdef DEBUG
dlog("No further timeouts");
#endif
t = now + ONE_HOUR;
}
if (t <= now) {
t = now + 6;
plog(XLOG_ERROR, "Got a zero interval in timeout_mp()!");
}
if ((int)amd_state >= (int)Finishing)
t = now + 1;
#ifdef DEBUG
dlog("Next mount timeout in %ds", t - now);
#endif
timeout_mp_id = timeout(t - now, timeout_mp, 0);
#undef NEVER
#undef smallest_t
#undef IGNORE_FLAGS
}
void reschedule_timeout_mp()
{
if (timeout_mp_id)
untimeout(timeout_mp_id);
timeout_mp_id = timeout(0, timeout_mp, 0);
}