mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
Introduce is_vol1() in vtoc.h and update the user in libzds.c Signed-off-by: Eduard Shishkin <edward6@linux.ibm.com> Reviewed-by: Jan Hoeppner <hoeppner@linux.ibm.com> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
4195 lines
126 KiB
C
4195 lines
126 KiB
C
/**
|
|
* @file libzds.c
|
|
* This is the implementation of the internal library libzds.
|
|
* Please note that this library should currently only be used
|
|
* by programs in the s390-tools package. It is not yet meant
|
|
* for external use as interfaces and definitions may change
|
|
* without further notice.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <linux/types.h>
|
|
#include <malloc.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#ifdef HAVE_CURL
|
|
#include <curl/curl.h>
|
|
#endif /* HAVE_CURL */
|
|
|
|
#include "lib/util_libc.h"
|
|
#include "lib/dasd_base.h"
|
|
#include "lib/dasd_sys.h"
|
|
#include "lib/libzds.h"
|
|
#include "lib/vtoc.h"
|
|
|
|
/** @cond PRIVATE */
|
|
|
|
|
|
/******************************************************************************/
|
|
/* libzds structure definitions */
|
|
/******************************************************************************/
|
|
|
|
/**
|
|
* @brief Maximum size of a volume serial string (NOT including one byte for
|
|
* 0-termination)
|
|
*/
|
|
#define MAXVOLSER 6
|
|
|
|
|
|
/*
|
|
* The following structures are declared in libzds.h but defined here in
|
|
* the .c file to keep them opaque to the user of the library.
|
|
*/
|
|
|
|
|
|
struct errorlog {
|
|
struct util_list *entries;
|
|
};
|
|
|
|
/**
|
|
* @brief Size of the message buffer in errormsg
|
|
*
|
|
*/
|
|
#define ERRORMSG 240
|
|
|
|
#define BUSIDSIZE 8
|
|
|
|
#define EBCDIC_SP 0x40
|
|
#define EBCDIC_LF 0x25
|
|
|
|
/**
|
|
* @brief An internal structure that represents an entry in the error log.
|
|
*/
|
|
struct errormsg {
|
|
/** @brief List head to store a list of errormsg in struct errorlog */
|
|
struct util_list_node list;
|
|
/** @brief error code that was associated with this message*/
|
|
int error;
|
|
/** @brief a descriptive message text */
|
|
char text[ERRORMSG];
|
|
};
|
|
|
|
struct dscbiterator {
|
|
/** @brief The raw_vtoc this iterator refers to */
|
|
struct raw_vtoc *rawvtoc;
|
|
/** @brief Index to the vtocindex array in rawvtoc */
|
|
unsigned int i;
|
|
};
|
|
|
|
struct dasdhandle {
|
|
/** @brief The struct dasd this context relates to */
|
|
struct dasd *dasd;
|
|
/** @brief File descriptor for the block device.
|
|
* Should be -1 when the device not open */
|
|
int fd;
|
|
/** @brief Detailed error messages in case of a problem */
|
|
struct errorlog *log;
|
|
};
|
|
|
|
struct pdsmember {
|
|
/** @brief List head that is used to store a list of members in
|
|
* struct dataset */
|
|
struct util_list_node list;
|
|
|
|
/** @brief Member name, converted from EBCDIC to ASCII */
|
|
char name[MEMBERNAMELENGTH];
|
|
/** @brief The track the member starts in, relative to the data set.
|
|
*
|
|
* @note This number is relative to the data set, with track 0
|
|
* being the first track of the data set. It is independent
|
|
* of the DASD geometry or extent location. */
|
|
unsigned short track;
|
|
/** @brief First record of the member starts in, relative to the
|
|
* start track.*/
|
|
unsigned char record;
|
|
/** @brief Marks if pdsmember is an alias (we make no distinction
|
|
* between a regular member and an alias). */
|
|
unsigned char is_alias;
|
|
/** @brief Detailed error messages in case of a problem */
|
|
struct errorlog *log;
|
|
};
|
|
|
|
|
|
/**
|
|
* @brief An internal structure that represents part of a multi volume data set
|
|
*
|
|
* Data sets can be spread over several DASD devices (multi volume data set),
|
|
* and this structure represents one such part. Each data set has at least one
|
|
* datasetpart.
|
|
*/
|
|
struct datasetpart {
|
|
/** @brief The dasd that this part resides on */
|
|
struct dasd *dasdi;
|
|
/** @brief Pointer to the respective format 1 DSCB in the raw_vtoc
|
|
* of that dasd */
|
|
format1_label_t *f1;
|
|
/** @brief Each part can consist of up to MAXEXTENTS (16) extents */
|
|
extent_t ext[MAXEXTENTS];
|
|
};
|
|
|
|
|
|
struct dataset {
|
|
/** @brief List head that is used to store a list of data sets in
|
|
* struct zdsroot */
|
|
struct util_list_node list;
|
|
|
|
/** @brief Data set name, translated from EBCDIC to ASCII, 0-terminated
|
|
* and with any blank padding removed */
|
|
char name[MAXDSNAMELENGTH];
|
|
/** @brief Array of data set parts this data set consists of.
|
|
*
|
|
* We use just an regular array as the number of parts is limited.
|
|
* Each part has a specific position, as defined by the DS1VOLSQ
|
|
* value in the parts format 1 label.
|
|
*/
|
|
struct datasetpart *dsp[MAXVOLUMESPERDS];
|
|
/** @brief Number of parts this data set has
|
|
*
|
|
* @note This is the number of data set parts we have already found.
|
|
* As long as there are still gaps in the dsp array, dspcount may be
|
|
* smaller than the largest index of an element in the dsp array.
|
|
*/
|
|
int dspcount;
|
|
/** @brief Flag that is set to 1 if we have all parts
|
|
*
|
|
* @note: In cases where a dataset consists of only one part, the
|
|
* the fist part should be flagged as last part as well in DS1DSIND,
|
|
* but this seems not to be reliable. So, as long as we only have only
|
|
* found one part in position 0, we may set iscomplete, even if we
|
|
* have no 'last part' marker found.
|
|
*/
|
|
int iscomplete;
|
|
/** @brief If a data set is a partitioned data set (PDS), then this
|
|
* contains a list of members, otherwise the list is empty */
|
|
struct util_list *memberlist;
|
|
/** @brief Detailed error messages in case of a problem */
|
|
struct errorlog *log;
|
|
};
|
|
|
|
struct memberiterator {
|
|
/** @brief Data set that holds the members */
|
|
struct dataset *ds;
|
|
/** @brief The last selected member. */
|
|
struct pdsmember *memberi;
|
|
};
|
|
|
|
struct dsiterator {
|
|
/** @brief zdsroot that holds the data sets */
|
|
struct zdsroot *zdsroot;
|
|
/** @brief The last selected data set */
|
|
struct dataset *dsi;
|
|
};
|
|
|
|
struct dasditerator {
|
|
/** @brief zdsroot that holds the dasds */
|
|
struct zdsroot *zdsroot;
|
|
/** @brief The last selected dasd */
|
|
struct dasd *dasdi;
|
|
};
|
|
|
|
struct zdsroot {
|
|
/** @brief list of dasds */
|
|
struct util_list *dasdlist;
|
|
/** @brief list of data sets */
|
|
struct util_list *datasetlist;
|
|
/** @brief Detailed error messages in case of a problem */
|
|
struct errorlog *log;
|
|
};
|
|
|
|
/**
|
|
* @brief Internal structure to keep track of offsets in the data set
|
|
*/
|
|
struct seekelement {
|
|
/** @brief Data set part this element refers to */
|
|
unsigned char dsp_no;
|
|
/** @brief The extent on that part/dasd */
|
|
unsigned char ext_seq_no;
|
|
/** @brief The starting track on that part/dasd */
|
|
unsigned int bufstarttrk;
|
|
/** @brief The absolute offset in the data set */
|
|
long long databufoffset;
|
|
};
|
|
|
|
/**
|
|
* @brief Default value for the tracks value in dshandle
|
|
*/
|
|
#define TRACK_BUFFER_DEFAULT 128
|
|
|
|
struct dshandle {
|
|
/** @brief Data set this context relates to */
|
|
struct dataset *ds;
|
|
/** @brief Pointer to member, only applicable to PDS */
|
|
struct pdsmember *member;
|
|
/** @brief One dasdhandle per data set part
|
|
*
|
|
* The dshandle functions do not read directly from the devices,
|
|
* instead they use the dasdhandle interfacesw.
|
|
*/
|
|
struct dasdhandle *dasdhandle[MAXVOLUMESPERDS];
|
|
|
|
/** @brief A multiplier that is used to determine the various
|
|
buffer sizes. Number of tracks in one track frame. */
|
|
unsigned int tracks_per_frame;
|
|
|
|
/** @brief Flag: While interpreting the data, keep the record
|
|
* descriptor words in the data stream */
|
|
int keepRDW;
|
|
/** @brief Flag that is set between open and close */
|
|
int is_open;
|
|
/** @brief This flag is set when during interpretation of the track
|
|
* buffer the end of the data is found */
|
|
int eof_reached;
|
|
|
|
|
|
/* The following values describe our current position within the data
|
|
* set */
|
|
|
|
/** @brief Index number of the current data set part */
|
|
int dsp_no;
|
|
/** @brief The sequence number of the current extent in the current
|
|
* data set part */
|
|
int ext_seq_no;
|
|
/** @brief Start buffer interpretation at this record.
|
|
*
|
|
* Data set members may start in the middle of a track. So we need
|
|
* to know with which record to start.
|
|
*/
|
|
unsigned char startrecord;
|
|
/** @brief The first track of the extent that dsp_no and ext_seq_no
|
|
* point to */
|
|
unsigned int extstarttrk;
|
|
/** @brief The last track of the extent that dsp_no and ext_seq_no
|
|
* point to */
|
|
unsigned int extendtrk;
|
|
/** @brief Start of the area that is currently in the rawbuffer */
|
|
unsigned int bufstarttrk;
|
|
/** @brief End of the area that is currently in the rawbuffer */
|
|
unsigned int bufendtrk;
|
|
/** @brief Running number of the current track frame */
|
|
long long frameno;
|
|
|
|
/** @brief Buffer for the raw track images */
|
|
char *rawbuffer;
|
|
/** @brief Buffer for the extracted user data */
|
|
char *databuffer;
|
|
/** @brief Size of the rawbuffer */
|
|
long long rawbufmax;
|
|
/** @brief Size of the databuffer */
|
|
long long databufmax;
|
|
/** @brief Size of the currently used part of the rawbuffer */
|
|
long long rawbufsize;
|
|
/** @brief Size of the currently used part of the databuffer */
|
|
long long databufsize;
|
|
/** @brief Current position of the databuffer relative to the begin
|
|
* of the data set */
|
|
long long databufoffset;
|
|
/** @brief Current position in the databuffer */
|
|
long long bufpos;
|
|
|
|
|
|
/** @brief Buffer for seek data points */
|
|
struct seekelement *seekbuf;
|
|
/** @brief Total number of elements in seekbuf */
|
|
unsigned long long seek_count;
|
|
/** @brief Number of used elements in seekbuf */
|
|
unsigned long long seek_current;
|
|
/** @brief Modulo that determines which track frame is stored in the
|
|
* seek buffer
|
|
*
|
|
* Example: If skip is 2, then every 2'nd frame is stored.
|
|
*/
|
|
unsigned long long skip;
|
|
/** @brief Detailed error messages in case of a problem */
|
|
struct errorlog *log;
|
|
|
|
char *session_ref;
|
|
iconv_t *iconv;
|
|
char *convbuffer;
|
|
};
|
|
|
|
/** @endcond */
|
|
|
|
|
|
/******************************************************************************/
|
|
/* BASIC level functions */
|
|
/******************************************************************************/
|
|
|
|
static void dasd_free(struct dasd *dasd);
|
|
static void dataset_free_memberlist(struct dataset *ds);
|
|
static void errorlog_free(struct errorlog *log);
|
|
static void errorlog_clear(struct errorlog *log);
|
|
static int errorlog_add_message(struct errorlog **log,
|
|
struct errorlog *oldlog,
|
|
int error_code,
|
|
const char *message_format,
|
|
...) __attribute__ ((format (printf, 4, 5)));
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
* Since the zdsroot is the root for all the other data structures,
|
|
* this should be one of the first functions to call.
|
|
* @param[out] root Reference to a pointer variable in which the newly
|
|
* allocated structure will be returned.
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - ENOMEM Could not allocate structure due to lack of memory.
|
|
*/
|
|
int lzds_zdsroot_alloc(struct zdsroot **root)
|
|
{
|
|
struct zdsroot *tmproot;
|
|
|
|
*root = NULL;
|
|
tmproot = malloc(sizeof(*tmproot));
|
|
if (!tmproot)
|
|
return ENOMEM;
|
|
memset(tmproot, 0, sizeof(*tmproot));
|
|
|
|
tmproot->dasdlist = util_list_new(struct dasd, list);
|
|
tmproot->datasetlist = util_list_new(struct dataset, list);
|
|
|
|
*root = tmproot;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* It should be noted that this frees all structures that are owned by the
|
|
* root structure as well. For example, a pointer to a struct dasd that
|
|
* has been returned by lzds_zdsroot_add_device is not valid anymore.
|
|
*
|
|
* @param[in] root Reference to the zdsroot structure that is to be freed.
|
|
*/
|
|
void lzds_dslist_free(struct zdsroot *root)
|
|
{
|
|
struct dataset *ds, *nextds;
|
|
int i;
|
|
|
|
util_list_iterate_safe(root->datasetlist, ds, nextds) {
|
|
util_list_remove(root->datasetlist, ds);
|
|
dataset_free_memberlist(ds);
|
|
for (i = 0; i < MAXVOLUMESPERDS; ++i)
|
|
free(ds->dsp[i]);
|
|
errorlog_free(ds->log);
|
|
free(ds);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* It should be noted that this frees all structures that are owned by the
|
|
* root structure as well. For example, a pointer to a struct dasd that
|
|
* has been returned by lzds_zdsroot_add_device is not valid anymore.
|
|
*
|
|
* @param[in] root Reference to the zdsroot structure that is to be freed.
|
|
*/
|
|
void lzds_zdsroot_free(struct zdsroot *root)
|
|
{
|
|
struct dasd *dasd, *nextdasd;
|
|
|
|
if (!root)
|
|
return;
|
|
|
|
util_list_iterate_safe(root->dasdlist, dasd, nextdasd) {
|
|
util_list_remove(root->dasdlist, dasd);
|
|
dasd_free(dasd);
|
|
}
|
|
util_list_free(root->dasdlist);
|
|
lzds_dslist_free(root);
|
|
util_list_free(root->datasetlist);
|
|
errorlog_free(root->log);
|
|
free(root);
|
|
}
|
|
|
|
/**
|
|
* @brief Subroutine of lzds_zdsroot_add_device
|
|
*
|
|
* This function determines some basic DASD geometry information and stores
|
|
* it in the struct dasd for later use.
|
|
*
|
|
* @param[in] dasd Reference to the dasd to work on.
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - EIO Some error prevented us from gaining this information
|
|
*
|
|
*/
|
|
static int dasd_read_geometry(struct dasd *dasd)
|
|
{
|
|
unsigned long long size_in_bytes;
|
|
|
|
errorlog_clear(dasd->log);
|
|
|
|
if (dasd_get_blocksize_in_bytes(dasd->device, &size_in_bytes) != 0)
|
|
return errorlog_add_message(
|
|
&dasd->log, NULL, EIO,
|
|
"read geometry: could not get size from device %s\n",
|
|
dasd->device);
|
|
|
|
/* label_block and heads are simply hard coded with the correct values
|
|
* for ECKD DASDs. This makes us independent from any DASD specific
|
|
* ioctls like BIODASDINFO and allows us to work on DASD images via
|
|
* loopback device.
|
|
*/
|
|
dasd->label_block = 2;
|
|
dasd->heads = 15;
|
|
dasd->cylinders = (size_in_bytes / (dasd->heads * RAWTRACKSIZE));
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief Subroutine of lzds_zdsroot_add_device
|
|
*
|
|
* This function goes through the list of dasds in root and verifies that
|
|
* the a dasd with the given device name is not yet present.
|
|
* @param[in] root Reference to the zdsroot structure the new struct dasd
|
|
* is to be added to.
|
|
* @param[in] devnode String that holds the name of the device node,
|
|
* e.g. "/dev/dasdb".
|
|
* @return true if matching dasd has been found, false if not
|
|
*/
|
|
static int zdsroot_is_duplicate_device(struct zdsroot *root,
|
|
const char *devnode)
|
|
{
|
|
struct dasd *dasd;
|
|
|
|
dasd = NULL;
|
|
lzds_zdsroot_get_dasd_by_node_name(root, devnode, &dasd);
|
|
return !(dasd == NULL);
|
|
}
|
|
|
|
/**
|
|
* This function creates a new struct dasd and adds it to the root.
|
|
* It can later be traversed using the dasditerator functions.
|
|
*
|
|
* @param[in] root Reference to the zdsroot structure the new struct dasd
|
|
* is to be added to.
|
|
* @param[in] devnode String that holds the name of the device node,
|
|
* e.g. "/dev/dasdb".
|
|
* @param[out] dasd Reference to a pointer variable in which the newly
|
|
* allocated structure will be returned.
|
|
* This pointer is returned for the convenience of the user,
|
|
* to be used with follow on calls, e.g to lzds_dasd_read_vlabel.
|
|
*
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - ENOMEM Could not allocate structure due to lack of memory.
|
|
* - ENOTTY The used ioctl is not supported by the device (i.e. the
|
|
* device is not a DASD.)
|
|
* - EIO Some other error prevented us from gaining this information
|
|
*
|
|
* @note It is not guaranteed that ENOTTY is returned when the device is
|
|
* not a DASD. It depends on the device whether ENOTTY or EIO is returned.
|
|
*/
|
|
int lzds_zdsroot_add_device(struct zdsroot *root, const char *devnode,
|
|
struct dasd **dasd)
|
|
{
|
|
struct dasd *dasdtmp;
|
|
int rc;
|
|
|
|
errorlog_clear(root->log);
|
|
if (zdsroot_is_duplicate_device(root, devnode)) {
|
|
return errorlog_add_message(
|
|
&root->log, NULL, EINVAL,
|
|
"add device: duplicate device %s\n",
|
|
devnode);
|
|
}
|
|
dasdtmp = malloc(sizeof(*dasdtmp));
|
|
if (!dasdtmp)
|
|
return ENOMEM;
|
|
memset(dasdtmp, 0, sizeof(*dasdtmp));
|
|
dasdtmp->device = strdup(devnode);
|
|
dasdtmp->inusefd = open(dasdtmp->device, O_RDONLY);
|
|
if (dasdtmp->inusefd < 0) {
|
|
errorlog_add_message(
|
|
&root->log, dasdtmp->log, EIO,
|
|
"add device: could open device %s\n",
|
|
dasdtmp->device);
|
|
dasd_free(dasdtmp);
|
|
return EIO;
|
|
}
|
|
rc = dasd_read_geometry(dasdtmp);
|
|
if (rc) {
|
|
errorlog_add_message(
|
|
&root->log, dasdtmp->log, EIO,
|
|
"add device: could not read device data from %s\n",
|
|
dasdtmp->device);
|
|
close(dasdtmp->inusefd);
|
|
dasd_free(dasdtmp);
|
|
return EIO;
|
|
}
|
|
util_list_add_tail(root->dasdlist, dasdtmp);
|
|
if (dasd)
|
|
*dasd = dasdtmp;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @param[in] dasd A dasd on which an error occurred.
|
|
* @param[out] log Reference to a variable in which the errorlog
|
|
* is returned.
|
|
*/
|
|
void lzds_dasd_get_errorlog(struct dasd *dasd, struct errorlog **log)
|
|
{
|
|
*log = dasd->log;
|
|
}
|
|
|
|
/**
|
|
* @brief Subroutine of lzds_zdsroot_free. Frees the struct dasd and everything
|
|
* that belogns to it.
|
|
*
|
|
* @param[in] dasd Pointer to the struct dasd that is to be freed.
|
|
*/
|
|
static void dasd_free(struct dasd *dasd)
|
|
{
|
|
free(dasd->device);
|
|
free(dasd->vlabel);
|
|
if (dasd->rawvtoc) {
|
|
free(dasd->rawvtoc->rawdata);
|
|
free(dasd->rawvtoc->vtocindex);
|
|
errorlog_free(dasd->rawvtoc->log);
|
|
free(dasd->rawvtoc);
|
|
}
|
|
errorlog_free(dasd->log);
|
|
close(dasd->inusefd);
|
|
free(dasd);
|
|
}
|
|
|
|
/**
|
|
* @param[in] zdsroot Reference to struct zdsroot that the iterator will be
|
|
* bound to. The iterator will traverse the dasds stored
|
|
* in this zdsroot.
|
|
* @param[out] it Reference to a pointer variable in which the newly allocated
|
|
* structure will be returned.
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - ENOMEM Could not allocate structure due to lack of memory.
|
|
*/
|
|
int lzds_zdsroot_alloc_dasditerator(struct zdsroot *zdsroot,
|
|
struct dasditerator **it)
|
|
{
|
|
*it = malloc(sizeof(struct dasditerator));
|
|
if (*it) {
|
|
(*it)->dasdi = NULL;
|
|
(*it)->zdsroot = zdsroot;
|
|
return 0;
|
|
}
|
|
return ENOMEM;
|
|
}
|
|
|
|
/**
|
|
* @param[in] it Pointer to the struct dasditerator that is to be freed.
|
|
*/
|
|
void lzds_dasditerator_free(struct dasditerator *it)
|
|
{
|
|
free(it);
|
|
}
|
|
|
|
/**
|
|
* @param[out] it Reference to the struct dasditerator we use to traverse the
|
|
* dasd list.
|
|
* @param[out] dasd Reference to a pointer variable in which the next dasd in
|
|
* the sequence will be returned. If there is no next DASD,
|
|
* this variable will be set to NULL.
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - EPERM The end of the list has been reached. There is no further dasd.
|
|
*/
|
|
int lzds_dasditerator_get_next_dasd(struct dasditerator *it, struct dasd **dasd)
|
|
{
|
|
struct dasd *dasdtmp;
|
|
|
|
if (!it->dasdi)
|
|
dasdtmp = util_list_start(it->zdsroot->dasdlist);
|
|
else
|
|
dasdtmp = util_list_next(it->zdsroot->dasdlist, it->dasdi);
|
|
*dasd = dasdtmp;
|
|
if (!dasdtmp)
|
|
return EPERM;
|
|
it->dasdi = dasdtmp;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @param[in] dasd The struct dasd we want to know the device of.
|
|
* @param[out] device Reference to a pointer variable in which the device
|
|
* string will be returned. This string holds the device
|
|
* name as it was given to lzds_zdsroot_add_device.
|
|
*/
|
|
void lzds_dasd_get_device(struct dasd *dasd, char **device)
|
|
{
|
|
*device = dasd->device;
|
|
}
|
|
|
|
/**
|
|
* @param[in] root Reference to the zdsroot that holds the dasd.
|
|
* @param[in] device Pointer to a character string that holds the device node
|
|
* name that we are looking for. It must be the same name as
|
|
* previously given to lzds_zdsroot_add_device
|
|
* @param[out] dasd Reference to a pointer variable in which the found struct
|
|
* dasd will be returned. If no dasd was found,
|
|
* this will be set to NULL
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - ENOMEM Could not allocate internal structures due to lack of memory.
|
|
* - ENODEV No matching struct dasd was found.
|
|
*/
|
|
int lzds_zdsroot_get_dasd_by_node_name(struct zdsroot *root, const char *device,
|
|
struct dasd **dasd)
|
|
{
|
|
struct dasditerator *dasdit;
|
|
int rc;
|
|
struct dasd *tempdasd;
|
|
char *dasddev;
|
|
|
|
errorlog_clear(root->log);
|
|
rc = lzds_zdsroot_alloc_dasditerator(root, &dasdit);
|
|
if (rc)
|
|
return ENOMEM;
|
|
rc = ENODEV;
|
|
*dasd = NULL;
|
|
while (!lzds_dasditerator_get_next_dasd(dasdit, &tempdasd)) {
|
|
lzds_dasd_get_device(tempdasd, &dasddev);
|
|
if (!strcmp(device, dasddev)) {
|
|
rc = 0;
|
|
*dasd = tempdasd;
|
|
break;
|
|
}
|
|
}
|
|
lzds_dasditerator_free(dasdit);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* @param[in] root A zdsroot on which an error occurred.
|
|
* @param[out] log Reference to a variable in which the errorlog
|
|
* is returned.
|
|
*/
|
|
void lzds_zdsroot_get_errorlog(struct zdsroot *root, struct errorlog **log)
|
|
{
|
|
*log = root->log;
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief free storage for a single error message
|
|
*
|
|
* @param[in] msg The message to be freed
|
|
*/
|
|
static void errormsg_free(struct errormsg *msg)
|
|
{
|
|
free(msg);
|
|
}
|
|
|
|
/**
|
|
* @brief allocate storage for a single error message
|
|
*
|
|
* @param[out] msg Reference to a pointer variable in which the newly allocated
|
|
* structure will be returned.
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - ENOMEM Could not allocate structure due to lack of memory.
|
|
*/
|
|
static int errormsg_alloc(struct errormsg **msg)
|
|
{
|
|
struct errormsg *tmpmsg;
|
|
|
|
*msg = NULL;
|
|
tmpmsg = malloc(sizeof(*tmpmsg));
|
|
if (!tmpmsg)
|
|
return ENOMEM;
|
|
memset(tmpmsg, 0, sizeof(*tmpmsg));
|
|
*msg = tmpmsg;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief remove and free all messages from a given errolog
|
|
*
|
|
* After this operation new messages can be added to the log.
|
|
*
|
|
* @param[in] log The message log to be cleared. This may be NULL.
|
|
*/
|
|
static void errorlog_clear(struct errorlog *log)
|
|
{
|
|
struct errormsg *msg, *nextmsg;
|
|
|
|
if (!log)
|
|
return;
|
|
util_list_iterate_safe(log->entries, msg, nextmsg) {
|
|
util_list_remove(log->entries, msg);
|
|
errormsg_free(msg);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief free storage for an error log, including all messages
|
|
*
|
|
* @param[in] log The error log to be freed. This may be NULL.
|
|
*/
|
|
static void errorlog_free(struct errorlog *log)
|
|
{
|
|
if (!log)
|
|
return;
|
|
errorlog_clear(log);
|
|
util_list_free(log->entries);
|
|
free(log);
|
|
}
|
|
|
|
/**
|
|
* @brief allocate storage for an error log
|
|
*
|
|
* @param[out] log Reference to a pointer variable in which the newly allocated
|
|
* structure will be returned.
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - ENOMEM Could not allocate structure due to lack of memory.
|
|
*/
|
|
static int errorlog_alloc(struct errorlog **log)
|
|
{
|
|
struct errorlog *tmplog;
|
|
|
|
*log = NULL;
|
|
tmplog = malloc(sizeof(*tmplog));
|
|
if (!tmplog)
|
|
return ENOMEM;
|
|
memset(tmplog, 0, sizeof(*tmplog));
|
|
tmplog->entries = util_list_new(struct errormsg, list);
|
|
*log = tmplog;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief add a new message to the front of a log.
|
|
*
|
|
* @param[out] log A reference to a errorlog pointer variable. If a log already
|
|
* exists, old messages are cleared, otherwise a new log will
|
|
* be created.
|
|
* @param[in] oldlog A log that already contains messages, usually from a call
|
|
* to a subordinate function. This may be the same errorlog as
|
|
* referenced by log, in which case the existing messages
|
|
* are retained. This may also be NULL.
|
|
* @param[in] error_code The error code that will be stored in the new errormsg.
|
|
* This is also the return value.
|
|
* @param[in] message_format A format string for the message string
|
|
* (see vsnprintf man page).
|
|
* @param[in] ... A variable number of further parameters.
|
|
* Must match the message_format string.
|
|
*/
|
|
static int errorlog_add_message(struct errorlog **log,
|
|
struct errorlog *oldlog,
|
|
int error_code,
|
|
const char *message_format,
|
|
...)
|
|
{
|
|
struct errormsg *msg, *nextmsg;
|
|
struct errorlog *tmplog;
|
|
va_list ap;
|
|
int rc;
|
|
|
|
if (!log)
|
|
return error_code;
|
|
if (log && !*log) {
|
|
errorlog_alloc(&tmplog);
|
|
if (!tmplog)
|
|
return error_code;
|
|
*log = tmplog;
|
|
} else {
|
|
tmplog = *log;
|
|
}
|
|
|
|
if (tmplog != oldlog) {
|
|
errorlog_clear(tmplog);
|
|
if (oldlog) {
|
|
util_list_iterate_safe(oldlog->entries, msg, nextmsg) {
|
|
util_list_remove(oldlog->entries, msg);
|
|
util_list_add_tail(tmplog->entries, msg);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!message_format)
|
|
return error_code;
|
|
|
|
rc = errormsg_alloc(&msg);
|
|
if (rc)
|
|
return error_code;
|
|
|
|
va_start(ap, message_format);
|
|
vsnprintf(msg->text, ERRORMSG - 1, message_format, ap);
|
|
va_end(ap);
|
|
msg->error = error_code;
|
|
util_list_add_head(tmplog->entries, msg);
|
|
|
|
return error_code;
|
|
}
|
|
|
|
/**
|
|
* This is pretty a very simple implementation that just goes through
|
|
* the list of messages in the log and for each message it prints
|
|
* "rc <error>: <text>"
|
|
*
|
|
* @param[in] log A log that contains messages.
|
|
* @param[in] stream The stream that these messages will be printed to.
|
|
*/
|
|
int lzds_errorlog_fprint(struct errorlog *log, FILE *stream)
|
|
{
|
|
struct errormsg *msg;
|
|
int rc;
|
|
|
|
if (!log)
|
|
return 0;
|
|
util_list_iterate(log->entries, msg) {
|
|
rc = fprintf(stream, "rc %d: %s", msg->error, msg->text);
|
|
if (rc < 0)
|
|
return -rc;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
/* LOW level functions */
|
|
/******************************************************************************/
|
|
|
|
|
|
/**
|
|
* @param[in] dasd The DASD to whose geometry we refer to.
|
|
* @param[in] p Cylinder and head address
|
|
* @param[out] track The sequential track number for the given
|
|
* cylinder and head address.
|
|
*/
|
|
void lzds_dasd_cchh2trk(struct dasd *dasd, cchh_t *p, unsigned int *track)
|
|
{
|
|
*track = vtoc_get_cyl_from_cchh(p) * dasd->heads +
|
|
vtoc_get_head_from_cchh(p);
|
|
}
|
|
|
|
/**
|
|
* @param[in] dasd The DASD to whose geometry we refer to.
|
|
* @param[out] cylinders The number of cylinders that DASD has
|
|
*/
|
|
void lzds_dasd_get_cylinders(struct dasd *dasd, unsigned int *cylinders)
|
|
{
|
|
*cylinders = dasd->cylinders;
|
|
}
|
|
|
|
/**
|
|
* @param[in] dasd The DASD to whose geometry we refer to
|
|
* @param[out] heads The number of heads that DASD has
|
|
*/
|
|
void lzds_dasd_get_heads(struct dasd *dasd, unsigned int *heads)
|
|
{
|
|
*heads = dasd->heads;
|
|
}
|
|
|
|
/**
|
|
* @param[in] dasd Reference to struct dasd that represents
|
|
* the DASD that we want to read from.
|
|
* @param[out] dasdh Reference to a pointer variable in which the newly
|
|
* allocated structure will be returned.
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - ENOMEM Could not allocate structure due to lack of memory.
|
|
*/
|
|
int lzds_dasd_alloc_dasdhandle(struct dasd *dasd, struct dasdhandle **dasdh)
|
|
{
|
|
struct dasdhandle *dasdhtmp;
|
|
|
|
dasdhtmp = malloc(sizeof(*dasdhtmp));
|
|
if (!dasdhtmp)
|
|
return ENOMEM;
|
|
memset(dasdhtmp, 0, sizeof(*dasdhtmp));
|
|
dasdhtmp->fd = -1;
|
|
dasdhtmp->dasd = dasd;
|
|
*dasdh = dasdhtmp;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @param[in] dasdh Pointer to the struct dasdhandle that is to be freed.
|
|
*/
|
|
void lzds_dasdhandle_free(struct dasdhandle *dasdh)
|
|
{
|
|
if (!dasdh)
|
|
return;
|
|
/* we close the file descriptor in case it wasn't done properly */
|
|
lzds_dasdhandle_close(dasdh);
|
|
errorlog_free(dasdh->log);
|
|
free(dasdh);
|
|
}
|
|
|
|
/**
|
|
* @param[in] dasdh The dasd handle for the dasd that is to be opened.
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - EIO Could not open underlying device.
|
|
*/
|
|
int lzds_dasdhandle_open(struct dasdhandle *dasdh)
|
|
{
|
|
errorlog_clear(dasdh->log);
|
|
dasdh->fd = open(dasdh->dasd->device, O_RDONLY | O_DIRECT);
|
|
if (dasdh->fd < 0) {
|
|
dasdh->fd = -1;
|
|
return errorlog_add_message(
|
|
&dasdh->log, NULL, EIO,
|
|
"dasdhandle: could not open %s, errno %d\n",
|
|
dasdh->dasd->device, errno);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @param[in] dasdh The dasdhandle that has to be closed
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - EIO Error when closing underlying dasd device.
|
|
*/
|
|
int lzds_dasdhandle_close(struct dasdhandle *dasdh)
|
|
{
|
|
int rc;
|
|
errorlog_clear(dasdh->log);
|
|
rc = 0;
|
|
if (dasdh->fd >= 0)
|
|
rc = close(dasdh->fd);
|
|
dasdh->fd = -1;
|
|
if (rc)
|
|
return errorlog_add_message(
|
|
&dasdh->log, NULL, EIO,
|
|
"dasdhandle: could not close %s\n",
|
|
dasdh->dasd->device);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @param[in] dasdh The dasdhandle we are reading from
|
|
* @param[in] starttrck First track to read
|
|
* @param[in] endtrck Last track to read
|
|
* @param[out] trackdata Target buffer we read into, must have at least the
|
|
* size (endtrk - starttrk + 1) * RAWTRACKSIZE
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - EINVAL starttrck or endtrck are not within the boundaries of the
|
|
* underlying DASD device.
|
|
* - EPROTO Could not read a full track image
|
|
* - EIO Other I/O error
|
|
*/
|
|
int lzds_dasdhandle_read_tracks_to_buffer(struct dasdhandle *dasdh,
|
|
unsigned int starttrck,
|
|
unsigned int endtrck,
|
|
char *trackdata)
|
|
{
|
|
off_t trckseek;
|
|
ssize_t residual;
|
|
off_t rc;
|
|
ssize_t count;
|
|
|
|
unsigned int cylinders;
|
|
unsigned int heads;
|
|
|
|
errorlog_clear(dasdh->log);
|
|
/* verify that endtrck is not beyond the end of the dasd */
|
|
lzds_dasd_get_cylinders(dasdh->dasd, &cylinders);
|
|
lzds_dasd_get_heads(dasdh->dasd, &heads);
|
|
if (starttrck > endtrck || endtrck >= cylinders * heads)
|
|
return errorlog_add_message(
|
|
&dasdh->log, NULL, EINVAL,
|
|
"dasdhandle read tracks: start %u, end %u is"
|
|
" out of bounds for device %s\n",
|
|
starttrck, endtrck, dasdh->dasd->device);
|
|
/*
|
|
* Compute seek address of the first track and number of tracks
|
|
* to be read. Please note that geo.sectors does not match our raw
|
|
* track size of 16*4KB, so we use the RAWTRACKSIZE explicitly
|
|
*/
|
|
trckseek = (off_t)starttrck * RAWTRACKSIZE;
|
|
/* residual is the number of bytes we still have to read */
|
|
residual = (off_t)(endtrck - starttrck + 1) * RAWTRACKSIZE;
|
|
rc = lseek(dasdh->fd, trckseek, SEEK_SET);
|
|
if (rc < 0)
|
|
return errorlog_add_message(
|
|
&dasdh->log, NULL, EINVAL,
|
|
"dasdhandle read tracks: seek to %llu, failed"
|
|
" for device %s\n",
|
|
(unsigned long long)trckseek, dasdh->dasd->device);
|
|
|
|
while (residual) {
|
|
count = read(dasdh->fd, trackdata, residual);
|
|
if (count < 0)
|
|
return errorlog_add_message(
|
|
&dasdh->log, NULL, EIO,
|
|
"dasdhandle read tracks: read failed"
|
|
" for device %s, start %u, end %u\n",
|
|
dasdh->dasd->device, starttrck, endtrck);
|
|
if (count % RAWTRACKSIZE) /* No full track read */
|
|
return errorlog_add_message(
|
|
&dasdh->log, NULL, EPROTO,
|
|
"dasdhandle read tracks: read returned "
|
|
"unaligned data for device %s,"
|
|
"start %u, end %u\n",
|
|
dasdh->dasd->device, starttrck, endtrck);
|
|
residual -= count;
|
|
trackdata += count;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/******************************************************************************/
|
|
/* MID level functions */
|
|
/******************************************************************************/
|
|
/**
|
|
* @brief Helper function that iterates through the records in a track buffer.
|
|
*
|
|
* @param[in] buffer Address of the track buffer
|
|
* @param[in] size Size of the buffer
|
|
* @param[in,out] record Pointer that has the current record pointer as input
|
|
* and gets a pointer to the next record as output.
|
|
* If it the current record pointer is null, then the
|
|
* pointer to the first record is returned.
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - ENOENT If we have reached the end of the buffer and there are no
|
|
* further records
|
|
*/
|
|
static int buffer_get_next_record(char *buffer, size_t size, char **record)
|
|
{
|
|
char *data, *next_record;
|
|
unsigned long offset;
|
|
unsigned int record_size;
|
|
struct eckd_count *ecount;
|
|
|
|
/* If *record contains no record yet, then we return the first record */
|
|
if (!*record) {
|
|
*record = buffer;
|
|
return 0;
|
|
}
|
|
data = *record;
|
|
ecount = (struct eckd_count *)data;
|
|
record_size = sizeof(*ecount) + ecount->kl + ecount->dl;
|
|
data += record_size;
|
|
next_record = NULL;
|
|
while (!next_record) {
|
|
/* check if we have reached the end of the buffer */
|
|
if (data >= buffer + size) {
|
|
*record = NULL;
|
|
return ENOENT;
|
|
}
|
|
/* If the 'next' record is the pseudo record, then we have
|
|
* reached the end of data in this track and we have to jump
|
|
* to the start of the next track to find the next record.
|
|
*/
|
|
if ((*(unsigned long long *)data) == ENDTOKEN) {
|
|
offset = (unsigned long)data - (unsigned long)buffer;
|
|
offset &= ~(RAWTRACKSIZE - 1);
|
|
offset += RAWTRACKSIZE;
|
|
data = buffer + offset;
|
|
continue;
|
|
}
|
|
next_record = data;
|
|
}
|
|
*record = next_record;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Helper function that does the whole open/read/close cycle in one go.
|
|
*
|
|
* @param[in] dasd Pointer to struct dasd that represents
|
|
* the DASD that we want to read from.
|
|
* @param[in] starttrck First track to read
|
|
* @param[in] endtrck Last track to read
|
|
* @param[out] trackdata Target buffer we read into, must have at least the
|
|
* size (endtrk - starttrk + 1) * RAWTRACKSIZE
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - ENOMEM Could not allocate internal structure due to lack of memory.
|
|
* - EINVAL starttrck or endtrck are not within the boundaries of the
|
|
* underlying DASD device.
|
|
* - EPROTO Could not read a full track image
|
|
* - EIO Other I/O error
|
|
*/
|
|
static int dasd_read_tracks(struct dasd *dasd,
|
|
unsigned int starttrck,
|
|
unsigned int endtrck,
|
|
char *trackdata)
|
|
{
|
|
struct dasdhandle *dasdh;
|
|
int rc, rc2;
|
|
|
|
rc = lzds_dasd_alloc_dasdhandle(dasd, &dasdh);
|
|
if (rc)
|
|
return errorlog_add_message(
|
|
&dasd->log, dasd->log, rc,
|
|
"dasd read tracks: could not allocate dasdhandle\n");
|
|
|
|
rc = lzds_dasdhandle_open(dasdh);
|
|
if (rc) {
|
|
errorlog_add_message(
|
|
&dasd->log, dasdh->log, rc,
|
|
"dasd read tracks: could not open dasdhandle\n");
|
|
lzds_dasdhandle_free(dasdh);
|
|
return rc;
|
|
}
|
|
rc = lzds_dasdhandle_read_tracks_to_buffer(dasdh, starttrck,
|
|
endtrck, trackdata);
|
|
if (rc)
|
|
errorlog_add_message(
|
|
&dasd->log, dasdh->log, rc,
|
|
"dasd read tracks: read error\n");
|
|
rc2 = lzds_dasdhandle_close(dasdh);
|
|
/* report close error only if we had no read error */
|
|
if (rc2 && !rc) {
|
|
errorlog_add_message(
|
|
&dasd->log, dasdh->log, rc,
|
|
"dasd read tracks: could not close dasdhandle\n");
|
|
rc = rc2;
|
|
}
|
|
lzds_dasdhandle_free(dasdh);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* @brief Helper function that reads a volume label from a DASD.
|
|
*
|
|
* @param[in] dasd Pointer to struct dasd that represents
|
|
* the DASD that we want to read from.
|
|
* @param[out] vlabel Buffer to read the label into.
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - ENOMEM Could not allocate internal structure due to lack of memory.
|
|
* - EIO Other I/O error
|
|
*/
|
|
static int dasd_read_vlabel_to_buffer(struct dasd *dasd,
|
|
struct volume_label *vlabel)
|
|
{
|
|
int rc;
|
|
unsigned int i;
|
|
char *trackdata, *record;
|
|
struct volume_label *label;
|
|
struct eckd_count *ecount;
|
|
unsigned long labelend, trackend;
|
|
size_t label_size;
|
|
|
|
trackdata = memalign(4096, RAWTRACKSIZE); /* page align for O_DIRECT */
|
|
if (!trackdata)
|
|
return ENOMEM;
|
|
|
|
rc = dasd_read_tracks(dasd, 0, 0, trackdata);
|
|
if (rc) {
|
|
free(trackdata);
|
|
return errorlog_add_message(
|
|
&dasd->log, dasd->log, EIO,
|
|
"read vlabel: could not read track 0\n");
|
|
}
|
|
/* fist step, find label record */
|
|
record = NULL;
|
|
label = NULL;
|
|
ecount = NULL;
|
|
i = 0;
|
|
while (!buffer_get_next_record(trackdata, RAWTRACKSIZE, &record)) {
|
|
if (i == (dasd->label_block + 1)) {
|
|
ecount = (struct eckd_count *)record;
|
|
label = (struct volume_label *)(ecount + 1);
|
|
break;
|
|
}
|
|
++i;
|
|
}
|
|
if (!ecount || !label) {
|
|
free(trackdata);
|
|
return errorlog_add_message(
|
|
&dasd->log, dasd->log, EPROTO,
|
|
"read vlabel: could not find label record\n");
|
|
}
|
|
/* verify record layout */
|
|
memset(vlabel, 0, sizeof(*vlabel));
|
|
labelend = (unsigned long)label + ecount->kl + ecount->dl;
|
|
trackend = (unsigned long)trackdata + RAWTRACKSIZE;
|
|
if ((ecount->kl + ecount->dl == 84) && (labelend <= trackend)) {
|
|
/* VOL1 label */
|
|
memcpy(vlabel, label, ecount->kl + ecount->dl);
|
|
} else if ((ecount->kl == 0) && (labelend <= trackend)) {
|
|
/* LNX1 / CMS1 label */
|
|
label_size = MIN(ecount->dl, sizeof(*vlabel) - 4);
|
|
memcpy(&vlabel->vollbl, label, label_size);
|
|
} else {
|
|
free(trackdata);
|
|
return errorlog_add_message(
|
|
&dasd->log, dasd->log, EPROTO,
|
|
"read vlabel: record layout does not match VOL1"
|
|
" label\n");
|
|
}
|
|
free(trackdata);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @param[in] dasd Pointer to struct dasd that represents
|
|
* the DASD that we want to read from.
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - ENOMEM Could not allocate internal structure due to lack of memory.
|
|
* - EIO Other I/O error
|
|
*/
|
|
int lzds_dasd_read_vlabel(struct dasd *dasd)
|
|
{
|
|
struct volume_label *vlabel;
|
|
int rc;
|
|
|
|
errorlog_clear(dasd->log);
|
|
free(dasd->vlabel);
|
|
dasd->vlabel = NULL;
|
|
vlabel = malloc(sizeof(*vlabel));
|
|
if (!vlabel)
|
|
return ENOMEM;
|
|
rc = dasd_read_vlabel_to_buffer(dasd, vlabel);
|
|
if (rc)
|
|
free(vlabel);
|
|
else
|
|
dasd->vlabel = vlabel;
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* @param[in] dasd Reference to struct dasd that we want to get the label
|
|
* from.
|
|
* @param[out] vlabel Reference to a pointer variable in which the struct
|
|
* volume_label will be returned.
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - EINVAL The volume lable has not yet been read from the device.
|
|
*/
|
|
int lzds_dasd_get_vlabel(struct dasd *dasd, struct volume_label **vlabel)
|
|
{
|
|
*vlabel = dasd->vlabel;
|
|
if (*vlabel)
|
|
return 0;
|
|
else
|
|
return EINVAL;
|
|
}
|
|
|
|
/**
|
|
* @param[in] rawvtoc Reference to struct raw_vtoc that the iterator will be
|
|
* bound to. The iterator will traverse the DSCBs stored
|
|
* in this raw_vtoc.
|
|
* @param[out] it Reference to a pointer variable in which the newly allocated
|
|
* structure will be returned.
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - ENOMEM Could not allocate structure due to lack of memory.
|
|
*/
|
|
int lzds_raw_vtoc_alloc_dscbiterator(struct raw_vtoc *rawvtoc,
|
|
struct dscbiterator **it)
|
|
{
|
|
*it = malloc(sizeof(**it));
|
|
if (*it) {
|
|
(*it)->i = rawvtoc->vtocrecno - 1;
|
|
(*it)->rawvtoc = rawvtoc;
|
|
return 0;
|
|
}
|
|
return ENOMEM;
|
|
}
|
|
|
|
/**
|
|
* @param[in] it Pointer to the struct dscbiterator that is to be freed.
|
|
*/
|
|
void lzds_dscbiterator_free(struct dscbiterator *it)
|
|
{
|
|
free(it);
|
|
}
|
|
|
|
/**
|
|
* @param[out] it Reference to the struct dscb iterator we use to traverse
|
|
* the VTOC.
|
|
* @param[out] dscb Reference to a pointer variable in which the next dscb in
|
|
* the sequence will be returned. If there is no next dscb,
|
|
* this variable will be set to NULL.
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - EPERM There is no further DSCB in the VTOC.
|
|
*/
|
|
int lzds_dscbiterator_get_next_dscb(struct dscbiterator *it, struct dscb **dscb)
|
|
{
|
|
struct eckd_count *ecount;
|
|
unsigned int i;
|
|
|
|
i = it->i + 1;
|
|
while (i < it->rawvtoc->vtocindexcount) {
|
|
ecount = (struct eckd_count *)(it->rawvtoc->vtocindex[i]);
|
|
if (ecount && (ecount->kl == 44) && (ecount->dl == 96))
|
|
break;
|
|
else
|
|
++i;
|
|
}
|
|
if (i < it->rawvtoc->vtocindexcount) {
|
|
it->i = i;
|
|
*dscb = (struct dscb *)(it->rawvtoc->vtocindex[it->i]
|
|
+ sizeof(*ecount));
|
|
return 0;
|
|
} else {
|
|
*dscb = NULL;
|
|
return EPERM;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Subroutine of lzds_raw_vtoc_get_dscb_from_cchhb
|
|
*
|
|
* This function takes a cylinder, head, block address as it can be
|
|
* found in DSCBs and returns an index to the matching entry in the
|
|
* raw_vtoc vtocindex.
|
|
*
|
|
* The cchhb2blk function of the libvtoc does not work for raw devices
|
|
* as the 'sectors per track' value in the geo structure has no meaning
|
|
* for a raw DASD. We need to take this value from the context,
|
|
* e.g. from the format 4 label of the VTOC.
|
|
* Since this computation is very specialized, we can go all the way and
|
|
* just compute the index to the vtoc array.
|
|
*
|
|
* @param[in] rv The raw_vtoc we refer to.
|
|
* @param[in] p The cylinder, head, block address structure.
|
|
* @return index to the vtocindex array
|
|
*/
|
|
static long long vtocindex_from_cchhb(struct raw_vtoc *rv, cchhb_t *p)
|
|
{
|
|
long long recno;
|
|
|
|
recno = (long long) vtoc_get_cyl_from_cchhb(p) *
|
|
rv->dasd->heads * rv->vtoc_rec_per_track +
|
|
vtoc_get_head_from_cchhb(p) * rv->vtoc_rec_per_track +
|
|
p->b;
|
|
return recno - (rv->vtoctrackoffset * rv->vtoc_rec_per_track);
|
|
}
|
|
|
|
/**
|
|
* @note A cchhb address within a VTOC dscb is often set to zero to
|
|
* indicate that this entry does not point anywhere. For example this
|
|
* is the case at the end of a format 3 dscb chain. This special case
|
|
* is handled by setting the dscb pointer to NULL and having a return
|
|
* value of 0 (no error).
|
|
*
|
|
* @param[in] rv The raw_vtoc we refer to.
|
|
* @param[in] p The cylinder, head, block address of the DSCB.
|
|
* @param[out] dscb Reference to a pointer variable in which a pointer to
|
|
* the respective dscb in the raw_vtoc will be returned.
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - EINVAL The address in *p refers to a record that is not a valid DSCB.
|
|
* - ERANGE The cylinder, head, block address lies not within the VTOC.
|
|
*/
|
|
int lzds_raw_vtoc_get_dscb_from_cchhb(struct raw_vtoc *rv, cchhb_t *p,
|
|
struct dscb **dscb)
|
|
{
|
|
long long index;
|
|
char *record;
|
|
|
|
errorlog_clear(rv->log);
|
|
index = vtocindex_from_cchhb(rv, p);
|
|
*dscb = NULL;
|
|
if (!p->cc && !p->hh && !p->b)
|
|
return 0;
|
|
/* record zero is part of the track image, but not a dscb */
|
|
if (!p->b)
|
|
return errorlog_add_message(
|
|
&rv->log, NULL, EINVAL,
|
|
"raw vtoc: DSCB address is empty\n");
|
|
if (index < rv->vtocrecno || index >= rv->vtocindexcount)
|
|
return errorlog_add_message(
|
|
&rv->log, NULL, ERANGE,
|
|
"raw vtoc: DSCB address is outside VTOC\n");
|
|
record = rv->vtocindex[vtocindex_from_cchhb(rv, p)];
|
|
if (!record)
|
|
return errorlog_add_message(
|
|
&rv->log, NULL, EINVAL,
|
|
"raw vtoc: DSCB address points to nonexistent DSCB\n");
|
|
*dscb = (struct dscb *)(record + sizeof(struct eckd_count));
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @param[in] dasd The struct dasd that represents the device we want to read
|
|
* the VTOC from.
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - ENOMEM Could not allocate internal structure due to lack of memory.
|
|
* - EINVAL The volume label has not yet been read or it is not valid.
|
|
* - EPROTO The VTOC data is not in a valid format.
|
|
* - EIO Other I/O error
|
|
*/
|
|
int lzds_dasd_read_rawvtoc(struct dasd *dasd, struct raw_vtoc *rawvtoc)
|
|
{
|
|
unsigned long long vtoctrckno, vtocrecno;
|
|
unsigned int vtoctrack_start, vtoctrack_end, vtocindexsize;
|
|
unsigned int vtoc_rec_per_track;
|
|
unsigned int i;
|
|
int rc;
|
|
char *record;
|
|
struct eckd_count *ecount;
|
|
format4_label_t *f4;
|
|
unsigned long long rawvtocsize;
|
|
|
|
volume_label_t *vlabel = NULL;
|
|
char *trackdata = NULL;
|
|
|
|
errorlog_clear(dasd->log);
|
|
|
|
rc = lzds_dasd_get_vlabel(dasd, &vlabel);
|
|
if (rc) {
|
|
errorlog_add_message(
|
|
&dasd->log, NULL, rc,
|
|
"read VTOC: there is no volume label data available\n");
|
|
goto cleanup;
|
|
}
|
|
/* verify that we have a proper VOL1 label */
|
|
if (!is_vol1(vlabel->volkey) || !is_vol1(vlabel->vollbl)) {
|
|
rc = EINVAL;
|
|
errorlog_add_message(
|
|
&dasd->log, NULL, rc,
|
|
"read VTOC: volume label is not a VOL1 label\n");
|
|
goto cleanup;
|
|
}
|
|
|
|
/* The label contains the address of the first block of the vtoc. */
|
|
vtoctrckno = (unsigned long long) vtoc_get_cyl_from_cchhb(&vlabel->vtoc)
|
|
* dasd->heads + vtoc_get_head_from_cchhb(&vlabel->vtoc);
|
|
vtocrecno = vlabel->vtoc.b;
|
|
|
|
/* We do not know how large the VTOC is, yet. So first, we read only
|
|
* one track of the VTOC to access the format 4 DSCB in the first record
|
|
* of the VTOC.
|
|
*/
|
|
trackdata = memalign(4096, RAWTRACKSIZE); /* page align for O_DIRECT */
|
|
if (!trackdata) {
|
|
rc = ENOMEM;
|
|
goto cleanup;
|
|
}
|
|
rc = dasd_read_tracks(dasd, vtoctrckno, vtoctrckno, trackdata);
|
|
if (rc) {
|
|
errorlog_add_message(
|
|
&dasd->log, dasd->log, rc,
|
|
"read VTOC: error when reading VTOC start\n");
|
|
goto cleanup;
|
|
}
|
|
record = NULL;
|
|
f4 = NULL;
|
|
i = 0;
|
|
while (!buffer_get_next_record(trackdata, RAWTRACKSIZE, &record)) {
|
|
if (i == vtocrecno) {
|
|
f4 = (format4_label_t *)(record + 8);
|
|
ecount = (struct eckd_count *)record;
|
|
break;
|
|
}
|
|
++i;
|
|
}
|
|
/* verify that the found record has the expected format */
|
|
if (!(f4 &&
|
|
(ecount->kl == 44) && (ecount->dl == 96) &&
|
|
(f4->DS4KEYCD[0] == 0x04) &&
|
|
(f4->DS4KEYCD[43] == 0x04) &&
|
|
(f4->DS4IDFMT == 0xf4))) {
|
|
rc = EPROTO;
|
|
errorlog_add_message(
|
|
&dasd->log, NULL, rc,
|
|
"read VTOC: could not find format 4 DSCB\n");
|
|
goto cleanup;
|
|
}
|
|
/* We have found a format 4 label at the position indicated by the
|
|
* label.
|
|
* How to determine the size of the VTOC:
|
|
* - DS4VTOCE contains the VTOC extent, or in other words, lower and
|
|
* uper boundary of the VTOC
|
|
*
|
|
* Searching through the VTOC tracks record by record is tedious, so
|
|
* we build an array of pointers to the DSCBs, our VTOC index:
|
|
* Number of entries in the index is the number of tracks times the
|
|
* number of DSCBS per track plus one for record zero
|
|
*/
|
|
lzds_dasd_cchh2trk(dasd, &f4->DS4VTOCE.llimit, &vtoctrack_start);
|
|
lzds_dasd_cchh2trk(dasd, &f4->DS4VTOCE.ulimit, &vtoctrack_end);
|
|
vtoc_rec_per_track = (f4->DS4DEVCT.DS4DEVDT + 1);
|
|
/* A VTOC consists of whole tracks, so the index size is number of
|
|
* tracks multiplied by records per track
|
|
*/
|
|
vtocindexsize = (vtoctrack_end - vtoctrack_start + 1) *
|
|
vtoc_rec_per_track;
|
|
|
|
rawvtocsize = ((unsigned long long)vtoctrack_end - vtoctrack_start + 1)
|
|
* RAWTRACKSIZE;
|
|
|
|
f4 = NULL;
|
|
record = NULL;
|
|
free(trackdata);
|
|
trackdata = memalign(4096, rawvtocsize); /* page align for O_DIRECT */
|
|
if (!trackdata) {
|
|
rc = ENOMEM;
|
|
goto cleanup;
|
|
}
|
|
|
|
/* read in the full VTOC from disk into memory */
|
|
rc = dasd_read_tracks(dasd, vtoctrack_start, vtoctrack_end, trackdata);
|
|
if (rc) {
|
|
errorlog_add_message(
|
|
&dasd->log, dasd->log, rc,
|
|
"read VTOC: error when reading VTOC\n");
|
|
goto cleanup;
|
|
}
|
|
|
|
rawvtoc->rawdata = trackdata;
|
|
rawvtoc->rawdatasize = rawvtocsize;
|
|
rawvtoc->vtoc_rec_per_track = vtoc_rec_per_track;
|
|
rawvtoc->vtoctrackoffset = vtoctrack_start;
|
|
rawvtoc->vtocrecno = vtocrecno;
|
|
rawvtoc->vtocindexcount = vtocindexsize;
|
|
|
|
/* Now parse all VTOC tracks in memory and create an index of
|
|
* all records (including record 0)
|
|
*/
|
|
rawvtoc->vtocindex = malloc(sizeof(char *) * vtocindexsize);
|
|
if (!rawvtoc->vtocindex) {
|
|
rc = ENOMEM;
|
|
goto cleanup;
|
|
}
|
|
memset(rawvtoc->vtocindex, 0, (sizeof(char *) * vtocindexsize));
|
|
|
|
record = NULL;
|
|
f4 = NULL;
|
|
i = 0;
|
|
while (!buffer_get_next_record(trackdata, rawvtocsize, &record)) {
|
|
/* verify that we do not get too many records */
|
|
if (i >= vtocindexsize) {
|
|
rc = EPROTO;
|
|
errorlog_add_message(
|
|
&dasd->log, NULL, rc,
|
|
"read VTOC: too many records in VTOC\n");
|
|
goto cleanup;
|
|
}
|
|
rawvtoc->vtocindex[i] = record;
|
|
++i;
|
|
}
|
|
|
|
return 0;
|
|
|
|
cleanup:
|
|
free(trackdata);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* @param[in] dasd The struct dasd that represents the device we want to read
|
|
* the VTOC from.
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - ENOMEM Could not allocate internal structure due to lack of memory.
|
|
* - EINVAL The volume label has not yet been read or it is not valid.
|
|
* - EPROTO The VTOC data is not in a valid format.
|
|
* - EIO Other I/O error
|
|
*/
|
|
int lzds_dasd_alloc_rawvtoc(struct dasd *dasd)
|
|
{
|
|
struct raw_vtoc *rawvtoc = NULL;
|
|
int rc;
|
|
|
|
/* cleanup the old rawvtoc structures before we read new ones */
|
|
rawvtoc = dasd->rawvtoc;
|
|
dasd->rawvtoc = NULL;
|
|
if (rawvtoc) {
|
|
free(rawvtoc->rawdata);
|
|
free(rawvtoc->vtocindex);
|
|
free(rawvtoc);
|
|
}
|
|
|
|
rawvtoc = malloc(sizeof(*rawvtoc));
|
|
if (!rawvtoc)
|
|
return ENOMEM;
|
|
memset(rawvtoc, 0, sizeof(*rawvtoc));
|
|
rawvtoc->dasd = dasd;
|
|
|
|
rc = lzds_dasd_read_rawvtoc(dasd, rawvtoc);
|
|
if (rc) {
|
|
free(rawvtoc->vtocindex);
|
|
free(rawvtoc);
|
|
} else {
|
|
dasd->rawvtoc = rawvtoc;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* @param[in] dasd Pointer to the struct dasd we want to get the raw_vtoc from.
|
|
* @param[out] vtoc Reference to a pointer variable in which a pointer to
|
|
* the previously read struct raw_vtoc will be returned.
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - EINVAL The VTOC has not yet been read.
|
|
*/
|
|
int lzds_dasd_get_rawvtoc(struct dasd *dasd, struct raw_vtoc **vtoc)
|
|
{
|
|
errorlog_clear(dasd->log);
|
|
*vtoc = dasd->rawvtoc;
|
|
if (!*vtoc)
|
|
return EINVAL;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
|
|
/******************************************************************************/
|
|
/* HIGH level functions */
|
|
/******************************************************************************/
|
|
|
|
/**
|
|
* @param[in] zdsroot Reference to struct zdsroot that the iterator will be
|
|
* bound to. The iterator will traverse the data sets stored
|
|
* in this zdsroot.
|
|
* @param[out] it Reference to a pointer variable in which the newly allocated
|
|
* structure will be returned.
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - ENOMEM Could not allocate structure due to lack of memory.
|
|
*/
|
|
int lzds_zdsroot_alloc_dsiterator(struct zdsroot *zdsroot,
|
|
struct dsiterator **it)
|
|
{
|
|
*it = malloc(sizeof(struct dsiterator));
|
|
if (*it) {
|
|
(*it)->dsi = NULL;
|
|
(*it)->zdsroot = zdsroot;
|
|
return 0;
|
|
}
|
|
return ENOMEM;
|
|
}
|
|
|
|
/**
|
|
* @param[in] it Pointer to the struct dsiterator that is to be freed.
|
|
*/
|
|
void lzds_dsiterator_free(struct dsiterator *it)
|
|
{
|
|
free(it);
|
|
}
|
|
|
|
/**
|
|
* @param[in] it Reference to the struct dsiterator we use to traverse the
|
|
* data set list.
|
|
* @param[out] ds Reference to a pointer variable in which the next
|
|
* data set in the sequence will be returned. If there
|
|
* is no next data set, this variable will be set to NULL.
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - EPERM The end of the list has been reached. There is no further dataset.
|
|
*/
|
|
int lzds_dsiterator_get_next_dataset(struct dsiterator *it, struct dataset **ds)
|
|
{
|
|
struct dataset *dstmp;
|
|
|
|
if (!it->dsi)
|
|
dstmp = util_list_start(it->zdsroot->datasetlist);
|
|
else
|
|
dstmp = util_list_next(it->zdsroot->datasetlist, it->dsi);
|
|
*ds = dstmp;
|
|
if (!dstmp)
|
|
return EPERM;
|
|
it->dsi = dstmp;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* @param[in] root Reference to struct zdsroot that holds the list of data
|
|
* sets that this function shall search through.
|
|
* @param[in] name Name of the data set.
|
|
* @param[out] ds Reference to a pointer variable in which the found dataset
|
|
* structure will be returned. If no data set was found, this
|
|
* variable will be set to NULL
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - ENOMEM Could not internal structure due to lack of memory.
|
|
* - ENOENT A dataset with the given name was not found.
|
|
*/
|
|
int lzds_zdsroot_find_dataset(struct zdsroot *root, const char *name,
|
|
struct dataset **ds)
|
|
{
|
|
struct dsiterator *dsit;
|
|
struct dataset *tempds;
|
|
int rc;
|
|
|
|
errorlog_clear(root->log);
|
|
*ds = NULL;
|
|
rc = lzds_zdsroot_alloc_dsiterator(root, &dsit);
|
|
if (rc)
|
|
return ENOMEM;
|
|
while (!lzds_dsiterator_get_next_dataset(dsit, &tempds)) {
|
|
if (!strcmp(tempds->name, name)) {
|
|
*ds = tempds;
|
|
break;
|
|
}
|
|
}
|
|
lzds_dsiterator_free(dsit);
|
|
if (!*ds)
|
|
return ENOENT;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @param[in] ds Reference to the struct dataset that the iterator will be
|
|
* bound to. The iterator will traverse the members stored
|
|
* in this data set.
|
|
* @param[out] it Reference to a pointer variable in which the newly allocated
|
|
* structure will be returned.
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - ENOMEM Could not allocate structure due to lack of memory.
|
|
* - EINVAL Failed to allocate a memberiterator because the data set does
|
|
* not support members (is not a PDS).
|
|
*/
|
|
int lzds_dataset_alloc_memberiterator(struct dataset *ds,
|
|
struct memberiterator **it)
|
|
{
|
|
if (!ds->memberlist) {
|
|
*it = NULL;
|
|
return errorlog_add_message(
|
|
&ds->log, NULL, EINVAL,
|
|
"alloc memberiterator: this data set has no members\n");
|
|
|
|
}
|
|
*it = malloc(sizeof(struct memberiterator));
|
|
if (*it) {
|
|
(*it)->memberi = NULL;
|
|
(*it)->ds = ds;
|
|
return 0;
|
|
}
|
|
return ENOMEM;
|
|
}
|
|
|
|
/**
|
|
* @param[in] it Pointer to the struct meberiterator that is to be freed.
|
|
*/
|
|
void lzds_memberiterator_free(struct memberiterator *it)
|
|
{
|
|
free(it);
|
|
}
|
|
|
|
/**
|
|
* @param[out] it Reference to the struct memberiterator we use to traverse
|
|
* the member list.
|
|
* @param[out] member Reference to a pointer variable in which the next member
|
|
* in the sequence will be returned. If there is no next
|
|
* member, this variable will be set to NULL.
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - EPERM The end of the list has been reached. There is no further dasd.
|
|
*/
|
|
int lzds_memberiterator_get_next_member(struct memberiterator *it,
|
|
struct pdsmember **member)
|
|
{
|
|
struct pdsmember *memtmp;
|
|
|
|
if (!it->memberi)
|
|
memtmp = util_list_start(it->ds->memberlist);
|
|
else
|
|
memtmp = util_list_next(it->ds->memberlist, it->memberi);
|
|
*member = memtmp;
|
|
if (!memtmp)
|
|
return EPERM;
|
|
it->memberi = memtmp;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief Subroutine of raw_vtoc_get_datasetpart_from_dscb
|
|
*
|
|
* Check the validity of the extent and copy it to the extent array in the
|
|
* datasetpart.
|
|
* @param[in] extent Pointer to the extent that is to be copied.
|
|
* @param[in] dsp The target datasetpart.
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - EPROTO The extent is not valid.
|
|
*/
|
|
static int copy_extent_to_datasetpart(extent_t *extent, struct datasetpart *dsp)
|
|
{
|
|
/* sanity check: if the extent is valid then make sure that seqno
|
|
* will not cause us to go beyond the array limits
|
|
*/
|
|
if (extent->typeind && extent->seqno >= MAXEXTENTS)
|
|
return EPROTO;
|
|
if (extent->typeind)
|
|
dsp->ext[extent->seqno] = *extent;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Subroutine of raw_vtoc_get_datasetpart_from_dscb
|
|
*/
|
|
static int raw_vtoc_add_extent_error_message(struct raw_vtoc *rv)
|
|
{
|
|
return errorlog_add_message(
|
|
&rv->log, NULL, EPROTO,
|
|
"vtoc: an extent descriptor is not valid \n");
|
|
}
|
|
|
|
/**
|
|
* @brief Subroutine of create_dataset_from_dscb
|
|
*
|
|
* This function copies the necessary data from a format 1/8 DSCB
|
|
* into a given datasetpart structure.
|
|
* @param[in] rv The raw_vtoc that f1 belongs to.
|
|
* @param[in] f1 The f1/f8 DSCB that the datasetpart is based on.
|
|
* @param[in] dsp The target datasetpart.
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - EPROTO Invalid data in the DSCB or dependent DSCBs.
|
|
*/
|
|
static int raw_vtoc_get_datasetpart_from_dscb(struct raw_vtoc *rv,
|
|
format1_label_t *f1,
|
|
struct datasetpart *dsp)
|
|
{
|
|
format3_label_t *f3;
|
|
format9_label_t *f9;
|
|
struct dscb *dscb;
|
|
int rc, j;
|
|
|
|
errorlog_clear(rv->log);
|
|
memset(dsp, 0, sizeof(*dsp));
|
|
dsp->f1 = f1;
|
|
|
|
/* Find the first format 3 DSCB that is chained format 1 or 8 DSCB.
|
|
* In a format 8 dscb we will first have one or more format 9
|
|
* DSCBs that we need to pass over.
|
|
*/
|
|
rc = lzds_raw_vtoc_get_dscb_from_cchhb(rv, &f1->DS1PTRDS, &dscb);
|
|
while (!rc && dscb && dscb->fmtid == 0xf9) {
|
|
f9 = (format9_label_t *)dscb;
|
|
rc = lzds_raw_vtoc_get_dscb_from_cchhb(rv, &f9->DS9PTRDS,
|
|
&dscb);
|
|
}
|
|
if (rc)
|
|
return errorlog_add_message(
|
|
&rv->log, rv->log, EPROTO,
|
|
"vtoc: format 9 DSCB chain not valid \n");
|
|
/* We may or may not have a format 3 DSCB */
|
|
f3 = (dscb && dscb->fmtid == 0xf3) ? (format3_label_t *)dscb : NULL;
|
|
|
|
/* In any case we have three extents in the f1/8 label itself */
|
|
rc = copy_extent_to_datasetpart(&f1->DS1EXT1, dsp);
|
|
if (rc)
|
|
return raw_vtoc_add_extent_error_message(rv);
|
|
rc = copy_extent_to_datasetpart(&f1->DS1EXT2, dsp);
|
|
if (rc)
|
|
return raw_vtoc_add_extent_error_message(rv);
|
|
rc = copy_extent_to_datasetpart(&f1->DS1EXT3, dsp);
|
|
if (rc)
|
|
return raw_vtoc_add_extent_error_message(rv);
|
|
/* now follow the f3 chain */
|
|
while (f3) {
|
|
if (f3->DS3FMTID != 0xf3)
|
|
return errorlog_add_message(
|
|
&rv->log, rv->log, EPROTO,
|
|
"vtoc: format 3 DSCB not valid \n");
|
|
for (j = 0; j < 4; ++j) {
|
|
rc = copy_extent_to_datasetpart(&f3->DS3EXTNT[j], dsp);
|
|
if (rc)
|
|
return raw_vtoc_add_extent_error_message(rv);
|
|
}
|
|
for (j = 0; j < 9; ++j) {
|
|
rc = copy_extent_to_datasetpart(&f3->DS3ADEXT[j], dsp);
|
|
if (rc)
|
|
return raw_vtoc_add_extent_error_message(rv);
|
|
}
|
|
rc = lzds_raw_vtoc_get_dscb_from_cchhb(rv, &f3->DS3PTRDS,
|
|
(struct dscb **)&f3);
|
|
if (rc)
|
|
return errorlog_add_message(
|
|
&rv->log, rv->log, EPROTO,
|
|
"vtoc: format 3 DSCB reference not valid\n");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Subroutine of lzds_zdsroot_extract_datasets_from_dasd
|
|
*
|
|
* This functions takes the data of a format 1/8 label, fills in
|
|
* a given struct dataset and creates exactly one dataset part.
|
|
* In case of a multi volume data set this part may not be the the
|
|
* first in the ds->dsp array, but is placed according to its
|
|
* volume sequence number!
|
|
* @param[in] dasd The dasd the data set belongs to.
|
|
* @param[in] f1 The f1/f8 DSCB that the dataset(part) is based on.
|
|
* @param[in] ds A dataset structure that will be filled with data,
|
|
* in particular a data set part.
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - ENOMEM Could not allocate structure due to lack of memory.
|
|
* - EPROTO Invalid data in the DSCB: An extent is not valid.
|
|
*/
|
|
static int create_dataset_from_dscb(struct dasd *dasd, format1_label_t *f1,
|
|
struct dataset *ds)
|
|
{
|
|
struct datasetpart *dsp;
|
|
char *end;
|
|
int rc;
|
|
int dspindex;
|
|
|
|
errorlog_clear(dasd->log);
|
|
memset(ds, 0, sizeof(*ds));
|
|
|
|
dsp = malloc(sizeof(*dsp));
|
|
if (!dsp)
|
|
return ENOMEM;
|
|
|
|
/* convert EBCDIC fixed length name into ascii 0-terminated string */
|
|
strncpy(ds->name, f1->DS1DSNAM, MAXDSNAMELENGTH - 1);
|
|
vtoc_ebcdic_dec(ds->name, ds->name, MAXDSNAMELENGTH - 1);
|
|
end = strchr(ds->name, ' ');
|
|
if (end)
|
|
*end = 0;
|
|
|
|
rc = raw_vtoc_get_datasetpart_from_dscb(dasd->rawvtoc, f1, dsp);
|
|
if (rc) {
|
|
free(dsp);
|
|
return errorlog_add_message(
|
|
&dasd->log, dasd->rawvtoc->log, rc,
|
|
"create data sets: get data set part failed for %s\n",
|
|
ds->name);
|
|
}
|
|
dsp->dasdi = dasd;
|
|
dspindex = f1->DS1VOLSQ - 1;
|
|
if (dspindex < 0 || dspindex >= MAXVOLUMESPERDS) {
|
|
free(dsp);
|
|
return errorlog_add_message(
|
|
&dasd->log, NULL, EPROTO,
|
|
"create data sets: data set sequence number "
|
|
"out of bounds failed for %s\n",
|
|
ds->name);
|
|
}
|
|
ds->dsp[dspindex] = dsp;
|
|
ds->dspcount = 1;
|
|
/* Note: we cannot tell the difference between the first volume of
|
|
* a multi volume data set and a single volume data set,
|
|
* so the following is just a first assumption
|
|
*/
|
|
if (dspindex == 0)
|
|
ds->iscomplete = 1;
|
|
else
|
|
ds->iscomplete = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief Subroutine of extract_members_from_track
|
|
*
|
|
* Take the information from a pds_member_entry, create a new pdsmember
|
|
* and add it to the datasets memberlist
|
|
* @param[in] ds The dataset that the new struct pdsmember will be added to.
|
|
* @param[in] memberentry The PDS directory entry that describes the member.
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - ENOMEM Could not allocate structure due to lack of memory.
|
|
*/
|
|
static int dataset_add_member(struct dataset *ds,
|
|
struct pds_member_entry *memberentry)
|
|
{
|
|
char name[9];
|
|
char *end;
|
|
struct pdsmember *member;
|
|
|
|
/* convert name to ascii and truncate trailing spaces */
|
|
strncpy(name, memberentry->name, 8);
|
|
name[8] = 0;
|
|
vtoc_ebcdic_dec(name, name, 8);
|
|
end = strchr(name, ' ');
|
|
if (end)
|
|
*end = 0;
|
|
|
|
member = malloc(sizeof(*member));
|
|
if (!member)
|
|
return ENOMEM;
|
|
memset(member, 0, sizeof(*member));
|
|
strcpy(member->name, name);
|
|
member->track = memberentry->track;
|
|
member->record = memberentry->record;
|
|
member->is_alias = memberentry->is_alias;
|
|
util_list_add_tail(ds->memberlist, member);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Helper function that removes and frees all elements in the
|
|
* member list in a struct dataset.
|
|
*
|
|
* @param[in] ds The dataset whose memberlist is to be freed.
|
|
*/
|
|
static void dataset_free_memberlist(struct dataset *ds)
|
|
{
|
|
struct pdsmember *member, *next;
|
|
|
|
if (!ds->memberlist)
|
|
return;
|
|
util_list_iterate_safe(ds->memberlist, member, next) {
|
|
util_list_remove(ds->memberlist, member);
|
|
errorlog_free(member->log);
|
|
free(member);
|
|
}
|
|
util_list_free(ds->memberlist);
|
|
ds->memberlist = NULL;
|
|
}
|
|
|
|
/**
|
|
* @brief Helper function that just checks if the type of an extend
|
|
* indicates that it contains user data or not.
|
|
*
|
|
* @param[in] ext The extent that gets evaluated.
|
|
* @return 1 if the extent contains user data, 0 otherwise.
|
|
*/
|
|
static int extent_contains_userdata(extent_t *ext)
|
|
{
|
|
return ((ext->typeind == 0x01) || (ext->typeind == 0x81));
|
|
}
|
|
|
|
/**
|
|
* @brief Subroutine of dataset_member_analysis.
|
|
*
|
|
* This function parses one track of a PDS directory and adds all found
|
|
* members to the dataset. A PDS directory may span more than one track.
|
|
* The variable dirend is used to indicate the end of the directory.
|
|
*
|
|
* @note In case of an error there is no cleanup done for the data set.
|
|
*
|
|
* @param[in] trackdata The raw track that contains the PDS directory.
|
|
* @param[in] ds The dataset the found members will be added to.
|
|
* @param[out] dirend If the end of the directory is found, dirend is
|
|
* set to 1, else it is 0.
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - ENOMEM Could not allocate structure due to lack of memory.
|
|
* - EPROTO The track layout is not valid.
|
|
*/
|
|
static int extract_members_from_track(char *trackdata, struct dataset *ds,
|
|
int *dirend)
|
|
{
|
|
char *record, *data;
|
|
int r;
|
|
struct eckd_count *ecount;
|
|
int used_bytes, residual, user_data_size;
|
|
struct pds_member_entry *member;
|
|
int rc;
|
|
|
|
*dirend = 0;
|
|
record = NULL;
|
|
r = 0;
|
|
while (!buffer_get_next_record(trackdata, RAWTRACKSIZE, &record)) {
|
|
/* jump over record zero */
|
|
if (r == 0) {
|
|
++r;
|
|
continue;
|
|
}
|
|
data = record;
|
|
ecount = (struct eckd_count *)data;
|
|
/* sanity check: do key and data length match the format of
|
|
* a directory record? */
|
|
if ((ecount->kl != PDS_DIR_KL) || (ecount->dl != PDS_DIR_DL))
|
|
return errorlog_add_message(
|
|
&ds->log, NULL, EPROTO,
|
|
"member analysis: directory record layout"
|
|
" not valid, offset %lu\n",
|
|
(unsigned long)ecount - (unsigned long)trackdata);
|
|
data += sizeof(*ecount);
|
|
/* compare key to directory end token */
|
|
if ((*(unsigned long long *)data) == ENDTOKEN)
|
|
*dirend = 1;
|
|
data += ecount->kl;
|
|
/* First element in the data area are two bytes that denote how
|
|
* may bytes of the data area are used for directory entries.
|
|
* This number includes the first two bytes.
|
|
*/
|
|
used_bytes = (*(unsigned short *)data);
|
|
residual = used_bytes - sizeof(unsigned short);
|
|
data += sizeof(unsigned short);
|
|
/* Loop over directory entries in record */
|
|
while (residual > 0) {
|
|
/* A pseudo directory entry marks directory end */
|
|
if ((*(unsigned long long *)data) == ENDTOKEN) {
|
|
*dirend = 1; /* should already be set */
|
|
break;
|
|
}
|
|
member = (struct pds_member_entry *)data;
|
|
rc = dataset_add_member(ds, member);
|
|
if (rc)
|
|
return rc;
|
|
/* A directory entry may contain a user data part
|
|
* that follows the pds_member_entry structure.
|
|
*/
|
|
user_data_size = 2 * member->user_data_count;
|
|
data += sizeof(*member) + user_data_size;
|
|
residual -= (sizeof(*member) + user_data_size);
|
|
}
|
|
++r;
|
|
if (*dirend)
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Subroutine of lzds_zdsroot_extract_datasets_from_dasd.
|
|
*
|
|
* This function checks if a data set is a PDS, analyzes the PDS directory
|
|
* and creates a corresponding list of struct pdsmember in the dataset.
|
|
*
|
|
* @param[in] ds The dataset that is to be analyzed and the found
|
|
* members will be added to.
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - ENOMEM Could not allocate structure due to lack of memory.
|
|
* - EPROTO The track layout is not valid.
|
|
* - EINVAL An internal error happened.
|
|
* - EIO An error happened while reading data from disk.
|
|
*/
|
|
static int dataset_member_analysis(struct dataset *ds)
|
|
{
|
|
char *trackdata;
|
|
unsigned int extstarttrk, extendtrk, currenttrack;
|
|
int j;
|
|
int dirend;
|
|
struct datasetpart *dsp;
|
|
struct dasd *dasd;
|
|
struct dasdhandle *dasdh;
|
|
int rc, rc2;
|
|
int issupported;
|
|
|
|
errorlog_clear(ds->log);
|
|
rc2 = 0;
|
|
/* a partitioned data set has only one volume, so we only need dsp[0] */
|
|
dsp = ds->dsp[0];
|
|
/* if it is not a partitioned data set, do nothing */
|
|
if (!dsp || !(dsp->f1->DS1DSRG1 & 0x02))
|
|
return 0;
|
|
/* do not do member analysis if we do not support the format (PDSE) */
|
|
lzds_dataset_get_is_supported(ds, &issupported);
|
|
if (!issupported)
|
|
return 0;
|
|
|
|
dasd = dsp->dasdi;
|
|
|
|
dataset_free_memberlist(ds);
|
|
ds->memberlist = util_list_new(struct pdsmember, list);
|
|
|
|
/* track buffer must be page aligned for O_DIRECT */
|
|
trackdata = memalign(4096, RAWTRACKSIZE);
|
|
if (!trackdata)
|
|
return ENOMEM;
|
|
|
|
rc = lzds_dasd_alloc_dasdhandle(dasd, &dasdh);
|
|
if (rc)
|
|
goto out1;
|
|
rc = lzds_dasdhandle_open(dasdh);
|
|
if (rc) {
|
|
errorlog_add_message(
|
|
&ds->log, dasdh->log, rc,
|
|
"member analysis: could not open dasdhandle\n");
|
|
goto out2;
|
|
}
|
|
dirend = 0;
|
|
/* loop over all extents in dataset*/
|
|
for (j = 0; j < MAXEXTENTS; ++j) {
|
|
if (!extent_contains_userdata(&dsp->ext[j]))
|
|
continue;
|
|
lzds_dasd_cchh2trk(dasd, &dsp->ext[j].llimit, &extstarttrk);
|
|
lzds_dasd_cchh2trk(dasd, &dsp->ext[j].ulimit, &extendtrk);
|
|
currenttrack = extstarttrk;
|
|
/* loop over tracks in extent */
|
|
while (currenttrack <= extendtrk) {
|
|
rc = lzds_dasdhandle_read_tracks_to_buffer(
|
|
dasdh, currenttrack, currenttrack, trackdata);
|
|
if (rc) {
|
|
errorlog_add_message(
|
|
&ds->log, dasdh->log, rc,
|
|
"member analysis: read error\n");
|
|
goto out4;
|
|
}
|
|
rc = extract_members_from_track(trackdata, ds, &dirend);
|
|
if (rc) {
|
|
errorlog_add_message(
|
|
&ds->log, ds->log, rc,
|
|
"member analysis: error "
|
|
"extracting members from track %u\n",
|
|
currenttrack);
|
|
goto out4;
|
|
}
|
|
currenttrack++;
|
|
if (dirend)
|
|
break;
|
|
}
|
|
if (dirend)
|
|
break;
|
|
}
|
|
|
|
rc = 0;
|
|
goto out3;
|
|
|
|
out4:
|
|
dataset_free_memberlist(ds);
|
|
out3:
|
|
rc2 = lzds_dasdhandle_close(dasdh);
|
|
/* report close error only if we had no read error */
|
|
if (rc2 && !rc) {
|
|
errorlog_add_message(
|
|
&ds->log, dasdh->log, rc,
|
|
"member analysis: could not close dasdhandle\n");
|
|
rc = rc2;
|
|
}
|
|
out2:
|
|
lzds_dasdhandle_free(dasdh);
|
|
out1:
|
|
free(trackdata);
|
|
rc = rc ? rc : rc2;
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* @brief Subroutine of zdsroot_merge_dataset
|
|
*
|
|
* Merge two dataset structures that are two halves of a multi volume data set.
|
|
* All datasetparts of the second dataset are copied to the first dataset.
|
|
*
|
|
* @param[in] baseds The dataset that the data will be merged into.
|
|
* @param[in] newds The dataset that will be merged with baseds.
|
|
* This strucure can be freed after the merge, but do not
|
|
* free the data set parts it contained, as those belong
|
|
* to baseds now.
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - EPROTO The data is not mergable because of conflicting entries.
|
|
*/
|
|
static int dataset_merge_dataset(struct dataset *baseds, struct dataset *newds)
|
|
{
|
|
int k, l, dspcount;
|
|
|
|
for (k = 0; k < MAXVOLUMESPERDS; ++k) {
|
|
/* if both datasets have a part in position k,
|
|
* then something is wrong */
|
|
if (baseds->dsp[k] && newds->dsp[k])
|
|
return errorlog_add_message(
|
|
&baseds->log, NULL, EPROTO, "merge dataset: "
|
|
"part %d was previously found on device %s\n",
|
|
k, baseds->dsp[k]->dasdi->device);
|
|
/* if the new data set has a part that is not present in the
|
|
* base data set, than copy the dsp pointer to the base
|
|
*/
|
|
if (!baseds->dsp[k] && newds->dsp[k]) {
|
|
/* Each format 1/8 DSCB of a part in a multi volume data
|
|
* set has a reference to the volume serial of the first
|
|
* volume. Need to verify that the new data set parts
|
|
* refer to the correct volume serial in f1->DS1DSSN.
|
|
* Since dsp[0] may not be set yet, we loop over the
|
|
* base dsp array until we find an entry.
|
|
*/
|
|
for (l = 0; l < MAXVOLUMESPERDS; ++l) {
|
|
if (!baseds->dsp[l])
|
|
continue;
|
|
if (memcmp(baseds->dsp[l]->f1->DS1DSSN,
|
|
newds->dsp[k]->f1->DS1DSSN,
|
|
MAXVOLSER))
|
|
return errorlog_add_message(
|
|
&baseds->log, NULL, EPROTO,
|
|
"merge dataset: part %d has incompatible base volume serial\n",
|
|
k);
|
|
else
|
|
break;
|
|
}
|
|
|
|
baseds->dsp[k] = newds->dsp[k];
|
|
baseds->dspcount++;
|
|
|
|
}
|
|
}
|
|
/* check for completeness:
|
|
* If element (dspcount - 1) exists and is the last part in a multi
|
|
* volume data set, then all other parts must have been found as well.
|
|
*/
|
|
dspcount = baseds->dspcount;
|
|
if (baseds->dsp[dspcount - 1] &&
|
|
(baseds->dsp[dspcount - 1]->f1->DS1DSIND & 0x80))
|
|
baseds->iscomplete = 1;
|
|
else
|
|
baseds->iscomplete = 0;
|
|
/* The last statement is only true for a correct multi volume data set.
|
|
* Since the data on the DASDs may be incorrect and we will rely later
|
|
* on the fact that the first dspcount elements of the dsp array are
|
|
* valid, we must make sure that they are all filled.
|
|
*/
|
|
if (baseds->iscomplete)
|
|
for (l = 0; l < baseds->dspcount; ++l)
|
|
if (!baseds->dsp[l]) {
|
|
baseds->iscomplete = 0;
|
|
return errorlog_add_message(
|
|
&baseds->log, NULL, EPROTO,
|
|
"merge dataset: inconsistent data set"
|
|
" part list at index %d\n", l);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Subroutine of lzds_zdsroot_extract_datasets_from_dasd
|
|
*
|
|
* Takes the data from newds and merges it with a matching dataset in
|
|
* root. If no matching dataset exists yet, a new struct dataset is
|
|
* created, so that the caller of this function can release newds in
|
|
* any case.
|
|
* It is important to note that while newds is just a temporary
|
|
* structure that can be released after the function returns, the
|
|
* elements and structures that are contained by newds (e.g the
|
|
* datasetparts) are transferred to the struct dataset in root and must
|
|
* not be released.
|
|
*
|
|
* @param[in] root The zdsroot that the dataset will be merged into.
|
|
* @param[in] newds The dataset that will be merged.
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - ENOMEM Could not allocate internal structure due to lack of memory.
|
|
* - EPROTO The data is not mergable because of conflicting entries.
|
|
*/
|
|
static int zdsroot_merge_dataset(struct zdsroot *root, struct dataset *newds)
|
|
{
|
|
struct dataset *rootds;
|
|
int rc;
|
|
|
|
/* first, try to find a matching data set in the old list */
|
|
rc = lzds_zdsroot_find_dataset(root, newds->name, &rootds);
|
|
if (!rc) { /* match found */
|
|
rc = dataset_merge_dataset(rootds, newds);
|
|
if (rc)
|
|
return errorlog_add_message(
|
|
&root->log, rootds->log, rc,
|
|
"merge dataset: "
|
|
"merge with existing data set failed\n");
|
|
} else if (rc == ENOENT) { /* no match found */
|
|
rootds = malloc(sizeof(*rootds));
|
|
if (!rootds)
|
|
return ENOMEM;
|
|
memcpy(rootds, newds, sizeof(*rootds));
|
|
util_list_add_tail(root->datasetlist, rootds);
|
|
} else
|
|
return rc;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* This function finds all data set descriptions in the VTOC of the
|
|
* dasd and creates respective struct dataset representations. These
|
|
* struct dataset are stored in the zdsroot and can later be traversed
|
|
* using a dsiterator. In case that it finds a dataset that is
|
|
* already present in the zdsroot, it verifies that both are parts of
|
|
* the same multivolume data set and then merges the new data with the
|
|
* existing struct dataset. If the conflicting data sets are indeed
|
|
* individual data sets and not parts of a single one, the function
|
|
* returns an error.
|
|
*
|
|
* @param[in] root The zdsroot that the dataset will be merged into.
|
|
* @param[in] dasd The datasets found in this dasd will be merged.
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - ENOMEM Could not allocate structure due to lack of memory.
|
|
* - EPROTO The data is not mergable because of conflicting entries,
|
|
* or invalid data in the VTOC of the dasd.
|
|
*/
|
|
int lzds_zdsroot_extract_datasets_from_dasd(struct zdsroot *root,
|
|
struct dasd *dasd)
|
|
{
|
|
format1_label_t *f1;
|
|
struct dscb *dscb;
|
|
struct dscbiterator *it;
|
|
int rc;
|
|
struct dataset tmpds;
|
|
int i;
|
|
|
|
errorlog_clear(root->log);
|
|
memset(&tmpds, 0, sizeof(tmpds));
|
|
rc = lzds_raw_vtoc_alloc_dscbiterator(dasd->rawvtoc, &it);
|
|
if (rc)
|
|
return ENOMEM;
|
|
while (!lzds_dscbiterator_get_next_dscb(it, &dscb)) {
|
|
if (dscb->fmtid == 0xf1 || dscb->fmtid == 0xf8) {
|
|
f1 = (format1_label_t *)dscb;
|
|
rc = create_dataset_from_dscb(dasd, f1, &tmpds);
|
|
if (rc) {
|
|
errorlog_add_message(
|
|
&root->log, dasd->log, rc,
|
|
"extract data sets: "
|
|
"creating dataset failed for %s\n",
|
|
dasd->device);
|
|
break;
|
|
}
|
|
rc = dataset_member_analysis(&tmpds);
|
|
if (rc) {
|
|
errorlog_add_message(
|
|
&root->log, tmpds.log, rc,
|
|
"extract data sets: "
|
|
"member analysis failed for %s\n",
|
|
tmpds.name);
|
|
break;
|
|
}
|
|
rc = zdsroot_merge_dataset(root, &tmpds);
|
|
if (rc) {
|
|
errorlog_add_message(
|
|
&root->log, root->log, rc,
|
|
"extract data sets: "
|
|
"merge dataset failed for %s\n",
|
|
tmpds.name);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (rc) {
|
|
dataset_free_memberlist(&tmpds);
|
|
for (i = 0; i < MAXVOLUMESPERDS; ++i)
|
|
free(tmpds.dsp[i]);
|
|
errorlog_free(tmpds.log);
|
|
}
|
|
lzds_dscbiterator_free(it);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* @brief Subroutine of lzds_dataset_get_size_in_tracks
|
|
*
|
|
* Computes the number of tracks in a given extent.
|
|
* Returns 0 for anything but user data.
|
|
* @param[in] ext The extent we want to know the size of.
|
|
* @param[in] dasd The dasd that the extent is located on.
|
|
* @return Number of tracks the extent contains
|
|
*/
|
|
static unsigned int get_extent_size_in_tracks(extent_t *ext, struct dasd *dasd)
|
|
{
|
|
unsigned int starttrck, endtrck;
|
|
|
|
if (!extent_contains_userdata(ext))
|
|
return 0;
|
|
|
|
lzds_dasd_cchh2trk(dasd, &ext->llimit, &starttrck);
|
|
lzds_dasd_cchh2trk(dasd, &ext->ulimit, &endtrck);
|
|
|
|
return endtrck - starttrck + 1;
|
|
}
|
|
|
|
/**
|
|
* @param[in] ds The dataset we we want to know the size of.
|
|
* @param[out] tracks Reference to a return buffer for the number of tracks.
|
|
*/
|
|
void lzds_dataset_get_size_in_tracks(struct dataset *ds,
|
|
unsigned long long *tracks)
|
|
{
|
|
unsigned long long sumtracks;
|
|
int i, j;
|
|
|
|
*tracks = 0;
|
|
sumtracks = 0;
|
|
for (i = 0; i < MAXVOLUMESPERDS; ++i)
|
|
if (ds->dsp[i])
|
|
for (j = 0; j < MAXEXTENTS; ++j)
|
|
sumtracks += get_extent_size_in_tracks(
|
|
&ds->dsp[i]->ext[j], ds->dsp[i]->dasdi);
|
|
*tracks = sumtracks;
|
|
}
|
|
|
|
/**
|
|
* @param[in] member The PDS member we want to know the name of.
|
|
* @param[out] name Reference to a pointer variable in which a pointer to
|
|
* the name string will be returned.
|
|
*/
|
|
void lzds_pdsmember_get_name(struct pdsmember *member, char **name)
|
|
{
|
|
*name = member->name;
|
|
}
|
|
|
|
/**
|
|
* @param[in] ds The dataset we want to know the name of.
|
|
* @param[out] name Reference to a pointer variable in which a pointer to
|
|
* the name string will be returned.
|
|
*/
|
|
void lzds_dataset_get_name(struct dataset *ds, char **name)
|
|
{
|
|
*name = ds->name;
|
|
}
|
|
|
|
/**
|
|
* @param[in] ds Is this dataset a PDS?
|
|
* @param[out] ispds Reference to a pointer variable in which
|
|
* 1 (true) or 0 (false) is returned.
|
|
*/
|
|
void lzds_dataset_get_is_PDS(struct dataset *ds, int *ispds)
|
|
{
|
|
|
|
if (ds->dsp[0]->f1->DS1DSRG1 & 0x02) /* is PDS */
|
|
*ispds = 1;
|
|
else
|
|
*ispds = 0;
|
|
}
|
|
|
|
/**
|
|
* The returned DSCB belongs always to the first volume of a data set.
|
|
*
|
|
* @param[in] ds The dataset we want to know the DSCB of.
|
|
* @param[out] f1 Reference to a pointer variable in which a pointer to
|
|
* the format 1 DSCB will be returned.
|
|
*/
|
|
void lzds_dataset_get_format1_dscb(struct dataset *ds, format1_label_t **f1)
|
|
{
|
|
*f1 = ds->dsp[0]->f1;
|
|
}
|
|
|
|
/**
|
|
* @param[in] ds Is this dataset complete?
|
|
* @param[out] iscomplete Reference to a pointer variable in which
|
|
* 1 (true) or 0 (false) is returned.
|
|
*/
|
|
void lzds_dataset_get_is_complete(struct dataset *ds, int *iscomplete)
|
|
{
|
|
*iscomplete = ds->iscomplete;
|
|
}
|
|
|
|
/**
|
|
* @param[in] ds Is this dataset supported?
|
|
* @param[out] issupported Reference to a pointer variable in which
|
|
* 1 (true) or 0 (false) is returned.
|
|
*/
|
|
void lzds_dataset_get_is_supported(struct dataset *ds, int *issupported)
|
|
{
|
|
int complete, org_supported, format_supported, not_ext_fmt;
|
|
char DS1RECFM;
|
|
|
|
if (!ds->dsp[0]) {
|
|
*issupported = 0;
|
|
return;
|
|
}
|
|
/* do we have all parts of the data set? */
|
|
lzds_dataset_get_is_complete(ds, &complete);
|
|
|
|
/* is this a supported organisation (PS or PDS)?*/
|
|
org_supported = 0;
|
|
if ((ds->dsp[0]->f1->DS1DSRG1 & 0x40) || /* PS */
|
|
(ds->dsp[0]->f1->DS1DSRG1 & 0x02)) /* PDS */
|
|
org_supported = 1;
|
|
/* extended format datasets are not supported */
|
|
not_ext_fmt = 0;
|
|
if (!(ds->dsp[0]->f1->DS1SMSFG & 0x0C))
|
|
not_ext_fmt = 1;
|
|
/* fixed, variable or undefined length records are supported */
|
|
DS1RECFM = ds->dsp[0]->f1->DS1RECFM;
|
|
format_supported = 0;
|
|
if (DS1RECFM & 0xC0)
|
|
format_supported = 1;
|
|
/* track overflow (legacy) is not supported */
|
|
if ((DS1RECFM & 0x20))
|
|
format_supported = 0;
|
|
/* all other RECFM flags are modifiers of the above and are supported */
|
|
*issupported = complete && org_supported && format_supported
|
|
&& not_ext_fmt;
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* @param[in] ds The dataset that is searched for the member.
|
|
* @param[in] membername The name of the member (ASCII string).
|
|
* @param[out] member Reference to a pointer variable in which the found
|
|
* pdsmember is returned. If no member is found, this
|
|
* is set to NULL.
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - ENOMEM Could not allocate internal structure due to lack of memory.
|
|
* - ENOENT No matching member was found.
|
|
*/
|
|
int lzds_dataset_get_member_by_name(struct dataset *ds, char *membername,
|
|
struct pdsmember **member)
|
|
{
|
|
struct memberiterator *it;
|
|
struct pdsmember *tmpmember;
|
|
int rc;
|
|
|
|
errorlog_clear(ds->log);
|
|
*member = NULL;
|
|
rc = lzds_dataset_alloc_memberiterator(ds, &it);
|
|
if (rc)
|
|
return ENOMEM;
|
|
while (!lzds_memberiterator_get_next_member(it, &tmpmember)) {
|
|
if (!strcmp(tmpmember->name, membername)) {
|
|
*member = tmpmember;
|
|
break;
|
|
}
|
|
}
|
|
lzds_memberiterator_free(it);
|
|
if (!*member)
|
|
return ENOENT;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @param[in] dsh Pointer to structure that is to be freed.
|
|
*/
|
|
void lzds_dshandle_free(struct dshandle *dsh)
|
|
{
|
|
int i;
|
|
|
|
if (!dsh)
|
|
return;
|
|
for (i = 0; i < MAXVOLUMESPERDS; ++i)
|
|
if (dsh->dasdhandle[i])
|
|
lzds_dasdhandle_free(dsh->dasdhandle[i]);
|
|
free(dsh->databuffer);
|
|
free(dsh->rawbuffer);
|
|
if (dsh->seekbuf)
|
|
free(dsh->seekbuf);
|
|
errorlog_free(dsh->log);
|
|
free(dsh);
|
|
}
|
|
|
|
/**
|
|
* @param[in] ds The dataset we want to read from.
|
|
* @param[in] tracks_per_frame The number of tracks that the internal buffers
|
|
* can hold. If 0, then the default value 128 is used.
|
|
* @param[out] dsh Reference to a pointer variable which will be used
|
|
* to store the new dshandle.
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - ENOMEM Could not allocate structure due to lack of memory.
|
|
*/
|
|
int lzds_dataset_alloc_dshandle(struct dataset *ds,
|
|
unsigned int tracks_per_frame,
|
|
struct dshandle **dsh)
|
|
{
|
|
struct dshandle *dshtmp;
|
|
int i, rc;
|
|
|
|
dshtmp = malloc(sizeof(*dshtmp));
|
|
if (!dshtmp)
|
|
return ENOMEM;
|
|
memset(dshtmp, 0, sizeof(*dshtmp));
|
|
for (i = 0; i < ds->dspcount; ++i) {
|
|
rc = lzds_dasd_alloc_dasdhandle(ds->dsp[i]->dasdi,
|
|
&dshtmp->dasdhandle[i]);
|
|
if (rc) {
|
|
lzds_dshandle_free(dshtmp);
|
|
return rc;
|
|
}
|
|
}
|
|
if (tracks_per_frame)
|
|
dshtmp->tracks_per_frame = tracks_per_frame;
|
|
else
|
|
dshtmp->tracks_per_frame = TRACK_BUFFER_DEFAULT;
|
|
dshtmp->rawbufmax = dshtmp->tracks_per_frame * RAWTRACKSIZE;
|
|
/* track buffer must be page aligned for O_DIRECT */
|
|
dshtmp->rawbuffer = memalign(4096, dshtmp->rawbufmax);
|
|
if (!dshtmp->rawbuffer) {
|
|
lzds_dshandle_free(dshtmp);
|
|
return ENOMEM;
|
|
}
|
|
|
|
dshtmp->databufmax = dshtmp->tracks_per_frame * MAXRECSIZE;
|
|
dshtmp->databuffer = malloc(dshtmp->databufmax);
|
|
if (!dshtmp->databuffer) {
|
|
lzds_dshandle_free(dshtmp);
|
|
return ENOMEM;
|
|
}
|
|
|
|
dshtmp->ds = ds;
|
|
*dsh = dshtmp;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* The number of user data bytes per track is not predictable as record
|
|
* sizes and number of records per track may vary. Seeking forward will
|
|
* always require us to read all the data between the current position
|
|
* and the seek target. To improve performance of seeking backwards
|
|
* we can buffer previous positions in the data set.
|
|
* For a given seek buffer size and the known number of tracks of the
|
|
* data set, we can compute how many track frames we need to skip if
|
|
* we and to store track frames in regular intervals.
|
|
*
|
|
* @param[in] dsh The dshandle we want to modify.
|
|
* @param[in] seek_buffer_size The maximum number of bytes to be allocated
|
|
* for the seek buffer.
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - ENOMEM Could not allocate structure due to lack of memory.
|
|
*/
|
|
int lzds_dshandle_set_seekbuffer(struct dshandle *dsh,
|
|
unsigned long long seek_buffer_size)
|
|
{
|
|
unsigned long long totaltracks;
|
|
size_t entries, frames;
|
|
unsigned int extents, skip;
|
|
struct dataset *ds;
|
|
int i, j;
|
|
unsigned long long buf_count;
|
|
|
|
errorlog_clear(dsh->log);
|
|
if (dsh->seekbuf)
|
|
free(dsh->seekbuf);
|
|
dsh->seekbuf = NULL;
|
|
dsh->seek_count = 0;
|
|
dsh->seek_current = 0;
|
|
dsh->skip = 0;
|
|
|
|
if (!seek_buffer_size)
|
|
return 0;
|
|
|
|
ds = dsh->ds;
|
|
lzds_dataset_get_size_in_tracks(ds, &totaltracks);
|
|
|
|
/* compute the total number of extents */
|
|
extents = 0;
|
|
for (i = 0; i < ds->dspcount; ++i)
|
|
for (j = 0; j < MAXEXTENTS; ++j)
|
|
if (ds->dsp[i]->ext[j].typeind != 0x00)
|
|
++extents;
|
|
|
|
entries = seek_buffer_size / sizeof(struct seekelement);
|
|
|
|
/* track frames at the end of an extent may be shorter,
|
|
* increasing the maximum number of frames we need to read */
|
|
frames = (totaltracks / dsh->tracks_per_frame) + 1 + extents;
|
|
skip = (frames / entries) + 1;
|
|
buf_count = (frames / skip) + 1;
|
|
|
|
dsh->seekbuf = malloc(buf_count * sizeof(struct seekelement));
|
|
if (!dsh->seekbuf)
|
|
return ENOMEM;
|
|
memset(dsh->seekbuf, 0, buf_count * sizeof(struct seekelement));
|
|
dsh->seek_count = buf_count;
|
|
dsh->skip = skip;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* If dsh points to a partitioned data set, the library needs to know
|
|
* which member of that PDS should be read. So this function must be
|
|
* called before lzds_dshandle_open. This setting cannot be changed
|
|
* for open dsh, so this function must not be used after
|
|
* lzds_dshandle_open, unless the dsh has been closed with
|
|
* lzds_dsh_close again.
|
|
*
|
|
* @pre The dsh must not be open when this function is called.
|
|
*
|
|
* @param[in] dsh The dshandle we want to modify.
|
|
* @param[in] membername The name of the member that shall be read via
|
|
* this handle.
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - ENOMEM Could not allocate internal structure due to lack of memory.
|
|
* - ENOENT No matching member was found.
|
|
* - EBUSY The handle is already open.
|
|
* - EINVAL The data set is not a PDS.
|
|
*/
|
|
int lzds_dshandle_set_member(struct dshandle *dsh, char *membername)
|
|
{
|
|
int ispds, rc;
|
|
struct pdsmember *member;
|
|
|
|
errorlog_clear(dsh->log);
|
|
if (dsh->is_open)
|
|
return errorlog_add_message(
|
|
&dsh->log, NULL, EBUSY,
|
|
"dshandle: cannot set member while handle is open\n");
|
|
dsh->member = NULL;
|
|
lzds_dataset_get_is_PDS(dsh->ds, &ispds);
|
|
if (!ispds)
|
|
return errorlog_add_message(
|
|
&dsh->log, NULL, EINVAL,
|
|
"dshandle: cannot set member, not a PDS\n");
|
|
|
|
rc = lzds_dataset_get_member_by_name(dsh->ds, membername, &member);
|
|
if (rc)
|
|
return errorlog_add_message(
|
|
&dsh->log, NULL, rc,
|
|
"dshandle: could not find member %s in dataset %s\n",
|
|
membername, dsh->ds->name);
|
|
|
|
dsh->member = member;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @param[in] dsh The dshandle that we want to know the member of.
|
|
* @param[out] member Reference to a pointer variable in which the found
|
|
* pdsmember is returned. If no member has been set
|
|
* before, this is set to NULL.
|
|
*/
|
|
void lzds_dshandle_get_member(struct dshandle *dsh, struct pdsmember **member)
|
|
{
|
|
*member = dsh->member;
|
|
}
|
|
|
|
/**
|
|
* @pre The dsh must not be open when this function is called.
|
|
*
|
|
* @param[in] dsh The dshandle we want to modify.
|
|
* @param[in] keepRDW Set this to 1 to enable the keep RDW feature or
|
|
* 0 to disable it.
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - EBUSY The handle is already open.
|
|
*/
|
|
int lzds_dshandle_set_keepRDW(struct dshandle *dsh, int keepRDW)
|
|
{
|
|
errorlog_clear(dsh->log);
|
|
if (dsh->is_open)
|
|
return errorlog_add_message(
|
|
&dsh->log, NULL, EBUSY,
|
|
"dshandle: cannot set RDW while handle is open\n");
|
|
dsh->keepRDW = keepRDW;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @pre The dsh must not be open when this function is called.
|
|
*
|
|
* @param[in] dsh The dshandle we want to modify.
|
|
* @param[in] iconv_t The iconv handle for codepage conversion.
|
|
*
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - EBUSY The handle is already open.
|
|
*/
|
|
int lzds_dshandle_set_iconv(struct dshandle *dsh, iconv_t *iconv)
|
|
{
|
|
errorlog_clear(dsh->log);
|
|
if (dsh->is_open)
|
|
return errorlog_add_message(
|
|
&dsh->log, NULL, EBUSY,
|
|
"dshandle: cannot set iconv while handle is open\n");
|
|
|
|
/*
|
|
* if conversion is enabled the returned data might in worst case
|
|
* be 4 times the size of the input buffer. So realloc the buffer.
|
|
* If for whatever very unlikely reason the converted size is still
|
|
* larger the conversion will fail.
|
|
*/
|
|
if (iconv) {
|
|
dsh->databufmax *= 4;
|
|
dsh->databuffer = util_realloc(dsh->databuffer,
|
|
dsh->databufmax);
|
|
}
|
|
dsh->iconv = iconv;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @param[in] dsh The dshandle that we want to know the member of.
|
|
* @param[out] keepRDW Reference to a variable in which the previously
|
|
* set keepRDW value is returned.
|
|
*/
|
|
void lzds_dshandle_get_keepRDW(struct dshandle *dsh, int *keepRDW)
|
|
{
|
|
*keepRDW = dsh->keepRDW;
|
|
}
|
|
|
|
/**
|
|
* @brief Helper function that initializes the given handle so that it
|
|
* points to the beginning of the dataset or member.
|
|
*
|
|
* @param[in] dsh The dshandle that keeps track of the I/O operations.
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - EPROTO The dataset data is inconsistent.
|
|
*/
|
|
static int initialize_buffer_positions_for_first_read(struct dshandle *dsh)
|
|
{
|
|
|
|
unsigned long long tracksum, extentsize;
|
|
unsigned int starttrck, endtrck;
|
|
int j;
|
|
|
|
/* make sure that read knows that we have no ready data in our buffer */
|
|
dsh->bufpos = 0;
|
|
dsh->databufsize = 0;
|
|
dsh->databufoffset = 0;
|
|
dsh->eof_reached = 0;
|
|
|
|
/* we need to set the bufendtrk and sequence number so,
|
|
* that the current track buffer seems to end with the
|
|
* track that comes before the first track of the
|
|
* data set or member
|
|
*/
|
|
|
|
/* When we read the first track frame this will be incremented to 0 */
|
|
dsh->frameno = -1;
|
|
|
|
/* We allways start with data set part 0. Partitioned
|
|
* data sets have only one part, so this correct for
|
|
* both partitioned and non partitioned data sets.
|
|
*/
|
|
dsh->dsp_no = 0;
|
|
|
|
/* for a non partitioned data set we just need to set the
|
|
* extentsequence number to -1 so read will start with the
|
|
* first track of extent number 0
|
|
*/
|
|
if (!dsh->member) {
|
|
dsh->ext_seq_no = -1;
|
|
dsh->bufstarttrk = 0;
|
|
dsh->bufendtrk = 0;
|
|
dsh->extstarttrk = 0;
|
|
dsh->extendtrk = 0;
|
|
return 0;
|
|
}
|
|
|
|
/* sanity check: a partitioned data set cannot be a multi volume data
|
|
* set.
|
|
*/
|
|
if (dsh->ds->dspcount != 1)
|
|
return errorlog_add_message(
|
|
&dsh->log, NULL, EPROTO,
|
|
"initialize read buffer: dataset %s is inconsistent,"
|
|
" PDS must not span more than one volume\n",
|
|
dsh->ds->name);
|
|
/* For a partitioned data set we need to find the correct start
|
|
* track and point the current buffer just before it.
|
|
* As we always need to read full tracks, any additional
|
|
* record offset will be set explicitly and handled during
|
|
* track interpretation.
|
|
*/
|
|
dsh->startrecord = dsh->member->record;
|
|
|
|
/* member->track is an offset based on the start of the data set
|
|
* I will have to add up extents until I have got the right number
|
|
* of tracks
|
|
*/
|
|
tracksum = 0;
|
|
/* Note: No need to loop over all data set parts, a PDS has only one */
|
|
for (j = 0; j < MAXEXTENTS; ++j) {
|
|
if (!extent_contains_userdata(&dsh->ds->dsp[0]->ext[j]))
|
|
continue;
|
|
lzds_dasd_cchh2trk(dsh->ds->dsp[0]->dasdi,
|
|
&dsh->ds->dsp[0]->ext[j].llimit, &starttrck);
|
|
lzds_dasd_cchh2trk(dsh->ds->dsp[0]->dasdi,
|
|
&dsh->ds->dsp[0]->ext[j].ulimit, &endtrck);
|
|
extentsize = endtrck - starttrck + 1;
|
|
|
|
/* If offset in the extent (member->track - tracksum) == 0,
|
|
* then we must set the dsh buffer to the end of the previous
|
|
* extent, so that rdf_read will start with the first track
|
|
* of the next extent.
|
|
* However, since rdf_read checks for bufendtrk < extendtrk
|
|
* we can set both to 0 and do not need a special case for the
|
|
* first extend.
|
|
*/
|
|
if (dsh->member->track == tracksum) {
|
|
dsh->ext_seq_no = j - 1;
|
|
dsh->bufendtrk = 0;
|
|
dsh->extendtrk = 0;
|
|
break;
|
|
}
|
|
/* If the offset is within the current extent an not the
|
|
* special case above, then we can need to adjust the dsh so,
|
|
* as if we have just already read data up to the track before
|
|
* our target track
|
|
*/
|
|
if (dsh->member->track < tracksum + extentsize) {
|
|
dsh->ext_seq_no = j;
|
|
dsh->extstarttrk = starttrck;
|
|
dsh->extendtrk = endtrck;
|
|
dsh->bufstarttrk = dsh->extstarttrk;
|
|
dsh->bufendtrk = dsh->bufstarttrk +
|
|
(dsh->member->track - tracksum) - 1;
|
|
break;
|
|
}
|
|
tracksum += extentsize;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* @param[in] dsh The dshandle that keeps track of the I/O operations.
|
|
*/
|
|
void lzds_dshandle_close(struct dshandle *dsh)
|
|
{
|
|
int i;
|
|
for (i = 0; i < MAXVOLUMESPERDS; ++i)
|
|
if (dsh->dasdhandle[i])
|
|
lzds_dasdhandle_close(dsh->dasdhandle[i]);
|
|
free(dsh->convbuffer);
|
|
free(dsh->iconv);
|
|
dsh->is_open = 0;
|
|
}
|
|
|
|
#ifdef HAVE_CURL
|
|
|
|
struct response_data {
|
|
char *session_ref;
|
|
unsigned long statuscode;
|
|
};
|
|
|
|
static size_t
|
|
parse_response_callback(void *data, size_t size, size_t member, void *target)
|
|
{
|
|
struct response_data *response = target;
|
|
|
|
if (strstr(data, "HTTP/1.1 500 Internal Server Error")) {
|
|
response->statuscode = 500;
|
|
} else if (strstr(data, "HTTP/1.1 200 OK")) {
|
|
response->statuscode = 200;
|
|
} else
|
|
sscanf(data, "X-IBM-Session-Ref: %m[^\n]\n",
|
|
&response->session_ref);
|
|
|
|
return size*member;
|
|
}
|
|
|
|
static size_t write_discard_callback(void *UNUSED(data), size_t size, size_t member,
|
|
void *UNUSED(target))
|
|
{
|
|
/* do nothing just pretend all data has been processed */
|
|
return size*member;
|
|
}
|
|
|
|
CURL *lzds_prepare_curl(char *url)
|
|
{
|
|
CURL *curl;
|
|
|
|
curl = curl_easy_init();
|
|
if (!curl)
|
|
return NULL;
|
|
|
|
curl_easy_setopt(curl, CURLOPT_URL, url);
|
|
curl_easy_setopt(curl, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
|
|
curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
|
|
curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 0L);
|
|
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "GET");
|
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_discard_callback);
|
|
|
|
return curl;
|
|
}
|
|
|
|
/**
|
|
* Ping the z/OSMS REST server.
|
|
* Used to check if the server is responding and accessible and to prevent
|
|
* the ENQ from timing out. If not used it would be automatically released
|
|
* after 10 minutes.
|
|
*
|
|
* @param[in] dsh The dshandle that keeps track of the I/O operations.
|
|
* server The URL to the z/OSMF REST services
|
|
* @return 1 on success, 0 otherwise
|
|
*/
|
|
int lzds_rest_ping(struct dshandle *dsh, char *server)
|
|
{
|
|
struct curl_slist *list = NULL;
|
|
char *release;
|
|
CURLcode res;
|
|
size_t size;
|
|
CURL *curl;
|
|
char *url;
|
|
|
|
url = util_strcat_realloc(NULL, server);
|
|
url = util_strcat_realloc(url, "restfiles/ping");
|
|
|
|
curl = lzds_prepare_curl(url);
|
|
if (!curl) {
|
|
free(url);
|
|
return 0;
|
|
}
|
|
|
|
list = curl_slist_append(list, "X-CSRF-ZOSMF-HEADER: none");
|
|
if (dsh && dsh->session_ref) {
|
|
size = sizeof("X-IBM-Session-Ref: ") + strlen(dsh->session_ref);
|
|
release = util_zalloc(size);
|
|
snprintf(release, size, "X-IBM-Session-Ref: %s",
|
|
dsh->session_ref);
|
|
list = curl_slist_append(list, release);
|
|
}
|
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
|
|
res = curl_easy_perform(curl);
|
|
curl_slist_free_all(list);
|
|
curl_easy_cleanup(curl);
|
|
|
|
if (res == CURLE_OK) {
|
|
free(url);
|
|
return 1;
|
|
}
|
|
|
|
fprintf(stderr, "URL: %s\n", url);
|
|
fprintf(stderr, "Error: %s\n", curl_easy_strerror(res));
|
|
free(url);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Mark the dataset as in use for z/OS.
|
|
* Use z/OSMF REST services to read a small amount of data and get an exclusive
|
|
* ENQ that prevents z/OS applications from writing to the dataset in parallel
|
|
* until the ENQ is released.
|
|
*
|
|
* @param[in] dsh The dshandle that keeps track of the I/O operations.
|
|
* server The URL to the z/OSMF REST services
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - ENOTSUP Unable to setup curl and therefore no further access possible.
|
|
* - EPERM ENQ not obtained and therefore access is not allowed.
|
|
*/
|
|
int lzds_rest_get_enq(struct dshandle *dsh, char *server)
|
|
{
|
|
struct curl_slist *list = NULL;
|
|
struct response_data response;
|
|
int first_run;
|
|
CURLcode res;
|
|
CURL *curl;
|
|
char *url;
|
|
int rc;
|
|
|
|
url = util_strcat_realloc(NULL, server);
|
|
url = util_strcat_realloc(url, "restfiles/ds/");
|
|
url = util_strcat_realloc(url, dsh->ds->name);
|
|
|
|
memset(&response, 0, sizeof(response));
|
|
/*
|
|
* in the first run provide a range statement to read only 1 record of
|
|
* the dataset to get an ENQ.
|
|
* For the unlikely case that the dataset is empty
|
|
* "500 Internal Server Error" will be returned.
|
|
* If this is the case give it a second try without a range statement
|
|
*/
|
|
first_run = 1;
|
|
list = curl_slist_append(list, "X-IBM-Record-Range: 0-1");
|
|
retry:
|
|
rc = 1;
|
|
curl = lzds_prepare_curl(url);
|
|
if (!curl) {
|
|
free(url);
|
|
return errorlog_add_message(
|
|
&dsh->log,
|
|
NULL, ENOTSUP,
|
|
"curl handle not established for dataset %s\n",
|
|
dsh->ds->name);
|
|
}
|
|
|
|
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, parse_response_callback);
|
|
curl_easy_setopt(curl, CURLOPT_HEADERDATA, &response);
|
|
list = curl_slist_append(list, "X-CSRF-ZOSMF-HEADER: none");
|
|
list = curl_slist_append(list, "X-IBM-Obtain-ENQ: EXCLU");
|
|
|
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
|
|
res = curl_easy_perform(curl);
|
|
|
|
if (res != CURLE_OK) {
|
|
rc = errorlog_add_message(&dsh->log, NULL, ECONNREFUSED,
|
|
"Error: %s\n",
|
|
curl_easy_strerror(res));
|
|
} else {
|
|
if (first_run && response.statuscode == 500) {
|
|
curl_slist_free_all(list);
|
|
curl_easy_cleanup(curl);
|
|
first_run = 0;
|
|
list = NULL;
|
|
goto retry;
|
|
}
|
|
/* expect that the callback function found a reference string, double check */
|
|
if (response.statuscode == 200 && response.session_ref) {
|
|
dsh->session_ref = response.session_ref;
|
|
rc = 0;
|
|
} else {
|
|
rc = errorlog_add_message(
|
|
&dsh->log,
|
|
NULL, EPERM,
|
|
"no session ref obtained for dataset %s rest rc %ld\n",
|
|
dsh->ds->name, response.statuscode);
|
|
}
|
|
}
|
|
|
|
free(url);
|
|
curl_slist_free_all(list);
|
|
curl_easy_cleanup(curl);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Mark the dataset as no longer in use for z/OS.
|
|
* Use z/OSMF REST services to read a small amount of data and release the exclusive
|
|
* ENQ that was previously obtained.
|
|
*
|
|
* @param[in] dsh The dshandle that keeps track of the I/O operations.
|
|
* server The URL to the z/OSMF REST services
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - ENOTSUP Unable to release the ENQ.
|
|
*/
|
|
int lzds_rest_release_enq(struct dshandle *dsh, char *server)
|
|
{
|
|
struct curl_slist *list = NULL;
|
|
struct response_data response;
|
|
char *release;
|
|
int first_run;
|
|
CURLcode res;
|
|
CURL *curl;
|
|
char *url;
|
|
|
|
|
|
if (!dsh->session_ref) {
|
|
fprintf(stderr, "No ENQ to release.\n");
|
|
return 0;
|
|
}
|
|
|
|
url = util_strcat_realloc(NULL, server);
|
|
url = util_strcat_realloc(url, "restfiles/ds/");
|
|
url = util_strcat_realloc(url, dsh->ds->name);
|
|
|
|
release = util_strcat_realloc(NULL, "X-IBM-Session-Ref: ");
|
|
release = util_strcat_realloc(release, dsh->session_ref);
|
|
|
|
memset(&response, 0, sizeof(response));
|
|
/*
|
|
* in the first run provide a range statement to read only 1 record of
|
|
* the dataset to release the ENQ.
|
|
* For the unlikely case that the dataset is empty
|
|
* "500 Internal Server Error" will be returned.
|
|
* If this is the case give it a second try without a range statement
|
|
*/
|
|
first_run = 1;
|
|
list = curl_slist_append(list, "X-IBM-Record-Range: 0-1");
|
|
retry:
|
|
curl = lzds_prepare_curl(url);
|
|
if (!curl) {
|
|
free(url);
|
|
free(release);
|
|
return errorlog_add_message(
|
|
&dsh->log,
|
|
NULL, ENOTSUP,
|
|
"curl handle not established for dataset %s\n",
|
|
dsh->ds->name);
|
|
}
|
|
|
|
list = curl_slist_append(list, "X-CSRF-ZOSMF-HEADER: none");
|
|
list = curl_slist_append(list, "X-IBM-Release-ENQ: true");
|
|
list = curl_slist_append(list, release);
|
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
|
|
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, parse_response_callback);
|
|
curl_easy_setopt(curl, CURLOPT_HEADERDATA, &response);
|
|
res = curl_easy_perform(curl);
|
|
|
|
if (res != CURLE_OK) {
|
|
errorlog_add_message(&dsh->log, NULL, ENOTSUP, "Error: %s\n",
|
|
curl_easy_strerror(res));
|
|
} else if (first_run && response.statuscode == 500) {
|
|
curl_slist_free_all(list);
|
|
curl_easy_cleanup(curl);
|
|
first_run = 0;
|
|
list = NULL;
|
|
goto retry;
|
|
}
|
|
|
|
curl_slist_free_all(list);
|
|
curl_easy_cleanup(curl);
|
|
free(dsh->session_ref);
|
|
free(release);
|
|
free(url);
|
|
dsh->session_ref = NULL;
|
|
|
|
return res;
|
|
}
|
|
|
|
#endif /* HAVE_CURL */
|
|
|
|
|
|
/**
|
|
* This makes the data set context ready for read operations.
|
|
* All settings on the dsh must be done before it is opened.
|
|
* @pre For a partitioned data set a member must be set before
|
|
* this function is called.
|
|
*
|
|
* @param[in] dsh The dshandle that keeps track of the I/O operations.
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - ENOTSUP The dataset is of a type that is not supported.
|
|
* - EINVAL Tried to open a PDS without setting a member before..
|
|
* - EIO Could not open underlying device.
|
|
*/
|
|
int lzds_dshandle_open(struct dshandle *dsh)
|
|
{
|
|
int i, rc;
|
|
int ispds, issupported;
|
|
|
|
/* sanity check: Open will fail if the data set type is not supported.
|
|
* We do this check here and not during dshandle creation, as it may
|
|
* depend on settings on the dshandle that the user has to make
|
|
* between creation and open.
|
|
*/
|
|
errorlog_clear(dsh->log);
|
|
lzds_dataset_get_is_supported(dsh->ds, &issupported);
|
|
if (!issupported)
|
|
return errorlog_add_message(
|
|
&dsh->log,
|
|
NULL, ENOTSUP,
|
|
"data set open: data set %s is not supported\n",
|
|
dsh->ds->name);
|
|
lzds_dataset_get_is_PDS(dsh->ds, &ispds);
|
|
if (ispds && !dsh->member)
|
|
return errorlog_add_message(
|
|
&dsh->log,
|
|
NULL, EINVAL,
|
|
"data set open: a member must be set"
|
|
" before PDS %s can be opened\n", dsh->ds->name);
|
|
rc = initialize_buffer_positions_for_first_read(dsh);
|
|
if (rc)
|
|
return errorlog_add_message(
|
|
&dsh->log,
|
|
dsh->log, rc,
|
|
"data set open: error when initializing buffers"
|
|
" for data set %s\n", dsh->ds->name);
|
|
for (i = 0; i < dsh->ds->dspcount; ++i) {
|
|
rc = lzds_dasdhandle_open(dsh->dasdhandle[i]);
|
|
if (rc) {
|
|
errorlog_add_message(
|
|
&dsh->log,
|
|
dsh->dasdhandle[i]->log, rc,
|
|
"data set open: error opening DASD "
|
|
"for data set %s\n", dsh->ds->name);
|
|
lzds_dshandle_close(dsh);
|
|
return rc;
|
|
}
|
|
}
|
|
if (dsh->iconv)
|
|
dsh->convbuffer = util_zalloc(dsh->databufmax);
|
|
|
|
dsh->is_open = 1;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief subroutine of parse_fixed_record for codepage conversion
|
|
*
|
|
* Converts the provided data from one codepage to another using iconv.
|
|
* Stores converted data directly in the target buffer.
|
|
* Adds a linebreak at the end of each record to end the line.
|
|
* Also remove trailing spaces.
|
|
*
|
|
* @param[in] dsh The dshandle that keeps track of the I/O operations.
|
|
* @param[in] rec Pointer to the record buffer.
|
|
* @param[in] targetdata Pointer to the data buffer.
|
|
* @return Number of copied data bytes on success,
|
|
* otherwise one of the following (negative) error codes:
|
|
* - -EPROTO The record is malformed.
|
|
*/
|
|
static ssize_t convert_fixed_record(struct dshandle *dsh,
|
|
char *rec, char *targetdata)
|
|
{
|
|
struct eckd_count *ecount = (struct eckd_count *)rec;
|
|
size_t in_count, out_count, max_count;
|
|
int reclen, blocksize, reccount;
|
|
char *inbuf, *outbuf;
|
|
char *src, *target;
|
|
size_t rc;
|
|
int i;
|
|
|
|
blocksize = ecount->dl;
|
|
reclen = dsh->ds->dsp[0]->f1->DS1LRECL;
|
|
reccount = blocksize / reclen;
|
|
|
|
outbuf = targetdata;
|
|
out_count = max_count =
|
|
(unsigned long)dsh->databuffer + dsh->databufmax -
|
|
(unsigned long)targetdata;
|
|
in_count = 0;
|
|
inbuf = dsh->convbuffer;
|
|
|
|
/* skip block header */
|
|
src = (rec + sizeof(*ecount) + ecount->kl);
|
|
target = inbuf;
|
|
/* for each record aka line */
|
|
for (i = 0; i < reccount; i++) {
|
|
/* remove trailing spaces */
|
|
while (reclen && (*(src + reclen - 1) == EBCDIC_SP))
|
|
reclen--;
|
|
/* move remaining data and add linebreak at end of record */
|
|
memcpy(target, src, reclen);
|
|
target += reclen;
|
|
*target = EBCDIC_LF;
|
|
target++;
|
|
/* count how much chars remain after whitespace cleanup */
|
|
in_count += reclen + 1;
|
|
|
|
/* reset for next line */
|
|
reclen = dsh->ds->dsp[0]->f1->DS1LRECL;
|
|
src += reclen;
|
|
}
|
|
/* convert directly into target buffer */
|
|
rc = iconv(*(dsh->iconv), &inbuf, &in_count, &outbuf, &out_count);
|
|
if ((rc == (size_t) -1) || (in_count != 0))
|
|
return -errorlog_add_message(
|
|
&dsh->log, NULL, EPROTO,
|
|
"fixed record parser: codepage conversion failed\n");
|
|
/* return how much was written in the target buffer */
|
|
return max_count - out_count;
|
|
}
|
|
|
|
/**
|
|
* @brief subroutine of parse_variable_record for codepage conversion
|
|
*
|
|
* Converts the record data from one codepage to another using iconv.
|
|
* Stores converted data directly in the target buffer
|
|
*
|
|
* @param[in] dsh The dshandle that keeps track of the I/O operations.
|
|
* @param[in] reclen Length of the record.
|
|
* @param[in] rec Pointer to the record buffer.
|
|
* @param[in] targetdata Pointer to the data buffer.
|
|
* @return Number of copied data bytes on success,
|
|
* otherwise one of the following (negative) error codes:
|
|
* - -EPROTO The record is malformed.
|
|
*/
|
|
static ssize_t convert_variable_record(struct dshandle *dsh, int reclen,
|
|
char *rec, char *targetdata)
|
|
{
|
|
size_t in_count, out_count, max_count;
|
|
char *inbuf, *outbuf;
|
|
size_t rc;
|
|
|
|
inbuf = rec;
|
|
outbuf = targetdata;
|
|
in_count = reclen + 1;
|
|
out_count = max_count =
|
|
(unsigned long)dsh->databuffer + dsh->databufmax -
|
|
(unsigned long)targetdata;
|
|
/*
|
|
* we can not overwrite the track end marker since it is still used
|
|
* for this case we have to make a copy of the source data to add the
|
|
* linebreak
|
|
*/
|
|
if (inbuf[reclen] == 0xFF) {
|
|
inbuf = dsh->convbuffer;
|
|
memcpy(inbuf, rec, reclen);
|
|
}
|
|
|
|
/* add linebreak */
|
|
inbuf[reclen] = 0x25;
|
|
|
|
rc = iconv(*(dsh->iconv), &inbuf, &in_count, &outbuf, &out_count);
|
|
if ((rc == (size_t) -1) || (in_count != 0))
|
|
return -errorlog_add_message(
|
|
&dsh->log, NULL, EPROTO,
|
|
"variable record parser: codepage conversion failed\n");
|
|
|
|
/* return how much was written in the target buffer */
|
|
return max_count - out_count;
|
|
}
|
|
|
|
/**
|
|
* @brief subroutine of dshandle_extract_data_from_trackbuffer
|
|
*
|
|
* @param[in] dsh The dshandle that keeps track of the I/O operations.
|
|
* @param[in] rec Pointer to the raw record.
|
|
* @param[in] targetdata Pointer to the data buffer.
|
|
* @return Number of copied data bytes on success,
|
|
* otherwise one of the following (negative) error codes:
|
|
* - -EPROTO The record is malformed.
|
|
*/
|
|
static ssize_t parse_fixed_record(struct dshandle *dsh,
|
|
char *rec, char *targetdata)
|
|
{
|
|
struct eckd_count *ecount;
|
|
int count;
|
|
|
|
ecount = (struct eckd_count *)rec;
|
|
/* Make sure that we do not copy data beyond the end of
|
|
* the data buffer
|
|
*/
|
|
if ((unsigned long)targetdata + ecount->dl >
|
|
(unsigned long)dsh->databuffer + dsh->databufmax)
|
|
return - errorlog_add_message(
|
|
&dsh->log, NULL, EPROTO,
|
|
"fixed record to long for target buffer\n");
|
|
if (dsh->iconv) {
|
|
count = convert_fixed_record(dsh, rec, targetdata);
|
|
} else {
|
|
memcpy(targetdata, (rec + sizeof(*ecount) + ecount->kl), ecount->dl);
|
|
count = ecount->dl;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
/**
|
|
* @brief subroutine of dshandle_extract_data_from_trackbuffer
|
|
*
|
|
* @param[in] dsh The dshandle that keeps track of the I/O operations.
|
|
* @param[in] rec Pointer to the raw record.
|
|
* @param[in] targetdata Pointer to the data buffer.
|
|
* @param[in] keepRDW Flag that specifies if the RDW should be copied to
|
|
* the data buffer or or not.
|
|
* @return Number of copied data bytes on success,
|
|
* otherwise one of the following (negative) error codes:
|
|
* - -EPROTO The record is malformed.
|
|
*/
|
|
static ssize_t parse_variable_record(struct dshandle *dsh, char *rec,
|
|
char *targetdata, int keepRDW)
|
|
{
|
|
struct eckd_count *ecount;
|
|
unsigned int blocklength, segmentlength, residual;
|
|
char *data;
|
|
struct segment_header *blockhead;
|
|
struct segment_header *seghead;
|
|
size_t totaldatalength;
|
|
int count;
|
|
|
|
/* We must not rely on the data in rec, as it was read from disk and
|
|
* may be broken. Wherever we interprete the data we must have sanity
|
|
* checks.
|
|
*/
|
|
ecount = (struct eckd_count *)rec;
|
|
totaldatalength = 0;
|
|
/* An empty record is expected at the end of dataset or member */
|
|
if (ecount->dl == 0)
|
|
return 0;
|
|
/* If the data area is not zero but to small to contain a segment header
|
|
* then the record contents cannot be valid.
|
|
*/
|
|
if (ecount->dl < sizeof(struct segment_header))
|
|
return - errorlog_add_message(
|
|
&dsh->log, NULL, EPROTO,
|
|
"variable record parser: record length to small\n");
|
|
data = (rec + sizeof(*ecount) + ecount->kl);
|
|
blockhead = (struct segment_header *)data;
|
|
blocklength = blockhead->length;
|
|
/* If the length in the block descriptor is 0, then the block contains
|
|
* no data. Not sure if this is a valid case, but we tolerate it. */
|
|
if (!blocklength)
|
|
return totaldatalength;
|
|
/* If blocklength is to small to contain the block descriptor or to
|
|
* large to fit in the data area, then the block descriptor is broken */
|
|
if ((blocklength < sizeof(*blockhead)) || (blocklength > ecount->dl))
|
|
return - errorlog_add_message(
|
|
&dsh->log, NULL, EPROTO,
|
|
"variable record parser: block length to small\n");
|
|
data += sizeof(*blockhead);
|
|
residual = blocklength - sizeof(*blockhead);
|
|
while (residual) {
|
|
seghead = (struct segment_header *)data;
|
|
segmentlength = seghead->length;
|
|
if (seghead->nullsegment || !segmentlength) {
|
|
/* null segment found -> end of data in block */
|
|
return totaldatalength;
|
|
}
|
|
/* If segmentlength is to small to contain the record descriptor
|
|
* descriptor or to large to fit in the residual data area, then
|
|
* the record descriptor is broken
|
|
*/
|
|
if ((residual < segmentlength) ||
|
|
(segmentlength < sizeof(*seghead)))
|
|
return - errorlog_add_message(
|
|
&dsh->log, NULL, EPROTO,
|
|
"variable record parser: segment length %d "
|
|
"inconsistent at offset %lu\n",
|
|
segmentlength,
|
|
(unsigned long)seghead - (unsigned long)rec);
|
|
residual -= segmentlength;
|
|
if (!keepRDW) {
|
|
data += sizeof(*seghead);
|
|
segmentlength -= sizeof(*seghead);
|
|
}
|
|
/* Make sure that we do not copy data beyond the end of
|
|
* the data buffer
|
|
*/
|
|
if ((unsigned long)targetdata + segmentlength >
|
|
(unsigned long)dsh->databuffer + dsh->databufmax)
|
|
return - errorlog_add_message(
|
|
&dsh->log, NULL, EPROTO,
|
|
"variable record parser: "
|
|
"record to long for target buffer\n");
|
|
if (dsh->iconv) {
|
|
count = convert_variable_record(dsh, segmentlength,
|
|
data, targetdata);
|
|
if (count < 0)
|
|
return count;
|
|
|
|
totaldatalength += count;
|
|
targetdata += count;
|
|
} else {
|
|
memcpy(targetdata, data, segmentlength);
|
|
totaldatalength += segmentlength;
|
|
targetdata += segmentlength;
|
|
}
|
|
data += segmentlength;
|
|
}
|
|
return totaldatalength;
|
|
}
|
|
|
|
/**
|
|
* @brief subroutine of lzds_dshandle_read
|
|
*
|
|
* Parses the raw track buffer in dsh and copies the user data to
|
|
* the databuffer in dsh.
|
|
*
|
|
* @param[in] dsh The dshandle that keeps track of the I/O operations.
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - EPROTO The raw track data is malformed.
|
|
*/
|
|
static int dshandle_extract_data_from_trackbuffer(struct dshandle *dsh)
|
|
{
|
|
char *track;
|
|
size_t i, trckcount;
|
|
struct eckd_count *ecount;
|
|
char *rawdata, *targetdata;
|
|
unsigned int record;
|
|
char DS1RECFM;
|
|
ssize_t tdsize;
|
|
|
|
DS1RECFM = dsh->ds->dsp[0]->f1->DS1RECFM;
|
|
trckcount = dsh->rawbufsize / RAWTRACKSIZE;
|
|
track = dsh->rawbuffer;
|
|
targetdata = dsh->databuffer;
|
|
dsh->databufsize = 0;
|
|
/* Record zero is not part of the regular data, so I must not copy its
|
|
* data. In case of a PDS member, we may need to skip a few extra
|
|
* records on the first track. In this case startrecord is already set
|
|
* and will be reset to 1 after the first track has been read.
|
|
*/
|
|
if (!dsh->startrecord)
|
|
dsh->startrecord = 1;
|
|
for (i = 0; i < trckcount && !dsh->eof_reached; ++i) {
|
|
record = 0;
|
|
rawdata = track;
|
|
while (!dsh->eof_reached) {
|
|
tdsize = 0;
|
|
if (record >= dsh->startrecord) {
|
|
/* fixed or undefined record size */
|
|
if ((DS1RECFM & 0x80))
|
|
tdsize = parse_fixed_record(dsh,
|
|
rawdata,
|
|
targetdata);
|
|
/* variable records */
|
|
if (!(DS1RECFM & 0x80) && (DS1RECFM & 0x40))
|
|
tdsize = parse_variable_record(dsh,
|
|
rawdata,
|
|
targetdata,
|
|
dsh->keepRDW);
|
|
if (tdsize < 0)
|
|
return errorlog_add_message(
|
|
&dsh->log, dsh->log, EPROTO,
|
|
"data extraction: error at "
|
|
"record %u, offset %lu\n",
|
|
record,
|
|
(unsigned long)rawdata
|
|
- (unsigned long)dsh->rawbuffer);
|
|
targetdata += tdsize;
|
|
dsh->databufsize += tdsize;
|
|
}
|
|
ecount = (struct eckd_count *)rawdata;
|
|
rawdata += sizeof(*ecount) + ecount->kl + ecount->dl;
|
|
/* An empty record marks the end of a member / data set
|
|
* We need to take startrecord into account or we might
|
|
* find the end marker of the previous member.
|
|
*/
|
|
if ((record >= dsh->startrecord) &&
|
|
(!ecount->kl) && (!ecount->dl))
|
|
dsh->eof_reached = 1;
|
|
++record;
|
|
if ((*(unsigned long long *)rawdata) == ENDTOKEN)
|
|
break;
|
|
if ((unsigned long)rawdata >=
|
|
(unsigned long)track + RAWTRACKSIZE)
|
|
return errorlog_add_message(
|
|
&dsh->log, NULL, EPROTO,
|
|
"data extraction: run over end of"
|
|
" track buffer\n");
|
|
}
|
|
dsh->startrecord = 1;
|
|
track += RAWTRACKSIZE;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief subroutine of lzds_dshandle_read
|
|
*
|
|
* Find the next range of extents and prepare dsh for the next read.
|
|
* The return value indicates whether there is more data to read or not.
|
|
*
|
|
* @pre: For the first call to this function, dsh should be set to the
|
|
* last track before the first track to read.
|
|
* If the first track to read is the first track in the dataset
|
|
* then set dsh->ext_seq_no to -1.
|
|
*
|
|
* @param[in] dsh The dshandle that keeps track of the I/O operations.
|
|
*
|
|
* @return
|
|
* 0 when there is no further raw data available,
|
|
* 1 when there is more data available and dsh is prepared
|
|
*/
|
|
static int dshandle_prepare_for_next_read_tracks(struct dshandle *dsh)
|
|
{
|
|
int found, dsp_no, ext_seq_no;
|
|
|
|
/* If there are still unread tracks in the current extent, we just need
|
|
* to point dsh to the next range of tracks
|
|
*/
|
|
if (dsh->bufendtrk < dsh->extendtrk) {
|
|
dsh->bufstarttrk = dsh->bufendtrk + 1;
|
|
dsh->bufendtrk = dsh->bufstarttrk +
|
|
(dsh->rawbufmax / RAWTRACKSIZE) - 1;
|
|
dsh->bufendtrk = MIN(dsh->bufendtrk, dsh->extendtrk);
|
|
dsh->rawbufsize = (dsh->bufendtrk - dsh->bufstarttrk + 1)
|
|
* RAWTRACKSIZE;
|
|
dsh->databufoffset = dsh->databufoffset + dsh->databufsize;
|
|
dsh->databufsize = 0;
|
|
dsh->bufpos = 0;
|
|
dsh->frameno++;
|
|
return 1;
|
|
}
|
|
/* There are no more tracks left in the current extent.
|
|
* Loop over data set parts and extends in these parts until a valid
|
|
* extent is found or the end of the data set is reached
|
|
*/
|
|
ext_seq_no = dsh->ext_seq_no;
|
|
dsp_no = dsh->dsp_no;
|
|
found = 0;
|
|
while (!found) {
|
|
++ext_seq_no;
|
|
if (ext_seq_no >= MAXEXTENTS) {
|
|
ext_seq_no = 0;
|
|
++dsp_no;
|
|
}
|
|
if (dsp_no >= dsh->ds->dspcount)
|
|
break;
|
|
if (extent_contains_userdata(
|
|
&dsh->ds->dsp[dsp_no]->ext[ext_seq_no]))
|
|
found = 1;
|
|
}
|
|
if (!found)
|
|
return 0;
|
|
/* We have found the next valid extent. Get lower and upper track
|
|
* limits and set dsh to the first range of tracks */
|
|
dsh->ext_seq_no = ext_seq_no;
|
|
dsh->dsp_no = dsp_no;
|
|
lzds_dasd_cchh2trk(dsh->ds->dsp[dsp_no]->dasdi,
|
|
&dsh->ds->dsp[dsp_no]->ext[ext_seq_no].llimit,
|
|
&dsh->extstarttrk);
|
|
lzds_dasd_cchh2trk(dsh->ds->dsp[dsp_no]->dasdi,
|
|
&dsh->ds->dsp[dsp_no]->ext[ext_seq_no].ulimit,
|
|
&dsh->extendtrk);
|
|
dsh->bufstarttrk = dsh->extstarttrk;
|
|
dsh->bufendtrk = dsh->bufstarttrk + (dsh->rawbufmax / RAWTRACKSIZE) - 1;
|
|
dsh->bufendtrk = MIN(dsh->bufendtrk, dsh->extendtrk);
|
|
dsh->rawbufsize = (dsh->bufendtrk - dsh->bufstarttrk + 1)
|
|
* RAWTRACKSIZE;
|
|
dsh->databufoffset = dsh->databufoffset + dsh->databufsize;
|
|
dsh->databufsize = 0;
|
|
dsh->bufpos = 0;
|
|
dsh->frameno++;
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* @brief subroutine of lzds_dshandle_read
|
|
*
|
|
* As we progress in reading data from the dataset, we store
|
|
* track/data offsets in the dshandle for late use by the
|
|
* lzds_dshandle_lseek and related operations.
|
|
*
|
|
* @param[in] dsh The dshandle that keeps track of the I/O operations.
|
|
*
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - EPROTO The existing seek buffer data is inconsistent.
|
|
* - EINVAL The existing seek buffer data is inconsistent.
|
|
* - ERANGE We try to add more elements than the prepared buffer can hold.
|
|
*/
|
|
static int dshandle_store_trackframe(struct dshandle *dsh)
|
|
{
|
|
unsigned long long index;
|
|
|
|
/* if we have no skip or seekbuf we cannot store anything */
|
|
if (!dsh->skip || !dsh->seekbuf)
|
|
return 0;
|
|
|
|
/* if this is a frame we want to skip, just return 0 */
|
|
if (dsh->frameno % dsh->skip)
|
|
return 0;
|
|
/* make sure we do not access elements beyond the end of the buffer */
|
|
if (dsh->seek_current >= dsh->seek_count)
|
|
return errorlog_add_message(
|
|
&dsh->log,
|
|
NULL, ERANGE,
|
|
"store track frame: frame list size is inconsistent\n");
|
|
|
|
/* our seek code relies on the fact that element n refers to frame
|
|
* n * skip, so we need to make sure we that we do not leave gaps */
|
|
index = dsh->frameno / dsh->skip;
|
|
if (index > dsh->seek_current)
|
|
return errorlog_add_message(
|
|
&dsh->log,
|
|
NULL, EPROTO,
|
|
"store track frame: frame list inconsistent\n");
|
|
|
|
/* if we have visited this frame before, return */
|
|
if (index < dsh->seek_current) {
|
|
if (dsh->seekbuf[index].dsp_no != dsh->dsp_no ||
|
|
dsh->seekbuf[index].ext_seq_no != dsh->ext_seq_no ||
|
|
dsh->seekbuf[index].bufstarttrk != dsh->bufstarttrk ||
|
|
dsh->seekbuf[index].databufoffset != dsh->databufoffset)
|
|
return errorlog_add_message(
|
|
&dsh->log,
|
|
NULL, EINVAL,
|
|
"store track frame: frame data inconsistent\n");
|
|
else
|
|
return 0;
|
|
}
|
|
/* the seek_current = index case */
|
|
dsh->seekbuf[index].dsp_no = dsh->dsp_no;
|
|
dsh->seekbuf[index].ext_seq_no = dsh->ext_seq_no;
|
|
dsh->seekbuf[index].bufstarttrk = dsh->bufstarttrk;
|
|
dsh->seekbuf[index].databufoffset = dsh->databufoffset;
|
|
dsh->seek_current++;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @param[in] dsh The dshandle that keeps track of the I/O operations.
|
|
* @param[in] buf The target buffer for the read data.
|
|
* @param[in] size The number of bytes that are to be read.
|
|
* @param[out] rcsize Reference to a variable in which the actual number
|
|
* of read bytes is returned.
|
|
* If this is 0, the end of the file is reached.
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - EINVAL The data in dsh is inconsistent.
|
|
* - ERANGE The data in dsh is inconsistent.
|
|
* - EPROTO The data read from the disk does not conform to the
|
|
* expected format.
|
|
* - EIO I/O error when reading from device.
|
|
*/
|
|
int lzds_dshandle_read(struct dshandle *dsh, char *buf,
|
|
size_t size, ssize_t *rcsize)
|
|
{
|
|
ssize_t copysize;
|
|
int rc;
|
|
|
|
errorlog_clear(dsh->log);
|
|
if (!dsh->is_open)
|
|
return errorlog_add_message(
|
|
&dsh->log, NULL, EINVAL,
|
|
"data set read: dshandle is not open\n");
|
|
*rcsize = 0;
|
|
while (*rcsize < (long long)size) {
|
|
if (dsh->bufpos >= dsh->databufsize) {
|
|
/* need to fill dsh data buffer */
|
|
if (dsh->eof_reached)
|
|
break; /* end of data in data set reached */
|
|
if (!dshandle_prepare_for_next_read_tracks(dsh))
|
|
break; /* end of data set extents reached */
|
|
rc = lzds_dasdhandle_read_tracks_to_buffer(
|
|
dsh->dasdhandle[dsh->dsp_no], dsh->bufstarttrk,
|
|
dsh->bufendtrk, dsh->rawbuffer);
|
|
if (rc)
|
|
return errorlog_add_message(
|
|
&dsh->log,
|
|
dsh->dasdhandle[dsh->dsp_no]->log, rc,
|
|
"data set read: error reading data set"
|
|
" %s\n", dsh->ds->name);
|
|
rc = dshandle_extract_data_from_trackbuffer(dsh);
|
|
if (rc)
|
|
return errorlog_add_message(
|
|
&dsh->log,
|
|
dsh->log,
|
|
rc,
|
|
"data set read: extracting data set "
|
|
"%s from %s, tracks %u to %u\n",
|
|
dsh->ds->name,
|
|
dsh->dasdhandle[dsh->dsp_no]->dasd->device,
|
|
dsh->bufstarttrk,
|
|
dsh->bufendtrk);
|
|
rc = dshandle_store_trackframe(dsh);
|
|
if (rc)
|
|
return errorlog_add_message(
|
|
&dsh->log,
|
|
dsh->log, rc,
|
|
"data set read: storing track frame "
|
|
"%s\n", dsh->ds->name);
|
|
}
|
|
/* if databuf has data to copy */
|
|
if (dsh->bufpos < dsh->databufsize) {
|
|
/* copy data from databuf to buf */
|
|
copysize = MIN(((long long)size - *rcsize),
|
|
(dsh->databufsize - dsh->bufpos));
|
|
memcpy(buf, &dsh->databuffer[dsh->bufpos], copysize);
|
|
buf += copysize;
|
|
dsh->bufpos += copysize;
|
|
*rcsize += copysize;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief subroutine of lzds_dshandle_lseek
|
|
*
|
|
* Find the closest buffered seekelement that starts before offset
|
|
*
|
|
* @param[in] dsh The dshandle that keeps track of the I/O operations.
|
|
* @param[in] offset The data offset in the dataset that we want to reach.
|
|
* @param[out] se_index Reference to a variable in which the found index
|
|
* to dsh->seekbuf is returned.
|
|
*
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - EINVAL There is no seekbuffer available.
|
|
*/
|
|
static int dshandle_find_seekelement(struct dshandle *dsh, off_t offset,
|
|
long long *se_index)
|
|
{
|
|
unsigned long long low, high, index;
|
|
|
|
if (!dsh->seek_current)
|
|
return EINVAL;
|
|
|
|
/* special case for the last element in the list */
|
|
if (dsh->seekbuf[dsh->seek_current - 1].databufoffset <= offset) {
|
|
*se_index = dsh->seek_current - 1;
|
|
return 0;
|
|
}
|
|
/* search starts with 'high' set to the second to last element */
|
|
index = 0;
|
|
high = dsh->seek_current - 2;
|
|
low = 0;
|
|
*se_index = 0;
|
|
index = (high + low) / 2;
|
|
while (low != high) {
|
|
if (dsh->seekbuf[index].databufoffset <= offset) {
|
|
low = index;
|
|
index = (high + low + 1) / 2;
|
|
} else {
|
|
high = index - 1;
|
|
index = (high + low) / 2;
|
|
}
|
|
}
|
|
*se_index = low;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief subroutine of lzds_dshandle_lseek
|
|
*
|
|
* Reset the internel buffers etc, so that the next read will read
|
|
* the track frame pointed to by the seekelement.
|
|
*
|
|
* @param[in] dsh The dshandle that keeps track of the I/O operations.
|
|
* @param[out] se_index Index to the seekelement in dsh->seekbuf.
|
|
*/
|
|
static void dshandle_reset_buffer_position_to_seekelement(
|
|
struct dshandle *dsh, long long se_index)
|
|
{
|
|
/* make sure that read knows that we have no ready data in our buffer */
|
|
dsh->bufpos = 0;
|
|
dsh->databufsize = 0;
|
|
dsh->eof_reached = 0;
|
|
|
|
/* we need to set the bufendtrk and sequence number so,
|
|
* that the current track buffer seems to end with the
|
|
* track that comes before the first track of the
|
|
* data set or member
|
|
*/
|
|
|
|
/* framno will be incremented during read, so do a -1 here */
|
|
dsh->frameno = (se_index * dsh->skip) - 1;
|
|
dsh->databufoffset = dsh->seekbuf[se_index].databufoffset;
|
|
dsh->dsp_no = dsh->seekbuf[se_index].dsp_no;
|
|
|
|
/* For a partitioned data set we need to find the correct start
|
|
* track and point the current buffer just before it.
|
|
* As we always need to read full tracks, any additional
|
|
* record offset will be set explicitly and handled during
|
|
* track interpretation.
|
|
*/
|
|
if (dsh->member && (dsh->frameno == -1))
|
|
dsh->startrecord = dsh->member->record;
|
|
|
|
/* In most cases our track frame will be in the middle of the
|
|
* disk, so we set bufendtrk to the last track before our track
|
|
* frame. In the special case that the track frame begins
|
|
* on track 0, we set the ext_seq_no to that of the frame -1,
|
|
* so that the read code will advance to the next extend and
|
|
* the first track of that extent
|
|
*/
|
|
if (!dsh->seekbuf[se_index].bufstarttrk) {
|
|
dsh->ext_seq_no = dsh->seekbuf[se_index].ext_seq_no - 1;
|
|
dsh->bufendtrk = 0;
|
|
dsh->extendtrk = 0;
|
|
return;
|
|
}
|
|
|
|
dsh->ext_seq_no = dsh->seekbuf[se_index].ext_seq_no;
|
|
|
|
lzds_dasd_cchh2trk(dsh->ds->dsp[dsh->dsp_no]->dasdi,
|
|
&dsh->ds->dsp[dsh->dsp_no]->ext[dsh->ext_seq_no].llimit,
|
|
&dsh->extstarttrk);
|
|
lzds_dasd_cchh2trk(dsh->ds->dsp[dsh->dsp_no]->dasdi,
|
|
&dsh->ds->dsp[dsh->dsp_no]->ext[dsh->ext_seq_no].ulimit,
|
|
&dsh->extendtrk);
|
|
|
|
dsh->bufstarttrk = 0;
|
|
dsh->bufendtrk = dsh->seekbuf[se_index].bufstarttrk - 1;
|
|
|
|
dsh->rawbufsize = 0;
|
|
dsh->databufoffset = dsh->seekbuf[se_index].databufoffset;
|
|
dsh->databufsize = 0;
|
|
dsh->bufpos = 0;
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* It is not possible to seek beyond the end of the data, but an
|
|
* attempt to do so is a common occurrence as we may not know the
|
|
* actual data size beforehand. In this case, the returned rcoffset
|
|
* is smaller than offset and points to the offset directly following
|
|
* the last data byte.
|
|
*
|
|
* @param[in] dsh The dshandle that keeps track of the I/O operations.
|
|
* @param[in] offset The data offset in the dataset that we want to reach.
|
|
* @param[out] rcoffset Reference to a variable in which the actual offset
|
|
* is returned.
|
|
*
|
|
* @return 0 on success, otherwise one of the following error codes:
|
|
* - EINVAL The data in dsh is inconsistent.
|
|
* - ERANGE The data in dsh is inconsistent.
|
|
* - EPROTO The data read from the disk does not conform to the
|
|
* expected format.
|
|
* - EIO I/O error when reading from device.
|
|
*/
|
|
int lzds_dshandle_lseek(struct dshandle *dsh, long long offset,
|
|
long long *rcoffset)
|
|
{
|
|
char foo;
|
|
ssize_t rcsize;
|
|
int rc;
|
|
long long se_index;
|
|
|
|
errorlog_clear(dsh->log);
|
|
if (dsh->databufoffset <= offset &&
|
|
offset < dsh->databufoffset + dsh->databufsize) {
|
|
/* offset is within the current track frame */
|
|
dsh->bufpos = offset - dsh->databufoffset;
|
|
*rcoffset = offset;
|
|
return 0;
|
|
}
|
|
/* need to seek to some other track frame */
|
|
if (!dshandle_find_seekelement(dsh, offset, &se_index)) {
|
|
/* do not reset our context if we can seek forward from
|
|
* our current position */
|
|
if (!(dsh->seekbuf[se_index].databufoffset < dsh->databufoffset
|
|
&& dsh->databufoffset <= offset)) {
|
|
dshandle_reset_buffer_position_to_seekelement(dsh,
|
|
se_index);
|
|
}
|
|
} else if (offset < dsh->databufoffset) {
|
|
/* if we have no seekbuffer, we can only reset to the
|
|
* start of the data set */
|
|
rc = initialize_buffer_positions_for_first_read(dsh);
|
|
if (rc)
|
|
return errorlog_add_message(
|
|
&dsh->log,
|
|
dsh->log, rc,
|
|
"data set seek: error when initializing buffers"
|
|
" for data set %s\n", dsh->ds->name);
|
|
}
|
|
/* from here on we just need to go forward by reading track
|
|
* frames until we find a frame that contains the offset
|
|
*/
|
|
while (dsh->databufoffset + dsh->databufsize <= offset) {
|
|
dsh->bufpos = dsh->databufsize;
|
|
rc = lzds_dshandle_read(dsh, &foo, sizeof(foo), &rcsize);
|
|
if (rc || !rcsize) {
|
|
*rcoffset = dsh->databufoffset + dsh->databufsize;
|
|
if (rc)
|
|
errorlog_add_message(
|
|
&dsh->log,
|
|
dsh->log, rc,
|
|
"data set seek: error reading data from"
|
|
" data set %s\n", dsh->ds->name);
|
|
return rc;
|
|
}
|
|
}
|
|
dsh->bufpos = offset - dsh->databufoffset;
|
|
*rcoffset = offset;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @param[in] dsh The dshandle that keeps track of the I/O operations.
|
|
* @param[out] offset Reference to a variable in which the current offset
|
|
* is returned.
|
|
*/
|
|
void lzds_dshandle_get_offset(struct dshandle *dsh, long long *offset)
|
|
{
|
|
*offset = dsh->databufoffset + dsh->bufpos;
|
|
}
|
|
|
|
/**
|
|
* @param[in] dsh The dshandle that keeps track of the I/O operations.
|
|
* @param[out] log Reference to a variable in which the errorlog
|
|
* is returned.
|
|
*/
|
|
void lzds_dshandle_get_errorlog(struct dshandle *dsh, struct errorlog **log)
|
|
{
|
|
*log = dsh->log;
|
|
}
|
|
|
|
|
|
/******************************************************************************/
|
|
/* libzds helper functions */
|
|
/******************************************************************************/
|
|
|
|
/**
|
|
* This function takes the DS1RECFM byte as defined for the format 1 DSCB, and
|
|
* creates a string of the usual characters F, V, U, T, B, S, A, and M.
|
|
*
|
|
* @param[in] DS1RECFM Input byte.
|
|
* @param[out] buffer Buffer for the output string.
|
|
* The buffer must be at least 7 characters long.
|
|
*/
|
|
void lzds_DS1RECFM_to_recfm(char DS1RECFM, char *buffer)
|
|
{
|
|
if ((DS1RECFM & 0x80) && !(DS1RECFM & 0x40))
|
|
*buffer++ = 'F'; /* fixed records */
|
|
|
|
if (!(DS1RECFM & 0x80) && (DS1RECFM & 0x40))
|
|
*buffer++ = 'V'; /* variable records */
|
|
|
|
if ((DS1RECFM & 0x80) && (DS1RECFM & 0x40))
|
|
*buffer++ = 'U'; /* undefined length records */
|
|
|
|
if ((DS1RECFM & 0x20))
|
|
*buffer++ = 'T'; /* track overflow (legacy) */
|
|
|
|
if ((DS1RECFM & 0x10))
|
|
*buffer++ = 'B'; /* blocked */
|
|
|
|
if ((DS1RECFM & 0x08))
|
|
*buffer++ = 'S'; /* standard records */
|
|
|
|
if ((DS1RECFM & 0x04) && !(DS1RECFM & 0x02))
|
|
*buffer++ = 'A'; /* ISO / ANSI control characters */
|
|
|
|
if (!(DS1RECFM & 0x04) && (DS1RECFM & 0x02))
|
|
*buffer++ = 'M'; /* machine control characters */
|
|
*buffer = 0;
|
|
|
|
/* The combinations ((DS1RECFM & 0x04) && (DS1RECFM & 0x02))
|
|
* and (DS1RECFM & 0x01) are reserved
|
|
*
|
|
* If we count only one byte for the mutual exclusive F, V and U,
|
|
* three bytes for T, B and S,
|
|
* one byte for the mutual exclusive A and M,
|
|
* one byte for a possible future definition of 0x01, and
|
|
* one byte for the zero termination,
|
|
* then we get a required buffer length of 7 bytes
|
|
*/
|
|
}
|
|
|
|
int lzds_analyse_open_count(struct zdsroot *root, int warn)
|
|
{
|
|
struct dasd *dasd;
|
|
int value;
|
|
int rc = 0;
|
|
|
|
util_list_iterate(root->dasdlist, dasd) {
|
|
value = dasd_get_host_access_count(dasd->device);
|
|
|
|
if (value < 0) {
|
|
fprintf(stderr,
|
|
"Hosts access information not available for disk %s.\n",
|
|
dasd->device);
|
|
rc = value;
|
|
continue;
|
|
}
|
|
|
|
if (value == 1)
|
|
continue;
|
|
|
|
if (warn)
|
|
fprintf(stderr,
|
|
"\nWARNING:\n"
|
|
"Disk %s is online on operating system instances in %d different LPARs.\n"
|
|
"Ensure that the disk is not being used by a system outside your LPAR.\n"
|
|
"Note: Your installation might include z/VM systems that are configured to\n"
|
|
"automatically vary on disks, regardless of whether they are subsequently used.\n",
|
|
dasd->device, value);
|
|
else {
|
|
fprintf(stderr,
|
|
"\nERROR:\n"
|
|
"Disk %s is online on operating system instances in %d different LPARs.\n"
|
|
"Ensure that the disk is not being used by a system outside your LPAR.\n"
|
|
"Note: Your installation might include z/VM systems that are configured to\n"
|
|
"automatically vary on disks, regardless of whether they are subsequently used.\n",
|
|
dasd->device, value);
|
|
rc = -EACCES;
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|