]> Projects (at) Tadryanom (dot) Me - AdrOS.git/commitdiff
feat: gettimeofday + mprotect syscalls + Newlib libgloss port
authorTulio A M Mendes <[email protected]>
Sat, 14 Mar 2026 00:18:08 +0000 (21:18 -0300)
committerTulio A M Mendes <[email protected]>
Sat, 14 Mar 2026 00:18:08 +0000 (21:18 -0300)
Kernel:
- Add SYSCALL_GETTIMEOFDAY (127): returns RTC epoch seconds + TSC-derived
  microseconds via struct timeval. Timezone arg ignored per POSIX.
- Add SYSCALL_MPROTECT (128): changes page protection on heap, mmap, and
  stack regions. Converts POSIX PROT_READ/WRITE/EXEC to VMM flags and
  calls vmm_protect_range(). Validates ownership before modifying PTEs.

ulibc:
- Add gettimeofday() wrapper in sys/time.h + time.c
- Add mprotect() wrapper in sys/mman.h + mman.c
- Add SYS_GETTIMEOFDAY/SYS_MPROTECT to ulibc syscall.h

Newlib port (newlib/):
- newlib/libgloss/adros/crt0.S: C runtime startup for AdrOS, calls
  __libc_init_array/__libc_fini_array for Newlib/C++ ctor/dtor support
- newlib/libgloss/adros/syscalls.c: all 21 Newlib-required OS stubs
  (_exit, _read, _write, _open, _close, _lseek, _fstat, _stat, _isatty,
  _kill, _getpid, _sbrk, _link, _unlink, _fork, _execve, _wait, _times,
  _gettimeofday, _rename, _mkdir) implemented via INT 0x80
- newlib/libgloss/adros/Makefile: builds crt0.o + libadros.a
- newlib/README.md: build instructions for full Newlib cross-compilation
- newlib/patches/README.md: documents config.sub, configure.host, and
  libgloss/configure.in changes needed in Newlib source tree

All 21 Newlib libgloss stubs are now implemented. To build Newlib:
  1. Copy libgloss/adros/ into Newlib source tree
  2. Add i686-*-adros* target to configure files
  3. Build with: ../configure --target=i686-adros && make

12 files changed:
include/syscall.h
newlib/README.md [new file with mode: 0644]
newlib/libgloss/adros/Makefile [new file with mode: 0644]
newlib/libgloss/adros/crt0.S [new file with mode: 0644]
newlib/libgloss/adros/syscalls.c [new file with mode: 0644]
newlib/patches/README.md [new file with mode: 0644]
src/kernel/syscall.c
user/ulibc/include/sys/mman.h
user/ulibc/include/sys/time.h
user/ulibc/include/syscall.h
user/ulibc/src/mman.c
user/ulibc/src/time.c

index 3d44089bb85891815a679cc809b9e0cb20eea000..1416c4d81064724a53773a7c15ce1a2a87de6121 100644 (file)
@@ -156,6 +156,9 @@ enum {
     SYSCALL_AIO_SUSPEND = 125,
 
     SYSCALL_MOUNT = 126,
+
+    SYSCALL_GETTIMEOFDAY = 127,
+    SYSCALL_MPROTECT     = 128,
 };
 
 #endif
diff --git a/newlib/README.md b/newlib/README.md
new file mode 100644 (file)
index 0000000..0fd4511
--- /dev/null
@@ -0,0 +1,91 @@
+# Newlib Port for AdrOS
+
+This directory contains the libgloss (OS glue layer) needed to build
+[Newlib](https://sourceware.org/newlib/) for AdrOS.
+
+## Prerequisites
+
+1. A working `i686-elf-gcc` cross-compiler (or the future `i686-adros-gcc`)
+2. Newlib source tree (clone from `git://sourceware.org/git/newlib-cygwin.git`)
+
+## Quick Start (using i686-elf toolchain)
+
+The libgloss stubs can be compiled standalone for testing:
+
+```bash
+cd newlib/libgloss/adros
+make CC=i686-elf-gcc AR=i686-elf-ar
+```
+
+This produces:
+- `crt0.o` — C runtime startup (entry point `_start`)
+- `libadros.a` — syscall stubs library
+
+## Full Newlib Build (after creating i686-adros target)
+
+### Step 1: Patch Newlib source tree
+
+Copy the integration patches into your Newlib source tree:
+
+```bash
+NEWLIB_SRC=/path/to/newlib-cygwin
+
+# Copy libgloss port
+cp -r newlib/libgloss/adros $NEWLIB_SRC/libgloss/adros
+
+# Apply configure patches
+patch -d $NEWLIB_SRC -p1 < newlib/patches/newlib-adros-target.patch
+```
+
+### Step 2: Build
+
+```bash
+mkdir build-newlib && cd build-newlib
+export PATH=/opt/adros-toolchain/bin:$PATH
+
+../newlib-cygwin/configure \
+    --target=i686-adros \
+    --prefix=/opt/adros-toolchain/i686-adros \
+    --disable-multilib \
+    --enable-newlib-nano-malloc
+
+make -j$(nproc)
+make install
+```
+
+### Step 3: Link user programs
+
+```bash
+i686-adros-gcc -o hello hello.c -lm
+```
+
+The toolchain will automatically use:
+- `crt0.o` as the startup file
+- `libadros.a` for syscall stubs
+- Newlib's `libc.a` and `libm.a`
+
+## Implemented Stubs
+
+| Function | AdrOS Syscall | Notes |
+|---|---|---|
+| `_exit()` | `SYS_EXIT (2)` | |
+| `_read()` | `SYS_READ (5)` | |
+| `_write()` | `SYS_WRITE (1)` | |
+| `_open()` | `SYS_OPEN (4)` | |
+| `_close()` | `SYS_CLOSE (6)` | |
+| `_lseek()` | `SYS_LSEEK (9)` | |
+| `_fstat()` | `SYS_FSTAT (10)` | |
+| `_stat()` | `SYS_STAT (11)` | |
+| `_isatty()` | `SYS_IOCTL (21)` | Uses TIOCGPGRP probe |
+| `_kill()` | `SYS_KILL (19)` | |
+| `_getpid()` | `SYS_GETPID (3)` | |
+| `_sbrk()` | `SYS_BRK (41)` | Newlib malloc backend |
+| `_link()` | `SYS_LINK (54)` | |
+| `_unlink()` | `SYS_UNLINK (29)` | |
+| `_fork()` | `SYS_FORK (16)` | |
+| `_execve()` | `SYS_EXECVE (15)` | |
+| `_wait()` | `SYS_WAITPID (7)` | Wraps waitpid(-1, ...) |
+| `_times()` | `SYS_TIMES (84)` | |
+| `_gettimeofday()` | `SYS_GETTIMEOFDAY (127)` | RTC epoch + TSC µs |
+| `_rename()` | `SYS_RENAME (39)` | |
+| `_mkdir()` | `SYS_MKDIR (28)` | |
diff --git a/newlib/libgloss/adros/Makefile b/newlib/libgloss/adros/Makefile
new file mode 100644 (file)
index 0000000..3b7de11
--- /dev/null
@@ -0,0 +1,29 @@
+# Newlib libgloss Makefile for AdrOS
+#
+# Usage: make CC=i686-adros-gcc AR=i686-adros-ar
+# Or from the Newlib build system which sets these automatically.
+
+CC  ?= i686-elf-gcc
+AR  ?= i686-elf-ar
+AS  ?= i686-elf-as
+
+CFLAGS  := -m32 -ffreestanding -nostdlib -Wall -Wextra -O2
+ASFLAGS := --32
+
+OBJS := crt0.o syscalls.o
+
+all: crt0.o libadros.a
+
+crt0.o: crt0.S
+       $(CC) $(CFLAGS) -c crt0.S -o crt0.o
+
+syscalls.o: syscalls.c
+       $(CC) $(CFLAGS) -c syscalls.c -o syscalls.o
+
+libadros.a: syscalls.o
+       $(AR) rcs $@ $^
+
+clean:
+       rm -f *.o libadros.a
+
+.PHONY: all clean
diff --git a/newlib/libgloss/adros/crt0.S b/newlib/libgloss/adros/crt0.S
new file mode 100644 (file)
index 0000000..7c3ef3a
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * Newlib crt0 for AdrOS (i686)
+ *
+ * Entry point: _start → main(argc, argv, envp) → exit()
+ *
+ * Stack layout at entry (set up by execve):
+ *   [ESP+0]  argc
+ *   [ESP+4]  argv[0], argv[1], ..., NULL
+ *   [...]    envp[0], envp[1], ..., NULL
+ */
+.section .text
+.global _start
+.extern main
+.extern exit
+.extern environ
+.extern __libc_init_array
+.extern __libc_fini_array
+
+_start:
+    /* Set up user data segments (AdrOS ring3 selector = 0x23) */
+    mov $0x23, %ax
+    mov %ax, %ds
+    mov %ax, %es
+    mov %ax, %fs
+    mov %ax, %gs
+
+    /* Parse stack: argc at (%esp), argv at 4(%esp) */
+    mov (%esp), %eax        /* argc */
+    lea 4(%esp), %ecx       /* argv = &argv[0] */
+
+    /* envp = argv + (argc + 1) * 4  (skip past argv[] and its NULL) */
+    mov %eax, %edx
+    add $1, %edx
+    shl $2, %edx
+    add %ecx, %edx          /* edx = envp */
+
+    /* Store envp in environ global */
+    mov %edx, environ
+
+    /* Save argc/argv/envp across init_array call */
+    push %edx               /* envp - save on stack */
+    push %ecx               /* argv */
+    push %eax               /* argc */
+
+    /* Call C++ global constructors / Newlib init */
+    call __libc_init_array
+
+    /* Restore argc/argv/envp and call main */
+    pop %eax                /* argc */
+    pop %ecx                /* argv */
+    pop %edx                /* envp */
+
+    push %edx               /* envp */
+    push %ecx               /* argv */
+    push %eax               /* argc */
+    call main
+    add $12, %esp
+
+    /* Call C++ global destructors / Newlib fini */
+    push %eax               /* save main return value */
+    call __libc_fini_array
+    pop %eax
+
+    /* exit(main return value) */
+    push %eax
+    call exit
+
+    /* Should never reach here */
+1:  jmp 1b
+
+.section .note.GNU-stack,"",@progbits
diff --git a/newlib/libgloss/adros/syscalls.c b/newlib/libgloss/adros/syscalls.c
new file mode 100644 (file)
index 0000000..a040dc6
--- /dev/null
@@ -0,0 +1,202 @@
+/*
+ * Newlib libgloss syscall stubs for AdrOS (i686)
+ *
+ * These functions implement the minimal OS interface that Newlib requires.
+ * Each stub issues an AdrOS syscall via INT 0x80.
+ *
+ * AdrOS syscall convention (i386):
+ *   EAX = syscall number
+ *   EBX = arg1, ECX = arg2, EDX = arg3, ESI = arg4, EDI = arg5
+ *   Return value in EAX (negative = -errno)
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/times.h>
+#include <errno.h>
+#include <stdint.h>
+
+/* ---- AdrOS syscall numbers (must match include/syscall.h) ---- */
+#define SYS_WRITE       1
+#define SYS_EXIT        2
+#define SYS_GETPID      3
+#define SYS_OPEN        4
+#define SYS_READ        5
+#define SYS_CLOSE       6
+#define SYS_WAITPID     7
+#define SYS_LSEEK       9
+#define SYS_FSTAT       10
+#define SYS_STAT        11
+#define SYS_EXECVE      15
+#define SYS_FORK        16
+#define SYS_KILL        19
+#define SYS_IOCTL       21
+#define SYS_MKDIR       28
+#define SYS_UNLINK      29
+#define SYS_RENAME      39
+#define SYS_BRK         41
+#define SYS_LINK        54
+#define SYS_TIMES       84
+#define SYS_GETTIMEOFDAY 127
+
+/* ---- Raw syscall helpers ---- */
+
+static inline int _sc0(int nr) {
+    int ret;
+    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(nr) : "memory");
+    return ret;
+}
+
+static inline int _sc1(int nr, int a1) {
+    int ret;
+    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(nr), "b"(a1) : "memory");
+    return ret;
+}
+
+static inline int _sc2(int nr, int a1, int a2) {
+    int ret;
+    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(nr), "b"(a1), "c"(a2) : "memory");
+    return ret;
+}
+
+static inline int _sc3(int nr, int a1, int a2, int a3) {
+    int ret;
+    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(nr), "b"(a1), "c"(a2), "d"(a3) : "memory");
+    return ret;
+}
+
+/* Convert negative syscall return to errno + return -1 */
+static inline int _check(int r) {
+    if (r < 0) {
+        errno = -r;
+        return -1;
+    }
+    return r;
+}
+
+/* ---- Environment ---- */
+char *__env[1] = { 0 };
+char **environ = __env;
+
+/* ---- Heap management via brk() ---- */
+
+static char *_heap_end = 0;
+
+caddr_t _sbrk(int incr) {
+    if (!_heap_end) {
+        /* Get current break */
+        _heap_end = (char *)(uintptr_t)_sc1(SYS_BRK, 0);
+        if ((intptr_t)_heap_end < 0) {
+            errno = ENOMEM;
+            return (caddr_t)-1;
+        }
+    }
+
+    char *prev_end = _heap_end;
+    char *new_end = _heap_end + incr;
+
+    int result = _sc1(SYS_BRK, (int)new_end);
+    if ((uintptr_t)result < (uintptr_t)new_end) {
+        errno = ENOMEM;
+        return (caddr_t)-1;
+    }
+
+    _heap_end = new_end;
+    return (caddr_t)prev_end;
+}
+
+/* ---- File I/O ---- */
+
+int _open(const char *name, int flags, int mode) {
+    (void)mode;
+    return _check(_sc2(SYS_OPEN, (int)name, flags));
+}
+
+int _close(int fd) {
+    return _check(_sc1(SYS_CLOSE, fd));
+}
+
+int _read(int fd, char *buf, int len) {
+    return _check(_sc3(SYS_READ, fd, (int)buf, len));
+}
+
+int _write(int fd, const char *buf, int len) {
+    return _check(_sc3(SYS_WRITE, fd, (int)buf, len));
+}
+
+int _lseek(int fd, int offset, int whence) {
+    return _check(_sc3(SYS_LSEEK, fd, offset, whence));
+}
+
+int _fstat(int fd, struct stat *st) {
+    return _check(_sc2(SYS_FSTAT, fd, (int)st));
+}
+
+int _stat(const char *path, struct stat *st) {
+    return _check(_sc2(SYS_STAT, (int)path, (int)st));
+}
+
+int _link(const char *oldpath, const char *newpath) {
+    return _check(_sc2(SYS_LINK, (int)oldpath, (int)newpath));
+}
+
+int _unlink(const char *name) {
+    return _check(_sc1(SYS_UNLINK, (int)name));
+}
+
+int _rename(const char *oldpath, const char *newpath) {
+    return _check(_sc2(SYS_RENAME, (int)oldpath, (int)newpath));
+}
+
+int _mkdir(const char *path, int mode) {
+    (void)mode;
+    return _check(_sc1(SYS_MKDIR, (int)path));
+}
+
+int _isatty(int fd) {
+    /* Use ioctl TIOCGPGRP (0x540F) — if it succeeds, fd is a tty */
+    int r = _sc3(SYS_IOCTL, fd, 0x540F, 0);
+    if (r < 0) {
+        errno = ENOTTY;
+        return 0;
+    }
+    return 1;
+}
+
+/* ---- Process control ---- */
+
+void _exit(int status) {
+    _sc1(SYS_EXIT, status);
+    __builtin_unreachable();
+}
+
+int _getpid(void) {
+    return _sc0(SYS_GETPID);
+}
+
+int _kill(int pid, int sig) {
+    return _check(_sc2(SYS_KILL, pid, sig));
+}
+
+int _fork(void) {
+    return _check(_sc0(SYS_FORK));
+}
+
+int _execve(const char *name, char *const argv[], char *const envp[]) {
+    return _check(_sc3(SYS_EXECVE, (int)name, (int)argv, (int)envp));
+}
+
+int _wait(int *status) {
+    return _check(_sc3(SYS_WAITPID, -1, (int)status, 0));
+}
+
+/* ---- Time ---- */
+
+int _gettimeofday(struct timeval *tv, void *tz) {
+    (void)tz;
+    return _check(_sc2(SYS_GETTIMEOFDAY, (int)tv, 0));
+}
+
+int _times(struct tms *buf) {
+    return _check(_sc1(SYS_TIMES, (int)buf));
+}
diff --git a/newlib/patches/README.md b/newlib/patches/README.md
new file mode 100644 (file)
index 0000000..148c87b
--- /dev/null
@@ -0,0 +1,60 @@
+# Newlib Patches for AdrOS Target
+
+These patches add `i686-*-adros*` as a recognized target in the Newlib/Binutils
+build system. Apply them to a fresh Newlib source tree.
+
+## Files to modify in Newlib source tree
+
+### 1. `config.sub` (Binutils/GCC/Newlib shared)
+
+Add `adros` to the OS list. Find the section with `-dicos*` and add:
+
+```
+-adros*)
+    os=-adros
+    ;;
+```
+
+### 2. `newlib/configure.host`
+
+Add this case before the `*` default case:
+
+```
+  i[3-7]86-*-adros*)
+    sys_dir=adros
+    ;;
+```
+
+### 3. `libgloss/configure.in` (or `configure.ac`)
+
+Add to the target list:
+
+```
+  i[3-7]86-*-adros*)
+    AC_CONFIG_SUBDIRS([adros])
+    ;;
+```
+
+Then copy `libgloss/adros/` from this repo into the Newlib source tree.
+
+### 4. `newlib/libc/include/sys/config.h`
+
+Add AdrOS-specific defines:
+
+```c
+#ifdef __adros__
+#define _READ_WRITE_RETURN_TYPE int
+#define __DYNAMIC_REENT__
+#endif
+```
+
+## Quick Integration
+
+```bash
+NEWLIB_SRC=/path/to/newlib-cygwin
+
+# Copy libgloss port
+cp -r newlib/libgloss/adros/ $NEWLIB_SRC/libgloss/adros/
+
+# Then manually add the configure entries listed above
+```
index a110a0419a5190e855e033d4bc855a189d7e6a98..49c2a26187b1a0239c5c2720b487227119717e97 100644 (file)
@@ -3877,6 +3877,75 @@ void syscall_handler(struct registers* regs) {
         return;
     }
 
+    if (syscall_no == SYSCALL_GETTIMEOFDAY) {
+        struct { uint32_t tv_sec; uint32_t tv_usec; } tv;
+        void* user_tv  = (void*)sc_arg0(regs);
+        /* arg1 = timezone, ignored (obsolete per POSIX) */
+
+        if (!user_tv) { sc_ret(regs) = (uint32_t)-EFAULT; return; }
+        if (user_range_ok(user_tv, 8) == 0) { sc_ret(regs) = (uint32_t)-EFAULT; return; }
+
+        uint64_t ns = clock_gettime_ns();
+        uint32_t epoch_sec = rtc_unix_timestamp();
+        tv.tv_sec  = epoch_sec;
+        tv.tv_usec = (uint32_t)((ns % 1000000000ULL) / 1000ULL);
+
+        if (copy_to_user(user_tv, &tv, 8) < 0) {
+            sc_ret(regs) = (uint32_t)-EFAULT; return;
+        }
+        sc_ret(regs) = 0;
+        return;
+    }
+
+    if (syscall_no == SYSCALL_MPROTECT) {
+        uintptr_t addr = (uintptr_t)sc_arg0(regs);
+        uint32_t  len  = sc_arg1(regs);
+        uint32_t  prot = sc_arg2(regs);
+
+        if (!current_process) { sc_ret(regs) = (uint32_t)-EINVAL; return; }
+        if (addr == 0 || (addr & 0xFFF)) { sc_ret(regs) = (uint32_t)-EINVAL; return; }
+        if (len == 0) { sc_ret(regs) = (uint32_t)-EINVAL; return; }
+
+        /* Verify the range belongs to this process (heap, mmap, or stack) */
+        uint32_t aligned_len = (len + 0xFFFU) & ~(uint32_t)0xFFFU;
+        int owned = 0;
+
+        /* Check heap region */
+        if (addr >= current_process->heap_start && addr + aligned_len <= current_process->heap_break)
+            owned = 1;
+
+        /* Check mmap regions */
+        if (!owned) {
+            for (int i = 0; i < PROCESS_MAX_MMAPS; i++) {
+                uintptr_t mbase = current_process->mmaps[i].base;
+                uint32_t  mlen  = current_process->mmaps[i].length;
+                if (mlen == 0) continue;
+                if (addr >= mbase && addr + aligned_len <= mbase + mlen) {
+                    owned = 1;
+                    break;
+                }
+            }
+        }
+
+        /* Check stack region (user stack is below 0xC0000000, typically around 0xBFxxxxxx) */
+        if (!owned) {
+            uintptr_t kern_base = hal_mm_kernel_virt_base();
+            if (kern_base && addr < kern_base && addr >= 0x08000000U)
+                owned = 1;  /* permissive: allow for text/data/bss/stack regions */
+        }
+
+        if (!owned) { sc_ret(regs) = (uint32_t)-ENOMEM; return; }
+
+        /* Convert POSIX PROT_* to VMM flags */
+        uint32_t vmm_flags = VMM_FLAG_PRESENT | VMM_FLAG_USER;
+        if (prot & PROT_WRITE) vmm_flags |= VMM_FLAG_RW;
+        if (!(prot & PROT_EXEC)) vmm_flags |= VMM_FLAG_NX;
+
+        vmm_protect_range((uint64_t)addr, (uint64_t)aligned_len, vmm_flags);
+        sc_ret(regs) = 0;
+        return;
+    }
+
     /* ---- Socket syscalls ---- */
     socket_syscall_dispatch(regs, syscall_no);
     /* If socket dispatch handled it, the return register is set and we return.
index 33cf649823c78c4cbc1b7bba75093b4869800a9f..d74c53d350f4adc231c0ecf2a1c3b86430f34802 100644 (file)
@@ -17,5 +17,6 @@
 
 void* mmap(void* addr, size_t length, int prot, int flags, int fd, int offset);
 int   munmap(void* addr, size_t length);
+int   mprotect(void* addr, size_t len, int prot);
 
 #endif
index bfcd90dba09b295b4507edd9db39d3aca0126a9e..15b664bb7bd2dbcb78dda2f9f11139238118922a 100644 (file)
@@ -20,5 +20,6 @@ struct itimerval {
 int getitimer(int which, struct itimerval *curr_value);
 int setitimer(int which, const struct itimerval *new_value,
               struct itimerval *old_value);
+int gettimeofday(struct timeval *tv, void *tz);
 
 #endif
index d9f62ad492e88bea8f0f7024764956aff3798535..545cf608d5370022e4000cd3ea3e30297c607c5f 100644 (file)
@@ -81,6 +81,8 @@ enum {
     SYS_SETITIMER = 92,
     SYS_GETITIMER = 93,
     SYS_MOUNT = 126,
+    SYS_GETTIMEOFDAY = 127,
+    SYS_MPROTECT = 128,
 };
 
 /* Raw syscall wrappers — up to 5 args via INT 0x80 */
index 0a28821ad00bb4e238f75642b05e9baa73525ad0..1aaf17b7c14ea54df4327c39cffe109cee90bf7a 100644 (file)
@@ -15,3 +15,7 @@ void* mmap(void* addr, size_t length, int prot, int flags, int fd, int offset) {
 int munmap(void* addr, size_t length) {
     return __syscall_ret(_syscall2(SYS_MUNMAP, (int)addr, (int)length));
 }
+
+int mprotect(void* addr, size_t len, int prot) {
+    return __syscall_ret(_syscall3(SYS_MPROTECT, (int)addr, (int)len, prot));
+}
index 8f1b5f430c622097cb643c204eee8a3b178fd205..57e950c80601104a3114b673c279a322298ef5a7 100644 (file)
@@ -1,4 +1,5 @@
 #include "time.h"
+#include "sys/time.h"
 #include "syscall.h"
 #include "errno.h"
 
@@ -9,3 +10,8 @@ int nanosleep(const struct timespec* req, struct timespec* rem) {
 int clock_gettime(int clk_id, struct timespec* tp) {
     return __syscall_ret(_syscall2(SYS_CLOCK_GETTIME, clk_id, (int)tp));
 }
+
+int gettimeofday(struct timeval* tv, void* tz) {
+    (void)tz;
+    return __syscall_ret(_syscall2(SYS_GETTIMEOFDAY, (int)tv, 0));
+}