#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/param.h>
#include <pthread.h>
#include <fstab.h>
#include <errno.h>
#include <assert.h>
#include <mntopts.h>
#include "autofs.h"
#include "automount.h"
#include "auto_mntopts.h"
#define MIN_CACHE_TIME 30 // min cache time for fstab cache (sec)
struct fstabhost {
char *name;
struct fstabnode *fstab_ents;
struct fstabhost *next;
};
#define HASHTABLESIZE 251
static struct fstabhost *fstab_hashtable[HASHTABLESIZE];
static struct staticmap *static_hashtable[HASHTABLESIZE];
static pthread_rwlock_t fstab_cache_lock = PTHREAD_RWLOCK_INITIALIZER;
static time_t min_cachetime; static time_t max_cachetime;
static int
process_fstab_mntopts(struct fstab *fs, char **mntops_outp, char **url)
{
char *mntops_out;
char *mntops_copy;
size_t optlen;
char *p, *pp;
char *optp;
optlen = strlen(fs->fs_mntops) + strlen(fs->fs_type) + 1 + 1;
if (optlen > MAXOPTSLEN)
return (ENAMETOOLONG);
mntops_out = (char *)malloc(optlen);
if (mntops_out == NULL)
return (ENOMEM);
strcpy(mntops_out, "");
*url = NULL;
if ((mntops_copy = strdup(fs->fs_mntops)) == NULL) {
free(mntops_out);
return (ENOMEM);
}
pp = mntops_copy;
while ((p = strsep(&pp, ",")) != NULL) {
if (strcmp(p, "net") == 0 || strcmp(p, "bg") == 0 ||
strcmp(p, "bg") == 0)
continue;
if (strncmp(p, "url==", 5) == 0) {
if (*url != NULL)
free(*url);
*url = strdup(p + 5);
if (*url == NULL) {
free(mntops_out);
free(mntops_copy);
return (ENOMEM);
}
continue;
}
if (strcmp(p, "browse") == 0)
optp = "findervol";
else if (strcmp(p, "nobrowse") == 0)
optp = "nofindervol";
else
optp = p;
if (mntops_out[0] != '\0') {
CHECK_STRCAT(mntops_out, ",", optlen);
}
CHECK_STRCAT(mntops_out, optp, optlen);
}
if (fs->fs_type[0] != '\0') {
if (mntops_out[0] != '\0') {
CHECK_STRCAT(mntops_out, ",", optlen);
}
CHECK_STRCAT(mntops_out, fs->fs_type, optlen);
}
*mntops_outp = mntops_out;
free(mntops_copy);
return (0);
}
static void
freefst_ent(fst)
struct fstabnode *fst;
{
if (fst->fst_dir != NULL)
free(fst->fst_dir);
if (fst->fst_vfstype != NULL)
free(fst->fst_vfstype);
if (fst->fst_mntops != NULL)
free(fst->fst_mntops);
if (fst->fst_url != NULL)
free(fst->fst_url);
free((char *)fst);
}
static void
freefst_list(fst)
struct fstabnode *fst;
{
struct fstabnode *tmpfst;
while (fst) {
tmpfst = fst->fst_next;
freefst_ent(fst);
fst = tmpfst;
}
}
static struct fstabhost *
find_host_entry(const char *host, struct fstabhost ***bucketp)
{
size_t hash;
const unsigned char *hashp;
unsigned char c;
struct fstabhost **bucket;
struct fstabhost *hostent;
hash = 0;
for (hashp = (const unsigned char *)host; (c = *hashp) != '\0'; hashp++)
hash += tolower(c);
bucket = &fstab_hashtable[hash % HASHTABLESIZE];
if (bucketp != NULL)
*bucketp = bucket;
for (hostent = *bucket; hostent != NULL; hostent = hostent->next) {
if (strcasecmp(hostent->name, host) == 0)
return (hostent);
}
return NULL;
}
static struct staticmap *
find_staticmap_entry(const char *dir, struct staticmap ***bucketp)
{
size_t hash;
const unsigned char *hashp;
unsigned char c;
struct staticmap **bucket;
struct staticmap *staticent;
hash = 0;
for (hashp = (const unsigned char *)dir; (c = *hashp) != '\0'; hashp++)
hash += c;
bucket = &static_hashtable[hash % HASHTABLESIZE];
if (bucketp != NULL)
*bucketp = bucket;
for (staticent = *bucket; staticent != NULL; staticent = staticent->next) {
if (strcmp(staticent->dir, dir) == 0)
return (staticent);
}
return NULL;
}
static void
clean_hashtables(void)
{
int i;
struct fstabhost *host_ent, *next_host_ent;
struct staticmap *static_ent, *next_static_ent;
for (i = 0; i < HASHTABLESIZE; i++) {
for (host_ent = fstab_hashtable[i]; host_ent != NULL;
host_ent = next_host_ent) {
next_host_ent = host_ent->next;
free(host_ent->name);
freefst_list(host_ent->fstab_ents);
free(host_ent);
}
fstab_hashtable[i] = NULL;
}
for (i = 0; i < HASHTABLESIZE; i++) {
for (static_ent = static_hashtable[i]; static_ent != NULL;
static_ent = next_static_ent) {
next_static_ent = static_ent->next;
free(static_ent->dir);
free(static_ent->vfstype);
free(static_ent->mntops);
free(static_ent->host);
free(static_ent->localpath);
free(static_ent->spec);
free(static_ent);
}
static_hashtable[i] = NULL;
}
}
static void
sort_fstab_entries(void)
{
int i;
struct fstabhost *host_ent;
struct fstabnode *fst = NULL;
struct fstabnode *tfstlist, **tfstp, *fstnext;
size_t fstlen;
int duplicate;
for (i = 0; i < HASHTABLESIZE; i++) {
for (host_ent = fstab_hashtable[i]; host_ent != NULL;
host_ent = host_ent->next) {
tfstlist = NULL;
for (fst = host_ent->fstab_ents; fst; fst = fstnext) {
fstnext = fst->fst_next;
fstlen = strlen(fst->fst_dir);
duplicate = 0;
for (tfstp = &tfstlist; *tfstp;
tfstp = &((*tfstp)->fst_next)) {
if (fstlen < strlen((*tfstp)->fst_dir))
break;
duplicate = (strcmp(fst->fst_dir, (*tfstp)->fst_dir) == 0);
if (duplicate) {
freefst_ent(fst);
break;
}
}
if (!duplicate) {
fst->fst_next = *tfstp;
*tfstp = fst;
}
}
host_ent->fstab_ents = tfstlist;
}
}
}
static const struct mntopt mopts_net[] = {
MOPT_NET,
{ NULL, 0, 0, 0 }
};
static int
readfstab(void)
{
int err;
struct fstab *fs;
char *p;
int is_local_entry;
mntoptparse_t mop;
int flags;
int altflags;
char *mntops, *url, *host, *localpath;
if (trace > 1)
trace_prt(1, "readfstab called\n");
pthread_rwlock_unlock(&fstab_cache_lock);
err = pthread_rwlock_wrlock(&fstab_cache_lock);
if (err != 0) {
pr_msg("Error attempting to get write lock on fstab cache: %m");
return (err);
}
if (time(NULL) < min_cachetime)
return (0);
clean_hashtables();
setfsent();
while ((fs = getfsent()) != NULL) {
char *vfstype;
if (fs->fs_type[0] != '\0' &&
strcmp(fs->fs_type, FSTAB_RW) != 0 &&
strcmp(fs->fs_type, FSTAB_RO) != 0) {
continue;
}
if (fs->fs_spec[0] == '/') {
continue;
}
is_local_entry = 1;
for (p = fs->fs_spec; *p != '\0' && *p != '='; p++) {
if ((*p & 0x80) != 0) {
is_local_entry = 0;
break;
}
if (!isalnum(*p) && *p != '-' && *p != '_') {
is_local_entry = 0;
break;
}
}
if (is_local_entry) {
continue;
}
flags = altflags = 0;
getmnt_silent = 1;
mop = getmntopts(fs->fs_mntops, mopts_net, &flags, &altflags);
if (mop == NULL) {
pr_msg("Couldn't parse mount options \"%s\" for %s: %m",
fs->fs_mntops, fs->fs_spec);
continue;
}
freemntopts(mop);
p = strchr(fs->fs_spec, ':');
if (p == NULL) {
pr_msg("Mount for %s has no path for the directory to mount", fs->fs_spec);
continue;
}
if (p == fs->fs_spec) {
pr_msg("Mount for %s has an empty host name", fs->fs_spec);
continue;
}
*p = '\0';
host = strdup(fs->fs_spec);
if (host == NULL)
goto outofmem;
localpath = strdup(p+1);
if (localpath == NULL) {
free(host);
goto outofmem;
}
*p = ':';
err = process_fstab_mntopts(fs, &mntops, &url);
if (err == ENAMETOOLONG) {
pr_msg("Mount options for %s are too long",
fs->fs_spec);
free(localpath);
free(host);
continue;
}
if (err == ENOMEM) {
free(localpath);
free(host);
goto outofmem;
}
vfstype = fs->fs_vfstype;
if (vfstype[0] == '\0')
vfstype = "nfs";
if (strcmp(vfstype, "url") == 0) {
if (url == NULL) {
pr_msg("Mount for %s has type url but no URL",
fs->fs_spec);
free(mntops);
free(localpath);
free(host);
continue;
}
} else {
if (url != NULL) {
pr_msg("Mount for %s has type %s but has a URL",
fs->fs_spec, vfstype);
free(mntops);
free(url);
free(localpath);
free(host);
continue;
}
}
if (altflags & FSTAB_MNT_NET) {
struct fstabnode *fst;
struct fstabhost *host_entry;
struct fstabhost **fstab_bucket;
fst = calloc(1, sizeof (struct fstabnode));
if (fst == NULL) {
free(url);
free(mntops);
free(localpath);
free(host);
goto outofmem;
}
fst->fst_dir = localpath;
fst->fst_vfstype = strdup(vfstype);
if (fst->fst_vfstype == NULL) {
free(fst->fst_dir);
free(fst);
free(url);
free(mntops);
free(host);
goto outofmem;
}
fst->fst_mntops = mntops;
fst->fst_url = url;
host_entry = find_host_entry(host, &fstab_bucket);
if (host_entry == NULL) {
host_entry = malloc(sizeof (struct fstabhost));
if (host_entry == NULL) {
free(fst->fst_vfstype);
free(fst->fst_dir);
free(fst);
free(url);
free(mntops);
free(host);
goto outofmem;
}
host_entry->name = host;
host_entry->fstab_ents = fst;
host_entry->next = *fstab_bucket;
*fstab_bucket = host_entry;
} else {
fst->fst_next = host_entry->fstab_ents;
host_entry->fstab_ents = fst;
free(host);
}
} else {
struct staticmap *static_ent;
struct staticmap **static_bucket;
if (strlen(fs->fs_file) == 0) {
pr_msg("Mount for %s has an empty mount point path",
fs->fs_spec);
continue;
}
static_ent = find_staticmap_entry(fs->fs_file,
&static_bucket);
if (static_ent == NULL) {
static_ent = malloc(sizeof (struct staticmap));
if (static_ent == NULL) {
free(url);
free(mntops);
free(localpath);
free(host);
goto outofmem;
}
static_ent->dir = strdup(fs->fs_file);
if (static_ent->dir == NULL) {
free(static_ent);
free(url);
free(mntops);
free(localpath);
free(host);
goto outofmem;
}
static_ent->vfstype = strdup(vfstype);
if (static_ent->vfstype == NULL) {
free(static_ent->dir);
free(static_ent);
free(url);
free(mntops);
free(localpath);
free(host);
goto outofmem;
}
static_ent->mntops = mntops;
static_ent->host = host;
if (url != NULL) {
static_ent->localpath = localpath;
static_ent->spec = url;
} else {
static_ent->localpath = localpath;
static_ent->spec = strdup(localpath);
if (static_ent->spec == NULL) {
free(static_ent->localpath);
free(static_ent->host);
free(static_ent->vfstype);
free(static_ent->dir);
free(static_ent);
free(url);
free(mntops);
goto outofmem;
}
}
static_ent->next = *static_bucket;
*static_bucket = static_ent;
} else {
free(mntops);
free(url);
free(localpath);
free(host);
}
}
}
endfsent();
sort_fstab_entries();
min_cachetime = time(NULL) + MIN_CACHE_TIME;
max_cachetime = time(NULL) + RDDIR_CACHE_TIME * 2;
return (0);
outofmem:
endfsent();
clean_hashtables();
pthread_rwlock_unlock(&fstab_cache_lock);
pr_msg("Memory allocation failed while reading fstab");
return (ENOMEM);
}
int
fstab_process_host(const char *host, int (*callback)(struct fstabnode *, void *),
void *callback_arg)
{
int err;
struct fstabhost *hostent;
struct fstabnode *fst;
err = pthread_rwlock_rdlock(&fstab_cache_lock);
if (err != 0)
return (err);
hostent = find_host_entry(host, NULL);
if (hostent == NULL) {
err = readfstab();
if (err != 0) {
return (err);
}
hostent = find_host_entry(host, NULL);
if (hostent == NULL) {
pthread_rwlock_unlock(&fstab_cache_lock);
return (-1);
}
}
err = 0;
for (fst = hostent->fstab_ents; fst != NULL; fst = fst->fst_next) {
err = (*callback)(fst, callback_arg);
if (err != 0)
break;
}
pthread_rwlock_unlock(&fstab_cache_lock);
return (err);
}
static int
scan_fstab(struct dir_entry **list, struct dir_entry **lastp)
{
int i;
struct fstabhost *host_ent;
int err;
*lastp = NULL;
for (i = 0; i < HASHTABLESIZE; i++) {
for (host_ent = fstab_hashtable[i]; host_ent != NULL;
host_ent = host_ent->next) {
err = add_dir_entry(host_ent->name, NULL, NULL, list,
lastp);
if (err != -1) {
if (err)
return (err);
assert(*lastp != NULL);
}
}
}
return (0);
}
int
getfstabkeys(struct dir_entry **list, int *error, int *cache_time)
{
int err;
time_t timediff;
struct dir_entry *last;
char thishost[MAXHOSTNAMELEN];
char *p;
err = pthread_rwlock_rdlock(&fstab_cache_lock);
if (err != 0) {
*error = err;
return (__NSW_UNAVAIL);
}
timediff = max_cachetime - time(NULL);
if (timediff < 0)
timediff = 0;
else if (timediff > INT_MAX)
timediff = INT_MAX;
*cache_time = (int)timediff;
*error = 0;
if (trace > 1)
trace_prt(1, "getfstabkeys called\n");
*error = scan_fstab(list, &last);
if (*error == 0) {
if (*list == NULL) {
err = readfstab();
if (err != 0) {
*error = err;
return (__NSW_UNAVAIL);
}
*error = scan_fstab(list, &last);
}
}
if (*list != NULL) {
*error = 0;
}
if (*error == 0) {
if (we_are_a_server()) {
gethostname(thishost, MAXHOSTNAMELEN);
err = add_dir_entry(thishost, NULL, NULL, list, &last);
if (err != -1) {
if (err)
return (err);
assert(last != NULL);
}
p = strchr(thishost, '.');
if (p != NULL) {
*p = '\0';
err = add_dir_entry(thishost, NULL, NULL, list,
&last);
if (err != -1) {
if (err)
return (err);
assert(last != NULL);
}
}
}
}
pthread_rwlock_unlock(&fstab_cache_lock);
return (__NSW_SUCCESS);
}
int
havefstabkeys(void)
{
int err;
int ret;
int i;
if (we_are_a_server())
return (1);
err = pthread_rwlock_rdlock(&fstab_cache_lock);
if (err != 0)
return (0);
if (trace > 1)
trace_prt(1, "havefstabkeys called\n");
for (i = 0; i < HASHTABLESIZE; i++) {
if (fstab_hashtable[i] != NULL) {
ret = 1;
goto done;
}
}
err = readfstab();
if (err != 0) {
ret = 0;
goto done;
}
for (i = 0; i < HASHTABLESIZE; i++) {
if (fstab_hashtable[i] != NULL) {
ret = 1;
goto done;
}
}
ret = 0;
done:
pthread_rwlock_unlock(&fstab_cache_lock);
return (ret);
}
int
loaddirect_static(local_map, opts, stack, stkptr)
char *local_map, *opts;
char **stack, ***stkptr;
{
int done = 0;
int i;
struct staticmap *static_ent;
if (pthread_rwlock_rdlock(&fstab_cache_lock) != 0)
return (__NSW_UNAVAIL);
for (i = 0; i < HASHTABLESIZE; i++) {
for (static_ent = static_hashtable[i]; static_ent != NULL;
static_ent = static_ent->next) {
dirinit(static_ent->dir, local_map, opts, 1, stack, stkptr);
done++;
}
}
if (!done) {
if (readfstab() != 0) {
return (__NSW_UNAVAIL);
}
for (i = 0; i < HASHTABLESIZE; i++) {
for (static_ent = static_hashtable[i];
static_ent != NULL;
static_ent = static_ent->next) {
dirinit(static_ent->dir, local_map, opts, 1, stack, stkptr);
done++;
}
}
}
pthread_rwlock_unlock(&fstab_cache_lock);
return (done ? __NSW_SUCCESS : __NSW_NOTFOUND);
}
struct staticmap *
get_staticmap_entry(const char *dir)
{
struct staticmap *static_ent;
if (pthread_rwlock_rdlock(&fstab_cache_lock) != 0)
return (NULL);
static_ent = find_staticmap_entry(dir, NULL);
if (static_ent == NULL) {
if (readfstab() != 0) {
pthread_rwlock_unlock(&fstab_cache_lock);
return (NULL);
}
static_ent = find_staticmap_entry(dir, NULL);
if (static_ent == NULL)
pthread_rwlock_unlock(&fstab_cache_lock);
}
return (static_ent);
}
void
release_staticmap_entry(__unused struct staticmap *static_ent)
{
pthread_rwlock_unlock(&fstab_cache_lock);
}
void
clean_fstab_cache(int scheduled)
{
if (scheduled && max_cachetime > time(NULL))
return;
if (pthread_rwlock_wrlock(&fstab_cache_lock) != 0)
return;
clean_hashtables();
min_cachetime = 0;
pthread_rwlock_unlock(&fstab_cache_lock);
}