How to stop when blocked.

I hit an interesting challenge when working with SMF-real-time.

An application can connect to the SMF real time service, and it will be sent data as it is produced. Depending on a flag the application can use:

  • Non blocking. The application gets a record if there is one available. If not it gets are return code saying “No records available”.
  • Blocking. The application waits (potentially for ever) for a record.

Which is the best one to use?

This is a general problem, it not just SMF real time that has these challenges.

What are the options- generally?

Non blocking

You need to loop to see if there are records, and if not wait. The challenge is how long should you wait for. If you wait for 10 seconds, then you may get a 10 second delay between the record being created, and your application getting it. If you specify a shorter time you reduce this window. If you reduce the delay time, then the application does more loops per minute and so there is an increase in CPU cost.

Blocking

If you use blocking – there is no extra CPU looping round. The problem is how to do you stop processing your application cleanly when it is in a blocked wait, to allow cleaning up at the end of the processing.

A third way

MQSeries process application messages asynchronously. You can say wait for a message – but time out after a specified time interval if no messages have been received.

This method is very successful. But some application abused it. They want their application to wake up every n second, and check their application’s shutdown flag. If their flag is set, then shutdown.

The correct answer in this case is to have MQ Post an Event Control Block(ECB), the application’s code posts another ECB; gthe mainline code waits for either of the EBCs to be posted, and take the appropriate action. However the lazy way of sleeping, waking, checking and sleeping is quick and easy to code.

What are the options for SMF real time?

With the SMF real time code, while one thread is running – and in a blocked wait, another thread can issue a disconnect() request. This wakes up the waiting thread with a “you’ve been woken up because of a disconnect” return code.

The solution is to use a threading model.

The basic SMF get code

while True:
x, rc = f.get(wait=True) # Blocking get
if x == "" : # Happens after disc()
print("Get returned Null string, exiting loop")
break
if rc != "OK":
print("get returned",rc)
print("Disconnect",f.disc())
i += 1
# it worked do something with the record
...
print(i, len(x)) # Process the data
...

Make the SMF get a blocking get with timeout

With every get request, create another thread to wake up and issue the disconnect, to cause the blocked get to wake up.

This may be expensive doing a timer thread create and cancel with every record.

def blocking_get_loop(f, timeout=10, event=None,max_records=None,):
i = 0
while True:
t = Timer(timeout, timerpop(f)) # execute the timeout function after 10 seconds
x, rc = f.get(wait=True) # Blocking get
t.cancel()

....


# This wakes after the specified interval then executes.
def timerpop(f):
f.disc()

Wait for a terminal interrupt

Vignesh S sent me some code which I’ve taken and modified.

The code to the SMF get is in a separate thread. This means the main thread can execute the disconnect and wake up the blocked request.

# Usage example: run until Enter or interrupt, or max_records
if __name__ == "__main__":
blocking_smf(max_records=6) # execute the code below.

def blocking_smf(stream_name="IFASMF.INMEM", debug=0, max_records=None):
f = pySMFRT(stream_name, debug=debug)
f.conn(stream_name,debug=2) # Explicit connect

# Start the blocking loop in a separate thread
get_thread = threading.Thread(target=mainlogic,args=(f),kwargs={"max_records": 4}))
get_thread.start()


try:
# Main thread: wait for user input to stop
input("Press Enter to stop...\n")
print("Stopping...")
except KeyboardInterrupt:
print("Interrupted, stopping...")
finally:
f.disc() # This unblocks the get() call
get_thread.join() # Wait for thread to exit

The key code is

get_thread = threading.Thread(target=mainlogic,args=(f),kwargs={"max_records": 4}))
get_thread.start()

This attaches a thread, execute the function mainlogic, passing the positional parameters f, and keyword arguments max_records.

The code to do the gets and process the requests is the same as before, with the addition of the count of records processed.

def blocking_get_loop(f, max_records=None):
i = 0
while True:
x, rc = f.get(wait=True) # Blocking get
if x == "" : # Happens after disc()
print("Get returned Null string, exiting loop")
break
if rc != "OK":
print("get returned",rc)
print("Disconnect",f.disc())
i += 1
# it worked do something with the record
...
print(i, len(x)) # Process the data
...
#
if max_records and i >= max_records:
print("Reached max records, stopping")
print("Disconnect",f.disc()) # clean up if ended because of number of records
break

If the requested number of records has been processed, or there has been an error, or unexpected data, then disconnect is called, and the function returns.

Handle a terminal interrupt

The code

try:
# Main thread: wait for user input to stop
input("Press Enter to stop...\n")
print("Stopping...")
except KeyboardInterrupt:
print("Interrupted, stopping...")
finally:
f.disc() # This unblocks the get() call
get_thread.join() # Wait for thread to exit

waits for input from the terminal.

This solution is not perfect because if the requested number of records are processed quickly, you still have to enter something at the keyboard.

Use an event with time out

One problem has been notifying the main task when the SMF get task has finished. You can use an event for this.

In the main logic have

def blocking_smf(stream_name="IFASMF.INMEM", debug=0, max_records=None):
f = pySMFRT(stream_name, debug=debug)
f.conn(stream_name,debug=2) # Explicit connect
myevent = threading.Event()

# Start the blocking loop in a separate thread
get_thread = threading.Thread(target=blocking_get_loop, args=(f,),
kwargs={"max_records": max_records,
"event":myevent})
get_thread.start()
# wait for the SMF get task to end - or the event time out
if myevent.wait(timeout=30) is False:
print("We timed out")
f.disc() # wakeup the blocking get

get_thread.join # Wait for it to finish

In the SMF code

def blocking_get_loop(f, t,max_records=None, event=None):
i = 0
while True:
#t = Timer(timeout, timerpop(f))
x, rc = f.get(wait=True) # Blocking get
#t.cancel()
if x == "" : # Happens after disc()
print("Get returned Null string, exiting loop")
break
if rc != "OK":
print("get returned",rc)
print("Disconnect",f.disc())
i += 1
print(i, len(x)) # Process the data
if max_records and i >= max_records:
print("Reached max records, stopping")
print("Disconnect",f.disc())
break
if event is not None:
event.set() # wake up the main task

s

Getting from C enumerations to Python dicts.

I wanted to create Python enumerates from C code. For example with system ssl, there is a datatype x509_attribute_type.

This has a definition

typedef enum {                                                                    
x509_attr_unknown = 0,
x509_attr_name = 1, /* 2.5.4.41 */
x509_attr_surname = 2, /* 2.5.4.4 */
x509_attr_givenName = 3, /* 2.5.4.42 */
x509_attr_initials = 4, /* 2.5.4.43 */
...
} x509_attribute_type;

I wanted to created

x509_attribute_type = {
"x509_attr_unknown" : 0,
"x509_attr_name" : 1,
"x509_attr_surname" : 2,
"x509_attr_givenName" : 3,
"x509_attr_initials" : 4,
...
}

I’ve done this using ISPF macros, but thought it would be easier(!) to automate it.

There is a standard way for compilers to products information for debuggers to understand the structure of programs. DWARF is a debugging information file format used by many compilers and debuggers to support source level debugging. The data is stored internally using Executable and Linkable Format(ELF).

Getting the DWARF file

To get the structures into the DWARF file, it looks like you have to use the structure, that is, if you #include a file, by default the definitions are not stored in the DWARF file.

When I used

#include <gskcms.h>
...
x509_attribute_type aaa;
x509_name_type nt;
x509_string_type st;
x509_ecurve_type et;
int l = sizeof(aaa) sizeof(nt) + sizeof(st) + sizeof(et);

I got the structures x509_attribute_type etc in the DWARF file.

Compiling the file

I used USS xlc command with

xlc ...-Wc,debug(FORMAT(DWARF),level(9))... abc.c

or

/bin/xlclang -v -qlist=d.lst -qsource qdebug=format=dwarf -g -c abc.c -o abc.o

This created a file abc.dbg

The .dbg file includes an eye catcher of ELF (in ASCII)

I downloaded the file in binary to Linux.

There are various packages which are meant to be able to process the file. The only one I got to work successfully was dwarfdump. The Linux version has many options to specify what data you want to select and how you want to report it. dwarfdump reported some errors, but I got most of the information out.

readelf displays some of the information in the file, but I could not get it to display the information about the variables.

What does the output from dwarfdump look like?

The format has changed slightly since I first used this a year or so ago. The data are not always aligned on the same columns, and values like <146> and 2426 (used as a locator id) are now hexadecimal offsets.

The older format

<1>< 2426>      DW_TAG_subprogram
DW_AT_type <146>
DW_AT_name printCert
DW_AT_external yes
...
<2>< 2456> DW_TAG_formal_parameter
DW_AT_name id
DW_AT_type <10387>
...

The newer format

< 1><0x0000132d>    DW_TAG_typedef
DW_AT_type <0x00001349>
DW_AT_name x509_attribute_type
DW_AT_decl_file 0x00000002
DW_AT_decl_line 0x000000a7
DW_AT_decl_column 0x00000003
< 1><0x00001349> DW_TAG_enumeration_type
DW_AT_name __3
DW_AT_byte_size 0x00000002
DW_AT_decl_file 0x00000002
DW_AT_decl_line 0x00000091
DW_AT_decl_column 0x0000000e
DW_AT_sibling <0x00001553>
< 2><0x00001356> DW_TAG_enumerator
DW_AT_name x509_attr_unknown
DW_AT_const_value 0
< 2><0x0000136a> DW_TAG_enumerator
DW_AT_name x509_attr_name
DW_AT_const_value 1

Some of the fields are obvious… others are more cyptic, with varying levels of indirection.

  • <1><0x0000132d> is a high level object <1> with id <0x0000132d>
    • DW_TAG_typedef is a typedef
    • DW_AT_type <0x00001349> see <0x00001349> for the definition (below)
    • DW_AT_name x509_attribute_type is the name of the typedef
    • DW_AT_decl_file 0x00000002 there is a file definition #2… but I could not find it
    • DW_AT_decl_line 0x000000a7 the position within the file
    • DW_AT_decl_column 0x00000003
  • < 1><0x00001349> DW_TAG_enumeration_type. This is referred to by the previous element
    • DW_AT_name __3 this an internally generated name
  • < 2><0x00001356> DW_TAG_enumerator This is part of the <1> <0x00001349> above. It is an enumerator.
    • DW_AT_name x509_attr_unknown this is the label of the value
    • DW_AT_const_value 0 with value 0
  • the next is label x509_attr_name with value 1

Other interesting data

I have a function

int colin(char * cinput, gsk_buffer * binput )
{
...
}
and
typedef struct _gsk_data_buffer {
gsk_size length;
void * data;
} gsk_data_buffer, gsk_buffer;

Breaking this down into its parts, there is an entry in the DWARF output for “int”, “colin”, “*”, “char”, “cinput”, “*”, gsk_buffer (which has levels within it), “binput”

< 1><0x0000009a>    DW_TAG_base_type
DW_AT_name int
DW_AT_encoding DW_ATE_signed
DW_AT_byte_size 0x00000004

< 1><0x00000142> DW_TAG_subprogram
DW_AT_type <0x0000009a>
DW_AT_name colin
DW_AT_external yes(1)
...
< 2><0x0000015c> DW_TAG_formal_parameter
DW_AT_name cinput
DW_AT_type <0x00001fa9>

< 2><0x00000173> DW_TAG_formal_parameter
DW_AT_name binput
DW_AT_type <0x00001fb5>
:

< 2><0x0000018a> DW_TAG_variable
DW_AT_name __func__
DW_AT_type <0x00001fc0>

for cinput

< 1><0x00001fa9>    DW_TAG_pointer_type
DW_AT_type <0x0000006e>
DW_AT_address_class 0x0000000a
< 1><0x0000006e> DW_TAG_base_type
DW_AT_name unsigned char
DW_AT_encoding DW_ATE_unsigned_char
DW_AT_byte_size 0x00000001

For binput

  • binput (1fbf) -> DW_TAG_pointer_type (1e2e) -> gsk_buffer (1e41) -> _gsk_data_buffer of length 8.
  • _gsk_data_buffer has two component
    • length _> gsk_size…
    • data (1faf) -> pointer_type (13c)-> unspecified type void

Processing the data in the file

Parse the data

For an entry like DW_AT_type <0x0000006e> which refers to a key of <0x0000006e>. The definition for this could be before after the current entry being processed.

I found it easiest to process the whole file (in Python), and build up a Python dictionary of each high level defintion.

I could then process the dict one element at a time, and know that all the elements it refers to are in the dict.

There are definitions like

typedef struct _x509_tbs_certificate { 
x509_version version;
gsk_buffer serialNumber;
x509_algorithm_identifier signature;
x509_name issuer;
x509_validity validity;
x509_name subject;
x509_public_key_info subjectPublicKeyInfo;
gsk_bitstring issuerUniqueId;
gsk_bitstring subjectUniqueId;
x509_extensions extensions;
gsk_octet rsvd[16];
} x509_tbs_certificate;

Some elements like x509_algorithm_identifier have a complex structure, which refer to other structures. I think the maximum depth for one of the structures was 6 levels deep.
If you are processing a structure you need to decide how many levels deep you process. For the enumeration I was just interested in the level < 1> and < 2> definitions and ignored any below that depth.

For each < 1> element, there may be zero or more < 2> elements. I added each < 2> element to a Python list within the < 1> element.

You may decide to ignore entries such as which file, row or column a definition is in.

My Python code to parse the file is

fn = "./dwarf.txt"
with open(fn) as fp:
# skip the stuff at the front
for line in fp:
if line[0:5] == "LOCAL":
break
all = {} # return the data here
for line in fp:
# if the line starts with ".debug" we're done
if line[0:6] == ".debug":
break
lhs = line[0:4]
# do not process nested requests
if line[0:1] == "<":
if lhs in ["< 1>","< 2>"]:
keep = True
else:
keep = False
else:
if line[0:1] != " ":
continue
if keep is False: # only within <1> and <2>
continue
# we now have records of interest < 1> and < 2> and records underneath them
if line[0:4] == "< 1>":
key = line[4:16].strip() # "< 123>"
kwds1 = {}
all[key] = kwds1
kwds1["l2"] = [] # empty list to which we add
state = 1 # <1> element
kwds1["type"] = line[20:-1]
elif line[0:4] == "< 2>":
kwds2 = {}
kwds1["l2"].append(kwds2)
kwds2["type"] =line[21:-1]
state = 2 # <2> element
else:
tag = line[0:47].strip()
value = line[47:-1].strip()
if state == 1:
kwds1[tag] = value
else:
kwds2[tag] = value

Process the data

I want to look for the enumerate definitions and only process those

print("=================================")
for e in all:
d = all[e]
if d["type"] == "DW_TAG_typedef": # only these
print(d["DW_AT_name"],"{")
at_type = d["DW_AT_type" ] # get the key of the value
l = all[at_type]["l2"] # and the <2> elements within it
for ll in l:
if "DW_AT_const_value" in ll:
print(' "'+ll["DW_AT_name"]+'":',ll["DW_AT_const_value"])
print("}")

This produced code like

x509_attribute_type = {
"x509_attr_unknown" : 0,
"x509_attr_name" : 1,
"x509_attr_surname" : 2,
"x509_attr_givenName" : 3,
"x509_attr_initials" : 4,
...
}

You can do more advanced things if you want to, for example create structures for Python Struct (struct — Interpret bytes as packed binary data) to build control blocks. With this you can pass in a dict of names, and the struct definitions, and it converts the bytes into the specified definitions ( int, char, byte etc) with the correct bigend/little-end processing etc.

Python calling C functions

  1. You can have Python programs which are pure Python.
  2. You can call C programs that act like Python programs, using Python constructs within the C program
  3. You can call a C program from Python, and it processes parameters like a normal C program.

This blog post is about the third, calling a C program from Python, passing simple data types such as char, integers and strings.

I have based a lot of this on the well written pyzfile package by @daveyc.

The glue that makes it work is the ctypes package a “foreign function library” package.

Before you start

The blog post is called “Python calling C functions”. I tried using a z/OS stub code directly. This is not written in C, and I got.

CEE3595S DLL ... does not contain a CELQSTRT CSECT.

Which shows you must supply a C program.

The C program that I wrote, calls z/OS services. These must be defined like (or default to)

#pragma linkage(...,OS64_NOSTACK)     

Getting started

My C program has several functions including

int query() {  
return 0;
}

The compile instructions said exportall – so all functions are visible from outside of the load module.

You access this from Python using code like

lib_file = pathlib.Path(__file__).parent / "pySMFRealTime.so"
self.lib = ctypes.CDLL(str(lib_file))
...
result = self.lib.query()

Where

  • lib_file = pathlib.Path(__file__).parent / “pySMFRealTime.so” says get the file path of the .so module in the same directory as the current Python file.
  • self.lib = ctypes.CDLL(str(lib_file)) load the file and extract information.
  • result = self.lib.query() execute the query function, passing no parameters, and store any return code in the variable result

Passing simple parameters

A more realistic program, passing parameters in, and getting data back in the parameters is

int conn(const char* resource_name,  // input:  what we want to connect to
char * pOut, // output: where we return the handle
int * rc, // output: return code
int * rs, // output: reason code
int * debug) // input: pass in debug information
{
int lName = strlen(resource_name);
if (*debug >= 1)
{
printf("===resource_namen");
printHex(stdout,pFn,20);
}
...
return 0;
}

The Python code has

lib_file = pathlib.Path(__file__).parent / "pySMFRealTime.so"
self.lib = ctypes.CDLL(str(lib_file))
self.lib.conn.argtypes = [c_char_p, # the name of stream
c_char_p, # the returned buffer
ctypes.POINTER(ctypes.c_int), # rc
ctypes.POINTER(ctypes.c_int), # rs
ctypes.POINTER(ctypes.c_int), # debug
]
self.lib.conn.restype = c_int

The code to do the connection is

def conn(self,name: str,):
token = ctypes.create_string_buffer(16) # 16 byte handle
rc = ctypes.c_int(0)
rs = ctypes.c_int(0)
debug = ctypes.c_int(self.debug)
self.token = None
retcode = self.lib.conn(name.encode("cp500"),
token,
rc,
rs,
debug)
if retcode != 0:
print("returned rc",rc, "reason",rs)
print(">>>>>>>>>>>>>>>>> connect error ")
return None
print("returned rc",rc, "reason",rs)
self.token = token
return rc

The code does

  • def conn(self,name: str,): define the conn function and pass in the variable name which is a string
  • token = ctypes.create_string_buffer(16) # 16 byte handle create a 16 byte buffer and wrap it in ctypes stuff.
  • rc = ctypes.c_int(0), rs = ctypes.c_int(0), debug = ctypes.c_int(self.debug) create 3 integer variables.
  • self.token = None preset this
  • retcode = self.lib.conn( invoke the conn function
    • name.encode(“cp500”), convert the name from ASCII (all Python printable strings are in ASCII) to code page 500.
    • token, the 16 byte token defined above
    • rc, rs, debug) the three integer variables
  • if retcode != 0: print out error messages
  • print(“returned rc”,rc, “reason”,rs) print the return and reason code
  • self.token = token save the token for the next operation
  • return rc return to caller, with the return code.

Once I got my head round the ctypes… it was easy.

The C program

There are some things you need to be aware of.

  • Python is compiled with the -qascii compiler option, so all strings etc are in ASCII. The code name.encode(“cp500”), converts it from ASCII to EBCDIC. The called C program sees the data as a valid EBCDIC string (null terminated).
  • If a character string is returned, with printable text. Either your program coverts it to ASCII, or your Python calling code needs to convert it.
  • Your C program can be compiled with -qascii – or as EBCDIC(no -qascii)
    • Because Python is compiled in ASCII, the printf routines are configured to print ASCII. If your program is compiled as ASCII, printf(“ABCD”) will print as ABCD. If your program is compiled as EBCDIC printf(“ABCD”) will print garbage – because the hex values for EBCDIC ABCD are not printable as ASCII characters.
    • If your program is compiled as ASCII you can define EBCDIC constants.
      • #pragma convert(“IBM-1047”)
      • char * pEyeCatcher = “EYEC”; // EBCDIC eye catcher for control block
      • #pragma convert(pop)

Python calling C functions – passing structures

I’ve written how you can pass simple data from Python to a C function, see Python calling C functions.

This article explains how you can pass structures and point to buffers in the Python program. it extends Python calling C functions. It allows you to move logic from the C program to a Python program.

Using complex arguments

The examples in Python calling C functions were for using simple elements, such as Integers or strings.

I have a C structure I need to pass to a C function. The example below passes in an eye catcher, some lengths, and a buffer for the C function to use.

The C structure

typedef struct querycb {                                                         
char Eyecatcher[4]; /* Eye catcher offset 0 */
uint16_t Length; /* Length of the block 4 */
char Rsvd1[1]; /* Reserved 6 */
uint8_t Version; /* Version number 7 */
char Flags[2]; /* Flags 8 */
uint16_t Reserved8; // 10
uint32_t Count; // number returned 12
uint32_t lBuffer; // length of buffer 16
uint32_t Reservedx ; // 20
void *pBuffer; // 24
} querycb;

The Python code

# create the variables
eyec = "EYEC".encode("cp500") # char[4] eye catcher
l = 32 # uint16_t
res1 = 0 # char[1]
version = 1 # uint8_t -same as a char
flags = 0 # char[2]
res2 = 0 # uint16_t
count = 0 # uint32_t
lBuffer = 4000 # uint32_t
res3 = 0 # uint32_t
# pBuffer # void *
# allocate a buffer for the C program to use and put some data
# into it
pBuffer = ctypes.create_string_buffer(b'abcdefg',size=lBuffer)
# cast the pBuffer so it is a void *
pB = ctypes.cast(pBuffer, ctypes.c_void_p)
# use the struct.pack function. See @4shbbhhiiiP below
# @4 is 4 bytes, the eye catcher
# h half word
# bb two char fields res1, and version
# hh two half word s flags and res2
# iii three integer fields. count lBuffer and res3
# P void * pointer
# Note pB is a ctype, we need the value of it, so pB.value
p = pack("@4shbbhhiiiP", eyec,l,res1,version,flags,
res2,count,lBuffer,res3,pB.value)

#create first parm
p1 = ctypes.c_int(3) # pass in the integer 3 as an example
# create second parm
p2 = ctypes.cast(p, ctypes.c_void_p)

# invoke the function

retcode = lib.conn(p1,p2)

The C program

int conn(int * p1, char * p2) 
// int conn(int max,...)
{
typedef struct querycb {
char Eyecatcher[4]; /* Eye catcher 0 */
uint16_t Length; /* Length of the block 4 */
char Rsvd1[1]; /* Reserved 6 */
uint8_t Version; /* Version number 7 */
char Flags[2]; /* Flags 8 */
uint16_t Reserved8; // 10
uint32_t Count; // number returned 12
uint32_t lBuffer; // length of buffer 16
uint32_t Reservedx ; // 20
void *pBuffer; // 24
} querycb;

querycb * pcb = (querycb * ) p2;

printf("P1 %i\n",*p1);
printHex(stdout,p2,32);
printf("Now the structure\n")
printHex(stdout,pcb -> pBuffer,32);
return 0 ;
}

The output

P1 3
00000000 : D8D9D7C2 00200001 00000000 00000000 ..... .......... EYEC............
00000010 : 00000FA0 00000000 00000050 0901BCB0 ...........P.... ...........&....
Now the structure
00000000 : 61626364 65666700 00000000 00000000 abcdefg......... /...............
00000010 : 00000000 00000000 00000000 00000000 ................ ................

Where

  • EYEC is the passed in eye catcher
  • 00000FA0 is the length of 4000
  • 00000050 0901BCB0 is the 64 address of the structure
  • abcdefg is the data used to initialise the buffer

Observations

It took me a couple of hours to get this to work. I found it hard to get the cast, and the ctype…. functions to work successfully. There may be a better way of coding it, if so please tell me. The code works, which is the objective – but there may be better more correct ways of doing it.

Benefits

By using this technique I was able to move code from my C program to set up the structure needed by the z/OS service into C. My C program was just parse input parameters, set up the linkage for the z/OS service, and invoke the service.

If course I did not have the constants available from the C header file for the service, but that’s a different problem.

Python safely iterating

I was using a Python program to access a z/OS service, and found there were times when my code did not clean up and close the resource.

It took me an afternoon to find out how to do it. I found pyzfile by daveyc an excellent example of how to cover Python advanced topics.

pyzfile example

The documentation has

from pyzfile import *
try:
with ZFile("//'USERID.CNTL(JCL)'", "rb,type=record",encoding='cp1047') as file:
for rec in file:
print(rec)
except ZFileError as e:
print(e)

Breaking this down

Understanding the “with”

try:
with ZFile("//'USERID.CNTL(JCL)'", "rb,type=record",encoding='cp1047') as file:

...
do something with file
...
except ZFileError as e:
print(e)

When the with ZFile(…) as file: is executed the code conceptually does

  • standard set up processing
  • open the file and return the handle
  • do processing using the file handle
  • when ever it leaves the with code section perform the close activity

Note:This could have been done with

try:
open the file
...
do something
...
except:
...
finally: # do this every time
if the file was opened:
close the file

but this is not quite so tidy and compact as the with syntax

In more detail…

  • The def __init__(self,..): method is invoked and passed the parameters. It saves parameters using statements like self.p1
  • The __enter__(self): is invoked passing the instance data(self). It seems to have no other parameters.
    • In the pyzfile, the code issues return self._open(). This invokes the function _open to open the data set.
  • When the with processing completes, it invokes the function __exit__(self, exc_type, exc_value, exc_traceback): This is invoked whether the code returned normally, or got an exception.
    • In the pyzfile, the code issue executes self.close(). So however the “with” processing ends, the close is always executed

Handing errors

I’ve seen that using the “with” clause, people tend to throw exceptions when problems are found

For example with the pyfile code there is

class ZFileError(Exception):
""" ZFile exception """
def __init__(self, message: str, amrc: dict = None):
self.message = message
self.amrc = amrc
if amrc is None:
self.amrc = {}
super().__init__(self.message)

def __str__(self) -> str:
return self.message

def amrc(self):
"""
Returns the amrc dict at the time of error.

:return: The ``__amrc`` structure at the time of error.
"""
return self.amrc

class ZFile:
...
def _open(self):
...
self.handle = open...
if not self.handle:
raise ZFileError(f"Error opening file '{self.filename}':
{self.lib.zfile_strerror().decode('utf-8')}")
return self

Understanding the “for”

The code above had

    with ZFile("//'USERID.CNTL(JCL)'", "rb,type=record",encoding='cp1047') as file:
for rec in file:
print(rec)

How does the “for” work?

The key to this code are the functions

##################################################
# Iterators
##################################################
def __iter__(self):
return self

def __next__(self):
ret = self.read()
if not ret:
raise StopIteration
return ret

When the for statement is processed it processes the __next__ function. This does the work of getting the next record and returning it.

There is a lot of confusing documentation about iterators, iteration and iterables. Let’s see if my description helps clarify or just adds more confusion.

Something is iter-able of you can do iteration on it; where iteration means taking each element in turn.

In Python a list is iter-able

for l in [0,1,2,3,]
print(l)

will iterate over the list and return the element from the list

0
1
2
3

Records in a file are a bit more abstract, you cannot see the whole file, but you can say get the next record – and move through the file until there are no more records.

An iterator is the mechanism by which you iterate. Think of it as a function. The Python documentation is pretty clear.

Most people define

  def __iter__(self):
return self

For most people, just specify this. The PhD class may use something different.

The mechanism of “for” uses the __next__ function

    def __next__(self):
ret = self.read()
if not ret:
raise StopIteration
return ret

Which obtains the next element of data. If there are no more elements, then raise the StopIteration exception.

If you do not handle the StopIteration exception, then Python handles it for you and leaves the processing loop.

Conclusion

With both of these techniques “with” and “for” I could extract records from a z/OS resource.

I’ve used the “with” and “for” with yield to hide implementation detail

# create the function to read the file
def readfile(name):
try:
with ZFile(name, "rb,type=record,noseek") as file:
for rec in file:
yield rec
except ZFileError as e:
print(e)
# process the file using for ... readline()
def reader(...):
for line in readfile("//'IBMUSER.RMF'"):
do something with the data

The Python interface to RACF is great.

The Python package pysear to work with RACF is great. The source is on github, and the documentation starts here. It is well documented, and there are good examples.

I’ve managed to do a lot of processing with very little of my own code.

One project I’ve been meaning to do for a time is to extract the contents of a RACF database and compare them with a different database and show the differences. IBM provides a batch program, and a very large Rexx exec. This has some bugs and is not very nice to use. There is a Rexx interface, which worked, but I found I was writing a lot of code. Then I found the pysear code.

Background

The data returned for userids (and other types of data) have segments.
You can display the base segment for a user.

tso lu colin

To display the tso base segment

tso lu colin tso

Field names returned by pysear have the segment name as a prefix, for example base:max_incorrect_password_attempts.

My first query

What are the active classes in RACF?

See the example.

from sear import sear
import json
import sys
result = sear(
    {
        "operation": "extract",
        "admin_type": "racf-options"
    },
)
json_data = json.dumps(result.result   , indent=2)
print(json_data)

For error handling see error handling

This produces output like

{
"profile": {
"base": {
"base:active_classes": [
"DATASET",
"USER",...
],
"base:add_creator_to_access_list": true,
...
"base:max_incorrect_password_attempts": 3,

...
}

To process the active classes one at a time you need code like

for ac in result.result["profile"]["base"]["base:active_classes"]:
    print("Active class:",ac)

The returned attributes are called traits. See here for the traits for RACF options. The traits show

Traitbase:max_incorrect_password_attempts
RACF Keyrevoke
Data TypesString
Operators Allowed“set”,”delete”
Supported Operations“alter”,”extract”

For this attribute because it is a single valued object, you can set it or delete it.

You can use this attribute for example

result = sear(
    {
        "operation": "alter",
        "admin_type": "racf-options",
        "traits": {
            "base:max_incorrect_password_attempts": 5,
        },
    },
)

The trait “base:active_classes” is list of classes [“DATASET”, “USER”,…]

The trait is

Traitbase:active_classes
RACF Keyclassact
Data Typesstring
Operators Allowed"add", "remove"
Supported Operations"alter", "extract"

Because it is a list, you can add or remove an element, you do not use set or delete which would replace the whole list.

Some traits, such as use counts, have Operators Allowed of N/A. You can only extract and display the information.

My second query

What are the userids in RACF?

The traits are listed here, and code examples are here.

I used

from sear import sear
import json

# get all userids begining with ZWE
users = sear(
    {
        "operation": "search",
        "admin_type": "user",
        "userid_filter": "ZWE",
    },
)
profiles  = users.result["profiles"]
# Now process each profile in turn.
# because this is for userid profiles we need admin_type=user and userid=....
for profile in profiles:
    user = sear(
       {
          "operation": "extract",
          "admin_type": "user",
          "userid": profile,
       }, 
    )
    segments = user.result["profile"]
    #print("segment",segments)
    for segment in segments:   # eg base or omvs
      for w1,v1 in segments[segment].items():
          #print(w1,v1)
          #for w2,v2 in v1.items():
          #  print(w1,w2,v2 )
          json_data = json.dumps(v1  , indent=2)
          print(w1,json_data)

This gave

==PROFILE=== ZWESIUSR
base:auditor false
base:automatic_dataset_protection false
base:create_date "05/06/20"
base:default_group "ZWEADMIN"
base:group_connections [
  {
    ...
    "base:group_connection_group": "IZUADMIN",
    ...
    "base:group_connection_owner": "IBMUSER",
    ...
},
{
    ...
    "base:group_connection_group": "IZUUSER",
   ...
}
...
omvs:default_shell "/bin/sh"
omvs:home_directory "/apps/zowe/v10/home/zwesiusr"
omvs:uid 990017
===PROFILE=== ZWESVUSR
...

Notes on using search and extract

If you use “operation”: “search” you need a ….._filter. If you use extract you use the data type directly, such as “userid”:…

Processing resources

You can process RACF resources. For example a OPERCMDS provide for MVS.DISPLAY commands.

The sear command need a “class”:…. value, for example

result = sear(
{
"operation": "search",
"admin_type": "resource",
"class": "OPERCMDS",
"resource_filter": "MVS.**",
},
)
result = sear(
{
"operation": "extract",
"admin_type": "resource",
"resource": "MVS.DISPLAY",
"class": "Opercmds",
},
)

The value of the class is converted to upper case.

Changing a profile

If you change a profile, for example to issue the PERMIT command

from sear import sear
import json

result = sear(
    {   "operation": "alter",
        "admin_type": "permission",
        "resource": "MVS.DISPLAY.*",
        "userid": "ADCDG",
        "traits": {
          "base:access": "CONTROL"
        },
        "class": "OPERCMDS"

    },
)
json_data = json.dumps(result.result   , indent=2)
print(json_data)

The output was

{
  "commands": [
    {
      "command": "PERMIT MVS.DISPLAY.* CLASS(OPERCMDS)ACCESS (CONTROL) ID(ADCDG)",
      "messages": [
        "ICH06011I RACLISTED PROFILES FOR OPERCMDS WILL NOT REFLECT THE UPDATE(S) UNTIL A SETROPTS REFRESH IS ISSUED"
      ]
    }
  ],
  "return_codes": {
    "racf_reason_code": 0,
    "racf_return_code": 0,
    "saf_return_code": 0,
    "sear_return_code": 0
  }
}

Error handling

Return codes and errors messages

There are two layers of error handling.

  • Invalid requests – problems detected by pysear
  • Non zero return code from the underlying RACF code.

If pysear detects a problem it returns it in

result.result.get("errors") 

For example you have specified an invalid parameter such as “userzzz“:”MINE”

If you do not have this field, then the request was passed to the RACF service. This returns multiple values. See IRRSMO00 return and reason codes. There will be values for

  • SAF return code
  • RACF return code
  • RACF reason code
  • sear return code.

If the RACF return code is zero then the request was successful.

To make error handling easier – and have one error handling for all requests I used


try:
result = try_sear(search)
except Exception as ex:
print("Exception-Colin Line112:",ex)
quit()

Where try_sear was

def try_sear(data):
# execute the request
result = sear(data)
if result.result.get("errors") != None:
print("Request:",result.request)
print("Error with request:",result.result["errors"])
raise ValueError("errors")
elif (result.result["return_codes"]["racf_reason_code"] != 0):
rcs = result.result["return_codes"]
print("SAF Return code",rcs["saf_return_code"],
"RACF Return code", rcs["racf_return_code"],
"RACF Reason code",["racf_reason_code"],
)
raise ValueError("return codes")
return result

Overall

This interface is very easy to do.
I use it to extract definitions from one RACF database, save them as JSON files. Repeat with a different (historical) RACF database, then compare the two JSON files to see the differences.

Note: The sear command only works with the active database, so I had to make the historical database active, run the commands, and switch back to the current data base.

Processing a dataset is easy in Python.

I’ve been doing more with Python on z/OS, and have spent time using datasets. With the pyzfile package this is very easy! (Before this you had to copy a data set to a file in Unix Services).

You can do all the things you normally expect to do: open, close, read, write, locate, info etc.

from pyzfile import * 
def readfile():
try:
with ZFile("//'COLIN.DCOLLECT.OUT'", "rb,type=record,noseek") as file:
for kw,value in file.info().items():
print(kw,":",value)
for rec in file:
yield rec
except ZFileError as e:
print(e,file=sys.stderr)

def reader(nrecords):
nth = 0
for line in readfile():
nth += 1
if nth > nrecords:
break
if nth % 100 == 99:
print("Progress:",nth+1,file=sys.stderr)
## Do something..

It gave

access_method : QSAM
blksize : 27998
device : DISK
dsname : COLIN.DCOLLECT.OUT
dsorgConcat : False
dsorgHFS : False
dsorgHiper : False
dsorgMem : False
dsorgPDE : False
dsorgPDSdir : False
dsorgPDSmem : False
dsorgPO : False
dsorgPS : True
dsorgTemp : False
dsorgVSAM : False
maxreclen : 1020
mode : {'append': False, 'read': True, 'update': False, 'write': False}
noseek_to_seek : NOSWITCH
openmode : RECORD
recfmASA : False
recfmB : True
recfmBlk : True
recfmF : False
recfmM : False
recfmS : False
recfmU : False
recfmV : True
vsamRKP : 0
vsamRLS : NORLS
vsamkeylen : 0
vsamtype : NOTVSAM
Progress: 100
...

Where the fields are taken from fldata().

Great stuff!

Of course once you’ve got a record, doing something with it may be harder, because , for example Python is ASCII, and you’ll need to convert any character strings from EBCDIC to ASCII.

Getting table data out of html – successfully

A couple of times I’ve wanted to get information from documentation into my program for example, from

I want to extract

  • “cics:operator_class” : "set","add","remove","delete"
  • “cics:operator_classes”: N/A

then extract those which have N/A (or those with “set” etc).

Background

From the picture you can see the HTML table is not simple, it has a coloured background, some text is in one font, and other text is in a different font.

The table is not like

<table>
<tr><td>"cics:operator_classes"</td>...<td>N/A</td></tr>
</table>

and so relatively easy to parse.

It will be more like one long string containing

<td headers="ubase__tablebasesegment__entry__39 ubase__tablebasesegment__entry__2 " 
class="tdleft">
&nbsp;
</td>
<td headers="ubase__tablebasesegment__entry__39 ubase__tablebasesegment__entry__3 "
class="tdleft">
'Y'
</td>

Where &nbsp. is a non blank space.

Getting the source

Some browsers allow you do save the source of a page, and some do not.
I use Chrome to display and save the page.

You can use Python facilities to capture a web page.

My first attempt with Python

For me, the obvious approach was to use Python to process it. Unfortunately it complained about some of the HTML, so I spent some time using Linux utilities to remove the HTML causing problems. This got more and more complex, so I gave up. See Getting table data out of html – unsuccessfully.

Using Python again

I found Python has different parsers for HTML (and XML), and there was a better one than the one I had been using. The BeautifulSoup parser handled the complex HTML with no problems.

My entire program was (it is very short!)

from lxml import etree
from bs4 import BeautifulSoup

utf8_parser = etree.XMLParser(encoding='utf-8',recover=True)

# read the data from the file
file="/home/colin/Downloads/Dataset SEAR.html"
with open(file,"r") as myfile:
    data=myfile.read()

soup = BeautifulSoup(data,  'html.parser')
#nonBreakSpace = u'\xa0'
tables = soup.find_all(['table'])
for table in tables:
    tr = table.find_all("tr")
    for t in tr:
        line = list(t)
        if len(line) == 11:            
            print(line[1].get_text().strip(),line[7].get_text().strip())
        else: 
            print("len:",len(line),line)
quit()  

This does the following

  • file =… with open… data =… reads the data from a file. You could always use a URL and read directly from the internet.
  • tables = soup.find_all([‘table’]) extract the data within the specified tags. That is all the data between <table…>…</table> tags.
  • for table in tables: for each table in turn (it is lucky we do not have nested tables)
  • tr = table.find_all(“tr”) extract all the rows within the current table.
  • for t in tr: for each row
  • line = list(t) return all of the fields as a list

the variable line has fields like

' ', 
<td><code class="language-plaintext highlighter-rouge">"tme:roles"</code></td>,
' ',
<td><code class="language-plaintext highlighter-rouge">roles</code></td>,
' ',
<td><code class="language-plaintext highlighter-rouge">string</code></td>,
' ',
<td>N/A</td>,
' ',
<td><code class="language-plaintext highlighter-rouge">"extract"</code></td>,
' '
  • print(line[1].get_text().strip(),… takes the second line, and extracts the value from it ignoring any tags (“tme:roles”) and removes any leading or trailing blanks and prints it.
  • print(…line[7].get_text().strip()) takes the line, extracts the value (N/A), removes any leading or trailing blanks, and prints it.

This produced a list like

  • “base:global_auditing” N/A
  • “base:security_label” “set””delete”
  • “base:security_level” “set””delete

I was only interested in those with N/A, so I used

python3 ccpsear.py |grep N/A | sed 's.N/A.,.g '> mylist.py

which selected those with N/A, changed N/A to “,” and created a file mylist.py

Note:Some tables have non blank space in tables to represent and empty cell. These sometimes caused problems, so I had code to handle this.

nonBreakSpace = u'\xa0'
for each field:
if value == " ":
continue
if value == nonBreakSpace:
continue

Which certificates does Python install (PIP) use on z/OS?

Using the Python pip install … command I was getting error message

ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1019)
...
urllib.error.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1019)

On Discord, someone said the ca-certificates package seems to be missing on z/OS, I give a possible solution below.

How I fixed it see Upload the certificate from Linux.

I used Wireshark to monitor the web sites being used, and z/OS could not validate the certificate sent to it. It had a signing chain of 3 Certificate Authorities.
I tried capturing the certificates using openssl s_client…, but they didn’t work.

There is a pip option –trusted-host github.com –trusted-host 20.26.156.215 which says ignore the certificate validation from the specified sites. This did not work.

The pip command worked on Linux, so it was clearly a problem with certificates on z/OS.

I had zopen installed on my z/OS, and could issue the command

openssl s_client -connect github.com:443

This gave

subject=CN=github.com                                                                                                        
issuer=C=GB, ST=Greater Manchester, L=Salford, O=..., CN=...Secure Server CA          
---                                                                                                                          
No client certificate CA names sent                                                                                          
Peer signing digest: SHA256                                                                                                  
Peer signature type: ecdsa_secp256r1_sha256                                                                                  
Peer Temp Key: X25519, 253 bits                                                                                              
---                                                                                                                          
SSL handshake has read 3480 bytes and written 1605 bytes                                                                     
Verification error: unable to get local issuer certificate                                                                    

This was much quicker than trying to wait for Pip to process the request.

Where does Python expect the certificates to be?

I executed a small Python program to display the paths used

COLIN:/u/colin: >python
Python 3.12.3 ....on zos
Type "help", "copyright", "credits" or "license" for more information.

import _ssl
print(_ssl.get_default_verify_paths())
quit()

This gave

(‘SSL_CERT_FILE’, ‘/usr/local/ssl/cert.pem’, ‘SSL_CERT_DIR’, ‘/usr/local/ssl/certs’)

This was unexpected because I have openssl certificates in /usr/ssl/certs.

Upload the certificate from Linux

The Linux command

sudo apt reinstall ca-certificates

downloads the latest ca certificates into /etc/ssl/certs/ca-certificates.crt

I uploaded this to z/OS into /usr/local/ssl/cert.pem, for the Python code.

echo “aa” | openssl s_client -connect github.com:443 -verifyCAfile /etc/ssl/certs/ca-certificates.crt

worked. The certificate was verified.

I also uploaded it to /etc/ssl/certs/ca-certificates.crt for Python.

The openssl documentation

The openssl documentation discusses the location of the certificate store. The environment variable OPENSSLDIR locates where the certificate is stored, and how to download trusted certificates in a single file. Specifying OPENSSLDIR did not help.

Zowe: Getting data from Zowe

As part of an effort to trace the https traffic from Zowe, I found there are trace points you can enable.

You can get a list of these from a request like “https://10.1.1.2:7558/application/loggers&#8221;. In the browser it returns one long string like (my formatting)

{"levels":["OFF","ERROR","WARN","INFO","DEBUG","TRACE"],
"loggers":{"ROOT":{"configuredLevel":"INFO","effectiveLevel":"INFO"},
"_org":{"configuredLevel":null,"effectiveLevel":"INFO"},
"_org.springframework":{"configuredLevel":null,"effectiveLevel":"INFO"},
"_org.springframework.web":{"configuredLevel":null,"effectiveLevel":"INFO"},
...

Once you know the trace point, you can change it. See here.

Using https module

certs="--cert colinpaice.pem --cert-key colinpaice.key.pem"
verify="--verify no"
url="https://10.1.1.2:7558/application/loggers"
https GET ${url} $certs $verify

This displayed the data, nicely formatted. But if you pipe it, the next stage receives one long character string.

Using Python

#!/usr/bin/env python3

import ssl
import json
import sys
from http.client import HTTPConnection 
import requests
import urllib3
# trace the traffic flow
HTTPConnection.debuglevel = 1

my_header = {  'Accept' : 'application/json' }

urllib3.disable_warnings()
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)

certificate="colinpaice.pem"
key="colinpaice.key.pem"
cpcert=(certificate,key)
jar = requests.cookies.RequestsCookieJar()

s = requests.Session()
geturl="https://10.1.1.2:7558/application/loggers"

res = s.get(geturl,headers=my_header,cookies=jar,cert=cpcert,verify=False)

if res.status_code != 200:
    print("error code",res.status_code)
    sys.exit(8)

headers = res.headers

for h in headers:
    print(h,headers[h])

cookies = res.cookies.get_dict()
for c in cookies:
    print("cookie",c,cookies[c])

js = json.loads(res.text)
print("type",js.keys())
print(js['levels'])
print(js['groups'])
loggers = js['loggers']
for ll in loggers:
    print(ll,loggers[ll])

This prints out one line per item.

The command

python3  zloggers.py |grep HTTP

gives

...
org.apache.http {'configuredLevel': 'DEBUG', 'effectiveLevel': 'DEBUG'}
org.apache.http.conn {'configuredLevel': None, 'effectiveLevel': 'DEBUG'}
org.apache.http.conn.ssl {'configuredLevel': None, 'effectiveLevel': 'DEBUG'}
...