summaryrefslogtreecommitdiff
path: root/tests/tst-dup.c
diff options
context:
space:
mode:
Diffstat (limited to 'tests/tst-dup.c')
-rw-r--r--tests/tst-dup.c187
1 files changed, 187 insertions, 0 deletions
diff --git a/tests/tst-dup.c b/tests/tst-dup.c
new file mode 100644
index 0000000..3941378
--- /dev/null
+++ b/tests/tst-dup.c
@@ -0,0 +1,187 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+
+#include <libmcpu.h>
+#include <sys/asm/errno.h>
+#include <sys/asm/fcntl.h>
+#include <sys/asm/syscall.h>
+
+#include "mcpu-asm.h"
+
+
+#define CHECK(cond,msg) \
+ do { \
+ if( !(cond) ) \
+ { \
+ fprintf( stderr, "FAIL: %s\n", (msg) ); \
+ return( 1 ); \
+ } \
+ } while( 0 )
+
+
+static void
+put32( __mpu_uint32_t address, __mpu_uint32_t value )
+{
+ __cpu_data_put_u32( __cpu_reg_ptr( 32, address ), value );
+}
+
+
+static void
+put64( __mpu_uint32_t address, __mpu_uint64_t value )
+{
+ __cpu_data_put_u64( __cpu_reg_ptr( 64, address ), value );
+}
+
+
+static __mpu_uint64_t
+reg64( __mpu_uint32_t address )
+{
+ return( __cpu_data_get_u64( __cpu_reg_ptr( 64, address ) ) );
+}
+
+
+static int
+run_syscall( __mpu_uint32_t nargs, __mpu_uint32_t sysno,
+ __mpu_uint64_t arg0, __mpu_uint64_t arg1,
+ __mpu_uint64_t arg2, __mpu_uint64_t *ret )
+{
+ int rc;
+
+ put32( CPU_SYSCALL_NARGS_ADDR, nargs );
+ put32( CPU_SYSCALL_SYSNO_ADDR, sysno );
+ put64( CPU_SYSCALL_ARG0_ADDR, arg0 );
+ put64( CPU_SYSCALL_ARG1_ADDR, arg1 );
+ put64( CPU_SYSCALL_ARG2_ADDR, arg2 );
+
+ __cpu_pc = 0;
+ rc = __cpu_run();
+ if( rc != CPU_EXEC_SUCCESS ) return( rc );
+
+ *ret = reg64( CPU_SYSCALL_RET_ADDR );
+ return( 0 );
+
+} /* End of run_syscall() */
+
+
+static __mpu_int64_t
+signed_result( __mpu_uint64_t value )
+{
+ __mpu_int64_t ret;
+
+ memcpy( &ret, &value, sizeof( ret ) );
+ return( ret );
+
+} /* End of signed_result() */
+
+
+int
+main( void )
+{
+ static const char source[] =
+ ".text\n"
+ "SYSCALL\n"
+ "HALT\n";
+ cpu_asm_image image;
+ char err[256];
+ __mpu_uint64_t ret;
+
+ memset( &image, 0, sizeof( image ) );
+
+ CHECK( __NR_dup == 0x126, "public __NR_dup ABI number" );
+ CHECK( __NR_dup2 == 0x127, "public __NR_dup2 ABI number" );
+
+ __cpu_init();
+
+ CHECK( cpu_assemble_text( source, &image, err, sizeof( err ) ) == 0, err );
+ CHECK( __cpu_program_set( image.code, image.code_size ) == 0,
+ "program load" );
+
+ /* dup() returns the lowest free guest descriptor. */
+ CHECK( run_syscall( 1, __NR_dup, 1, 0, 0, &ret ) == 0, "dup stdout" );
+ CHECK( ret == 3, "dup returns lowest free guest fd 3" );
+
+ /* Descriptor flags are not copied by dup(). */
+ CHECK( run_syscall( 3, __NR_fcntl, 1, F_SETFD, FD_CLOEXEC, &ret ) == 0,
+ "set FD_CLOEXEC on source" );
+ CHECK( ret == 0, "F_SETFD source result" );
+ CHECK( run_syscall( 3, __NR_fcntl, 3, F_GETFD, 0, &ret ) == 0,
+ "F_GETFD duplicated descriptor" );
+ CHECK( ret == 0, "dup clears descriptor flags" );
+
+ /* dup2() installs exactly the requested guest descriptor. */
+ CHECK( run_syscall( 2, __NR_dup2, 1, 10, 0, &ret ) == 0,
+ "dup2 stdout to guest fd 10" );
+ CHECK( ret == 10, "dup2 returns requested guest fd" );
+
+ CHECK( run_syscall( 3, __NR_fcntl, 10, F_SETFD, FD_CLOEXEC, &ret ) == 0,
+ "set FD_CLOEXEC on dup2 target" );
+ CHECK( ret == 0, "set target flags" );
+
+ /* Replacing an occupied target clears descriptor flags. */
+ CHECK( run_syscall( 2, __NR_dup2, 3, 10, 0, &ret ) == 0,
+ "dup2 replaces occupied target" );
+ CHECK( ret == 10, "dup2 replacement return" );
+ CHECK( run_syscall( 3, __NR_fcntl, 10, F_GETFD, 0, &ret ) == 0,
+ "F_GETFD replaced target" );
+ CHECK( ret == 0, "dup2 replacement clears descriptor flags" );
+
+ /* oldfd == newfd is a no-op and preserves descriptor flags. */
+ CHECK( run_syscall( 3, __NR_fcntl, 1, F_GETFD, 0, &ret ) == 0,
+ "F_GETFD source before dup2 self" );
+ CHECK( ret == FD_CLOEXEC, "source flag before dup2 self" );
+ CHECK( run_syscall( 2, __NR_dup2, 1, 1, 0, &ret ) == 0,
+ "dup2 oldfd equals newfd" );
+ CHECK( ret == 1, "dup2 self return" );
+ CHECK( run_syscall( 3, __NR_fcntl, 1, F_GETFD, 0, &ret ) == 0,
+ "F_GETFD source after dup2 self" );
+ CHECK( ret == FD_CLOEXEC, "dup2 self preserves descriptor flags" );
+
+ /* Closing a borrowed standard guest fd does not close HOST stdio, and dup()
+ may subsequently reuse that lowest free guest slot. */
+ CHECK( run_syscall( 1, __NR_close, 0, 0, 0, &ret ) == 0,
+ "close borrowed guest stdin" );
+ CHECK( ret == 0, "close borrowed guest stdin result" );
+ CHECK( run_syscall( 1, __NR_dup, 1, 0, 0, &ret ) == 0,
+ "dup reuses closed guest fd 0" );
+ CHECK( ret == 0, "dup returns lowest free guest fd 0" );
+
+ /* Invalid source and target descriptors use the stable MCPU EBADF value. */
+ CHECK( run_syscall( 1, __NR_dup, 255, 0, 0, &ret ) == 0,
+ "dup invalid source" );
+ CHECK( signed_result( ret ) == -(__mpu_int64_t)EBADF,
+ "dup invalid source returns -EBADF" );
+
+ CHECK( run_syscall( 2, __NR_dup2, 1, 256, 0, &ret ) == 0,
+ "dup2 invalid target" );
+ CHECK( signed_result( ret ) == -(__mpu_int64_t)EBADF,
+ "dup2 invalid target returns -EBADF" );
+
+ CHECK( run_syscall( 3, __NR_fcntl, 10, F_SETFD, FD_CLOEXEC, &ret ) == 0,
+ "set target flag before invalid-source dup2" );
+ CHECK( run_syscall( 2, __NR_dup2, 255, 10, 0, &ret ) == 0,
+ "dup2 invalid source with occupied target" );
+ CHECK( signed_result( ret ) == -(__mpu_int64_t)EBADF,
+ "dup2 invalid source returns -EBADF" );
+ CHECK( run_syscall( 3, __NR_fcntl, 10, F_GETFD, 0, &ret ) == 0,
+ "target survives invalid-source dup2" );
+ CHECK( ret == FD_CLOEXEC,
+ "invalid-source dup2 leaves target unchanged" );
+
+ CHECK( run_syscall( 1, __NR_close, 0, 0, 0, &ret ) == 0,
+ "close reused guest fd 0" );
+ CHECK( run_syscall( 1, __NR_close, 3, 0, 0, &ret ) == 0,
+ "close dup guest fd 3" );
+ CHECK( run_syscall( 1, __NR_close, 10, 0, 0, &ret ) == 0,
+ "close dup2 guest fd 10" );
+
+ cpu_asm_image_free( &image );
+ __cpu_free_context();
+
+ puts( "libmcpu dup/dup2 regression: PASS" );
+ return( 0 );
+
+} /* End of main() */