1 /*
2  *  The Regina Rexx Interpreter
3  *  Copyright (C) 1992-1994  Anders Christensen <anders@pvv.unit.no>
4  *
5  *  This library is free software; you can redistribute it and/or
6  *  modify it under the terms of the GNU Library General Public
7  *  License as published by the Free Software Foundation; either
8  *  version 2 of the License, or (at your option) any later version.
9  *
10  *  This library is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  *  Library General Public License for more details.
14  *
15  *  You should have received a copy of the GNU Library General Public
16  *  License along with this library; if not, write to the Free
17  *  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18  */
19 
20 /*
21  * This module is a real pain, since file I/O is one of the features
22  * that varies most between different platforms. And what makes it
23  * even more of a pain, is the fact that it must be coordinated with
24  * the handling of the condition NOTREADY. Anyway, here are the
25  * decisions set up before (well ... during) the implementation that
26  * guide how this this thing is supposed to work.
27  *
28  * There are four kind of routines, structured in four levels:
29  *
30  * (1)---------+       (2)--------+
31  * | builtin   | ----> | general  |   B   C library
32  * | functions |   A   | routines | ---->  Routines
33  * +-----------+       +----------+
34  *       |                  |
35  *       |                  |
36  *       |                  V     (3)--------+
37  *       +----------------->+---> | Error    |
38  *                                | routines |
39  *                                +----------+
40  *
41  * 1) Builtin functions, these has the "std_" prefix which is standard
42  *    for all buildin functions. The task for these functions are to
43  *    process parameters, call (2) which specializes on operations (like
44  *    read, write, position etc), and return a decent answer back to its
45  *    caller. There is one routine in this level for each of the
46  *    functions in the library of built-in functions. Most of them are
47  *    std_* functions, but there are a few others too.
48  *
49  * 2) These are general operations for reading, writing, positioning,
50  *    etc.  They may call the C library routines directly, or
51  *    indirectly, through calls to (3). The interface (A) between (1)
52  *    and (2) is based on the local structure fileboxptr and strengs.
53  *    There are one function in this level for each of the basic
54  *    operations needed to be performed on a file. Opening, closing,
55  *    reading a line, writing a line, line positioning, reading chars,
56  *    writing chars, positioning chars, counting lines, counting
57  *    chars, etc. The interface (B) to the C library routines uses
58  *    FILE* and char* for its operations.
59  *
60  * 3) General routines to perform 'trivial' tasks. In this level,
61  *    things like retriving Rexx's file table entries are implemented,
62  *    and all the errorhandling. These are called from both the two
63  *    previous levels.
64  *
65  * There are three standard files, called "<stdin>", "<stdout>" and
66  * "<stderr>" (note that the "<" and ">" are part of the filename.)
67  * These are handles for the equivalent Unix standard files. This
68  * might cause problems if you actually do want a file calls that, or
69  * if one of these files is closed, and the more information is
70  * written to it (I can easily visulize Users trying to delete such a
71  * file :-)) So the standard files -- having set flag SURVIVOR -- will
72  * never be closed or reopened.
73  *
74  * Error_file is called by that routine which actually discovers the
75  * problem. If it is an CALL ON condition, it will set the FLAG_FAKE
76  * flag, which all other routines will check for.
77  */
78 
79 #pragma clang diagnostic ignored "-Wincompatible-pointer-types-discards-qualifiers"
80 
81 /*
82  * Bug in LCC complier wchar.h that incorrectly says it defines stat struct
83  * but doesn't
84  */
85 #if defined(__LCC__)
86 # include <sys/stat.h>
87 #endif
88 
89 #include "rexx.h"
90 #include <errno.h>
91 #include <stdio.h>
92 #include <string.h>
93 #ifdef HAVE_ASSERT_H
94 # include <assert.h>
95 #endif
96 #ifdef HAVE_LIMITS_H
97 # include <limits.h>
98 #endif
99 #include <time.h>
100 #if defined(VMS)
101 # include <stat.h>
102 #elif defined(OS2)
103 # include <sys/stat.h>
104 # ifdef HAVE_UNISTD_H
105 #  include <unistd.h>
106 # endif
107 #elif (defined(__WATCOMC__) && !defined(__QNX__)) || defined(_MSC_VER) || defined(__LCC__)
108 # include <sys/stat.h>                                  /* MH 10-06-96 */
109 # include <fcntl.h>                                     /* MH 10-06-96 */
110 # ifdef HAVE_UNISTD_H
111 #  include <unistd.h>                                   /* MH 10-06-96 */
112 # endif
113 # if defined(_MSC_VER) && !defined(__WINS__)
114 #  include <io.h>
115 # endif
116 #elif defined(WIN32) && defined(__IBMC__)               /* LM 26-02-99 */
117 # include <io.h>
118 # include <sys/stat.h>
119 # include <fcntl.h>
120 #elif defined(MAC)
121 # include "mac.h"
122 #else
123 # include <sys/stat.h>
124 # ifdef HAVE_PWD_H
125 #  include <pwd.h>
126 #endif
127 # ifdef HAVE_GRP_H
128 #  include <grp.h>
129 # endif
130 # include <fcntl.h>
131 # ifdef HAVE_UNISTD_H
132 #  include <unistd.h>
133 # endif
134 #endif
135 
136 #ifdef HAVE_DIRECT_H
137 # include <direct.h>
138 #endif
139 
140 #ifdef __EMX__
141 # include <io.h>
142 #endif
143 
144 #ifdef WIN32
145 # ifdef _MSC_VER
146 #  if _MSC_VER >= 1100
147 /* Stupid MSC can't compile own headers without warning at least in VC 5.0 */
148 #   pragma warning(disable: 4115 4201 4214 4514)
149 #  endif
150 # endif
151 # include <windows.h>
152 # ifdef _MSC_VER
153 #  if _MSC_VER >= 1100
154 #   pragma warning(default: 4115 4201 4214)
155 #  endif
156 # endif
157 # if defined(__WATCOMC__) || defined(__BORLANDC__)
158 #  include <io.h>
159 # endif
160 #endif
161 
162 #ifndef min
163 # define min(a,b) (((a) < (b)) ? (a) : (b))
164 #endif
165 
166 /*
167  * Stuff for FSTAT
168  */
169 #if defined(S_IFDIR) && !defined(S_ISDIR)
170 # define S_ISDIR(mode) (((mode) & S_IFDIR)==S_IFDIR)
171 #endif
172 #if defined(S_IFREG) && !defined(S_ISREG)
173 # define S_ISREG(mode) (((mode) & S_IFREG)==S_IFREG)
174 #endif
175 #if defined(S_IFCHR) && !defined(S_ISCHR)
176 # define S_ISCHR(mode) (((mode) & S_IFCHR)==S_IFCHR)
177 #endif
178 #if defined(S_IFBLK) && !defined(S_ISBLK)
179 # define S_ISBLK(mode) (((mode) & S_IFBLK)==S_IFBLK)
180 #endif
181 #if defined(S_IFLNK) && !defined(S_ISLNK)
182 # define S_ISLNK(mode) (((mode) & S_IFLNK)==S_IFLNK)
183 #endif
184 #if defined(S_IFFIFO) && !defined(S_ISFIFO)
185 # define S_ISFIFO(mode) (((mode) & S_IFFIFO)==S_IFFIFO)
186 #endif
187 #if defined(S_IFSOCK) && !defined(S_ISSOCK)
188 # define S_ISSOCK(mode) (((mode) & S_IFSOCK)==S_IFSOCK)
189 #endif
190 #if defined(S_IFNAM) && !defined(S_ISNAM)
191 # define S_ISNAM(mode) (((mode) & S_IFNAM)==S_IFNAM)
192 #endif
193 
194 #if !defined(ACCESSPERMS)
195 # if defined(S_IRWXU) && defined(S_IRWXG) && defined(S_IRWXO)
196 #  define ACCESSPERMS (S_IRWXU|S_IRWXG|S_IRWXO)
197 # else
198 #  if defined(S_IREAD) && defined(S_IWRITE) && defined(S_IEXEC)
199 #   define ACCESSPERMS (S_IREAD|S_IWRITE|S_IEXEC)
200 #  else
201 #   define ACCESSPERMS (0xfff)
202 #  endif
203 # endif
204 #endif
205 /*
206  * The macrodefinition below defines the various modes in which a
207  * file may be opened. These modes are:
208  *
209  *  READ      - Open for readonly access. The current read position
210  *              is set to the the start of the file. If the file does
211  *              not exist, report an error.
212  *
213  *  WRITE     - Open for read and write. The current read position is
214  *              set to the start of the file, while the current write
215  *              position is set to EOF. If file does not exist, create
216  *              it. If file does exist, use existing data.
217  *
218  *  UPDATE    - The combined operation of READ and WRITE, but if file
219  *              does not exist, issue an error.
220  *
221  *  APPEND    - Open in APPEND mode, i.e. open for writeonly, position
222  *              at the End-Of-File, and (if possible) open in a mode
223  *              that disallows positioning. The file will be a transient
224  *              file. If the file does not exist, create it.
225  *
226  *  CREATE    - Open for write, but if the file does exist, truncate
227  *              it at the beginning after opening it.
228  */
229 #define ACCESS_NONE           0
230 #define ACCESS_READ           1
231 #define ACCESS_WRITE          2
232 #define ACCESS_UPDATE         3
233 #define ACCESS_APPEND         4
234 #define ACCESS_CREATE         5
235 #define ACCESS_STREAM_APPEND  6
236 #define ACCESS_STREAM_REPLACE 7
237 
238 /*
239  * These macros is used to set the value of the 'oper' in the filebox
240  * data structure. If last operation on a file was a read or a write,
241  * set 'oper' to OPER_READ or OPER_WRITE, respectively. If last
242  * operation was repositioning or flushing, use OPER_NONE. See
243  * description of 'oper' field in definition of 'filebox'.
244  */
245 #define OPER_NONE          0
246 #define OPER_READ          1
247 #define OPER_WRITE         2
248 
249 /*
250  * Flags, carrying information about files. The 'flag' field in the
251  * 'filebox' structure is set to values matching these defintions. The
252  * meaning of each of these flags is:
253  *
254  * PERSIST   - Set if file is persistent, if unset, file is treated
255  *             as a transient file.
256  * EOF       - Currently not in use
257  * READ      - File has been opened for read access.
258  * WRITE     - File has been opened for write access.
259  * CREATE    - Currently not in use
260  * ERROR     - Set if the file is in error state. If operations are
261  *             attempted performed on files in state error, the
262  *             NOTREADY condition will in general be raised, and the
263  *             operation will fail.
264  * SURVIVOR  - Set for certain special files; the default streams, which
265  *             is not really to be closed or reopened.
266  * FAKE      - Meaningful only if ERROR is set. If FAKE is set, and
267  *             an operation on the file is attempted, the operation is
268  *             'faked' (NOTREADY is not triggered, and the result returned
269  *             for write operations does not report that the output was
270  *             not written.
271  * WREOF     - Current write position is at EOF. If line output is
272  *             performed, there is no need to truncate the file.
273  * RDEOF     - Current read position is at EOF. Reading EOF raises the
274  *             NOTREADY condition, but does not put the file into error
275  *             state.
276  * AFTER_RDEOF - Bit of a hack here. This flag is set after an attempt
277  * (Added by MH) is made to read a stream once the RDEOF flag is set.
278  *               The reason for this is that all the "read" stream
279  *               functions; LINEIN, LINES, CHARIN, etc set the RDEOF
280  *               flag at the point that they determine a RDEOF has
281  *               occurred. This is usually at the end of the function.
282  *               Therefore a LINEIN that reads EOF sets RDEOF and a
283  *               subsequent call to STREAM(stream,'S') will return
284  *               NOTREADY.  This to me is logical, but the behaviour
285  *               of other interpreters is that the first call to
286  *               STREAM(stream,'S') after reaching EOF should still return
287  *               READY. Only when ANOTHER "read" stream function is
288  *               called does STREAM(stream,'S') return NOTREADY.
289  * SWAPPED   - This flag is set if the file is currently swapped out, that
290  *             is, the file is closed in order to let another file use
291  *             the system's file table sloth freed when the file was
292  *             temporarily closed.
293  */
294 #define FLAG_PERSIST     0x0001
295 #define FLAG_EOF         0x0002
296 #define FLAG_READ        0x0004
297 #define FLAG_WRITE       0x0008
298 #define FLAG_CREATE      0x0010
299 #define FLAG_ERROR       0x0020
300 #define FLAG_SURVIVOR    0x0040
301 #define FLAG_FAKE        0x0080
302 #define FLAG_WREOF       0x0100
303 #define FLAG_RDEOF       0x0200
304 #define FLAG_SWAPPED     0x0400
305 #define FLAG_AFTER_RDEOF 0x0800
306 
307 /*
308  * So, what is the big difference between FAKE and ERROR. Well, when a
309  * file gets it ERROR flag set, it signalizes that the file is in
310  * error state, and that no fileoperations should be performed on it.
311  * The FAKE flag is only meaningful when the ERROR flag is set. If set
312  * the FAKE flag tells that file operations should be faked in order to
313  * give the user the impression that everything is OK, while if FAKE is
314  * not set, errors are returned.
315  *
316  * The clue is that if a statement contains several operations on one
317  * file, and the first operation bombs, CALL ON NOTREADY will not take
318  * effect before the next statement boundary at the same procedural
319  * level So, for the rest of the file operations until that statement
320  * has finished, the FAKE flag is set, and signalizes that OK result
321  * should be returned whenever positioning or write is performed, and
322  * that NOTREADY should not be raised again.
323  *
324  * The reason for the RDEOF flag is that reading beyond EOF is not really
325  * a capital crime, and a lot of programmers are likely to do that, and
326  * expect things to be OK after repositioning current read position to
327  * another part of the file. If a file is put into ERROR state, it has
328  * to be explicitly reset in order to do any useful to it afterwards.
329  * Therefore, if EOF is seen on input, RDEOF is set, and NOTREADY is
330  * raised, but the file is not put into ERROR state.
331  */
332 
333 /*
334  * The following macros defines symbolic names to the commands available
335  * in the Rexx built-in function STREAM(). The meaning of each of these
336  * commands are:
337  *
338  *  READ       - Opens the file with the corresponding mode. For a deeper
339  *  WRITE        description of each of these modes, see the defininition
340  *  APPEND       of the ACCESS_* macros. STREAM() is used to explicitly
341  *  UPDATE       open a file, while Rexx is totally happy with the
342  *  CREATE       traditional implicit opening, i.e. that the file is
343  *               opened for the needed access at the time when it is
344  *               first used. If the file to be opened is already open,
345  *               it will first be closed, and then opened in the
346  *               specified mode.
347  *
348  *  CLOSE      - Closes a file, works for any type of access. But if
349  *               the file is a default stream, it will not be closed.
350  *               Default streams should not be closed.
351  *
352  *  FLUSH      - Performs flushing on the file. Actually, I'm not so
353  *               sure whether that is very interesting, since flushing
354  *               is always performed after a write, anyway. Though, it
355  *               might become an important function if the automatic
356  *               flushing after write is removed (e.g. to improve speed).
357  *
358  *  STATUS     - Returns status information assiciated with the file as
359  *               a human readable string. The information returned is the
360  *               internal information that Rexx stores in the Rexx file
361  *               table entry for that file. Use FSTAT to get information
362  *               about the file from the operating system. See the
363  *               function 'getrexxstatus()' for more information about
364  *               the layout of the returned string.
365  *
366  *  FSTAT      - Returns status information associated with the file as
367  *               a human readable string. The information returned is the
368  *               information normally returned by the stat() system call
369  *               under Unix (i.e. size, dates, access modes, etc). Use
370  *               STATUS to get Rexx's information about the file. See
371  *               the function 'getstatus()' for more information about
372  *               the layout of the string returned.
373  *
374  * RESET       - Resets the file after an error. Of course, this will
375  *               only work for files which are 'resettable'. If the error
376  *               is too serious, resetting will help little to fix the
377  *               problem. E.g. writing beyond end-of-file can easily be
378  *               fixed by RESET, trying to use a file which is named
379  *               by an invalid syntax can not be correctly reset.
380  *
381  * READABLE    - Checks that the file in question is available in the
382  * WRITABLE      mode given, for the user that is executing the script.
383  * EXECUTABLE    I.e. READABLE will return '1' for a file, if the file
384  *               is readable for the user, else '0' is returned. Note
385  *               that FSTAT returns the information about the accessmodes
386  *               for a file, these returns the 'accessmode' which is
387  *               relevant for a particular user. Also note that if your
388  *               machine are using suid-bit (i.e. Unix), this function
389  *               will check for the real uid, not the effective uid.
390  *               Consequently, it may not give the wanted result for
391  *               suid rexx scripts, see the Unix access() function. (And
392  *               anyway, suid scripts are a _very_ bad idea under Unix,
393  *               so this is probably not a problem ... :-)
394  */
395 #define COMMAND_NONE                       0
396 #define COMMAND_READ                       1
397 #define COMMAND_WRITE                      2
398 #define COMMAND_APPEND                     3
399 #define COMMAND_UPDATE                     4
400 #define COMMAND_CREATE                     5
401 #define COMMAND_CLOSE                      6
402 #define COMMAND_FLUSH                      7
403 #define COMMAND_STATUS                     8
404 #define COMMAND_FSTAT                      9
405 #define COMMAND_RESET                     10
406 #define COMMAND_READABLE                  11
407 #define COMMAND_WRITEABLE                 12
408 #define COMMAND_EXECUTABLE                13
409 #define COMMAND_LIST                      14
410 #define COMMAND_QUERY_DATETIME            15
411 #define COMMAND_QUERY_EXISTS              16
412 #define COMMAND_QUERY_HANDLE              17
413 #define COMMAND_QUERY_SEEK                18
414 #define COMMAND_QUERY_SIZE                19
415 #define COMMAND_QUERY_STREAMTYPE          20
416 #define COMMAND_QUERY_TIMESTAMP           21
417 #define COMMAND_QUERY_CREATETIME          22
418 #define COMMAND_QUERY_MODIFYTIME          23
419 #define COMMAND_QUERY_ACCESSTIME          24
420 #define COMMAND_QUERY_POSITION            25
421 #define COMMAND_QUERY                     26
422 #define COMMAND_QUERY_POSITION_READ       27
423 #define COMMAND_QUERY_POSITION_WRITE      28
424 #define COMMAND_QUERY_POSITION_SYS        29
425 #define COMMAND_QUERY_POSITION_READ_CHAR  30
426 #define COMMAND_QUERY_POSITION_READ_LINE  31
427 #define COMMAND_QUERY_POSITION_WRITE_CHAR 32
428 #define COMMAND_QUERY_POSITION_WRITE_LINE 33
429 #define COMMAND_OPEN                      34
430 #define COMMAND_OPEN_READ                 35
431 #define COMMAND_OPEN_WRITE                36
432 #define COMMAND_OPEN_BOTH                 37
433 #define COMMAND_OPEN_BOTH_APPEND          38
434 #define COMMAND_OPEN_BOTH_REPLACE         39
435 #define COMMAND_OPEN_WRITE_APPEND         40
436 #define COMMAND_OPEN_WRITE_REPLACE        41
437 #define COMMAND_SEEK                      42
438 #define COMMAND_POSITION                  43
439 
440 #define STREAMTYPE_UNKNOWN     0
441 #define STREAMTYPE_PERSISTENT  1
442 #define STREAMTYPE_TRANSIENT   2
443 /*
444  * Define TRUE_TRL_IO, if you want the I/O system to be even more like
445  * TRL. It will try to mimic the behaviour in TRL exactly. Note that if
446  * you _do_ define this, you might experience a degrade in runtime
447  * performance.
448  */
449 #define TRUE_TRL_IO
450 
451 /*
452  * There are two ways to report an error for file I/O operations. Either
453  * as an "error" or as a "warning". Both will raise the NOTREADY
454  * condition, but only ERROR will actually put the file into ERROR mode.
455  * Warnings are used for e.g. EOF while reading. Both are implemented
456  * by the same routine.
457  */
458 #define file_error(a,b,c)   handle_file_error(TSD,a,b,c,1)
459 #define file_warning(a,b,c) handle_file_error(TSD,a,b,c,0)
460 
461 /*
462  * CASE_SENSITIVE_FILENAMES is used to determine if internal file
463  * pointers respect the case of files and treat "ABC" as a different
464  * file to "abc".
465  */
466 #ifdef UNIX
467 # define CASE_SENSITIVE_FILENAMES
468 #endif
469 /*
470  * Regina truncates a file when repositioning by the use of a line
471  * count. That is, if the file has ten lines, and you use the BIF
472  * lineout(file,,4), it will be truncated after the fourth line.
473  * Truncating is not performed for character repositioning.
474  *
475  * If you don't want truncating after line repositioning, undefine
476  * the macro HAVE_FTRUNCATE in config.h. Also, if your system doesn't
477  * have ftruncate(), undefine HAVE_FTRUNCATE, and survive without the
478  * truncating.
479  *
480  * The function ftruncate() is a BSDism; if you have trouble finding
481  * it, try linking with -lbsd or -lucb or something like that. Since
482  * it is not a standard POSIX feature, some machines may generate
483  * warnings during compilation. Let's help these machines ...
484  */
485 #if defined(FIX_PROTOS) && defined(HAVE_FTRUNCATE)
486 # if defined(ultrix)
487    int ftruncate( int fd, int length ) ;
488 # endif
489 #endif
490 
491 /*
492  * Since development of Ultrix has ceased, and they never managed to
493  * fix a few things, we want to define a few things, just in order
494  * to kill a few warnings ...
495  */
496 #if defined(FIX_PROTOS) && defined(FIX_ALL_PROTOS) && defined(ultrix)
497    int fstat( int fd, struct stat *buf ) ;
498    int stat( char *path, struct stat *buf ) ;
499 #endif
500 
501 #include "regina64.h"
502 
503 #if defined(WIN32)
504 /*
505  * Work around for bug in WIN32 stat() function; can't have trailing slash
506  */
507 # if defined(HAVE__STATI64)
rx_w32_stat(const char * path,struct _stati64 * buffer)508 static int rx_w32_stat( const char *path, struct _stati64 *buffer )
509 {
510    int lastpos;
511    int rc;
512    char tmpstring[REXX_PATH_MAX ];
513 
514    rc = _stati64( path, buffer );
515    if ( rc != 0 && errno == ENOENT )
516    {
517       if ( path )
518       {
519          lastpos = strlen( path ) -  1;
520          if ( path[lastpos] == '\\' || path[lastpos] == '/')
521          {
522             memcpy( tmpstring, path, lastpos ) ;
523             tmpstring[lastpos] = '\0';
524             rc = _stati64( tmpstring, buffer );
525          }
526       }
527    }
528    return rc;
529 }
530 # else
rx_w32_stat(const char * path,struct _stat * buffer)531 static int rx_w32_stat( const char *path, struct _stat *buffer )
532 {
533    int lastpos;
534    int rc;
535    char tmpstring[REXX_PATH_MAX ];
536 
537    rc = stat( path, buffer );
538    if ( rc != 0 && errno == ENOENT )
539    {
540       if ( path )
541       {
542          lastpos = strlen( path ) -  1;
543          if ( path[lastpos] == '\\'|| path[lastpos] == '/')
544          {
545             memcpy( tmpstring, path, lastpos ) ;
546             tmpstring[lastpos+1] = '\0';
547             rc = stat( tmpstring, buffer );
548          }
549       }
550    }
551    return rc;
552 }
553 # endif
554 #endif
555 
556 /*
557  * Here comes another 'sunshine-story' ...  Since SunOS don't have
558  * a decent set of include-files in the standard version of the OS,
559  * their <stdio.h> don't define these macros. Instead, Sun seems to
560  * survive with the old custom of using the numberic values of these
561  * macros directly. If compiled with "SunKlugdes" defined, try to
562  * fix this.
563  *
564  * If you are using gcc on a Sun, you may want to run the program
565  * fixincludes that comes with gcc. It will fix this more permanently.
566  * At least one recent version of GCC for VMS doesn't have this
567 
568  */
569 #if defined(SunKludges) || (defined(__GNUC__) && defined(VMS))
570 # define SEEK_SET  0
571 # define SEEK_CUR  1
572 # define SEEK_END  2
573 #endif
574 
575 /*
576  * Some machines don't defined these ... they should!
577  */
578 #if defined(VMS) || defined(_MSC_VER) || (defined(WIN32) && defined(__IBMC__)) || (defined(WIN32) && defined(__BORLANDC__)) || defined(__LCC__)
579 # define F_OK 0
580 # define X_OK 1
581 # define W_OK 2
582 # define R_OK 4
583 #endif
584 
585 /*
586  * Here is the datastructure in which to store the information about
587  * open files. The storage format of the file table is discussed
588  * elsewhere. The fields used to handle storing are 'next' and 'prev'
589  * which is used to implement a double linked list of files having
590  * the same hashfunc; and 'newer' and 'older' which are used to maintain
591  * a large double linked list of all files in order of the most
592  * recently used file.
593  *
594  * The other fields are:
595  *
596  *  fileptr    - Pointer to the filehandle use by the system when
597  *               accessing the file through the normal I/O calls.
598  *               If this pointer is NULL, it means that the file is
599  *               not currently open.
600  *  oper       - Holds the value that tells whether the most recent
601  *               operation on this file was a read or a write. This has
602  *               importance for flushing, since a read can't imediately
603  *               follow a write (or vice versa) without a flush (or
604  *               a repositioning) inbetween. Takes the values OPER_READ,
605  *               OPER_WRITE or OPER_NONE (signalizes that most recent
606  *               operation can be followed by either read or write).
607  *  flag       - Bitfield that holds information about the file. The
608  *               significance of the various fields are described by
609  *               the FLAG_* macros.
610  *  error      - Most recently 'errno' code for this file. It could have
611  *               been stored into 'errmsg' instead, but that would require
612  *               copying of data which might not be used. If undefined,
613  *               it will have the value 0.
614  *  readpos    - The current read position in the file, as a character
615  *               position. Note that this is in 'C-syntax', i.e. the
616  *               first character in the file is numbered "0". A value of
617  *               -1 means that the value is unknown or undefined.
618  *  readline   - The line number of the current read position, which must
619  *               be positive if define. A value of zero means that the
620  *               line number is undefined or unknown. If current read
621  *               position is at an EOL, 'readline' refers to the line
622  *               preceding the EOL.
623  *  writepos   - Similar to 'readpos' but for current write position.
624  *  writeline  - Similar to 'readline' but for current write position.
625  *  filename0  - Pointer to string containing the filename assiciated
626  *               with this file. This string is garanteed to have an
627  *               ASCII NUL following the last character, so it can be
628  *               used directly in file operations. This field *must*
629  *               be defined.
630  *  errmsg     - Error message associated with the file. Some errors are
631  *               not trapped during call to system routines, and these
632  *               does not have an error message defined by the opsys.
633  *               E.g. when positioning current read position after EOF.
634  *               This field stores errormessages for these situations.
635  *               If undefined, it will be a NULL pointer.
636  *
637  * Both errmsg and error can not be defined simultaneously.
638  */
639 
640 typedef struct fileboxtype *fileboxptr ;
641 typedef const struct fileboxtype *cfileboxptr ;
642 typedef struct fileboxtype {
643    FILE *fileptr ;
644    unsigned char oper ;
645 //   size_t readpos, writepos, thispos ;
646    rx_64 readpos, writepos, thispos ;
647    rx_64 readline, writeline, linesleft ;
648    int flag, error;
649    fileboxptr prev, next ;    /* within a filehash entry */
650    fileboxptr newer, older ;
651    streng *filename0 ;
652    streng *errmsg ;
653 } filebox ;
654 
655 /* POSIX denies read and write operations on streams without intermediate
656  * fflush, fseek, fsetpos or rewind (list from EMX). We use the following
657  * macros to switch directly before an I/O operation. "Useful" fseeks should
658  * be error checked. This is not necessary here since the following operation
659  * will fault in case of an error.
660  */
661 
662 #define SWITCH_OPER_READ(fptr)  {if (fptr->oper==OPER_WRITE)          \
663                                     rx_fseek(fptr->fileptr,0l,SEEK_CUR); \
664                                  fptr->oper=OPER_READ;}
665 #define SWITCH_OPER_WRITE(fptr) {if (fptr->oper==OPER_READ)           \
666                                     rx_fseek(fptr->fileptr,0l,SEEK_CUR); \
667                                  fptr->oper=OPER_WRITE;}
668 
669 typedef struct
670 {  /* fil_tsd: static variables of this module (thread-safe) */
671    /*
672     * The following two pointers are pointers into the doble linked list
673     * of all files in the file table. They points to the most recently
674     * used file, and the least recently used open file. Note that the latter
675     * of these are _not_ the same as the last file in the list. If the
676     * Rexx' file table contains more files than the system's file table
677     * can contain, 'lrufile' will point to the last open file in the double
678     * linked list. Files further out in the list are 'swapped' out.
679     */
680    fileboxptr mrufile;
681 
682    fileboxptr stdio_ptr[6];
683    void *     rdarea;
684    fileboxptr filehash[131];
685    int        rol_size ;       /* readoneline() */
686    char *     rol_string ;     /* readoneline() */
687    int        got_eof ;        /* readkbdline() */
688 } fil_tsd_t; /* thread-specific but only needed by this module. see
689               * init_filetable
690               */
691 /*
692  * Structure to define stream types; names and whether persistent,transient or unknown
693  */
694 typedef struct
695 {
696    int streamtype;
697    char *streamname;
698 } stream_type_t;
699 
700 #define STREAMTYPE_DIRECTORY          1
701 #define STREAMTYPE_CHARACTERSPECIAL   2
702 #define STREAMTYPE_BLOCKSPECIAL       3
703 #define STREAMTYPE_REGULARFILE        4
704 #define STREAMTYPE_FIFO               5
705 #define STREAMTYPE_SYMBOLICLINK       6
706 #define STREAMTYPE_SOCKET             7
707 #define STREAMTYPE_SPECIALNAME        8
708 
709 static const stream_type_t stream_types[] =
710 {
711    { STREAMTYPE_UNKNOWN  , ""                  },
712    { STREAMTYPE_UNKNOWN  , " Directory"        },
713    { STREAMTYPE_PERSISTENT," CharacterSpecial" },
714    { STREAMTYPE_PERSISTENT," BlockSpecial"     },
715    { STREAMTYPE_PERSISTENT," RegularFile"      },
716    { STREAMTYPE_UNKNOWN  , " FIFO"             },
717    { STREAMTYPE_UNKNOWN  , " SymbolicLink"     },
718    { STREAMTYPE_UNKNOWN  , " Socket"           },
719    { STREAMTYPE_UNKNOWN  , " SpecialName"      },
720 };
721 
722 static rx_64 positioncharfile( tsd_t *TSD, const char *bif, int argno, fileboxptr fileptr, int oper, rx_64 where, int from );
723 static rx_64 positionfile( tsd_t *TSD, const char *bif, int argno, fileboxptr ptr, int oper, rx_64 lineno, int from );
724 static void handle_file_error( tsd_t *TSD, fileboxptr ptr, int rc, const char *errmsg, int level) ;
725 static int flush_output( tsd_t *TSD, fileboxptr ptr );
726 
727 /*
728  * Based on the st_mode filed returned from stat(), determine the Regina "stream type".
729  * The returned value is an index into stream_types array, which allows the caller to
730  * determine if the stream is persistent, transient or unknown, and also allows the
731  * user to look up the stream type name.
732  * Added to resolve 802114.
733  */
determine_stream_type(int mode)734 static int determine_stream_type( int mode )
735 {
736 #ifdef S_ISDIR
737    if ( S_ISDIR(mode) )
738       return STREAMTYPE_DIRECTORY;
739 #endif
740 #ifdef S_ISCHR
741    if ( S_ISCHR(mode) )
742       return STREAMTYPE_CHARACTERSPECIAL;
743 #endif
744 #ifdef S_ISBLK
745    if ( S_ISBLK(mode) )
746       return STREAMTYPE_BLOCKSPECIAL;
747 #endif
748 #ifdef S_ISREG
749    if ( S_ISREG(mode) )
750       return STREAMTYPE_REGULARFILE;
751 #endif
752 #ifdef S_ISFIFO
753    if ( S_ISFIFO(mode) )
754       return STREAMTYPE_FIFO;
755 #endif
756 #ifdef S_ISLNK
757    if ( S_ISLNK(mode) )
758       return STREAMTYPE_SYMBOLICLINK;
759 #endif
760 #ifdef S_ISSOCK
761    if ( S_ISSOCK(mode) )
762       return STREAMTYPE_SOCKET;
763 #endif
764 #ifdef S_ISNAM
765    if ( S_ISNAM(mode) )
766       return STREAMTYPE_SPECIALNAME;
767 #endif
768    return STREAMTYPE_UNKNOWN;
769 }
770 
771 /*
772  * Marks all entries in the filetable. Used only by the memory
773  * management. Does not really change anything, so you can in general
774  * forget this one. This routine is called from memory.c in order to
775  * mark all statically defined data in this file.
776  */
777 #ifdef TRACEMEM
mark_filetable(const tsd_t * TSD)778 void mark_filetable( const tsd_t *TSD )
779 {
780    fileboxptr ptr=NULL ;
781    fil_tsd_t *ft;
782 
783    ft = (fil_tsd_t *)TSD->fil_tsd;
784    for (ptr=ft->mrufile; ptr; ptr=ptr->older)
785    {
786       markmemory( ptr, TRC_FILEPTR ) ;
787       markmemory( ptr->filename0, TRC_FILEPTR ) ;
788       if (ptr->errmsg)
789          markmemory( ptr->errmsg, TRC_FILEPTR ) ;
790    }
791 
792    if (ft->rdarea)
793       markmemory( ft->rdarea, TRC_FILEPTR ) ;
794 
795 }
796 #endif /* TRACEMEM */
797 
798 #if defined(WIN32) && defined(_MSC_VER)
799 /*
800  * This is a replacement fo the BSD ftruncate() function.
801  * The code in this function was written by Les Moull.
802  */
803 
ftruncate(int fd,long pos)804 int ftruncate( int fd, long pos )
805 {
806    HANDLE h = (HANDLE)_get_osfhandle( fd ) ;
807 
808    if (SetFilePointer( h, pos, NULL, FILE_BEGIN) == 0xFFFFFFFF)
809       return -1;
810 
811    if ( !SetEndOfFile( h ) )
812       return -1;
813 
814    return 0;
815 }
816 #endif
817 
818 #if defined(__WATCOMC__) && defined(__QNX__)
819 # define ftruncate( fd, pos ) ltrunc( fd, pos, SEEK_SET )
820 #endif
821 
822 /*
823  * This command maps the string 'cmd' into a number which is to be
824  * interpreted according to the settings of the COMMAND_ macros.
825  * The input strings must be one of the valid command, or else the
826  * COMMAND_NONE value is returned.
827  *
828  * Well, this routine should really have been implemented differently,
829  * since sequential searching through a list of strings is not very
830  * efficient. But still, it is not so many entries in the list, and
831  * this function is not going to be called often, so I suppose it
832  * doesn't matter too much. Ideallistic, it should be rewritten to
833  * a binary search.
834  */
835 
get_command(streng * cmd)836 static char get_command( streng *cmd )
837 {
838    Str_upper(cmd);
839 
840    if (cmd->len==4 && !memcmp(cmd->value, "READ", 4))
841       return COMMAND_READ ;
842    if (cmd->len==5 && !memcmp(cmd->value, "WRITE", 5))
843       return COMMAND_WRITE ;
844    if (cmd->len==6 && !memcmp(cmd->value, "APPEND", 6))
845       return COMMAND_APPEND ;
846    if (cmd->len==6 && !memcmp(cmd->value, "UPDATE", 6))
847       return COMMAND_UPDATE ;
848    if (cmd->len==6 && !memcmp(cmd->value, "CREATE", 6))
849       return COMMAND_CREATE ;
850    if (cmd->len==5 && !memcmp(cmd->value, "CLOSE", 5))
851       return COMMAND_CLOSE ;
852    if (cmd->len==5 && !memcmp(cmd->value, "FLUSH", 5))
853       return COMMAND_FLUSH ;
854    if (cmd->len==6 && !memcmp(cmd->value, "STATUS", 6))
855       return COMMAND_STATUS ;
856    if (cmd->len==5 && !memcmp(cmd->value, "FSTAT", 5))
857       return COMMAND_FSTAT ;
858    if (cmd->len==5 && !memcmp(cmd->value, "RESET", 5))
859       return COMMAND_RESET ;
860    if (cmd->len==8 && !memcmp(cmd->value, "READABLE", 8))
861       return COMMAND_READABLE ;
862    if (cmd->len==8 && !memcmp(cmd->value, "WRITABLE", 8))
863       return COMMAND_WRITEABLE ;
864    if (cmd->len==10 && !memcmp(cmd->value, "EXECUTABLE", 10))
865       return COMMAND_EXECUTABLE ;
866    if (cmd->len==4 && !memcmp(cmd->value,  "LIST", 4))
867       return COMMAND_LIST ;
868    if (cmd->len>=4 && !memcmp(cmd->value,  "OPEN", 4))
869       return COMMAND_OPEN ;
870    if (cmd->len>=5 && !memcmp(cmd->value,  "QUERY", 5))
871       return COMMAND_QUERY ;
872    if (cmd->len>=4 && !memcmp(cmd->value,  "SEEK", 4))
873       return COMMAND_SEEK ;
874    if (cmd->len>=8 && !memcmp(cmd->value,  "POSITION", 8))
875       return COMMAND_POSITION ;
876    return COMMAND_NONE ;
877 }
878 
get_querycommand(const streng * cmd)879 static char get_querycommand( const streng *cmd )
880 {
881    if (cmd->len==8 && !memcmp(cmd->value,  "DATETIME", 8))
882       return COMMAND_QUERY_DATETIME ;
883    if (cmd->len==6 && !memcmp(cmd->value,  "EXISTS", 6))
884       return COMMAND_QUERY_EXISTS ;
885    if (cmd->len==6 && !memcmp(cmd->value,  "HANDLE", 6))
886       return COMMAND_QUERY_HANDLE ;
887    if (cmd->len>=4 && !memcmp(cmd->value,  "SEEK", 4))
888       return COMMAND_QUERY_SEEK ;
889    if (cmd->len>=8 && !memcmp(cmd->value,  "POSITION", 8))
890       return COMMAND_QUERY_POSITION ;
891    if (cmd->len==4  && !memcmp(cmd->value, "SIZE", 4))
892       return COMMAND_QUERY_SIZE ;
893    if (cmd->len==10 && !memcmp(cmd->value, "STREAMTYPE", 10))
894       return COMMAND_QUERY_STREAMTYPE ;
895    if (cmd->len==9  && !memcmp(cmd->value, "TIMESTAMP", 9))
896       return COMMAND_QUERY_TIMESTAMP ;
897    if (cmd->len==10  && !memcmp(cmd->value, "CREATETIME", 10))
898       return COMMAND_QUERY_CREATETIME;
899    if (cmd->len==10  && !memcmp(cmd->value, "MODIFYTIME", 10))
900       return COMMAND_QUERY_MODIFYTIME;
901    if (cmd->len==10  && !memcmp(cmd->value, "ACCESSTIME", 10))
902       return COMMAND_QUERY_ACCESSTIME;
903    return COMMAND_NONE ;
904 }
905 
get_querypositioncommand(const streng * cmd)906 static char get_querypositioncommand( const streng *cmd )
907 {
908    if (cmd->len>=4 && !memcmp(cmd->value,  "READ", 4))
909       return COMMAND_QUERY_POSITION_READ ;
910    if (cmd->len>=5 && !memcmp(cmd->value,  "WRITE", 5))
911       return COMMAND_QUERY_POSITION_WRITE ;
912    if (cmd->len==3 && !memcmp(cmd->value,  "SYS", 3))
913       return COMMAND_QUERY_POSITION_SYS ;
914    return COMMAND_NONE ;
915 }
916 
get_querypositionreadcommand(const streng * cmd)917 static char get_querypositionreadcommand( const streng *cmd )
918 {
919    if (cmd->len==4 && !memcmp(cmd->value,  "CHAR", 4))
920       return COMMAND_QUERY_POSITION_READ_CHAR ;
921    if (cmd->len==4 && !memcmp(cmd->value,  "LINE", 4))
922       return COMMAND_QUERY_POSITION_READ_LINE ;
923    if (cmd->len==0)
924       return COMMAND_QUERY_POSITION_READ_CHAR ;
925    return COMMAND_NONE ;
926 }
927 
get_querypositionwritecommand(const streng * cmd)928 static char get_querypositionwritecommand( const streng *cmd )
929 {
930    if (cmd->len==4 && !memcmp(cmd->value,  "CHAR", 4))
931       return COMMAND_QUERY_POSITION_WRITE_CHAR ;
932    if (cmd->len==4 && !memcmp(cmd->value,  "LINE", 4))
933       return COMMAND_QUERY_POSITION_WRITE_LINE ;
934    if (cmd->len==0)
935       return COMMAND_QUERY_POSITION_WRITE_CHAR ;
936    return COMMAND_NONE ;
937 }
938 
get_opencommand(const streng * cmd)939 static char get_opencommand( const streng *cmd )
940 {
941    if (cmd->len>=4 && !memcmp(cmd->value,  "BOTH", 4))
942       return COMMAND_OPEN_BOTH ;
943    if (cmd->len==4 && !memcmp(cmd->value,  "READ", 4))
944       return COMMAND_OPEN_READ ;
945    if (cmd->len>=5 && !memcmp(cmd->value,  "WRITE", 5))
946       return COMMAND_OPEN_WRITE ;
947    if (cmd->len==0)
948       return COMMAND_OPEN_BOTH ;
949    return COMMAND_NONE ;
950 }
951 
get_opencommandboth(const streng * cmd)952 static char get_opencommandboth( const streng *cmd )
953 {
954    if (cmd->len==6 && !memcmp(cmd->value,  "APPEND", 6))
955       return COMMAND_OPEN_BOTH_APPEND ;
956    if (cmd->len==7 && !memcmp(cmd->value,  "REPLACE", 7))
957       return COMMAND_OPEN_BOTH_REPLACE ;
958    if (cmd->len==0)
959       return COMMAND_OPEN_BOTH ;
960    return COMMAND_NONE ;
961 }
962 
get_opencommandwrite(const streng * cmd)963 static char get_opencommandwrite( const streng *cmd )
964 {
965    if (cmd->len==6 && !memcmp(cmd->value,  "APPEND", 6))
966       return COMMAND_OPEN_WRITE_APPEND ;
967    if (cmd->len==7 && !memcmp(cmd->value,  "REPLACE", 7))
968       return COMMAND_OPEN_WRITE_REPLACE ;
969    if (cmd->len==0)
970       return COMMAND_OPEN_WRITE ;
971    return COMMAND_NONE ;
972 }
973 
974 
975 /* ==================================================================== */
976 /*                       level 3 routines                               */
977 
978 /*
979  * Returns the fileboxptr of a file, if is has already been opened.
980  * If it does not exist in Rexx's file table, a NULL pointer is
981  * returned in stead. It is easy to change the datastruction in
982  * which the file table is stored.
983  *
984  * If using VMS, or another opsys that has a caseinsensitive file
985  * system, maybe it should disregard the case of the filename. In
986  * general, maybe it should 'normalize' the file name before storing
987  * it in the file table (do we sence an upcoming namei() :-)
988  */
989 
990 #define FILEHASH_SIZE (sizeof(((fil_tsd_t*)0)->filehash) / \
991                        sizeof(((fil_tsd_t*)0)->filehash[0]))
992 
993 #ifdef CASE_SENSITIVE_FILENAMES
994 #define filehashvalue(strng) (hashvalue(strng->value, strng->len) % FILEHASH_SIZE)
995 #else
996 #define filehashvalue(strng) (hashvalue_ic(strng->value, strng->len) % FILEHASH_SIZE)
997 #endif
998 
removefileptr(const tsd_t * TSD,cfileboxptr ptr)999 static void removefileptr( const tsd_t *TSD, cfileboxptr ptr )
1000 {
1001    fil_tsd_t *ft;
1002 
1003    ft = (fil_tsd_t *)TSD->fil_tsd;
1004 
1005    if (ft->mrufile==ptr)
1006       ft->mrufile = ptr->older ;
1007 
1008    if (ptr->older)
1009       ptr->older->newer = ptr->newer ;
1010 
1011    if (ptr->newer)
1012       ptr->newer->older = ptr->older ;
1013 
1014    if (ptr->next)
1015       ptr->next->prev = ptr->prev ;
1016 
1017    if (ptr->prev)
1018       ptr->prev->next = ptr->next ;
1019    else
1020       ft->filehash[filehashvalue(ptr->filename0)] = ptr->next ;
1021 }
1022 
1023 /* enterfileptr initializes a fileboxptr. It must be allocated and the
1024  * following fields must already been set:
1025  * errmsg, error, fileptr, flag, filename0
1026  */
enterfileptr(const tsd_t * TSD,fileboxptr ptr)1027 static void enterfileptr( const tsd_t *TSD, fileboxptr ptr )
1028 {
1029    int hashval=0 ;
1030    fil_tsd_t *ft;
1031 
1032    ft = (fil_tsd_t *)TSD->fil_tsd;
1033 
1034    /*
1035     * First, get the magic number for this file. Note that when we're
1036     * doing hashing like this, we *may* get trouble on machines that
1037     * don't differ between upper and lower case letters in filenames.
1038     */
1039    hashval = filehashvalue(ptr->filename0) ;
1040    /*
1041     * Then, link it into the list of values having the same hashvalue
1042     */
1043    ptr->next = ft->filehash[hashval] ;
1044    if (ptr->next)
1045       ptr->next->prev = ptr ;
1046    ft->filehash[hashval] = ptr ;
1047    ptr->prev = NULL ;
1048 
1049    /*
1050     * Then, link it into the 'global' list of files, sorted by how
1051     * recently they have been used.
1052     */
1053    ptr->older = ft->mrufile ;
1054    if (ptr->older)
1055       ptr->older->newer = ptr ;
1056    ptr->newer = NULL ;
1057    ft->mrufile = ptr ;
1058 
1059    ptr->readline = 0 ;
1060    ptr->linesleft = 0 ;
1061    ptr->writeline = 0 ;
1062    ptr->thispos = (size_t) EOF ;
1063    ptr->readpos = (size_t) EOF ;
1064    ptr->writepos = (size_t) EOF ;
1065    ptr->oper = OPER_NONE;
1066 }
1067 
1068 /* swapout_file swaps out one closeable file. The state persists. Use
1069  * swapin_file to reuse it.
1070  * The given fileboxptr MUST NOT be swapped out. It indicates a file which
1071  * should be swapped in after this operation. dont_swap may be NULL.
1072  */
swapout_file(tsd_t * TSD,fileboxptr dont_swap)1073 static void swapout_file( tsd_t *TSD, fileboxptr dont_swap )
1074 {
1075    fil_tsd_t *ft;
1076    fileboxptr start, run, found;
1077 
1078    ft = (fil_tsd_t *)TSD->fil_tsd;
1079    /*
1080     * Too many open files simultaneously, we have to close one down
1081     * in order to free one file descriptor, but only if there actually
1082     * are some files that can be closed down.
1083     */
1084    found = NULL;
1085 
1086    if ( ( start = dont_swap ) == NULL ) /* any start point is better than */
1087       start = ft->mrufile ;             /* mru head. We need the opposite */
1088 
1089    /* first try finding an older file */
1090    for ( run = start ; run ; run = run->older )
1091    {
1092       if ( ( (run->flag & ( FLAG_SURVIVOR | FLAG_SWAPPED ) ) == 0 ) &&
1093            ( run->fileptr != NULL ) &&
1094            ( run != dont_swap ) )
1095          found = run ; /* continue looking for an older file */
1096    }
1097 
1098    /* if !found, try finding a more recent swapable file */
1099    if ( found == NULL )
1100    {
1101       for ( run = start ; run ; run = run->newer )
1102       {
1103          if ( ( (run->flag & ( FLAG_SURVIVOR | FLAG_SWAPPED ) ) == 0 ) &&
1104               ( run->fileptr != NULL ) &&
1105               ( run != dont_swap ) )
1106          {
1107             found = run ; /* least newer swapable file */
1108             break;
1109          }
1110       }
1111    }
1112    if ( found == NULL )
1113       exiterror( ERR_SYSTEM_FAILURE, 0 )  ;
1114 
1115    flush_output( TSD, found );
1116 }
1117 
1118 
1119 /* swapout_all swaps out all closeable files. The states persist. Use
1120  * swapin_file to reuse them.
1121  * This function is useful when exiting the external's interface.
1122  */
swapout_all(tsd_t * TSD)1123 static void swapout_all( tsd_t *TSD )
1124 {
1125    fil_tsd_t *ft;
1126    fileboxptr run;
1127 
1128    ft = (fil_tsd_t *)TSD->fil_tsd;
1129 
1130    for ( run = ft->mrufile ; run ; run = run->older )
1131    {
1132       flush_output( TSD, run );
1133    }
1134 }
1135 
1136 #ifdef VMS
1137 static const char *acc_mode[] = { "r",  "r+",  "a"  } ;
1138 #else
1139 static const char *acc_mode[] = { "rb", "r+b", "ab" } ;
1140 #endif
1141 
1142 #define ACCMODE_READ  0
1143 #define ACCMODE_RDWRT 1
1144 #define ACCMODE_WRITE 2
1145 #define ACCMODE_NONE  3
1146 
swapin_file(tsd_t * TSD,fileboxptr ptr)1147 static void swapin_file( tsd_t *TSD, fileboxptr ptr )
1148 {
1149    int faccess=0, itmp=0 ;
1150 
1151    /*
1152     * First, just try to reopen the file, we _might_ have a vacant
1153     * entry in the system file table, so, use that.
1154     */
1155    itmp = (ptr->flag & (FLAG_READ | FLAG_WRITE)) ;
1156    if (itmp==(FLAG_READ | FLAG_WRITE))
1157       faccess = ACCMODE_RDWRT ;
1158    else if (itmp==(FLAG_READ))
1159       faccess = ACCMODE_READ ;
1160    else if (itmp==(FLAG_WRITE))
1161       faccess = ACCMODE_WRITE ;
1162    else
1163      faccess = ACCMODE_NONE ;
1164 
1165    if (faccess == ACCMODE_NONE)
1166       exiterror( ERR_INTERPRETER_FAILURE, 1, __FILE__, __LINE__, "" )  ;
1167 
1168 tryagain:
1169 #ifdef SGIkludges
1170    errno = EMFILE ;
1171 #else
1172    errno = 0 ;
1173 #endif
1174    ptr->fileptr = fopen( ptr->filename0->value, acc_mode[faccess] ) ;
1175    if ((!ptr->fileptr) && (errno == EMFILE))
1176    {
1177       swapout_file( TSD, ptr ) ;
1178       goto tryagain ;
1179    }
1180 
1181    ptr->flag &= ~(FLAG_SWAPPED) ;
1182    if (ptr->fileptr==NULL)
1183       file_error( ptr, errno, NULL ) ;
1184    else
1185    {
1186       if ( ptr->thispos == (size_t) EOF )
1187          rx_fseek( ptr->fileptr, 0, SEEK_SET ) ;
1188       else
1189          rx_fseek( ptr->fileptr, ptr->thispos, SEEK_SET ) ;
1190       /*
1191        * If the swapped-in file was already after EOF; ie
1192        * ptr->flag contained FLAG_RDEOF, then force eof()
1193        * to return true.
1194        */
1195       if ( ptr->flag & FLAG_RDEOF )
1196       {
1197          rx_fseek( ptr->fileptr, 0, SEEK_END );
1198          fgetc( ptr->fileptr );
1199       }
1200    }
1201 }
1202 
1203 /*
1204  * Compares two filesnames, either with ignoring or respecting the letter case.
1205  */
filename_cmp(const streng * name1,const streng * name2)1206 int filename_cmp( const streng *name1, const streng *name2 )
1207 {
1208 #ifdef CASE_SENSITIVE_FILENAMES
1209    return Str_cmp( name1, name2 );
1210 #else
1211    return Str_ccmp( name1, name2 );
1212 #endif
1213 }
1214 
getfileptr(tsd_t * TSD,const streng * name)1215 static fileboxptr getfileptr( tsd_t *TSD, const streng *name )
1216 {
1217    int hashval ;
1218    fileboxptr ptr ;
1219    fil_tsd_t *ft;
1220 
1221    ft = (fil_tsd_t *)TSD->fil_tsd;
1222 
1223    hashval = filehashvalue( name ) ;
1224    /*
1225     * First, try to find the correct file in this slot of the
1226     * hash table. If one is found, ptr points to it, else, ptr is set
1227     * to NULL
1228     */
1229    for (ptr=ft->filehash[hashval];ptr;ptr=ptr->next)
1230    {
1231       if ( !filename_cmp( name, ptr->filename0 ) )
1232          break ;
1233    }
1234    /*
1235     * In order not to create any problems later, just return with NULL
1236     * (signifying that no file was found) if that is the case. Then we may
1237     * able to assume that ptr _is_ set later.
1238     */
1239    if (!ptr)
1240       return NULL ;
1241 
1242    /*
1243     * Now, put the file in front of the list of files stored by how
1244     * recently they were used. We assume that any access to a file is
1245     * equivalent to the file being used.
1246     */
1247    if ( ptr != ft->mrufile )
1248    {
1249       if ( ptr->newer )
1250       {
1251          ptr->newer->older = ptr->older ;
1252       }
1253       if ( ptr->older )
1254       {
1255          ptr->older->newer = ptr->newer ;
1256       }
1257 
1258       ptr->older = ft->mrufile ;
1259       ptr->newer = NULL ;
1260       ft->mrufile->newer = ptr ;
1261       ft->mrufile = ptr ;
1262    }
1263    if ( ptr != ft->filehash[ hashval ] )
1264    {
1265       /* Why the hell do we use ft->mrufile? Is it useful for anything?
1266        * The following code speeds up getfileptr much more.
1267        * If the current file pointer (ptr) is not the first file pointer
1268        * in the list for this hash value; reposition it so that it is
1269        * at the front of the list, and move the current file pointer
1270        * that is at the front of the list after ptr.
1271        * Fixes bug 594553
1272        */
1273       if ( ptr->next )
1274          ptr->next->prev = ptr->prev ;
1275       if ( ptr->prev )
1276          ptr->prev->next = ptr->next ;
1277 
1278       ptr->prev = NULL;
1279       ptr->next = ft->filehash[ hashval ];
1280       ft->filehash[ hashval ]->prev = ptr;
1281       ft->filehash[ hashval ] = ptr ;
1282    }
1283 
1284    /*
1285     * If this file has been swapped out, we have to reopen it, so we can
1286     * continue to access it.
1287     */
1288    if (ptr->flag & FLAG_SWAPPED)
1289       swapin_file( TSD, ptr ) ;
1290 
1291    return ptr ;
1292 }
1293 
1294 
flush_input(cfileboxptr dummy)1295 static void flush_input( cfileboxptr dummy )
1296 {
1297    dummy = dummy; /* keep compiler happy */
1298    return ;
1299 }
1300 
1301 
1302 /*
1303  * flush_output does close the file. The purpose of this function is to free
1304  * up space for opening another file while maintaining all state information about
1305  * this file if/when it is needed again.
1306  * Returns -1 in case of an error, 0 on success.
1307  */
flush_output(tsd_t * TSD,fileboxptr ptr)1308 static int flush_output( tsd_t *TSD, fileboxptr ptr )
1309 {
1310    int h;
1311 
1312    errno = 0;
1313 
1314    if ( ptr->fileptr == NULL || ptr->flag & FLAG_SWAPPED )
1315       return 0;
1316 
1317    if ( ptr->flag & FLAG_SURVIVOR )
1318    {
1319       if ( ptr->flag & FLAG_WRITE )
1320       {
1321          if ( fflush( ptr->fileptr ) != 0 )
1322          {
1323             file_error( ptr, errno, NULL );
1324             return -1;
1325          }
1326       }
1327       return 0;
1328    }
1329 
1330    if ( fflush( ptr->fileptr ) != 0 )
1331    {
1332       h = errno;
1333       fclose( ptr->fileptr );
1334       ptr->fileptr = NULL;
1335       ptr->flag |= FLAG_SWAPPED;
1336       file_error( ptr, h, NULL );
1337       return -1;
1338    }
1339    if ( fclose( ptr->fileptr ) == EOF )
1340    {
1341       h = errno;
1342       ptr->fileptr = NULL;
1343       ptr->flag |= FLAG_SWAPPED;
1344       file_error( ptr, h, NULL );
1345       return -1;
1346    }
1347 
1348    ptr->fileptr = NULL;
1349    ptr->flag |= FLAG_SWAPPED;
1350    return 0;
1351 }
1352 
1353 /*
1354  * Sets up the internal filetable for REXX, and initializes it with
1355  * the three standard files under Unix, stderr, stdout og and stdin.
1356  * Should only be called once, from the main routine. We should also
1357  * add code to register the routine for marking memory from this
1358  * routine.
1359  *
1360  * As a shortcut to access these three default files, there is a
1361  * variable 'stdio_ptr' which contains pointers to them. This allows
1362  * for quick access to the default streams.
1363  * The function returns 1 on success, 0 if memory is short.
1364  *
1365  * IMPORTANT!
1366  * The entry for stdin must be the same as the following #define for
1367  * DEFAULT_STDIN_INDEX below. Never changes it.
1368  * This assumption is used in readkbdline().
1369  */
1370 #define DEFAULT_STDIN_INDEX 0
1371 #define DEFAULT_STDOUT_INDEX 1
1372 #define DEFAULT_STDERR_INDEX 2
init_filetable(tsd_t * TSD)1373 int init_filetable( tsd_t *TSD )
1374 {
1375    int i=0 ;
1376    fil_tsd_t *ft;
1377 
1378    if (TSD->fil_tsd != NULL)
1379       return(1);
1380 
1381    if ( ( TSD->fil_tsd = MallocTSD( sizeof(fil_tsd_t) ) ) == NULL )
1382       return(0);
1383    ft = (fil_tsd_t *)TSD->fil_tsd;
1384    memset( ft, 0, sizeof(fil_tsd_t) );
1385 
1386    for ( i = 0; i < 6; i++ )
1387    {
1388       ft->stdio_ptr[i] = (fileboxptr)MallocTSD( sizeof( filebox )) ;
1389       ft->stdio_ptr[i]->errmsg = NULL ;
1390       ft->stdio_ptr[i]->error = 0 ;
1391    }
1392 
1393    ft->stdio_ptr[0]->fileptr = ft->stdio_ptr[3]->fileptr = stdin ;
1394    ft->stdio_ptr[1]->fileptr = ft->stdio_ptr[4]->fileptr = stdout ;
1395    ft->stdio_ptr[2]->fileptr = ft->stdio_ptr[5]->fileptr = stderr ;
1396 
1397    ft->stdio_ptr[0]->flag = ft->stdio_ptr[3]->flag = ( FLAG_SURVIVOR + FLAG_READ ) ;
1398    ft->stdio_ptr[1]->flag = ft->stdio_ptr[4]->flag = ( FLAG_SURVIVOR + FLAG_WRITE ) ;
1399    ft->stdio_ptr[2]->flag = ft->stdio_ptr[5]->flag = ( FLAG_SURVIVOR + FLAG_WRITE ) ;
1400 
1401    ft->stdio_ptr[0]->filename0 = Str_crestrTSD( "<stdin>" ) ;
1402    ft->stdio_ptr[1]->filename0 = Str_crestrTSD( "<stdout>" ) ;
1403    ft->stdio_ptr[2]->filename0 = Str_crestrTSD( "<stderr>" ) ;
1404    ft->stdio_ptr[3]->filename0 = Str_crestrTSD( "stdin" ) ;
1405    ft->stdio_ptr[4]->filename0 = Str_crestrTSD( "stdout" ) ;
1406    ft->stdio_ptr[5]->filename0 = Str_crestrTSD( "stderr" ) ;
1407 
1408    for (i=0; i<6; i++)
1409       enterfileptr( TSD, ft->stdio_ptr[i] ) ;
1410 
1411    return(1);
1412 }
1413 
purge_filetable(tsd_t * TSD)1414 void purge_filetable( tsd_t *TSD )
1415 {
1416    fileboxptr ptr1, ptr2, save_ptr1, save_ptr2 ;
1417    int i;
1418    fil_tsd_t *ft;
1419 
1420    ft = (fil_tsd_t *)TSD->fil_tsd;
1421    /* Naming this the "removal loop".   */
1422    for ( ptr1=ft->mrufile; ptr1; )
1423    {
1424       save_ptr1 = ptr1->older ;
1425       for ( ptr2=ptr1; ptr2; )
1426       {
1427          save_ptr2 = ptr2->next ;      /* this was moved from third parm of loop
1428                                           so that it did not address the free'd
1429                                           memory.  See if statement below.   */
1430          /*
1431           * If this is one of the default streams, don't let it be closed.
1432           * These file shall stay open, whatever happens.
1433           */
1434          /*
1435           * JH 19991105 if was modified to include the next 5 statements.  Originally,
1436           * the file was not closed, but all other references to it were deleted.  In
1437           * situations where one *.exe invokes Rexx mutiple times, subsequent calls to
1438           * the standard streams caused an error.  (getfileptr() failed, the file name
1439           * for stdio_ptr[?] comes up blank.)
1440           */
1441          if (!(ptr2->flag & FLAG_SURVIVOR)
1442          &&  ptr2->fileptr)
1443          {
1444             fclose( ptr2->fileptr ) ;
1445 
1446             removefileptr( TSD, ptr2 ) ;
1447 
1448             if (ptr2->errmsg)
1449                Free_stringTSD( ptr2->errmsg ) ;
1450 
1451             Free_stringTSD( ptr2->filename0 ) ;
1452             FreeTSD( ptr2 ) ;
1453          }
1454          ptr2 = save_ptr2 ;
1455       }
1456       ptr1 = save_ptr1 ;
1457    }
1458 
1459    ft->mrufile = NULL;
1460 
1461    /*
1462     * Now lets be absolutely paranoid, and remove all entries from the
1463     * filehash table...
1464     */
1465    memset( ft->filehash, 0, sizeof(ft->filehash) );
1466    /*
1467     * JH 19991105 The following loop was added to re-instate the std streams into the
1468     * hash table.  It seems easier to do this then to muck around with reseting the pointers
1469     * as the fileboxptr's are deleted.  Cannot modify the loop above to look at filenames
1470     * before removing from filehas table, it might be pointing to a fileboxptr that got removed
1471     * by the "removal loop".
1472     */
1473    for (i=0; i<6; i++)
1474    {
1475       enterfileptr( TSD, ft->stdio_ptr[i] ) ;
1476    }
1477 
1478 #if 0
1479 // can't free this as the next call to RexxStart() expects this memory to be here
1480    if ( TSD->fil_tsd )
1481    {
1482       FreeTSD( TSD->fil_tsd );
1483       TSD->fil_tsd = NULL;
1484    }
1485 #endif
1486 }
1487 
1488 /*
1489  * checkProperStreamName raises 40.27 if errno describes an error leading
1490  * to the assumption that the filename was malformed according to ANSI 9.2.1.
1491  */
checkProperStreamName(tsd_t * TSD,streng * kill,const char * fn,int eno)1492 static void checkProperStreamName( tsd_t *TSD, streng *kill, const char *fn,
1493                                    int eno )
1494 {
1495    static const int bad[] = {
1496 #if defined(ENAMETOOLONG)
1497       ENAMETOOLONG,
1498 #endif
1499       0
1500    };
1501 
1502    int i;
1503 
1504    for ( i = 0; bad[i] != 0; i++ )
1505    {
1506       if ( eno == bad[i] )
1507       {
1508          /*
1509           * ANSI 9.2.1 wants us to raise 40.27 if stream is malformed.
1510           * Feel free to provide more errno values describing this situation.
1511           */
1512          if ( kill )
1513            Free_stringTSD( kill ) ;
1514          exiterror( ERR_INCORRECT_CALL, 27, BIFname( TSD ), fn );
1515       }
1516    }
1517 }
1518 
1519 /*
1520  * Sets the proper error conditions for the file, including providing a
1521  * a hook into the CALL/SIGNAL ON system. Now, we also like to set some
1522  * other information, like the status of the file (taken from rc).
1523  *
1524  * First parameter is the file to operate on, the second and third
1525  * parameters are the error message to set (they can't both be defined),
1526  * and the last parameter is the level of 'severity'. If set, the file
1527  * is thrown into error state.
1528  */
handle_file_error(tsd_t * TSD,fileboxptr ptr,int rc,const char * errmsg,int level)1529 static void handle_file_error( tsd_t *TSD, fileboxptr ptr, int rc, const char *errmsg, int level)
1530 {
1531    trap *traps=NULL ;
1532 
1533    assert( !(rc && errmsg) ) ;
1534 
1535    if ((ptr->flag & FLAG_ERROR) && (ptr->flag & FLAG_FAKE))
1536    {
1537       /*
1538        * If we are faking for this file already, don't bother to do anything
1539        * more. In particular, we do not want to set a new error, since that
1540        * will in general only overwrite the old (and probably more relevant)
1541        * error message. However, faking are _only_ done when NOTREADY is
1542        * being trapped.
1543        */
1544       return ;
1545    }
1546    else
1547    {
1548       /*
1549        * If the file is not already in error, set the ERROR flag, and record
1550        * the error message. Also, clear the FAKE flag. This flag is only
1551        * defined when the ERROR flag is set, and we don't want any old
1552        * values laying around (it will be set later if needed).
1553        */
1554       if (level)
1555       {
1556          ptr->flag &= ~FLAG_FAKE ;
1557          ptr->flag |= FLAG_ERROR ;
1558       }
1559       else if (ptr->flag & FLAG_RDEOF)
1560       {
1561          /*
1562           * If the file was in RDEOF state; ie EOF was read on the file
1563           * set the AFTER_RDEOF flag to ensure STREAM(stream,'S') works
1564           * like other interpreters.
1565           */
1566          ptr->flag |= FLAG_AFTER_RDEOF;
1567       }
1568 
1569       checkProperStreamName( TSD, ptr->errmsg, Str_val( ptr->filename0 ), rc );
1570 
1571       /*
1572        * Set the error message, but only if one was given. This routine
1573        * can be called _without_ any errormessage, and if so, keep the
1574        * old one (if any)
1575        */
1576       if (rc || errmsg)
1577       {
1578          if (ptr->errmsg)
1579            Free_stringTSD( ptr->errmsg ) ;
1580 
1581          ptr->error = rc ;
1582          if ( errmsg )
1583             ptr->errmsg = Str_creTSD( errmsg ) ;
1584          else
1585 #ifdef WIN32
1586          {
1587             /*
1588              * For Win32 always get the last error, and store that;
1589              * it seems to be more meaningful than strerror( errno )
1590              * Address bug ???? FIXME
1591              */
1592             CHAR LastError[256];
1593             ULONG last_error = GetLastError();
1594             if ( last_error )
1595             {
1596                FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT), LastError, 256, NULL ) ;
1597                ptr->errmsg = Str_creTSD( LastError ) ;
1598             }
1599             else
1600                ptr->errmsg = NULL;
1601          }
1602 #else
1603          ptr->errmsg = NULL;
1604 #endif
1605       }
1606 
1607       /*
1608        * OK, the file has been put into ERROR state, now we must check
1609        * to see if we should raise the NOTREADY condition. If NOTREADY
1610        * is not currently enabled, don't bother to try to raise it.
1611        */
1612       traps = gettraps( TSD, TSD->currlevel ) ;
1613       if (traps[SIGNAL_NOTREADY].on_off)
1614       {
1615          /*
1616           * The NOTREADY condition is being trapped; set the FAKE flag
1617           * so that we don't create more errors for this file. But _only_
1618           * set the FAKE flag if NOTREADY is trapped by method CALL.
1619           * Then raise the condition ...
1620           */
1621          if (!traps[SIGNAL_NOTREADY].invoked)
1622             ptr->flag |= FLAG_FAKE ;
1623 
1624          condition_hook(TSD,SIGNAL_NOTREADY,rc+100,0,-1,Str_dupTSD(ptr->filename0),NULL);
1625       }
1626    }
1627 }
1628 
1629 
1630 
1631 /*
1632  * This routine is supposed to be called when the condition is triggered
1633  * by method CALL. From the time the condition is raised until the CALL is
1634  * is triggered, I/O to the file is faked. But before the condition handler
1635  * is called, we try to tidy things up a bit.
1636  *
1637  * At least, we have to clear the FAKE flag. Other 'nice' things to do
1638  * is to clear error indicator in the file pointer, and to reset the
1639  * file in general. The ERROR state is not cleared, _unless_ the file
1640  * is one of the default streams.
1641  */
1642 
fixup_file(tsd_t * TSD,const streng * filename)1643 void fixup_file( tsd_t *TSD, const streng *filename )
1644 {
1645    fileboxptr ptr=NULL ;
1646 
1647    if ( filename )
1648    {
1649       /*
1650        * filename will be NULL when condition_hook() called with a NULL description
1651        * argument. This happens when NOTREADY occurs when pulling from an external
1652        * queue with a timeout and the timeout expires.
1653        */
1654       ptr = getfileptr( TSD, filename ) ;
1655       if (ptr)
1656       {
1657          /*
1658           * If the file is open, try to clear it, first clear the error
1659           * indicator, and then try to fseek() to a 'safe' point. If the
1660           * seeking didn't work out, don't bother, it was worth a try.
1661           */
1662          if (ptr->fileptr)
1663          {
1664             clearerr( ptr->fileptr ) ;
1665             if ( ptr->flag & FLAG_PERSIST )
1666                rx_fseek( ptr->fileptr, 0, SEEK_SET ) ;
1667             ptr->thispos = 0 ;
1668             ptr->oper = OPER_NONE ;
1669          }
1670 
1671          if (ptr->flag & FLAG_SURVIVOR)
1672          {
1673             ptr->flag &= ~(FLAG_ERROR) ;
1674             /*
1675              * MHES Added following 4 flag resets - 30-11-2004
1676              */
1677             ptr->flag &= ~(FLAG_RDEOF) ;
1678             ptr->flag &= ~(FLAG_WREOF) ;
1679             ptr->flag &= ~(FLAG_AFTER_RDEOF) ;
1680          }
1681 
1682          ptr->flag &= ~(FLAG_FAKE) ;
1683       }
1684    }
1685 }
1686 
1687 
1688 
1689 
1690 /*
1691  * This is stupid ... if the file exists, but is in error mode, we
1692  * shall not close it, but leave it open, so that the rest of the
1693  * operations on this file in this statement don't trip. Same happens
1694  * if we are not able to close it properly. Oh well ...
1695  *
1696  * On second thoughts ... Faking only applies for input and output.
1697  * So closing doesn't have to be faked. Remove the file, whatever
1698  * happens.
1699  */
closefile(tsd_t * TSD,const streng * name)1700 void closefile( tsd_t *TSD, const streng *name )
1701 {
1702    fileboxptr ptr=NULL ;
1703 
1704    /* If it isn't open, don't try to close it ... */
1705    ptr = getfileptr( TSD, name ) ;
1706    if (ptr)
1707    {
1708       /*
1709        * If this is one of the default streams, don't let it be closed.
1710        * These file shall stay open, whatever happens.
1711        */
1712       if (ptr->flag & FLAG_SURVIVOR)
1713          return ;
1714 
1715       /*
1716        * If the fileptr seems to point to something ... close it. We
1717        * really don't want to leak file table slots. Actually, we should
1718        * check that the close was ok, and not let the fileptr go unless
1719        * we know that it was really closed (and released for new use).
1720        * Previously, it only closed when file was not in error. I don't
1721        * know what is the correct action, but this seems to be the most
1722        * sensible ...
1723        */
1724       if (ptr->fileptr)
1725          fclose( ptr->fileptr ) ;
1726 
1727       removefileptr( TSD, ptr ) ;
1728 
1729       if (ptr->errmsg)
1730          Free_stringTSD( ptr->errmsg ) ;
1731 
1732       Free_stringTSD( ptr->filename0 ) ;
1733       FreeTSD( ptr ) ;
1734    }
1735 }
1736 
1737 
1738 
1739 
1740 /*
1741  * This function is called when we need some kind of access to a file
1742  * but don't (yet) have it. It will only be called when we want to
1743  * open a file implicitly, e.g. it is open for reading, and it has then
1744  * been named in a output function.
1745  *
1746  * This is rather primitive ... but this function can only be called
1747  * when the file is open for read, and we want to open it for write;
1748  * or if the file i open for write, and we want to open it for read.
1749  * So I think this will suffice. It ignores the 'access' parameter
1750  * And just assumes that the file must be opened in both read and
1751  * write.
1752  *
1753  * To improve on this function, we ought to do a lot more checking,
1754  * e.g. that the 'access' wanted are required, and that the file is
1755  * already open in some kind of mode. If this don't hold, we probably
1756  * have an error condition.
1757  *
1758  * We should also check another thing, that the new file which is opened
1759  * is in fact the same file that we closed. Perferably, we should open
1760  * the new file, then check the device and inode of both the old and
1761  * new file to see whether they are the same (using stat()). If they
1762  * are not the same, the reopening should fail. As it is implemented
1763  * now, the Unix method for temporary files (open it, remove it,
1764  * use it, and then close it) will fail; and we loose access to the
1765  * original file too.
1766  */
reopen_file(tsd_t * TSD,fileboxptr ptr)1767 static void reopen_file( tsd_t *TSD, fileboxptr ptr )
1768 {
1769    if (!ptr)
1770       exiterror( ERR_INTERPRETER_FAILURE, 1, __FILE__, __LINE__, "" )  ;
1771 
1772    /*
1773     * We can not reopen the default streams, that makes no sence. If
1774     * tried, report an error.
1775     */
1776    if (ptr->flag & FLAG_SURVIVOR)
1777    {
1778       file_error( ptr, 0, "Invalid operation on default stream" ) ;
1779       return ;
1780    }
1781 
1782    /*
1783     * Close the old file, and try to reopen the new file. There is the
1784     * same problem here as in closefile(); if closing didn't work (for
1785     * some mysterious reason), the system's file table should become
1786     * full. Better checking might be required.
1787     */
1788    errno = 0 ;
1789    fclose( ptr->fileptr ) ;
1790 #ifdef VMS
1791    ptr->fileptr = fopen( ptr->filename0->value, "r+" ) ;
1792 #else
1793    ptr->fileptr = fopen( ptr->filename0->value, "r+b" ) ;
1794 #endif
1795    if (ptr->fileptr==NULL)
1796    {
1797       file_error( ptr, errno, NULL ) ;
1798       return ;
1799    }
1800    ptr->oper = OPER_NONE ;
1801 
1802    /*
1803     * We definitively want to set the close-on-exec flag. Suppose
1804     * an output file has not been flushed, and we execute a command.
1805     * This might perform an exec() and then a system(), which _will_
1806     * flush all files (close them). The result is that the file might
1807     * be flushed twice ... not good.
1808     *
1809     * This don't work on VMS ... but the file system on VMS is so
1810     * different anyway, so it will probably not create any problems.
1811     * Besides, we don't do exec() and system() on VMS.
1812     */
1813 #if !defined(VMS) && !defined(MAC) && !defined(OS2) && !defined(DOS) && !defined(__WATCOMC__) && !defined(_MSC_VER) && !(defined(WIN32) && defined(__IBMC__)) && !defined(__MINGW32__) && !defined(__BORLANDC__) && !defined(__EPOC32__) && !defined(__LCC__) && !defined(SKYOS)
1814    if (ptr && ptr->fileptr)
1815    {
1816       int flags, fno ;
1817       fno = fileno( ptr->fileptr ) ;
1818       assert( fno >= -1) ;
1819       flags = fcntl( fno, F_GETFD ) ;
1820       flags |= FD_CLOEXEC ;
1821       if (fcntl( fno, F_SETFD, flags)== -1)
1822          exiterror( ERR_SYSTEM_FAILURE, 1, strerror(errno) )  ;
1823    }
1824 #endif
1825 
1826    /*
1827     * If readposition is EOF (=illegal), then we "probably" needed to
1828     * open it in read mode. Set the current read position to the start
1829     * of the file.
1830     */
1831    if (ptr->readpos == (size_t) EOF)
1832    {
1833       ptr->readline = 1 ;
1834       ptr->linesleft = 0 ;
1835       ptr->readpos = 0 ;
1836       ptr->thispos = 0 ;
1837       if ( ptr->flag & FLAG_PERSIST )
1838          rx_fseek( ptr->fileptr, 0, SEEK_SET ) ;
1839    }
1840 
1841    /*
1842     * Then do the same thing for write access. We always set this to the
1843     * end-of-file -- the default -- even though there are other write
1844     * modes available. If the file is implicitly open in write mode,
1845     * then the current write position should be set to the default
1846     * value.
1847     */
1848    if (ptr->writepos == (size_t) EOF)
1849    {
1850       ptr->writeline = 0 ;
1851       if ( ptr->flag & FLAG_PERSIST )
1852          rx_fseek( ptr->fileptr, 0, SEEK_END ) ;
1853       ptr->writepos = rx_ftell( ptr->fileptr ) ;
1854       ptr->thispos = ptr->writepos ;
1855    }
1856 
1857    /*
1858     * Then, at last, do some simple bookkeeping, set both read and
1859     * write access, and clear any previous problem.
1860     */
1861    ptr->flag = FLAG_READ | FLAG_WRITE | FLAG_PERSIST ;
1862    ptr->error = 0 ;
1863    if (ptr->errmsg)
1864       Free_stringTSD(ptr->errmsg) ;
1865 
1866    ptr->errmsg = NULL ;
1867 }
1868 
1869 
1870 
1871 /*
1872  * This function explicitly opens a file. It will be called if the user
1873  * has called the built-in function STREAM() in order to open a file
1874  * in a particular mode. It will also be called if the file is not
1875  * previously open, and is used in a read or write operation.
1876  *
1877  * It takes two parameters, the name of the file to open, and the
1878  * mode in which it is to be opened. The mode has a value which is
1879  * matched by the ACCESS_ macros defined earlier.
1880  *
1881  * If the file is actually open in advance, then we close it before we
1882  * do any other operations. If the user is interested in the file in
1883  * one particular mode, he is probably not interested in any previous
1884  * modes.
1885  */
openfile(tsd_t * TSD,const streng * name,int faccess)1886 static fileboxptr openfile( tsd_t *TSD, const streng *name, int faccess )
1887 {
1888    fileboxptr ptr=NULL ;
1889    long lpos=0L ;
1890 
1891    /*
1892     * First check wether this file is already open, and use that open
1893     * file if possible. However, that may not be possible, since we
1894     * may want to use the file for another operation now. So, if the
1895     * file _is_ open, check to see if access is right.
1896     */
1897    ptr = getfileptr( TSD, name ) ;
1898    if (ptr)
1899    {
1900       if (ptr->flag & FLAG_SURVIVOR)
1901       {
1902          file_error( ptr, 0, "Can't open a default stream" ) ;
1903          return ptr ;
1904       }
1905       closefile( TSD, name ) ;
1906    }
1907 
1908    /*
1909     * Now, get a new file table entry, and fill in the various
1910     * field with appropriate (i.e. default) values.
1911     */
1912    ptr = (fileboxptr)MallocTSD( sizeof(filebox) ) ;
1913    ptr->filename0 = Str_dupstrTSD( name ) ;
1914    ptr->flag = 0 ;
1915    ptr->error = 0 ;
1916    ptr->errmsg = NULL ;
1917    ptr->readline = 0 ;
1918    ptr->linesleft = 0 ;
1919    ptr->writeline = 0 ;
1920    ptr->thispos = (size_t) EOF ;
1921    ptr->readpos = (size_t) EOF ;
1922    ptr->writepos = (size_t) EOF ;
1923    ptr->oper = OPER_NONE;
1924 
1925    /*
1926     * suppose we tried to open, but didn't manage, well, stuff it into
1927     * the file table, we might want to retrieve information about it
1928     * later on. _And_ we need to know about the problem if the file
1929     * I/O is to be faked later on.
1930     */
1931    enterfileptr( TSD, ptr ) ;
1932    name = ptr->filename0 ;
1933    goto try_to_open ;
1934 
1935 kill_one_file:
1936    swapout_file( TSD, ptr ) ;
1937 
1938 try_to_open:
1939    /*
1940     * In most of these, we have to check that the file opened is really
1941     * a persistent file. We should not take that for granted.
1942     */
1943    errno = 0 ;
1944    if (faccess==ACCESS_READ)
1945    {
1946 #ifdef VMS
1947       if ((ptr->fileptr = fopen( name->value, "r" )) != NULL)
1948 #else
1949       if ((ptr->fileptr = fopen( name->value, "rb" )) != NULL)
1950 #endif
1951       {
1952          ptr->flag = FLAG_READ | FLAG_PERSIST ;
1953          ptr->readline = 1 ;
1954          ptr->linesleft = 0 ;
1955          ptr->thispos = ptr->readpos = 0 ;
1956       }
1957       else if (errno==EMFILE)
1958          goto kill_one_file ;
1959       else
1960       {
1961          file_error( ptr, errno, NULL ) ;
1962       }
1963    }
1964    else if (faccess==ACCESS_WRITE)
1965    {
1966       /*
1967        * This is really a problem. If opened in mode "w", it will
1968        * truncate the file if it did exist. If opened int mode "r+",
1969        * it will fail if the file did not exist. So we try to
1970        * combine the two.
1971        */
1972       ptr->flag = FLAG_READ ;
1973 #ifdef VMS
1974       ptr->fileptr = fopen( name->value, "r+" ) ;
1975 #else
1976       ptr->fileptr = fopen( name->value, "r+b" ) ;
1977 #endif
1978       errno = 0 ;
1979       if (!ptr->fileptr)
1980 #ifdef VMS
1981          ptr->fileptr = fopen( name->value, "w+" ) ;
1982 #else
1983          ptr->fileptr = fopen( name->value, "w+b" ) ;
1984 #endif
1985 
1986       errno = 0 ;
1987       if (!ptr->fileptr)
1988       {
1989 #ifdef SGIkludges
1990          errno = EMFILE ;
1991 #else
1992          errno = 0 ;
1993 #endif
1994 #ifdef VMS
1995          ptr->fileptr = fopen( name->value, "w" ) ;
1996 #else
1997          ptr->fileptr = fopen( name->value, "wb" ) ;
1998 #endif
1999          ptr->flag &= 0 ;
2000       }
2001 
2002       /*
2003        * Then set the current read and write positions to the start and
2004        * the end of the file, respectively. When we first open the file
2005        * we can quickly determine readpos, writepos and readline, but
2006        * writeline is expensive to determine, because we have to read
2007        * the whole file to determine this. So we don't do this when we
2008        * open the file, because we may never use it. Instead we set the
2009        * value of writeline to 0 to indicate that the actual position
2010        * is unknown.  When we do want to use writeline, we have to
2011        * determine it then.
2012        */
2013       if (ptr->fileptr)
2014       {
2015          ptr->flag |= FLAG_WRITE | FLAG_PERSIST ;
2016          rx_fseek( ptr->fileptr, 0, SEEK_END ) ;
2017          lpos = rx_ftell( ptr->fileptr ) ;
2018          ptr->thispos = ptr->writepos = lpos ;
2019          ptr->writeline = 0 ;
2020          ptr->readpos = 0 ;
2021          ptr->readline = 1 ;
2022          ptr->linesleft = 0 ;
2023       }
2024       else if (errno==EMFILE)
2025          goto kill_one_file ;
2026       else
2027          file_error( ptr, errno, NULL ) ;
2028    }
2029    else if (faccess==ACCESS_APPEND)
2030    {
2031       /*
2032        * In append mode, the file is opened as a transient file, all
2033        * writing must be done at the end of the file. It is not
2034        * possible to perform reading on the file. Useful for files
2035        * to which you have write, but not read access (e.g. logfiles).
2036        */
2037 #ifdef VMS
2038       if ((ptr->fileptr = fopen( name->value, "a" )) != NULL)
2039 #else
2040       if ((ptr->fileptr = fopen( name->value, "ab" )) != NULL)
2041 #endif
2042       {
2043          ptr->flag = FLAG_WRITE | FLAG_WREOF ;
2044       }
2045       else if (errno==EMFILE)
2046          goto kill_one_file ;
2047       else
2048          file_error( ptr, errno, NULL ) ;
2049    }
2050    else if (faccess==ACCESS_STREAM_APPEND)
2051    {
2052       /*
2053        * In "stream" append mode, the file is opened as a persistent file, all
2054        * writing must be done at the end of the file. It is not
2055        * possible to perform reading on the file. Useful for files
2056        * to which you have write, but not read access (e.g. logfiles).
2057        */
2058 #ifdef VMS
2059       if ((ptr->fileptr = fopen( name->value, "a" )) != NULL)
2060 #else
2061       if ((ptr->fileptr = fopen( name->value, "ab" )) != NULL)
2062 #endif
2063       {
2064          ptr->flag = FLAG_WRITE | FLAG_WREOF | FLAG_PERSIST;
2065          if ( ptr->flag & FLAG_PERSIST )
2066             rx_fseek( ptr->fileptr, 0, SEEK_END ) ;
2067          lpos = rx_ftell( ptr->fileptr ) ;
2068          ptr->thispos = ptr->writepos = lpos ;
2069          ptr->writeline = 0 ; /* unknown position */
2070          ptr->readpos = 0 ;
2071          ptr->readline = 1 ;
2072          ptr->linesleft = 0 ;
2073       }
2074       else if (errno==EMFILE)
2075          goto kill_one_file ;
2076       else
2077          file_error( ptr, errno, NULL ) ;
2078    }
2079    else if (faccess==ACCESS_STREAM_REPLACE)
2080    {
2081       /*
2082        * The file is created if it didn't exist, and if it did exist
2083        * it is truncated and the file pointers set to the start of file.
2084        */
2085 #ifdef VMS
2086       if ((ptr->fileptr = fopen( name->value, "w+" )) != NULL)
2087 #else
2088       if ((ptr->fileptr = fopen( name->value, "w+b" )) != NULL)
2089 #endif
2090       {
2091          ptr->flag = FLAG_WRITE | FLAG_READ | FLAG_WREOF | FLAG_RDEOF |
2092                      FLAG_PERSIST ;
2093          ptr->writeline = ptr->readline = 1 ;
2094          ptr->linesleft = 0 ;
2095          ptr->readpos = ptr->writepos = ptr->thispos = 0 ;
2096       }
2097       else if (errno==EMFILE)
2098          goto kill_one_file ;
2099       else
2100          file_error( ptr, errno, NULL ) ;
2101    }
2102    else if (faccess==ACCESS_UPDATE)
2103    {
2104       /*
2105        * Like read access, but it will not create the file if it didn't
2106        * already exist. Instead, an error is reported.
2107        */
2108 #ifdef VMS
2109       if ((ptr->fileptr = fopen( name->value, "r+" )) != NULL)
2110 #else
2111       if ((ptr->fileptr = fopen( name->value, "r+b" )) != NULL)
2112 #endif
2113       {
2114          ptr->flag = FLAG_WRITE | FLAG_READ | FLAG_PERSIST ;
2115          ptr->readline = 0 ;
2116          ptr->linesleft = 0 ;
2117          ptr->writeline = 0 ; /* unknown */
2118       }
2119       else if (errno==EMFILE)
2120          goto kill_one_file ;
2121       else
2122          file_error( ptr, errno, NULL ) ;
2123    }
2124    else if (faccess==ACCESS_CREATE)
2125    {
2126       /*
2127        * The file is created if it didn't exist, and if it did exist
2128        * it is truncated.
2129        */
2130 #ifdef VMS
2131       if ((ptr->fileptr = fopen( name->value, "w+" )) != NULL)
2132 #else
2133       if ((ptr->fileptr = fopen( name->value, "w+b" )) != NULL)
2134 #endif
2135       {
2136          ptr->flag = FLAG_WRITE | FLAG_READ | FLAG_WREOF | FLAG_RDEOF |
2137                      FLAG_PERSIST ;
2138          ptr->writeline = ptr->readline = 1 ;
2139          ptr->linesleft = 0 ;
2140          ptr->readpos = ptr->writepos = ptr->thispos = 0 ;
2141       }
2142       else if (errno==EMFILE)
2143          goto kill_one_file ;
2144       else
2145          file_error( ptr, errno, NULL ) ;
2146    }
2147    else
2148       exiterror( ERR_INTERPRETER_FAILURE, 1, __FILE__, __LINE__, "" )  ;
2149 
2150 #if !defined(VMS) && !defined(MAC) && !defined(OS2) && !defined(DOS) && !defined(__WATCOMC__) && !defined(_MSC_VER) && !defined(__MINGW32__) && !defined(__BORLANDC__) && !defined(__EPOC32__) && !defined(__LCC__) && !defined(SKYOS)
2151    /*
2152     * Then we check to see if this is a transient or persistent file.
2153     * We can remove a 'persistent' setting, but add one, since we
2154     * sometimes want to access a persistent file as transient (append).
2155     */
2156    if (ptr->fileptr)
2157    {
2158       int fno, rc ;
2159       struct rx_stat_buf statbuf ;
2160       errno = 0 ;
2161       fno = fileno(ptr->fileptr) ;
2162       rc = rx_fstat( fno, &statbuf ) ;
2163       if (rc==0 && !S_ISREG(statbuf.st_mode))
2164          ptr->flag &= ~(FLAG_PERSIST) ;
2165       else if (rc!=0)
2166          file_error( ptr, errno, NULL ) ;
2167    }
2168 
2169    /*
2170     * As with reopen_file(), we want to set the close-on-exec flag,
2171     * see reopen_file for more information.
2172     */
2173    if (ptr->fileptr)
2174    {
2175       int flags, fno ;
2176       fno = fileno(ptr->fileptr) ;
2177       assert( fno >= -1) ;
2178       flags = fcntl( fno, F_GETFD ) ;
2179       flags |= FD_CLOEXEC ;
2180       if (fcntl( fno, F_SETFD, flags)== -1)
2181          exiterror( ERR_SYSTEM_FAILURE, 1, strerror(errno) )  ;
2182    }
2183 #endif
2184 
2185    return (ptr) ;
2186 }
2187 
2188 
2189 
2190 
2191 /* ------------------------------------------------------------------- */
2192 /*                     High level utility routines                     */
2193 
2194 
2195 
2196 
2197 /*
2198  * This function is really just an interface to the function getfileptr().
2199  * It takes a (possible) filename, and retrieves the corresponding
2200  * Rexx file table entry. If the file does not exist, it is opened in
2201  * the mode indicated by 'open_mode'. If it does exist, this routine
2202  * verifies that it it has been opened in a mode corresponding to
2203  * 'access' (OPER_READ or OPER_WRITE).
2204  *
2205  * If the file does not exist, it is opened in either normal read
2206  * or normal write. This correcspinds to an "implicit" file open
2207  * in Rexx.
2208  */
get_file_ptr(tsd_t * TSD,const streng * name,int faccess,int open_mode)2209 static fileboxptr get_file_ptr( tsd_t *TSD, const streng *name, int faccess, int open_mode )
2210 {
2211    fileboxptr ptr=NULL ;
2212 
2213    ptr = getfileptr( TSD, name ) ;
2214    if (ptr==NULL)
2215       return openfile( TSD, name, open_mode ) ;
2216 
2217    if (ptr->flag & FLAG_ERROR)
2218       return ptr ;
2219 
2220    if (faccess==OPER_READ && (!(ptr->flag & FLAG_READ)))
2221       reopen_file( TSD, ptr ) ;
2222    else if (faccess==OPER_WRITE && (!(ptr->flag & FLAG_WRITE)))
2223       reopen_file( TSD, ptr ) ;
2224 
2225    return ptr ;
2226 }
2227 
2228 
2229 
2230 /*
2231  * This routine reads one complete line from the file indicated by
2232  * the file table entry 'ptr'. Or rather, it read from the current
2233  * read position, until and including the first EOL mark, and returns
2234  * that. If the EOL mark is implemented as certain characters, they are
2235  * not returned. It closely corresponds to the LINEIN() built-in
2236  * function.
2237  *
2238  * What is the upper limit for the size that we might read in? It's
2239  * best not to have any limit, so the method is the following: A
2240  * temporary area is used for storing the data read from the file.
2241  * We never know the size needed until the EOL mark is found. So
2242  * just read the data into the temporary area. If the EOL is found,
2243  * then we know the size, and we can transfer the data into a 'streng'
2244  * of suitable size. If the temporary area is too small, allocate
2245  * an area twice the size, and copy the data over. Afterwards, keep the
2246  * new area as the temporary area.
2247  *
2248  * This way, we normally use little memory, but we are still able to
2249  * read as large lines as the memory allows, if it is needed.
2250  *
2251  * No error condition is raised if noerrors is set. Instead, NULL is returned.
2252  */
readoneline(tsd_t * TSD,fileboxptr ptr)2253 static streng *readoneline( tsd_t *TSD, fileboxptr ptr )
2254 {
2255    int i=0, j=0, eolf=0, eolchars=1 ;
2256 /*#if !defined(UNIX)*/
2257    int k=0;
2258 /*#endif*/
2259    streng *ret=NULL ;
2260    fil_tsd_t *ft;
2261 
2262    ft = (fil_tsd_t *)TSD->fil_tsd;
2263 
2264    /*
2265     * First verify that we actually have a file that is not in an
2266     * ERROR state. If so, don't perform any operations.
2267     */
2268    if ( ptr->flag & FLAG_ERROR )
2269    {
2270       if (!(ptr->flag & FLAG_FAKE))
2271          file_error( ptr, 0, NULL ) ;
2272 
2273       return nullstringptr() ;
2274    }
2275 
2276    /*
2277     * If we have an EOF from the last linein() then cause the NOTREADY
2278     * condition now.
2279     */
2280    if ( ptr->flag & FLAG_RDEOF )
2281    {
2282       file_warning( ptr, 0, "EOF on line input" ) ;
2283    }
2284 
2285    /*
2286     * If the string is not yet allocated, allocate it, and use an
2287     * initial size of 512 bytes. This can be increased during runtime.
2288     * Using higher initial sizes will waste allocation time on modern systems
2289     * with page sizes around 4KB. 512 fits most cases at its best.
2290 
2291     * MH 4 Sep 02 We should read into the ft->rol_string 512 bytes at a
2292     * time and search through the buffer for the EOL. getc() for every
2293     * character is innefficient, however (at least on Linux) getc() is
2294     * implemented as a read( 4096 ), and then returns each character
2295     * from some internal buffer.
2296     */
2297    if (!ft->rol_string)
2298    {
2299       ft->rol_string = (char *)MallocTSD( (ft->rol_size=512) ) ;
2300 #ifdef TRACEMEM
2301       ft->rdarea = ft->rol_string ;
2302 #endif
2303    }
2304 
2305  /*
2306    if (ptr->fileptr==stdin)
2307       fcntl( stdin, F_SETFL, O_NONBLOCK | fcntl(stdin,F_GETFL)) ;
2308   */
2309    errno = 0 ;
2310    SWITCH_OPER_READ(ptr);
2311    /*
2312     * Switch to the current readpos setting in case the current position
2313     * is based on the write position.
2314     */
2315    ptr->thispos = ptr->readpos;
2316    if ( ptr->flag & FLAG_PERSIST )
2317       rx_fseek(ptr->fileptr,ptr->thispos,SEEK_SET);
2318    for (i=0; ; i++)
2319    {
2320       j = getc(ptr->fileptr);
2321       if (j == REGINA_EOL)
2322       {
2323          eolf = REGINA_EOL;
2324          break;
2325       }
2326 /*#if !defined(UNIX) && !defined(MAC)*/
2327       /*
2328        * MH 25042003 - all platforms can read lines with
2329        * CRLF, LF, or CR - consistent with ObjectRexx
2330        */
2331       if (j == REGINA_CR)
2332       {
2333          k = getc(ptr->fileptr);
2334          if (k == REGINA_EOL)
2335          {
2336             eolf = REGINA_EOL;
2337             eolchars = 2;
2338             break;
2339          }
2340          else
2341          {
2342             ungetc(k,ptr->fileptr);
2343             eolf = REGINA_EOL;
2344             break;
2345          }
2346       }
2347 /*#endif*/
2348       /*
2349        * If we hit end-of-file, handle it carefully, and terminate the
2350        * reading. Note that this means that there we have read an
2351        * incomplete last line, so return what we've got, and report
2352        * an NOTREADY condition. (Partly, I disagree, but this is how
2353        * TRL defines it ... Case I: Programmer using Emacs forgets to
2354        * add a EOL after the last line; Rexx triggers NOTREADY when
2355        * reading that last, incomplete line.
2356 
2357        * MH 4-Sep-2002 - change in semantics happened a while ago.
2358        * We treat incomplete line as a "normal" line, and NOTREADY
2359        * is NOT raised when reading an incomplete line.
2360        */
2361       if (j==EOF)
2362       {
2363          ptr->flag |= FLAG_RDEOF ;
2364 /*            file_warning( ptr, 0, "EOF on line input" ) ; */
2365          break ;
2366       }
2367 
2368       /*
2369        * We are trying to avoid any limits other than memory-imposed
2370        * limits. So if the buffer size that we currently have are too
2371        * small, double it, and hide the operation from the rest of the
2372        * interpreter.
2373        */
2374       if (i>=ft->rol_size)
2375       {
2376          char *tmpstring ;
2377 
2378          assert( i == ft->rol_size ) ;
2379          tmpstring = (char *)MallocTSD( 2*ft->rol_size+10 ) ;
2380          memcpy( tmpstring, ft->rol_string, ft->rol_size ) ;
2381          FreeTSD( ft->rol_string ) ;
2382          ft->rol_string = tmpstring ;
2383          ft->rol_size *= 2 ;
2384 #ifdef TRACEMEM
2385          ft->rdarea = ft->rol_string ;
2386 #endif
2387       }
2388 
2389       /*
2390        * Just an ordinary character ... append it to the buffer
2391        */
2392       ft->rol_string[i] = (char) j ;
2393    }
2394    /*
2395     * Attempt to set the read pointer and the current file
2396     * pointer based on the length of the line we just read.
2397     */
2398 #if 1 /* really MH */
2399    if ( ptr->thispos == ptr->readpos )
2400    {
2401       if ( ptr->thispos == (size_t) EOF )
2402       {
2403          errno = 0 ;
2404          ptr->thispos = ptr->readpos = rx_ftell( ptr->fileptr ) ;
2405       }
2406       else
2407       {
2408          ptr->thispos += (i - (j==EOF)) + eolchars ;
2409          ptr->readpos = ptr->thispos ;
2410       }
2411    }
2412    else
2413    {
2414       errno = 0 ;
2415       ptr->thispos = ptr->readpos = rx_ftell( ptr->fileptr ) ;
2416    }
2417 #else
2418    if (ptr->thispos != (size_t) EOF)
2419       ptr->thispos += (i - (j==EOF)) + eolchars ;
2420 
2421    if (ptr->readpos != (size_t) EOF)
2422       ptr->readpos = ptr->thispos ;
2423 #endif
2424    /*
2425     * If we did read a complete line, we have to increment the line
2426     * count for the current read pointer of this file. This part of
2427     * the code is a bit Unix-ish. It will have to be reworked for
2428     * other types of operating systems.
2429     */
2430    if ((eolf==REGINA_EOL) && (ptr->readline > 0))
2431    {
2432 #if 1 /* really MH */
2433       ptr->readline += 1 ;  /* only if we actually saw the "\n" !!! */
2434 #else
2435       ptr->readline += eolchars ;  /* only if we actually saw the "\n" !!! */
2436 #endif
2437       if (ptr->linesleft)
2438          ptr->linesleft-- ;
2439    }
2440    /*
2441     * A bit of a hack here.  Because countlines() determines if any lines
2442     * are left in the stream by using the feof() function, we have to
2443     * attempt to read the EOF after each line, and set the file's state
2444     * to EOF.  If the character read is not EOF, then put it back on
2445     * the stream to be read later.
2446     * Only do this for persistent streams!!
2447     */
2448    if ( ptr->flag & FLAG_PERSIST
2449    &&   !feof( ptr->fileptr ) )
2450    {
2451       int ch0;
2452       ch0 = getc(ptr->fileptr);
2453       if (feof(ptr->fileptr))
2454       {
2455          ptr->flag |= FLAG_RDEOF ;
2456 /*         file_warning( ptr, 0, "EOF on line input" ) ; */
2457       }
2458       else
2459       {
2460          ungetc(ch0,ptr->fileptr);
2461       }
2462    }
2463 
2464    /*
2465     * Wrap up the data that was read, and return it as a 'streng'.
2466     *
2467     */
2468 /*   if (i>1000) i = 1000 ; */
2469    ret = Str_makeTSD( i ) ;
2470    memcpy( ret->value, ft->rol_string, ret->len=i ) ;
2471    return ret ;
2472 }
2473 
2474 
positionfile_SEEK_SET(tsd_t * TSD,const char * bif,int argno,fileboxptr ptr,int oper,rx_64 lineno)2475 static rx_64 positionfile_SEEK_SET( tsd_t *TSD, const char *bif, int argno, fileboxptr ptr, int oper, rx_64 lineno )
2476 {
2477    int ch=0x00;
2478    rx_64 from_line=0, tmp=0 ;
2479    rx_64 from_char=0L ;
2480    rx_64 ret;
2481 
2482    /*
2483     * We know the line number of at most three positions in the file:
2484     * the start of the file, the write position and the read position.
2485     * If the file is open only for reading or writing, we know at most
2486     * two positions. And in addition, the read and/or the write
2487     * position may be be invalid (i.e. previous operation was
2488     * character oriented). But at least, we know the line number of
2489     * one position, the start of the file, which is the first line.
2490     *
2491     * The best method seems to be: First start with the start of file
2492     * and then see if using the read or the write position instead is
2493     * a better deal. There is one drawback ... we assume that all lines
2494     * are equally long. That assumption is probably not too bad for text
2495     * files, but it may create unnecessary overhead for 'peculiar' files
2496     */
2497    from_line = 1 ;
2498    from_char = 0 ;
2499 
2500    if ( oper & OPER_READ
2501    &&   ptr->flag & FLAG_PERSIST )
2502    {
2503       if ( rx_fseek( ptr->fileptr, ptr->readpos, SEEK_SET ) )
2504       {
2505          file_error( ptr, errno, NULL ) ;
2506          return 0;
2507       }
2508       ptr->thispos = ptr->readpos ;
2509    }
2510    if ( oper & OPER_WRITE
2511    &&   ptr->flag & FLAG_PERSIST )
2512    {
2513       if ( rx_fseek( ptr->fileptr, ptr->writepos, SEEK_SET ) )
2514       {
2515          file_error( ptr, errno, NULL ) ;
2516          return 0;
2517       }
2518       ptr->thispos = ptr->writepos ;
2519    }
2520 
2521    /*
2522     * First, let's check to see if we gain anything from using the
2523     * read position instead. If the distance from the current read
2524     * position to the wanted line (counted in number of lines) is smaller
2525     * than the number of lines from the first line to the wanted line,
2526     * use the current read position in stead. But only if the current
2527     * read position is defined.
2528     */
2529    if ((ptr->flag & FLAG_READ) && (ptr->readline > 0))
2530    {
2531       assert( ptr->readpos != (size_t) EOF) ;
2532       tmp = ptr->readline - lineno ;
2533       if (tmp<0)
2534          tmp = (-tmp) ;
2535 
2536       if (tmp < (lineno - from_line))
2537       {
2538          from_line = ptr->readline ;
2539          from_char = ptr->readpos ;
2540       }
2541    }
2542 
2543    /*
2544     * Then, we check to see whether we can gain even more if we use
2545     * the current write position of the file instead.
2546     */
2547    if ((ptr->flag & FLAG_WRITE) && (ptr->writeline > 0))
2548    {
2549       assert( ptr->writepos != (size_t) EOF ) ;
2550       tmp = ptr->writeline - lineno ;
2551       if (tmp<0)
2552          tmp = (-tmp) ;
2553 
2554       if (tmp < (lineno - from_line))
2555       {
2556          from_line = ptr->writeline ;
2557          from_char = ptr->writepos ;
2558       }
2559    }
2560 
2561    /*
2562     * By now, the variables from_line, and from_char should contain
2563     * the optimal starting point from where a seek for the 'lineno'
2564     * line in the file can start, so first, move there. An in addition,
2565     * it should be the known position which is closest to the wanted
2566     * line.
2567     */
2568    if (from_char != (long) ptr->thispos)
2569    {
2570       errno = 0 ;
2571       if ( ptr->flag & FLAG_PERSIST
2572       &&  rx_fseek( ptr->fileptr, from_char, SEEK_SET ))
2573       {
2574          file_error( ptr, errno, NULL ) ;
2575          return 0;
2576       }
2577       ptr->oper = OPER_NONE;
2578       ptr->thispos = from_char ;
2579    }
2580    assert( from_char == rx_ftell(ptr->fileptr) ) ;
2581 
2582    /*
2583     * Now we are positioned at the right spot, so seek forwards or
2584     * backwards until we reach the correct line. Actually, the method
2585     * we are going to use may seem a bit strange at first. First we
2586     * seek forward until we pass the line, and then we seek backwards
2587     * until we reach the line and at the end we back up to the first
2588     * preceding end-of-line marker. This may seem awkward, but it is
2589     * fairly simple. And in addition, it will always position us at
2590     * the _start_ of the wanted line.
2591     */
2592    once_more:
2593    while ((lineno>from_line))   /* seek forward */
2594    {
2595       SWITCH_OPER_READ(ptr);
2596       for (;((ch=getc(ptr->fileptr))!=EOF)&&(ch!=REGINA_EOL);from_char++) ;
2597       if (ch==REGINA_EOL)
2598          from_line++ ;
2599       else
2600          break ;
2601    }
2602 
2603    /*
2604     * Then we seek backwards until we reach the line. The backwards
2605     * movement is _really_ awkward, so perhaps we should read in 512
2606     * bytes, and analyse the data in it instead? Indeed, another
2607     * algoritm should be chosen. Maybe later ...
2608     */
2609    while (lineno<=from_line && from_char>0)
2610    {
2611       errno = 0 ;
2612       if ( ptr->flag & FLAG_PERSIST
2613       &&  rx_fseek(ptr->fileptr, -1, SEEK_CUR))
2614       {
2615          /*
2616           * Should this happen? Only if someone overwrites EOF chars in
2617           * the file, but that _may_ happend ...   Report error for
2618           * any errors from the fseek and ftell. If we hit the start of
2619           * the file, reset from_line check whether we are _below_ lineno
2620           * If so, jump back and seek from the start (then we *must*
2621           * start at line 1, since the data we've got are illegal).
2622           *
2623           * It will also happen if we are seeking backwards for the
2624           * first line.
2625           */
2626          errno = 0 ;
2627          if (rx_fseek(ptr->fileptr,0,SEEK_SET))
2628          {
2629             file_error( ptr, errno, NULL ) ;
2630             return 0;
2631          }
2632          ptr->oper = OPER_NONE;
2633 
2634          from_line = 1 ;
2635          ptr->thispos = 0 ;
2636          if (from_line<lineno)
2637          {
2638             ptr->readline = (-1);
2639             ptr->writeline = 0; /* unknown */
2640             goto once_more ;
2641          }
2642 
2643          break ;  /* we were looking for the first line ... how lucky :-) */
2644       }
2645 
2646       /*
2647        * After seeking one character backwards, we must read the character
2648        * that we just skipped over. Do that, and test whether it is
2649        * a end-of-line character.
2650        */
2651       SWITCH_OPER_READ(ptr);
2652       ch = getc(ptr->fileptr) ;
2653       if (ch==REGINA_EOL)
2654       {
2655          if (lineno==from_line)
2656             break ;
2657 
2658          from_line-- ;
2659       }
2660 
2661       /*
2662        * Then we move backwards once more, in order to compensate for
2663        * reading the character. Sigh, we are really doing a lot of
2664        * forward and backward reading, arn't we?
2665        */
2666       errno = 0 ;
2667       if ( ptr->flag & FLAG_PERSIST
2668       &&  rx_fseek(ptr->fileptr, -1, SEEK_CUR))
2669       {
2670          file_error( ptr, errno, NULL ) ;
2671          return 0;
2672       }
2673       ptr->oper = OPER_NONE;
2674    }
2675 
2676    /*
2677     * Now we are almost finished. We just have to set the correct
2678     * information in the Rexx file table entry.
2679     */
2680    ptr->thispos = rx_ftell( ptr->fileptr ) ;
2681    if (oper & OPER_READ)
2682    {
2683       ptr->readline = from_line ; /* was lineno */
2684       ptr->readpos = ptr->thispos ;
2685       ptr->flag &= ~(FLAG_RDEOF) ;
2686       ptr->flag &= ~(FLAG_AFTER_RDEOF) ;
2687    }
2688    if (oper & OPER_WRITE)
2689    {
2690       ptr->writeline = from_line ; /* was lineno */
2691       ptr->writepos = ptr->thispos ;
2692       ptr->flag &= ~(FLAG_WREOF) ;
2693    }
2694 
2695    if (oper & OPER_READ)
2696       ret = ptr->readline ;
2697    else
2698       ret = ptr->writeline ;
2699    return ret;
2700 }
2701 
positionfile_SEEK_CUR(tsd_t * TSD,const char * bif,int argno,fileboxptr ptr,int oper,rx_64 lineno,rx_64 from_line,rx_64 from_char)2702 static rx_64 positionfile_SEEK_CUR( tsd_t *TSD, const char *bif, int argno, fileboxptr ptr, int oper, rx_64 lineno, rx_64 from_line, rx_64 from_char )
2703 {
2704    rx_64 tmp,ret;
2705 
2706    /*
2707     * Do simple checks first.
2708     * If we are seeking back before the first line, then set:
2709     * READ:
2710     * ptr->readline = 1
2711     * ptr->readpos = 1
2712     * return 1
2713     * WRITE:
2714     * ptr->writeline = 1
2715     * ptr->writepos = 0
2716     * return 0
2717     */
2718    tmp = from_line + lineno;
2719 
2720    if ( tmp < 1 )
2721    {
2722       /*
2723        * We have positioned to before the first line
2724        *
2725        */
2726       rx_fseek( ptr->fileptr, 0L, SEEK_SET );
2727       ptr->thispos = rx_ftell( ptr->fileptr );
2728       if ( oper == OPER_READ )
2729       {
2730          ptr->readline = 1;
2731          ptr->readpos = 1;
2732          ptr->oper = OPER_READ;
2733          return 1;
2734       }
2735       else
2736       {
2737          ptr->writeline = 1;
2738          ptr->writepos = 0;
2739          ptr->oper = OPER_WRITE;
2740          return 0;
2741       }
2742    }
2743    /*
2744     * We now have an absolute line number from the +ve relative line
2745     * number, so use the absolute positioning code.
2746     */
2747    ret = positionfile_SEEK_SET( TSD, bif, argno, ptr, oper, tmp );
2748    return ret;
2749 }
2750 
positionfile_SEEK_END(tsd_t * TSD,const char * bif,int argno,fileboxptr ptr,int oper,rx_64 lineno)2751 static rx_64 positionfile_SEEK_END( tsd_t *TSD, const char *bif, int argno, fileboxptr ptr, int oper, rx_64 lineno )
2752 {
2753    /*
2754     * This function does file positioning on a line basis from the
2755     * end of the file.
2756     * There is not a lot of optimisation we can do reading backwards.
2757     * We first need to determine the last line; does it end in an EOL,
2758     * or is it incomplete; ie does not end in EOL. We treat an incomplete
2759     * last line as a line.
2760     * Our initial attempt at this will position the file at the
2761     * end, and read backwards; ie getc(ptr->fileptr), seek() back 2 chars,
2762     * until the start of file, or we have the specified number of lines.
2763     */
2764    rx_64 here, next, save_pos, i, ret, this_lineno, num_lines;
2765    size_t bret;
2766    char buf[512];
2767    int found = 0;
2768 
2769    SWITCH_OPER_READ(ptr);
2770    /*
2771     * First, get the size of the file. We can only do positioning on
2772     * persistant files...
2773     */
2774    if ( ! (ptr->flag & FLAG_PERSIST) )
2775    {
2776       file_error( ptr, 0, "Cannot position on transient stream" );
2777       return 0;
2778    }
2779    if ( rx_fseek( ptr->fileptr, 0L, SEEK_END ) )
2780    {
2781       file_error( ptr, errno, NULL ) ;
2782       return 0;
2783    }
2784 
2785    here = rx_ftell( ptr->fileptr );
2786    /*
2787     * Seek backwards one character and read the character. If the last
2788     * character is REGINA_EOL, then DON'T treat this as a new line.
2789     */
2790    if ( rx_fseek( ptr->fileptr, -1L, SEEK_CUR ) )
2791    {
2792       file_error( ptr, errno, NULL ) ;
2793       return 0;
2794    }
2795    buf[0] = (char)getc( ptr->fileptr );
2796    if ( buf[0] == REGINA_EOL )
2797       num_lines = 0;
2798    else
2799       num_lines = 1;
2800    /*
2801     * Move the file pointer back to after the last character
2802     */
2803    if ( rx_fseek( ptr->fileptr, 0L, SEEK_END ) )
2804    {
2805       file_error( ptr, errno, NULL ) ;
2806       return 0;
2807    }
2808 
2809    /*
2810     * We have to read backwards in the file until we reach a known point.
2811     * The only point which we can guarantee is known is the start of the
2812     * file. We can't use ptr->(read/write)line as we really don't know
2813     * how many lines are in the file (we may have appended several earlier).
2814     * An incomplete last line of a file (ie no CRLF/LF) is treated as a complete
2815     * line.
2816     */
2817    for ( ; ; )
2818    {
2819       /*
2820        * Determine where we want to move the file pointer backwards
2821        * into the file, and then move the file pointer there ready for
2822        * reading a buffer.
2823        */
2824       next = min( 512, here );
2825       if ( rx_fseek( ptr->fileptr, -next, SEEK_CUR ) )
2826       {
2827          file_error( ptr, errno, NULL ) ;
2828          return 0;
2829       }
2830       /*
2831        * Save our current position; start of the buffer being read
2832        */
2833       save_pos = rx_ftell( ptr->fileptr );
2834       /*
2835        * Read a buffer, this moves the file pointer forward to the
2836        * end of the buffer
2837        */
2838 
2839       bret = fread( buf, sizeof(char), next, ptr->fileptr );
2840       if ( bret != next
2841       &&   bret != EOF )
2842       {
2843          file_error( ptr, errno, NULL ) ;
2844          return 0;
2845       }
2846       /*
2847        * Count the number of lines from the end of the buffer
2848        */
2849       for ( i = next-1; i >= 0; i-- )
2850       {
2851          if ( buf[i] == REGINA_EOL )
2852          {
2853             num_lines++;
2854             if ( num_lines > lineno
2855             &&   !found )
2856             {
2857                /*
2858                 * Calculate the actual char file position of the EOL
2859                 */
2860                /*
2861                 * the +1 is to point at the character AFTER the EOL; the
2862                 * first character of the next line.
2863                 */
2864                ptr->thispos = save_pos + i + 1;
2865                found = 1;
2866             }
2867          }
2868       }
2869       /*
2870        * move the file pointer back to the start of the buffer just
2871        * read, so the next fseek() backwards is from the START of the
2872        * buffer.
2873        */
2874       if ( rx_fseek( ptr->fileptr, save_pos, SEEK_SET ) )
2875       {
2876          file_error( ptr, errno, NULL ) ;
2877          return 0;
2878       }
2879       /*
2880        * Calculate our own file position
2881        */
2882       here -= next;
2883       if ( here == 0 )
2884          break;
2885    }
2886    /*
2887     * We are at the start of the file. If we haven't found our lineno
2888     * (because it was greater than the number of lines in the file),
2889     * that's where we stay.
2890     */
2891    if ( found )
2892    {
2893       /* ptr->thispos already set */
2894       this_lineno = 1 + (num_lines - lineno);
2895    }
2896    else
2897    {
2898       ptr->thispos = 0;
2899       this_lineno = 1;
2900    }
2901 
2902    /*
2903     * Now we are almost finished. We just have to set the correct
2904     * information in the Rexx file table entry.
2905     */
2906    if ( rx_fseek( ptr->fileptr, ptr->thispos, SEEK_SET ) )
2907    {
2908       file_error( ptr, errno, NULL ) ;
2909       return 0;
2910    }
2911    if ( oper & OPER_READ )
2912    {
2913       ptr->readline = this_lineno;
2914       ptr->readpos = ptr->thispos;
2915       ptr->flag &= ~(FLAG_RDEOF);
2916       ptr->flag &= ~(FLAG_AFTER_RDEOF);
2917    }
2918    if ( oper & OPER_WRITE )
2919    {
2920       ptr->writeline = this_lineno;
2921       ptr->writepos = ptr->thispos ;
2922       ptr->flag &= ~(FLAG_WREOF) ;
2923    }
2924    /*
2925     * We just counted the number of lines between the end of the file and
2926     * our current position, so we know how many lines are left.
2927     * The number of lines counted is one more than actually left in the file.
2928     */
2929    ptr->linesleft = num_lines-1;
2930    if ( oper & OPER_READ )
2931       ret = ptr->readline ;
2932    else
2933       ret = ptr->writeline ;
2934    return ret;
2935 }
2936 
2937 /*
2938  * This routine will position the current read or write position
2939  * of a file, to the start of a particular line. The file to be
2940  * operated on is 'ptr', the pointer to manipulate is indicated
2941  * by 'oper' (either OPER_READ or OPER_WRITE or both), and the linenumber
2942  * to position at is 'lineno'.
2943  * 'from' specifies if the positioning is done as an absolute position SEEK_SET,
2944  * a relative position from the current position SEEK_CUR, or relative to the
2945  * end of file: SEEK_END.
2946  *
2947  * There are (at least) two ways to do the backup of the current
2948  * position in the file. First to backup to the start of the file
2949  * and then to seek forward, or to seek backwards from the current
2950  * position of the file.
2951  *
2952  * Perhaps the first is best for the standard case, and the second
2953  * should be activated when the line-parameter is negative ... ?
2954  */
2955 
positionfile(tsd_t * TSD,const char * bif,int argno,fileboxptr ptr,int oper,rx_64 lineno,int from)2956 static rx_64 positionfile( tsd_t *TSD, const char *bif, int argno, fileboxptr ptr, int oper, rx_64 lineno, int from )
2957 {
2958    rx_64 from_line=0;
2959    rx_64 from_char=0;
2960    rx_64 ret=0;
2961 
2962    /*
2963     * If file is in ERROR state, don't touch it.
2964     */
2965    if (ptr->flag & FLAG_ERROR)
2966    {
2967       if (!(ptr->flag & FLAG_FAKE))
2968          file_error( ptr, 0, NULL ) ;
2969       return 0;
2970    }
2971 
2972    /*
2973     * If this isn't a persistent file, then report an error. We can only
2974     * perform repositioning in persistent files.
2975     */
2976 
2977    if (!(ptr->flag & FLAG_PERSIST ))
2978       exiterror( ERR_INCORRECT_CALL, 42, bif, tmpstr_of( TSD, ptr->filename0 ) ) ;
2979 
2980    /*
2981     * If the operation is READ, but the file is not open for READ,
2982     * return an error.
2983     */
2984    if ((oper&OPER_READ) && !(ptr->flag & FLAG_READ))
2985       exiterror( ERR_INCORRECT_CALL, 921, bif, argno, "READ" ) ;
2986    /*
2987     * If the operation is WRITE, but the file is not open for WRITE,
2988     * return an error.
2989     */
2990    if ( (oper&OPER_WRITE) && !(ptr->flag & FLAG_WRITE) )
2991       exiterror( ERR_INCORRECT_CALL, 921, bif, argno, "WRITE" ) ;
2992 
2993    /*
2994     * If we do any repositioning, then make the old estimate of lines
2995     * left to read invalid. This is not really needed in all cases, but
2996     * it is a good start. And you _may_ even want to recalculate the
2997     * number of lines left!
2998     */
2999    if ( ptr->linesleft > 0 )
3000       ptr->linesleft = 0 ;
3001 
3002    if ( ptr->thispos == (size_t) EOF )
3003    {
3004       errno = 0 ;
3005       ptr->thispos = rx_ftell( ptr->fileptr ) ;
3006    }
3007 
3008    /*
3009     * So, what we are going to do depends partly on whether we are moving
3010     * the read or the write position of the file. We may even be as
3011     * lucky as not to have to move anything ... :-) First we can clear
3012     * the EOF flag, if set. Repositioning will clean up any EOF state.
3013     */
3014    if (oper & OPER_READ)
3015    {
3016       ptr->flag &= ~(FLAG_RDEOF) ;
3017       ptr->flag &= ~(FLAG_AFTER_RDEOF) ;
3018    }
3019    if (oper & OPER_WRITE)
3020       ptr->flag &= ~(FLAG_WREOF) ;
3021 
3022    /*
3023     * Positioning by line in a forwards direction is always going to be more efficient
3024     * starting at the current line position and reading forward.
3025     * Positioning by line in a backwards direction may be more efficient to start at the
3026     * beginning of the file and read forwards, rather than reading backwards; it
3027     * depends on how far back we are positioning.
3028     */
3029    switch( from )
3030    {
3031       case SEEK_CUR: /* position relative to current position */
3032          if ( oper & OPER_READ )
3033          {
3034             if ( ptr->readline > 0 )
3035             {
3036                from_line = ptr->readline ;
3037                from_char = ptr->readpos ;
3038                ret = positionfile_SEEK_CUR( TSD, bif, argno, ptr, OPER_READ, lineno, from_line, from_char );
3039             }
3040             else
3041             {
3042                /*
3043                 * If the readpos is set, then we can (inefficiently) determine the line
3044                 * position by starting at the beginning of the file.
3045                 */
3046                if ( ptr->readpos != (size_t) -1 )
3047                {
3048                   /*
3049                    * FIXME: We need a mechanism to convert to readpos into a readline;
3050                    * positionfile_SEEK_SET() doesn't do it for us.
3051                    *
3052                    */
3053                   errno = 2;
3054                   ret = -1;
3055                   break;
3056                }
3057                else
3058                {
3059                   /*
3060                    * ERROR:2
3061                    * Can't seek lines relatively if there is no current read char position
3062                    */
3063                   errno = 2;
3064                   ret = -1;
3065                   break;
3066                }
3067             }
3068          }
3069          if ( oper & OPER_WRITE )
3070          {
3071             if ( ptr->writeline > 0 )
3072             {
3073                from_line = ptr->writeline ;
3074                from_char = ptr->writepos ;
3075                ret = positionfile_SEEK_CUR( TSD, bif, argno, ptr, OPER_WRITE, lineno, from_line, from_char );
3076             }
3077             else
3078             {
3079                /*
3080                 * If the writepos is set, then we can (inefficiently) determine the line
3081                 * position by starting at the beginning of the file.
3082                 */
3083                if ( ptr->writepos != (size_t) -1 )
3084                {
3085                   /*
3086                    * FIXME: We need a mechanism to convert to writepos into a writeline;
3087                    * positionfile_SEEK_SET() doesn't do it for us.
3088                    *
3089                    */
3090                   errno = 2;
3091                   ret = -1;
3092                   break;
3093                }
3094                else
3095                {
3096                   /*
3097                    * ERROR:2
3098                    * Can't seek lines relatively if there is no current write char position
3099                    */
3100                   errno = 2;
3101                   ret = -1;
3102                   break;
3103                }
3104             }
3105          }
3106          /*
3107           * Now we are almost finished. We just have to set the correct
3108           * information in the Rexx file table entry.
3109           */
3110          if ( (oper & OPER_READ) && (oper & OPER_WRITE) )
3111          {
3112             ptr->oper = OPER_NONE;
3113          }
3114          if ( oper & OPER_READ )
3115          {
3116             ptr->flag &= ~(FLAG_RDEOF) ;
3117             ptr->flag &= ~(FLAG_AFTER_RDEOF) ;
3118          }
3119          if ( oper & OPER_WRITE )
3120          {
3121             ptr->flag &= ~(FLAG_WREOF) ;
3122          }
3123          break;
3124       case SEEK_END: /* position relative to end of file */
3125          ret = positionfile_SEEK_END( TSD, bif, argno, ptr, oper, lineno );
3126          break;
3127       case SEEK_SET: /* position absolute */
3128          ret = positionfile_SEEK_SET( TSD, bif, argno, ptr, oper, lineno );
3129       default: /* should not get here */
3130          break;
3131    }
3132    return ret;
3133 }
3134 
3135 
3136 
3137 /*
3138  * I wish every function would be as easy as this! Basically, it
3139  * only contain simple error checking, and a direct positioning.
3140  * it is called by the built-in function CHARIN() and CHAROUT()
3141  * in order to position the current read or write position at the
3142  * correct place in the file.
3143  */
positioncharfile(tsd_t * TSD,const char * bif,int argno,fileboxptr fileptr,int oper,rx_64 where,int from)3144 static rx_64 positioncharfile( tsd_t *TSD, const char *bif, int argno, fileboxptr fileptr, int oper, rx_64 where, int from )
3145 {
3146    rx_64 where_read=0,where_write=0;
3147    /*
3148     * If the file is in state ERROR, don't touch it! Since we are not
3149     * to return any data, don't bother about the state of FAKE.
3150     */
3151    if (fileptr->flag & FLAG_ERROR)
3152    {
3153       if (!(fileptr->flag & FLAG_FAKE))
3154          file_error( fileptr, 0, NULL ) ;
3155       return 0;
3156    }
3157 
3158    /*
3159     * If the file is not persistent, then positioning is not allowed.
3160     * Give the appropriate error for this.
3161     */
3162    if (!(fileptr->flag & FLAG_PERSIST))
3163       exiterror( ERR_INCORRECT_CALL, 42, bif, tmpstr_of( TSD, fileptr->filename0 ) ) ;
3164    /*
3165     * If the operation is READ, but the file is not open for READ,
3166     * return an error.
3167     */
3168    if ((oper&OPER_READ) && !(fileptr->flag & FLAG_READ))
3169       exiterror( ERR_INCORRECT_CALL, 921, bif, argno, "READ" ) ;
3170    /*
3171     * If the operation is WRITE, but the file is not open for WRITE,
3172     * return an error.
3173     */
3174    if ((oper&OPER_WRITE) && !(fileptr->flag & FLAG_WRITE))
3175       exiterror( ERR_INCORRECT_CALL, 921, bif, argno, "WRITE" ) ;
3176 
3177 #ifdef TRUE_TRL_IO
3178    /*
3179     * TRL says that positioning the read position to after the last
3180     * character in the file, is an error. Unix allows it, and gives
3181     * an EOF at the next reading. So, we have to handle this as a
3182     * special case ...  Check that the new position is valid.
3183     *
3184     * Should we give "Incorrect call to routine" when the character
3185     * position is greater than the size of the file? Perhaps we should
3186     * raise the NOTREADY condition instead?
3187     */
3188    {
3189       rx_64 oldp, endp ;
3190 
3191       oldp = rx_ftell( fileptr->fileptr ) ;
3192       rx_fseek(fileptr->fileptr, 0, SEEK_END) ;
3193       endp = rx_ftell( fileptr->fileptr ) ;
3194       rx_fseek( fileptr->fileptr, oldp, SEEK_SET ) ;
3195       fileptr->oper = OPER_NONE;
3196 
3197       /*
3198        * Determine the value of "where" depending on the starting
3199        * location determined by "from". "where" is passed in in an
3200        * external format; ie 1 based, internally it must be 0 based
3201        */
3202       switch(from)
3203       {
3204          case SEEK_CUR:
3205             if ( oper & OPER_READ )
3206                where_read = 1 + where + fileptr->readpos;
3207             if ( oper & OPER_WRITE )
3208                where_write = 1 + where + fileptr->writepos;
3209             break;
3210          case SEEK_END:
3211             if ( oper & OPER_READ )
3212                where_read = endp - where;
3213 #if SEEK_TO_EOF_FOR_WRITE_IS_AT_EOF
3214             if ( oper & OPER_WRITE )
3215                where_write = endp - where;
3216 #else
3217             if ( oper & OPER_WRITE )
3218                where_write = 1 + endp - where;
3219 #endif
3220             break;
3221          default:  /* SEEK_SET */
3222             if ( oper & OPER_READ )
3223                where_read = where;
3224             if ( oper & OPER_WRITE )
3225                where_write = where;
3226             break;
3227       }
3228       if ( oper & OPER_READ )
3229       {
3230          if ( where_read < 1 )
3231          {
3232             file_error( fileptr, 0, "Repositioning before start of file" ) ;
3233             return 0;
3234          }
3235          if ( endp < where_read )
3236          {
3237             file_error( fileptr, 0, "Repositioning at or after EOF" ) ;
3238             return 0;
3239          }
3240       }
3241       if ( oper & OPER_WRITE )
3242       {
3243          if ( where_write < 1 )
3244          {
3245             file_error( fileptr, 0, "Repositioning before start of file" ) ;
3246             return 0;
3247          }
3248          if ( (endp+1) < where_write )
3249          {
3250             file_error( fileptr, 0, "Repositioning after EOF" ) ;
3251             return 0;
3252          }
3253       }
3254    }
3255 #endif
3256 
3257    /*
3258     * Then do the actual positioning. Remember to clear errno first.
3259     * Previously, this code tested afterwards to see if ftell()
3260     * returned the same position that fseek() tried to set. Surely, that
3261     * must be unnecessary?
3262     * We need to reposition using both the read and write postions (if
3263     * required).
3264     */
3265    errno = 0 ;
3266    /*
3267     * Position the real file pointer to the write or read pointers
3268     * calculated. The "thispos" member is set to the last seek
3269     * executed. READ is done last as this is probably the most
3270     * likely use of character positioning, hence it may be slightly
3271     * more efficient.
3272     */
3273    if ( oper & OPER_WRITE )
3274    {
3275       if ( rx_fseek(fileptr->fileptr,(where_write-1),SEEK_SET ) )
3276       {
3277          file_error( fileptr, errno, NULL ) ;
3278          return 0;
3279       }
3280       fileptr->thispos = where_write ; /* this was where-1; is that correct ?*/
3281    }
3282    if ( oper & OPER_READ )
3283    {
3284       if ( rx_fseek(fileptr->fileptr,(where_read-1),SEEK_SET) )
3285       {
3286          file_error( fileptr, errno, NULL ) ;
3287          return 0;
3288       }
3289       fileptr->thispos = where_read ; /* this was where-1; is that correct ?*/
3290    }
3291    fileptr->oper = OPER_NONE;
3292 
3293    /*
3294     * Then we have to update the file pointers in the entry in our
3295     * file table.
3296     *
3297     * Clear the end-of-file flag. Even if we *did* position to the
3298     * end of file, we don't want to discover that until we actually
3299     * _read_ data that is _off_ the end-of-file.
3300     */
3301 
3302    if (oper & OPER_READ)
3303    {
3304       fileptr->readpos = where_read-1 ;
3305       fileptr->flag &= ~(FLAG_RDEOF) ;
3306       fileptr->flag &= ~(FLAG_AFTER_RDEOF) ;
3307    }
3308    if (oper & OPER_WRITE)
3309    {
3310       fileptr->writepos = where_write-1 ;
3311       fileptr->flag &= ~(FLAG_WREOF) ;
3312    }
3313    if (oper == OPER_NONE)
3314       file_error( fileptr, 0, NULL ) ;
3315 
3316    /*
3317     * We have moved the file pointer by a number of characters which
3318     * may have spanned any number of lines. So we have no idea which
3319     * line we are in the file, so we need to invalidate the
3320     * read or write line position.
3321     */
3322    if (oper & OPER_READ)
3323       fileptr->readline = 0 ;
3324    if (oper & OPER_WRITE)
3325       fileptr->writeline = 0 ;
3326 
3327    /*
3328     * Return the new position of the file pointer.  If both file
3329     * pointers were set, then readpos and writepos are the same, so
3330     * the following test is valid.
3331     */
3332    if (oper & OPER_READ)
3333       return fileptr->readpos + 1; /* external representation */
3334    else
3335       return fileptr->writepos + 1; /* external representation */
3336 }
3337 
3338 
3339 
3340 /*
3341  * This routine reads a string of data from a file indicated by
3342  * the Rexx file table entry 'ptr'. The read starts at the current
3343  * read position, and the length will be 'length' characters.
3344  *
3345  * Then, what if the data to be read are more than what is possible
3346  * to store in one string; let's say length=100,000, and the size of
3347  * length in a string is 16 bit. Well, That should return an error
3348  * in Str_makeTSD(), but maybe we should handle it more elegantly?
3349  *
3350  * No file_error() is thrown if noerrors is set.
3351  */
readbytes(tsd_t * TSD,fileboxptr fileptr,size_t length,int noerrors)3352 static streng *readbytes( tsd_t *TSD, fileboxptr fileptr, size_t length, int noerrors )
3353 {
3354    size_t didread=0 ;
3355    streng *retvalue=NULL ;
3356 
3357    /*
3358     * If state is ERROR, then refuse to handle the file further.
3359     * If the state was 'only' EOF, then don't bother, the length of
3360     * the file might have increased since last attempt to read.
3361     */
3362    if (fileptr->flag & FLAG_ERROR)
3363    {
3364       if (!noerrors && !(fileptr->flag & FLAG_FAKE))
3365          file_error( fileptr, 0, NULL ) ;
3366       return nullstringptr() ;
3367    }
3368 
3369    assert( fileptr->flag & FLAG_READ ) ;
3370 
3371    /*
3372     * If we are not at the current read position, we have to
3373     * seek to the correct position, but first we have to the validity
3374     * of these positions.
3375     */
3376    if (fileptr->flag & FLAG_PERSIST)
3377    {
3378       if (fileptr->thispos != fileptr->readpos)
3379       {
3380          errno = 0 ;
3381          if ( fileptr->flag & FLAG_PERSIST
3382          &&  rx_fseek(fileptr->fileptr, fileptr->readpos, SEEK_SET ))
3383          {
3384             if (!noerrors)
3385                file_error( fileptr, errno, NULL ) ;
3386             return nullstringptr() ;
3387          }
3388          fileptr->thispos = fileptr->readpos ;
3389          fileptr->oper = OPER_NONE ;
3390       }
3391    }
3392 
3393    /*
3394     * The joy of POSIX ...  If a file is open for input and output, it
3395     * must be flushed when changing between the two. Therefore, check
3396     * the type of the last operation. Actually, this are not very likely
3397     * since that situation would in general have been handled above.
3398     */
3399    if (fileptr->oper==OPER_WRITE)
3400    {
3401       errno = 0 ;
3402       if ( fileptr->flag & FLAG_PERSIST
3403       &&  rx_fseek( fileptr->fileptr, 0L, SEEK_CUR ))
3404       {
3405          /* Hey, how could this have happened?!?! NFS down? */
3406          if (!noerrors)
3407             file_error( fileptr, errno, NULL ) ;
3408          return nullstringptr() ;
3409       }
3410       fileptr->oper = OPER_NONE ;
3411    }
3412 
3413    /*
3414     * Lets get ready for the big event. First allocate enough space to
3415     * hold the data we are hoping to be able to read. Then read it
3416     * directly into the string.
3417     */
3418    retvalue = Str_makeTSD(length+1) ;
3419    errno = 0 ;
3420    didread = fread( retvalue->value, 1, length, fileptr->fileptr ) ;
3421    fileptr->oper = OPER_READ;
3422 
3423    /*
3424     * Variable 'read' contains the number of items (=bytes) read, or
3425     * it contains EOF if an error occurred. Handle the error the
3426     * normal way; i.e. trigger file_error and return nothing.
3427     */
3428    if (didread==EOF)
3429    {
3430       if (!noerrors)
3431          file_error( fileptr, errno, NULL ) ;
3432       return nullstringptr() ;
3433    }
3434 
3435    /*
3436     * What if we didn't manage to read all the data? Well, return what
3437     * we got, but still trigger an error, since EOF should be
3438     * considered a NOTREADY condition. However, we try to handle EOF
3439     * a bit more elegantly than other errors, since lots of programmers
3440     * are probably not bothering about EOF; an EOF condition should be
3441     * able to be reset using a file positioning.
3442     */
3443    assert( 0<=didread && didread<=length ) ;   /* It'd better be! */
3444    retvalue->len = didread ;
3445    if (didread<length)
3446    {
3447       if (!noerrors)
3448          file_warning( fileptr, 0, "EOF on char input" ) ;
3449       fileptr->flag |= FLAG_RDEOF ;
3450    }
3451    else
3452    {
3453       fileptr->flag &= ~FLAG_RDEOF ;
3454       fileptr->flag &= ~FLAG_AFTER_RDEOF ;
3455    }
3456 
3457    /*
3458     * Then, at the end, we have to set the pointers and counter to
3459     * the correct values
3460     */
3461    fileptr->thispos += didread ;
3462    fileptr->readpos += didread ;
3463    fileptr->readline = (-1) ;
3464    fileptr->linesleft = 0 ;
3465 
3466    return retvalue ;
3467 }
3468 
3469 
3470 
3471 /*
3472  * This routines write a string to a file pointed to by the Rexx file
3473  * table entry 'fileptr'. The string to be written is 'string', and the
3474  * length of the write is implicitly given as the length of 'string'
3475  *
3476  * This routine is called from the Rexx built-in function CHAROUT().
3477  * It is a fairly straight forward implementation.
3478  *
3479  * No file_error() is thrown if noerrors is set.
3480  */
writebytes(tsd_t * TSD,fileboxptr fileptr,const streng * string,int noerrors)3481 static size_t writebytes( tsd_t *TSD, fileboxptr fileptr, const streng *string, int noerrors )
3482 {
3483 #ifdef WIN32
3484    int rc=0;
3485 #endif
3486    long todo, done, written=0 ;
3487    const char *buf ;
3488 
3489    /*
3490     * First, if this file is in state ERROR, don't touch it, what to
3491     * return depends on whether the file is in state FAKE.
3492     */
3493    if ( fileptr->flag & FLAG_ERROR )
3494    {
3495       if ( fileptr->flag & FLAG_FAKE )
3496          return string->len ;
3497       else
3498       {
3499          if (!noerrors)
3500             file_error( fileptr, 0, NULL ) ;
3501          if (fileptr->flag & FLAG_FAKE)
3502             return string->len ;
3503 
3504          return 0 ;
3505       }
3506    }
3507    /*
3508     * If we are not at the current write position, we have to
3509     * seek to the correct position
3510     */
3511    if (fileptr->thispos != fileptr->writepos)
3512    {
3513       errno = 0 ;
3514       if ( fileptr->flag & FLAG_PERSIST
3515       &&  rx_fseek(fileptr->fileptr, fileptr->writepos, SEEK_SET ))
3516       {
3517          if (!noerrors)
3518             file_error( fileptr, errno, NULL ) ;
3519          return 0 ;
3520       }
3521       fileptr->thispos = fileptr->writepos ;
3522       fileptr->oper = OPER_NONE ;
3523    }
3524 
3525    /*
3526     * If previous operation on this file was a read, we have to flush
3527     * the file before we can perform any write operations. This will
3528     * seldom happen, since it is in general handled above.
3529     */
3530    if (fileptr->oper == OPER_READ)
3531    {
3532       errno = 0 ;
3533       if ( fileptr->flag & FLAG_PERSIST
3534       &&  rx_fseek(fileptr->fileptr, 0, SEEK_CUR))
3535       {
3536          if (!noerrors)
3537             file_error( fileptr, errno, NULL ) ;
3538          return (fileptr->flag & FLAG_FAKE) ? string->len : 0 ;
3539       }
3540       fileptr->oper = OPER_NONE ;
3541    }
3542 
3543    /*
3544     * Here comes the actual writing. This also works when the length
3545     * of string is zero.
3546     */
3547    errno = 0 ;
3548    buf = string->value ;
3549    todo = string->len ;
3550    fileptr->oper = OPER_WRITE ;
3551    do
3552    {
3553 #ifdef WIN32
3554       done = fwrite( buf, 1, todo, fileptr->fileptr ) ;
3555 #else
3556       done = fwrite( buf, 1, todo, fileptr->fileptr ) ;
3557 #endif
3558       /*
3559        * Win32 has a bug with fwrite and disk full. If the size of the
3560        * chunk to write is < 4096 and the disk fills up, then you don't get
3561        * an error indication. So flush the stream if the size of data is
3562        * < 4096 and test the result of fflush(). Bug 731664
3563        */
3564 #ifdef WIN32
3565       if (string->len < 4096 )
3566          rc = fflush( fileptr->fileptr );
3567       if (done < 0 || rc != 0 )
3568 #else
3569       if (done < 0)
3570 #endif
3571       {
3572          written = -1 ;
3573          break ;
3574       } else if (done == 0)
3575          break;
3576       else
3577          written += done ;
3578       buf += done ;
3579       todo -= done ;
3580    } while ( ( todo > 0 ) && noerrors ) ;
3581 
3582    /*
3583     * Here comes the error checking. Note that this function will
3584     * return the number of elements written, it will never return
3585     * EOF as fread can, since the problems surrounding EOF can not
3586     * occur in this operation. Therefore, report a fullfleged error
3587     * whenever rc is less than the length of string.
3588     */
3589    assert( 0<=written && written<=string->len ) ;
3590    if (written < string->len )
3591    {
3592       if (!noerrors)
3593          file_error( fileptr, errno, NULL ) ;
3594    }
3595    else
3596    {
3597       /*
3598        * If the operation was successful, then we set misc status
3599        * information about the file, and the counters and pointers.
3600        */
3601       fileptr->writeline = 0 ;
3602       fileptr->flag &= ~FLAG_RDEOF ;
3603       fileptr->flag &= ~FLAG_AFTER_RDEOF ;
3604       fileptr->thispos += written ;
3605       fileptr->writepos += written ;
3606 
3607       fflush( fileptr->fileptr ) ;
3608       fileptr->oper = OPER_NONE;
3609    }
3610 
3611    return written ;
3612 }
3613 
3614 
3615 /*
3616  * This routine calculates the number of bytes remaining in the file,
3617  * i.e the number of bytes from the current read position until the
3618  * end-of-file.  It is, of course, called from the Rexx built-in
3619  * function CHARS()
3620  */
3621 
calc_chars_left(tsd_t * TSD,fileboxptr ptr)3622 static rx_64 calc_chars_left( tsd_t *TSD, fileboxptr ptr )
3623 {
3624    rx_64 left=0 ;
3625    rx_64 oldpoint=0L, newpoint=0L ;
3626 
3627    if (!(ptr->flag & FLAG_READ))
3628       return 0 ;
3629 
3630    /*
3631     * First, determine whether this file is in ERROR state. If so, we
3632     * don't want to touch it. Whether or not the file is in FAKE state
3633     * is fairly irrelevant in this situation
3634     */
3635    if ( ptr->flag & FLAG_ERROR )
3636    {
3637       if (!(ptr->flag & FLAG_FAKE))
3638          file_error( ptr, 0, NULL ) ;
3639       return 0 ;
3640    }
3641 
3642    /*
3643     * If this is not a persistent file, then we have no means of finding
3644     * out how much of the file is available. Then, return 1 if we are not
3645     * at the end-of-file, and 0 otherwise.
3646     */
3647    if (!(ptr->flag & FLAG_PERSIST))
3648    {
3649 #if 0
3650       left = ( !(ptr->flag & FLAG_RDEOF)) ;
3651 #else
3652       struct rx_stat_buf finfo;
3653       int fno;
3654 
3655       fno = fileno( ptr->fileptr ) ;
3656       rx_fstat( fno, &finfo );
3657       left = finfo.st_size;
3658 #endif
3659    }
3660    else
3661    {
3662       /*
3663        * This is a persistent file, which is not in error state. OK, then
3664        * we must record the current point, fseek to the end-of-file,
3665        * ftell to get that position, and fseek back to where we started.
3666        * And we have to check for errors everywhere ... sigh.
3667        *
3668        * First, record the current position in the file.
3669        */
3670       errno = 0 ;
3671       oldpoint = rx_ftell( ptr->fileptr ) ;
3672       if (oldpoint==EOF)
3673       {
3674          file_error( ptr, errno, NULL ) ;
3675          return 0 ;
3676       }
3677 
3678       /*
3679        * Then, move the current position to the end-of-file
3680        */
3681       errno = 0 ;
3682       if (rx_fseek(ptr->fileptr, 0L, SEEK_END))
3683       {
3684          file_error( ptr, errno, NULL ) ;
3685          return 0 ;
3686       }
3687       ptr->oper = OPER_NONE;
3688 
3689       /*
3690        * And record the position of the end-of-file
3691        */
3692       errno = 0 ;
3693       newpoint = rx_ftell( ptr->fileptr ) ;
3694       if (newpoint==EOF)
3695       {
3696          file_error( ptr, errno, NULL ) ;
3697          return 0 ;
3698       }
3699 
3700       /*
3701        * And, at last, position back to the place where we started.
3702        * Actually, this may not be necessary, since we _can_ leave the
3703        * current position at the end-of-file. After all, the next read
3704        * or write _will_ position back correctly. However, let's be
3705        * nice ...
3706        */
3707       errno = 0 ;
3708       if (rx_fseek(ptr->fileptr, oldpoint, SEEK_SET))
3709       {
3710          file_error( ptr, errno, NULL ) ;
3711          return 0 ;
3712       }
3713 
3714       /*
3715        * Then we have some accounting to do; calculate the size of the
3716        * last part of the file. And also set oper to NONE, we _have_
3717        * done a repositioning ... actually, several :-)
3718        */
3719       left = newpoint - ptr->readpos ;
3720 /*      left = newpoint - oldpoint ; */ /* YURI - wrong */
3721       ptr->oper = OPER_NONE ;
3722    }
3723 
3724    return left ;
3725 }
3726 
3727 
3728 /*
3729  * This routine counts the complete lines remaining in the file
3730  * pointed to by the Rexx file table entry 'ptr'. The count starts
3731  * at the current read or write position, and the current line will be counted
3732  * even if the current read position points to the middle of a line.
3733  * The last line will only be counted if it was actually terminated
3734  * by a EOL marker. If the current line is the last line, but it was
3735  * not explicitly terminated by a EOL marker, zero is returned.
3736  */
countlines(tsd_t * TSD,fileboxptr ptr,int actual,int oper)3737 static rx_64 countlines( tsd_t *TSD, fileboxptr ptr, int actual, int oper )
3738 {
3739    long oldpoint=0L ;
3740    int left=0, ch=0;
3741    int prevch=-1 ;
3742 
3743    /*
3744     * If this file is in ERROR state, we really don't want to try to
3745     * operate on it. Just report an error, and return 0.
3746     */
3747    if ( ptr->flag & FLAG_ERROR )
3748    {
3749       if (!(ptr->flag & FLAG_FAKE))
3750          file_error( ptr, 0, NULL ) ;
3751       return 0 ;
3752    }
3753 
3754    /*
3755     * Counting lines requires us to reposition in the file. However,
3756     * we can not reposition in transient files. If this is not a
3757     * persistent file, don't do any repositioning, just return one
3758     * for any situation where we are not sure whether there are more
3759     * data or not (i.e. unless we are sure that there are no more data,
3760     * return "1"
3761     */
3762    if (!(ptr->flag & FLAG_PERSIST))
3763       return (!feof(ptr->fileptr)) ;
3764    else if (!actual)
3765    {
3766       /*
3767        * We just want to know of there are any lines left for a persistent
3768        * file.  First check if we have reached EOF. Return 0 if so.
3769        * Next check if the file size is 0; if so then return 0, otherwise
3770        * there is at least 1 line left; return 1
3771        */
3772       if ( feof(ptr->fileptr ) )
3773          return 0;
3774       else
3775       {
3776          struct rx_stat_buf buffer ;
3777          int fno;
3778          memset( &buffer, 0, sizeof(buffer) );
3779          fno = fileno( ptr->fileptr ) ;
3780          rx_fstat( fno, &buffer ) ;
3781          return ((buffer.st_size==0) ? 0 : 1);
3782       }
3783    }
3784    else
3785    {
3786       /*
3787        * Take advantage of the cached value of the lines left in the
3788        * file
3789        */
3790       if (ptr->linesleft)
3791          return ptr->linesleft ;
3792 
3793       /*
3794        * If, however, this is a persistent file, we have to read from
3795        * the current read position to the end-of-file, and count all
3796        * the lines. First, make sure that we position at the current
3797        * read position.
3798        */
3799       errno = 0 ;
3800       oldpoint = rx_ftell( ptr->fileptr ) ;
3801       if (oldpoint==EOF)
3802       {
3803          file_error( ptr, errno, NULL ) ;
3804          return 0 ;
3805       }
3806 
3807       /*
3808        * Then read the rest of the file, and keep a count of all the files
3809        * read in the process.
3810        */
3811       SWITCH_OPER_READ(ptr);
3812       /*
3813        * Switch to the current read or write pos setting
3814        */
3815       if ( oper == OPER_READ )
3816          ptr->thispos = ptr->readpos;
3817       else
3818          ptr->thispos = ptr->writepos;
3819       rx_fseek(ptr->fileptr,ptr->thispos,SEEK_SET);
3820 #if defined(UNIX) || defined(MAC)
3821       for(left=0;((ch=getc(ptr->fileptr))!=EOF);)
3822       {
3823          if (ch==REGINA_EOL)
3824             left++ ;
3825          prevch = ch;
3826       }
3827       if (prevch != REGINA_EOL
3828       &&  prevch != -1)
3829          left++;
3830 #else
3831       for(left=0;;)
3832       {
3833          ch = getc(ptr->fileptr);
3834          if (ch == EOF)
3835             break;
3836          if ( ch == REGINA_CR)
3837             left++ ;
3838          else
3839          {
3840             if ( ch == REGINA_EOL && prevch != REGINA_CR)
3841                left++ ;
3842          }
3843          prevch = ch;
3844       }
3845       if (prevch != REGINA_EOL
3846       &&  prevch != REGINA_CR
3847       &&  prevch != -1)
3848          left++;
3849 #endif
3850 
3851       /*
3852        * At the end, try to reposition back to the old current read
3853        * position, and report an error if that attempt failed.
3854        */
3855       errno = 0 ;
3856       if ( ptr->flag & FLAG_PERSIST
3857       &&  rx_fseek(ptr->fileptr, oldpoint, SEEK_SET))
3858       {
3859          file_error( ptr, errno, NULL ) ;
3860          return 0 ;
3861       }
3862       ptr->oper = OPER_NONE;
3863       ptr->linesleft = left ;
3864    }
3865    return left ;
3866 }
3867 
3868 
3869 
3870 /*
3871  * This routine writes a line to the file indicated by 'ptr'. The line
3872  * to be written is 'data', and it will be terminated by an extra
3873  * EOL marker after the charactrers in 'data'.
3874  */
writeoneline(tsd_t * TSD,fileboxptr ptr,const streng * data)3875 static int writeoneline( tsd_t *TSD, fileboxptr ptr, const streng *data )
3876 {
3877    const char *i=NULL ;
3878    int num_eol_chars=0;
3879 
3880    /*
3881     * First, make sure that the file is not in ERROR state. If it is
3882     * report an error, and return a result depending on whether this
3883     * file is to be faked.
3884     */
3885    if (ptr->flag & FLAG_ERROR)
3886    {
3887       if (ptr->flag & FLAG_FAKE)
3888          return 0 ;
3889       else
3890       {
3891          file_error( ptr, 0, NULL ) ;
3892          if (ptr->flag & FLAG_FAKE)
3893             return 0 ;
3894          return 1 ;
3895       }
3896    }
3897 
3898    /*
3899     * If we are to write a new line, we ought to truncate the file after
3900     * that line. Or rather, we truncate the file at the start of the
3901     * new line, before we write it out. But only if we have the non-POSIX
3902     * function ftruncate() available. And not if we are already there.
3903     */
3904 #if defined(HAVE_FTRUNCATE)
3905    if ( get_options_flag( TSD->currlevel, EXT_LINEOUTTRUNC ) )
3906    {
3907       if (ptr->oper != OPER_WRITE && !(ptr->flag & (FLAG_WREOF)) &&
3908                                      (ptr->flag & FLAG_PERSIST))
3909       {
3910         int fno ;
3911         errno = 0 ;
3912         SWITCH_OPER_WRITE(ptr); /* Maybe, ftruncate is a write operation in
3913                                  * the meaning of POSIX. This shouldn't do
3914                                  * any harm in other systems.
3915                                  */
3916 
3917         fno = fileno( ptr->fileptr ) ;
3918         if (ftruncate( fno, ptr->writepos) == -1)
3919         {
3920            file_error( ptr, errno, NULL ) ;
3921            return !(ptr->flag & FLAG_FAKE) ;
3922         }
3923         if ( ptr->flag & FLAG_PERSIST )
3924            rx_fseek( ptr->fileptr, 0, SEEK_END ) ;
3925         ptr->oper = OPER_NONE;
3926         ptr->thispos = ptr->writepos = rx_ftell( ptr->fileptr ) ;
3927         if (ptr->readpos>ptr->thispos && ptr->readpos!= (size_t) EOF)
3928         {
3929            ptr->readpos = ptr->thispos ;
3930            ptr->readline = 0 ;
3931            ptr->linesleft = 0 ;
3932         }
3933       }
3934    }
3935 #endif
3936 
3937    /*
3938     * Then, output the characters in 'data', and sense any problem.
3939     * If there is a problem, report an error
3940     */
3941    errno = 0 ;
3942    SWITCH_OPER_WRITE(ptr);
3943    for (i=data->value; i<Str_end(data); i++)
3944    {
3945       if (putc( *i, ptr->fileptr)==EOF)
3946       {
3947          file_error( ptr, errno, NULL ) ;
3948          return 1 ;
3949       }
3950    }
3951 
3952    /*
3953     * After all the data has been written out, we have to explicitly
3954     * terminate the file with an end-of-line marker. Under Unix this
3955     * is the single character EOL. Under Macintosh this is the single
3956     * character CR, and all others it is CR and EOL.
3957     */
3958 #if !defined(UNIX)
3959    SWITCH_OPER_WRITE(ptr);
3960    if (putc( REGINA_CR, ptr->fileptr)==EOF)
3961    {
3962       file_error( ptr, errno, NULL ) ;
3963       return 1 ;
3964    }
3965    num_eol_chars++;
3966 #endif
3967 #if !defined(MAC)
3968    SWITCH_OPER_WRITE(ptr);
3969    if (putc( REGINA_EOL, ptr->fileptr)==EOF)
3970    {
3971       file_error( ptr, errno, NULL ) ;
3972       return 1 ;
3973    }
3974    num_eol_chars++;
3975 #endif
3976 
3977    /*
3978     * Then we have to update the counters and pointers in the Rexx
3979     * file table entry. We must do that in order to be able to keep
3980     * track of where we are.
3981     */
3982    ptr->thispos += data->len + num_eol_chars ; /* fix 736578 */
3983    ptr->writepos = ptr->thispos ;
3984    ptr->oper = OPER_WRITE ;
3985 
3986    /*
3987     * FIXME - under what circumstances will writeline be 0 ?
3988     * If it hasn't been determined by what calls this function, the
3989     * a) the calling function(s) should determine the line number, or
3990     * b) the current line number should be determined here.
3991     */
3992    if (ptr->writeline)
3993       ptr->writeline++ ;
3994 
3995    ptr->flag |= FLAG_WREOF ;
3996 
3997    /*
3998     * At the end, we flush the data. We do this in order to avoid
3999     * surprises later. Maybe we shouldn't do that, since it may force
4000     * a systemcall, which might give away the timeslice and decrease
4001     * system time. So you might want to remove this call ... at your
4002     * own risk :-)
4003     */
4004    errno = 0 ;
4005    if (fflush( ptr->fileptr ))
4006    {
4007       file_error( ptr, errno, NULL ) ;
4008       return 1 ;
4009    }
4010 
4011    return 0 ;
4012 }
4013 
4014 /*
4015  * This routine is a way of retrieving the information returned by the
4016  * standard Unix call stat(). It takes the name of a file as parameter,
4017  * and return information about that file. This is not standard Rexx,
4018  * but quite useful. It is accessed through the built-in function
4019  * STREAM(), command 'FSTAT'
4020  * This is now also used for the "standard" STREAM() options.
4021  * *Persistent will be set to 1 if the stream's type is a file. The setting
4022  * happens on success and if Persistent isn't NULL.
4023  */
getstatus(tsd_t * TSD,const streng * filename,int subcommand)4024 static streng *getstatus( tsd_t *TSD, const streng *filename , int subcommand )
4025 {
4026    fileboxptr ptr=NULL ;
4027    int rc=0 ;
4028    int fno=0 ;
4029    rx_64 pos_read = -2L, pos_write = -2L, line_read = -2L, line_write = -2;
4030    int streamtype = STREAMTYPE_UNKNOWN;
4031    streng *result=NULL ;
4032    struct rx_stat_buf buffer ;
4033    struct tm tmdata, *tmptr ;
4034    char *fn=NULL;
4035 #if 0
4036    static const char *fmt = "%02d-%02d-%02d %02d:%02d:%02d" ;
4037    static const char *iso = "%04d-%02d-%02d %02d:%02d:%02d" ;
4038 #endif
4039    static const char *streamdesc[] = { "UNKNOWN", "PERSISTENT", "TRANSIENT" };
4040    char tmppwd[50];
4041    char tmpgrp[50];
4042    char *ptmppwd=tmppwd,*ptmpgrp=tmpgrp;
4043 #if !(defined(VMS) || defined(MAC) || defined(OS2) || defined(DOS) || (defined (__WATCOMC__) && !defined(__QNX__)) || defined(_MSC_VER) || (defined(WIN32) && defined(__IBMC__)) || defined(__MINGW32__) || defined(__BORLANDC__) || defined(__EPOC32__) || defined(__LCC__))
4044    struct passwd *ppwd;
4045    struct group *pgrp;
4046 #endif
4047 
4048    memset( &buffer, 0, sizeof(buffer) );
4049    /*
4050     * Nul terminate the input filename string, as stat() will barf if
4051     * it isn't and other functions stuff up!
4052     */
4053    fn = str_ofTSD(filename);
4054    /*
4055     * First get the Rexx file table entry associated with the file,
4056     * and then call stat() for that file. If the file is already open,
4057     * then call fstat, since that will in general be a 'safer' way
4058     * to be sure that it is _really_ the file that is open in Rexx.
4059     */
4060    ptr = getfileptr( TSD, filename ) ;
4061    if (ptr && ptr->fileptr)
4062    {
4063       fno = fileno( ptr->fileptr ) ;
4064       rc = rx_fstat( fno, &buffer ) ;
4065       if (ptr->flag & FLAG_PERSIST)
4066          streamtype = STREAMTYPE_PERSISTENT;
4067       else
4068          streamtype = STREAMTYPE_TRANSIENT;
4069       pos_read = ptr->readpos;
4070       pos_write = ptr->writepos;
4071       line_read = ptr->readline;
4072       line_write = ptr->writeline;
4073    }
4074    else
4075    {
4076       /*
4077        * To be consistent with other functions when determining persistence,
4078        * we need to check for a "regular" file. Everything other than a
4079        * "regular" file in transient.
4080        * If we don't have S_ISREG macro, then revert to a simple check; if the
4081        * stream is a directory it is transent; ugly!
4082        */
4083       rc = rx_stat( fn, &buffer ) ;
4084       if ( rc != 0 )
4085          streamtype = STREAMTYPE_UNKNOWN;
4086       else
4087       {
4088          /*
4089           * Resolves 802114.
4090           */
4091          streamtype = stream_types[determine_stream_type( buffer.st_mode )].streamtype;
4092       }
4093    }
4094 
4095    /*
4096     * If we were able to retrieve any useful information, store it
4097     * in a string of suitable length, and return that string.
4098     * If the filename does not exist, always return an empty string.
4099     */
4100    if ( rc == -1 )
4101    {
4102       if ( fn )
4103          FreeTSD( fn );
4104       checkProperStreamName( TSD, NULL, (const char *) tmpstr_of( TSD, filename ), errno );
4105       return nullstringptr();
4106    }
4107    switch ( subcommand )
4108    {
4109       case COMMAND_FSTAT:
4110 #ifdef HAVE_LSTAT
4111          /*
4112           * If we have lstat(), use it to gather details, this is the only
4113           * way to determine if the file is a symlink.
4114           */
4115          lstat(fn, &buffer) ;
4116 #endif
4117 #if defined(VMS) || defined(MAC) || defined(OS2) || defined(DOS) || (defined (__WATCOMC__) && !defined(__QNX__)) || defined(_MSC_VER) || (defined(WIN32) && defined(__IBMC__)) || defined(__MINGW32__) || defined(__BORLANDC__) || defined(__EPOC32__) || defined(__LCC__)
4118          ptmppwd = "USER";
4119          ptmpgrp = "GROUP";
4120 #else
4121          ppwd = getpwuid( buffer.st_uid );
4122          if ( ppwd )
4123             ptmppwd = ppwd->pw_name;
4124          else
4125             sprintf( tmppwd, "%d", buffer.st_uid );
4126 
4127          pgrp = getgrgid( buffer.st_gid );
4128          if ( pgrp )
4129             ptmpgrp = pgrp->gr_name;
4130          else
4131             sprintf( tmpgrp, "%d", buffer.st_gid );
4132 #endif
4133          result = Str_makeTSD( 128 ) ;
4134 #if defined(HAVE_I64U)
4135          sprintf( result->value,
4136             "%ld %ld %03o %d %s %s %I64u",
4137             (long)(buffer.st_dev), (long)(buffer.st_ino),
4138             buffer.st_mode & ACCESSPERMS, buffer.st_nlink,
4139             ptmppwd, ptmpgrp,
4140             buffer.st_size ) ;
4141 #else
4142          if ( sizeof(rx_64) > 4 )
4143             sprintf( result->value,
4144                "%ld %ld %03o %lu %s %s %ld",
4145                (long)(buffer.st_dev), (long)(buffer.st_ino),
4146                buffer.st_mode & ACCESSPERMS, buffer.st_nlink,
4147                ptmppwd, ptmpgrp,
4148                (rx_64)(buffer.st_size) ) ;
4149          else
4150             sprintf( result->value,
4151                "%ld %ld %03o %lu %s %s %ld",
4152                (long)(buffer.st_dev), (long)(buffer.st_ino),
4153                buffer.st_mode & ACCESSPERMS, buffer.st_nlink,
4154                ptmppwd, ptmpgrp,
4155                (long)(buffer.st_size) ) ;
4156 #endif
4157          /*
4158           * Append the stream type name...
4159           */
4160          strcat( result->value, stream_types[determine_stream_type( buffer.st_mode )].streamname );
4161          break;
4162       case COMMAND_QUERY_EXISTS:
4163          if ( streamtype == STREAMTYPE_TRANSIENT )
4164          {
4165             result = nullstringptr();
4166          }
4167          else
4168          {
4169             result = Str_makeTSD( REXX_PATH_MAX );
4170             my_fullpath( TSD, result->value, fn );
4171             result->len = strlen( result->value );
4172          }
4173          break;
4174       case COMMAND_QUERY_SIZE:
4175          if ( streamtype == STREAMTYPE_TRANSIENT )
4176          {
4177             result = nullstringptr() ;
4178          }
4179          else
4180          {
4181             result = Str_makeTSD( 50 ) ;
4182 #if defined(HAVE_I64U)
4183             sprintf( result->value, "%I64u", buffer.st_size ) ;
4184 #else
4185             if ( sizeof(rx_64) > 4 )
4186                sprintf( result->value, "%ld", (rx_64)(buffer.st_size) ) ;
4187             else
4188                sprintf( result->value, "%ld", (long)(buffer.st_size) ) ;
4189 #endif
4190          }
4191          break;
4192       case COMMAND_QUERY_HANDLE:
4193          if (fno)
4194          {
4195             result = Str_makeTSD( 10 ) ;
4196             sprintf( result->value, "%d", fno ) ;
4197          }
4198          else
4199             result = nullstringptr() ;
4200          break;
4201       case COMMAND_QUERY_STREAMTYPE:
4202          result = Str_makeTSD( 12 ) ;
4203          sprintf( result->value, "%s", streamdesc[streamtype] ) ;
4204          break;
4205       case COMMAND_QUERY_DATETIME:
4206          if ( streamtype == STREAMTYPE_TRANSIENT )
4207          {
4208             result = nullstringptr() ;
4209          }
4210          else
4211          {
4212             time_t num64;
4213             num64 = buffer.st_mtime;
4214             if ( ( tmptr = localtime( &num64 ) ) != NULL )
4215                tmdata = *tmptr;
4216             else
4217                memset(&tmdata,0,sizeof(tmdata)); /* what shall we do in this case? */
4218             result = Str_makeTSD( 20 ) ;
4219 #if 0
4220             sprintf( result->value, fmt, tmdata.tm_mon+1, tmdata.tm_mday,
4221                   (tmdata.tm_year % 100), tmdata.tm_hour, tmdata.tm_min,
4222                   tmdata.tm_sec ) ;
4223 #else
4224             strftime( result->value, 20, "%m-%d-%y %H:%M:%S", &tmdata );
4225 #endif
4226          }
4227          break;
4228       case COMMAND_QUERY_TIMESTAMP:
4229          if ( streamtype == STREAMTYPE_TRANSIENT )
4230          {
4231             result = nullstringptr() ;
4232          }
4233          else
4234          {
4235             time_t num64;
4236             num64 = buffer.st_mtime;
4237             if ( ( tmptr = localtime( &num64 ) ) != NULL )
4238                tmdata = *tmptr;
4239             else
4240                memset( &tmdata, 0, sizeof(tmdata) ); /* what shall we do in this case? */
4241             result = Str_makeTSD( 20 ) ;
4242 #if 0
4243             sprintf( result->value, iso, tmdata.tm_year+1900, tmdata.tm_mon+1,
4244                   tmdata.tm_mday,
4245                   tmdata.tm_hour, tmdata.tm_min,
4246                   tmdata.tm_sec ) ;
4247 #else
4248             strftime( result->value, 20, "%Y-%m-%d %H:%M:%S", &tmdata );
4249 #endif
4250          }
4251          break;
4252       case COMMAND_QUERY_CREATETIME:
4253          if ( streamtype == STREAMTYPE_TRANSIENT )
4254          {
4255             result = nullstringptr() ;
4256          }
4257          else
4258          {
4259             result = Str_makeTSD( 20 ) ;
4260 #if defined(HAVE_I64U)
4261             sprintf( result->value, "%I64u", buffer.st_ctime );
4262 #else
4263             if ( sizeof(off_t) > 4 )
4264                sprintf( result->value, "%lld", (long long) buffer.st_ctime );
4265             else
4266                sprintf( result->value, "%ld", (long) buffer.st_ctime);
4267 #endif
4268          }
4269          break;
4270       case COMMAND_QUERY_MODIFYTIME:
4271          if ( streamtype == STREAMTYPE_TRANSIENT )
4272          {
4273             result = nullstringptr() ;
4274          }
4275          else
4276          {
4277             result = Str_makeTSD( 20 ) ;
4278 #if defined(HAVE_I64U)
4279             sprintf( result->value, "%I64u", buffer.st_mtime );
4280 #else
4281             if ( sizeof(off_t) > 4 )
4282                sprintf( result->value, "%lld", (long long) buffer.st_mtime );
4283             else
4284                sprintf( result->value, "%ld", (long) buffer.st_mtime);
4285 #endif
4286          }
4287          break;
4288       case COMMAND_QUERY_ACCESSTIME:
4289          if ( streamtype == STREAMTYPE_TRANSIENT )
4290          {
4291             result = nullstringptr() ;
4292          }
4293          else
4294          {
4295             result = Str_makeTSD( 20 ) ;
4296 #if defined(HAVE_I64U)
4297             sprintf( result->value, "%I64u", buffer.st_atime );
4298 #else
4299             if ( sizeof(off_t) > 4 )
4300                sprintf( result->value, "%lld", (long long) buffer.st_atime );
4301             else
4302                sprintf( result->value, "%ld", (long) buffer.st_atime);
4303 #endif
4304          }
4305          break;
4306       case COMMAND_QUERY_POSITION_READ_CHAR:
4307       case COMMAND_QUERY_POSITION_SYS:
4308          if (pos_read != (-2))
4309          {
4310             result = Str_makeTSD( 50 ) ;
4311 #if defined(HAVE_I64U)
4312             sprintf( result->value, "%I64u", pos_read + 1 );
4313 #else
4314             if ( sizeof(off_t) > 4 )
4315                sprintf( result->value, "%ld", pos_read + 1 );
4316             else
4317                sprintf( result->value, "%ld", (long)(pos_read + 1) );
4318 #endif
4319          }
4320          else
4321             result = nullstringptr() ;
4322          break;
4323       case COMMAND_QUERY_POSITION_WRITE_CHAR:
4324          if (pos_write != (-2))
4325          {
4326             result = Str_makeTSD( 50 ) ;
4327 #if defined(HAVE_I64U)
4328             sprintf( result->value, "%I64u", pos_write + 1 );
4329 #else
4330             if ( sizeof(off_t) > 4 )
4331                sprintf( result->value, "%ld", pos_write + 1 );
4332             else
4333                sprintf( result->value, "%ld", (long)(pos_write + 1) );
4334 #endif
4335          }
4336          else
4337             result = nullstringptr() ;
4338          break;
4339       case COMMAND_QUERY_POSITION_READ_LINE:
4340          if (line_read != (-2))
4341          {
4342             result = Str_makeTSD( 50 ) ;
4343 #if defined(HAVE_I64U)
4344             sprintf( result->value, "%I64u", line_read );
4345 #else
4346             if ( sizeof(off_t) > 4 )
4347                sprintf( result->value, "%ld", line_read );
4348             else
4349                sprintf( result->value, "%ld", (long)line_read );
4350 #endif
4351          }
4352          else
4353             result = nullstringptr() ;
4354          break;
4355       case COMMAND_QUERY_POSITION_WRITE_LINE:
4356          if ( line_write == 0 )
4357          {
4358             rx_64 here;
4359             rx_64 char_count;
4360             int ch;
4361             /*
4362              * When a file is first opened for both read and
4363              * write (default for implicit open), it is inexpensive
4364              * to determine pos_read, pos_write and line_read, but
4365              * is very expensive to determine line_write, so we
4366              * don't do it. It is set to 0 indicating that we don't
4367              * know the current write position. So to reduce the
4368              * cost when we may never use it, we have to pay the
4369              * price the first time we need the value; this is
4370              * one of the times we pay the price!
4371              */
4372             result = Str_makeTSD( 50 ) ;
4373             /*
4374              * We can't use countlines(), so do our our counting
4375              * of lines form the beginning of the file to the current
4376              * write pos...
4377              */
4378             here = rx_ftell( ptr->fileptr );
4379             rx_fseek( ptr->fileptr, 0L, SEEK_SET );
4380             SWITCH_OPER_READ(ptr);
4381             for( char_count = 0, line_write = 0; char_count < (long) ptr->writepos; char_count++ )
4382             {
4383                ch = getc( ptr->fileptr );
4384                if ( ch == EOF )
4385                   break;
4386                if ( ch == REGINA_EOL )
4387                   line_write++;
4388             }
4389 #if defined(HAVE_I64U)
4390             sprintf( result->value, "%I64u", line_write + 1 );
4391 #else
4392             if ( sizeof(off_t) > 4 )
4393                sprintf( result->value, "%ld", line_write + 1 ) ;
4394             else
4395                sprintf( result->value, "%ld", (long)(line_write + 1) ) ;
4396 #endif
4397             rx_fseek( ptr->fileptr, here, SEEK_SET );
4398          }
4399          else if (line_write != (-2))
4400          {
4401             result = Str_makeTSD( 50 ) ;
4402 #if defined(HAVE_I64U)
4403             sprintf( result->value, "%I64u", line_write );
4404 #else
4405             if ( sizeof(off_t) > 4 )
4406                sprintf( result->value, "%ld", line_write ) ;
4407             else
4408                sprintf( result->value, "%ld", (long)line_write ) ;
4409 #endif
4410          }
4411          else
4412             result = nullstringptr() ;
4413          break;
4414    }
4415    result->len = strlen( result->value ) ;
4416 
4417    if ( fn )
4418       FreeTSD( fn );
4419    return result ;
4420 }
4421 
4422 
4423 /*
4424  * This little sweet routine returns information stored in the Rexx
4425  * file table entry about the named file 'filename'. It is perhaps more
4426  * of a debugging function than a Rexx function. It is accessed by the
4427  * Rexx built-in function STREAM(), command 'STATUS'. One of the nice
4428  * pieces of information this function returns is whether a file is
4429  * transient or persistent.
4430  *
4431  * This is really a simple function, just retrieve the Rexx file
4432  * table entry, and store the information in that entry into a string
4433  * and return that string.
4434  *
4435  * The difference between getrexxstatus() and getstatus() is that
4436  * that former returns information stored in Rexx's datastructures,
4437  * while the latter return information about the file stored in and
4438  * managed by the operating system
4439  */
getrexxstatus(const tsd_t * TSD,cfileboxptr ptr)4440 static streng *getrexxstatus( const tsd_t *TSD, cfileboxptr ptr )
4441 {
4442    streng *result=NULL ;
4443 
4444    if (ptr==NULL)
4445       return nullstringptr() ;
4446 
4447    result = Str_makeTSD(64) ;  /* Ought to be enough */
4448    result->value[0] = 0x00 ;
4449 
4450    if ((ptr->flag & FLAG_READ) && (ptr->flag & FLAG_WRITE))
4451       strcat( result->value, "READ/WRITE" ) ;
4452    else if (ptr->flag & FLAG_READ)
4453       strcat( result->value, "READ" ) ;
4454    else if (ptr->flag & FLAG_WRITE)
4455       strcat( result->value, "WRITE" ) ;
4456    else
4457       strcat( result->value, "NONE" ) ;
4458 
4459 #if defined(HAVE_I64U)
4460    sprintf( result->value + strlen(result->value),
4461             " READ: char=%I64u line=%I64u WRITE: char=%I64u line=%I64u %s",
4462             (ptr->readpos+1), ptr->readline,
4463             (ptr->writepos+1), ptr->writeline,
4464             (ptr->flag & FLAG_PERSIST) ? "PERSISTENT" : "TRANSIENT" ) ;
4465 #else
4466    if ( sizeof(rx_64) > 4 )
4467       sprintf( result->value + strlen(result->value),
4468             " READ: char=%ld line=%ld WRITE: char=%ld line=%ld %s",
4469             (ptr->readpos+1), ptr->readline,
4470             (ptr->writepos+1), ptr->writeline,
4471             (ptr->flag & FLAG_PERSIST) ? "PERSISTENT" : "TRANSIENT" ) ;
4472    else
4473       sprintf( result->value + strlen(result->value),
4474             " READ: char=%ld line=%ld WRITE: char=%ld line=%ld %s",
4475             (long)(ptr->readpos+1), (long)ptr->readline,
4476             (long)(ptr->writepos+1), (long)ptr->writeline,
4477             (ptr->flag & FLAG_PERSIST) ? "PERSISTENT" : "TRANSIENT" ) ;
4478 #endif
4479 
4480    result->len = strlen(result->value) ;
4481    return result ;
4482 }
4483 
4484 
4485 /*
4486  * This routine parses the remainder of the parameters passed to the
4487  * Stream(,'C','QUERY...') function.
4488  */
getquery(tsd_t * TSD,const streng * filename,const streng * subcommand)4489 static streng *getquery( tsd_t *TSD, const streng *filename , const streng *subcommand)
4490 {
4491    streng *result=NULL, *psub=NULL, *psubsub=NULL ;
4492    char oper = 0;
4493    char seek_oper = 0;
4494 
4495    /*
4496     * Get the subcommand to QUERY
4497     */
4498    oper = get_querycommand( subcommand );
4499    switch ( oper )
4500    {
4501       case COMMAND_QUERY_DATETIME :
4502       case COMMAND_QUERY_TIMESTAMP :
4503       case COMMAND_QUERY_CREATETIME :
4504       case COMMAND_QUERY_MODIFYTIME :
4505       case COMMAND_QUERY_ACCESSTIME :
4506       case COMMAND_QUERY_EXISTS :
4507       case COMMAND_QUERY_HANDLE :
4508       case COMMAND_QUERY_SIZE :
4509       case COMMAND_QUERY_STREAMTYPE :
4510          result = getstatus( TSD, filename, oper );
4511          break;
4512       case COMMAND_QUERY_SEEK :
4513       case COMMAND_QUERY_POSITION :
4514          if ( oper == COMMAND_QUERY_SEEK )
4515          {
4516             psub = Str_nodupTSD( subcommand, 4, subcommand->len - 4 );
4517             seek_oper = 1;
4518          }
4519          else
4520          {
4521             psub = Str_nodupTSD( subcommand, 8, subcommand->len - 8 );
4522             seek_oper = 0;
4523          }
4524          psub = Str_strp( psub, ' ', STRIP_LEADING);
4525          oper = get_querypositioncommand( psub );
4526          switch ( oper )
4527          {
4528             case COMMAND_QUERY_POSITION_SYS :
4529                result = getstatus(TSD, filename, oper );
4530                break;
4531             case COMMAND_QUERY_POSITION_READ :
4532                psubsub = Str_nodupTSD( psub, 4, psub->len - 4 );
4533                psubsub = Str_strp( psubsub, ' ', STRIP_LEADING);
4534                oper = get_querypositionreadcommand( psubsub );
4535                switch( oper )
4536                {
4537                   case COMMAND_QUERY_POSITION_READ_CHAR:
4538                   case COMMAND_QUERY_POSITION_READ_LINE:
4539                      result = getstatus( TSD, filename, oper );
4540                      break;
4541                   default:
4542                      exiterror( ERR_STREAM_COMMAND, 1, (seek_oper)?"QUERY SEEK READ":"QUERY POSITION READ", "CHAR LINE ''", tmpstr_of( TSD, psubsub ) )  ;
4543                      break;
4544                }
4545                break;
4546             case COMMAND_QUERY_POSITION_WRITE :
4547                psubsub = Str_nodupTSD( psub, 5, psub->len - 5 );
4548                psubsub = Str_strp( psubsub, ' ', STRIP_LEADING);
4549                oper = get_querypositionwritecommand( psubsub );
4550                switch( oper )
4551                {
4552                   case COMMAND_QUERY_POSITION_WRITE_CHAR:
4553                   case COMMAND_QUERY_POSITION_WRITE_LINE:
4554                      result = getstatus( TSD, filename, oper );
4555                      break;
4556                   default:
4557                      exiterror( ERR_STREAM_COMMAND, 1, (seek_oper)?"QUERY SEEK WRITE":"QUERY POSITION WRITE", "CHAR LINE ''", tmpstr_of( TSD, psubsub ) )  ;
4558                      break;
4559                }
4560                break;
4561             default:
4562                exiterror( ERR_STREAM_COMMAND, 1, (seek_oper)?"QUERY SEEK":"QUERY POSITION", "READ WRITE SYS", tmpstr_of( TSD, psub ) )  ;
4563                break;
4564          }
4565          Free_stringTSD(psub);
4566          break;
4567       default:
4568          exiterror( ERR_STREAM_COMMAND, 1, "QUERY", "DATETIME TIMESTAMP CREATETIME MODIFYTIME ACCESSTIME EXISTS HANDLE SIZE STREAMTYPE SEEK POSITION", tmpstr_of( TSD, subcommand ) )  ;
4569          break;
4570    }
4571 
4572    return result ;
4573 }
4574 
4575 /*
4576  * This routine parses the remainder of the parameters passed to the
4577  * Stream(,'C','OPEN...') function.
4578  */
getopen(tsd_t * TSD,const streng * filename,const streng * subcommand)4579 static streng *getopen( tsd_t *TSD, const streng *filename , const streng *subcommand)
4580 {
4581    fileboxptr ptr=NULL ;
4582    streng *result=NULL, *psub=NULL ;
4583    char oper = 0;
4584    char buf[20];
4585 
4586    /*
4587     * Get the subcommand to OPEN
4588     */
4589    oper = get_opencommand( subcommand );
4590    switch ( oper )
4591    {
4592       case COMMAND_OPEN_BOTH :
4593          if ( subcommand->len >= 4
4594          &&   memcmp(subcommand->value, "BOTH", 4) == 0 )
4595             psub = Str_nodupTSD( subcommand, 4, subcommand->len - 4 );
4596          else
4597             psub = Str_dupTSD( subcommand );
4598          psub = Str_strp( psub, ' ', STRIP_LEADING);
4599          oper = get_opencommandboth( psub );
4600          if ( TSD->restricted )
4601             exiterror( ERR_RESTRICTED, 4 )  ;
4602          switch ( oper )
4603          {
4604             case COMMAND_OPEN_BOTH :
4605                closefile( TSD, filename ) ;
4606                ptr = openfile( TSD, filename, ACCESS_WRITE ) ;
4607                break;
4608             case COMMAND_OPEN_BOTH_APPEND :
4609                closefile( TSD, filename ) ;
4610                ptr = openfile( TSD, filename, ACCESS_STREAM_APPEND ) ;
4611                break;
4612             case COMMAND_OPEN_BOTH_REPLACE :
4613                closefile( TSD, filename ) ;
4614                ptr = openfile( TSD, filename, ACCESS_STREAM_REPLACE ) ;
4615                break;
4616             default:
4617                exiterror( ERR_STREAM_COMMAND, 1, "OPEN BOTH", "APPEND REPLACE ''", tmpstr_of( TSD, psub ) )  ;
4618                break;
4619          }
4620          Free_stringTSD(psub);
4621          if (ptr->fileptr)
4622             result = Str_creTSD( "READY:" ) ;
4623          else
4624          {
4625             sprintf(buf,"ERROR:%d",errno);
4626             result = Str_creTSD( buf ) ;
4627          }
4628          break;
4629       case COMMAND_OPEN_READ :
4630          closefile( TSD, filename ) ;
4631          ptr = openfile( TSD, filename, ACCESS_READ ) ;
4632          if (ptr->fileptr)
4633             result = Str_creTSD( "READY:" ) ;
4634          else
4635          {
4636             sprintf(buf,"ERROR:%d",errno);
4637             result = Str_creTSD( buf ) ;
4638          }
4639          break;
4640       case COMMAND_OPEN_WRITE :
4641          if ( TSD->restricted )
4642             exiterror( ERR_RESTRICTED, 4 )  ;
4643          psub = Str_nodupTSD( subcommand, 5, subcommand->len - 5 );
4644          psub = Str_strp( psub, ' ', STRIP_LEADING);
4645          oper = get_opencommandwrite( psub );
4646 
4647          switch ( oper )
4648          {
4649             case COMMAND_OPEN_WRITE :
4650                closefile( TSD, filename ) ;
4651                ptr = openfile( TSD, filename, ACCESS_WRITE ) ;
4652                break;
4653             case COMMAND_OPEN_WRITE_APPEND :
4654                closefile( TSD, filename ) ;
4655                ptr = openfile( TSD, filename, ACCESS_STREAM_APPEND ) ;
4656                break;
4657             case COMMAND_OPEN_WRITE_REPLACE :
4658                closefile( TSD, filename ) ;
4659                ptr = openfile( TSD, filename, ACCESS_STREAM_REPLACE ) ;
4660                break;
4661             default:
4662                exiterror( ERR_STREAM_COMMAND, 1, "OPEN WRITE", "APPEND REPLACE ''", tmpstr_of( TSD, psub ) )  ;
4663                break;
4664          }
4665          Free_stringTSD(psub);
4666          if (ptr->fileptr)
4667             result = Str_creTSD( "READY:" ) ;
4668          else
4669          {
4670             sprintf(buf,"ERROR:%d",errno);
4671             result = Str_creTSD( buf ) ;
4672          }
4673          break;
4674       default:
4675          exiterror( ERR_STREAM_COMMAND, 1, "OPEN", "BOTH READ WRITE ''", tmpstr_of( TSD, subcommand ) )  ;
4676          break;
4677    }
4678 
4679    return result ;
4680 }
4681 
4682 
getseek(tsd_t * TSD,const streng * filename,const streng * cmd)4683 static streng *getseek( tsd_t *TSD, const streng *filename, const streng *cmd )
4684 {
4685 #define STATE_START 0
4686 #define STATE_WORD  1
4687 #define STATE_DELIM 2
4688    char *word[5] = {NULL,NULL,NULL,NULL};
4689    char *str;
4690    char *offset=NULL;
4691    int i,j=0;
4692    int state=STATE_START;
4693    int seek_by_line=0;
4694    int seek_type=0;
4695    int seek_sign=0;
4696    rx_64 seek_offset=0,pos=0;
4697    int pos_type=OPER_NONE,num_params=0;
4698    int str_start=0,str_end=(-1),words;
4699    fileboxptr ptr;
4700    streng *result=NULL;
4701    char buf[20];
4702 
4703    str = str_ofTSD(cmd);
4704    words = 4;
4705    for (i=0;i<Str_len(cmd);i++)
4706    {
4707       switch(state)
4708       {
4709          case STATE_START:
4710             if (*(str+i) == ' ')
4711             {
4712                state = STATE_DELIM;
4713                break;
4714             }
4715             if ( j < 3 )
4716                word[j] = str+str_start;
4717             j++;
4718             if (str_end != (-1))
4719             {
4720                *(str+str_end) = '\0';
4721             }
4722             state = STATE_WORD;
4723             break;
4724          case STATE_WORD:
4725             if (*(str+i) == ' ')
4726             {
4727                state = STATE_DELIM;
4728                str_end = i;
4729                str_start = str_end + 1;
4730                break;
4731             }
4732             break;
4733          case STATE_DELIM:
4734             state = STATE_WORD;
4735             if (*(str+i) == ' ')
4736             {
4737                state = STATE_DELIM;
4738             }
4739             if (state == STATE_WORD)
4740             {
4741                if ( j < 3 )
4742                   word[j] = str+str_start;
4743                j++;
4744                if (str_end != (-1))
4745                {
4746                   *(str+str_end) = '\0';
4747                }
4748             }
4749             break;
4750       }
4751    }
4752    num_params = j;
4753    if (num_params < 1)
4754       exiterror( ERR_INCORRECT_CALL, 922, "STREAM", 3, 2, num_params+1 );
4755    if (num_params > 3)
4756       exiterror( ERR_INCORRECT_CALL, 923, "STREAM", 3, 4, num_params+1 );
4757 
4758    switch( num_params )
4759    {
4760       case 3:
4761          if (strcmp(word[2],"CHAR") == 0)
4762             seek_by_line = 0;
4763          else
4764          {
4765             if (strcmp(word[2],"LINE") == 0)
4766                seek_by_line = 1;
4767             else
4768                exiterror( ERR_INCORRECT_CALL, 924, "STREAM", 3, "CHAR LINE", word[2] );
4769          }
4770       /* meant to fall through */
4771       case 2:
4772          /*
4773           * 2 params(to SEEK), last one (word[1]) could be READ/WRITE or CHAR/LINE
4774           */
4775          if (strcmp(word[1],"READ") == 0)
4776             pos_type = OPER_READ;
4777          else if (strcmp(word[1],"WRITE") == 0)
4778             pos_type = OPER_WRITE;
4779          else if (strcmp(word[1],"CHAR") == 0)
4780             seek_by_line = 0;
4781          else if (strcmp(word[1],"LINE") == 0)
4782             seek_by_line = 1;
4783          else
4784             exiterror( ERR_INCORRECT_CALL, 924, "STREAM", 3, "READ WRITE CHAR LINE", word[1] );
4785    }
4786    /*
4787     * Determine the position type if not supplied prior
4788     */
4789    if ( pos_type == OPER_NONE )
4790    {
4791       ptr = getfileptr( TSD, filename ) ;
4792       if ( ptr != NULL )
4793       {
4794          if ( ptr->flag & FLAG_READ )
4795             pos_type |= OPER_READ;
4796          if ( ptr->flag & FLAG_WRITE )
4797             pos_type |= OPER_WRITE;
4798       }
4799    }
4800    offset = word[0];
4801    switch(*offset)
4802    {
4803       case '=':
4804          seek_type = SEEK_SET;
4805          offset++;
4806          break;
4807       case '-':
4808          seek_type = SEEK_CUR;
4809          seek_sign = 1;
4810          offset++;
4811          break;
4812       case '+':
4813          seek_type = SEEK_CUR;
4814          seek_sign = 0;
4815          offset++;
4816          break;
4817       case '<':
4818          seek_type = SEEK_END;
4819          offset++;
4820          break;
4821       default:
4822          seek_type = SEEK_SET;
4823          break;
4824    }
4825    for (i=0;i<(int)strlen(offset);i++)
4826    {
4827       if (!rx_isdigit(*(offset+i)))
4828          exiterror( ERR_INCORRECT_CALL, 924, "STREAM", 3, "n, +n, -n, =n or <n", word[0] );
4829    }
4830 #if defined(HAVE_ATOLL)
4831    seek_offset = atoll(offset);
4832 #elif defined(HAVE__ATOI64)
4833    seek_offset = _atoi64(offset);
4834 #else
4835    seek_offset = atol(offset);
4836 #endif
4837    if (seek_sign) /* negative */
4838       seek_offset *= -1;
4839    ptr = get_file_ptr( TSD, filename, pos_type, (pos_type&OPER_WRITE) ? ACCESS_WRITE : ACCESS_READ ) ;
4840    if (!ptr)
4841    {
4842       sprintf(buf,"ERROR:%d",errno);
4843       result = Str_creTSD( buf ) ;
4844    }
4845    if (seek_by_line) /* position by line */
4846       pos = positionfile( TSD, "STREAM", 3, ptr, pos_type, seek_offset, seek_type ) ;
4847    else
4848       pos = positioncharfile( TSD, "STREAM", 3, ptr, pos_type, seek_offset, seek_type ) ;
4849    if ( pos >= 0 )
4850    {
4851       result = Str_makeTSD( 20 ) ; /* should be enough digits */
4852 #if defined(HAVE_I64U)
4853       sprintf(result->value, "%I64u", pos );
4854 #else
4855       if ( sizeof(rx_64) > 4 )
4856          sprintf(result->value, "%ld", pos );
4857       else
4858          sprintf(result->value, "%ld", (long)pos );
4859 #endif
4860       Str_len( result ) = strlen( result->value );
4861    }
4862    else
4863    {
4864       sprintf(buf,"ERROR:%d",errno);
4865       result = Str_creTSD( buf ) ;
4866    }
4867    FreeTSD(str);
4868    return result ;
4869 }
4870 
4871 
4872 
4873 /* ------------------------------------------------------------------- */
4874 /*                   Rexx builtin functions (level 3)                  */
4875 /*
4876  * This part consists of one function for each of the Rexx builtin
4877  * functions that operates on filesystem I/O
4878  */
4879 
4880 
4881 /*
4882  * This routine implements the Rexx built-in function CHARS(). It is
4883  * really quite simple, little more than a wrap-around to the
4884  * function calc_chars_left.
4885  */
std_chars(tsd_t * TSD,cparamboxptr parms)4886 streng *std_chars( tsd_t *TSD, cparamboxptr parms )
4887 {
4888    char opt = 'N';
4889    streng *string=NULL ;
4890    fileboxptr ptr=NULL ;
4891    int was_closed=0;
4892    rx_64 result=0 ;
4893    fil_tsd_t *ft;
4894 
4895    ft = (fil_tsd_t *)TSD->fil_tsd;
4896 
4897    /* Syntax: chars([filename]) */
4898    checkparam(  parms,  0,  2 , "CHARS" ) ;
4899 
4900    if (parms&&parms->next&&parms->next->value)
4901       opt = getoptionchar( TSD, parms->next->value, "CHARS", 2, "CN", "" ) ;
4902 
4903    string = (parms->value && parms->value->len) ? parms->value : ft->stdio_ptr[0]->filename0 ;
4904    /*
4905     * Get a pointer to the Rexx file table entry of the file, and
4906     * calculate the number of characters left.
4907     */
4908    ptr = getfileptr( TSD, string ) ;
4909    was_closed = (ptr==NULL) ;
4910    if (!ptr)
4911       ptr = get_file_ptr( TSD, string, OPER_READ, ACCESS_READ ) ;
4912 
4913    result = calc_chars_left( TSD, ptr ) ;
4914    if (was_closed)
4915       closefile( TSD, string ) ;
4916 
4917    return rx64_to_streng( TSD, result ) ;
4918 }
4919 
4920 
4921 
4922 /*
4923  * Implements the Rexx builtin function charin(). This function takes
4924  * three parameters, and they are treated pretty straight forward
4925  * according to TRL. If called with no start position, and a length of
4926  * zero, it may be used to do some fancy work (flushing I/O?), although
4927  * that is probably more needed for output :-)  Note that the file in
4928  * entered into the file table in this case, so it might be used to
4929  * explicitly open a file for reading. However, consider using stream()
4930  * to do this, it's a much cleaner approach!
4931  */
std_charin(tsd_t * TSD,cparamboxptr parms)4932 streng *std_charin( tsd_t *TSD, cparamboxptr parms )
4933 {
4934    streng *filename=NULL, *result=NULL ;
4935    fileboxptr ptr=NULL ;
4936    size_t length=0 ;
4937    rx_64 start=0 ;
4938    fil_tsd_t *ft;
4939 
4940    ft = (fil_tsd_t *)TSD->fil_tsd;
4941 
4942    /* Syntax: charin([filename][,[start][,length]]) */
4943    checkparam(  parms,  0,  3 , "CHARIN" ) ;
4944 
4945    /*
4946     * First, let's get the information about the file from the
4947     * file table, and open it in the correct mode if is not already
4948     * availble as such.
4949     */
4950    filename = (parms->value && parms->value->len) ? (parms->value) : ft->stdio_ptr[0]->filename0 ;
4951    ptr = get_file_ptr( TSD, filename, OPER_READ, ACCESS_READ ) ;
4952 
4953    /*
4954     * Then, get the starting point, or set it to zero.
4955     */
4956    parms = parms->next ;
4957    if ((parms)&&(parms->value))
4958       start = atoposrx64( TSD, parms->value, "CHARIN", 2 ) ;
4959    else
4960       start = 0 ;
4961 
4962    /*
4963     * At last, get the length, or use the default value one.
4964     */
4965    if (parms)
4966       parms = parms->next ;
4967 
4968    if ((parms)&&(parms->value))
4969       length = atozpos( TSD, parms->value, "CHARIN", 3 ) ;
4970    else
4971       length = 1 ;
4972 
4973    /*
4974     * Position current position in file if necessary
4975     */
4976    if (start)
4977       positioncharfile( TSD, "CHARIN", 2, ptr, OPER_READ, start, SEEK_SET ) ;
4978 
4979    if (length)
4980       result = readbytes( TSD, ptr, length, 0 ) ;
4981    else
4982    {
4983       if (!start)
4984          flush_input( ptr )  ;  /* Whatever happens ... */
4985       result = nullstringptr() ;
4986    }
4987 
4988    return result ;
4989 }
4990 
4991 
4992 
4993 /*
4994  * This function implements the Rexx built-in function CHAROUT(). It
4995  * is basically a wrap-around for the two functions that perform
4996  * character repositioning in a file; and writes out characters.
4997  */
4998 
std_charout(tsd_t * TSD,cparamboxptr parms)4999 streng *std_charout( tsd_t *TSD, cparamboxptr parms )
5000 {
5001    streng *filename=NULL, *string=NULL ;
5002    int length=0 ;
5003    long pos=0 ;
5004    fileboxptr ptr=NULL ;
5005    fil_tsd_t *ft;
5006 
5007    ft = (fil_tsd_t *)TSD->fil_tsd;
5008 
5009    if ( TSD->restricted )
5010       exiterror( ERR_RESTRICTED, 1, "CHAROUT" )  ;
5011 
5012    /* Syntax: charout([filename][,[string][,start]]) */
5013    checkparam(  parms,  0,  3 , "CHAROUT" ) ;
5014 
5015    filename = (parms->value && parms->value->len) ? (parms->value) : ft->stdio_ptr[1]->filename0 ;
5016 
5017    /* Read the data to be written, if any */
5018    parms = parms->next ;
5019    if (parms && parms->value )
5020       string = parms->value ;
5021    else
5022       string = NULL ;
5023 
5024    /* Read the position to start writing, is any */
5025    if (parms)
5026       parms = parms->next ;
5027 
5028    if ( parms && parms->value )
5029       pos = atopos( TSD, parms->value, "CHAROUT", 3 ) ;
5030    else
5031       pos = 0 ;
5032 
5033    ptr = get_file_ptr( TSD, filename, OPER_WRITE, ACCESS_WRITE ) ;
5034 
5035    /*
5036     * If we are to position the write position somewhere, do that first.
5037     */
5038    if (pos)
5039       positioncharfile( TSD, "CHAROUT", 3, ptr, OPER_WRITE, pos, SEEK_SET ) ;
5040 
5041    /*
5042     * Then, write the actual data, or flush output if neither data nor
5043     * position was given.
5044     */
5045    if (string)
5046       length = string->len - writebytes( TSD, ptr, string, 0 ) ;
5047    else
5048    {
5049       length = 0;
5050       if ( !pos )
5051       {
5052          /*
5053           * flush_output() will swap out the file and close it, but leave ALL positions
5054           * intact
5055           * We need to set the write positions to end of file (NOT EOF)
5056           * See ANSI 9.7.2, 9.7.5, A.5.8.9
5057           * For efficiency sake, we will have to set writeline = 0 :-(
5058           * We do this BEFORE flush_output() otherwise we won't have a ptr->fileptr!
5059           */
5060          if ( ptr->flag & FLAG_PERSIST )
5061          {
5062             rx_fseek( ptr->fileptr, 0, SEEK_END ) ;
5063             ptr->writepos = rx_ftell( ptr->fileptr ) ;
5064          }
5065          else
5066             ptr->writepos = 0;
5067          ptr->writeline = 0;
5068 
5069          if ( flush_output( TSD, ptr ) == -1 )
5070             length = 1; /* simulate (at least) 1 byte not written */
5071       }
5072    }
5073 
5074    return int_to_streng( TSD, length ) ;
5075 }
5076 
5077 
5078 
5079 /*
5080  * Simple routine that implements the Rexx built-in function LINES().
5081  * Really just a wrap-around to the countlines() routine.
5082  */
5083 
std_lines(tsd_t * TSD,cparamboxptr parms)5084 streng *std_lines( tsd_t *TSD, cparamboxptr parms )
5085 {
5086    char opt = 'N';
5087    fileboxptr ptr=NULL ;
5088    streng *filename=NULL ;
5089    int was_closed=0;
5090    rx_64 result=0 ;
5091    int actual;
5092    fil_tsd_t *ft;
5093 
5094    ft = (fil_tsd_t *)TSD->fil_tsd;
5095 
5096    /* Syntax: lines([filename][,C|N]) */
5097    checkparam(  parms,  0,  2 , "LINES" ) ;
5098 
5099    if (parms&&parms->next&&parms->next->value)
5100       opt = getoptionchar( TSD, parms->next->value, "LINES", 2, "CN", "" ) ;
5101 
5102    /*
5103     * Get the name of the file (use defaults if necessary), and get
5104     * a pointer to the entry of that file from the file table
5105     */
5106    if (parms->value
5107    &&  parms->value->len)
5108       filename = parms->value ;
5109    else
5110       filename = ft->stdio_ptr[0]->filename0 ;
5111 
5112    /*
5113     * Try to get the Rexx file table entry, if it doesn't work, then
5114     * try again ... and a bit harder
5115     */
5116    ptr = getfileptr( TSD, filename ) ;
5117    was_closed = (ptr==NULL) ;
5118    if (!ptr)
5119       ptr = get_file_ptr( TSD, filename, OPER_READ, ACCESS_READ ) ;
5120 
5121    /*
5122     * If 'C' is passed, we ALWAYS get the actual number of lines
5123     * If 'N' is passed (or unset), we get the actual number of lines
5124     * unless FAST_LINES_BIF is set (default is set)
5125     * Bug: 1000227
5126     */
5127    if ( opt == 'C' )
5128       actual = 1;
5129    else
5130    {
5131       if ( get_options_flag( TSD->currlevel, EXT_FAST_LINES_BIF_DEFAULT ) )
5132          actual = 0;
5133       else
5134          actual = 1;
5135    }
5136    result = countlines( TSD, ptr, actual, OPER_READ ) ;
5137 
5138    if (was_closed)
5139       closefile( TSD, filename ) ;
5140 
5141 
5142    return rx64_to_streng( TSD, result ) ;
5143 }
5144 
5145 
5146 
5147 /*
5148  * The Rexx built-in function LINEIN() reads a line from a file.
5149  * The actual reading is performed in 'readoneline', while this routine
5150  * takes care of range checking of parameters, and decides which
5151  * lower level routines to call.
5152  */
5153 
std_linein(tsd_t * TSD,cparamboxptr parms)5154 streng *std_linein( tsd_t *TSD, cparamboxptr parms )
5155 {
5156    streng *filename=NULL, *res=NULL ;
5157    fileboxptr ptr=NULL ;
5158    int count=0, line=0 ;
5159    fil_tsd_t *ft;
5160 
5161    ft = (fil_tsd_t *)TSD->fil_tsd;
5162 
5163    /* Syntax: linein([filename][,[line][,count]]) */
5164    checkparam(  parms,  0,  3 , "LINEIN" ) ;
5165 
5166    /*
5167     * First get the name of the file, or use the appropriate default
5168     */
5169    if (parms->value
5170    &&  parms->value->len)
5171       filename = parms->value ;
5172    else
5173       filename = ft->stdio_ptr[0]->filename0 ;
5174 
5175    /*
5176     * Then get the line number at which the read it to start, or set
5177     * set it to zero if none was specified.
5178     */
5179    if (parms)
5180       parms = parms->next ;
5181 
5182    if (parms && parms->value)
5183       line = atopos( TSD, parms->value, "LINEIN", 2 ) ;
5184    else
5185       line = 0 ;  /* Illegal value */
5186 
5187    /*
5188     * And at last, read the count, which can be only 0 or 1, and which
5189     * is the number of lines to read.
5190     */
5191    if (parms)
5192       parms = parms->next ;
5193 
5194    if (parms && parms->value)
5195    {
5196       count = atozpos( TSD, parms->value, "LINEIN", 3 ) ;
5197       if (count!=0 && count!=1)
5198          exiterror( ERR_INCORRECT_CALL, 39, "LINEIN", tmpstr_of( TSD, parms->value ) )  ;
5199    }
5200    else
5201       count = 1 ;  /* The default */
5202 
5203    /*
5204     * Now, get the pointer to the entry in the file table that contains
5205     * information about this file, or make it automatically create
5206     * an entry if one didn't exist.
5207     */
5208    ptr = get_file_ptr( TSD, filename, OPER_READ, ACCESS_READ ) ;
5209 
5210    /*
5211     * If line was specified, we must reposition the current read
5212     * position of the file.
5213     */
5214    if (line)
5215       positionfile( TSD, "LINEIN", 2, ptr, OPER_READ, line, SEEK_SET ) ;
5216 
5217    /*
5218     * As the last thing, read in the data. If no data was wanted, skip it
5219     * but call flushing if line wasn't specified either.
5220     */
5221    if (count)
5222       res = readoneline( TSD, ptr ) ;
5223    else
5224    {
5225       if (!line)
5226          flush_input( ptr ) ;
5227       res = nullstringptr() ;
5228    }
5229 
5230    return res ;
5231 }
5232 
5233 
5234 
5235 
5236 /*
5237  * This function is a wrap-around for the Rexx built-in function
5238  * LINEOUT(). It performs parameter checking and decides which lower
5239  * level routines to call.
5240  */
5241 
std_lineout(tsd_t * TSD,cparamboxptr parms)5242 streng *std_lineout( tsd_t *TSD, cparamboxptr parms )
5243 {
5244    streng *string=NULL, *file=NULL ;
5245    int lineno=0, result=0 ;
5246    fileboxptr ptr=NULL ;
5247    fil_tsd_t *ft;
5248 
5249    ft = (fil_tsd_t *)TSD->fil_tsd;
5250 
5251    if ( TSD->restricted )
5252       exiterror( ERR_RESTRICTED, 1, "LINEOUT" )  ;
5253 
5254    /* Syntax: lineout([filename][,[string][,line]]) */
5255    checkparam(  parms,  0,  3 , "LINEOUT" ) ;
5256 
5257    /*
5258     * First get the pointer for the file to operate on. If omitted,
5259     * use the standard output stream
5260     */
5261    if (parms->value
5262    &&  parms->value->len)
5263       file = parms->value ;
5264    else
5265       file = ft->stdio_ptr[1]->filename0 ;
5266    /*
5267     * The file pointer is needed in ALL circumstances!
5268     */
5269    ptr = get_file_ptr( TSD, file, OPER_WRITE, ACCESS_WRITE ) ;
5270 
5271    /*
5272     * Then, get the data to be written, if any.
5273     */
5274    if (parms)
5275       parms = parms->next ;
5276 
5277    if (parms && parms->value)
5278       string = parms->value ;
5279    else
5280       string = NULL ;
5281 
5282    /*
5283     * At last, we must find the line number of the file to write. We
5284     * must position the file at this line before the write.
5285     */
5286    if (parms)
5287       parms = parms->next ;
5288 
5289    if (parms && parms->value)
5290       lineno = atopos( TSD, parms->value, "LINEOUT", 3 ) ;
5291    else
5292       lineno = 0 ;  /* illegal value */
5293 
5294    /*
5295     * First, let's reposition the file if necessary.
5296     */
5297    if (lineno)
5298       positionfile( TSD, "LINEOUT", 2, ptr, OPER_WRITE, lineno, SEEK_SET ) ;
5299 
5300    /*
5301     * And then, we write out the data. If there are not data, it may have
5302     * been just positioning. However, if there are neither data nor
5303     * a linenumber, something magic may happen.
5304     */
5305    if (string)
5306       result = writeoneline( TSD, ptr, string ) ;
5307    else
5308    {
5309       if ( !lineno )
5310       {
5311          /*
5312           * flush_output() will swap out the file and close it, but leave ALL positions
5313           * intact
5314           * We need to set the write positions to end of file (NOT EOF)
5315           * See ANSI 9.7.2, 9.7.5, A.5.8.9
5316           * For efficiency sake, we will have to set writeline = 0 :-(
5317           * We do this BEFORE flush_output() otherwise we won't have a ptr->fileptr!
5318           */
5319          if ( ptr->flag & FLAG_PERSIST )
5320          {
5321             /*
5322              * When using LINEOUT() to close a file and that file is NOT writeable, ptr->fileptr
5323              * gets set to NULL, so guard against this
5324              */
5325             if ( ptr->fileptr )
5326             {
5327                rx_fseek( ptr->fileptr, 0, SEEK_END ) ;
5328                ptr->writepos = rx_ftell( ptr->fileptr ) ;
5329             }
5330          }
5331          else
5332             ptr->writepos = 0;
5333          ptr->writeline = 0;
5334          /*
5335           * ANSI states that a file is not necessarily closed in this case.
5336           * Position of file pointers is explicitly stated in ANSI and if the
5337           * file is NOT closed they cause breakage.
5338           * Therefore implement ANSI in STRICT_ANSI mode and normal behaviour
5339           * (that does not cause breakage) in "regina" mode.
5340           * MH 22/06/2004 - after non-conclusive discussions on ANSI mailing list
5341           */
5342          if ( get_options_flag( TSD->currlevel, EXT_STRICT_ANSI ) )
5343          {
5344             flush_output( TSD, ptr );
5345          }
5346          else
5347          {
5348             closefile( TSD, file ) ;
5349          }
5350       }
5351       result = 0;
5352    }
5353 
5354    return int_to_streng( TSD, result ) ;
5355 }
5356 
5357 
5358 
5359 
5360 /*
5361  * This function checks whether a particular file is accessable by
5362  * the user in a certain mode, which may be read, write or execute.
5363  * Unfortunately, this function differs a bit from the functionality
5364  * of several others. It explicitly checks a file, so that if the
5365  * file didn't exist in advance, it is _not_ opened. And even _if_
5366  * the file existed, the file in the file system is checked, not the
5367  * file opened by Regina. The two may differ slightly under certain
5368  * circumstances.
5369  */
5370 
is_accessable(const tsd_t * TSD,const streng * filename,int mode)5371 static int is_accessable( const tsd_t *TSD, const streng *filename, int mode )
5372 {
5373    int res=0 ;
5374    char *fn ;
5375 
5376    fn = str_ofTSD( filename ) ;
5377    /*
5378     * First, call access() with the 'correct' parameters, and store
5379     * the result in 'res'. If 'mode' had an "impossible" value, give
5380     * an error.
5381     */
5382 #if defined(WIN32) && ( defined(__IBMC__) || defined(__LCC__) )
5383 {
5384    DWORD Attrib;
5385    res=-1;
5386    Attrib=GetFileAttributes(fn);
5387    if (Attrib==(DWORD)-1)
5388       exiterror( ERR_INTERPRETER_FAILURE, 1, __FILE__, __LINE__, "" )  ;
5389    if ((Attrib&FILE_ATTRIBUTE_DIRECTORY)!=FILE_ATTRIBUTE_DIRECTORY)
5390    {
5391       if ((mode == COMMAND_READABLE) && ((Attrib&FILE_ATTRIBUTE_READONLY)==FILE_ATTRIBUTE_READONLY))
5392          res = 0 ;
5393       else if ((mode == COMMAND_WRITEABLE) || (mode == COMMAND_EXECUTABLE))
5394          res = 0 ;
5395    }
5396 }
5397 #else
5398    if (mode == COMMAND_READABLE)
5399       res = access( fn, R_OK ) ;
5400    else if (mode == COMMAND_WRITEABLE)
5401       res = access( fn, W_OK ) ;
5402    else if (mode == COMMAND_EXECUTABLE)
5403       res = access( fn, X_OK ) ;
5404    else
5405       exiterror( ERR_INTERPRETER_FAILURE, 1, __FILE__, __LINE__, "" )  ;
5406 #endif
5407 
5408    /*
5409     * Perhaps we should analyze the output a bit before returning?
5410     * If res==EACCES, that is not really an error, while other errno
5411     * code _do_ signify an error. However ... since the return code
5412     * a boolean variable, just return it.
5413     */
5414    FreeTSD(fn) ;
5415    return (res==0) ;
5416 }
5417 
5418 
5419 
5420 /*
5421  * This little function implements the RESET command of the Rexx
5422  * built-in function STREAM(). Basically, most of the job is done in
5423  * the function 'fixup_file()'. Except from removing the ERROR flag.
5424  * The 'fixup_file()' function is intended for fixing the file at the
5425  * start of a condition handler for the NOTREADY condition.
5426  *
5427  * The value returned from this function is either "READY" or "UNKNOWN",
5428  * and reflects the philosophy that the file _is_ fixed, unless it
5429  * is impossible to open it. Of course, that may be a false READY,
5430  * since the actual _problem_ might not have been fixed, but at least
5431  * you have another try at the problem.
5432  */
5433 
reset_file(tsd_t * TSD,fileboxptr fileptr)5434 static streng *reset_file( tsd_t *TSD, fileboxptr fileptr )
5435 {
5436    if (!fileptr)
5437       return nullstringptr() ;
5438 
5439    fixup_file( TSD, fileptr->filename0 ) ;
5440    fileptr->flag &= ~(FLAG_ERROR | FLAG_FAKE) ;
5441 
5442    if (fileptr->fileptr)
5443       return Str_creTSD( "READY" ) ;   /* Per definition */
5444    else
5445       return Str_creTSD( "UNKNOWN" ) ;
5446 }
5447 
5448 
5449 
5450 /*
5451  * The built-in function STREAM() is new in TRL2. It is supposed to be
5452  * a sort of all-round function for just about anything having to do with
5453  * files. The details of its specification in TRL2 leaves a lot of room
5454  * for the implementors. Two of the options to this command -- the Status
5455  * and Description options are treated as defined by TRL, the Command
5456  * option takes several command, defined by the COMMAND_ macros.
5457  */
std_stream(tsd_t * TSD,cparamboxptr parms)5458 streng* std_stream( tsd_t *TSD, cparamboxptr parms )
5459 {
5460    char oper=' ' ;
5461    streng *command=NULL, *result=NULL, *filename=NULL, *psub=NULL ;
5462    fileboxptr ptr=NULL ;
5463 
5464    /* Syntax: stream(filename[,[oper][,command ...]]) */
5465    if ((!parms)||(!parms->value))
5466       exiterror( ERR_INCORRECT_CALL, 5, "STREAM", 1 )  ;
5467    checkparam(  parms,  1,  3 , "STREAM" ) ;
5468 
5469    /*
5470     * Get the filepointer to Rexx's file table, but make sure that the
5471     * file is not in any way created if it didn't exist.
5472     */
5473    filename = Str_dupstrTSD( parms->value );
5474    ptr = getfileptr( TSD, filename ) ;
5475    /*
5476     * Read the 'operation'. This is really just an 'option'. The
5477     * default option is 'S'.
5478     */
5479    parms = parms->next ;
5480    if (parms && parms->value)
5481       oper = getoptionchar( TSD, parms->value, "STREAM", 2, "CSD", "" ) ;
5482    else
5483       oper = 'S' ;
5484 
5485    /*
5486     * If the operation was 'C', we _must_ have a third parameter, on the
5487     * other hand, if it was not 'C', we must never have a third parameter.
5488     * Make sure that these rules are followed.
5489     */
5490    command = NULL ;
5491    if (oper=='C')
5492    {
5493       parms = parms->next ;
5494       if (parms && parms->value)
5495          command = parms->value ;
5496       else
5497           exiterror( ERR_INCORRECT_CALL, 3, "STREAM", 3 )  ;
5498    }
5499    else
5500       if (parms && parms->next && parms->next->value)
5501           exiterror( ERR_INCORRECT_CALL, 4, "STREAM", 2 )  ;
5502 
5503    /*
5504     * Here comes the main loop.
5505     */
5506    result = NULL ;
5507    switch ( oper )
5508    {
5509       case 'C':
5510          /*
5511           * Read the command, and 'translate' it into an integer which
5512           * describes it, see the implementation of get_command(), and
5513           * the COMMAND_ macros. The first of these are rather simple,
5514           * in fact, they could probably be compressed to save some
5515           * space.
5516           */
5517          command = Str_strp( command, ' ', STRIP_BOTH );
5518          oper = get_command( command ) ;
5519          switch(oper)
5520          {
5521             case COMMAND_READ:
5522                closefile( TSD, filename ) ;
5523                ptr = openfile( TSD, filename, ACCESS_READ ) ;
5524                break;
5525             case COMMAND_WRITE:
5526                closefile( TSD, filename ) ;
5527                ptr = openfile( TSD, filename, ACCESS_WRITE ) ;
5528                break;
5529             case COMMAND_APPEND:
5530                closefile( TSD, filename ) ;
5531                ptr = openfile( TSD, filename, ACCESS_APPEND ) ;
5532                break;
5533             case COMMAND_UPDATE:
5534                closefile( TSD, filename ) ;
5535                ptr = openfile( TSD, filename, ACCESS_UPDATE ) ;
5536                break;
5537             case COMMAND_CREATE:
5538                closefile( TSD, filename ) ;
5539                ptr = openfile( TSD, filename, ACCESS_CREATE ) ;
5540                break;
5541             case COMMAND_CLOSE:
5542                /*
5543                 * The file is always unknown after is has been closed. Does
5544                 * that sound convincing, or does it sound like I didn't feel
5545                 * to implement the rest of this ... ?
5546                 */
5547                closefile( TSD, filename ) ;
5548                result = Str_creTSD( "UNKNOWN" ) ;
5549                break ;
5550             case COMMAND_FLUSH:
5551                /*
5552                 * Flush the file. Actually, this might not be needed, since
5553                 * the functions that write out data may contain explicit
5554                 * calls to fflush()
5555                 */
5556                ptr = getfileptr( TSD, filename ) ;
5557                if (ptr && ptr->fileptr)
5558                {
5559                   errno = 0 ;
5560                   if (fflush( ptr->fileptr))
5561                   {
5562                      file_error( ptr, errno, NULL ) ;
5563                      result = Str_creTSD( "ERROR" ) ;
5564                   }
5565                   else
5566                      result = Str_creTSD( "READY" ) ;
5567                }
5568                else if (ptr)
5569                   result = Str_creTSD( "ERROR" ) ;
5570                else
5571                   result = Str_creTSD( "UNKNOWN" ) ;
5572                break ;
5573             case COMMAND_STATUS:
5574                ptr = getfileptr( TSD, filename ) ;
5575                result = getrexxstatus( TSD, ptr ) ;
5576                break;
5577             case COMMAND_FSTAT:
5578                result = getstatus( TSD, filename, COMMAND_FSTAT );
5579                break;
5580             case COMMAND_RESET:
5581                ptr = getfileptr( TSD, filename ) ;
5582                result = reset_file( TSD, ptr ) ;
5583                break;
5584             case COMMAND_READABLE:
5585             case COMMAND_WRITEABLE:
5586             case COMMAND_EXECUTABLE:
5587                result = int_to_streng( TSD, is_accessable( TSD, filename, oper )) ;
5588                break;
5589             case COMMAND_QUERY:
5590                /*
5591                 * We have to further parse the remainder of the command
5592                 * to determine what sub-command has been passed.
5593                 */
5594                psub = Str_nodupTSD( command , 5, command->len - 5);
5595                psub = Str_strp( psub, ' ', STRIP_LEADING);
5596                result = getquery( TSD, filename, psub ) ;
5597                Free_stringTSD(psub);
5598                break;
5599             case COMMAND_OPEN:
5600                /*
5601                 * We have to further parse the remainder of the command
5602                 * to determine what sub-command has been passed.
5603                 */
5604                psub = Str_nodupTSD( command , 4, command->len - 4);
5605                psub = Str_strp( psub, ' ', STRIP_LEADING);
5606                result = getopen( TSD, filename, psub ) ;
5607                Free_stringTSD(psub);
5608                break;
5609             case COMMAND_SEEK:
5610                psub = Str_nodupTSD( command , 4, command->len - 4);
5611                psub = Str_strp( psub, ' ', STRIP_LEADING);
5612                result = getseek( TSD, filename, psub ) ;
5613                Free_stringTSD(psub);
5614                break;
5615             case COMMAND_POSITION:
5616                psub = Str_nodupTSD( command , 8, command->len - 8);
5617                psub = Str_strp( psub, ' ', STRIP_LEADING);
5618                result = getseek( TSD, filename, psub ) ;
5619                Free_stringTSD(psub);
5620                break;
5621             default:
5622                exiterror( ERR_STREAM_COMMAND, 3, "CLOSE FLUSH OPEN POSITION QUERY SEEK", tmpstr_of( TSD, command ) )  ;
5623                break;
5624          }
5625          break ;
5626 
5627       case 'D':
5628          /*
5629           * Get a description of the most recent error for this file
5630           */
5631          if (ptr)
5632          {
5633             if (ptr->errmsg)
5634                result = Str_dupTSD(ptr->errmsg) ;
5635             else if (ptr->error)
5636                result = Str_creTSD( strerror(ptr->error) ) ;
5637          }
5638          break ;
5639 
5640       case 'S':
5641          /*
5642           * Get a simple status for the file in question. If the file
5643           * doesn't exist in Rexx's tables, UNKNOWN is returned. If the
5644           * file is in error state, return ERROR, else return READY,
5645           * unless current read position is at EOF, in which case
5646           * NOTREADY is return. Note that ERROR and NOTREADY are the
5647           * two states that will raise the NOTREADY condition.
5648           */
5649          if (ptr)
5650          {
5651             if (ptr->flag & FLAG_ERROR)
5652             {
5653                result = Str_creTSD( "ERROR" ) ;
5654             }
5655 #if 1 /* really MH */
5656             else if (ptr->flag & FLAG_AFTER_RDEOF)
5657             {
5658                result = Str_creTSD( "NOTREADY" ) ;
5659             }
5660 #else
5661             else if (ptr->flag & FLAG_RDEOF)
5662             {
5663                result = Str_creTSD( "NOTREADY" ) ;
5664             }
5665 #endif
5666             else
5667             {
5668                result = Str_creTSD( "READY" ) ;
5669             }
5670          }
5671          else
5672             result = Str_creTSD( "UNKNOWN" ) ;
5673 
5674          break ;
5675 
5676       default:
5677          exiterror( ERR_INTERPRETER_FAILURE, 1, __FILE__, __LINE__, "" )  ;
5678    }
5679 
5680    if (result==NULL)
5681       result = nullstringptr() ;
5682 
5683    Free_stringTSD(filename);
5684    return result ;
5685 }
5686 
5687 
5688 
5689 /*
5690  * This routine will traverse the list of open files, and dump relevant
5691  * information about each of them. Really a debugging routine. It is
5692  * not available when Regina is compiled with optimalization.
5693  */
5694 #ifndef NDEBUG
dbg_dumpfiles(tsd_t * TSD,cparamboxptr parms)5695 streng *dbg_dumpfiles( tsd_t *TSD, cparamboxptr parms )
5696 {
5697    fileboxptr ptr ;
5698    int i=0, fno ;
5699    char string[11] ;
5700    fil_tsd_t *ft;
5701    char opt='N' ;
5702    int slot;
5703 
5704    ft = (fil_tsd_t *)TSD->fil_tsd;
5705 
5706    checkparam(  parms,  0,  2 , "DUMPFILES" ) ;
5707 
5708    if ( parms && parms->value )
5709       opt = getoptionchar( TSD, parms->value, "DUMPFILES", 1, "LN", "" ) ;
5710 
5711    if ( parms )
5712       parms = parms->next ;
5713 
5714    if ( parms && parms->value )
5715       slot = atopos( TSD, parms->value, "DUMPFILES", 2 ) ;
5716    else
5717       slot = -1 ;  /* dump all slots */
5718 
5719    if (TSD->stddump == NULL)
5720       return nullstringptr() ;
5721 
5722    if ( opt == 'N' )
5723    {
5724       fprintf(TSD->stddump,
5725         "                                                Read      Write\n" ) ;
5726       fprintf(TSD->stddump,
5727         "File Filename                       Flags    line char  line char\n");
5728 
5729       for ( ptr = ft->mrufile; ptr ; ptr = ptr->older )
5730       {
5731          fno = fileno( ptr->fileptr ) ;
5732          fprintf( TSD->stddump,"%4d %-30s", fno, ptr->filename0->value);
5733          i = 0 ;
5734 
5735          string[0] = (char) (( ptr->flag & FLAG_READ     ) ? 'r' : ' ') ;
5736          string[1] = (char) (( ptr->flag & FLAG_WRITE    ) ? 'w' : ' ') ;
5737          string[2] = (char) (( ptr->flag & FLAG_PERSIST  ) ? 'p' : 't') ;
5738          string[3] = (char) (( ptr->flag & FLAG_RDEOF    ) ? 'R' : ' ') ;
5739          string[4] = (char) (( ptr->flag & FLAG_AFTER_RDEOF    ) ? 'A' : ' ') ;
5740          string[5] = (char) (( ptr->flag & FLAG_WREOF    ) ? 'W' : ' ') ;
5741          string[6] = (char) (( ptr->flag & FLAG_SURVIVOR ) ? 'S' : ' ') ;
5742          string[7] = (char) (( ptr->flag & FLAG_ERROR    ) ? 'E' : ' ') ;
5743          string[8] = (char) (((ptr->flag & FLAG_FAKE) && (ptr->flag & FLAG_ERROR)   ) ? 'F' : ' ') ;
5744          string[9] = 0x00 ;
5745 
5746 #if defined(HAVE_I64U)
5747          fprintf( TSD->stddump, " %8s %4I64u %4I64u  %4I64u %4I64u\n", string,
5748                 ptr->readline, ptr->readpos,
5749                 ptr->writeline,ptr->writepos ) ;
5750 #else
5751             if ( sizeof(rx_64) > 4 )
5752                fprintf( TSD->stddump, " %8s %4lld %4lld  %4lld %4lld\n", string,
5753                       ptr->readline, ptr->readpos,
5754                       ptr->writeline,ptr->writepos ) ;
5755             else
5756                fprintf( TSD->stddump, " %8s %4ld %4ld  %4ld %4ld\n", string,
5757                       (long)(ptr->readline), (long)(ptr->readpos),
5758                       (long)(ptr->writeline),(long)(ptr->writepos) ) ;
5759 #endif
5760          if (ptr->flag & FLAG_ERROR)
5761          {
5762             if (ptr->errmsg)
5763                fprintf(TSD->stddump, "     ==> %s\n", ptr->errmsg->value ) ;
5764             else if (ptr->error)
5765                fprintf(TSD->stddump, "     ==> %s\n", strerror( ptr->error )) ;
5766          }
5767       }
5768       fprintf( TSD->stddump,"  r=read, w=write, p=persistent, t=transient, e=eof\n");
5769       fprintf( TSD->stddump,"  R=read-eof, W=write-eof, S=special, E=error, F=fake\n");
5770    }
5771    else
5772    {
5773       int start, end;
5774       if ( slot == -1 )
5775       {
5776          start = 0;
5777          end = 131;
5778       }
5779       else
5780       {
5781          start = slot;
5782          end = start + 1;
5783       }
5784       for ( i = start; i < end; i++ )
5785       {
5786          ptr = ft->filehash[i];
5787          if ( ptr )
5788          {
5789             fno = 0;
5790             if ( ptr->fileptr )
5791                fno = fileno( ptr->fileptr ) ;
5792             fprintf( TSD->stddump,"Slot: %3d: %4d %8lx prev: %8lx next: %8lx %-30s\n",
5793                i, fno, (long)ptr, (long)ptr->prev, (long)ptr->next, ptr->filename0->value);
5794             for ( ptr = ptr->next; ptr; ptr = ptr->next )
5795             {
5796                fno = 0;
5797                if ( ptr->fileptr )
5798                   fno = fileno( ptr->fileptr ) ;
5799                fprintf( TSD->stddump,"           %4d %8lx prev: %8lx next: %8lx %-30s\n",
5800                   fno, (long)ptr, (long)ptr->prev, (long)ptr->next, ptr->filename0->value );
5801             }
5802          }
5803          else
5804          {
5805             fprintf( TSD->stddump,"Slot: %3d is empty\n", i );
5806          }
5807       }
5808    }
5809 
5810    return nullstringptr() ;
5811 }
5812 #endif
5813 
5814 
5815 
5816 
5817 /*
5818  * Read from stdin using readoneline()
5819  */
readkbdline(tsd_t * TSD)5820 streng *readkbdline( tsd_t *TSD )
5821 {
5822    fil_tsd_t *ft;
5823 
5824    ft = (fil_tsd_t *)TSD->fil_tsd;
5825    return readoneline( TSD, ft->stdio_ptr[DEFAULT_STDIN_INDEX] );
5826 }
5827 
addr_reopen_file(tsd_t * TSD,const streng * filename,char code,int iserror)5828 void *addr_reopen_file( tsd_t *TSD, const streng *filename, char code,
5829                         int iserror )
5830 /* This is the open routine for the ADDRESS WITH-redirection. filename is
5831  * the name of the file. code is either 'r' for "READ",
5832  * 'A' for "WRITE APPEND", 'R' for "WRITE REPLACE". In case of READ
5833  * already opened files will be reused. In case of APPEND or REPLACE the
5834  * files are (re-)opened. An internal structure for files is returned and
5835  * should be used for calls to addr_io_file.
5836  * An already opened file for write can't be used. See J18PUB.pdf, 5.5.1.
5837  * The return value may be NULL in case of an error. A NOTREADY condition
5838  * may have been raised in this case.
5839  * filename may be NULL for a default file.
5840  * iserror can be set or not. If set, stderr instead of stdout should be used.
5841  */
5842 {
5843    fileboxptr ptr;
5844    fil_tsd_t *ft;
5845 
5846    ft = (fil_tsd_t *)TSD->fil_tsd;
5847 
5848    iserror = ( iserror ) ? 1 : 0;
5849    switch ( code )
5850    {
5851       case 'r':
5852          if ( ( filename == NULL ) || ( Str_len( filename ) == 0 ) )
5853             return ft->stdio_ptr[DEFAULT_STDIN_INDEX];
5854          ptr = get_file_ptr( TSD, filename, OPER_READ, ACCESS_READ );
5855          if ( ptr != NULL )
5856             ptr->readpos = 0;
5857          break;
5858 
5859       case 'A':
5860          if ( ( filename == NULL ) || ( Str_len( filename ) == 0 ) )
5861             return ft->stdio_ptr[DEFAULT_STDOUT_INDEX + iserror];
5862          if ( ( ptr = getfileptr( TSD, filename ) ) != NULL )
5863          {
5864             if ( ptr->flag & FLAG_SURVIVOR )
5865                return get_file_ptr( TSD, filename, OPER_WRITE, ACCESS_WRITE );
5866          }
5867          closefile( TSD, filename );
5868          ptr = openfile( TSD, filename, ACCESS_STREAM_APPEND );
5869          break;
5870 
5871       case 'R':
5872          if ( ( filename == NULL ) || ( Str_len( filename ) == 0 ) )
5873             return ft->stdio_ptr[DEFAULT_STDOUT_INDEX + iserror];
5874          if ( ( ptr = getfileptr( TSD, filename ) ) != NULL )
5875          {
5876             if ( ptr->flag & FLAG_SURVIVOR )
5877                return get_file_ptr( TSD, filename, OPER_WRITE, ACCESS_WRITE );
5878          }
5879          closefile( TSD, filename );
5880          ptr = openfile( TSD, filename, ACCESS_STREAM_REPLACE );
5881          break;
5882 
5883       default:
5884          ptr = NULL;
5885          break;
5886    }
5887 
5888    if ( ( ptr != NULL ) && ( ptr->fileptr == NULL ) )
5889       ptr = NULL;
5890 
5891    return ptr;
5892 }
5893 
addr_io_file(tsd_t * TSD,void * fileptr,const streng * buffer)5894 streng *addr_io_file( tsd_t *TSD, void *fileptr, const streng *buffer )
5895 /* This is the working routine for the ADDRESS WITH-redirection. fileptr is
5896  * the return value of addr_reopen_file. buffer must be NULL for a read
5897  * operation or a filled buffer.
5898  * The return value is NULL in case of a write operation or in case of EOF
5899  * while reading.
5900  * All IO is done by charin/charout.
5901  * A NOTREADY condition won't be raised.
5902  */
5903 {
5904    streng *retval = NULL ;
5905 
5906    if ( fileptr == NULL ) /* fixes bug 806948 */
5907       return retval;
5908 
5909    if ( buffer == NULL )
5910       retval = readbytes( TSD, (fileboxptr)fileptr, 0x1000, 1 ) ;
5911    else
5912       writebytes( TSD, (fileboxptr)fileptr, buffer, 1 ) ;
5913 
5914    return( retval ) ;
5915 }
5916 
addr_reset_file(tsd_t * TSD,void * fileptr)5917 void addr_reset_file( tsd_t *TSD, void *fileptr )
5918 /* This is the "close" routine for the ADDRESS WITH-redirection. We don't close
5919  * the stream, we simply perform a reset, that's enough to restart reading
5920  * or writing.
5921  */
5922 {
5923    fileboxptr ptr = (fileboxptr)fileptr;
5924 
5925    if ( fileptr == NULL ) /* fixes bug 806948 */
5926       return;
5927 
5928    if ( ptr->fileptr )
5929    {
5930       clearerr( ptr->fileptr );
5931       if ( ptr->flag & FLAG_PERSIST )
5932          rx_fseek( ptr->fileptr, 0, SEEK_SET );
5933       ptr->thispos = 0;
5934       ptr->oper = OPER_NONE;
5935    }
5936 
5937    if ( ptr->flag & FLAG_SURVIVOR )
5938       ptr->flag &= ~FLAG_ERROR;
5939 
5940    ptr->flag &= ~FLAG_FAKE;
5941 }
5942 
5943 
addr_file_info(tsd_t * TSD,const streng * source,int defchannel)5944 streng *addr_file_info( tsd_t *TSD, const streng *source, int defchannel )
5945 /*
5946  * addr_file_info is a helper for the ADDRESS WITH-redirection. source is the
5947  * name of the file and may be NULL for a default channel. The channel's number
5948  * is used then, 0 = stdin, 1 = stdout, 2 = stderr.
5949  * The return name is a fresh copy of the qualified name of the file.
5950  */
5951 {
5952    fil_tsd_t *ft;
5953    streng *result;
5954    fileboxptr p;
5955 
5956    ft = (fil_tsd_t *)TSD->fil_tsd;
5957 
5958    /*
5959     * We don't know anything about the default channels. So just return our
5960     * internal name.
5961     */
5962    if ( source == NULL )
5963       return Str_dupstrTSD( ft->stdio_ptr[ defchannel ]->filename0 );
5964 
5965    /*
5966     * Check for well known devices, the SURVIVORs. Return a comparable name
5967     * which makes sure "<stdin>" is equal to "stdin".
5968     */
5969    if ( ( p = getfileptr( TSD, source ) ) != NULL )
5970    {
5971       if ( p->flag & FLAG_SURVIVOR )
5972       {
5973          if ( p->fileptr == stdin )
5974             defchannel = 0;
5975          else if ( p->fileptr == stdout )
5976             defchannel = 1;
5977          else
5978             defchannel = 2;
5979 
5980          return Str_dupstrTSD( ft->stdio_ptr[ defchannel ]->filename0 );
5981       }
5982    }
5983 
5984    /*
5985     * Even in case of character devices a name resolution makes sense, e.g.
5986     * "tty" may expand to "/dev/tty" or the fifo "local" may expand to
5987     * "/somethere/local".
5988     */
5989    result = Str_makeTSD( REXX_PATH_MAX );
5990    my_fullpathstreng( TSD, result->value, source );
5991    result->len = strlen( result->value );
5992 
5993    return result;
5994 }
5995 
5996 
5997 /*
5998  * This routine is not really interesting. You should use the STREAM()
5999  * built-in function for greater portability and functionality. It is
6000  * left in the code for portability reasons.
6001  */
unx_open(tsd_t * TSD,cparamboxptr parms)6002 streng *unx_open( tsd_t *TSD, cparamboxptr parms )
6003 {
6004    fileboxptr ptr=NULL ;
6005    char ch=' ' ;
6006    int iaccess=ACCESS_NONE ;
6007 
6008    checkparam(  parms,  1,  2 , "OPEN" ) ;
6009 
6010    if ((parms->next)&&(parms->next->value))
6011    {
6012       ch = getoptionchar( TSD, parms->next->value, "OPEN", 2, "RW", "" ) ;
6013       if ( ch == 'R' ) /* bja */
6014          iaccess = ACCESS_READ ;
6015       else if ( ch == 'W' ) /* bja */
6016          iaccess = ACCESS_WRITE ;
6017       else
6018          assert( 0 ) ;
6019    }
6020    else
6021       iaccess = ACCESS_READ ;
6022 
6023    ptr = openfile( TSD, parms->value, iaccess ) ;
6024 
6025    return int_to_streng( TSD,( ptr && ptr->fileptr )) ;
6026 }
6027 
6028 
6029 /*
6030  * This routine is not really interesting. You should use the CLOSE
6031  * command of the STREAM() built-in function for greater portability
6032  * and compatibility. It is left in the code only for compatibility
6033  * reasons.
6034  */
unx_close(tsd_t * TSD,cparamboxptr parms)6035 streng *unx_close( tsd_t *TSD, cparamboxptr parms )
6036 {
6037    fileboxptr ptr=NULL ;
6038 
6039    checkparam(  parms,  1,  1 , "CLOSE" ) ;
6040    ptr = getfileptr( TSD, parms->value ) ;
6041    closefile( TSD, parms->value ) ;
6042 
6043    return int_to_streng( TSD, ptr!=NULL ) ;
6044 }
6045 
6046 
6047 /*
6048  * a function called exists that checks if a file with a certain name
6049  * exists. This function was taken from the ARexx API.
6050  */
arexx_exists(tsd_t * TSD,cparamboxptr parms)6051 streng *arexx_exists( tsd_t *TSD, cparamboxptr parms )
6052 {
6053    char *name;
6054    streng *retval;
6055    struct rx_stat_buf st;
6056 
6057    checkparam( parms, 1, 1, "EXISTS" ) ;
6058 
6059    name = str_of( TSD, parms->value ) ;
6060    retval = int_to_streng( TSD, rx_stat( name, &st ) != -1 ) ;
6061    Free_TSD( TSD, name ) ;
6062 
6063    return retval;
6064 }
6065 
6066 /*
6067  * get_external_routine_file opens a file in binary mode and returns the
6068  * fully qualified path name on success. NULL is returned otherwise.
6069  * The opened file pointer is returned in *fp.
6070  */
get_external_routine_file(const tsd_t * TSD,const char * inname,FILE ** fp)6071 static streng *get_external_routine_file( const tsd_t *TSD,
6072                                           const char *inname, FILE **fp )
6073 {
6074    char buf[3 * REXX_PATH_MAX + 1];
6075 
6076 #ifdef VMS
6077    *fp = fopen( inname, "r" );
6078 #else
6079    *fp = fopen( inname, "rb" );
6080 #endif
6081    if ( *fp == NULL )
6082       return NULL;
6083 
6084    my_fullpath( (tsd_t *) TSD, buf, inname );
6085 
6086    return Str_crestrTSD( buf );
6087 }
6088 
6089 /*
6090  * See get_external_routine for comments. This function processes one path
6091  * element which is passed in path.
6092  * suffixes is either NULL or the list of extra suffixes which should be
6093  * tested. *fp must be NULL on entry.
6094  * path may be NULL if no further directory processing shall happen.
6095  */
get_external_routine_path(const tsd_t * TSD,const char * inname,FILE ** fp,const char * path,const char * suffixes,int emptySuffixAllowed)6096 static streng *get_external_routine_path( const tsd_t *TSD,
6097                                           const char *inname, FILE **fp,
6098                                           const char *path,
6099                                           const char *suffixes,
6100                                           int emptySuffixAllowed )
6101 {
6102    char outname[REXX_PATH_MAX+1];
6103    int i,ilen,hlen;
6104    streng *retval;
6105    static const char *default_suffixes = "rexx,rex,cmd,rx";
6106    const char *suffixlist[2];
6107    const char *suffix;
6108    int suffixlen;
6109 
6110    ilen = strlen( inname );
6111    if ( !path )
6112       hlen = 0;
6113    else
6114       hlen = strlen( path );
6115    if ( !hlen )
6116    {
6117       if ( ilen > REXX_PATH_MAX )
6118          return NULL;
6119       strcpy( outname, inname );
6120    }
6121    else
6122    {
6123       if ( ( strchr( FILE_SEPARATORS, inname[0] ) == NULL ) &&
6124            ( strchr( FILE_SEPARATORS, path[hlen - 1] ) == NULL ) )
6125       {
6126          if ( ilen + hlen + 1 > REXX_PATH_MAX )
6127             return NULL;
6128          strcpy( outname, path );
6129          strcat( outname, FILE_SEPARATOR_STR );
6130          strcat( outname, inname );
6131       }
6132       else
6133       {
6134          if ( ilen + hlen > REXX_PATH_MAX )
6135             return NULL;
6136          strcpy( outname, path );
6137          strcat( outname, inname );
6138       }
6139    }
6140 
6141    /*
6142     * The filename is constructed. Try without fiddling with suffixes first.
6143     */
6144    if ( emptySuffixAllowed )
6145    {
6146       if ( ( retval = get_external_routine_file( TSD, outname, fp ) ) != NULL )
6147          return retval;
6148    }
6149 
6150    /*
6151     * Next try the supplied suffix list, then try the default list.
6152     * First check if a known extension exists, after every check do the
6153     * application.
6154     */
6155 
6156    suffixlist[0] = suffixes;
6157    suffixlist[1] = default_suffixes;
6158    ilen = strlen( outname );
6159 #define IsDelim(c) ( ( (c) == ',' ) || ( (c) == '.' ) || \
6160                      ( (c) == PATH_SEPARATOR ) || rx_isspace(c) )
6161    for ( i = 0; i < 2; i++ )
6162    {
6163       suffixes = suffixlist[i];
6164 
6165       while ( suffixes )
6166       {
6167          while ( IsDelim(*suffixes) )
6168             suffixes++;
6169          if ( *suffixes == '\0' )
6170             break;
6171 
6172          for ( suffixlen = 1; !IsDelim(suffixes[suffixlen]); suffixlen++ )
6173             if ( suffixes[suffixlen] == '\0' )
6174                break;
6175 
6176          suffix = suffixes;
6177          suffixes += suffixlen;
6178 
6179          if ( suffixlen + 1 > ilen )
6180             continue;
6181          if ( outname[ ilen - suffixlen - 1 ] != '.' )
6182             continue;
6183 #ifdef CASE_SENSITIVE_FILENAMES
6184          if ( memcmp( suffix, outname + ilen - suffixlen, suffixlen - 1 ) )
6185 #else
6186          if ( mem_cmpic( suffix, outname + ilen - suffixlen, suffixlen - 1 ) )
6187 #endif
6188             continue;
6189 
6190          /*
6191           * A matching suffix forces us to terminate every further seeking a
6192           * proper file.
6193           */
6194          if ( !emptySuffixAllowed )
6195             return get_external_routine_file( TSD, outname, fp );
6196          return NULL;
6197       }
6198    }
6199 
6200    /*
6201     * Try the extensions.
6202     */
6203    for ( i = 0; i < 2; i++ )
6204    {
6205       suffixes = suffixlist[i];
6206 
6207       while ( suffixes )
6208       {
6209          while ( IsDelim(*suffixes) )
6210             suffixes++;
6211          if ( *suffixes == '\0' )
6212             break;
6213 
6214          for ( suffixlen = 1; !IsDelim(suffixes[suffixlen]); suffixlen++ )
6215             if ( suffixes[suffixlen] == '\0' )
6216                break;
6217 
6218          suffix = suffixes;
6219          suffixes += suffixlen;
6220 
6221          if ( suffixlen + 1 + ilen > REXX_PATH_MAX )
6222             continue;
6223          outname[ ilen ] = '.';
6224          memcpy( outname + ilen + 1, suffix, suffixlen );
6225          outname[ilen + 1 + suffixlen] = '\0';
6226          if ( ( retval = get_external_routine_file( TSD, outname, fp ) ) !=
6227                                                                          NULL )
6228             return retval;
6229       }
6230    }
6231 
6232 #undef Delim
6233    return NULL;
6234 }
6235 
6236 /*
6237  * See get_external_routine for comments. This function processes a list of
6238  * path elements delimited by the path separator which is passed in paths.
6239  * suffixes is either NULL or the list of extra suffixes which should be
6240  * tested. *fp must be NULL on entry.
6241  * paths will be destroyed.
6242  */
get_external_routine_paths(const tsd_t * TSD,const char * inname,FILE ** fp,char * paths,const char * suffixes,int emptySuffixAllowed)6243 static streng *get_external_routine_paths( const tsd_t *TSD,
6244                                            const char *inname, FILE **fp,
6245                                            char *paths, const char *suffixes,
6246                                            int emptySuffixAllowed )
6247 {
6248    char *path;
6249    streng *retval;
6250 
6251    if ( *paths == '\0' )
6252       return NULL;
6253 
6254    while ( paths )
6255    {
6256       path = paths;
6257       paths = strchr( paths, PATH_SEPARATOR );
6258       if ( paths != NULL )
6259          *paths++ = '\0';
6260 
6261       if ( *path == '\0')
6262       {
6263          /*
6264           * An empty string is counted as "." in unix systems and ignored in
6265           * all other systems.
6266           */
6267 #ifdef UNIX
6268          path = ".";
6269 #else
6270          continue;
6271 #endif
6272       }
6273 
6274       retval = get_external_routine_path( TSD, inname, fp, path, suffixes,
6275                                           emptySuffixAllowed );
6276 
6277       if ( retval )
6278          return retval;
6279    }
6280 
6281    return NULL;
6282 }
6283 
6284 /*
6285  * get_external_routine searches for a script called inname. Some paths are
6286  * search if the file is not found and an extension may be added if no file is
6287  * found.
6288  *
6289  * On success *fp is set to the opened (binary) file and the return value is
6290  * the fully qualified file name. If no file was found the return value is
6291  * NULL and *fp will be NULL, too.
6292  * The returned file name is extended by a terminating '\0' without counting
6293  * is in the string's length.
6294  *
6295  * This is the search algorithm:
6296  *
6297  * First of all we process the environment variable REGINA_MACROS. If no file
6298  * is found we proceed with the current directory and then with the environment
6299  * variable PATH. The semantics of the use of REGINA_MACROS and PATH are the
6300  * same, and the search in the current directory is omitted for the superuser
6301  * in unix systems for security reasons. The current directory must be
6302  * specified explicitely by the superuser.
6303  * When processing an environment variable the content is split into the
6304  * different paths and each path is processed separately.
6305  * Note that the search algorithm to this point is ignored if the script name
6306  * contains a file path specification. eg. If "CALL .\MYPROG" is called, then
6307  * no searching of REGINA_MACROS or PATH is done; only the concatenation of
6308  * suffixes is carried out.
6309  *
6310  * For each file name and path element a concatenated file name is created. If
6311  * a known file extension is part of the file name only this file is searched,
6312  * otherwise the file name is extended by the extensions "<empty>", ".rexx",
6313  * ".rex", ".cmd", ".rx" in this order. The file name case is ignored on
6314  * systems that ignore the character case for normal file operations like DOS,
6315  * Windows, OS/2.
6316  *
6317  * The first matching file terminates the whole algorithm and the found file
6318  * is returned.
6319  *
6320  * The environment variable REGINA_SUFFIXES extends the list of known suffixes
6321  * as specified above, and is inserted after the "<empty"> extension in the
6322  * process. REGINA_SUFFIXES has to contain a space or comma separated list of
6323  * extensions, a dot in front of each entry is allowed, e.g.
6324  * ".macro,.mac,regina" or "macro mac regina"
6325  *
6326  * Note that it is planned to extend the list of known suffixes by ".rxc" in
6327  * version 3.4 to allow for seemless integration of precompiled macros.
6328  */
get_external_routine(const tsd_t * TSD,const char * inname,FILE ** fp)6329 streng *get_external_routine( const tsd_t *TSD, const char *inname, FILE **fp )
6330 {
6331    streng *retval=NULL;
6332    char *paths;
6333    char *suffixes;
6334 
6335    *fp = NULL;
6336 
6337    suffixes = mygetenv( TSD, "REGINA_SUFFIXES", NULL, 0 );
6338 
6339    if ( strpbrk( inname, FILE_SEPARATORS ) != NULL )
6340    {
6341       retval = get_external_routine_path( TSD, inname, fp, NULL, suffixes, 1 );
6342       if ( retval )
6343       {
6344          if ( suffixes )
6345             FreeTSD( suffixes );
6346          return retval;
6347       }
6348       return NULL;
6349    }
6350 
6351    if ( ( paths = mygetenv( TSD, "REGINA_MACROS", NULL, 0 ) ) != NULL )
6352    {
6353       retval = get_external_routine_paths( TSD, inname, fp, paths, suffixes, 1 );
6354       FreeTSD( paths );
6355       if ( retval )
6356       {
6357          if ( suffixes )
6358             FreeTSD( suffixes );
6359          return retval;
6360       }
6361    }
6362 
6363    paths = ".";
6364 #ifdef UNIX
6365    if ( geteuid() == 0 )
6366       paths = NULL;
6367 #endif
6368    if ( paths )
6369    {
6370       retval = get_external_routine_path( TSD, inname, fp, paths, suffixes, 1 );
6371       if ( retval )
6372       {
6373          if ( suffixes )
6374             FreeTSD( suffixes );
6375          return retval;
6376       }
6377    }
6378 
6379    if ( ( paths = mygetenv( TSD, "PATH", NULL, 0 ) ) != NULL )
6380    {
6381       retval = get_external_routine_paths( TSD, inname, fp, paths, suffixes, 0 );
6382       FreeTSD( paths );
6383    }
6384 
6385    if ( suffixes )
6386       FreeTSD( suffixes );
6387    return retval;
6388 }
6389 
6390 /*
6391  * find_shared_library is used for HP/UX purpose only.
6392  * It looks for the file inname in the content of the environment variable
6393  * inenv and puts the result into retname. retname has to have a size of
6394  * at least REXX_PATH_MAX+1.
6395  * retname becomes inname if no other file is found.
6396  */
find_shared_library(const tsd_t * TSD,const char * inname,const char * inenv,char * retname)6397 void find_shared_library(const tsd_t *TSD, const char *inname, const char *inenv, char *retname)
6398 {
6399    char *paths;
6400    char outname[REXX_PATH_MAX+1];
6401    char *env_path;
6402 
6403    strcpy( retname, inname );
6404    env_path = mygetenv( TSD, inenv, NULL, 0 ); /* fixes bug 595293 */
6405    if ( !env_path )
6406       return;
6407    paths = env_path;
6408    while ( paths && *paths )
6409    {
6410       int pathlen;
6411       char *sep;
6412 
6413       sep = strchr( paths, PATH_SEPARATOR );
6414       pathlen = sep ? sep-paths : strlen( paths );
6415       strncpy( outname, paths, pathlen );
6416       outname[pathlen] = 0;
6417 
6418       if ( ( pathlen > 0 ) && ( outname[pathlen-1] != FILE_SEPARATOR ) )
6419          strcat( outname, FILE_SEPARATOR_STR );
6420       strcat( outname, inname );
6421       paths = sep ? sep+1 : 0; /* set up for next pass */
6422       if ( access( outname,F_OK ) == 0)
6423       {
6424          strcpy( retname,outname );
6425          break;
6426       }
6427    }
6428    FreeTSD( env_path );
6429    return;
6430 }
6431 
6432 /* CloseOpenFiles closes all scripting input files and it closes all opened
6433  * STREAM files without destroying the associated informations.
6434  * Bug 982062: Added FilePtrDisposition to allow for purging fileptr
6435  * table if requested.
6436  */
CloseOpenFiles(const tsd_t * TSD,FilePtrDisposition fpd)6437 void CloseOpenFiles( const tsd_t *TSD, FilePtrDisposition fpd )
6438 {
6439    sysinfobox *ptr;
6440 
6441    if ( fpd == fpdRETAIN )
6442    {
6443       ptr = TSD->systeminfo;
6444       while (ptr)
6445       {
6446          if (ptr->input_fp)
6447          {
6448             fclose(ptr->input_fp);
6449             ptr->input_fp = NULL;
6450          }
6451          ptr = ptr->previous;
6452       }
6453       /*
6454        * Cheat about the const-state.
6455        */
6456       swapout_all( ( tsd_t *) TSD );
6457    }
6458    else
6459    {
6460       purge_filetable( ( tsd_t *) TSD );
6461    }
6462    return;
6463 }
6464 
ConfigStreamQualified(tsd_t * TSD,const streng * name)6465 streng *ConfigStreamQualified( tsd_t *TSD, const streng *name )
6466 {
6467    char *fn=NULL;
6468    streng *result=NULL ;
6469 
6470    /*
6471     * Nul terminate the input filename string, as stat() will barf if
6472     * it isn't and other functions stuff up!
6473     */
6474    fn = str_ofTSD(name);
6475 
6476    result = Str_makeTSD( REXX_PATH_MAX );
6477    if ( my_fullpath( TSD, result->value, fn ) == -1 )
6478    {
6479       /*
6480        * my_fullpath failed, so split the supplied file into filename
6481        * and directory. Then look for directory and append the filename
6482        * TODO -
6483        */
6484    }
6485    result->len = strlen( result->value );
6486    return( result );
6487 }
6488 
6489 #if defined(HAVE__FULLPATH) || defined(__EMX__)
6490 /*
6491  * my_fullpath tries to get the fully qualified name of a file or directory
6492  * even if it doesn't exist. It tries to return a reasonable value even if
6493  * a path element is missing.
6494  * The return value is 0 on success, -1 in case of a severe error.
6495  */
my_fullpath(tsd_t * TSD,char * dst,const char * src)6496 int my_fullpath( tsd_t *TSD, char *dst, const char *src )
6497 {
6498    char *copy=NULL;
6499    int rc = 0, len;
6500 # if defined(__EMX__)
6501    int i;
6502 
6503    if ( _fullpath( dst, src, REXX_PATH_MAX ) == -1)
6504       strcpy( dst, src );
6505    /*
6506     * Convert / back to \.
6507     */
6508    len = strlen( dst );
6509    for ( i = 0; i < len; i++ )
6510    {
6511       if ( dst[i] == '/' )
6512          dst[i] = '\\';
6513    }
6514 # else
6515    /* attempt to get the full path with the supplied source */
6516    if ( _fullpath( dst, src, REXX_PATH_MAX ) == NULL )
6517    {
6518 #  if defined(WIN32)
6519       /* now try by removing trailing slash */
6520       /* this code may not actually be needed */
6521       len = strlen( src ) -  1;
6522       if ( src[len] == '\\' || src[len] == '/')
6523       {
6524          copy = (char *)MallocTSD( len+2 ) ;
6525          strcpy( copy, src );
6526          copy[len] = '\0';
6527          if ( _fullpath( dst, copy, REXX_PATH_MAX ) == NULL )
6528          {
6529             strcpy( dst, src );
6530             rc = -1;
6531          }
6532       }
6533       else
6534       {
6535          strcpy( dst, src );
6536          rc = -1;
6537       }
6538       if ( copy )
6539          FreeTSD( copy );
6540 #  else
6541       strcpy( dst, src );
6542       rc = -1;
6543 #  endif
6544    }
6545 # endif
6546    return rc;
6547 }
6548 #elif defined(HAVE__TRUENAME)
6549 
my_fullpath(tsd_t * TSD,char * dst,const char * src)6550 int my_fullpath( tsd_t *TSD, char *dst, const char *src )
6551 {
6552    _truename( src, dst );
6553 
6554    return 0;
6555 }
6556 #elif defined(HAVE_REALPATH)
6557 
my_fullpath(tsd_t * TSD,char * dst,const char * src)6558 int my_fullpath( tsd_t *TSD, char *dst, const char *src )
6559 {
6560    /* hack for leading ~/ */
6561    int len = strlen( src );
6562    char *source=NULL;
6563    char *copy=NULL;
6564    if ( ( len > 1 && strncmp( src, "~/", 2 ) == 0 ) || ( len == 1 && strncmp( src, "~", 1 ) == 0) )
6565    {
6566       char *env = getenv( "HOME" );
6567       if ( env != NULL )
6568       {
6569          int len2 = strlen( env );
6570          copy = (char *)MallocTSD( len+len2+2 ) ;
6571          strcpy( copy, env );
6572          strcat( copy, src+1 );
6573          source = copy;
6574       }
6575       else
6576       {
6577          source = src;
6578       }
6579    }
6580    else
6581    {
6582       source = src;
6583    }
6584    realpath( source, dst );
6585    if ( copy )
6586       FreeTSD( copy );
6587 
6588    return 0;
6589 }
6590 #elif defined(VMS)
6591 #  include <ssdef.h>
6592 #  include <rmsdef.h>
6593 #  include <descrip.h>
6594 
my_fullpath(tsd_t * TSD,char * dst,const char * src)6595 int my_fullpath( tsd_t *TSD, char *dst, const char *src )
6596 {
6597    char *s;
6598    int status, context = 0;
6599    struct dsc$descriptor_d result_dx = {0, DSC$K_DTYPE_T, DSC$K_CLASS_D, 0};
6600    struct dsc$descriptor_d finddesc_dx = {0, DSC$K_DTYPE_T, DSC$K_CLASS_D, 0};
6601 
6602    finddesc_dx.dsc$a_pointer = (char *)src; /* You may need to cast this */
6603    finddesc_dx.dsc$w_length = strlen(src);
6604    status = lib$find_file( &finddesc_dx, &result_dx, &context, 0, 0, 0, 0 );
6605    if ( status == RMS$_NORMAL )
6606    {
6607       memcpy(dst,result_dx.dsc$a_pointer,result_dx.dsc$w_length);
6608       *(dst+result_dx.dsc$w_length) = '\0';
6609    }
6610    else
6611       strcpy(dst,src);
6612    lib$find_file_end(&context);
6613    str$free1_dx(&result_dx);
6614    return(0);
6615 }
6616 #else /* neither _FULLPATH, _TRUENAME, REALNAME, VMS */
6617 
my_fullpath(tsd_t * TSD,char * dst,const char * src)6618 int my_fullpath( tsd_t *TSD, char *dst, const char *src )
6619 {
6620    char tmp[REXX_PATH_MAX+1];
6621    char curr_path[REXX_PATH_MAX+1];
6622    char path[REXX_PATH_MAX+1];
6623    char fname[REXX_PATH_MAX+1];
6624    int i = 0, len = -1, retval;
6625    struct rx_stat_buf stat_buf;
6626 
6627    getcwd(curr_path,REXX_PATH_MAX);
6628    strcpy(tmp,src);
6629    /*
6630     * First determine if the supplied filename is a directory.
6631     */
6632 # if defined(DJGPP) || defined(__WINS__) || defined(__EPOC32__)
6633    for ( i = 0; i < strlen( tmp ); i++ )
6634       if ( tmp[ i ] == '\\' )
6635          tmp[ i ] = '/';
6636 # endif
6637    if ((rx_stat(tmp,&stat_buf) == 0)
6638    &&  (stat_buf.st_mode & S_IFMT) == S_IFDIR)
6639    {
6640       strcpy(path,tmp);
6641       strcpy(fname,"");
6642    }
6643    else          /* here if the file doesn't exist or is not a directory */
6644    {
6645       for (i=strlen(tmp),len=-1;i>-1;i--)
6646       {
6647          if (tmp[i] == '/')
6648          {
6649             len = i;
6650             break;
6651          }
6652       }
6653       switch(len)
6654       {
6655          case (-1):
6656             getcwd(path,REXX_PATH_MAX);
6657             strcpy(fname,tmp);
6658             break;
6659          case 0:
6660             strcpy(path,tmp);
6661             path[1] = '\0';
6662             strcpy(fname,tmp+1+len);
6663             break;
6664          default:
6665             strcpy(path,tmp);
6666             path[len] = '\0';
6667             strcpy(fname,tmp+1+len);
6668             break;
6669       }
6670    }
6671    /*
6672     * Change directory to the supplied path, if possible and store the
6673     * expanded path.
6674     * If an error, restore the current path.
6675     */
6676    if (chdir(path) != 0)
6677    {
6678       retval = -1;
6679    }
6680    else
6681    {
6682       getcwd(path,REXX_PATH_MAX);
6683       retval = 0;
6684    }
6685    chdir(curr_path);
6686    /*
6687     * Append the OS directory character to the path if it doesn't already
6688     * end in the character.
6689     */
6690    len = strlen(path);
6691    if (len > 0)
6692    {
6693 # if defined(__WINS__) || defined(__EPOC32__)
6694       if ( path[ len - 1 ] != '\\'
6695 # else
6696       if ( path[ len - 1 ] != '/'
6697 # endif
6698       &&  strlen( fname ) != 0 )
6699       {
6700          strcat(path,"/");
6701          len++;
6702       }
6703 # if defined(DJGPP) || defined(__WINS__) || defined(__EPOC32__)
6704       for ( i = 0; i < len; i++ )
6705          if ( path[ i ] == '/' )
6706             path[ i ] = '\\';
6707 # endif
6708    }
6709    strcpy(dst,path);
6710    strcat(dst,fname);
6711 
6712    return retval;
6713 }
6714 #endif
6715 
my_fullpathstreng(const tsd_t * TSD,char * dst,const streng * src)6716 int my_fullpathstreng( const tsd_t *TSD, char *dst, const streng *src )
6717 {
6718    char *copy;
6719    int retval;
6720 
6721    copy = str_ofTSD( src );
6722    retval = my_fullpath( (tsd_t *) TSD, dst, copy );
6723    FreeTSD( copy );
6724 
6725    return retval;
6726 }
6727 
6728 #if !defined(HAVE__SPLITPATH2) && !defined(HAVE__SPLITPATH) && !defined(__EMX__) && !defined(DJGPP)
my_splitpath2(const char * in,char * out,char ** drive,char ** dir,char ** name,char ** ext)6729 int my_splitpath2( const char *in, char *out, char **drive, char **dir, char **name, char **ext )
6730 {
6731    int inlen = strlen(in);
6732    int last_slash_pos=-1,last_dot_pos=-1,last_pos=0,i=0;
6733 
6734    for (i=0;i<inlen;i++)
6735    {
6736       if ( *(in+i) == '/' || *(in+i) == '\\' )
6737          last_slash_pos = i;
6738       else if ( *(in+i) == '.' )
6739          last_dot_pos = i;
6740    }
6741    /*
6742     * drive is always empty !
6743     */
6744    out[0] = '\0';
6745    *drive = out;
6746 
6747    *ext = out+1;
6748    if (last_dot_pos > last_slash_pos)
6749    {
6750       strcpy(*ext,in+last_dot_pos);
6751       last_pos = 2 + (inlen - last_dot_pos);
6752       inlen = last_dot_pos;
6753    }
6754    else
6755    {
6756       **ext = '\0';
6757       last_pos = 2;
6758    }
6759    *dir = out+last_pos;
6760    /*
6761     * If there is a path component (last_slash_pos not -1), then copy
6762     * from the start of the in string to the last_slash_pos to out[1]
6763     */
6764    if (last_slash_pos != -1)
6765    {
6766       memcpy(*dir, in, last_slash_pos + 1);
6767       last_pos += last_slash_pos + 1;
6768       out[last_pos++] = '\0';
6769       *name = out+last_pos;
6770       memcpy(*name, in+last_slash_pos+1,(inlen - last_slash_pos - 1) );
6771       out[last_pos + (inlen - last_slash_pos - 1)] = '\0';
6772    }
6773    else
6774    {
6775       **dir = '\0';
6776       last_pos++;
6777       *name = out+last_pos;
6778       memcpy(*name, in, inlen);
6779       *(*name+inlen) = '\0';
6780    }
6781    return(0);
6782 }
6783 #endif
6784