Skip to content

Commit b902742

Browse files
rexeminddimatos
andauthored
[1.5.0-beta.1] [Bug] [zos_copy] Ported bugfix for newlines in record length (#639)
* Ported bugfix for #599 * Added changelog from 1.4.1 * Too many blanks lines Signed-off-by: ddimatos <[email protected]> --------- Signed-off-by: ddimatos <[email protected]> Co-authored-by: ddimatos <[email protected]>
1 parent 2599d7f commit b902742

File tree

4 files changed

+166
-8
lines changed

4 files changed

+166
-8
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
bugfixes:
2+
- zos_copy - fixes a bug where the computed record length for a new destination
3+
dataset would include newline characters.
4+
(https://github.com/ansible-collections/ibm_zos_core/pull/620)

plugins/module_utils/encode.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,32 @@ def mvs_convert_encoding(
453453

454454
return convert_rc
455455

456+
def uss_file_tag(self, file_path):
457+
"""Returns the current tag set for a file.
458+
Arguments:
459+
file_path {str} -- USS path to the file.
460+
Returns:
461+
str -- Current tag set for the file, as returned by 'ls -T'
462+
None -- If the file does not exist or the command fails.
463+
"""
464+
if not os.path.exists(file_path):
465+
return None
466+
467+
try:
468+
tag_cmd = "ls -T {0}".format(file_path)
469+
rc, stdout, stderr = self.module.run_command(tag_cmd)
470+
471+
if rc != 0:
472+
return None
473+
474+
# The output from 'ls -T' should be like this:
475+
# t IBM-037 T=on ansible-zos-copy-payload-D230123-T123818
476+
# The second item from the split should be the tag.
477+
ls_parts = stdout.split()
478+
return ls_parts[1]
479+
except Exception as err:
480+
return None
481+
456482

457483
class EncodeError(Exception):
458484
def __init__(self, message):

plugins/modules/zos_copy.py

Lines changed: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -732,7 +732,6 @@ def copy_to_seq(
732732
transferred data to
733733
conv_path {str} -- Path to the converted source file
734734
dest {str} -- Name of destination data set
735-
src_ds_type {str} -- The type of source
736735
"""
737736
new_src = conv_path or temp_path or src
738737
copy_args = dict()
@@ -916,6 +915,58 @@ def _merge_hash(self, *args):
916915
result.update(arg)
917916
return result
918917

918+
def file_has_crlf_endings(self, src):
919+
"""Reads src as a binary file and checks whether it uses CRLF or LF
920+
line endings.
921+
922+
Arguments:
923+
src {str} -- Path to a USS file
924+
925+
Returns:
926+
{bool} -- True if the file uses CRLF endings, False if it uses LF
927+
ones.
928+
"""
929+
with open(src, "rb") as src_file:
930+
# readline() will read until it finds a \n.
931+
content = src_file.readline()
932+
933+
# In EBCDIC, \r\n are bytes 0d and 15, respectively.
934+
if content.endswith(b'\x0d\x15'):
935+
return True
936+
else:
937+
return False
938+
939+
def create_temp_with_lf_endings(self, src):
940+
"""Creates a temporary file with the same content as src but without
941+
carriage returns.
942+
943+
Arguments:
944+
src {str} -- Path to a USS source file.
945+
946+
Raises:
947+
CopyOperationError: If the conversion fails.
948+
949+
Returns:
950+
{str} -- Path to the temporary file created.
951+
"""
952+
try:
953+
fd, converted_src = tempfile.mkstemp()
954+
os.close(fd)
955+
956+
with open(converted_src, "wb") as converted_file:
957+
with open(src, "rb") as src_file:
958+
current_line = src_file.read()
959+
converted_file.write(current_line.replace(b'\x0d', b''))
960+
961+
self._tag_file_encoding(converted_src, encode.Defaults.DEFAULT_EBCDIC_MVS_CHARSET)
962+
963+
return converted_src
964+
except Exception as err:
965+
raise CopyOperationError(
966+
msg="Error while trying to convert EOL sequence for source.",
967+
stderr=to_native(err)
968+
)
969+
919970

920971
class USSCopyHandler(CopyHandler):
921972
def __init__(
@@ -1407,8 +1458,10 @@ def get_file_record_length(file):
14071458
current_line = src_file.readline()
14081459

14091460
while current_line:
1410-
if len(current_line) > max_line_length:
1411-
max_line_length = len(current_line)
1461+
line_length = len(current_line.rstrip("\n\r"))
1462+
1463+
if line_length > max_line_length:
1464+
max_line_length = line_length
14121465

14131466
current_line = src_file.readline()
14141467

@@ -2316,6 +2369,37 @@ def run_module(module, arg_def):
23162369
# Copy to sequential data set (PS / SEQ)
23172370
# ---------------------------------------------------------------------
23182371
elif dest_ds_type in data_set.DataSet.MVS_SEQ:
2372+
if src_ds_type == "USS" and not is_binary:
2373+
# Before copying into the destination dataset, we'll make sure that
2374+
# the source file doesn't contain any carriage returns that would
2375+
# result in empty records in the destination.
2376+
# Due to the differences between encodings, we'll normalize to IBM-037
2377+
# before checking the EOL sequence.
2378+
new_src = conv_path or temp_path or src
2379+
enc_utils = encode.EncodeUtils()
2380+
src_tag = enc_utils.uss_file_tag(new_src)
2381+
2382+
if src_tag == "untagged":
2383+
src_tag = encode.Defaults.DEFAULT_EBCDIC_USS_CHARSET
2384+
2385+
if src_tag not in encode.Defaults.DEFAULT_EBCDIC_MVS_CHARSET:
2386+
fd, converted_src = tempfile.mkstemp()
2387+
os.close(fd)
2388+
2389+
enc_utils.uss_convert_encoding(
2390+
new_src,
2391+
converted_src,
2392+
src_tag,
2393+
encode.Defaults.DEFAULT_EBCDIC_MVS_CHARSET
2394+
)
2395+
copy_handler._tag_file_encoding(converted_src, encode.Defaults.DEFAULT_EBCDIC_MVS_CHARSET)
2396+
new_src = converted_src
2397+
2398+
if copy_handler.file_has_crlf_endings(new_src):
2399+
new_src = copy_handler.create_temp_with_lf_endings(new_src)
2400+
2401+
conv_path = new_src
2402+
23192403
copy_handler.copy_to_seq(
23202404
src,
23212405
temp_path,

tests/functional/modules/test_zos_copy_func.py

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -927,6 +927,55 @@ def test_copy_non_existent_file_fails(ansible_zos_module, is_remote):
927927
assert "does not exist" in result.get("msg")
928928

929929

930+
@pytest.mark.uss
931+
@pytest.mark.seq
932+
def test_copy_file_record_length_to_sequential_data_set(ansible_zos_module):
933+
hosts = ansible_zos_module
934+
dest = "USER.TEST.SEQ.FUNCTEST"
935+
936+
fd, src = tempfile.mkstemp()
937+
os.close(fd)
938+
with open(src, "w") as infile:
939+
infile.write(DUMMY_DATA)
940+
941+
try:
942+
hosts.all.zos_data_set(name=dest, state="absent")
943+
944+
copy_result = hosts.all.zos_copy(
945+
src=src,
946+
dest=dest,
947+
remote_src=False,
948+
is_binary=False
949+
)
950+
951+
verify_copy = hosts.all.shell(
952+
cmd="cat \"//'{0}'\" > /dev/null 2>/dev/null".format(dest),
953+
executable=SHELL_EXECUTABLE,
954+
)
955+
956+
verify_recl = hosts.all.shell(
957+
cmd="dls -l {0}".format(dest),
958+
executable=SHELL_EXECUTABLE,
959+
)
960+
961+
for cp_res in copy_result.contacted.values():
962+
assert cp_res.get("msg") is None
963+
assert cp_res.get("changed") is True
964+
assert cp_res.get("dest") == dest
965+
for v_cp in verify_copy.contacted.values():
966+
assert v_cp.get("rc") == 0
967+
for v_recl in verify_recl.contacted.values():
968+
assert v_recl.get("rc") == 0
969+
stdout = v_recl.get("stdout").split()
970+
assert len(stdout) == 5
971+
assert stdout[1] == "PS"
972+
assert stdout[2] == "FB"
973+
assert stdout[3] == "31"
974+
finally:
975+
hosts.all.zos_data_set(name=dest, state="absent")
976+
os.remove(src)
977+
978+
930979
@pytest.mark.uss
931980
@pytest.mark.seq
932981
@pytest.mark.parametrize("src", [
@@ -964,11 +1013,6 @@ def test_copy_file_to_non_existing_sequential_data_set(ansible_zos_module, src):
9641013
finally:
9651014
hosts.all.zos_data_set(name=dest, state="absent")
9661015

967-
if src["is_file"]:
968-
copy_result = hosts.all.zos_copy(src=src["src"], dest=dest, remote_src=src["is_remote"], is_binary=src["is_binary"])
969-
else:
970-
copy_result = hosts.all.zos_copy(content=src["src"], dest=dest, remote_src=src["is_remote"], is_binary=src["is_binary"])
971-
9721016

9731017
@pytest.mark.uss
9741018
@pytest.mark.seq

0 commit comments

Comments
 (0)