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