diff --git a/CMakeLists.txt b/CMakeLists.txt index ec5674d1..d157b219 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,6 +55,10 @@ if(ZOSLIB_QUICK_STARTUP) ZOSLIB_QUICK_STARTUP) endif() +# Dataset I/O is always compiled in, controlled by ZOSLIB_DATASET_SUPPORT env var at runtime +list(APPEND zoslib_defines + ZOSLIB_ENABLE_DATASETIO=1) + if(DEFINED ENV{BUILD_VERSION}) list(APPEND zoslib_defines BUILD_VERSION="$ENV{BUILD_VERSION}") endif() diff --git a/build.sh b/build.sh index 2b1b769b..7c5bb68f 100755 --- a/build.sh +++ b/build.sh @@ -13,6 +13,7 @@ $0 Builds zoslib, uses xlclang/xlclang++ by default, unless CC is set. Options: -c Clean build +-d Enable dataset I/O support (default is Disabled) -h Display this message -r Release build (default is Debug) -t Build and run tests @@ -26,6 +27,7 @@ BLD_TYPE="Debug" IS_CLEAN=0 RUN_TESTS="OFF" BLD_TESTS= +BLD_DSIO= if test -z "$CC"; then export CC=xlclang && export CXX=xlclang++ && export LINK=xlclang++ @@ -33,11 +35,14 @@ if test -z "$CC"; then fi nargs=0 -while getopts "chrt" o; do +while getopts "cdhrt" o; do case "${o}" in c) IS_CLEAN=1 ((nargs++)) ;; + d) BLD_DSIO="-DZOSLIB_ENABLE_DATASETIO=ON" + ((nargs++)) + ;; r) BLD_TYPE="Release" ((nargs++)) ;; @@ -65,7 +70,7 @@ pushd build export MAKEFLAGS='-j4' if((IS_CLEAN==1)) || ! test -s CMakeCache.txt; then - cmake .. -DCMAKE_C_COMPILER=${CC} -DCMAKE_CXX_COMPILER=${CXX} -DCMAKE_ASM_COMPILER=${CC} ${BLD_TESTS} -DCMAKE_BUILD_TYPE=${BLD_TYPE} -DCMAKE_INSTALL_PREFIX=${SCRIPT_DIR}/install + cmake .. -DCMAKE_C_COMPILER=${CC} -DCMAKE_CXX_COMPILER=${CXX} -DCMAKE_ASM_COMPILER=${CC} ${BLD_TESTS} ${BLD_DSIO} -DCMAKE_BUILD_TYPE=${BLD_TYPE} -DCMAKE_INSTALL_PREFIX=${SCRIPT_DIR}/install fi cmake --build . --target install diff --git a/include/fcntl.h b/include/fcntl.h index 28a5f94d..86ea6bc1 100644 --- a/include/fcntl.h +++ b/include/fcntl.h @@ -18,6 +18,7 @@ extern "C" { /** * Same as C open but tags new files as ASCII (819) */ +__Z_EXPORT extern int __open_ds_file(const char *filename, int opts, ...); __Z_EXPORT extern int __open_ascii(const char *filename, int opts, ...); __Z_EXPORT extern int __creat_ascii(const char *filename, mode_t mode); #if defined(__cplusplus) @@ -38,7 +39,7 @@ __Z_EXPORT extern int __creat_ascii(const char *filename, mode_t mode); extern "C" { #endif -__Z_EXPORT extern int open(const char *filename, int opts, ...) __asm("__open_ascii"); +__Z_EXPORT extern int open(const char *filename, int opts, ...) __asm("__open_ds_file"); __Z_EXPORT extern int creat(const char *filename, mode_t mode) __asm("__creat_ascii"); #if defined(__cplusplus) diff --git a/include/stdio.h b/include/stdio.h index 7a3cc5a2..89682cea 100644 --- a/include/stdio.h +++ b/include/stdio.h @@ -21,6 +21,7 @@ extern "C" { * Same as C open but tags new files as ASCII (819) */ __Z_EXPORT extern FILE *__fopen_ascii(const char *filename, const char *mode); +__Z_EXPORT extern FILE *__fopen_ds_file(const char *filename, const char *mode); #if defined(__cplusplus) } @@ -37,7 +38,7 @@ __Z_EXPORT extern FILE *__fopen_ascii(const char *filename, const char *mode); extern "C" { #endif -__Z_EXPORT extern FILE *fopen(const char *filename, const char *mode) __asm("__fopen_ascii"); +__Z_EXPORT extern FILE *fopen(const char *filename, const char *mode) __asm("__fopen_ds_file"); __Z_EXPORT ssize_t getline(char **lineptr, size_t *n, FILE *stream); __Z_EXPORT ssize_t getdelim(char **lineptr, size_t *n, int delimiter, FILE *stream); __Z_EXPORT int vasprintf(char **strp, const char *fmt, va_list ap); diff --git a/include/stdlib.h b/include/stdlib.h index b3cdf611..570fe0da 100644 --- a/include/stdlib.h +++ b/include/stdlib.h @@ -22,6 +22,7 @@ extern "C" { __Z_EXPORT char *__realpath_extended(const char * __restrict__, char * __restrict__); #ifdef __NATIVE_ASCII_F __Z_EXPORT int __mkstemp_ascii(char*); +__Z_EXPORT int __mkstemp_ds_file(char*); #endif #if defined(__cplusplus) } @@ -82,7 +83,7 @@ __Z_EXPORT void free(void* ptr) __THROW __asm("__zoslib_free") ; * Same as C mkstemp but tags fd as ASCII (819) */ #undef mkstemp -__Z_EXPORT int mkstemp(char*) __asm("__mkstemp_ascii"); +__Z_EXPORT int mkstemp(char*) __asm("__mkstemp_ds_file"); #endif /* __NATIVE_ASCII_F */ #if defined(__cplusplus) diff --git a/include/sys/stat.h b/include/sys/stat.h index 06bec1de..c889d97b 100644 --- a/include/sys/stat.h +++ b/include/sys/stat.h @@ -20,6 +20,8 @@ extern "C" { * Same as C mkfifo but tags FIFO special files as ASCII (819) */ __Z_EXPORT extern int __mkfifo_ascii(const char *pathname, mode_t mode); +__Z_EXPORT extern int __stat_ds_file(const char *path, struct stat *buf); +__Z_EXPORT extern int __fstat_ds_file(int fd, struct stat *buf); #if defined(__cplusplus) } @@ -29,14 +31,22 @@ __Z_EXPORT extern int __mkfifo_ascii(const char *pathname, mode_t mode); #undef mkfifo #define mkfifo __mkfifo_replaced +#undef stat +#define stat(x,y) __stat_replaced(x,y) +#undef fstat +#define fstat(x,y) __fstat_replaced(x,y) #include_next #undef mkfifo +#undef stat +#undef fstat #if defined(__cplusplus) extern "C" { #endif __Z_EXPORT extern int mkfifo(const char *pathname, mode_t mode) __asm("__mkfifo_ascii"); +__Z_EXPORT extern int stat(const char *path, struct stat *buf) __asm("__stat_ds_file"); +__Z_EXPORT extern int fstat(int fd, struct stat *buf) __asm("__fstat_ds_file"); #if defined(__cplusplus) } diff --git a/include/unistd.h b/include/unistd.h index 70047d8b..3456ad63 100644 --- a/include/unistd.h +++ b/include/unistd.h @@ -10,6 +10,7 @@ #define ZOS_UNISTD_H_ #include "zos-macros.h" +#include #if defined(__cplusplus) extern "C" { @@ -17,6 +18,9 @@ extern "C" { __Z_EXPORT int __pipe_ascii(int [2]); __Z_EXPORT int __close(int); __Z_EXPORT int __sysconf(int name); +__Z_EXPORT ssize_t __write_ds_file(int fd, const void *buf, size_t count); +__Z_EXPORT ssize_t __read_ds_file(int fd, void *buf, size_t count); +__Z_EXPORT off_t __lseek_ds_file(int fd, off_t offset, int whence); #if defined(__cplusplus) } @@ -32,11 +36,20 @@ __Z_EXPORT int __sysconf(int name); #define sysconf __sysconf_replaced #undef readlink #define readlink __readlink_replaced +#undef write +#define write __write_replaced +#undef read +#define read __read_replaced +#undef lseek +#define lseek __lseek_replaced #include_next #undef pipe #undef close #undef sysconf #undef readlink +#undef write +#undef read +#undef lseek #if defined(__cplusplus) extern "C" { @@ -46,10 +59,11 @@ extern "C" { */ __Z_EXPORT int pipe(int [2]) __asm("__pipe_ascii"); __Z_EXPORT int close(int) __asm("__close"); -__Z_EXPORT int close(int) __asm("__close"); __Z_EXPORT int sysconf(int name) __asm("__sysconf"); __Z_EXPORT ssize_t readlink(const char *path, char *buf, size_t bufsiz) __asm("__readlink"); - +__Z_EXPORT ssize_t write(int fd, const void *buf, size_t count) __asm("__write_ds_file"); +__Z_EXPORT ssize_t read(int fd, void *buf, size_t count) __asm("__read_ds_file"); +__Z_EXPORT off_t lseek(int fd, off_t offset, int whence) __asm("__lseek_ds_file"); #if defined(__cplusplus) } diff --git a/include/zos-base.h b/include/zos-base.h index 8030d523..226faf9a 100644 --- a/include/zos-base.h +++ b/include/zos-base.h @@ -66,6 +66,7 @@ extern "builtin" void *_gdsa(); #define MEMORY_USAGE_LOG_FILE_ENVAR_DEFAULT "__MEMORY_USAGE_LOG_FILE" #define MEMORY_USAGE_LOG_LEVEL_ENVAR_DEFAULT "__MEMORY_USAGE_LOG_LEVEL" #define MEMORY_USAGE_LOG_INC_ENVAR_DEFAULT "__MEMORY_USAGE_LOG_INC" +#define DATASET_SUPPORT_ENVAR_DEFAULT "ZOSLIB_DATASET_SUPPORT" typedef enum { __NO_TAG_READ_DEFAULT = 0, @@ -74,6 +75,11 @@ typedef enum { __NO_TAG_READ_STRICT = 3 } notagread_t; +typedef enum { + DS_SUPPORT_YES = 0, + DS_SUPPORT_NO = 1 +} ds_support_mode_t; + struct timespec; extern const char *__zoslib_version; @@ -506,6 +512,10 @@ typedef struct __Z_EXPORT zoslib_config { * allocated, in bytes, after which logging occurs. */ const char *MEMORY_USAGE_LOG_INC_ENVAR = MEMORY_USAGE_LOG_INC_ENVAR_DEFAULT; + /** + * String to indicate the envar to be used to toggle the dataset support mode. + */ + const char *DATASET_SUPPORT_ENVAR = DATASET_SUPPORT_ENVAR_DEFAULT; } zoslib_config_t; @@ -553,6 +563,15 @@ typedef struct __Z_EXPORT zoslib_config { * to display when memory is allocated or freed. */ const char *MEMORY_USAGE_LOG_LEVEL_ENVAR; + /** + * String to indicate the envar to be used to specify the increase in memory + * allocated, in bytes, after which logging occurs. + */ + const char *MEMORY_USAGE_LOG_INC_ENVAR; + /** + * String to indicate the envar to be used to toggle the dataset support mode. + */ + const char *DATASET_SUPPORT_ENVAR; } zoslib_config_t; /** @@ -667,6 +686,7 @@ struct zoslibEnvar { class __zinit { int mode; int cvstate; + ds_support_mode_t ds_support_mode; std::terminate_handler _th; public: @@ -678,6 +698,7 @@ class __zinit { ~__zinit(); int initialize(const zoslib_config_t &config); + ds_support_mode_t get_ds_support_mode(void) const { return ds_support_mode; } bool isValidZOSLIBEnvar(std::string envar); int setEnvarHelpMap(void); void populateLEFunctionPointers(void); diff --git a/include/zos-datasetio.h b/include/zos-datasetio.h new file mode 100644 index 00000000..6d948e9d --- /dev/null +++ b/include/zos-datasetio.h @@ -0,0 +1,511 @@ +#ifndef __DATASET_IO__ +#define __DATASET_IO__ 1 + +#if defined(__cplusplus) +extern "C" { +#endif + +#include +#include +#include +#include +#include +#include + +#ifndef __ssize_t + #define __ssize_t 1 + typedef signed long ssize_t; +#endif +#ifndef __size_t + #define __size_t 1 + typedef unsigned long size_t; +#endif +#ifndef __mode_t + #define __mode_t 1 + typedef int mode_t ; +#endif + +int open_dataset(const char* name, int flags, mode_t mode); +int close_dataset(int fd); +int mkstemp_dataset(char* tmplate); +ssize_t write_dataset(int fd, const void* buf, size_t count); +ssize_t read_dataset(int fd, void* buf, size_t count); +off_t lseek_dataset(int fd, off_t offset, int whence); +int stat_dataset(const char* pathname, struct stat *statbuf); +int fstat_dataset(int fd, struct stat *statbuf); + +/* + * The following functions are only required for testing, + * not for the shared library. + * + * Datasets are always in the form: //'' or //'()' + */ +char* temp_file_name(char* result, size_t len); +char* temp_dataset_name(char* result, size_t len); +int allocate_dataset(const char* dataset); +int delete_dataset(const char* dataset); + +/* ======================================================================== + * ERROR HANDLING + * ======================================================================== */ + +/* Simplified error codes grouped by category */ +typedef enum { + DSIO_SUCCESS = 0, + DSIO_ERR_INVALID_PARAM, /* Invalid name, fd, recfm, dsorg, name too long */ + DSIO_ERR_IO_FAILED, /* Open, read, write, close, seek, tell failures */ + DSIO_ERR_RESOURCE, /* Allocation failed, buffer overflow, record too long */ + DSIO_ERR_UNSUPPORTED, /* Unsupported operations, CCSID conversion not implemented */ + DSIO_ERR_INTERNAL /* Internal errors, unexpected conditions */ +} dsio_error_t; + +/* Legacy error code aliases for backward compatibility */ +#define DSIO_ERR_INVALID_NAME DSIO_ERR_INVALID_PARAM +#define DSIO_ERR_NAME_TOO_LONG DSIO_ERR_INVALID_PARAM +#define DSIO_ERR_INVALID_FD DSIO_ERR_INVALID_PARAM +#define DSIO_ERR_INVALID_RECFM DSIO_ERR_INVALID_PARAM +#define DSIO_ERR_INVALID_DSORG DSIO_ERR_INVALID_PARAM +#define DSIO_ERR_NOT_A_DATASET DSIO_ERR_INVALID_PARAM +#define DSIO_ERR_OPEN_FAILED DSIO_ERR_IO_FAILED +#define DSIO_ERR_READ_FAILED DSIO_ERR_IO_FAILED +#define DSIO_ERR_WRITE_FAILED DSIO_ERR_IO_FAILED +#define DSIO_ERR_CLOSE_FAILED DSIO_ERR_IO_FAILED +#define DSIO_ERR_FSEEK_FAILED DSIO_ERR_IO_FAILED +#define DSIO_ERR_FTELL_FAILED DSIO_ERR_IO_FAILED +#define DSIO_ERR_FLDATA_FAILED DSIO_ERR_IO_FAILED +#define DSIO_ERR_MEMBER_NOT_FOUND DSIO_ERR_IO_FAILED +#define DSIO_ERR_ALLOC_FAILED DSIO_ERR_RESOURCE +#define DSIO_ERR_BUFFER_OVERFLOW DSIO_ERR_RESOURCE +#define DSIO_ERR_RECORD_TOO_LONG DSIO_ERR_RESOURCE +#define DSIO_ERR_CCSID_CONVERSION DSIO_ERR_UNSUPPORTED +#define DSIO_ERR_UNSUPPORTED_OPERATION DSIO_ERR_UNSUPPORTED +#define DSIO_ERR_INTERNAL_ERROR DSIO_ERR_INTERNAL + +/* Get error message for error code */ +const char* dsio_strerror(dsio_error_t error); + +/* Get last error for a file descriptor */ +dsio_error_t dsio_get_last_error(int fd); + +/* Get detailed error message for a file descriptor */ +const char* dsio_get_error_message(int fd); + +/* Clear error state for a file descriptor */ +void dsio_clear_error(int fd); + +/* ======================================================================== + * DATASET METADATA + * ======================================================================== */ + +/* Record formats */ +typedef enum { + DSIO_RECFM_UNKNOWN = 0, + DSIO_RECFM_F = 1, /* Fixed */ + DSIO_RECFM_V = 2, /* Variable */ + DSIO_RECFM_U = 3, /* Undefined */ + DSIO_RECFM_FB = 4, /* Fixed Blocked */ + DSIO_RECFM_VB = 5, /* Variable Blocked */ + DSIO_RECFM_FBA = 6, /* Fixed Blocked ASA */ + DSIO_RECFM_VBA = 7, /* Variable Blocked ASA */ + DSIO_RECFM_FA = 8, + DSIO_RECFM_VA = 9 +} dsio_recfm_t; + +/* Dataset organizations */ +typedef enum { + DSIO_DSORG_UNKNOWN = 0, + DSIO_DSORG_PS = 1, /* Physical Sequential */ + DSIO_DSORG_PO = 2, /* Partitioned (PDS) */ + DSIO_DSORG_POE = 3, /* Partitioned Extended (PDSE) */ + DSIO_DSORG_DA = 4, /* Direct Access */ + DSIO_DSORG_VS = 5 /* VSAM */ +} dsio_dsorg_t; + +/* Dataset metadata structure */ +typedef struct { + dsio_recfm_t recfm; /* Record format */ + dsio_dsorg_t dsorg; /* Dataset organization */ + size_t reclen; /* Logical record length */ + size_t blksize; /* Block size */ + uint16_t file_ccsid; /* File CCSID */ + uint16_t program_ccsid; /* Program CCSID */ + char member_name[9]; /* Member name (if PDS/PDSE) */ + char hlq[9]; /* High-level qualifier */ + char llq[9]; /* Low-level qualifier */ + int is_pds_member; /* 1 if this is a PDS member */ + int readonly; /* 1 if read-only */ +} dsio_metadata_t; + +/* Get dataset metadata */ +int dsio_get_metadata(int fd, dsio_metadata_t* metadata); + +/* Get specific metadata fields */ +int dsio_get_recfm(int fd, dsio_recfm_t* recfm); +int dsio_get_lrecl(int fd, size_t* reclen); +int dsio_get_dsorg(int fd, dsio_dsorg_t* dsorg); +int dsio_get_ccsid(int fd, uint16_t* file_ccsid, uint16_t* program_ccsid); +int dsio_get_member_name(int fd, char* member, size_t len); +int dsio_get_hlq(int fd, char* hlq, size_t len); +int dsio_get_llq(int fd, char* llq, size_t len); + +/* Check dataset properties */ +int dsio_is_pds(int fd); +int dsio_is_pdse(int fd); +int dsio_is_sequential(int fd); +int dsio_has_member(int fd); +int dsio_is_readonly(int fd); + +/* ======================================================================== + * RECORD FORMAT UTILITIES + * ======================================================================== */ + +/* Check if record format has length prefix */ +int dsio_has_length_prefix(dsio_recfm_t recfm); + +/* Check if record format is blocked */ +int dsio_is_blocked(dsio_recfm_t recfm); + +/* Check if record format has ASA control characters */ +int dsio_has_asa(dsio_recfm_t recfm); + +/* Convert record format to string */ +const char* dsio_recfm_to_string(dsio_recfm_t recfm); + +/* Convert dataset organization to string */ +const char* dsio_dsorg_to_string(dsio_dsorg_t dsorg); + +/* Parse record format from string */ +dsio_recfm_t dsio_string_to_recfm(const char* str); + +/* Parse dataset organization from string */ +dsio_dsorg_t dsio_string_to_dsorg(const char* str); + +/* ======================================================================== + * CCSID CONVERSION + * ======================================================================== */ + +/* CCSID configuration */ +typedef struct { + uint16_t source_ccsid; + uint16_t target_ccsid; + int auto_detect; /* Auto-detect source CCSID */ + int conversion_enabled; /* Enable/disable conversion */ +} dsio_ccsid_config_t; + +/* Set CCSID configuration for a file descriptor */ +int dsio_set_ccsid_config(int fd, const dsio_ccsid_config_t* config); + +/* Get CCSID configuration for a file descriptor */ +int dsio_get_ccsid_config(int fd, dsio_ccsid_config_t* config); + +/* Convert buffer between CCSIDs (standalone utility) */ +void* dsio_convert_buffer(void* buf, size_t len, + uint16_t from_ccsid, + uint16_t to_ccsid); + +/* ======================================================================== + * DATASET NAME PARSING + * ======================================================================== */ + +/* Dataset name components */ +typedef struct { + char full_name[55]; /* Full dataset name */ + char hlq[9]; /* High-level qualifier */ + char mlqs[45]; /* Mid-level qualifiers */ + char llq[9]; /* Low-level qualifier */ + char member[9]; /* Member name (if any) */ + int has_member; /* 1 if member specified */ + int is_quoted; /* 1 if name was quoted */ +} dsio_name_parts_t; + +/* Parse dataset name into components */ +int dsio_parse_dataset_name(const char* name, dsio_name_parts_t* parts); + +/* Validate dataset name */ +int dsio_validate_dataset_name(const char* name); + +/* Check if name is a dataset (starts with //) */ +int dsio_is_dataset_name(const char* name); + +/* ======================================================================== + * LOGGING AND DEBUGGING + * ======================================================================== */ + +/* Log levels */ +typedef enum { + DSIO_LOG_NONE = 0, + DSIO_LOG_ERROR, + DSIO_LOG_WARN, + DSIO_LOG_INFO, + DSIO_LOG_DEBUG, + DSIO_LOG_TRACE +} dsio_log_level_t; + +/* Set global log level */ +void dsio_set_log_level(dsio_log_level_t level); + +/* Set log output stream */ +void dsio_set_log_stream(FILE* stream); + +/* Enable/disable debug mode */ +void dsio_enable_debug(int enable); + +/* Log a message (internal use) */ +void dsio_log(dsio_log_level_t level, const char* format, ...); + + +/* ======================================================================== + * UTILITY FUNCTIONS + * ======================================================================== */ + +/* Get maximum record length for a dataset */ +int dsio_get_max_reclen(int fd); + +/* Check if dataset is empty */ +int dsio_is_empty(int fd); + +/* Get dataset size in bytes */ +ssize_t dsio_get_size(int fd); + +/* Flush any pending writes */ +int dsio_flush(int fd); + +/* ======================================================================== + * CONSTANTS + * ======================================================================== */ + +/* Maximum lengths */ +#define DSIO_MAX_DATASET_NAME 54 +#define DSIO_MAX_MEMBER_NAME 8 +#define DSIO_MAX_QUALIFIER 8 +#define DSIO_MAX_ERROR_MSG 256 + +/* Special CCSID values */ +#define DSIO_CCSID_BINARY (-1) +#define DSIO_CCSID_EBCDIC 1047 +#define DSIO_CCSID_ASCII 819 +#define DSIO_CCSID_UTF8 1208 + +/* ======================================================================== + * INTERNAL STRUCTURES AND MACROS + * ======================================================================== */ + +/* In z/OS, the 32nd bit of a 64-bit address is never on */ +/* We can use this as a safe way to distinguish between a 64-bit pointer to a 'dataset descriptor' */ +/* and a 31-bit (unsigned) bit file descriptor (an invalid descriptor will not be stored in the table */ + +#define MAX_FDS 1024 +#define INV_ADDR_BIT (0x0000000080000000ULL) + +/* Thread-safe descriptor table access */ +#include + +extern pthread_mutex_t descriptor_table_mutex; +extern void* descriptor_table[MAX_FDS]; + +/* Thread-safe helper functions for descriptor table access */ +static inline void add_fd_safe(int fd) { + pthread_mutex_lock(&descriptor_table_mutex); + if (fd >= 0 && fd < MAX_FDS) { + descriptor_table[fd] = ((void*)(((unsigned long long)(fd)) | INV_ADDR_BIT)); + } + pthread_mutex_unlock(&descriptor_table_mutex); +} + +static inline void add_dd_safe(int fd, void* dd) { + pthread_mutex_lock(&descriptor_table_mutex); + if (fd >= 0 && fd < MAX_FDS) { + descriptor_table[fd] = dd; + } + pthread_mutex_unlock(&descriptor_table_mutex); +} + +static inline void* get_dd_safe(int fd) { + void* result = NULL; + pthread_mutex_lock(&descriptor_table_mutex); + if (fd >= 0 && fd < MAX_FDS) { + result = descriptor_table[fd]; + } + pthread_mutex_unlock(&descriptor_table_mutex); + return result; +} + +static inline void clear_dd_safe(int fd) { + pthread_mutex_lock(&descriptor_table_mutex); + if (fd >= 0 && fd < MAX_FDS) { + descriptor_table[fd] = NULL; + } + pthread_mutex_unlock(&descriptor_table_mutex); +} + +static inline int is_valid_slot_safe(int slot) { + int valid = 0; + pthread_mutex_lock(&descriptor_table_mutex); + if (slot >= 0 && slot < MAX_FDS && descriptor_table[slot] != NULL) { + valid = 1; + } + pthread_mutex_unlock(&descriptor_table_mutex); + return valid; +} + +static inline int is_fd_safe(int slot) { + int is_fd = 0; + pthread_mutex_lock(&descriptor_table_mutex); + if (slot >= 0 && slot < MAX_FDS && descriptor_table[slot] != NULL && + ((((unsigned long long)(descriptor_table[slot])) & INV_ADDR_BIT) != 0)) { + is_fd = 1; + } + pthread_mutex_unlock(&descriptor_table_mutex); + return is_fd; +} + +static inline int is_dd_safe(int slot) { + int is_dd = 0; + pthread_mutex_lock(&descriptor_table_mutex); + if (slot >= 0 && slot < MAX_FDS && descriptor_table[slot] != NULL && + ((((unsigned long long)(descriptor_table[slot])) & INV_ADDR_BIT) == 0)) { + is_dd = 1; + } + pthread_mutex_unlock(&descriptor_table_mutex); + return is_dd; +} + +/* Simplified macros using helper functions */ +#define ADD_FD(fd) add_fd_safe(fd) +#define ADD_DD(fd,dd) add_dd_safe(fd, dd) +#define GET_DD(fd) get_dd_safe(fd) +#define CLEAR_DD(fd) clear_dd_safe(fd) +#define IS_VALID_SLOT(slot) is_valid_slot_safe(slot) +#define IS_FD(slot) is_fd_safe(slot) +#define IS_DD(slot) is_dd_safe(slot) + +typedef struct DatasetEntry { + /* Original fields */ + FILE* file_ptr; + unsigned short file_ccsid; + unsigned short program_ccsid; + unsigned char conversion_state; + + /* Error tracking */ + dsio_error_t last_error; + char error_message[DSIO_MAX_ERROR_MSG]; + + /* Dataset metadata */ + dsio_recfm_t recfm; + dsio_dsorg_t dsorg; + size_t reclen; + size_t blksize; + + /* Dataset name components */ + char full_path[DSIO_MAX_DATASET_NAME + 1]; + char member_name[DSIO_MAX_MEMBER_NAME + 1]; + char hlq[DSIO_MAX_QUALIFIER + 1]; + char llq[DSIO_MAX_QUALIFIER + 1]; + int is_pds_member; + int readonly; + + /* Record buffer for stream emulation */ + char* rec_buf; /* internal record I/O buffer */ + size_t rec_buf_size; /* allocated size (= blksize or reclen) */ + size_t rec_buf_len; /* valid bytes currently in buffer */ + size_t rec_buf_pos; /* current read position within buffer */ + size_t stream_offset; /* virtual byte offset for lseek */ + int newline_pending; /* 1 if we need to emit \n before next record */ + int is_fixed_recfm; /* 1 if FB/FBS - use binary I/O, not type=record */ + int open_flags; /* original O_RDONLY/O_WRONLY/O_RDWR flags */ + int dirty; /* 1 if buffer has pending writes */ + int eof_reached; /* 1 if we reached physical EOF */ + + /* Size caching */ + int size_calculated; /* 1 if emulated size has been calculated */ + size_t cached_size; /* Cached emulated size */ +} DatasetEntry; + + + +#define GET_DUMMY_FD(flags) (open("/dev/null", (flags) & (O_RDONLY | O_WRONLY | O_RDWR), 0)) +#define IS_DATASET(name) ((name) && ((name)[0] == '/') && ((name)[1] == '/')) + +/* Enable/disable logging - set to 1 to enable log_* calls */ +#ifndef ZOSLIB_DATASET_LOGGING + #define ZOSLIB_DATASET_LOGGING 1 +#endif + +#if ZOSLIB_DATASET_LOGGING + /* Simplified logging macros using centralized dsio_log() function */ + #define DSIO_LOG_ERROR(fmt, ...) dsio_log(DSIO_LOG_ERROR, fmt, ##__VA_ARGS__) + #define DSIO_LOG_WARN(fmt, ...) dsio_log(DSIO_LOG_WARN, fmt, ##__VA_ARGS__) + #define DSIO_LOG_INFO(fmt, ...) dsio_log(DSIO_LOG_INFO, fmt, ##__VA_ARGS__) + #define DSIO_LOG_DEBUG(fmt, ...) dsio_log(DSIO_LOG_DEBUG, fmt, ##__VA_ARGS__) + #define DSIO_LOG_TRACE(fmt, ...) dsio_log(DSIO_LOG_TRACE, fmt, ##__VA_ARGS__) +#else /* ZOSLIB_DATASET_LOGGING == 0 */ + #define DSIO_LOG_ERROR(fmt, ...) ((void)0) + #define DSIO_LOG_WARN(fmt, ...) ((void)0) + #define DSIO_LOG_INFO(fmt, ...) ((void)0) + #define DSIO_LOG_DEBUG(fmt, ...) ((void)0) + #define DSIO_LOG_TRACE(fmt, ...) ((void)0) +#endif /* ZOSLIB_DATASET_LOGGING */ + +/* ======================================================================== + * ENHANCED INTERNAL STRUCTURES + * ======================================================================== */ + +extern void* descriptor_table[MAX_FDS]; + + + +/* Global state */ +extern dsio_log_level_t g_log_level; +extern FILE* g_log_stream; +extern int g_debug_enabled; + +/* ======================================================================== + * INTERNAL HELPER FUNCTIONS + * ======================================================================== */ + +/* Create dataset entry */ +DatasetEntry* create_entry(FILE* fp, unsigned short file_ccsid); + +/* Free dataset entry */ +void free_entry(DatasetEntry* entry); + +/* Parse dataset name and extract components */ +int parse_and_store_name(DatasetEntry* entry, const char* dataset_name); + +/* Set error on entry */ +void set_entry_error(DatasetEntry* entry, dsio_error_t error, const char* message); + + +/* Logging helpers */ +void log_error(const char* format, ...); +void log_warn(const char* format, ...); +void log_info(const char* format, ...); +void log_debug(const char* format, ...); +void log_trace(const char* format, ...); +void* convert_ebcdic_to_ascii(void* buf, size_t len); +void* convert_ascii_to_ebcdic(void* buf, size_t len); + +/* Dataset name validation */ +int validate_dataset_name_internal(const char* name); +int extract_member_name(const char* name, char* member, size_t len); +int extract_qualifiers(const char* name, char* hlq, char* llq, size_t len); + +/* Utility macros */ +#define ENTRY_TO(entry) ((DatasetEntry*)(entry)) + +/* Error message templates */ +#define ERR_MSG_INVALID_NAME "Invalid dataset name: %s" +#define ERR_MSG_OPEN_FAILED "Failed to open dataset: %s" +#define ERR_MSG_READ_FAILED "Failed to read from dataset" +#define ERR_MSG_WRITE_FAILED "Failed to write to dataset" +#define ERR_MSG_CLOSE_FAILED "Failed to close dataset" +#define ERR_MSG_ALLOC_FAILED "Memory allocation failed" +#define ERR_MSG_INVALID_FD "Invalid file descriptor: %d" +#define ERR_MSG_FLDATA_FAILED "fldata() failed for dataset" +#define ERR_MSG_CCSID_CONV "CCSID conversion failed: %d -> %d" + +#if defined(__cplusplus) +} +#endif + +#endif /* __DATASET_IO__ */ diff --git a/include/zos.h b/include/zos.h index f89c5fee..231eb3f2 100644 --- a/include/zos.h +++ b/include/zos.h @@ -21,5 +21,6 @@ #include "zos-base.h" #include "zos-semaphore.h" #include "zos-sys-info.h" +#include "zos-datasetio.h" #endif // ZOS_H_ diff --git a/man/zoslib.1 b/man/zoslib.1 index b7b1b4f2..f3df7f9c 100644 --- a/man/zoslib.1 +++ b/man/zoslib.1 @@ -55,6 +55,14 @@ name of the log file associated with __MEMORY_USAGE_LOG_LEVEL, including 'stdout .B __RUNDEBUG set to toggle debug ZOSLIB mode +.TP +.B ZOSLIB_DATASET_SUPPORT +controls dataset I/O support at runtime. Set to NO or 0 to disable dataset I/O operations, or YES or 1 to enable. When enabled, POSIX file operations (open, read, write, etc.) can work with z/OS datasets using the //'DATASET.NAME' syntax. Dataset I/O is disabled by default at runtime; set this variable to YES or 1 to enable. + +.TP +.B ZOSLIB_DEBUG +set to enable debug logging for dataset I/O operations when __DATASET_SUPPORT is enabled. Logs are written to stderr and include detailed information about dataset operations, record processing, and error conditions. + .SH EXAMPLES To set the __UNTAGGED_READ_MODE environment variable to STRICT and disable explicit conversion of data: @@ -68,6 +76,34 @@ To set the STDOUT CCSID to 819 (ASCII): This will cause ZOSLIB to tag the stdout file descriptor to 819 and enable auto-conversion. +To enable dataset I/O support at runtime: + +.B export ZOSLIB_DATASET_SUPPORT=YES + +This will enable dataset I/O operations, allowing POSIX file operations to work with z/OS datasets. + +To disable dataset I/O support at runtime: + +.B export ZOSLIB_DATASET_SUPPORT=NO + +This will disable dataset I/O operations, causing POSIX file operations to only work with USS files. + +To enable debug logging for dataset I/O operations: + +.B export ZOSLIB_DEBUG=1 + +.B cat //'MY.DATASET' + +This will enable detailed logging of dataset operations to stderr, useful for troubleshooting dataset I/O issues. + +To read from a z/OS dataset using standard tools: + +.B cat //'USER.DATA.SET' + +.B grep "pattern" //'SYS1.PARMLIB(IEASYS00)' + +These commands will work with z/OS datasets when dataset I/O support is enabled (default). + .SH AUTHOR Written by Igor Todorovski. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 92484c1e..365eb76e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -29,6 +29,7 @@ set(libsrc zos-time.c zos-filesystem.cc zos-fts.c + zos-datasetio.c ) set(zoslib-help zoslib-help.cc) diff --git a/src/zos-datasetio.c b/src/zos-datasetio.c new file mode 100644 index 00000000..195f69f6 --- /dev/null +++ b/src/zos-datasetio.c @@ -0,0 +1,1921 @@ +#define _XOPEN_SOURCE_EXTENDED 1 +#define _EXT 1 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "zos-datasetio.h" + +extern FILE *__fopen_orig(const char *filename, const char *mode) __asm("@@A00246"); + +/* __close_orig is defined in zos-io.cc as the original close() syscall. + * We need it in close_dataset to close the dummy /dev/null fd without + * routing through our __close() override (which would cause reentrancy). + */ +extern int __close_orig(int) __asm("close"); + +/* Thread-safe descriptor table */ +void* descriptor_table[MAX_FDS] = { 0 }; +pthread_mutex_t descriptor_table_mutex = PTHREAD_MUTEX_INITIALIZER; + +static dsio_recfm_t detect_recfm_from_fldata(const fldata_t* fdata); +static int map_dsio_error_to_errno(dsio_error_t dsio_err); + +static dsio_dsorg_t detect_dsorg_from_fldata(const fldata_t* fdata); +static int flush_rec_buf(DatasetEntry* dentry); + +/* + * cleanup_dataset_resources - Centralized cleanup for error paths + * + * Ensures all resources are properly freed in the correct order. + * Safe to call with NULL pointers - will skip cleanup for NULL values. + * + * @param dentry: DatasetEntry to free (will free rec_buf and dentry itself) + * @param fp: FILE* to close (if different from dentry->file_ptr) + * @param fd: File descriptor to close (if >= 0) + */ +static void cleanup_dataset_resources(DatasetEntry* dentry, FILE* fp, int fd) { + /* Close file descriptor first (if valid and in range) */ + if (fd >= 0 && fd < MAX_FDS) { + __close_orig(fd); + } + + /* Close FILE* if provided and different from dentry's file_ptr */ + if (fp && (!dentry || fp != dentry->file_ptr)) { + fclose(fp); + } + + /* Free DatasetEntry and its resources */ + if (dentry) { + /* Close dentry's FILE* if not already closed */ + if (dentry->file_ptr && dentry->file_ptr != fp) { + fclose(dentry->file_ptr); + } + + /* Free record buffer */ + if (dentry->rec_buf) { + free(dentry->rec_buf); + dentry->rec_buf = NULL; + } + + /* Free the entry itself */ + free(dentry); + } +} + +static const char DATASET_CHAR[] = "ABCDEFGHIJKLMNOPQRSTUVWYZ$#@"; + +#define DATASET_CHAR_LEN (sizeof(DATASET_CHAR)-1) + +static char* generate_name(char* tmplate) +{ + struct timeval tv; + unsigned long long us; + int start=0; + int end=strlen(tmplate); + int index; + + for (start=0; start start) { + if (tmplate[end] == 'X') { + break; + } + --end; + } + if (end - start + 1 < 3) { /* at least 3 X's required in the tmplate */ + errno = EINVAL; + return NULL; + } + + gettimeofday(&tv, NULL); + us = ((unsigned long long) (tv.tv_sec)) * 1000000UL + ((unsigned long long) (tv.tv_usec)); + + for (index=start; index<=end; ++index) { + int entry = us % DATASET_CHAR_LEN; + us /= DATASET_CHAR_LEN; + tmplate[index] = DATASET_CHAR[entry]; + } + return tmplate; +} + +/* + * create_dataset_fd - Open a dataset, create a DatasetEntry, register it, and return an fd. + * + * Handles the full lifecycle: fopen, fldata query, FB/FBS reopen optimization, + * buffer allocation, and DD table registration. + * Returns a valid fd on success, -1 on failure (all resources cleaned up). + */ +int create_dataset_fd(const char* name, unsigned short file_ccsid, int flags) +{ + /* + * Performance recommendations per IBM z/OS documentation: + * https://www.ibm.com/docs/en/zos/3.1.0?topic=considerations-accessing-mvs-data-sets + * + * We initially open with type=record to query attributes via fldata(). + * For FB/FBS datasets, we then reopen in plain binary mode for + * multi-record I/O. For V/U, we keep type=record. + */ + int accmode = flags & O_ACCMODE; + const char* fopen_mode; + if (accmode == O_RDONLY) { + fopen_mode = "rb,type=record,recfm=+"; + } else if (accmode == O_WRONLY) { + fopen_mode = (flags & O_APPEND) ? "ab,type=record,recfm=+,noseek" : "wb,type=record,recfm=+,noseek"; + } else if (accmode == O_RDWR) { + fopen_mode = (flags & O_APPEND) ? "ab+,type=record,recfm=+" : "rb+,type=record,recfm=+"; + } else { + fopen_mode = "rb,type=record,recfm=+"; + } + + DSIO_LOG_DEBUG("Open with mode %s\n", fopen_mode); + FILE* dd = __fopen_orig(name, fopen_mode); + if (!dd) { + perror("dataset open failed"); + errno = EIO; + return -1; + } + + DatasetEntry* dentry = create_entry(dd, file_ccsid); + if (!dentry) { + cleanup_dataset_resources(NULL, dd, -1); + errno = ENOMEM; + return -1; + } + + if (parse_and_store_name(dentry, name) != 0) { + cleanup_dataset_resources(dentry, NULL, -1); + errno = EINVAL; + return -1; + } + + dentry->open_flags = flags; + dentry->conversion_state = SETCVTON; /* Default to conversion on */ + + /* + * Query dataset attributes via fldata(). + * IBM recommendation: "If you are using FB or FBS files, use binary I/O + * instead of record I/O. This way, you can read or write more than one + * record at a time." + */ + fldata_t fld; + if (fldata(dd, NULL, &fld) == 0) { + dentry->recfm = detect_recfm_from_fldata(&fld); + dentry->reclen = fld.__maxreclen > 0 ? fld.__maxreclen : 80; + dentry->blksize = fld.__blksize > 0 ? fld.__blksize : dentry->reclen; + dentry->is_fixed_recfm = (fld.__recfmF && !fld.__recfmV && !fld.__recfmU) ? 1 : 0; + DSIO_LOG_DEBUG("open_dataset: fldata attributes - recfm=%s, reclen=%zu, blksize=%zu, is_fixed=%d\n", + dsio_recfm_to_string(dentry->recfm), dentry->reclen, dentry->blksize, dentry->is_fixed_recfm); + } else { + /* Default to FB80 if fldata fails */ + dentry->recfm = DSIO_RECFM_FB; + dentry->reclen = 80; + dentry->blksize = 80; + dentry->is_fixed_recfm = 1; + } + + /* + * For FB/FBS datasets, reopen in plain binary mode (without type=record) + * so we can read/write multiple records per fread/fwrite call. + */ + if (dentry->is_fixed_recfm) { + fclose(dd); + dd = NULL; + const char* reopen_mode; + if (accmode == O_RDONLY) { + reopen_mode = "rb,recfm=+"; + } else if (accmode == O_WRONLY) { + reopen_mode = (flags & O_APPEND) ? "ab,recfm=+,noseek" : "wb,recfm=+,noseek"; + } else if (accmode == O_RDWR) { + reopen_mode = (flags & O_APPEND) ? "ab+,recfm=+" : "rb+,recfm=+"; + } else { + reopen_mode = "rb,recfm=+"; + } + DSIO_LOG_DEBUG("FB optimization: reopening with mode %s\n", reopen_mode); + dd = __fopen_orig(name, reopen_mode); + if (!dd) { + set_entry_error(dentry, DSIO_ERR_OPEN_FAILED, "Failed to reopen dataset in binary mode"); + cleanup_dataset_resources(dentry, NULL, -1); + errno = map_dsio_error_to_errno(DSIO_ERR_OPEN_FAILED); + return -1; + } + dentry->file_ptr = dd; + } + + /* Optimization: Since we are already buffering in rec_buf to handle record boundaries, + * disabling C runtime buffering (_IONBF) avoids an unnecessary internal memcpy inside fwrite. + */ + setvbuf(dentry->file_ptr, NULL, _IONBF, 0); + + /* + * Buffer sizing: + * - For FB: use blksize to enable multi-record I/O + * - For V/U: use maxreclen (one record at a time via type=record) + */ + dentry->rec_buf_size = dentry->is_fixed_recfm ? dentry->blksize : dentry->reclen; + + /* Allocate record buffer (+1 for potential null terminator during conversion) */ + dentry->rec_buf = malloc(dentry->rec_buf_size + 1); + if (!dentry->rec_buf) { + set_entry_error(dentry, DSIO_ERR_ALLOC_FAILED, "Failed to allocate record buffer"); + cleanup_dataset_resources(dentry, NULL, -1); + errno = map_dsio_error_to_errno(DSIO_ERR_ALLOC_FAILED); + return -1; + } + + int fd = GET_DUMMY_FD(flags); + if (fd < 0 || fd >= MAX_FDS) { + DSIO_LOG_DEBUG("create_dataset_fd: ERROR - GET_DUMMY_FD failed or fd out of range (fd=%d, max=%d)\n", fd, MAX_FDS); + set_entry_error(dentry, DSIO_ERR_INTERNAL_ERROR, "Failed to allocate or track file descriptor"); + cleanup_dataset_resources(dentry, NULL, fd); + errno = (fd >= MAX_FDS) ? EMFILE : map_dsio_error_to_errno(DSIO_ERR_INTERNAL_ERROR); + return -1; + } + DSIO_LOG_DEBUG("create_dataset_fd: Assigned dummy fd %d\n", fd); + ADD_DD(fd, dentry); + return fd; +} + +int mkstemp_dataset(char* tmplate) +{ + /* Without fopen(...,"wx" support, there is a race condition that can only be fixed by coding (maybe) in assembler */ + /* So... this code could overwrite another (probably temporary) file name */ + char* name = generate_name(tmplate); + if (!name) { + return -1; + } + + /* Delegate to create_dataset_fd which handles the full lifecycle: + * fopen, fldata query, FB reopen, buffer allocation, and DD registration. + */ + return create_dataset_fd(tmplate, 1047, O_RDWR); +} + +int open_dataset(const char* name, int flags, mode_t mode) +{ + /* Start with support for the following 'access mode' flags: + * O_RDONLY, O_WRONLY, O_RDWR + * Start with support for the following 'file creation flags' + * O_APPEND O_TRUNC + * In particular, O_CREAT is _not_ supported since the dataset + * is presumed to already exist. + * Use fopen, mkstemp, or other C services to allocate datasets + * + * Define the modes carefully so the dataset will not get created if it + * does not already exist - we want this to be a failure + * https://tech.mikefulton.ca/fopen_pdse_member + */ + /* + * Validate flags before delegating to create_dataset_fd, + * which handles opening, attribute detection, and registration. + */ + bool pds_member = strchr(name, '('); + int accmode = flags & O_ACCMODE; + DSIO_LOG_DEBUG("open_dataset: name %s, flags %d, mode %d\n", name, flags, mode); + + if ((flags & O_APPEND) && (pds_member)) { + DSIO_LOG_DEBUG("open_dataset: O_APPEND not supported for PDS member %s\n", name); + errno = EINVAL; + return -1; + } + + /* O_ACCMODE check: O_RDONLY is 0, O_WRONLY is 1, O_RDWR is 2 */ + if (accmode != O_RDONLY && accmode != O_WRONLY && accmode != O_RDWR) { + DSIO_LOG_DEBUG("open_dataset: Invalid access mode in flags %d\n", flags); + errno = EINVAL; + return -1; + } + + /* Strip flags that we don't support but shouldn't fail on */ + flags &= ~(O_LARGEFILE | O_NONBLOCK); + + if (flags & O_CREAT) { + if (pds_member) { + /* This is ok - the fopen will inherit the PDS(E) attributes for the open and + * so we can safely ignore that they asked for 'CREAT' + */ + ; + } else { + /* No support for sequential datasets or DDNames being created through open + * of a dataset at this point + */ + DSIO_LOG_DEBUG("open_dataset: O_CREAT not supported for non-PDS member %s\n", name); + errno = EINVAL; + return -1; + } + } + + int fd = create_dataset_fd(name, 1047, flags); + DSIO_LOG_DEBUG("open_dataset: create_dataset_fd returned fd %d\n", fd); + return fd; +} + +ssize_t write_dataset(int fd, const void* buf, size_t count) +{ + if (!IS_DD(fd)) { + errno = EBADF; + return -1; + } + void* dd = GET_DD(fd); + + DatasetEntry* dentry = (DatasetEntry*) (dd); + + /* Clear any previous errors */ + dentry->last_error = DSIO_SUCCESS; + + FILE* fp = dentry->file_ptr; + + DSIO_LOG_DEBUG("write_dataset: fd=%d count=%zu ccsid=%d->%d conv=%d\n", + fd, count, dentry->program_ccsid, dentry->file_ccsid, dentry->conversion_state); + + const char* src = (const char*) buf; + size_t total_written = 0; + size_t pos = 0; + + while (pos < count) { + /* How many bytes can we fit in the current record? */ + size_t space_in_rec = dentry->reclen - dentry->rec_buf_pos; + size_t remaining = count - pos; + size_t scan_len = (remaining < space_in_rec) ? remaining : space_in_rec; + + /* Look for newline in the current available record space */ + const char* nl = memchr(src + pos, '\n', scan_len); + + if (nl) { + /* Copy everything up to the newline */ + size_t to_copy = nl - (src + pos); + if (to_copy > 0) { + memcpy(dentry->rec_buf + dentry->rec_buf_pos, src + pos, to_copy); + dentry->rec_buf_pos += to_copy; + } + + /* Record finalized by newline - flush it */ + if (dentry->is_fixed_recfm) { + /* Pad FB record to reclen with spaces */ + while (dentry->rec_buf_pos < dentry->reclen) { + dentry->rec_buf[dentry->rec_buf_pos++] = ' '; + } + } + + if (dentry->rec_buf_pos > 0) { + if (dentry->conversion_state == SETCVTON) { + dsio_convert_buffer(dentry->rec_buf, dentry->rec_buf_pos, + dentry->program_ccsid, dentry->file_ccsid); + } + + if (fwrite(dentry->rec_buf, 1, dentry->rec_buf_pos, fp) != dentry->rec_buf_pos) { + set_entry_error(dentry, DSIO_ERR_WRITE_FAILED, "Failed to write record to dataset"); + errno = map_dsio_error_to_errno(DSIO_ERR_WRITE_FAILED); + return total_written > 0 ? (ssize_t) total_written : -1; + } + dentry->rec_buf_pos = 0; + dentry->dirty = 0; + } else if (!dentry->is_fixed_recfm) { + /* + * For variable records, write a record with a single space to represent a blank line, + * as fwrite with length 0 is often a no-op on z/OS record I/O. + */ + char space = ' '; + fwrite(&space, 1, 1, fp); + } + + pos += to_copy + 1; /* skip the data and the newline */ + total_written += to_copy + 1; + } else { + /* No newline found in this record's space - copy what we can */ + size_t to_copy = scan_len; + if (to_copy > 0) { + memcpy(dentry->rec_buf + dentry->rec_buf_pos, src + pos, to_copy); + dentry->rec_buf_pos += to_copy; + dentry->dirty = 1; + } + + pos += to_copy; + total_written += to_copy; + + /* If record is full (no newline), flush it as a complete record */ + if (dentry->rec_buf_pos >= dentry->reclen) { + if (dentry->conversion_state == SETCVTON) { + dsio_convert_buffer(dentry->rec_buf, dentry->rec_buf_pos, + dentry->program_ccsid, dentry->file_ccsid); + } + + if (fwrite(dentry->rec_buf, 1, dentry->rec_buf_pos, fp) != dentry->rec_buf_pos) { + set_entry_error(dentry, DSIO_ERR_WRITE_FAILED, "Failed to write full record to dataset"); + errno = map_dsio_error_to_errno(DSIO_ERR_WRITE_FAILED); + return total_written > 0 ? (ssize_t) total_written : -1; + } + dentry->rec_buf_pos = 0; + dentry->dirty = 0; + } + } + } + + dentry->stream_offset += total_written; + /* Invalidate cached size since content has changed */ + if (total_written > 0) { + dentry->size_calculated = 0; + } + return (ssize_t) total_written; +} + +ssize_t read_dataset(int fd, void* buf, size_t count) +{ + if (!IS_DD(fd)) { + errno = EBADF; + return -1; + } + void* dd = GET_DD(fd); + + DatasetEntry* dentry = (DatasetEntry*) (dd); + + /* Clear any previous errors */ + dentry->last_error = DSIO_SUCCESS; + FILE* fp = dentry->file_ptr; + + /* If there are pending writes, flush them before reading. + * We bypass C runtime buffering with _IONBF and do our own buffering in rec_buf. + */ + if (dentry->dirty) { + if (flush_rec_buf(dentry) != 0) { + errno = map_dsio_error_to_errno(DSIO_ERR_WRITE_FAILED); + return -1; + } + /* Reset read state for switch to read mode */ + dentry->rec_buf_pos = 0; + dentry->rec_buf_len = 0; + } + + DSIO_LOG_DEBUG("read_dataset: fd=%d count=%zu offset=%zu recfm=%s\n", + fd, count, dentry->stream_offset, + dsio_recfm_to_string(dentry->recfm)); + + char* dst = (char*) buf; + size_t bytes_copied = 0; + + while (bytes_copied < count) { + /* Fast-path: Emit pending newline */ + if (dentry->newline_pending) { + dst[bytes_copied++] = '\n'; + dentry->newline_pending = 0; + dentry->stream_offset++; + continue; + } + + /* Fast-path: Serve from existing buffer data using memcpy */ + if (dentry->rec_buf_pos < dentry->rec_buf_len) { + size_t avail = dentry->rec_buf_len - dentry->rec_buf_pos; + + /* Record boundaries for fixed-format: insert newline after each LRECL */ + if (dentry->is_fixed_recfm && dentry->reclen > 0) { + size_t pos_in_stream = dentry->stream_offset % (dentry->reclen + 1); + size_t rec_avail = dentry->reclen - pos_in_stream; + if (avail > rec_avail) avail = rec_avail; + } + + size_t to_copy = (count - bytes_copied < avail) ? (count - bytes_copied) : avail; + memcpy(dst + bytes_copied, dentry->rec_buf + dentry->rec_buf_pos, to_copy); + + dentry->rec_buf_pos += to_copy; + bytes_copied += to_copy; + dentry->stream_offset += to_copy; + + /* Check for record boundary after copying */ + if (dentry->is_fixed_recfm && dentry->reclen > 0) { + if ((dentry->stream_offset % (dentry->reclen + 1)) == dentry->reclen) { + dentry->newline_pending = 1; + } + } else if (dentry->rec_buf_pos >= dentry->rec_buf_len) { + dentry->newline_pending = 1; + } + continue; + } + + /* Slow-path: Read next block/record from dataset */ + if (dentry->eof_reached) { + if (dentry->newline_pending) continue; + break; + } + + size_t rc = fread(dentry->rec_buf, 1, dentry->rec_buf_size, fp); + DSIO_LOG_DEBUG("read_dataset: fread returned %zu, buf_size=%zu, feof=%d, ferror=%d\n", + rc, dentry->rec_buf_size, feof(fp), ferror(fp)); + if (rc == 0) { + if (ferror(fp)) { + set_entry_error(dentry, DSIO_ERR_READ_FAILED, "Failed to read from dataset"); + errno = map_dsio_error_to_errno(DSIO_ERR_READ_FAILED); + return bytes_copied > 0 ? (ssize_t)bytes_copied : -1; + } + dentry->eof_reached = 1; + break; /* Will return bytes_copied if > 0, else 0 for EOF */ + } + + /* Convert CCSID of the newly read data in-place */ + if (dentry->conversion_state == SETCVTON && rc > 0) { + dsio_convert_buffer(dentry->rec_buf, rc, dentry->file_ccsid, dentry->program_ccsid); + } + + dentry->rec_buf_len = rc; + dentry->rec_buf_pos = 0; + } + + return (bytes_copied == 0 && count > 0) ? 0 : (ssize_t) bytes_copied; +} + +int close_dataset(int fd) +{ + if (!IS_DD(fd)) { + errno = EBADF; + return -1; + } + void* dd = GET_DD(fd); + + DatasetEntry* dentry = (DatasetEntry*) (dd); + + /* Clear any previous errors */ + dentry->last_error = DSIO_SUCCESS; + + FILE* fp = dentry->file_ptr; + + /* Flush any partial record remaining in the write buffer */ + if (dentry->dirty) { + flush_rec_buf(dentry); + } + + int rc = fclose(fp); + if (rc != 0) { + set_entry_error(dentry, DSIO_ERR_CLOSE_FAILED, "fclose() failed"); + errno = map_dsio_error_to_errno(DSIO_ERR_CLOSE_FAILED); + } + + /* Free record buffer and deallocate DatasetEntry */ + if (dentry->rec_buf) { + free(dentry->rec_buf); + } + free(dentry); + /* Clear the DD entry BEFORE closing the dummy fd to avoid reentrancy: + * close(fd) routes through __close() which would call close_dataset() + * again if CLEAR_DD hasn't been called yet. + */ + CLEAR_DD(fd); + __close_orig(fd); + + return rc; +} + +char* temp_dataset_name(char* result, size_t len) +{ + char* orig = getenv("__POSIX_TMPNAM"); + char temp[L_tmpnam+1]; + setenv("__POSIX_TMPNAM", "NO", 1); + tmpnam(temp); + if (orig) + setenv("__POSIX_TMPNAM", orig, 1); + else + unsetenv("__POSIX_TMPNAM"); + snprintf(result, len, "//'%s'", temp); + return result; +} + +char* temp_file_name(char* result, size_t len) +{ + char* orig = getenv("__POSIX_TMPNAM"); + setenv("__POSIX_TMPNAM", "YES", 1); + tmpnam(result); + if (orig) + setenv("__POSIX_TMPNAM", orig, 1); + else + unsetenv("__POSIX_TMPNAM"); + return result; +} + +/* + * Input dataset name should be of the form: //'' + */ +int allocate_dataset(const char* dataset) +{ + __dyn_t ip; + char sysdd[] = "????????"; + int rca=0; + int rcb=0; + + dyninit(&ip); + + char mvs_style_dataset[45]; + size_t dataset_len = strlen(dataset); + + if (dataset_len > 48) { + return -1; + } + if (dataset[0] != '/' || dataset[1] != '/' || dataset[2] != '\'' || dataset[dataset_len-1] != '\'') { + return -1; + } + memcpy(mvs_style_dataset, &dataset[3], dataset_len-4); + mvs_style_dataset[dataset_len-4] = '\0'; + + DSIO_LOG_DEBUG("Allocate dataset: %s\n", mvs_style_dataset); + + ip.__ddname = sysdd; + ip.__dsname = mvs_style_dataset; + ip.__status = __DISP_NEW; + ip.__normdisp = __DISP_CATLG; + ip.__dsorg = __DSORG_PO; + ip.__recfm = _FB_; + ip.__lrecl = 80; + ip.__dsntype = __DSNT_LIBRARY; + + ip.__primary = 100; + ip.__secondary = 10; + ip.__alcunit = __TRK; + + rca = dynalloc(&ip); + if (!rca) { + rcb = dynfree(&ip); + } + if (rca | rcb) { + fprintf(stderr, "Dataset allocation error. Dynalloc or Dynfree failed with error code %d, info code %d\n", ip.__errcode, ip.__infocode); + } + + return rca | rcb; +} + +int delete_dataset(const char* dataset) +{ + int rc = remove(dataset); + if (rc) { + perror("remove"); + } + return rc; +} + +int zos_fcntl(int fd, int cmd, struct f_cnvrt* req) +{ + /* Only F_CONTROL_CVT is supported for dataset fds */ + if (cmd == F_CONTROL_CVT) { + if (!req || fd < 0) + return -1; + + if (!IS_DD(fd)) { + errno = EBADF; + return -1; + } + + DatasetEntry* dentry = (DatasetEntry*) GET_DD(fd); + + if (req->cvtcmd == QUERYCVT) { + req->pccsid = dentry->program_ccsid; + req->fccsid = dentry->file_ccsid; + req->cvtcmd = dentry->conversion_state; + } else { + dentry->program_ccsid = req->pccsid; + dentry->file_ccsid = req->fccsid; + dentry->conversion_state = req->cvtcmd; + } + return 0; + } + + errno = EINVAL; + return -1; +} + + +/* ======================================================================== + * lseek() - File Positioning + * ======================================================================== */ + +off_t lseek_dataset(int fd, off_t offset, int whence) { + DSIO_LOG_DEBUG("lseek_dataset: ENTER fd=%d, offset=%lld, whence=%d\n", fd, (long long)offset, whence); + + /* Validate fd */ + void* dd = GET_DD(fd); + if (!dd) { + errno = EBADF; + DSIO_LOG_DEBUG("lseek_dataset: ERROR - EBADF (NULL dd) for fd %d\n", fd); + return (off_t)-1; + } + + DatasetEntry* dentry = (DatasetEntry*) dd; + + /* Flush any pending writes before seeking */ + if (dentry->dirty) { + if (flush_rec_buf(dentry) != 0) { + errno = map_dsio_error_to_errno(DSIO_ERR_WRITE_FAILED); + return (off_t)-1; + } + } + + /* Clear any previous errors and EOF state */ + dentry->last_error = DSIO_SUCCESS; + dentry->eof_reached = 0; + if (!dentry->file_ptr) { + errno = EBADF; + DSIO_LOG_DEBUG("lseek_dataset: ERROR - EBADF (NULL file_ptr) for fd %d\n", fd); + return (off_t)-1; + } + FILE* fp = dentry->file_ptr; + + /* Calculate target position */ + off_t target; + switch (whence) { + case SEEK_SET: + target = offset; + break; + + case SEEK_CUR: + target = (off_t)dentry->stream_offset + offset; + break; + + case SEEK_END: { + /* Use dsio_get_size for accurate size calculation */ + ssize_t file_size = dsio_get_size(fd); + if (file_size < 0) { + set_entry_error(dentry, DSIO_ERR_FSEEK_FAILED, "Failed to get file size for SEEK_END"); + errno = map_dsio_error_to_errno(DSIO_ERR_FSEEK_FAILED); + return (off_t)-1; + } + target = (off_t)file_size + offset; + break; + } + + default: + errno = EINVAL; + return (off_t)-1; + } + + /* Validate target */ + if (target < 0) { + errno = EINVAL; + return (off_t)-1; + } + + /* Check if already at target */ + if ((size_t)target == dentry->stream_offset) { + return (off_t)target; + } + + /* Handle backward seek */ + if ((size_t)target < dentry->stream_offset) { + rewind(fp); + dentry->stream_offset = 0; + dentry->rec_buf_pos = 0; + dentry->rec_buf_len = 0; + dentry->newline_pending = 0; + } + + /* Handle forward seek */ + if (dentry->is_fixed_recfm && dentry->reclen > 0) { + /* FB: Optimize by calculating native position directly */ + size_t total_records = (size_t)target / (dentry->reclen + 1); + size_t byte_in_record = (size_t)target % (dentry->reclen + 1); + + /* Handle seeking to newline position */ + if (byte_in_record == dentry->reclen) { + dentry->newline_pending = 1; + byte_in_record = 0; + total_records++; + } else { + dentry->newline_pending = 0; + } + + long target_native = (long)(total_records * dentry->reclen + byte_in_record); + + DSIO_LOG_DEBUG("lseek_dataset: FB target=%zu, records=%zu, byte=%zu, native=%ld, newline=%d\n", + (size_t)target, total_records, byte_in_record, target_native, dentry->newline_pending); + + if (fseek(fp, target_native, SEEK_SET) != 0) { + set_entry_error(dentry, DSIO_ERR_FSEEK_FAILED, "fseek() failed during lseek"); + errno = map_dsio_error_to_errno(DSIO_ERR_FSEEK_FAILED); + return (off_t)-1; + } + + dentry->stream_offset = (size_t)target; + dentry->rec_buf_pos = 0; + dentry->rec_buf_len = 0; + + } else { + /* VB/U: Must read and discard (no optimization possible) */ + while (dentry->stream_offset < (size_t)target) { + char discard[4096]; /* Larger buffer for efficiency */ + size_t need = (size_t)target - dentry->stream_offset; + size_t chunk = need < sizeof(discard) ? need : sizeof(discard); + ssize_t n = read_dataset(fd, discard, chunk); + if (n < 0) { + /* Read error occurred */ + return (off_t)-1; + } else if (n == 0) { + /* EOF reached before target - update offset to target to match standard lseek behavior */ + dentry->stream_offset = (size_t)target; + break; + } + } + } + + DSIO_LOG_DEBUG("lseek_dataset: fd=%d target=%lld final=%zu\n", + fd, (long long)target, dentry->stream_offset); + + return (off_t)dentry->stream_offset; +} + +/* ======================================================================== + * fstat() / stat() - File Metadata + * ======================================================================== */ + + +int fstat_dataset(int fd, struct stat *buf) { + DSIO_LOG_DEBUG("fstat_dataset: ENTER fd=%d\n", fd); + + /* Validate parameters */ + if (!buf) { + errno = EINVAL; + DSIO_LOG_DEBUG("fstat_dataset: ERROR - NULL buffer for fd %d\n", fd); + return -1; + } + + void* dd = GET_DD(fd); + if (!dd) { + errno = EBADF; + DSIO_LOG_DEBUG("fstat_dataset: ERROR - EBADF (NULL dd) for fd %d\n", fd); + return -1; + } + + DatasetEntry* dentry = (DatasetEntry*) dd; + + /* Clear any previous errors */ + dentry->last_error = DSIO_SUCCESS; + if (!dentry->file_ptr) { + errno = EBADF; + DSIO_LOG_DEBUG("fstat_dataset: ERROR - EBADF (NULL file_ptr) for fd %d\n", fd); + return -1; + } + + /* Initialize stat buffer */ + memset(buf, 0, sizeof(struct stat)); + + /* + * Provide non-zero st_dev and st_ino to prevent tools (like ggrep) from + * incorrectly assuming all datasets are the same file (since st_dev=0, st_ino=0 + * is often returned for both). + */ + buf->st_dev = 0xFFFF; /* Magic value for MVS Datasets */ + + /* Simple Jenkins hash for st_ino based on the dataset name */ + uint32_t hash = 0; + const char* key = dentry->full_path; + while (*key) { + hash += (unsigned char)(*key++); + hash += (hash << 10); + hash ^= (hash >> 6); + } + hash += (hash << 3); + hash ^= (hash >> 11); + hash += (hash << 15); + buf->st_ino = (ino_t)(hash ? hash : 1); + + buf->st_mode = S_IFREG | 0666; + buf->st_nlink = 1; + buf->st_uid = getuid(); + buf->st_gid = getgid(); + buf->st_blksize = dentry->blksize; + + /* Set timestamps to current time */ + time_t now = time(NULL); + buf->st_atime = now; + buf->st_mtime = now; + buf->st_ctime = now; + + /* Calculate emulated stream size. For VB/U datasets, this requires + * reading all records (one-time cost, cached afterward). If size + * cannot be determined (e.g. U-format with no seek support), return + * st_size=0 so callers like `tail` can fall back to sequential reading + * rather than failing entirely. + */ + ssize_t size = dsio_get_size(fd); + if (size < 0) { + DSIO_LOG_DEBUG("fstat_dataset: dsio_get_size failed (fd=%d), returning st_size=0\n", fd); + size = 0; + } + + buf->st_size = (off_t)size; + + DSIO_LOG_DEBUG("fstat_dataset: fd=%d size=%lld\n", fd, (long long)buf->st_size); + + return 0; +} + +int stat_dataset(const char *pathname, struct stat *statbuf) { + DSIO_LOG_DEBUG("stat_dataset: ENTER path=%s\n", pathname); + if (!pathname || !statbuf) { + errno = EINVAL; + return -1; + } + + /* Open the dataset temporarily to get stats */ + int fd = open_dataset(pathname, O_RDONLY, 0); + if (fd < 0) { + DSIO_LOG_DEBUG("stat_dataset: open_dataset failed for %s, errno=%d\n", pathname, errno); + return -1; + } + + int result = fstat_dataset(fd, statbuf); + int save_errno = errno; + close_dataset(fd); + if (result != 0) errno = save_errno; + + DSIO_LOG_DEBUG("stat_dataset: RETURN %d for %s\n", result, pathname); + return result; +} + + + + +/* Global state */ + +dsio_log_level_t g_log_level = DSIO_LOG_DEBUG; +FILE* g_log_stream = NULL; +int g_debug_enabled = 0; + +/* ======================================================================== + * ERROR HANDLING IMPLEMENTATION + * Adapted from libdio's comprehensive error handling + * ======================================================================== */ + +static const char* error_messages[] = { + [DSIO_SUCCESS] = "Success", + [DSIO_ERR_INVALID_PARAM] = "Invalid parameter (name, fd, recfm, dsorg, etc.)", + [DSIO_ERR_IO_FAILED] = "I/O operation failed (open, read, write, close, seek, etc.)", + [DSIO_ERR_RESOURCE] = "Resource error (allocation, buffer overflow, record too long)", + [DSIO_ERR_UNSUPPORTED] = "Unsupported operation or feature", + [DSIO_ERR_INTERNAL] = "Internal error" +}; + +const char* dsio_strerror(dsio_error_t error) { + if (error >= 0 && error < sizeof(error_messages)/sizeof(error_messages[0])) { + return error_messages[error]; + } + return "Unknown error"; +} + +dsio_error_t dsio_get_last_error(int fd) { + void* dd = GET_DD(fd); + if (!dd || IS_FD(fd)) { + return DSIO_ERR_INVALID_FD; + } + + DatasetEntry* entry = ENTRY_TO(dd); + return entry->last_error; +} + +const char* dsio_get_error_message(int fd) { + void* dd = GET_DD(fd); + if (!dd || IS_FD(fd)) { + return "Invalid file descriptor"; + } + + DatasetEntry* entry = ENTRY_TO(dd); + return entry->error_message; +} + +void dsio_clear_error(int fd) { + void* dd = GET_DD(fd); + if (!dd || IS_FD(fd)) { + return; + } + + DatasetEntry* entry = ENTRY_TO(dd); + entry->last_error = DSIO_SUCCESS; + entry->error_message[0] = '\0'; +} + +/* Map DSIO error codes to POSIX errno values */ +static int map_dsio_error_to_errno(dsio_error_t dsio_err) { + switch (dsio_err) { + case DSIO_SUCCESS: + return 0; + case DSIO_ERR_INVALID_PARAM: + return EINVAL; + case DSIO_ERR_IO_FAILED: + return EIO; + case DSIO_ERR_RESOURCE: + return ENOMEM; + case DSIO_ERR_UNSUPPORTED: + return ENOTSUP; + case DSIO_ERR_INTERNAL: + default: + return EIO; + } +} + +void set_entry_error(DatasetEntry* entry, dsio_error_t error, const char* message) { + if (!entry) return; + + entry->last_error = error; + if (message) { + strncpy(entry->error_message, message, DSIO_MAX_ERROR_MSG - 1); + entry->error_message[DSIO_MAX_ERROR_MSG - 1] = '\0'; + } else { + strncpy(entry->error_message, dsio_strerror(error), DSIO_MAX_ERROR_MSG - 1); + entry->error_message[DSIO_MAX_ERROR_MSG - 1] = '\0'; + } + + DSIO_LOG_ERROR("Error %d: %s", error, entry->error_message); + +} + +/* ======================================================================== + * RECORD FORMAT UTILITIES + * Adapted from libdio's record format handling + * ======================================================================== */ + +int dsio_has_length_prefix(dsio_recfm_t recfm) { + return (recfm == DSIO_RECFM_V || + recfm == DSIO_RECFM_VB || + recfm == DSIO_RECFM_VBA); +} + +int dsio_is_blocked(dsio_recfm_t recfm) { + return (recfm == DSIO_RECFM_FB || + recfm == DSIO_RECFM_VB || + recfm == DSIO_RECFM_FBA || + recfm == DSIO_RECFM_VBA); +} + +int dsio_has_asa(dsio_recfm_t recfm) { + return (recfm == DSIO_RECFM_FBA || + recfm == DSIO_RECFM_VBA); +} + +const char* dsio_recfm_to_string(dsio_recfm_t recfm) { + switch (recfm) { + case DSIO_RECFM_F: return "F"; + case DSIO_RECFM_V: return "V"; + case DSIO_RECFM_U: return "U"; + case DSIO_RECFM_FB: return "FB"; + case DSIO_RECFM_VB: return "VB"; + case DSIO_RECFM_FBA: return "FBA"; + case DSIO_RECFM_VBA: return "VBA"; + case DSIO_RECFM_FA: return "FA"; + case DSIO_RECFM_VA: return "VA"; + default: return "UNKNOWN"; + } +} + +const char* dsio_dsorg_to_string(dsio_dsorg_t dsorg) { + switch (dsorg) { + case DSIO_DSORG_PS: return "PS"; + case DSIO_DSORG_PO: return "PO"; + case DSIO_DSORG_POE: return "POE"; + case DSIO_DSORG_DA: return "DA"; + case DSIO_DSORG_VS: return "VS"; + default: return "UNKNOWN"; + } +} + +dsio_recfm_t dsio_string_to_recfm(const char* str) { + if (!str) return DSIO_RECFM_UNKNOWN; + + if (strcmp(str, "F") == 0) return DSIO_RECFM_F; + if (strcmp(str, "V") == 0) return DSIO_RECFM_V; + if (strcmp(str, "U") == 0) return DSIO_RECFM_U; + if (strcmp(str, "FB") == 0) return DSIO_RECFM_FB; + if (strcmp(str, "VB") == 0) return DSIO_RECFM_VB; + if (strcmp(str, "FBA") == 0) return DSIO_RECFM_FBA; + if (strcmp(str, "VBA") == 0) return DSIO_RECFM_VBA; + + return DSIO_RECFM_UNKNOWN; +} + +dsio_dsorg_t dsio_string_to_dsorg(const char* str) { + if (!str) return DSIO_DSORG_UNKNOWN; + + if (strcmp(str, "PS") == 0) return DSIO_DSORG_PS; + if (strcmp(str, "PO") == 0) return DSIO_DSORG_PO; + if (strcmp(str, "POE") == 0) return DSIO_DSORG_POE; + if (strcmp(str, "DA") == 0) return DSIO_DSORG_DA; + if (strcmp(str, "VS") == 0) return DSIO_DSORG_VS; + + return DSIO_DSORG_UNKNOWN; +} + +/* ======================================================================== + * DATASET NAME PARSING + * Adapted from libdio's dataset name handling + * ======================================================================== */ + +int dsio_is_dataset_name(const char* name) { + if (!name || strlen(name) < 2) { + return 0; + } + return (name[0] == '/' && name[1] == '/'); +} + +int dsio_validate_dataset_name(const char* name) { + if (!dsio_is_dataset_name(name)) { + return 0; + } + + size_t len = strlen(name); + if (len > DSIO_MAX_DATASET_NAME) { + return 0; + } + + /* Check for valid characters and structure */ + int in_quotes = 0; + int in_member = 0; + + for (size_t i = 2; i < len; i++) { + char c = name[i]; + + if (c == '\'') { + in_quotes = !in_quotes; + } else if (c == '(' && !in_quotes) { + in_member = 1; + } else if (c == ')' && !in_quotes) { + in_member = 0; + } else if (!in_quotes && !in_member) { + /* Outside quotes and member: allow alphanumeric, $, #, @, . */ + if (!isalnum(c) && c != '$' && c != '#' && c != '@' && c != '.') { + return 0; + } + } + } + + return 1; +} + +int dsio_parse_dataset_name(const char* name, dsio_name_parts_t* parts) { + if (!name || !parts) { + return -1; + } + + memset(parts, 0, sizeof(dsio_name_parts_t)); + + if (!dsio_validate_dataset_name(name)) { + return -1; + } + + /* Copy full name */ + strncpy(parts->full_name, name, sizeof(parts->full_name) - 1); + + /* Skip // prefix */ + const char* ds_start = name + 2; + + /* Check for quotes */ + if (*ds_start == '\'') { + parts->is_quoted = 1; + ds_start++; + } + + /* Find member name if present */ + const char* member_start = strchr(ds_start, '('); + const char* ds_end = member_start ? member_start : (ds_start + strlen(ds_start)); + + if (member_start) { + parts->has_member = 1; + const char* member_end = strchr(member_start, ')'); + if (member_end) { + size_t member_len = member_end - member_start - 1; + if (member_len > 0 && member_len <= DSIO_MAX_MEMBER_NAME) { + strncpy(parts->member, member_start + 1, member_len); + parts->member[member_len] = '\0'; + } + } + } + + /* Parse qualifiers */ + char ds_name[DSIO_MAX_DATASET_NAME]; + size_t ds_len = ds_end - ds_start; + if (ds_len >= sizeof(ds_name)) { + return -1; + } + strncpy(ds_name, ds_start, ds_len); + ds_name[ds_len] = '\0'; + + /* Remove trailing quote if present */ + if (parts->is_quoted && ds_len > 0 && ds_name[ds_len-1] == '\'') { + ds_name[ds_len-1] = '\0'; + ds_len--; + } + + /* Split into qualifiers */ + char* first_dot = strchr(ds_name, '.'); + char* last_dot = strrchr(ds_name, '.'); + + if (first_dot) { + /* Extract HLQ */ + size_t hlq_len = first_dot - ds_name; + if (hlq_len <= DSIO_MAX_QUALIFIER) { + strncpy(parts->hlq, ds_name, hlq_len); + parts->hlq[hlq_len] = '\0'; + } + + /* Extract LLQ */ + if (last_dot && last_dot != first_dot) { + strncpy(parts->llq, last_dot + 1, DSIO_MAX_QUALIFIER); + parts->llq[DSIO_MAX_QUALIFIER] = '\0'; + + /* Extract MLQs */ + size_t mlq_start = first_dot - ds_name + 1; + size_t mlq_len = last_dot - first_dot - 1; + if (mlq_len > 0 && mlq_len < sizeof(parts->mlqs)) { + strncpy(parts->mlqs, ds_name + mlq_start, mlq_len); + parts->mlqs[mlq_len] = '\0'; + } + } else { + /* Only two qualifiers */ + strncpy(parts->llq, first_dot + 1, DSIO_MAX_QUALIFIER); + parts->llq[DSIO_MAX_QUALIFIER] = '\0'; + } + } else { + /* Single qualifier - treat as HLQ */ + strncpy(parts->hlq, ds_name, DSIO_MAX_QUALIFIER); + parts->hlq[DSIO_MAX_QUALIFIER] = '\0'; + } + + return 0; +} + +/* ======================================================================== + * CCSID CONVERSION + * Adapted from libdio's CCSID handling + * ======================================================================== */ + +void* dsio_convert_buffer(void* buf, size_t len, + uint16_t from_ccsid, + uint16_t to_ccsid) { + if (!buf || len == 0) { + return buf; + } + + if (from_ccsid == to_ccsid) { + return buf; + } + + if (from_ccsid == DSIO_CCSID_EBCDIC && to_ccsid == DSIO_CCSID_ASCII) { + return convert_ebcdic_to_ascii(buf, len); + } else if (from_ccsid == DSIO_CCSID_ASCII && to_ccsid == DSIO_CCSID_EBCDIC) { + return convert_ascii_to_ebcdic(buf, len); + } + + DSIO_LOG_WARN("Unsupported CCSID conversion: %d -> %d", from_ccsid, to_ccsid); + return buf; +} + +void* convert_ebcdic_to_ascii(void* buf, size_t len) { + __e2a_l(buf, len); + return buf; +} + +void* convert_ascii_to_ebcdic(void* buf, size_t len) { + __a2e_l(buf, len); + return buf; +} + +/* ======================================================================== + * LOGGING IMPLEMENTATION + * Adapted from libdio's logging framework + * ======================================================================== */ + +void dsio_set_log_level(dsio_log_level_t level) { g_log_level = level; } +void dsio_set_log_stream(FILE* stream) { g_log_stream = stream; } + +void dsio_enable_debug(int enable) { + g_debug_enabled = enable; + if (enable) { + if (g_log_level < DSIO_LOG_DEBUG) g_log_level = DSIO_LOG_DEBUG; + if (!g_log_stream) { + char logpath[256]; + snprintf(logpath, sizeof(logpath), "/tmp/zoslib_dsio_%d.log", getpid()); + g_log_stream = fopen(logpath, "a"); + if (g_log_stream) fprintf(stderr, "DSIO: Logging to %s\n", logpath); + } + } +} + +void dsio_log(dsio_log_level_t level, const char* format, ...) { + if (!g_debug_enabled || level > g_log_level) return; + + static const char* levels[] = {"", "ERROR", "WARN ", "INFO ", "DEBUG", "TRACE"}; + const char* level_str = (level >= 0 && level < 6) ? levels[level] : "?????"; + + FILE* stream = g_log_stream ? g_log_stream : stderr; + fprintf(stream, "[%s] ", level_str); + + va_list args; + va_start(args, format); + vfprintf(stream, format, args); + va_end(args); + fflush(stream); +} + +extern void __console(const void *p_in, int len_i); + +void log_error(const char* format, ...) { + if (DSIO_LOG_ERROR > g_log_level) return; + + va_list args; + va_start(args, format); + FILE* stream = g_log_stream ? g_log_stream : stderr; + fprintf(stream, "[ERROR] "); + vfprintf(stream, format, args); + fflush(stream); + va_end(args); +} + +void log_warn(const char* format, ...) { + if (DSIO_LOG_WARN > g_log_level) return; + + va_list args; + va_start(args, format); + FILE* stream = g_log_stream ? g_log_stream : stderr; + fprintf(stream, "[WARN ] "); + vfprintf(stream, format, args); + fflush(stream); + va_end(args); +} + +void log_info(const char* format, ...) { + if (DSIO_LOG_INFO > g_log_level) return; + + va_list args; + va_start(args, format); + FILE* stream = g_log_stream ? g_log_stream : stderr; + fprintf(stream, "[INFO ] "); + vfprintf(stream, format, args); + fflush(stream); + va_end(args); +} + +void log_debug(const char* format, ...) { + if (DSIO_LOG_DEBUG > g_log_level) return; + + va_list args; + va_start(args, format); + FILE* stream = g_log_stream ? g_log_stream : stderr; + fprintf(stream, "[DEBUG] "); + vfprintf(stream, format, args); + fflush(stream); + va_end(args); +} + +void log_trace(const char* format, ...) { + if (DSIO_LOG_TRACE > g_log_level) return; + + va_list args; + va_start(args, format); + FILE* stream = g_log_stream ? g_log_stream : stderr; + fprintf(stream, "[TRACE] "); + vfprintf(stream, format, args); + fflush(stream); + va_end(args); +} + +/* ======================================================================== + * STATISTICS IMPLEMENTATION + * ======================================================================== */ + + +/* ======================================================================== + * METADATA IMPLEMENTATION (Partial - showing key functions) + * This would use fldata() to get actual dataset attributes + * ======================================================================== */ + +/* ======================================================================== + * HELPER FUNCTIONS IMPLEMENTATION + * ======================================================================== */ + +static dsio_recfm_t detect_recfm_from_fldata(const fldata_t* fdata) { + if (!fdata) return DSIO_RECFM_UNKNOWN; + + /* Check record format from fldata structure */ + if (fdata->__recfmF && fdata->__recfmBlk && fdata->__recfmASA) { + return DSIO_RECFM_FBA; + } else if (fdata->__recfmF && fdata->__recfmBlk) { + return DSIO_RECFM_FB; + } else if (fdata->__recfmF && fdata->__recfmASA) { + return DSIO_RECFM_FA; + } else if (fdata->__recfmF) { + return DSIO_RECFM_F; + } else if (fdata->__recfmV && fdata->__recfmBlk && fdata->__recfmASA) { + return DSIO_RECFM_VBA; + } else if (fdata->__recfmV && fdata->__recfmBlk) { + return DSIO_RECFM_VB; + } else if (fdata->__recfmV && fdata->__recfmASA) { + return DSIO_RECFM_VA; + } else if (fdata->__recfmV) { + return DSIO_RECFM_V; + } else if (fdata->__recfmU) { + return DSIO_RECFM_U; + } + + return DSIO_RECFM_UNKNOWN; +} + +static dsio_dsorg_t detect_dsorg_from_fldata(const fldata_t* fdata) { + if (!fdata) return DSIO_DSORG_UNKNOWN; + + /* Check dataset organization from fldata structure */ + if (fdata->__dsorgPO && fdata->__dsorgPDSE) { + return DSIO_DSORG_POE; + } else if (fdata->__dsorgPO) { + return DSIO_DSORG_PO; + } else if (fdata->__dsorgPS) { + return DSIO_DSORG_PS; + } else if (fdata->__dsorgVSAM) { + return DSIO_DSORG_VS; + } + + return DSIO_DSORG_UNKNOWN; +} + +static int flush_rec_buf(DatasetEntry* dentry) { + if (!dentry || !dentry->file_ptr || !dentry->dirty || dentry->rec_buf_pos == 0) { + return 0; + } + + FILE* fp = dentry->file_ptr; + + /* For FB datasets, pad final record to reclen with spaces */ + if (dentry->is_fixed_recfm) { + while (dentry->rec_buf_pos < dentry->reclen) { + dentry->rec_buf[dentry->rec_buf_pos++] = ' '; + } + } + + /* Convert CCSID before writing */ + if (dentry->conversion_state == 1 /* SETCVTON */) { + dsio_convert_buffer(dentry->rec_buf, dentry->rec_buf_pos, + dentry->program_ccsid, dentry->file_ccsid); + } + + /* Write record */ + if (fwrite(dentry->rec_buf, 1, dentry->rec_buf_pos, fp) != dentry->rec_buf_pos) { + set_entry_error(dentry, DSIO_ERR_WRITE_FAILED, "Failed to flush internal record buffer"); + return -1; + } + + dentry->rec_buf_pos = 0; + dentry->dirty = 0; + return 0; +} + +DatasetEntry* create_entry(FILE* fp, unsigned short file_ccsid) { + DatasetEntry* entry = calloc(1, sizeof(DatasetEntry)); + if (!entry) { + DSIO_LOG_ERROR("Failed to allocate DatasetEntry"); + return NULL; + } + + entry->file_ptr = fp; + entry->file_ccsid = file_ccsid; + entry->program_ccsid = 819; /* ASCII */ + entry->conversion_state = 1; /* SETCVTON */ + entry->last_error = DSIO_SUCCESS; + + /* Try to load metadata */ + if (fp) { + fldata_t fdata; + if (fldata(fp, NULL, &fdata) == 0) { + entry->recfm = detect_recfm_from_fldata(&fdata); + entry->dsorg = detect_dsorg_from_fldata(&fdata); + entry->reclen = fdata.__maxreclen; + entry->blksize = fdata.__blksize; + } + } + + return entry; +} + +void free_entry(DatasetEntry* entry) { + if (entry) { + free(entry); + } +} + +int parse_and_store_name(DatasetEntry* entry, const char* dataset_name) { + if (!entry || !dataset_name) { + return -1; + } + + dsio_name_parts_t parts; + if (dsio_parse_dataset_name(dataset_name, &parts) != 0) { + set_entry_error(entry, DSIO_ERR_INVALID_NAME, "Failed to parse dataset name"); + return -1; + } + + /* Store components */ + strncpy(entry->full_path, dataset_name, DSIO_MAX_DATASET_NAME); + entry->full_path[DSIO_MAX_DATASET_NAME] = '\0'; + + strncpy(entry->hlq, parts.hlq, DSIO_MAX_QUALIFIER); + entry->hlq[DSIO_MAX_QUALIFIER] = '\0'; + + strncpy(entry->llq, parts.llq, DSIO_MAX_QUALIFIER); + entry->llq[DSIO_MAX_QUALIFIER] = '\0'; + + if (parts.has_member) { + strncpy(entry->member_name, parts.member, DSIO_MAX_MEMBER_NAME); + entry->member_name[DSIO_MAX_MEMBER_NAME] = '\0'; + entry->is_pds_member = 1; + } else { + entry->member_name[0] = '\0'; + entry->is_pds_member = 0; + } + + return 0; +} + +/* ======================================================================== + * METADATA ACCESS FUNCTIONS + * ======================================================================== */ + +int dsio_get_metadata(int fd, dsio_metadata_t* metadata) { + if (!metadata) return -1; + + void* dd = GET_DD(fd); + if (!dd || IS_FD(fd)) { + return -1; + } + + DatasetEntry* entry = ENTRY_TO(dd); + + /* Copy metadata */ + metadata->recfm = entry->recfm; + metadata->dsorg = entry->dsorg; + metadata->reclen = entry->reclen; + metadata->blksize = entry->blksize; + metadata->file_ccsid = entry->file_ccsid; + metadata->program_ccsid = entry->program_ccsid; + metadata->is_pds_member = entry->is_pds_member; + metadata->readonly = entry->readonly; + + strncpy(metadata->member_name, entry->member_name, sizeof(metadata->member_name) - 1); + metadata->member_name[sizeof(metadata->member_name) - 1] = '\0'; + strncpy(metadata->hlq, entry->hlq, sizeof(metadata->hlq) - 1); + metadata->hlq[sizeof(metadata->hlq) - 1] = '\0'; + strncpy(metadata->llq, entry->llq, sizeof(metadata->llq) - 1); + metadata->llq[sizeof(metadata->llq) - 1] = '\0'; + + return 0; +} + +int dsio_get_recfm(int fd, dsio_recfm_t* recfm) { + if (!recfm) return -1; + + void* dd = GET_DD(fd); + if (!dd || IS_FD(fd)) { + return -1; + } + + DatasetEntry* entry = ENTRY_TO(dd); + *recfm = entry->recfm; + return 0; +} + +int dsio_get_lrecl(int fd, size_t* lrecl) { + if (!lrecl) return -1; + + void* dd = GET_DD(fd); + if (!dd || IS_FD(fd)) { + return -1; + } + + DatasetEntry* entry = ENTRY_TO(dd); + *lrecl = entry->reclen; + return 0; +} + +int dsio_get_dsorg(int fd, dsio_dsorg_t* dsorg) { + if (!dsorg) return -1; + + void* dd = GET_DD(fd); + if (!dd || IS_FD(fd)) { + return -1; + } + + DatasetEntry* entry = ENTRY_TO(dd); + *dsorg = entry->dsorg; + return 0; +} + +int dsio_get_ccsid(int fd, uint16_t* file_ccsid, uint16_t* program_ccsid) { + void* dd = GET_DD(fd); + if (!dd || IS_FD(fd)) { + return -1; + } + + DatasetEntry* entry = ENTRY_TO(dd); + + if (file_ccsid) { + *file_ccsid = entry->file_ccsid; + } + if (program_ccsid) { + *program_ccsid = entry->program_ccsid; + } + + return 0; +} + +int dsio_get_member_name(int fd, char* member, size_t len) { + if (!member || len == 0) return -1; + + void* dd = GET_DD(fd); + if (!dd || IS_FD(fd)) { + return -1; + } + + DatasetEntry* entry = ENTRY_TO(dd); + strncpy(member, entry->member_name, len - 1); + member[len - 1] = '\0'; + + return 0; +} + +int dsio_get_hlq(int fd, char* hlq, size_t len) { + if (!hlq || len == 0) return -1; + + void* dd = GET_DD(fd); + if (!dd || IS_FD(fd)) { + return -1; + } + + DatasetEntry* entry = ENTRY_TO(dd); + strncpy(hlq, entry->hlq, len - 1); + hlq[len - 1] = '\0'; + + return 0; +} + +int dsio_get_llq(int fd, char* llq, size_t len) { + if (!llq || len == 0) return -1; + + void* dd = GET_DD(fd); + if (!dd || IS_FD(fd)) { + return -1; + } + + DatasetEntry* entry = ENTRY_TO(dd); + strncpy(llq, entry->llq, len - 1); + llq[len - 1] = '\0'; + + return 0; +} + +/* ======================================================================== + * DATASET PROPERTY CHECKS + * ======================================================================== */ + +int dsio_is_pds(int fd) { + dsio_dsorg_t dsorg; + if (dsio_get_dsorg(fd, &dsorg) != 0) { + return 0; + } + return (dsorg == DSIO_DSORG_PO); +} + +int dsio_is_pdse(int fd) { + dsio_dsorg_t dsorg; + if (dsio_get_dsorg(fd, &dsorg) != 0) { + return 0; + } + return (dsorg == DSIO_DSORG_POE); +} + +int dsio_is_sequential(int fd) { + dsio_dsorg_t dsorg; + if (dsio_get_dsorg(fd, &dsorg) != 0) { + return 0; + } + return (dsorg == DSIO_DSORG_PS); +} + +int dsio_has_member(int fd) { + void* dd = GET_DD(fd); + if (!dd || IS_FD(fd)) { + return 0; + } + + DatasetEntry* entry = ENTRY_TO(dd); + return entry->is_pds_member; +} + +int dsio_is_readonly(int fd) { + void* dd = GET_DD(fd); + if (!dd || IS_FD(fd)) { + return 0; + } + + DatasetEntry* entry = ENTRY_TO(dd); + return entry->readonly; +} + +/* ======================================================================== + * UTILITY FUNCTIONS + * ======================================================================== */ + +int dsio_get_max_reclen(int fd) { + size_t lrecl; + if (dsio_get_lrecl(fd, &lrecl) != 0) { + return -1; + } + return (int)lrecl; +} + +int dsio_is_empty(int fd) { + ssize_t size = dsio_get_size(fd); + if (size < 0) return -1; + return (size == 0) ? 1 : 0; +} + +/* + * Helper function to calculate emulated stream size for VB datasets + * by reading all records and summing their lengths (with newlines). + * This is a one-time cost that gets cached. + */ +static ssize_t calculate_vb_emulated_size(FILE* fp, DatasetEntry* entry) { + size_t total_size = 0; + size_t rec_count = 0; + + if (!entry->is_fixed_recfm) { + /* VB/U: Already in type=record mode, save and restore position */ + fpos_t saved_pos; + if (fgetpos(fp, &saved_pos) != 0) { + set_entry_error(entry, DSIO_ERR_FTELL_FAILED, "fgetpos() failed in calculate_vb_emulated_size"); + return -1; + } + + /* rewind() == fseek(0,SEEK_SET) + clearerr(), more robust for record-mode files */ + rewind(fp); + if (ferror(fp)) { + set_entry_error(entry, DSIO_ERR_FSEEK_FAILED, "rewind() failed in calculate_vb_emulated_size"); + fsetpos(fp, &saved_pos); + return -1; + } + + /* Read each record, stripping trailing spaces, counting bytes+newline */ + /* Read each record */ + while (1) { + size_t rc = fread(entry->rec_buf, 1, entry->rec_buf_size, fp); + if (rc == 0) { + if (ferror(fp)) { + set_entry_error(entry, DSIO_ERR_READ_FAILED, "fread() failed in calculate_vb_emulated_size"); + fsetpos(fp, &saved_pos); + return -1; + } + break; /* EOF */ + } + + total_size += rc + 1; /* +1 for newline */ + rec_count++; + } + /* Restore position */ + if (fsetpos(fp, &saved_pos) != 0) { + DSIO_LOG_DEBUG("calculate_vb_emulated_size: WARNING - failed to restore file position\n"); + } + /* Overwrote buffer, so clear any pending read data */ + entry->rec_buf_len = 0; + entry->rec_buf_pos = 0; + } + + DSIO_LOG_DEBUG("calculate_vb_emulated_size: %zu bytes (%zu records)\n", total_size, rec_count); + + return (ssize_t)total_size; +} + +ssize_t dsio_get_size(int fd) { + void* dd = GET_DD(fd); + if (!dd || IS_FD(fd)) { + errno = EBADF; + return -1; + } + + DatasetEntry* entry = ENTRY_TO(dd); + if (!entry->file_ptr) { + errno = EBADF; + return -1; + } + + /* Return cached size if available */ + if (entry->size_calculated) { + return (ssize_t)entry->cached_size; + } + + FILE* fp = entry->file_ptr; + ssize_t emulated_size; + + if (entry->is_fixed_recfm && entry->reclen > 0) { + /* + * FB (binary mode): fseek(SEEK_END) is supported. + * Native size in bytes divided by reclen gives record count; + * add one newline per record for the emulated stream size. + */ + fpos_t pos; + if (fgetpos(fp, &pos) != 0) { + return -1; + } + if (fseek(fp, 0, SEEK_END) != 0) { + fsetpos(fp, &pos); + return -1; + } + long native_size = ftell(fp); + if (fsetpos(fp, &pos) != 0) { + DSIO_LOG_DEBUG("dsio_get_size: WARNING - fsetpos() failed\n"); + } + if (native_size < 0) { + return -1; + } + size_t num_records = (size_t)native_size / entry->reclen; + emulated_size = (ssize_t)(native_size + num_records); + DSIO_LOG_DEBUG("dsio_get_size: fd=%d FB native=%ld reclen=%zu emulated=%zd\n", + fd, native_size, entry->reclen, emulated_size); + } else { + /* + * VB/U (type=record mode): fseek(SEEK_END) is NOT supported by the + * z/OS C runtime for variable-length record files. + * calculate_vb_emulated_size() handles its own fgetpos/fread/fsetpos. + */ + emulated_size = calculate_vb_emulated_size(fp, entry); + DSIO_LOG_DEBUG("dsio_get_size: fd=%d VB/U emulated=%zd\n", fd, emulated_size); + } + + if (emulated_size >= 0) { + entry->cached_size = (size_t)emulated_size; + entry->size_calculated = 1; + } + + return emulated_size; +} + +int dsio_flush(int fd) { + void* dd = GET_DD(fd); + if (!dd || IS_FD(fd)) { + return -1; + } + + DatasetEntry* entry = ENTRY_TO(dd); + if (!entry->file_ptr) { + return -1; + } + + return fflush(entry->file_ptr); +} + +/* ======================================================================== + * CCSID CONVERSION STUBS + * These would use iconv() or __atoe()/__etoa() for actual conversion + * ======================================================================== */ + +int dsio_set_ccsid_config(int fd, const dsio_ccsid_config_t* config) { + if (!config) return -1; + + void* dd = GET_DD(fd); + if (!dd || IS_FD(fd)) { + return -1; + } + + DatasetEntry* entry = ENTRY_TO(dd); + + /* Store CCSID configuration */ + entry->file_ccsid = config->source_ccsid; + entry->program_ccsid = config->target_ccsid; + entry->conversion_state = config->conversion_enabled ? 1 : 0; + + + + return 0; +} + +int dsio_get_ccsid_config(int fd, dsio_ccsid_config_t* config) { + if (!config) return -1; + + void* dd = GET_DD(fd); + if (!dd || IS_FD(fd)) { + return -1; + } + + DatasetEntry* entry = ENTRY_TO(dd); + + config->source_ccsid = entry->file_ccsid; + config->target_ccsid = entry->program_ccsid; + config->conversion_enabled = entry->conversion_state; + config->auto_detect = 0; + + return 0; +} + diff --git a/src/zos-io.cc b/src/zos-io.cc index 100cf206..9ee1ffd2 100644 --- a/src/zos-io.cc +++ b/src/zos-io.cc @@ -25,6 +25,7 @@ #include #include #include +#include "zos-datasetio.h" namespace { const char MEMLOG_LEVEL_WARNING = '1'; @@ -37,6 +38,10 @@ bool __gLogMemoryAll = false; bool __gLogMemoryWarning = false; bool __gLogMemoryShowPid = true; FILE *fp_memprintf = nullptr; + +static bool is_dataset_supported(const char* name) { + return IS_DATASET(name) && __get_instance()->get_ds_support_mode() != DS_SUPPORT_NO; +} } #ifdef __cplusplus @@ -833,6 +838,11 @@ int __pthread_create_orig(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg) asm("@@PT3C"); ssize_t __writev_orig(int fd, const struct iovec *iov, int iovcnt) asm("writev"); ssize_t __readv_orig(int fd, const struct iovec *iov, int iovcnt) asm("readv"); +ssize_t __write_orig(int fd, const void *buf, size_t count) asm("write"); +ssize_t __read_orig(int fd, void *buf, size_t count) asm("read"); +off_t __lseek_orig(int fd, off_t offset, int whence) asm("lseek"); +int __stat_orig(const char *path, struct stat *buf) asm("@@A00131"); +int __fstat_orig(int fd, struct stat *buf) asm("fstat"); int utmpxname(char * file) { char buf[PATH_MAX]; @@ -902,6 +912,30 @@ int __open_ascii(const char *filename, int opts, ...) { return fd; } +int __open_ds_file(const char *filename, int opts, ...) { + int fd; + int perms = 0; + if (opts & O_CREAT) { + va_list ap; + va_start(ap, opts); + perms = va_arg(ap, int); + va_end(ap); + } + + DSIO_LOG_DEBUG("calling __open_ds_file file %s\n", filename); + if (is_dataset_supported(filename)) { + fd = open_dataset(filename, opts, perms); + DSIO_LOG_DEBUG("calling open-dataset fd %d\n", fd); + } else { + fd = __open_ascii(filename, opts, perms); + if (fd >= 0) { + ADD_FD(fd); + } + DSIO_LOG_DEBUG("calling open-file fd %d\n", fd); + } + return fd; +} + int __creat_ascii(const char *filename, mode_t mode) { return __open_ascii(filename, O_CREAT|O_WRONLY|O_TRUNC, mode); } @@ -945,6 +979,14 @@ FILE *__fopen_ascii(const char *filename, const char *mode) { return fp; } +FILE *__fopen_ds_file(const char *filename, const char *mode) { + if (is_dataset_supported(filename)) { + return __fopen_orig(filename, mode); + } else { + return __fopen_ascii(filename, mode); + } +} + int __pipe_ascii(int fd[2]) { int ret = __pipe_orig(fd); if (ret < 0) @@ -976,15 +1018,81 @@ int __mkstemp_ascii(char * tmpl) { return ret; } +int __mkstemp_ds_file(char * tmpl) { + int fd; + if (is_dataset_supported(tmpl)) { + DSIO_LOG_DEBUG("calling mkstemp-dataset\n"); + fd = mkstemp_dataset(tmpl); + } else { + DSIO_LOG_DEBUG("calling mkstemp-file\n"); + fd = __mkstemp_ascii(tmpl); + if (fd >= 0) { + ADD_FD(fd); + } + } + return fd; +} + +ssize_t __write_ds_file(int fd, const void *buf, size_t count) { + if (IS_DD(fd)) { + DSIO_LOG_DEBUG("calling write-dataset fd %d\n", fd); + return write_dataset(fd, buf, count); + } + DSIO_LOG_DEBUG("calling write-file fd %d\n", fd); + return __write_orig(fd, buf, count); +} + +ssize_t __read_ds_file(int fd, void *buf, size_t count) { + if (IS_DD(fd)) { + DSIO_LOG_DEBUG("calling read-dataset fd %d\n", fd); + return read_dataset(fd, buf, count); + } + DSIO_LOG_DEBUG("calling read-file fd %d\n", fd); + return __read_orig(fd, buf, count); +} + int __close(int fd) { + if (IS_DD(fd)) { + DSIO_LOG_DEBUG("calling close-dataset fd %d\n", fd); + return close_dataset(fd); + } + DSIO_LOG_DEBUG("calling close-file fd %d\n", fd); int ret = __close_orig(fd); - if (ret < 0) - return ret; - - __fd_close(fd); + if (ret >= 0) + __fd_close(fd); return ret; } +off_t __lseek_ds_file(int fd, off_t offset, int whence) { + if (IS_DD(fd)) { + DSIO_LOG_DEBUG("calling lseek-dataset fd %d offset %lld whence %d\n", fd, (long long)offset, whence); + return lseek_dataset(fd, offset, whence); + } + DSIO_LOG_DEBUG("calling lseek-file fd %d offset %lld whence %d\n", fd, (long long)offset, whence); + return __lseek_orig(fd, offset, whence); +} + +int __stat_ds_file(const char *pathname, struct stat *statbuf) { + if (is_dataset_supported(pathname)) { + DSIO_LOG_DEBUG("calling stat-dataset path %s\n", pathname); + return stat_dataset(pathname, statbuf); + } else { + DSIO_LOG_DEBUG("calling stat-file path %s\n", pathname); + return __stat_orig(pathname, statbuf); + } +} + +int __fstat_ds_file(int fd, struct stat *statbuf) { + if (IS_DD(fd)) { + DSIO_LOG_DEBUG("calling fstat-dataset fd %d\n", fd); + return fstat_dataset(fd, statbuf); + } else { + DSIO_LOG_DEBUG("calling fstat-file fd %d\n", fd); + return __fstat_orig(fd, statbuf); + } +} + + int __socketpair_ascii(int domain, int type, int protocol, int sv[2]) { int ret = __socketpair_orig(domain, type, protocol, sv); if (__is_os_level_at_or_above(ZOSLVL_V2R5)) { @@ -1280,7 +1388,7 @@ int dprintf(int fd, const char *format, ...) { va_end(args); // Write the formatted string to the specified file descriptor - written = write(fd, buffer, length); + written = __write_orig(fd, buffer, length); // Clean up free(buffer); @@ -1319,8 +1427,8 @@ static ssize_t ebcdic_writev(int fd, const struct iovec *iov, int iovcnt) { ptr += iov[i].iov_len; } - // Write the entire converted buffer at once. - ssize_t written = write(fd, converted_buf, total_len); + // Write the entire converted buffer at once using the dataset-aware wrapper. + ssize_t written = __write_ds_file(fd, converted_buf, total_len); if (using_heap) { free(converted_buf); @@ -1330,6 +1438,10 @@ static ssize_t ebcdic_writev(int fd, const struct iovec *iov, int iovcnt) { } ssize_t __writev_ascii(int fd, const struct iovec *iov, int iovcnt) { + if (IS_DD(fd)) { + errno = ENOSYS; + return -1; + } if (!isatty(fd)) { return __writev_orig(fd, iov, iovcnt); @@ -1348,7 +1460,7 @@ static ssize_t ebcdic_readv(int fd, const struct iovec *iov, int iovcnt) { ssize_t total_read = 0; for (int i = 0; i < iovcnt; i++) { - ssize_t bytes_read = read(fd, iov[i].iov_base, iov[i].iov_len); + ssize_t bytes_read = __read_orig(fd, iov[i].iov_base, iov[i].iov_len); if (bytes_read < 0) { perror("read failed"); return -1; // Return error if read fails @@ -1365,6 +1477,10 @@ static ssize_t ebcdic_readv(int fd, const struct iovec *iov, int iovcnt) { ssize_t __readv_ascii(int fd, const struct iovec *iov, int iovcnt) { + if (IS_DD(fd)) { + errno = ENOSYS; + return -1; + } if (!isatty(fd)) { return __readv_orig(fd, iov, iovcnt); diff --git a/src/zos.cc b/src/zos.cc index ad9018af..7d8b88ab 100644 --- a/src/zos.cc +++ b/src/zos.cc @@ -15,6 +15,7 @@ #include "edcwccwi.h" #include "zos-getentropy.h" #include "zos.h" +#include "zos-datasetio.h" #include <_Ccsid.h> #include <_Nascii.h> @@ -2468,6 +2469,7 @@ bool __zinit::isValidZOSLIBEnvar(std::string envar) { } __zinit::__zinit() { + ds_support_mode = DS_SUPPORT_NO; __gMainThreadId = gettid(); __gMainThreadSelf = pthread_self(); @@ -2673,7 +2675,28 @@ static void setProcessEnvars() { } int __zinit::initialize(const zoslib_config_t &aconfig) { + ADD_FD(STDIN_FILENO); + ADD_FD(STDOUT_FILENO); + ADD_FD(STDERR_FILENO); + + char* env = getenv("ZOSLIB_DEBUG"); + if (env && (strcmp(env, "1") == 0 || strcasecmp(env, "ON") == 0)) { + dsio_enable_debug(1); + } else { + g_debug_enabled = 0; + } + memcpy(&config, &aconfig, sizeof(config)); + + char* ds_env = getenv(config.DATASET_SUPPORT_ENVAR); + if (ds_env && (strcasecmp(ds_env, "YES") == 0 || strcmp(ds_env, "1") == 0)) { + ds_support_mode = DS_SUPPORT_YES; + } else if (ds_env && (strcasecmp(ds_env, "NO") == 0 || strcmp(ds_env, "0") == 0)) { + ds_support_mode = DS_SUPPORT_NO; + } else { + ds_support_mode = DS_SUPPORT_NO; + } + __galloc_info = new __Cache; mode = __ae_thread_swapmode(__AE_ASCII_MODE); @@ -2805,7 +2828,14 @@ int __zinit::setEnvarHelpMap() { "memory statistics summary, and any error messages are " "always displayed if logging of memory diagnostic " "messages is enabled")); - + + envarHelpMap.insert(std::make_pair( + zoslibEnvar(config.DATASET_SUPPORT_ENVAR, std::string("YES")), + "Enable support for MVS datasets via // prefix")); + + envarHelpMap.insert(std::make_pair( + zoslibEnvar(config.DATASET_SUPPORT_ENVAR, std::string("NO")), + "(default) Disable MVS dataset support")); return __update_envar_settings(NULL); } @@ -3046,6 +3076,8 @@ extern "C" void init_zoslib_config(zoslib_config_t *const config) { UNTAGGED_READ_MODE_CCSID1047_DEFAULT; config->MEMORY_USAGE_LOG_FILE_ENVAR = MEMORY_USAGE_LOG_FILE_ENVAR_DEFAULT; config->MEMORY_USAGE_LOG_LEVEL_ENVAR = MEMORY_USAGE_LOG_LEVEL_ENVAR_DEFAULT; + config->MEMORY_USAGE_LOG_INC_ENVAR = MEMORY_USAGE_LOG_INC_ENVAR_DEFAULT; + config->DATASET_SUPPORT_ENVAR = DATASET_SUPPORT_ENVAR_DEFAULT; } extern "C" void init_zoslib(const zoslib_config_t config) {