#define PERL_NO_GET_CONTEXT
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "perliol.h"
#include "ppport.h"
#ifdef WIN32
#define WORKBUF_SIZE 40
#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
#endif
typedef struct {
struct _PerlIO base;
/* the CRT handle, typically 1 or 2 */
int fd;
/* the Win32 handle */
HANDLE h;
/* mode of the handle*/
int imode;
/* buffer containing incomplete utf8 characters
or possible escape sequences.
*/
U8 workbuf[WORKBUF_SIZE];
size_t workbuf_used;
/* used when translating utf-8 to utf-16 */
/* expanded as needed */
wchar_t *outbuf;
int outbuf_size;
} PerlIOW32Con;
/* we largely ignore the flags at this point, but do propagate them
for dup.
This is PerlIOUnix_oflags() from perlio.c
*/
int
PerlIOW32Con_oflags(const char *mode)
{
int oflags = -1;
if (*mode == IoTYPE_IMPLICIT || *mode == IoTYPE_NUMERIC)
mode++;
switch (*mode) {
case 'r':
oflags = O_RDONLY;
if (*++mode == '+') {
oflags = O_RDWR;
mode++;
}
break;
case 'w':
oflags = O_CREAT | O_TRUNC;
if (*++mode == '+') {
oflags |= O_RDWR;
mode++;
}
else
oflags |= O_WRONLY;
break;
case 'a':
oflags = O_CREAT | O_APPEND;
if (*++mode == '+') {
oflags |= O_RDWR;
mode++;
}
else
oflags |= O_WRONLY;
break;
}
/* XXX TODO: PerlIO_open() test that exercises 'rb' and 'rt'. */
/* Unless O_BINARY is different from O_TEXT, first bit-or:ing one
* of them in, and then bit-and-masking the other them away, won't
* have much of an effect. */
switch (*mode) {
case 'b':
#if O_TEXT != O_BINARY
oflags |= O_BINARY;
oflags &= ~O_TEXT;
#endif
mode++;
break;
case 't':
#if O_TEXT != O_BINARY
oflags |= O_TEXT;
oflags &= ~O_BINARY;
#endif
mode++;
break;
default:
#if O_BINARY != 0
/* bit-or:ing with zero O_BINARY would be useless. */
/*
* If neither "t" nor "b" was specified, open the file
* in O_BINARY mode.
*
* Note that if something else than the zero byte was seen
* here (e.g. bogus mode "rx"), just few lines later we will
* set the errno and invalidate the flags.
*/
oflags |= O_BINARY;
#endif
break;
}
if (*mode || oflags == -1) {
SETERRNO(EINVAL, LIB_INVARG);
oflags = -1;
}
return oflags;
}
static IV
PerlIOW32Con_pushed(pTHX_ PerlIO* f, const char* mode, SV* arg,
PerlIO_funcs* tab) {
PERL_UNUSED_ARG(mode);
PERL_UNUSED_ARG(tab);
/* FIXME: check mode? */
/* mode is NULL on binmode? */
if (SvOK(arg)) {
STRLEN len;
(void)SvPV(arg, len);
if (len) {
errno = EINVAL;
return -1;
}
}
PerlIOW32Con *con = PerlIOSelf(f, PerlIOW32Con);
PerlIO *next = PerlIONext(f);
if (next) {
/* FIXME: flush? */
/* otherwise it should come from open
as with :unix, we never call down
*/
con->fd = PerlIO_fileno(next);
}
con->imode = mode ? PerlIOW32Con_oflags(mode) : 0;
con->h = (HANDLE)win32_get_osfhandle(con->fd);
con->outbuf = NULL;
con->outbuf_size = 0;
DWORD cmode;
if (!GetConsoleMode(con->h, &cmode)) {
errno = ENOTTY;
return -1;
}
cmode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
SetConsoleMode(con->h, cmode);
PerlIOBase(f)->flags |= PERLIO_F_UTF8 | PERLIO_F_OPEN;
return 0;
}
IV
PerlIOW32Con_popped(pTHX_ PerlIO *f)
{
PerlIOW32Con * const os = PerlIOSelf(f, PerlIOW32Con);
PERL_UNUSED_CONTEXT;
if (os->outbuf) {
PerlMemShared_free(os->outbuf);
os->outbuf = NULL;
os->outbuf_size = 0;
}
return 0;
}
static void
PerlIOW32Con_setfd(pTHX_ PerlIO *f, int fd) {
PerlIOSelf(f, PerlIOW32Con)->fd = fd;
}
/* largely PerlIOUnix_open() */
static PerlIO *
PerlIOW32Con_open(pTHX_ PerlIO_funcs *self, PerlIO_list_t *layers,
IV n, const char *mode, int fd, int imode,
int perm, PerlIO *f, int narg, SV **args)
{
/* cloexec functions not visible */
/*bool known_cloexec = 0;*/
if (PerlIOValid(f)) {
if (PerlIOBase(f)->tab && PerlIOBase(f)->flags & PERLIO_F_OPEN)
(*PerlIOBase(f)->tab->Close)(aTHX_ f);
}
if (narg > 0) {
if (*mode == IoTYPE_NUMERIC)
mode++;
else {
imode = PerlIOW32Con_oflags(mode);
#ifdef VMS
perm = 0777; /* preserve RMS defaults, ACL inheritance, etc. */
#else
perm = 0666;
#endif
}
if (imode != -1) {
STRLEN len;
const char *path = SvPV_const(*args, len);
if (!IS_SAFE_PATHNAME(path, len, "open"))
return NULL;
fd = _open(path, imode, perm);
/*known_cloexec = 1;*/
}
}
if (fd >= 0) {
#if 0
/* these functions not exported or not win32? */
if (known_cloexec)
Perl_setfd_inhexec_for_sysfd(aTHX_ fd);
else
Perl_setfd_cloexec_or_inhexec_by_sysfdness(aTHX_ fd);
#endif
if (*mode == IoTYPE_IMPLICIT)
mode++;
if (!f) {
f = PerlIO_allocate(aTHX);
}
if (!PerlIOValid(f)) {
/* push sets the handle */
if (!(f = PerlIO_push(aTHX_ f, self, mode, PerlIOArg))) {
PerlLIO_close(fd);
return NULL;
}
}
PerlIOW32Con_setfd(aTHX_ f, fd);
PerlIOBase(f)->flags |= PERLIO_F_OPEN;
return f;
}
else {
if (f) {
NOOP;
/*
* FIXME: pop layers ???
*/
}
return NULL;
}
}
static IV
PerlIOW32Con_fileno(pTHX_ PerlIO *f)
{
PERL_UNUSED_CONTEXT;
return PerlIOSelf(f, PerlIOW32Con)->fd;
}
static PerlIO *
PerlIOW32Con_dup(pTHX_ PerlIO *f, PerlIO *o, CLONE_PARAMS *param, int flags)
{
const PerlIOW32Con * const os = PerlIOSelf(o, PerlIOW32Con);
HANDLE h2 = NULL;
if (!DuplicateHandle(GetCurrentProcess(), os->h,
GetCurrentProcess(), &h2,
0, FALSE, DUPLICATE_SAME_ACCESS)) {
return NULL;
}
int fd = win32_open_osfhandle((intptr_t)h2, os->imode);
PerlIO *df = PerlIOBase_dup(aTHX_ f, o, param, flags);
if (!f) {
return NULL;
}
PerlIOW32Con_setfd(aTHX_ df, fd);
return df;
}
SSize_t
PerlIOW32Con_read(pTHX_ PerlIO *f, void *vbuf, Size_t count)
{
PERL_UNUSED_ARG(f);
PERL_UNUSED_ARG(vbuf);
PERL_UNUSED_ARG(count);
/* not implemented */
errno = EINVAL;
return -1;
}
SSize_t
PerlIOW32Con_write(pTHX_ PerlIO *f, const void *vbuf, Size_t count)
{
/* FIXME: locks */
/* FIXME: put unconsumed bytes in workbuf and use them the next time around */
/* FIXME: handle/discard out of range UTF-8? */
/* TODO: escape codes - might be possible with SetConsoleMode(... ENABLE_VIRTUAL_TERMINAL_PROCESSING) */
PerlIOW32Con * const os = PerlIOSelf(f, PerlIOW32Con);
LPCSTR in = vbuf;
int wcount = MultiByteToWideChar(CP_UTF8, 0, in, count, os->outbuf, os->outbuf_size);
if (wcount > os->outbuf_size) {
/* out of space, expand and try again */
int newsize = os->outbuf_size ? os->outbuf_size * 2 : WORKBUF_SIZE;
if (newsize < wcount)
newsize = wcount;
os->outbuf = PerlMemShared_realloc(os->outbuf, newsize * sizeof(wchar_t));
os->outbuf_size = newsize;
wcount = MultiByteToWideChar(CP_UTF8, 0, in, count, os->outbuf, os->outbuf_size);
}
if (wcount > 0
&& WriteConsoleW(os->h, os->outbuf, wcount, NULL, NULL)) {
/* assume we wrote all */
return count;
}
errno = EINVAL; /* FIXME: error code */
return -1;
}
Off_t
PerlIOW32Con_tell(pTHX_ PerlIO *f)
{
PERL_UNUSED_ARG(f);
errno = ESPIPE;
return -1;
}
IV
PerlIOW32Con_seek(pTHX_ PerlIO *f, Off_t offset, int whence)
{
PERL_UNUSED_ARG(f);
PERL_UNUSED_ARG(offset);
PERL_UNUSED_ARG(whence);
errno = ESPIPE;
return -1;
}
IV
PerlIOW32Con_close(pTHX_ PerlIO *f)
{
/* FIXME: flush? */
/* FIXME: error handling */
const int fd = PerlIOSelf(f, PerlIOW32Con)->fd;
_close(fd);
return 0;
}
PERLIO_FUNCS_DECL(PerlIO_win32console) = {
sizeof(PerlIO_funcs),
"win32console",
sizeof(PerlIOW32Con),
PERLIO_K_RAW,
PerlIOW32Con_pushed,
PerlIOW32Con_popped,
PerlIOW32Con_open,
PerlIOBase_binmode, /* binmode */
NULL,
PerlIOW32Con_fileno,
PerlIOW32Con_dup,
PerlIOW32Con_read,
PerlIOBase_unread,
PerlIOW32Con_write,
PerlIOW32Con_seek,
PerlIOW32Con_tell,
PerlIOW32Con_close,
PerlIOBase_noop_ok, /* flush */
PerlIOBase_noop_fail, /* fill */
PerlIOBase_eof,
PerlIOBase_error,
PerlIOBase_clearerr,
PerlIOBase_setlinebuf,
NULL, /* get_base */
NULL, /* get_bufsiz */
NULL, /* get_ptr */
NULL, /* get_cnt */
NULL, /* set_ptrcnt */
};
#endif
MODULE = PerlIO::win32console PACKAGE = PerlIO::win32console
BOOT:
#ifdef WIN32
PerlIO_define_layer(aTHX_ (PerlIO_funcs*)&PerlIO_win32console);
#endif