/** * @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 #include #include #include #include #include #include #ifdef HAVE_CURL #include #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 : " * * @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; }