1 /* vms_fwrite.c - augmentation for the fwrite() function.
2
3 Copyright (C) 1991-1996, 2010, 2011, 2014, 2016
4 the Free Software Foundation, Inc.
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software Foundation,
18 Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
19
20 #include "awk.h" /* really "../awk.h" */
21
22 #ifndef NO_TTY_FWRITE
23 #include "vms.h"
24 #include <stdio.h>
25 #include <errno.h>
26
27 #ifdef VAXC_BUILTINS
28 #pragma builtins /* VAXC V3.0 & up */
29 # define find_c(s,n,c) ((n) - _LOCC((c),(n),(s)))
30 #else /*VAXC_BUILTINS*/
find_c(const char * s,int n,char c)31 static int find_c( const char *s, int n, char c ) {
32 register const char *t = (const char *)memchr(s, c, n);
33 return (t == 0 ? n : t - s); /* 0..n-1, or n if not found */
34 }
35 #endif /*VAXC_BUILTINS*/
36 #define is_stdout(file_no) ((file_no) == 1) /* fileno(stdout) */
37 #define is_stderr(file_no) ((file_no) == 2) /* fileno(stderr) */
38
39 #define PREFIX_CR 0x008D0000 /* leading carriage return */
40 #define POSTFIX_CR 0x8D000000 /* trailing carriage return (=> lf/cr) */
41
42 static short channel[_NFILE] = {0};
43 static FILE *prev_file = 0;
44 static int prev_file_num;
45
46 /*
47 * VAXCRTL's fwrite() seems to flush after every character when
48 * writing to a terminal. This routine is a limited functionality
49 * substitute that is *much* faster. However, calls to fwrite()
50 * should not be mixed with other stdio calls to the same file
51 * unless fflush() is always called first. Also, this routine
52 * will not detect that a freopen() call has finished with the
53 * original terminal; tty_fclose() should be used to close a file.
54 *
55 * When running gawk's debugging version we stick with normal
56 * fwrite because dgawk also uses other stdio calls for output.
57 */
58 #ifdef fwrite
59 # undef fwrite
60 #endif
61 /* tty_fwrite() - performance hack for fwrite() to a terminal */
62 size_t
tty_fwrite(const void * buf,size_t size,size_t number,FILE * file)63 tty_fwrite( const void *buf, size_t size, size_t number, FILE *file )
64 {
65 static long evfn = -1;
66 short chan;
67 int file_num, result;
68
69 if (!size || !number)
70 return 0;
71 else if (!file || !*file)
72 return 0 * (errno = EBADF); /* kludge alert! */
73 else if (file == prev_file)
74 file_num = prev_file_num;
75 else /* note: VAXCRTL's fileno() is a function, not just a macro */
76 prev_file_num = file_num = fileno(file), prev_file = file;
77
78 chan = file_num < _NFILE ? channel[file_num] : -1;
79 if (chan == 0) { /* if not initialized, need to assign a channel */
80 if (isatty(file_num) > 0 /* isatty: 1=yes, 0=no, -1=problem */
81 && ! do_debug) {
82 struct dsc$descriptor_s device;
83 char devnam[255+1];
84
85 fgetname(file, devnam); /* get 'file's name */
86 /* create descriptor */
87 device.dsc$w_length = strlen(device.dsc$a_pointer = devnam);
88 device.dsc$b_dtype = DSC$K_DTYPE_T;
89 device.dsc$b_class = DSC$K_CLASS_S;
90 if (vmswork(SYS$ASSIGN(&device, &chan, 0,
91 (struct dsc$descriptor_s *)0))) {
92 /* get an event flag; use #0 if problem */
93 if (evfn == -1 && vmsfail(LIB$GET_EF(&evfn))) evfn = 0;
94 } else chan = 0; /* $ASSIGN failed */
95 }
96 /* store channel for later use; -1 => don't repeat failed init attempt */
97 channel[file_num] = (chan > 0 ? chan : -1);
98 }
99
100 /* chan > 0 iff 'file' is a terminal and we're not running as dgawk */
101 if (chan > 0) {
102 struct _iosbw { U_Short status, count; U_Long rt_kludge; } iosb;
103 register U_Long sts = 1;
104 register char *pt = (char *)buf;
105 register int offset, pos, count = size * number;
106 U_Long cc_fmt, io_func = IO$_WRITEVBLK;
107 int extra = 0;
108
109 result = 0;
110 if (is_stderr(file_num)) /* if it's SYS$ERROR (stderr)... */
111 io_func |= IO$M_CANCTRLO; /* cancel ^O (resume tty output) */
112 while (count > 0) {
113 /* special handling for line-feeds to make them be 'newlines' */
114 offset = 0;
115 if (*pt == '\n') { /* got at least one leading line-feed */
116 cc_fmt = PREFIX_CR, extra++; /* precede 1st LF with a CR */
117 do offset++;
118 while (offset < count && *(pt + offset) == '\n');
119 } else
120 cc_fmt = 0;
121 /* look for another line-feed; if found, break line there */
122 pos = offset + find_c(pt + offset, count - offset, '\n');
123 if (pos >= BUFSIZ) pos = BUFSIZ - 1; /* throttle quota usage */
124 else if (pos < count) pos++, cc_fmt |= POSTFIX_CR, extra++;
125 /* wait for previous write, if any, to complete */
126 if (pt > (char *)buf) {
127 sts = SYS$SYNCH(evfn, &iosb);
128 if (vmswork(sts)) sts = iosb.status, result += iosb.count;
129 if (vmsfail(sts)) break;
130 }
131 /* queue an asynchronous write */
132 sts = SYS$QIO(evfn, chan, io_func, &iosb, (void (*)(U_Long))0, 0L,
133 pt, pos, 0, cc_fmt, 0, 0);
134 if (vmsfail(sts)) break; /*(should never happen)*/
135 pt += pos, count -= pos;
136 }
137 /* wait for last write to complete */
138 if (pt > (char *)buf && vmswork(sts)) {
139 sts = SYS$SYNCH(evfn, &iosb);
140 if (vmswork(sts)) sts = iosb.status, result += iosb.count;
141 }
142 if (vmsfail(sts)) errno = EVMSERR, vaxc$errno = sts;
143 else if (iosb.rt_kludge == 0) result = number + extra;
144 result -= extra; /* subtract the additional carriage-returns */
145 } else { /* use stdio */
146 /* Note: we assume that we're writing text, not binary data.
147 For stream format files, 'size' and 'number' are effectively
148 interchangable, and fwrite works fine. However, for record
149 format files, 'size' governs the maximum record length, so
150 fwrite(string, size(char), strlen(string), file)
151 will produce a sequence of 1-byte records, which is hardly
152 what we want in this (assumed) situation. Line-feeds ('\n')
153 are converted into newlines (ie, record separators) by the
154 run-time library, but strings that don't end with a newline
155 still become separate records. The simplest work around
156 is just to use fputs() instead of fwrite(); unfortunately,
157 we have to provide special treatment for NULs ('\0's).
158 At present, only stdout might be in record format (via
159 >$'filename' redirection on the command line).
160 */
161 if (size > 1) { /* not used by GAWK */
162 result = fwrite((void *)buf, size, number, file);
163 } else if (*((char *)buf + number - 1) == '\n' || !is_stdout(file_num)) {
164 result = fwrite((void *)buf, number, size, file);
165 result = result * number / size; /*(same as 'result = number')*/
166 } else {
167 #ifdef NO_ALLOCA
168 # define alloca(n) ((n) <= abuf_siz ? abuf : \
169 ((abuf_siz > 0 ? (free(abuf),0) : 0), \
170 (abuf = malloc(abuf_siz = (n)+20))))
171 static void *abuf = 0;
172 static size_t abuf_siz = 0;
173 #endif /*NO_ALLOCA*/
174 register char *pt = (char *)buf;
175 register int pos, count = number;
176
177 if (pt[count] != '\0') { /*(out of bounds, but relatively safe)*/
178 pt = (char *)alloca(count + 1);
179 memcpy(pt, buf, count), pt[count] = '\0';
180 /* if exiting this block undoes the alloca(), we're hosed :-( */
181 }
182 result = 0;
183 while (count > 0) {
184 pos = find_c(pt, count, '\0');
185 if (fputs(pt, file) < 0) break;
186 if (pos < count) {
187 if (fputc('\0', file) < 0) break;
188 pos++; /* 0..n-1 -> 1..n */
189 }
190 result += pos, pt += pos, count -= pos;
191 }
192 }
193 }
194 return result;
195 }
196 #define fwrite(b,s,n,f) tty_fwrite((b),(s),(n),(f))
197
198 #ifdef fclose
199 # undef fclose
200 #endif
201 /* tty_fclose() - keep tty_fwrite() up to date when closing a file */
202 int
tty_fclose(FILE * file)203 tty_fclose( FILE *file )
204 {
205 if (file && *file) { /* note: VAXCRTL stdio has extra level of indirection */
206 int file_num = fileno(file);
207 short chan = file_num < _NFILE ? channel[file_num] : -1;
208
209 if (chan > 0)
210 (void)SYS$DASSGN(chan); /* deassign the channel (ie, close) */
211 if (file_num < _NFILE)
212 channel[file_num] = 0; /* clear stale info */
213 }
214 prev_file = 0; /* force tty_fwrite() to reset */
215 return fclose(file);
216 }
217 #define fclose(f) tty_fclose(f)
218
219 #endif /*!NO_TTY_FWRITE*/
220