aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bench/snippets/cat.bun.js5
-rw-r--r--bench/snippets/cat.mjs1
-rw-r--r--bench/snippets/cat.node.js4
-rw-r--r--bench/snippets/copy.bun.js4
-rw-r--r--examples/cat.ts5
-rw-r--r--examples/hashing.js (renamed from examples/bun/hashing.js)0
-rw-r--r--examples/html-rewriter.ts (renamed from examples/bun/html-rewriter.ts)0
-rw-r--r--examples/http-file.ts (renamed from examples/bun/http-file.ts)0
-rw-r--r--examples/http.ts (renamed from examples/bun/http.ts)0
-rw-r--r--examples/mmap/1.js (renamed from examples/bun/mmap/1.js)0
-rw-r--r--examples/mmap/2.js (renamed from examples/bun/mmap/2.js)0
-rw-r--r--examples/mmap/mmap.txt (renamed from examples/bun/mmap/mmap.txt)0
-rw-r--r--examples/openInEditor.js (renamed from examples/bun/openInEditor.js)0
-rw-r--r--examples/ssl.ts (renamed from examples/bun/ssl.ts)0
-rw-r--r--examples/tsconfig.json (renamed from examples/bun/tsconfig.json)3
m---------src/deps/mimalloc0
-rw-r--r--src/javascript/jsc/api/bun.zig78
-rw-r--r--src/javascript/jsc/api/html_rewriter.zig15
-rw-r--r--src/javascript/jsc/api/server.zig29
-rw-r--r--src/javascript/jsc/bindings/bindings.zig5
-rw-r--r--src/javascript/jsc/node/syscall.zig2
-rw-r--r--src/javascript/jsc/rare_data.zig95
-rw-r--r--src/javascript/jsc/webcore/response.zig424
-rw-r--r--src/linux_c.zig17
-rw-r--r--src/string_immutable.zig2
25 files changed, 550 insertions, 139 deletions
diff --git a/bench/snippets/cat.bun.js b/bench/snippets/cat.bun.js
new file mode 100644
index 000000000..1bb1c809a
--- /dev/null
+++ b/bench/snippets/cat.bun.js
@@ -0,0 +1,5 @@
+import { resolve } from "path";
+const { write, stdout, file } = Bun;
+const input = resolve(process.argv[process.argv.length - 1]);
+
+await write(stdout, file(input));
diff --git a/bench/snippets/cat.mjs b/bench/snippets/cat.mjs
index 7e4f3da54..ca6dfe838 100644
--- a/bench/snippets/cat.mjs
+++ b/bench/snippets/cat.mjs
@@ -1,3 +1,4 @@
+// works in both bun & node
import { readFileSync } from "node:fs";
const count = parseInt(process.env.ITERATIONS || "1", 10) || 1;
const arg = process.argv.slice(1);
diff --git a/bench/snippets/cat.node.js b/bench/snippets/cat.node.js
new file mode 100644
index 000000000..d38d7c537
--- /dev/null
+++ b/bench/snippets/cat.node.js
@@ -0,0 +1,4 @@
+const path = require("path");
+const fs = require("fs");
+const input = path.resolve(process.argv[process.argv.length - 1]);
+fs.createReadStream(input).pipe(process.stdout);
diff --git a/bench/snippets/copy.bun.js b/bench/snippets/copy.bun.js
new file mode 100644
index 000000000..20269212a
--- /dev/null
+++ b/bench/snippets/copy.bun.js
@@ -0,0 +1,4 @@
+import path from "path";
+const input = path.resolve(process.argv[process.argv.length - 2]);
+const output = path.resolve(process.argv[process.argv.length - 1]);
+await Bun.write(Bun.file(output), Bun.file(input));
diff --git a/examples/cat.ts b/examples/cat.ts
new file mode 100644
index 000000000..1bb1c809a
--- /dev/null
+++ b/examples/cat.ts
@@ -0,0 +1,5 @@
+import { resolve } from "path";
+const { write, stdout, file } = Bun;
+const input = resolve(process.argv[process.argv.length - 1]);
+
+await write(stdout, file(input));
diff --git a/examples/bun/hashing.js b/examples/hashing.js
index cf4772ffe..cf4772ffe 100644
--- a/examples/bun/hashing.js
+++ b/examples/hashing.js
diff --git a/examples/bun/html-rewriter.ts b/examples/html-rewriter.ts
index 2b370c5ed..2b370c5ed 100644
--- a/examples/bun/html-rewriter.ts
+++ b/examples/html-rewriter.ts
diff --git a/examples/bun/http-file.ts b/examples/http-file.ts
index f2c0773e8..f2c0773e8 100644
--- a/examples/bun/http-file.ts
+++ b/examples/http-file.ts
diff --git a/examples/bun/http.ts b/examples/http.ts
index 4d58f270f..4d58f270f 100644
--- a/examples/bun/http.ts
+++ b/examples/http.ts
diff --git a/examples/bun/mmap/1.js b/examples/mmap/1.js
index 1c4f2c969..1c4f2c969 100644
--- a/examples/bun/mmap/1.js
+++ b/examples/mmap/1.js
diff --git a/examples/bun/mmap/2.js b/examples/mmap/2.js
index c4b68bd9a..c4b68bd9a 100644
--- a/examples/bun/mmap/2.js
+++ b/examples/mmap/2.js
diff --git a/examples/bun/mmap/mmap.txt b/examples/mmap/mmap.txt
index 6931040dd..6931040dd 100644
--- a/examples/bun/mmap/mmap.txt
+++ b/examples/mmap/mmap.txt
diff --git a/examples/bun/openInEditor.js b/examples/openInEditor.js
index 59282c098..59282c098 100644
--- a/examples/bun/openInEditor.js
+++ b/examples/openInEditor.js
diff --git a/examples/bun/ssl.ts b/examples/ssl.ts
index df30a0bd6..df30a0bd6 100644
--- a/examples/bun/ssl.ts
+++ b/examples/ssl.ts
diff --git a/examples/bun/tsconfig.json b/examples/tsconfig.json
index ab7f30fc9..4e99331ee 100644
--- a/examples/bun/tsconfig.json
+++ b/examples/tsconfig.json
@@ -3,7 +3,6 @@
"lib": ["ESNext"],
"module": "esnext",
"target": "esnext",
- "typeRoots": ["../../types"],
- "types": ["bun"]
+ "typeRoots": ["~/.bun/types"]
}
}
diff --git a/src/deps/mimalloc b/src/deps/mimalloc
-Subproject 9e41263d39041aee3b647eff64d5ef4918a60ce
+Subproject 817569dfad79732233fb86649c89e04387ce02e
diff --git a/src/javascript/jsc/api/bun.zig b/src/javascript/jsc/api/bun.zig
index 26faf75d7..8826acc2e 100644
--- a/src/javascript/jsc/api/bun.zig
+++ b/src/javascript/jsc/api/bun.zig
@@ -255,6 +255,75 @@ pub fn getOrigin(
return ZigString.init(VirtualMachine.vm.origin.origin).toValue(ctx.ptr()).asRef();
}
+pub fn getStdin(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSValueRef,
+ _: js.JSStringRef,
+ _: js.ExceptionRef,
+) js.JSValueRef {
+ var existing = ctx.ptr().getCachedObject(&ZigString.init("BunSTDIN"));
+ if (existing.isEmpty()) {
+ var rare_data = JSC.VirtualMachine.vm.rareData();
+ var store = rare_data.stdin();
+ var blob = bun.default_allocator.create(JSC.WebCore.Blob) catch unreachable;
+ blob.* = JSC.WebCore.Blob.initWithStore(store, ctx.ptr());
+
+ return ctx.ptr().putCachedObject(
+ &ZigString.init("BunSTDIN"),
+ JSC.JSValue.fromRef(JSC.WebCore.Blob.Class.make(ctx, blob)),
+ ).asObjectRef();
+ }
+
+ return existing.asObjectRef();
+}
+
+pub fn getStderr(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSValueRef,
+ _: js.JSStringRef,
+ _: js.ExceptionRef,
+) js.JSValueRef {
+ var existing = ctx.ptr().getCachedObject(&ZigString.init("BunSTDERR"));
+ if (existing.isEmpty()) {
+ var rare_data = JSC.VirtualMachine.vm.rareData();
+ var store = rare_data.stderr();
+ var blob = bun.default_allocator.create(JSC.WebCore.Blob) catch unreachable;
+ blob.* = JSC.WebCore.Blob.initWithStore(store, ctx.ptr());
+
+ return ctx.ptr().putCachedObject(
+ &ZigString.init("BunSTDERR"),
+ JSC.JSValue.fromRef(JSC.WebCore.Blob.Class.make(ctx, blob)),
+ ).asObjectRef();
+ }
+
+ return existing.asObjectRef();
+}
+
+pub fn getStdout(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSValueRef,
+ _: js.JSStringRef,
+ _: js.ExceptionRef,
+) js.JSValueRef {
+ var existing = ctx.ptr().getCachedObject(&ZigString.init("BunSTDOUT"));
+ if (existing.isEmpty()) {
+ var rare_data = JSC.VirtualMachine.vm.rareData();
+ var store = rare_data.stdout();
+ var blob = bun.default_allocator.create(JSC.WebCore.Blob) catch unreachable;
+ blob.* = JSC.WebCore.Blob.initWithStore(store, ctx.ptr());
+
+ return ctx.ptr().putCachedObject(
+ &ZigString.init("BunSTDOUT"),
+ JSC.JSValue.fromRef(JSC.WebCore.Blob.Class.make(ctx, blob)),
+ ).asObjectRef();
+ }
+
+ return existing.asObjectRef();
+}
+
pub fn enableANSIColors(
_: void,
ctx: js.JSContextRef,
@@ -1063,6 +1132,15 @@ pub const Class = NewClass(
.get = getOrigin,
.ts = d.ts{ .name = "origin", .@"return" = "string" },
},
+ .stdin = .{
+ .get = getStdin,
+ },
+ .stdout = .{
+ .get = getStdout,
+ },
+ .stderr = .{
+ .get = getStderr,
+ },
.routesDir = .{
.get = getRoutesDir,
.ts = d.ts{ .name = "routesDir", .@"return" = "string" },
diff --git a/src/javascript/jsc/api/html_rewriter.zig b/src/javascript/jsc/api/html_rewriter.zig
index f75829418..70406ace5 100644
--- a/src/javascript/jsc/api/html_rewriter.zig
+++ b/src/javascript/jsc/api/html_rewriter.zig
@@ -304,7 +304,7 @@ pub const HTMLRewriter = struct {
if (is_pending) {
input.doReadFileInternal(*BufferOutputSink, sink, onFinishedLoading, global);
- } else if (sink.runOutputSink(input.sharedView(), false)) |error_value| {
+ } else if (sink.runOutputSink(input.sharedView(), false, false)) |error_value| {
return error_value;
}
@@ -337,15 +337,24 @@ pub const HTMLRewriter = struct {
return;
},
.result => |data| {
- _ = sink.runOutputSink(data, true);
+ _ = sink.runOutputSink(data.buf, true, data.is_temporary);
},
}
}
- pub fn runOutputSink(sink: *BufferOutputSink, bytes: []const u8, is_async: bool) ?JSValue {
+ pub fn runOutputSink(
+ sink: *BufferOutputSink,
+ bytes: []const u8,
+ is_async: bool,
+ free_bytes_on_end: bool,
+ ) ?JSValue {
+ defer if (free_bytes_on_end)
+ bun.default_allocator.free(bun.constStrToU8(bytes));
+
sink.bytes.growBy(bytes.len) catch unreachable;
var global = sink.global;
var response = sink.response;
+
sink.rewriter.write(bytes) catch {
sink.deinit();
bun.default_allocator.destroy(sink);
diff --git a/src/javascript/jsc/api/server.zig b/src/javascript/jsc/api/server.zig
index 01854eecb..57fbe6d34 100644
--- a/src/javascript/jsc/api/server.zig
+++ b/src/javascript/jsc/api/server.zig
@@ -357,7 +357,9 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
sendfile: SendfileContext = undefined,
request_js_object: JSC.C.JSObjectRef = null,
request_body_buf: std.ArrayListUnmanaged(u8) = .{},
- fallback_buf: std.ArrayListUnmanaged(u8) = .{},
+ /// Used either for temporary blob data or fallback
+ /// When the response body is a temporary value
+ response_buf_owned: std.ArrayListUnmanaged(u8) = .{},
pub const RequestContextStackAllocator = std.heap.StackFallbackAllocator(@sizeOf(RequestContext) * 2048 + 4096);
@@ -478,15 +480,19 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
return;
}
- this.fallback_buf = std.ArrayListUnmanaged(u8){ .items = bb.items, .capacity = bb.capacity };
- this.resp.onWritable(*RequestContext, onWritableFallback, this);
+ this.response_buf_owned = std.ArrayListUnmanaged(u8){ .items = bb.items, .capacity = bb.capacity };
+ this.renderResponseBuffer();
}
- pub fn onWritableFallback(this: *RequestContext, write_offset: c_ulong, resp: *App.Response) callconv(.C) bool {
+ pub fn renderResponseBuffer(this: *RequestContext) void {
+ this.resp.onWritable(*RequestContext, onWritableResponseBuffer, this);
+ }
+
+ pub fn onWritableResponseBuffer(this: *RequestContext, write_offset: c_ulong, resp: *App.Response) callconv(.C) bool {
if (this.aborted) {
return false;
}
- return this.sendWritableBytes(this.fallback_buf.items, write_offset, resp);
+ return this.sendWritableBytes(this.response_buf_owned.items, write_offset, resp);
}
pub fn create(this: *RequestContext, server: *ThisServer, req: *uws.Request, resp: *App.Response) void {
@@ -542,7 +548,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
this.response_headers = null;
}
- this.fallback_buf.clearAndFree(bun.default_allocator);
+ this.response_buf_owned.clearAndFree(bun.default_allocator);
}
pub fn finalize(this: *RequestContext) void {
this.finalizeWithoutDeinit();
@@ -731,8 +737,15 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
return;
}
- this.blob.resolveSize();
- this.doRenderBlob();
+ const is_temporary = result.result.is_temporary;
+ if (!is_temporary) {
+ this.blob.resolveSize();
+ this.doRenderBlob();
+ } else {
+ this.blob.size = @truncate(Blob.SizeType, result.result.buf.len);
+ this.response_buf_owned = .{ .items = result.result.buf, .capacity = result.result.buf.len };
+ this.renderResponseBuffer();
+ }
}
pub fn doRenderWithBodyLocked(this: *anyopaque, value: *JSC.WebCore.Body.Value) void {
diff --git a/src/javascript/jsc/bindings/bindings.zig b/src/javascript/jsc/bindings/bindings.zig
index d4fcdaef0..7797ae4c5 100644
--- a/src/javascript/jsc/bindings/bindings.zig
+++ b/src/javascript/jsc/bindings/bindings.zig
@@ -2533,7 +2533,10 @@ pub const JSValue = enum(u64) {
pub fn asArrayBuffer(this: JSValue, global: *JSGlobalObject) ?ArrayBuffer {
var out: ArrayBuffer = undefined;
- if (this.asArrayBuffer_(global, &out)) return out;
+ if (this.asArrayBuffer_(global, &out)) {
+ out.value = this;
+ return out;
+ }
return null;
}
diff --git a/src/javascript/jsc/node/syscall.zig b/src/javascript/jsc/node/syscall.zig
index cd7a56157..ac7d51d8b 100644
--- a/src/javascript/jsc/node/syscall.zig
+++ b/src/javascript/jsc/node/syscall.zig
@@ -90,6 +90,8 @@ pub const Tag = enum(u8) {
fcopyfile,
recv,
send,
+ sendfile,
+ splice,
pub var strings = std.EnumMap(Tag, JSC.C.JSStringRef).initFull(null);
};
diff --git a/src/javascript/jsc/rare_data.zig b/src/javascript/jsc/rare_data.zig
index 72bf45fbd..efd602085 100644
--- a/src/javascript/jsc/rare_data.zig
+++ b/src/javascript/jsc/rare_data.zig
@@ -1,3 +1,98 @@
const EditorContext = @import("../../open.zig").EditorContext;
+const Blob = @import("./webcore/response.zig").Blob;
+const default_allocator = @import("../../global.zig").default_allocator;
+const Output = @import("../../global.zig").Output;
+const RareData = @This();
+const Syscall = @import("./node/syscall.zig");
+const JSC = @import("javascript_core");
+const std = @import("std");
editor_context: EditorContext = EditorContext{},
+stderr_store: ?*Blob.Store = null,
+stdin_store: ?*Blob.Store = null,
+stdout_store: ?*Blob.Store = null,
+
+pub fn stderr(rare: *RareData) *Blob.Store {
+ return rare.stderr_store orelse brk: {
+ var store = default_allocator.create(Blob.Store) catch unreachable;
+ var mode: JSC.Node.Mode = 0;
+ switch (Syscall.fstat(std.os.STDERR_FILENO)) {
+ .result => |stat| {
+ mode = stat.mode;
+ },
+ .err => {},
+ }
+
+ store.* = Blob.Store{
+ .ref_count = 2,
+ .allocator = default_allocator,
+ .data = .{
+ .file = Blob.FileStore{
+ .pathlike = .{
+ .fd = std.os.STDERR_FILENO,
+ },
+ .is_atty = Output.stderr_descriptor_type == .terminal,
+ .mode = mode,
+ },
+ },
+ };
+ rare.stderr_store = store;
+ break :brk store;
+ };
+}
+
+pub fn stdout(rare: *RareData) *Blob.Store {
+ return rare.stdout_store orelse brk: {
+ var store = default_allocator.create(Blob.Store) catch unreachable;
+ var mode: JSC.Node.Mode = 0;
+ switch (Syscall.fstat(std.os.STDOUT_FILENO)) {
+ .result => |stat| {
+ mode = stat.mode;
+ },
+ .err => {},
+ }
+ store.* = Blob.Store{
+ .ref_count = 2,
+ .allocator = default_allocator,
+ .data = .{
+ .file = Blob.FileStore{
+ .pathlike = .{
+ .fd = std.os.STDOUT_FILENO,
+ },
+ .is_atty = Output.stdout_descriptor_type == .terminal,
+ .mode = mode,
+ },
+ },
+ };
+ rare.stdout_store = store;
+ break :brk store;
+ };
+}
+
+pub fn stdin(rare: *RareData) *Blob.Store {
+ return rare.stdin_store orelse brk: {
+ var store = default_allocator.create(Blob.Store) catch unreachable;
+ var mode: JSC.Node.Mode = 0;
+ switch (Syscall.fstat(std.os.STDIN_FILENO)) {
+ .result => |stat| {
+ mode = stat.mode;
+ },
+ .err => {},
+ }
+ store.* = Blob.Store{
+ .allocator = default_allocator,
+ .ref_count = 2,
+ .data = .{
+ .file = Blob.FileStore{
+ .pathlike = .{
+ .fd = std.os.STDIN_FILENO,
+ },
+ .is_atty = std.os.isatty(std.os.STDIN_FILENO),
+ .mode = mode,
+ },
+ },
+ };
+ rare.stdin_store = store;
+ break :brk store;
+ };
+}
diff --git a/src/javascript/jsc/webcore/response.zig b/src/javascript/jsc/webcore/response.zig
index c18dd1431..f1fa6223b 100644
--- a/src/javascript/jsc/webcore/response.zig
+++ b/src/javascript/jsc/webcore/response.zig
@@ -586,6 +586,8 @@ pub const Response = struct {
}
};
+const null_fd = std.math.maxInt(JSC.Node.FileDescriptor);
+
pub const Fetch = struct {
const headers_string = "headers";
const method_string = "method";
@@ -1404,6 +1406,7 @@ pub const Blob = struct {
) js.JSObjectRef {
var args = JSC.Node.ArgumentsSlice.from(arguments);
defer args.deinit();
+
var path = JSC.Node.PathOrFileDescriptor.fromJS(ctx, &args, exception) orelse {
exception.* = JSC.toInvalidArguments("Expected file path string or file descriptor", .{}, ctx).asObjectRef();
return js.JSValueMakeUndefined(ctx);
@@ -1424,12 +1427,31 @@ pub const Blob = struct {
return Blob.initWithStore(blob, globalThis);
}
- if (path == .path) {
- path.path = .{
- .string = bun.PathString.init(
- (bun.default_allocator.dupeZ(u8, path.path.slice()) catch unreachable)[0..path.path.slice().len],
- ),
- };
+ switch (path) {
+ .path => {
+ path.path = .{
+ .string = bun.PathString.init(
+ (bun.default_allocator.dupeZ(u8, path.path.slice()) catch unreachable)[0..path.path.slice().len],
+ ),
+ };
+ },
+ .fd => {
+ switch (path.fd) {
+ std.os.STDIN_FILENO => return Blob.initWithStore(
+ VirtualMachine.vm.rareData().stdin(),
+ globalThis,
+ ),
+ std.os.STDERR_FILENO => return Blob.initWithStore(
+ VirtualMachine.vm.rareData().stderr(),
+ globalThis,
+ ),
+ std.os.STDOUT_FILENO => return Blob.initWithStore(
+ VirtualMachine.vm.rareData().stdout(),
+ globalThis,
+ ),
+ else => {},
+ }
+ },
}
const result = Blob.initWithStore(Blob.Store.initFile(path, null, bun.default_allocator) catch unreachable, globalThis);
@@ -1567,7 +1589,7 @@ pub const Blob = struct {
}
pub fn getFd(this: *This) AsyncIO.OpenError!JSC.Node.FileDescriptor {
- if (this.opened_fd != 0) {
+ if (this.opened_fd != null_fd) {
return this.opened_fd;
}
@@ -1641,7 +1663,7 @@ pub const Blob = struct {
&this.close_completion,
this.opened_fd,
);
- this.opened_fd = 0;
+ this.opened_fd = null_fd;
suspend {
this.close_frame = @frame().*;
@@ -1678,7 +1700,7 @@ pub const Blob = struct {
errno: ?anyerror = null,
system_error: ?JSC.SystemError = null,
open_completion: HTTPClient.NetworkThread.Completion = undefined,
- opened_fd: JSC.Node.FileDescriptor = 0,
+ opened_fd: JSC.Node.FileDescriptor = null_fd,
size: SizeType = 0,
store: *Store = undefined,
@@ -1749,12 +1771,12 @@ pub const Blob = struct {
}
fn _runAsync(this: *OpenAndStatFile) void {
- this.opened_fd = 0;
+ this.opened_fd = null_fd;
if (this.file_store.pathlike == .fd) {
this.opened_fd = this.file_store.pathlike.fd;
}
const fd =
- if (this.opened_fd == 0)
+ if (this.opened_fd == null_fd)
this.getFd() catch return
else
this.opened_fd;
@@ -1767,9 +1789,18 @@ pub const Blob = struct {
},
};
- if (!std.os.S.ISREG(stat.mode)) {
- this.errno = error.ENOTSUP;
- return;
+ if (Environment.isMac) {
+ if (!std.os.S.ISREG(stat.mode)) {
+ this.errno = error.ENOTSUP;
+ return;
+ }
+ }
+
+ if (Environment.isLinux) {
+ if (!(std.os.S.ISREG(stat.mode) or std.os.S.ISFIFO(stat.mode))) {
+ this.errno = error.ENOTSUP;
+ return;
+ }
}
this.size = @truncate(SizeType, @intCast(u64, @maximum(@intCast(i64, stat.size), 0)));
@@ -1790,7 +1821,7 @@ pub const Blob = struct {
read_frame: @Frame(ReadFile.doRead) = undefined,
close_frame: @Frame(ReadFile.doClose) = undefined,
open_completion: HTTPClient.NetworkThread.Completion = undefined,
- opened_fd: JSC.Node.FileDescriptor = 0,
+ opened_fd: JSC.Node.FileDescriptor = null_fd,
read_completion: HTTPClient.NetworkThread.Completion = undefined,
read_len: SizeType = 0,
read_off: SizeType = 0,
@@ -1804,7 +1835,13 @@ pub const Blob = struct {
onCompleteCtx: *anyopaque = undefined,
onCompleteCallback: OnReadFileCallback = undefined,
- pub const ResultType = SystemError.Maybe([]u8);
+ convert_to_byte_blob: bool = false,
+
+ pub const Read = struct {
+ buf: []u8,
+ is_temporary: bool = false,
+ };
+ pub const ResultType = SystemError.Maybe(Read);
pub const OnReadFileCallback = fn (ctx: *anyopaque, bytes: ResultType) void;
@@ -1908,7 +1945,7 @@ pub const Blob = struct {
}
var store = this.store.?;
- if (this.file_store.pathlike == .path) {
+ if (this.convert_to_byte_blob and this.file_store.pathlike == .path) {
VirtualMachine.vm.removeFileBlob(this.file_store.pathlike);
}
@@ -1919,17 +1956,20 @@ pub const Blob = struct {
return;
}
- var bytes = this.buffer;
- if (store.data == .bytes) {
- bun.default_allocator.free(this.buffer);
- bytes = store.data.bytes.slice();
- } else if (store.data == .file) {
- if (this.file_store.pathlike == .path) {
- if (this.file_store.pathlike.path == .string) {
- bun.default_allocator.free(this.file_store.pathlike.path.slice());
+ var buf = this.buffer;
+ const is_temporary = !this.convert_to_byte_blob;
+ if (this.convert_to_byte_blob) {
+ if (store.data == .bytes) {
+ bun.default_allocator.free(this.buffer);
+ buf = store.data.bytes.slice();
+ } else if (store.data == .file) {
+ if (this.file_store.pathlike == .path) {
+ if (this.file_store.pathlike.path == .string) {
+ bun.default_allocator.free(this.file_store.pathlike.path.slice());
+ }
}
+ store.data = .{ .bytes = ByteStore.init(buf, bun.default_allocator) };
}
- store.data = .{ .bytes = ByteStore.init(bytes, bun.default_allocator) };
}
bun.default_allocator.destroy(this);
@@ -1937,9 +1977,9 @@ pub const Blob = struct {
// Attempt to free it as soon as possible
if (store.ref_count > 1) {
store.deref();
- cb(cb_ctx, .{ .result = bytes });
+ cb(cb_ctx, .{ .result = .{ .buf = buf, .is_temporary = is_temporary } });
} else {
- cb(cb_ctx, .{ .result = bytes });
+ cb(cb_ctx, .{ .result = .{ .buf = buf, .is_temporary = is_temporary } });
store.deref();
}
}
@@ -1983,7 +2023,7 @@ pub const Blob = struct {
}
const fd = this.getFd() catch return;
- const needs_close = this.file_store.pathlike == .path and fd != 0;
+ const needs_close = this.file_store.pathlike == .path and fd != null_fd and fd > 2;
const stat: std.os.Stat = switch (JSC.Node.Syscall.fstat(fd)) {
.result => |result| result,
.err => |err| {
@@ -1992,24 +2032,34 @@ pub const Blob = struct {
return;
},
};
- if (!std.os.S.ISREG(stat.mode)) {
- this.errno = error.ENOTSUP;
+ if (std.os.S.ISDIR(stat.mode)) {
+ this.errno = error.EISDIR;
this.system_error = JSC.SystemError{
- .code = ZigString.init(std.mem.span(@errorName(error.TODO))),
+ .code = ZigString.init("EISDIR"),
.path = if (this.file_store.pathlike == .path)
ZigString.init(this.file_store.pathlike.path.slice())
else
ZigString.Empty,
- .message = ZigString.init("Non-regular files are not supported yet"),
+ .message = ZigString.init("Directories cannot be read like files"),
.syscall = ZigString.init("read"),
};
return;
}
- this.size = @minimum(
- @truncate(SizeType, @intCast(SizeType, @maximum(@intCast(i64, stat.size), 0))),
- this.max_length,
- );
+ if (stat.size > 0 and std.os.S.ISREG(stat.mode)) {
+ this.size = @minimum(
+ @truncate(SizeType, @intCast(SizeType, @maximum(@intCast(i64, stat.size), 0))),
+ this.max_length,
+ );
+ // read up to 4k at a time if
+ // they didn't explicitly set a size and we're reading from something that's not a regular file
+ } else if (stat.size == 0 and !std.os.S.ISREG(stat.mode)) {
+ this.size = if (this.max_length == Blob.max_size)
+ 4096
+ else
+ this.max_length;
+ }
+
if (this.size == 0) {
this.buffer = &[_]u8{};
this.byte_store = ByteStore.init(this.buffer, bun.default_allocator);
@@ -2019,6 +2069,7 @@ pub const Blob = struct {
}
return;
}
+
var bytes = bun.default_allocator.alloc(u8, this.size) catch |err| {
this.errno = err;
if (needs_close) {
@@ -2027,6 +2078,7 @@ pub const Blob = struct {
return;
};
this.buffer = bytes;
+ this.convert_to_byte_blob = std.os.S.ISREG(stat.mode) and this.file_store.pathlike == .path;
var remain = bytes;
while (remain.len > 0) {
@@ -2056,7 +2108,7 @@ pub const Blob = struct {
file_blob: Blob,
bytes_blob: Blob,
- opened_fd: JSC.Node.FileDescriptor = 0,
+ opened_fd: JSC.Node.FileDescriptor = null_fd,
open_frame: OpenFrameType = undefined,
write_frame: @Frame(WriteFile.doWrite) = undefined,
close_frame: @Frame(WriteFile.doClose) = undefined,
@@ -2078,6 +2130,7 @@ pub const Blob = struct {
pub usingnamespace FileOpenerMixin(WriteFile);
pub usingnamespace FileCloserMixin(WriteFile);
+ // Do not open with APPEND because we may use pwrite()
pub const open_flags = std.os.O.WRONLY | std.os.O.CREAT | std.os.O.TRUNC;
pub fn createWithCtx(
@@ -2129,14 +2182,15 @@ pub const Blob = struct {
) AsyncIO.WriteError!SizeType {
var aio = &AsyncIO.global;
this.wrote = 0;
+ const fd = this.opened_fd;
aio.write(
*WriteFile,
this,
onWrite,
&this.write_completion,
- this.opened_fd,
+ fd,
buffer,
- file_offset,
+ if (fd > 2) file_offset else 0,
);
suspend {
@@ -2205,8 +2259,8 @@ pub const Blob = struct {
this.opened_fd = file.pathlike.fd;
}
- _ = this.getFd() catch return;
- const needs_close = file.pathlike == .path;
+ const fd = this.getFd() catch return;
+ const needs_close = file.pathlike == .path and fd > 2;
var remain = this.bytes_blob.sharedView();
@@ -2264,8 +2318,8 @@ pub const Blob = struct {
offset: SizeType = 0,
size: SizeType = 0,
max_length: SizeType = Blob.max_size,
- destination_fd: JSC.Node.FileDescriptor = 0,
- source_fd: JSC.Node.FileDescriptor = 0,
+ destination_fd: JSC.Node.FileDescriptor = null_fd,
+ source_fd: JSC.Node.FileDescriptor = null_fd,
system_error: ?SystemError = null,
@@ -2324,7 +2378,10 @@ pub const Blob = struct {
system_error.path = ZigString.init(this.source_file_store.pathlike.path.slice());
system_error.path.mark();
}
- system_error.message = ZigString.init("Failed to copy file");
+
+ if (system_error.message.len == 0) {
+ system_error.message = ZigString.init("Failed to copy file");
+ }
var instance = system_error.toErrorInstance(this.globalThis);
if (this.store) |store| {
@@ -2349,9 +2406,8 @@ pub const Blob = struct {
}
pub fn doClose(this: *CopyFile) void {
- // const repos = await fetch("https://api.github.com/users/octocat/repos")
- const close_input = this.destination_file_store.pathlike != .fd and this.destination_fd != 0;
- const close_output = this.source_file_store.pathlike != .fd and this.source_fd != 0;
+ const close_input = this.destination_file_store.pathlike != .fd and this.destination_fd != null_fd;
+ const close_output = this.source_file_store.pathlike != .fd and this.source_fd != null_fd;
if (close_input and close_output) {
this.doCloseFile(.both);
@@ -2420,41 +2476,86 @@ pub const Blob = struct {
}
}
- pub fn doCopyFileRange(this: *CopyFile) anyerror!void {
+ const TryWith = enum {
+ sendfile,
+ copy_file_range,
+ splice,
+
+ pub const tag = std.EnumMap(TryWith, JSC.Node.Syscall.Tag).init(.{
+ .sendfile = .sendfile,
+ .copy_file_range = .copy_file_range,
+ .splice = .splice,
+ });
+ };
+
+ pub fn doCopyFileRange(
+ this: *CopyFile,
+ comptime use: TryWith,
+ comptime clear_append_if_invalid: bool,
+ ) anyerror!void {
this.read_off += this.offset;
var remain = @as(usize, this.max_length);
- if (remain == 0) {
+ if (remain == max_size or remain == 0) {
// sometimes stat lies
- // let's give it 2048 and see how it goes
- remain = 2048;
+ // let's give it 4096 and see how it goes
+ remain = 4096;
}
var total_written: usize = 0;
const src_fd = this.source_fd;
const dest_fd = this.destination_fd;
+
defer {
- this.read_off = this.offset;
this.read_len = @truncate(SizeType, total_written);
}
- while (remain > 0) {
- // Linux Kernel 5.3 or later
- const written = linux.copy_file_range(src_fd, null, dest_fd, null, remain, 0);
+
+ var has_unset_append = false;
+
+ while (true) {
+ const written = switch (comptime use) {
+ .copy_file_range => linux.copy_file_range(src_fd, null, dest_fd, null, remain, 0),
+ .sendfile => linux.sendfile(dest_fd, src_fd, null, remain),
+ .splice => bun.C.splice(src_fd, null, dest_fd, null, remain, 0),
+ };
+
switch (linux.getErrno(written)) {
.SUCCESS => {},
+
+ .INVAL => {
+ if (comptime clear_append_if_invalid) {
+ if (!has_unset_append) {
+ // https://kylelaker.com/2018/08/31/stdout-oappend.html
+ // make() can set STDOUT / STDERR to O_APPEND
+ // this messes up sendfile()
+ has_unset_append = true;
+ const flags = linux.fcntl(dest_fd, linux.F.GETFL, 0);
+ if ((flags & O.APPEND) != 0) {
+ _ = linux.fcntl(dest_fd, linux.F.SETFL, flags ^ O.APPEND);
+ continue;
+ }
+ }
+ }
+
+ this.system_error = (JSC.Node.Syscall.Error{
+ .errno = @intCast(JSC.Node.Syscall.Error.Int, @enumToInt(linux.E.INVAL)),
+ .syscall = TryWith.tag.get(use).?,
+ }).toSystemError();
+ return AsyncIO.asError(linux.E.INVAL);
+ },
else => |errno| {
this.system_error = (JSC.Node.Syscall.Error{
.errno = @intCast(JSC.Node.Syscall.Error.Int, @enumToInt(errno)),
- .syscall = .copy_file_range,
+ .syscall = TryWith.tag.get(use).?,
}).toSystemError();
return AsyncIO.asError(errno);
},
}
// wrote zero bytes means EOF
- if (written == 0) break;
remain -|= written;
total_written += written;
+ if (written == 0 or remain == 0) break;
}
}
@@ -2462,6 +2563,7 @@ pub const Blob = struct {
switch (JSC.Node.Syscall.fcopyfile(this.source_fd, this.destination_fd, os.system.COPYFILE_DATA)) {
.err => |errno| {
this.system_error = errno.toSystemError();
+
return AsyncIO.asError(errno.errno);
},
.result => {},
@@ -2500,7 +2602,7 @@ pub const Blob = struct {
}
// Do we need to open both files?
- if (this.destination_fd == 0 and this.source_fd == 0) {
+ if (this.destination_fd == null_fd and this.source_fd == null_fd) {
// First, we attempt to clonefile() on macOS
// This is the fastest way to copy a file.
@@ -2555,12 +2657,12 @@ pub const Blob = struct {
this.doOpenFile(.both) catch return;
// Do we need to open only one file?
- } else if (this.destination_fd == 0) {
+ } else if (this.destination_fd == null_fd) {
this.source_fd = this.source_file_store.pathlike.fd;
this.doOpenFile(.destination) catch return;
// Do we need to open only one file?
- } else if (this.source_fd == 0) {
+ } else if (this.source_fd == null_fd) {
this.destination_fd = this.destination_file_store.pathlike.fd;
this.doOpenFile(.source) catch return;
@@ -2570,8 +2672,10 @@ pub const Blob = struct {
return;
}
- std.debug.assert(this.destination_fd != 0);
- std.debug.assert(this.source_fd != 0);
+ std.debug.assert(this.destination_fd != null_fd);
+ std.debug.assert(this.source_fd != null_fd);
+
+ if (this.destination_file_store.pathlike == .fd) {}
const stat: std.os.Stat = stat_ orelse switch (JSC.Node.Syscall.fstat(this.source_fd)) {
.result => |result| result,
@@ -2595,35 +2699,70 @@ pub const Blob = struct {
return;
}
- if (this.max_length > std.mem.page_size) {
+ if (os.S.ISREG(stat.mode) and
+ this.max_length > std.mem.page_size and
+ this.max_length != Blob.max_size)
+ {
bun.C.preallocate_file(this.destination_fd, 0, this.max_length) catch {};
}
}
- if (os.S.ISREG(stat.mode)) {
- if (comptime Environment.isLinux) {
- this.doCopyFileRange() catch {
- this.doClose();
+ if (comptime Environment.isLinux) {
- return;
- };
- } else if (comptime Environment.isMac) {
- this.doFCopyFile() catch {
- this.doClose();
+ // Bun.write(Bun.file("a"), Bun.file("b"))
+ if (os.S.ISREG(stat.mode) and (os.S.ISREG(this.destination_file_store.mode) or this.destination_file_store.mode == 0)) {
+ if (this.destination_file_store.is_atty orelse false) {
+ this.doCopyFileRange(.copy_file_range, true) catch {};
+ } else {
+ this.doCopyFileRange(.copy_file_range, false) catch {};
+ }
- return;
- };
- if (stat.size != 0 and @intCast(SizeType, stat.size) > this.max_length) {
- _ = darwin.ftruncate(this.destination_fd, @intCast(std.os.off_t, this.max_length));
+ this.doClose();
+ return;
+ }
+
+ // $ bun run foo.js | bun run bar.js
+ if (os.S.ISFIFO(stat.mode) and os.S.ISFIFO(this.destination_file_store.mode)) {
+ if (this.destination_file_store.is_atty orelse false) {
+ this.doCopyFileRange(.splice, true) catch {};
+ } else {
+ this.doCopyFileRange(.splice, false) catch {};
}
- } else {
- @compileError("TODO: implement copyfile");
+
+ this.doClose();
+ return;
}
- } else {
+
+ if (os.S.ISREG(stat.mode) or os.S.ISCHR(stat.mode) or os.S.ISSOCK(stat.mode)) {
+ if (this.destination_file_store.is_atty orelse false) {
+ this.doCopyFileRange(.sendfile, true) catch {};
+ } else {
+ this.doCopyFileRange(.sendfile, false) catch {};
+ }
+
+ this.doClose();
+ return;
+ }
+
this.system_error = unsupported_non_regular_file_error;
+ this.doClose();
+ return;
}
- this.doClose();
+ if (comptime Environment.isMac) {
+ this.doFCopyFile() catch {
+ this.doClose();
+
+ return;
+ };
+ if (stat.size != 0 and @intCast(SizeType, stat.size) > this.max_length) {
+ _ = darwin.ftruncate(this.destination_fd, @intCast(std.os.off_t, this.max_length));
+ }
+
+ this.doClose();
+ } else {
+ @compileError("TODO: implement copyfile");
+ }
}
};
};
@@ -2631,6 +2770,8 @@ pub const Blob = struct {
pub const FileStore = struct {
pathlike: JSC.Node.PathOrFileDescriptor,
mime_type: HTTPClient.MimeType = HTTPClient.MimeType.other,
+ is_atty: ?bool = null,
+ mode: JSC.Node.Mode = 0,
pub fn init(pathlike: JSC.Node.PathOrFileDescriptor, mime_type: ?HTTPClient.MimeType) FileStore {
return .{ .pathlike = pathlike, .mime_type = mime_type orelse HTTPClient.MimeType.other };
@@ -2754,7 +2895,7 @@ pub const Blob = struct {
_: []const js.JSValueRef,
_: js.ExceptionRef,
) JSC.C.JSObjectRef {
- return promisified(this.toJSON(ctx.ptr()), ctx.ptr()).asObjectRef();
+ return promisified(this.toJSON(ctx.ptr(), .share), ctx.ptr()).asObjectRef();
}
pub fn getArrayBufferTransfer(
@@ -3082,6 +3223,8 @@ pub const Blob = struct {
clone,
transfer,
share,
+ /// When reading from a fifo like STDIN/STDERR
+ temporary,
};
pub fn setIsASCIIFlag(this: *Blob, is_all_ascii: bool) void {
@@ -3091,7 +3234,7 @@ pub const Blob = struct {
// we can update the store's is_all_ascii flag
// and any other Blob that points to the same store
// can skip checking the encoding
- if (this.size > 0 and this.offset == 0) {
+ if (this.size > 0 and this.offset == 0 and this.store.?.data == .bytes) {
this.store.?.is_all_ascii = is_all_ascii;
}
}
@@ -3108,11 +3251,16 @@ pub const Blob = struct {
var globalThis = handler.globalThis;
bun.default_allocator.destroy(handler);
switch (bytes_) {
- .result => |bytes| {
+ .result => |result| {
+ const bytes = result.buf;
+ const is_temporary = result.is_temporary;
if (blob.size > 0)
blob.size = @minimum(@truncate(u32, bytes.len), blob.size);
-
- promise.resolve(globalThis, Function(&blob, globalThis, comptime lifetime));
+ if (!is_temporary) {
+ promise.resolve(globalThis, Function(&blob, globalThis, bytes, comptime lifetime));
+ } else {
+ promise.resolve(globalThis, Function(&blob, globalThis, bytes, .temporary));
+ }
},
.err => |err| {
promise.reject(globalThis, err.toErrorInstance(globalThis));
@@ -3202,18 +3350,7 @@ pub const Blob = struct {
return this.store != null and this.store.?.data == .file;
}
- pub fn toString(this: *Blob, global: *JSGlobalObject, comptime lifetime: Lifetime) JSValue {
- if (this.needsToReadFile()) {
- return this.doReadFile(toString, lifetime, global);
- }
-
- var view_: []const u8 =
- this.sharedView();
-
- if (view_.len == 0)
- return ZigString.Empty.toValue(global);
-
- var buf = view_;
+ pub fn toStringWithBytes(this: *Blob, global: *JSGlobalObject, buf: []const u8, comptime lifetime: Lifetime) JSValue {
// null == unknown
// false == can't be
const could_be_all_ascii = this.is_all_ascii orelse this.store.?.is_all_ascii;
@@ -3222,15 +3359,21 @@ pub const Blob = struct {
// if toUTF16Alloc returns null, it means there are no non-ASCII characters
// instead of erroring, invalid characters will become a U+FFFD replacement character
if (strings.toUTF16Alloc(bun.default_allocator, buf, false) catch unreachable) |external| {
- this.setIsASCIIFlag(false);
+ if (lifetime != .temporary)
+ this.setIsASCIIFlag(false);
if (lifetime == .transfer) {
this.detach();
}
+
+ if (lifetime == .temporary) {
+ bun.default_allocator.free(bun.constStrToU8(buf));
+ }
+
return ZigString.toExternalU16(external.ptr, external.len, global);
}
- this.setIsASCIIFlag(true);
+ if (lifetime != .temporary) this.setIsASCIIFlag(true);
}
switch (comptime lifetime) {
@@ -3251,16 +3394,29 @@ pub const Blob = struct {
this.store.?.ref();
return ZigString.init(buf).external(global, this.store.?, Store.external);
},
+ .temporary => {
+ return ZigString.init(buf).toExternalValue(global);
+ },
}
}
- pub fn toJSONShare(this: *Blob, global: *JSGlobalObject, comptime _: Lifetime) JSValue {
- return toJSON(this, global);
+ pub fn toString(this: *Blob, global: *JSGlobalObject, comptime lifetime: Lifetime) JSValue {
+ if (this.needsToReadFile()) {
+ return this.doReadFile(toStringWithBytes, lifetime, global);
+ }
+
+ const view_: []u8 =
+ bun.constStrToU8(this.sharedView());
+
+ if (view_.len == 0)
+ return ZigString.Empty.toValue(global);
+
+ return toStringWithBytes(this, global, view_, lifetime);
}
- pub fn toJSON(this: *Blob, global: *JSGlobalObject) JSValue {
+ pub fn toJSON(this: *Blob, global: *JSGlobalObject, comptime lifetime: Lifetime) JSValue {
if (this.needsToReadFile()) {
- return this.doReadFile(toJSONShare, .share, global);
+ return this.doReadFile(toJSONWithBytes, lifetime, global);
}
var view_ = this.sharedView();
@@ -3268,9 +3424,10 @@ pub const Blob = struct {
if (view_.len == 0)
return ZigString.Empty.toValue(global);
- // TODO: use the index to make this one pass instead of two passes
- var buf = view_;
+ return toJSONWithBytes(this, global, view_, lifetime);
+ }
+ pub fn toJSONWithBytes(this: *Blob, global: *JSGlobalObject, buf: []const u8, comptime lifetime: Lifetime) JSValue {
// null == unknown
// false == can't be
const could_be_all_ascii = this.is_all_ascii orelse this.store.?.is_all_ascii;
@@ -3278,37 +3435,35 @@ pub const Blob = struct {
if (could_be_all_ascii == null or !could_be_all_ascii.?) {
// if toUTF16Alloc returns null, it means there are no non-ASCII characters
if (strings.toUTF16Alloc(bun.default_allocator, buf, false) catch null) |external| {
- this.setIsASCIIFlag(false);
+ if (comptime lifetime != .temporary) this.setIsASCIIFlag(false);
return ZigString.toExternalU16(external.ptr, external.len, global).parseJSON(global);
}
- this.setIsASCIIFlag(true);
+ if (comptime lifetime != .temporary) this.setIsASCIIFlag(true);
}
- return ZigString.init(buf).toValue(
- global,
- ).parseJSON(global);
- }
- pub fn toArrayBuffer(this: *Blob, global: *JSGlobalObject, comptime lifetime: Lifetime) JSValue {
- if (this.needsToReadFile()) {
- return this.doReadFile(toArrayBuffer, lifetime, global);
+ if (comptime lifetime == .temporary) {
+ return ZigString.init(buf).toExternalValue(
+ global,
+ ).parseJSON(global);
+ } else {
+ return ZigString.init(buf).toValue(
+ global,
+ ).parseJSON(global);
}
+ }
- var view_ = this.sharedView();
-
- if (view_.len == 0)
- return JSC.ArrayBuffer.fromBytes(&[_]u8{}, .ArrayBuffer).toJS(global.ref(), null);
-
+ pub fn toArrayBufferWithBytes(this: *Blob, global: *JSGlobalObject, buf: []u8, comptime lifetime: Lifetime) JSValue {
switch (comptime lifetime) {
.clone => {
- var clone = bun.default_allocator.alloc(u8, view_.len) catch unreachable;
- @memcpy(clone.ptr, view_.ptr, view_.len);
+ var clone = bun.default_allocator.alloc(u8, buf.len) catch unreachable;
+ @memcpy(clone.ptr, buf.ptr, buf.len);
return JSC.ArrayBuffer.fromBytes(clone, .ArrayBuffer).toJS(global.ref(), null);
},
.share => {
this.store.?.ref();
- return JSC.ArrayBuffer.fromBytes(bun.constStrToU8(view_), .ArrayBuffer).toJSWithContext(
+ return JSC.ArrayBuffer.fromBytes(buf, .ArrayBuffer).toJSWithContext(
global.ref(),
this.store.?,
JSC.BlobArrayBuffer_deallocator,
@@ -3318,14 +3473,33 @@ pub const Blob = struct {
.transfer => {
var store = this.store.?;
this.transfer();
- return JSC.ArrayBuffer.fromBytes(bun.constStrToU8(view_), .ArrayBuffer).toJSWithContext(
+ return JSC.ArrayBuffer.fromBytes(buf, .ArrayBuffer).toJSWithContext(
global.ref(),
store,
JSC.BlobArrayBuffer_deallocator,
null,
);
},
+ .temporary => {
+ return JSC.ArrayBuffer.fromBytes(buf, .ArrayBuffer).toJS(
+ global.ref(),
+ null,
+ );
+ },
+ }
+ }
+
+ pub fn toArrayBuffer(this: *Blob, global: *JSGlobalObject, comptime lifetime: Lifetime) JSValue {
+ if (this.needsToReadFile()) {
+ return this.doReadFile(toArrayBufferWithBytes, lifetime, global);
}
+
+ var view_ = this.sharedView();
+
+ if (view_.len == 0)
+ return JSC.ArrayBuffer.fromBytes(&[_]u8{}, .ArrayBuffer).toJS(global.ref(), null);
+
+ return toArrayBufferWithBytes(this, global, bun.constStrToU8(view_), lifetime);
}
pub inline fn fromJS(
@@ -3796,7 +3970,7 @@ pub const Body = struct {
promise.asPromise().?.resolve(global, JSValue.fromRef(blob.getTextTransfer(global.ref())));
},
.getJSON => {
- promise.asPromise().?.resolve(global, blob.toJSON(global));
+ promise.asPromise().?.resolve(global, blob.toJSON(global, .share));
blob.detach();
},
.getArrayBuffer => {
diff --git a/src/linux_c.zig b/src/linux_c.zig
index 6247df61d..ae5e9a3cb 100644
--- a/src/linux_c.zig
+++ b/src/linux_c.zig
@@ -280,3 +280,20 @@ pub const SystemErrno = enum(u8) {
pub fn preallocate_file(fd: std.os.fd_t, offset: std.os.off_t, len: std.os.off_t) anyerror!void {
_ = std.os.linux.fallocate(fd, 0, @intCast(i64, offset), len);
}
+
+/// splice() moves data between two file descriptors without copying
+/// between kernel address space and user address space. It
+/// transfers up to len bytes of data from the file descriptor fd_in
+/// to the file descriptor fd_out, where one of the file descriptors
+/// must refer to a pipe.
+pub fn splice(fd_in: std.os.fd_t, off_in: ?*i64, fd_out: std.os.fd_t, off_out: ?*i64, len: usize, flags: u32) usize {
+ return std.os.linux.syscall6(
+ .splice,
+ @bitCast(usize, @as(isize, fd_in)),
+ @ptrToInt(off_in),
+ @bitCast(usize, @as(isize, fd_out)),
+ @ptrToInt(off_out),
+ len,
+ flags,
+ );
+}
diff --git a/src/string_immutable.zig b/src/string_immutable.zig
index 4c7c07eae..1483729fa 100644
--- a/src/string_immutable.zig
+++ b/src/string_immutable.zig
@@ -1106,6 +1106,8 @@ pub fn convertUTF8BytesIntoUTF16(sequence: *const [4]u8) UTF16Replacement {
(@as(u32, sequence[2]) << 6) + @as(u32, sequence[3])) - 0x03C82080,
};
},
+ // invalid unicode sequence
+ 0 => return UTF16Replacement{ .len = 1 },
else => unreachable,
}
}