416 lines
12 KiB
C
416 lines
12 KiB
C
/****************************************************************
|
|
* *
|
|
* Copyright 2001, 2012 Fidelity Information Services, Inc *
|
|
* *
|
|
* This source code contains the intellectual property *
|
|
* of its copyright holder(s), and is made available *
|
|
* under a license. If you do not know the terms of *
|
|
* the license, please stop and do not read further. *
|
|
* *
|
|
****************************************************************/
|
|
|
|
#include "mdef.h"
|
|
|
|
#include "gtm_string.h"
|
|
#include <errno.h>
|
|
|
|
#include "gtm_stdio.h"
|
|
#include "gtm_unistd.h"
|
|
#include "io.h"
|
|
#include "iormdef.h"
|
|
#include "iormdefsp.h"
|
|
#include "gtmio.h"
|
|
#include "min_max.h"
|
|
#ifdef UNICODE_SUPPORTED
|
|
#include "gtm_conv.h"
|
|
#include "gtm_utf8.h"
|
|
#endif
|
|
|
|
GBLREF io_pair io_curr_device;
|
|
#ifdef UNICODE_SUPPORTED
|
|
LITREF mstr chset_names[];
|
|
#endif
|
|
|
|
#define MAX_WIDTH 65535
|
|
|
|
error_def(ERR_NOTTOEOFONPUT);
|
|
error_def(ERR_DEVICEREADONLY);
|
|
error_def(ERR_SYSCALL);
|
|
|
|
/* write ASCII characters converting to UTF16 if needed
|
|
* returns bytes written
|
|
*/
|
|
int iorm_write_utf_ascii(io_desc *iod, char *string, int len)
|
|
{
|
|
int outlen, mblen, status;
|
|
wint_t utf_code;
|
|
unsigned char *outstart, *out, *top, *outptr, *nextoutptr, *outptrtop, *nextmb;
|
|
d_rm_struct *rm_ptr;
|
|
|
|
rm_ptr = (d_rm_struct *)iod->dev_sp;
|
|
assert(NULL != rm_ptr);
|
|
if (CHSET_UTF8 != iod->ochset)
|
|
{
|
|
outstart = outptr = &rm_ptr->outbuf[rm_ptr->out_bytes];
|
|
outptrtop = rm_ptr->outbuf + rm_ptr->recordsize; /* buffer is larger than recordsize to allow for EOL */
|
|
assert(len <= (&rm_ptr->outbuf[rm_ptr->outbufsize] - outstart));
|
|
for (out = (unsigned char*)string, top = out + len, outlen = 0; out < top && outptr <= outptrtop;
|
|
out = nextmb, outptr = nextoutptr)
|
|
{
|
|
nextmb = UTF8_MBTOWC(out, top, utf_code);
|
|
assert(nextmb == (out + 1));
|
|
if (WEOF == utf_code)
|
|
{
|
|
iod->dollar.za = 9;
|
|
UTF8_BADCHAR((int)(nextmb - out), out, top, 0, NULL);
|
|
}
|
|
if (CHSET_UTF16BE == iod->ochset)
|
|
nextoutptr = UTF16BE_WCTOMB(utf_code, outptr);
|
|
else
|
|
nextoutptr = UTF16LE_WCTOMB(utf_code, outptr);
|
|
if (nextoutptr == outptr)
|
|
{ /* invalid codepoint */
|
|
iod->dollar.za = 9;
|
|
UTF8_BADCHAR((int)(nextmb - out), out, top, chset_names[iod->ochset].len,
|
|
chset_names[iod->ochset].addr);
|
|
}
|
|
mblen = (int)(nextoutptr - outptr);
|
|
outlen += mblen;
|
|
}
|
|
} else
|
|
{
|
|
outstart = (unsigned char *)string;
|
|
outlen = len;
|
|
}
|
|
if (0 < outlen)
|
|
{
|
|
DOWRITERC(rm_ptr->fildes, outstart, outlen, status);
|
|
if (0 != status)
|
|
{
|
|
DOLLAR_DEVICE_WRITE(rm_ptr,status);
|
|
iod->dollar.za = 9;
|
|
rts_error(VARLSTCNT(1) status);
|
|
}
|
|
rm_ptr->out_bytes += outlen;
|
|
}
|
|
return outlen;
|
|
}
|
|
|
|
void iorm_write_utf(mstr *v)
|
|
{
|
|
int4 inchars, char_count; /* in characters */
|
|
int4 inlen, outbytes, mblen; /* in bytes */
|
|
int4 availwidth, usedwidth, mbwidth; /* in display columns */
|
|
int status, padsize;
|
|
wint_t utf_code;
|
|
io_desc *iod;
|
|
d_rm_struct *rm_ptr;
|
|
unsigned char *inptr, *top, *nextmb, *outptr, *nextoutptr, *outstart, temppad, temppadarray[2];
|
|
boolean_t utf8_active = TRUE; /* needed by GTM_IO_WCWIDTH macro */
|
|
|
|
iod = io_curr_device.out;
|
|
rm_ptr = (d_rm_struct *)iod->dev_sp;
|
|
assert(NULL != rm_ptr);
|
|
inptr = (unsigned char *)v->addr;
|
|
inlen = v->len;
|
|
top = inptr + inlen;
|
|
if (!rm_ptr->fixed && 0 == iod->dollar.x)
|
|
rm_ptr->out_bytes = 0; /* user reset $X */
|
|
inchars = UTF8_LEN_STRICT(v->addr, v->len); /* validate and get good char count */
|
|
if (0 >= inchars)
|
|
return;
|
|
usedwidth = 0;
|
|
if (rm_ptr->stream && !iod->wrap)
|
|
{
|
|
availwidth = iod->width;
|
|
/* For STREAM and NOWRAP, allow a maximum of "rm_ptr->recordsize" bytes to be written as part of
|
|
* the current WRITE. Any number of future WRITEs to this same record are allowed as long as
|
|
* each of them is within "rm_ptr->recordsize" bytes (truncated otherwise). This means that
|
|
* it does not matter how many bytes we have already written as part of the current record
|
|
* which is "rm_ptr->out_bytes". Reset it to 0 so we can use it for the current WRITE calculations.
|
|
*/
|
|
rm_ptr->out_bytes = 0;
|
|
} else
|
|
availwidth = iod->width - iod->dollar.x;
|
|
outbytes = 0;
|
|
if (CHSET_UTF8 != iod->ochset)
|
|
{
|
|
outstart = nextoutptr = outptr = &rm_ptr->outbuf[rm_ptr->out_bytes];
|
|
if (!rm_ptr->done_1st_write)
|
|
{
|
|
if (CHSET_UTF16 == iod->ochset)
|
|
{ /* Write BOM but do not count it towards the bytes in the current record */
|
|
memcpy(outptr, UTF16BE_BOM, UTF16BE_BOM_LEN);
|
|
outbytes = UTF16BE_BOM_LEN;
|
|
outptr += UTF16BE_BOM_LEN;
|
|
DOWRITERC(rm_ptr->fildes, outstart, outbytes, status);
|
|
if (0 != status)
|
|
{
|
|
DOLLAR_DEVICE_WRITE(rm_ptr,status);
|
|
iod->dollar.za = 9;
|
|
rts_error(VARLSTCNT(1) status);
|
|
}
|
|
outptr = outstart;
|
|
rm_ptr->out_bytes = outbytes = 0;
|
|
iod->ochset = CHSET_UTF16BE;
|
|
get_chset_desc(&chset_names[iod->ochset]);
|
|
}
|
|
}
|
|
} else
|
|
outstart = (unsigned char *)v->addr; /* write from the UTF-8 string */
|
|
if (!rm_ptr->done_1st_write)
|
|
rm_ptr->done_1st_write = TRUE;
|
|
for (char_count = mblen = 0; inptr <= top ; )
|
|
{
|
|
if (inptr < top)
|
|
{ /* get "mblen" and "mbwidth" of of next character in buffer */
|
|
nextmb = UTF8_MBTOWC(inptr, top, utf_code);
|
|
if (WEOF == utf_code)
|
|
{
|
|
iod->dollar.za = 9;
|
|
UTF8_BADCHAR(0, inptr, top, 0, NULL);
|
|
}
|
|
if (CHSET_UTF8 != iod->ochset)
|
|
{
|
|
if (CHSET_UTF16BE == iod->ochset)
|
|
nextoutptr = UTF16BE_WCTOMB(utf_code, outptr);
|
|
else
|
|
nextoutptr = UTF16LE_WCTOMB(utf_code, outptr);
|
|
if (nextoutptr == outptr)
|
|
{ /* invalid codepoint */
|
|
iod->dollar.za = 9;
|
|
UTF8_BADCHAR((int)((nextmb - inptr)), inptr, top, chset_names[iod->ochset].len,
|
|
chset_names[iod->ochset].addr);
|
|
}
|
|
mblen = (int)(nextoutptr - outptr);
|
|
outptr = nextoutptr;
|
|
} else
|
|
mblen = (int)(nextmb - inptr);
|
|
GTM_IO_WCWIDTH(utf_code, mbwidth);
|
|
assert(mblen);
|
|
}
|
|
assert(rm_ptr->out_bytes <= rm_ptr->recordsize);
|
|
/* Note that "mblen" and "mbwidth" are valid only if "inptr < top".
|
|
* This is why they are used after the "inptr >= top" check below
|
|
*/
|
|
if (inptr >= top || ((usedwidth + mbwidth) > availwidth) || ((rm_ptr->out_bytes + mblen) > rm_ptr->recordsize))
|
|
{ /* filled to WIDTH or end of input or full record */
|
|
if (0 < outbytes)
|
|
{
|
|
if (rm_ptr->fifo || rm_ptr->pipe)
|
|
{
|
|
WRITEPIPE(rm_ptr->fildes, rm_ptr->pipe_buff_size, outstart, outbytes, status);
|
|
} else
|
|
{
|
|
DOWRITERC(rm_ptr->fildes, outstart, outbytes, status);
|
|
}
|
|
if (0 != status)
|
|
{
|
|
DOLLAR_DEVICE_WRITE(rm_ptr,status);
|
|
iod->dollar.za = 9;
|
|
rts_error(VARLSTCNT(1) status);
|
|
}
|
|
}
|
|
iod->dollar.x += usedwidth;
|
|
if (inptr >= top)
|
|
break; /* end of input */
|
|
if (char_count >= inchars)
|
|
break; /* end of adjusted input characters */
|
|
if (!rm_ptr->stream || iod->wrap)
|
|
{ /* implicit record termination for non-stream files or stream files with the "wrap" option. */
|
|
if (!iod->wrap) /* non-stream device wants NOWRAP, so break right away without writing any more */
|
|
break;
|
|
if (!rm_ptr->fixed && iod->wrap)
|
|
iorm_write_utf_ascii(iod, RMEOL, STRLEN(RMEOL));
|
|
else if (rm_ptr->fixed && rm_ptr->out_bytes < rm_ptr->recordsize)
|
|
{ /* padding bytes needed */
|
|
temppad = rm_ptr->padchar;
|
|
if (CHSET_UTF16LE == iod->ochset)
|
|
{
|
|
temppadarray[0] = temppad;
|
|
temppadarray[1] = '\0';
|
|
padsize = 2;
|
|
} else if (CHSET_UTF16BE == iod->ochset)
|
|
{
|
|
temppadarray[0] = '\0';
|
|
temppadarray[1] = temppad;
|
|
padsize = 2;
|
|
} else
|
|
{
|
|
assert(CHSET_UTF8 == iod->ochset);
|
|
temppadarray[0] = temppad;
|
|
padsize = 1;
|
|
}
|
|
for ( ; rm_ptr->out_bytes < rm_ptr->recordsize; rm_ptr->out_bytes += padsize)
|
|
{
|
|
DOWRITERC(rm_ptr->fildes, temppadarray, padsize, status);
|
|
if (0 != status)
|
|
{
|
|
DOLLAR_DEVICE_WRITE(rm_ptr,status);
|
|
iod->dollar.za = 9;
|
|
rts_error(VARLSTCNT(1) status);
|
|
}
|
|
}
|
|
assert(rm_ptr->out_bytes == rm_ptr->recordsize);
|
|
}
|
|
iod->dollar.x = 0; /* don't use wteol to terminate wrapped records for fixed. */
|
|
iod->dollar.y++; /* \n is reserved as an end-of-rec delimiter for variable format */
|
|
if (iod->length) /* and fixed format requires no padding for wrapped records */
|
|
iod->dollar.y %= iod->length;
|
|
availwidth = iod->width;
|
|
} else
|
|
{ /* STREAM specified with NOWRAP */
|
|
/* We can continue to write even if device width is exceeded since NOWRAP has
|
|
* been specified. But if RECORDSIZE limit has been exceeded then we need to
|
|
* automatically terminate this WRITE (not the RECORD though) and return right away.
|
|
* In order to allow further WRITEs to add to this RECORD, reset rm_ptr->out_bytes to 0.
|
|
*/
|
|
if ((rm_ptr->out_bytes + mblen) > rm_ptr->recordsize)
|
|
{
|
|
rm_ptr->out_bytes = 0;
|
|
break;
|
|
}
|
|
}
|
|
rm_ptr->out_bytes = 0;
|
|
/* For UTF16, since input and output streams ("inptr" and "outstart") point to different buffers,
|
|
* the last parsed UTF16 character in "inptr" has not yet been written to "outstart" so
|
|
* redo the parsing by continuing to the beginning of the loop without incrementing "inptr".
|
|
* For UTF8, both are the same so we can increment "inptr" without any issues and avoid reparsing.
|
|
*/
|
|
if (CHSET_UTF8 == iod->ochset)
|
|
{
|
|
outstart = outstart + outbytes; /* advance within input string */
|
|
outbytes = usedwidth = 0;
|
|
} else
|
|
{
|
|
outstart = nextoutptr = outptr = &rm_ptr->outbuf[rm_ptr->out_bytes];
|
|
outbytes = usedwidth = 0;
|
|
continue;
|
|
}
|
|
}
|
|
assert(mblen);
|
|
assert(mblen <= rm_ptr->recordsize); /* there is room in RECORDSIZE to write at least one character */
|
|
rm_ptr->out_bytes += mblen;
|
|
outbytes += mblen;
|
|
inptr = nextmb; /* next input byte */
|
|
assert(inptr <= top);
|
|
char_count++;
|
|
usedwidth += mbwidth;
|
|
assert(usedwidth <= availwidth); /* there is room in display WIDTH to write at least one character */
|
|
}
|
|
iod->dollar.za = 0;
|
|
return;
|
|
}
|
|
|
|
void iorm_write(mstr *v)
|
|
{
|
|
io_desc *iod;
|
|
char *out;
|
|
int inlen, outlen, status, len;
|
|
d_rm_struct *rm_ptr;
|
|
int flags;
|
|
int fcntl_res;
|
|
|
|
iod = io_curr_device.out;
|
|
#ifdef __MVS__
|
|
if (NULL == iod->dev_sp)
|
|
rm_ptr = (d_rm_struct *)(iod->pair.in)->dev_sp;
|
|
else
|
|
rm_ptr = (d_rm_struct *)(iod->pair.out)->dev_sp;
|
|
#else
|
|
rm_ptr = (d_rm_struct *)iod->dev_sp;
|
|
#endif
|
|
memcpy(rm_ptr->dollar_device, "0", SIZEOF("0"));
|
|
|
|
if (rm_ptr->noread)
|
|
rts_error(VARLSTCNT(1) ERR_DEVICEREADONLY);
|
|
if (!iod->dollar.zeof && !rm_ptr->fifo && !rm_ptr->pipe)
|
|
{
|
|
iod->dollar.za = 9;
|
|
rts_error(VARLSTCNT(1) ERR_NOTTOEOFONPUT);
|
|
}
|
|
|
|
/* if it's a fifo and not system output/error, last operation was not a write and O_NONBLOCK is not set
|
|
then set it. A read will turn it off */
|
|
if (rm_ptr->fifo && (2 < rm_ptr->fildes) && (RM_WRITE != rm_ptr->lastop))
|
|
{
|
|
flags = 0;
|
|
FCNTL2(rm_ptr->fildes, F_GETFL, flags);
|
|
if (0 > flags)
|
|
rts_error(VARLSTCNT(8) ERR_SYSCALL, 5, LEN_AND_LIT("fcntl"), CALLFROM, errno);
|
|
if (!(flags & O_NONBLOCK))
|
|
{
|
|
FCNTL3(rm_ptr->fildes, F_SETFL, (flags | O_NONBLOCK), fcntl_res);
|
|
if (0 > fcntl_res)
|
|
rts_error(VARLSTCNT(8) ERR_SYSCALL, 5, LEN_AND_LIT("fcntl"), CALLFROM, errno);
|
|
}
|
|
}
|
|
|
|
rm_ptr->lastop = RM_WRITE;
|
|
if (IS_UTF_CHSET(iod->ochset))
|
|
{
|
|
iorm_write_utf(v);
|
|
return;
|
|
}
|
|
inlen = v->len;
|
|
if (rm_ptr->stream && !iod->wrap)
|
|
outlen = iod->width;
|
|
else
|
|
outlen = iod->width - iod->dollar.x;
|
|
|
|
if (!iod->wrap && inlen > outlen && outlen != MAX_WIDTH)
|
|
inlen = outlen;
|
|
if (!inlen)
|
|
return;
|
|
if (outlen > inlen)
|
|
outlen = inlen;
|
|
for (out = v->addr; ; out += len)
|
|
{
|
|
len = MIN(inlen, outlen);
|
|
if (rm_ptr->fifo || rm_ptr->pipe)
|
|
{
|
|
WRITEPIPE(rm_ptr->fildes, rm_ptr->pipe_buff_size, out, len, status);
|
|
} else
|
|
{
|
|
DOWRITERC(rm_ptr->fildes, out, len, status);
|
|
}
|
|
if (0 != status)
|
|
{
|
|
DOLLAR_DEVICE_WRITE(rm_ptr,status);
|
|
iod->dollar.za = 9;
|
|
rts_error(VARLSTCNT(1) status);
|
|
}
|
|
iod->dollar.x += len;
|
|
if ((inlen -= len) <= 0)
|
|
break;
|
|
|
|
if (!rm_ptr->stream || iod->wrap)
|
|
/* implicit record termination for non-stream files
|
|
* or stream files with the "wrap" option.
|
|
*/
|
|
{
|
|
if (!rm_ptr->fixed && iod->wrap)
|
|
{
|
|
DOWRITERC(rm_ptr->fildes, RMEOL, STRLEN(RMEOL), status);
|
|
if (0 != status)
|
|
{
|
|
DOLLAR_DEVICE_WRITE(rm_ptr,status);
|
|
iod->dollar.za = 9;
|
|
rts_error(VARLSTCNT(1) status);
|
|
}
|
|
}
|
|
|
|
iod->dollar.x = 0; /* don't use wteol to terminate wrapped records for fixed. */
|
|
iod->dollar.y++; /* \n is reserved as an end-of-rec delimiter for variable format */
|
|
if (iod->length) /* and fixed format requires no padding for wrapped records */
|
|
iod->dollar.y %= iod->length;
|
|
|
|
outlen = iod->width;
|
|
}
|
|
if (outlen > inlen)
|
|
outlen = inlen;
|
|
}
|
|
iod->dollar.za = 0;
|
|
return;
|
|
}
|