mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
Compiling against fuse3 shows 'incompatible pointer type' warnings due to the additional function arguments in the new API. Therefore, adjust the declarations of zdsfs_getattr() and zdsfs_readdir() to match. GitHub-ID: https://github.com/ibm-s390-linux/s390-tools/pull/117 Signed-off-by: Graham Inggs <ginggs@debian.org> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
1793 lines
43 KiB
C
1793 lines
43 KiB
C
/*
|
|
* 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;
|
|
}
|