├── .gitignore ├── COPYING ├── MVS ├── $$$INDEX ├── $BUILD ├── $COPYING ├── $DEBUG ├── $RUN ├── CTCSERV ├── DSLIST ├── MBRLIST ├── READ ├── SUBMIT └── WRITEDS ├── README.md └── ctcserver ├── api.go ├── config.go ├── config.json.sample ├── go.mod ├── go.sum ├── internal ├── ctc │ ├── ctc.go │ └── ebcdic.go └── ctcapi │ ├── commands.go │ └── ctcapi.go └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | ctcserver/ctcserver 3 | ctcserver/config.json 4 | 5 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /MVS/$$$INDEX: -------------------------------------------------------------------------------- 1 | $$$INDEX - This index of members. 2 | $BUILD - (jcl) Procedures to build and link the program. 3 | $COPYING - (txt) GNU General Public License. 4 | $DEBUG - (jcl) Procedure to run the program under MVS DDT debugger. 5 | $RUN - (jcl) Procedure to run the program. 6 | CTCSERV - (asm) Main program entry point. 7 | DSLIST - (asm) DSLIST (cmd 0x01) implementation. 8 | MBRLIST - (asm) MBRLIST (cmd 0x02) implementation. 9 | READ - (asm) READ (cmd 0x03) implementation. 10 | SUBMIT - (asm) SUBMIT (cmd 0x04) implementation. 11 | WRITEDS - (asm) WRITEDS (cmd 0x05) implementation. 12 | -------------------------------------------------------------------------------- /MVS/$BUILD: -------------------------------------------------------------------------------- 1 | //MWILSONS JOB CLASS=A,MSGCLASS=X 2 | //ASM PROC 3 | //ASM EXEC PGM=IFOX00,REGION=512K,PARM='LIST,LOAD,NODECK' 4 | //SYSLIB DD DISP=SHR,DSN=SYS1.MACLIB 5 | // DD DISP=SHR,DSN=SYS1.AMODGEN 6 | //SYSUT1 DD DSN=&&SYSUT1,UNIT=SYSDA,SPACE=(1700,(600,100)) 7 | //SYSUT2 DD DSN=&&SYSUT2,UNIT=SYSDA,SPACE=(1700,(300,50)) 8 | //SYSUT3 DD DSN=&&SYSUT3,UNIT=SYSDA,SPACE=(1700,(300,50)) 9 | //SYSPRINT DD SYSOUT=* 10 | //SYSPUNCH DD DUMMY 11 | //SYSGO DD DSN=&&OBJSET(&MODNAME),UNIT=SYSDA, 12 | // SPACE=(80,(200,50,4)), 13 | // DISP=(MOD,PASS) 14 | //SYSIN DD DSN=MWILSON.CTCSERV(&MODNAME),DISP=(SHR) 15 | // PEND 16 | //* 17 | //CTCSERV EXEC ASM,MODNAME=CTCSERV 18 | //DSLIST EXEC ASM,MODNAME=DSLIST 19 | //MBRLIST EXEC ASM,MODNAME=MBRLIST 20 | //READ EXEC ASM,MODNAME=READ 21 | //SUBMIT EXEC ASM,MODNAME=SUBMIT 22 | //WRITE EXEC ASM,MODNAME=WRITEDS 23 | //* 24 | //LKED EXEC PGM=IEWL,PARM=(XREF,LET,LIST,NCAL),REGION=512K, 25 | // COND=(0,NE) 26 | //OBJECTS DD DSN=&&OBJSET,DISP=(OLD,DELETE) 27 | //SYSLIN DD * 28 | ENTRY CTCSERV 29 | INCLUDE OBJECTS(CTCSERV,DSLIST,MBRLIST,READ,SUBMIT,WRITEDS) 30 | //SYSLMOD DD DISP=SHR,DSN=MWILSON.LOAD(CTCSERV) 31 | //SYSUT1 DD DSN=&&SYSUT1,UNIT=SYSDA,SPACE=(1024,(50,20)) 32 | //SYSPRINT DD SYSOUT=* 33 | -------------------------------------------------------------------------------- /MVS/$DEBUG: -------------------------------------------------------------------------------- 1 | //MWILSONS JOB CLASS=A,MSGCLASS=X,TIME=1440 2 | //DEBUG EXEC PGM=MVSDDT,PARM=CTCSERV,REGION=512K 3 | //STEPLIB DD DISP=SHR,DSN=MWILSON.LOAD 4 | //CTCCMD DD UNIT=502,DISP=OLD 5 | //CTCDATA DD UNIT=503,DISP=OLD 6 | //SYSPRINT DD SYSOUT=* 7 | //SYSABEND DD SYSOUT=* 8 | //#DDTCMD DD UNIT=500 9 | //#DDTDATA DD UNIT=501 10 | // 11 | -------------------------------------------------------------------------------- /MVS/$RUN: -------------------------------------------------------------------------------- 1 | //MWILSONS JOB CLASS=A,MSGCLASS=X,MSGLEVEL=(1,0),TIME=1440 2 | //GO EXEC PGM=CTCSERV,REGION=512K 3 | //STEPLIB DD DISP=SHR,DSN=MWILSON.LOAD 4 | //CTCCMD DD UNIT=502,DISP=OLD 5 | //CTCDATA DD UNIT=503,DISP=OLD 6 | //SYSPRINT DD SYSOUT=* 7 | //SYSABEND DD SYSOUT=* 8 | // 9 | -------------------------------------------------------------------------------- /MVS/CTCSERV: -------------------------------------------------------------------------------- 1 | *********************************************************************** 2 | * MVS SERVICES OVER CTC - MAIN ENTRY POINT * 3 | * * 4 | * Copyright 2022-2023 Matthew R. Wilson * 5 | * * 6 | * This file is part of CTC Mainframe API. CTC Mainframe API is free * 7 | * software: you can redistribute it and/or modify it under the terms * 8 | * of the GNU General Public License as published by the Free Software * 9 | * Foundation, either version 3 of the license, or (at your option) * 10 | * any later version. * 11 | *********************************************************************** 12 | * 13 | PRINT GEN 14 | CTCSERV CSECT 15 | SAVE (14,12),,* Save caller's registers 16 | BALR R12,0 Load current address 17 | USING *,R12 Establish addressability 18 | ST R13,SAVEAREA+4 Store caller's savearea address 19 | LA R13,SAVEAREA Load address of our savearea 20 | OPEN (CTCCMD) 21 | OPEN (CTCDATA) 22 | *** 23 | *** SENSLOOP is the main loop -- we issue a SENSE command against the 24 | *** CTC adapter to read the command byte of the other side of 25 | *** the connection. If we get back a CONTROL byte, that is the 26 | *** other side's indication to us that there is data ready to 27 | *** by sent to us. Our SENSE will clear the other side's 28 | *** CONTROL command, and then it will perform a WRITE which we 29 | *** will be READ. After READing, we return back to SENSLOOP to 30 | *** wait for the next CONTROL command to arrive. 31 | *** 32 | SENSLOOP ORG * 33 | LA R1,CCWSENSE Load address of CCWSENSE to R1 34 | ST R1,IOBCCWAD Point our IOB to our SENSE CCW 35 | LA R1,CTCCMD Load address of the CMD DCB to R1 36 | ST R1,IOBDCBAD Point our IOB to our DCB 37 | XC EXCPECB,EXCPECB Clear EXCPECB 38 | EXCP IOB Run the SENSE command 39 | WAIT ECB=EXCPECB 40 | CLI EXCPECB,X'7F' Successful completion? 41 | BNE BADSENSE 42 | CLI INREC,CONTROL Did we sense a CONTROL command? 43 | BNE SLEEP No, sleep and retry 44 | LA R1,CCWRDCMD Load address of CCWRDCMD to R1 45 | ST R1,IOBCCWAD Point our IOB to our READ CCW 46 | XC EXCPECB,EXCPECB Clear EXCPECB 47 | EXCP IOB Run the READ command 48 | WAIT ECB=EXCPECB 49 | CLI EXCPECB,X'7F' Successful complete? 50 | BNE BADREAD No, report the error and quit 51 | CHK01 CLI CMDOPCD,X'01' Did we receive the DSLIST command? 52 | BNE CHK02 No, go on to next check 53 | CALL DSLIST,(CTCCMD,CTCDATA,CMDIN) Yes, do it 54 | B SENSLOOP 55 | CHK02 CLI CMDOPCD,X'02' Did we receive the MBRLIST command? 56 | BNE CHK03 No, go on to next check 57 | CALL MBRLIST,(CTCCMD,CTCDATA,CMDIN) Yes, do it 58 | B SENSLOOP 59 | CHK03 CLI CMDOPCD,X'03' Did we receive the READ command? 60 | BNE CHK04 No, go on to next check 61 | CALL READ,(CTCCMD,CTCDATA,CMDIN) Yes, do it 62 | B SENSLOOP 63 | CHK04 CLI CMDOPCD,X'04' Did we receive the SUBMIT command? 64 | BNE CHK05 No, go to next check 65 | CALL SUBMIT,(CTCCMD,CTCDATA,CMDIN) Yes, do it 66 | B SENSLOOP 67 | CHK05 CLI CMDOPCD,X'05' Did we receive the WRITE command? 68 | BNE CHKFF No, go to next check 69 | CALL WRITEDS,(CTCCMD,CTCDATA,CMDIN) Yes, do it 70 | B SENSLOOP 71 | CHKFF CLI CMDOPCD,X'FF' Did we receive the quit command? 72 | BE QUITCMD Yes 73 | * TODO: Send an "unknown command" response to reset client 74 | WTO 'CTCSERV: Unknown command received' 75 | B SENSLOOP 76 | * If we get an unsuccessful completion waiting for a SENSE, just try 77 | * closing the CTC devices to reset them and try again. 78 | BADSENSE CLOSE (CTCCMD) 79 | CLOSE (CTCDATA) 80 | OPEN (CTCCMD) 81 | OPEN (CTCDATA) 82 | B SLEEP 83 | SLEEP STIMER WAIT,BINTVL=SLPTIM Sleep for 250ms 84 | B SENSLOOP ...then retry sense 85 | BADREAD WTO 'SNS: READ failed: didn''t receive 7F' 86 | B FINISH 87 | ********************************************************************** 88 | * COMMAND: QUIT (0xFF) * 89 | * No parameters. * 90 | ********************************************************************** 91 | QUITCMD B FINISH 92 | ********************************************************************** 93 | FINISH CLOSE (CTCCMD) 94 | CLOSE (CTCDATA) 95 | * Return to caller 96 | QUIT L R13,SAVEAREA+4 Restore address of caller's save area 97 | RETURN (14,12),RC=0 98 | * 99 | ********************************************************************** 100 | ********************************************************************** 101 | * 102 | ***** Main Loop Channel Programs 103 | CCWRDCMD CCW READ,CMDIN,SLI,CMDINLEN 104 | CCWSENSE CCW SENSE,INREC,SLI,1 105 | WRITE EQU X'01' 106 | READ EQU X'02' 107 | CONTROL EQU X'07' 108 | SENSE EQU X'14' 109 | SLI EQU X'20' 110 | CC EQU X'40' 111 | * EXCP IOB 112 | IOB DS 0F 113 | IOBFLAGS DC XL2'0000' 114 | IOBSENSE DC XL2'0000' 115 | IOBECBAD DC A(EXCPECB) 116 | IOBCSW DC A(0) 117 | IOBCSWFL DC XL2'0000' 118 | IOBRESDL DC H'00' 119 | IOBCCWAD DC A(CCWRDCMD) 120 | IOBDCBAD DC A(CTCCMD) 121 | DC F'0' 122 | DC F'0' 123 | SLPTIM DC F'25' 124 | EXCPECB DS F 125 | INREC DS CL5 126 | ***** Input command area 127 | CMDIN DS 0F 128 | CMDOPCD DS C 129 | CMDPLEN DS XL2 130 | CMDPARM DS CL255 131 | CMDINLEN EQU *-CMDIN 132 | DS 0F 133 | *********************************************************************** 134 | SAVEAREA DS 18F 135 | CTCCMD DCB DDNAME=CTCCMD,MACRF=(E),DEVD=DA 136 | CTCDATA DCB DDNAME=CTCDATA,MACRF=(E),DEVD=DA 137 | SYSPRINT DCB DSORG=PS,MACRF=PM,DDNAME=SYSPRINT, + 138 | BLKSIZE=132,LRECL=132,RECFM=F 139 | ********************************************************************** 140 | * Register symbols * 141 | ********************************************************************** 142 | R0 EQU 0 143 | R1 EQU 1 144 | R2 EQU 2 145 | R3 EQU 3 146 | R4 EQU 4 147 | R5 EQU 5 148 | R6 EQU 6 149 | R7 EQU 7 150 | R8 EQU 8 151 | R9 EQU 9 152 | R10 EQU 10 153 | R11 EQU 11 154 | R12 EQU 12 155 | R13 EQU 13 156 | R14 EQU 14 157 | R15 EQU 15 158 | END CTCSERV 159 | -------------------------------------------------------------------------------- /MVS/DSLIST: -------------------------------------------------------------------------------- 1 | *********************************************************************** 2 | * MVS SERVICES OVER CTC - DSLIST Command (0x01) * 3 | * * 4 | * Copyright 2022 Matthew R. Wilson * 5 | * * 6 | * This file is part of CTC Mainframe API. CTC Mainframe API is free * 7 | * software: you can redistribute it and/or modify it under the terms * 8 | * of the GNU General Public License as published by the Free Software * 9 | * Foundation, either version 3 of the license, or (at your option) * 10 | * any later version. * 11 | *********************************************************************** 12 | * 13 | PRINT GEN 14 | DSLIST CSECT 15 | SAVE (14,12),,* Save caller's registers 16 | BALR R12,0 Load current address 17 | USING *,R12 Establish addressability 18 | ST R13,SAVEAREA+4 Store caller's savearea address 19 | LA R13,SAVEAREA Load address of our savearea 20 | ********************************************************************** 21 | * COMMAND: DSLIST (0x01) * 22 | * Command parameter will be the base name to search the catalog for. * 23 | ********************************************************************** 24 | DSLSTCMD ORG * 25 | * Copy parameter list addresses 26 | MVC CTCCMDAD,0(R1) Address of CTCCMD DCB 27 | MVC CTCDTAAD,4(R1) Address of CTCDATA DCB 28 | MVC CMDINAD,8(R1) Address of command input data 29 | L R2,CMDINAD 30 | L R1,0(,R2) Get command parameter length 31 | N R1,CMDLNMSK Mask out the command param length 32 | SRL R1,8 Shift right 8 bits 33 | STC R1,LOCLEN Set the locate name length 34 | MVC LOCNAME,3(R2) Move the parameter into locate name 35 | * Locate DSNAME 36 | XC CTGPL(CTGPLLEN),CTGPL Zero data in CTGPL 37 | MVI CTGOPTN1,CTGNAME+CTGGENLD Set up LOCATE options 38 | MVI CTGOPTN3,CTGSUPLT+CTGAM0 Set up LOCATE options 39 | LA R0,LOCLEN Set our LOCLEN + LOCNAME as the... 40 | ST R0,CTGENT ...CATALOG entry point address 41 | L R0,=A(X'FFFF') Get 64k work area 42 | GETMAIN RU,LV=(R0) 43 | ST R1,CTGWKA Save allocated memory addr to CTGPL 44 | STH R0,0(,R1) Store save area size into work area hdr 45 | LA R0,0 R0 = 0 46 | STH R0,2(,R1) Store save area used into work area hdr 47 | CATALOG CTGPL 48 | * Process result 49 | ST R15,DSRRSLT Store result code 50 | LA R0,0 Initialize number of results... 51 | STH R0,DSRLEN ...to 0 52 | NR R15,R15 Is result code 0? 53 | BNZ DSLNORES ...no, go straight to response WRITE 54 | * Calculate number of datasets names we got 55 | L R2,CTGWKA Get address of work area 56 | LA R3,2 Need to add two bytes to it 57 | AR R2,R3 R2 = R2 + R3 58 | LH R0,0(,R2) Second halfword of workarea is len 59 | LA R3,4 R3 = 4 60 | SR R0,R3 Subtract header length of 4 61 | SRDA R0,32 R0:R1 is now doubleword length value 62 | LA R3,45 R3 = 45 (length of each dsname entry) 63 | DR R0,R3 R1 = R0:R1 / 45 = # of name entries 64 | STH R1,DSRLEN Set DSRLEN to the # of entries 65 | B DSLSTWRI 66 | DSLNORES ORG * 67 | DSLSTWRI ORG * Write the response over the CTCA 68 | LA R1,DSRCCW1 Load address of DSRCCW1 to R1 69 | ST R1,IOBCCWAD Point our IOB to our WRITE CCW 70 | L R1,CTCDTAAD Load address of CTCDATA DCB to R1 71 | ST R1,IOBDCBAD Point our IOB to our DCB 72 | XC EXCPECB,EXCPECB Clear EXCPECB 73 | EXCP IOB Run our WRITE command 74 | WAIT ECB=EXCPECB 75 | CLI EXCPECB,X'7F' Successful completion? 76 | BNE DSLSTERR ...No, bail out 77 | * Loop over result rows and send 78 | LA R1,DSRCCW2 Load address of DSRCCW2 to R1 79 | ST R1,IOBCCWAD Point our IOB to our WRITE CCW 80 | LA R2,45 R2 = entry size 81 | L R3,CTGWKA R3 = offset into WORKAREA entry 82 | LA R0,4 83 | AR R3,R0 R3 = R3 + 4; move past workarea header 84 | LH R4,DSRLEN R4 = # of entries remaining to send 85 | LTR R4,R4 LH doesn't set condition code, so LTR 86 | BZ DSLSTDON If there are no entries, we're done 87 | LA R5,DSLSTLP R5 = address of DSLIST Loop location 88 | DSLSTLP XC DSRENT(DSRENTLN),DSRENT Clear output entry 89 | MVC DSRENT(45),0(R3) Copy current work area entry to DSRENT 90 | MVC LOCNAME,1(R3) Copy current DSNAME to LOCNAME 91 | LOCATE LOCCMLST LOCATE the dataset name in the catalog 92 | LTR R15,R15 Success? 93 | BNZ DSRSND ...no, just skip to sending this record 94 | MVC DSRVOL,LOCWRK+6 Copy 1st volume serial # 95 | MVC OBTVOLSR,DSRVOL And copy it to our OBTAIN command 96 | OBTAIN OBTCMLST Get the DSBC for the dataset 97 | LTR R15,R15 Successful completion? 98 | BNZ DSRSND ...no, just skip to sending this record 99 | MVC DSRDSCB,DSCBAREA Copy the DSCB data to output record 100 | DSRSND XC EXCPECB,EXCPECB Clear EXCPECB 101 | EXCP IOB Run our WRITE command 102 | WAIT ECB=EXCPECB 103 | CLI EXCPECB,X'7F' Successful completion? 104 | BNZ DSLSTERR ...No, bail out 105 | AR R3,R2 Update offset into work area to next 106 | BCTR R4,R5 Decrement R4; if non-zero loop 107 | B DSLSTDON Zero entries left - all done 108 | DSLSTERR WTO 'Unsuccessful CTC WRITE during DSLSTCMD' 109 | DSLSTDON ORG * Finish DSLIST processing 110 | L R0,=A(X'FFFF') Release 64k work area 111 | L R1,CTGWKA 112 | FREEMAIN R,LV=(R0),A=(R1) 113 | * Return to caller 114 | QUIT L R13,SAVEAREA+4 Restore address of caller's save area 115 | RETURN (14,12),RC=0 116 | * 117 | ********************************************************************** 118 | ********************************************************************** 119 | * 120 | ***** Parameters passed into us 121 | CTCCMDAD DS F 122 | CTCDTAAD DS F 123 | CMDINAD DS F 124 | ***** Storage and CCWs for DSLIST command 125 | * Initial response 126 | DSRRESP DS 0F 127 | DSRRSLT DS F 128 | DSRLEN DS H 129 | DSRRESPL EQU *-DSRRESP 130 | * DSLIST entry record 131 | DSRENT DS 0F 132 | DSRTYPE DS C 133 | DSRNAME DS CL44 134 | DSRVOL DS CL6 135 | DSRDSCB DS CL96 136 | DSRENTLN EQU *-DSRENT 137 | * Channel programs 138 | DSRCCW1 CCW CONTROL,DSRRESP,SLI+CC,1 139 | CCW WRITE,DSRRESP,SLI,DSRRESPL 140 | DSRCCW2 CCW CONTROL,DSRENT,SLI+CC,1 141 | CCW WRITE,DSRENT,SLI,DSRENTLN 142 | WRITE EQU X'01' 143 | READ EQU X'02' 144 | CONTROL EQU X'07' 145 | SENSE EQU X'14' 146 | SLI EQU X'20' 147 | CC EQU X'40' 148 | * EXCP IOB 149 | IOB DS 0F 150 | IOBFLAGS DC XL2'0000' 151 | IOBSENSE DC XL2'0000' 152 | IOBECBAD DC A(EXCPECB) 153 | IOBCSW DC A(0) 154 | IOBCSWFL DC XL2'0000' 155 | IOBRESDL DC H'00' 156 | IOBCCWAD DC A(0) 157 | IOBDCBAD DC A(0) 158 | DC F'0' 159 | DC F'0' 160 | SLPTIM DC F'25' 161 | EXCPECB DS F 162 | * Utility variables 163 | DS 0F 164 | CMDLNMSK DC X'00FFFF00' Mask to get the param length 165 | *********************************************************************** 166 | * CATALOG PARAMETER LIST * 167 | * * 168 | * This is just enough of the definition of CTGPL from the IEZCTGPL * 169 | * macro from, e.g., the MVS 3.8J Optional Materials Tape SYM6-1, that * 170 | * we need to perform a catalog generic locate operation. For full * 171 | * details on our undocumented-by-IBM use of SVC 26, see Peter * 172 | * Sawyer's materials on CBT689. * 173 | *********************************************************************** 174 | CTGPL DS 0D 175 | CTGOPTN1 DS XL1 FIRST OPTION INDICATOR 176 | CTGNAME EQU X'04' 1 - 44-BYTE NAME OR VOLSER 177 | CTGGENLD EQU X'01' GENERIC LOCATE REQUEST 178 | CTGOPTN2 DS XL1 SECOND OPTION INDICATOR 179 | CTGOPTN3 DS XL1 THIRD OPTION INDICATOR 180 | CTGSUPLT EQU X'10' SUPER LOCATE 181 | CTGAM0 EQU X'01' VSAM REQ VERSUS NONVSAM 182 | CTGOPTN4 DS XL1 FOURTH OPTION INDICATOR 183 | CTGENT DS A USER ENTRY ADDRESS 184 | DS A 185 | CTGWKA DS A WORKAREA ADDRESS 186 | DS XL12 187 | CTGPLLEN EQU *-CTGPL 188 | *********************************************************************** 189 | * Parameters for our CTGPL * 190 | *********************************************************************** 191 | LOCLEN DS XL1 192 | LOCNAME DS CL44 193 | * And for our LOCATE and OBTAIN calls 194 | LOCCMLST CAMLST NAME,LOCNAME,,LOCWRK 195 | LOCWRK DS 0D 196 | DS 265C 197 | OBTCMLST CAMLST SEARCH,LOCNAME,OBTVOLSR,DSCBAREA 198 | OBTVOLSR DS CL6 199 | DSCBAREA DS 0D 200 | DS CL140 201 | *********************************************************************** 202 | SAVEAREA DS 18F 203 | ********************************************************************** 204 | * Register symbols * 205 | ********************************************************************** 206 | R0 EQU 0 207 | R1 EQU 1 208 | R2 EQU 2 209 | R3 EQU 3 210 | R4 EQU 4 211 | R5 EQU 5 212 | R6 EQU 6 213 | R7 EQU 7 214 | R8 EQU 8 215 | R9 EQU 9 216 | R10 EQU 10 217 | R11 EQU 11 218 | R12 EQU 12 219 | R13 EQU 13 220 | R14 EQU 14 221 | R15 EQU 15 222 | END DSLIST 223 | -------------------------------------------------------------------------------- /MVS/MBRLIST: -------------------------------------------------------------------------------- 1 | *********************************************************************** 2 | * MVS SERVICES OVER CTC - MBRLIST Command (0x02) * 3 | * * 4 | * Copyright 2022 Matthew R. Wilson * 5 | * * 6 | * This file is part of CTC Mainframe API. CTC Mainframe API is free * 7 | * software: you can redistribute it and/or modify it under the terms * 8 | * of the GNU General Public License as published by the Free Software * 9 | * Foundation, either version 3 of the license, or (at your option) * 10 | * any later version. * 11 | *********************************************************************** 12 | * 13 | PRINT GEN 14 | MBRLIST CSECT 15 | SAVE (14,12),,* Save caller's registers 16 | BALR R12,0 Load current address 17 | USING *,R12 Establish addressability 18 | ST R13,SAVEAREA+4 Store caller's savearea address 19 | LA R13,SAVEAREA Load address of our savearea 20 | ********************************************************************** 21 | * COMMAND: MBRLIST (0x02) * 22 | * Command parameter will be the name of a PDS we wish to read the * 23 | * member directory from. * 24 | ********************************************************************** 25 | * Copy paramater list addresses 26 | MVC CTCCMDAD,0(R1) Address of CTCCMD DCB 27 | MVC CTCDTAAD,4(R1) Address of CTCDATA DCB 28 | MVC CMDINAD,8(R1) Address of command input data 29 | * Check that the parameter (dataset name) length is 44 bytes 30 | L R2,CMDINAD Get address of command input data 31 | L R1,0(,R2) Get command parameter length 32 | N R1,CMDLNMSK Mask out the command param length 33 | SRL R1,8 Shift right 8 bits 34 | LA R3,44 R3 = 44 35 | CLR R1,R3 Length = 44? 36 | BNE BADLEN No, bail out 37 | * Get the DSNAME from the command input area 38 | MVC DYNDSN,3(R2) 39 | * 40 | * Before we try to read a directory from the dataset, we will first 41 | * locate it in the catalog then read the DSCB from the volume's VTOC to 42 | * ensure the DSORG is PO. 43 | * 44 | * LOCATE the dataset in the catalog. 45 | LOCATE LOCCMLST LOCATE the dataset name in the catalog 46 | LTR R15,R15 Success? 47 | BNZ LOCERR ...no, return the condition code 48 | * OBTAIN the DSCB 49 | MVC OBTVOLSR,LOCWRK+6 Copy 1st volume serial # to our OBTAIN 50 | OBTAIN OBTCMLST Get the DSBC for the dataset 51 | LTR R15,R15 Successful completion? 52 | BNZ LOCERR ...no, return the condition code 53 | * Is this a PDS? 54 | LA R1,DSCBAREA Get the address of our DSCB data 55 | CLI 0(R1),X'F1' Is this a format-1 DSCB? 56 | BNE NONPDS ...no 57 | NI 38(R1),X'02' Mask DS1DSORG with the PO bit 58 | BZ NONPDS If last op = 0, it's not a PDS 59 | * At this point, we think we have a partitioned dataset 60 | * 61 | * To read the directory from a PDS, we need to open the dataset as 62 | * a sequential dataset. The directory is at the beginning, in 256 63 | * byte blocks. Since we don't know the DSNAME of the PDS ahead of 64 | * time, we need to dynamically allocate the dataset at runtime using 65 | * the DYNALLOC / SVC99 capability of MVS. Building our dynamic 66 | * allocation request block here is modeled on the example beginning 67 | * on page 74 of GC28-0627-2, OS/VS2 MVS System Programming Library: 68 | * Job Management. 69 | * 70 | * Get storage for our dynamic allocation SVC 99 request 71 | STORSIZE EQU 120 120 bytes is a few more than we need 72 | LA R0,STORSIZE 73 | GETMAIN R,LV=(R0) Get the storage necessary 74 | ST R1,DYNAREA Save the address to DYNAREA 75 | XC 0(STORSIZE,R1),0(R1) Zero out the memory 76 | * ...and build the request block (RB) 77 | LR R8,R1 Save the address to R8 78 | USING S99RBP,R8 Addressability for RBPTR DSECT 79 | LA R4,S99RBPTR+4 Point 4 bytes beyond start of RBPTR 80 | USING S99RB,R4 Addressability for RB DSECT 81 | ST R4,S99RBPTR Point RBPTR to RB 82 | OI S99RBPTR,S99RBPND Turn on the high order bit in RBPTR 83 | XC S99RB(RBLEN),S99RB Zero out RB 84 | MVI S99RBLN,RBLEN Put the length of RB in its length fld 85 | MVI S99VERB,S99VRBAL Set verb to allocation function 86 | LA R5,S99RB+RBLEN Point 20 bytes beyond start of RB 87 | USING S99TUPL,R5 Addressability for text unit ptrs 88 | ST R5,S99TXTPP Init text points address in RB 89 | LA R6,S99TUPL+16 Point just past 4 text pointers 90 | USING S99TUNIT,R6 Addressability for 1st text unit 91 | * Text Unit 1 - DALRTDDN (Return DDNAME) 92 | ST R6,S99TUPTR Point 1st TU ptr to 1st TU 93 | LA R7,DALRTDDN Get the key for DDNAME 94 | STH R7,S99TUKEY Put the key in the text unit key field 95 | LA R7,1 Set count = 1 96 | STH R7,S99TUNUM Set count = 1 97 | LA R7,8 DDNAMEs may be up to 8 characters long 98 | STH R7,S99TULNG ...and put it in the TU length field 99 | LA R1,S99TUPAR Get addr where the DDNAME will end up 100 | ST R1,DDN ...and save to storage 101 | * Text Unit 2 - DSNAME 102 | LA R6,S99TUNIT+14 Point just past 1st text unit 103 | LA R5,S99TUPL+4 Point to the 2nd text unit ptr in list 104 | ST R6,S99TUPTR Point 2nd TU ptr to 2nd TU 105 | LA R7,DALDSNAM Get the key for DSNAME 106 | STH R7,S99TUKEY Put the key in the text unit key field 107 | LA R7,1 Set count = 1 108 | STH R7,S99TUNUM Set count = 1 109 | LA R7,L'DYNDSN Get the length of the DSNAME 110 | STH R7,S99TULNG ...and put it in the TU length field 111 | MVC S99TUPAR(L'DYNDSN),DYNDSN Put the DSNAME into parm fld 112 | * Text Unit 3 - STATUS (set to SHR) 113 | LA R6,S99TUNIT+6+L'DYNDSN Point just past 2nd text unit 114 | LA R5,S99TUPL+4 Point to the 3rd text unit ptr in list 115 | ST R6,S99TUPTR Point 3rd TU ptr to 3rd TU 116 | LA R7,DALSTATS Get the key for status specification 117 | STH R7,S99TUKEY Put the key in the text unit key field 118 | LA R7,1 Set count = 1 119 | STH R7,S99TUNUM Set count = 1 120 | STH R7,S99TULNG Set length = 1 121 | MVI S99TUPAR,X'08' Set parm to SHR 122 | * Text Unit 4 - CLOSE - deallocate at close 123 | LA R6,S99TUNIT+7 Point just past 3rd text unit 124 | LA R5,S99TUPL+4 Point to the 4rd text unit ptr in list 125 | ST R6,S99TUPTR Point 4rd TU ptr to 4rd TU 126 | OI S99TUPTR,S99TUPLN Turn on high bit to indicate last ptr 127 | LA R7,DALCLOSE Get the key for status specification 128 | STH R7,S99TUKEY Put the key in the text unit key field 129 | LA R7,0 Set count = 0 130 | * Done building dynamic allocation request 131 | DROP R4,R5,R6,R8 132 | LR R1,R8 Put request block ptr in R1 133 | DYNALLOC Invoke DYNALLOC to process request 134 | LTR R15,R15 DYNALLOC return code 135 | BNZ SVC99ERR ...was not successful 136 | * Copy the DDNAME to our DCB 137 | L R1,DDN Get address of DDNAME param 138 | MVC DYNDCB+40(8),0(R1) Copy the DDNAME 139 | * 140 | OPEN (DYNDCB,(INPUT)) 141 | * TODO: Should check that the open was successful 142 | * 143 | * Send the initial response 144 | LA R9,0 Just hard-code an "ok" response 145 | ST R9,RESPONSE 146 | LA R9,DSRCCW1 Load address of DSRCCW1 to R9 147 | ST R9,IOBCCWAD Point our IOB to our WRITE CCW 148 | L R9,CTCDTAAD Load address of CTCDATA DCB to R9 149 | ST R9,IOBDCBAD Point our IOB to our DCB 150 | XC EXCPECB,EXCPECB Clear EXCPECB 151 | EXCP IOB Run our WRITE command 152 | WAIT ECB=EXCPECB 153 | CLI EXCPECB,X'7F' Successful completion? 154 | BNE WRITERR ...No, bail out 155 | * Prepare the IOB for sending the member names 156 | LA R9,DSRCCW2 Load address of DSRCCW2 to R9 157 | ST R9,IOBCCWAD Point our IOB to our WRITE CCW 158 | * Now we have the DYNDCB open and ready to read the member directory. 159 | * The format of the directory is described on page 98 of 160 | * GC26-3875-0P OS/VS2 MVS Data Management Services Guide. 161 | * 162 | LOOP GET DYNDCB,INREC Read the next directory block 163 | LH R8,INREC Get # of bytes used in this block 164 | LA R2,2 R2 = 2 165 | SR R8,R2 Subtract 2 bytes for the length 166 | LA R2,INREC+2 Point to first member entry in block 167 | LOOP01 XC OUTREC(OUTLEN),OUTREC Clear output area 168 | MVC OUTNAME,0(R2) Copy member name to output area 169 | MVC OUTC,11(R2) Copy "C" to output area 170 | IC R3,11(R2) Load # of user data halfwords into R3 171 | LA R4,X'1F' ... 172 | NR R3,R4 ...and mask out extraneous bits 173 | SLA R3,1 Multiply by 2 = # of user data bytes 174 | EX R3,MVCUDATA Move R3 bytes of data into output area 175 | * Write output line 176 | XC EXCPECB,EXCPECB Clear EXCPECB 177 | EXCP IOB Run our WRITE command 178 | WAIT ECB=EXCPECB 179 | CLI EXCPECB,X'7F' Successful completion? 180 | BNZ WRITERR ...No, bail out 181 | * Prepare for next iteration through loop 182 | CLC MAXNAME,0(R2) Last member sentinal name? 183 | BE DIRDONE Yes, goto DIRDONE 184 | LA R4,12 Set R4 = 12 185 | AR R2,R4 R2 = R2 + 12 186 | SR R8,R4 R8 = R8 - 12 (bytes remaining) 187 | AR R2,R3 R2 = R2 + # of user data bytes 188 | SR R8,R3 R8 = R8 - R3 (bytes remaining) 189 | * R2 now contains the new address in INREC for the next entry, and 190 | * R8 contains the number of bytes in this directory block remaining. 191 | BZ LOOP Zero bytes remaining in block? 192 | B LOOP01 No, read the next entry in this block 193 | * 194 | * Handle various errors and send unsuccessful result code 195 | BADLEN LA R9,X'F0' Invalid DS length = 0xF0 196 | LA R8,0 No need to free memory 197 | B SENDERR 198 | LOCERR ST R15,RESPCOD2 Move the LOCATE/OBTAIN result RESPCOD2 199 | LA R9,X'F1' Dataset locate error = 0xF1 200 | LA R8,0 No need to free memory 201 | B SENDERR 202 | NONPDS LA R9,X'F2' Requested DS is not a PDS 203 | LA R8,0 No need to free memory 204 | B SENDERR 205 | SVC99ERR LA R9,X'F3' Dynamic allocation error 206 | LA R8,1 Need to free memory 207 | WTO 'Unsuccessful DYNALLOC during MBLIST' 208 | B SENDERR 209 | SENDERR ST R9,RESPCODE Save the result code to RESPONSE 210 | LA R9,DSRCCW1 Load address of DSRCCW1 to R9 211 | ST R9,IOBCCWAD Point our IOB to our WRITE CCW 212 | L R9,CTCDTAAD Load address of CTCDATA DCB to R9 213 | ST R9,IOBDCBAD Point our IOB to our DCB 214 | XC EXCPECB,EXCPECB Clear EXCPECB 215 | EXCP IOB Run our WRITE command 216 | WAIT ECB=EXCPECB 217 | CLI EXCPECB,X'7F' Successful completion? 218 | BE AFTERERR ...Yes, we can quit 219 | WTO 'Unsuccessful CTC WRITE during MBLIST error write' 220 | B AFTERERR 221 | AFTERERR LTR R8,R8 Do we need to free memory? 222 | BZ QUIT ...No, we can quit 223 | B DIRDONE ...Yes, clean up 224 | WRITERR WTO 'Unsuccessful CTC WRITE during MBLIST' 225 | DIRDONE CLOSE (DYNDCB) 226 | FREEPOOL DYNDCB Release buffer pool for this DCB 227 | LA R0,STORSIZE 228 | L R2,DYNAREA 229 | FREEMAIN R,LV=(R0),A=(R2) Free our storage 230 | * Return to caller 231 | QUIT L R13,4(R13) Restore address of caller's save area 232 | LM R14,R12,12(R13) Restore caller's registers 233 | LA R15,0 RC=0 234 | BR R14 Return to caller 235 | * 236 | ********************************************************************** 237 | ********************************************************************** 238 | * 239 | ***** Parameters passed into us 240 | CTCCMDAD DS F 241 | CTCDTAAD DS F 242 | CMDINAD DS F 243 | * 244 | ***** Storage and CCWs for MBRLIST command 245 | * Initial response 246 | RESPONSE DS 0F 247 | RESPCODE DS F 248 | RESPCOD2 DC F'0' 249 | RESPLEN EQU *-RESPONSE 250 | * MBRLIST entry record 251 | OUTREC DS 0F 252 | OUTNAME DS CL8 253 | OUTC DS CL1 254 | OUTUDATA DS CL62 255 | OUTLEN EQU *-OUTREC 256 | * Buffer for reading directory blocks 257 | DS 0F 258 | INREC DS CL256 259 | SAVEAREA DS 18F 260 | DYNAREA DS A 261 | DDN DS A Address of location of dynamic DDNAME 262 | DYNDSN DS CL44 DSNAME to dynamically allocate 263 | DYNDCB DCB DDNAME=XXXXXXXX,DSORG=PS,MACRF=GM,BLKSIZE=256,RECFM=F, + 264 | LRECL=256 265 | * The following instruction is used by an EX instruction to move the 266 | * variable-length sized user data to the output area. 267 | DS 0F 268 | MVCUDATA MVC OUTUDATA(1),12(R2) 269 | * Maximum byte value member name indicates end of directory 270 | MAXNAME DC X'FFFFFFFFFFFFFFFF' 271 | * LOCATE and OBTAIN storage 272 | LOCCMLST CAMLST NAME,DYNDSN,,LOCWRK Will locate DSNAME in DYNDSN 273 | LOCWRK DS 0D 274 | DS 265C 275 | OBTCMLST CAMLST SEARCH,DYNDSN,OBTVOLSR,DSCBAREA 276 | OBTVOLSR DS CL6 277 | DSCBAREA DS 0D 278 | DS CL140 279 | *********************************************************************** 280 | * Channel programs 281 | DSRCCW1 CCW CONTROL,OUTREC,SLI+CC,1 282 | CCW WRITE,RESPONSE,SLI,RESPLEN 283 | DSRCCW2 CCW CONTROL,OUTREC,SLI+CC,1 284 | CCW WRITE,OUTREC,SLI,OUTLEN 285 | WRITE EQU X'01' 286 | READ EQU X'02' 287 | CONTROL EQU X'07' 288 | SENSE EQU X'14' 289 | SLI EQU X'20' 290 | CC EQU X'40' 291 | * EXCP IOB 292 | IOB DS 0F 293 | IOBFLAGS DC XL2'0000' 294 | IOBSENSE DC XL2'0000' 295 | IOBECBAD DC A(EXCPECB) 296 | IOBCSW DC A(0) 297 | IOBCSWFL DC XL2'0000' 298 | IOBRESDL DC H'00' 299 | IOBCCWAD DC A(0) 300 | IOBDCBAD DC A(0) 301 | DC F'0' 302 | DC F'0' 303 | SLPTIM DC F'25' 304 | EXCPECB DS F 305 | * Utility variables 306 | DS 0F 307 | CMDLNMSK DC X'00FFFF00' Mask to get the param length 308 | PRINT NOGEN 309 | IEFZB4D0 , DYNALLOC DSECT 310 | IEFZB4D2 , DYNALLOC symbolic names 311 | RBLEN EQU S99RBEND-S99RB Length of SVC99 request block (RB) 312 | ********************************************************************** 313 | * Register symbols * 314 | ********************************************************************** 315 | R0 EQU 0 316 | R1 EQU 1 317 | R2 EQU 2 318 | R3 EQU 3 319 | R4 EQU 4 320 | R5 EQU 5 321 | R6 EQU 6 322 | R7 EQU 7 323 | R8 EQU 8 324 | R9 EQU 9 325 | R10 EQU 10 326 | R11 EQU 11 327 | R12 EQU 12 328 | R13 EQU 13 329 | R14 EQU 14 330 | R15 EQU 15 331 | END MBRLIST 332 | -------------------------------------------------------------------------------- /MVS/READ: -------------------------------------------------------------------------------- 1 | *********************************************************************** 2 | * MVS SERVICES OVER CTC - READ Command (0x03) * 3 | * * 4 | * Copyright 2022-2023 Matthew R. Wilson * 5 | * * 6 | * This file is part of CTC Mainframe API. CTC Mainframe API is free * 7 | * software: you can redistribute it and/or modify it under the terms * 8 | * of the GNU General Public License as published by the Free Software * 9 | * Foundation, either version 3 of the license, or (at your option) * 10 | * any later version. * 11 | *********************************************************************** 12 | * 13 | PRINT GEN 14 | READ CSECT 15 | SAVE (14,12),,* Save caller's registers 16 | BALR R12,0 Load current address 17 | USING *,R12 Establish addressability 18 | ST R13,SAVEAREA+4 Store caller's savearea address 19 | LA R13,SAVEAREA Load address of our savearea 20 | ********************************************************************** 21 | * COMMAND: READ (0x03) * 22 | * First 44 bytes of command parameter will be the name of the data- * 23 | * set to read. The following 8 bytes is the optional member name if * 24 | * it's a PDS. If not requesting a PDS member, the first byte of the * 25 | * member name must be a space. * 26 | ********************************************************************** 27 | * Copy parameter list addresses 28 | MVC CTCCMDAD,0(R1) Address of CTCCMD DCB 29 | MVC CTCDTAAD,4(R1) Address of CTCDATA DCB 30 | MVC CMDINAD,8(R1) Address of command input data 31 | * Check that the parameter (dataset+mbr name) length is 52 bytes 32 | L R2,CMDINAD Get address of command input data 33 | L R1,0(,R2) Get command parameter length 34 | N R1,CMDLNMSK Mask out the command param length 35 | SRL R1,8 Shift right 8 bits 36 | LA R3,52 R3 = 52 37 | CLR R1,R3 Length = 52? 38 | BNE BADLEN No, bail out 39 | * Get the DSNAME and member name from the command input area 40 | MVC DYNDSN,3(R2) 41 | MVC DYNMBR,47(R2) 42 | * 43 | * Before we try to read a dataset, we will first locate it in the 44 | * catalog then read the DSCB from the volume's VTOC. We want to check 45 | * if it's a supported type (non-VSAM, PS or PO, fixed record length) 46 | * and if a member name is provided, that the dataset is PO. 47 | * 48 | * LOCATE the dataset in the catalog. 49 | LOCATE LOCCMLST LOCATE the dataset name in the catalog 50 | LTR R15,R15 Success? 51 | BNZ LOCERR ...no, return the condition code 52 | * OBTAIN the DSCB 53 | MVC OBTVOLSR,LOCWRK+6 Copy 1st volume serial # to our OBTAIN 54 | OBTAIN OBTCMLST Get the DSBC for the dataset 55 | LTR R15,R15 Successful completion? 56 | BNZ LOCERR ...no, return the condition code 57 | * Make sure we got a format-1 DSCB 58 | LA R1,DSCBAREA Get the address of our DSCB data 59 | CLI 0(R1),X'F1' Is this a format-1 DSCB? 60 | BNE FMTERR ...no, report format error 61 | * Now we check if the dataset is PO or PS 62 | CLI DYNMBR,C' ' Is first character of member name ' '? 63 | BNE CHKPO ...no, check that DSORG is PO 64 | CHKPS NI 38(R1),X'40' ...yes, check that DSORG is PS 65 | BZ FMTERR ...no, org of dataset is unsup. 66 | B CHKORG ...yes, DSORG=PS 67 | CHKPO NI 38(R1),X'02' Check that DSORG is PO 68 | BZ FMTERR ...no, organization of dataset is unsup 69 | CHKORG L R5,40(R1) Load recfm into R5 70 | N R5,RECFMSK Mask out the non-RECFM bits 71 | SRL R5,24 Shift top byte into last byte in R5 72 | LR R6,R5 R6=R5 73 | LA R7,X'80' Fixed recln 74 | NR R6,R7 R6&0x80 75 | BNZ ORGF Record length is fixed 76 | LR R6,R5 R6=R5 (restore recfm byte) 77 | LA R7,X'40' Variable recln 78 | NR R6,R7 R6&0x40 79 | BNZ ORGV Record length is variable 80 | B FMTERR Something else... like U 81 | ORGF LA R7,1 R7 = 1 82 | ST R7,FIXED FIXED = 1 83 | B CHKDONE 84 | ORGV LA R7,0 R7 = 0 85 | ST R7,FIXED FIXED = 0 86 | CHKDONE EQU * 87 | * 88 | * At this point, we think we have a dataset that is supported and 89 | * matches the user's request. 90 | * 91 | * Since we don't know the DSNAME of the dataset ahead of time, we need 92 | * to dynamically allocate the dataset at runtime using the DYNALLOC / 93 | * SVC99 capability of MVS. Building our dynamic allocation request 94 | * block here is modeled on the example beginning on page 74 of 95 | * GC28-0627-2, OS/VS2 MVS System Programming Library: Job Management. 96 | * 97 | * Get storage for our dynamic allocation SVC 99 request 98 | STORSIZE EQU 200 200 bytes is well more than we need 99 | LA R0,STORSIZE 100 | GETMAIN R,LV=(R0) Get the storage necessary 101 | ST R1,DYNAREA Save the address to DYNAREA 102 | * There's a complication: we've verified that the dataset exists, but 103 | * if the caller requested a member name in a PDS, we haven't verified 104 | * that the specific member exists. If we set up a dynamic allocation 105 | * request with a member name that doesn't exist, our program will abend 106 | * so we first need to check if the member name exists. 107 | CLI DYNMBR,C' ' Is member name empty? 108 | BE NOMBRCHK ...yes, no member check needed 109 | * ...otherwise, we need to first allocate 110 | * the PDS without a member name to 111 | * check the directory 112 | XC 0(STORSIZE,R1),0(R1) Zero out the memory 113 | * ...and build the request block (RB) 114 | LR R8,R1 Save the address to R8 115 | USING S99RBP,R8 Addressability for RBPTR DSECT 116 | LA R4,S99RBPTR+4 Point 4 bytes beyond start of RBPTR 117 | USING S99RB,R4 Addressability for RB DSECT 118 | ST R4,S99RBPTR Point RBPTR to RB 119 | OI S99RBPTR,S99RBPND Turn on the high order bit in RBPTR 120 | XC S99RB(RBLEN),S99RB Zero out RB 121 | MVI S99RBLN,RBLEN Put the length of RB in its length fld 122 | MVI S99VERB,S99VRBAL Set verb to allocation function 123 | LA R5,S99RB+RBLEN Point 20 bytes beyond start of RB 124 | USING S99TUPL,R5 Addressability for text unit ptrs 125 | ST R5,S99TXTPP Init text points address in RB 126 | LA R6,S99TUPL+20 Point just past 5 text pointers 127 | USING S99TUNIT,R6 Addressability for 1st text unit 128 | * Text Unit 1 - DALRTDDN (Return DDNAME) 129 | ST R6,S99TUPTR Point 1st TU ptr to 1st TU 130 | LA R7,DALRTDDN Get the key for DDNAME 131 | STH R7,S99TUKEY Put the key in the text unit key field 132 | LA R7,1 Set count = 1 133 | STH R7,S99TUNUM Set count = 1 134 | LA R7,8 DDNAMEs may be up to 8 characters long 135 | STH R7,S99TULNG ...and put it in the TU length field 136 | LA R1,S99TUPAR Get addr where the DDNAME will end up 137 | ST R1,DDN ...and save to storage 138 | * Text Unit 2 - DSNAME 139 | LA R6,S99TUNIT+14 Point just past 1st text unit 140 | LA R5,S99TUPL+4 Point to the 2nd text unit ptr in list 141 | ST R6,S99TUPTR Point 2nd TU ptr to 2nd TU 142 | LA R7,DALDSNAM Get the key for DSNAME 143 | STH R7,S99TUKEY Put the key in the text unit key field 144 | LA R7,1 Set count = 1 145 | STH R7,S99TUNUM Set count = 1 146 | LA R7,L'DYNDSN Load length 147 | STH R7,S99TULNG ...and put it in the TU length field 148 | MVC S99TUPAR(L'DYNDSN),DYNDSN Put the DSNAME into parm fld 149 | * Text Unit 3 - STATUS (set to SHR) 150 | LA R6,S99TUNIT+6+L'DYNDSN Point just past 2nd text unit 151 | LA R5,S99TUPL+4 Point to the 3rd text unit ptr in list 152 | ST R6,S99TUPTR Point 3rd TU ptr to 3rd TU 153 | LA R7,DALSTATS Get the key for status specification 154 | STH R7,S99TUKEY Put the key in the text unit key field 155 | LA R7,1 Set count = 1 156 | STH R7,S99TUNUM Set count = 1 157 | STH R7,S99TULNG Set length = 1 158 | MVI S99TUPAR,X'08' Set parm to SHR 159 | * Text Unit 4 - CLOSE - deallocate at close 160 | LA R6,S99TUNIT+7 Point just past 3rd text unit 161 | LA R5,S99TUPL+4 Point to the 4rd text unit ptr in list 162 | ST R6,S99TUPTR Point 4rd TU ptr to 4rd TU 163 | LA R7,DALCLOSE Get the key for status specification 164 | STH R7,S99TUKEY Put the key in the text unit key field 165 | LA R7,0 Set count = 0 166 | STH R7,S99TUNUM Set count = 0 167 | OI S99TUPTR,S99TUPLN Turn on high bit to indicate last ptr 168 | * Done building dynamic allocation request 169 | DROP R4,R5,R6,R8 170 | LR R1,R8 Put request block ptr in R1 171 | DYNALLOC Invoke DYNALLOC to process request 172 | LTR R15,R15 DYNALLOC return code 173 | BNZ SVC99ERR ...was not successful 174 | * Prepare our DCB by resetting to default values 175 | MVC DYNDCB(DCBLEN),MDLDCB 176 | * Copy the DDNAME to our DCB 177 | L R1,DDN Get address of DDNAME param 178 | MVC DYNDCB+40(8),0(R1) Copy the DDNAME 179 | * 180 | OPEN (DYNDCB,(INPUT)) 181 | MVC BLDLNAME,DYNMBR Copy member name to our BLDL list 182 | BLDL DYNDCB,BLDLLIST Build the directory entry list 183 | LTR R3,R15 Copy return code to R3 to check later 184 | CLOSE (DYNDCB) 185 | FREEPOOL DYNDCB Release the system-allocated buffer 186 | * pool for this DCB. 187 | LTR R3,R3 Original return code 188 | BZ NOMBRCHK Success: member must exist, proceed 189 | B MBRERROR Failure: Bail out and report the error 190 | * Either the member check has succeeded, or we're not trying to open 191 | * a PDS member, so we can now create our DYNALLOC request to open the 192 | * caller's requested dataset. 193 | NOMBRCHK L R1,DYNAREA 194 | XC 0(STORSIZE,R1),0(R1) Zero out the memory 195 | * ...and build the request block (RB) 196 | LR R8,R1 Save the address to R8 197 | USING S99RBP,R8 Addressability for RBPTR DSECT 198 | LA R4,S99RBPTR+4 Point 4 bytes beyond start of RBPTR 199 | USING S99RB,R4 Addressability for RB DSECT 200 | ST R4,S99RBPTR Point RBPTR to RB 201 | OI S99RBPTR,S99RBPND Turn on the high order bit in RBPTR 202 | XC S99RB(RBLEN),S99RB Zero out RB 203 | MVI S99RBLN,RBLEN Put the length of RB in its length fld 204 | MVI S99VERB,S99VRBAL Set verb to allocation function 205 | LA R5,S99RB+RBLEN Point 20 bytes beyond start of RB 206 | USING S99TUPL,R5 Addressability for text unit ptrs 207 | ST R5,S99TXTPP Init text points address in RB 208 | LA R6,S99TUPL+20 Point just past 5 text pointers 209 | USING S99TUNIT,R6 Addressability for 1st text unit 210 | * Text Unit 1 - DALRTDDN (Return DDNAME) 211 | ST R6,S99TUPTR Point 1st TU ptr to 1st TU 212 | LA R7,DALRTDDN Get the key for DDNAME 213 | STH R7,S99TUKEY Put the key in the text unit key field 214 | LA R7,1 Set count = 1 215 | STH R7,S99TUNUM Set count = 1 216 | LA R7,8 DDNAMEs may be up to 8 characters long 217 | STH R7,S99TULNG ...and put it in the TU length field 218 | LA R1,S99TUPAR Get addr where the DDNAME will end up 219 | ST R1,DDN ...and save to storage 220 | * Text Unit 2 - DSNAME 221 | LA R6,S99TUNIT+14 Point just past 1st text unit 222 | LA R5,S99TUPL+4 Point to the 2nd text unit ptr in list 223 | ST R6,S99TUPTR Point 2nd TU ptr to 2nd TU 224 | LA R7,DALDSNAM Get the key for DSNAME 225 | STH R7,S99TUKEY Put the key in the text unit key field 226 | LA R7,1 Set count = 1 227 | STH R7,S99TUNUM Set count = 1 228 | * We will copy up to 44 characters, but we need to provide the real 229 | * count this time because space padding on the end doesn't work when 230 | * there's potentially a member name involved. 231 | LA R9,C' ' R9 = space character 232 | LA R2,DYNDSN R2 = address of DYNDSN 233 | LA R10,0 R10 = 0 234 | LA R11,1 R11 = 1 235 | FINDSPC CLM R9,1,0(R2) Is DYNDSN[R10] = ' '? 236 | BE FOUNDIT ...yes 237 | AR R10,R11 ...no, R10 = R10+1 238 | AR R2,R11 ...and R2 = R2 + 1 239 | B FINDSPC 240 | FOUNDIT STH R10,S99TULNG ...yes, put length in the TU len fld 241 | MVC S99TUPAR(L'DYNDSN),DYNDSN Put the DSNAME into parm fld 242 | * Text Unit 3 - STATUS (set to SHR) 243 | LA R6,S99TUNIT+6+L'DYNDSN Point just past 2nd text unit 244 | LA R5,S99TUPL+4 Point to the 3rd text unit ptr in list 245 | ST R6,S99TUPTR Point 3rd TU ptr to 3rd TU 246 | LA R7,DALSTATS Get the key for status specification 247 | STH R7,S99TUKEY Put the key in the text unit key field 248 | LA R7,1 Set count = 1 249 | STH R7,S99TUNUM Set count = 1 250 | STH R7,S99TULNG Set length = 1 251 | MVI S99TUPAR,X'08' Set parm to SHR 252 | * Text Unit 4 - CLOSE - deallocate at close 253 | LA R6,S99TUNIT+7 Point just past 3rd text unit 254 | LA R5,S99TUPL+4 Point to the 4rd text unit ptr in list 255 | ST R6,S99TUPTR Point 4rd TU ptr to 4rd TU 256 | LA R7,DALCLOSE Get the key for status specification 257 | STH R7,S99TUKEY Put the key in the text unit key field 258 | LA R7,0 Set count = 0 259 | STH R7,S99TUNUM Set count = 0 260 | * Do we need to add member name text unit? 261 | CLI DYNMBR,C' ' Is member name blank? 262 | BNE TUMEMBER ...no, add the member name text unit 263 | OI S99TUPTR,S99TUPLN Turn on high bit to indicate last ptr 264 | B SKIPMBR 265 | * Text Unit 5 - member name 266 | TUMEMBER LA R6,S99TUNIT+4 Point just past 4th text unit 267 | LA R5,S99TUPL+4 Point to the 5th text unit ptr in list 268 | ST R6,S99TUPTR Point 5th TU ptr to 5th TU 269 | LA R7,DALMEMBR Get the key for member name 270 | STH R7,S99TUKEY Put the key in the text unit key field 271 | LA R7,1 Set count = 1 272 | STH R7,S99TUNUM Set count = 1 273 | LA R7,L'DYNMBR Set length = 8 274 | STH R7,S99TULNG Set length = 8 275 | MVC S99TUPAR(L'DYNMBR),DYNMBR Set member name 276 | OI S99TUPTR,S99TUPLN Turn on high bit to indicate last ptr 277 | * Done building dynamic allocation request 278 | SKIPMBR EQU * 279 | DROP R4,R5,R6,R8 280 | LR R1,R8 Put request block ptr in R1 281 | DYNALLOC Invoke DYNALLOC to process request 282 | LTR R15,R15 DYNALLOC return code 283 | BNZ SVC99ERR ...was not successful 284 | * Prepare our DCB with the default values 285 | MVC DYNDCB(DCBLEN),MDLDCB 286 | * Copy the DDNAME to our DCB 287 | L R1,DDN Get address of DDNAME param 288 | MVC DYNDCB+40(8),0(R1) Copy the DDNAME 289 | LA R4,STORSIZE 290 | L R5,DYNAREA 291 | FREEMAIN R,LV=(R4),A=(R5) Free our storage 292 | OPEN (DYNDCB,(INPUT)) 293 | * TODO: Should check that the open was successful 294 | * 295 | * Get the LRECL from the opened DCB 296 | LH R10,DYNDCB+82 R10 = DCBLRECL 297 | STH R10,DSRCCW2B+6 Set CCW length to LRECL 298 | GETMAIN R,LV=(R10) Get memory of LRECL length 299 | ST R1,GETAREA Save the address to GETAREA 300 | ST R1,DSRCCW2B Save the address to our CCW 301 | LA R1,WRITE ...restore the CCW command byte 302 | STC R1,DSRCCW2B ...restore the CCW command byte 303 | ST R10,RECL Save the LRECL to storage 304 | * 305 | * Send the initial response 306 | LA R9,0 Just hard-code an "ok" response 307 | ST R9,RESPCODE 308 | MVC RESPCOD2,FIXED Tell the server if fixed recln 309 | LA R9,DSRCCW1 Load address of DSRCCW1 to R9 310 | ST R9,IOBCCWAD Point our IOB to our WRITE CCW 311 | L R9,CTCDTAAD Load address of CTCDATA DCB to R9 312 | ST R9,IOBDCBAD Point our IOB to our DCB 313 | XC EXCPECB,EXCPECB Clear EXCPECB 314 | EXCP IOB Run our WRITE command 315 | WAIT ECB=EXCPECB 316 | CLI EXCPECB,X'7F' Successful completion? 317 | BNE WRITERR ...No, bail out 318 | * Prepare the IOB for sending the dataset records 319 | LA R9,DSRCCW2A Load address of DSRCCW2 to R9 320 | ST R9,IOBCCWAD Point our IOB to our WRITE CCW 321 | L R9,GETAREA Address of our get buffer in R9 322 | * Now we have the DYNDCB open and ready to read the records. 323 | LOOP GET DYNDCB,(R9) 324 | XC EXCPECB,EXCPECB Clear EXCPECB 325 | EXCP IOB Run our WRITE command 326 | WAIT ECB=EXCPECB 327 | CLI EXCPECB,X'7F' Successful completion? 328 | BNZ WRITERR ...No, bail out 329 | B LOOP ...Yes, read next record 330 | EOF EQU * All done 331 | LA R9,DSRCCW3 Load address of DSRCCW3 to R9 332 | ST R9,IOBCCWAD Point our IOB to our final CCW 333 | XC EXCPECB,EXCPECB Clear EXCPECB 334 | EXCP IOB Send our end of file WRITE command 335 | WAIT ECB=EXCPECB 336 | CLI EXCPECB,X'7F' Successful completion? 337 | BNE WRITERR ...No, bail out 338 | B DONE ...Yes, we're done 339 | * 340 | * Handle various errors and send unsuccessful result code 341 | BADLEN LA R9,X'F0' Invalid DS length = 0xF0 342 | B SENDERR 343 | LOCERR ST R15,RESPCOD2 Move the LOCATE/OBTAIN result RESPCOD2 344 | LA R9,X'F1' Dataset locate error = 0xF1 345 | B SENDERR 346 | FMTERR LA R9,X'F2' Requested DS is not supported 347 | B SENDERR 348 | SVC99ERR LA R9,X'F3' Dynamic allocation error 349 | B CLEANUP 350 | MBRERROR LA R9,X'F4' Member doesn't exist error 351 | CLEANUP LA R4,STORSIZE 352 | L R5,DYNAREA 353 | FREEMAIN R,LV=(R4),A=(R5) Free our storage 354 | WTO 'Unsuccessful DYNALLOC during READ' 355 | B SENDERR 356 | SENDERR ST R9,RESPCODE Save the result code to RESPONSE 357 | LA R9,DSRCCW1 Load address of DSRCCW1 to R9 358 | ST R9,IOBCCWAD Point our IOB to our WRITE CCW 359 | L R9,CTCDTAAD Load address of CTCDATA DCB to R9 360 | ST R9,IOBDCBAD Point our IOB to our DCB 361 | XC EXCPECB,EXCPECB Clear EXCPECB 362 | EXCP IOB Run our WRITE command 363 | WAIT ECB=EXCPECB 364 | CLI EXCPECB,X'7F' Successful completion? 365 | BE QUIT ...Yes, we can quit 366 | WTO 'Unsuccessful CTC WRITE during READ error write' 367 | B QUIT 368 | WRITERR WTO 'Unsuccessful CTC WRITE during READ' 369 | DONE CLOSE (DYNDCB) 370 | FREEPOOL DYNDCB 371 | L R4,GETAREA 372 | L R5,RECL 373 | FREEMAIN R,LV=(R5),A=(R4) Free our memory 374 | * Return to caller 375 | QUIT L R13,4(R13) Restore address of caller's save area 376 | LM R14,R12,12(R13) Restore caller's registers 377 | LA R15,0 RC=0 378 | BR R14 Return to caller 379 | * 380 | ********************************************************************** 381 | ********************************************************************** 382 | * 383 | ***** Parameters passed into us 384 | CTCCMDAD DS F 385 | CTCDTAAD DS F 386 | CMDINAD DS F 387 | * 388 | ***** Storage and CCWs for MBRLIST command 389 | FIXED DS F Non-zero means fixed record length 390 | * Initial response 391 | RESPONSE DS 0F 392 | RESPCODE DS F 393 | RESPCOD2 DC F'0' 394 | RESPLEN EQU *-RESPONSE 395 | * 396 | GETAREA DS A 397 | RECL DS F 398 | EOFREC DC X'FF' 399 | * 400 | SAVEAREA DS 18F 401 | DYNAREA DS A 402 | DDN DS A Address of location of dynamic DDNAME 403 | DYNDSN DS CL44 DSNAME to dynamically allocate 404 | DC C' ' Terminating space (right after DYNDSN 405 | * for safety) 406 | DYNMBR DS CL8 Member name to dynamically allocate 407 | DYNDCB DCB DDNAME=XXXXXXXX,MACRF=GM,DSORG=PS,EODAD=EOF 408 | * Model DCB that we will use to reset the DCB to default state after 409 | * each use. 410 | MDLDCB DCB DDNAME=XXXXXXXX,MACRF=GM,DSORG=PS,EODAD=EOF 411 | DCBLEN EQU *-MDLDCB 412 | * LOCATE and OBTAIN storage 413 | LOCCMLST CAMLST NAME,DYNDSN,,LOCWRK Will locate DSNAME in DYNDSN 414 | LOCWRK DS 0D 415 | DS 265C 416 | OBTCMLST CAMLST SEARCH,DYNDSN,OBTVOLSR,DSCBAREA 417 | OBTVOLSR DS CL6 418 | DSCBAREA DS 0D 419 | DS CL140 420 | * BLDL storage 421 | BLDLLIST DS 0H 422 | DC H'1' Number of entries in the list 423 | DC H'12' Length of each list entry 424 | BLDLNAME DS CL8 Eight characters for member name 425 | DS XL4 Four more byte to round out the entry 426 | *********************************************************************** 427 | * Channel programs 428 | DSRCCW1 CCW CONTROL,RESPONSE,SLI+CC,1 429 | CCW WRITE,RESPONSE,SLI,RESPLEN 430 | DSRCCW2A CCW CONTROL,RESPONSE,SLI+CC,1 431 | DSRCCW2B CCW WRITE,RESPONSE,SLI,1 432 | DSRCCW3 CCW CONTROL,EOFREC,SLI+CC,1 433 | CCW WRITE,EOFREC,SLI,1 434 | WRITE EQU X'01' 435 | CONTROL EQU X'07' 436 | SENSE EQU X'14' 437 | SLI EQU X'20' 438 | CC EQU X'40' 439 | * EXCP IOB 440 | IOB DS 0F 441 | IOBFLAGS DC XL2'0000' 442 | IOBSENSE DC XL2'0000' 443 | IOBECBAD DC A(EXCPECB) 444 | IOBCSW DC A(0) 445 | IOBCSWFL DC XL2'0000' 446 | IOBRESDL DC H'00' 447 | IOBCCWAD DC A(0) 448 | IOBDCBAD DC A(0) 449 | DC F'0' 450 | DC F'0' 451 | SLPTIM DC F'25' 452 | EXCPECB DS F 453 | * Utility variables 454 | DS 0F 455 | CMDLNMSK DC X'00FFFF00' Mask to get the param length 456 | RECFMSK DC X'FF000000' Mask to get the recfm 457 | PRINT NOGEN 458 | IEFZB4D0 , DYNALLOC DSECT 459 | IEFZB4D2 , DYNALLOC symbolic names 460 | RBLEN EQU S99RBEND-S99RB Length of SVC99 request block (RB) 461 | ********************************************************************** 462 | * Register symbols * 463 | ********************************************************************** 464 | R0 EQU 0 465 | R1 EQU 1 466 | R2 EQU 2 467 | R3 EQU 3 468 | R4 EQU 4 469 | R5 EQU 5 470 | R6 EQU 6 471 | R7 EQU 7 472 | R8 EQU 8 473 | R9 EQU 9 474 | R10 EQU 10 475 | R11 EQU 11 476 | R12 EQU 12 477 | R13 EQU 13 478 | R14 EQU 14 479 | R15 EQU 15 480 | END READ 481 | -------------------------------------------------------------------------------- /MVS/SUBMIT: -------------------------------------------------------------------------------- 1 | *********************************************************************** 2 | * MVS SERVICES OVER CTC - SUBMIT Command (0x04) * 3 | * * 4 | * Copyright 2022-2023 Matthew R. Wilson * 5 | * * 6 | * This file is part of CTC Mainframe API. CTC Mainframe API is free * 7 | * software: you can redistribute it and/or modify it under the terms * 8 | * of the GNU General Public License as published by the Free Software * 9 | * Foundation, either version 3 of the license, or (at your option) * 10 | * any later version. * 11 | *********************************************************************** 12 | * 13 | PRINT GEN 14 | SUBMIT CSECT 15 | SAVE (14,12),,* Save caller's registers 16 | BALR R12,0 Load current address 17 | USING *,R12 Establish addressability 18 | ST R13,SAVEAREA+4 Store caller's savearea address 19 | LA R13,SAVEAREA Load address of our savearea 20 | ********************************************************************** 21 | * COMMAND: SUBMIT (0x04) * 22 | * First 4 bytes of command parameter will be a binary fullword * 23 | * indicating the number of records that will follow. We will then * 24 | * read that number of 80-byte records from the command channel. * 25 | * After submitting the job, we will return the assigned job number. * 26 | ********************************************************************** 27 | * Copy parameter list addresses 28 | MVC CTCCMDAD,0(R1) Address of CTCCMD DCB 29 | MVC CTCDTAAD,4(R1) Address of CTCDATA DCB 30 | MVC CMDINAD,8(R1) Address of command input data 31 | * Check that the parameter length is 4 bytes 32 | L R2,CMDINAD Get address of command input data 33 | L R1,0(,R2) Get command parameter length 34 | N R1,CMDLNMSK Mask out the command param length 35 | SRL R1,8 Shift right 8 bits 36 | LA R3,4 R3 = 4 37 | CLR R1,R3 Length = 4? 38 | BNE BADLEN No, bail out 39 | * Get the number of job records from the command input area 40 | MVC JOBLEN,3(R2) 41 | * Check that records > 0 42 | L R1,JOBLEN 43 | LTR R1,R1 44 | BNP BADLEN Error if joblen isn't positive 45 | * 46 | * Dynamically allocate an internal reader. This is described on page 47 | * 77 of GC28-0672-2, OS/VS2 MVS System Programming Library: Job 48 | * Management. Building our dynamic allocation request block is modeled 49 | * on the example beginning on page 74 of the same manual. 50 | * 51 | * Get storage for our dynamic allocation SVC 99 request 52 | STORSIZE EQU 200 200 bytes is well more than we need 53 | LA R0,STORSIZE 54 | GETMAIN R,LV=(R0) Get the storage necessary 55 | ST R1,DYNAREA Save the address to DYNAREA 56 | XC 0(STORSIZE,R1),0(R1) Zero out the memory 57 | * ...and build the request block (RB) 58 | LR R8,R1 Save the address to R8 59 | USING S99RBP,R8 Addressability for RBPTR DSECT 60 | LA R4,S99RBPTR+4 Point 4 bytes beyond start of RBPTR 61 | USING S99RB,R4 Addressability for RB DSECT 62 | ST R4,S99RBPTR Point RBPTR to RB 63 | OI S99RBPTR,S99RBPND Turn on the high order bit in RBPTR 64 | XC S99RB(RBLEN),S99RB Zero out RB 65 | MVI S99RBLN,RBLEN Put the length of RB in its length fld 66 | MVI S99VERB,S99VRBAL Set verb to allocation function 67 | LA R5,S99RB+RBLEN Point 20 bytes beyond start of RB 68 | USING S99TUPL,R5 Addressability for text unit ptrs 69 | ST R5,S99TXTPP Init text points address in RB 70 | LA R6,S99TUPL+16 Point just past 4 text pointers 71 | USING S99TUNIT,R6 Addressability for 1st text unit 72 | * Text Unit 1 - DDNAME (Set to 'INTRDR') 73 | ST R6,S99TUPTR Point 1st TU ptr to 1st TU 74 | LA R7,DALDDNAM Get the key for DDNAME 75 | STH R7,S99TUKEY Put the key in the text unit key field 76 | LA R7,1 Set count = 1 77 | STH R7,S99TUNUM Set count = 1 78 | LA R7,6 Our DDNAME will be 'INTRDR' 79 | STH R7,S99TULNG ...and put it in the TU length field 80 | MVC S99TUPAR(6),DDN Put the DDNAME into parm field 81 | * Text Unit 2 - DALSYSOU (SYSOUT data set) 82 | LA R6,S99TUNIT+12 Point just past 1st text unit 83 | LA R5,S99TUPL+4 Point to the 2nd text unit ptr in list 84 | ST R6,S99TUPTR Point 2nd TU ptr to 2nd TU 85 | LA R7,DALSYSOU Get the key for SYSOUT specification 86 | STH R7,S99TUKEY Put the key in the text unit key field 87 | LA R7,0 Set count = 0 88 | STH R7,S99TUNUM Set count = 0 89 | * Text Unit 3 - DALSPGNM (SYSOUT Program Name - set to INTRDR) 90 | LA R6,S99TUNIT+4 Point just past 2nd text unit 91 | LA R5,S99TUPL+4 Point to the 3rd text unit ptr in list 92 | ST R6,S99TUPTR Point 3rd TU ptr to 3rd TU 93 | LA R7,DALSPGNM Get the key for SYSOUT program name 94 | STH R7,S99TUKEY Put the key in the text unit key field 95 | LA R7,1 Set count = 1 96 | STH R7,S99TUNUM Set count = 1 97 | LA R7,6 Set length = 6 98 | STH R7,S99TULNG Set length = 6 99 | MVC S99TUPAR(6),=C'INTRDR' Set parm to 'INTRDR' 100 | * Text Unit 4 - CLOSE - deallocate at close 101 | LA R6,S99TUNIT+12 Point just past 3rd text unit 102 | LA R5,S99TUPL+4 Point to the 4rd text unit ptr in list 103 | ST R6,S99TUPTR Point 4rd TU ptr to 4rd TU 104 | LA R7,DALCLOSE Get the key for status specification 105 | STH R7,S99TUKEY Put the key in the text unit key field 106 | LA R7,0 Set count = 0 107 | STH R7,S99TUNUM Set count = 0 108 | OI S99TUPTR,S99TUPLN Turn on high bit to indicate last ptr 109 | * Done building dynamic allocation request 110 | DROP R4,R5,R6,R8 111 | LR R1,R8 Put request block ptr in R1 112 | DYNALLOC Invoke DYNALLOC to process request 113 | LTR R15,R15 DYNALLOC return code 114 | BNZ SVC99ERR ...was not successful 115 | * Create an ACB with our DDNAME 116 | GENCB BLK=ACB, X 117 | MACRF=(ADR,SEQ,OUT), X 118 | DDNAME=INTRDR 119 | LTR R15,R15 Error? 120 | BNZ ACBERR ...yes 121 | ST R1,ACBADDR Store the address of the generated ACB 122 | ST R0,ACBLEN Store the length of the generated ACB 123 | * Open the internal reader 124 | LR R2,R1 R2 = R1 (address of ACB) 125 | OPEN ((R2)) Open the internal reader 126 | * Create an RPL for our PUTs 127 | GENCB BLK=RPL, X 128 | ACB=(R2), X 129 | OPTCD=(ADR,SEQ,SYN,NUP,MVE), X 130 | RECLEN=80, X 131 | AREA=JCLBUF, X 132 | AREALEN=80 133 | LTR R15,R15 Error? 134 | BNZ RPLERR ...yes 135 | ST R1,RPLADDR Store the address of the generated RPL 136 | ST R0,RPLLEN Store the length of the generated RPL 137 | * 138 | * We've opened the internal reader and set up our RPL. Now we can 139 | * indicate to the other side that they can proceed with sending the 140 | * first JCL record. 141 | * 142 | * Send the initial response 143 | LA R9,0 Just hard-code an "ok" response 144 | ST R9,RESPONSE 145 | LA R9,SUBCCW1 Load address of SUBCCW1 to R9 146 | ST R9,IOBCCWAD Point our IOB to our WRITE CCW 147 | L R9,CTCDTAAD Load address of CTCDATA DCB to R9 148 | ST R9,IOBDCBAD Point our IOB to our DCB 149 | XC EXCPECB,EXCPECB Clear EXCPECB 150 | EXCP IOB Run our WRITE command 151 | WAIT ECB=EXCPECB 152 | CLI EXCPECB,X'7F' Successful completion? 153 | BNE WRITERR ...No, bail out 154 | * While record count > 0, read a JCL record from the CTC adapter, 155 | * write it to the internal reader, and send the "ok" (hopefully) 156 | * response over the CTC adapter. 157 | L R10,JOBLEN Load the number of JCL records to R10 158 | LOOP L R9,CTCCMDAD Load address of CTCCMD DCB to R9 159 | ST R9,IOBDCBAD Point our IOB to the CTCCMD DCB 160 | LA R9,SUBCCW4 Load address of our SENSE CCW 161 | ST R9,IOBCCWAD Point our IOB to our SENSE CCW 162 | SENSLOOP XC EXCPECB,EXCPECB Clear EXCPECB 163 | XC IOBFLAGS,IOBFLAGS 164 | XC IOBSENSE,IOBSENSE 165 | XC IOBCSWFL,IOBCSWFL 166 | XC IOBRESDL,IOBRESDL 167 | EXCP IOB Run our SENSE command 168 | WAIT ECB=EXCPECB 169 | CLI EXCPECB,X'7F' Successful completion? 170 | BNE SENSERR ...No, bail out 171 | CLI SENSEREC,CONTROL Did we sense a CONTROL command? 172 | BNE SLEEP No, sleep and retry 173 | B DOREAD Yes, do the read now 174 | SLEEP STIMER WAIT,BINTVL=SLPTIM Sleep for 10ms 175 | B SENSLOOP ...then retry sense 176 | DOREAD LA R9,SUBCCW2 Load address of our READ CCW 177 | ST R9,IOBCCWAD Point our IOB to our READ CCW 178 | XC EXCPECB,EXCPECB Clear EXCPECB 179 | EXCP IOB Run our READ command 180 | WAIT ECB=EXCPECB 181 | CLI EXCPECB,X'7F' Successful completion? 182 | BNE READERR ...No, bail out 183 | L R9,RPLADDR Load address of the internal rdr RPL 184 | PUT RPL=(R9) Write the JCL record to the reader 185 | LTR R15,R15 Success? 186 | BNZ PUTERR ...No, bail out 187 | L R9,CTCDTAAD Load address of CTCDAT DCB to R9 188 | ST R9,IOBDCBAD Point our IOB to the CTCDAT DCB 189 | LA R9,SUBCCW1 Load address of our WRITE CCW 190 | ST R9,IOBCCWAD Point our IOB to our WRITE CCW 191 | LA R9,0 Load OK result code 192 | ST R9,RESPONSE Store it in the response buffer 193 | XC EXCPECB,EXCPECB Clear EXCPECB 194 | EXCP IOB Run our WRITE command 195 | WAIT ECB=EXCPECB 196 | CLI EXCPECB,X'7F' Successful completion? 197 | BNE WRITERR ...No, bail out 198 | BCT R10,LOOP Decrement count and loop if not 0 199 | * 200 | * We have read all of the JCL records and written them to the internal 201 | * reader. Now we submit the job with ENDREQ, get the job name, and 202 | * close the reader. 203 | * 204 | L R9,RPLADDR Load address of the internal rdr RPL 205 | ENDREQ RPL=(R9) 206 | LTR R15,R15 Success? 207 | BNZ ENDERR ...No, bail out 208 | MVC JOBNAME(8),60(R9) Copy job name (from RPLRBAR) 209 | * Send final response with job name 210 | LA R9,0 "ok" response 211 | ST R9,FINRESP 212 | LA R9,SUBCCW3 Load address of SUBCCW3 to R9 213 | ST R9,IOBCCWAD Point our IOB to our WRITE CCW 214 | L R9,CTCDTAAD Load address of CTCDATA DCB to R9 215 | ST R9,IOBDCBAD Point our IOB to our DCB 216 | XC EXCPECB,EXCPECB Clear EXCPECB 217 | EXCP IOB Run our WRITE command 218 | WAIT ECB=EXCPECB 219 | CLI EXCPECB,X'7F' Successful completion? 220 | BNE WRITERR ...No, bail out 221 | B CLEANUP We're done! 222 | * 223 | * Handle various errors and send unsuccessful result code. We will 224 | * put the response code in R9, and the address of the point at which to 225 | * start deallocating resources in R10. SENDERR will send the response 226 | * code then jump to R10. 227 | * 228 | BADLEN LA R9,X'F0' Invalid command length = 0xF0 229 | LA R10,QUIT 230 | B SENDERR 231 | SVC99ERR LA R9,X'F1' DYNALLOC error = 0xF1 232 | LA R10,CLEANS99 233 | B SENDERR 234 | ACBERR LA R9,X'F2' Generate ACB error = 0xF2 235 | LA R10,CLEANS99 236 | B SENDERR 237 | RPLERR LA R9,X'F3' Generate RPL error = 0xF3 238 | LA R10,CLOSEACB 239 | B SENDERR 240 | PUTERR LA R9,X'F4' PUT error = 0xF4 241 | LA R10,CLEANRPL 242 | B SENDERR 243 | ENDERR LA R9,X'F5' ENDREQ error = 0xF5 244 | LA R10,CLEANRPL 245 | B SENDERR 246 | SENDERR ST R9,RESPONSE Save the result code to RESPONSE 247 | LA R9,SUBCCW1 Load address of SUBCCW1 to R9 248 | ST R9,IOBCCWAD Point our IOB to our WRITE CCW 249 | L R9,CTCDTAAD Load address of CTCDATA DCB to R9 250 | ST R9,IOBDCBAD Point our IOB to our DCB 251 | XC EXCPECB,EXCPECB Clear EXCPECB 252 | EXCP IOB Run our WRITE command 253 | WAIT ECB=EXCPECB 254 | CLI EXCPECB,X'7F' Successful completion? 255 | BE CLEANUP ...Yes, we can quit 256 | WTO 'Unsuccessful CTC WRITE during SUBMIT error write' 257 | B CLEANUP 258 | READERR WTO 'Unsuccessful CTC READ during SUBMIT' 259 | B CLEANUP 260 | SENSERR WTO 'Unsuccessful CTC SENSE during SUBMIT' 261 | B CLEANUP 262 | WRITERR WTO 'Unsuccessful CTC WRITE during SUBMIT' 263 | * 264 | * The cleanup tasks run in reverse order of allocation, so from 265 | * different error points we can branch into them and let the remaining 266 | * ones run sequentially. 267 | * 268 | CLEANUP EQU * 269 | CLEANRPL L R4,RPLADDR 270 | L R5,RPLLEN 271 | FREEMAIN R,LV=(R5),A=(R4) Free the VSAM RPL 272 | CLOSEACB L R4,ACBADDR 273 | CLOSE ((R4)) Close our VSAM control block 274 | CLEANACB L R4,ACBADDR 275 | L R5,ACBLEN 276 | FREEMAIN R,LV=(R5),A=(R4) Free the VSAM ACB 277 | CLEANS99 L R4,DYNAREA 278 | LA R5,STORSIZE 279 | FREEMAIN R,LV=(R5),A=(R4) Free our SVC 99 request 280 | * Return to caller 281 | QUIT L R13,4(R13) Restore address of caller's save area 282 | LM R14,R12,12(R13) Restore caller's registers 283 | LA R15,0 RC=0 284 | BR R14 Return to caller 285 | * 286 | ********************************************************************** 287 | ********************************************************************** 288 | * 289 | ***** Parameters passed into us 290 | CTCCMDAD DS F 291 | CTCDTAAD DS F 292 | CMDINAD DS F 293 | * 294 | ***** Storage and CCWs for SUBMIT command 295 | SENSEREC DS CL1 Command byte from SENSE command 296 | * Initial and final responses 297 | RESPONSE DS F 298 | RESPLEN EQU *-RESPONSE 299 | FINRESP DS F Final response - result code and job nm 300 | JOBNAME DS CL8 301 | FINLEN EQU *-FINRESP 302 | * 303 | SAVEAREA DS 18F 304 | DYNAREA DS A 305 | DDN DC C'INTRDR' DDNAME to dynamically allocate 306 | JOBLEN DS F Number of job records 307 | JCLBUF DS CL80 JCL record buffer 308 | ACBADDR DS A Generated ACB address 309 | ACBLEN DS F Generated ACB size 310 | RPLADDR DS A Generated RPL address 311 | RPLLEN DS F Generated RPL size 312 | *********************************************************************** 313 | * Channel programs 314 | SUBCCW1 CCW CONTROL,RESPONSE,SLI+CC,1 Send CONTROL+WRITE with 315 | CCW WRITE,RESPONSE,SLI,RESPLEN response code 316 | SUBCCW2 CCW READ,JCLBUF,SLI,80 Read a JCL record 317 | SUBCCW3 CCW CONTROL,RESPONSE,SLI+CC,1 Send CONTROL+WRITE with 318 | CCW WRITE,FINRESP,SLI,FINLEN response code + job name 319 | SUBCCW4 CCW SENSE,SENSEREC,SLI,1 Send SENSE 320 | WRITE EQU X'01' 321 | READ EQU X'02' 322 | CONTROL EQU X'07' 323 | SENSE EQU X'14' 324 | SLI EQU X'20' 325 | CC EQU X'40' 326 | * EXCP IOB 327 | IOB DS 0F 328 | IOBFLAGS DC XL2'0000' 329 | IOBSENSE DC XL2'0000' 330 | IOBECBAD DC A(EXCPECB) 331 | IOBCSW DC A(0) 332 | IOBCSWFL DC XL2'0000' 333 | IOBRESDL DC H'00' 334 | IOBCCWAD DC A(0) 335 | IOBDCBAD DC A(0) 336 | DC F'0' 337 | DC F'0' 338 | EXCPECB DS F 339 | * Utility variables 340 | DS 0F 341 | CMDLNMSK DC X'00FFFF00' Mask to get the param length 342 | SLPTIM DC F'1' 343 | PRINT NOGEN 344 | IEFZB4D0 , DYNALLOC DSECT 345 | IEFZB4D2 , DYNALLOC symbolic names 346 | RBLEN EQU S99RBEND-S99RB Length of SVC99 request block (RB) 347 | ********************************************************************** 348 | * Register symbols * 349 | ********************************************************************** 350 | R0 EQU 0 351 | R1 EQU 1 352 | R2 EQU 2 353 | R3 EQU 3 354 | R4 EQU 4 355 | R5 EQU 5 356 | R6 EQU 6 357 | R7 EQU 7 358 | R8 EQU 8 359 | R9 EQU 9 360 | R10 EQU 10 361 | R11 EQU 11 362 | R12 EQU 12 363 | R13 EQU 13 364 | R14 EQU 14 365 | R15 EQU 15 366 | END SUBMIT 367 | -------------------------------------------------------------------------------- /MVS/WRITEDS: -------------------------------------------------------------------------------- 1 | *********************************************************************** 2 | * MVS SERVICES OVER CTC - WRITE Command (0x05) * 3 | * * 4 | * Copyright 2023 Matthew R. Wilson * 5 | * * 6 | * This file is part of CTC Mainframe API. CTC Mainframe API is free * 7 | * software: you can redistribute it and/or modify it under the terms * 8 | * of the GNU General Public License as published by the Free Software * 9 | * Foundation, either version 3 of the license, or (at your option) * 10 | * any later version. * 11 | *********************************************************************** 12 | * 13 | PRINT GEN 14 | WRITEDS CSECT 15 | SAVE (14,12),,* Save caller's registers 16 | BALR R12,0 Load current address 17 | USING *,R12 Establish addressability 18 | ST R13,SAVEAREA+4 Store caller's savearea address 19 | LA R13,SAVEAREA Load address of our savearea 20 | ********************************************************************** 21 | * COMMAND: WRITE (0x05) * 22 | * First 44 bytes of command parameter will be the name of the data- * 23 | * set to write. The following 8 bytes is the optional member name if * 24 | * it's a PDS. If not requesting a PDS member, the first byte of the * 25 | * member name must be a space. * 26 | * * 27 | * If the dataset exists, and if a member name is requested, if the * 28 | * dataset is a PDS, and if the dataset has fixed length records, * 29 | * we will reply with an "OK" response and the record length of the * 30 | * dataset. * 31 | * * 32 | * At that point, the caller will respond with an "intent to proceed" * 33 | * and number of records response, or a "cancel" response (e.g. if * 34 | * the records the caller was planning on sending are too long for * 35 | * the dataset's record length. * 36 | * * 37 | * If the caller proceeds, we will loop until the record count is 0, * 38 | * reading one record from the CTC adapter at a time and putting it * 39 | * into the dataset. After the counter gets to 0, we will close the * 40 | * dataset. * 41 | ********************************************************************** 42 | * Copy parameter list addresses 43 | MVC CTCCMDAD,0(R1) Address of CTCCMD DCB 44 | MVC CTCDTAAD,4(R1) Address of CTCDATA DCB 45 | MVC CMDINAD,8(R1) Address of command input data 46 | * Check that the parameter (dataset+mbr name) length is 52 bytes 47 | L R2,CMDINAD Get address of command input data 48 | L R1,0(,R2) Get command parameter length 49 | N R1,CMDLNMSK Mask out the command param length 50 | SRL R1,8 Shift right 8 bits 51 | LA R3,52 R3 = 52 52 | CLR R1,R3 Length = 52? 53 | BNE BADLEN No, bail out 54 | * Get the DSNAME and member name from the command input area 55 | MVC DYNDSN,3(R2) 56 | MVC DYNMBR,47(R2) 57 | * Reset put error status code from any prior invocations 58 | XC PUTERROR,PUTERROR Reset PUTERROR to 0 59 | * 60 | * Before we try to write a dataset, we will first locate it in the 61 | * catalog then read the DSCB from the volume's VTOC. We want to check 62 | * if it's a supported type (non-VSAM, PS or PO, fixed record length) 63 | * and if a member name is provided, that the dataset is PO. 64 | * 65 | * LOCATE the dataset in the catalog. 66 | LOCATE LOCCMLST LOCATE the dataset name in the catalog 67 | LTR R15,R15 Success? 68 | BNZ LOCERR ...no, return the condition code 69 | * OBTAIN the DSCB 70 | MVC OBTVOLSR,LOCWRK+6 Copy 1st volume serial # to our OBTAIN 71 | OBTAIN OBTCMLST Get the DSBC for the dataset 72 | LTR R15,R15 Successful completion? 73 | BNZ LOCERR ...no, return the condition code 74 | * Make sure we got a format-1 DSCB 75 | LA R1,DSCBAREA Get the address of our DSCB data 76 | CLI 0(R1),X'F1' Is this a format-1 DSCB? 77 | BNE FMTERR ...no, report format error 78 | * Now we check if the dataset is PO or PS 79 | CLI DYNMBR,C' ' Is first character of member name ' '? 80 | BNE CHKPO ...no, check that DSORG is PO 81 | CHKPS NI 38(R1),X'40' ...yes, check that DSORG is PS 82 | BZ FMTERR ...no, org of dataset is unsup. 83 | B CHKORG ...yes, DSORG=PS 84 | CHKPO NI 38(R1),X'02' Check that DSORG is PO 85 | BZ FMTERR ...no, organization of dataset is unsup 86 | CHKORG NI 40(R1),X'80' Fixed recln? 87 | BZ FMTERR ...no, dataset is unsupported 88 | * 89 | * At this point, we think we have a dataset that is supported and 90 | * matches the user's request. 91 | * 92 | * Since we don't know the DSNAME of the dataset ahead of time, we need 93 | * to dynamically allocate the dataset at runtime using the DYNALLOC / 94 | * SVC99 capability of MVS. Building our dynamic allocation request 95 | * block here is modeled on the example beginning on page 74 of 96 | * GC28-0627-2, OS/VS2 MVS System Programming Library: Job Management. 97 | * 98 | * Get storage for our dynamic allocation SVC 99 request 99 | STORSIZE EQU 200 200 bytes is well more than we need 100 | LA R0,STORSIZE 101 | GETMAIN R,LV=(R0) Get the storage necessary 102 | ST R1,DYNAREA Save the address to DYNAREA 103 | XC 0(STORSIZE,R1),0(R1) Zero out the memory 104 | * ...and build the request block (RB) 105 | LR R8,R1 Save the address to R8 106 | USING S99RBP,R8 Addressability for RBPTR DSECT 107 | LA R4,S99RBPTR+4 Point 4 bytes beyond start of RBPTR 108 | USING S99RB,R4 Addressability for RB DSECT 109 | ST R4,S99RBPTR Point RBPTR to RB 110 | OI S99RBPTR,S99RBPND Turn on the high order bit in RBPTR 111 | XC S99RB(RBLEN),S99RB Zero out RB 112 | MVI S99RBLN,RBLEN Put the length of RB in its length fld 113 | MVI S99VERB,S99VRBAL Set verb to allocation function 114 | LA R5,S99RB+RBLEN Point 20 bytes beyond start of RB 115 | USING S99TUPL,R5 Addressability for text unit ptrs 116 | ST R5,S99TXTPP Init text points address in RB 117 | LA R6,S99TUPL+20 Point just past 5 text pointers 118 | USING S99TUNIT,R6 Addressability for 1st text unit 119 | * Text Unit 1 - DALRTDDN (Return DDNAME) 120 | ST R6,S99TUPTR Point 1st TU ptr to 1st TU 121 | LA R7,DALRTDDN Get the key for DDNAME 122 | STH R7,S99TUKEY Put the key in the text unit key field 123 | LA R7,1 Set count = 1 124 | STH R7,S99TUNUM Set count = 1 125 | LA R7,8 DDNAMEs may be up to 8 characters long 126 | STH R7,S99TULNG ...and put it in the TU length field 127 | LA R1,S99TUPAR Get addr where the DDNAME will end up 128 | ST R1,DDN ...and save to storage 129 | * Text Unit 2 - DSNAME 130 | LA R6,S99TUNIT+14 Point just past 1st text unit 131 | LA R5,S99TUPL+4 Point to the 2nd text unit ptr in list 132 | ST R6,S99TUPTR Point 2nd TU ptr to 2nd TU 133 | LA R7,DALDSNAM Get the key for DSNAME 134 | STH R7,S99TUKEY Put the key in the text unit key field 135 | LA R7,1 Set count = 1 136 | STH R7,S99TUNUM Set count = 1 137 | * We will copy up to 44 characters, but we need to provide the real 138 | * count this time because space padding on the end doesn't work when 139 | * there's potentially a member name involved. 140 | LA R9,C' ' R9 = space character 141 | LA R2,DYNDSN R2 = address of DYNDSN 142 | LA R10,0 R10 = 0 143 | LA R11,1 R11 = 1 144 | FINDSPC CLM R9,1,0(R2) Is DYNDSN[R10] = ' '? 145 | BE FOUNDIT ...yes 146 | AR R10,R11 ...no, R10 = R10+1 147 | AR R2,R11 ...and R2 = R2 + 1 148 | B FINDSPC 149 | FOUNDIT STH R10,S99TULNG ...yes, put length in the TU len fld 150 | MVC S99TUPAR(L'DYNDSN),DYNDSN Put the DSNAME into parm fld 151 | * Text Unit 3 - STATUS (set to OLD) 152 | LA R6,S99TUNIT+6+L'DYNDSN Point just past 2nd text unit 153 | LA R5,S99TUPL+4 Point to the 3rd text unit ptr in list 154 | ST R6,S99TUPTR Point 3rd TU ptr to 3rd TU 155 | LA R7,DALSTATS Get the key for status specification 156 | STH R7,S99TUKEY Put the key in the text unit key field 157 | LA R7,1 Set count = 1 158 | STH R7,S99TUNUM Set count = 1 159 | STH R7,S99TULNG Set length = 1 160 | MVI S99TUPAR,X'01' Set parm to OLD 161 | * Text Unit 4 - CLOSE - deallocate at close 162 | LA R6,S99TUNIT+7 Point just past 3rd text unit 163 | LA R5,S99TUPL+4 Point to the 4rd text unit ptr in list 164 | ST R6,S99TUPTR Point 4rd TU ptr to 4rd TU 165 | LA R7,DALCLOSE Get the key for status specification 166 | STH R7,S99TUKEY Put the key in the text unit key field 167 | LA R7,0 Set count = 0 168 | STH R7,S99TUNUM Set count = 0 169 | * Do we need to add member name text unit? 170 | CLI DYNMBR,C' ' Is member name blank? 171 | BNE TUMEMBER ...no, add the member name text unit 172 | OI S99TUPTR,S99TUPLN Turn on high bit to indicate last ptr 173 | B SKIPMBR 174 | * Text Unit 5 - member name 175 | TUMEMBER LA R6,S99TUNIT+4 Point just past 4th text unit 176 | LA R5,S99TUPL+4 Point to the 5th text unit ptr in list 177 | ST R6,S99TUPTR Point 5th TU ptr to 5th TU 178 | LA R7,DALMEMBR Get the key for member name 179 | STH R7,S99TUKEY Put the key in the text unit key field 180 | LA R7,1 Set count = 1 181 | STH R7,S99TUNUM Set count = 1 182 | LA R7,L'DYNMBR Set length = 8 183 | STH R7,S99TULNG Set length = 8 184 | MVC S99TUPAR(L'DYNMBR),DYNMBR Set member name 185 | OI S99TUPTR,S99TUPLN Turn on high bit to indicate last ptr 186 | * Done building dynamic allocation request 187 | SKIPMBR EQU * 188 | DROP R4,R5,R6,R8 189 | LR R1,R8 Put request block ptr in R1 190 | DYNALLOC Invoke DYNALLOC to process request 191 | LTR R15,R15 DYNALLOC return code 192 | BNZ SVC99ERR ...was not successful 193 | * Prepare our DCB with the default values 194 | MVC DYNDCB(DCBLEN),MDLDCB 195 | * Copy the DDNAME to our DCB 196 | L R1,DDN Get address of DDNAME param 197 | MVC DYNDCB+40(8),0(R1) Copy the DDNAME 198 | LA R4,STORSIZE 199 | L R5,DYNAREA 200 | FREEMAIN R,LV=(R4),A=(R5) Free our storage 201 | * Tell the caller about the successful allocation and return the LRECL 202 | * of the allocated dataset. 203 | XC RESPCODE,RESPCODE Set RESPCODE to 0 for "ok" 204 | LH R10,DSCBAREA+44 R10 = LRECL 205 | ST R10,RESPCOD2 Store the LRECL into RESPCODE2 206 | ST R10,RECL ...and into RECL 207 | GETMAIN R,LV=(R10) Get memory of LRECL length 208 | ST R1,GETAREA Save the address to GETAREA 209 | * Send the initial response 210 | LA R9,WRTCCW1 Load address of WRTCCW1 to R9 211 | ST R9,IOBCCWAD Point our IOB to our WRITE CCW 212 | L R9,CTCDTAAD Load address of CTCDATA DCB to R9 213 | ST R9,IOBDCBAD Point our IOB to our DCB 214 | XC EXCPECB,EXCPECB Clear EXCPECB 215 | EXCP IOB Run our WRITE command 216 | WAIT ECB=EXCPECB 217 | CLI EXCPECB,X'7F' Successful completion? 218 | BNE WRITERR ...No, bail out 219 | * Read "proceed" or "abort" from command channel 220 | L R9,CTCCMDAD Load address of CTCCMD DCB to R9 221 | ST R9,IOBDCBAD Point our IOB to our DCB 222 | LA R9,WRTCCW2 Load address of our "read" WRTCCW2 223 | ST R9,IOBCCWAD Point our IOB to our READ CCW 224 | XC EXCPECB,EXCPECB Clear EXCPECB 225 | EXCP IOB 226 | WAIT ECB=EXCPECB 227 | CLI EXCPECB,X'7F' Successful completion? 228 | BNE READERR ...No, bail out 229 | * Check if caller intends to proceed 230 | L R9,RESPCODE Load the caller's response status word 231 | LTR R9,R9 Check the value in R9 232 | BNZ ABORT Caller didn't use 0 "intent to proceed" 233 | * Proceed. Get the number of records the caller intends to send 234 | L R8,RESPCOD2 R8 = # of records 235 | OPEN (DYNDCB,(OUTPUT)) Open the output dataset 236 | * Send "okay to proceed" to caller 237 | L R9,CTCDTAAD Load address of CTCDAT DCB to R9 238 | ST R9,IOBDCBAD Point our IOB to the CTCDAT DCB 239 | LA R9,WRTCCW1 Load address of our WRITE CCW 240 | ST R9,IOBCCWAD Point our IOB to our WRITE CCW 241 | LA R9,0 Load OK result code 242 | ST R9,RESPCODE Store it in the response buffer 243 | XC EXCPECB,EXCPECB Clear EXCPECB 244 | EXCP IOB Run our WRITE command 245 | WAIT ECB=EXCPECB 246 | CLI EXCPECB,X'7F' Successful completion? 247 | BNE WRITERR ...No, bail out 248 | * Set up for loop over each record 249 | L R10,RECL R10 = LRECL 250 | STH R10,WRTCCW3+6 Set CCW length to LRECL 251 | L R1,GETAREA R1 = address of our buffer storage 252 | ST R1,WRTCCW3 Save the address to our CCW 253 | LA R1,READ ...restore the CCW command byte 254 | STC R1,WRTCCW3 ...restore the CCW command byte 255 | * While record count > 0, read a record from the CTC adapter, 256 | * write it to the dataset, and send the "ok" (hopefully) 257 | * response over the CTC adapter. 258 | LOOP L R9,CTCCMDAD Load address of CTCCMD DCB to R9 259 | ST R9,IOBDCBAD Point our IOB to the CTCCMD DCB 260 | LA R9,WRTCCW4 Load address of our SENSE CCW 261 | ST R9,IOBCCWAD Point our IOB to our SENSE CCW 262 | SENSLOOP XC EXCPECB,EXCPECB Clear EXCPECB 263 | EXCP IOB Run our SENSE command 264 | WAIT ECB=EXCPECB 265 | CLI EXCPECB,X'7F' Successful completion? 266 | BNE SENSERR ...No, bail out 267 | CLI SENSEREC,CONTROL Did we sense a CONTROL command? 268 | BNE SLEEP No, sleep and retry 269 | B DOREAD Yes, do the read now 270 | SLEEP STIMER WAIT,BINTVL=SLPTIM Sleep for 10ms 271 | B SENSLOOP ...then retry sense 272 | DOREAD LA R9,WRTCCW3 Load address of our READ CCW 273 | ST R9,IOBCCWAD Point our IOB to our READ CCW 274 | XC EXCPECB,EXCPECB Clear EXCPECB 275 | EXCP IOB Run our READ command 276 | WAIT ECB=EXCPECB 277 | CLI EXCPECB,X'7F' Successful completion? 278 | BNE READERR ...No, bail out 279 | L R9,GETAREA Load address of the record buffer 280 | PUT DYNDCB,(R9) Write the record to the dataset 281 | L R15,PUTERROR Load the result our SYNAD handler set 282 | LTR R15,R15 Success? 283 | BNZ PUTERR ...No, bail out 284 | L R9,CTCDTAAD Load address of CTCDAT DCB to R9 285 | ST R9,IOBDCBAD Point our IOB to the CTCDAT DCB 286 | LA R9,WRTCCW1 Load address of our WRITE CCW 287 | ST R9,IOBCCWAD Point our IOB to our WRITE CCW 288 | LA R9,0 Load OK result code 289 | ST R9,RESPCODE Store it in the response buffer 290 | XC EXCPECB,EXCPECB Clear EXCPECB 291 | EXCP IOB Run our WRITE command 292 | WAIT ECB=EXCPECB 293 | CLI EXCPECB,X'7F' Successful completion? 294 | BNE WRITERR ...No, bail out 295 | BCT R8,LOOP Decrement count and loop if not 0 296 | B DONE ...Yes, we're done 297 | ABORT OPEN (DYNDCB,(INPUT)) 298 | B DONE 299 | * 300 | * Handle various errors and send unsuccessful result code 301 | BADLEN LA R9,X'F0' Invalid DS length = 0xF0 302 | B SENDERR 303 | LOCERR ST R15,RESPCOD2 Move the LOCATE/OBTAIN result RESPCOD2 304 | LA R9,X'F1' Dataset locate error = 0xF1 305 | B SENDERR 306 | FMTERR LA R9,X'F2' Requested DS is not supported 307 | B SENDERR 308 | READERR LA R9,X'F5' Error during CTC READ 309 | B SENDERR 310 | PUTERR LA R9,X'F6' Error during access method PUT 311 | B SENDERR 312 | SENSERR LA R9,X'F7' Error during CTC SESNE 313 | B SENDERR 314 | SVC99ERR LA R9,X'F3' Dynamic allocation error 315 | B CLEANUP 316 | CLEANUP LA R4,STORSIZE 317 | L R5,DYNAREA 318 | FREEMAIN R,LV=(R4),A=(R5) Free our storage 319 | WTO 'Unsuccessful DYNALLOC during READ' 320 | B SENDERR 321 | SENDERR ST R9,RESPCODE Save the result code to RESPONSE 322 | LA R9,WRTCCW1 Load address of WRTCCW1 to R9 323 | ST R9,IOBCCWAD Point our IOB to our WRITE CCW 324 | L R9,CTCDTAAD Load address of CTCDATA DCB to R9 325 | ST R9,IOBDCBAD Point our IOB to our DCB 326 | XC EXCPECB,EXCPECB Clear EXCPECB 327 | EXCP IOB Run our WRITE command 328 | WAIT ECB=EXCPECB 329 | CLI EXCPECB,X'7F' Successful completion? 330 | BE QUIT ...Yes, we can quit 331 | WTO 'Unsuccessful CTC WRITE during READ error write' 332 | B QUIT 333 | WRITERR WTO 'Unsuccessful CTC WRITE during READ' 334 | DONE CLOSE (DYNDCB) 335 | FREEPOOL DYNDCB 336 | * Send final "success" status 337 | L R9,CTCDTAAD Load address of CTCDAT DCB to R9 338 | ST R9,IOBDCBAD Point our IOB to the CTCDAT DCB 339 | LA R9,WRTCCW1 Load address of our WRITE CCW 340 | ST R9,IOBCCWAD Point our IOB to our WRITE CCW 341 | LA R9,0 Load OK result code 342 | ST R9,RESPCODE Store it in the response buffer 343 | XC EXCPECB,EXCPECB Clear EXCPECB 344 | EXCP IOB Run our WRITE command 345 | WAIT ECB=EXCPECB 346 | CLI EXCPECB,X'7F' Successful completion? 347 | BNE WRITERR ...No, bail out 348 | L R4,GETAREA 349 | L R5,RECL 350 | FREEMAIN R,LV=(R5),A=(R4) Free our record buffer memory 351 | * Return to caller 352 | QUIT L R13,4(R13) Restore address of caller's save area 353 | LM R14,R12,12(R13) Restore caller's registers 354 | LA R15,0 RC=0 355 | BR R14 Return to caller 356 | * PUT error handling routine 357 | ERRHAND LA R1,1 358 | ST R1,PUTERROR 359 | BR R14 360 | PUTERROR DC F'0' 361 | * 362 | ********************************************************************** 363 | ********************************************************************** 364 | * 365 | ***** Parameters passed into us 366 | CTCCMDAD DS F 367 | CTCDTAAD DS F 368 | CMDINAD DS F 369 | * 370 | ***** Storage and CCWs for WRITE command 371 | * Initial response 372 | RESPONSE DS 0F 373 | RESPCODE DS F 374 | RESPCOD2 DC F'0' 375 | RESPLEN EQU *-RESPONSE 376 | * 377 | GETAREA DS A 378 | RECL DS F 379 | SENSEREC DS CL1 380 | * 381 | SAVEAREA DS 18F 382 | DYNAREA DS A 383 | DDN DS A Address of location of dynamic DDNAME 384 | DYNDSN DS CL44 DSNAME to dynamically allocate 385 | DC C' ' Terminating space (right after DYNDSN 386 | * for safety) 387 | DYNMBR DS CL8 Member name to dynamically allocate 388 | DYNDCB DCB DDNAME=XXXXXXXX,MACRF=PM,DSORG=PS,SYNAD=ERRHAND 389 | * Model DCB that we will use to reset the DCB to default state after 390 | * each use. 391 | MDLDCB DCB DDNAME=XXXXXXXX,MACRF=PM,DSORG=PS,SYNAD=ERRHAND 392 | DCBLEN EQU *-MDLDCB 393 | * LOCATE and OBTAIN storage 394 | LOCCMLST CAMLST NAME,DYNDSN,,LOCWRK Will locate DSNAME in DYNDSN 395 | LOCWRK DS 0D 396 | DS 265C 397 | OBTCMLST CAMLST SEARCH,DYNDSN,OBTVOLSR,DSCBAREA 398 | OBTVOLSR DS CL6 399 | DSCBAREA DS 0D 400 | DS CL140 401 | *********************************************************************** 402 | * Channel programs 403 | WRTCCW1 CCW CONTROL,RESPONSE,SLI+CC,1 404 | CCW WRITE,RESPONSE,SLI,RESPLEN 405 | WRTCCW2 CCW READ,RESPONSE,SLI,RESPLEN 406 | WRTCCW3 CCW READ,0,SLI,1 0 will be set at runtime 407 | WRTCCW4 CCW SENSE,SENSEREC,SLI,1 Send SENSE 408 | WRITE EQU X'01' 409 | READ EQU X'02' 410 | CONTROL EQU X'07' 411 | SENSE EQU X'14' 412 | SLI EQU X'20' 413 | CC EQU X'40' 414 | * EXCP IOB 415 | IOB DS 0F 416 | IOBFLAGS DC XL2'0000' 417 | IOBSENSE DC XL2'0000' 418 | IOBECBAD DC A(EXCPECB) 419 | IOBCSW DC A(0) 420 | IOBCSWFL DC XL2'0000' 421 | IOBRESDL DC H'00' 422 | IOBCCWAD DC A(0) 423 | IOBDCBAD DC A(0) 424 | DC F'0' 425 | DC F'0' 426 | SLPTIM DC F'1' 427 | EXCPECB DS F 428 | * Utility variables 429 | DS 0F 430 | CMDLNMSK DC X'00FFFF00' Mask to get the param length 431 | PRINT NOGEN 432 | IEFZB4D0 , DYNALLOC DSECT 433 | IEFZB4D2 , DYNALLOC symbolic names 434 | RBLEN EQU S99RBEND-S99RB Length of SVC99 request block (RB) 435 | ********************************************************************** 436 | * Register symbols * 437 | ********************************************************************** 438 | R0 EQU 0 439 | R1 EQU 1 440 | R2 EQU 2 441 | R3 EQU 3 442 | R4 EQU 4 443 | R5 EQU 5 444 | R6 EQU 6 445 | R7 EQU 7 446 | R8 EQU 8 447 | R9 EQU 9 448 | R10 EQU 10 449 | R11 EQU 11 450 | R12 EQU 12 451 | R13 EQU 13 452 | R14 EQU 14 453 | R15 EQU 15 454 | END WRITEDS 455 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CTC Mainframe API 2 | 3 | An HTTP web service for your MVS 3.8 mainframe, via an emulated 4 | channel-to-channel adapter, with support for all versions of Hercules in 5 | common use (3.13, Spinhawk, and SDL-Hyperion). 6 | 7 | No guarantees are made as to functionality or reliability. Additionally, see 8 | the "Limitations and security" section of this document for important security 9 | information. 10 | 11 | By Matthew R. Wilson, . Original repository at 12 | 13 | 14 | **This is very preliminary and everything is subject to change.** 15 | 16 | ## How-To 17 | 18 | ### Configure Hercules 19 | 20 | All Hercules versions and forks from Hercules 3.13 onward are supported. 21 | However, the CTC implementation in Hercules 3.13 CTC is much less robust than 22 | in Spinhawk and Hyperion in terms of initial connection order of operations 23 | and connection retry capability. 24 | 25 | Add a pair of CTC adapters to your Hercules configuration file. The following 26 | syntax works in Hercules 3.13, Spinhawk, and Hyperion. 27 | 28 | ``` 29 | # lport rhost rport 30 | 0502 CTCE 15620 127.0.0.1 15600 31 | 0503 CTCE 15630 127.0.0.1 15610 32 | ``` 33 | 34 | If using Hercules 3.13, lport and rport all must be even numbers. rhost is the 35 | address of the system you're running the Go binary on that will host the HTTP 36 | API. 37 | 38 | The device numbers (502 and 503 in this example) must go into the JCL 39 | procedure that starts the CTCSERV program in MVS in the `CTCCMD` and `CTCDATA` 40 | DD statements. The devices must have been defined as CTC devices in your 41 | system. 500-503 are available for use in the default Moseley sysgen. 42 | 43 | ### Configure ctcserver 44 | 45 | Next, copy the config.json.sample file to config.json and adjust it 46 | appropriately: 47 | 48 | * `listen_port` is the HTTP listener port the service will listen on. 49 | * `hercules_host` is the address of your system Hercules runs on. 50 | * `hercules_v313` must be true for Hercules 3.13, false for all other 51 | versions (spinhawk, hyperion). 52 | * `hercules_host_bigendian` should be false for most users. If your Hercules 53 | is running on a big endian system (sparcv9, ppc64be, s390x, etc.), set to 54 | true. 55 | * `cmd_local_port` should match the lport of your first CTC definition in 56 | Hercules (15620 in the above example). 57 | * `cmd_remote_port` should match the rport of your first CTC definition in 58 | Hercules (15600 in the above example). 59 | * `data_local_port` should match the lport of your second CTC definition in 60 | Hercules (15630 in the above example). 61 | * `data_remote_port` should match the lport of your second CTC definition in 62 | Hercules (15610 in the above example). 63 | 64 | ### Start everything 65 | 66 | **If you're using Hercules 3.13**, startup order is very important: 67 | 68 | 1. Start the ctcserver binary on your host system. 69 | 2. Start Hercules on your host system. 70 | 3. IPL MVS. 71 | 72 | For spinhawk and hyperion, startup order doesn't matter. Those versions of 73 | Hercules will always attempt to re-establish the CTC connections, so you can 74 | start and stop the ctcserver binary without needing to shut down and restart 75 | Hercules and MVS. 76 | 77 | Once ctcserver is running and MVS is IPLed, start the CTCSERV job under MVS. 78 | You may now make HTTP requests against the API. 79 | 80 | The available functions are listed in the "Available functions" section of 81 | this document. 82 | 83 | ### Recovering from problems 84 | 85 | The CTC adapters are very sensitive to maintaing correct state synchronization 86 | between all parties involved. Furthermore, any bugs in my code could also 87 | contribute to things getting out of a good state. If you're using Hercules 88 | 3.13... you probably just need to shut everything down and start over. 89 | 90 | But if you're using Spinhawk or Hyperion and things stop working, you can 91 | recover without needing to re-IPL MVS: 92 | 93 | 1. Make sure the CTCSERV job on MVS is stopped (e.g. cancel it from the 94 | console if you have to). 95 | 2. Take the CTC adapters offline from the MVS console (e.g. `V 502,OFFLINE` 96 | and `V 503,OFFLINE`). 97 | 3. Remove the CTC adapters from Hercules (e.g. `detach 502` and 98 | `detach 503`). 99 | 4. Re-add the CTC adapters to Hercules (e.g. 100 | `attach 502 CTCE 15620 127.0.0.1 15600` and 101 | `attach 503 CTCE 15630 127.0.0.1 15610`). 102 | 5. Vary the CTC adapters online from the MVS console (e.g. `V 502,ONLINE` and 103 | `V 503,ONLINE`). 104 | 6. Start the CTCSERV job in MVS again, and start the ctcserver binary on the 105 | host system again. 106 | 107 | ## Repository layout 108 | 109 | This repository contains the following subdirectories for each component of 110 | the overall product: 111 | 112 | **ctcserver**: Go program that communicates with the MVS service over the 113 | emulated CTC adapter and presents the functions as a web service API. 114 | 115 | **MVS**: the members of a partitioned dataset in MVS with the assembler source 116 | for the MVS-side service and various JCL procedures to build and run the 117 | service. 118 | 119 | ## Available functions 120 | 121 | ### Dataset list 122 | 123 | `GET /api/dslist/` 124 | 125 | The dataset list will search the catalog for all datasets that begin with 126 | `` and return basic information about them. If the prefix is a single 127 | component (e.g. `FOO` instead of `FOO.BAR`), the API will add a period 128 | (`FOO.`) so that actual datasets are returned beginning with `FOO.` instead of 129 | just the `FOO` alias entry in the catalog. 130 | 131 | ### PDS member list 132 | 133 | `GET /api/mbrlist/` 134 | 135 | If `` is a partitioned dataset, the member list API will return the list 136 | of member names. 137 | 138 | ### Read dataset 139 | 140 | `GET /api/read/` 141 | 142 | `` is the name of a dataset you wish to read. The response body will be 143 | of type text/plain containing the ASCII-converted records with trailing spaces 144 | trimmed and a newline inserted after each record. 145 | 146 | Alternatively, for the raw EBCDIC version of the data, add an `ebcdic=true` 147 | query parameter: `GET /api/read/?ebcdic=true`. This will return a content 148 | type of application/octet-stream with the data from the mainframe left 149 | untouched. 150 | 151 | Sequential datasets (e.g. `HLQ.DS1`) and members of partitioned datasets (e.g. 152 | `HLQ.DS2(MEMBER)`) are supported. Datasets with fixed or variable record 153 | length (F, FB, V, or VB) are supported. 154 | 155 | When using raw EBCDIC mode, the output from datasets with variable record 156 | length will include the 4-byte Record Descriptor Word. 157 | 158 | ### Submit job 159 | 160 | `POST /api/submit` 161 | 162 | The request body is the JCL of the job to submit, each line of which must be 163 | 80 characters or fewer (including any in-stream data). If successfully 164 | submitted, the response body will be the job identifier assigned by the system 165 | (e.g. `JOB00073`). Note that in some cases, if the job cannot be processed, 166 | instead of an error result, you will get a seemingly successful result, but 167 | with the job identifier matching the CTC Server job's identifier instead of a 168 | newly generated job identifier. 169 | 170 | For example, to send a job with cURL: 171 | 172 | ``` 173 | curl -X POST --data-binary @- http://localhost:8370/api/submit << __EOF__ 174 | //APIJOB JOB CLASS=A,MSGCLASS=X 175 | //NOTHING EXEC PGM=IEFBR14 176 | __EOF__ 177 | ``` 178 | 179 | ### Write to a dataset 180 | 181 | `POST /api/write/` 182 | 183 | `` is the fully-qualified dataset name (optionally including a member 184 | name if the dataset is a PDS) to write to. The dataset **must** already be 185 | allocated, and it must be a non-VSAM, fixed-record-length PO or PS dataset. 186 | 187 | The request body consists of the records to place into the dataset. All 188 | existing records in the dataset will be deleted and the new version of the 189 | dataset will include only the records provided in the API call. Each record 190 | must be less than or equal to the number of characters that the dataset LRECL 191 | is allocated with. 192 | 193 | For example, to write to a dataset with cURL: 194 | 195 | ``` 196 | curl -X POST --data-binary @- http://localhost:8370/api/write/HERC01.MEMO(HI) << __EOF__ 197 | Hello from CTC Mainframe API. 198 | This dataset contents was written via the API call named "write". 199 | __EOF__ 200 | ``` 201 | 202 | This, of course, assumes that HERC01.MEMO is already allocated as a F or FB, 203 | PO dataset with an LRECL >= 65 (to handle the longest line of the input data). 204 | 205 | ### Quit 206 | 207 | `GET /api/quit` 208 | 209 | Calling this API will stop the job running the CTC service on the MVS side. To 210 | prevent CTC device syncronization problems, you should not make further API 211 | calls to the web service until the CTC server job is started on the MVS side 212 | again. 213 | 214 | ## Example API usage 215 | 216 | The combination of the _PDS member list_ API and the _Read dataset_ API allow 217 | you to easily save all the members from a PDS to a local directory. For 218 | example, with the API service running on port 8370, I wish to retrieve all 219 | members of the partitioned dataset `MWILSON.CTCSERV` into a new directory to 220 | backup the source code for this project: 221 | 222 | ``` 223 | $ mkdir CTCSERV 224 | $ cd CTCSERV 225 | $ for x in $(curl -s http://127.0.0.1:8370/api/mbrlist/mwilson.ctcserv | jq -r '.[]') 226 | for> do 227 | for> curl -s -o "$x" "http://127.0.0.1:8370/api/read/mwilson.ctcserv($x)" 228 | for> done 229 | $ ls 230 | '$$$INDEX' '$BUILD' '$COPYING' '$DEBUG' '$RUN' CTCSERV DSLIST MBRLIST 231 | READ 232 | ``` 233 | 234 | ## Limitations and security 235 | 236 | **Security: there is none**. No security is implemented at all on either the 237 | web service side or the MVS service side. Anyone who has access to the web 238 | service, or directly to the emulated CTC device ports on your Hercules 239 | instance, will be able to make full use of the services. 240 | 241 | I have not tested this on an MVS system with RAKF (or, for that matter, RACF) 242 | installed. A security product may limit the actions the service can take to 243 | those that the user running the service can take. If this is important to you, 244 | you would need to thoroughly test that assumption. Future updates to the 245 | MVS-side code may require that it run APF-authorized; at that point, even your 246 | security product may not apply its access controls to operations performed 247 | through this service. _Caveat emptor_. 248 | 249 | A _non-exhaustive_ list of current known limitations includes: 250 | 251 | - All access to datasets assumes that they are cataloged; there is no support 252 | for specifying volumes to access uncataloged names. 253 | - Any actions involving datasets that span multiple volumes are untested. 254 | 255 | The **only** public interface is the HTTP API provided by the Go server; the 256 | CTC interface on the mainframe side is intended only for use by the 257 | accompanying Go code. It does not do any input validation; it is programmed to 258 | assume the calling Go code already takes care of this. 259 | 260 | ## TODO 261 | 262 | One problem with the implementation right now is that I haven't yet figured 263 | out how to wait on data to arrive at the CTC adapter, so I have to sit in a 264 | polling loop running a SENSE CCW. That's not ideal. There may be a way to get 265 | MVS to act on the real device attention interruption and POST to a WAIT in the 266 | service... the first attempt I've made at this didn't work, but there's still 267 | something else to try. 268 | 269 | Otherwise, it'd be cool to add: 270 | 271 | * Improve the write API call: more detailed error handling with the underlying 272 | access method result codes available to callers when error occur. Also need 273 | to handle any ABENDs during writes and catch them so the whole server job 274 | doesn't crash. Might also be good to stage incoming records in a temporary 275 | dataset until we successfully receive all of them from the client before we 276 | open (and therefore overwrite) the existing dataset, so an error receiving 277 | records mid-job won't corrupt the old dataset. 278 | * Get job status and job output (as far as I can tell from some other 279 | software on MVS 3.8, the only way to do this is to read the SYS1.HASPCKPT 280 | dataset directly...I've not found any documentation for the format of the 281 | data in there yet, though). 282 | * Could probably add functions to list online volumes and some other MVS 283 | status information. 284 | * Could support uncataloged datasets when a volume name is provided and 285 | listing VTOCs for a volume instead of just a catalog search. 286 | 287 | ## License 288 | 289 | Copyright 2022-2023 Matthew R. Wilson . 290 | 291 | This program is free software: you can redistribute it and/or modify it under 292 | the terms of the GNU General Public License as published by the Free Software 293 | Foundation, either version 3 of the License, or (at your option) any later 294 | version. 295 | 296 | This program is distributed in the hope that it will be useful, but WITHOUT 297 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 298 | FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 299 | 300 | You should have received a copy of the GNU General Public License along with 301 | this program. If not, see . 302 | -------------------------------------------------------------------------------- /ctcserver/api.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Copyright 2022-2023 Matthew R. Wilson 4 | // 5 | // This file is part of CTC Mainframe API. CTC Mainframe API is free software: 6 | // you can redistribute it and/or modify it under the terms of the GNU General 7 | // Public License as published by the Free Software Foundation, either version 8 | // 3 of the license, or (at your option) any later version. 9 | // 10 | // https://github.com/racingmars/ctc-mainframe-api/ 11 | 12 | import ( 13 | "bufio" 14 | "bytes" 15 | "net/http" 16 | "strings" 17 | 18 | "github.com/labstack/echo/v4" 19 | "github.com/rs/zerolog/log" 20 | 21 | "github.com/racingmars/ctc-mainframe-api/ctcserver/internal/ctcapi" 22 | ) 23 | 24 | type api struct { 25 | ctcapi ctcapi.CTCAPI 26 | } 27 | 28 | type errorResponse struct { 29 | Error string `json:"error"` 30 | } 31 | 32 | func (app *api) dslist(c echo.Context) error { 33 | prefix := c.Param("prefix") 34 | 35 | results, err := app.ctcapi.GetDSList(prefix) 36 | if err != nil { 37 | log.Error().Err(err).Msgf("CTC API error reading dslist for '%s'", 38 | prefix) 39 | return c.JSON(http.StatusInternalServerError, 40 | errorResponse{Error: err.Error()}) 41 | } 42 | 43 | return c.JSON(http.StatusOK, results) 44 | } 45 | 46 | func (app *api) mbrlist(c echo.Context) error { 47 | pdsName := c.Param("pdsName") 48 | 49 | results, err := app.ctcapi.GetMemberList(pdsName) 50 | if err != nil { 51 | log.Error().Err(err).Msgf("CTC API error reading member list for '%s'", 52 | pdsName) 53 | return c.JSON(http.StatusInternalServerError, 54 | errorResponse{Error: err.Error()}) 55 | } 56 | 57 | return c.JSON(http.StatusOK, results) 58 | } 59 | 60 | func (app *api) read(c echo.Context) error { 61 | dsn := c.Param("dsn") 62 | ebcdicQueryParam := c.QueryParam("ebcdic") 63 | 64 | raw := false 65 | if ebcdicQueryParam == "true" { 66 | raw = true 67 | } 68 | 69 | results, err := app.ctcapi.Read(dsn, raw) 70 | if err != nil { 71 | log.Error().Err(err).Msgf("CTC API error reading dataset '%s'", dsn) 72 | return c.JSON(http.StatusInternalServerError, 73 | errorResponse{Error: err.Error()}) 74 | } 75 | 76 | // ASCII-translated output 77 | if !raw { 78 | var output strings.Builder 79 | for _, record := range results { 80 | output.WriteString(string(record)) 81 | output.WriteString("\n") 82 | } 83 | return c.String(http.StatusOK, output.String()) 84 | } 85 | 86 | // Raw binary output 87 | var output bytes.Buffer 88 | for _, record := range results { 89 | output.Write(record) 90 | } 91 | return c.Blob(http.StatusOK, "application/octet-stream", output.Bytes()) 92 | 93 | } 94 | 95 | func (app *api) submit(c echo.Context) error { 96 | var records []string 97 | scanner := bufio.NewScanner(c.Request().Body) 98 | for scanner.Scan() { 99 | line := scanner.Text() 100 | log.Trace().Msgf("Scanned one JCL record: %s", line) 101 | records = append(records, line) 102 | } 103 | if err := scanner.Err(); err != nil { 104 | return err 105 | } 106 | 107 | result, err := app.ctcapi.Submit(records) 108 | if err != nil { 109 | log.Error().Err(err).Msg("CTC API error submitting job") 110 | return c.JSON(http.StatusInternalServerError, 111 | errorResponse{Error: err.Error()}) 112 | } 113 | 114 | return c.String(http.StatusOK, result) 115 | } 116 | 117 | func (app *api) write(c echo.Context) error { 118 | dsn := c.Param("dsn") 119 | var records []string 120 | scanner := bufio.NewScanner(c.Request().Body) 121 | for scanner.Scan() { 122 | line := scanner.Text() 123 | log.Trace().Msgf("Scanned one record: %s", line) 124 | records = append(records, line) 125 | } 126 | if err := scanner.Err(); err != nil { 127 | return err 128 | } 129 | 130 | err := app.ctcapi.Write(dsn, records) 131 | if err != nil { 132 | log.Error().Err(err).Msg("CTC API error writing dataset") 133 | return c.JSON(http.StatusInternalServerError, 134 | errorResponse{Error: err.Error()}) 135 | } 136 | 137 | return c.String(http.StatusOK, "dataset successfully saved") 138 | } 139 | 140 | func (app *api) quit(c echo.Context) error { 141 | err := app.ctcapi.Quit() 142 | if err != nil { 143 | log.Error().Err(err).Msg("CTC API error sending quit command") 144 | return c.JSON(http.StatusInternalServerError, 145 | errorResponse{Error: err.Error()}) 146 | } 147 | 148 | return c.NoContent(http.StatusOK) 149 | } 150 | -------------------------------------------------------------------------------- /ctcserver/config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Copyright 2022 Matthew R. Wilson 4 | // 5 | // This file is part of CTC Mainframe API. CTC Mainframe API is free software: 6 | // you can redistribute it and/or modify it under the terms of the GNU General 7 | // Public License as published by the Free Software Foundation, either version 8 | // 3 of the license, or (at your option) any later version. 9 | // 10 | // https://github.com/racingmars/ctc-mainframe-api/ 11 | 12 | import ( 13 | "encoding/json" 14 | "fmt" 15 | "os" 16 | ) 17 | 18 | type configuration struct { 19 | ListenPort uint16 `json:"listen_port"` 20 | HerculesHost string `json:"hercules_host"` 21 | Hercules313 bool `json:"hercules_v313"` 22 | HerculesHostBigEndian bool `json:"hercules_host_bigendian"` 23 | CmdLPort uint16 `json:"cmd_local_port"` 24 | CmdRPort uint16 `json:"cmd_remote_port"` 25 | DataLPort uint16 `json:"data_local_port"` 26 | DataRPort uint16 `json:"data_remote_port"` 27 | } 28 | 29 | func readConfig(path string) (configuration, error) { 30 | var c configuration 31 | 32 | f, err := os.Open(path) 33 | if err != nil { 34 | return c, fmt.Errorf("couldn't open config file '%s': %v", path, err) 35 | } 36 | defer f.Close() 37 | 38 | decoder := json.NewDecoder(f) 39 | if err := decoder.Decode(&c); err != nil { 40 | return c, fmt.Errorf("couldn't decode config JSON: %v", err) 41 | } 42 | 43 | return c, nil 44 | } 45 | -------------------------------------------------------------------------------- /ctcserver/config.json.sample: -------------------------------------------------------------------------------- 1 | { 2 | "listen_port": 8370, 3 | "hercules_host": "127.0.0.1", 4 | "hercules_v313": false, 5 | "hercules_host_bigendian": false, 6 | "cmd_local_port": 15600, 7 | "cmd_remote_port": 15620, 8 | "data_local_port": 15610, 9 | "data_remote_port": 15630 10 | } 11 | -------------------------------------------------------------------------------- /ctcserver/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/racingmars/ctc-mainframe-api/ctcserver 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/labstack/echo/v4 v4.10.1 7 | github.com/rs/zerolog v1.29.0 8 | ) 9 | 10 | require ( 11 | github.com/golang-jwt/jwt v3.2.2+incompatible // indirect 12 | github.com/labstack/gommon v0.4.0 // indirect 13 | github.com/mattn/go-colorable v0.1.13 // indirect 14 | github.com/mattn/go-isatty v0.0.17 // indirect 15 | github.com/valyala/bytebufferpool v1.0.0 // indirect 16 | github.com/valyala/fasttemplate v1.2.2 // indirect 17 | golang.org/x/crypto v0.6.0 // indirect 18 | golang.org/x/net v0.7.0 // indirect 19 | golang.org/x/sys v0.5.0 // indirect 20 | golang.org/x/text v0.7.0 // indirect 21 | golang.org/x/time v0.3.0 // indirect 22 | ) 23 | -------------------------------------------------------------------------------- /ctcserver/go.sum: -------------------------------------------------------------------------------- 1 | github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 6 | github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= 7 | github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= 8 | github.com/labstack/echo/v4 v4.9.1 h1:GliPYSpzGKlyOhqIbG8nmHBo3i1saKWFOgh41AN3b+Y= 9 | github.com/labstack/echo/v4 v4.9.1/go.mod h1:Pop5HLc+xoc4qhTZ1ip6C0RtP7Z+4VzRLWZZFKqbbjo= 10 | github.com/labstack/echo/v4 v4.10.1 h1:rB+D8In9PWjsp1OpHaqK+t04nQv/SBD1IoIcXCg0lpY= 11 | github.com/labstack/echo/v4 v4.10.1/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k= 12 | github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= 13 | github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= 14 | github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= 15 | github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= 16 | github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= 17 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 18 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 19 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 20 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 21 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 22 | github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= 23 | github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 24 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 25 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 26 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 27 | github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= 28 | github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY= 29 | github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= 30 | github.com/rs/zerolog v1.29.0 h1:Zes4hju04hjbvkVkOhdl2HpZa+0PmVwigmo8XoORE5w= 31 | github.com/rs/zerolog v1.29.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= 32 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 33 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 34 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 35 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 36 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 37 | github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= 38 | github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= 39 | github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= 40 | github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= 41 | golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ= 42 | golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 43 | golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= 44 | golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= 45 | golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY= 46 | golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 47 | golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= 48 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 49 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 50 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 51 | golang.org/x/sys v0.0.0-20211103235746-7861aae1554b h1:1VkfZQv42XQlA/jchYumAnv1UPo6RgF9rJFkTgZIxO4= 52 | golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 53 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 54 | golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= 55 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 56 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 57 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 58 | golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= 59 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 60 | golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE= 61 | golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 62 | golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= 63 | golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 64 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 65 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 66 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 67 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 68 | -------------------------------------------------------------------------------- /ctcserver/internal/ctc/ctc.go: -------------------------------------------------------------------------------- 1 | package ctc 2 | 3 | // Copyright 2022-2023 Matthew R. Wilson 4 | // 5 | // This file is part of CTC Mainframe API. CTC Mainframe API is free software: 6 | // you can redistribute it and/or modify it under the terms of the GNU General 7 | // Public License as published by the Free Software Foundation, either version 8 | // 3 of the license, or (at your option) any later version. 9 | // 10 | // https://github.com/racingmars/ctc-mainframe-api/ 11 | 12 | import ( 13 | "bytes" 14 | "encoding/binary" 15 | "errors" 16 | "fmt" 17 | "net" 18 | "time" 19 | 20 | "github.com/rs/zerolog/log" 21 | ) 22 | 23 | type CTCCmd byte 24 | 25 | const ( 26 | CTCCmdTest CTCCmd = 0x00 27 | CTCCmdWrite CTCCmd = 0x01 28 | CTCCmdRead CTCCmd = 0x02 29 | CTCCmdControl CTCCmd = 0x07 30 | CTCCmdSense CTCCmd = 0x14 31 | ) 32 | 33 | // HerculesVersion indicates which version of Hercules this CTC interface will 34 | // connect to. Use HerculesVersionOld for Hercules 3.13, or HerculesVersionNew 35 | // for Spinhawk and Hyperion. 36 | type HerculesVersion int 37 | 38 | const ( 39 | // HerculesVersionOld is for use with Hercules 3.13. 40 | HerculesVersionOld HerculesVersion = iota 41 | 42 | // HerculesVersionNew if for use with Hercules Spinhawk and Hyperion. 43 | HerculesVersionNew 44 | ) 45 | 46 | type CTC interface { 47 | Close() 48 | Connect() error 49 | Send(cmd CTCCmd, count uint16, data []byte) error 50 | Read() (cmd CTCCmd, count uint16, data []byte, err error) 51 | 52 | // ControlWrite will send a CONTROL, wait for the SENSE from the remote 53 | // side to clear the CONTROL, send the data with WRITE, and wait for the 54 | // READ from the remote side. Count for the WRITE will be the length of 55 | // the data. 56 | ControlWrite(data []byte) error 57 | 58 | NakedWrite(data []byte) error 59 | 60 | // SenseWait will await a SENSE, send a CONTROL in response, then perform 61 | // a READ, returning the bytes that were read. 62 | SenseRead() ([]byte, error) 63 | } 64 | 65 | // ErrAlreadyConnected is the error returned by Connect when at least half of 66 | // the connection is already established. Call Close to reset the CTC 67 | // connection before trying to connect again. 68 | var ErrAlreadyConnected = errors.New("already connected") 69 | 70 | // ErrInvalidVersion is the error returned by New when the version parameter 71 | // is not HerculesVersionOld or HerculesVersionNew. 72 | var ErrInvalidVersion = errors.New("invalid Hercules version") 73 | 74 | // ErrNotConnected is the error returned when a send or receive operation is 75 | // attempted on a CTC connection that is not connected. 76 | var ErrNotConnected = errors.New("not connected") 77 | 78 | type ctc struct { 79 | raddr string 80 | rIP net.IP 81 | rport uint16 82 | lport uint16 83 | recvsock, sendsock net.Conn 84 | seq uint16 85 | devnum uint16 86 | ver HerculesVersion 87 | bo binary.ByteOrder 88 | } 89 | 90 | const ctcHdrLenOld = 12 91 | 92 | type ctcHdrOld struct { 93 | CmdReg CTCCmd 94 | FsmState byte 95 | SCount uint16 96 | PktSeq uint16 97 | SndLen uint16 98 | DevNum uint16 99 | SSID uint16 100 | } 101 | 102 | const ctcHdrLenNew = 16 103 | 104 | type ctcHdrNew struct { 105 | CmdReg CTCCmd 106 | FsmState byte 107 | SCount uint16 108 | PktSeq uint16 109 | _ uint16 110 | SndLen uint16 111 | DevNum uint16 112 | SSID uint16 113 | _ uint16 114 | } 115 | 116 | const ssid uint16 = 1 117 | 118 | type ctcInitMsg struct { 119 | HercInfo uint16 120 | LocalPort uint16 121 | RemoteIP net.IP 122 | SndLen uint16 123 | DevNum uint16 124 | SSID uint16 125 | _ uint16 126 | } 127 | 128 | func New(lport, rport, devnum uint16, raddr string, version HerculesVersion, 129 | byteOrder binary.ByteOrder) (CTC, error) { 130 | 131 | if !(version == HerculesVersionOld || version == HerculesVersionNew) { 132 | return nil, ErrInvalidVersion 133 | } 134 | 135 | rIP, err := net.ResolveIPAddr("ip", raddr) 136 | if err != nil { 137 | return nil, err 138 | } 139 | 140 | return &ctc{ 141 | raddr: raddr, 142 | rport: rport, 143 | lport: lport, 144 | rIP: rIP.IP, 145 | seq: 1, 146 | devnum: devnum, 147 | ver: version, 148 | bo: byteOrder, 149 | }, nil 150 | } 151 | 152 | func (c *ctc) Close() { 153 | if c.sendsock != nil { 154 | log.Debug().Msg("Closing sendsock") 155 | c.sendsock.Close() 156 | } 157 | 158 | if c.recvsock != nil { 159 | log.Debug().Msg("Closing recvsock") 160 | c.recvsock.Close() 161 | } 162 | 163 | // Reset the ctc to its initial state 164 | c.sendsock = nil 165 | c.recvsock = nil 166 | c.seq = 1 167 | } 168 | 169 | func (c *ctc) Connect() error { 170 | if c.sendsock != nil || c.recvsock != nil { 171 | return ErrAlreadyConnected 172 | } 173 | 174 | // First, we wait for Hercules to connect to us. If the remote side is 175 | // Hercules 3.13, we listen on the odd port number and connect to the 176 | // odd port. 177 | lport := c.lport 178 | rport := c.rport 179 | if c.ver == HerculesVersionOld { 180 | lport++ 181 | rport++ 182 | } 183 | 184 | log.Info().Msgf("Waiting for Hercules to connect to us on port %d", 185 | lport) 186 | 187 | listener, err := net.Listen("tcp", fmt.Sprintf(":%d", lport)) 188 | if err != nil { 189 | return err 190 | } 191 | defer listener.Close() 192 | 193 | recvsock, err := listener.Accept() 194 | if err != nil { 195 | return err 196 | } 197 | log.Info().Msgf("Got connection from %s", recvsock.RemoteAddr().String()) 198 | listener.Close() 199 | 200 | var sendsock net.Conn 201 | 202 | log.Info().Msgf("Connecting to remote hercules at %s:%d", c.raddr, rport) 203 | if c.ver == HerculesVersionNew { 204 | sendsock, err = net.Dial("tcp", fmt.Sprintf("%s:%d", c.raddr, rport)) 205 | if err != nil { 206 | recvsock.Close() 207 | return err 208 | } 209 | } else { 210 | // Hercules 3.13 requires that we connect with a *source port* that 211 | // matches the remote port configured in its CTCE device. 212 | srcaddr, err := net.ResolveTCPAddr("tcp", 213 | fmt.Sprintf("0.0.0.0:%d", c.lport)) 214 | if err != nil { 215 | recvsock.Close() 216 | return err 217 | } 218 | sendaddr, err := net.ResolveTCPAddr("tcp", 219 | fmt.Sprintf("%s:%d", c.raddr, rport)) 220 | if err != nil { 221 | recvsock.Close() 222 | return err 223 | } 224 | sendsock, err = net.DialTCP("tcp", srcaddr, sendaddr) 225 | if err != nil { 226 | recvsock.Close() 227 | return err 228 | } 229 | } 230 | 231 | c.recvsock = recvsock 232 | c.sendsock = sendsock 233 | 234 | if c.ver == HerculesVersionNew { 235 | if err := c.handshake(); err != nil { 236 | recvsock.Close() 237 | sendsock.Close() 238 | c.recvsock = nil 239 | c.sendsock = nil 240 | return fmt.Errorf("handshake error: %v", err) 241 | } 242 | log.Info().Msg("Hercules handshake successful") 243 | } 244 | 245 | c.recvsock = recvsock 246 | c.sendsock = sendsock 247 | 248 | return nil 249 | } 250 | 251 | func (c *ctc) handshake() error { 252 | // Expect 16 bytes from Hercules, which we will simply discard. 253 | buf := make([]byte, ctcHdrLenNew) 254 | for n := 0; n < ctcHdrLenNew; { 255 | nn, err := c.recvsock.Read(buf[n:]) 256 | if err != nil { 257 | return err 258 | } 259 | n += nn 260 | } 261 | 262 | // Now send our side of the handshake 263 | var sendbuf bytes.Buffer 264 | binary.Write(&sendbuf, c.bo, uint16(0x8010)) // "hercules info" 265 | binary.Write(&sendbuf, c.bo, c.lport) // our listening port 266 | // our IP address, network byte order (big endian) 267 | if remoteip := c.rIP.To4(); remoteip != nil { 268 | sendbuf.Write(remoteip) 269 | } else { 270 | // I guess just send the first 4 bytes of the IPv6 address? Hercules 271 | // doesn't seem to handle anything but IPv4 in this field. 272 | sendbuf.Write(c.rIP[0:4]) 273 | } 274 | binary.Write(&sendbuf, c.bo, uint16(ctcHdrLenNew)) // send length 275 | binary.Write(&sendbuf, c.bo, c.devnum) // our device number 276 | binary.Write(&sendbuf, c.bo, ssid) // our ssid 277 | sendbuf.WriteByte(0) // padding 278 | sendbuf.WriteByte(0) // padding 279 | 280 | if _, err := c.sendsock.Write(sendbuf.Bytes()); err != nil { 281 | return err 282 | } 283 | 284 | return nil 285 | } 286 | 287 | func (c *ctc) Send(cmd CTCCmd, count uint16, data []byte) error { 288 | var buf bytes.Buffer 289 | 290 | if c.sendsock == nil || c.recvsock == nil { 291 | return ErrNotConnected 292 | } 293 | 294 | commandName := "unknown" 295 | var fsmState byte 296 | 297 | switch cmd { 298 | case CTCCmdControl: 299 | commandName = "CONTROL" 300 | fsmState = 0x01 301 | case CTCCmdRead: 302 | commandName = "READ" 303 | fsmState = 0x04 304 | case CTCCmdSense: 305 | commandName = "SENSE" 306 | fsmState = 0x04 307 | case CTCCmdWrite: 308 | commandName = "WRITE" 309 | fsmState = 0x03 310 | } 311 | 312 | if c.ver == HerculesVersionOld { 313 | binary.Write(&buf, c.bo, ctcHdrOld{ 314 | CmdReg: cmd, 315 | FsmState: fsmState, 316 | SCount: count, 317 | PktSeq: c.seq, 318 | SndLen: ctcHdrLenOld + uint16(len(data)), 319 | DevNum: c.devnum, 320 | SSID: ssid, 321 | }) 322 | } else { 323 | binary.Write(&buf, c.bo, ctcHdrNew{ 324 | CmdReg: cmd, 325 | FsmState: fsmState, 326 | SCount: count, 327 | PktSeq: c.seq, 328 | SndLen: ctcHdrLenNew + uint16(len(data)), 329 | DevNum: c.devnum, 330 | SSID: ssid, 331 | }) 332 | } 333 | 334 | buf.Write(data) 335 | 336 | log.Trace().Str("command", commandName).Hex("data", buf.Bytes()).Msg("SEND") 337 | 338 | if _, err := c.sendsock.Write(buf.Bytes()); err != nil { 339 | return err 340 | } 341 | 342 | c.seq++ 343 | return nil 344 | } 345 | 346 | func (c *ctc) Read() (cmd CTCCmd, count uint16, data []byte, err error) { 347 | for { 348 | cmd, count, data, err = c.read() 349 | if err != nil { 350 | return cmd, count, data, err 351 | } 352 | 353 | if cmd != CTCCmdTest { 354 | return cmd, count, data, nil 355 | } 356 | 357 | // If we got a test I/O command, discard it and wait for next command. 358 | } 359 | } 360 | 361 | func (c *ctc) read() (cmd CTCCmd, count uint16, data []byte, err error) { 362 | var buf []byte 363 | if c.ver == HerculesVersionOld { 364 | buf = make([]byte, ctcHdrLenOld) 365 | } else { 366 | buf = make([]byte, ctcHdrLenNew) 367 | } 368 | 369 | // Read the header info 370 | for n := 0; n < len(buf); { 371 | nn, err := c.recvsock.Read(buf[n:]) 372 | if err != nil { 373 | return 0, 0, nil, err 374 | } 375 | n += nn 376 | } 377 | 378 | log.Trace().Hex("header", buf).Msg("READ") 379 | 380 | var dataLen uint16 381 | 382 | if c.ver == HerculesVersionOld { 383 | var header ctcHdrOld 384 | if err := binary.Read(bytes.NewBuffer(buf), c.bo, &header); err != nil { 385 | return 0, 0, nil, err 386 | } 387 | 388 | cmd = header.CmdReg 389 | count = header.SCount 390 | dataLen = header.SndLen - ctcHdrLenOld 391 | } else { 392 | var header ctcHdrNew 393 | if err := binary.Read(bytes.NewBuffer(buf), c.bo, &header); err != nil { 394 | return 0, 0, nil, err 395 | } 396 | 397 | cmd = header.CmdReg 398 | count = header.SCount 399 | dataLen = header.SndLen - ctcHdrLenNew 400 | } 401 | 402 | // Now read the rest of the data, if any. 403 | if dataLen > 0 { 404 | data = make([]byte, dataLen) 405 | for n := 0; n < len(data); { 406 | nn, err := c.recvsock.Read(data[n:]) 407 | if err != nil { 408 | return cmd, count, data, err 409 | } 410 | n += nn 411 | } 412 | } else { 413 | data = make([]byte, 0) 414 | } 415 | 416 | log.Trace().Hex("data", data).Msg("READ") 417 | 418 | return cmd, count, data, nil 419 | } 420 | 421 | // ControlWrite will send a CONTROL, wait for the SENSE from the remote side 422 | // to clear the CONTROL, send the data with WRITE, and wait for the READ from 423 | // the remote side. Count for the WRITE will be the length of the data. 424 | func (c *ctc) ControlWrite(data []byte) error { 425 | log.Debug().Msg("ctc.ControlWrite(): sending CONTROL") 426 | if err := c.Send(CTCCmdControl, 1, nil); err != nil { 427 | return fmt.Errorf("couldn't send CONTROL: %v", err) 428 | } 429 | 430 | // Expect a SENSE command in response. 431 | log.Debug().Msg("ctc.ControlWrite(): awaiting SENSE") 432 | cmd, _, _, err := c.Read() 433 | if err != nil { 434 | return fmt.Errorf("couldn't read while awaiting SENSE: %v", err) 435 | } 436 | if cmd != CTCCmdSense { 437 | return fmt.Errorf("expected SENSE but got %02x", cmd) 438 | } 439 | 440 | // Putting this brief pause between doing the control/sense then the 441 | // write seems to eliminate an intermittent condition during stress 442 | // testing on my system where we get the sense from Hercules/MVS, but 443 | // then either Hercules or MVS never picks up the write state change. 444 | time.Sleep(10 * time.Millisecond) 445 | log.Debug().Msg("ctc.ControlWrite: sending WRITE") 446 | if err := c.Send(CTCCmdWrite, uint16(len(data)), data); err != nil { 447 | return fmt.Errorf("couldn't send WRITE: %v", err) 448 | } 449 | 450 | // Expect the corresponding READ command from the other side 451 | log.Debug().Msg("ctc.ControlWrite(): awaiting READ") 452 | cmd, _, _, err = c.Read() 453 | if err != nil { 454 | return fmt.Errorf("couldn't read while awaiting READ: %v", err) 455 | } 456 | if cmd != CTCCmdRead { 457 | return fmt.Errorf("expected READ, but got %02x", cmd) 458 | } 459 | 460 | return nil 461 | } 462 | 463 | // NakedWrite will send the data with WRITE, and wait for the READ from 464 | // the remote side. Count for the WRITE will be the length of the data. 465 | func (c *ctc) NakedWrite(data []byte) error { 466 | 467 | log.Debug().Msg("ctc.NakedWrite: sending WRITE") 468 | if err := c.Send(CTCCmdWrite, uint16(len(data)), data); err != nil { 469 | return fmt.Errorf("couldn't send WRITE: %v", err) 470 | } 471 | 472 | // Expect the corresponding READ command from the other side 473 | log.Debug().Msg("ctc.ConNakedWritetrolWrite(): awaiting READ") 474 | cmd, _, _, err := c.Read() 475 | if err != nil { 476 | return fmt.Errorf("couldn't read while awaiting READ: %v", err) 477 | } 478 | if cmd != CTCCmdRead { 479 | return fmt.Errorf("expected READ, but got %02x", cmd) 480 | } 481 | 482 | return nil 483 | } 484 | 485 | // SenseWait will await a SENSE, send a CONTROL in response, then perform a 486 | // READ, returning the bytes that were read. 487 | func (c *ctc) SenseRead() ([]byte, error) { 488 | log.Debug().Msg("ctc.SenseRead(): awaiting CONTROL") 489 | cmd, _, _, err := c.Read() 490 | if err != nil { 491 | return nil, fmt.Errorf("couldn't read while awaiting CONTROL: %v", err) 492 | } 493 | if cmd != CTCCmdControl { 494 | return nil, fmt.Errorf("expected CONTROL, but got %02x", cmd) 495 | } 496 | 497 | // Send a SENSE command in response 498 | log.Debug().Msg("ctc.SenseRead(): sending SENSE") 499 | if err := c.Send(CTCCmdSense, 1, nil); err != nil { 500 | return nil, fmt.Errorf("couldn't send SENSE: %v", err) 501 | } 502 | 503 | // Read the data 504 | log.Debug().Msg("ctc.SenseRead(): reading data") 505 | cmd, count, data, err := c.Read() 506 | if err != nil { 507 | return nil, fmt.Errorf("couldn't read data: %v", err) 508 | } 509 | log.Debug(). 510 | Hex("command", []byte{byte(cmd)}). 511 | Uint16("count", count). 512 | Hex("data", data). 513 | Msg("data read from CTC adapter") 514 | if cmd != CTCCmdWrite { 515 | return data, fmt.Errorf("expected WRITE, but got %02x", cmd) 516 | } 517 | // Now send our READ command to indicate we've read the response 518 | if err := c.Send(CTCCmdRead, count, nil); err != nil { 519 | return data, fmt.Errorf("couldn't send READ in respone to WRITE") 520 | } 521 | 522 | return data, nil 523 | } 524 | -------------------------------------------------------------------------------- /ctcserver/internal/ctc/ebcdic.go: -------------------------------------------------------------------------------- 1 | package ctc 2 | 3 | // Copyright 2022 Matthew R. Wilson 4 | // 5 | // This file is part of CTC Mainframe API. CTC Mainframe API is free software: 6 | // you can redistribute it and/or modify it under the terms of the GNU General 7 | // Public License as published by the Free Software Foundation, either version 8 | // 3 of the license, or (at your option) any later version. 9 | // 10 | // https://github.com/racingmars/ctc-mainframe-api/ 11 | 12 | import ( 13 | "github.com/rs/zerolog/log" 14 | ) 15 | 16 | type Codepage string 17 | 18 | const ( 19 | CodepageCP37 Codepage = "cp37" 20 | CodepageBracket Codepage = "bracket" 21 | ) 22 | 23 | func SetGlobalCodepage(cp Codepage) { 24 | switch cp { 25 | case CodepageCP37: 26 | activeE2U = cp037toUC 27 | activeU2E = ucToCP037 28 | case CodepageBracket: 29 | activeE2U = bracketToUC 30 | activeU2E = ucToBracket 31 | default: 32 | log.Error().Msgf("Unknown code page %s. No change made.", cp) 33 | } 34 | } 35 | 36 | // CP037<->Unicode mapping based on the data in 37 | // ftp://ftp.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/EBCDIC/CP037.TXT 38 | 39 | var cp037toUC []rune = []rune{ 40 | 0x00, 0x01, 0x02, 0x03, 0x9c, 0x09, 0x86, 0x7f, // 0x00..0x07 41 | 0x97, 0x8d, 0x8e, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, // 0x08..0x0f 42 | 0x10, 0x11, 0x12, 0x13, 0x9d, 0x85, 0x08, 0x87, // 0x10..0x17 43 | 0x18, 0x19, 0x92, 0x8f, 0x1c, 0x1d, 0x1e, 0x1f, // 0x18..0x1f 44 | 0x80, 0x81, 0x82, 0x83, 0x84, 0x0a, 0x17, 0x1b, // 0x20..0x27 45 | 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x05, 0x06, 0x07, // 0x28..0x2f 46 | 0x90, 0x91, 0x16, 0x93, 0x94, 0x95, 0x96, 0x04, // 0x30..0x37 47 | 0x98, 0x99, 0x9a, 0x9b, 0x14, 0x15, 0x9e, 0x1a, // 0x38..0x3f 48 | 0x20, 0xa0, 0xe2, 0xe4, 0xe0, 0xe1, 0xe3, 0xe5, // 0x40..0x47 49 | 0xe7, 0xf1, 0xa2, 0x2e, 0x3c, 0x28, 0x2b, 0x7c, // 0x48..0x4f 50 | 0x26, 0xe9, 0xea, 0xeb, 0xe8, 0xed, 0xee, 0xef, // 0x50..0x57 51 | 0xec, 0xdf, 0x21, 0x24, 0x2a, 0x29, 0x3b, 0xac, // 0x58..0x5f 52 | 0x2d, 0x2f, 0xc2, 0xc4, 0xc0, 0xc1, 0xc3, 0xc5, // 0x60..0x67 53 | 0xc7, 0xd1, 0xa6, 0x2c, 0x25, 0x5f, 0x3e, 0x3f, // 0x68..0x6f 54 | 0xf8, 0xc9, 0xca, 0xcb, 0xc8, 0xcd, 0xce, 0xcf, // 0x70..0x77 55 | 0xcc, 0x60, 0x3a, 0x23, 0x40, 0x27, 0x3d, 0x22, // 0x78..0x7f 56 | 0xd8, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, // 0x80..0x87 57 | 0x68, 0x69, 0xab, 0xbb, 0xf0, 0xfd, 0xfe, 0xb1, // 0x88..0x8f 58 | 0xb0, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, // 0x90..0x97 59 | 0x71, 0x72, 0xaa, 0xba, 0xe6, 0xb8, 0xc6, 0xa4, // 0x98..0x9f 60 | 0xb5, 0x7e, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, // 0xa0..0xa7 61 | 0x79, 0x7a, 0xa1, 0xbf, 0xd0, 0xdd, 0xde, 0xae, // 0xa8..0xaf 62 | 0x5e, 0xa3, 0xa5, 0xb7, 0xa9, 0xa7, 0xb6, 0xbc, // 0xb0..0xb7 63 | 0xbd, 0xbe, 0x5b, 0x5d, 0xaf, 0xa8, 0xb4, 0xd7, // 0xb8..0xbf 64 | 0x7b, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, // 0xc0..0xc7 65 | 0x48, 0x49, 0xad, 0xf4, 0xf6, 0xf2, 0xf3, 0xf5, // 0xc8..0xcf 66 | 0x7d, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, // 0xd0..0xd7 67 | 0x51, 0x52, 0xb9, 0xfb, 0xfc, 0xf9, 0xfa, 0xff, // 0xd8..0xdf 68 | 0x5c, 0xf7, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, // 0xe0..0xe7 69 | 0x59, 0x5a, 0xb2, 0xd4, 0xd6, 0xd2, 0xd3, 0xd5, // 0xe8..0xef 70 | 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, // 0xf0..0xf7 71 | 0x38, 0x39, 0xb3, 0xdb, 0xdc, 0xd9, 0xda, 0x9f, // 0xf8..0xff 72 | } 73 | 74 | var ucToCP037 []byte = []byte{ 75 | 0x00, 0x01, 0x02, 0x03, 0x37, 0x2d, 0x2e, 0x2f, // 0x00..0x07 76 | 0x16, 0x05, 0x25, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, // 0x08..0x0f 77 | 0x10, 0x11, 0x12, 0x13, 0x3c, 0x3d, 0x32, 0x26, // 0x10..0x17 78 | 0x18, 0x19, 0x3f, 0x27, 0x1c, 0x1d, 0x1e, 0x1f, // 0x18..0x1f 79 | 0x40, 0x5a, 0x7f, 0x7b, 0x5b, 0x6c, 0x50, 0x7d, // 0x20..0x27 80 | 0x4d, 0x5d, 0x5c, 0x4e, 0x6b, 0x60, 0x4b, 0x61, // 0x28..0x2f 81 | 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, // 0x30..0x37 82 | 0xf8, 0xf9, 0x7a, 0x5e, 0x4c, 0x7e, 0x6e, 0x6f, // 0x38..0x3f 83 | 0x7c, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, // 0x40..0x47 84 | 0xc8, 0xc9, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, // 0x48..0x4f 85 | 0xd7, 0xd8, 0xd9, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, // 0x50..0x57 86 | 0xe7, 0xe8, 0xe9, 0xba, 0xe0, 0xbb, 0xb0, 0x6d, // 0x58..0x5f 87 | 0x79, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, // 0x60..0x67 88 | 0x88, 0x89, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, // 0x68..0x6f 89 | 0x97, 0x98, 0x99, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, // 0x70..0x77 90 | 0xa7, 0xa8, 0xa9, 0xc0, 0x4f, 0xd0, 0xa1, 0x07, // 0x78..0x7f 91 | 0x20, 0x21, 0x22, 0x23, 0x24, 0x15, 0x06, 0x17, // 0x80..0x87 92 | 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x09, 0x0a, 0x1b, // 0x88..0x8f 93 | 0x30, 0x31, 0x1a, 0x33, 0x34, 0x35, 0x36, 0x08, // 0x90..0x97 94 | 0x38, 0x39, 0x3a, 0x3b, 0x04, 0x14, 0x3e, 0xff, // 0x98..0x9f 95 | 0x41, 0xaa, 0x4a, 0xb1, 0x9f, 0xb2, 0x6a, 0xb5, // 0xa0..0xa7 96 | 0xbd, 0xb4, 0x9a, 0x8a, 0x5f, 0xca, 0xaf, 0xbc, // 0xa8..0xaf 97 | 0x90, 0x8f, 0xea, 0xfa, 0xbe, 0xa0, 0xb6, 0xb3, // 0xb0..0xb7 98 | 0x9d, 0xda, 0x9b, 0x8b, 0xb7, 0xb8, 0xb9, 0xab, // 0xb8..0xbf 99 | 0x64, 0x65, 0x62, 0x66, 0x63, 0x67, 0x9e, 0x68, // 0xc0..0xc7 100 | 0x74, 0x71, 0x72, 0x73, 0x78, 0x75, 0x76, 0x77, // 0xc8..0xcf 101 | 0xac, 0x69, 0xed, 0xee, 0xeb, 0xef, 0xec, 0xbf, // 0xd0..0xd7 102 | 0x80, 0xfd, 0xfe, 0xfb, 0xfc, 0xad, 0xae, 0x59, // 0xd8..0xdf 103 | 0x44, 0x45, 0x42, 0x46, 0x43, 0x47, 0x9c, 0x48, // 0xe0..0xe7 104 | 0x54, 0x51, 0x52, 0x53, 0x58, 0x55, 0x56, 0x57, // 0xe8..0xef 105 | 0x8c, 0x49, 0xcd, 0xce, 0xcb, 0xcf, 0xcc, 0xe1, // 0xf0..0xf7 106 | 0x70, 0xdd, 0xde, 0xdb, 0xdc, 0x8d, 0x8e, 0xdf, // 0xf8..0xff 107 | } 108 | 109 | // These tables are the same as the above CP037<->Unicode tables, except 110 | // for the square bracket [ ] characters. x3270 and friends, and Vista3270 111 | // use this alternate mapping by default. 112 | // 113 | // https://x3270.miraheze.org/wiki/Why_are_the_square_bracket_characters_displayed_wrong%3F 114 | var bracketToUC []rune = []rune{ 115 | 0x00, 0x01, 0x02, 0x03, 0x9c, 0x09, 0x86, 0x7f, // 0x00..0x07 116 | 0x97, 0x8d, 0x8e, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, // 0x08..0x0f 117 | 0x10, 0x11, 0x12, 0x13, 0x9d, 0x85, 0x08, 0x87, // 0x10..0x17 118 | 0x18, 0x19, 0x92, 0x8f, 0x1c, 0x1d, 0x1e, 0x1f, // 0x18..0x1f 119 | 0x80, 0x81, 0x82, 0x83, 0x84, 0x0a, 0x17, 0x1b, // 0x20..0x27 120 | 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x05, 0x06, 0x07, // 0x28..0x2f 121 | 0x90, 0x91, 0x16, 0x93, 0x94, 0x95, 0x96, 0x04, // 0x30..0x37 122 | 0x98, 0x99, 0x9a, 0x9b, 0x14, 0x15, 0x9e, 0x1a, // 0x38..0x3f 123 | 0x20, 0xa0, 0xe2, 0xe4, 0xe0, 0xe1, 0xe3, 0xe5, // 0x40..0x47 124 | 0xe7, 0xf1, 0xa2, 0x2e, 0x3c, 0x28, 0x2b, 0x7c, // 0x48..0x4f 125 | 0x26, 0xe9, 0xea, 0xeb, 0xe8, 0xed, 0xee, 0xef, // 0x50..0x57 126 | 0xec, 0xdf, 0x21, 0x24, 0x2a, 0x29, 0x3b, 0xac, // 0x58..0x5f 127 | 0x2d, 0x2f, 0xc2, 0xc4, 0xc0, 0xc1, 0xc3, 0xc5, // 0x60..0x67 128 | 0xc7, 0xd1, 0xa6, 0x2c, 0x25, 0x5f, 0x3e, 0x3f, // 0x68..0x6f 129 | 0xf8, 0xc9, 0xca, 0xcb, 0xc8, 0xcd, 0xce, 0xcf, // 0x70..0x77 130 | 0xcc, 0x60, 0x3a, 0x23, 0x40, 0x27, 0x3d, 0x22, // 0x78..0x7f 131 | 0xd8, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, // 0x80..0x87 132 | 0x68, 0x69, 0xab, 0xbb, 0xf0, 0xfd, 0xfe, 0xb1, // 0x88..0x8f 133 | 0xb0, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, // 0x90..0x97 134 | 0x71, 0x72, 0xaa, 0xba, 0xe6, 0xb8, 0xc6, 0xa4, // 0x98..0x9f 135 | 0xb5, 0x7e, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, // 0xa0..0xa7 136 | 0x79, 0x7a, 0xa1, 0xbf, 0xd0, 0x5b, 0xde, 0xae, // 0xa8..0xaf 137 | 0x5e, 0xa3, 0xa5, 0xb7, 0xa9, 0xa7, 0xb6, 0xbc, // 0xb0..0xb7 138 | 0xbd, 0xbe, 0xdd, 0xa8, 0xaf, 0x5d, 0xb4, 0xd7, // 0xb8..0xbf 139 | 0x7b, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, // 0xc0..0xc7 140 | 0x48, 0x49, 0xad, 0xf4, 0xf6, 0xf2, 0xf3, 0xf5, // 0xc8..0xcf 141 | 0x7d, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, // 0xd0..0xd7 142 | 0x51, 0x52, 0xb9, 0xfb, 0xfc, 0xf9, 0xfa, 0xff, // 0xd8..0xdf 143 | 0x5c, 0xf7, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, // 0xe0..0xe7 144 | 0x59, 0x5a, 0xb2, 0xd4, 0xd6, 0xd2, 0xd3, 0xd5, // 0xe8..0xef 145 | 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, // 0xf0..0xf7 146 | 0x38, 0x39, 0xb3, 0xdb, 0xdc, 0xd9, 0xda, 0x9f, // 0xf8..0xff 147 | } 148 | 149 | var ucToBracket []byte = []byte{ 150 | 0x00, 0x01, 0x02, 0x03, 0x37, 0x2d, 0x2e, 0x2f, // 0x00..0x07 151 | 0x16, 0x05, 0x25, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, // 0x08..0x0f 152 | 0x10, 0x11, 0x12, 0x13, 0x3c, 0x3d, 0x32, 0x26, // 0x10..0x17 153 | 0x18, 0x19, 0x3f, 0x27, 0x1c, 0x1d, 0x1e, 0x1f, // 0x18..0x1f 154 | 0x40, 0x5a, 0x7f, 0x7b, 0x5b, 0x6c, 0x50, 0x7d, // 0x20..0x27 155 | 0x4d, 0x5d, 0x5c, 0x4e, 0x6b, 0x60, 0x4b, 0x61, // 0x28..0x2f 156 | 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, // 0x30..0x37 157 | 0xf8, 0xf9, 0x7a, 0x5e, 0x4c, 0x7e, 0x6e, 0x6f, // 0x38..0x3f 158 | 0x7c, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, // 0x40..0x47 159 | 0xc8, 0xc9, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, // 0x48..0x4f 160 | 0xd7, 0xd8, 0xd9, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, // 0x50..0x57 161 | 0xe7, 0xe8, 0xe9, 0xad, 0xe0, 0xbd, 0xb0, 0x6d, // 0x58..0x5f 162 | 0x79, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, // 0x60..0x67 163 | 0x88, 0x89, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, // 0x68..0x6f 164 | 0x97, 0x98, 0x99, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, // 0x70..0x77 165 | 0xa7, 0xa8, 0xa9, 0xc0, 0x4f, 0xd0, 0xa1, 0x07, // 0x78..0x7f 166 | 0x20, 0x21, 0x22, 0x23, 0x24, 0x15, 0x06, 0x17, // 0x80..0x87 167 | 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x09, 0x0a, 0x1b, // 0x88..0x8f 168 | 0x30, 0x31, 0x1a, 0x33, 0x34, 0x35, 0x36, 0x08, // 0x90..0x97 169 | 0x38, 0x39, 0x3a, 0x3b, 0x04, 0x14, 0x3e, 0xff, // 0x98..0x9f 170 | 0x41, 0xaa, 0x4a, 0xb1, 0x9f, 0xb2, 0x6a, 0xb5, // 0xa0..0xa7 171 | 0xbb, 0xb4, 0x9a, 0x8a, 0x5f, 0xca, 0xaf, 0xbc, // 0xa8..0xaf 172 | 0x90, 0x8f, 0xea, 0xfa, 0xbe, 0xa0, 0xb6, 0xb3, // 0xb0..0xb7 173 | 0x9d, 0xda, 0x9b, 0x8b, 0xb7, 0xb8, 0xb9, 0xab, // 0xb8..0xbf 174 | 0x64, 0x65, 0x62, 0x66, 0x63, 0x67, 0x9e, 0x68, // 0xc0..0xc7 175 | 0x74, 0x71, 0x72, 0x73, 0x78, 0x75, 0x76, 0x77, // 0xc8..0xcf 176 | 0xac, 0x69, 0xed, 0xee, 0xeb, 0xef, 0xec, 0xbf, // 0xd0..0xd7 177 | 0x80, 0xfd, 0xfe, 0xfb, 0xfc, 0xba, 0xae, 0x59, // 0xd8..0xdf 178 | 0x44, 0x45, 0x42, 0x46, 0x43, 0x47, 0x9c, 0x48, // 0xe0..0xe7 179 | 0x54, 0x51, 0x52, 0x53, 0x58, 0x55, 0x56, 0x57, // 0xe8..0xef 180 | 0x8c, 0x49, 0xcd, 0xce, 0xcb, 0xcf, 0xcc, 0xe1, // 0xf0..0xf7 181 | 0x70, 0xdd, 0xde, 0xdb, 0xdc, 0x8d, 0x8e, 0xdf, // 0xf8..0xff 182 | } 183 | 184 | // Set to brackets mapping by default 185 | var activeE2U = bracketToUC 186 | var activeU2E = ucToBracket 187 | 188 | // StoE converts a Go UTF-8 string to an EBCDIC byte array. Only Unicode code 189 | // points <=0xFF are translated, anything else will be converted to an EBCDIC 190 | // space (0x40). 191 | func StoE(s string) []byte { 192 | runes := []rune(s) 193 | output := make([]byte, len(runes)) 194 | for i, r := range runes { 195 | if r > 0xFF { 196 | // outside our mappable characters. Replace with a space. 197 | output[i] = 0x40 198 | continue 199 | } 200 | 201 | output[i] = activeU2E[r] 202 | } 203 | 204 | return output 205 | } 206 | 207 | // EtoS converts an EBCDIC byte array to a Go UTF-8 string. 208 | func EtoS(e []byte) string { 209 | output := make([]rune, len(e)) 210 | for i, b := range e { 211 | output[i] = activeE2U[b] 212 | } 213 | return string(output) 214 | } 215 | -------------------------------------------------------------------------------- /ctcserver/internal/ctcapi/commands.go: -------------------------------------------------------------------------------- 1 | package ctcapi 2 | 3 | // Copyright 2022-2023 Matthew R. Wilson 4 | // 5 | // This file is part of CTC Mainframe API. CTC Mainframe API is free software: 6 | // you can redistribute it and/or modify it under the terms of the GNU General 7 | // Public License as published by the Free Software Foundation, either version 8 | // 3 of the license, or (at your option) any later version. 9 | // 10 | // https://github.com/racingmars/ctc-mainframe-api/ 11 | 12 | import ( 13 | "bytes" 14 | "encoding/binary" 15 | "fmt" 16 | "regexp" 17 | "strings" 18 | 19 | "github.com/rs/zerolog/log" 20 | 21 | "github.com/racingmars/ctc-mainframe-api/ctcserver/internal/ctc" 22 | ) 23 | 24 | type DSInfo struct { 25 | Type string 26 | Name string 27 | Volume string 28 | DSOrg string 29 | RecFM string 30 | BlockSize int 31 | LRecLen int 32 | } 33 | 34 | var dsprefixRegex = regexp.MustCompile( 35 | `^[a-zA-Z$#@-][a-zA-Z0-9$#@-]{0,7}` + 36 | `(\.[a-zA-Z$#@-][a-zA-Z0-9$#@-]{0,7})*\.?$`) 37 | 38 | var dsnameRegex = regexp.MustCompile( 39 | `^[a-zA-Z$#@-][a-zA-Z0-9$#@-]{0,7}` + 40 | `(\.[a-zA-Z$#@-][a-zA-Z0-9$#@-]{0,7})*$`) 41 | 42 | var dsnameOptionalMemberRegex = regexp.MustCompile( 43 | `^([a-zA-Z$#@-][a-zA-Z0-9$#@-]{0,7}` + 44 | `(?:\.[a-zA-Z$#@-][a-zA-Z0-9$#@-]{0,7})*)` + 45 | `(?:\(([a-zA-Z$#@-][a-zA-Z0-9$#@-]{0,7})\))?$`) 46 | 47 | func (c *ctcapi) GetDSList(basename string) ([]DSInfo, error) { 48 | if len(basename) > 44 { 49 | return nil, fmt.Errorf("dataset name too long; got %d characters "+ 50 | "but needs to be 44 or fewer", len(basename)) 51 | } 52 | 53 | if !dsprefixRegex.MatchString(basename) { 54 | return nil, fmt.Errorf("dataset search prefix is invalid") 55 | } 56 | 57 | // Always treat a bare HLQ as a complete, specific HLQ and add a period to 58 | // the end of it. This will cause the catalog search to return all of the 59 | // datasets under that HLQ instead of just returning a single result with 60 | // the alias entry in the master catalog for the HLQ. 61 | if !strings.Contains(basename, ".") { 62 | basename += "." 63 | } 64 | 65 | basenameEbcdic := ctc.StoE(strings.ToUpper(basename)) 66 | 67 | log.Debug().Hex("ebcdic", basenameEbcdic).Msgf( 68 | "GetDSList(): performing catalog search for '%s'", basename) 69 | 70 | c.ctcMutex.Lock() 71 | defer c.ctcMutex.Unlock() 72 | 73 | if err := c.sendCommand(opDSList, basenameEbcdic); err != nil { 74 | log.Error().Err(err).Send() 75 | return nil, err 76 | } 77 | 78 | log.Debug().Msg("GetDSList(): reading initial response") 79 | data, err := c.ctcdata.SenseRead() 80 | if err != nil { 81 | return nil, fmt.Errorf("GetDSList(): couldn't perform SenseRead(): %v", 82 | err) 83 | } 84 | if len(data) != 6 { 85 | return nil, fmt.Errorf("GetDSList(): got %d bytes of data, expected 6", 86 | len(data)) 87 | } 88 | 89 | resultCode := binary.BigEndian.Uint32(data[0:4]) 90 | numEntries := binary.BigEndian.Uint16(data[4:6]) 91 | if resultCode != 0 { 92 | log.Info().Msgf("GetDSList(): unsuccessful result code: %02x", 93 | resultCode) 94 | return nil, fmt.Errorf("unsuccessful catalog search result code: %02x", 95 | resultCode) 96 | } 97 | 98 | log.Debug().Msgf("GetDSList(): number of results: %d", numEntries) 99 | 100 | var entries []DSInfo 101 | for i := 0; i < int(numEntries); i++ { 102 | log.Debug().Msgf("GetDSList(): reading item %d of %d", i+1, numEntries) 103 | data, err := c.ctcdata.SenseRead() 104 | if err != nil { 105 | return nil, err 106 | } 107 | 108 | if len(data) != 147 { 109 | log.Error().Msgf("got length %d DSList record, but expected 147", 110 | len(data)) 111 | // Rather than bailing out early, we will at least try to get 112 | // system state back in sync by continuing to read records. 113 | continue 114 | } 115 | 116 | var dsinfo DSInfo 117 | dsinfo.Type = ctc.EtoS(data[0:1]) 118 | dsinfo.Name = strings.TrimSpace(ctc.EtoS(data[1:45])) 119 | dsinfo.Volume = strings.TrimSpace(ctc.EtoS(data[45:51])) 120 | 121 | // data[] starting at index 51 corresponds to the 96 bytes of a 122 | // (likely) format-1 DSCB beginning at offset 44/0x2C (that is, the 96 123 | // bytes returned as part of the OBTAIN macro). 124 | 125 | if data[51] != 0xF1 && dsinfo.Type != "X" { 126 | log.Warn().Msgf("GetDSLIST(): unexpected DSCB format type: "+ 127 | "expecting F1, but got %02x for %s", data[51], dsinfo.Name) 128 | } 129 | 130 | // For DSORG bit definitions, see DS1DSORG in SYS1.AMODGEN(IECSDSL1) 131 | switch { 132 | case data[89]&0x80 > 0: 133 | dsinfo.DSOrg = "IS" 134 | case data[89]&0x40 > 0: 135 | dsinfo.DSOrg = "PS" 136 | case data[89]&0x20 > 0: 137 | dsinfo.DSOrg = "DA" 138 | case data[89]&0x10 > 0: 139 | dsinfo.DSOrg = "CX" 140 | case data[89]&0x02 > 0: 141 | dsinfo.DSOrg = "PO" 142 | case data[90]&0x08 > 0: // note 2nd byte of DS1DSORG 143 | dsinfo.DSOrg = "VS" 144 | default: 145 | dsinfo.DSOrg = "Unk" 146 | } 147 | 148 | switch { 149 | case data[91]&0xC0 == 0x80: 150 | dsinfo.RecFM = "F" 151 | case data[91]&0xC0 == 0x40: 152 | dsinfo.RecFM = "V" 153 | case data[91]&0xC0 == 0xC0: 154 | dsinfo.RecFM = "U" 155 | } 156 | 157 | // Additionally, we can add a "B" for blocked 158 | if data[91]&0x10 == 0x10 { 159 | dsinfo.RecFM += "B" 160 | } 161 | 162 | // And if it's variable, it can be spanned 163 | if data[91]&0xC0 == 0x40 && data[91]&0x08 == 0x08 { 164 | dsinfo.RecFM += "S" 165 | } 166 | 167 | dsinfo.BlockSize = int(binary.BigEndian.Uint16(data[93:95])) 168 | dsinfo.LRecLen = int(binary.BigEndian.Uint16(data[95:97])) 169 | 170 | entries = append(entries, dsinfo) 171 | } 172 | 173 | return entries, nil 174 | } 175 | 176 | func (c *ctcapi) GetMemberList(pdsName string) ([]string, error) { 177 | if len(pdsName) > 44 { 178 | return nil, fmt.Errorf("dataset name too long; got %d characters "+ 179 | "but needs to be 44 or fewer", len(pdsName)) 180 | } 181 | 182 | if !dsnameRegex.MatchString(pdsName) { 183 | return nil, fmt.Errorf("dataset name is invalid") 184 | } 185 | 186 | // The dataset name to MBRLIST must be 44 characters, padded with (EBCDIC) 187 | // spaces. 188 | pdsEbcdic := ctc.StoE(strings.ToUpper(pdsName)) 189 | pdsPadded := make([]byte, 44) 190 | for i := range pdsPadded { 191 | pdsPadded[i] = 0x40 192 | } 193 | copy(pdsPadded, pdsEbcdic) 194 | 195 | c.ctcMutex.Lock() 196 | defer c.ctcMutex.Unlock() 197 | 198 | log.Debug().Hex("pds", pdsEbcdic).Msgf("getting member list for '%s'", 199 | pdsName) 200 | 201 | if err := c.sendCommand(opMbrList, pdsPadded); err != nil { 202 | log.Error().Err(err).Msg("sendCommand() error in GetMemberList()") 203 | return nil, err 204 | } 205 | 206 | log.Debug().Msg("GetMemberList(): reading initial response") 207 | data, err := c.ctcdata.SenseRead() 208 | if err != nil { 209 | return nil, fmt.Errorf( 210 | "GetMemberList(): couldn't perform SenseRead(): %v", err) 211 | } 212 | if len(data) != 8 { 213 | return nil, fmt.Errorf( 214 | "GetMemberList(): got %d bytes in initial response but expected 8", 215 | len(data)) 216 | } 217 | 218 | resultCode := binary.BigEndian.Uint32(data[0:4]) 219 | if resultCode != 0 { 220 | additionalCode := binary.BigEndian.Uint32(data[4:8]) 221 | log.Info().Msgf("GetMemberList(): unsuccessful result code: %02x/%02x", 222 | resultCode, additionalCode) 223 | return nil, fmt.Errorf("unsuccessful result code: %02x/%02x", 224 | resultCode, additionalCode) 225 | } 226 | 227 | var entries []string 228 | var i int 229 | for { 230 | i++ 231 | log.Debug().Msgf("GetMemberList(): reading item %d", i) 232 | data, err := c.ctcdata.SenseRead() 233 | if err != nil { 234 | return nil, fmt.Errorf("couldn't read item %d: %v", i, err) 235 | } 236 | if len(data) < 8 { 237 | log.Error().Msgf("got length %d member record, but expected >=8", 238 | len(data)) 239 | // Rather than bailing out early, we will at least try to get 240 | // system state back in sync by continuing to read records. 241 | continue 242 | } 243 | 244 | if data[0] == 0xFF && data[1] == 0xFF && data[2] == 0xFF && 245 | data[3] == 0xFF && data[4] == 0xFF && data[5] == 0xFF && 246 | data[6] == 0xFF && data[7] == 0xFF { 247 | // last member entry all high bytes. Done 248 | log.Debug().Msg("GetMemberList(): got end record") 249 | break 250 | } 251 | 252 | name := strings.TrimRight(ctc.EtoS(data[0:8]), " ") 253 | entries = append(entries, name) 254 | } 255 | 256 | return entries, nil 257 | } 258 | 259 | func (c *ctcapi) Read(dsn string, raw bool) ([][]byte, error) { 260 | 261 | if !dsnameOptionalMemberRegex.MatchString(dsn) { 262 | return nil, fmt.Errorf("dataset name is invalid") 263 | } 264 | 265 | matches := dsnameOptionalMemberRegex.FindStringSubmatch(dsn) 266 | pdsName := matches[1] 267 | mbrName := matches[2] 268 | 269 | if len(pdsName) > 44 { 270 | return nil, fmt.Errorf("dataset name too long; got %d characters "+ 271 | "but needs to be 44 or fewer", len(pdsName)) 272 | } 273 | if len(mbrName) > 8 { 274 | return nil, fmt.Errorf("member name too long; got %d characters "+ 275 | "but needs to be 8 or fewer", len(mbrName)) 276 | } 277 | 278 | // The dataset name must be 44 characters, padded with (EBCDIC) spaces. 279 | pdsEbcdic := ctc.StoE(strings.ToUpper(pdsName)) 280 | pdsPadded := make([]byte, 44) 281 | for i := range pdsPadded { 282 | pdsPadded[i] = 0x40 283 | } 284 | copy(pdsPadded, pdsEbcdic) 285 | 286 | // The member name must be 8 characters, padded with (EBCDIC) spaces. 287 | mbrEbcdic := ctc.StoE(strings.ToUpper(mbrName)) 288 | mbrPadded := make([]byte, 8) 289 | for i := range mbrPadded { 290 | mbrPadded[i] = 0x40 291 | } 292 | copy(mbrPadded, mbrEbcdic) 293 | 294 | c.ctcMutex.Lock() 295 | defer c.ctcMutex.Unlock() 296 | 297 | log.Debug().Hex("pds", pdsEbcdic).Msgf("reading dataset '%s'", 298 | pdsName) 299 | if len(mbrName) > 0 { 300 | log.Debug().Hex("member", mbrEbcdic).Msgf("reading member '%s'", 301 | mbrName) 302 | } 303 | 304 | // Complete input is the 44-byte DS name followed by 8-byte member 305 | pdsPadded = append(pdsPadded, mbrPadded...) 306 | 307 | if err := c.sendCommand(opRead, pdsPadded); err != nil { 308 | log.Error().Err(err).Msg("sendCommand() error in ReadDS()") 309 | return nil, err 310 | } 311 | 312 | log.Debug().Msg("Read(): reading initial response") 313 | data, err := c.ctcdata.SenseRead() 314 | if err != nil { 315 | return nil, fmt.Errorf("Read(): couldn't perform SenseRead(): %v", err) 316 | } 317 | if len(data) != 8 { 318 | return nil, fmt.Errorf("Read(): got %d bytes of data, expected 8", 319 | len(data)) 320 | } 321 | 322 | resultCode := binary.BigEndian.Uint32(data[0:4]) 323 | if resultCode != 0 { 324 | additionalCode := binary.BigEndian.Uint32(data[4:8]) 325 | log.Info().Msgf("Read(): unsuccessful result code: %02x/%02x", 326 | resultCode, additionalCode) 327 | return nil, fmt.Errorf("unsuccessful result code: %02x/%02x", 328 | resultCode, additionalCode) 329 | } 330 | fixedCode := binary.BigEndian.Uint32(data[4:8]) 331 | fixed := false 332 | if fixedCode > 0 { 333 | fixed = true 334 | } 335 | log.Debug().Bool("fixed", fixed).Send() 336 | 337 | var entries [][]byte 338 | var i int 339 | for { 340 | i++ 341 | log.Debug().Msgf("Read(): reading record %d", i) 342 | data, err := c.ctcdata.SenseRead() 343 | if err != nil { 344 | return nil, err 345 | } 346 | 347 | if len(data) == 1 && data[0] == 0xFF { 348 | // last record. Done 349 | break 350 | } 351 | 352 | if !fixed { 353 | // for variable-length records, we get the record length and 354 | // trim off the Record Descriptor Word (RDW) (see page 24-25 355 | // of GC26-3874-0, OS/VS2 MVS Data Management Services Guide). 356 | if len(data) < 4 { 357 | log.Error().Msgf("Invalid record length for a "+ 358 | "variable-length record: %d", len(data)) 359 | continue 360 | } 361 | recl := binary.BigEndian.Uint16(data[0:2]) 362 | if len(data) < int(recl) { 363 | log.Error().Msgf("Invalid record length for a "+ 364 | "variable-length record that indicates %d length: %d", 365 | recl, len(data)) 366 | continue 367 | } 368 | // recl includes the 4-byte RDW 369 | data = data[0:recl] 370 | } 371 | 372 | if raw { 373 | entries = append(entries, data) 374 | } else { 375 | if !fixed { 376 | // Trim the RDW 377 | data = data[4:] 378 | } 379 | record := strings.TrimRight(ctc.EtoS(data), " ") 380 | entries = append(entries, []byte(record)) 381 | } 382 | } 383 | 384 | return entries, nil 385 | } 386 | 387 | func (c *ctcapi) Submit(jcl []string) (string, error) { 388 | // Confirm we have some JCL 389 | if len(jcl) < 1 { 390 | err := fmt.Errorf("JCL must contain at least 1 record") 391 | log.Debug().Err(err).Msg("invalid JCL in Submit") 392 | return "", err 393 | } 394 | 395 | // Confirm all lines are <=80 characters 396 | for i := range jcl { 397 | if len(jcl[i]) > 80 { 398 | err := fmt.Errorf( 399 | "line %d of JCL is %d characters; must be <= 80", 400 | i+1, len(jcl[i])) 401 | log.Debug().Err(err).Msg("invalid JCL in Submit") 402 | return "", err 403 | } 404 | } 405 | 406 | c.ctcMutex.Lock() 407 | defer c.ctcMutex.Unlock() 408 | 409 | log.Debug().Msgf("sending submit command with %d job lines", len(jcl)) 410 | 411 | recordCountBytes := binary.BigEndian.AppendUint32(nil, uint32(len(jcl))) 412 | if err := c.sendCommand(opSubmit, recordCountBytes); err != nil { 413 | return "", err 414 | } 415 | 416 | data, err := c.ctcdata.SenseRead() 417 | if err != nil { 418 | return "", fmt.Errorf("Submit(): couldn't perform SenseRead(): %v", 419 | err) 420 | } 421 | if len(data) != 4 { 422 | return "", fmt.Errorf("got %d bytes in intial response, expected 4", 423 | len(data)) 424 | } 425 | 426 | resultCode := binary.BigEndian.Uint32(data[0:4]) 427 | if resultCode != 0 { 428 | return "", fmt.Errorf("unsuccessful result code: %02x", 429 | resultCode) 430 | } 431 | log.Debug().Msgf("Submit(): initial response code: %08x", data[0:4]) 432 | 433 | for i, line := range jcl { 434 | // convert input line to a right-space-padded 80 character record. 435 | // (we already verified earlier that all lines are <= 80 characters) 436 | e := ctc.StoE(line) 437 | padded := make([]byte, 80) 438 | for j := range padded { 439 | padded[j] = 0x40 440 | } 441 | copy(padded, e) 442 | 443 | log.Debug().Msg("Submit(): sending JCL record") 444 | if err := c.ctccmd.ControlWrite(padded); err != nil { 445 | return "", fmt.Errorf("error writing JCL record: %v", err) 446 | } 447 | 448 | // We also expect a response on the data channel 449 | log.Debug().Msg("Submit(): reading response") 450 | data, err := c.ctcdata.SenseRead() 451 | if err != nil { 452 | return "", fmt.Errorf("error reading JCL record response: %v", err) 453 | } 454 | 455 | if len(data) != 4 { 456 | return "", fmt.Errorf("got %d response length, expected 4", 457 | len(data)) 458 | } 459 | log.Debug().Msgf("Submit(): got response %08x after record %d", 460 | data, i) 461 | resultCode := binary.BigEndian.Uint32(data[0:4]) 462 | if resultCode != 0 { 463 | errmsg := fmt.Errorf( 464 | "Submit(): unsuccessful result code %08x after record %d", 465 | data, i) 466 | log.Error().Msgf("%v", errmsg) 467 | return "", errmsg 468 | } 469 | } 470 | 471 | log.Debug().Msg("Submit(): getting job number") 472 | data, err = c.ctcdata.SenseRead() 473 | if err != nil { 474 | return "", fmt.Errorf("error reading job number: %v", err) 475 | } 476 | 477 | if !(len(data) == 12 || len(data) == 4) { 478 | return "", fmt.Errorf("unexpected final response length: %d", 479 | len(data)) 480 | } 481 | resultCode = binary.BigEndian.Uint32(data[0:4]) 482 | if resultCode != 0 { 483 | errmsg := fmt.Errorf("Submit(): unexpected final response code %08x", 484 | data[0:4]) 485 | log.Error().Msgf("%v", errmsg) 486 | return "", err 487 | } 488 | 489 | jobnum := ctc.EtoS(data[4:]) 490 | 491 | log.Debug().Msgf("Submit(): job number is %s", jobnum) 492 | return jobnum, nil 493 | } 494 | 495 | func (c *ctcapi) Write(dsn string, inputds []string) error { 496 | // Confirm we have some records 497 | if len(inputds) < 1 { 498 | err := fmt.Errorf("Data must contain at least 1 record") 499 | log.Debug().Err(err).Msg("invalid data in Submit") 500 | return err 501 | } 502 | 503 | if !dsnameOptionalMemberRegex.MatchString(dsn) { 504 | return fmt.Errorf("dataset name is invalid") 505 | } 506 | 507 | matches := dsnameOptionalMemberRegex.FindStringSubmatch(dsn) 508 | pdsName := matches[1] 509 | mbrName := matches[2] 510 | 511 | if len(pdsName) > 44 { 512 | return fmt.Errorf("dataset name too long; got %d characters "+ 513 | "but needs to be 44 or fewer", len(pdsName)) 514 | } 515 | if len(mbrName) > 8 { 516 | return fmt.Errorf("member name too long; got %d characters "+ 517 | "but needs to be 8 or fewer", len(mbrName)) 518 | } 519 | 520 | // The dataset name must be 44 characters, padded with (EBCDIC) spaces. 521 | pdsEbcdic := ctc.StoE(strings.ToUpper(pdsName)) 522 | pdsPadded := make([]byte, 44) 523 | for i := range pdsPadded { 524 | pdsPadded[i] = 0x40 525 | } 526 | copy(pdsPadded, pdsEbcdic) 527 | 528 | // The member name must be 8 characters, padded with (EBCDIC) spaces. 529 | mbrEbcdic := ctc.StoE(strings.ToUpper(mbrName)) 530 | mbrPadded := make([]byte, 8) 531 | for i := range mbrPadded { 532 | mbrPadded[i] = 0x40 533 | } 534 | copy(mbrPadded, mbrEbcdic) 535 | 536 | c.ctcMutex.Lock() 537 | defer c.ctcMutex.Unlock() 538 | 539 | log.Debug().Hex("pds", pdsEbcdic).Msgf("writing dataset '%s'", 540 | pdsName) 541 | if len(mbrName) > 0 { 542 | log.Debug().Hex("member", mbrEbcdic).Msgf("writing member '%s'", 543 | mbrName) 544 | } 545 | 546 | // Complete input is the 44-byte DS name followed by 8-byte member 547 | pdsPadded = append(pdsPadded, mbrPadded...) 548 | 549 | if err := c.sendCommand(opWrite, pdsPadded); err != nil { 550 | log.Error().Err(err).Msg("sendCommand() error in Write()") 551 | return err 552 | } 553 | 554 | log.Debug().Msg("Write(): reading initial response") 555 | data, err := c.ctcdata.SenseRead() 556 | if err != nil { 557 | return fmt.Errorf("Write(): couldn't perform SenseRead(): %v", err) 558 | } 559 | if len(data) != 8 { 560 | return fmt.Errorf("Write(): got %d bytes of data, expected 8", 561 | len(data)) 562 | } 563 | 564 | resultCode := binary.BigEndian.Uint32(data[0:4]) 565 | if resultCode != 0 { 566 | log.Info().Msgf("Write(): unsuccessful result code: %02x", 567 | resultCode) 568 | return fmt.Errorf("unsuccessful result code: %02x", 569 | resultCode) 570 | } 571 | lrecl := binary.BigEndian.Uint32(data[4:8]) 572 | 573 | // Confirm all lines are <=lrecl characters 574 | var lengthErr error 575 | for i := range inputds { 576 | if len(inputds[i]) > int(lrecl) { 577 | lengthErr = fmt.Errorf( 578 | "line %d of input is %d characters; must be <= %d", 579 | i+1, len(inputds[i]), lrecl) 580 | log.Debug().Err(err).Msg("invalid data length in Write()") 581 | } 582 | } 583 | 584 | var proceedCommand int32 585 | var numLines int32 586 | 587 | if lengthErr != nil { 588 | proceedCommand = 1 589 | } 590 | numLines = int32(len(inputds)) 591 | 592 | var initialRespBuf bytes.Buffer 593 | binary.Write(&initialRespBuf, binary.BigEndian, proceedCommand) 594 | binary.Write(&initialRespBuf, binary.BigEndian, numLines) 595 | 596 | log.Debug().Msgf("Sending intent to proceed %02x with %d records", 597 | proceedCommand, numLines) 598 | if err := c.ctccmd.NakedWrite(initialRespBuf.Bytes()); err != nil { 599 | return err 600 | } 601 | 602 | data, err = c.ctcdata.SenseRead() 603 | if err != nil { 604 | return fmt.Errorf("Write(): couldn't perform SenseRead() after "+ 605 | "intent to proceed: %v", err) 606 | } 607 | resultCode = binary.BigEndian.Uint32(data[0:4]) 608 | if resultCode != 0 { 609 | log.Info().Msgf("Write(): unsuccessful result code after intent to "+ 610 | "proceed: %02x", resultCode) 611 | return fmt.Errorf("unsuccessful result code after intend to "+ 612 | "proceed: %02x", resultCode) 613 | } 614 | 615 | log.Debug().Msgf("sending write command with %d records", len(inputds)) 616 | 617 | for i, line := range inputds { 618 | // convert input line to a right-space-padded lrecl character record. 619 | // (we already verified earlier that all lines are <= lcrecl characters) 620 | e := ctc.StoE(line) 621 | padded := make([]byte, lrecl) 622 | for j := range padded { 623 | padded[j] = 0x40 624 | } 625 | copy(padded, e) 626 | 627 | log.Debug().Msg("Write(): sending record") 628 | if err := c.ctccmd.ControlWrite(padded); err != nil { 629 | return fmt.Errorf("error writing record: %v", err) 630 | } 631 | 632 | // We also expect a response on the data channel 633 | log.Debug().Msg("Write(): reading response") 634 | data, err := c.ctcdata.SenseRead() 635 | if err != nil { 636 | return fmt.Errorf("error reading record response: %v", err) 637 | } 638 | 639 | if len(data) != 8 { 640 | return fmt.Errorf("got %d response length, expected 8", 641 | len(data)) 642 | } 643 | log.Debug().Msgf("Write(): got response %08x after record %d", 644 | data, i) 645 | resultCode := binary.BigEndian.Uint32(data[0:4]) 646 | if resultCode != 0 { 647 | errmsg := fmt.Errorf( 648 | "Write(): unsuccessful result code %08x after record %d", 649 | data, i) 650 | log.Error().Msgf("%v", errmsg) 651 | return errmsg 652 | } 653 | } 654 | 655 | log.Debug().Msg("Write(): getting final result") 656 | data, err = c.ctcdata.SenseRead() 657 | if err != nil { 658 | return fmt.Errorf("error reading final result: %v", err) 659 | } 660 | 661 | if len(data) != 8 { 662 | return fmt.Errorf("unexpected final response length: %d", 663 | len(data)) 664 | } 665 | resultCode = binary.BigEndian.Uint32(data[0:4]) 666 | if resultCode != 0 { 667 | errmsg := fmt.Errorf("Write(): unexpected final response code %08x", 668 | data[0:4]) 669 | log.Error().Msgf("%v", errmsg) 670 | return err 671 | } 672 | 673 | return nil 674 | } 675 | 676 | // Quit will instruct the CTC server job on the MVS side to quit. 677 | func (c *ctcapi) Quit() error { 678 | c.ctcMutex.Lock() 679 | defer c.ctcMutex.Unlock() 680 | 681 | log.Debug().Msg("sending quit command") 682 | 683 | if err := c.sendCommand(opQuit, nil); err != nil { 684 | log.Error().Err(err).Msgf("Quit(): error sending quit command") 685 | return err 686 | } 687 | 688 | return nil 689 | } 690 | -------------------------------------------------------------------------------- /ctcserver/internal/ctcapi/ctcapi.go: -------------------------------------------------------------------------------- 1 | package ctcapi 2 | 3 | // Copyright 2022-2023 Matthew R. Wilson 4 | // 5 | // This file is part of CTC Mainframe API. CTC Mainframe API is free software: 6 | // you can redistribute it and/or modify it under the terms of the GNU General 7 | // Public License as published by the Free Software Foundation, either version 8 | // 3 of the license, or (at your option) any later version. 9 | // 10 | // https://github.com/racingmars/ctc-mainframe-api/ 11 | 12 | import ( 13 | "bytes" 14 | "encoding/binary" 15 | "sync" 16 | 17 | "github.com/racingmars/ctc-mainframe-api/ctcserver/internal/ctc" 18 | "github.com/rs/zerolog/log" 19 | ) 20 | 21 | type CTCAPI interface { 22 | GetDSList(basename string) ([]DSInfo, error) 23 | GetMemberList(pdsName string) ([]string, error) 24 | Read(dsn string, raw bool) ([][]byte, error) 25 | Write(dsn string, data []string) error 26 | Submit(jcl []string) (string, error) 27 | Quit() error 28 | } 29 | 30 | type ctcapi struct { 31 | ctccmd, ctcdata ctc.CTC 32 | ctcMutex sync.Mutex 33 | } 34 | 35 | type opcode byte 36 | 37 | const ( 38 | opDSList opcode = 0x01 39 | opMbrList opcode = 0x02 40 | opRead opcode = 0x03 41 | opSubmit opcode = 0x04 42 | opWrite opcode = 0x05 43 | opQuit opcode = 0xFF 44 | ) 45 | 46 | func New(ctccmd, ctcdata ctc.CTC) CTCAPI { 47 | c := ctcapi{ 48 | ctccmd: ctccmd, 49 | ctcdata: ctcdata, 50 | } 51 | 52 | return &c 53 | } 54 | 55 | func (c *ctcapi) sendCommand(op opcode, param []byte) error { 56 | // Build the command buffer -- pad the parameter to 255 length w/ EBCDIC 57 | // spaces 58 | var buf bytes.Buffer 59 | buf.WriteByte(byte(op)) 60 | binary.Write(&buf, binary.BigEndian, uint16(len(param))) 61 | parampadded := make([]byte, 255) 62 | copy(parampadded, param) 63 | buf.Write(parampadded) 64 | 65 | // Send it with a CONTROL+WRITE 66 | log.Debug().Msgf("Sending opcode %02x with param %x") 67 | if err := c.ctccmd.ControlWrite(buf.Bytes()); err != nil { 68 | return err 69 | } 70 | 71 | return nil 72 | } 73 | -------------------------------------------------------------------------------- /ctcserver/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Copyright 2022-2023 Matthew R. Wilson 4 | // 5 | // This file is part of CTC Mainframe API. CTC Mainframe API is free software: 6 | // you can redistribute it and/or modify it under the terms of the GNU General 7 | // Public License as published by the Free Software Foundation, either version 8 | // 3 of the license, or (at your option) any later version. 9 | // 10 | // https://github.com/racingmars/ctc-mainframe-api/ 11 | 12 | import ( 13 | "encoding/binary" 14 | "flag" 15 | "fmt" 16 | "os" 17 | "sync" 18 | 19 | "github.com/labstack/echo/v4" 20 | "github.com/labstack/echo/v4/middleware" 21 | "github.com/rs/zerolog" 22 | "github.com/rs/zerolog/log" 23 | 24 | "github.com/racingmars/ctc-mainframe-api/ctcserver/internal/ctc" 25 | "github.com/racingmars/ctc-mainframe-api/ctcserver/internal/ctcapi" 26 | ) 27 | 28 | func main() { 29 | flagDebug := flag.Bool("debug", false, "Enable debug logging") 30 | flagTrace := flag.Bool("trace", false, "Enable trace logging") 31 | flagPretty := flag.Bool("pretty", false, "Enable pretty logging") 32 | flagConfig := flag.String("config", "config.json", "Config file path") 33 | flagCodepage := flag.String("codepage", "bracket", 34 | "Code page - 'bracket' or 'cp37'") 35 | 36 | fmt.Println() 37 | fmt.Println("CTC Mainframe API") 38 | fmt.Println("Copyright 2022 Matthew R. Wilson ") 39 | fmt.Println("https://github.com/racingmars/ctc-mainframe-api/") 40 | fmt.Println() 41 | fmt.Println("This program comes with ABSOLUTELY NO WARRANTY.") 42 | fmt.Println() 43 | fmt.Println("This is free software, and you are welcome to redistribute it") 44 | fmt.Println("and/or modify it under the terms of the GNU General Public") 45 | fmt.Println("License as published by the Free Software Foundation, either") 46 | fmt.Println("version 3 of the License, or (at your option) any later") 47 | fmt.Println("version.") 48 | fmt.Println() 49 | 50 | flag.Parse() 51 | 52 | if *flagPretty { 53 | log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) 54 | } 55 | 56 | // -trace is higher precedence than -debug 57 | if *flagTrace { 58 | zerolog.SetGlobalLevel(zerolog.TraceLevel) 59 | log.Trace().Msg("trace logging enabled") 60 | } else if *flagDebug { 61 | zerolog.SetGlobalLevel(zerolog.DebugLevel) 62 | log.Debug().Msg("debug logging enabled") 63 | } else { 64 | zerolog.SetGlobalLevel(zerolog.InfoLevel) 65 | } 66 | 67 | // Set up selected codepage globally. Eventually this should probably be 68 | // per-API-call. 69 | switch *flagCodepage { 70 | case "bracket": 71 | ctc.SetGlobalCodepage(ctc.CodepageBracket) 72 | case "cp37": 73 | ctc.SetGlobalCodepage(ctc.CodepageCP37) 74 | default: 75 | log.Error().Msgf("-codepage parameter must be \"bracket\" or "+ 76 | "\"cp37\". \"%s\" is not supported.", *flagCodepage) 77 | os.Exit(1) 78 | } 79 | 80 | if i := realMain(*flagConfig); i > 0 { 81 | os.Exit(i) 82 | } 83 | } 84 | 85 | // we wrap most of "main" in a realMain() function that returns an exit code. 86 | // This allows the real main() function to use os.Exit() if necessary, but 87 | // returning from realMain() allows any defers to still run. 88 | func realMain(configPath string) int { 89 | 90 | config, err := readConfig(configPath) 91 | if err != nil { 92 | log.Error().Err(err).Msg("couldn't read server configuration") 93 | return 1 94 | } 95 | 96 | // Get our CTC command and data emulated devices 97 | ctccmd, ctcdata, err := connect(config) 98 | if err != nil { 99 | log.Error().Err(err).Msg("unable to connect to Hercules") 100 | return 1 101 | } 102 | 103 | defer ctccmd.Close() 104 | defer ctcdata.Close() 105 | 106 | // ...and use them for our CTC API 107 | capi := ctcapi.New(ctccmd, ctcdata) 108 | app := api{ 109 | ctcapi: capi, 110 | } 111 | 112 | // Set up the echo HTTP service 113 | e := echo.New() 114 | e.HideBanner = true 115 | e.Use(middleware.CORS()) 116 | e.Use(middleware.Logger()) 117 | 118 | // Add our API endpoints 119 | e.GET("/api/dslist/:prefix", app.dslist) 120 | e.GET("/api/mbrlist/:pdsName", app.mbrlist) 121 | e.GET("/api/read/:dsn", app.read) 122 | e.POST("/api/submit", app.submit) 123 | e.POST("/api/write/:dsn", app.write) 124 | e.GET("/api/quit", app.quit) 125 | 126 | // Run it 127 | e.Logger.Fatal(e.Start(fmt.Sprintf(":%d", config.ListenPort))) 128 | 129 | return 0 130 | } 131 | 132 | func connect(config configuration) (ctccmd, ctcdata ctc.CTC, err error) { 133 | var hercVer ctc.HerculesVersion 134 | var byteOrder binary.ByteOrder 135 | 136 | if config.Hercules313 { 137 | hercVer = ctc.HerculesVersionOld 138 | } else { 139 | hercVer = ctc.HerculesVersionNew 140 | } 141 | 142 | if config.HerculesHostBigEndian { 143 | byteOrder = binary.BigEndian 144 | } else { 145 | byteOrder = binary.LittleEndian 146 | } 147 | 148 | // We need to listen for both CTC connections simultaneously. This is 149 | // particularly important for Hercules 3.13, which has absolutely no 150 | // re-connection logic, unlike Spinhawk and later. We'll fire both 151 | // connection attempts off in goroutines and wait for them. 152 | 153 | var wg sync.WaitGroup 154 | var connectError bool 155 | wg.Add(2) 156 | 157 | // Establish the command device connection. 158 | go func() { 159 | defer wg.Done() 160 | var err error // don't race on err from the outer function 161 | ctccmd, err = ctc.New(config.CmdLPort, config.CmdRPort, 0x500, 162 | config.HerculesHost, hercVer, byteOrder) 163 | if err != nil { 164 | connectError = true 165 | log.Error().Err(err).Msg( 166 | "couldn't create CTC command device connection") 167 | return 168 | } 169 | 170 | if err := ctccmd.Connect(); err != nil { 171 | connectError = true 172 | log.Error().Err(err).Msg("couldn't create CTC command device") 173 | return 174 | } 175 | }() 176 | 177 | // Establish the data device connection. 178 | go func() { 179 | defer wg.Done() 180 | var err error // don't race on err from the outer function 181 | ctcdata, err = ctc.New(config.DataLPort, config.DataRPort, 0x501, 182 | config.HerculesHost, hercVer, byteOrder) 183 | if err != nil { 184 | connectError = true 185 | log.Error().Err(err).Msg( 186 | "couldn't create CTC data device connection") 187 | return 188 | } 189 | 190 | if err := ctcdata.Connect(); err != nil { 191 | connectError = true 192 | log.Error().Err(err).Msg("couldn't connect CTC data device") 193 | return 194 | } 195 | }() 196 | 197 | wg.Wait() 198 | if connectError { 199 | // We don't know which connection failed, so we'll be careful during 200 | // cleanup. 201 | if ctccmd != nil { 202 | ctccmd.Close() 203 | } 204 | if ctcdata != nil { 205 | ctcdata.Close() 206 | } 207 | return nil, nil, fmt.Errorf("couldn't connect to Hercules") 208 | } 209 | 210 | return ctccmd, ctcdata, nil 211 | } 212 | --------------------------------------------------------------------------------