#pragma ident "@(#)autod_readdir.c 1.23 05/06/08 SMI"
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/param.h>
#include <errno.h>
#include <pwd.h>
#include <locale.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <fcntl.h>
#include "automount.h"
#include "automountd.h"
static void build_dir_entry_list(struct rddir_cache *rdcp,
struct dir_entry *list);
static void build_subdir_entry_list(struct dir_entry *list, ino_t start_inonum);
static int rddir_cache_enter(const char *map, uint_t bucket_size,
struct rddir_cache **rdcpp);
static int rddir_cache_lookup(const char *map, struct rddir_cache **rdcpp);
static int rddir_cache_delete(struct rddir_cache *rdcp);
static struct dir_entry *scan_cache_entry_for_bucket(struct rddir_cache *rdcp,
off_t offset);
static int create_dirents(struct dir_entry *list, off_t offset,
uint32_t rda_count,
off_t *rddir_offset,
boolean_t *rddir_eof,
byte_buffer *rddir_entries,
mach_msg_type_number_t *rddir_entriesCnt);
static void free_offset_tbl(struct off_tbl *head);
static void free_dir_list(struct dir_entry *head);
#define OFFSET_BUCKET_SIZE 100
pthread_rwlock_t rddir_cache_lock;
struct rddir_cache *rddir_head;
int
do_readdir(autofs_pathname rda_map, off_t rda_offset,
uint32_t rda_count, off_t *rddir_offset,
boolean_t *rddir_eof, byte_buffer *rddir_entries,
mach_msg_type_number_t *rddir_entriesCnt)
{
struct dir_entry *list = NULL, *l, *bucket;
struct rddir_cache *rdcp = NULL;
int error;
int cache_time = RDDIR_CACHE_TIME;
if (automountd_nobrowse) {
*rddir_entriesCnt = 0;
*rddir_eof = TRUE;
*rddir_entries = NULL;
return (0);
}
pthread_rwlock_rdlock(&rddir_cache_lock);
error = rddir_cache_lookup(rda_map, &rdcp);
if (error) {
pthread_rwlock_unlock(&rddir_cache_lock);
pthread_rwlock_wrlock(&rddir_cache_lock);
error = rddir_cache_lookup(rda_map, &rdcp);
if (error) {
if (trace > 2)
trace_prt(1,
"map %s not found, adding...\n", rda_map);
error = rddir_cache_enter(rda_map,
OFFSET_BUCKET_SIZE, &rdcp);
}
}
pthread_rwlock_unlock(&rddir_cache_lock);
if (error)
return (error);
assert(rdcp != NULL);
assert(rdcp->in_use);
if (!rdcp->full) {
pthread_rwlock_wrlock(&rdcp->rwlock);
if (!rdcp->full) {
char *stack[STACKSIZ];
char **stkptr;
stack_op(INIT, NULL, stack, &stkptr);
(void) getmapkeys(rda_map, &list, &error,
&cache_time, stack, &stkptr);
if (!error)
build_dir_entry_list(rdcp, list);
else if (list) {
free_dir_list(list);
list = NULL;
}
}
} else
pthread_rwlock_rdlock(&rdcp->rwlock);
if (!error) {
bucket = scan_cache_entry_for_bucket(rdcp, rda_offset);
error = create_dirents(bucket, rda_offset, rda_count,
rddir_offset, rddir_eof, rddir_entries,
rddir_entriesCnt);
if (error) {
if (rdcp->offtp) {
free_offset_tbl(rdcp->offtp);
rdcp->offtp = NULL;
}
if (rdcp->entp) {
free_dir_list(rdcp->entp);
rdcp->entp = NULL;
}
rdcp->full = 0;
list = NULL;
}
}
if (trace > 2) {
for (l = list; l != NULL; l = l->next)
trace_prt(0, "%s\n", l->name);
trace_prt(0, "\n");
}
if (!error) {
if (cache_time) {
rdcp->ttl = time((time_t *)NULL) + cache_time;
} else {
if (rdcp->offtp) {
free_offset_tbl(rdcp->offtp);
rdcp->offtp = NULL;
}
if (rdcp->entp) {
free_dir_list(rdcp->entp);
rdcp->entp = NULL;
}
rdcp->full = 0;
}
} else {
*rddir_entriesCnt = 0;
*rddir_eof = TRUE;
*rddir_entries = NULL;
}
pthread_rwlock_unlock(&rdcp->rwlock);
pthread_mutex_lock(&rdcp->lock);
rdcp->in_use--;
pthread_mutex_unlock(&rdcp->lock);
assert(rdcp->in_use >= 0);
return (error);
}
int
do_readsubdir(autofs_pathname rda_map, char *key,
autofs_pathname rda_subdir, autofs_opts mapopts, uint32_t rda_dirino,
off_t rda_offset, uint32_t rda_count, off_t *rddir_offset,
boolean_t *rddir_eof, byte_buffer *rddir_entries,
mach_msg_type_number_t *rddir_entriesCnt)
{
struct mapent *me, *mapents;
int err;
bool_t isrestricted = hasrestrictopt(mapopts);
char *p;
struct dir_entry *list = NULL;
struct dir_entry *last = NULL;
mapents = parse_entry(key, rda_map, mapopts, rda_subdir, FALSE,
NULL, isrestricted, TRUE, &err);
if (mapents == NULL) {
return (err);
}
for (me = mapents; me; me = me->map_next) {
p = me->map_mntpnt;
if (p == NULL) {
syslog(LOG_ERR, "null mountpoint in entry in %s",
me->map_root ? me->map_root : "<NULL>");
continue;
}
while (*p == '/')
p++;
err = add_dir_entry(p, NULL, NULL, &list, &last);
if (err != -1) {
if (err != 0) {
if (list)
free_dir_list(list);
free_mapent(mapents);
*rddir_entriesCnt = 0;
*rddir_eof = TRUE;
*rddir_entries = NULL;
return (err);
}
}
}
if (mapents)
free_mapent(mapents);
rda_dirino = ((rda_dirino >> 16) & 0x0000FFFF) |
((rda_dirino << 16) & 0xFFFF0000);
if (rda_dirino & 0x00000001)
rda_dirino &= ~0x00010001;
build_subdir_entry_list(list, rda_dirino);
err = create_dirents(list, rda_offset, rda_count,
rddir_offset, rddir_eof, rddir_entries,
rddir_entriesCnt);
if (err) {
*rddir_entriesCnt = 0;
*rddir_eof = TRUE;
*rddir_entries = NULL;
}
return (err);
}
static struct dir_entry *
scan_cache_entry_for_bucket(struct rddir_cache *rdcp, off_t offset)
{
struct off_tbl *offtp, *next = NULL;
int this_bucket = 0;
struct dir_entry *list = NULL;
int x = 0;
#if 0
assert(RW_LOCK_HELD(&rdcp->rwlock));
#endif
for (offtp = rdcp->offtp; offtp != NULL; offtp = next) {
x++;
next = offtp->next;
this_bucket = (next == NULL);
if (!this_bucket)
this_bucket = (offset < next->offset);
if (this_bucket) {
assert(offset >= offtp->offset);
list = offtp->first;
break;
}
}
if (trace > 2)
trace_prt(1, "%s: offset searches (%d)\n", rdcp->map, x);
return (list);
}
static int
create_dirents(struct dir_entry *list, off_t offset, uint32_t rda_count,
off_t *rddir_offset, boolean_t *rddir_eof, byte_buffer *rddir_entries,
mach_msg_type_number_t *rddir_entriesCnt)
{
uint_t total_bytes_wanted;
size_t bufsize;
size_t this_reclen;
uint_t outcount = 0;
int namelen;
struct dir_entry *l;
kern_return_t ret;
vm_address_t buffer_vm_address;
struct dirent_nonext *dp;
uint8_t *outbuf;
int error = 0;
int y = 0;
for (l = list; l != NULL && l->offset < offset; l = l->next)
y++;
if (l == NULL) {
error = 0;
goto empty;
}
if (trace > 2)
trace_prt(1, "offset searches (%d)\n", y);
total_bytes_wanted = rda_count;
bufsize = total_bytes_wanted + sizeof (struct dirent_nonext);
ret = vm_allocate(current_task(), &buffer_vm_address,
bufsize, VM_FLAGS_ANYWHERE);
if (ret != KERN_SUCCESS) {
syslog(LOG_ERR, "memory allocation error: %s",
mach_error_string(ret));
error = ENOMEM;
goto empty;
}
outbuf = (uint8_t *)buffer_vm_address;
memset(outbuf, 0, bufsize);
dp = (struct dirent_nonext *)outbuf;
for (;;) {
namelen = (int)strlen(l->name);
this_reclen = DIRENT_RECLEN(namelen);
if (outcount + this_reclen > total_bytes_wanted) {
break;
}
dp->d_ino = (__uint32_t)l->nodeid;
dp->d_reclen = this_reclen;
#if 0
dp->d_type = DT_DIR;
#else
dp->d_type = DT_UNKNOWN;
#endif
dp->d_namlen = namelen;
(void) strlcpy(dp->d_name, l->name, NAME_MAX);
outcount += dp->d_reclen;
dp = (struct dirent_nonext *)((char *)dp + dp->d_reclen);
assert(outcount <= total_bytes_wanted);
if (!l->next)
break;
l = l->next;
}
*rddir_offset = l->offset + 1;
if (outcount > 0) {
*rddir_entriesCnt = outcount;
*rddir_eof = (l == NULL);
*rddir_entries = outbuf;
error = 0;
} else {
*rddir_entriesCnt = 0;
*rddir_eof = FALSE;
*rddir_entries = NULL;
vm_deallocate(current_task(), buffer_vm_address, bufsize);
syslog(LOG_ERR,
"byte count in readdir too small for one directory entry");
error = EIO;
}
return (error);
empty: *rddir_entriesCnt = 0;
*rddir_eof = TRUE;
*rddir_entries = NULL;
return (error);
}
static int
rddir_cache_enter(const char *map, uint_t bucket_size,
struct rddir_cache **rdcpp)
{
struct rddir_cache *p;
int len;
#if 0
assert(RW_LOCK_HELD(&rddir_cache_lock));
#endif
p = (struct rddir_cache *)malloc(sizeof (*p));
if (p == NULL) {
syslog(LOG_ERR,
"rddir_cache_enter: memory allocation failed\n");
return (ENOMEM);
}
memset((char *)p, 0, sizeof (*p));
len = (int) strlen(map) + 1;
p->map = malloc(len);
if (p->map == NULL) {
syslog(LOG_ERR,
"rddir_cache_enter: memory allocation failed\n");
free(p);
return (ENOMEM);
}
strlcpy(p->map, map, len);
p->bucket_size = bucket_size;
p->in_use = 1;
(void) pthread_rwlock_init(&p->rwlock, NULL);
(void) pthread_mutex_init(&p->lock, NULL);
if (rddir_head == NULL)
rddir_head = p;
else {
p->next = rddir_head;
rddir_head = p;
}
*rdcpp = p;
return (0);
}
static int
rddir_cache_lookup(const char *map, struct rddir_cache **rdcpp)
{
struct rddir_cache *p;
#if 0
assert(RW_LOCK_HELD(&rddir_cache_lock));
#endif
for (p = rddir_head; p != NULL; p = p->next) {
if (strcmp(p->map, map) == 0) {
*rdcpp = p;
pthread_mutex_lock(&p->lock);
p->in_use++;
pthread_mutex_unlock(&p->lock);
return (0);
}
}
return (ENOENT);
}
static void
free_offset_tbl(struct off_tbl *head)
{
struct off_tbl *p, *next = NULL;
for (p = head; p != NULL; p = next) {
next = p->next;
free(p);
}
}
static void
free_dir_list(struct dir_entry *head)
{
struct dir_entry *p, *next = NULL;
for (p = head; p != NULL; p = next) {
next = p->next;
free(p->line);
free(p->lineq);
assert(p->name);
free(p->name);
free(p);
}
}
static void
rddir_cache_entry_free(struct rddir_cache *p)
{
#if 0
assert(RW_LOCK_HELD(&rddir_cache_lock));
#endif
assert(!p->in_use);
if (p->map)
free(p->map);
if (p->offtp)
free_offset_tbl(p->offtp);
if (p->entp)
free_dir_list(p->entp);
free(p);
}
static int
rddir_cache_delete(struct rddir_cache *rdcp)
{
struct rddir_cache *p, *prev;
#if 0
assert(RW_LOCK_HELD(&rddir_cache_lock));
#endif
prev = NULL;
for (p = rddir_head; p != NULL; p = p->next) {
if (p == rdcp) {
if (p->in_use)
return (EBUSY);
if (prev)
prev->next = p->next;
else
rddir_head = p->next;
rddir_cache_entry_free(p);
return (0);
}
prev = p;
}
syslog(LOG_ERR, "Couldn't find entry %p in cache\n", rdcp);
return (ENOENT);
}
struct dir_entry *
rddir_entry_lookup(const char *mapname, const char *name)
{
int err;
struct rddir_cache *rdcp;
struct dir_entry *p = NULL;
pthread_rwlock_rdlock(&rddir_cache_lock);
err = rddir_cache_lookup(mapname, &rdcp);
if (!err && rdcp->full) {
pthread_rwlock_unlock(&rddir_cache_lock);
if (pthread_rwlock_tryrdlock(&rdcp->rwlock) == 0) {
p = btree_lookup(rdcp->entp, name);
pthread_rwlock_unlock(&rdcp->rwlock);
}
} else
pthread_rwlock_unlock(&rddir_cache_lock);
if (!err) {
pthread_mutex_lock(&rdcp->lock);
rdcp->in_use--;
assert(rdcp->in_use >= 0);
pthread_mutex_unlock(&rdcp->lock);
}
return (p);
}
static void
build_dir_entry_list(struct rddir_cache *rdcp, struct dir_entry *list)
{
struct dir_entry *p;
off_t offset = AUTOFS_DAEMONCOOKIE, offset_list = AUTOFS_DAEMONCOOKIE;
struct off_tbl *offtp, *last = NULL;
ino_t inonum = 4;
#if 0
assert(RW_LOCK_HELD(&rdcp->rwlock));
#endif
assert(rdcp->entp == NULL);
rdcp->entp = list;
for (p = list; p != NULL; p = p->next) {
p->nodeid = inonum;
p->offset = offset;
if (offset >= offset_list) {
offtp = (struct off_tbl *)
malloc(sizeof (struct off_tbl));
if (offtp != NULL) {
offtp->offset = offset;
offtp->first = p;
offtp->next = NULL;
offset_list += rdcp->bucket_size;
} else {
syslog(LOG_ERR,
"WARNING: build_dir_entry_list: could not add offset to index table\n");
continue;
}
if (rdcp->offtp == NULL)
rdcp->offtp = offtp;
else
last->next = offtp;
last = offtp;
}
offset++;
inonum += 2;
}
rdcp->full = 1;
}
static void
build_subdir_entry_list(struct dir_entry *list, ino_t start_inonum)
{
struct dir_entry *p;
off_t offset = AUTOFS_DAEMONCOOKIE;
ino_t inonum = start_inonum;
for (p = list; p != NULL; p = p->next) {
p->nodeid = inonum;
p->offset = offset;
offset++;
inonum += 2;
}
}
pthread_mutex_t cleanup_lock;
pthread_cond_t cleanup_start_cv;
pthread_cond_t cleanup_done_cv;
void *
cache_cleanup(__unused void *unused)
{
struct timespec abstime;
struct rddir_cache *p, *next = NULL;
int error;
pthread_setname_np("cache cleanup");
pthread_mutex_init(&cleanup_lock, NULL);
pthread_cond_init(&cleanup_start_cv, NULL);
pthread_cond_init(&cleanup_done_cv, NULL);
pthread_mutex_lock(&cleanup_lock);
for (;;) {
abstime.tv_sec = time(NULL) + RDDIR_CACHE_TIME/2;
abstime.tv_nsec = 0;
if ((error = pthread_cond_timedwait(
&cleanup_start_cv, &cleanup_lock, &abstime)) != 0) {
if (error != ETIMEDOUT) {
if (trace > 1)
trace_prt(1,
"cleanup thread wakeup (%d)\n", error);
continue;
}
}
pthread_mutex_unlock(&cleanup_lock);
pthread_rwlock_wrlock(&rddir_cache_lock);
for (p = rddir_head; p != NULL; p = next) {
next = p->next;
if (p->in_use > 0) {
if (trace > 1) {
trace_prt(1,
"%s cache in use\n", p->map);
}
continue;
}
if (error == ETIMEDOUT && (p->ttl > time((time_t *)NULL))) {
if (trace > 1) {
trace_prt(1,
"%s cache still valid\n", p->map);
}
continue;
}
if (trace > 1)
trace_prt(1, "%s freeing cache\n", p->map);
assert(!p->in_use);
error = rddir_cache_delete(p);
assert(!error);
}
pthread_rwlock_unlock(&rddir_cache_lock);
clean_fstab_cache(error == ETIMEDOUT);
pthread_mutex_lock(&cleanup_lock);
pthread_cond_broadcast(&cleanup_done_cv);
}
return NULL;
}