gcc/libga68/ga68-posix.c

464 lines
10 KiB
C

/* Support run-time routines for the POSIX prelude.
Copyright (C) 2025 Jose E. Marchesi.
GCC is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
GCC is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
details.
Under Section 7 of GPL version 3, you are granted additional permissions
described in the GCC Runtime Library Exception, version 3.1, as published by
the Free Software Foundation.
You should have received a copy of the GNU General Public License and a copy
of the GCC Runtime Library Exception along with this program; see the files
COPYING3 and COPYING.RUNTIME respectively. If not, see
<http://www.gnu.org/licenses/>. */
#include "ga68.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h> /* For open. */
#include <unistd.h> /* For close and write. */
#include <errno.h> /* For errno. */
#include <sys/socket.h>
#include <sys/stat.h> /* For struct stat */
#include <netinet/in.h>
#include <netdb.h> /* For gethostbyname. */
#include <limits.h> /* For LLONG_MAX */
#define EOF_PSEUDO_CHARACTER -1
/* Some Unicode code points used in this file. */
#define REPLACEMENT_CHARACTER 0xFFFD
#define NEWLINE 0x000A
/* Errno. */
static int _libga68_errno;
/* Simple I/O based on POSIX file descriptors. */
int
_libga68_posixerrno (void)
{
return _libga68_errno;
}
void
_libga68_posixperror (uint32_t *s, size_t len, size_t stride)
{
size_t u8len;
uint8_t *u8str = _libga68_u32_to_u8 (s, len, stride, NULL, &u8len);
const char *errstr = strerror (_libga68_errno);
(void) write (2, u8str, u8len);
(void) write (2, ": ", 2);
(void) write (2, errstr, strlen (errstr));
(void) write (2, "\n", 1);
}
uint32_t *
_libga68_posixstrerror (int errnum, size_t *len)
{
const char *str = strerror (errnum);
return _libga68_u8_to_u32 ((const uint8_t *)str, strlen (str), NULL, len);
}
/* Helper for _libga68_posixfopen. */
static int
_libga68_open (const char *path, unsigned int flags)
{
int fd = open (path, flags);
_libga68_errno = errno;
return fd;
}
#define FILE_O_DEFAULT 0x99999999
#define FILE_O_RDONLY 0x0
#define FILE_O_WRONLY 0x1
#define FILE_O_RDWR 0x2
#define FILE_O_TRUNC 0x8
int
_libga68_posixfopen (const uint32_t *pathname, size_t len, size_t stride,
unsigned int flags)
{
int fd;
int openflags = 0;
size_t u8len;
const uint8_t *u8pathname = _libga68_u32_to_u8 (pathname, len, stride, NULL,
&u8len);
char *filepath = (char *) _libga68_malloc_internal (u8len + 1);
memcpy (filepath, u8pathname, u8len);
filepath[u8len] = '\0';
/* Default mode: try read-write initially.
If that fails, then try read-only.
If that fails, then try write-only. */
if (flags == FILE_O_DEFAULT)
{
openflags = O_RDWR;
if ((fd = _libga68_open (filepath, openflags)) < 0)
{
openflags = O_RDONLY;
if ((fd = _libga68_open (filepath, openflags)) < 0)
{
openflags = O_WRONLY;
fd = _libga68_open (filepath, openflags);
_libga68_free_internal (filepath);
return fd;
}
}
_libga68_free_internal (filepath);
return fd;
}
if (flags & FILE_O_RDONLY)
openflags |= O_RDONLY;
if (flags & FILE_O_WRONLY)
openflags |= O_WRONLY;
if (flags & FILE_O_RDWR)
openflags |= O_RDWR;
if (flags & FILE_O_TRUNC)
openflags |= O_TRUNC;
fd = _libga68_open (filepath, openflags);
_libga68_free_internal (filepath);
return fd;
}
int
_libga68_posixcreat (uint32_t *pathname, size_t len, size_t stride,
uint32_t mode)
{
size_t u8len;
uint8_t *u8pathname = _libga68_u32_to_u8 (pathname, len, stride, NULL, &u8len);
u8pathname[u8len] = '\0';
int res = creat (u8pathname, mode);
_libga68_errno = errno;
return res;
}
int
_libga68_posixclose (int fd)
{
int res = close (fd);
_libga68_errno = errno;
return res;
}
/* Implementation of the posix prelude `posix argc'. */
int
_libga68_posixargc (void)
{
return _libga68_argc;
}
/* Implementation of the posix prelude `posix argv'. */
uint32_t *
_libga68_posixargv (int n, size_t *len)
{
if (n < 0 || n > _libga68_argc)
{
/* Return an empty string. */
*len = 0;
return NULL;
}
else
{
char *arg = _libga68_argv[n - 1];
return _libga68_u8_to_u32 (arg, strlen (arg), NULL, len);
}
}
/* Implementation of the posix prelude `posix getenv'. */
void
_libga68_posixgetenv (uint32_t *s, size_t len, size_t stride,
uint32_t **r, size_t *rlen)
{
size_t varlen;
char *varname = _libga68_u32_to_u8 (s, len, stride, NULL, &varlen);
char *var = _libga68_malloc_internal (varlen + 1);
memcpy (var, varname, varlen);
var[varlen] = '\0';
char *val = getenv (var);
_libga68_free_internal (var);
if (val == NULL)
{
/* Return an empty string. */
*r = NULL;
*rlen = 0;
}
else
*r = _libga68_u8_to_u32 (val, strlen (val), NULL, rlen);
}
/* Implementation of the posix prelude `posix puts'. */
void
_libga68_posixputs (uint32_t *s, size_t len, size_t stride)
{
(void) _libga68_posixfputs (1, s, len, stride);
}
/* Implementation of the posix prelude `posix fputs'. */
int
_libga68_posixfputs (int fd, uint32_t *s, size_t len, size_t stride)
{
size_t u8len;
uint8_t *u8str = _libga68_u32_to_u8 (s, len, stride, NULL, &u8len);
ssize_t ret = write (fd, u8str, u8len);
_libga68_errno = errno;
if (ret == -1)
return 0;
else
return u8len;
}
/* Implementation of the posix prelude `posix putc'. */
uint32_t
_libga68_posixfputc (int fd, uint32_t c)
{
uint8_t u8[6];
int u8len = _libga68_u8_uctomb (u8, c, 6);
if (u8len < 0)
return EOF_PSEUDO_CHARACTER;
ssize_t ret = write (fd, &u8, u8len);
if (ret == -1)
return EOF_PSEUDO_CHARACTER;
else
return c;
}
/* Implementation of the posix prelude `posix putchar'. */
uint32_t
_libga68_posixputchar (uint32_t c)
{
return _libga68_posixfputc (1, c);
}
/* Implementation of the posix prelude `posix fgetc'. */
uint32_t
_libga68_posixfgetc (int fd)
{
/* We need to read one char (byte) at a time from FD, until we complete a
full Unicode character. Then we convert to UCS-4. */
uint8_t c;
uint8_t u8c[6];
size_t morechars = 0;
size_t i;
/* Read first UTF-8 character. This gives us the total length of the
character. */
if (read (fd, &c, 1) != 1)
return EOF_PSEUDO_CHARACTER;
if (c < 128)
morechars = 0;
else if (c < 224)
morechars = 1;
else if (c < 240)
morechars = 2;
else
morechars = 3;
u8c[0] = c;
for (i = 0; i < morechars; ++i)
{
if (read (fd, &c, 1) != 1)
return EOF_PSEUDO_CHARACTER;
u8c[i + 1] = c;
}
uint32_t res;
int num_units = morechars + 1;
int length = _libga68_u8_mbtouc (&res, (const uint8_t *) &u8c, num_units);
if (res == REPLACEMENT_CHARACTER || length != num_units)
return REPLACEMENT_CHARACTER;
else
return res;
}
/* Implementation of the posix prelude `posix getchar'. */
uint32_t
_libga68_posixgetchar (void)
{
return _libga68_posixfgetc (0);
}
/* Implementation of the posix prelude `posix fgets'. */
uint32_t *
_libga68_posixfgets (int fd, int nchars, size_t *len)
{
uint32_t *res = NULL;
int n = 0;
uint32_t uc;
if (nchars > 0)
{
/* Read exactly nchar or until EOF. */
res = _libga68_malloc (nchars * sizeof (uint32_t));
do
{
uc = _libga68_posixfgetc (fd);
if (uc == EOF_PSEUDO_CHARACTER)
break;
res[n++] = uc;
}
while (n < nchars);
}
else
{
/* Read until newline or EOF. */
size_t allocated = 80 * sizeof (uint32_t);
res = _libga68_malloc (allocated);
do
{
uc = _libga68_posixfgetc (fd);
if (uc != EOF_PSEUDO_CHARACTER)
{
if (n % 80 == 0)
res = _libga68_realloc (res, n * 80 * sizeof (uint32_t) + 80 * sizeof (uint32_t));
res[n++] = uc;
}
}
while (uc != NEWLINE && uc != EOF_PSEUDO_CHARACTER);
if (n > 0)
res = _libga68_realloc (res, n * 80 * sizeof (uint32_t));
}
*len = n;
return res;
}
/* Implementation of the posix prelude `posix gets'. */
uint32_t *
_libga68_posixgets (int nchars, size_t *len)
{
return _libga68_posixfgets (0, nchars, len);
}
/* Implementation of the posix prelude `fconnect'. */
int
_libga68_posixfconnect (uint32_t *str, size_t len, size_t stride,
int port)
{
size_t u8len;
uint8_t *u8host = _libga68_u32_to_u8 (str, len, stride, NULL, &u8len);
/* Create a stream socket. */
int fd = socket (AF_INET, SOCK_STREAM, 0);
_libga68_errno = errno;
if (fd < 0)
goto error;
/* Lookup the specified host. */
char *host = _libga68_malloc_internal (u8len + 1);
memcpy (host, u8host, u8len);
host[u8len] = '\0';
struct hostent *server = gethostbyname (host);
if (server == NULL)
{
_libga68_errno = h_errno;
goto close_fd_and_error;
}
/* Connect the socket to the server. */
struct sockaddr_in serv_addr;
memset (&serv_addr, 0, sizeof (serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons (port);
memcpy (&serv_addr.sin_addr.s_addr,
server->h_addr,
server->h_length);
int res = connect (fd, (struct sockaddr *) &serv_addr,
sizeof (serv_addr));
_libga68_errno = errno;
if (res == -1)
goto close_fd_and_error;
_libga68_free_internal (host);
return fd;
close_fd_and_error:
close (fd);
error:
_libga68_free_internal (host);
return -1;
}
/* Implementation of the posix prelude `fsize'. */
long long int
_libga68_posixfsize (int fd)
{
struct stat stat;
if (fstat (fd, &stat) == -1)
{
_libga68_errno = errno;
return -1;
}
if (stat.st_size > LLONG_MAX)
{
_libga68_errno = EOVERFLOW;
return -1;
}
return (long int) stat.st_size;
}
/* Implementation of the posix prelude `lseek'. */
#define A68_SEEK_CUR 0
#define A68_SEEK_END 1
#define A68_SEEK_SET 2
long long int
_libga68_posixlseek (int fd, long long int offset, int whence)
{
switch (whence)
{
case A68_SEEK_CUR:
whence = SEEK_CUR;
break;
case A68_SEEK_END:
whence = SEEK_END;
break;
case A68_SEEK_SET:
whence = SEEK_SET;
break;
}
long long int ret = (long long int) lseek(fd, offset, whence);
_libga68_errno = errno;
return ret;
}