fis-gtm/sr_unix/iorm_write.c

413 lines
12 KiB
C

/****************************************************************
* *
* Copyright 2001, 2011 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
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)
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;
}