/* 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 . */ #include "ga68.h" #include #include #include #include /* For open. */ #include /* For close and write. */ #include /* For errno. */ #include #include /* For struct stat */ #include #include /* For gethostbyname. */ #include /* 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; } /* Implementation of the posix prelude `exit'. */ void _libga68_posixexit (int status) { exit (status); }