diff --git a/src/lib/buzz_os.zig b/src/lib/buzz_os.zig index ab155f02..6d106d57 100644 --- a/src/lib/buzz_os.zig +++ b/src/lib/buzz_os.zig @@ -133,7 +133,7 @@ pub export fn buzzExit(ctx: *api.NativeCtx) c_int { return 0; } -fn handleSpawnError(ctx: *api.NativeCtx, err: anytype) void { +fn handleRunError(ctx: *api.NativeCtx, err: anytype) void { switch (err) { error.AccessDenied, error.BadPathName, @@ -152,6 +152,9 @@ fn handleSpawnError(ctx: *api.NativeCtx, err: anytype) void { error.SystemFdQuotaExceeded, error.SystemResources, error.FileNotFound, + error.InputOutput, + error.SocketNotConnected, + error.WouldBlock, => ctx.vm.pushErrorEnum("errors.FileSystemError", @errorName(err)), error.CurrentWorkingDirectoryUnlinked, @@ -164,6 +167,20 @@ fn handleSpawnError(ctx: *api.NativeCtx, err: anytype) void { error.WaitTimeOut, => ctx.vm.pushErrorEnum("errors.ExecError", @errorName(err)), + error.NetworkSubsystemFailed, + => ctx.vm.pushErrorEnum("errors.SocketError", @errorName(err)), + + error.OperationAborted, + error.BrokenPipe, + error.ConnectionResetByPeer, + error.ConnectionTimedOut, + error.NotOpenForReading, + => ctx.vm.pushErrorEnum("errors.ReadWriteError", @errorName(err)), + + error.StdoutStreamTooLong, + error.StderrStreamTooLong, + => ctx.vm.pushErrorEnum("errors.ReadWriteError", "StreamTooLong"), + error.OutOfMemory => { ctx.vm.bz_panic("Out of memory", "Out of memory".len); unreachable; @@ -176,14 +193,23 @@ pub export fn execute(ctx: *api.NativeCtx) c_int { var command = std.ArrayList([]const u8).init(api.VM.allocator); defer command.deinit(); + const cmd = ctx.vm.bz_peek(1); + var cmd_len: usize = 0; + var cmd_str = cmd.bz_valueToString(&cmd_len); + + command.append(cmd_str.?[0..cmd_len]) catch { + ctx.vm.bz_panic("Out of memory", "Out of memory".len); + unreachable; + }; + const argv_value = ctx.vm.bz_peek(0); const argv = api.ObjList.bz_valueToList(argv_value); const len = argv.bz_listLen(); - var i: usize = 0; + var i: i32 = 0; while (i < len) : (i += 1) { const arg = api.ObjList.bz_listGet( argv_value, - @intCast(i), + i, false, ); var arg_len: usize = 0; @@ -197,21 +223,62 @@ pub export fn execute(ctx: *api.NativeCtx) c_int { }; } - var child_process = std.process.Child.init(command.items, api.VM.allocator); - child_process.disable_aslr = builtin.target.isDarwin(); - - const term = child_process.spawnAndWait() catch |err| { - handleSpawnError(ctx, err); + const result = std.process.Child.run( + .{ + .allocator = api.VM.allocator, + .argv = command.items, + }, + ) catch |err| { + handleRunError(ctx, err); return -1; }; - switch (term) { - .Exited => ctx.vm.bz_pushInteger(@intCast(term.Exited)), - .Signal => ctx.vm.bz_pushInteger(@intCast(term.Signal)), - .Stopped => ctx.vm.bz_pushInteger(@intCast(term.Stopped)), - .Unknown => ctx.vm.bz_pushInteger(@intCast(term.Unknown)), - } + const runResult = api.ObjObject.bz_instanceQualified( + ctx.vm, + "os.RunResult", // FIXME: doesn't work if user renames namespace + "os.RunResult".len, + ); + + ctx.vm.bz_push(runResult); + + api.ObjObject.bz_setInstanceProperty( + ctx.vm, + runResult, + 0, + api.Value.fromInteger(switch (result.term) { + .Exited => @intCast(result.term.Exited), + .Signal => @intCast(result.term.Signal), + .Stopped => @intCast(result.term.Stopped), + .Unknown => @intCast(result.term.Unknown), + }), + ); + + api.ObjObject.bz_setInstanceProperty( + ctx.vm, + runResult, + 1, + api.ObjString.bz_objStringToValue( + api.ObjString.bz_string( + ctx.vm, + result.stdout.ptr, + result.stdout.len, + ) orelse @panic("Out of memory"), + ), + ); + + api.ObjObject.bz_setInstanceProperty( + ctx.vm, + runResult, + 2, + api.ObjString.bz_objStringToValue( + api.ObjString.bz_string( + ctx.vm, + result.stderr.ptr, + result.stderr.len, + ) orelse @panic("Out of memory"), + ), + ); return 1; } diff --git a/src/lib/os.buzz b/src/lib/os.buzz index ee01fea1..2127f851 100644 --- a/src/lib/os.buzz +++ b/src/lib/os.buzz @@ -25,10 +25,16 @@ extern fun buzzExit(int exitCode) > void; export buzzExit as exit; +export object RunResult { + int term, + str stdout, + str stderr, +} + || Execute command and return its exit code || @param command command to execute || @return exit code of the command -export extern fun execute([str] command) > int !> errors.FileSystemError, errors.UnexpectedError; +export extern fun execute(str command, [str] args = []) > RunResult !> errors.FileSystemError, errors.UnexpectedError, errors.ReadWriteError, errors.SocketError; || @private extern fun SocketConnect(str address, int port, int netProtocol) > int !> errors.InvalidArgumentError, errors.SocketError, errors.NotYetImplementedError; @@ -142,4 +148,4 @@ export object TcpServer { export SocketProtocol; export Socket; -export TcpServer; \ No newline at end of file +export TcpServer; diff --git a/tests/024-os.buzz b/tests/024-os.buzz index 0f64cabb..773a84b6 100644 --- a/tests/024-os.buzz +++ b/tests/024-os.buzz @@ -19,9 +19,11 @@ test "os.tmpFilename" { } test "os.execute" { - std.assert(os.execute(["./zig-out/bin/buzz", "--version"]) == 0, message: "Could execute a command"); + const result = os.execute("./zig-out/bin/buzz", args: ["--version"]); + std.assert(result.term == 0, message: "Could execute a command"); + std.assert(result.stdout.startsWith("\nšŸ‘Øā€šŸš€ buzz")); } test "os.sleep" { os.sleep(500.0); -} \ No newline at end of file +}