/*
 * zdsfs - FUSE file system for z/OS data set access
 *
 * Copyright IBM Corp. 2013, 2017
 *
 * s390-tools is free software; you can redistribute it and/or modify
 * it under the terms of the MIT license. See LICENSE for details.
 */

/* The fuse version define tells fuse that we want to use the new API */
#define FUSE_USE_VERSION 30

#include <errno.h>
#include <fcntl.h>
#include <fuse.h>
#include <fuse_opt.h>
#include <limits.h>
#include <malloc.h>
#include <pthread.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>
#include <time.h>
#include <signal.h>

#include <iconv.h>

#ifdef HAVE_SETXATTR
#include <linux/xattr.h>
#endif

#include "lib/libzds.h"
#include "lib/util_libc.h"
#include "lib/zt_common.h"

#define COMP "zdsfs: "

#define METADATAFILE "metadata.txt"

/* defaults for file and directory permissions (octal) */
#define DEF_FILE_PERM 0440
#define DEF_DIR_PERM 0550
/* default timer interval 9 minutes, enq times out after 10 minutes */
#define DEFAULT_KEEPALIVE_SEC	         540

#define SECTION_ENTRIES 3
static char CODEPAGE_EDF[] = "CP1047";
static char CODEPAGE_LINUX[] = "UTF-8";

struct zdsfs_info {
	int devcount;
	int allow_inclomplete_multi_volume;
	int keepRDW;
	int host_count;
	unsigned int tracks_per_frame;
	unsigned long long seek_buffer_size;
	struct zdsroot *zdsroot;

	char *metadata;  /* buffer that contains the content of metadata.txt */
	size_t metasize; /* total size of meta data buffer */
	size_t metaused; /* how many bytes of buffer are already filled */
	time_t metatime; /* when did we create this meta data */
	char *configfile;
	int restapi;
	unsigned int nr_server;
	int active_server;
	char *server[MAX_SERVER];
	long keepalive;
	int codepage_convert;
	char *codepage_from;
	char *codepage_to;
	iconv_t iconv;
	struct util_list *dsclist;
	char *dsfile;
};

static struct zdsfs_info zdsfsinfo;
static int zdsfs_create_meta_data_buffer(struct zdsfs_info *);
static int zdsfs_verify_datasets(void);
static struct util_list *open_dsh;
static int timer_running;

struct dsh_node {
	struct util_list_node node;
	struct dshandle *dsh;
};

struct zdsfs_file_info {
	struct dshandle *dsh;
	pthread_mutex_t mutex;

	int is_metadata_file;
	size_t metaread; /* how many bytes have already been read */
};

struct dsconvert {
	char *name;
	char *codepage_from;
	char *codepage_to;
	bool keeprdw;
};

struct dsc_node {
	struct util_list_node node;
	struct dsconvert *dsc;
};

/* Allocate and initialize a new list of struct dsh_node. */
static struct util_list *dshlist_alloc(void)
{
	struct util_list *list;

	list = util_malloc(sizeof(struct util_list));
	util_list_init(list, struct dsh_node, node);

	return list;
}

/* free list of struct dsh_node. */
static void dshlist_free(struct util_list *list)
{
	struct dsh_node *s, *n;

	if (!list)
		return;

	util_list_iterate_safe(list, s, n) {
		util_list_remove(list, s);
		free(s);
	}

	free(list);
}

/* add dsh to list */
static void dshlist_add(struct util_list *list, struct dshandle *dsh)
{
	struct dsh_node *s;

	s = util_malloc(sizeof(struct dsh_node));
	s->dsh = dsh;
	util_list_add_tail(list, s);
}

/* Find a dsh_node. */
static struct dsh_node *dshlist_find(struct util_list *list, struct dshandle *dsh)
{
	struct dsh_node *s;

	util_list_iterate(list, s) {
		if (s->dsh == dsh)
			return s;
	}

	return NULL;
}

/* Remove a dsh_node from the list. */
void dshlist_remove(struct util_list *list, struct dshandle *dsh)
{
	struct dsh_node *p;

	p = dshlist_find(list, dsh);
	if (p) {
		util_list_remove(list, p);
		free(p);
	}
}

/* Allocate and initialize a new list of struct dsc_node. */
static struct util_list *dsclist_alloc(void)
{
	struct util_list *list;

	list = util_malloc(sizeof(struct util_list));
	util_list_init(list, struct dsc_node, node);

	return list;
}

/* free struct dsconvert. */
static void dsc_free(struct dsconvert *dsc)
{
	free(dsc->name);
	free(dsc->codepage_from);
	free(dsc->codepage_to);
	free(dsc);
}

/* free list of struct dsc_node. */
static void dsclist_free(struct util_list *list)
{
	struct dsc_node *s, *n;

	if (!list)
		return;

	util_list_iterate_safe(list, s, n) {
		util_list_remove(list, s);
		dsc_free(s->dsc);
		free(s);
	}

	free(list);
}

/* add dsc to list */
static void dsclist_add(struct util_list *list, struct dsconvert *dsc)
{
	struct dsc_node *s;

	s = util_malloc(sizeof(struct dsc_node));
	s->dsc = dsc;
	util_list_add_tail(list, s);
}

/* Find a dsc_node by name. */
static struct dsc_node *dsclist_find_by_name(struct util_list *list, char *name)
{
	struct dsc_node *s;

	util_list_iterate(list, s) {
		if (strcmp(s->dsc->name, name) == 0)
			return s;
	}

	return NULL;
}

/* normalize the given path name to a dataset name
 * so that we can compare it to the names in the vtoc. This means:
 * - remove the leading /
 * - remove member names (everything after a subsequent / )
 * Note: we do no upper case, EBCDIC conversion or padding
 */
static void path_to_ds_name(const char *path, char *normds, size_t size)
{
	char *end;

	if (*path == '/')
		++path;
	util_strlcpy(normds, path, size);
	end = strchr(normds, '/');
	if (end)
		*end = '\0';
}

static void path_to_member_name(const char *path, char *normds, size_t size)
{
	if (*path == '/')
		++path;
	path = strchr(path, '/');
	if (!path)
		normds[0] = '\0';
	else {
		++path;
		util_strlcpy(normds, path, size);
	}
}

static int setup_iconv(iconv_t *conv, const char *from, const char *to)
{
	*conv = iconv_open(to, from);
	if (*conv == ((iconv_t) -1)) {
		fprintf(stderr, "error when setting up iconv\n");
		return -1;
	}
	return 0;
}

static void setup_timer(long sec)
{
	static struct itimerval timer;

	memset(&timer, 0, sizeof(struct itimerval));
	timer.it_value.tv_sec = sec;
	setitimer(ITIMER_REAL, &timer, NULL);
}

static void keep_alive(int UNUSED(signal))
{
	struct dsh_node *s;

	if (!open_dsh) {
		timer_running = 0;
		setup_timer(0);
		return;
	}

	util_list_iterate(open_dsh, s) {
		lzds_rest_ping(s->dsh,
			       zdsfsinfo.server[zdsfsinfo.active_server]);
	}
	timer_running = 1;
	setup_timer(zdsfsinfo.keepalive);
}

void keepalive_start(void)
{
	struct sigaction act;

	if (!open_dsh || timer_running)
		return;

	timer_running = 1;
	/* Setup timer for periodic ping. */
	memset(&act, 0, sizeof(struct sigaction));
	act.sa_handler = &keep_alive;
	sigaction(SIGALRM, &act, NULL);
	setup_timer(zdsfsinfo.keepalive);
}

static int zdsfs_getattr(const char *path, struct stat *stbuf,
			 struct fuse_file_info *UNUSED(fi))
{
	char normds[MAXDSNAMELENGTH];
	size_t dssize;
	struct dataset *ds;
	struct pdsmember *member;
	int rc, ispds, issupported;
	unsigned long long tracks;
	format1_label_t *f1;
	time_t time;
	struct tm tm;

	memset(stbuf, 0, sizeof(struct stat));
	/* root directory case */
	if (strcmp(path, "/") == 0) {
		stbuf->st_mode = S_IFDIR | DEF_DIR_PERM;
		stbuf->st_nlink = 2;
		stbuf->st_atime = zdsfsinfo.metatime;
		stbuf->st_mtime = zdsfsinfo.metatime;
		stbuf->st_ctime = zdsfsinfo.metatime;
		return 0;
	}

	if (strcmp(path, "/"METADATAFILE) == 0) {
		stbuf->st_mode = S_IFREG | DEF_FILE_PERM;
		stbuf->st_nlink = 1;
		stbuf->st_size = zdsfsinfo.metaused;
		stbuf->st_atime = zdsfsinfo.metatime;
		stbuf->st_mtime = zdsfsinfo.metatime;
		stbuf->st_ctime = zdsfsinfo.metatime;
		return 0;
	}

	path_to_ds_name(path, normds, sizeof(normds));
	rc = lzds_zdsroot_find_dataset(zdsfsinfo.zdsroot, normds, &ds);
	if (rc)
		return -rc;

	lzds_dataset_get_is_supported(ds, &issupported);
	if (!issupported)
		return -ENOENT;

	/* upper limit for the size of the whole data set */
	lzds_dataset_get_size_in_tracks(ds, &tracks);
	dssize = MAXRECSIZE * tracks;

	/* get the last access time */
	lzds_dataset_get_format1_dscb(ds, &f1);
	memset(&tm, 0, sizeof(tm));
	if (f1->DS1REFD.year || f1->DS1REFD.day) {
		tm.tm_year = f1->DS1REFD.year;
		tm.tm_mday = f1->DS1REFD.day;
	} else {
		tm.tm_year = f1->DS1CREDT.year;
		tm.tm_mday = f1->DS1CREDT.day;
	}
	tm.tm_isdst = -1;
	time = mktime(&tm);
	if (time < 0)
		time = 0;

	lzds_dataset_get_is_PDS(ds, &ispds);
	if (ispds) {
		path_to_member_name(path, normds, sizeof(normds));
		/* the dataset itself is represented as directory */
		if (strcmp(normds, "") == 0) {
			stbuf->st_mode = S_IFDIR | DEF_DIR_PERM;
			stbuf->st_nlink = 2;
			stbuf->st_size = 0;
			stbuf->st_atime = time;
			stbuf->st_mtime = time;
			stbuf->st_ctime = time;
			stbuf->st_blocks = tracks * 16 * 8;
			stbuf->st_size = dssize;
			return 0;
		}
		rc = lzds_dataset_get_member_by_name(ds, normds, &member);
		if (rc)
			return -ENOENT;
		stbuf->st_mode = S_IFREG | DEF_FILE_PERM;
		stbuf->st_nlink = 1;
		/* the member cannot be bigger than the data set */
		stbuf->st_size = dssize;
		return 0;
	} else { /* normal data set */
		stbuf->st_mode = S_IFREG | DEF_FILE_PERM;
		stbuf->st_nlink = 1;
		stbuf->st_size = dssize;
		stbuf->st_blocks = tracks * 16 * 8;
		stbuf->st_atime = time;
		stbuf->st_mtime = time;
		stbuf->st_ctime = time;
	}
	return 0;
}

static void zdsfs_read_device(struct dasd *newdasd, const char *device)
{
	struct errorlog *log;
	int rc;

	rc = dasd_disk_reserve(device);
	if (rc) {
		fprintf(stderr, "error when reserving device %s: %s\n",
			device, strerror(rc));
		lzds_dasd_get_errorlog(newdasd, &log);
		lzds_errorlog_fprint(log, stderr);
		exit(1);
	}
	rc = lzds_dasd_alloc_rawvtoc(newdasd);
	if (rc) {
		fprintf(stderr, "error when reading VTOC from device %s: %s\n",
			device, strerror(rc));
		lzds_dasd_get_errorlog(newdasd, &log);
		lzds_errorlog_fprint(log, stderr);
		exit(1);
	}
	rc = lzds_zdsroot_extract_datasets_from_dasd(zdsfsinfo.zdsroot,
						     newdasd);
	if (rc) {
		fprintf(stderr,
			"error when extracting data sets from dasd %s: %s\n",
			device, strerror(rc));
		lzds_zdsroot_get_errorlog(zdsfsinfo.zdsroot, &log);
		lzds_errorlog_fprint(log, stderr);
		exit(1);
	}
	rc = dasd_disk_release(device);
	if (rc) {
		fprintf(stderr, "error when releasing device %s: %s\n",
			device, strerror(rc));
		lzds_dasd_get_errorlog(newdasd, &log);
		lzds_errorlog_fprint(log, stderr);
		exit(1);
	}
}


static int zdsfs_statfs(const char *UNUSED(path), struct statvfs *statvfs)
{
	struct dasditerator *dasdit;
	unsigned int cyls, heads;
	struct dasd *dasd;
	struct dataset *ds;
	struct dsiterator *dsit;
	unsigned long long totaltracks, usedtracks, dstracks;
	int rc;

	totaltracks = 0;
	rc = lzds_zdsroot_alloc_dasditerator(zdsfsinfo.zdsroot, &dasdit);
	if (rc)
		return -ENOMEM;
	while (!lzds_dasditerator_get_next_dasd(dasdit, &dasd)) {
		lzds_dasd_get_cylinders(dasd, &cyls);
		lzds_dasd_get_heads(dasd, &heads);
		totaltracks += cyls * heads;
	}
	lzds_dasditerator_free(dasdit);

	usedtracks = 0;
	rc = lzds_zdsroot_alloc_dsiterator(zdsfsinfo.zdsroot, &dsit);
	if (rc)
		return -ENOMEM;
	while (!lzds_dsiterator_get_next_dataset(dsit, &ds)) {
		/* To compute the occupied space we consider all data sets,
		 * not just the supported ones */
		lzds_dataset_get_size_in_tracks(ds, &dstracks);
		usedtracks += dstracks;
	}
	lzds_dsiterator_free(dsit);

	if (totaltracks < usedtracks)
		return -EPROTO;

	memset(statvfs, 0, sizeof(*statvfs));
	statvfs->f_bsize = RAWTRACKSIZE;
	statvfs->f_frsize = RAWTRACKSIZE;
	statvfs->f_blocks = totaltracks;
	statvfs->f_bfree = totaltracks - usedtracks;
	statvfs->f_bavail = totaltracks - usedtracks;
	statvfs->f_namemax = MAXDSNAMELENGTH - 1;
	return 0;
}


static int zdsfs_update_vtoc(void)
{
	struct dasditerator *dasdit;
	struct dasd *dasd;
	int rc;

	lzds_dslist_free(zdsfsinfo.zdsroot);
	rc = lzds_zdsroot_alloc_dasditerator(zdsfsinfo.zdsroot, &dasdit);
	if (rc)
		return -ENOMEM;

	while (!lzds_dasditerator_get_next_dasd(dasdit, &dasd))
		zdsfs_read_device(dasd, dasd->device);

	lzds_dasditerator_free(dasdit);
	rc = zdsfs_verify_datasets();
	if (rc)
		return rc;

	rc = zdsfs_create_meta_data_buffer(&zdsfsinfo);
	if (rc)
		return rc;

	return 0;
}

static int zdsfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
			 off_t UNUSED(offset), struct fuse_file_info *UNUSED(fi),
			 enum fuse_readdir_flags UNUSED(flags))
{
	char normds[MAXDSNAMELENGTH];
	char *mbrname;
	char *dsname;
	struct dataset *ds;
	struct dsiterator *dsit;
	struct memberiterator *it;
	struct pdsmember *member;
	int rc;
	int ispds, issupported;

	rc = zdsfs_update_vtoc();
	if (rc)
		return rc;

	/* we have two type of directories
	 * type one: the root directory contains all data sets
	 */
	if (strcmp(path, "/") == 0) {
		filler(buf, ".", NULL, 0, 0);
		filler(buf, "..", NULL, 0, 0);
		filler(buf, METADATAFILE, NULL, 0, 0);
		/* note that we do not need to distinguish between
		 * normal files and directories here, that is done
		 * in the rdf_getattr function
		 */
		rc = lzds_zdsroot_alloc_dsiterator(zdsfsinfo.zdsroot, &dsit);
		if (rc)
			return -ENOMEM;
		while (!lzds_dsiterator_get_next_dataset(dsit, &ds)) {
			lzds_dataset_get_is_supported(ds, &issupported);
			if (issupported) {
				lzds_dataset_get_name(ds, &dsname);
				filler(buf, dsname, NULL, 0, 0);
			}
		}
		lzds_dsiterator_free(dsit);
		return 0;
	}

	/* type two: a partitioned data set, contains all PDS members */
	path_to_ds_name(path, normds, sizeof(normds));
	rc = lzds_zdsroot_find_dataset(zdsfsinfo.zdsroot, normds, &ds);
	if (rc)
		return -ENOENT;
	lzds_dataset_get_is_PDS(ds, &ispds);
	if (ispds) {
		filler(buf, ".", NULL, 0, 0);
		filler(buf, "..", NULL, 0, 0);
		rc = lzds_dataset_alloc_memberiterator(ds, &it);
		if (rc)
			return -ENOMEM;
		while (!lzds_memberiterator_get_next_member(it, &member)) {
			lzds_pdsmember_get_name(member, &mbrname);
			filler(buf, mbrname, NULL, 0, 0);
		}
		lzds_memberiterator_free(it);
	} else
		return -ENOENT;

	return 0;
}

/*
 * walk through the serverlist and check if the URLs start with http or https
 * if not attach a https:// prefix
 * also check if they end with / and if not attach it
 *
 * afterwards ping the z/OSMF server and use the first working one
 *
 * return 1 if working server found 0 otherwise
 */
static int zdsfs_test_restserver(void)
{
	unsigned int i;
	char *server;
	char *prefix;

	for (i = 0; i < zdsfsinfo.nr_server; i++) {
		server = zdsfsinfo.server[i];
		if (strncmp(server, "http", 4)) {
			prefix = util_strdup("https://");
			server = util_strcat_realloc(prefix, server);
			free(zdsfsinfo.server[i]);
			zdsfsinfo.server[i] = server;
		}
		if (strncmp(server + strlen(server) - 1, "/", 1)) {
			server = util_strcat_realloc(server, "/");
			zdsfsinfo.server[i] = server;
		}
		if (lzds_rest_ping(NULL, zdsfsinfo.server[i])) {
			zdsfsinfo.active_server = i;
			fprintf(stdout, "Using z/OSMF REST services on %s\n",
				zdsfsinfo.server[i]);
			return 1;
		}
	}
	return 0;
}

/*
 * check if a dsconvert entry exists that match the given DS name
 * if the dsconvert entry ends in an asterisk only match the prefix
 * otherwise look for an exact match
 */
static struct dsconvert *zdsfs_get_matching_dsc(char *name)
{
	struct dsconvert *dsc;
	unsigned int length;
	struct dsc_node *n;
	char *match;

	util_list_iterate(zdsfsinfo.dsclist, n) {
		dsc = n->dsc;
		match = dsc->name;
		length = strlen(match);
		if (strcmp(&match[length - 1], "*") == 0)
			length--;
		else if (strlen(name) != length)
			continue;

		if (strncmp(name, match, length) == 0)
			return dsc;
	}
	return NULL;
}

/*
 * Setup iconv conversion for a given dataset.
 * The codepage settings can be obtained from (in descending priority)
 *   - globally set codepage settings
 *   - a dsconvert entry matching the DS name
 *   - default codepage settings.
 */
static int zdsfs_setup_conversion(struct dshandle *dsh, struct dataset *ds)
{
	struct dsconvert *dsc;
	const char *from, *to;
	struct errorlog *log;
	iconv_t *iconv;
	char *dsname;
	int rc;

	from = to = NULL;
	lzds_dataset_get_name(ds, &dsname);
	dsc = zdsfs_get_matching_dsc(dsname);
	/* the DS matches a dsconvert entry */
	if (dsc) {
		from = dsc->codepage_from;
		to = dsc->codepage_to;
	}

	/* globally set conversion overwriting possible config file settings */
	if (zdsfsinfo.codepage_from && zdsfsinfo.codepage_to) {
		from = zdsfsinfo.codepage_from;
		to = zdsfsinfo.codepage_to;
	}

	/*
	 * globally set conversion using defaults
	 * if not specified otherwise already
	 */
	if (zdsfsinfo.codepage_convert) {
		if (!from)
			from = CODEPAGE_EDF;
		if  (!to)
			to = CODEPAGE_LINUX;
	}

	/* no conversion */
	if (!from || !to)
		return 0;

	iconv = util_malloc(sizeof(*iconv));
	rc = setup_iconv(iconv, from, to);
	if (rc) {
		fprintf(stderr,	"Error when preparing iconv setting:\n");
		lzds_dshandle_get_errorlog(dsh, &log);
		lzds_errorlog_fprint(log, stderr);
		rc = -rc;
		goto out;
	}
	rc = lzds_dshandle_set_iconv(dsh, iconv);
	if (rc) {
		fprintf(stderr,	"Error when setting iconv handle:\n");
		lzds_dshandle_get_errorlog(dsh, &log);
		lzds_errorlog_fprint(log, stderr);
		rc = -rc;
		goto out;
	}
	return 0;
out:
	free(iconv);
	return rc;
}


static int zdsfs_open(const char *path, struct fuse_file_info *fi)
{
	char normds[45];
	struct dshandle *dsh;
	struct dataset *ds;
	struct zdsfs_file_info *zfi;
	int rc;
	int ispds, issupported;
	struct errorlog *log;

	if ((fi->flags & 3) != O_RDONLY)
		return -EACCES;
	/*
	 * The contents of the dataset is smaller than the data set
	 * size we return in zdsfs_getattr. We need to set direct_io
	 * to make sure that fuse will report the end of the data with EOF.
	 */
	fi->direct_io = 1;

	zfi = malloc(sizeof(*zfi));
	if (!zfi)
		return -ENOMEM;
	rc = pthread_mutex_init(&zfi->mutex, NULL);
	if (rc)
		goto error1;

	if (strcmp(path, "/"METADATAFILE) == 0) {
		rc = zdsfs_update_vtoc();
		if (rc)
			return rc;
		zfi->dsh = NULL;
		zfi->is_metadata_file = 1;
		zfi->metaread = 0;
		fi->fh = (unsigned long)zfi;
		return 0;
	}

	path_to_ds_name(path, normds, sizeof(normds));
	rc = lzds_zdsroot_find_dataset(zdsfsinfo.zdsroot, normds, &ds);
	if (rc)
		goto error1;

	lzds_dataset_get_is_supported(ds, &issupported);
	if (!issupported) {
		/* we should never get this error, as unsupported data sets are
		 * not listed. But just in case, print a message */
		fprintf(stderr,	"Error: Data set %s is not supported\n", normds);
		rc = -ENOENT;
		goto error1;
	}

	rc = lzds_dataset_alloc_dshandle(ds, zdsfsinfo.tracks_per_frame, &dsh);
	if (rc) {
		rc = -rc;
		goto error1;
	}

	rc = lzds_dshandle_set_seekbuffer(dsh, zdsfsinfo.seek_buffer_size);
	if (rc) {
		fprintf(stderr,	"Error when preparing seek buffer:\n");
		lzds_dshandle_get_errorlog(dsh, &log);
		lzds_errorlog_fprint(log, stderr);
		rc = -rc;
		goto error2;
	}
	/* if the data set is a PDS, then the path must contain a valid
	 * member name, and the context must be set to this member
	 */
	lzds_dataset_get_is_PDS(ds, &ispds);
	if (ispds) {
		path_to_member_name(path, normds, sizeof(normds));
		rc = lzds_dshandle_set_member(dsh, normds);
		if (rc) {
			fprintf(stderr,	"Error when preparing member:\n");
			lzds_dshandle_get_errorlog(dsh, &log);
			lzds_errorlog_fprint(log, stderr);
			rc = -rc;
			goto error2;
		}
	}
	rc = lzds_dshandle_set_keepRDW(dsh, zdsfsinfo.keepRDW);
	if (rc) {
		fprintf(stderr,	"Error when preparing RDW setting:\n");
		lzds_dshandle_get_errorlog(dsh, &log);
		lzds_errorlog_fprint(log, stderr);
		rc = -rc;
		goto error2;
	}
	zdsfs_setup_conversion(dsh, ds);

retry:
	if (zdsfsinfo.restapi && zdsfsinfo.active_server >= 0) {
		rc = lzds_rest_get_enq(dsh,
				  zdsfsinfo.server[zdsfsinfo.active_server]);
		/* if the REST server is not responding try the other */
		if (rc == ECONNREFUSED && zdsfs_test_restserver()) {
			goto retry;
		} else if (rc) {
			lzds_dshandle_get_errorlog(dsh, &log);
			lzds_errorlog_fprint(log, stderr);
			rc = -rc;
			goto error2;
		} else {
			dshlist_add(open_dsh, dsh);
			/* add to open dsh list */
			keepalive_start();
		}
	}
	rc = lzds_dshandle_open(dsh);
	if (rc) {
		fprintf(stderr,	"Error when opening data set:\n");
		lzds_dshandle_get_errorlog(dsh, &log);
		lzds_errorlog_fprint(log, stderr);
		rc = -rc;
		goto error3;
	}
	zfi->is_metadata_file = 0;
	zfi->metaread = 0;
	zfi->dsh = dsh;
	fi->fh = (uint64_t)(unsigned long)zfi;
	return 0;

error3:
	dshlist_remove(open_dsh, dsh);
error2:
	lzds_dshandle_free(dsh);
error1:
	free(zfi);
	return rc;
}

static int zdsfs_release(const char *UNUSED(path), struct fuse_file_info *fi)
{
	struct zdsfs_file_info *zfi;
	int rc;

	if (!fi->fh)
		return -EINVAL;
	zfi = (struct zdsfs_file_info *)(unsigned long)fi->fh;
	if (zfi->dsh) {
		lzds_rest_release_enq(zfi->dsh,
				      zdsfsinfo.server[zdsfsinfo.active_server]);
		lzds_dshandle_close(zfi->dsh);
		dshlist_remove(open_dsh, zfi->dsh);
		lzds_dshandle_free(zfi->dsh);
	}
	rc = pthread_mutex_destroy(&zfi->mutex);
	if (rc)
		fprintf(stderr,	"Error: could not destroy mutex, rc=%d\n", rc);
	free(zfi);
	return 0;
}

static int zdsfs_read(const char *UNUSED(path), char *buf, size_t size,
		      off_t offset, struct fuse_file_info *fi)
{
	struct zdsfs_file_info *zfi;
	ssize_t count;
	int rc, rc2;
	long long rcoffset;
	struct errorlog *log;

	if (!fi->fh)
		return -ENOENT;
	zfi = (struct zdsfs_file_info *)(unsigned long)fi->fh;

	rc2 = pthread_mutex_lock(&zfi->mutex);
	if (rc2) {
		fprintf(stderr,	"Error: could not lock mutex, rc=%d\n", rc2);
		return -EIO;
	}
	rc = 0;
	if (zfi->is_metadata_file) {
		if (zfi->metaread >= zdsfsinfo.metaused) {
			pthread_mutex_unlock(&zfi->mutex);
			return 0;
		}
		count = zdsfsinfo.metaused - zfi->metaread;
		if (size < (size_t)count)
			count = size;
		memcpy(buf, &zdsfsinfo.metadata[zfi->metaread], count);
		zfi->metaread += count;
	} else {
		lzds_dshandle_get_offset(zfi->dsh, &rcoffset);
		if (rcoffset != offset)
			rc = lzds_dshandle_lseek(zfi->dsh, offset, &rcoffset);
		if (!rc)
			rc = lzds_dshandle_read(zfi->dsh, buf, size, &count);
	}
	rc2 = pthread_mutex_unlock(&zfi->mutex);
	if (rc2)
		fprintf(stderr,	"Error: could not unlock mutex, rc=%d\n", rc2);
	if (rc) {
		fprintf(stderr,	"Error when reading from data set:\n");
		lzds_dshandle_get_errorlog(zfi->dsh, &log);
		lzds_errorlog_fprint(log, stderr);
		return -rc;
	}
	return count;
}


#ifdef HAVE_SETXATTR

#define RECFMXATTR "user.recfm"
#define LRECLXATTR "user.lrecl"
#define DSORGXATTR "user.dsorg"

static int zdsfs_listxattr(const char *path, char *list, size_t size)
{
	int pos = 0;
	size_t list_len;

	/* root directory and the metadata have no extended attributes */
	if (!strcmp(path, "/") || !strcmp(path, "/"METADATAFILE))
		return 0;

	list_len = strlen(RECFMXATTR) + 1 +
		strlen(LRECLXATTR) + 1 +
		strlen(DSORGXATTR) + 1;
	/* size 0 is a special case to query the necessary buffer length */
	if (!size)
		return list_len;
	if (size < list_len)
		return -ERANGE;
	strcpy(list, RECFMXATTR);
	pos += strlen(RECFMXATTR) + 1;
	strcpy(&list[pos], LRECLXATTR);
	pos += strlen(LRECLXATTR) + 1;
	strcpy(&list[pos], DSORGXATTR);
	pos += strlen(DSORGXATTR) + 1;
	return pos;
}

static int zdsfs_getxattr(const char *path, const char *name, char *value,
			  size_t size)
{
	char normds[45];
	struct dataset *ds;
	format1_label_t *f1;
	int rc;
	char buffer[20];
	size_t length;
	int ispds;

	/* nothing for root directory but clear error code needed */
	if (!strcmp(path, "/") || !strcmp(path, "/"METADATAFILE))
		return -ENODATA;

	path_to_ds_name(path, normds, sizeof(normds));
	rc = lzds_zdsroot_find_dataset(zdsfsinfo.zdsroot, normds, &ds);
	if (rc)
		return -rc;
	lzds_dataset_get_format1_dscb(ds, &f1);

	/* null terminate strings */
	memset(value, 0, size);
	memset(buffer, 0, sizeof(buffer));

	/* returned size should be the string length, getfattr fails if I
	 * include an extra byte for the zero termination */
	if (strcmp(name, RECFMXATTR) == 0) {
		lzds_DS1RECFM_to_recfm(f1->DS1RECFM, buffer);
	} else if (strcmp(name, LRECLXATTR) == 0) {
		snprintf(buffer, sizeof(buffer), "%d", f1->DS1LRECL);
	} else if (strcmp(name, DSORGXATTR) == 0) {
		lzds_dataset_get_is_PDS(ds, &ispds);
		if (ispds) {
			/* It is valid to ask for attributes of the directory */
			path_to_member_name(path, normds, sizeof(normds));
			if (strlen(normds))
				snprintf(buffer, sizeof(buffer), "PS");
			else
				snprintf(buffer, sizeof(buffer), "PO");
		} else
			snprintf(buffer, sizeof(buffer), "PS");
	} else
		return -ENODATA;

	length = strlen(buffer);
	if (size == 0) /* special case to query the necessary buffer size */
		return length;
	if (size < length)
		return -ERANGE;
	strcpy(value, buffer);
	return length;

}

#endif /* HAVE_SETXATTR */


static struct fuse_operations rdf_oper = {
	.getattr   = zdsfs_getattr,
	.statfs    = zdsfs_statfs,
	.readdir   = zdsfs_readdir,
	.open      = zdsfs_open,
	.release   = zdsfs_release,
	.read      = zdsfs_read,
#ifdef HAVE_SETXATTR
	.listxattr = zdsfs_listxattr,
	.getxattr  = zdsfs_getxattr,
	/* no setxattr, removexattr since our xattrs are virtual */
#endif
};


static int zdsfs_verify_datasets(void)
{
	int allcomplete, rc;
	struct dataset *ds;
	char *dsname;
	struct dsiterator *dsit;
	int iscomplete, issupported;

	allcomplete = 1;

	rc = lzds_zdsroot_alloc_dsiterator(zdsfsinfo.zdsroot, &dsit);
	if (rc)
		return ENOMEM;
	while (!lzds_dsiterator_get_next_dataset(dsit, &ds)) {
		lzds_dataset_get_name(ds, &dsname);
		lzds_dataset_get_is_complete(ds, &iscomplete);
		if (!iscomplete) {
			lzds_dataset_get_name(ds, &dsname);
			fprintf(stderr,	"Warning: Data set %s is not "
				"complete\n", dsname);
			allcomplete = 0;
			continue;
		}
		lzds_dataset_get_is_supported(ds, &issupported);
		if (!issupported) {
			lzds_dataset_get_name(ds, &dsname);
			fprintf(stderr,	"Warning: Data set %s is not "
				"supported\n", dsname);
		}
	}
	lzds_dsiterator_free(dsit);

	if (!allcomplete && zdsfsinfo.allow_inclomplete_multi_volume) {
		fprintf(stderr, "Continue operation with incomplete data"
			" sets\n");
		return 0;
	}

	if (allcomplete) {
		return 0;
	} else {
		fprintf(stderr,
			"Error: Some multi volume data sets are not complete.\n"
			"Specify option 'ignore_incomplete' to allow operation "
			"with incomplete data sets, or add missing volumes.\n");
		return EPROTO;
	}
}


static int zdsfs_create_meta_data_buffer(struct zdsfs_info *info)
{
	char *mbrname;
	char *dsname;
	struct dataset *ds;
	struct dsiterator *dsit;
	struct memberiterator *it;
	struct pdsmember *member;
	int rc;
	int ispds, issupported;

	char buffer[200]; /* large enough for one line of meta data */
	char recfm[20];
	char *temp;
	char *metadata;
	size_t metasize; /* total size of meta data buffer */
	size_t metaused; /* how many bytes of buffer are already filled */
	size_t count;
	format1_label_t *f1;

	metadata = malloc(4096);
	if (!metadata)
		return -ENOMEM;
	metasize = 4096;
	metaused = 0;
	metadata[metaused] = 0;

	rc = lzds_zdsroot_alloc_dsiterator(zdsfsinfo.zdsroot, &dsit);
	if (rc) {
		rc = -ENOMEM;
		goto error;
	}
	while (!lzds_dsiterator_get_next_dataset(dsit, &ds)) {
		lzds_dataset_get_is_supported(ds, &issupported);
		if (!issupported)
			continue;
		lzds_dataset_get_name(ds, &dsname);
		lzds_dataset_get_format1_dscb(ds, &f1);
		lzds_DS1RECFM_to_recfm(f1->DS1RECFM, recfm);
		lzds_dataset_get_is_PDS(ds, &ispds);
		count = snprintf(buffer, sizeof(buffer),
				 "dsn=%s,recfm=%s,lrecl=%u,"
				 "dsorg=%s\n",
				 dsname, recfm, f1->DS1LRECL,
				 ispds ? "PO" : "PS");
		if (count >= sizeof(buffer)) {	/* just a sanity check */
			count = sizeof(buffer) - 1;
			buffer[count] = 0;
		}
		if (metaused + count + 1 > metasize) {
			temp = realloc(metadata,  metasize + 4096);
			if (!temp) {
				rc = -ENOMEM;
				goto error;
			}
			metadata = temp;
			metasize += 4096;
		}
		memcpy(&metadata[metaused], buffer, count + 1);
		metaused += count;

		/* if the dataset is a PDS then we need to process all members
		 * of the PDS, otherwise continue with the next dataset */
		if (!ispds)
			continue;

		rc = lzds_dataset_alloc_memberiterator(ds, &it);
		if (rc) {
			rc = -ENOMEM;
			goto error;
		}
		while (!lzds_memberiterator_get_next_member(it, &member)) {
			lzds_pdsmember_get_name(member, &mbrname);
			count = snprintf(buffer, sizeof(buffer),
					 "dsn=%s(%s),recfm=%s,lrecl=%u,"
					 "dsorg=PS\n",
					 dsname, mbrname, recfm, f1->DS1LRECL);
			if (count >= sizeof(buffer)) {
				count = sizeof(buffer) - 1;
				buffer[count] = 0;
			}
			if (metaused + count + 1 > metasize) {
				temp = realloc(metadata,  metasize + 4096);
				if (!temp) {
					rc = -ENOMEM;
					goto error;
				}
				metadata = temp;
				metasize += 4096;
			}
			memcpy(&metadata[metaused], buffer, count + 1);
			metaused += count;
		}
		lzds_memberiterator_free(it);
		it = NULL;
	}
	lzds_dsiterator_free(dsit);
	dsit = NULL;

	if (info->metadata)
		free(info->metadata);
	info->metadata = metadata;
	info->metasize = metasize;
	info->metaused = metaused;
	info->metatime = time(NULL);
	return 0;

error:
	free(metadata);
	lzds_dsiterator_free(dsit);
	lzds_memberiterator_free(it);
	return rc;
}



enum {
	KEY_HELP,
	KEY_VERSION,
	KEY_DEVFILE,
	KEY_TRACKS,
	KEY_SEEKBUFFER,
	KEY_CONFIG,
	KEY_DSCONFIG,
	KEY_SERVER,
	KEY_CODE_FROM,
	KEY_CODE_TO,
};

#define ZDSFS_OPT(t, p, v) { t, offsetof(struct zdsfs_info, p), v }

static const struct fuse_opt zdsfs_opts[] = {
	FUSE_OPT_KEY("-h",		KEY_HELP),
	FUSE_OPT_KEY("--help",		KEY_HELP),
	FUSE_OPT_KEY("-v",		KEY_VERSION),
	FUSE_OPT_KEY("--version",	KEY_VERSION),
	FUSE_OPT_KEY("-l %s",		KEY_DEVFILE),
	FUSE_OPT_KEY("tracks=",         KEY_TRACKS),
	FUSE_OPT_KEY("seekbuffer=",     KEY_SEEKBUFFER),
	FUSE_OPT_KEY("-c %s",           KEY_CONFIG),
	FUSE_OPT_KEY("-x %s",           KEY_DSCONFIG),
	FUSE_OPT_KEY("restserver=",     KEY_SERVER),
	FUSE_OPT_KEY("codepage_from=",  KEY_CODE_FROM),
	FUSE_OPT_KEY("codepage_to=",    KEY_CODE_TO),
	ZDSFS_OPT("rdw",                keepRDW, 1),
	ZDSFS_OPT("ignore_incomplete",  allow_inclomplete_multi_volume, 1),
	ZDSFS_OPT("check_host_count",   host_count, 1),
	ZDSFS_OPT("restapi",            restapi, 1),
	ZDSFS_OPT("codepage_convert",   codepage_convert, 1),
	FUSE_OPT_END
};


static void usage(const char *progname)
{
	fprintf(stdout,
"Usage: %s <devices> <mountpoint> [<options>]\n"
"\n"
"Use the zdsfs command to provide read access to data sets stored on one or\n"
"more z/OS DASD devices.\n\n"
"General options:\n"
"    -o opt,[opt...]        Mount options\n"
"    -h   --help            Print help, then exit\n"
"    -v   --version         Print version, then exit\n"
"\n"
"ZDSFS options:\n"
"    -l list_file           Text file that contains a list of DASD device"
" nodes\n"
"    -c config_file         Text file that contains configuration options\n"
"                           for zdsfs\n"
"    -x ds_config_file      Text file that contains conversion options\n"
"                           for specific datasets\n"
"    -o rdw                 Keep record descriptor words in byte stream\n"
"    -o ignore_incomplete   Continue processing even if parts of a multi"
" volume\n"
"                           data set are missing\n"
"    -o tracks=N            Size of the track buffer in tracks (default 128)\n"
"    -o seekbuffer=S        Upper limit in bytes for the seek history buffer\n"
"                           size (default 1048576)\n"
"    -o check_host_count    Stop processing if the device is used by another\n"
"                           operating system instance\n"
"    -o restapi             Enable using z/OSMF REST services for coordinated\n"
"                           access to datasets\n"
"    -o restserver=URL      The URL of the z/OSMF REST server to be used for\n"
"                           coordinated access to datasets\n"
"    -o codepage_convert    Enable codepage conversion using default codepages\n"
"                           from 'CP1047' to 'UTF-8'\n"
"    -o codepage_from=from  Set codepage for source. See 'iconv -l' for a list\n"
"    -o codepage_to=to      Set codepage for target. See 'iconv -l' for a list\n"
		, progname);
}

static void zdsfs_process_device(const char *device)
{
	struct dasd *newdasd;
	struct errorlog *log;
	int rc;

	rc = lzds_zdsroot_add_device(zdsfsinfo.zdsroot, device, &newdasd);
	if (rc) {
		fprintf(stderr, "error when adding device %s: %s\n", device,
			strerror(rc));
		lzds_zdsroot_get_errorlog(zdsfsinfo.zdsroot, &log);
		lzds_errorlog_fprint(log, stderr);
		exit(1);
	}
	zdsfsinfo.devcount++;
	rc = lzds_dasd_read_vlabel(newdasd);
	if (rc) {
		fprintf(stderr, "error when reading volume label from "
			"device %s: %s\n", device, strerror(rc));
		lzds_dasd_get_errorlog(newdasd, &log);
		lzds_errorlog_fprint(log, stderr);
		exit(1);
	}
	zdsfs_read_device(newdasd, device);
}

static void zdsfs_process_device_file(const char *devfile)
{
	struct stat sb;
	int fd;
	ssize_t count;
	size_t size, residual;
	char *buffer;
	char *runbuf;
	char *token;

	fd = open(devfile, O_RDONLY);
	if (fd < 0) {
		fprintf(stderr, "could not open file %s: %s\n",
			devfile, strerror(errno));
		exit(1);
	}
	if (fstat(fd, &sb) < 0) {
		fprintf(stderr, "could not stat file %s: %s\n",
			devfile, strerror(errno));
		exit(1);
	}
	if (!S_ISREG(sb.st_mode)) {
		fprintf(stderr, "not a regular file %s\n", devfile);
		exit(1);
	}
	if (sb.st_size) {
		size = (size_t)sb.st_size + 1;
		buffer = malloc(size);
		bzero(buffer, size);
		if (!buffer) {
			fprintf(stderr, "could not allocate memory to buffer"
				" file %s\n", devfile);
			exit(1);
		}
	} else
		return;

	count = 0;
	residual = (size_t)sb.st_size;
	while (residual) {
		count = read(fd, buffer, residual);
		if (count < 0) {
			fprintf(stderr, "error when reading file %s: %s\n",
				devfile, strerror(errno));
			exit(1);
		}
		if (!count) /* EOF */
			residual = 0;
		residual -= count;
	}

	runbuf = buffer;
	while ((token = strsep(&runbuf, " \t\n"))) {
		/* if several delimiters follow after another, token points
		 * to an empty string
		 */
		if (!strlen(token))
			continue;

		if (stat(token, &sb) < 0) {
			fprintf(stderr, "could not stat device %s from"
				" device list, %s\n", token, strerror(errno));
			exit(1);
		}
		if (S_ISBLK(sb.st_mode))
			zdsfs_process_device(token);
	}
	free(buffer);
}

void remove_whitespace(const char *s, char *t)
{
	while (*s != '\0') {
		if (!isblank(*s)) {
			*t = *s;
			t++;
		}
		s++;
	}
	*t = '\0';
}

static void zdsfs_process_config_file(const char *config)
{
	char line[MAX_LINE_LENGTH];
	char *tmp, *key, *value;
	FILE *fd;
	char delimiter[] = " =#\n";
	unsigned long enabled;

	fd = fopen(config, "r");
	if (!fd) {
		fprintf(stderr, "could not open file %s: %s\n",
			config, strerror(errno));
		return;
	}

	while (fgets(line, sizeof(line), fd)) {
		/* skip empty lines */
		if (*line == '\n' || *line == '#')
			continue;

		/* remove all whitespaces */
		tmp = util_malloc(strlen(line) + 1);
		remove_whitespace(line, tmp);

		key = strtok(tmp, delimiter);
		if (strcmp(key, "restserver")  == 0) {
			if (zdsfsinfo.nr_server >= MAX_SERVER) {
				free(tmp);
				continue;
			}
			value = strtok(NULL, delimiter);
			zdsfsinfo.server[zdsfsinfo.nr_server] =
				util_strdup(value);
			zdsfsinfo.nr_server++;
		} else if (strcmp(key, "restapi") == 0) {
			value = strtok(NULL, delimiter);
			enabled = strtoul(value, NULL, 0);
			if (enabled == 1)
				zdsfsinfo.restapi = true;
		} else if (strcmp(key, "keepalive") == 0) {
			value = strtok(NULL, delimiter);
			zdsfsinfo.keepalive = strtoul(value, NULL, 0);
		}
		free(tmp);
	}
	fclose(fd);
}

static struct dsconvert *zdsfs_allocate_dsc(char *name, const char *config)
{
	struct dsconvert *dsc;

	/* check for duplicate entries */
	if (dsclist_find_by_name(zdsfsinfo.dsclist, name)) {
		fprintf(stderr,
			"Error in config file %s. Duplicate entry found: %s\n",
			config, name);
		return NULL;
	}
	dsc = util_zalloc(sizeof(*dsc));
	dsc->name = util_strdup(name);

	return dsc;
}

static int zdsfs_check_codepage_setting(char *from, char *to)
{
	iconv_t iconv;

	/* no conversion is OK */
	if (!from && !to)
		return 0;

	/* partial setup is not OK */
	if ((from && !to) || (to && !from))
		return 1;

	/* return if the codepages are valid */
	return setup_iconv(&iconv, from, to);
}

/*
 * process a dataset configuration file that specifies conversion on a per dataset basis
 *
 * expect a section title each 3 lines
 * the section should contain a rdw= and conv= line
 * valid values for rdw= are 0/1
 * valid values for conv= are 0/1 or a comma separated list
 * of codepage_from and codepage_to arguments
 */
static int zdsfs_process_dataset_conf(const char *config)
{
	char line[MAX_LINE_LENGTH];
	char delimiter[] = " =#\n";
	char *tmp, *key, *value;
	int linecount, in_section;
	unsigned long enabled;
	struct dsconvert *dsc;
	int rc = 1;
	FILE *fd;

	fd = fopen(config, "r");
	if (!fd) {
		fprintf(stderr, "could not open file %s: %s\n",
			config, strerror(errno));
		return 0;
	}
	in_section = 0;
	linecount = 0;
	dsc = NULL;
	tmp = NULL;
	while (fgets(line, sizeof(line), fd)) {
		linecount++;
		/* remove all whitespaces */
		tmp = util_malloc(strlen(line) + 1);
		remove_whitespace(line, tmp);
		/* skip empty lines */
		if (*tmp == '\n' || *tmp == '#') {
			free(tmp);
			tmp = NULL;
			continue;
		}
		if (!in_section) {
			/* the section title should not contain a '=' */
			if (strchr(line, '=') != NULL) {
				fprintf(stderr,
					"Error in config file %s line %d. Expected section title instead of %s\n",
					config, linecount, line);
				goto out;
			}
		}
		key = strtok(tmp, delimiter);
		if (!in_section) {
			dsc = zdsfs_allocate_dsc(key, config);
			if (!dsc)
				goto out;
			in_section = SECTION_ENTRIES;
		} else if (strcmp(key, "rdw")  == 0) {
			value = strtok(NULL, delimiter);
			enabled = strtoul(value, NULL, 0);
			if (enabled == 1)
				dsc->keeprdw = true;
			else
				dsc->keeprdw = false;
		} else if (strcmp(key, "conv") == 0) {
			value = strtok(NULL, delimiter);
			if (strchr(value, ',') != NULL) {
				/* use provided codepages */
				value = strtok(value, ",");
				dsc->codepage_from = util_strdup(value);
				value = strtok(NULL, delimiter);
				dsc->codepage_to = util_strdup(value);
			} else if (strcmp(value, "1") == 0) {
				/* use default codepages */
				dsc->codepage_from = CODEPAGE_EDF;
				dsc->codepage_to = CODEPAGE_LINUX;
			} else if  (strcmp(value, "0") == 0) {
				/* disable conversion */
				dsc->codepage_from = NULL;
				dsc->codepage_to = NULL;
			} else {
				fprintf(stderr,
					"Error in config file %s line %d. Invalid 'conv' statement: %s.\n",
					config, linecount, value);
				goto out;
			}
		} else {
			fprintf(stderr,
				"Error in config file %s line %d. Missing 'rdw' or 'conv' statement.\n",
				config, linecount);
			goto out;
		}
		in_section--;
		/* if the section was parsed completely, add the dsc to the list */
		if (!in_section) {
			if (zdsfs_check_codepage_setting(dsc->codepage_from,
							 dsc->codepage_to)) {
				fprintf(stderr,
					"Error in config file %s. Invalid codepage setting: %s %s.\n",
					config, dsc->codepage_from,
					dsc->codepage_to);
				goto out;
			}
			dsclist_add(zdsfsinfo.dsclist, dsc);
			dsc = NULL;
		}
		free(tmp);
		tmp = NULL;
	}
	/* find incomplete last section */
	if (in_section)
		fprintf(stderr,
			"Error in config file %s. Missing 'rdw' or 'conv' statement.\n",
			config);
	else
		rc = 0;

out:
	fclose(fd);
	free(tmp);
	dsc_free(dsc);

	return rc;
}

static int zdsfs_process_args(void *UNUSED(data), const char *arg, int key,
			      struct fuse_args *outargs)
{
	struct stat sb;
	unsigned long tracks_per_frame;
	unsigned long long seek_buffer_size;
	const char *value;
	char *endptr;

	switch (key) {
	case FUSE_OPT_KEY_OPT:
		return 1;
	case FUSE_OPT_KEY_NONOPT:
		if (stat(arg, &sb) < 0) {
			fprintf(stderr, "could not stat device %s, %s\n",
				arg, strerror(errno));
			return 1;
		}
		if (S_ISBLK(sb.st_mode)) {
			zdsfs_process_device(arg);
			return 0;
		}
		/* not a block device, so let fuse parse it */
		return 1;
	case KEY_DEVFILE:
		/* note that arg starts with "-l" */
		zdsfs_process_device_file(arg + 2);
		return 0;
	case KEY_TRACKS:
		value = arg + strlen("tracks=");
		/* strtoul does not complain about negative values  */
		if (*value == '-') {
			errno = EINVAL;
		} else {
			errno = 0;
			tracks_per_frame = strtoul(value, &endptr, 10);
		}
		if (!errno && tracks_per_frame <= UINT_MAX)
			zdsfsinfo.tracks_per_frame = tracks_per_frame;
		else
			errno = ERANGE;
		if (errno || (endptr && (*endptr != '\0'))) {
			fprintf(stderr, "Invalid value '%s' for option "
				"'tracks'\n", value);
			exit(1);
		}
		return 0;
	case KEY_SEEKBUFFER:
		value = arg + strlen("seekbuffer=");
		/* strtoull does not complain about negative values  */
		if (*value == '-') {
			errno = EINVAL;
		} else {
			errno = 0;
			seek_buffer_size = strtoull(value, &endptr, 10);
		}
		if (errno || (endptr && (*endptr != '\0'))) {
			fprintf(stderr,	"Invalid value '%s' for option "
				"'seekbuffer'\n", value);
			exit(1);
		}
		zdsfsinfo.seek_buffer_size = seek_buffer_size;
		return 0;
	case KEY_HELP:
		usage(outargs->argv[0]);

		/*
		 * Usage output needs to go to stdout to be consistent with
		 * coding guidelines. FUSE versions before 3.0.0 print help
		 * output to stderr. Redirect stderr to stdout here to enforce
		 * consistent behavior.
		 */
		fflush(stderr);
		dup2(STDOUT_FILENO, STDERR_FILENO);

		fuse_opt_add_arg(outargs, "-ho");
		/* call fuse_main to let library print fuse options */
		fuse_main(outargs->argc, outargs->argv, &rdf_oper, NULL);
		exit(0);
	case KEY_VERSION:
		fprintf(stdout, COMP "FUSE file system for z/OS data set access"
			", program version %s\n", RELEASE_STRING);
		fprintf(stdout, "Copyright IBM Corp. 2013, 2017\n");
		exit(0);
	case KEY_CONFIG:
		/* note that arg starts with "-c" */
		zdsfsinfo.configfile = util_strdup(arg + 2);
		return 0;
	case KEY_DSCONFIG:
		/* note that arg starts with "-x" */
		zdsfsinfo.dsfile = util_strdup(arg + 2);
		return 0;
	case KEY_SERVER:
		if (zdsfsinfo.nr_server >= MAX_SERVER)
			return 0;
		value = arg + strlen("restserver=");
		zdsfsinfo.server[zdsfsinfo.nr_server] =
			util_strdup(value);
		zdsfsinfo.nr_server++;
		return 0;
	case KEY_CODE_FROM:
		value = arg + strlen("codepage_from=");
		zdsfsinfo.codepage_from =
			util_strdup(value);
		return 0;
	case KEY_CODE_TO:
		value = arg + strlen("codepage_to=");
		zdsfsinfo.codepage_to =
			util_strdup(value);
		return 0;
	default:
		fprintf(stderr, "Unknown argument key %x\n", key);
		exit(1);
	}
}


int main(int argc, char *argv[])
{
	struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
	int rc;

	timer_running = 0;
	bzero(&zdsfsinfo, sizeof(zdsfsinfo));
	zdsfsinfo.keepRDW = 0;
	zdsfsinfo.allow_inclomplete_multi_volume = 0;
	zdsfsinfo.tracks_per_frame = 128;
	zdsfsinfo.seek_buffer_size = 1048576;
	zdsfsinfo.configfile = "/etc/zdsfs.conf";
	zdsfsinfo.dsfile = "/etc/zdsfs-dataset.conf";
	zdsfsinfo.keepalive = DEFAULT_KEEPALIVE_SEC;
	zdsfsinfo.active_server = -1;

	rc = lzds_zdsroot_alloc(&zdsfsinfo.zdsroot);
	open_dsh = dshlist_alloc();
	zdsfsinfo.dsclist = dsclist_alloc();

	if (rc) {
		fprintf(stderr, "Could not allocate internal structures\n");
		exit(1);
	}
	if (fuse_opt_parse(&args, &zdsfsinfo, zdsfs_opts,
			   zdsfs_process_args) == -1) {
		fprintf(stderr, "Failed to parse option\n");
		exit(1);
	}
	if (zdsfs_check_codepage_setting(zdsfsinfo.codepage_from,
					 zdsfsinfo.codepage_to)) {
		fprintf(stderr, "Ivalid codepage setting from '%s' to '%s'\n",
			zdsfsinfo.codepage_from,
			zdsfsinfo.codepage_to);
		rc = -EINVAL;
		goto cleanup;
	}
	zdsfs_process_config_file(zdsfsinfo.configfile);
	if (zdsfs_process_dataset_conf(zdsfsinfo.dsfile)) {
		rc = -EACCES;
		goto cleanup;
	}
	if (!zdsfsinfo.devcount) {
		fprintf(stderr, "Please specify a block device\n");
		fprintf(stderr, "Try '%s --help' for more information\n",
			argv[0]);
		exit(1);
	}

	if (zdsfsinfo.host_count) {
		/* check, print error and exit if multiple online */
		rc = lzds_analyse_open_count(zdsfsinfo.zdsroot, 0);
		if (rc == -EACCES)
			goto cleanup;
	} else {
		/* check, print warning if multiple online */
		lzds_analyse_open_count(zdsfsinfo.zdsroot, 1);
	}

	rc = zdsfs_verify_datasets();
	if (rc)
		goto cleanup;

	rc = zdsfs_create_meta_data_buffer(&zdsfsinfo);
	if (rc)
		goto cleanup;

	if (zdsfsinfo.restapi) {
		curl_global_init(CURL_GLOBAL_DEFAULT);
		zdsfs_test_restserver();
		if (zdsfsinfo.active_server < 0) {
			fprintf(stderr, "Error: No z/OSMF REST Server reachable\n");
			rc = -EACCES;
			goto cleanup;
		}
	}
	rc = fuse_main(args.argc, args.argv, &rdf_oper, NULL);

cleanup:
	curl_global_cleanup();
	dshlist_free(open_dsh);
	dsclist_free(zdsfsinfo.dsclist);
	lzds_zdsroot_free(zdsfsinfo.zdsroot);

	fuse_opt_free_args(&args);
	return rc;
}
