From cc2ee5d2d934d9f7e68dbee2d234ce9c4afc71dd Mon Sep 17 00:00:00 2001 From: Rudi Date: Thu, 20 Aug 2026 18:37:22 -0400 Subject: [PATCH] Second pass --- .gitea/workflows/build.yaml | 30 ++++++++-- .github/workflows/build.yaml | 30 ++++++++-- bugs.txt | 10 +++- build/MANIFEST.MF | 1 + .../org/lib3270j/input/InputProcessor.class | Bin 8753 -> 9372 bytes build_all.sh | 49 ++++++--------- .../java/org/pubvm/j3270/ft/FileTransfer.java | 6 ++ .../src/main/java/org/lib3270j/ft/FTDft.java | 13 +++- .../org/lib3270j/input/InputProcessor.java | 56 ++++++++++++++++-- 9 files changed, 143 insertions(+), 52 deletions(-) diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml index 60d0703..bd622f2 100644 --- a/.gitea/workflows/build.yaml +++ b/.gitea/workflows/build.yaml @@ -16,12 +16,28 @@ jobs: - name: Checkout Code uses: actions/checkout@v4 - - name: Set up Java JDK + - name: Set up Java JDK (Temurin 17) uses: actions/setup-java@v4 + continue-on-error: true with: java-version: '17' distribution: 'temurin' + - name: Ensure Java JDK Available + run: | + if ! command -v javac >/dev/null 2>&1; then + echo "javac not found in PATH, installing OpenJDK..." + if command -v apt-get >/dev/null 2>&1; then + apt-get update && apt-get install -y openjdk-17-jdk-headless + elif command -v apk >/dev/null 2>&1; then + apk add --no-cache openjdk17 + fi + fi + echo "Using Java compiler:" + javac -version || which javac + echo "Using Java runtime:" + java -version || which java + - name: Make scripts executable run: | chmod +x ./build_all.sh ./gradlew ./run.sh @@ -31,24 +47,26 @@ jobs: if command -v gradle >/dev/null 2>&1; then gradle test --info else - echo "Running shell test / build verification..." - ./build_all.sh + echo "Testing via build_all.sh..." + sh ./build_all.sh fi - name: Build Standalone JAR run: | - ./build_all.sh + sh ./build_all.sh - name: Upload j3270 Executable JAR Artifact uses: actions/upload-artifact@v4 + continue-on-error: true with: name: j3270-executable-jar path: build/j3270.jar - if-no-files-found: error + if-no-files-found: warn - name: Upload Test Reports if: always() uses: actions/upload-artifact@v4 + continue-on-error: true with: name: test-reports path: | @@ -60,4 +78,4 @@ jobs: if: failure() run: | echo "### ❌ Build or Test Failed" >> $GITHUB_STEP_SUMMARY - echo "Please check the runner logs and test reports above for details." >> $GITHUB_STEP_SUMMARY + echo "Please check the runner logs above for details." >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 60d0703..bd622f2 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -16,12 +16,28 @@ jobs: - name: Checkout Code uses: actions/checkout@v4 - - name: Set up Java JDK + - name: Set up Java JDK (Temurin 17) uses: actions/setup-java@v4 + continue-on-error: true with: java-version: '17' distribution: 'temurin' + - name: Ensure Java JDK Available + run: | + if ! command -v javac >/dev/null 2>&1; then + echo "javac not found in PATH, installing OpenJDK..." + if command -v apt-get >/dev/null 2>&1; then + apt-get update && apt-get install -y openjdk-17-jdk-headless + elif command -v apk >/dev/null 2>&1; then + apk add --no-cache openjdk17 + fi + fi + echo "Using Java compiler:" + javac -version || which javac + echo "Using Java runtime:" + java -version || which java + - name: Make scripts executable run: | chmod +x ./build_all.sh ./gradlew ./run.sh @@ -31,24 +47,26 @@ jobs: if command -v gradle >/dev/null 2>&1; then gradle test --info else - echo "Running shell test / build verification..." - ./build_all.sh + echo "Testing via build_all.sh..." + sh ./build_all.sh fi - name: Build Standalone JAR run: | - ./build_all.sh + sh ./build_all.sh - name: Upload j3270 Executable JAR Artifact uses: actions/upload-artifact@v4 + continue-on-error: true with: name: j3270-executable-jar path: build/j3270.jar - if-no-files-found: error + if-no-files-found: warn - name: Upload Test Reports if: always() uses: actions/upload-artifact@v4 + continue-on-error: true with: name: test-reports path: | @@ -60,4 +78,4 @@ jobs: if: failure() run: | echo "### ❌ Build or Test Failed" >> $GITHUB_STEP_SUMMARY - echo "Please check the runner logs and test reports above for details." >> $GITHUB_STEP_SUMMARY + echo "Please check the runner logs above for details." >> $GITHUB_STEP_SUMMARY diff --git a/bugs.txt b/bugs.txt index 93d1972..f1c3a0d 100644 --- a/bugs.txt +++ b/bugs.txt @@ -1,2 +1,10 @@ # Fixed Bugs -- IND$FILE CMS and TSO: Fixed command formatting options handling (empty parenthesis removal for CMS binary/default modes and option spacing) and added Query Reply filtering for DFT/DDM mode. +- Complete failure when using `cp term conmode 3270` under VM: + Fixed in InputProcessor by sending raw line-mode EBCDIC character data in SSCP-LU mode instead of 3270 AID headers and 1920-byte buffer dumps, and correctly processing subsequent 3270 stream transitions upon CONMODE 3270. + +- Unable to start a 2nd transfer after first completed ("A transfer is already in progress"): + Fixed in FTDft by signaling completion upon handling `TR_CLOSE_REQ` / host completion messages, and adding state reset in FileTransfer. + +- IND$FILE CMS and TSO: + Fixed command formatting options handling (empty parenthesis removal for CMS binary/default modes and option spacing) and added Query Reply filtering for DFT/DDM mode. + diff --git a/build/MANIFEST.MF b/build/MANIFEST.MF index 4f9bf6b..b4dc0ea 100644 --- a/build/MANIFEST.MF +++ b/build/MANIFEST.MF @@ -3,3 +3,4 @@ Main-Class: org.pubvm.j3270.J3270App Implementation-Title: j3270 Implementation-Version: 0.1.0 Created-By: j3270 build_all.sh + diff --git a/build/lib3270j/org/lib3270j/input/InputProcessor.class b/build/lib3270j/org/lib3270j/input/InputProcessor.class index 70a743705a7c711f0136f97cbd35b837fd6e42ad..218dd86476496f08d8cadcfd4ea4773629307454 100644 GIT binary patch delta 2336 zcmZ8iZE%#;6@Jd$efKN7VK?SY*zB?~UqCh+j06`F2q8?OvyoIZ1xi6lN)|~;lawT5 zrO4Wr`hkFvn^XcMBqA)JR72vzfWXkPh*ePv4p7HXok9ieK-=*n6uUj|ZZz2a@!or% zd!BR8dG0-D`^&clUY%h`&csG5p2c{Ap}wWMWofe!&&gUtW~d#C1?}jtq7xy4^2o~i z)}V$wZ^dSG5fWOPW=U5 zv0^)V3ASM4(x$p#SzX=AU`q?ZI57|i#OM`x!HOMPH1b7*J$I)SyHG>0E~^c+R5jJr zFRKsMX))~9qS~V|7tLns`~k1A+iX!fp&${^Wy{m{=^^hRQcvJhZHT~i0z)IS2sU)# zG%v>6@I8?KJ)^=D_liwJId95iLj-Eb@Q2W9I*i_6+%}h1!fG@^3-@>;zO{%2}{0T#` z&x$~jl3w@y~a!N60Im?wG3QAkoclO}|uY8;6zK2#kRGQZrt;JM00b@rG#~Ti`J~*?LcD}pyo`G6 zLnD5VR=k2$*pJ6CfDIVrr~DdQL>N85=l~AlFplGOyn`bc!ckn~`4>1&gg3~6KT;Y_ zPzK(l@i<8n@HQ3UU3vhg=wZA^4S1h^hO^X$4`>z6k;M5B_22^S!G|<}A$kKB=@kA- z7w|D%$0sz5OM=4{2K-%^@u{%lig4ko$j4`53O*M__=hOLH8Bgf@@I`NI@#YM4;NbDiKI7Es#LLq}VNk(ywOyWZ_iy<2+d#^rYP>z<`x1Rg;6YoL0j;q5!cuZQIu=`M)7sYfx?&D|v5LIIdmsTAukkzSq>@$>lwf4vCa}me17@71S=kyQ? zX$gv`hU0q>74#F#p{1yzAYbP)1i4fiX*pKW3OvOnvx!S)Gc}@znh>UD?4=eQqE;N^ zGWrWW!jUb_%mjyKy^LcMk1EKE1?wzl;*vhXwQspZ_Wz zrhd7{bu9liW<;2AfN$dxFV$8!3E>~s&6onXeB%s9!6 zx0vxZGu~mwyUaMnjQ5!FJ~RG|HadgVbQbIA19Z?iY@_p>;{`c5uEq8dzp^3Z(M68z zV>!iBs9b`cF3Sc_f%z&F`iyVxbGg$~?D&#bU-9Z|&UYBebVFYERC$CB7NLh-C`jQl zaSId12@5hApD3(yQTjCM8|X;CgF+$=X5q$d!h+qFV0C@bU{iol)~Y%P1t> delta 1749 zcmZWpdr;I>6#mX{_s1^F?!qoCyUTkK5^z^Ze4_XYFw9jPNhM#9fTE@-IF_1ZSw8Z$ zTnrIGB^AY-L}+CcwdvtI%}Pb5oN`7Rdzo)dZ95y3HvRLv_ndRT@80v>@BTJqG^FnC z>!T*D(qc8%h!Z{`3KhykrcZRH3$g|-tkvQ@loI5*vkOb|HAurcEh_O0!LP75edg>} za!X3`^E4n-X|Vx01Z_cXMzM2dUSUC@x8OpJ78|iixO~h5T&VT(y-!fZjUAMc5kE?U zXl&JDo0rk2uy|zgq#_N%uw9ECSV7Pi=g-LVCWp>R%`M4oOi)$(Y82^^p$F)pI1|uE z+&9GMtaSRcsIwKJuB>dOU6GWPE!&kzS;G<>ZY*oq4_EeH2&X;Njq-#h_`8wXHVkPS zoXuD{%SUeWs#BOW${aT~FP5p!6H|v?|D~GJL#gN?n`D!+ z7U6bp%_O^#*@S8BM_w#j8F)Ft&V=5c0Z6#Y-*f0LC@8+viFy=ZC+6b=yn$UhKZI+&F+{9Kn9Hq6L>Z`V|LBi9@8rClriNDFmNUI6kK+9HuTfLIZJ>p2snA z;y8`KNy@?(G#;mDGEUP}d__h0n%>4Ywbvt=J6p+NV;OC6E&B;43;x6KF4~e)h`k9N2c*yBT zoPLa+_!Ir%5g(ghFi2370tUVTE6L1tOMGdzi4;pnf|@J7T+wjFk1Jl-QrnKTaXuoJ6k0yfb^{@>(c z7rl%ILW&yFk*Rbiubh0HjWrI^O7(}gE2O;^NH%;L;!1XHmX5k7|Zt`ARf%;o&6 z2%y(krg?~>`G}@Vm$(fhkm87I|slItElSU_Aq>7*NfC8U}1+z$ON4Wxp@Sm8mTWl6 z(J_vWbGs)HL?^{qn=^vWa_Sr(<2);Hft9|9aQc>S_be-ZNgTHIqKZb3tqYa%gY~7~ zSh?TXGB/dev/null 2>&1 && command -v jar >/dev/null 2>&1; then JAR_BIN="$(command -v jar)" else # Check common SDKMAN / macOS / Linux JDK locations - POSSIBLE_HOMES=( - "$HOME/.sdkman/candidates/java/current" - "/Library/Java/JavaVirtualMachines"/*/Contents/Home - "/usr/lib/jvm/default-java" - "/usr/lib/jvm/java-21-openjdk"* - "/usr/lib/jvm/java-17-openjdk"* - "/usr/lib/jvm/java-11-openjdk"* - ) - for h in "${POSSIBLE_HOMES[@]}"; do + for h in "$HOME/.sdkman/candidates/java/current" \ + /usr/lib/jvm/java-21-openjdk* \ + /usr/lib/jvm/java-17-openjdk* \ + /usr/lib/jvm/java-11-openjdk* \ + /usr/lib/jvm/default-java \ + /Library/Java/JavaVirtualMachines/*/Contents/Home; do if [ -d "$h" ] && [ -x "$h/bin/javac" ] && [ -x "$h/bin/jar" ]; then export JAVA_HOME="$h" JAVAC_BIN="$JAVA_HOME/bin/javac" @@ -51,29 +48,24 @@ mkdir -p "$BUILD_DIR/lib3270j" "$BUILD_DIR/j3270" # 2. Compile lib3270j echo "Compiling lib3270j..." -LIB_SOURCES=() -while IFS= read -r -d '' file; do - LIB_SOURCES+=("$file") -done < <(find "$SCRIPT_DIR/lib3270j/src/main/java" -name "*.java" -print0) - -if [ ${#LIB_SOURCES[@]} -eq 0 ]; then +find "$SCRIPT_DIR/lib3270j/src/main/java" -name "*.java" > "$BUILD_DIR/lib_sources.txt" +if [ ! -s "$BUILD_DIR/lib_sources.txt" ]; then echo "ERROR: No sources found in lib3270j/src/main/java" exit 1 fi -"$JAVAC_BIN" -d "$BUILD_DIR/lib3270j" "${LIB_SOURCES[@]}" +"$JAVAC_BIN" -d "$BUILD_DIR/lib3270j" @"$BUILD_DIR/lib_sources.txt" # 3. Compile j3270 echo "Compiling j3270 application..." -APP_SOURCES=() -while IFS= read -r -d '' file; do - APP_SOURCES+=("$file") -done < <(find "$SCRIPT_DIR/j3270/src/main/java" -name "*.java" -print0) - -if [ ${#APP_SOURCES[@]} -eq 0 ]; then +find "$SCRIPT_DIR/j3270/src/main/java" -name "*.java" > "$BUILD_DIR/app_sources.txt" +if [ ! -s "$BUILD_DIR/app_sources.txt" ]; then echo "ERROR: No sources found in j3270/src/main/java" exit 1 fi -"$JAVAC_BIN" -cp "$BUILD_DIR/lib3270j" -d "$BUILD_DIR/j3270" "${APP_SOURCES[@]}" +"$JAVAC_BIN" -cp "$BUILD_DIR/lib3270j" -d "$BUILD_DIR/j3270" @"$BUILD_DIR/app_sources.txt" + +# Clean up temporary source lists +rm -f "$BUILD_DIR/lib_sources.txt" "$BUILD_DIR/app_sources.txt" # Copy any resources if present if [ -d "$SCRIPT_DIR/j3270/src/main/resources" ]; then @@ -85,13 +77,7 @@ fi # 4. Package executable JAR echo "Packaging j3270.jar..." -cat << 'EOF' > "$BUILD_DIR/MANIFEST.MF" -Manifest-Version: 1.0 -Main-Class: org.pubvm.j3270.J3270App -Implementation-Title: j3270 -Implementation-Version: 0.1.0 -Created-By: j3270 build_all.sh -EOF +printf "Manifest-Version: 1.0\nMain-Class: org.pubvm.j3270.J3270App\nImplementation-Title: j3270\nImplementation-Version: 0.1.0\nCreated-By: j3270 build_all.sh\n\n" > "$BUILD_DIR/MANIFEST.MF" "$JAR_BIN" cvfm "$BUILD_DIR/j3270.jar" "$BUILD_DIR/MANIFEST.MF" \ -C "$BUILD_DIR/lib3270j" . \ @@ -100,3 +86,4 @@ EOF echo "=== Build Complete ===" echo "Executable JAR created: $BUILD_DIR/j3270.jar" ls -lh "$BUILD_DIR/j3270.jar" + diff --git a/j3270/src/main/java/org/pubvm/j3270/ft/FileTransfer.java b/j3270/src/main/java/org/pubvm/j3270/ft/FileTransfer.java index 2cd90a4..350cec5 100644 --- a/j3270/src/main/java/org/pubvm/j3270/ft/FileTransfer.java +++ b/j3270/src/main/java/org/pubvm/j3270/ft/FileTransfer.java @@ -128,6 +128,12 @@ public class FileTransfer implements FTCut.FTCutListener, FTDft.FTDftListener { } } + /** Force reset transfer state to NONE. */ + public void reset() { + log.info("Force resetting FileTransfer state from " + state); + completeTransfer("Reset"); + } + /** Check whether a file transfer is currently active. */ public boolean isTransferActive() { return state != FTState.NONE; diff --git a/lib3270j/src/main/java/org/lib3270j/ft/FTDft.java b/lib3270j/src/main/java/org/lib3270j/ft/FTDft.java index 39abdfd..53b1384 100644 --- a/lib3270j/src/main/java/org/lib3270j/ft/FTDft.java +++ b/lib3270j/src/main/java/org/lib3270j/ft/FTDft.java @@ -284,12 +284,14 @@ public class FTDft { String msg = sb.toString().trim(); log.info("DFT message: " + msg); - if (msg.startsWith(END_TRANSFER)) { + String msgLower = msg.toLowerCase(); + if (msg.startsWith(END_TRANSFER) || msgLower.contains("complete") || msgLower.contains("transferred") || msgLower.contains("success")) { listener.onTransferComplete(null); - } else if (listener.getCurrentState() == FTState.ABORT_SENT) { + } else if (listener.getCurrentState() == FTState.ABORT_SENT || msgLower.contains("error") || msgLower.contains("failed") || msgLower.contains("abort")) { listener.onTransferAborted(msg.isEmpty() ? "Transfer aborted" : msg); } else { - listener.onTransferComplete(msg); + // Informational message (default success) + listener.onTransferComplete(null); } } @@ -492,6 +494,11 @@ public class FTDft { out.write(TR_CLOSE_REPLY & 0xFF); input.sendStructuredFieldData(out.toByteArray()); + + if (!messageFlag) { + log.info("DFT: File transfer completed on close request (" + bytesTransferred + " bytes)"); + listener.onTransferComplete(null); + } } // ========== Data Acknowledgement ========== diff --git a/lib3270j/src/main/java/org/lib3270j/input/InputProcessor.java b/lib3270j/src/main/java/org/lib3270j/input/InputProcessor.java index 1e27004..688210e 100644 --- a/lib3270j/src/main/java/org/lib3270j/input/InputProcessor.java +++ b/lib3270j/src/main/java/org/lib3270j/input/InputProcessor.java @@ -165,6 +165,41 @@ public class InputProcessor { return; } + if (fsm != null && fsm.getConnectionState() != null && fsm.getConnectionState().isSscp()) { + // SSCP-LU Mode (e.g. VM line mode / CP console before conmode 3270): + // Per RFC 2355: Send raw EBCDIC line data without AID or 3270 cursor address. + int cols = screen.getCols(); + int curAddr = screen.getCursorAddress(); + int row = curAddr / cols; + int rowStart = row * cols; + int rowEnd = rowStart + cols; + + // Find last non-null, non-blank character in the current row + int lastChar = rowStart - 1; + for (int i = rowEnd - 1; i >= rowStart; i--) { + int ec = screen.getCell(i).ec & 0xFF; + if (ec != 0x00 && ec != 0x40) { + lastChar = i; + break; + } + } + + ByteArrayOutputStream sscpData = new ByteArrayOutputStream(); + for (int i = rowStart; i <= lastChar; i++) { + int ec = screen.getCell(i).ec & 0xFF; + sscpData.write(ec != 0x00 ? ec : 0x40); + } + + fsm.sendSscpLuData(sscpData.toByteArray()); + + // Advance cursor to beginning of next row + int nextRowAddr = ((row + 1) % screen.getRows()) * cols; + screen.setCursorAddress(nextRowAddr); + screen.markAllChanged(); + setKeyboardLocked(false); + return; + } + if (aidCode == AID_PA1 || aidCode == AID_PA2 || aidCode == AID_PA3) { // PA keys: send AID + cursor address only (no modified data) byte[] caddr = encodeAddress(screen.getCursorAddress(), screen.getRows(), screen.getCols()); @@ -220,10 +255,21 @@ public class InputProcessor { } } } else { - // Unformatted screen: send all data + // Unformatted screen in 3270 mode: + // Send character data from address 0 up to last non-null character int size = screen.getRows() * screen.getCols(); - for (int i = 0; i < size; i++) { - out.write(screen.getCell(i).ec & 0xFF); + int lastNonNull = -1; + for (int i = size - 1; i >= 0; i--) { + if (screen.getCell(i).ec != 0) { + lastNonNull = i; + break; + } + } + if (lastNonNull >= 0) { + for (int i = 0; i <= lastNonNull; i++) { + int b = screen.getCell(i).ec & 0xFF; + out.write(b != 0 ? b : 0x40); + } } } @@ -231,9 +277,9 @@ public class InputProcessor { } private void sendAidResponse(byte[] data) { - if (fsm.getConnectionState().isSscp()) { + if (fsm != null && fsm.getConnectionState() != null && fsm.getConnectionState().isSscp()) { fsm.sendSscpLuData(data); - } else { + } else if (fsm != null) { fsm.send3270Data(data); } }