From 4ee14ed989b151711fe7fd58639e24a19caafa3b Mon Sep 17 00:00:00 2001 From: sachintu47 Date: Wed, 4 Feb 2026 05:02:24 -0500 Subject: [PATCH 01/28] Integrate from datasetio. Modify open and close syscall --- include/fcntl.h | 3 +- include/ihapds.h | 171 ++++ include/ispf.h | 50 ++ include/ispf_reader.h | 23 + include/zos-datasetio.h | 473 ++++++++++ include/ztime.h | 11 + src/CMakeLists.txt | 1 + src/zos-datasetio.c | 1838 +++++++++++++++++++++++++++++++++++++++ src/zos-io.cc | 35 +- 9 files changed, 2599 insertions(+), 6 deletions(-) create mode 100644 include/ihapds.h create mode 100644 include/ispf.h create mode 100644 include/ispf_reader.h create mode 100644 include/zos-datasetio.h create mode 100644 include/ztime.h create mode 100644 src/zos-datasetio.c 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/ihapds.h b/include/ihapds.h new file mode 100644 index 00000000..be2a44e5 --- /dev/null +++ b/include/ihapds.h @@ -0,0 +1,171 @@ +#ifndef __IHAPDS_H__ +#define __IHAPDS_H__ + +/* Standalone PDS directory entry structures - no external dependencies */ + +#pragma pack(1) + +struct pds2 { + unsigned char pds2name[8]; /* MEMBER NAME OR ALIAS NAME */ + unsigned char pds2ttrp[3]; /* TTR OF FIRST BLOCK OF NAMED MEMBER */ + char pds2cnct; /* CONCATENATION NUMBER OF THE DATA SET */ + unsigned char pds2libf; /* LIBRARY FLAG FIELD */ + unsigned char pds2indc; /* INDICATOR BYTE */ + union { + unsigned char pds2usrd; /* START OF VARIABLE LENGTH USER DATA FIELD */ + unsigned char pds2ttrt[3]; /* TTR OF FIRST BLOCK OF TEXT */ + }; + unsigned char pds2zero; /* ZERO */ + unsigned char pds2ttrn[3]; /* TTR OF NOTE LIST OR SCATTER/TRANSLATION */ + char pds2nl; /* NUMBER OF ENTRIES IN NOTE LIST FOR */ + union { + unsigned char pds2atr[2]; /* TWO-BYTE PROGRAM ATTRIBUTE FIELD */ + struct { + unsigned char pds2atr1; /* FIRST BYTE OF PROGRAM ATTRIBUTE FIELD */ + unsigned int pds2flvl : 1, /* If one, the program cannot be processed */ + pds2org0 : 1, /* ORIGIN OF FIRST BLOCK OF TEXT IS ZERO */ + pds2ep0 : 1, /* ENTRY POINT IS ZERO */ + pds2nrld : 1, /* PROGRAM CONTAINS NO RLD ITEMS */ + pds2nrep : 1, /* PROGRAM CANNOT BE REPROCESSED BY LINKAGE */ + pds2tstn : 1, /* PROGRAM CONTAINS TESTRAN SYMBOL CARDS */ + pds2lef : 1, /* PROGRAM CREATED BY LINKAGE EDITOR F */ + pds2refr : 1; /* REFRESHABLE PROGRAM */ + }; + }; + int pds2stor : 24; /* TOTAL CONTIGUOUS MAIN STORAGE REQUIREMENT */ + short int pds2ftbl; /* LENGTH OF FIRST BLOCK OF TEXT */ + unsigned int pds2epa : 24; /* ENTRY POINT ADDRESS ASSOCIATED WITH */ + union { + unsigned char pds2ftbo[3]; /* FLAG BYTES (MVS USE OF FIELD) @LCC */ + struct { + unsigned char pds2ftb1; /* BYTE 1 OF PDS2FTBO */ + unsigned int pds2altp : 1, /* ALTERNATE PRIMARY FLAG. IF ON (FOR A @L8A */ + : 1, + pdslrm64 : 1, /* 0: RMODE is 24 or 31. @LDA */ + pdslrmod : 1, /* 0: RMODE 24 or reserved @LDA */ + pdsaamod : 2, /* ALIAS ENTRY POINT ADDRESSING MODE @L6A */ + pdsmamod : 2; /* MAIN ENTRY POINT ADDRESSING MODE @L6A */ + struct { + unsigned int pds2nmig : 1, /* THIS PROGRAM OBJECT CANNOT BE CONVERTED */ + pds2prim : 1, /* FETCHOPT PRIME WAS SPECIFIED @L7A */ + pds2pack : 1, /* FETCHOPT PACK WAS SPECIFIED @L7A */ + : 5; + } pds2rlds; /* NUMBER OF RLD/CONTROL RECORDS WHICH @L6A */ + }; + }; + short int pds2slsz; /* NUMBER OF BYTES IN SCATTER LIST */ + short int pds2ttsz; /* NUMBER OF BYTES IN TRANSLATION TABLE */ + unsigned char pds2esdt[2]; /* IDENTIFICATION OF ESD ITEM (ESDID) OF */ + unsigned char pds2esdc[2]; /* IDENTIFICATION OF ESD ITEM (ESDID) OF */ + unsigned int pds2epm : 24; /* ENTRY POINT FOR MEMBER NAME */ + unsigned char pds2mnm[8]; /* MEMBER NAME OF PROGRAM. WHEN THE */ + union { + short int pdss03; /* FORCE HALF-WORD ALIGNMENT FOR SSI */ + unsigned char pdsssiwd[4]; /* SSI INFORMATION WORD */ + struct { + char pdschlvl; /* CHANGE LEVEL OF MEMBER */ + unsigned char pdsssifb; /* SSI FLAG BYTE */ + unsigned char pdsmbrsn[2]; /* MEMBER SERIAL NUMBER */ + struct { + char pdsapfct; /* LENGTH OF PROGRAM AUTHORIZATION CODE */ + unsigned char pdsapfac; /* PROGRAM AUTHORIZATION CODE */ + } pdsapf; /* PROGRAM AUTHORIZATION FACILITY (APF) */ + }; + }; + union { + char pds2lpol; /* LARGE PROGRAM OBJECT SECTION LENGTH @L7A */ + char pds2llml; /* ALTERNATE NAME FOR PDS2LLML @L7A */ + }; + int pds2vstr; /* VIRTUAL STORAGE REQUIREMENT FOR THIS */ + int pds2mepa; /* MAIN ENTRY POINT OFFSET */ + int pds2aepa; /* ALIAS ENTRY POINT OFFSET. ONLY VALID */ + unsigned int : 4, + pds2xattr_optn_mask : 4; /* Bits 4-7 of PDS2XATTRBYTE0 identify the */ + unsigned int pds2longparm : 1, /* PARM > 100 chars allowed @LBA */ + : 7; + char _filler1; /* Reserved @LBA */ + }; + +/* Values for field "pds2libf" */ +#define pds2lnrm 0x00 /* NORMAL CASE */ +#define pds2llnk 0x01 /* IF DCB OPERAND IN BLDL MACRO INTRUCTION */ +#define pds2ljob 0x02 /* IF DCB OPERAND IN BLDL MACRO INTRUCTION */ + +/* Values for field "pds2indc" */ +#define pds2alis 0x80 /* NAME IN THE FIELD PDS2NAME IS AN ALIAS */ +#define dealias 0x80 /* --- ALIAS FOR PDS2ALIS */ +#define pds2nttr 0x60 /* NUMBER OF TTR'S IN THE USER DATA FIELD */ +#define pds2lusr 0x1F /* - LENGTH OF USER DATA FIELD */ + +/* Values for field "pds2atr1" */ +#define pds2rent 0x80 /* REENTERABLE */ +#define dereen 0x80 /* --- ALIAS FOR PDS2RENT */ +#define pds2reus 0x40 /* REUSABLE */ +#define pds2ovly 0x20 /* IN OVERLAY STRUCTURE */ +#define deovly 0x20 /* --- ALIAS FOR PDS2OVLY */ +#define pds2test 0x10 /* PROGRAM TO BE TESTED - TESTRAN */ +#define pds2load 0x08 /* ONLY LOADABLE */ +#define delody 0x08 /* --- ALIAS FOR PDS2LOAD */ +#define pds2sctr 0x04 /* SCATTER FORMAT */ +#define descat 0x04 /* --- ALIAS FOR PDS2SCTR */ +#define pds2exec 0x02 /* EXECUTABLE */ +#define dexcut 0x02 /* --- ALIAS FOR PDS2EXEC */ +#define pds21blk 0x01 /* IF ZERO, PROGRAM CONTAINS MULTIPLE */ + +/* Values for field "pds2ftb1" */ +#define pdsaosle 0x80 /* Program has been processed by OS/VS1 or */ +#define pds2big 0x40 /* THE LARGE PROGRAM OBJECT EXTENSION */ +#define pds2paga 0x20 /* PAGE ALIGNMENT REQUIRED FOR PROGRAM */ +#define pds2ssi 0x10 /* SSI INFORMATION PRESENT */ +#define pdsapflg 0x08 /* INFORMATION IN PDSAPF IS VALID */ +#define pds2pgmo 0x04 /* PROGRAM OBJECT. THE PDS2FTB3 */ +#define pds2lfmt 0x04 /* ALTERNATE NAME FOR PDS2PGMO @L7A */ +#define pds2sign 0x02 /* PROGRAM OBJECT IS SIGNED. VERIFIED ON */ +#define pds2xatr 0x01 /* PDS2XATTR SECTION @LBA */ + +/* Values for field "pdsssifb" */ +#define pdsforce 0x40 /* A FORCE CONTROL CARD WAS USED WHEN */ +#define pdsusrch 0x20 /* A CHANGE WAS MADE TO MEMBER BY THE */ +#define pdsemfix 0x10 /* SET WHEN AN EMERGENCY IBM-AUTHORIZED */ +#define pdsdepch 0x08 /* A CHANGE MADE TO THE MEMBER IS DEPENDENT */ +#define pdssysgn 0x06 /* FLAGS THAT INDICATE WHETHER A */ +#define pdsnosgn 0x00 /* NOT CRITICAL FOR SYSTEM GENERATION */ +#define pdscmsgn 0x02 /* MAY REQUIRE COMPLETE REGENERATION */ +#define pdsptsgn 0x04 /* MAY REQUIRE PARTIAL REGENERATION */ +#define pdsibmmb 0x01 /* MEMBER IS SUPPLIED BY IBM */ + +#define bit0 128 +#define bit1 64 +#define bit2 32 +#define bit3 16 +#define bit4 8 +#define bit5 4 +#define bit6 2 +#define bit7 1 +#define dezbyte 0x0C /* --- ALIAS */ +#define pdsbcend 0x23 /* END OF BASIC SECTION */ +#define pdsbcln 0x23 /* - LENGTH OF BASIC SECTION */ +#define pdss01 0x23 /* START OF SCATTER LOAD SECTION */ +#define pdss01nd 0x2B /* END OF SCATTER LOAD SECTION */ +#define pdss01ln 0x08 /* - LENGTH OF SCATTER LOAD SECTION */ +#define pdss02 0x2B /* START OF ALIAS SECTION */ +#define deentbk 0x2B /* --- ALIAS */ +#define pdss02nd 0x36 /* END OF ALIAS SECTION */ +#define pdss02ln 0x0B /* - LENGTH OF ALIAS SECTION */ +#define pdss03nd 0x3A /* END OF SSI SECTION */ +#define pdss03ln 0x04 /* LENGTH OF SSI SECTION */ +#define pdss04 0x3A /* START OF APF SECTION */ +#define pdss04nd 0x3C /* END OF APF SECTION */ +#define pdss04ln 0x02 /* LENGTH OF APF SECTION */ +#define pdslpo 0x3C /* START OF LARGE PROGRAM OBJECT SECTION@L7A */ +#define pdsllm 0x3C /* ALTERNATE NAME FOR PDSLPO @L7A */ +#define pdslpond 0x49 /* END OF LARGE PROGRAM OBJECT SECTION */ +#define pdsllmnd 0x49 /* ALTERNATE NAME FOR PDSLPOND */ +#define pdslpoln 0x0D /* LENGTH OF LLM SECTION @L7A */ +#define pdsllmln 0x0D /* ALTERNATE NAME FOR PDSLPOLN @L7A */ +#define pds2xattr 0x49 /* Start of extended attributes @LBA */ +#define pds2xattr_opt 0x4C /* Start of optional fields. Number of */ + +#pragma pack(pop) + +#endif // __IHAPDS_H__ diff --git a/include/ispf.h b/include/ispf.h new file mode 100644 index 00000000..53a12b2a --- /dev/null +++ b/include/ispf.h @@ -0,0 +1,50 @@ +#ifndef __ISPF__ + #define __ISPF__ + + #include + + #pragma pack(1) + struct ispf_disk_stats { + unsigned char ver_num; + unsigned char mod_num; + int sclm:1; + int reserve_a:1; + int extended:1; + int reserve_b:5; + unsigned char pd_mod_seconds; + + unsigned char create_century; + char pd_create_julian[3]; + + unsigned char mod_century; + char pd_mod_julian[3]; + + unsigned char pd_mod_hours; + unsigned char pd_mod_minutes; + unsigned short curr_num_lines; + + unsigned short init_num_lines; + unsigned short mod_num_lines; + + char userid[8]; + + /* following is available only in extended format */ + unsigned int full_curr_num_lines; + unsigned int full_init_num_lines; + unsigned int full_mod_num_lines; + }; + #pragma pack(pop) + + struct ispf_stats { + struct tm create_time; + struct tm mod_time; + unsigned int curr_num_lines; + unsigned int init_num_lines; + unsigned int mod_num_lines; + unsigned char userid[8+1]; + unsigned char ver_num; + unsigned char mod_num; + unsigned char sclm; + }; + +#endif diff --git a/include/ispf_reader.h b/include/ispf_reader.h new file mode 100644 index 00000000..384d157f --- /dev/null +++ b/include/ispf_reader.h @@ -0,0 +1,23 @@ +#ifndef __ISPF_READER_H__ +#define __ISPF_READER_H__ + +#include +#include "ispf.h" + +/* + * Read ISPF statistics for a PDS member + * + * Parameters: + * fp - FILE pointer to the open PDS + * member_name - Name of the member (up to 8 characters) + * stats - Output structure to receive ISPF statistics + * + * Returns: + * 0 on success + * -1 if ISPF stats not available or error occurred + */ +int read_member_ispf_stats(FILE* fp, const char* member_name, struct ispf_stats* stats); + +#endif /* __ISPF_READER_H__ */ + +// Made with Bob diff --git a/include/zos-datasetio.h b/include/zos-datasetio.h new file mode 100644 index 00000000..6859e530 --- /dev/null +++ b/include/zos-datasetio.h @@ -0,0 +1,473 @@ +#ifndef __DATASET_IO__ +#define __DATASET_IO__ 1 + +#include +#include +#include +#include +#include +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +#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); + +/* + * 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); +char* temp_dataset_name(char* result); +int allocate_dataset(const char* dataset); +int delete_dataset(const char* dataset); + +/* ======================================================================== + * ERROR HANDLING + * ======================================================================== */ + +/* Comprehensive error codes for better diagnostics */ +typedef enum { + DSIO_SUCCESS = 0, + DSIO_ERR_INVALID_NAME, + DSIO_ERR_NAME_TOO_LONG, + DSIO_ERR_OPEN_FAILED, + DSIO_ERR_READ_FAILED, + DSIO_ERR_WRITE_FAILED, + DSIO_ERR_CLOSE_FAILED, + DSIO_ERR_ALLOC_FAILED, + DSIO_ERR_INVALID_FD, + DSIO_ERR_INVALID_RECFM, + DSIO_ERR_INVALID_DSORG, + DSIO_ERR_CCSID_CONVERSION, + DSIO_ERR_BUFFER_OVERFLOW, + DSIO_ERR_MEMBER_NOT_FOUND, + DSIO_ERR_NOT_A_DATASET, + DSIO_ERR_FLDATA_FAILED, + DSIO_ERR_FSEEK_FAILED, + DSIO_ERR_FTELL_FAILED, + DSIO_ERR_RECORD_TOO_LONG, + DSIO_ERR_UNSUPPORTED_OPERATION, + DSIO_ERR_INTERNAL_ERROR +} dsio_error_t; + +/* 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 */ + uint16_t lrecl; /* Logical record length */ + uint32_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, uint16_t* lrecl); +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, ...); + +/* ======================================================================== + * STATISTICS AND MONITORING + * ======================================================================== */ + +/* I/O statistics */ +typedef struct { + size_t bytes_read; /* Total bytes read */ + size_t bytes_written; /* Total bytes written */ + size_t read_operations; /* Number of read calls */ + size_t write_operations; /* Number of write calls */ + size_t open_operations; /* Number of open calls */ + size_t close_operations; /* Number of close calls */ + size_t errors; /* Number of errors */ +} dsio_stats_t; + +/* Get statistics for a file descriptor */ +int dsio_get_stats(int fd, dsio_stats_t* stats); + +/* Reset statistics for a file descriptor */ +int dsio_reset_stats(int fd); + +/* Get global statistics */ +void dsio_get_global_stats(dsio_stats_t* stats); + +/* Reset global statistics */ +void dsio_reset_global_stats(void); + +/* ======================================================================== + * 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) + +#define ADD_FD(fd) (descriptor_table[(fd)] = ((void*)(((unsigned long long) fd) | INV_ADDR_BIT))) +#define ADD_DD(fd,dd) (descriptor_table[(fd)] = (dd)) +#define GET_DD(fd) (descriptor_table[(fd)]) +#define CLEAR_DD(fd) (descriptor_table[(fd)] = 0) + +#define IS_FD(slot) ((((unsigned long long) (descriptor_table[(slot)])) & INV_ADDR_BIT) != 0) +#define IS_DD(slot) ((((unsigned long long) (descriptor_table[(slot)])) & INV_ADDR_BIT) == 0) + +typedef struct DatasetEntry { + FILE* file_ptr; + unsigned short file_ccsid; + unsigned short process_ccsid; + unsigned short program_ccsid; + unsigned char conversion_state; +} DatasetEntry; + +/* Directory structure for PDS/PDSE member listing */ +typedef struct DatasetDir { + char dataset_name[256]; + char** member_list; + int member_count; + int current_index; + int is_dataset_dir; +} DatasetDir; + +#define GET_DUMMY_FD() (open("/dev/null", O_WRONLY, 0)) +#define IS_DATASET(name) ((name) && ((name)[0] == '/') && ((name)[1] == '/')) + +#ifdef DEBUG + #define DEBUG_PRINT0(str) puts(str) + #define DEBUG_PRINT1(fmt,field) (printf(fmt,field)) +#else + #define DEBUG_PRINT0(str) + #define DEBUG_PRINT1(fmt,field) +#endif + +/* ======================================================================== + * ENHANCED INTERNAL STRUCTURES + * ======================================================================== */ + +extern void* descriptor_table[MAX_FDS]; + +/* Enhanced DatasetEntry structure */ +typedef struct DatasetEntryEnhanced { + /* Original fields from DatasetEntry */ + FILE* file_ptr; + unsigned short file_ccsid; + unsigned short process_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; + uint16_t lrecl; + uint32_t blksize; + + /* Dataset name components */ + 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; + + /* I/O statistics */ + size_t bytes_read; + size_t bytes_written; + size_t read_operations; + size_t write_operations; + + /* State flags */ + int metadata_loaded; + int stats_enabled; + +} DatasetEntryEnhanced; + +/* Global statistics */ +typedef struct { + size_t total_bytes_read; + size_t total_bytes_written; + size_t total_read_operations; + size_t total_write_operations; + size_t total_open_operations; + size_t total_close_operations; + size_t total_errors; + int stats_enabled; +} GlobalStats; + +/* Global state */ +extern GlobalStats g_stats; +extern dsio_log_level_t g_log_level; +extern FILE* g_log_stream; +extern int g_debug_enabled; + +/* ======================================================================== + * INTERNAL HELPER FUNCTIONS + * ======================================================================== */ + +/* Create enhanced dataset entry */ +DatasetEntryEnhanced* create_enhanced_entry(FILE* fp, unsigned short file_ccsid); + +/* Free enhanced dataset entry */ +void free_enhanced_entry(DatasetEntryEnhanced* entry); + +/* Load metadata from FILE* using fldata() */ +int load_metadata_from_file(DatasetEntryEnhanced* entry); + +/* Parse dataset name and extract components */ +int parse_and_store_name(DatasetEntryEnhanced* entry, const char* dataset_name); + +/* Set error on entry */ +void set_entry_error(DatasetEntryEnhanced* entry, dsio_error_t error, const char* message); + +/* Update statistics */ +void update_read_stats(DatasetEntryEnhanced* entry, size_t bytes); +void update_write_stats(DatasetEntryEnhanced* entry, size_t bytes); +void update_global_stats_open(void); +void update_global_stats_close(void); +void update_global_stats_error(void); + +/* 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, ...); + +/* CCSID conversion helpers */ +void* convert_ebcdic_to_ascii(void* buf, size_t len); +void* convert_ascii_to_ebcdic(void* buf, size_t len); +void* convert_buffer_ccsid(void* buf, size_t len, uint16_t from, uint16_t to); + +/* 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_ENHANCED(entry) ((DatasetEntryEnhanced*)(entry)) +#define IS_ENHANCED_ENTRY(entry) ((entry) && ((DatasetEntryEnhanced*)(entry))->metadata_loaded >= 0) + +/* 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/ztime.h b/include/ztime.h new file mode 100644 index 00000000..cfb667b7 --- /dev/null +++ b/include/ztime.h @@ -0,0 +1,11 @@ +#ifndef __Z_TIME__ + #define __Z_TIME__ 1 + + #include + + int pdjd_to_tm(const char* pdjd, int start_century, struct tm* ltime); + void tm_to_pdjd(unsigned char* century, char* pdjd, struct tm* ltime); + time_t tod_to_time(unsigned long long tod); + unsigned int pd_to_d(unsigned char pd); + unsigned char d_to_pd(unsigned int val, int set_positive_sign); +#endif 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..092f3494 --- /dev/null +++ b/src/zos-datasetio.c @@ -0,0 +1,1838 @@ +#define _XOPEN_SOURCE_EXTENDED 1 +#define _EXT 1 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "zos-datasetio.h" +#include "ispf.h" +#include "ztime.h" +#include "ihapds.h" +#include "ispf_reader.h" + +void* descriptor_table[MAX_FDS] = { 0 }; + +static dsio_recfm_t detect_recfm_from_fldata(const fldata_t* fdata); +static dsio_dsorg_t detect_dsorg_from_fldata(const fldata_t* fdata); + +static const char DATASET_CHAR[] = "ABCDEFGHIJKLMNOPQRSTUVWYZ$#@"; + +//TODO: add to header +void* convertBuffer(void* buf, unsigned short from_ccsid, unsigned short to_ccsid); +DatasetEntry* createDatasetEntry(FILE* dd, unsigned short file_ccsid); + +#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 */ + return NULL; /* TBD: need to set proper errno's */ + } + + 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; +} + +static int mkstemp_dataset(char* tmplate) +{ + void* dd; + int fd = -1; + + /* 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; + } + + /* Current just have 'dd' be the FILE pointer - may want something more substantial */ + dd = fopen(tmplate, "w"); + if (!dd) { + return -1; + } + + /* Use enhanced entry creation */ + DatasetEntryEnhanced* dentry = create_enhanced_entry(dd, 1047); + if (!dentry) { + fclose(dd); + return -1; + } + + parse_and_store_name(dentry, tmplate); + update_global_stats_open(); + + fd = GET_DUMMY_FD(); + ADD_DD(fd, dentry); + + log_info("Created temporary dataset: %s (fd=%d)", tmplate, fd); + + return fd; +} + +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 + */ + + void* dd; + const char* fopen_mode; + unsigned short file_ccsid; + + int fd = -1; + bool pds_member = strchr(name, '('); + + if ((flags & O_APPEND) && (pds_member)) { + errno = EINVAL; + return -1; + } else if (flags & O_RDONLY) { + fopen_mode = "r,recfm=+"; + } else if (flags & O_WRONLY) { + if (flags & O_APPEND) { + fopen_mode = "a,recfm=+"; + } else { + fopen_mode = "w,recfm=+"; + } + } else if (flags & O_RDWR) { + if (flags & O_APPEND) { + fopen_mode = "a+,recfm=+"; + } else { + fopen_mode = "r+,recfm=+"; + } + } else { + errno = EINVAL; + return -1; + } + + if ((flags & O_LARGEFILE) || (flags & O_NOCTTY) || (flags & O_NONBLOCK)) { + errno = EACCES; + return -1; + } + + 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 + */ + errno = EINVAL; + return -1; + } + } + + DEBUG_PRINT1("Open with mode %s\n", fopen_mode); + dd = fopen(name, fopen_mode); + if (!dd) { + perror("dataset open failed"); + return -1; + } + + /* Use enhanced entry creation with metadata loading */ + DatasetEntryEnhanced* dentry = create_enhanced_entry(dd, 1047); + if (!dentry) { + fclose(dd); + return -1; + } + + /* Parse and store dataset name components */ + parse_and_store_name(dentry, name); + + /* Update global statistics */ + update_global_stats_open(); + + fd = GET_DUMMY_FD(); + ADD_DD(fd, dentry); + + log_info("Opened dataset: %s (fd=%d, RECFM=%s, LRECL=%d)", + name, fd, + dsio_recfm_to_string(dentry->recfm), + dentry->lrecl); + + return fd; +} + +static ssize_t write_dataset(int fd, const void* buf, size_t count) +{ + void* dd = GET_DD(fd); + + DatasetEntryEnhanced* dentry = (DatasetEntryEnhanced*) (dd); + FILE* fp = dentry->file_ptr; + + DEBUG_PRINT1("In Write, File ptr ccsid: %p\n", dentry->file_ptr); + DEBUG_PRINT1("In Write, File ccsid: %d\n", dentry->file_ccsid); + DEBUG_PRINT1("In Write, Program ccsid: %d\n", dentry->program_ccsid); + DEBUG_PRINT1("In Write, Conversion state %d\n", dentry->conversion_state); + + char* write_buffer = (char *)malloc(count); + if (write_buffer == NULL) { + set_entry_error(dentry, DSIO_ERR_ALLOC_FAILED, "Memory allocation failed for write buffer"); + fprintf(stderr, "Memory allocation failed\n"); + return -1; + } + memcpy(write_buffer, buf, count); + + write_buffer = convertBuffer(write_buffer, dentry->program_ccsid, dentry->file_ccsid); + size_t rc = fwrite(write_buffer, 1, count, fp); + free(write_buffer); + + if (rc == count) { + /* Update statistics on successful write */ + update_write_stats(dentry, rc); + log_trace("Wrote %zu bytes to fd=%d", rc, fd); + return (ssize_t) rc; + } else { + set_entry_error(dentry, DSIO_ERR_WRITE_FAILED, "fwrite() returned fewer bytes than requested"); + return -1; + } +} + +static ssize_t read_dataset(int fd, void* buf, size_t count) +{ + void* dd = GET_DD(fd); + + DatasetEntryEnhanced* dentry = (DatasetEntryEnhanced*) (dd); + FILE* fp = dentry->file_ptr; + + DEBUG_PRINT1("In Read, File ptr ccsid: %p\n", dentry->file_ptr); + DEBUG_PRINT1("In Read, File ccsid: %d\n", dentry->file_ccsid); + DEBUG_PRINT1("In Read, Program ccsid: %d\n", dentry->program_ccsid); + DEBUG_PRINT1("In Read, Conversion state %d\n", dentry->conversion_state); + + size_t rc = fread(buf, 1, count, fp); + + if (rc > 0) { + buf = convertBuffer(buf, dentry->file_ccsid, dentry->program_ccsid); + /* Update statistics on successful read */ + update_read_stats(dentry, rc); + log_trace("Read %zu bytes from fd=%d", rc, fd); + } else if (rc == 0 && ferror(fp)) { + set_entry_error(dentry, DSIO_ERR_READ_FAILED, "fread() failed"); + return -1; + } + + return (ssize_t) rc; +} + +int close_dataset(int fd) +{ + void* dd = GET_DD(fd); + + DatasetEntryEnhanced* dentry = (DatasetEntryEnhanced*) (dd); + FILE* fp = dentry->file_ptr; + + log_info("Closing dataset fd=%d (read=%zu bytes, wrote=%zu bytes, ops=%zu/%zu)", + fd, dentry->bytes_read, dentry->bytes_written, + dentry->read_operations, dentry->write_operations); + + int rc = fclose(fp); + if (!rc) { + /* Update global statistics */ + update_global_stats_close(); + + close(fd); + + /* Deallocate enhanced DatasetEntry */ + free_enhanced_entry(dentry); + CLEAR_DD(fd); + } else { + set_entry_error(dentry, DSIO_ERR_CLOSE_FAILED, "fclose() failed"); + } + return rc; +} + +ssize_t read_zos(int fd, void* buf, size_t count) +{ + if (IS_FD(fd)) { + DEBUG_PRINT0("calling read-file\n"); + return read(fd, buf, count); + } else { + DEBUG_PRINT0("calling read-dataset\n"); + return read_dataset(fd, buf, count); + } +} + +ssize_t write_zos(int fd, const void* buf, size_t count) +{ + if (IS_FD(fd)) { + DEBUG_PRINT0("calling write-file\n"); + return write(fd, buf, count); + } else { + DEBUG_PRINT0("calling write-dataset\n"); + return write_dataset(fd, buf, count); + } +} + +int mkstemp_zos(char* tmplate) +{ + int fd; + if (IS_DATASET(tmplate)) { + DEBUG_PRINT0("calling mkstemp-dataset\n"); + fd = mkstemp_dataset(tmplate); + } else { + DEBUG_PRINT0("calling mkstemp-file\n"); + fd = mkstemp(tmplate); + if (fd >= 0) { + ADD_FD(fd); + } + } + return fd; +} + +char* temp_dataset_name(char* result) +{ + char* orig = getenv("__POSIX_TMPNAM"); + char temp[L_tmpnam+1]; + setenv("__POSIX_TMPNAM", "NO", 1); + tmpnam(temp); + setenv("__POSIX_TMPNAM", orig, 1); + sprintf(result, "//'%s'", temp); + return result; +} + +char* temp_file_name(char* result) +{ + char* orig = getenv("__POSIX_TMPNAM"); + setenv("__POSIX_TMPNAM", "YES", 1); + tmpnam(result); + setenv("__POSIX_TMPNAM", orig, 1); + 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'; + + DEBUG_PRINT1("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; +} + +void* convertBuffer(void* buf, unsigned short from_ccsid, unsigned short to_ccsid) +{ + if (from_ccsid == 1047 && to_ccsid == 819) { + __e2a_s(buf); + } else if (from_ccsid == 819 && to_ccsid == 1047) { + __a2e_s(buf); + } else { + fprintf(stderr, "from_ccsid: %d to to_ccsid: %d not supported\n", from_ccsid, to_ccsid); + } + return buf; +} + +int zos_fcntl(int fd, int cmd, struct f_cnvrt* req); + +int fcntl_zos(int fd, int cmd, struct f_cnvrt* req) +{ + return zos_fcntl(fd, cmd, req); +} + +int zos_fcntl(int fd, int cmd, struct f_cnvrt* req) +{ + //TODO: Only has support for F_CONTROL_CVT for now + if (cmd == F_CONTROL_CVT) { + if (!req) + return -1; + + if (fd < 0) + return -1; + + void* dd = GET_DD(fd); + DatasetEntry* dentry = (DatasetEntry*) (dd); + + 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; +} + +DatasetEntry* createDatasetEntry(FILE* dd, unsigned short file_ccsid) +{ + DatasetEntry* dentry = malloc(sizeof(DatasetEntry)); + dentry->file_ptr = dd; + dentry->file_ccsid = file_ccsid; + dentry->program_ccsid = 819; + dentry->conversion_state = SETCVTON; + return dentry; +} + + +/* ======================================================================== + * PHASE 1 SYSTEM CALL IMPLEMENTATIONS + * ======================================================================== */ + +/* ======================================================================== + * lseek() - File Positioning + * ======================================================================== */ + +#undef lseek + +static off_t lseek_dataset(int fd, off_t offset, int whence) { + void* dd = GET_DD(fd); + DatasetEntryEnhanced* dentry = (DatasetEntryEnhanced*)dd; + FILE* fp = dentry->file_ptr; + + int fseek_whence; + switch (whence) { + case SEEK_SET: + fseek_whence = SEEK_SET; + break; + case SEEK_CUR: + fseek_whence = SEEK_CUR; + break; + case SEEK_END: + fseek_whence = SEEK_END; + break; + default: + errno = EINVAL; + set_entry_error(dentry, DSIO_ERR_FSEEK_FAILED, "Invalid whence parameter"); + return -1; + } + + if (fseek(fp, offset, fseek_whence) != 0) { + set_entry_error(dentry, DSIO_ERR_FSEEK_FAILED, "fseek() failed"); + return -1; + } + + long pos = ftell(fp); + if (pos < 0) { + set_entry_error(dentry, DSIO_ERR_FTELL_FAILED, "ftell() failed"); + return -1; + } + + log_trace("lseek: fd=%d, offset=%ld, whence=%d, new_pos=%ld", fd, offset, whence, pos); + return (off_t)pos; +} + +off_t lseek_zos(int fd, off_t offset, int whence) { + if (IS_FD(fd)) { + DEBUG_PRINT0("calling lseek-file\n"); + return lseek(fd, offset, whence); + } else { + DEBUG_PRINT0("calling lseek-dataset\n"); + return lseek_dataset(fd, offset, whence); + } +} + +/* ======================================================================== + * fstat() / stat() - File Metadata + * ======================================================================== */ + +#undef fstat +#undef stat + +/* Helper function to read ISPF statistics from PDS member */ +static int read_ispf_stats(FILE* fp, struct ispf_stats* stats) { + if (!fp || !stats) { + return -1; + } + + /* Get file data to check if this is a PDS member */ + fldata_t fdata; + if (fldata(fp, NULL, &fdata) != 0) { + return -1; + } + + /* Check if this is a PDS/PDSE member */ + if (fdata.__dsorgPO == 0) { + return -1; /* Not a PDS/PDSE */ + } + + /* Get member name from fldata */ + char member_name[9] = {0}; + if (fdata.__dsname != NULL) { + return -1; /* No member name */ + } + memcpy(member_name, fdata.__dsname, 8); + member_name[8] = '\0'; + + /* Use the ISPF reader module to get statistics */ + return read_member_ispf_stats(fp, member_name, stats); +} + +static int fstat_dataset(int fd, struct stat *statbuf) { + if (!statbuf) { + errno = EINVAL; + return -1; + } + + void* dd = GET_DD(fd); + DatasetEntryEnhanced* dentry = (DatasetEntryEnhanced*)dd; + FILE* fp = dentry->file_ptr; + + /* Initialize stat buffer */ + memset(statbuf, 0, sizeof(struct stat)); + + /* Use fldata() to get dataset information */ + fldata_t fdata; + if (fldata(fp, NULL, &fdata) != 0) { + set_entry_error(dentry, DSIO_ERR_FLDATA_FAILED, "fldata() failed"); + return -1; + } + + /* Get file size using fseek/ftell */ + long current_pos = ftell(fp); + if (fseek(fp, 0, SEEK_END) == 0) { + long size = ftell(fp); + if (size >= 0) { + statbuf->st_size = size; + } + fseek(fp, current_pos, SEEK_SET); /* Restore position */ + } + + /* Fill in stat structure with dataset information */ + statbuf->st_mode = S_IFREG | S_IRUSR | S_IWUSR; /* Regular file, user r/w */ + statbuf->st_nlink = 1; + statbuf->st_blksize = fdata.__blksize; + + /* Calculate blocks used */ + if (statbuf->st_size > 0 && statbuf->st_blksize > 0) { + statbuf->st_blocks = (statbuf->st_size + statbuf->st_blksize - 1) / statbuf->st_blksize; + } + + /* Use enhanced metadata if available */ + if (dentry->metadata_loaded && dentry->blksize > 0) { + statbuf->st_blksize = dentry->blksize; + statbuf->st_blocks = (statbuf->st_size + dentry->blksize - 1) / dentry->blksize; + } + + log_debug("fstat: fd=%d, size=%ld, blksize=%ld, blocks=%ld", + fd, (long)statbuf->st_size, (long)statbuf->st_blksize, (long)statbuf->st_blocks); + + return 0; +} + +int fstat_zos(int fd, struct stat *statbuf) { + if (IS_FD(fd)) { + DEBUG_PRINT0("calling fstat-file\n"); + return fstat(fd, statbuf); + } else { + DEBUG_PRINT0("calling fstat-dataset\n"); + return fstat_dataset(fd, statbuf); + } +} + +static int stat_dataset(const char *pathname, struct stat *statbuf) { + 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) { + return -1; + } + + int result = fstat_dataset(fd, statbuf); + close_dataset(fd); + + return result; +} + +int stat_zos(const char *pathname, struct stat *statbuf) { + if (IS_DATASET(pathname)) { + DEBUG_PRINT0("calling stat-dataset\n"); + return stat_dataset(pathname, statbuf); + } else { + DEBUG_PRINT0("calling stat-file\n"); + return stat(pathname, statbuf); + } +} + +/* ======================================================================== + * opendir() / readdir() / closedir() - Directory Operations for PDS/PDSE + * ======================================================================== */ + +#undef opendir +#undef readdir +#undef closedir + +/* Helper function to read PDS directory using BPAM or simple approach */ +static int read_pds_directory(const char* dataset_name, char*** member_list, int* member_count) { + /* This is a simplified implementation + * A full implementation would use BPAM to read the directory + * For now, we'll use a simple approach with fopen and directory reading + */ + + *member_list = NULL; + *member_count = 0; + + /* Try to open the PDS as a directory */ + /* In z/OS, we can list members by opening the PDS and reading directory blocks */ + /* This is a placeholder - real implementation would use BPAM or ISPF services */ + + log_warn("PDS directory reading not fully implemented yet for: %s", dataset_name); + + /* For now, return empty directory */ + /* TODO: Implement actual BPAM directory reading */ + + return 0; +} + +static DIR* opendir_dataset(const char *name) { + if (!name) { + errno = EINVAL; + return NULL; + } + + /* Check if this is a PDS/PDSE (no member specified) */ + if (strchr(name, '(') != NULL) { + /* Member specified - not a directory */ + errno = ENOTDIR; + log_error("opendir: Cannot open PDS member as directory: %s", name); + return NULL; + } + + /* Allocate directory structure */ + DatasetDir* dir = calloc(1, sizeof(DatasetDir)); + if (!dir) { + errno = ENOMEM; + return NULL; + } + + strncpy(dir->dataset_name, name, sizeof(dir->dataset_name) - 1); + dir->is_dataset_dir = 1; + dir->current_index = 0; + + /* Read PDS directory */ + if (read_pds_directory(name, &dir->member_list, &dir->member_count) != 0) { + free(dir); + errno = EIO; + log_error("opendir: Failed to read PDS directory: %s", name); + return NULL; + } + + log_info("opendir: Opened PDS directory: %s (%d members)", name, dir->member_count); + + return (DIR*)dir; +} + +DIR* opendir_zos(const char *name) { + if (IS_DATASET(name)) { + DEBUG_PRINT0("calling opendir-dataset\n"); + return opendir_dataset(name); + } else { + DEBUG_PRINT0("calling opendir-file\n"); + return opendir(name); + } +} + +static struct dirent* readdir_dataset(DIR *dirp) { + if (!dirp) { + errno = EINVAL; + return NULL; + } + + DatasetDir* dir = (DatasetDir*)dirp; + + /* Check if we've read all members */ + if (dir->current_index >= dir->member_count) { + return NULL; /* End of directory */ + } + + /* Allocate dirent structure (static for simplicity) */ + static struct dirent entry; + memset(&entry, 0, sizeof(entry)); + + /* Copy member name */ + strncpy(entry.d_name, dir->member_list[dir->current_index], sizeof(entry.d_name) - 1); + + dir->current_index++; + + log_trace("readdir: Read member: %s", entry.d_name); + + return &entry; +} + +struct dirent* readdir_zos(DIR *dirp) { + if (!dirp) { + return NULL; + } + + /* Check if this is a dataset directory */ + DatasetDir* dir = (DatasetDir*)dirp; + if (dir->is_dataset_dir) { + DEBUG_PRINT0("calling readdir-dataset\n"); + return readdir_dataset(dirp); + } else { + DEBUG_PRINT0("calling readdir-file\n"); + return readdir(dirp); + } +} + +static int closedir_dataset(DIR *dirp) { + if (!dirp) { + errno = EINVAL; + return -1; + } + + DatasetDir* dir = (DatasetDir*)dirp; + + /* Free member list */ + if (dir->member_list) { + for (int i = 0; i < dir->member_count; i++) { + free(dir->member_list[i]); + } + free(dir->member_list); + } + + log_info("closedir: Closed PDS directory: %s", dir->dataset_name); + + free(dir); + return 0; +} + +int closedir_zos(DIR *dirp) { + if (!dirp) { + return -1; + } + + /* Check if this is a dataset directory */ + DatasetDir* dir = (DatasetDir*)dirp; + if (dir->is_dataset_dir) { + DEBUG_PRINT0("calling closedir-dataset\n"); + return closedir_dataset(dirp); + } else { + DEBUG_PRINT0("calling closedir-file\n"); + return closedir(dirp); + } +} + +// Made with Bob - Phase 1 System Calls + + +/* Global state */ +GlobalStats g_stats = {0}; +dsio_log_level_t g_log_level = DSIO_LOG_ERROR; +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_NAME] = "Invalid dataset name", + [DSIO_ERR_NAME_TOO_LONG] = "Dataset name too long", + [DSIO_ERR_OPEN_FAILED] = "Failed to open dataset", + [DSIO_ERR_READ_FAILED] = "Failed to read from dataset", + [DSIO_ERR_WRITE_FAILED] = "Failed to write to dataset", + [DSIO_ERR_CLOSE_FAILED] = "Failed to close dataset", + [DSIO_ERR_ALLOC_FAILED] = "Memory allocation failed", + [DSIO_ERR_INVALID_FD] = "Invalid file descriptor", + [DSIO_ERR_INVALID_RECFM] = "Invalid record format", + [DSIO_ERR_INVALID_DSORG] = "Invalid dataset organization", + [DSIO_ERR_CCSID_CONVERSION] = "CCSID conversion failed", + [DSIO_ERR_BUFFER_OVERFLOW] = "Buffer overflow", + [DSIO_ERR_MEMBER_NOT_FOUND] = "Member not found", + [DSIO_ERR_NOT_A_DATASET] = "Not a dataset", + [DSIO_ERR_FLDATA_FAILED] = "fldata() failed", + [DSIO_ERR_FSEEK_FAILED] = "fseek() failed", + [DSIO_ERR_FTELL_FAILED] = "ftell() failed", + [DSIO_ERR_RECORD_TOO_LONG] = "Record too long", + [DSIO_ERR_UNSUPPORTED_OPERATION] = "Unsupported operation", + [DSIO_ERR_INTERNAL_ERROR] = "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; + } + + DatasetEntryEnhanced* entry = ENTRY_TO_ENHANCED(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"; + } + + DatasetEntryEnhanced* entry = ENTRY_TO_ENHANCED(dd); + return entry->error_message; +} + +void dsio_clear_error(int fd) { + void* dd = GET_DD(fd); + if (!dd || IS_FD(fd)) { + return; + } + + DatasetEntryEnhanced* entry = ENTRY_TO_ENHANCED(dd); + entry->last_error = DSIO_SUCCESS; + entry->error_message[0] = '\0'; +} + +void set_entry_error(DatasetEntryEnhanced* 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'; + } + + log_error("Error %d: %s", error, entry->error_message); + update_global_stats_error(); +} + +/* ======================================================================== + * 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"; + 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); + } + + 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 && g_log_level < DSIO_LOG_DEBUG) { + g_log_level = DSIO_LOG_DEBUG; + } +} + +void dsio_log(dsio_log_level_t level, const char* format, ...) { + if (level > g_log_level) { + return; + } + + FILE* stream = g_log_stream ? g_log_stream : stderr; + + const char* level_str; + switch (level) { + case DSIO_LOG_ERROR: level_str = "ERROR"; break; + case DSIO_LOG_WARN: level_str = "WARN "; break; + case DSIO_LOG_INFO: level_str = "INFO "; break; + case DSIO_LOG_DEBUG: level_str = "DEBUG"; break; + case DSIO_LOG_TRACE: level_str = "TRACE"; break; + default: level_str = "?????"; break; + } + + fprintf(stream, "[%s] ", level_str); + + va_list args; + va_start(args, format); + vfprintf(stream, format, args); + va_end(args); + + fprintf(stream, "\n"); + fflush(stream); +} + +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); + fprintf(stream, "\n"); + 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); + fprintf(stream, "\n"); + 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); + fprintf(stream, "\n"); + 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); + fprintf(stream, "\n"); + 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); + fprintf(stream, "\n"); + fflush(stream); + va_end(args); +} + +/* ======================================================================== + * STATISTICS IMPLEMENTATION + * ======================================================================== */ + +void update_read_stats(DatasetEntryEnhanced* entry, size_t bytes) { + if (!entry || !entry->stats_enabled) return; + + entry->bytes_read += bytes; + entry->read_operations++; + + if (g_stats.stats_enabled) { + g_stats.total_bytes_read += bytes; + g_stats.total_read_operations++; + } +} + +void update_write_stats(DatasetEntryEnhanced* entry, size_t bytes) { + if (!entry || !entry->stats_enabled) return; + + entry->bytes_written += bytes; + entry->write_operations++; + + if (g_stats.stats_enabled) { + g_stats.total_bytes_written += bytes; + g_stats.total_write_operations++; + } +} + +void update_global_stats_open(void) { + if (g_stats.stats_enabled) { + g_stats.total_open_operations++; + } +} + +void update_global_stats_close(void) { + if (g_stats.stats_enabled) { + g_stats.total_close_operations++; + } +} + +void update_global_stats_error(void) { + if (g_stats.stats_enabled) { + g_stats.total_errors++; + } +} + +int dsio_get_stats(int fd, dsio_stats_t* stats) { + if (!stats) return -1; + + void* dd = GET_DD(fd); + if (!dd || IS_FD(fd)) { + return -1; + } + + DatasetEntryEnhanced* entry = ENTRY_TO_ENHANCED(dd); + + stats->bytes_read = entry->bytes_read; + stats->bytes_written = entry->bytes_written; + stats->read_operations = entry->read_operations; + stats->write_operations = entry->write_operations; + stats->open_operations = 1; /* This fd was opened once */ + stats->close_operations = 0; /* Not closed yet */ + stats->errors = (entry->last_error != DSIO_SUCCESS) ? 1 : 0; + + return 0; +} + +int dsio_reset_stats(int fd) { + void* dd = GET_DD(fd); + if (!dd || IS_FD(fd)) { + return -1; + } + + DatasetEntryEnhanced* entry = ENTRY_TO_ENHANCED(dd); + + entry->bytes_read = 0; + entry->bytes_written = 0; + entry->read_operations = 0; + entry->write_operations = 0; + + return 0; +} + +void dsio_get_global_stats(dsio_stats_t* stats) { + if (!stats) return; + + stats->bytes_read = g_stats.total_bytes_read; + stats->bytes_written = g_stats.total_bytes_written; + stats->read_operations = g_stats.total_read_operations; + stats->write_operations = g_stats.total_write_operations; + stats->open_operations = g_stats.total_open_operations; + stats->close_operations = g_stats.total_close_operations; + stats->errors = g_stats.total_errors; +} + +void dsio_reset_global_stats(void) { + memset(&g_stats, 0, sizeof(g_stats)); + g_stats.stats_enabled = 1; /* Re-enable after reset */ +} + +/* ======================================================================== + * METADATA IMPLEMENTATION (Partial - showing key functions) + * This would use fldata() to get actual dataset attributes + * ======================================================================== */ + +int load_metadata_from_file(DatasetEntryEnhanced* entry) { + if (!entry || !entry->file_ptr) { + return -1; + } + + /* Use fldata() to get file information */ + fldata_t fdata; + if (fldata(entry->file_ptr, NULL, &fdata) != 0) { + set_entry_error(entry, DSIO_ERR_FLDATA_FAILED, "fldata() failed"); + return -1; + } + + /* Extract metadata from fldata */ + entry->recfm = detect_recfm_from_fldata(&fdata); + entry->dsorg = detect_dsorg_from_fldata(&fdata); + entry->lrecl = fdata.__maxreclen; + entry->blksize = fdata.__blksize; + + entry->metadata_loaded = 1; + + log_debug("Loaded metadata: RECFM=%s, LRECL=%d, BLKSIZE=%d", + dsio_recfm_to_string(entry->recfm), + entry->lrecl, + entry->blksize); + + return 0; +} + +/* Additional functions would be implemented here... */ +/* This is a partial implementation showing the key patterns */ + +// Made with Bob + + +/* ======================================================================== + * 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; +} + +DatasetEntryEnhanced* create_enhanced_entry(FILE* fp, unsigned short file_ccsid) { + DatasetEntryEnhanced* entry = calloc(1, sizeof(DatasetEntryEnhanced)); + if (!entry) { + log_error("Failed to allocate DatasetEntryEnhanced"); + 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; + entry->stats_enabled = 1; + entry->metadata_loaded = 0; + + /* Try to load metadata */ + if (fp) { + load_metadata_from_file(entry); + } + + return entry; +} + +void free_enhanced_entry(DatasetEntryEnhanced* entry) { + if (entry) { + free(entry); + } +} + +int parse_and_store_name(DatasetEntryEnhanced* 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->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; + } + + DatasetEntryEnhanced* entry = ENTRY_TO_ENHANCED(dd); + + /* Ensure metadata is loaded */ + if (!entry->metadata_loaded) { + if (load_metadata_from_file(entry) != 0) { + return -1; + } + } + + /* Copy metadata */ + metadata->recfm = entry->recfm; + metadata->dsorg = entry->dsorg; + metadata->lrecl = entry->lrecl; + 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); + strncpy(metadata->hlq, entry->hlq, sizeof(metadata->hlq) - 1); + strncpy(metadata->llq, entry->llq, sizeof(metadata->llq) - 1); + + 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; + } + + DatasetEntryEnhanced* entry = ENTRY_TO_ENHANCED(dd); + if (!entry->metadata_loaded) { + if (load_metadata_from_file(entry) != 0) { + return -1; + } + } + + *recfm = entry->recfm; + return 0; +} + +int dsio_get_lrecl(int fd, uint16_t* lrecl) { + if (!lrecl) return -1; + + void* dd = GET_DD(fd); + if (!dd || IS_FD(fd)) { + return -1; + } + + DatasetEntryEnhanced* entry = ENTRY_TO_ENHANCED(dd); + if (!entry->metadata_loaded) { + if (load_metadata_from_file(entry) != 0) { + return -1; + } + } + + *lrecl = entry->lrecl; + 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; + } + + DatasetEntryEnhanced* entry = ENTRY_TO_ENHANCED(dd); + if (!entry->metadata_loaded) { + if (load_metadata_from_file(entry) != 0) { + return -1; + } + } + + *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; + } + + DatasetEntryEnhanced* entry = ENTRY_TO_ENHANCED(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; + } + + DatasetEntryEnhanced* entry = ENTRY_TO_ENHANCED(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; + } + + DatasetEntryEnhanced* entry = ENTRY_TO_ENHANCED(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; + } + + DatasetEntryEnhanced* entry = ENTRY_TO_ENHANCED(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; + } + + DatasetEntryEnhanced* entry = ENTRY_TO_ENHANCED(dd); + return entry->is_pds_member; +} + +int dsio_is_readonly(int fd) { + void* dd = GET_DD(fd); + if (!dd || IS_FD(fd)) { + return 0; + } + + DatasetEntryEnhanced* entry = ENTRY_TO_ENHANCED(dd); + return entry->readonly; +} + +/* ======================================================================== + * UTILITY FUNCTIONS + * ======================================================================== */ + +int dsio_get_max_reclen(int fd) { + uint16_t lrecl; + if (dsio_get_lrecl(fd, &lrecl) != 0) { + return -1; + } + return (int)lrecl; +} + +int dsio_is_empty(int fd) { + void* dd = GET_DD(fd); + if (!dd || IS_FD(fd)) { + return -1; + } + + DatasetEntryEnhanced* entry = ENTRY_TO_ENHANCED(dd); + if (!entry->file_ptr) { + return -1; + } + + /* Check file size */ + long current_pos = ftell(entry->file_ptr); + if (current_pos < 0) { + return -1; + } + + if (fseek(entry->file_ptr, 0, SEEK_END) != 0) { + return -1; + } + + long size = ftell(entry->file_ptr); + fseek(entry->file_ptr, current_pos, SEEK_SET); + + return (size == 0) ? 1 : 0; +} + +ssize_t dsio_get_size(int fd) { + void* dd = GET_DD(fd); + if (!dd || IS_FD(fd)) { + return -1; + } + + DatasetEntryEnhanced* entry = ENTRY_TO_ENHANCED(dd); + if (!entry->file_ptr) { + return -1; + } + + /* Get file size */ + long current_pos = ftell(entry->file_ptr); + if (current_pos < 0) { + return -1; + } + + if (fseek(entry->file_ptr, 0, SEEK_END) != 0) { + return -1; + } + + long size = ftell(entry->file_ptr); + fseek(entry->file_ptr, current_pos, SEEK_SET); + + return (ssize_t)size; +} + +int dsio_flush(int fd) { + void* dd = GET_DD(fd); + if (!dd || IS_FD(fd)) { + return -1; + } + + DatasetEntryEnhanced* entry = ENTRY_TO_ENHANCED(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; + } + + DatasetEntryEnhanced* entry = ENTRY_TO_ENHANCED(dd); + + /* Store CCSID configuration */ + entry->file_ccsid = config->source_ccsid; + entry->program_ccsid = config->target_ccsid; + entry->conversion_state = config->conversion_enabled ? 1 : 0; + + log_debug("Set CCSID config: source=%d, target=%d, enabled=%d", + config->source_ccsid, config->target_ccsid, config->conversion_enabled); + + 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; + } + + DatasetEntryEnhanced* entry = ENTRY_TO_ENHANCED(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; +} + + + +// Made with Bob diff --git a/src/zos-io.cc b/src/zos-io.cc index 100cf206..752d742b 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'; @@ -902,6 +903,26 @@ int __open_ascii(const char *filename, int opts, ...) { return fd; } +int __open_ds_file(const char *filename, int opts, ...) { + int fd; + va_list ap; + va_start(ap, opts); + int perms = va_arg(ap, int); + + if (IS_DATASET(filename)) { + DEBUG_PRINT0("calling open-dataset\n"); + fd = open_dataset(filename, opts, perms); + } else { + DEBUG_PRINT0("calling open-file\n"); + fd = __open_ascii(filename, opts, perms); + if (fd >= 0) { + ADD_FD(fd); + } + } + va_end(ap); + return fd; +} + int __creat_ascii(const char *filename, mode_t mode) { return __open_ascii(filename, O_CREAT|O_WRONLY|O_TRUNC, mode); } @@ -977,12 +998,16 @@ int __mkstemp_ascii(char * tmpl) { } int __close(int fd) { - int ret = __close_orig(fd); - if (ret < 0) + if (IS_FD(fd)) { + DEBUG_PRINT0("calling close-file\n"); + int ret = __close_orig(fd); + if (ret >= 0) + __fd_close(fd); return ret; - - __fd_close(fd); - return ret; + } else { + DEBUG_PRINT0("calling close-dataset\n"); + return close_dataset(fd); + } } int __socketpair_ascii(int domain, int type, int protocol, int sv[2]) { From 1f81165f9acc040f31355188d414643a3bc7c7a3 Mon Sep 17 00:00:00 2001 From: sachintu47 Date: Wed, 4 Feb 2026 10:49:59 -0500 Subject: [PATCH 02/28] Add write, read, mkstemp for dataset --- include/stdlib.h | 3 ++- include/unistd.h | 11 ++++++++++ include/zos-datasetio.h | 3 +++ src/zos-datasetio.c | 44 +++---------------------------------- src/zos-io.cc | 48 +++++++++++++++++++++++++++++++++++++---- 5 files changed, 63 insertions(+), 46 deletions(-) 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/unistd.h b/include/unistd.h index 70047d8b..407a91c6 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,8 @@ 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); #if defined(__cplusplus) } @@ -32,11 +35,17 @@ __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 #include_next #undef pipe #undef close #undef sysconf #undef readlink +#undef write +#undef read #if defined(__cplusplus) extern "C" { @@ -49,6 +58,8 @@ __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"); #if defined(__cplusplus) diff --git a/include/zos-datasetio.h b/include/zos-datasetio.h index 6859e530..ab98f5b8 100644 --- a/include/zos-datasetio.h +++ b/include/zos-datasetio.h @@ -27,6 +27,9 @@ extern "C" { 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); /* * The following functions are only required for testing, diff --git a/src/zos-datasetio.c b/src/zos-datasetio.c index 092f3494..0805d904 100644 --- a/src/zos-datasetio.c +++ b/src/zos-datasetio.c @@ -70,7 +70,7 @@ static char* generate_name(char* tmplate) return tmplate; } -static int mkstemp_dataset(char* tmplate) +int mkstemp_dataset(char* tmplate) { void* dd; int fd = -1; @@ -201,7 +201,7 @@ int open_dataset(const char* name, int flags, mode_t mode) return fd; } -static ssize_t write_dataset(int fd, const void* buf, size_t count) +ssize_t write_dataset(int fd, const void* buf, size_t count) { void* dd = GET_DD(fd); @@ -236,7 +236,7 @@ static ssize_t write_dataset(int fd, const void* buf, size_t count) } } -static ssize_t read_dataset(int fd, void* buf, size_t count) +ssize_t read_dataset(int fd, void* buf, size_t count) { void* dd = GET_DD(fd); @@ -290,44 +290,6 @@ int close_dataset(int fd) return rc; } -ssize_t read_zos(int fd, void* buf, size_t count) -{ - if (IS_FD(fd)) { - DEBUG_PRINT0("calling read-file\n"); - return read(fd, buf, count); - } else { - DEBUG_PRINT0("calling read-dataset\n"); - return read_dataset(fd, buf, count); - } -} - -ssize_t write_zos(int fd, const void* buf, size_t count) -{ - if (IS_FD(fd)) { - DEBUG_PRINT0("calling write-file\n"); - return write(fd, buf, count); - } else { - DEBUG_PRINT0("calling write-dataset\n"); - return write_dataset(fd, buf, count); - } -} - -int mkstemp_zos(char* tmplate) -{ - int fd; - if (IS_DATASET(tmplate)) { - DEBUG_PRINT0("calling mkstemp-dataset\n"); - fd = mkstemp_dataset(tmplate); - } else { - DEBUG_PRINT0("calling mkstemp-file\n"); - fd = mkstemp(tmplate); - if (fd >= 0) { - ADD_FD(fd); - } - } - return fd; -} - char* temp_dataset_name(char* result) { char* orig = getenv("__POSIX_TMPNAM"); diff --git a/src/zos-io.cc b/src/zos-io.cc index 752d742b..b33f430c 100644 --- a/src/zos-io.cc +++ b/src/zos-io.cc @@ -834,6 +834,8 @@ 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"); int utmpxname(char * file) { char buf[PATH_MAX]; @@ -997,6 +999,43 @@ int __mkstemp_ascii(char * tmpl) { return ret; } +int __mkstemp_ds_file(char * tmpl) { + int fd; + if (IS_DATASET(tmpl)) { + DEBUG_PRINT0("calling mkstemp-dataset\n"); + fd = mkstemp_dataset(tmpl); + } else { + DEBUG_PRINT0("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_FD(fd)) { + DEBUG_PRINT0("calling write-file\n"); + return __write_orig(fd, buf, count); + } + else if(IS_DD(fd)) { + DEBUG_PRINT0("calling write-dataset\n"); + return write_dataset(fd, buf, count); + } +} + +ssize_t __read_ds_file(int fd, void *buf, size_t count) { + if (IS_FD(fd)) { + DEBUG_PRINT0("calling read-file\n"); + return __read_orig(fd, buf, count); + } + else if(IS_DD(fd)) { + DEBUG_PRINT0("calling read-dataset\n"); + return read_dataset(fd, buf, count); + } +} + int __close(int fd) { if (IS_FD(fd)) { DEBUG_PRINT0("calling close-file\n"); @@ -1004,7 +1043,8 @@ int __close(int fd) { if (ret >= 0) __fd_close(fd); return ret; - } else { + } + else if(IS_DD(fd)) { DEBUG_PRINT0("calling close-dataset\n"); return close_dataset(fd); } @@ -1305,7 +1345,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); @@ -1345,7 +1385,7 @@ static ssize_t ebcdic_writev(int fd, const struct iovec *iov, int iovcnt) { } // Write the entire converted buffer at once. - ssize_t written = write(fd, converted_buf, total_len); + ssize_t written = __write_orig(fd, converted_buf, total_len); if (using_heap) { free(converted_buf); @@ -1373,7 +1413,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 From 69885a04bf73b97a7814720835aef1581d62eccc Mon Sep 17 00:00:00 2001 From: sachintu47 Date: Thu, 5 Feb 2026 01:49:51 -0500 Subject: [PATCH 03/28] fopen override --- include/stdio.h | 3 ++- src/zos-io.cc | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) 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/src/zos-io.cc b/src/zos-io.cc index b33f430c..01681229 100644 --- a/src/zos-io.cc +++ b/src/zos-io.cc @@ -968,6 +968,15 @@ FILE *__fopen_ascii(const char *filename, const char *mode) { return fp; } +FILE *__fopen_ds_file(const char *filename, const char *mode) { + if (IS_DATASET(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) From d8f3bfc89c3fba688cd9bcb5569105eb28dbfc15 Mon Sep 17 00:00:00 2001 From: sachintu47 Date: Thu, 5 Feb 2026 02:17:12 -0500 Subject: [PATCH 04/28] lseek override --- include/unistd.h | 3 ++- include/zos-datasetio.h | 1 + src/zos-datasetio.c | 14 +------------- src/zos-io.cc | 12 ++++++++++++ 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/include/unistd.h b/include/unistd.h index 407a91c6..7a655d58 100644 --- a/include/unistd.h +++ b/include/unistd.h @@ -20,6 +20,7 @@ __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) } @@ -60,7 +61,7 @@ __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-datasetio.h b/include/zos-datasetio.h index ab98f5b8..06ffd9ac 100644 --- a/include/zos-datasetio.h +++ b/include/zos-datasetio.h @@ -30,6 +30,7 @@ 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); /* * The following functions are only required for testing, diff --git a/src/zos-datasetio.c b/src/zos-datasetio.c index 0805d904..0bfd64af 100644 --- a/src/zos-datasetio.c +++ b/src/zos-datasetio.c @@ -433,9 +433,7 @@ DatasetEntry* createDatasetEntry(FILE* dd, unsigned short file_ccsid) * lseek() - File Positioning * ======================================================================== */ -#undef lseek - -static off_t lseek_dataset(int fd, off_t offset, int whence) { +off_t lseek_dataset(int fd, off_t offset, int whence) { void* dd = GET_DD(fd); DatasetEntryEnhanced* dentry = (DatasetEntryEnhanced*)dd; FILE* fp = dentry->file_ptr; @@ -472,16 +470,6 @@ static off_t lseek_dataset(int fd, off_t offset, int whence) { return (off_t)pos; } -off_t lseek_zos(int fd, off_t offset, int whence) { - if (IS_FD(fd)) { - DEBUG_PRINT0("calling lseek-file\n"); - return lseek(fd, offset, whence); - } else { - DEBUG_PRINT0("calling lseek-dataset\n"); - return lseek_dataset(fd, offset, whence); - } -} - /* ======================================================================== * fstat() / stat() - File Metadata * ======================================================================== */ diff --git a/src/zos-io.cc b/src/zos-io.cc index 01681229..cc604896 100644 --- a/src/zos-io.cc +++ b/src/zos-io.cc @@ -836,6 +836,7 @@ 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 utmpxname(char * file) { char buf[PATH_MAX]; @@ -1059,6 +1060,17 @@ int __close(int fd) { } } +off_t __lseek_ds_file(int fd, off_t offset, int whence) { + if (IS_FD(fd)) { + DEBUG_PRINT0("calling lseek-file\n"); + return __lseek_orig(fd, offset, whence); + } + else { + DEBUG_PRINT0("calling lseek-dataset\n"); + return lseek_dataset(fd, offset, whence); + } +} + 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)) { From cad67d29b07c9f98f705f0dc5d27056fe155061e Mon Sep 17 00:00:00 2001 From: sachintu47 Date: Mon, 9 Feb 2026 04:10:44 -0500 Subject: [PATCH 05/28] Add default unix fds to database --- include/zos.h | 1 + src/zos.cc | 5 +++++ 2 files changed, 6 insertions(+) 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/src/zos.cc b/src/zos.cc index ad9018af..74b92c76 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> @@ -2673,6 +2674,10 @@ static void setProcessEnvars() { } int __zinit::initialize(const zoslib_config_t &aconfig) { + ADD_FD(STDIN_FILENO); + ADD_FD(STDOUT_FILENO); + ADD_FD(STDERR_FILENO); + memcpy(&config, &aconfig, sizeof(config)); __galloc_info = new __Cache; From 353de8790b1182a0ffcaa907b197f296d1a53535 Mon Sep 17 00:00:00 2001 From: sachintu47 Date: Mon, 9 Feb 2026 22:24:55 -0500 Subject: [PATCH 06/28] override stat, fstat --- include/sys/stat.h | 10 +++ include/zos-datasetio.h | 2 + src/zos-datasetio.c | 178 +++++++++++++++++++++++++++++----------- src/zos-io.cc | 21 +++++ 4 files changed, 162 insertions(+), 49 deletions(-) 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/zos-datasetio.h b/include/zos-datasetio.h index 06ffd9ac..bd3313c6 100644 --- a/include/zos-datasetio.h +++ b/include/zos-datasetio.h @@ -31,6 +31,8 @@ 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, diff --git a/src/zos-datasetio.c b/src/zos-datasetio.c index 0bfd64af..5163b805 100644 --- a/src/zos-datasetio.c +++ b/src/zos-datasetio.c @@ -425,10 +425,6 @@ DatasetEntry* createDatasetEntry(FILE* dd, unsigned short file_ccsid) } -/* ======================================================================== - * PHASE 1 SYSTEM CALL IMPLEMENTATIONS - * ======================================================================== */ - /* ======================================================================== * lseek() - File Positioning * ======================================================================== */ @@ -474,10 +470,8 @@ off_t lseek_dataset(int fd, off_t offset, int whence) { * fstat() / stat() - File Metadata * ======================================================================== */ -#undef fstat -#undef stat - /* Helper function to read ISPF statistics from PDS member */ +#if 0 static int read_ispf_stats(FILE* fp, struct ispf_stats* stats) { if (!fp || !stats) { return -1; @@ -496,7 +490,7 @@ static int read_ispf_stats(FILE* fp, struct ispf_stats* stats) { /* Get member name from fldata */ char member_name[9] = {0}; - if (fdata.__dsname != NULL) { + if (fdata.__dsname == NULL) { return -1; /* No member name */ } memcpy(member_name, fdata.__dsname, 8); @@ -505,70 +499,166 @@ static int read_ispf_stats(FILE* fp, struct ispf_stats* stats) { /* Use the ISPF reader module to get statistics */ return read_member_ispf_stats(fp, member_name, stats); } +#endif -static int fstat_dataset(int fd, struct stat *statbuf) { +int fstat_dataset(int fd, struct stat *statbuf) { if (!statbuf) { errno = EINVAL; return -1; } void* dd = GET_DD(fd); + if (!dd) { + errno = EBADF; + return -1; + } + DatasetEntryEnhanced* dentry = (DatasetEntryEnhanced*)dd; FILE* fp = dentry->file_ptr; + if (!fp) { + set_entry_error(dentry, DSIO_ERR_INVALID_FD, "Invalid file pointer"); + errno = EBADF; + return -1; + } + /* Initialize stat buffer */ memset(statbuf, 0, sizeof(struct stat)); + /* Ensure metadata is loaded */ + if (!dentry->metadata_loaded) { + if (load_metadata_from_file(dentry) != 0) { + log_warn("fstat: Failed to load metadata, continuing with limited info"); + } + } + /* Use fldata() to get dataset information */ fldata_t fdata; if (fldata(fp, NULL, &fdata) != 0) { set_entry_error(dentry, DSIO_ERR_FLDATA_FAILED, "fldata() failed"); + errno = EIO; return -1; } - /* Get file size using fseek/ftell */ - long current_pos = ftell(fp); - if (fseek(fp, 0, SEEK_END) == 0) { - long size = ftell(fp); - if (size >= 0) { - statbuf->st_size = size; - } - fseek(fp, current_pos, SEEK_SET); /* Restore position */ + /* Get file size using utility function */ + ssize_t size = dsio_get_size(fd); + if (size >= 0) { + statbuf->st_size = size; + } else { + log_warn("fstat: Failed to get file size"); + statbuf->st_size = 0; } - /* Fill in stat structure with dataset information */ - statbuf->st_mode = S_IFREG | S_IRUSR | S_IWUSR; /* Regular file, user r/w */ - statbuf->st_nlink = 1; - statbuf->st_blksize = fdata.__blksize; + /* Determine file mode based on dataset type */ + mode_t mode = S_IFREG; /* Regular file by default */ + + /* Check if PDS/PDSE without member (acts like directory) */ + if ((dsio_is_pds(fd) || dsio_is_pdse(fd)) && !dsio_has_member(fd)) { + mode = S_IFDIR; + } + + /* Set permissions based on readonly status */ + if (dsio_is_readonly(fd)) { + mode |= S_IRUSR | S_IRGRP | S_IROTH; /* Read-only */ + } else { + mode |= S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; /* Read-write for user */ + } - /* Calculate blocks used */ - if (statbuf->st_size > 0 && statbuf->st_blksize > 0) { - statbuf->st_blocks = (statbuf->st_size + statbuf->st_blksize - 1) / statbuf->st_blksize; + /* Add execute permission for directories */ + if (mode & S_IFDIR) { + mode |= S_IXUSR | S_IXGRP | S_IXOTH; } - /* Use enhanced metadata if available */ + statbuf->st_mode = mode; + statbuf->st_nlink = 1; + + /* Set block size from metadata or fldata */ if (dentry->metadata_loaded && dentry->blksize > 0) { statbuf->st_blksize = dentry->blksize; - statbuf->st_blocks = (statbuf->st_size + dentry->blksize - 1) / dentry->blksize; + } else if (fdata.__blksize > 0) { + statbuf->st_blksize = fdata.__blksize; + } else { + statbuf->st_blksize = 4096; /* Default */ } - log_debug("fstat: fd=%d, size=%ld, blksize=%ld, blocks=%ld", - fd, (long)statbuf->st_size, (long)statbuf->st_blksize, (long)statbuf->st_blocks); + /* Calculate blocks used (in 512-byte units for POSIX compatibility) */ + if (statbuf->st_size > 0) { + statbuf->st_blocks = (statbuf->st_size + 511) / 512; + } - return 0; -} - -int fstat_zos(int fd, struct stat *statbuf) { - if (IS_FD(fd)) { - DEBUG_PRINT0("calling fstat-file\n"); - return fstat(fd, statbuf); - } else { - DEBUG_PRINT0("calling fstat-dataset\n"); - return fstat_dataset(fd, statbuf); + /* Set device and inode numbers (simulated for datasets) */ + statbuf->st_dev = 0x5A05; /* 'ZOS' in hex */ + + /* Generate pseudo-inode from dataset name components */ + unsigned long inode = 0; + if (dentry->hlq[0]) { + for (int i = 0; dentry->hlq[i] && i < DSIO_MAX_QUALIFIER; i++) { + inode = (inode * 31) + (unsigned char)dentry->hlq[i]; + } + } + if (dentry->member_name[0]) { + for (int i = 0; dentry->member_name[i] && i < DSIO_MAX_MEMBER_NAME; i++) { + inode = (inode * 31) + (unsigned char)dentry->member_name[i]; + } } + statbuf->st_ino = inode ? inode : 1; + + /* Set user and group IDs */ + statbuf->st_uid = getuid(); + statbuf->st_gid = getgid(); + + /* Try to get ISPF statistics for timestamps (PDS members only) */ + int has_ispf = 0; + if (dsio_has_member(fd) && (dsio_is_pds(fd) || dsio_is_pdse(fd))) { + struct ispf_stats ispf_stats; + /* + if (read_ispf_stats(fp, &ispf_stats) == 0) { + has_ispf = 1; + + // Convert struct tm to time_t + statbuf->st_ctime = mktime(&ispf_stats.create_time); + statbuf->st_atime = statbuf->st_ctime; + + statbuf->st_mtime = mktime(&ispf_stats.mod_time); + if (statbuf->st_mtime == -1) { + statbuf->st_mtime = statbuf->st_ctime; + } + + log_debug("fstat: Using ISPF stats - created=%ld, modified=%ld, ver=%d.%d", + (long)statbuf->st_ctime, (long)statbuf->st_mtime, + ispf_stats.ver_num, ispf_stats.mod_num); + } + */ + } + + /* If no ISPF stats, try file system times or use current time */ + if (!has_ispf) { + int fileno_val = fileno(fp); + struct stat fs_stat; + + if (fileno_val >= 0 && fstat(fileno_val, &fs_stat) == 0) { + statbuf->st_atime = fs_stat.st_atime; + statbuf->st_mtime = fs_stat.st_mtime; + statbuf->st_ctime = fs_stat.st_ctime; + log_trace("fstat: Using file system timestamps"); + } else { + /* Fallback to current time */ + time_t now = time(NULL); + statbuf->st_atime = now; + statbuf->st_mtime = now; + statbuf->st_ctime = now; + log_trace("fstat: Using current time for timestamps"); + } + } + + log_debug("fstat: fd=%d, size=%ld, blksize=%ld, blocks=%ld, mode=%o, inode=%lu", + fd, (long)statbuf->st_size, (long)statbuf->st_blksize, + (long)statbuf->st_blocks, statbuf->st_mode, (unsigned long)statbuf->st_ino); + + return 0; } -static int stat_dataset(const char *pathname, struct stat *statbuf) { +int stat_dataset(const char *pathname, struct stat *statbuf) { if (!pathname || !statbuf) { errno = EINVAL; return -1; @@ -586,16 +676,6 @@ static int stat_dataset(const char *pathname, struct stat *statbuf) { return result; } -int stat_zos(const char *pathname, struct stat *statbuf) { - if (IS_DATASET(pathname)) { - DEBUG_PRINT0("calling stat-dataset\n"); - return stat_dataset(pathname, statbuf); - } else { - DEBUG_PRINT0("calling stat-file\n"); - return stat(pathname, statbuf); - } -} - /* ======================================================================== * opendir() / readdir() / closedir() - Directory Operations for PDS/PDSE * ======================================================================== */ diff --git a/src/zos-io.cc b/src/zos-io.cc index cc604896..60856646 100644 --- a/src/zos-io.cc +++ b/src/zos-io.cc @@ -837,6 +837,10 @@ 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"); +//#pragma map(__stat_orig, "stat") +//#pragma map(__fstat_orig, "fstat") +int __stat_orig(const char *path, struct stat *buf) asm("stat"); +int __fstat_orig(int fd, struct stat *buf) asm("fstat"); int utmpxname(char * file) { char buf[PATH_MAX]; @@ -1071,6 +1075,23 @@ off_t __lseek_ds_file(int fd, off_t offset, int whence) { } } +int __stat_ds_file(const char *pathname, struct stat *statbuf) { + if (IS_DATASET(pathname)) { + return stat_dataset(pathname, statbuf); + } else { + return __stat_orig(pathname, statbuf); + } +} + +int __fstat_ds_file(int fd, struct stat *statbuf) { + if (IS_DD(fd)) { + return fstat_dataset(fd, statbuf); + } else { + 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)) { From 97bf2b5dfa0d651d46b9a015357c5a3f9acbc387 Mon Sep 17 00:00:00 2001 From: sachintu47 Date: Mon, 16 Feb 2026 00:29:21 -0500 Subject: [PATCH 07/28] Fix fseek and filesize --- include/zos-datasetio.h | 23 ++++-- src/zos-datasetio.c | 154 ++++++++++++++++++++++++++++++++++++---- src/zos-io.cc | 61 ++++++++-------- 3 files changed, 191 insertions(+), 47 deletions(-) diff --git a/include/zos-datasetio.h b/include/zos-datasetio.h index bd3313c6..352a7212 100644 --- a/include/zos-datasetio.h +++ b/include/zos-datasetio.h @@ -321,8 +321,16 @@ int dsio_flush(int fd); #define GET_DD(fd) (descriptor_table[(fd)]) #define CLEAR_DD(fd) (descriptor_table[(fd)] = 0) -#define IS_FD(slot) ((((unsigned long long) (descriptor_table[(slot)])) & INV_ADDR_BIT) != 0) -#define IS_DD(slot) ((((unsigned long long) (descriptor_table[(slot)])) & INV_ADDR_BIT) == 0) +/* Validate descriptor slot is within bounds and occupied */ +#define IS_VALID_SLOT(slot) ((slot) >= 0 && (slot) < MAX_FDS && descriptor_table[(slot)] != NULL) + +/* Check if slot contains a file descriptor (has INV_ADDR_BIT set) */ +#define IS_FD(slot) (IS_VALID_SLOT(slot) && \ + ((((unsigned long long) (descriptor_table[(slot)])) & INV_ADDR_BIT) != 0)) + +/* Check if slot contains a dataset descriptor (no INV_ADDR_BIT) */ +#define IS_DD(slot) (IS_VALID_SLOT(slot) && \ + ((((unsigned long long) (descriptor_table[(slot)])) & INV_ADDR_BIT) == 0)) typedef struct DatasetEntry { FILE* file_ptr; @@ -344,12 +352,12 @@ typedef struct DatasetDir { #define GET_DUMMY_FD() (open("/dev/null", O_WRONLY, 0)) #define IS_DATASET(name) ((name) && ((name)[0] == '/') && ((name)[1] == '/')) -#ifdef DEBUG - #define DEBUG_PRINT0(str) puts(str) - #define DEBUG_PRINT1(fmt,field) (printf(fmt,field)) +#if 1 + #define DEBUG_PRINT0(str) dsio_debug_print(str) + #define DEBUG_PRINT1(fmt, ...) dsio_debug_printf(fmt, __VA_ARGS__) #else #define DEBUG_PRINT0(str) - #define DEBUG_PRINT1(fmt,field) + #define DEBUG_PRINT1(fmt, ...) #endif /* ======================================================================== @@ -447,6 +455,9 @@ void log_info(const char* format, ...); void log_debug(const char* format, ...); void log_trace(const char* format, ...); +void dsio_debug_print(const char* str); +void dsio_debug_printf(const char* format, ...); + /* CCSID conversion helpers */ void* convert_ebcdic_to_ascii(void* buf, size_t len); void* convert_ascii_to_ebcdic(void* buf, size_t len); diff --git a/src/zos-datasetio.c b/src/zos-datasetio.c index 5163b805..2a95f488 100644 --- a/src/zos-datasetio.c +++ b/src/zos-datasetio.c @@ -193,7 +193,7 @@ int open_dataset(const char* name, int flags, mode_t mode) fd = GET_DUMMY_FD(); ADD_DD(fd, dentry); - log_info("Opened dataset: %s (fd=%d, RECFM=%s, LRECL=%d)", + DEBUG_PRINT1("Opened dataset: %s (fd=%d, RECFM=%s, LRECL=%d)\n", name, fd, dsio_recfm_to_string(dentry->recfm), dentry->lrecl); @@ -212,6 +212,7 @@ ssize_t write_dataset(int fd, const void* buf, size_t count) DEBUG_PRINT1("In Write, File ccsid: %d\n", dentry->file_ccsid); DEBUG_PRINT1("In Write, Program ccsid: %d\n", dentry->program_ccsid); DEBUG_PRINT1("In Write, Conversion state %d\n", dentry->conversion_state); + DEBUG_PRINT1("write_dataset fd %d count %d\n", fd, count); char* write_buffer = (char *)malloc(count); if (write_buffer == NULL) { @@ -228,7 +229,7 @@ ssize_t write_dataset(int fd, const void* buf, size_t count) if (rc == count) { /* Update statistics on successful write */ update_write_stats(dentry, rc); - log_trace("Wrote %zu bytes to fd=%d", rc, fd); + DEBUG_PRINT1("Wrote %zu bytes to fd=%d\n", rc, fd); return (ssize_t) rc; } else { set_entry_error(dentry, DSIO_ERR_WRITE_FAILED, "fwrite() returned fewer bytes than requested"); @@ -247,14 +248,14 @@ ssize_t read_dataset(int fd, void* buf, size_t count) DEBUG_PRINT1("In Read, File ccsid: %d\n", dentry->file_ccsid); DEBUG_PRINT1("In Read, Program ccsid: %d\n", dentry->program_ccsid); DEBUG_PRINT1("In Read, Conversion state %d\n", dentry->conversion_state); - + DEBUG_PRINT1("read_dataset fd %d count %d\n", fd, count); size_t rc = fread(buf, 1, count, fp); if (rc > 0) { buf = convertBuffer(buf, dentry->file_ccsid, dentry->program_ccsid); /* Update statistics on successful read */ update_read_stats(dentry, rc); - log_trace("Read %zu bytes from fd=%d", rc, fd); + DEBUG_PRINT1("Read %zu bytes from fd=%d\n", rc, fd); } else if (rc == 0 && ferror(fp)) { set_entry_error(dentry, DSIO_ERR_READ_FAILED, "fread() failed"); return -1; @@ -270,7 +271,7 @@ int close_dataset(int fd) DatasetEntryEnhanced* dentry = (DatasetEntryEnhanced*) (dd); FILE* fp = dentry->file_ptr; - log_info("Closing dataset fd=%d (read=%zu bytes, wrote=%zu bytes, ops=%zu/%zu)", + DEBUG_PRINT1("Closing dataset fd=%d (read=%zu bytes, wrote=%zu bytes, ops=%zu/%zu)\n", fd, dentry->bytes_read, dentry->bytes_written, dentry->read_operations, dentry->write_operations); @@ -430,10 +431,41 @@ DatasetEntry* createDatasetEntry(FILE* dd, unsigned short file_ccsid) * ======================================================================== */ off_t lseek_dataset(int fd, off_t offset, int whence) { + /* Validate file descriptor */ + if (fd < 0 || fd >= MAX_FDS) { + errno = EBADF; + log_error("lseek_dataset: Invalid fd=%d", fd); + return -1; + } + void* dd = GET_DD(fd); + if (!dd) { + errno = EBADF; + log_error("lseek_dataset: No dataset descriptor for fd=%d", fd); + return -1; + } + + /* Check if this is actually a dataset fd (not a regular file fd) */ + if (IS_FD(fd)) { + errno = EINVAL; + log_error("lseek_dataset: fd=%d is not a dataset descriptor", fd); + return -1; + } + DatasetEntryEnhanced* dentry = (DatasetEntryEnhanced*)dd; + if (!dentry->file_ptr) { + errno = EBADF; + set_entry_error(dentry, DSIO_ERR_INVALID_FD, "Invalid file pointer"); + log_error("lseek_dataset: NULL file pointer for fd=%d", fd); + return -1; + } + FILE* fp = dentry->file_ptr; + /* Log the seek operation with correct format specifiers */ + DEBUG_PRINT1("lseek_dataset fd=%d offset=%lld whence=%d\n", fd, (long long)offset, whence); + + /* Validate whence parameter */ int fseek_whence; switch (whence) { case SEEK_SET: @@ -448,22 +480,78 @@ off_t lseek_dataset(int fd, off_t offset, int whence) { default: errno = EINVAL; set_entry_error(dentry, DSIO_ERR_FSEEK_FAILED, "Invalid whence parameter"); + log_error("lseek_dataset: Invalid whence=%d for fd=%d", whence, fd); return -1; } - if (fseek(fp, offset, fseek_whence) != 0) { + /* Load metadata if not already loaded to check record format constraints */ + if (!dentry->metadata_loaded) { + if (load_metadata_from_file(dentry) != 0) { + DEBUG_PRINT1("lseek_dataset: Failed to load metadata for fd=%d, continuing anyway", fd); + } + } + + /* Check for dataset-specific constraints based on record format + * Note: On z/OS, fseek/ftell behavior varies by record format: + * - Fixed (F, FB): Seeking works by byte offset, relatively predictable + * - Variable (V, VB): Seeking by byte offset is problematic due to RDWs + * - Undefined (U): Seeking is unreliable and not recommended + * - ASA formats: Additional complexity with carriage control characters + */ + if (dentry->metadata_loaded) { + dsio_recfm_t recfm = dentry->recfm; + + /* Variable-length records: seeking by byte offset doesn't align with record boundaries */ + if (recfm == DSIO_RECFM_V || recfm == DSIO_RECFM_VB || + recfm == DSIO_RECFM_VA || recfm == DSIO_RECFM_VBA) { + DEBUG_PRINT1("lseek_dataset: Seeking in variable-length dataset (RECFM=%s) - byte offsets may not align with record boundaries", + dsio_recfm_to_string(recfm)); + } + + /* Undefined format: seeking is not reliable */ + if (recfm == DSIO_RECFM_U) { + DEBUG_PRINT0("lseek_dataset: Seeking in undefined format dataset (RECFM=U) is not recommended and may produce unexpected results"); + } + + /* For fixed-length records, automatically align offset to record boundaries */ + if ((recfm == DSIO_RECFM_F || recfm == DSIO_RECFM_FB || + recfm == DSIO_RECFM_FA || recfm == DSIO_RECFM_FBA) && + whence == SEEK_SET && dentry->lrecl > 0) { + /* Check if offset aligns with record boundaries */ + if (offset % dentry->lrecl != 0) { + off_t aligned_offset = (offset / dentry->lrecl) * dentry->lrecl; + DEBUG_PRINT1("lseek_dataset: Offset %lld not aligned with LRECL %d, rounding down to %lld\n", + (long long)offset, dentry->lrecl, (long long)aligned_offset); + offset = aligned_offset; + } + } + } + + /* Perform the seek operation using fseeko() to handle off_t properly */ + if (fseeko(fp, offset, fseek_whence) != 0) { + int saved_errno = errno; set_entry_error(dentry, DSIO_ERR_FSEEK_FAILED, "fseek() failed"); + DEBUG_PRINT1("lseek_dataset: fseek() failed for fd=%d, offset=%lld, whence=%d, errno=%d (%s)", + fd, (long long)offset, whence, saved_errno, strerror(saved_errno)); + errno = saved_errno; return -1; } - long pos = ftell(fp); + /* Get the new position using ftello() to return off_t */ + off_t pos = ftello(fp); if (pos < 0) { - set_entry_error(dentry, DSIO_ERR_FTELL_FAILED, "ftell() failed"); + int saved_errno = errno; + set_entry_error(dentry, DSIO_ERR_FTELL_FAILED, "ftello() failed"); + DEBUG_PRINT1("lseek_dataset: ftello() failed for fd=%d, errno=%d (%s)", + fd, saved_errno, strerror(saved_errno)); + errno = saved_errno; return -1; } - log_trace("lseek: fd=%d, offset=%ld, whence=%d, new_pos=%ld", fd, offset, whence, pos); - return (off_t)pos; + DEBUG_PRINT1("lseek_dataset: fd=%d, offset=%lld, whence=%d, new_pos=%lld\n", + fd, (long long)offset, whence, (long long)pos); + + return pos; } /* ======================================================================== @@ -502,6 +590,7 @@ static int read_ispf_stats(FILE* fp, struct ispf_stats* stats) { #endif int fstat_dataset(int fd, struct stat *statbuf) { + DEBUG_PRINT1("Enter fstat_dataset %d\n", 0); if (!statbuf) { errno = EINVAL; return -1; @@ -521,6 +610,7 @@ int fstat_dataset(int fd, struct stat *statbuf) { errno = EBADF; return -1; } + DEBUG_PRINT1("fstat_dataset %d\n", 1); /* Initialize stat buffer */ memset(statbuf, 0, sizeof(struct stat)); @@ -651,7 +741,7 @@ int fstat_dataset(int fd, struct stat *statbuf) { } } - log_debug("fstat: fd=%d, size=%ld, blksize=%ld, blocks=%ld, mode=%o, inode=%lu", + DEBUG_PRINT1("fstat: fd=%d, size=%ld, blksize=%ld, blocks=%ld, mode=%o, inode=%lu", fd, (long)statbuf->st_size, (long)statbuf->st_blksize, (long)statbuf->st_blocks, statbuf->st_mode, (unsigned long)statbuf->st_ino); @@ -1222,6 +1312,29 @@ void dsio_log(dsio_log_level_t level, const char* format, ...) { fflush(stream); } +void dsio_debug_print(const char* str) { + if (g_log_stream == NULL) { + g_log_stream = fopen("zoslib.debug.log", "a"); + } + if (g_log_stream) { + fprintf(g_log_stream, "%s", str); + fflush(g_log_stream); + } +} + +void dsio_debug_printf(const char* format, ...) { + if (g_log_stream == NULL) { + g_log_stream = fopen("zoslib.debug.log", "a"); + } + if (g_log_stream) { + va_list args; + va_start(args, format); + vfprintf(g_log_stream, format, args); + va_end(args); + fflush(g_log_stream); + } +} + void log_error(const char* format, ...) { if (DSIO_LOG_ERROR > g_log_level) return; @@ -1788,7 +1901,7 @@ ssize_t dsio_get_size(int fd) { if (!entry->file_ptr) { return -1; } - +#if 0 /* Get file size */ long current_pos = ftell(entry->file_ptr); if (current_pos < 0) { @@ -1803,6 +1916,23 @@ ssize_t dsio_get_size(int fd) { fseek(entry->file_ptr, current_pos, SEEK_SET); return (ssize_t)size; +#endif + long long total = 0; + char buf[8192]; + + long cur = ftell(entry->file_ptr); + fseek(entry->file_ptr, 0, SEEK_SET); + + while (1) { + size_t n = fread(buf, 1, sizeof(buf), entry->file_ptr); + total += n; + if (n == 0) + break; + } + DEBUG_PRINT1("get_size fd %d size %d\n", fd, total); + + fseek(entry->file_ptr, cur, SEEK_SET); + return (ssize_t)total; } int dsio_flush(int fd) { diff --git a/src/zos-io.cc b/src/zos-io.cc index 60856646..c67f7a86 100644 --- a/src/zos-io.cc +++ b/src/zos-io.cc @@ -837,9 +837,7 @@ 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"); -//#pragma map(__stat_orig, "stat") -//#pragma map(__fstat_orig, "fstat") -int __stat_orig(const char *path, struct stat *buf) asm("stat"); +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) { @@ -916,15 +914,16 @@ int __open_ds_file(const char *filename, int opts, ...) { va_start(ap, opts); int perms = va_arg(ap, int); + DEBUG_PRINT1("calling __open_ds_file file %s\n", filename); if (IS_DATASET(filename)) { - DEBUG_PRINT0("calling open-dataset\n"); fd = open_dataset(filename, opts, perms); + DEBUG_PRINT1("calling open-dataset fd %d\n", fd); } else { - DEBUG_PRINT0("calling open-file\n"); fd = __open_ascii(filename, opts, perms); if (fd >= 0) { ADD_FD(fd); } + DEBUG_PRINT1("calling open-file fd %d\n", fd); } va_end(ap); return fd; @@ -1029,64 +1028,68 @@ int __mkstemp_ds_file(char * tmpl) { } ssize_t __write_ds_file(int fd, const void *buf, size_t count) { - if (IS_FD(fd)) { - DEBUG_PRINT0("calling write-file\n"); - return __write_orig(fd, buf, count); - } - else if(IS_DD(fd)) { - DEBUG_PRINT0("calling write-dataset\n"); + if (IS_DD(fd)) { + DEBUG_PRINT1("calling write-dataset fd %d\n", fd); return write_dataset(fd, buf, count); + } + else if (IS_FD(fd)) { + DEBUG_PRINT1("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_FD(fd)) { - DEBUG_PRINT0("calling read-file\n"); - return __read_orig(fd, buf, count); - } - else if(IS_DD(fd)) { - DEBUG_PRINT0("calling read-dataset\n"); + if (IS_DD(fd)) { + DEBUG_PRINT1("calling read-dataset fd %d\n", fd); return read_dataset(fd, buf, count); + } + else if (IS_FD(fd)) { + DEBUG_PRINT1("calling read-file fd %d\n", fd); + return __read_orig(fd, buf, count); } } int __close(int fd) { - if (IS_FD(fd)) { - DEBUG_PRINT0("calling close-file\n"); + if (IS_DD(fd)) { + DEBUG_PRINT1("calling close-dataset fd %d\n", fd); + return close_dataset(fd); + } + else if (IS_FD(fd)) { + DEBUG_PRINT1("calling close-file fd %d\n", fd); int ret = __close_orig(fd); if (ret >= 0) __fd_close(fd); return ret; - } - else if(IS_DD(fd)) { - DEBUG_PRINT0("calling close-dataset\n"); - return close_dataset(fd); } } off_t __lseek_ds_file(int fd, off_t offset, int whence) { - if (IS_FD(fd)) { - DEBUG_PRINT0("calling lseek-file\n"); - return __lseek_orig(fd, offset, whence); - } - else { - DEBUG_PRINT0("calling lseek-dataset\n"); + if (IS_DD(fd)) { + DEBUG_PRINT1("calling lseek-dataset fd %d offset %d whence %d\n", fd, offset, whence); return lseek_dataset(fd, offset, whence); } + else if (IS_FD(fd)) { + DEBUG_PRINT1("calling lseek-file fd %d offset %d whence %d\n", fd, offset, whence); + return __lseek_orig(fd, offset, whence); + } } int __stat_ds_file(const char *pathname, struct stat *statbuf) { if (IS_DATASET(pathname)) { + DEBUG_PRINT1("calling stat-dataset path %s\n", pathname); return stat_dataset(pathname, statbuf); } else { + DEBUG_PRINT1("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)) { + DEBUG_PRINT1("calling fstat-dataset11111111111111111111111 fd %d\n", fd); return fstat_dataset(fd, statbuf); } else { + DEBUG_PRINT1("calling fstat-file22222222222222222222 fd %d\n", fd); return __fstat_orig(fd, statbuf); } } From 6100fb85a5026eb5a77b05785fc642a198b89e18 Mon Sep 17 00:00:00 2001 From: sachintu47 Date: Wed, 25 Feb 2026 07:25:18 -0500 Subject: [PATCH 08/28] refactor datasetentry structure --- include/zos-datasetio.h | 89 +++++++++++++++++++---------------------- src/zos-datasetio.c | 74 +++++++++++++++++----------------- 2 files changed, 78 insertions(+), 85 deletions(-) diff --git a/include/zos-datasetio.h b/include/zos-datasetio.h index 352a7212..6d1824f7 100644 --- a/include/zos-datasetio.h +++ b/include/zos-datasetio.h @@ -333,42 +333,7 @@ int dsio_flush(int fd); ((((unsigned long long) (descriptor_table[(slot)])) & INV_ADDR_BIT) == 0)) typedef struct DatasetEntry { - FILE* file_ptr; - unsigned short file_ccsid; - unsigned short process_ccsid; - unsigned short program_ccsid; - unsigned char conversion_state; -} DatasetEntry; - -/* Directory structure for PDS/PDSE member listing */ -typedef struct DatasetDir { - char dataset_name[256]; - char** member_list; - int member_count; - int current_index; - int is_dataset_dir; -} DatasetDir; - -#define GET_DUMMY_FD() (open("/dev/null", O_WRONLY, 0)) -#define IS_DATASET(name) ((name) && ((name)[0] == '/') && ((name)[1] == '/')) - -#if 1 - #define DEBUG_PRINT0(str) dsio_debug_print(str) - #define DEBUG_PRINT1(fmt, ...) dsio_debug_printf(fmt, __VA_ARGS__) -#else - #define DEBUG_PRINT0(str) - #define DEBUG_PRINT1(fmt, ...) -#endif - -/* ======================================================================== - * ENHANCED INTERNAL STRUCTURES - * ======================================================================== */ - -extern void* descriptor_table[MAX_FDS]; - -/* Enhanced DatasetEntry structure */ -typedef struct DatasetEntryEnhanced { - /* Original fields from DatasetEntry */ + /* Original fields */ FILE* file_ptr; unsigned short file_ccsid; unsigned short process_ccsid; @@ -402,7 +367,35 @@ typedef struct DatasetEntryEnhanced { int metadata_loaded; int stats_enabled; -} DatasetEntryEnhanced; +} DatasetEntry; + +/* Directory structure for PDS/PDSE member listing */ +typedef struct DatasetDir { + char dataset_name[256]; + char** member_list; + int member_count; + int current_index; + int is_dataset_dir; +} DatasetDir; + +#define GET_DUMMY_FD() (open("/dev/null", O_WRONLY, 0)) +#define IS_DATASET(name) ((name) && ((name)[0] == '/') && ((name)[1] == '/')) + +#if 1 + #define DEBUG_PRINT0(str) dsio_debug_print(str) + #define DEBUG_PRINT1(fmt, ...) dsio_debug_printf(fmt, __VA_ARGS__) +#else + #define DEBUG_PRINT0(str) + #define DEBUG_PRINT1(fmt, ...) +#endif + +/* ======================================================================== + * ENHANCED INTERNAL STRUCTURES + * ======================================================================== */ + +extern void* descriptor_table[MAX_FDS]; + + /* Global statistics */ typedef struct { @@ -426,24 +419,24 @@ extern int g_debug_enabled; * INTERNAL HELPER FUNCTIONS * ======================================================================== */ -/* Create enhanced dataset entry */ -DatasetEntryEnhanced* create_enhanced_entry(FILE* fp, unsigned short file_ccsid); +/* Create dataset entry */ +DatasetEntry* create_entry(FILE* fp, unsigned short file_ccsid); -/* Free enhanced dataset entry */ -void free_enhanced_entry(DatasetEntryEnhanced* entry); +/* Free dataset entry */ +void free_entry(DatasetEntry* entry); /* Load metadata from FILE* using fldata() */ -int load_metadata_from_file(DatasetEntryEnhanced* entry); +int load_metadata_from_file(DatasetEntry* entry); /* Parse dataset name and extract components */ -int parse_and_store_name(DatasetEntryEnhanced* entry, const char* dataset_name); +int parse_and_store_name(DatasetEntry* entry, const char* dataset_name); /* Set error on entry */ -void set_entry_error(DatasetEntryEnhanced* entry, dsio_error_t error, const char* message); +void set_entry_error(DatasetEntry* entry, dsio_error_t error, const char* message); /* Update statistics */ -void update_read_stats(DatasetEntryEnhanced* entry, size_t bytes); -void update_write_stats(DatasetEntryEnhanced* entry, size_t bytes); +void update_read_stats(DatasetEntry* entry, size_t bytes); +void update_write_stats(DatasetEntry* entry, size_t bytes); void update_global_stats_open(void); void update_global_stats_close(void); void update_global_stats_error(void); @@ -469,8 +462,8 @@ 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_ENHANCED(entry) ((DatasetEntryEnhanced*)(entry)) -#define IS_ENHANCED_ENTRY(entry) ((entry) && ((DatasetEntryEnhanced*)(entry))->metadata_loaded >= 0) +#define ENTRY_TO(entry) ((DatasetEntry*)(entry)) +#define IS_ENTRY(entry) ((entry) && ((DatasetEntry*)(entry))->metadata_loaded >= 0) /* Error message templates */ #define ERR_MSG_INVALID_NAME "Invalid dataset name: %s" diff --git a/src/zos-datasetio.c b/src/zos-datasetio.c index 2a95f488..fcb08d7e 100644 --- a/src/zos-datasetio.c +++ b/src/zos-datasetio.c @@ -89,7 +89,7 @@ int mkstemp_dataset(char* tmplate) } /* Use enhanced entry creation */ - DatasetEntryEnhanced* dentry = create_enhanced_entry(dd, 1047); + DatasetEntry* dentry = create_entry(dd, 1047); if (!dentry) { fclose(dd); return -1; @@ -178,7 +178,7 @@ int open_dataset(const char* name, int flags, mode_t mode) } /* Use enhanced entry creation with metadata loading */ - DatasetEntryEnhanced* dentry = create_enhanced_entry(dd, 1047); + DatasetEntry* dentry = create_entry(dd, 1047); if (!dentry) { fclose(dd); return -1; @@ -205,7 +205,7 @@ ssize_t write_dataset(int fd, const void* buf, size_t count) { void* dd = GET_DD(fd); - DatasetEntryEnhanced* dentry = (DatasetEntryEnhanced*) (dd); + DatasetEntry* dentry = (DatasetEntry*) (dd); FILE* fp = dentry->file_ptr; DEBUG_PRINT1("In Write, File ptr ccsid: %p\n", dentry->file_ptr); @@ -241,7 +241,7 @@ ssize_t read_dataset(int fd, void* buf, size_t count) { void* dd = GET_DD(fd); - DatasetEntryEnhanced* dentry = (DatasetEntryEnhanced*) (dd); + DatasetEntry* dentry = (DatasetEntry*) (dd); FILE* fp = dentry->file_ptr; DEBUG_PRINT1("In Read, File ptr ccsid: %p\n", dentry->file_ptr); @@ -268,7 +268,7 @@ int close_dataset(int fd) { void* dd = GET_DD(fd); - DatasetEntryEnhanced* dentry = (DatasetEntryEnhanced*) (dd); + DatasetEntry* dentry = (DatasetEntry*) (dd); FILE* fp = dentry->file_ptr; DEBUG_PRINT1("Closing dataset fd=%d (read=%zu bytes, wrote=%zu bytes, ops=%zu/%zu)\n", @@ -283,7 +283,7 @@ int close_dataset(int fd) close(fd); /* Deallocate enhanced DatasetEntry */ - free_enhanced_entry(dentry); + free_entry(dentry); CLEAR_DD(fd); } else { set_entry_error(dentry, DSIO_ERR_CLOSE_FAILED, "fclose() failed"); @@ -452,7 +452,7 @@ off_t lseek_dataset(int fd, off_t offset, int whence) { return -1; } - DatasetEntryEnhanced* dentry = (DatasetEntryEnhanced*)dd; + DatasetEntry* dentry = (DatasetEntry*)dd; if (!dentry->file_ptr) { errno = EBADF; set_entry_error(dentry, DSIO_ERR_INVALID_FD, "Invalid file pointer"); @@ -602,7 +602,7 @@ int fstat_dataset(int fd, struct stat *statbuf) { return -1; } - DatasetEntryEnhanced* dentry = (DatasetEntryEnhanced*)dd; + DatasetEntry* dentry = (DatasetEntry*)dd; FILE* fp = dentry->file_ptr; if (!fp) { @@ -976,7 +976,7 @@ dsio_error_t dsio_get_last_error(int fd) { return DSIO_ERR_INVALID_FD; } - DatasetEntryEnhanced* entry = ENTRY_TO_ENHANCED(dd); + DatasetEntry* entry = ENTRY_TO(dd); return entry->last_error; } @@ -986,7 +986,7 @@ const char* dsio_get_error_message(int fd) { return "Invalid file descriptor"; } - DatasetEntryEnhanced* entry = ENTRY_TO_ENHANCED(dd); + DatasetEntry* entry = ENTRY_TO(dd); return entry->error_message; } @@ -996,12 +996,12 @@ void dsio_clear_error(int fd) { return; } - DatasetEntryEnhanced* entry = ENTRY_TO_ENHANCED(dd); + DatasetEntry* entry = ENTRY_TO(dd); entry->last_error = DSIO_SUCCESS; entry->error_message[0] = '\0'; } -void set_entry_error(DatasetEntryEnhanced* entry, dsio_error_t error, const char* message) { +void set_entry_error(DatasetEntry* entry, dsio_error_t error, const char* message) { if (!entry) return; entry->last_error = error; @@ -1404,7 +1404,7 @@ void log_trace(const char* format, ...) { * STATISTICS IMPLEMENTATION * ======================================================================== */ -void update_read_stats(DatasetEntryEnhanced* entry, size_t bytes) { +void update_read_stats(DatasetEntry* entry, size_t bytes) { if (!entry || !entry->stats_enabled) return; entry->bytes_read += bytes; @@ -1416,7 +1416,7 @@ void update_read_stats(DatasetEntryEnhanced* entry, size_t bytes) { } } -void update_write_stats(DatasetEntryEnhanced* entry, size_t bytes) { +void update_write_stats(DatasetEntry* entry, size_t bytes) { if (!entry || !entry->stats_enabled) return; entry->bytes_written += bytes; @@ -1454,7 +1454,7 @@ int dsio_get_stats(int fd, dsio_stats_t* stats) { return -1; } - DatasetEntryEnhanced* entry = ENTRY_TO_ENHANCED(dd); + DatasetEntry* entry = ENTRY_TO(dd); stats->bytes_read = entry->bytes_read; stats->bytes_written = entry->bytes_written; @@ -1473,7 +1473,7 @@ int dsio_reset_stats(int fd) { return -1; } - DatasetEntryEnhanced* entry = ENTRY_TO_ENHANCED(dd); + DatasetEntry* entry = ENTRY_TO(dd); entry->bytes_read = 0; entry->bytes_written = 0; @@ -1505,7 +1505,7 @@ void dsio_reset_global_stats(void) { * This would use fldata() to get actual dataset attributes * ======================================================================== */ -int load_metadata_from_file(DatasetEntryEnhanced* entry) { +int load_metadata_from_file(DatasetEntry* entry) { if (!entry || !entry->file_ptr) { return -1; } @@ -1587,10 +1587,10 @@ static dsio_dsorg_t detect_dsorg_from_fldata(const fldata_t* fdata) { return DSIO_DSORG_UNKNOWN; } -DatasetEntryEnhanced* create_enhanced_entry(FILE* fp, unsigned short file_ccsid) { - DatasetEntryEnhanced* entry = calloc(1, sizeof(DatasetEntryEnhanced)); +DatasetEntry* create_entry(FILE* fp, unsigned short file_ccsid) { + DatasetEntry* entry = calloc(1, sizeof(DatasetEntry)); if (!entry) { - log_error("Failed to allocate DatasetEntryEnhanced"); + log_error("Failed to allocate DatasetEntry"); return NULL; } @@ -1610,13 +1610,13 @@ DatasetEntryEnhanced* create_enhanced_entry(FILE* fp, unsigned short file_ccsid) return entry; } -void free_enhanced_entry(DatasetEntryEnhanced* entry) { +void free_entry(DatasetEntry* entry) { if (entry) { free(entry); } } -int parse_and_store_name(DatasetEntryEnhanced* entry, const char* dataset_name) { +int parse_and_store_name(DatasetEntry* entry, const char* dataset_name) { if (!entry || !dataset_name) { return -1; } @@ -1658,7 +1658,7 @@ int dsio_get_metadata(int fd, dsio_metadata_t* metadata) { return -1; } - DatasetEntryEnhanced* entry = ENTRY_TO_ENHANCED(dd); + DatasetEntry* entry = ENTRY_TO(dd); /* Ensure metadata is loaded */ if (!entry->metadata_loaded) { @@ -1692,7 +1692,7 @@ int dsio_get_recfm(int fd, dsio_recfm_t* recfm) { return -1; } - DatasetEntryEnhanced* entry = ENTRY_TO_ENHANCED(dd); + DatasetEntry* entry = ENTRY_TO(dd); if (!entry->metadata_loaded) { if (load_metadata_from_file(entry) != 0) { return -1; @@ -1711,7 +1711,7 @@ int dsio_get_lrecl(int fd, uint16_t* lrecl) { return -1; } - DatasetEntryEnhanced* entry = ENTRY_TO_ENHANCED(dd); + DatasetEntry* entry = ENTRY_TO(dd); if (!entry->metadata_loaded) { if (load_metadata_from_file(entry) != 0) { return -1; @@ -1730,7 +1730,7 @@ int dsio_get_dsorg(int fd, dsio_dsorg_t* dsorg) { return -1; } - DatasetEntryEnhanced* entry = ENTRY_TO_ENHANCED(dd); + DatasetEntry* entry = ENTRY_TO(dd); if (!entry->metadata_loaded) { if (load_metadata_from_file(entry) != 0) { return -1; @@ -1747,7 +1747,7 @@ int dsio_get_ccsid(int fd, uint16_t* file_ccsid, uint16_t* program_ccsid) { return -1; } - DatasetEntryEnhanced* entry = ENTRY_TO_ENHANCED(dd); + DatasetEntry* entry = ENTRY_TO(dd); if (file_ccsid) { *file_ccsid = entry->file_ccsid; @@ -1767,7 +1767,7 @@ int dsio_get_member_name(int fd, char* member, size_t len) { return -1; } - DatasetEntryEnhanced* entry = ENTRY_TO_ENHANCED(dd); + DatasetEntry* entry = ENTRY_TO(dd); strncpy(member, entry->member_name, len - 1); member[len - 1] = '\0'; @@ -1782,7 +1782,7 @@ int dsio_get_hlq(int fd, char* hlq, size_t len) { return -1; } - DatasetEntryEnhanced* entry = ENTRY_TO_ENHANCED(dd); + DatasetEntry* entry = ENTRY_TO(dd); strncpy(hlq, entry->hlq, len - 1); hlq[len - 1] = '\0'; @@ -1797,7 +1797,7 @@ int dsio_get_llq(int fd, char* llq, size_t len) { return -1; } - DatasetEntryEnhanced* entry = ENTRY_TO_ENHANCED(dd); + DatasetEntry* entry = ENTRY_TO(dd); strncpy(llq, entry->llq, len - 1); llq[len - 1] = '\0'; @@ -1838,7 +1838,7 @@ int dsio_has_member(int fd) { return 0; } - DatasetEntryEnhanced* entry = ENTRY_TO_ENHANCED(dd); + DatasetEntry* entry = ENTRY_TO(dd); return entry->is_pds_member; } @@ -1848,7 +1848,7 @@ int dsio_is_readonly(int fd) { return 0; } - DatasetEntryEnhanced* entry = ENTRY_TO_ENHANCED(dd); + DatasetEntry* entry = ENTRY_TO(dd); return entry->readonly; } @@ -1870,7 +1870,7 @@ int dsio_is_empty(int fd) { return -1; } - DatasetEntryEnhanced* entry = ENTRY_TO_ENHANCED(dd); + DatasetEntry* entry = ENTRY_TO(dd); if (!entry->file_ptr) { return -1; } @@ -1897,7 +1897,7 @@ ssize_t dsio_get_size(int fd) { return -1; } - DatasetEntryEnhanced* entry = ENTRY_TO_ENHANCED(dd); + DatasetEntry* entry = ENTRY_TO(dd); if (!entry->file_ptr) { return -1; } @@ -1941,7 +1941,7 @@ int dsio_flush(int fd) { return -1; } - DatasetEntryEnhanced* entry = ENTRY_TO_ENHANCED(dd); + DatasetEntry* entry = ENTRY_TO(dd); if (!entry->file_ptr) { return -1; } @@ -1962,7 +1962,7 @@ int dsio_set_ccsid_config(int fd, const dsio_ccsid_config_t* config) { return -1; } - DatasetEntryEnhanced* entry = ENTRY_TO_ENHANCED(dd); + DatasetEntry* entry = ENTRY_TO(dd); /* Store CCSID configuration */ entry->file_ccsid = config->source_ccsid; @@ -1983,7 +1983,7 @@ int dsio_get_ccsid_config(int fd, dsio_ccsid_config_t* config) { return -1; } - DatasetEntryEnhanced* entry = ENTRY_TO_ENHANCED(dd); + DatasetEntry* entry = ENTRY_TO(dd); config->source_ccsid = entry->file_ccsid; config->target_ccsid = entry->program_ccsid; From 3064bbe19eb0e57d9e42fe5248f42eb9f511f0a2 Mon Sep 17 00:00:00 2001 From: sachintu47 Date: Mon, 16 Mar 2026 03:28:04 -0400 Subject: [PATCH 09/28] feat(dsio): implement robust stream emulation for z/OS datasets - Refactor DatasetEntry to support byte-stream emulation with record buffering. - Implement newline insertion/stripping and record padding for FB datasets. - Optimize FB dataset I/O by reopening in binary mode for multi-record access. - Add VB size caching to improve performance of fstat and SEEK_END. - Fix write_dataset to prevent data loss at record boundaries and add bounds checking. - Use length-safe dsio_convert_buffer for all CCSID conversions. - Consolidate entry initialization and simplify redundant size checks. --- include/zos-datasetio.h | 14 + src/zos-datasetio.c | 957 +++++++++++++++++++++++++--------------- 2 files changed, 608 insertions(+), 363 deletions(-) diff --git a/include/zos-datasetio.h b/include/zos-datasetio.h index 6d1824f7..734f337a 100644 --- a/include/zos-datasetio.h +++ b/include/zos-datasetio.h @@ -367,6 +367,20 @@ typedef struct DatasetEntry { int metadata_loaded; int stats_enabled; + /* 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 */ + size_t reclen; /* logical record length from fldata */ + 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 */ + + /* VB size caching */ + int vb_size_calculated; /* 1 if VB size has been calculated */ + size_t vb_cached_size; /* Cached emulated size for VB datasets */ } DatasetEntry; /* Directory structure for PDS/PDSE member listing */ diff --git a/src/zos-datasetio.c b/src/zos-datasetio.c index fcb08d7e..180e2c91 100644 --- a/src/zos-datasetio.c +++ b/src/zos-datasetio.c @@ -106,6 +106,117 @@ int mkstemp_dataset(char* tmplate) return fd; } +/* + * 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. + */ + const char* fopen_mode; + if (flags & O_RDONLY) { + fopen_mode = "rb,type=record,recfm=+"; + } else if (flags & O_WRONLY) { + fopen_mode = (flags & O_APPEND) ? "ab,type=record,recfm=+,noseek" : "wb,type=record,recfm=+,noseek"; + } else if (flags & O_RDWR) { + fopen_mode = (flags & O_APPEND) ? "ab+,type=record,recfm=+" : "rb+,type=record,recfm=+"; + } else { + fopen_mode = "rb,type=record,recfm=+"; + } + + DEBUG_PRINT1("Open with mode %s\n", fopen_mode); + FILE* dd = fopen(name, fopen_mode); + if (!dd) { + perror("dataset open failed"); + return -1; + } + + DatasetEntry* dentry = create_entry(dd, file_ccsid); + if (!dentry) { + fclose(dd); + 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 = fld.__recfmF ? 2 /* F */ : + fld.__recfmV ? 1 /* V */ : + fld.__recfmU ? 3 /* U */ : 0; + 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; + } else { + /* Default to FB80 if fldata fails */ + dentry->recfm = 2; + 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 (flags & O_RDONLY) { + reopen_mode = "rb,recfm=+"; + } else if (flags & O_WRONLY) { + reopen_mode = (flags & O_APPEND) ? "ab,recfm=+,noseek" : "wb,recfm=+,noseek"; + } else if (flags & O_RDWR) { + reopen_mode = (flags & O_APPEND) ? "ab+,recfm=+" : "rb+,recfm=+"; + } else { + reopen_mode = "rb,recfm=+"; + } + DEBUG_PRINT1("FB optimization: reopening with mode %s\n", reopen_mode); + dd = fopen(name, reopen_mode); + if (!dd) { + free(dentry); + return -1; + } + dentry->file_ptr = dd; + } + + /* + * 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) { + fclose(dd); + free(dentry); + return -1; + } + + int fd = GET_DUMMY_FD(); + ADD_DD(fd, dentry); + return fd; +} + int open_dataset(const char* name, int flags, mode_t mode) { /* Start with support for the following 'access mode' flags: @@ -120,37 +231,27 @@ int open_dataset(const char* name, int flags, mode_t mode) * does not already exist - we want this to be a failure * https://tech.mikefulton.ca/fopen_pdse_member */ - - void* dd; - const char* fopen_mode; - unsigned short file_ccsid; - - int fd = -1; + /* + * Validate flags before delegating to create_dataset_fd, + * which handles opening, attribute detection, and registration. + */ bool pds_member = strchr(name, '('); + DEBUG_PRINT1("open_dataset: name %s, flags %d, mode %d\n", name, flags, mode); if ((flags & O_APPEND) && (pds_member)) { + DEBUG_PRINT1("open_dataset: O_APPEND not supported for PDS member %s\n", name); errno = EINVAL; return -1; - } else if (flags & O_RDONLY) { - fopen_mode = "r,recfm=+"; - } else if (flags & O_WRONLY) { - if (flags & O_APPEND) { - fopen_mode = "a,recfm=+"; - } else { - fopen_mode = "w,recfm=+"; - } - } else if (flags & O_RDWR) { - if (flags & O_APPEND) { - fopen_mode = "a+,recfm=+"; - } else { - fopen_mode = "r+,recfm=+"; - } - } else { + } + + if (!(flags & (O_RDONLY | O_WRONLY | O_RDWR))) { + DEBUG_PRINT1("open_dataset: Missing access mode in flags %d\n", flags); errno = EINVAL; return -1; } if ((flags & O_LARGEFILE) || (flags & O_NOCTTY) || (flags & O_NONBLOCK)) { + DEBUG_PRINT1("open_dataset: Unsupported flags in %d\n", flags); errno = EACCES; return -1; } @@ -165,76 +266,106 @@ int open_dataset(const char* name, int flags, mode_t mode) /* No support for sequential datasets or DDNames being created through open * of a dataset at this point */ + DEBUG_PRINT1("open_dataset: O_CREAT not supported for non-PDS member %s\n", name); errno = EINVAL; return -1; } } - DEBUG_PRINT1("Open with mode %s\n", fopen_mode); - dd = fopen(name, fopen_mode); - if (!dd) { - perror("dataset open failed"); - return -1; - } - - /* Use enhanced entry creation with metadata loading */ - DatasetEntry* dentry = create_entry(dd, 1047); - if (!dentry) { - fclose(dd); - return -1; - } - - /* Parse and store dataset name components */ - parse_and_store_name(dentry, name); - - /* Update global statistics */ - update_global_stats_open(); - - fd = GET_DUMMY_FD(); - ADD_DD(fd, dentry); - - DEBUG_PRINT1("Opened dataset: %s (fd=%d, RECFM=%s, LRECL=%d)\n", - name, fd, - dsio_recfm_to_string(dentry->recfm), - dentry->lrecl); - - return fd; + return create_dataset_fd(name, 1047, flags); } ssize_t write_dataset(int fd, const void* buf, size_t count) { void* dd = GET_DD(fd); + if (!dd) { + errno = EBADF; + return -1; + } DatasetEntry* dentry = (DatasetEntry*) (dd); FILE* fp = dentry->file_ptr; - DEBUG_PRINT1("In Write, File ptr ccsid: %p\n", dentry->file_ptr); DEBUG_PRINT1("In Write, File ccsid: %d\n", dentry->file_ccsid); DEBUG_PRINT1("In Write, Program ccsid: %d\n", dentry->program_ccsid); DEBUG_PRINT1("In Write, Conversion state %d\n", dentry->conversion_state); - DEBUG_PRINT1("write_dataset fd %d count %d\n", fd, count); - char* write_buffer = (char *)malloc(count); - if (write_buffer == NULL) { - set_entry_error(dentry, DSIO_ERR_ALLOC_FAILED, "Memory allocation failed for write buffer"); - fprintf(stderr, "Memory allocation failed\n"); - return -1; - } - memcpy(write_buffer, buf, count); + const char* src = (const char*) buf; + size_t total_written = 0; - write_buffer = convertBuffer(write_buffer, dentry->program_ccsid, dentry->file_ccsid); - size_t rc = fwrite(write_buffer, 1, count, fp); - free(write_buffer); - - if (rc == count) { - /* Update statistics on successful write */ - update_write_stats(dentry, rc); - DEBUG_PRINT1("Wrote %zu bytes to fd=%d\n", rc, fd); - return (ssize_t) rc; - } else { - set_entry_error(dentry, DSIO_ERR_WRITE_FAILED, "fwrite() returned fewer bytes than requested"); - return -1; + for (size_t i = 0; i < count; i++) { + char c = src[i]; + + if (c == '\n') { + /* Flush current buffer as a record */ + if (dentry->is_fixed_recfm) { + /* Pad FB record to reclen with spaces */ + while (dentry->rec_buf_pos < dentry->reclen && dentry->rec_buf_pos < dentry->rec_buf_size) { + dentry->rec_buf[dentry->rec_buf_pos++] = ' '; + } + } + + if (dentry->rec_buf_pos > 0) { + /* Convert CCSID before writing */ + if (dentry->conversion_state == SETCVTON) { + void* conv_result = dsio_convert_buffer(dentry->rec_buf, dentry->rec_buf_pos, + dentry->program_ccsid, dentry->file_ccsid); + if (conv_result == NULL) { + fprintf(stderr, "ERROR: CCSID conversion failed during write\n"); + errno = EILSEQ; + return total_written > 0 ? (ssize_t) total_written : -1; + } + } + + size_t rc = fwrite(dentry->rec_buf, 1, dentry->rec_buf_pos, fp); + if (rc != dentry->rec_buf_pos) { + fprintf(stderr, "ERROR: fwrite() wrote %zu bytes, expected %zu\n", + rc, dentry->rec_buf_pos); + return total_written > 0 ? (ssize_t) total_written : -1; + } + dentry->rec_buf_pos = 0; + } else if (!dentry->is_fixed_recfm) { + /* For VB datasets, write an empty record if newline is encountered at start of record */ + fwrite("", 1, 0, fp); + } + total_written++; /* count the newline as a written byte */ + } else { + /* If record is full, flush it before adding current character */ + if (dentry->rec_buf_pos >= dentry->reclen) { + if (dentry->conversion_state == SETCVTON) { + void* conv_result = dsio_convert_buffer(dentry->rec_buf, dentry->rec_buf_pos, + dentry->program_ccsid, dentry->file_ccsid); + if (conv_result == NULL) { + fprintf(stderr, "ERROR: CCSID conversion failed during record overflow flush\n"); + errno = EILSEQ; + return total_written > 0 ? (ssize_t) total_written : -1; + } + } + + size_t rc = fwrite(dentry->rec_buf, 1, dentry->rec_buf_pos, fp); + if (rc != dentry->rec_buf_pos) { + fprintf(stderr, "ERROR: fwrite() wrote %zu bytes during overflow, expected %zu\n", + rc, dentry->rec_buf_pos); + return total_written > 0 ? (ssize_t) total_written : -1; + } + dentry->rec_buf_pos = 0; + } + + /* Add character to buffer (with explicit bounds check) */ + if (dentry->rec_buf_pos < dentry->rec_buf_size) { + dentry->rec_buf[dentry->rec_buf_pos++] = c; + total_written++; + } else { + fprintf(stderr, "ERROR: Buffer overflow in write_dataset (pos %zu >= size %zu)\n", + dentry->rec_buf_pos, dentry->rec_buf_size); + errno = ENOBUFS; + return total_written > 0 ? (ssize_t) total_written : -1; + } + } } + + dentry->stream_offset += total_written; + return (ssize_t) total_written; } ssize_t read_dataset(int fd, void* buf, size_t count) @@ -244,24 +375,136 @@ ssize_t read_dataset(int fd, void* buf, size_t count) DatasetEntry* dentry = (DatasetEntry*) (dd); FILE* fp = dentry->file_ptr; - DEBUG_PRINT1("In Read, File ptr ccsid: %p\n", dentry->file_ptr); + DEBUG_PRINT1("read_dataset: fd=%d count=%zu offset=%zu recfm=%s\n", + fd, count, dentry->stream_offset, + dsio_recfm_to_string(dentry->recfm)); DEBUG_PRINT1("In Read, File ccsid: %d\n", dentry->file_ccsid); DEBUG_PRINT1("In Read, Program ccsid: %d\n", dentry->program_ccsid); DEBUG_PRINT1("In Read, Conversion state %d\n", dentry->conversion_state); - DEBUG_PRINT1("read_dataset fd %d count %d\n", fd, count); - size_t rc = fread(buf, 1, count, fp); - - if (rc > 0) { - buf = convertBuffer(buf, dentry->file_ccsid, dentry->program_ccsid); - /* Update statistics on successful read */ - update_read_stats(dentry, rc); - DEBUG_PRINT1("Read %zu bytes from fd=%d\n", rc, fd); - } else if (rc == 0 && ferror(fp)) { - set_entry_error(dentry, DSIO_ERR_READ_FAILED, "fread() failed"); - return -1; + DEBUG_PRINT1("In Read, RECFM=%s LRECL=%zu BLKSIZE=%zu\n", + dsio_recfm_to_string(dentry->recfm), dentry->reclen, dentry->blksize); + + char* dst = (char*) buf; + size_t bytes_copied = 0; + + while (bytes_copied < count) { + /* If a newline is pending between records, emit it */ + if (dentry->newline_pending) { + dst[bytes_copied++] = '\n'; + dentry->newline_pending = 0; + dentry->stream_offset++; + continue; + } + + /* If internal buffer has data, serve from it */ + if (dentry->rec_buf_pos < dentry->rec_buf_len) { + size_t avail = dentry->rec_buf_len - dentry->rec_buf_pos; + + /* FB Optimization: limit copy to the current record boundary to allow \n insertion */ + if (dentry->is_fixed_recfm && dentry->reclen > 0) { + size_t pos_in_rec = dentry->rec_buf_pos % dentry->reclen; + size_t rec_avail = dentry->reclen - pos_in_rec; + if (avail > rec_avail) avail = rec_avail; + } + + size_t to_copy = (count - bytes_copied < avail) + ? (count - bytes_copied) : avail; + + DEBUG_PRINT1("BEFORE memcpy: count=%zu, bytes_copied=%zu, avail=%zu, to_copy=%zu, rec_buf_pos=%zu, rec_buf_len=%zu, dst_offset=%zu\n", + count, bytes_copied, avail, to_copy, dentry->rec_buf_pos, dentry->rec_buf_len, bytes_copied); + + memcpy(dst + bytes_copied, dentry->rec_buf + dentry->rec_buf_pos, to_copy); + + DEBUG_PRINT1("AFTER memcpy: copied %zu bytes from rec_buf[%zu] to dst[%zu]\n", + to_copy, dentry->rec_buf_pos, bytes_copied); + + size_t old_pos = dentry->rec_buf_pos; + dentry->rec_buf_pos += to_copy; + bytes_copied += to_copy; + dentry->stream_offset += to_copy; + + /* If we've reached a record boundary (FB) or end of buffer, mark newline pending */ + if (dentry->is_fixed_recfm && dentry->reclen > 0) { + /* Check if we crossed a record boundary during this copy */ + size_t old_rec = old_pos / dentry->reclen; + size_t new_rec = dentry->rec_buf_pos / dentry->reclen; + if (new_rec > old_rec && (dentry->rec_buf_pos % dentry->reclen) == 0) { + dentry->newline_pending = 1; + } + } else if (dentry->rec_buf_pos >= dentry->rec_buf_len) { + /* For VB/U, end of buffer is the end of the single record we read */ + dentry->newline_pending = 1; + } + continue; + } + + /* Buffer exhausted — read next block (FB) or record (VB/U) */ + size_t rc = fread(dentry->rec_buf, 1, dentry->rec_buf_size, fp); + if (rc == 0) { + /* EOF or error */ + DEBUG_PRINT1("In Read, EOF reached at offset=%zu\n", dentry->stream_offset); + log_debug("read_dataset: fd=%d EOF reached at offset=%zu", fd, dentry->stream_offset); + break; + } + + DEBUG_PRINT1("In Read, fread returned %zu bytes\n", rc); + log_trace("read_dataset: fd=%d read %zu bytes from dataset", fd, rc); + + /* CRITICAL: Validate VB record length doesn't exceed buffer size */ + if (!dentry->is_fixed_recfm && rc > dentry->rec_buf_size) { + log_error("FATAL: VB record length %zu exceeds buffer size %zu (fd=%d)", + rc, dentry->rec_buf_size, fd); + log_error("Dataset may be corrupted or DCB LRECL is incorrect"); + set_entry_error(dentry, DSIO_ERR_RECORD_TOO_LONG, + "VB record exceeds buffer size (possible corruption)"); + errno = EFBIG; + return -1; + } + + /* Strip trailing spaces for fixed-format records */ + if (dentry->is_fixed_recfm) { + while (rc > 0 && dentry->rec_buf[rc - 1] == ' ') { + rc--; + } + } + + /* Convert CCSID after reading the whole block/record */ + if (dentry->conversion_state == SETCVTON && rc > 0) { + DEBUG_PRINT1("In Read, converting %zu bytes from CCSID %d to %d\n", + rc, dentry->file_ccsid, dentry->program_ccsid); + log_trace("read_dataset: fd=%d converting %zu bytes from CCSID %d to %d", + fd, rc, dentry->file_ccsid, dentry->program_ccsid); + void* conv_result = dsio_convert_buffer(dentry->rec_buf, rc, + dentry->file_ccsid, + dentry->program_ccsid); + if (conv_result == NULL) { + DEBUG_PRINT1("CCSID conversion failed: fd=%d from=%d to=%d", + fd, dentry->file_ccsid, dentry->program_ccsid); + set_entry_error(dentry, DSIO_ERR_CCSID_CONVERSION, + "CCSID conversion failed during read"); + errno = EILSEQ; + return -1; + } + DEBUG_PRINT1("In Read, CCSID conversion successful %d\n", 1); + } + + dentry->rec_buf_len = rc; + dentry->rec_buf_pos = 0; } - return (ssize_t) rc; + /* Update statistics before returning */ + if (bytes_copied > 0) { + update_read_stats(dentry, bytes_copied); + } + + DEBUG_PRINT1("In Read, returning %zu bytes (requested=%zu)\n", bytes_copied, count); + log_trace("read_dataset: fd=%d returning %zu bytes (requested=%zu)", + fd, bytes_copied, count); + + if (bytes_copied == 0 && count > 0) { + return 0; /* EOF */ + } + return (ssize_t) bytes_copied; } int close_dataset(int fd) @@ -271,22 +514,45 @@ int close_dataset(int fd) DatasetEntry* dentry = (DatasetEntry*) (dd); FILE* fp = dentry->file_ptr; - DEBUG_PRINT1("Closing dataset fd=%d (read=%zu bytes, wrote=%zu bytes, ops=%zu/%zu)\n", - fd, dentry->bytes_read, dentry->bytes_written, - dentry->read_operations, dentry->write_operations); + /* Flush any partial record remaining in the write buffer */ + if (dentry->rec_buf_pos > 0 && + (dentry->open_flags & (O_WRONLY | O_RDWR))) { + + /* 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 == SETCVTON) { + void* conv_result = dsio_convert_buffer(dentry->rec_buf, dentry->rec_buf_pos, + dentry->program_ccsid, dentry->file_ccsid); + if (conv_result == NULL) { + fprintf(stderr, "WARNING: CCSID conversion failed during close\n"); + /* Continue with close despite conversion error */ + } + } + + /* Write final record */ + size_t rc = fwrite(dentry->rec_buf, 1, dentry->rec_buf_pos, fp); + if (rc != dentry->rec_buf_pos) { + fprintf(stderr, "WARNING: Final record write incomplete (%zu of %zu bytes)\n", + rc, dentry->rec_buf_pos); + /* Continue with close despite write error */ + } + } int rc = fclose(fp); if (!rc) { - /* Update global statistics */ - update_global_stats_close(); - close(fd); - - /* Deallocate enhanced DatasetEntry */ - free_entry(dentry); + /* Free record buffer and deallocate DatasetEntry */ + if (dentry->rec_buf) { + free(dentry->rec_buf); + } + free(dentry); CLEAR_DD(fd); - } else { - set_entry_error(dentry, DSIO_ERR_CLOSE_FAILED, "fclose() failed"); } return rc; } @@ -431,127 +697,118 @@ DatasetEntry* createDatasetEntry(FILE* dd, unsigned short file_ccsid) * ======================================================================== */ off_t lseek_dataset(int fd, off_t offset, int whence) { - /* Validate file descriptor */ - if (fd < 0 || fd >= MAX_FDS) { - errno = EBADF; - log_error("lseek_dataset: Invalid fd=%d", fd); - return -1; - } + DEBUG_PRINT0("calling lseek-dataset\n"); + /* Validate fd */ void* dd = GET_DD(fd); if (!dd) { errno = EBADF; - log_error("lseek_dataset: No dataset descriptor for fd=%d", fd); - return -1; - } - - /* Check if this is actually a dataset fd (not a regular file fd) */ - if (IS_FD(fd)) { - errno = EINVAL; - log_error("lseek_dataset: fd=%d is not a dataset descriptor", fd); - return -1; + return (off_t)-1; } - DatasetEntry* dentry = (DatasetEntry*)dd; + DatasetEntry* dentry = (DatasetEntry*) dd; if (!dentry->file_ptr) { errno = EBADF; - set_entry_error(dentry, DSIO_ERR_INVALID_FD, "Invalid file pointer"); - log_error("lseek_dataset: NULL file pointer for fd=%d", fd); - return -1; + return (off_t)-1; } - FILE* fp = dentry->file_ptr; - /* Log the seek operation with correct format specifiers */ - DEBUG_PRINT1("lseek_dataset fd=%d offset=%lld whence=%d\n", fd, (long long)offset, whence); - - /* Validate whence parameter */ - int fseek_whence; + /* Calculate target position */ + off_t target; switch (whence) { case SEEK_SET: - fseek_whence = SEEK_SET; + target = offset; break; + case SEEK_CUR: - fseek_whence = SEEK_CUR; + target = (off_t)dentry->stream_offset + offset; break; - case SEEK_END: - fseek_whence = SEEK_END; + + case SEEK_END: { + /* Use dsio_get_size for accurate size calculation */ + ssize_t file_size = dsio_get_size(fd); + if (file_size < 0) { + return (off_t)-1; + } + target = (off_t)file_size + offset; break; + } + default: errno = EINVAL; - set_entry_error(dentry, DSIO_ERR_FSEEK_FAILED, "Invalid whence parameter"); - log_error("lseek_dataset: Invalid whence=%d for fd=%d", whence, fd); - return -1; + return (off_t)-1; } - /* Load metadata if not already loaded to check record format constraints */ - if (!dentry->metadata_loaded) { - if (load_metadata_from_file(dentry) != 0) { - DEBUG_PRINT1("lseek_dataset: Failed to load metadata for fd=%d, continuing anyway", fd); - } + /* Validate target */ + if (target < 0) { + errno = EINVAL; + return (off_t)-1; } - /* Check for dataset-specific constraints based on record format - * Note: On z/OS, fseek/ftell behavior varies by record format: - * - Fixed (F, FB): Seeking works by byte offset, relatively predictable - * - Variable (V, VB): Seeking by byte offset is problematic due to RDWs - * - Undefined (U): Seeking is unreliable and not recommended - * - ASA formats: Additional complexity with carriage control characters - */ - if (dentry->metadata_loaded) { - dsio_recfm_t recfm = dentry->recfm; + /* 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 delta = (size_t)target - dentry->stream_offset; + size_t records_to_skip = delta / (dentry->reclen + 1); + size_t byte_in_record = delta % (dentry->reclen + 1); + + /* Handle seeking to newline position */ + if (byte_in_record == dentry->reclen) { + dentry->newline_pending = 1; + byte_in_record = 0; + records_to_skip++; + } - /* Variable-length records: seeking by byte offset doesn't align with record boundaries */ - if (recfm == DSIO_RECFM_V || recfm == DSIO_RECFM_VB || - recfm == DSIO_RECFM_VA || recfm == DSIO_RECFM_VBA) { - DEBUG_PRINT1("lseek_dataset: Seeking in variable-length dataset (RECFM=%s) - byte offsets may not align with record boundaries", - dsio_recfm_to_string(recfm)); + /* Calculate native file position */ + long current_native = ftell(fp); + if (current_native < 0) { + errno = EIO; + return (off_t)-1; } - /* Undefined format: seeking is not reliable */ - if (recfm == DSIO_RECFM_U) { - DEBUG_PRINT0("lseek_dataset: Seeking in undefined format dataset (RECFM=U) is not recommended and may produce unexpected results"); + long target_native = current_native + (records_to_skip * dentry->reclen) + byte_in_record; + + if (fseek(fp, target_native, SEEK_SET) != 0) { + errno = EIO; + return (off_t)-1; } - /* For fixed-length records, automatically align offset to record boundaries */ - if ((recfm == DSIO_RECFM_F || recfm == DSIO_RECFM_FB || - recfm == DSIO_RECFM_FA || recfm == DSIO_RECFM_FBA) && - whence == SEEK_SET && dentry->lrecl > 0) { - /* Check if offset aligns with record boundaries */ - if (offset % dentry->lrecl != 0) { - off_t aligned_offset = (offset / dentry->lrecl) * dentry->lrecl; - DEBUG_PRINT1("lseek_dataset: Offset %lld not aligned with LRECL %d, rounding down to %lld\n", - (long long)offset, dentry->lrecl, (long long)aligned_offset); - offset = aligned_offset; + 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) { + /* EOF reached before target */ + break; } } } - /* Perform the seek operation using fseeko() to handle off_t properly */ - if (fseeko(fp, offset, fseek_whence) != 0) { - int saved_errno = errno; - set_entry_error(dentry, DSIO_ERR_FSEEK_FAILED, "fseek() failed"); - DEBUG_PRINT1("lseek_dataset: fseek() failed for fd=%d, offset=%lld, whence=%d, errno=%d (%s)", - fd, (long long)offset, whence, saved_errno, strerror(saved_errno)); - errno = saved_errno; - return -1; - } + DEBUG_PRINT1("lseek_dataset: fd=%d target=%lld final=%zu\n", + fd, (long long)target, dentry->stream_offset); - /* Get the new position using ftello() to return off_t */ - off_t pos = ftello(fp); - if (pos < 0) { - int saved_errno = errno; - set_entry_error(dentry, DSIO_ERR_FTELL_FAILED, "ftello() failed"); - DEBUG_PRINT1("lseek_dataset: ftello() failed for fd=%d, errno=%d (%s)", - fd, saved_errno, strerror(saved_errno)); - errno = saved_errno; - return -1; - } - - DEBUG_PRINT1("lseek_dataset: fd=%d, offset=%lld, whence=%d, new_pos=%lld\n", - fd, (long long)offset, whence, (long long)pos); - - return pos; + return (off_t)dentry->stream_offset; } /* ======================================================================== @@ -589,9 +846,11 @@ static int read_ispf_stats(FILE* fp, struct ispf_stats* stats) { } #endif -int fstat_dataset(int fd, struct stat *statbuf) { - DEBUG_PRINT1("Enter fstat_dataset %d\n", 0); - if (!statbuf) { +int fstat_dataset(int fd, struct stat *buf) { + DEBUG_PRINT0("calling fstat-dataset\n"); + + /* Validate parameters */ + if (!buf) { errno = EINVAL; return -1; } @@ -602,148 +861,36 @@ int fstat_dataset(int fd, struct stat *statbuf) { return -1; } - DatasetEntry* dentry = (DatasetEntry*)dd; - FILE* fp = dentry->file_ptr; - - if (!fp) { - set_entry_error(dentry, DSIO_ERR_INVALID_FD, "Invalid file pointer"); + DatasetEntry* dentry = (DatasetEntry*) dd; + if (!dentry->file_ptr) { errno = EBADF; return -1; } - DEBUG_PRINT1("fstat_dataset %d\n", 1); - + /* Initialize stat buffer */ - memset(statbuf, 0, sizeof(struct stat)); - - /* Ensure metadata is loaded */ - if (!dentry->metadata_loaded) { - if (load_metadata_from_file(dentry) != 0) { - log_warn("fstat: Failed to load metadata, continuing with limited info"); - } - } - - /* Use fldata() to get dataset information */ - fldata_t fdata; - if (fldata(fp, NULL, &fdata) != 0) { - set_entry_error(dentry, DSIO_ERR_FLDATA_FAILED, "fldata() failed"); - errno = EIO; - return -1; - } - - /* Get file size using utility function */ + memset(buf, 0, sizeof(struct stat)); + 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; + + /* Use dsio_get_size helper to calculate emulated stream size */ ssize_t size = dsio_get_size(fd); - if (size >= 0) { - statbuf->st_size = size; - } else { - log_warn("fstat: Failed to get file size"); - statbuf->st_size = 0; - } - - /* Determine file mode based on dataset type */ - mode_t mode = S_IFREG; /* Regular file by default */ - - /* Check if PDS/PDSE without member (acts like directory) */ - if ((dsio_is_pds(fd) || dsio_is_pdse(fd)) && !dsio_has_member(fd)) { - mode = S_IFDIR; - } - - /* Set permissions based on readonly status */ - if (dsio_is_readonly(fd)) { - mode |= S_IRUSR | S_IRGRP | S_IROTH; /* Read-only */ - } else { - mode |= S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; /* Read-write for user */ - } - - /* Add execute permission for directories */ - if (mode & S_IFDIR) { - mode |= S_IXUSR | S_IXGRP | S_IXOTH; - } - - statbuf->st_mode = mode; - statbuf->st_nlink = 1; - - /* Set block size from metadata or fldata */ - if (dentry->metadata_loaded && dentry->blksize > 0) { - statbuf->st_blksize = dentry->blksize; - } else if (fdata.__blksize > 0) { - statbuf->st_blksize = fdata.__blksize; - } else { - statbuf->st_blksize = 4096; /* Default */ - } - - /* Calculate blocks used (in 512-byte units for POSIX compatibility) */ - if (statbuf->st_size > 0) { - statbuf->st_blocks = (statbuf->st_size + 511) / 512; - } - - /* Set device and inode numbers (simulated for datasets) */ - statbuf->st_dev = 0x5A05; /* 'ZOS' in hex */ - - /* Generate pseudo-inode from dataset name components */ - unsigned long inode = 0; - if (dentry->hlq[0]) { - for (int i = 0; dentry->hlq[i] && i < DSIO_MAX_QUALIFIER; i++) { - inode = (inode * 31) + (unsigned char)dentry->hlq[i]; - } - } - if (dentry->member_name[0]) { - for (int i = 0; dentry->member_name[i] && i < DSIO_MAX_MEMBER_NAME; i++) { - inode = (inode * 31) + (unsigned char)dentry->member_name[i]; - } - } - statbuf->st_ino = inode ? inode : 1; - - /* Set user and group IDs */ - statbuf->st_uid = getuid(); - statbuf->st_gid = getgid(); - - /* Try to get ISPF statistics for timestamps (PDS members only) */ - int has_ispf = 0; - if (dsio_has_member(fd) && (dsio_is_pds(fd) || dsio_is_pdse(fd))) { - struct ispf_stats ispf_stats; - /* - if (read_ispf_stats(fp, &ispf_stats) == 0) { - has_ispf = 1; - - // Convert struct tm to time_t - statbuf->st_ctime = mktime(&ispf_stats.create_time); - statbuf->st_atime = statbuf->st_ctime; - - statbuf->st_mtime = mktime(&ispf_stats.mod_time); - if (statbuf->st_mtime == -1) { - statbuf->st_mtime = statbuf->st_ctime; - } - - log_debug("fstat: Using ISPF stats - created=%ld, modified=%ld, ver=%d.%d", - (long)statbuf->st_ctime, (long)statbuf->st_mtime, - ispf_stats.ver_num, ispf_stats.mod_num); - } - */ + if (size < 0) { + /* Error already set by dsio_get_size */ + return -1; } - /* If no ISPF stats, try file system times or use current time */ - if (!has_ispf) { - int fileno_val = fileno(fp); - struct stat fs_stat; - - if (fileno_val >= 0 && fstat(fileno_val, &fs_stat) == 0) { - statbuf->st_atime = fs_stat.st_atime; - statbuf->st_mtime = fs_stat.st_mtime; - statbuf->st_ctime = fs_stat.st_ctime; - log_trace("fstat: Using file system timestamps"); - } else { - /* Fallback to current time */ - time_t now = time(NULL); - statbuf->st_atime = now; - statbuf->st_mtime = now; - statbuf->st_ctime = now; - log_trace("fstat: Using current time for timestamps"); - } - } + buf->st_size = (off_t)size; - DEBUG_PRINT1("fstat: fd=%d, size=%ld, blksize=%ld, blocks=%ld, mode=%o, inode=%lu", - fd, (long)statbuf->st_size, (long)statbuf->st_blksize, - (long)statbuf->st_blocks, statbuf->st_mode, (unsigned long)statbuf->st_ino); + DEBUG_PRINT1("fstat_dataset: fd=%d size=%lld\n", fd, (long long)buf->st_size); return 0; } @@ -1865,74 +2012,158 @@ int dsio_get_max_reclen(int fd) { } int dsio_is_empty(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; - } + 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; - /* Check file size */ - long current_pos = ftell(entry->file_ptr); - if (current_pos < 0) { - return -1; - } + DEBUG_PRINT1("calculate_vb_emulated_size: ENTER rec_buf_size=%zu\n", entry->rec_buf_size); - if (fseek(entry->file_ptr, 0, SEEK_END) != 0) { - return -1; + 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) { + DEBUG_PRINT1("calculate_vb_emulated_size: RETURN -1 (fgetpos failed) %d\n", 1); + return -1; + } + + if (fseek(fp, 0, SEEK_SET) != 0) { + fsetpos(fp, &saved_pos); + DEBUG_PRINT1("calculate_vb_emulated_size: RETURN -1 (fseek failed) %d\n", 1); + return -1; + } + + /* Read each record */ + while (1) { + size_t rc = fread(entry->rec_buf, 1, entry->rec_buf_size, fp); + if (rc == 0) break; /* EOF */ + + DEBUG_PRINT1("calculate_vb_emulated_size: Read record #%zu, raw_length=%zu\n", rec_count + 1, rc); + + /* Validate VB record length doesn't exceed buffer */ + if (rc > entry->rec_buf_size) { + fprintf(stderr, "ERROR: VB record length %zu exceeds buffer size %zu\n", + rc, entry->rec_buf_size); + fsetpos(fp, &saved_pos); + errno = EFBIG; + DEBUG_PRINT1("calculate_vb_emulated_size: RETURN -1 (record too large) %d\n", 1); + return -1; + } + + size_t original_rc = rc; + /* Strip trailing spaces */ + while (rc > 0 && entry->rec_buf[rc - 1] == ' ') { + rc--; + } + + DEBUG_PRINT1("calculate_vb_emulated_size: Record #%zu stripped from %zu to %zu bytes\n", + rec_count + 1, original_rc, rc); + + total_size += rc + 1; /* +1 for newline */ + rec_count++; + + DEBUG_PRINT1("calculate_vb_emulated_size: Running total=%zu bytes, rec_count=%zu\n", + total_size, rec_count); + } + + /* Restore position */ + if (fsetpos(fp, &saved_pos) != 0) { + DEBUG_PRINT1("WARNING: Failed to restore file position after size calculation %d\n", 1); + } } - long size = ftell(entry->file_ptr); - fseek(entry->file_ptr, current_pos, SEEK_SET); + DEBUG_PRINT1("calculate_vb_emulated_size: RETURN %zu bytes (%zu records) for %s dataset\n", + total_size, rec_count, entry->is_fixed_recfm ? "FB" : "VB"); - return (size == 0) ? 1 : 0; + return (ssize_t)total_size; } ssize_t dsio_get_size(int fd) { + DEBUG_PRINT1("dsio_get_size: ENTER fd=%d\n", fd); + void* dd = GET_DD(fd); if (!dd || IS_FD(fd)) { + errno = EBADF; + DEBUG_PRINT1("dsio_get_size: RETURN -1 (invalid fd) %d\n", 1); return -1; } DatasetEntry* entry = ENTRY_TO(dd); if (!entry->file_ptr) { + errno = EBADF; + DEBUG_PRINT1("dsio_get_size: RETURN -1 (no file_ptr) %d\n", 1); return -1; } -#if 0 - /* Get file size */ - long current_pos = ftell(entry->file_ptr); - if (current_pos < 0) { + + FILE* fp = entry->file_ptr; + + /* For VB datasets, check if we have cached size */ + if (!entry->is_fixed_recfm && entry->vb_size_calculated) { + DEBUG_PRINT1("dsio_get_size: RETURN %zu (cached VB size)\n", entry->vb_cached_size); + return (ssize_t)entry->vb_cached_size; + } + + /* Calculate emulated stream size from native file size */ + fpos_t pos; + if (fgetpos(fp, &pos) != 0) { return -1; } - if (fseek(entry->file_ptr, 0, SEEK_END) != 0) { + if (fseek(fp, 0, SEEK_END) != 0) { + fsetpos(fp, &pos); return -1; } - long size = ftell(entry->file_ptr); - fseek(entry->file_ptr, current_pos, SEEK_SET); + long native_size = ftell(fp); + if (native_size < 0) { + fsetpos(fp, &pos); + DEBUG_PRINT1("dsio_get_size: RETURN -1 (ftell failed) %d\n", 1); + return -1; + } - return (ssize_t)size; -#endif - long long total = 0; - char buf[8192]; - - long cur = ftell(entry->file_ptr); - fseek(entry->file_ptr, 0, SEEK_SET); - - while (1) { - size_t n = fread(buf, 1, sizeof(buf), entry->file_ptr); - total += n; - if (n == 0) - break; + DEBUG_PRINT1("dsio_get_size: native_size=%ld, is_fixed_recfm=%d, reclen=%zu\n", + native_size, entry->is_fixed_recfm, entry->reclen); + + if (fsetpos(fp, &pos) != 0) { + DEBUG_PRINT1("WARNING: fsetpos() failed in dsio_get_size %d\n", 1); } - DEBUG_PRINT1("get_size fd %d size %d\n", fd, total); - - fseek(entry->file_ptr, cur, SEEK_SET); - return (ssize_t)total; + + /* Calculate emulated stream size based on record format */ + ssize_t emulated_size; + + if (entry->is_fixed_recfm && entry->reclen > 0) { + /* FB: native_size is total bytes, add newlines */ + size_t num_records = native_size / entry->reclen; + emulated_size = (ssize_t)(native_size + num_records); + DEBUG_PRINT1("dsio_get_size: FB calculation - native=%ld, reclen=%zu, num_records=%zu, emulated=%zd\n", + native_size, entry->reclen, num_records, emulated_size); + } else { + /* VB/U: Calculate by reading all records (one-time cost) */ + DEBUG_PRINT1("dsio_get_size: Calculating VB emulated size...%d\n", 1); + emulated_size = calculate_vb_emulated_size(fp, entry); + if (emulated_size >= 0) { + /* Cache the result for future calls */ + entry->vb_cached_size = (size_t)emulated_size; + entry->vb_size_calculated = 1; + DEBUG_PRINT1("dsio_get_size: VB size calculated and cached: %zd\n", emulated_size); + } else { + DEBUG_PRINT1("dsio_get_size: VB size calculation failed %d\n", 1); + } + } + + DEBUG_PRINT1("dsio_get_size: RETURN %zd (fd=%d, native=%ld, emulated=%zd)\n", + emulated_size, fd, native_size, emulated_size); + + return emulated_size; } int dsio_flush(int fd) { From 0db53804eb10ad6482a5d88f137b9cbae833baa2 Mon Sep 17 00:00:00 2001 From: sachintu47 Date: Mon, 16 Mar 2026 04:27:10 -0400 Subject: [PATCH 10/28] fixes --- include/zos-datasetio.h | 5 +- src/zos-datasetio.c | 215 ++++++++++++++++++---------------------- 2 files changed, 100 insertions(+), 120 deletions(-) diff --git a/include/zos-datasetio.h b/include/zos-datasetio.h index 734f337a..40c818bb 100644 --- a/include/zos-datasetio.h +++ b/include/zos-datasetio.h @@ -377,6 +377,7 @@ typedef struct DatasetEntry { size_t reclen; /* logical record length from fldata */ 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 */ /* VB size caching */ int vb_size_calculated; /* 1 if VB size has been calculated */ @@ -396,8 +397,8 @@ typedef struct DatasetDir { #define IS_DATASET(name) ((name) && ((name)[0] == '/') && ((name)[1] == '/')) #if 1 - #define DEBUG_PRINT0(str) dsio_debug_print(str) - #define DEBUG_PRINT1(fmt, ...) dsio_debug_printf(fmt, __VA_ARGS__) + #define DEBUG_PRINT0(str) do { if (g_debug_enabled) dsio_debug_print(str); } while(0) + #define DEBUG_PRINT1(fmt, ...) do { if (g_debug_enabled) dsio_debug_printf(fmt, __VA_ARGS__); } while(0) #else #define DEBUG_PRINT0(str) #define DEBUG_PRINT1(fmt, ...) diff --git a/src/zos-datasetio.c b/src/zos-datasetio.c index 180e2c91..249e7b15 100644 --- a/src/zos-datasetio.c +++ b/src/zos-datasetio.c @@ -197,6 +197,11 @@ int create_dataset_fd(const char* name, unsigned short file_ccsid, int flags) 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 @@ -292,74 +297,75 @@ ssize_t write_dataset(int fd, const void* buf, size_t count) const char* src = (const char*) buf; size_t total_written = 0; - - for (size_t i = 0; i < count; i++) { - char c = src[i]; - - if (c == '\n') { - /* Flush current buffer as a record */ + 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_pos < dentry->rec_buf_size) { + while (dentry->rec_buf_pos < dentry->reclen) { dentry->rec_buf[dentry->rec_buf_pos++] = ' '; } } if (dentry->rec_buf_pos > 0) { - /* Convert CCSID before writing */ if (dentry->conversion_state == SETCVTON) { - void* conv_result = dsio_convert_buffer(dentry->rec_buf, dentry->rec_buf_pos, - dentry->program_ccsid, dentry->file_ccsid); - if (conv_result == NULL) { - fprintf(stderr, "ERROR: CCSID conversion failed during write\n"); - errno = EILSEQ; - return total_written > 0 ? (ssize_t) total_written : -1; - } + dsio_convert_buffer(dentry->rec_buf, dentry->rec_buf_pos, + dentry->program_ccsid, dentry->file_ccsid); } - size_t rc = fwrite(dentry->rec_buf, 1, dentry->rec_buf_pos, fp); - if (rc != dentry->rec_buf_pos) { - fprintf(stderr, "ERROR: fwrite() wrote %zu bytes, expected %zu\n", - rc, dentry->rec_buf_pos); + if (fwrite(dentry->rec_buf, 1, dentry->rec_buf_pos, fp) != dentry->rec_buf_pos) { return total_written > 0 ? (ssize_t) total_written : -1; } dentry->rec_buf_pos = 0; + dentry->dirty = 0; } else if (!dentry->is_fixed_recfm) { - /* For VB datasets, write an empty record if newline is encountered at start of record */ fwrite("", 1, 0, fp); } - total_written++; /* count the newline as a written byte */ + + pos += to_copy + 1; /* skip the data and the newline */ + total_written += to_copy + 1; + dentry->dirty = 1; } else { - /* If record is full, flush it before adding current character */ + /* 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) { - void* conv_result = dsio_convert_buffer(dentry->rec_buf, dentry->rec_buf_pos, - dentry->program_ccsid, dentry->file_ccsid); - if (conv_result == NULL) { - fprintf(stderr, "ERROR: CCSID conversion failed during record overflow flush\n"); - errno = EILSEQ; - return total_written > 0 ? (ssize_t) total_written : -1; - } + dsio_convert_buffer(dentry->rec_buf, dentry->rec_buf_pos, + dentry->program_ccsid, dentry->file_ccsid); } - size_t rc = fwrite(dentry->rec_buf, 1, dentry->rec_buf_pos, fp); - if (rc != dentry->rec_buf_pos) { - fprintf(stderr, "ERROR: fwrite() wrote %zu bytes during overflow, expected %zu\n", - rc, dentry->rec_buf_pos); + if (fwrite(dentry->rec_buf, 1, dentry->rec_buf_pos, fp) != dentry->rec_buf_pos) { return total_written > 0 ? (ssize_t) total_written : -1; } dentry->rec_buf_pos = 0; - } - - /* Add character to buffer (with explicit bounds check) */ - if (dentry->rec_buf_pos < dentry->rec_buf_size) { - dentry->rec_buf[dentry->rec_buf_pos++] = c; - total_written++; - } else { - fprintf(stderr, "ERROR: Buffer overflow in write_dataset (pos %zu >= size %zu)\n", - dentry->rec_buf_pos, dentry->rec_buf_size); - errno = ENOBUFS; - return total_written > 0 ? (ssize_t) total_written : -1; + dentry->dirty = 0; } } } @@ -371,24 +377,53 @@ ssize_t write_dataset(int fd, const void* buf, size_t count) ssize_t read_dataset(int fd, void* buf, size_t count) { void* dd = GET_DD(fd); + if (!dd) { + errno = EBADF; + return -1; + } DatasetEntry* dentry = (DatasetEntry*) (dd); FILE* fp = dentry->file_ptr; + /* If there are pending writes, we should flush them before reading + * However, for FB binary mode, the C runtime should handle it if it was opened with ab+ or rb+ + * But we are bypassing C runtime buffering with _IONBF and doing our own buffering in rec_buf. + */ + if (dentry->dirty && dentry->rec_buf_pos > 0) { + /* Flush pending write buffer */ + if (dentry->is_fixed_recfm) { + while (dentry->rec_buf_pos < dentry->reclen) { + dentry->rec_buf[dentry->rec_buf_pos++] = ' '; + } + } + if (dentry->conversion_state == SETCVTON) { + dsio_convert_buffer(dentry->rec_buf, dentry->rec_buf_pos, + dentry->program_ccsid, dentry->file_ccsid); + } + fwrite(dentry->rec_buf, 1, dentry->rec_buf_pos, fp); + dentry->rec_buf_pos = 0; + dentry->dirty = 0; + } + + /* If the buffer currently contains data that was read, we don't clear it yet. + * If it was used for writing (and just flushed), dentry->rec_buf_pos is 0. + * If we are switching from write to read, we might need to reset rec_buf_len. + */ + if (dentry->dirty) { + dentry->rec_buf_len = 0; + dentry->rec_buf_pos = 0; + dentry->dirty = 0; + } + DEBUG_PRINT1("read_dataset: fd=%d count=%zu offset=%zu recfm=%s\n", fd, count, dentry->stream_offset, dsio_recfm_to_string(dentry->recfm)); - DEBUG_PRINT1("In Read, File ccsid: %d\n", dentry->file_ccsid); - DEBUG_PRINT1("In Read, Program ccsid: %d\n", dentry->program_ccsid); - DEBUG_PRINT1("In Read, Conversion state %d\n", dentry->conversion_state); - DEBUG_PRINT1("In Read, RECFM=%s LRECL=%zu BLKSIZE=%zu\n", - dsio_recfm_to_string(dentry->recfm), dentry->reclen, dentry->blksize); char* dst = (char*) buf; size_t bytes_copied = 0; while (bytes_copied < count) { - /* If a newline is pending between records, emit it */ + /* Fast-path: Emit pending newline */ if (dentry->newline_pending) { dst[bytes_copied++] = '\n'; dentry->newline_pending = 0; @@ -396,115 +431,60 @@ ssize_t read_dataset(int fd, void* buf, size_t count) continue; } - /* If internal buffer has data, serve from it */ + /* 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; - /* FB Optimization: limit copy to the current record boundary to allow \n insertion */ + /* Record boundaries for fixed-format: insert newline after each LRECL */ if (dentry->is_fixed_recfm && dentry->reclen > 0) { size_t pos_in_rec = dentry->rec_buf_pos % dentry->reclen; size_t rec_avail = dentry->reclen - pos_in_rec; if (avail > rec_avail) avail = rec_avail; } - size_t to_copy = (count - bytes_copied < avail) - ? (count - bytes_copied) : avail; - - DEBUG_PRINT1("BEFORE memcpy: count=%zu, bytes_copied=%zu, avail=%zu, to_copy=%zu, rec_buf_pos=%zu, rec_buf_len=%zu, dst_offset=%zu\n", - count, bytes_copied, avail, to_copy, dentry->rec_buf_pos, dentry->rec_buf_len, bytes_copied); - + 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); - DEBUG_PRINT1("AFTER memcpy: copied %zu bytes from rec_buf[%zu] to dst[%zu]\n", - to_copy, dentry->rec_buf_pos, bytes_copied); - - size_t old_pos = dentry->rec_buf_pos; dentry->rec_buf_pos += to_copy; bytes_copied += to_copy; dentry->stream_offset += to_copy; - /* If we've reached a record boundary (FB) or end of buffer, mark newline pending */ + /* Check for record boundary after copying */ if (dentry->is_fixed_recfm && dentry->reclen > 0) { - /* Check if we crossed a record boundary during this copy */ - size_t old_rec = old_pos / dentry->reclen; - size_t new_rec = dentry->rec_buf_pos / dentry->reclen; - if (new_rec > old_rec && (dentry->rec_buf_pos % dentry->reclen) == 0) { + if ((dentry->rec_buf_pos % dentry->reclen) == 0) { dentry->newline_pending = 1; } } else if (dentry->rec_buf_pos >= dentry->rec_buf_len) { - /* For VB/U, end of buffer is the end of the single record we read */ dentry->newline_pending = 1; } continue; } - /* Buffer exhausted — read next block (FB) or record (VB/U) */ + /* Slow-path: Read next block/record from dataset */ size_t rc = fread(dentry->rec_buf, 1, dentry->rec_buf_size, fp); - if (rc == 0) { - /* EOF or error */ - DEBUG_PRINT1("In Read, EOF reached at offset=%zu\n", dentry->stream_offset); - log_debug("read_dataset: fd=%d EOF reached at offset=%zu", fd, dentry->stream_offset); - break; - } + if (rc == 0) break; /* EOF or error */ - DEBUG_PRINT1("In Read, fread returned %zu bytes\n", rc); - log_trace("read_dataset: fd=%d read %zu bytes from dataset", fd, rc); - - /* CRITICAL: Validate VB record length doesn't exceed buffer size */ - if (!dentry->is_fixed_recfm && rc > dentry->rec_buf_size) { - log_error("FATAL: VB record length %zu exceeds buffer size %zu (fd=%d)", - rc, dentry->rec_buf_size, fd); - log_error("Dataset may be corrupted or DCB LRECL is incorrect"); - set_entry_error(dentry, DSIO_ERR_RECORD_TOO_LONG, - "VB record exceeds buffer size (possible corruption)"); - errno = EFBIG; - return -1; - } - - /* Strip trailing spaces for fixed-format records */ + /* Strip trailing spaces from fixed-length records */ if (dentry->is_fixed_recfm) { while (rc > 0 && dentry->rec_buf[rc - 1] == ' ') { rc--; } } - /* Convert CCSID after reading the whole block/record */ + /* Convert CCSID of the newly read data in-place */ if (dentry->conversion_state == SETCVTON && rc > 0) { - DEBUG_PRINT1("In Read, converting %zu bytes from CCSID %d to %d\n", - rc, dentry->file_ccsid, dentry->program_ccsid); - log_trace("read_dataset: fd=%d converting %zu bytes from CCSID %d to %d", - fd, rc, dentry->file_ccsid, dentry->program_ccsid); - void* conv_result = dsio_convert_buffer(dentry->rec_buf, rc, - dentry->file_ccsid, - dentry->program_ccsid); - if (conv_result == NULL) { - DEBUG_PRINT1("CCSID conversion failed: fd=%d from=%d to=%d", - fd, dentry->file_ccsid, dentry->program_ccsid); - set_entry_error(dentry, DSIO_ERR_CCSID_CONVERSION, - "CCSID conversion failed during read"); - errno = EILSEQ; - return -1; - } - DEBUG_PRINT1("In Read, CCSID conversion successful %d\n", 1); + dsio_convert_buffer(dentry->rec_buf, rc, dentry->file_ccsid, dentry->program_ccsid); } dentry->rec_buf_len = rc; dentry->rec_buf_pos = 0; } - /* Update statistics before returning */ if (bytes_copied > 0) { update_read_stats(dentry, bytes_copied); } - DEBUG_PRINT1("In Read, returning %zu bytes (requested=%zu)\n", bytes_copied, count); - log_trace("read_dataset: fd=%d returning %zu bytes (requested=%zu)", - fd, bytes_copied, count); - - if (bytes_copied == 0 && count > 0) { - return 0; /* EOF */ - } - return (ssize_t) bytes_copied; + return (bytes_copied == 0 && count > 0) ? 0 : (ssize_t) bytes_copied; } int close_dataset(int fd) @@ -515,9 +495,8 @@ int close_dataset(int fd) FILE* fp = dentry->file_ptr; /* Flush any partial record remaining in the write buffer */ - if (dentry->rec_buf_pos > 0 && + if (dentry->dirty && dentry->rec_buf_pos > 0 && (dentry->open_flags & (O_WRONLY | O_RDWR))) { - /* For FB datasets, pad final record to reclen with spaces */ if (dentry->is_fixed_recfm) { while (dentry->rec_buf_pos < dentry->reclen) { From 2087f63d7df5d2e934df77725034faf5d793eada Mon Sep 17 00:00:00 2001 From: sachintu47 Date: Tue, 17 Mar 2026 01:41:43 -0400 Subject: [PATCH 11/28] Implement unique st_dev/st_ino generation for datasets in fstat_dataset to prevent tools like ggrep from incorrectly identifying datasets as stdout --- include/zos-datasetio.h | 1 + src/zos-datasetio.c | 27 ++++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/include/zos-datasetio.h b/include/zos-datasetio.h index 40c818bb..7cab0d4f 100644 --- a/include/zos-datasetio.h +++ b/include/zos-datasetio.h @@ -351,6 +351,7 @@ typedef struct DatasetEntry { uint32_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]; diff --git a/src/zos-datasetio.c b/src/zos-datasetio.c index 249e7b15..9a330947 100644 --- a/src/zos-datasetio.c +++ b/src/zos-datasetio.c @@ -146,6 +146,7 @@ int create_dataset_fd(const char* name, unsigned short file_ccsid, int flags) fclose(dd); return -1; } + parse_and_store_name(dentry, name); dentry->open_flags = flags; dentry->conversion_state = SETCVTON; /* Default to conversion on */ @@ -255,7 +256,7 @@ int open_dataset(const char* name, int flags, mode_t mode) return -1; } - if ((flags & O_LARGEFILE) || (flags & O_NOCTTY) || (flags & O_NONBLOCK)) { + if ((flags & O_LARGEFILE) || (flags & O_NONBLOCK)) { DEBUG_PRINT1("open_dataset: Unsupported flags in %d\n", flags); errno = EACCES; return -1; @@ -848,6 +849,27 @@ int fstat_dataset(int fd, struct stat *buf) { /* 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(); @@ -1754,6 +1776,9 @@ int parse_and_store_name(DatasetEntry* entry, const char* dataset_name) { } /* 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'; From dd0c148b0b1e4bbac5b03c5a718e17994f584c7b Mon Sep 17 00:00:00 2001 From: sachintu47 Date: Tue, 17 Mar 2026 03:51:30 -0400 Subject: [PATCH 12/28] fix dataset record alignment --- include/zos-datasetio.h | 5 ++-- src/zos-datasetio.c | 66 ++++++++++++++++++++++++++++++----------- src/zos.cc | 4 +++ 3 files changed, 55 insertions(+), 20 deletions(-) diff --git a/include/zos-datasetio.h b/include/zos-datasetio.h index 7cab0d4f..26b63833 100644 --- a/include/zos-datasetio.h +++ b/include/zos-datasetio.h @@ -379,6 +379,7 @@ typedef struct DatasetEntry { 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 */ /* VB size caching */ int vb_size_calculated; /* 1 if VB size has been calculated */ @@ -398,8 +399,8 @@ typedef struct DatasetDir { #define IS_DATASET(name) ((name) && ((name)[0] == '/') && ((name)[1] == '/')) #if 1 - #define DEBUG_PRINT0(str) do { if (g_debug_enabled) dsio_debug_print(str); } while(0) - #define DEBUG_PRINT1(fmt, ...) do { if (g_debug_enabled) dsio_debug_printf(fmt, __VA_ARGS__); } while(0) + #define DEBUG_PRINT0(str) dsio_debug_print(str) + #define DEBUG_PRINT1(fmt, ...) dsio_debug_printf(fmt, __VA_ARGS__) #else #define DEBUG_PRINT0(str) #define DEBUG_PRINT1(fmt, ...) diff --git a/src/zos-datasetio.c b/src/zos-datasetio.c index 9a330947..6f415ef0 100644 --- a/src/zos-datasetio.c +++ b/src/zos-datasetio.c @@ -219,6 +219,13 @@ int create_dataset_fd(const char* name, unsigned short file_ccsid, int flags) } int fd = GET_DUMMY_FD(); + if (fd < 0) { + DEBUG_PRINT1("create_dataset_fd: ERROR - GET_DUMMY_FD failed, errno=%d\n", errno); + fclose(dd); + free(dentry); + return -1; + } + DEBUG_PRINT1("create_dataset_fd: Assigned dummy fd %d\n", fd); ADD_DD(fd, dentry); return fd; } @@ -278,7 +285,9 @@ int open_dataset(const char* name, int flags, mode_t mode) } } - return create_dataset_fd(name, 1047, flags); + int fd = create_dataset_fd(name, 1047, flags); + DEBUG_PRINT1("open_dataset: create_dataset_fd returned fd %d\n", fd); + return fd; } ssize_t write_dataset(int fd, const void* buf, size_t count) @@ -438,8 +447,8 @@ ssize_t read_dataset(int fd, void* buf, size_t count) /* Record boundaries for fixed-format: insert newline after each LRECL */ if (dentry->is_fixed_recfm && dentry->reclen > 0) { - size_t pos_in_rec = dentry->rec_buf_pos % dentry->reclen; - size_t rec_avail = dentry->reclen - pos_in_rec; + 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; } @@ -452,7 +461,7 @@ ssize_t read_dataset(int fd, void* buf, size_t count) /* Check for record boundary after copying */ if (dentry->is_fixed_recfm && dentry->reclen > 0) { - if ((dentry->rec_buf_pos % 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) { @@ -462,16 +471,15 @@ ssize_t read_dataset(int fd, void* buf, size_t count) } /* Slow-path: Read next block/record from dataset */ + if (dentry->eof_reached) break; + size_t rc = fread(dentry->rec_buf, 1, dentry->rec_buf_size, fp); - if (rc == 0) break; /* EOF or error */ - - /* Strip trailing spaces from fixed-length records */ - if (dentry->is_fixed_recfm) { - while (rc > 0 && dentry->rec_buf[rc - 1] == ' ') { - rc--; - } + if (rc == 0) { + dentry->eof_reached = 1; + dentry->newline_pending = 1; + break; /* Will emit newline on next loop or return if bytes_copied > 0 */ } - + /* 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); @@ -677,18 +685,20 @@ DatasetEntry* createDatasetEntry(FILE* dd, unsigned short file_ccsid) * ======================================================================== */ off_t lseek_dataset(int fd, off_t offset, int whence) { - DEBUG_PRINT0("calling lseek-dataset\n"); + DEBUG_PRINT1("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; + DEBUG_PRINT1("lseek_dataset: ERROR - EBADF (NULL dd) for fd %d\n", fd); return (off_t)-1; } DatasetEntry* dentry = (DatasetEntry*) dd; if (!dentry->file_ptr) { errno = EBADF; + DEBUG_PRINT1("lseek_dataset: ERROR - EBADF (NULL file_ptr) for fd %d\n", fd); return (off_t)-1; } FILE* fp = dentry->file_ptr; @@ -827,23 +837,26 @@ static int read_ispf_stats(FILE* fp, struct ispf_stats* stats) { #endif int fstat_dataset(int fd, struct stat *buf) { - DEBUG_PRINT0("calling fstat-dataset\n"); + DEBUG_PRINT1("fstat_dataset: ENTER fd=%d\n", fd); /* Validate parameters */ if (!buf) { errno = EINVAL; + DEBUG_PRINT1("fstat_dataset: ERROR - NULL buffer for fd %d\n", fd); return -1; } void* dd = GET_DD(fd); if (!dd) { errno = EBADF; + DEBUG_PRINT1("fstat_dataset: ERROR - EBADF (NULL dd) for fd %d\n", fd); return -1; } DatasetEntry* dentry = (DatasetEntry*) dd; if (!dentry->file_ptr) { errno = EBADF; + DEBUG_PRINT1("fstat_dataset: ERROR - EBADF (NULL file_ptr) for fd %d\n", fd); return -1; } @@ -1460,9 +1473,17 @@ void dsio_log(dsio_log_level_t level, const char* format, ...) { fflush(stream); } +extern void __console(const void *p_in, int len_i); + void dsio_debug_print(const char* str) { + if (g_debug_enabled <= 0) return; + if (g_log_stream == NULL) { g_log_stream = fopen("zoslib.debug.log", "a"); + if (g_log_stream == NULL) { + __console(str, strlen(str)); + return; + } } if (g_log_stream) { fprintf(g_log_stream, "%s", str); @@ -1471,14 +1492,23 @@ void dsio_debug_print(const char* str) { } void dsio_debug_printf(const char* format, ...) { + if (g_debug_enabled <= 0) return; + + char buf[1024]; + va_list args; + va_start(args, format); + int len = vsnprintf(buf, sizeof(buf), format, args); + va_end(args); + if (g_log_stream == NULL) { g_log_stream = fopen("zoslib.debug.log", "a"); + if (g_log_stream == NULL) { + __console(buf, len); + return; + } } if (g_log_stream) { - va_list args; - va_start(args, format); - vfprintf(g_log_stream, format, args); - va_end(args); + fprintf(g_log_stream, "%s", buf); fflush(g_log_stream); } } diff --git a/src/zos.cc b/src/zos.cc index 74b92c76..3815941a 100644 --- a/src/zos.cc +++ b/src/zos.cc @@ -2678,6 +2678,10 @@ int __zinit::initialize(const zoslib_config_t &aconfig) { ADD_FD(STDOUT_FILENO); ADD_FD(STDERR_FILENO); + extern int g_debug_enabled; + char* env = getenv("ZOSLIB_DEBUG"); + g_debug_enabled = (env && (strcmp(env, "1") == 0 || strcasecmp(env, "ON") == 0)) ? 1 : 0; + memcpy(&config, &aconfig, sizeof(config)); __galloc_info = new __Cache; From 50c873b3da38cf1bc12430bab18519569518c58a Mon Sep 17 00:00:00 2001 From: sachintu47 Date: Wed, 25 Mar 2026 01:18:00 -0400 Subject: [PATCH 13/28] refactor: replace DEBUG_PRINT macros with conditional DSIO_LOG_* macros in dataset I/O --- include/zos-datasetio.h | 21 +++++-- src/zos-datasetio.c | 133 ++++++++++++++++++++-------------------- src/zos-io.cc | 34 +++++----- 3 files changed, 99 insertions(+), 89 deletions(-) diff --git a/include/zos-datasetio.h b/include/zos-datasetio.h index 26b63833..251eb893 100644 --- a/include/zos-datasetio.h +++ b/include/zos-datasetio.h @@ -398,12 +398,23 @@ typedef struct DatasetDir { #define GET_DUMMY_FD() (open("/dev/null", O_WRONLY, 0)) #define IS_DATASET(name) ((name) && ((name)[0] == '/') && ((name)[1] == '/')) -#if 1 - #define DEBUG_PRINT0(str) dsio_debug_print(str) - #define DEBUG_PRINT1(fmt, ...) dsio_debug_printf(fmt, __VA_ARGS__) +/* Enable/disable logging - set to 1 to enable log_* calls */ +#ifndef ZOSLIB_DATASET_LOGGING + #define ZOSLIB_DATASET_LOGGING 0 +#endif + +#if ZOSLIB_DATASET_LOGGING + #define DSIO_LOG_ERROR(fmt, ...) log_error(fmt, ##__VA_ARGS__) + #define DSIO_LOG_WARN(fmt, ...) log_warn(fmt, ##__VA_ARGS__) + #define DSIO_LOG_INFO(fmt, ...) log_info(fmt, ##__VA_ARGS__) + #define DSIO_LOG_DEBUG(fmt, ...) log_debug(fmt, ##__VA_ARGS__) + #define DSIO_LOG_TRACE(fmt, ...) log_trace(fmt, ##__VA_ARGS__) #else - #define DEBUG_PRINT0(str) - #define DEBUG_PRINT1(fmt, ...) + #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 /* ======================================================================== diff --git a/src/zos-datasetio.c b/src/zos-datasetio.c index 6f415ef0..1e61f165 100644 --- a/src/zos-datasetio.c +++ b/src/zos-datasetio.c @@ -101,7 +101,7 @@ int mkstemp_dataset(char* tmplate) fd = GET_DUMMY_FD(); ADD_DD(fd, dentry); - log_info("Created temporary dataset: %s (fd=%d)", tmplate, fd); + return fd; } @@ -134,7 +134,7 @@ int create_dataset_fd(const char* name, unsigned short file_ccsid, int flags) fopen_mode = "rb,type=record,recfm=+"; } - DEBUG_PRINT1("Open with mode %s\n", fopen_mode); + DSIO_LOG_DEBUG("Open with mode %s\n", fopen_mode); FILE* dd = fopen(name, fopen_mode); if (!dd) { perror("dataset open failed"); @@ -189,7 +189,7 @@ int create_dataset_fd(const char* name, unsigned short file_ccsid, int flags) } else { reopen_mode = "rb,recfm=+"; } - DEBUG_PRINT1("FB optimization: reopening with mode %s\n", reopen_mode); + DSIO_LOG_DEBUG("FB optimization: reopening with mode %s\n", reopen_mode); dd = fopen(name, reopen_mode); if (!dd) { free(dentry); @@ -220,12 +220,12 @@ int create_dataset_fd(const char* name, unsigned short file_ccsid, int flags) int fd = GET_DUMMY_FD(); if (fd < 0) { - DEBUG_PRINT1("create_dataset_fd: ERROR - GET_DUMMY_FD failed, errno=%d\n", errno); + DSIO_LOG_DEBUG("create_dataset_fd: ERROR - GET_DUMMY_FD failed, errno=%d\n", errno); fclose(dd); free(dentry); return -1; } - DEBUG_PRINT1("create_dataset_fd: Assigned dummy fd %d\n", fd); + DSIO_LOG_DEBUG("create_dataset_fd: Assigned dummy fd %d\n", fd); ADD_DD(fd, dentry); return fd; } @@ -249,22 +249,22 @@ int open_dataset(const char* name, int flags, mode_t mode) * which handles opening, attribute detection, and registration. */ bool pds_member = strchr(name, '('); - DEBUG_PRINT1("open_dataset: name %s, flags %d, mode %d\n", name, flags, mode); + DSIO_LOG_DEBUG("open_dataset: name %s, flags %d, mode %d\n", name, flags, mode); if ((flags & O_APPEND) && (pds_member)) { - DEBUG_PRINT1("open_dataset: O_APPEND not supported for PDS member %s\n", name); + DSIO_LOG_DEBUG("open_dataset: O_APPEND not supported for PDS member %s\n", name); errno = EINVAL; return -1; } if (!(flags & (O_RDONLY | O_WRONLY | O_RDWR))) { - DEBUG_PRINT1("open_dataset: Missing access mode in flags %d\n", flags); + DSIO_LOG_DEBUG("open_dataset: Missing access mode in flags %d\n", flags); errno = EINVAL; return -1; } if ((flags & O_LARGEFILE) || (flags & O_NONBLOCK)) { - DEBUG_PRINT1("open_dataset: Unsupported flags in %d\n", flags); + DSIO_LOG_DEBUG("open_dataset: Unsupported flags in %d\n", flags); errno = EACCES; return -1; } @@ -279,14 +279,14 @@ int open_dataset(const char* name, int flags, mode_t mode) /* No support for sequential datasets or DDNames being created through open * of a dataset at this point */ - DEBUG_PRINT1("open_dataset: O_CREAT not supported for non-PDS member %s\n", name); + 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); - DEBUG_PRINT1("open_dataset: create_dataset_fd returned fd %d\n", fd); + DSIO_LOG_DEBUG("open_dataset: create_dataset_fd returned fd %d\n", fd); return fd; } @@ -301,9 +301,9 @@ ssize_t write_dataset(int fd, const void* buf, size_t count) DatasetEntry* dentry = (DatasetEntry*) (dd); FILE* fp = dentry->file_ptr; - DEBUG_PRINT1("In Write, File ccsid: %d\n", dentry->file_ccsid); - DEBUG_PRINT1("In Write, Program ccsid: %d\n", dentry->program_ccsid); - DEBUG_PRINT1("In Write, Conversion state %d\n", dentry->conversion_state); + DSIO_LOG_DEBUG("In Write, File ccsid: %d\n", dentry->file_ccsid); + DSIO_LOG_DEBUG("In Write, Program ccsid: %d\n", dentry->program_ccsid); + DSIO_LOG_DEBUG("In Write, Conversion state %d\n", dentry->conversion_state); const char* src = (const char*) buf; size_t total_written = 0; @@ -425,7 +425,7 @@ ssize_t read_dataset(int fd, void* buf, size_t count) dentry->dirty = 0; } - DEBUG_PRINT1("read_dataset: fd=%d count=%zu offset=%zu recfm=%s\n", + 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)); @@ -589,7 +589,7 @@ int allocate_dataset(const char* dataset) memcpy(mvs_style_dataset, &dataset[3], dataset_len-4); mvs_style_dataset[dataset_len-4] = '\0'; - DEBUG_PRINT1("Allocate dataset: %s\n", mvs_style_dataset); + DSIO_LOG_DEBUG("Allocate dataset: %s\n", mvs_style_dataset); ip.__ddname = sysdd; ip.__dsname = mvs_style_dataset; @@ -685,20 +685,20 @@ DatasetEntry* createDatasetEntry(FILE* dd, unsigned short file_ccsid) * ======================================================================== */ off_t lseek_dataset(int fd, off_t offset, int whence) { - DEBUG_PRINT1("lseek_dataset: ENTER fd=%d, offset=%lld, whence=%d\n", fd, (long long)offset, 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; - DEBUG_PRINT1("lseek_dataset: ERROR - EBADF (NULL dd) for fd %d\n", fd); + DSIO_LOG_DEBUG("lseek_dataset: ERROR - EBADF (NULL dd) for fd %d\n", fd); return (off_t)-1; } DatasetEntry* dentry = (DatasetEntry*) dd; if (!dentry->file_ptr) { errno = EBADF; - DEBUG_PRINT1("lseek_dataset: ERROR - EBADF (NULL file_ptr) for fd %d\n", fd); + DSIO_LOG_DEBUG("lseek_dataset: ERROR - EBADF (NULL file_ptr) for fd %d\n", fd); return (off_t)-1; } FILE* fp = dentry->file_ptr; @@ -795,7 +795,7 @@ off_t lseek_dataset(int fd, off_t offset, int whence) { } } - DEBUG_PRINT1("lseek_dataset: fd=%d target=%lld final=%zu\n", + 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; @@ -837,26 +837,26 @@ static int read_ispf_stats(FILE* fp, struct ispf_stats* stats) { #endif int fstat_dataset(int fd, struct stat *buf) { - DEBUG_PRINT1("fstat_dataset: ENTER fd=%d\n", fd); + DSIO_LOG_DEBUG("fstat_dataset: ENTER fd=%d\n", fd); /* Validate parameters */ if (!buf) { errno = EINVAL; - DEBUG_PRINT1("fstat_dataset: ERROR - NULL buffer for fd %d\n", fd); + DSIO_LOG_DEBUG("fstat_dataset: ERROR - NULL buffer for fd %d\n", fd); return -1; } void* dd = GET_DD(fd); if (!dd) { errno = EBADF; - DEBUG_PRINT1("fstat_dataset: ERROR - EBADF (NULL dd) for fd %d\n", fd); + DSIO_LOG_DEBUG("fstat_dataset: ERROR - EBADF (NULL dd) for fd %d\n", fd); return -1; } DatasetEntry* dentry = (DatasetEntry*) dd; if (!dentry->file_ptr) { errno = EBADF; - DEBUG_PRINT1("fstat_dataset: ERROR - EBADF (NULL file_ptr) for fd %d\n", fd); + DSIO_LOG_DEBUG("fstat_dataset: ERROR - EBADF (NULL file_ptr) for fd %d\n", fd); return -1; } @@ -904,12 +904,13 @@ int fstat_dataset(int fd, struct stat *buf) { buf->st_size = (off_t)size; - DEBUG_PRINT1("fstat_dataset: fd=%d size=%lld\n", fd, (long long)buf->st_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; @@ -918,12 +919,14 @@ int stat_dataset(const char *pathname, struct stat *statbuf) { /* 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); close_dataset(fd); + DSIO_LOG_DEBUG("stat_dataset: RETURN %d for %s\n", result, pathname); return result; } @@ -949,7 +952,7 @@ static int read_pds_directory(const char* dataset_name, char*** member_list, int /* In z/OS, we can list members by opening the PDS and reading directory blocks */ /* This is a placeholder - real implementation would use BPAM or ISPF services */ - log_warn("PDS directory reading not fully implemented yet for: %s", dataset_name); + DSIO_LOG_WARN("PDS directory reading not fully implemented yet for: %s", dataset_name); /* For now, return empty directory */ /* TODO: Implement actual BPAM directory reading */ @@ -967,7 +970,7 @@ static DIR* opendir_dataset(const char *name) { if (strchr(name, '(') != NULL) { /* Member specified - not a directory */ errno = ENOTDIR; - log_error("opendir: Cannot open PDS member as directory: %s", name); + DSIO_LOG_ERROR("opendir: Cannot open PDS member as directory: %s", name); return NULL; } @@ -986,21 +989,21 @@ static DIR* opendir_dataset(const char *name) { if (read_pds_directory(name, &dir->member_list, &dir->member_count) != 0) { free(dir); errno = EIO; - log_error("opendir: Failed to read PDS directory: %s", name); + DSIO_LOG_ERROR("opendir: Failed to read PDS directory: %s", name); return NULL; } - log_info("opendir: Opened PDS directory: %s (%d members)", name, dir->member_count); + return (DIR*)dir; } DIR* opendir_zos(const char *name) { if (IS_DATASET(name)) { - DEBUG_PRINT0("calling opendir-dataset\n"); + DSIO_LOG_DEBUG("calling opendir-dataset\n"); return opendir_dataset(name); } else { - DEBUG_PRINT0("calling opendir-file\n"); + DSIO_LOG_DEBUG("calling opendir-file\n"); return opendir(name); } } @@ -1027,7 +1030,7 @@ static struct dirent* readdir_dataset(DIR *dirp) { dir->current_index++; - log_trace("readdir: Read member: %s", entry.d_name); + return &entry; } @@ -1040,10 +1043,10 @@ struct dirent* readdir_zos(DIR *dirp) { /* Check if this is a dataset directory */ DatasetDir* dir = (DatasetDir*)dirp; if (dir->is_dataset_dir) { - DEBUG_PRINT0("calling readdir-dataset\n"); + DSIO_LOG_DEBUG("calling readdir-dataset\n"); return readdir_dataset(dirp); } else { - DEBUG_PRINT0("calling readdir-file\n"); + DSIO_LOG_DEBUG("calling readdir-file\n"); return readdir(dirp); } } @@ -1064,7 +1067,7 @@ static int closedir_dataset(DIR *dirp) { free(dir->member_list); } - log_info("closedir: Closed PDS directory: %s", dir->dataset_name); + free(dir); return 0; @@ -1078,10 +1081,10 @@ int closedir_zos(DIR *dirp) { /* Check if this is a dataset directory */ DatasetDir* dir = (DatasetDir*)dirp; if (dir->is_dataset_dir) { - DEBUG_PRINT0("calling closedir-dataset\n"); + DSIO_LOG_DEBUG("calling closedir-dataset\n"); return closedir_dataset(dirp); } else { - DEBUG_PRINT0("calling closedir-file\n"); + DSIO_LOG_DEBUG("calling closedir-file\n"); return closedir(dirp); } } @@ -1174,7 +1177,7 @@ void set_entry_error(DatasetEntry* entry, dsio_error_t error, const char* messag entry->error_message[DSIO_MAX_ERROR_MSG - 1] = '\0'; } - log_error("Error %d: %s", error, entry->error_message); + DSIO_LOG_ERROR("Error %d: %s", error, entry->error_message); update_global_stats_error(); } @@ -1411,7 +1414,7 @@ void* dsio_convert_buffer(void* buf, size_t len, return convert_ascii_to_ebcdic(buf, len); } - log_warn("Unsupported CCSID conversion: %d -> %d", from_ccsid, to_ccsid); + DSIO_LOG_WARN("Unsupported CCSID conversion: %d -> %d", from_ccsid, to_ccsid); return buf; } @@ -1703,10 +1706,7 @@ int load_metadata_from_file(DatasetEntry* entry) { entry->metadata_loaded = 1; - log_debug("Loaded metadata: RECFM=%s, LRECL=%d, BLKSIZE=%d", - dsio_recfm_to_string(entry->recfm), - entry->lrecl, - entry->blksize); + return 0; } @@ -1768,7 +1768,7 @@ static dsio_dsorg_t detect_dsorg_from_fldata(const fldata_t* fdata) { DatasetEntry* create_entry(FILE* fp, unsigned short file_ccsid) { DatasetEntry* entry = calloc(1, sizeof(DatasetEntry)); if (!entry) { - log_error("Failed to allocate DatasetEntry"); + DSIO_LOG_ERROR("Failed to allocate DatasetEntry"); return NULL; } @@ -2060,19 +2060,19 @@ static ssize_t calculate_vb_emulated_size(FILE* fp, DatasetEntry* entry) { size_t total_size = 0; size_t rec_count = 0; - DEBUG_PRINT1("calculate_vb_emulated_size: ENTER rec_buf_size=%zu\n", entry->rec_buf_size); + DSIO_LOG_DEBUG("calculate_vb_emulated_size: ENTER rec_buf_size=%zu\n", entry->rec_buf_size); 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) { - DEBUG_PRINT1("calculate_vb_emulated_size: RETURN -1 (fgetpos failed) %d\n", 1); + DSIO_LOG_DEBUG("calculate_vb_emulated_size: RETURN -1 (fgetpos failed) %d\n", 1); return -1; } if (fseek(fp, 0, SEEK_SET) != 0) { fsetpos(fp, &saved_pos); - DEBUG_PRINT1("calculate_vb_emulated_size: RETURN -1 (fseek failed) %d\n", 1); + DSIO_LOG_DEBUG("calculate_vb_emulated_size: RETURN -1 (fseek failed) %d\n", 1); return -1; } @@ -2081,7 +2081,7 @@ static ssize_t calculate_vb_emulated_size(FILE* fp, DatasetEntry* entry) { size_t rc = fread(entry->rec_buf, 1, entry->rec_buf_size, fp); if (rc == 0) break; /* EOF */ - DEBUG_PRINT1("calculate_vb_emulated_size: Read record #%zu, raw_length=%zu\n", rec_count + 1, rc); + DSIO_LOG_DEBUG("calculate_vb_emulated_size: Read record #%zu, raw_length=%zu\n", rec_count + 1, rc); /* Validate VB record length doesn't exceed buffer */ if (rc > entry->rec_buf_size) { @@ -2089,7 +2089,7 @@ static ssize_t calculate_vb_emulated_size(FILE* fp, DatasetEntry* entry) { rc, entry->rec_buf_size); fsetpos(fp, &saved_pos); errno = EFBIG; - DEBUG_PRINT1("calculate_vb_emulated_size: RETURN -1 (record too large) %d\n", 1); + DSIO_LOG_DEBUG("calculate_vb_emulated_size: RETURN -1 (record too large) %d\n", 1); return -1; } @@ -2099,42 +2099,42 @@ static ssize_t calculate_vb_emulated_size(FILE* fp, DatasetEntry* entry) { rc--; } - DEBUG_PRINT1("calculate_vb_emulated_size: Record #%zu stripped from %zu to %zu bytes\n", + DSIO_LOG_DEBUG("calculate_vb_emulated_size: Record #%zu stripped from %zu to %zu bytes\n", rec_count + 1, original_rc, rc); total_size += rc + 1; /* +1 for newline */ rec_count++; - DEBUG_PRINT1("calculate_vb_emulated_size: Running total=%zu bytes, rec_count=%zu\n", + DSIO_LOG_DEBUG("calculate_vb_emulated_size: Running total=%zu bytes, rec_count=%zu\n", total_size, rec_count); } /* Restore position */ if (fsetpos(fp, &saved_pos) != 0) { - DEBUG_PRINT1("WARNING: Failed to restore file position after size calculation %d\n", 1); + DSIO_LOG_DEBUG("WARNING: Failed to restore file position after size calculation %d\n", 1); } } - DEBUG_PRINT1("calculate_vb_emulated_size: RETURN %zu bytes (%zu records) for %s dataset\n", + DSIO_LOG_DEBUG("calculate_vb_emulated_size: RETURN %zu bytes (%zu records) for %s dataset\n", total_size, rec_count, entry->is_fixed_recfm ? "FB" : "VB"); return (ssize_t)total_size; } ssize_t dsio_get_size(int fd) { - DEBUG_PRINT1("dsio_get_size: ENTER fd=%d\n", fd); + DSIO_LOG_DEBUG("dsio_get_size: ENTER fd=%d\n", fd); void* dd = GET_DD(fd); if (!dd || IS_FD(fd)) { errno = EBADF; - DEBUG_PRINT1("dsio_get_size: RETURN -1 (invalid fd) %d\n", 1); + DSIO_LOG_DEBUG("dsio_get_size: RETURN -1 (invalid fd) %d\n", 1); return -1; } DatasetEntry* entry = ENTRY_TO(dd); if (!entry->file_ptr) { errno = EBADF; - DEBUG_PRINT1("dsio_get_size: RETURN -1 (no file_ptr) %d\n", 1); + DSIO_LOG_DEBUG("dsio_get_size: RETURN -1 (no file_ptr) %d\n", 1); return -1; } @@ -2142,7 +2142,7 @@ ssize_t dsio_get_size(int fd) { /* For VB datasets, check if we have cached size */ if (!entry->is_fixed_recfm && entry->vb_size_calculated) { - DEBUG_PRINT1("dsio_get_size: RETURN %zu (cached VB size)\n", entry->vb_cached_size); + DSIO_LOG_DEBUG("dsio_get_size: RETURN %zu (cached VB size)\n", entry->vb_cached_size); return (ssize_t)entry->vb_cached_size; } @@ -2160,15 +2160,15 @@ ssize_t dsio_get_size(int fd) { long native_size = ftell(fp); if (native_size < 0) { fsetpos(fp, &pos); - DEBUG_PRINT1("dsio_get_size: RETURN -1 (ftell failed) %d\n", 1); + DSIO_LOG_DEBUG("dsio_get_size: RETURN -1 (ftell failed) %d\n", 1); return -1; } - DEBUG_PRINT1("dsio_get_size: native_size=%ld, is_fixed_recfm=%d, reclen=%zu\n", + DSIO_LOG_DEBUG("dsio_get_size: native_size=%ld, is_fixed_recfm=%d, reclen=%zu\n", native_size, entry->is_fixed_recfm, entry->reclen); if (fsetpos(fp, &pos) != 0) { - DEBUG_PRINT1("WARNING: fsetpos() failed in dsio_get_size %d\n", 1); + DSIO_LOG_DEBUG("WARNING: fsetpos() failed in dsio_get_size %d\n", 1); } /* Calculate emulated stream size based on record format */ @@ -2178,23 +2178,23 @@ ssize_t dsio_get_size(int fd) { /* FB: native_size is total bytes, add newlines */ size_t num_records = native_size / entry->reclen; emulated_size = (ssize_t)(native_size + num_records); - DEBUG_PRINT1("dsio_get_size: FB calculation - native=%ld, reclen=%zu, num_records=%zu, emulated=%zd\n", + DSIO_LOG_DEBUG("dsio_get_size: FB calculation - native=%ld, reclen=%zu, num_records=%zu, emulated=%zd\n", native_size, entry->reclen, num_records, emulated_size); } else { /* VB/U: Calculate by reading all records (one-time cost) */ - DEBUG_PRINT1("dsio_get_size: Calculating VB emulated size...%d\n", 1); + DSIO_LOG_DEBUG("dsio_get_size: Calculating VB emulated size...%d\n", 1); emulated_size = calculate_vb_emulated_size(fp, entry); if (emulated_size >= 0) { /* Cache the result for future calls */ entry->vb_cached_size = (size_t)emulated_size; entry->vb_size_calculated = 1; - DEBUG_PRINT1("dsio_get_size: VB size calculated and cached: %zd\n", emulated_size); + DSIO_LOG_DEBUG("dsio_get_size: VB size calculated and cached: %zd\n", emulated_size); } else { - DEBUG_PRINT1("dsio_get_size: VB size calculation failed %d\n", 1); + DSIO_LOG_DEBUG("dsio_get_size: VB size calculation failed %d\n", 1); } } - DEBUG_PRINT1("dsio_get_size: RETURN %zd (fd=%d, native=%ld, emulated=%zd)\n", + DSIO_LOG_DEBUG("dsio_get_size: RETURN %zd (fd=%d, native=%ld, emulated=%zd)\n", emulated_size, fd, native_size, emulated_size); return emulated_size; @@ -2234,8 +2234,7 @@ int dsio_set_ccsid_config(int fd, const dsio_ccsid_config_t* config) { entry->program_ccsid = config->target_ccsid; entry->conversion_state = config->conversion_enabled ? 1 : 0; - log_debug("Set CCSID config: source=%d, target=%d, enabled=%d", - config->source_ccsid, config->target_ccsid, config->conversion_enabled); + return 0; } diff --git a/src/zos-io.cc b/src/zos-io.cc index c67f7a86..44ea9706 100644 --- a/src/zos-io.cc +++ b/src/zos-io.cc @@ -914,16 +914,16 @@ int __open_ds_file(const char *filename, int opts, ...) { va_start(ap, opts); int perms = va_arg(ap, int); - DEBUG_PRINT1("calling __open_ds_file file %s\n", filename); + DSIO_LOG_DEBUG("calling __open_ds_file file %s\n", filename); if (IS_DATASET(filename)) { fd = open_dataset(filename, opts, perms); - DEBUG_PRINT1("calling open-dataset fd %d\n", fd); + DSIO_LOG_DEBUG("calling open-dataset fd %d\n", fd); } else { fd = __open_ascii(filename, opts, perms); if (fd >= 0) { ADD_FD(fd); } - DEBUG_PRINT1("calling open-file fd %d\n", fd); + DSIO_LOG_DEBUG("calling open-file fd %d\n", fd); } va_end(ap); return fd; @@ -1015,10 +1015,10 @@ int __mkstemp_ascii(char * tmpl) { int __mkstemp_ds_file(char * tmpl) { int fd; if (IS_DATASET(tmpl)) { - DEBUG_PRINT0("calling mkstemp-dataset\n"); + DSIO_LOG_DEBUG("calling mkstemp-dataset\n"); fd = mkstemp_dataset(tmpl); } else { - DEBUG_PRINT0("calling mkstemp-file\n"); + DSIO_LOG_DEBUG("calling mkstemp-file\n"); fd = __mkstemp_ascii(tmpl); if (fd >= 0) { ADD_FD(fd); @@ -1029,33 +1029,33 @@ int __mkstemp_ds_file(char * tmpl) { ssize_t __write_ds_file(int fd, const void *buf, size_t count) { if (IS_DD(fd)) { - DEBUG_PRINT1("calling write-dataset fd %d\n", fd); + DSIO_LOG_DEBUG("calling write-dataset fd %d\n", fd); return write_dataset(fd, buf, count); } else if (IS_FD(fd)) { - DEBUG_PRINT1("calling write-file fd %d\n", fd); + 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)) { - DEBUG_PRINT1("calling read-dataset fd %d\n", fd); + DSIO_LOG_DEBUG("calling read-dataset fd %d\n", fd); return read_dataset(fd, buf, count); } else if (IS_FD(fd)) { - DEBUG_PRINT1("calling read-file fd %d\n", fd); + DSIO_LOG_DEBUG("calling read-file fd %d\n", fd); return __read_orig(fd, buf, count); } } int __close(int fd) { if (IS_DD(fd)) { - DEBUG_PRINT1("calling close-dataset fd %d\n", fd); + DSIO_LOG_DEBUG("calling close-dataset fd %d\n", fd); return close_dataset(fd); } else if (IS_FD(fd)) { - DEBUG_PRINT1("calling close-file fd %d\n", fd); + DSIO_LOG_DEBUG("calling close-file fd %d\n", fd); int ret = __close_orig(fd); if (ret >= 0) __fd_close(fd); @@ -1065,31 +1065,31 @@ int __close(int fd) { off_t __lseek_ds_file(int fd, off_t offset, int whence) { if (IS_DD(fd)) { - DEBUG_PRINT1("calling lseek-dataset fd %d offset %d whence %d\n", fd, offset, whence); + DSIO_LOG_DEBUG("calling lseek-dataset fd %d offset %d whence %d\n", fd, offset, whence); return lseek_dataset(fd, offset, whence); } else if (IS_FD(fd)) { - DEBUG_PRINT1("calling lseek-file fd %d offset %d whence %d\n", fd, offset, whence); + DSIO_LOG_DEBUG("calling lseek-file fd %d offset %d whence %d\n", fd, offset, whence); return __lseek_orig(fd, offset, whence); } } int __stat_ds_file(const char *pathname, struct stat *statbuf) { if (IS_DATASET(pathname)) { - DEBUG_PRINT1("calling stat-dataset path %s\n", pathname); + DSIO_LOG_DEBUG("calling stat-dataset path %s\n", pathname); return stat_dataset(pathname, statbuf); } else { - DEBUG_PRINT1("calling stat-file path %s\n", pathname); + 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)) { - DEBUG_PRINT1("calling fstat-dataset11111111111111111111111 fd %d\n", fd); + DSIO_LOG_DEBUG("calling fstat-dataset fd %d\n", fd); return fstat_dataset(fd, statbuf); } else { - DEBUG_PRINT1("calling fstat-file22222222222222222222 fd %d\n", fd); + DSIO_LOG_DEBUG("calling fstat-file fd %d\n", fd); return __fstat_orig(fd, statbuf); } } From 8663b0898290f1f50fa7d904e1e227a644d182d9 Mon Sep 17 00:00:00 2001 From: sachintu47 Date: Wed, 25 Mar 2026 02:26:26 -0400 Subject: [PATCH 14/28] refactor: remove ISPF implementation (deferred to future phase) --- include/ihapds.h | 171 ---------------------------------------- include/ispf.h | 50 ------------ include/ispf_reader.h | 23 ------ include/zos-datasetio.h | 10 +-- include/ztime.h | 11 --- src/zos-datasetio.c | 37 +-------- 6 files changed, 7 insertions(+), 295 deletions(-) delete mode 100644 include/ihapds.h delete mode 100644 include/ispf.h delete mode 100644 include/ispf_reader.h delete mode 100644 include/ztime.h diff --git a/include/ihapds.h b/include/ihapds.h deleted file mode 100644 index be2a44e5..00000000 --- a/include/ihapds.h +++ /dev/null @@ -1,171 +0,0 @@ -#ifndef __IHAPDS_H__ -#define __IHAPDS_H__ - -/* Standalone PDS directory entry structures - no external dependencies */ - -#pragma pack(1) - -struct pds2 { - unsigned char pds2name[8]; /* MEMBER NAME OR ALIAS NAME */ - unsigned char pds2ttrp[3]; /* TTR OF FIRST BLOCK OF NAMED MEMBER */ - char pds2cnct; /* CONCATENATION NUMBER OF THE DATA SET */ - unsigned char pds2libf; /* LIBRARY FLAG FIELD */ - unsigned char pds2indc; /* INDICATOR BYTE */ - union { - unsigned char pds2usrd; /* START OF VARIABLE LENGTH USER DATA FIELD */ - unsigned char pds2ttrt[3]; /* TTR OF FIRST BLOCK OF TEXT */ - }; - unsigned char pds2zero; /* ZERO */ - unsigned char pds2ttrn[3]; /* TTR OF NOTE LIST OR SCATTER/TRANSLATION */ - char pds2nl; /* NUMBER OF ENTRIES IN NOTE LIST FOR */ - union { - unsigned char pds2atr[2]; /* TWO-BYTE PROGRAM ATTRIBUTE FIELD */ - struct { - unsigned char pds2atr1; /* FIRST BYTE OF PROGRAM ATTRIBUTE FIELD */ - unsigned int pds2flvl : 1, /* If one, the program cannot be processed */ - pds2org0 : 1, /* ORIGIN OF FIRST BLOCK OF TEXT IS ZERO */ - pds2ep0 : 1, /* ENTRY POINT IS ZERO */ - pds2nrld : 1, /* PROGRAM CONTAINS NO RLD ITEMS */ - pds2nrep : 1, /* PROGRAM CANNOT BE REPROCESSED BY LINKAGE */ - pds2tstn : 1, /* PROGRAM CONTAINS TESTRAN SYMBOL CARDS */ - pds2lef : 1, /* PROGRAM CREATED BY LINKAGE EDITOR F */ - pds2refr : 1; /* REFRESHABLE PROGRAM */ - }; - }; - int pds2stor : 24; /* TOTAL CONTIGUOUS MAIN STORAGE REQUIREMENT */ - short int pds2ftbl; /* LENGTH OF FIRST BLOCK OF TEXT */ - unsigned int pds2epa : 24; /* ENTRY POINT ADDRESS ASSOCIATED WITH */ - union { - unsigned char pds2ftbo[3]; /* FLAG BYTES (MVS USE OF FIELD) @LCC */ - struct { - unsigned char pds2ftb1; /* BYTE 1 OF PDS2FTBO */ - unsigned int pds2altp : 1, /* ALTERNATE PRIMARY FLAG. IF ON (FOR A @L8A */ - : 1, - pdslrm64 : 1, /* 0: RMODE is 24 or 31. @LDA */ - pdslrmod : 1, /* 0: RMODE 24 or reserved @LDA */ - pdsaamod : 2, /* ALIAS ENTRY POINT ADDRESSING MODE @L6A */ - pdsmamod : 2; /* MAIN ENTRY POINT ADDRESSING MODE @L6A */ - struct { - unsigned int pds2nmig : 1, /* THIS PROGRAM OBJECT CANNOT BE CONVERTED */ - pds2prim : 1, /* FETCHOPT PRIME WAS SPECIFIED @L7A */ - pds2pack : 1, /* FETCHOPT PACK WAS SPECIFIED @L7A */ - : 5; - } pds2rlds; /* NUMBER OF RLD/CONTROL RECORDS WHICH @L6A */ - }; - }; - short int pds2slsz; /* NUMBER OF BYTES IN SCATTER LIST */ - short int pds2ttsz; /* NUMBER OF BYTES IN TRANSLATION TABLE */ - unsigned char pds2esdt[2]; /* IDENTIFICATION OF ESD ITEM (ESDID) OF */ - unsigned char pds2esdc[2]; /* IDENTIFICATION OF ESD ITEM (ESDID) OF */ - unsigned int pds2epm : 24; /* ENTRY POINT FOR MEMBER NAME */ - unsigned char pds2mnm[8]; /* MEMBER NAME OF PROGRAM. WHEN THE */ - union { - short int pdss03; /* FORCE HALF-WORD ALIGNMENT FOR SSI */ - unsigned char pdsssiwd[4]; /* SSI INFORMATION WORD */ - struct { - char pdschlvl; /* CHANGE LEVEL OF MEMBER */ - unsigned char pdsssifb; /* SSI FLAG BYTE */ - unsigned char pdsmbrsn[2]; /* MEMBER SERIAL NUMBER */ - struct { - char pdsapfct; /* LENGTH OF PROGRAM AUTHORIZATION CODE */ - unsigned char pdsapfac; /* PROGRAM AUTHORIZATION CODE */ - } pdsapf; /* PROGRAM AUTHORIZATION FACILITY (APF) */ - }; - }; - union { - char pds2lpol; /* LARGE PROGRAM OBJECT SECTION LENGTH @L7A */ - char pds2llml; /* ALTERNATE NAME FOR PDS2LLML @L7A */ - }; - int pds2vstr; /* VIRTUAL STORAGE REQUIREMENT FOR THIS */ - int pds2mepa; /* MAIN ENTRY POINT OFFSET */ - int pds2aepa; /* ALIAS ENTRY POINT OFFSET. ONLY VALID */ - unsigned int : 4, - pds2xattr_optn_mask : 4; /* Bits 4-7 of PDS2XATTRBYTE0 identify the */ - unsigned int pds2longparm : 1, /* PARM > 100 chars allowed @LBA */ - : 7; - char _filler1; /* Reserved @LBA */ - }; - -/* Values for field "pds2libf" */ -#define pds2lnrm 0x00 /* NORMAL CASE */ -#define pds2llnk 0x01 /* IF DCB OPERAND IN BLDL MACRO INTRUCTION */ -#define pds2ljob 0x02 /* IF DCB OPERAND IN BLDL MACRO INTRUCTION */ - -/* Values for field "pds2indc" */ -#define pds2alis 0x80 /* NAME IN THE FIELD PDS2NAME IS AN ALIAS */ -#define dealias 0x80 /* --- ALIAS FOR PDS2ALIS */ -#define pds2nttr 0x60 /* NUMBER OF TTR'S IN THE USER DATA FIELD */ -#define pds2lusr 0x1F /* - LENGTH OF USER DATA FIELD */ - -/* Values for field "pds2atr1" */ -#define pds2rent 0x80 /* REENTERABLE */ -#define dereen 0x80 /* --- ALIAS FOR PDS2RENT */ -#define pds2reus 0x40 /* REUSABLE */ -#define pds2ovly 0x20 /* IN OVERLAY STRUCTURE */ -#define deovly 0x20 /* --- ALIAS FOR PDS2OVLY */ -#define pds2test 0x10 /* PROGRAM TO BE TESTED - TESTRAN */ -#define pds2load 0x08 /* ONLY LOADABLE */ -#define delody 0x08 /* --- ALIAS FOR PDS2LOAD */ -#define pds2sctr 0x04 /* SCATTER FORMAT */ -#define descat 0x04 /* --- ALIAS FOR PDS2SCTR */ -#define pds2exec 0x02 /* EXECUTABLE */ -#define dexcut 0x02 /* --- ALIAS FOR PDS2EXEC */ -#define pds21blk 0x01 /* IF ZERO, PROGRAM CONTAINS MULTIPLE */ - -/* Values for field "pds2ftb1" */ -#define pdsaosle 0x80 /* Program has been processed by OS/VS1 or */ -#define pds2big 0x40 /* THE LARGE PROGRAM OBJECT EXTENSION */ -#define pds2paga 0x20 /* PAGE ALIGNMENT REQUIRED FOR PROGRAM */ -#define pds2ssi 0x10 /* SSI INFORMATION PRESENT */ -#define pdsapflg 0x08 /* INFORMATION IN PDSAPF IS VALID */ -#define pds2pgmo 0x04 /* PROGRAM OBJECT. THE PDS2FTB3 */ -#define pds2lfmt 0x04 /* ALTERNATE NAME FOR PDS2PGMO @L7A */ -#define pds2sign 0x02 /* PROGRAM OBJECT IS SIGNED. VERIFIED ON */ -#define pds2xatr 0x01 /* PDS2XATTR SECTION @LBA */ - -/* Values for field "pdsssifb" */ -#define pdsforce 0x40 /* A FORCE CONTROL CARD WAS USED WHEN */ -#define pdsusrch 0x20 /* A CHANGE WAS MADE TO MEMBER BY THE */ -#define pdsemfix 0x10 /* SET WHEN AN EMERGENCY IBM-AUTHORIZED */ -#define pdsdepch 0x08 /* A CHANGE MADE TO THE MEMBER IS DEPENDENT */ -#define pdssysgn 0x06 /* FLAGS THAT INDICATE WHETHER A */ -#define pdsnosgn 0x00 /* NOT CRITICAL FOR SYSTEM GENERATION */ -#define pdscmsgn 0x02 /* MAY REQUIRE COMPLETE REGENERATION */ -#define pdsptsgn 0x04 /* MAY REQUIRE PARTIAL REGENERATION */ -#define pdsibmmb 0x01 /* MEMBER IS SUPPLIED BY IBM */ - -#define bit0 128 -#define bit1 64 -#define bit2 32 -#define bit3 16 -#define bit4 8 -#define bit5 4 -#define bit6 2 -#define bit7 1 -#define dezbyte 0x0C /* --- ALIAS */ -#define pdsbcend 0x23 /* END OF BASIC SECTION */ -#define pdsbcln 0x23 /* - LENGTH OF BASIC SECTION */ -#define pdss01 0x23 /* START OF SCATTER LOAD SECTION */ -#define pdss01nd 0x2B /* END OF SCATTER LOAD SECTION */ -#define pdss01ln 0x08 /* - LENGTH OF SCATTER LOAD SECTION */ -#define pdss02 0x2B /* START OF ALIAS SECTION */ -#define deentbk 0x2B /* --- ALIAS */ -#define pdss02nd 0x36 /* END OF ALIAS SECTION */ -#define pdss02ln 0x0B /* - LENGTH OF ALIAS SECTION */ -#define pdss03nd 0x3A /* END OF SSI SECTION */ -#define pdss03ln 0x04 /* LENGTH OF SSI SECTION */ -#define pdss04 0x3A /* START OF APF SECTION */ -#define pdss04nd 0x3C /* END OF APF SECTION */ -#define pdss04ln 0x02 /* LENGTH OF APF SECTION */ -#define pdslpo 0x3C /* START OF LARGE PROGRAM OBJECT SECTION@L7A */ -#define pdsllm 0x3C /* ALTERNATE NAME FOR PDSLPO @L7A */ -#define pdslpond 0x49 /* END OF LARGE PROGRAM OBJECT SECTION */ -#define pdsllmnd 0x49 /* ALTERNATE NAME FOR PDSLPOND */ -#define pdslpoln 0x0D /* LENGTH OF LLM SECTION @L7A */ -#define pdsllmln 0x0D /* ALTERNATE NAME FOR PDSLPOLN @L7A */ -#define pds2xattr 0x49 /* Start of extended attributes @LBA */ -#define pds2xattr_opt 0x4C /* Start of optional fields. Number of */ - -#pragma pack(pop) - -#endif // __IHAPDS_H__ diff --git a/include/ispf.h b/include/ispf.h deleted file mode 100644 index 53a12b2a..00000000 --- a/include/ispf.h +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef __ISPF__ - #define __ISPF__ - - #include - - #pragma pack(1) - struct ispf_disk_stats { - unsigned char ver_num; - unsigned char mod_num; - int sclm:1; - int reserve_a:1; - int extended:1; - int reserve_b:5; - unsigned char pd_mod_seconds; - - unsigned char create_century; - char pd_create_julian[3]; - - unsigned char mod_century; - char pd_mod_julian[3]; - - unsigned char pd_mod_hours; - unsigned char pd_mod_minutes; - unsigned short curr_num_lines; - - unsigned short init_num_lines; - unsigned short mod_num_lines; - - char userid[8]; - - /* following is available only in extended format */ - unsigned int full_curr_num_lines; - unsigned int full_init_num_lines; - unsigned int full_mod_num_lines; - }; - #pragma pack(pop) - - struct ispf_stats { - struct tm create_time; - struct tm mod_time; - unsigned int curr_num_lines; - unsigned int init_num_lines; - unsigned int mod_num_lines; - unsigned char userid[8+1]; - unsigned char ver_num; - unsigned char mod_num; - unsigned char sclm; - }; - -#endif diff --git a/include/ispf_reader.h b/include/ispf_reader.h deleted file mode 100644 index 384d157f..00000000 --- a/include/ispf_reader.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef __ISPF_READER_H__ -#define __ISPF_READER_H__ - -#include -#include "ispf.h" - -/* - * Read ISPF statistics for a PDS member - * - * Parameters: - * fp - FILE pointer to the open PDS - * member_name - Name of the member (up to 8 characters) - * stats - Output structure to receive ISPF statistics - * - * Returns: - * 0 on success - * -1 if ISPF stats not available or error occurred - */ -int read_member_ispf_stats(FILE* fp, const char* member_name, struct ispf_stats* stats); - -#endif /* __ISPF_READER_H__ */ - -// Made with Bob diff --git a/include/zos-datasetio.h b/include/zos-datasetio.h index 251eb893..92d64ff5 100644 --- a/include/zos-datasetio.h +++ b/include/zos-datasetio.h @@ -404,11 +404,11 @@ typedef struct DatasetDir { #endif #if ZOSLIB_DATASET_LOGGING - #define DSIO_LOG_ERROR(fmt, ...) log_error(fmt, ##__VA_ARGS__) - #define DSIO_LOG_WARN(fmt, ...) log_warn(fmt, ##__VA_ARGS__) - #define DSIO_LOG_INFO(fmt, ...) log_info(fmt, ##__VA_ARGS__) - #define DSIO_LOG_DEBUG(fmt, ...) log_debug(fmt, ##__VA_ARGS__) - #define DSIO_LOG_TRACE(fmt, ...) log_trace(fmt, ##__VA_ARGS__) + #define DSIO_LOG_ERROR(fmt, ...) do { if (g_debug_enabled) log_error(fmt, ##__VA_ARGS__); } while(0) + #define DSIO_LOG_WARN(fmt, ...) do { if (g_debug_enabled) log_warn(fmt, ##__VA_ARGS__); } while(0) + #define DSIO_LOG_INFO(fmt, ...) do { if (g_debug_enabled) log_info(fmt, ##__VA_ARGS__); } while(0) + #define DSIO_LOG_DEBUG(fmt, ...) do { if (g_debug_enabled) log_debug(fmt, ##__VA_ARGS__); } while(0) + #define DSIO_LOG_TRACE(fmt, ...) do { if (g_debug_enabled) log_trace(fmt, ##__VA_ARGS__); } while(0) #else #define DSIO_LOG_ERROR(fmt, ...) ((void)0) #define DSIO_LOG_WARN(fmt, ...) ((void)0) diff --git a/include/ztime.h b/include/ztime.h deleted file mode 100644 index cfb667b7..00000000 --- a/include/ztime.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef __Z_TIME__ - #define __Z_TIME__ 1 - - #include - - int pdjd_to_tm(const char* pdjd, int start_century, struct tm* ltime); - void tm_to_pdjd(unsigned char* century, char* pdjd, struct tm* ltime); - time_t tod_to_time(unsigned long long tod); - unsigned int pd_to_d(unsigned char pd); - unsigned char d_to_pd(unsigned int val, int set_positive_sign); -#endif diff --git a/src/zos-datasetio.c b/src/zos-datasetio.c index 1e61f165..ad07cba3 100644 --- a/src/zos-datasetio.c +++ b/src/zos-datasetio.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -18,10 +19,6 @@ #include #include "zos-datasetio.h" -#include "ispf.h" -#include "ztime.h" -#include "ihapds.h" -#include "ispf_reader.h" void* descriptor_table[MAX_FDS] = { 0 }; @@ -805,36 +802,6 @@ off_t lseek_dataset(int fd, off_t offset, int whence) { * fstat() / stat() - File Metadata * ======================================================================== */ -/* Helper function to read ISPF statistics from PDS member */ -#if 0 -static int read_ispf_stats(FILE* fp, struct ispf_stats* stats) { - if (!fp || !stats) { - return -1; - } - - /* Get file data to check if this is a PDS member */ - fldata_t fdata; - if (fldata(fp, NULL, &fdata) != 0) { - return -1; - } - - /* Check if this is a PDS/PDSE member */ - if (fdata.__dsorgPO == 0) { - return -1; /* Not a PDS/PDSE */ - } - - /* Get member name from fldata */ - char member_name[9] = {0}; - if (fdata.__dsname == NULL) { - return -1; /* No member name */ - } - memcpy(member_name, fdata.__dsname, 8); - member_name[8] = '\0'; - - /* Use the ISPF reader module to get statistics */ - return read_member_ispf_stats(fp, member_name, stats); -} -#endif int fstat_dataset(int fd, struct stat *buf) { DSIO_LOG_DEBUG("fstat_dataset: ENTER fd=%d\n", fd); @@ -950,7 +917,7 @@ static int read_pds_directory(const char* dataset_name, char*** member_list, int /* Try to open the PDS as a directory */ /* In z/OS, we can list members by opening the PDS and reading directory blocks */ - /* This is a placeholder - real implementation would use BPAM or ISPF services */ + /* This is a placeholder - real implementation would use BPAM services */ DSIO_LOG_WARN("PDS directory reading not fully implemented yet for: %s", dataset_name); From a7e990b4bbc1ef7e92f8927b141543cd984193de Mon Sep 17 00:00:00 2001 From: sachintu47 Date: Wed, 25 Mar 2026 02:47:51 -0400 Subject: [PATCH 15/28] cleanup dataset I/O logging and remove incomplete features --- include/zos-datasetio.h | 71 +---------- src/zos-datasetio.c | 264 +--------------------------------------- 2 files changed, 11 insertions(+), 324 deletions(-) diff --git a/include/zos-datasetio.h b/include/zos-datasetio.h index 92d64ff5..bc2df16a 100644 --- a/include/zos-datasetio.h +++ b/include/zos-datasetio.h @@ -246,32 +246,6 @@ void dsio_enable_debug(int enable); /* Log a message (internal use) */ void dsio_log(dsio_log_level_t level, const char* format, ...); -/* ======================================================================== - * STATISTICS AND MONITORING - * ======================================================================== */ - -/* I/O statistics */ -typedef struct { - size_t bytes_read; /* Total bytes read */ - size_t bytes_written; /* Total bytes written */ - size_t read_operations; /* Number of read calls */ - size_t write_operations; /* Number of write calls */ - size_t open_operations; /* Number of open calls */ - size_t close_operations; /* Number of close calls */ - size_t errors; /* Number of errors */ -} dsio_stats_t; - -/* Get statistics for a file descriptor */ -int dsio_get_stats(int fd, dsio_stats_t* stats); - -/* Reset statistics for a file descriptor */ -int dsio_reset_stats(int fd); - -/* Get global statistics */ -void dsio_get_global_stats(dsio_stats_t* stats); - -/* Reset global statistics */ -void dsio_reset_global_stats(void); /* ======================================================================== * UTILITY FUNCTIONS @@ -358,15 +332,8 @@ typedef struct DatasetEntry { int is_pds_member; int readonly; - /* I/O statistics */ - size_t bytes_read; - size_t bytes_written; - size_t read_operations; - size_t write_operations; - /* State flags */ int metadata_loaded; - int stats_enabled; /* Record buffer for stream emulation */ char* rec_buf; /* internal record I/O buffer */ @@ -386,14 +353,7 @@ typedef struct DatasetEntry { size_t vb_cached_size; /* Cached emulated size for VB datasets */ } DatasetEntry; -/* Directory structure for PDS/PDSE member listing */ -typedef struct DatasetDir { - char dataset_name[256]; - char** member_list; - int member_count; - int current_index; - int is_dataset_dir; -} DatasetDir; + #define GET_DUMMY_FD() (open("/dev/null", O_WRONLY, 0)) #define IS_DATASET(name) ((name) && ((name)[0] == '/') && ((name)[1] == '/')) @@ -404,11 +364,11 @@ typedef struct DatasetDir { #endif #if ZOSLIB_DATASET_LOGGING - #define DSIO_LOG_ERROR(fmt, ...) do { if (g_debug_enabled) log_error(fmt, ##__VA_ARGS__); } while(0) - #define DSIO_LOG_WARN(fmt, ...) do { if (g_debug_enabled) log_warn(fmt, ##__VA_ARGS__); } while(0) - #define DSIO_LOG_INFO(fmt, ...) do { if (g_debug_enabled) log_info(fmt, ##__VA_ARGS__); } while(0) - #define DSIO_LOG_DEBUG(fmt, ...) do { if (g_debug_enabled) log_debug(fmt, ##__VA_ARGS__); } while(0) - #define DSIO_LOG_TRACE(fmt, ...) do { if (g_debug_enabled) log_trace(fmt, ##__VA_ARGS__); } while(0) + #define DSIO_LOG_ERROR(fmt, ...) do { if (g_debug_enabled && g_log_level >= DSIO_LOG_ERROR) log_error(fmt, ##__VA_ARGS__); } while(0) + #define DSIO_LOG_WARN(fmt, ...) do { if (g_debug_enabled && g_log_level >= DSIO_LOG_WARN) log_warn(fmt, ##__VA_ARGS__); } while(0) + #define DSIO_LOG_INFO(fmt, ...) do { if (g_debug_enabled && g_log_level >= DSIO_LOG_INFO) log_info(fmt, ##__VA_ARGS__); } while(0) + #define DSIO_LOG_DEBUG(fmt, ...) do { if (g_debug_enabled && g_log_level >= DSIO_LOG_DEBUG) log_debug(fmt, ##__VA_ARGS__); } while(0) + #define DSIO_LOG_TRACE(fmt, ...) do { if (g_debug_enabled && g_log_level >= DSIO_LOG_TRACE) log_trace(fmt, ##__VA_ARGS__); } while(0) #else #define DSIO_LOG_ERROR(fmt, ...) ((void)0) #define DSIO_LOG_WARN(fmt, ...) ((void)0) @@ -425,20 +385,7 @@ extern void* descriptor_table[MAX_FDS]; -/* Global statistics */ -typedef struct { - size_t total_bytes_read; - size_t total_bytes_written; - size_t total_read_operations; - size_t total_write_operations; - size_t total_open_operations; - size_t total_close_operations; - size_t total_errors; - int stats_enabled; -} GlobalStats; - /* Global state */ -extern GlobalStats g_stats; extern dsio_log_level_t g_log_level; extern FILE* g_log_stream; extern int g_debug_enabled; @@ -462,12 +409,6 @@ 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); -/* Update statistics */ -void update_read_stats(DatasetEntry* entry, size_t bytes); -void update_write_stats(DatasetEntry* entry, size_t bytes); -void update_global_stats_open(void); -void update_global_stats_close(void); -void update_global_stats_error(void); /* Logging helpers */ void log_error(const char* format, ...); diff --git a/src/zos-datasetio.c b/src/zos-datasetio.c index ad07cba3..cbe5b975 100644 --- a/src/zos-datasetio.c +++ b/src/zos-datasetio.c @@ -93,7 +93,7 @@ int mkstemp_dataset(char* tmplate) } parse_and_store_name(dentry, tmplate); - update_global_stats_open(); + fd = GET_DUMMY_FD(); ADD_DD(fd, dentry); @@ -487,7 +487,7 @@ ssize_t read_dataset(int fd, void* buf, size_t count) } if (bytes_copied > 0) { - update_read_stats(dentry, bytes_copied); + } return (bytes_copied == 0 && count > 0) ? 0 : (ssize_t) bytes_copied; @@ -897,171 +897,13 @@ int stat_dataset(const char *pathname, struct stat *statbuf) { return result; } -/* ======================================================================== - * opendir() / readdir() / closedir() - Directory Operations for PDS/PDSE - * ======================================================================== */ - -#undef opendir -#undef readdir -#undef closedir - -/* Helper function to read PDS directory using BPAM or simple approach */ -static int read_pds_directory(const char* dataset_name, char*** member_list, int* member_count) { - /* This is a simplified implementation - * A full implementation would use BPAM to read the directory - * For now, we'll use a simple approach with fopen and directory reading - */ - - *member_list = NULL; - *member_count = 0; - - /* Try to open the PDS as a directory */ - /* In z/OS, we can list members by opening the PDS and reading directory blocks */ - /* This is a placeholder - real implementation would use BPAM services */ - - DSIO_LOG_WARN("PDS directory reading not fully implemented yet for: %s", dataset_name); - - /* For now, return empty directory */ - /* TODO: Implement actual BPAM directory reading */ - - return 0; -} - -static DIR* opendir_dataset(const char *name) { - if (!name) { - errno = EINVAL; - return NULL; - } - - /* Check if this is a PDS/PDSE (no member specified) */ - if (strchr(name, '(') != NULL) { - /* Member specified - not a directory */ - errno = ENOTDIR; - DSIO_LOG_ERROR("opendir: Cannot open PDS member as directory: %s", name); - return NULL; - } - - /* Allocate directory structure */ - DatasetDir* dir = calloc(1, sizeof(DatasetDir)); - if (!dir) { - errno = ENOMEM; - return NULL; - } - - strncpy(dir->dataset_name, name, sizeof(dir->dataset_name) - 1); - dir->is_dataset_dir = 1; - dir->current_index = 0; - - /* Read PDS directory */ - if (read_pds_directory(name, &dir->member_list, &dir->member_count) != 0) { - free(dir); - errno = EIO; - DSIO_LOG_ERROR("opendir: Failed to read PDS directory: %s", name); - return NULL; - } - - - - return (DIR*)dir; -} - -DIR* opendir_zos(const char *name) { - if (IS_DATASET(name)) { - DSIO_LOG_DEBUG("calling opendir-dataset\n"); - return opendir_dataset(name); - } else { - DSIO_LOG_DEBUG("calling opendir-file\n"); - return opendir(name); - } -} - -static struct dirent* readdir_dataset(DIR *dirp) { - if (!dirp) { - errno = EINVAL; - return NULL; - } - - DatasetDir* dir = (DatasetDir*)dirp; - - /* Check if we've read all members */ - if (dir->current_index >= dir->member_count) { - return NULL; /* End of directory */ - } - - /* Allocate dirent structure (static for simplicity) */ - static struct dirent entry; - memset(&entry, 0, sizeof(entry)); - - /* Copy member name */ - strncpy(entry.d_name, dir->member_list[dir->current_index], sizeof(entry.d_name) - 1); - - dir->current_index++; - - - - return &entry; -} - -struct dirent* readdir_zos(DIR *dirp) { - if (!dirp) { - return NULL; - } - - /* Check if this is a dataset directory */ - DatasetDir* dir = (DatasetDir*)dirp; - if (dir->is_dataset_dir) { - DSIO_LOG_DEBUG("calling readdir-dataset\n"); - return readdir_dataset(dirp); - } else { - DSIO_LOG_DEBUG("calling readdir-file\n"); - return readdir(dirp); - } -} - -static int closedir_dataset(DIR *dirp) { - if (!dirp) { - errno = EINVAL; - return -1; - } - - DatasetDir* dir = (DatasetDir*)dirp; - - /* Free member list */ - if (dir->member_list) { - for (int i = 0; i < dir->member_count; i++) { - free(dir->member_list[i]); - } - free(dir->member_list); - } - - - - free(dir); - return 0; -} - -int closedir_zos(DIR *dirp) { - if (!dirp) { - return -1; - } - - /* Check if this is a dataset directory */ - DatasetDir* dir = (DatasetDir*)dirp; - if (dir->is_dataset_dir) { - DSIO_LOG_DEBUG("calling closedir-dataset\n"); - return closedir_dataset(dirp); - } else { - DSIO_LOG_DEBUG("calling closedir-file\n"); - return closedir(dirp); - } -} // Made with Bob - Phase 1 System Calls /* Global state */ -GlobalStats g_stats = {0}; -dsio_log_level_t g_log_level = DSIO_LOG_ERROR; + +dsio_log_level_t g_log_level = DSIO_LOG_DEBUG; FILE* g_log_stream = NULL; int g_debug_enabled = 0; @@ -1145,7 +987,7 @@ void set_entry_error(DatasetEntry* entry, dsio_error_t error, const char* messag } DSIO_LOG_ERROR("Error %d: %s", error, entry->error_message); - update_global_stats_error(); + } /* ======================================================================== @@ -1552,101 +1394,6 @@ void log_trace(const char* format, ...) { * STATISTICS IMPLEMENTATION * ======================================================================== */ -void update_read_stats(DatasetEntry* entry, size_t bytes) { - if (!entry || !entry->stats_enabled) return; - - entry->bytes_read += bytes; - entry->read_operations++; - - if (g_stats.stats_enabled) { - g_stats.total_bytes_read += bytes; - g_stats.total_read_operations++; - } -} - -void update_write_stats(DatasetEntry* entry, size_t bytes) { - if (!entry || !entry->stats_enabled) return; - - entry->bytes_written += bytes; - entry->write_operations++; - - if (g_stats.stats_enabled) { - g_stats.total_bytes_written += bytes; - g_stats.total_write_operations++; - } -} - -void update_global_stats_open(void) { - if (g_stats.stats_enabled) { - g_stats.total_open_operations++; - } -} - -void update_global_stats_close(void) { - if (g_stats.stats_enabled) { - g_stats.total_close_operations++; - } -} - -void update_global_stats_error(void) { - if (g_stats.stats_enabled) { - g_stats.total_errors++; - } -} - -int dsio_get_stats(int fd, dsio_stats_t* stats) { - if (!stats) return -1; - - void* dd = GET_DD(fd); - if (!dd || IS_FD(fd)) { - return -1; - } - - DatasetEntry* entry = ENTRY_TO(dd); - - stats->bytes_read = entry->bytes_read; - stats->bytes_written = entry->bytes_written; - stats->read_operations = entry->read_operations; - stats->write_operations = entry->write_operations; - stats->open_operations = 1; /* This fd was opened once */ - stats->close_operations = 0; /* Not closed yet */ - stats->errors = (entry->last_error != DSIO_SUCCESS) ? 1 : 0; - - return 0; -} - -int dsio_reset_stats(int fd) { - void* dd = GET_DD(fd); - if (!dd || IS_FD(fd)) { - return -1; - } - - DatasetEntry* entry = ENTRY_TO(dd); - - entry->bytes_read = 0; - entry->bytes_written = 0; - entry->read_operations = 0; - entry->write_operations = 0; - - return 0; -} - -void dsio_get_global_stats(dsio_stats_t* stats) { - if (!stats) return; - - stats->bytes_read = g_stats.total_bytes_read; - stats->bytes_written = g_stats.total_bytes_written; - stats->read_operations = g_stats.total_read_operations; - stats->write_operations = g_stats.total_write_operations; - stats->open_operations = g_stats.total_open_operations; - stats->close_operations = g_stats.total_close_operations; - stats->errors = g_stats.total_errors; -} - -void dsio_reset_global_stats(void) { - memset(&g_stats, 0, sizeof(g_stats)); - g_stats.stats_enabled = 1; /* Re-enable after reset */ -} /* ======================================================================== * METADATA IMPLEMENTATION (Partial - showing key functions) @@ -1744,7 +1491,6 @@ DatasetEntry* create_entry(FILE* fp, unsigned short file_ccsid) { entry->program_ccsid = 819; /* ASCII */ entry->conversion_state = 1; /* SETCVTON */ entry->last_error = DSIO_SUCCESS; - entry->stats_enabled = 1; entry->metadata_loaded = 0; /* Try to load metadata */ From 6e58f6ceaa6966c6e2dee365b2afecdb9bdc8ede Mon Sep 17 00:00:00 2001 From: sachintu47 Date: Wed, 25 Mar 2026 03:06:16 -0400 Subject: [PATCH 16/28] complete error handling API with DSIO-to-errno mapping --- src/zos-datasetio.c | 120 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 108 insertions(+), 12 deletions(-) diff --git a/src/zos-datasetio.c b/src/zos-datasetio.c index cbe5b975..93cd3785 100644 --- a/src/zos-datasetio.c +++ b/src/zos-datasetio.c @@ -23,6 +23,8 @@ void* descriptor_table[MAX_FDS] = { 0 }; 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 const char DATASET_CHAR[] = "ABCDEFGHIJKLMNOPQRSTUVWYZ$#@"; @@ -135,12 +137,14 @@ int create_dataset_fd(const char* name, unsigned short file_ccsid, int flags) FILE* dd = fopen(name, fopen_mode); if (!dd) { perror("dataset open failed"); + errno = EIO; return -1; } DatasetEntry* dentry = create_entry(dd, file_ccsid); if (!dentry) { fclose(dd); + errno = ENOMEM; return -1; } parse_and_store_name(dentry, name); @@ -189,7 +193,9 @@ int create_dataset_fd(const char* name, unsigned short file_ccsid, int flags) DSIO_LOG_DEBUG("FB optimization: reopening with mode %s\n", reopen_mode); dd = fopen(name, reopen_mode); if (!dd) { + set_entry_error(dentry, DSIO_ERR_OPEN_FAILED, "Failed to reopen dataset in binary mode"); free(dentry); + errno = map_dsio_error_to_errno(DSIO_ERR_OPEN_FAILED); return -1; } dentry->file_ptr = dd; @@ -210,16 +216,20 @@ int create_dataset_fd(const char* name, unsigned short file_ccsid, int flags) /* 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"); fclose(dd); free(dentry); + errno = map_dsio_error_to_errno(DSIO_ERR_ALLOC_FAILED); return -1; } int fd = GET_DUMMY_FD(); if (fd < 0) { DSIO_LOG_DEBUG("create_dataset_fd: ERROR - GET_DUMMY_FD failed, errno=%d\n", errno); + set_entry_error(dentry, DSIO_ERR_INTERNAL_ERROR, "Failed to allocate file descriptor"); fclose(dd); free(dentry); + errno = map_dsio_error_to_errno(DSIO_ERR_INTERNAL_ERROR); return -1; } DSIO_LOG_DEBUG("create_dataset_fd: Assigned dummy fd %d\n", fd); @@ -296,6 +306,10 @@ ssize_t write_dataset(int fd, const void* buf, size_t count) } DatasetEntry* dentry = (DatasetEntry*) (dd); + + /* Clear any previous errors */ + dentry->last_error = DSIO_SUCCESS; + FILE* fp = dentry->file_ptr; DSIO_LOG_DEBUG("In Write, File ccsid: %d\n", dentry->file_ccsid); @@ -338,6 +352,8 @@ ssize_t write_dataset(int fd, const void* buf, size_t count) } 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; @@ -369,6 +385,8 @@ ssize_t write_dataset(int fd, const void* buf, size_t count) } 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; @@ -390,6 +408,9 @@ ssize_t read_dataset(int fd, void* buf, size_t count) } DatasetEntry* dentry = (DatasetEntry*) (dd); + + /* Clear any previous errors */ + dentry->last_error = DSIO_SUCCESS; FILE* fp = dentry->file_ptr; /* If there are pending writes, we should flush them before reading @@ -407,7 +428,11 @@ ssize_t read_dataset(int fd, void* buf, size_t count) dsio_convert_buffer(dentry->rec_buf, dentry->rec_buf_pos, dentry->program_ccsid, dentry->file_ccsid); } - fwrite(dentry->rec_buf, 1, dentry->rec_buf_pos, fp); + 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 write buffer before read"); + errno = map_dsio_error_to_errno(DSIO_ERR_WRITE_FAILED); + return -1; + } dentry->rec_buf_pos = 0; dentry->dirty = 0; } @@ -472,6 +497,11 @@ ssize_t read_dataset(int fd, void* buf, size_t count) size_t rc = fread(dentry->rec_buf, 1, dentry->rec_buf_size, 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; dentry->newline_pending = 1; break; /* Will emit newline on next loop or return if bytes_copied > 0 */ @@ -496,8 +526,16 @@ ssize_t read_dataset(int fd, void* buf, size_t count) int close_dataset(int fd) { void* dd = GET_DD(fd); + if (!dd) { + errno = EBADF; + return -1; + } 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 */ @@ -515,6 +553,7 @@ int close_dataset(int fd) void* conv_result = dsio_convert_buffer(dentry->rec_buf, dentry->rec_buf_pos, dentry->program_ccsid, dentry->file_ccsid); if (conv_result == NULL) { + set_entry_error(dentry, DSIO_ERR_CCSID_CONVERSION, "CCSID conversion failed during close"); fprintf(stderr, "WARNING: CCSID conversion failed during close\n"); /* Continue with close despite conversion error */ } @@ -523,6 +562,7 @@ int close_dataset(int fd) /* Write final record */ size_t rc = fwrite(dentry->rec_buf, 1, dentry->rec_buf_pos, fp); if (rc != dentry->rec_buf_pos) { + set_entry_error(dentry, DSIO_ERR_WRITE_FAILED, "Final record write incomplete during close"); fprintf(stderr, "WARNING: Final record write incomplete (%zu of %zu bytes)\n", rc, dentry->rec_buf_pos); /* Continue with close despite write error */ @@ -530,15 +570,19 @@ int close_dataset(int fd) } int rc = fclose(fp); - if (!rc) { - close(fd); - /* Free record buffer and deallocate DatasetEntry */ - if (dentry->rec_buf) { - free(dentry->rec_buf); - } - free(dentry); - CLEAR_DD(fd); + if (rc != 0) { + set_entry_error(dentry, DSIO_ERR_CLOSE_FAILED, "fclose() failed"); + errno = map_dsio_error_to_errno(DSIO_ERR_CLOSE_FAILED); } + + close(fd); + /* Free record buffer and deallocate DatasetEntry */ + if (dentry->rec_buf) { + free(dentry->rec_buf); + } + free(dentry); + CLEAR_DD(fd); + return rc; } @@ -693,6 +737,9 @@ off_t lseek_dataset(int fd, off_t offset, int whence) { } DatasetEntry* dentry = (DatasetEntry*) dd; + + /* Clear any previous errors */ + dentry->last_error = DSIO_SUCCESS; if (!dentry->file_ptr) { errno = EBADF; DSIO_LOG_DEBUG("lseek_dataset: ERROR - EBADF (NULL file_ptr) for fd %d\n", fd); @@ -715,6 +762,8 @@ off_t lseek_dataset(int fd, off_t offset, int whence) { /* 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; @@ -763,14 +812,16 @@ off_t lseek_dataset(int fd, off_t offset, int whence) { /* Calculate native file position */ long current_native = ftell(fp); if (current_native < 0) { - errno = EIO; + set_entry_error(dentry, DSIO_ERR_FTELL_FAILED, "ftell() failed during lseek"); + errno = map_dsio_error_to_errno(DSIO_ERR_FTELL_FAILED); return (off_t)-1; } long target_native = current_native + (records_to_skip * dentry->reclen) + byte_in_record; if (fseek(fp, target_native, SEEK_SET) != 0) { - errno = EIO; + 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; } @@ -821,6 +872,9 @@ int fstat_dataset(int fd, struct stat *buf) { } 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); @@ -865,7 +919,8 @@ int fstat_dataset(int fd, struct stat *buf) { /* Use dsio_get_size helper to calculate emulated stream size */ ssize_t size = dsio_get_size(fd); if (size < 0) { - /* Error already set by dsio_get_size */ + set_entry_error(dentry, DSIO_ERR_FLDATA_FAILED, "Failed to get dataset size for fstat"); + errno = map_dsio_error_to_errno(DSIO_ERR_FLDATA_FAILED); return -1; } @@ -974,6 +1029,44 @@ void dsio_clear_error(int fd) { 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_NAME: + case DSIO_ERR_INVALID_RECFM: + case DSIO_ERR_INVALID_DSORG: + case DSIO_ERR_INVALID_FD: + return EINVAL; + case DSIO_ERR_NAME_TOO_LONG: + return ENAMETOOLONG; + case DSIO_ERR_OPEN_FAILED: + case DSIO_ERR_READ_FAILED: + case DSIO_ERR_WRITE_FAILED: + case DSIO_ERR_CLOSE_FAILED: + case DSIO_ERR_FLDATA_FAILED: + case DSIO_ERR_FSEEK_FAILED: + case DSIO_ERR_FTELL_FAILED: + return EIO; + case DSIO_ERR_ALLOC_FAILED: + return ENOMEM; + case DSIO_ERR_MEMBER_NOT_FOUND: + case DSIO_ERR_NOT_A_DATASET: + return ENOENT; + case DSIO_ERR_CCSID_CONVERSION: + return EILSEQ; + case DSIO_ERR_BUFFER_OVERFLOW: + case DSIO_ERR_RECORD_TOO_LONG: + return EOVERFLOW; + case DSIO_ERR_UNSUPPORTED_OPERATION: + return ENOTSUP; + case DSIO_ERR_INTERNAL_ERROR: + default: + return EIO; + } +} + void set_entry_error(DatasetEntry* entry, dsio_error_t error, const char* message) { if (!entry) return; @@ -1779,11 +1872,13 @@ static ssize_t calculate_vb_emulated_size(FILE* fp, DatasetEntry* entry) { /* 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"); DSIO_LOG_DEBUG("calculate_vb_emulated_size: RETURN -1 (fgetpos failed) %d\n", 1); return -1; } if (fseek(fp, 0, SEEK_SET) != 0) { + set_entry_error(entry, DSIO_ERR_FSEEK_FAILED, "fseek() failed in calculate_vb_emulated_size"); fsetpos(fp, &saved_pos); DSIO_LOG_DEBUG("calculate_vb_emulated_size: RETURN -1 (fseek failed) %d\n", 1); return -1; @@ -1798,6 +1893,7 @@ static ssize_t calculate_vb_emulated_size(FILE* fp, DatasetEntry* entry) { /* Validate VB record length doesn't exceed buffer */ if (rc > entry->rec_buf_size) { + set_entry_error(entry, DSIO_ERR_RECORD_TOO_LONG, "VB record length exceeds buffer size"); fprintf(stderr, "ERROR: VB record length %zu exceeds buffer size %zu\n", rc, entry->rec_buf_size); fsetpos(fp, &saved_pos); From 2c600aba47be47261cb18554fe054a906fe55320 Mon Sep 17 00:00:00 2001 From: sachintu47 Date: Mon, 30 Mar 2026 05:25:25 -0400 Subject: [PATCH 17/28] fix: resolve dataset I/O hang by clearing EOF on lseek and refactoring FB seeks. Enables runtime debug logging via ZOSLIB_DEBUG and implements size caching. --- include/zos-datasetio.h | 2 +- src/zos-datasetio.c | 67 ++++++++++++++++++++++++++--------------- src/zos.cc | 7 +++-- 3 files changed, 49 insertions(+), 27 deletions(-) diff --git a/include/zos-datasetio.h b/include/zos-datasetio.h index bc2df16a..ee5f699e 100644 --- a/include/zos-datasetio.h +++ b/include/zos-datasetio.h @@ -360,7 +360,7 @@ typedef struct DatasetEntry { /* Enable/disable logging - set to 1 to enable log_* calls */ #ifndef ZOSLIB_DATASET_LOGGING - #define ZOSLIB_DATASET_LOGGING 0 + #define ZOSLIB_DATASET_LOGGING 1 #endif #if ZOSLIB_DATASET_LOGGING diff --git a/src/zos-datasetio.c b/src/zos-datasetio.c index 93cd3785..35e926a2 100644 --- a/src/zos-datasetio.c +++ b/src/zos-datasetio.c @@ -159,15 +159,17 @@ int create_dataset_fd(const char* name, unsigned short file_ccsid, int flags) */ fldata_t fld; if (fldata(dd, NULL, &fld) == 0) { - dentry->recfm = fld.__recfmF ? 2 /* F */ : - fld.__recfmV ? 1 /* V */ : - fld.__recfmU ? 3 /* U */ : 0; + dentry->recfm = fld.__recfmF ? (fld.__recfmB ? DSIO_RECFM_FB : DSIO_RECFM_F) : + fld.__recfmV ? (fld.__recfmB ? DSIO_RECFM_VB : DSIO_RECFM_V) : + fld.__recfmU ? DSIO_RECFM_U : DSIO_RECFM_UNKNOWN; 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 = 2; + dentry->recfm = DSIO_RECFM_FB; dentry->reclen = 80; dentry->blksize = 80; dentry->is_fixed_recfm = 1; @@ -493,9 +495,14 @@ ssize_t read_dataset(int fd, void* buf, size_t count) } /* Slow-path: Read next block/record from dataset */ - if (dentry->eof_reached) break; + 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"); @@ -738,8 +745,9 @@ off_t lseek_dataset(int fd, off_t offset, int whence) { DatasetEntry* dentry = (DatasetEntry*) dd; - /* Clear any previous errors */ + /* 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); @@ -798,27 +806,23 @@ off_t lseek_dataset(int fd, off_t offset, int whence) { /* Handle forward seek */ if (dentry->is_fixed_recfm && dentry->reclen > 0) { /* FB: Optimize by calculating native position directly */ - size_t delta = (size_t)target - dentry->stream_offset; - size_t records_to_skip = delta / (dentry->reclen + 1); - size_t byte_in_record = delta % (dentry->reclen + 1); + 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; - records_to_skip++; - } - - /* Calculate native file position */ - long current_native = ftell(fp); - if (current_native < 0) { - set_entry_error(dentry, DSIO_ERR_FTELL_FAILED, "ftell() failed during lseek"); - errno = map_dsio_error_to_errno(DSIO_ERR_FTELL_FAILED); - return (off_t)-1; + total_records++; + } else { + dentry->newline_pending = 0; } - long target_native = current_native + (records_to_skip * dentry->reclen) + byte_in_record; + 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); @@ -1345,8 +1349,19 @@ void dsio_set_log_stream(FILE* stream) { void dsio_enable_debug(int enable) { g_debug_enabled = enable; - if (enable && g_log_level < DSIO_LOG_DEBUG) { - g_log_level = DSIO_LOG_DEBUG; + if (enable) { + if (g_log_level < DSIO_LOG_DEBUG) { + g_log_level = DSIO_LOG_DEBUG; + } + /* Auto-create temp log file if not already set */ + 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); + } + } } } @@ -1949,9 +1964,9 @@ ssize_t dsio_get_size(int fd) { FILE* fp = entry->file_ptr; - /* For VB datasets, check if we have cached size */ - if (!entry->is_fixed_recfm && entry->vb_size_calculated) { - DSIO_LOG_DEBUG("dsio_get_size: RETURN %zu (cached VB size)\n", entry->vb_cached_size); + /* Check if we already have the cached size */ + if (entry->vb_size_calculated) { + DSIO_LOG_DEBUG("dsio_get_size: RETURN %zu (cached size)\n", entry->vb_cached_size); return (ssize_t)entry->vb_cached_size; } @@ -1989,6 +2004,10 @@ ssize_t dsio_get_size(int fd) { emulated_size = (ssize_t)(native_size + num_records); DSIO_LOG_DEBUG("dsio_get_size: FB calculation - native=%ld, reclen=%zu, num_records=%zu, emulated=%zd\n", native_size, entry->reclen, num_records, emulated_size); + + /* Cache the result */ + entry->vb_cached_size = (size_t)emulated_size; + entry->vb_size_calculated = 1; } else { /* VB/U: Calculate by reading all records (one-time cost) */ DSIO_LOG_DEBUG("dsio_get_size: Calculating VB emulated size...%d\n", 1); diff --git a/src/zos.cc b/src/zos.cc index 3815941a..1f4e1790 100644 --- a/src/zos.cc +++ b/src/zos.cc @@ -2678,9 +2678,12 @@ int __zinit::initialize(const zoslib_config_t &aconfig) { ADD_FD(STDOUT_FILENO); ADD_FD(STDERR_FILENO); - extern int g_debug_enabled; char* env = getenv("ZOSLIB_DEBUG"); - g_debug_enabled = (env && (strcmp(env, "1") == 0 || strcasecmp(env, "ON") == 0)) ? 1 : 0; + if (env && (strcmp(env, "1") == 0 || strcasecmp(env, "ON") == 0)) { + dsio_enable_debug(1); + } else { + g_debug_enabled = 0; + } memcpy(&config, &aconfig, sizeof(config)); __galloc_info = new __Cache; From 770bfb634fd5c64c495327ccca1d4fcc69023896 Mon Sep 17 00:00:00 2001 From: sachintu47 Date: Tue, 31 Mar 2026 01:55:25 -0400 Subject: [PATCH 18/28] clean up unused --- include/zos-datasetio.h | 30 ++------ src/zos-datasetio.c | 158 +++++----------------------------------- src/zos-io.cc | 30 +++----- 3 files changed, 37 insertions(+), 181 deletions(-) diff --git a/include/zos-datasetio.h b/include/zos-datasetio.h index ee5f699e..41e3f0c0 100644 --- a/include/zos-datasetio.h +++ b/include/zos-datasetio.h @@ -118,8 +118,8 @@ typedef enum { typedef struct { dsio_recfm_t recfm; /* Record format */ dsio_dsorg_t dsorg; /* Dataset organization */ - uint16_t lrecl; /* Logical record length */ - uint32_t blksize; /* Block size */ + 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) */ @@ -134,7 +134,7 @@ 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, uint16_t* lrecl); +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); @@ -310,7 +310,6 @@ typedef struct DatasetEntry { /* Original fields */ FILE* file_ptr; unsigned short file_ccsid; - unsigned short process_ccsid; unsigned short program_ccsid; unsigned char conversion_state; @@ -321,8 +320,8 @@ typedef struct DatasetEntry { /* Dataset metadata */ dsio_recfm_t recfm; dsio_dsorg_t dsorg; - uint16_t lrecl; - uint32_t blksize; + size_t reclen; + size_t blksize; /* Dataset name components */ char full_path[DSIO_MAX_DATASET_NAME + 1]; @@ -332,9 +331,6 @@ typedef struct DatasetEntry { int is_pds_member; int readonly; - /* State flags */ - int metadata_loaded; - /* Record buffer for stream emulation */ char* rec_buf; /* internal record I/O buffer */ size_t rec_buf_size; /* allocated size (= blksize or reclen) */ @@ -342,15 +338,14 @@ typedef struct DatasetEntry { 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 */ - size_t reclen; /* logical record length from fldata */ 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 */ - /* VB size caching */ - int vb_size_calculated; /* 1 if VB size has been calculated */ - size_t vb_cached_size; /* Cached emulated size for VB datasets */ + /* Size caching */ + int size_calculated; /* 1 if emulated size has been calculated */ + size_t cached_size; /* Cached emulated size */ } DatasetEntry; @@ -400,9 +395,6 @@ DatasetEntry* create_entry(FILE* fp, unsigned short file_ccsid); /* Free dataset entry */ void free_entry(DatasetEntry* entry); -/* Load metadata from FILE* using fldata() */ -int load_metadata_from_file(DatasetEntry* entry); - /* Parse dataset name and extract components */ int parse_and_store_name(DatasetEntry* entry, const char* dataset_name); @@ -416,11 +408,6 @@ 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 dsio_debug_print(const char* str); -void dsio_debug_printf(const char* format, ...); - -/* CCSID conversion helpers */ void* convert_ebcdic_to_ascii(void* buf, size_t len); void* convert_ascii_to_ebcdic(void* buf, size_t len); void* convert_buffer_ccsid(void* buf, size_t len, uint16_t from, uint16_t to); @@ -432,7 +419,6 @@ int extract_qualifiers(const char* name, char* hlq, char* llq, size_t len); /* Utility macros */ #define ENTRY_TO(entry) ((DatasetEntry*)(entry)) -#define IS_ENTRY(entry) ((entry) && ((DatasetEntry*)(entry))->metadata_loaded >= 0) /* Error message templates */ #define ERR_MSG_INVALID_NAME "Invalid dataset name: %s" diff --git a/src/zos-datasetio.c b/src/zos-datasetio.c index 35e926a2..0c556578 100644 --- a/src/zos-datasetio.c +++ b/src/zos-datasetio.c @@ -29,10 +29,6 @@ static dsio_dsorg_t detect_dsorg_from_fldata(const fldata_t* fdata); static const char DATASET_CHAR[] = "ABCDEFGHIJKLMNOPQRSTUVWYZ$#@"; -//TODO: add to header -void* convertBuffer(void* buf, unsigned short from_ccsid, unsigned short to_ccsid); -DatasetEntry* createDatasetEntry(FILE* dd, unsigned short file_ccsid); - #define DATASET_CHAR_LEN (sizeof(DATASET_CHAR)-1) static char* generate_name(char* tmplate) @@ -672,25 +668,6 @@ int delete_dataset(const char* dataset) return rc; } -void* convertBuffer(void* buf, unsigned short from_ccsid, unsigned short to_ccsid) -{ - if (from_ccsid == 1047 && to_ccsid == 819) { - __e2a_s(buf); - } else if (from_ccsid == 819 && to_ccsid == 1047) { - __a2e_s(buf); - } else { - fprintf(stderr, "from_ccsid: %d to to_ccsid: %d not supported\n", from_ccsid, to_ccsid); - } - return buf; -} - -int zos_fcntl(int fd, int cmd, struct f_cnvrt* req); - -int fcntl_zos(int fd, int cmd, struct f_cnvrt* req) -{ - return zos_fcntl(fd, cmd, req); -} - int zos_fcntl(int fd, int cmd, struct f_cnvrt* req) { //TODO: Only has support for F_CONTROL_CVT for now @@ -717,16 +694,6 @@ int zos_fcntl(int fd, int cmd, struct f_cnvrt* req) return 0; } -DatasetEntry* createDatasetEntry(FILE* dd, unsigned short file_ccsid) -{ - DatasetEntry* dentry = malloc(sizeof(DatasetEntry)); - dentry->file_ptr = dd; - dentry->file_ccsid = file_ccsid; - dentry->program_ccsid = 819; - dentry->conversion_state = SETCVTON; - return dentry; -} - /* ======================================================================== * lseek() - File Positioning @@ -1395,44 +1362,6 @@ void dsio_log(dsio_log_level_t level, const char* format, ...) { extern void __console(const void *p_in, int len_i); -void dsio_debug_print(const char* str) { - if (g_debug_enabled <= 0) return; - - if (g_log_stream == NULL) { - g_log_stream = fopen("zoslib.debug.log", "a"); - if (g_log_stream == NULL) { - __console(str, strlen(str)); - return; - } - } - if (g_log_stream) { - fprintf(g_log_stream, "%s", str); - fflush(g_log_stream); - } -} - -void dsio_debug_printf(const char* format, ...) { - if (g_debug_enabled <= 0) return; - - char buf[1024]; - va_list args; - va_start(args, format); - int len = vsnprintf(buf, sizeof(buf), format, args); - va_end(args); - - if (g_log_stream == NULL) { - g_log_stream = fopen("zoslib.debug.log", "a"); - if (g_log_stream == NULL) { - __console(buf, len); - return; - } - } - if (g_log_stream) { - fprintf(g_log_stream, "%s", buf); - fflush(g_log_stream); - } -} - void log_error(const char* format, ...) { if (DSIO_LOG_ERROR > g_log_level) return; @@ -1508,37 +1437,6 @@ void log_trace(const char* format, ...) { * This would use fldata() to get actual dataset attributes * ======================================================================== */ -int load_metadata_from_file(DatasetEntry* entry) { - if (!entry || !entry->file_ptr) { - return -1; - } - - /* Use fldata() to get file information */ - fldata_t fdata; - if (fldata(entry->file_ptr, NULL, &fdata) != 0) { - set_entry_error(entry, DSIO_ERR_FLDATA_FAILED, "fldata() failed"); - return -1; - } - - /* Extract metadata from fldata */ - entry->recfm = detect_recfm_from_fldata(&fdata); - entry->dsorg = detect_dsorg_from_fldata(&fdata); - entry->lrecl = fdata.__maxreclen; - entry->blksize = fdata.__blksize; - - entry->metadata_loaded = 1; - - - - return 0; -} - -/* Additional functions would be implemented here... */ -/* This is a partial implementation showing the key patterns */ - -// Made with Bob - - /* ======================================================================== * HELPER FUNCTIONS IMPLEMENTATION * ======================================================================== */ @@ -1599,11 +1497,16 @@ DatasetEntry* create_entry(FILE* fp, unsigned short file_ccsid) { entry->program_ccsid = 819; /* ASCII */ entry->conversion_state = 1; /* SETCVTON */ entry->last_error = DSIO_SUCCESS; - entry->metadata_loaded = 0; /* Try to load metadata */ if (fp) { - load_metadata_from_file(entry); + 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; @@ -1662,17 +1565,10 @@ int dsio_get_metadata(int fd, dsio_metadata_t* metadata) { DatasetEntry* entry = ENTRY_TO(dd); - /* Ensure metadata is loaded */ - if (!entry->metadata_loaded) { - if (load_metadata_from_file(entry) != 0) { - return -1; - } - } - /* Copy metadata */ metadata->recfm = entry->recfm; metadata->dsorg = entry->dsorg; - metadata->lrecl = entry->lrecl; + metadata->reclen = entry->reclen; metadata->blksize = entry->blksize; metadata->file_ccsid = entry->file_ccsid; metadata->program_ccsid = entry->program_ccsid; @@ -1695,17 +1591,11 @@ int dsio_get_recfm(int fd, dsio_recfm_t* recfm) { } DatasetEntry* entry = ENTRY_TO(dd); - if (!entry->metadata_loaded) { - if (load_metadata_from_file(entry) != 0) { - return -1; - } - } - *recfm = entry->recfm; return 0; } -int dsio_get_lrecl(int fd, uint16_t* lrecl) { +int dsio_get_lrecl(int fd, size_t* lrecl) { if (!lrecl) return -1; void* dd = GET_DD(fd); @@ -1714,13 +1604,7 @@ int dsio_get_lrecl(int fd, uint16_t* lrecl) { } DatasetEntry* entry = ENTRY_TO(dd); - if (!entry->metadata_loaded) { - if (load_metadata_from_file(entry) != 0) { - return -1; - } - } - - *lrecl = entry->lrecl; + *lrecl = entry->reclen; return 0; } @@ -1733,12 +1617,6 @@ int dsio_get_dsorg(int fd, dsio_dsorg_t* dsorg) { } DatasetEntry* entry = ENTRY_TO(dd); - if (!entry->metadata_loaded) { - if (load_metadata_from_file(entry) != 0) { - return -1; - } - } - *dsorg = entry->dsorg; return 0; } @@ -1859,7 +1737,7 @@ int dsio_is_readonly(int fd) { * ======================================================================== */ int dsio_get_max_reclen(int fd) { - uint16_t lrecl; + size_t lrecl; if (dsio_get_lrecl(fd, &lrecl) != 0) { return -1; } @@ -1965,9 +1843,9 @@ ssize_t dsio_get_size(int fd) { FILE* fp = entry->file_ptr; /* Check if we already have the cached size */ - if (entry->vb_size_calculated) { - DSIO_LOG_DEBUG("dsio_get_size: RETURN %zu (cached size)\n", entry->vb_cached_size); - return (ssize_t)entry->vb_cached_size; + if (entry->size_calculated) { + DSIO_LOG_DEBUG("dsio_get_size: RETURN %zu (cached size)\n", entry->cached_size); + return (ssize_t)entry->cached_size; } /* Calculate emulated stream size from native file size */ @@ -2006,16 +1884,16 @@ ssize_t dsio_get_size(int fd) { native_size, entry->reclen, num_records, emulated_size); /* Cache the result */ - entry->vb_cached_size = (size_t)emulated_size; - entry->vb_size_calculated = 1; + entry->cached_size = (size_t)emulated_size; + entry->size_calculated = 1; } else { /* VB/U: Calculate by reading all records (one-time cost) */ DSIO_LOG_DEBUG("dsio_get_size: Calculating VB emulated size...%d\n", 1); emulated_size = calculate_vb_emulated_size(fp, entry); if (emulated_size >= 0) { /* Cache the result for future calls */ - entry->vb_cached_size = (size_t)emulated_size; - entry->vb_size_calculated = 1; + entry->cached_size = (size_t)emulated_size; + entry->size_calculated = 1; DSIO_LOG_DEBUG("dsio_get_size: VB size calculated and cached: %zd\n", emulated_size); } else { DSIO_LOG_DEBUG("dsio_get_size: VB size calculation failed %d\n", 1); diff --git a/src/zos-io.cc b/src/zos-io.cc index 44ea9706..c3cb1ec5 100644 --- a/src/zos-io.cc +++ b/src/zos-io.cc @@ -1032,10 +1032,8 @@ ssize_t __write_ds_file(int fd, const void *buf, size_t count) { DSIO_LOG_DEBUG("calling write-dataset fd %d\n", fd); return write_dataset(fd, buf, count); } - else if (IS_FD(fd)) { - DSIO_LOG_DEBUG("calling write-file fd %d\n", fd); - return __write_orig(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) { @@ -1043,10 +1041,8 @@ ssize_t __read_ds_file(int fd, void *buf, size_t count) { DSIO_LOG_DEBUG("calling read-dataset fd %d\n", fd); return read_dataset(fd, buf, count); } - else if (IS_FD(fd)) { - DSIO_LOG_DEBUG("calling read-file fd %d\n", fd); - return __read_orig(fd, buf, count); - } + DSIO_LOG_DEBUG("calling read-file fd %d\n", fd); + return __read_orig(fd, buf, count); } int __close(int fd) { @@ -1054,13 +1050,11 @@ int __close(int fd) { DSIO_LOG_DEBUG("calling close-dataset fd %d\n", fd); return close_dataset(fd); } - else if (IS_FD(fd)) { - DSIO_LOG_DEBUG("calling close-file fd %d\n", fd); - int ret = __close_orig(fd); - if (ret >= 0) - __fd_close(fd); - return ret; - } + DSIO_LOG_DEBUG("calling close-file fd %d\n", fd); + int ret = __close_orig(fd); + if (ret >= 0) + __fd_close(fd); + return ret; } off_t __lseek_ds_file(int fd, off_t offset, int whence) { @@ -1068,10 +1062,8 @@ off_t __lseek_ds_file(int fd, off_t offset, int whence) { DSIO_LOG_DEBUG("calling lseek-dataset fd %d offset %d whence %d\n", fd, offset, whence); return lseek_dataset(fd, offset, whence); } - else if (IS_FD(fd)) { - DSIO_LOG_DEBUG("calling lseek-file fd %d offset %d whence %d\n", fd, offset, whence); - return __lseek_orig(fd, offset, whence); - } + DSIO_LOG_DEBUG("calling lseek-file fd %d offset %d whence %d\n", fd, offset, whence); + return __lseek_orig(fd, offset, whence); } int __stat_ds_file(const char *pathname, struct stat *statbuf) { From 1175ca29ef4bfce8b3b13b9e9dcc283221ee5ba7 Mon Sep 17 00:00:00 2001 From: sachintu47 Date: Tue, 31 Mar 2026 05:33:52 -0400 Subject: [PATCH 19/28] feat: add dynamic and compile-time toggles for dataset support - Added ZOSLIB_ENABLE_DATASETIO compilation flag (defaults to 0). - Added __DATASET_SUPPORT environment variable for runtime control. - Consolidated DatasetEntry struct and cleaned up redundant functions. - Defaulted dataset support to disabled at both compile and runtime. --- include/zos-base.h | 21 ++++++++++++++++++ include/zos-datasetio.h | 44 +++++++++++++++++++++++++++++--------- src/zos-datasetio.c | 6 +++--- src/zos-io.cc | 47 +++++++++++++++++++++++++++++++++-------- src/zos.cc | 31 ++++++++++++++++++++++++++- 5 files changed, 126 insertions(+), 23 deletions(-) diff --git a/include/zos-base.h b/include/zos-base.h index 8030d523..b4c58b66 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 "__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 index 41e3f0c0..a4b11e79 100644 --- a/include/zos-datasetio.h +++ b/include/zos-datasetio.h @@ -1,6 +1,16 @@ #ifndef __DATASET_IO__ #define __DATASET_IO__ 1 +#ifndef ZOSLIB_ENABLE_DATASETIO + #define ZOSLIB_ENABLE_DATASETIO 0 +#endif /* ZOSLIB_ENABLE_DATASETIO */ + +#if defined(__cplusplus) +extern "C" { +#endif /* ZOSLIB_ENABLE_DATASETIO */ + +#if ZOSLIB_ENABLE_DATASETIO + #include #include #include @@ -8,22 +18,18 @@ #include #include -#if defined(__cplusplus) -extern "C" { -#endif - #ifndef __ssize_t #define __ssize_t 1 typedef signed long ssize_t; -#endif +#endif /* ZOSLIB_ENABLE_DATASETIO */ #ifndef __size_t #define __size_t 1 typedef unsigned long size_t; -#endif +#endif /* ZOSLIB_ENABLE_DATASETIO */ #ifndef __mode_t #define __mode_t 1 typedef int mode_t ; -#endif +#endif /* ZOSLIB_ENABLE_DATASETIO */ int open_dataset(const char* name, int flags, mode_t mode); int close_dataset(int fd); @@ -356,7 +362,7 @@ typedef struct DatasetEntry { /* Enable/disable logging - set to 1 to enable log_* calls */ #ifndef ZOSLIB_DATASET_LOGGING #define ZOSLIB_DATASET_LOGGING 1 -#endif +#endif /* ZOSLIB_ENABLE_DATASETIO */ #if ZOSLIB_DATASET_LOGGING #define DSIO_LOG_ERROR(fmt, ...) do { if (g_debug_enabled && g_log_level >= DSIO_LOG_ERROR) log_error(fmt, ##__VA_ARGS__); } while(0) @@ -364,13 +370,13 @@ typedef struct DatasetEntry { #define DSIO_LOG_INFO(fmt, ...) do { if (g_debug_enabled && g_log_level >= DSIO_LOG_INFO) log_info(fmt, ##__VA_ARGS__); } while(0) #define DSIO_LOG_DEBUG(fmt, ...) do { if (g_debug_enabled && g_log_level >= DSIO_LOG_DEBUG) log_debug(fmt, ##__VA_ARGS__); } while(0) #define DSIO_LOG_TRACE(fmt, ...) do { if (g_debug_enabled && g_log_level >= DSIO_LOG_TRACE) log_trace(fmt, ##__VA_ARGS__); } while(0) -#else +#else /* ZOSLIB_ENABLE_DATASETIO == 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 +#endif /* ZOSLIB_ENABLE_DATASETIO */ /* ======================================================================== * ENHANCED INTERNAL STRUCTURES @@ -431,6 +437,24 @@ int extract_qualifiers(const char* name, char* hlq, char* llq, size_t len); #define ERR_MSG_FLDATA_FAILED "fldata() failed for dataset" #define ERR_MSG_CCSID_CONV "CCSID conversion failed: %d -> %d" +#else /* ZOSLIB_ENABLE_DATASETIO == 0 */ + +#define IS_DATASET(name) (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) + +#define ADD_FD(fd) ((void)0) +#define ADD_DD(fd,dd) ((void)0) +#define GET_DD(fd) (NULL) +#define CLEAR_DD(fd) ((void)0) +#define IS_FD(fd) (1) +#define IS_DD(fd) (0) + +#endif /* ZOSLIB_ENABLE_DATASETIO */ + #if defined(__cplusplus) } #endif diff --git a/src/zos-datasetio.c b/src/zos-datasetio.c index 0c556578..d4313e43 100644 --- a/src/zos-datasetio.c +++ b/src/zos-datasetio.c @@ -20,6 +20,8 @@ #include "zos-datasetio.h" +#if ZOSLIB_ENABLE_DATASETIO + void* descriptor_table[MAX_FDS] = { 0 }; static dsio_recfm_t detect_recfm_from_fldata(const fldata_t* fdata); @@ -924,7 +926,6 @@ int stat_dataset(const char *pathname, struct stat *statbuf) { } -// Made with Bob - Phase 1 System Calls /* Global state */ @@ -1963,6 +1964,5 @@ int dsio_get_ccsid_config(int fd, dsio_ccsid_config_t* config) { return 0; } +#endif /* ZOSLIB_ENABLE_DATASETIO */ - -// Made with Bob diff --git a/src/zos-io.cc b/src/zos-io.cc index c3cb1ec5..849b7003 100644 --- a/src/zos-io.cc +++ b/src/zos-io.cc @@ -38,6 +38,12 @@ bool __gLogMemoryAll = false; bool __gLogMemoryWarning = false; bool __gLogMemoryShowPid = true; FILE *fp_memprintf = nullptr; + +#if ZOSLIB_ENABLE_DATASETIO +static bool is_dataset_supported(const char* name) { + return IS_DATASET(name) && __get_instance()->get_ds_support_mode() != DS_SUPPORT_NO; +} +#endif } #ifdef __cplusplus @@ -915,10 +921,13 @@ int __open_ds_file(const char *filename, int opts, ...) { int perms = va_arg(ap, int); DSIO_LOG_DEBUG("calling __open_ds_file file %s\n", filename); - if (IS_DATASET(filename)) { +#if ZOSLIB_ENABLE_DATASETIO + if (is_dataset_supported(filename)) { fd = open_dataset(filename, opts, perms); DSIO_LOG_DEBUG("calling open-dataset fd %d\n", fd); - } else { + } else +#endif + { fd = __open_ascii(filename, opts, perms); if (fd >= 0) { ADD_FD(fd); @@ -973,10 +982,13 @@ FILE *__fopen_ascii(const char *filename, const char *mode) { } FILE *__fopen_ds_file(const char *filename, const char *mode) { - if (IS_DATASET(filename)) { +#if ZOSLIB_ENABLE_DATASETIO + if (is_dataset_supported(filename)) { return __fopen_orig(filename, mode); } - else { + else +#endif + { return __fopen_ascii(filename, mode); } } @@ -1014,10 +1026,13 @@ int __mkstemp_ascii(char * tmpl) { int __mkstemp_ds_file(char * tmpl) { int fd; - if (IS_DATASET(tmpl)) { +#if ZOSLIB_ENABLE_DATASETIO + if (is_dataset_supported(tmpl)) { DSIO_LOG_DEBUG("calling mkstemp-dataset\n"); fd = mkstemp_dataset(tmpl); - } else { + } else +#endif + { DSIO_LOG_DEBUG("calling mkstemp-file\n"); fd = __mkstemp_ascii(tmpl); if (fd >= 0) { @@ -1028,28 +1043,34 @@ int __mkstemp_ds_file(char * tmpl) { } ssize_t __write_ds_file(int fd, const void *buf, size_t count) { +#if ZOSLIB_ENABLE_DATASETIO if (IS_DD(fd)) { DSIO_LOG_DEBUG("calling write-dataset fd %d\n", fd); return write_dataset(fd, buf, count); } +#endif 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 ZOSLIB_ENABLE_DATASETIO if (IS_DD(fd)) { DSIO_LOG_DEBUG("calling read-dataset fd %d\n", fd); return read_dataset(fd, buf, count); } +#endif DSIO_LOG_DEBUG("calling read-file fd %d\n", fd); return __read_orig(fd, buf, count); } int __close(int fd) { +#if ZOSLIB_ENABLE_DATASETIO if (IS_DD(fd)) { DSIO_LOG_DEBUG("calling close-dataset fd %d\n", fd); return close_dataset(fd); } +#endif DSIO_LOG_DEBUG("calling close-file fd %d\n", fd); int ret = __close_orig(fd); if (ret >= 0) @@ -1058,29 +1079,37 @@ int __close(int fd) { } off_t __lseek_ds_file(int fd, off_t offset, int whence) { +#if ZOSLIB_ENABLE_DATASETIO if (IS_DD(fd)) { DSIO_LOG_DEBUG("calling lseek-dataset fd %d offset %d whence %d\n", fd, offset, whence); return lseek_dataset(fd, offset, whence); } +#endif DSIO_LOG_DEBUG("calling lseek-file fd %d offset %d whence %d\n", fd, offset, whence); return __lseek_orig(fd, offset, whence); } int __stat_ds_file(const char *pathname, struct stat *statbuf) { - if (IS_DATASET(pathname)) { +#if ZOSLIB_ENABLE_DATASETIO + if (is_dataset_supported(pathname)) { DSIO_LOG_DEBUG("calling stat-dataset path %s\n", pathname); return stat_dataset(pathname, statbuf); - } else { + } else +#endif + { 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 ZOSLIB_ENABLE_DATASETIO if (IS_DD(fd)) { DSIO_LOG_DEBUG("calling fstat-dataset fd %d\n", fd); return fstat_dataset(fd, statbuf); - } else { + } else +#endif + { DSIO_LOG_DEBUG("calling fstat-file fd %d\n", fd); return __fstat_orig(fd, statbuf); } diff --git a/src/zos.cc b/src/zos.cc index 1f4e1790..6cd0778a 100644 --- a/src/zos.cc +++ b/src/zos.cc @@ -2674,18 +2674,34 @@ static void setProcessEnvars() { } int __zinit::initialize(const zoslib_config_t &aconfig) { +#if ZOSLIB_ENABLE_DATASETIO ADD_FD(STDIN_FILENO); ADD_FD(STDOUT_FILENO); ADD_FD(STDERR_FILENO); +#endif +#if ZOSLIB_ENABLE_DATASETIO char* env = getenv("ZOSLIB_DEBUG"); if (env && (strcmp(env, "1") == 0 || strcasecmp(env, "ON") == 0)) { dsio_enable_debug(1); } else { g_debug_enabled = 0; } +#endif memcpy(&config, &aconfig, sizeof(config)); + +#if ZOSLIB_ENABLE_DATASETIO + 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 { + ds_support_mode = DS_SUPPORT_NO; + } +#else + ds_support_mode = DS_SUPPORT_NO; +#endif + __galloc_info = new __Cache; mode = __ae_thread_swapmode(__AE_ASCII_MODE); @@ -2817,7 +2833,16 @@ int __zinit::setEnvarHelpMap() { "memory statistics summary, and any error messages are " "always displayed if logging of memory diagnostic " "messages is enabled")); - + +#if ZOSLIB_ENABLE_DATASETIO + 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")); +#endif return __update_envar_settings(NULL); } @@ -3058,6 +3083,10 @@ 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; +#if ZOSLIB_ENABLE_DATASETIO + config->DATASET_SUPPORT_ENVAR = DATASET_SUPPORT_ENVAR_DEFAULT; +#endif } extern "C" void init_zoslib(const zoslib_config_t config) { From 03edcb003ce62dca36de038730b03866c8bc89bb Mon Sep 17 00:00:00 2001 From: sachintu47 Date: Tue, 31 Mar 2026 06:27:18 -0400 Subject: [PATCH 20/28] build: add ZOSLIB_ENABLE_DATASETIO configuration option - Added ZOSLIB_ENABLE_DATASETIO option to CMakeLists.txt (default OFF). - Added -d flag to build.sh to enable dataset I/O support during build. --- CMakeLists.txt | 5 +++++ build.sh | 9 +++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ec5674d1..61b346a1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,6 +55,11 @@ if(ZOSLIB_QUICK_STARTUP) ZOSLIB_QUICK_STARTUP) endif() +if(ZOSLIB_ENABLE_DATASETIO) + list(APPEND zoslib_defines + ZOSLIB_ENABLE_DATASETIO=1) +endif() + 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 From 1b19b8013b59a11c9016fe65805fc4ab08f64c59 Mon Sep 17 00:00:00 2001 From: sachintu47 Date: Tue, 31 Mar 2026 20:47:51 +0530 Subject: [PATCH 21/28] fix(datasetio): avoid fseek(SEEK_END) on VB/U record-mode files in fstat/dsio_get_size Harden error paths: fix zos_fcntl null-deref, close_dataset write-mode check, generate_name errno, and reduce noisy debug logging. --- include/unistd.h | 1 - include/zos-base.h | 2 +- include/zos-datasetio.h | 20 +-- src/zos-datasetio.c | 344 +++++++++++++++++----------------------- src/zos-io.cc | 8 +- src/zos.cc | 2 + 6 files changed, 163 insertions(+), 214 deletions(-) diff --git a/include/unistd.h b/include/unistd.h index 7a655d58..5e58f16e 100644 --- a/include/unistd.h +++ b/include/unistd.h @@ -56,7 +56,6 @@ 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"); diff --git a/include/zos-base.h b/include/zos-base.h index b4c58b66..226faf9a 100644 --- a/include/zos-base.h +++ b/include/zos-base.h @@ -66,7 +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 "__DATASET_SUPPORT" +#define DATASET_SUPPORT_ENVAR_DEFAULT "ZOSLIB_DATASET_SUPPORT" typedef enum { __NO_TAG_READ_DEFAULT = 0, diff --git a/include/zos-datasetio.h b/include/zos-datasetio.h index a4b11e79..1110ad40 100644 --- a/include/zos-datasetio.h +++ b/include/zos-datasetio.h @@ -7,7 +7,7 @@ #if defined(__cplusplus) extern "C" { -#endif /* ZOSLIB_ENABLE_DATASETIO */ +#endif #if ZOSLIB_ENABLE_DATASETIO @@ -21,15 +21,15 @@ extern "C" { #ifndef __ssize_t #define __ssize_t 1 typedef signed long ssize_t; -#endif /* ZOSLIB_ENABLE_DATASETIO */ +#endif #ifndef __size_t #define __size_t 1 typedef unsigned long size_t; -#endif /* ZOSLIB_ENABLE_DATASETIO */ +#endif #ifndef __mode_t #define __mode_t 1 typedef int mode_t ; -#endif /* ZOSLIB_ENABLE_DATASETIO */ +#endif int open_dataset(const char* name, int flags, mode_t mode); int close_dataset(int fd); @@ -296,10 +296,11 @@ int dsio_flush(int fd); #define MAX_FDS 1024 #define INV_ADDR_BIT (0x0000000080000000ULL) -#define ADD_FD(fd) (descriptor_table[(fd)] = ((void*)(((unsigned long long) fd) | INV_ADDR_BIT))) -#define ADD_DD(fd,dd) (descriptor_table[(fd)] = (dd)) -#define GET_DD(fd) (descriptor_table[(fd)]) -#define CLEAR_DD(fd) (descriptor_table[(fd)] = 0) +/* Bounds-checked accessors to prevent out-of-bounds when fd >= MAX_FDS */ +#define ADD_FD(fd) do { if ((fd) >= 0 && (fd) < MAX_FDS) descriptor_table[(fd)] = ((void*)(((unsigned long long)(fd)) | INV_ADDR_BIT)); } while(0) +#define ADD_DD(fd,dd) do { if ((fd) >= 0 && (fd) < MAX_FDS) descriptor_table[(fd)] = (dd); } while(0) +#define GET_DD(fd) (((fd) >= 0 && (fd) < MAX_FDS) ? descriptor_table[(fd)] : NULL) +#define CLEAR_DD(fd) do { if ((fd) >= 0 && (fd) < MAX_FDS) descriptor_table[(fd)] = 0; } while(0) /* Validate descriptor slot is within bounds and occupied */ #define IS_VALID_SLOT(slot) ((slot) >= 0 && (slot) < MAX_FDS && descriptor_table[(slot)] != NULL) @@ -362,7 +363,7 @@ typedef struct DatasetEntry { /* Enable/disable logging - set to 1 to enable log_* calls */ #ifndef ZOSLIB_DATASET_LOGGING #define ZOSLIB_DATASET_LOGGING 1 -#endif /* ZOSLIB_ENABLE_DATASETIO */ +#endif #if ZOSLIB_DATASET_LOGGING #define DSIO_LOG_ERROR(fmt, ...) do { if (g_debug_enabled && g_log_level >= DSIO_LOG_ERROR) log_error(fmt, ##__VA_ARGS__); } while(0) @@ -416,7 +417,6 @@ 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); -void* convert_buffer_ccsid(void* buf, size_t len, uint16_t from, uint16_t to); /* Dataset name validation */ int validate_dataset_name_internal(const char* name); diff --git a/src/zos-datasetio.c b/src/zos-datasetio.c index d4313e43..ff30286f 100644 --- a/src/zos-datasetio.c +++ b/src/zos-datasetio.c @@ -22,6 +22,12 @@ #if ZOSLIB_ENABLE_DATASETIO +/* __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"); + void* descriptor_table[MAX_FDS] = { 0 }; static dsio_recfm_t detect_recfm_from_fldata(const fldata_t* fdata); @@ -53,7 +59,8 @@ static char* generate_name(char* tmplate) --end; } if (end - start + 1 < 3) { /* at least 3 X's required in the tmplate */ - return NULL; /* TBD: need to set proper errno's */ + errno = EINVAL; + return NULL; } gettimeofday(&tv, NULL); @@ -67,42 +74,6 @@ static char* generate_name(char* tmplate) return tmplate; } -int mkstemp_dataset(char* tmplate) -{ - void* dd; - int fd = -1; - - /* 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; - } - - /* Current just have 'dd' be the FILE pointer - may want something more substantial */ - dd = fopen(tmplate, "w"); - if (!dd) { - return -1; - } - - /* Use enhanced entry creation */ - DatasetEntry* dentry = create_entry(dd, 1047); - if (!dentry) { - fclose(dd); - return -1; - } - - parse_and_store_name(dentry, tmplate); - - - fd = GET_DUMMY_FD(); - ADD_DD(fd, dentry); - - - - return fd; -} - /* * create_dataset_fd - Open a dataset, create a DatasetEntry, register it, and return an fd. * @@ -120,12 +91,13 @@ int create_dataset_fd(const char* name, unsigned short file_ccsid, int flags) * 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 (flags & O_RDONLY) { + if (accmode == O_RDONLY) { fopen_mode = "rb,type=record,recfm=+"; - } else if (flags & O_WRONLY) { + } else if (accmode == O_WRONLY) { fopen_mode = (flags & O_APPEND) ? "ab,type=record,recfm=+,noseek" : "wb,type=record,recfm=+,noseek"; - } else if (flags & O_RDWR) { + } 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=+"; @@ -157,9 +129,7 @@ int create_dataset_fd(const char* name, unsigned short file_ccsid, int flags) */ fldata_t fld; if (fldata(dd, NULL, &fld) == 0) { - dentry->recfm = fld.__recfmF ? (fld.__recfmB ? DSIO_RECFM_FB : DSIO_RECFM_F) : - fld.__recfmV ? (fld.__recfmB ? DSIO_RECFM_VB : DSIO_RECFM_V) : - fld.__recfmU ? DSIO_RECFM_U : DSIO_RECFM_UNKNOWN; + 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; @@ -181,11 +151,11 @@ int create_dataset_fd(const char* name, unsigned short file_ccsid, int flags) fclose(dd); dd = NULL; const char* reopen_mode; - if (flags & O_RDONLY) { + if (accmode == O_RDONLY) { reopen_mode = "rb,recfm=+"; - } else if (flags & O_WRONLY) { + } else if (accmode == O_WRONLY) { reopen_mode = (flags & O_APPEND) ? "ab,recfm=+,noseek" : "wb,recfm=+,noseek"; - } else if (flags & O_RDWR) { + } else if (accmode == O_RDWR) { reopen_mode = (flags & O_APPEND) ? "ab+,recfm=+" : "rb+,recfm=+"; } else { reopen_mode = "rb,recfm=+"; @@ -237,6 +207,21 @@ int create_dataset_fd(const char* name, unsigned short file_ccsid, int flags) 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: @@ -256,6 +241,7 @@ int open_dataset(const char* name, int flags, mode_t mode) * 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)) { @@ -264,17 +250,15 @@ int open_dataset(const char* name, int flags, mode_t mode) return -1; } - if (!(flags & (O_RDONLY | O_WRONLY | O_RDWR))) { - DSIO_LOG_DEBUG("open_dataset: Missing access mode in flags %d\n", flags); + /* 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; } - if ((flags & O_LARGEFILE) || (flags & O_NONBLOCK)) { - DSIO_LOG_DEBUG("open_dataset: Unsupported flags in %d\n", flags); - errno = EACCES; - 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) { @@ -312,9 +296,8 @@ ssize_t write_dataset(int fd, const void* buf, size_t count) FILE* fp = dentry->file_ptr; - DSIO_LOG_DEBUG("In Write, File ccsid: %d\n", dentry->file_ccsid); - DSIO_LOG_DEBUG("In Write, Program ccsid: %d\n", dentry->program_ccsid); - DSIO_LOG_DEBUG("In Write, Conversion state %d\n", dentry->conversion_state); + 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; @@ -364,7 +347,6 @@ ssize_t write_dataset(int fd, const void* buf, size_t count) pos += to_copy + 1; /* skip the data and the newline */ total_written += to_copy + 1; - dentry->dirty = 1; } else { /* No newline found in this record's space - copy what we can */ size_t to_copy = scan_len; @@ -396,6 +378,10 @@ ssize_t write_dataset(int fd, const void* buf, size_t count) } 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; } @@ -413,37 +399,30 @@ ssize_t read_dataset(int fd, void* buf, size_t count) dentry->last_error = DSIO_SUCCESS; FILE* fp = dentry->file_ptr; - /* If there are pending writes, we should flush them before reading - * However, for FB binary mode, the C runtime should handle it if it was opened with ab+ or rb+ - * But we are bypassing C runtime buffering with _IONBF and doing our own buffering in rec_buf. + /* 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 && dentry->rec_buf_pos > 0) { - /* Flush pending write buffer */ - if (dentry->is_fixed_recfm) { - while (dentry->rec_buf_pos < dentry->reclen) { - dentry->rec_buf[dentry->rec_buf_pos++] = ' '; + if (dentry->dirty) { + if (dentry->rec_buf_pos > 0) { + /* Flush pending write buffer */ + if (dentry->is_fixed_recfm) { + while (dentry->rec_buf_pos < dentry->reclen) { + dentry->rec_buf[dentry->rec_buf_pos++] = ' '; + } + } + 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 flush write buffer before read"); + errno = map_dsio_error_to_errno(DSIO_ERR_WRITE_FAILED); + return -1; } } - 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 flush write buffer before read"); - errno = map_dsio_error_to_errno(DSIO_ERR_WRITE_FAILED); - return -1; - } + /* Reset buffer state for switch to read mode */ dentry->rec_buf_pos = 0; - dentry->dirty = 0; - } - - /* If the buffer currently contains data that was read, we don't clear it yet. - * If it was used for writing (and just flushed), dentry->rec_buf_pos is 0. - * If we are switching from write to read, we might need to reset rec_buf_len. - */ - if (dentry->dirty) { dentry->rec_buf_len = 0; - dentry->rec_buf_pos = 0; dentry->dirty = 0; } @@ -508,8 +487,13 @@ ssize_t read_dataset(int fd, void* buf, size_t count) return bytes_copied > 0 ? (ssize_t)bytes_copied : -1; } dentry->eof_reached = 1; - dentry->newline_pending = 1; - break; /* Will emit newline on next loop or return if bytes_copied > 0 */ + /* Only set newline_pending if we actually read data from this dataset. + * Without this guard, an empty dataset would return '\n' instead of 0. + */ + if (bytes_copied > 0) { + dentry->newline_pending = 1; + } + break; /* Will emit newline on next call or return if bytes_copied > 0 */ } /* Convert CCSID of the newly read data in-place */ @@ -521,10 +505,6 @@ ssize_t read_dataset(int fd, void* buf, size_t count) dentry->rec_buf_pos = 0; } - if (bytes_copied > 0) { - - } - return (bytes_copied == 0 && count > 0) ? 0 : (ssize_t) bytes_copied; } @@ -545,7 +525,7 @@ int close_dataset(int fd) /* Flush any partial record remaining in the write buffer */ if (dentry->dirty && dentry->rec_buf_pos > 0 && - (dentry->open_flags & (O_WRONLY | O_RDWR))) { + ((dentry->open_flags & O_ACCMODE) != O_RDONLY)) { /* For FB datasets, pad final record to reclen with spaces */ if (dentry->is_fixed_recfm) { while (dentry->rec_buf_pos < dentry->reclen) { @@ -580,13 +560,17 @@ int close_dataset(int fd) errno = map_dsio_error_to_errno(DSIO_ERR_CLOSE_FAILED); } - close(fd); /* 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; } @@ -597,7 +581,10 @@ char* temp_dataset_name(char* result) char temp[L_tmpnam+1]; setenv("__POSIX_TMPNAM", "NO", 1); tmpnam(temp); - setenv("__POSIX_TMPNAM", orig, 1); + if (orig) + setenv("__POSIX_TMPNAM", orig, 1); + else + unsetenv("__POSIX_TMPNAM"); sprintf(result, "//'%s'", temp); return result; } @@ -607,7 +594,10 @@ char* temp_file_name(char* result) char* orig = getenv("__POSIX_TMPNAM"); setenv("__POSIX_TMPNAM", "YES", 1); tmpnam(result); - setenv("__POSIX_TMPNAM", orig, 1); + if (orig) + setenv("__POSIX_TMPNAM", orig, 1); + else + unsetenv("__POSIX_TMPNAM"); return result; } @@ -672,16 +662,17 @@ int delete_dataset(const char* dataset) int zos_fcntl(int fd, int cmd, struct f_cnvrt* req) { - //TODO: Only has support for F_CONTROL_CVT for now + /* Only F_CONTROL_CVT is supported for dataset fds */ if (cmd == F_CONTROL_CVT) { - if (!req) + if (!req || fd < 0) return -1; - if (fd < 0) + if (!IS_DD(fd)) { + errno = EBADF; return -1; + } - void* dd = GET_DD(fd); - DatasetEntry* dentry = (DatasetEntry*) (dd); + DatasetEntry* dentry = (DatasetEntry*) GET_DD(fd); if (req->cvtcmd == QUERYCVT) { req->pccsid = dentry->program_ccsid; @@ -702,7 +693,7 @@ int zos_fcntl(int fd, int cmd, struct f_cnvrt* req) * ======================================================================== */ 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); + 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); @@ -816,8 +807,8 @@ off_t lseek_dataset(int fd, off_t offset, int whence) { } } - DSIO_LOG_DEBUG("lseek_dataset: fd=%d target=%lld final=%zu\n", - fd, (long long)target, dentry->stream_offset); + 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; } @@ -889,17 +880,21 @@ int fstat_dataset(int fd, struct stat *buf) { buf->st_mtime = now; buf->st_ctime = now; - /* Use dsio_get_size helper to calculate emulated stream size */ + /* 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) { - set_entry_error(dentry, DSIO_ERR_FLDATA_FAILED, "Failed to get dataset size for fstat"); - errno = map_dsio_error_to_errno(DSIO_ERR_FLDATA_FAILED); - return -1; + 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); + DSIO_LOG_DEBUG("fstat_dataset: fd=%d size=%lld\n", fd, (long long)buf->st_size); return 0; } @@ -1087,6 +1082,8 @@ const char* dsio_recfm_to_string(dsio_recfm_t recfm) { 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"; } } @@ -1357,7 +1354,6 @@ void dsio_log(dsio_log_level_t level, const char* format, ...) { vfprintf(stream, format, args); va_end(args); - fprintf(stream, "\n"); fflush(stream); } @@ -1371,7 +1367,6 @@ void log_error(const char* format, ...) { FILE* stream = g_log_stream ? g_log_stream : stderr; fprintf(stream, "[ERROR] "); vfprintf(stream, format, args); - fprintf(stream, "\n"); fflush(stream); va_end(args); } @@ -1384,7 +1379,6 @@ void log_warn(const char* format, ...) { FILE* stream = g_log_stream ? g_log_stream : stderr; fprintf(stream, "[WARN ] "); vfprintf(stream, format, args); - fprintf(stream, "\n"); fflush(stream); va_end(args); } @@ -1397,7 +1391,6 @@ void log_info(const char* format, ...) { FILE* stream = g_log_stream ? g_log_stream : stderr; fprintf(stream, "[INFO ] "); vfprintf(stream, format, args); - fprintf(stream, "\n"); fflush(stream); va_end(args); } @@ -1410,7 +1403,6 @@ void log_debug(const char* format, ...) { FILE* stream = g_log_stream ? g_log_stream : stderr; fprintf(stream, "[DEBUG] "); vfprintf(stream, format, args); - fprintf(stream, "\n"); fflush(stream); va_end(args); } @@ -1423,7 +1415,6 @@ void log_trace(const char* format, ...) { FILE* stream = g_log_stream ? g_log_stream : stderr; fprintf(stream, "[TRACE] "); vfprintf(stream, format, args); - fprintf(stream, "\n"); fflush(stream); va_end(args); } @@ -1760,149 +1751,106 @@ static ssize_t calculate_vb_emulated_size(FILE* fp, DatasetEntry* entry) { size_t total_size = 0; size_t rec_count = 0; - DSIO_LOG_DEBUG("calculate_vb_emulated_size: ENTER rec_buf_size=%zu\n", entry->rec_buf_size); - 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"); - DSIO_LOG_DEBUG("calculate_vb_emulated_size: RETURN -1 (fgetpos failed) %d\n", 1); return -1; } - if (fseek(fp, 0, SEEK_SET) != 0) { - set_entry_error(entry, DSIO_ERR_FSEEK_FAILED, "fseek() failed in calculate_vb_emulated_size"); + /* 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); - DSIO_LOG_DEBUG("calculate_vb_emulated_size: RETURN -1 (fseek failed) %d\n", 1); return -1; } - /* Read each record */ + /* Read each record, stripping trailing spaces, counting bytes+newline */ while (1) { size_t rc = fread(entry->rec_buf, 1, entry->rec_buf_size, fp); if (rc == 0) break; /* EOF */ - DSIO_LOG_DEBUG("calculate_vb_emulated_size: Read record #%zu, raw_length=%zu\n", rec_count + 1, rc); - - /* Validate VB record length doesn't exceed buffer */ - if (rc > entry->rec_buf_size) { - set_entry_error(entry, DSIO_ERR_RECORD_TOO_LONG, "VB record length exceeds buffer size"); - fprintf(stderr, "ERROR: VB record length %zu exceeds buffer size %zu\n", - rc, entry->rec_buf_size); - fsetpos(fp, &saved_pos); - errno = EFBIG; - DSIO_LOG_DEBUG("calculate_vb_emulated_size: RETURN -1 (record too large) %d\n", 1); - return -1; - } - - size_t original_rc = rc; /* Strip trailing spaces */ while (rc > 0 && entry->rec_buf[rc - 1] == ' ') { rc--; } - - DSIO_LOG_DEBUG("calculate_vb_emulated_size: Record #%zu stripped from %zu to %zu bytes\n", - rec_count + 1, original_rc, rc); - total_size += rc + 1; /* +1 for newline */ rec_count++; - - DSIO_LOG_DEBUG("calculate_vb_emulated_size: Running total=%zu bytes, rec_count=%zu\n", - total_size, rec_count); } /* Restore position */ if (fsetpos(fp, &saved_pos) != 0) { - DSIO_LOG_DEBUG("WARNING: Failed to restore file position after size calculation %d\n", 1); + DSIO_LOG_DEBUG("calculate_vb_emulated_size: WARNING - failed to restore file position\n"); } } - DSIO_LOG_DEBUG("calculate_vb_emulated_size: RETURN %zu bytes (%zu records) for %s dataset\n", - total_size, rec_count, entry->is_fixed_recfm ? "FB" : "VB"); + 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) { - DSIO_LOG_DEBUG("dsio_get_size: ENTER fd=%d\n", fd); - void* dd = GET_DD(fd); if (!dd || IS_FD(fd)) { errno = EBADF; - DSIO_LOG_DEBUG("dsio_get_size: RETURN -1 (invalid fd) %d\n", 1); return -1; } DatasetEntry* entry = ENTRY_TO(dd); if (!entry->file_ptr) { errno = EBADF; - DSIO_LOG_DEBUG("dsio_get_size: RETURN -1 (no file_ptr) %d\n", 1); return -1; } - FILE* fp = entry->file_ptr; - - /* Check if we already have the cached size */ + /* Return cached size if available */ if (entry->size_calculated) { - DSIO_LOG_DEBUG("dsio_get_size: RETURN %zu (cached size)\n", entry->cached_size); return (ssize_t)entry->cached_size; } - /* Calculate emulated stream size from native file 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 (native_size < 0) { - fsetpos(fp, &pos); - DSIO_LOG_DEBUG("dsio_get_size: RETURN -1 (ftell failed) %d\n", 1); - return -1; - } - - DSIO_LOG_DEBUG("dsio_get_size: native_size=%ld, is_fixed_recfm=%d, reclen=%zu\n", - native_size, entry->is_fixed_recfm, entry->reclen); - - if (fsetpos(fp, &pos) != 0) { - DSIO_LOG_DEBUG("WARNING: fsetpos() failed in dsio_get_size %d\n", 1); - } - - /* Calculate emulated stream size based on record format */ + FILE* fp = entry->file_ptr; ssize_t emulated_size; - + if (entry->is_fixed_recfm && entry->reclen > 0) { - /* FB: native_size is total bytes, add newlines */ - size_t num_records = native_size / entry->reclen; + /* + * 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: FB calculation - native=%ld, reclen=%zu, num_records=%zu, emulated=%zd\n", - native_size, entry->reclen, num_records, emulated_size); - - /* Cache the result */ - entry->cached_size = (size_t)emulated_size; - entry->size_calculated = 1; + 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: Calculate by reading all records (one-time cost) */ - DSIO_LOG_DEBUG("dsio_get_size: Calculating VB emulated size...%d\n", 1); + /* + * 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); - if (emulated_size >= 0) { - /* Cache the result for future calls */ - entry->cached_size = (size_t)emulated_size; - entry->size_calculated = 1; - DSIO_LOG_DEBUG("dsio_get_size: VB size calculated and cached: %zd\n", emulated_size); - } else { - DSIO_LOG_DEBUG("dsio_get_size: VB size calculation failed %d\n", 1); - } + DSIO_LOG_DEBUG("dsio_get_size: fd=%d VB/U emulated=%zd\n", fd, emulated_size); } - DSIO_LOG_DEBUG("dsio_get_size: RETURN %zd (fd=%d, native=%ld, emulated=%zd)\n", - emulated_size, fd, native_size, emulated_size); + if (emulated_size >= 0) { + entry->cached_size = (size_t)emulated_size; + entry->size_calculated = 1; + } return emulated_size; } diff --git a/src/zos-io.cc b/src/zos-io.cc index 849b7003..77037da6 100644 --- a/src/zos-io.cc +++ b/src/zos-io.cc @@ -1092,12 +1092,12 @@ off_t __lseek_ds_file(int fd, off_t offset, int whence) { int __stat_ds_file(const char *pathname, struct stat *statbuf) { #if ZOSLIB_ENABLE_DATASETIO if (is_dataset_supported(pathname)) { - DSIO_LOG_DEBUG("calling stat-dataset path %s\n", pathname); + DSIO_LOG_DEBUG("calling stat-dataset path %s\n", pathname); return stat_dataset(pathname, statbuf); } else #endif { - DSIO_LOG_DEBUG("calling stat-file path %s\n", pathname); + DSIO_LOG_DEBUG("calling stat-file path %s\n", pathname); return __stat_orig(pathname, statbuf); } } @@ -1105,12 +1105,12 @@ int __stat_ds_file(const char *pathname, struct stat *statbuf) { int __fstat_ds_file(int fd, struct stat *statbuf) { #if ZOSLIB_ENABLE_DATASETIO if (IS_DD(fd)) { - DSIO_LOG_DEBUG("calling fstat-dataset fd %d\n", fd); + DSIO_LOG_DEBUG("calling fstat-dataset fd %d\n", fd); return fstat_dataset(fd, statbuf); } else #endif { - DSIO_LOG_DEBUG("calling fstat-file fd %d\n", fd); + DSIO_LOG_DEBUG("calling fstat-file fd %d\n", fd); return __fstat_orig(fd, statbuf); } } diff --git a/src/zos.cc b/src/zos.cc index 6cd0778a..24d52b2d 100644 --- a/src/zos.cc +++ b/src/zos.cc @@ -2695,6 +2695,8 @@ int __zinit::initialize(const zoslib_config_t &aconfig) { 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; } From 27646060dd8f64426e39ecbc5bafa00a97f726e6 Mon Sep 17 00:00:00 2001 From: sachintu47 Date: Mon, 20 Apr 2026 08:33:01 -0400 Subject: [PATCH 22/28] augment review fixes --- include/unistd.h | 3 ++ include/zos-datasetio.h | 7 ++-- src/zos-datasetio.c | 90 ++++++++++++++++++++++++++--------------- src/zos-io.cc | 31 ++++++++++---- src/zos.cc | 3 +- 5 files changed, 89 insertions(+), 45 deletions(-) diff --git a/include/unistd.h b/include/unistd.h index 5e58f16e..3456ad63 100644 --- a/include/unistd.h +++ b/include/unistd.h @@ -40,6 +40,8 @@ __Z_EXPORT off_t __lseek_ds_file(int fd, off_t offset, int whence); #define write __write_replaced #undef read #define read __read_replaced +#undef lseek +#define lseek __lseek_replaced #include_next #undef pipe #undef close @@ -47,6 +49,7 @@ __Z_EXPORT off_t __lseek_ds_file(int fd, off_t offset, int whence); #undef readlink #undef write #undef read +#undef lseek #if defined(__cplusplus) extern "C" { diff --git a/include/zos-datasetio.h b/include/zos-datasetio.h index 1110ad40..05f76602 100644 --- a/include/zos-datasetio.h +++ b/include/zos-datasetio.h @@ -46,8 +46,8 @@ int fstat_dataset(int fd, struct stat *statbuf); * * Datasets are always in the form: //'' or //'()' */ -char* temp_file_name(char* result); -char* temp_dataset_name(char* result); +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); @@ -357,7 +357,7 @@ typedef struct DatasetEntry { -#define GET_DUMMY_FD() (open("/dev/null", O_WRONLY, 0)) +#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 */ @@ -449,6 +449,7 @@ int extract_qualifiers(const char* name, char* hlq, char* llq, size_t len); #define ADD_FD(fd) ((void)0) #define ADD_DD(fd,dd) ((void)0) #define GET_DD(fd) (NULL) +#define GET_DUMMY_FD(flags) (-1) #define CLEAR_DD(fd) ((void)0) #define IS_FD(fd) (1) #define IS_DD(fd) (0) diff --git a/src/zos-datasetio.c b/src/zos-datasetio.c index ff30286f..d43073b5 100644 --- a/src/zos-datasetio.c +++ b/src/zos-datasetio.c @@ -117,7 +117,14 @@ int create_dataset_fd(const char* name, unsigned short file_ccsid, int flags) errno = ENOMEM; return -1; } - parse_and_store_name(dentry, name); + + if (parse_and_store_name(dentry, name) != 0) { + fclose(dd); + free_entry(dentry); + errno = EINVAL; + return -1; + } + dentry->open_flags = flags; dentry->conversion_state = SETCVTON; /* Default to conversion on */ @@ -193,13 +200,15 @@ int create_dataset_fd(const char* name, unsigned short file_ccsid, int flags) return -1; } - int fd = GET_DUMMY_FD(); - if (fd < 0) { - DSIO_LOG_DEBUG("create_dataset_fd: ERROR - GET_DUMMY_FD failed, errno=%d\n", errno); - set_entry_error(dentry, DSIO_ERR_INTERNAL_ERROR, "Failed to allocate file descriptor"); + 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"); + if (fd >= 0) close(fd); fclose(dd); + if (dentry->rec_buf) free(dentry->rec_buf); free(dentry); - errno = map_dsio_error_to_errno(DSIO_ERR_INTERNAL_ERROR); + 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); @@ -283,11 +292,11 @@ int open_dataset(const char* name, int flags, mode_t mode) ssize_t write_dataset(int fd, const void* buf, size_t count) { - void* dd = GET_DD(fd); - if (!dd) { + if (!IS_DD(fd)) { errno = EBADF; return -1; } + void* dd = GET_DD(fd); DatasetEntry* dentry = (DatasetEntry*) (dd); @@ -342,7 +351,12 @@ ssize_t write_dataset(int fd, const void* buf, size_t count) dentry->rec_buf_pos = 0; dentry->dirty = 0; } else if (!dentry->is_fixed_recfm) { - fwrite("", 1, 0, fp); + /* + * 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 */ @@ -387,11 +401,11 @@ ssize_t write_dataset(int fd, const void* buf, size_t count) ssize_t read_dataset(int fd, void* buf, size_t count) { - void* dd = GET_DD(fd); - if (!dd) { + if (!IS_DD(fd)) { errno = EBADF; return -1; } + void* dd = GET_DD(fd); DatasetEntry* dentry = (DatasetEntry*) (dd); @@ -487,13 +501,7 @@ ssize_t read_dataset(int fd, void* buf, size_t count) return bytes_copied > 0 ? (ssize_t)bytes_copied : -1; } dentry->eof_reached = 1; - /* Only set newline_pending if we actually read data from this dataset. - * Without this guard, an empty dataset would return '\n' instead of 0. - */ - if (bytes_copied > 0) { - dentry->newline_pending = 1; - } - break; /* Will emit newline on next call or return if bytes_copied > 0 */ + break; /* Will return bytes_copied if > 0, else 0 for EOF */ } /* Convert CCSID of the newly read data in-place */ @@ -510,11 +518,11 @@ ssize_t read_dataset(int fd, void* buf, size_t count) int close_dataset(int fd) { - void* dd = GET_DD(fd); - if (!dd) { + if (!IS_DD(fd)) { errno = EBADF; return -1; } + void* dd = GET_DD(fd); DatasetEntry* dentry = (DatasetEntry*) (dd); @@ -575,7 +583,7 @@ int close_dataset(int fd) return rc; } -char* temp_dataset_name(char* result) +char* temp_dataset_name(char* result, size_t len) { char* orig = getenv("__POSIX_TMPNAM"); char temp[L_tmpnam+1]; @@ -585,11 +593,11 @@ char* temp_dataset_name(char* result) setenv("__POSIX_TMPNAM", orig, 1); else unsetenv("__POSIX_TMPNAM"); - sprintf(result, "//'%s'", temp); + snprintf(result, len, "//'%s'", temp); return result; } -char* temp_file_name(char* result) +char* temp_file_name(char* result, size_t len) { char* orig = getenv("__POSIX_TMPNAM"); setenv("__POSIX_TMPNAM", "YES", 1); @@ -683,8 +691,11 @@ int zos_fcntl(int fd, int cmd, struct f_cnvrt* req) dentry->file_ccsid = req->fccsid; dentry->conversion_state = req->cvtcmd; } + return 0; } - return 0; + + errno = EINVAL; + return -1; } @@ -800,8 +811,12 @@ off_t lseek_dataset(int fd, off_t offset, int whence) { 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) { - /* EOF reached before target */ + 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; } } @@ -914,7 +929,9 @@ int stat_dataset(const char *pathname, struct stat *statbuf) { } 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; @@ -1568,8 +1585,11 @@ int dsio_get_metadata(int fd, dsio_metadata_t* metadata) { 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; } @@ -1768,22 +1788,28 @@ static ssize_t calculate_vb_emulated_size(FILE* fp, DatasetEntry* entry) { } /* 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) break; /* EOF */ - - /* Strip trailing spaces */ - while (rc > 0 && entry->rec_buf[rc - 1] == ' ') { - rc--; + 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); diff --git a/src/zos-io.cc b/src/zos-io.cc index 77037da6..f6d64b20 100644 --- a/src/zos-io.cc +++ b/src/zos-io.cc @@ -916,9 +916,13 @@ int __open_ascii(const char *filename, int opts, ...) { int __open_ds_file(const char *filename, int opts, ...) { int fd; - va_list ap; - va_start(ap, opts); - int perms = va_arg(ap, int); + 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 ZOSLIB_ENABLE_DATASETIO @@ -934,7 +938,6 @@ int __open_ds_file(const char *filename, int opts, ...) { } DSIO_LOG_DEBUG("calling open-file fd %d\n", fd); } - va_end(ap); return fd; } @@ -1081,11 +1084,11 @@ int __close(int fd) { off_t __lseek_ds_file(int fd, off_t offset, int whence) { #if ZOSLIB_ENABLE_DATASETIO if (IS_DD(fd)) { - DSIO_LOG_DEBUG("calling lseek-dataset fd %d offset %d whence %d\n", fd, offset, whence); + DSIO_LOG_DEBUG("calling lseek-dataset fd %d offset %lld whence %d\n", fd, (long long)offset, whence); return lseek_dataset(fd, offset, whence); } #endif - DSIO_LOG_DEBUG("calling lseek-file fd %d offset %d whence %d\n", 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); } @@ -1450,8 +1453,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_orig(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); @@ -1461,6 +1464,12 @@ 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 ZOSLIB_ENABLE_DATASETIO + if (IS_DD(fd)) { + errno = ENOSYS; + return -1; + } +#endif if (!isatty(fd)) { return __writev_orig(fd, iov, iovcnt); @@ -1496,6 +1505,12 @@ 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 ZOSLIB_ENABLE_DATASETIO + if (IS_DD(fd)) { + errno = ENOSYS; + return -1; + } +#endif if (!isatty(fd)) { return __readv_orig(fd, iov, iovcnt); diff --git a/src/zos.cc b/src/zos.cc index 24d52b2d..9959853f 100644 --- a/src/zos.cc +++ b/src/zos.cc @@ -2469,6 +2469,7 @@ bool __zinit::isValidZOSLIBEnvar(std::string envar) { } __zinit::__zinit() { + ds_support_mode = DS_SUPPORT_NO; __gMainThreadId = gettid(); __gMainThreadSelf = pthread_self(); @@ -3086,9 +3087,7 @@ extern "C" void init_zoslib_config(zoslib_config_t *const config) { 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; -#if ZOSLIB_ENABLE_DATASETIO config->DATASET_SUPPORT_ENVAR = DATASET_SUPPORT_ENVAR_DEFAULT; -#endif } extern "C" void init_zoslib(const zoslib_config_t config) { From df5d89a5237fefe553e12aea1f21744bb5bdfe2f Mon Sep 17 00:00:00 2001 From: sachintu47 Date: Mon, 27 Apr 2026 13:59:52 -0400 Subject: [PATCH 23/28] Resolved Potential Data Loss on Seek --- src/zos-datasetio.c | 98 +++++++++++++++++++++++---------------------- 1 file changed, 50 insertions(+), 48 deletions(-) diff --git a/src/zos-datasetio.c b/src/zos-datasetio.c index d43073b5..6409f3e3 100644 --- a/src/zos-datasetio.c +++ b/src/zos-datasetio.c @@ -20,6 +20,8 @@ #include "zos-datasetio.h" +extern FILE *__fopen_orig(const char *filename, const char *mode) __asm("@@A00246"); + #if ZOSLIB_ENABLE_DATASETIO /* __close_orig is defined in zos-io.cc as the original close() syscall. @@ -34,6 +36,7 @@ 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); static const char DATASET_CHAR[] = "ABCDEFGHIJKLMNOPQRSTUVWYZ$#@"; @@ -104,7 +107,7 @@ int create_dataset_fd(const char* name, unsigned short file_ccsid, int flags) } DSIO_LOG_DEBUG("Open with mode %s\n", fopen_mode); - FILE* dd = fopen(name, fopen_mode); + FILE* dd = __fopen_orig(name, fopen_mode); if (!dd) { perror("dataset open failed"); errno = EIO; @@ -168,7 +171,7 @@ int create_dataset_fd(const char* name, unsigned short file_ccsid, int flags) reopen_mode = "rb,recfm=+"; } DSIO_LOG_DEBUG("FB optimization: reopening with mode %s\n", reopen_mode); - dd = fopen(name, 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"); free(dentry); @@ -417,27 +420,13 @@ ssize_t read_dataset(int fd, void* buf, size_t count) * We bypass C runtime buffering with _IONBF and do our own buffering in rec_buf. */ if (dentry->dirty) { - if (dentry->rec_buf_pos > 0) { - /* Flush pending write buffer */ - if (dentry->is_fixed_recfm) { - while (dentry->rec_buf_pos < dentry->reclen) { - dentry->rec_buf[dentry->rec_buf_pos++] = ' '; - } - } - 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 flush write buffer before read"); - errno = map_dsio_error_to_errno(DSIO_ERR_WRITE_FAILED); - return -1; - } + if (flush_rec_buf(dentry) != 0) { + errno = map_dsio_error_to_errno(DSIO_ERR_WRITE_FAILED); + return -1; } - /* Reset buffer state for switch to read mode */ + /* Reset read state for switch to read mode */ dentry->rec_buf_pos = 0; dentry->rec_buf_len = 0; - dentry->dirty = 0; } DSIO_LOG_DEBUG("read_dataset: fd=%d count=%zu offset=%zu recfm=%s\n", @@ -532,34 +521,8 @@ int close_dataset(int fd) FILE* fp = dentry->file_ptr; /* Flush any partial record remaining in the write buffer */ - if (dentry->dirty && dentry->rec_buf_pos > 0 && - ((dentry->open_flags & O_ACCMODE) != O_RDONLY)) { - /* 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 == SETCVTON) { - void* conv_result = dsio_convert_buffer(dentry->rec_buf, dentry->rec_buf_pos, - dentry->program_ccsid, dentry->file_ccsid); - if (conv_result == NULL) { - set_entry_error(dentry, DSIO_ERR_CCSID_CONVERSION, "CCSID conversion failed during close"); - fprintf(stderr, "WARNING: CCSID conversion failed during close\n"); - /* Continue with close despite conversion error */ - } - } - - /* Write final record */ - size_t rc = fwrite(dentry->rec_buf, 1, dentry->rec_buf_pos, fp); - if (rc != dentry->rec_buf_pos) { - set_entry_error(dentry, DSIO_ERR_WRITE_FAILED, "Final record write incomplete during close"); - fprintf(stderr, "WARNING: Final record write incomplete (%zu of %zu bytes)\n", - rc, dentry->rec_buf_pos); - /* Continue with close despite write error */ - } + if (dentry->dirty) { + flush_rec_buf(dentry); } int rc = fclose(fp); @@ -716,6 +679,14 @@ off_t lseek_dataset(int fd, off_t offset, int whence) { 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; @@ -1494,6 +1465,37 @@ static dsio_dsorg_t detect_dsorg_from_fldata(const fldata_t* fdata) { 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) { From a5e5cfe9a7013de4c122eac671aaeb2b93b98baa Mon Sep 17 00:00:00 2001 From: sachintu47 Date: Tue, 28 Apr 2026 04:37:45 -0400 Subject: [PATCH 24/28] fix: add thread safety and prevent memory leaks in dataset I/O --- include/zos-datasetio.h | 76 ++++++++++++++++++++++++++++++++--------- src/zos-datasetio.c | 56 ++++++++++++++++++++++++------ 2 files changed, 106 insertions(+), 26 deletions(-) diff --git a/include/zos-datasetio.h b/include/zos-datasetio.h index 05f76602..012e5245 100644 --- a/include/zos-datasetio.h +++ b/include/zos-datasetio.h @@ -296,22 +296,66 @@ int dsio_flush(int fd); #define MAX_FDS 1024 #define INV_ADDR_BIT (0x0000000080000000ULL) -/* Bounds-checked accessors to prevent out-of-bounds when fd >= MAX_FDS */ -#define ADD_FD(fd) do { if ((fd) >= 0 && (fd) < MAX_FDS) descriptor_table[(fd)] = ((void*)(((unsigned long long)(fd)) | INV_ADDR_BIT)); } while(0) -#define ADD_DD(fd,dd) do { if ((fd) >= 0 && (fd) < MAX_FDS) descriptor_table[(fd)] = (dd); } while(0) -#define GET_DD(fd) (((fd) >= 0 && (fd) < MAX_FDS) ? descriptor_table[(fd)] : NULL) -#define CLEAR_DD(fd) do { if ((fd) >= 0 && (fd) < MAX_FDS) descriptor_table[(fd)] = 0; } while(0) - -/* Validate descriptor slot is within bounds and occupied */ -#define IS_VALID_SLOT(slot) ((slot) >= 0 && (slot) < MAX_FDS && descriptor_table[(slot)] != NULL) - -/* Check if slot contains a file descriptor (has INV_ADDR_BIT set) */ -#define IS_FD(slot) (IS_VALID_SLOT(slot) && \ - ((((unsigned long long) (descriptor_table[(slot)])) & INV_ADDR_BIT) != 0)) - -/* Check if slot contains a dataset descriptor (no INV_ADDR_BIT) */ -#define IS_DD(slot) (IS_VALID_SLOT(slot) && \ - ((((unsigned long long) (descriptor_table[(slot)])) & INV_ADDR_BIT) == 0)) +/* Thread-safe descriptor table access */ +#include + +extern pthread_mutex_t descriptor_table_mutex; + +/* Thread-safe bounds-checked accessors to prevent out-of-bounds when fd >= MAX_FDS */ +#define ADD_FD(fd) do { \ + 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); \ +} while(0) + +#define ADD_DD(fd,dd) do { \ + pthread_mutex_lock(&descriptor_table_mutex); \ + if ((fd) >= 0 && (fd) < MAX_FDS) descriptor_table[(fd)] = (dd); \ + pthread_mutex_unlock(&descriptor_table_mutex); \ +} while(0) + +#define GET_DD(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); \ + __result; \ +}) + +#define CLEAR_DD(fd) do { \ + pthread_mutex_lock(&descriptor_table_mutex); \ + if ((fd) >= 0 && (fd) < MAX_FDS) descriptor_table[(fd)] = 0; \ + pthread_mutex_unlock(&descriptor_table_mutex); \ +} while(0) + +/* Thread-safe validation: descriptor slot is within bounds and occupied */ +#define IS_VALID_SLOT(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); \ + __valid; \ +}) + +/* Thread-safe check: slot contains a file descriptor (has INV_ADDR_BIT set) */ +#define IS_FD(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); \ + __is_fd; \ +}) + +/* Thread-safe check: slot contains a dataset descriptor (no INV_ADDR_BIT) */ +#define IS_DD(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); \ + __is_dd; \ +}) typedef struct DatasetEntry { /* Original fields */ diff --git a/src/zos-datasetio.c b/src/zos-datasetio.c index 6409f3e3..43f67430 100644 --- a/src/zos-datasetio.c +++ b/src/zos-datasetio.c @@ -30,7 +30,9 @@ extern FILE *__fopen_orig(const char *filename, const char *mode) __asm("@@A0024 */ 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); @@ -38,6 +40,45 @@ 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) @@ -116,14 +157,13 @@ int create_dataset_fd(const char* name, unsigned short file_ccsid, int flags) DatasetEntry* dentry = create_entry(dd, file_ccsid); if (!dentry) { - fclose(dd); + cleanup_dataset_resources(NULL, dd, -1); errno = ENOMEM; return -1; } if (parse_and_store_name(dentry, name) != 0) { - fclose(dd); - free_entry(dentry); + cleanup_dataset_resources(dentry, NULL, -1); errno = EINVAL; return -1; } @@ -174,7 +214,7 @@ int create_dataset_fd(const char* name, unsigned short file_ccsid, int flags) dd = __fopen_orig(name, reopen_mode); if (!dd) { set_entry_error(dentry, DSIO_ERR_OPEN_FAILED, "Failed to reopen dataset in binary mode"); - free(dentry); + cleanup_dataset_resources(dentry, NULL, -1); errno = map_dsio_error_to_errno(DSIO_ERR_OPEN_FAILED); return -1; } @@ -197,8 +237,7 @@ int create_dataset_fd(const char* name, unsigned short file_ccsid, int flags) 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"); - fclose(dd); - free(dentry); + cleanup_dataset_resources(dentry, NULL, -1); errno = map_dsio_error_to_errno(DSIO_ERR_ALLOC_FAILED); return -1; } @@ -207,10 +246,7 @@ int create_dataset_fd(const char* name, unsigned short file_ccsid, int 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"); - if (fd >= 0) close(fd); - fclose(dd); - if (dentry->rec_buf) free(dentry->rec_buf); - free(dentry); + cleanup_dataset_resources(dentry, NULL, fd); errno = (fd >= MAX_FDS) ? EMFILE : map_dsio_error_to_errno(DSIO_ERR_INTERNAL_ERROR); return -1; } From d1bab12781fac9365a4239950c97e3cde2ada12e Mon Sep 17 00:00:00 2001 From: sachintu47 Date: Tue, 28 Apr 2026 04:52:49 -0400 Subject: [PATCH 25/28] feat: enable dataset I/O support by default --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 61b346a1..ce2ee09f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,6 +55,8 @@ if(ZOSLIB_QUICK_STARTUP) ZOSLIB_QUICK_STARTUP) endif() +option(ZOSLIB_ENABLE_DATASETIO "Enable dataset I/O support" ON) + if(ZOSLIB_ENABLE_DATASETIO) list(APPEND zoslib_defines ZOSLIB_ENABLE_DATASETIO=1) From 4a29b178ddef178d63934f6df3849f15360a116e Mon Sep 17 00:00:00 2001 From: sachintu47 Date: Tue, 28 Apr 2026 05:03:47 -0400 Subject: [PATCH 26/28] Add documentation for ZOSLIB_DATASET_SUPPORT environment variable --- man/zoslib.1 | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) 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. From 6667c6ab3491c1c9aef0203b9f2118bc7abe040c Mon Sep 17 00:00:00 2001 From: sachintu47 Date: Tue, 28 Apr 2026 05:30:52 -0400 Subject: [PATCH 27/28] refactor: simplify dataset I/O implementation - Enable dataset I/O by default at runtime (opt-out instead of opt-in) - Replace mutex macros with inline helper functions for better readability - Consolidate 20+ error codes into 5 categories with backward-compatible aliases - Simplify logging macros to use centralized dsio_log() function --- include/zos-datasetio.h | 199 +++++++++++++++++++++++----------------- src/zos-datasetio.c | 91 ++++-------------- src/zos.cc | 7 +- 3 files changed, 135 insertions(+), 162 deletions(-) diff --git a/include/zos-datasetio.h b/include/zos-datasetio.h index 012e5245..ef92cd8a 100644 --- a/include/zos-datasetio.h +++ b/include/zos-datasetio.h @@ -55,31 +55,38 @@ int delete_dataset(const char* dataset); * ERROR HANDLING * ======================================================================== */ -/* Comprehensive error codes for better diagnostics */ +/* Simplified error codes grouped by category */ typedef enum { DSIO_SUCCESS = 0, - DSIO_ERR_INVALID_NAME, - DSIO_ERR_NAME_TOO_LONG, - DSIO_ERR_OPEN_FAILED, - DSIO_ERR_READ_FAILED, - DSIO_ERR_WRITE_FAILED, - DSIO_ERR_CLOSE_FAILED, - DSIO_ERR_ALLOC_FAILED, - DSIO_ERR_INVALID_FD, - DSIO_ERR_INVALID_RECFM, - DSIO_ERR_INVALID_DSORG, - DSIO_ERR_CCSID_CONVERSION, - DSIO_ERR_BUFFER_OVERFLOW, - DSIO_ERR_MEMBER_NOT_FOUND, - DSIO_ERR_NOT_A_DATASET, - DSIO_ERR_FLDATA_FAILED, - DSIO_ERR_FSEEK_FAILED, - DSIO_ERR_FTELL_FAILED, - DSIO_ERR_RECORD_TOO_LONG, - DSIO_ERR_UNSUPPORTED_OPERATION, - DSIO_ERR_INTERNAL_ERROR + 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); @@ -300,62 +307,83 @@ int dsio_flush(int fd); #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); +} -/* Thread-safe bounds-checked accessors to prevent out-of-bounds when fd >= MAX_FDS */ -#define ADD_FD(fd) do { \ - 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); \ -} while(0) - -#define ADD_DD(fd,dd) do { \ - pthread_mutex_lock(&descriptor_table_mutex); \ - if ((fd) >= 0 && (fd) < MAX_FDS) descriptor_table[(fd)] = (dd); \ - pthread_mutex_unlock(&descriptor_table_mutex); \ -} while(0) - -#define GET_DD(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); \ - __result; \ -}) - -#define CLEAR_DD(fd) do { \ - pthread_mutex_lock(&descriptor_table_mutex); \ - if ((fd) >= 0 && (fd) < MAX_FDS) descriptor_table[(fd)] = 0; \ - pthread_mutex_unlock(&descriptor_table_mutex); \ -} while(0) - -/* Thread-safe validation: descriptor slot is within bounds and occupied */ -#define IS_VALID_SLOT(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); \ - __valid; \ -}) - -/* Thread-safe check: slot contains a file descriptor (has INV_ADDR_BIT set) */ -#define IS_FD(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); \ - __is_fd; \ -}) - -/* Thread-safe check: slot contains a dataset descriptor (no INV_ADDR_BIT) */ -#define IS_DD(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); \ - __is_dd; \ -}) +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 */ @@ -410,18 +438,19 @@ typedef struct DatasetEntry { #endif #if ZOSLIB_DATASET_LOGGING - #define DSIO_LOG_ERROR(fmt, ...) do { if (g_debug_enabled && g_log_level >= DSIO_LOG_ERROR) log_error(fmt, ##__VA_ARGS__); } while(0) - #define DSIO_LOG_WARN(fmt, ...) do { if (g_debug_enabled && g_log_level >= DSIO_LOG_WARN) log_warn(fmt, ##__VA_ARGS__); } while(0) - #define DSIO_LOG_INFO(fmt, ...) do { if (g_debug_enabled && g_log_level >= DSIO_LOG_INFO) log_info(fmt, ##__VA_ARGS__); } while(0) - #define DSIO_LOG_DEBUG(fmt, ...) do { if (g_debug_enabled && g_log_level >= DSIO_LOG_DEBUG) log_debug(fmt, ##__VA_ARGS__); } while(0) - #define DSIO_LOG_TRACE(fmt, ...) do { if (g_debug_enabled && g_log_level >= DSIO_LOG_TRACE) log_trace(fmt, ##__VA_ARGS__); } while(0) -#else /* ZOSLIB_ENABLE_DATASETIO == 0 */ + /* 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_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_ENABLE_DATASETIO */ +#endif /* ZOSLIB_DATASET_LOGGING */ /* ======================================================================== * ENHANCED INTERNAL STRUCTURES diff --git a/src/zos-datasetio.c b/src/zos-datasetio.c index 43f67430..567eb5aa 100644 --- a/src/zos-datasetio.c +++ b/src/zos-datasetio.c @@ -960,26 +960,11 @@ int g_debug_enabled = 0; static const char* error_messages[] = { [DSIO_SUCCESS] = "Success", - [DSIO_ERR_INVALID_NAME] = "Invalid dataset name", - [DSIO_ERR_NAME_TOO_LONG] = "Dataset name too long", - [DSIO_ERR_OPEN_FAILED] = "Failed to open dataset", - [DSIO_ERR_READ_FAILED] = "Failed to read from dataset", - [DSIO_ERR_WRITE_FAILED] = "Failed to write to dataset", - [DSIO_ERR_CLOSE_FAILED] = "Failed to close dataset", - [DSIO_ERR_ALLOC_FAILED] = "Memory allocation failed", - [DSIO_ERR_INVALID_FD] = "Invalid file descriptor", - [DSIO_ERR_INVALID_RECFM] = "Invalid record format", - [DSIO_ERR_INVALID_DSORG] = "Invalid dataset organization", - [DSIO_ERR_CCSID_CONVERSION] = "CCSID conversion failed", - [DSIO_ERR_BUFFER_OVERFLOW] = "Buffer overflow", - [DSIO_ERR_MEMBER_NOT_FOUND] = "Member not found", - [DSIO_ERR_NOT_A_DATASET] = "Not a dataset", - [DSIO_ERR_FLDATA_FAILED] = "fldata() failed", - [DSIO_ERR_FSEEK_FAILED] = "fseek() failed", - [DSIO_ERR_FTELL_FAILED] = "ftell() failed", - [DSIO_ERR_RECORD_TOO_LONG] = "Record too long", - [DSIO_ERR_UNSUPPORTED_OPERATION] = "Unsupported operation", - [DSIO_ERR_INTERNAL_ERROR] = "Internal error" + [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) { @@ -1025,34 +1010,15 @@ static int map_dsio_error_to_errno(dsio_error_t dsio_err) { switch (dsio_err) { case DSIO_SUCCESS: return 0; - case DSIO_ERR_INVALID_NAME: - case DSIO_ERR_INVALID_RECFM: - case DSIO_ERR_INVALID_DSORG: - case DSIO_ERR_INVALID_FD: + case DSIO_ERR_INVALID_PARAM: return EINVAL; - case DSIO_ERR_NAME_TOO_LONG: - return ENAMETOOLONG; - case DSIO_ERR_OPEN_FAILED: - case DSIO_ERR_READ_FAILED: - case DSIO_ERR_WRITE_FAILED: - case DSIO_ERR_CLOSE_FAILED: - case DSIO_ERR_FLDATA_FAILED: - case DSIO_ERR_FSEEK_FAILED: - case DSIO_ERR_FTELL_FAILED: + case DSIO_ERR_IO_FAILED: return EIO; - case DSIO_ERR_ALLOC_FAILED: + case DSIO_ERR_RESOURCE: return ENOMEM; - case DSIO_ERR_MEMBER_NOT_FOUND: - case DSIO_ERR_NOT_A_DATASET: - return ENOENT; - case DSIO_ERR_CCSID_CONVERSION: - return EILSEQ; - case DSIO_ERR_BUFFER_OVERFLOW: - case DSIO_ERR_RECORD_TOO_LONG: - return EOVERFLOW; - case DSIO_ERR_UNSUPPORTED_OPERATION: + case DSIO_ERR_UNSUPPORTED: return ENOTSUP; - case DSIO_ERR_INTERNAL_ERROR: + case DSIO_ERR_INTERNAL: default: return EIO; } @@ -1328,56 +1294,35 @@ void* convert_ascii_to_ebcdic(void* buf, size_t len) { * 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_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; - } - /* Auto-create temp log file if not already set */ + 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); - } + if (g_log_stream) fprintf(stderr, "DSIO: Logging to %s\n", logpath); } } } void dsio_log(dsio_log_level_t level, const char* format, ...) { - if (level > g_log_level) { - return; - } - - FILE* stream = g_log_stream ? g_log_stream : stderr; + if (!g_debug_enabled || level > g_log_level) return; - const char* level_str; - switch (level) { - case DSIO_LOG_ERROR: level_str = "ERROR"; break; - case DSIO_LOG_WARN: level_str = "WARN "; break; - case DSIO_LOG_INFO: level_str = "INFO "; break; - case DSIO_LOG_DEBUG: level_str = "DEBUG"; break; - case DSIO_LOG_TRACE: level_str = "TRACE"; break; - default: level_str = "?????"; break; - } + 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); } diff --git a/src/zos.cc b/src/zos.cc index 9959853f..5e6e601d 100644 --- a/src/zos.cc +++ b/src/zos.cc @@ -2693,13 +2693,12 @@ int __zinit::initialize(const zoslib_config_t &aconfig) { memcpy(&config, &aconfig, sizeof(config)); #if ZOSLIB_ENABLE_DATASETIO + /* Default to enabled, only disable if explicitly set to NO/0 */ 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)) { + 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; + ds_support_mode = DS_SUPPORT_YES; } #else ds_support_mode = DS_SUPPORT_NO; From 6bff78236efb6c94196490560bcaa34163fe187b Mon Sep 17 00:00:00 2001 From: sachintu47 Date: Tue, 28 Apr 2026 06:56:15 -0400 Subject: [PATCH 28/28] refactor: remove ZOSLIB_ENABLE_DATASETIO build-time conditional Dataset I/O now always compiled in, controlled at runtime via ZOSLIB_DATASET_SUPPORT env var --- CMakeLists.txt | 9 +++----- include/zos-datasetio.h | 25 ---------------------- src/zos-datasetio.c | 4 ---- src/zos-io.cc | 46 +++++++---------------------------------- src/zos.cc | 17 ++++----------- 5 files changed, 15 insertions(+), 86 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ce2ee09f..d157b219 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,12 +55,9 @@ if(ZOSLIB_QUICK_STARTUP) ZOSLIB_QUICK_STARTUP) endif() -option(ZOSLIB_ENABLE_DATASETIO "Enable dataset I/O support" ON) - -if(ZOSLIB_ENABLE_DATASETIO) - list(APPEND zoslib_defines - ZOSLIB_ENABLE_DATASETIO=1) -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}") diff --git a/include/zos-datasetio.h b/include/zos-datasetio.h index ef92cd8a..6d948e9d 100644 --- a/include/zos-datasetio.h +++ b/include/zos-datasetio.h @@ -1,16 +1,10 @@ #ifndef __DATASET_IO__ #define __DATASET_IO__ 1 -#ifndef ZOSLIB_ENABLE_DATASETIO - #define ZOSLIB_ENABLE_DATASETIO 0 -#endif /* ZOSLIB_ENABLE_DATASETIO */ - #if defined(__cplusplus) extern "C" { #endif -#if ZOSLIB_ENABLE_DATASETIO - #include #include #include @@ -510,25 +504,6 @@ int extract_qualifiers(const char* name, char* hlq, char* llq, size_t len); #define ERR_MSG_FLDATA_FAILED "fldata() failed for dataset" #define ERR_MSG_CCSID_CONV "CCSID conversion failed: %d -> %d" -#else /* ZOSLIB_ENABLE_DATASETIO == 0 */ - -#define IS_DATASET(name) (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) - -#define ADD_FD(fd) ((void)0) -#define ADD_DD(fd,dd) ((void)0) -#define GET_DD(fd) (NULL) -#define GET_DUMMY_FD(flags) (-1) -#define CLEAR_DD(fd) ((void)0) -#define IS_FD(fd) (1) -#define IS_DD(fd) (0) - -#endif /* ZOSLIB_ENABLE_DATASETIO */ - #if defined(__cplusplus) } #endif diff --git a/src/zos-datasetio.c b/src/zos-datasetio.c index 567eb5aa..195f69f6 100644 --- a/src/zos-datasetio.c +++ b/src/zos-datasetio.c @@ -22,8 +22,6 @@ extern FILE *__fopen_orig(const char *filename, const char *mode) __asm("@@A00246"); -#if ZOSLIB_ENABLE_DATASETIO - /* __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). @@ -1921,5 +1919,3 @@ int dsio_get_ccsid_config(int fd, dsio_ccsid_config_t* config) { return 0; } -#endif /* ZOSLIB_ENABLE_DATASETIO */ - diff --git a/src/zos-io.cc b/src/zos-io.cc index f6d64b20..9ee1ffd2 100644 --- a/src/zos-io.cc +++ b/src/zos-io.cc @@ -39,11 +39,9 @@ bool __gLogMemoryWarning = false; bool __gLogMemoryShowPid = true; FILE *fp_memprintf = nullptr; -#if ZOSLIB_ENABLE_DATASETIO static bool is_dataset_supported(const char* name) { return IS_DATASET(name) && __get_instance()->get_ds_support_mode() != DS_SUPPORT_NO; } -#endif } #ifdef __cplusplus @@ -925,13 +923,10 @@ int __open_ds_file(const char *filename, int opts, ...) { } DSIO_LOG_DEBUG("calling __open_ds_file file %s\n", filename); -#if ZOSLIB_ENABLE_DATASETIO if (is_dataset_supported(filename)) { fd = open_dataset(filename, opts, perms); DSIO_LOG_DEBUG("calling open-dataset fd %d\n", fd); - } else -#endif - { + } else { fd = __open_ascii(filename, opts, perms); if (fd >= 0) { ADD_FD(fd); @@ -985,13 +980,9 @@ FILE *__fopen_ascii(const char *filename, const char *mode) { } FILE *__fopen_ds_file(const char *filename, const char *mode) { -#if ZOSLIB_ENABLE_DATASETIO if (is_dataset_supported(filename)) { return __fopen_orig(filename, mode); - } - else -#endif - { + } else { return __fopen_ascii(filename, mode); } } @@ -1029,13 +1020,10 @@ int __mkstemp_ascii(char * tmpl) { int __mkstemp_ds_file(char * tmpl) { int fd; -#if ZOSLIB_ENABLE_DATASETIO if (is_dataset_supported(tmpl)) { DSIO_LOG_DEBUG("calling mkstemp-dataset\n"); fd = mkstemp_dataset(tmpl); - } else -#endif - { + } else { DSIO_LOG_DEBUG("calling mkstemp-file\n"); fd = __mkstemp_ascii(tmpl); if (fd >= 0) { @@ -1046,34 +1034,28 @@ int __mkstemp_ds_file(char * tmpl) { } ssize_t __write_ds_file(int fd, const void *buf, size_t count) { -#if ZOSLIB_ENABLE_DATASETIO if (IS_DD(fd)) { DSIO_LOG_DEBUG("calling write-dataset fd %d\n", fd); return write_dataset(fd, buf, count); - } -#endif + } 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 ZOSLIB_ENABLE_DATASETIO if (IS_DD(fd)) { DSIO_LOG_DEBUG("calling read-dataset fd %d\n", fd); return read_dataset(fd, buf, count); - } -#endif + } DSIO_LOG_DEBUG("calling read-file fd %d\n", fd); return __read_orig(fd, buf, count); } int __close(int fd) { -#if ZOSLIB_ENABLE_DATASETIO if (IS_DD(fd)) { DSIO_LOG_DEBUG("calling close-dataset fd %d\n", fd); return close_dataset(fd); - } -#endif + } DSIO_LOG_DEBUG("calling close-file fd %d\n", fd); int ret = __close_orig(fd); if (ret >= 0) @@ -1082,37 +1064,29 @@ int __close(int fd) { } off_t __lseek_ds_file(int fd, off_t offset, int whence) { -#if ZOSLIB_ENABLE_DATASETIO 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); } -#endif 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 ZOSLIB_ENABLE_DATASETIO if (is_dataset_supported(pathname)) { DSIO_LOG_DEBUG("calling stat-dataset path %s\n", pathname); return stat_dataset(pathname, statbuf); - } else -#endif - { + } 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 ZOSLIB_ENABLE_DATASETIO if (IS_DD(fd)) { DSIO_LOG_DEBUG("calling fstat-dataset fd %d\n", fd); return fstat_dataset(fd, statbuf); - } else -#endif - { + } else { DSIO_LOG_DEBUG("calling fstat-file fd %d\n", fd); return __fstat_orig(fd, statbuf); } @@ -1464,12 +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 ZOSLIB_ENABLE_DATASETIO if (IS_DD(fd)) { errno = ENOSYS; return -1; } -#endif if (!isatty(fd)) { return __writev_orig(fd, iov, iovcnt); @@ -1505,12 +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 ZOSLIB_ENABLE_DATASETIO if (IS_DD(fd)) { errno = ENOSYS; return -1; } -#endif if (!isatty(fd)) { return __readv_orig(fd, iov, iovcnt); diff --git a/src/zos.cc b/src/zos.cc index 5e6e601d..7d8b88ab 100644 --- a/src/zos.cc +++ b/src/zos.cc @@ -2675,34 +2675,27 @@ static void setProcessEnvars() { } int __zinit::initialize(const zoslib_config_t &aconfig) { -#if ZOSLIB_ENABLE_DATASETIO ADD_FD(STDIN_FILENO); ADD_FD(STDOUT_FILENO); ADD_FD(STDERR_FILENO); -#endif -#if ZOSLIB_ENABLE_DATASETIO char* env = getenv("ZOSLIB_DEBUG"); if (env && (strcmp(env, "1") == 0 || strcasecmp(env, "ON") == 0)) { dsio_enable_debug(1); } else { g_debug_enabled = 0; } -#endif memcpy(&config, &aconfig, sizeof(config)); -#if ZOSLIB_ENABLE_DATASETIO - /* Default to enabled, only disable if explicitly set to NO/0 */ char* ds_env = getenv(config.DATASET_SUPPORT_ENVAR); - if (ds_env && (strcasecmp(ds_env, "NO") == 0 || strcmp(ds_env, "0") == 0)) { + 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_YES; + ds_support_mode = DS_SUPPORT_NO; } -#else - ds_support_mode = DS_SUPPORT_NO; -#endif __galloc_info = new __Cache; @@ -2836,7 +2829,6 @@ int __zinit::setEnvarHelpMap() { "always displayed if logging of memory diagnostic " "messages is enabled")); -#if ZOSLIB_ENABLE_DATASETIO envarHelpMap.insert(std::make_pair( zoslibEnvar(config.DATASET_SUPPORT_ENVAR, std::string("YES")), "Enable support for MVS datasets via // prefix")); @@ -2844,7 +2836,6 @@ int __zinit::setEnvarHelpMap() { envarHelpMap.insert(std::make_pair( zoslibEnvar(config.DATASET_SUPPORT_ENVAR, std::string("NO")), "(default) Disable MVS dataset support")); -#endif return __update_envar_settings(NULL); }