mt-lang 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/docs/index.html +4 -3
- data/lib/milk_tea/base.rb +1 -1
- data/std/asset_pack.mt +267 -0
- data/std/async/libuv_runtime.mt +539 -0
- data/std/async/mailbox.mt +191 -0
- data/std/async/runtime.mt +87 -0
- data/std/async.mt +87 -0
- data/std/base64.mt +185 -0
- data/std/behavior_tree.mt +396 -0
- data/std/binary.mt +314 -0
- data/std/binary_heap.mt +157 -0
- data/std/bitset.mt +239 -0
- data/std/box2d.mt +535 -0
- data/std/bytes.mt +46 -0
- data/std/c/box2d.mt +1254 -0
- data/std/c/cgltf.mt +647 -0
- data/std/c/cjson.mt +106 -0
- data/std/c/crypto.mt +11 -0
- data/std/c/crypto_support.h +51 -0
- data/std/c/ctype.mt +19 -0
- data/std/c/ctype_bindgen.h +29 -0
- data/std/c/curl.mt +1223 -0
- data/std/c/enet.mt +473 -0
- data/std/c/errno.mt +41 -0
- data/std/c/errno_bindgen.h +53 -0
- data/std/c/flecs.mt +2945 -0
- data/std/c/fs.linux.mt +43 -0
- data/std/c/fs.windows.mt +43 -0
- data/std/c/fs_support.h +954 -0
- data/std/c/gl.mt +2065 -0
- data/std/c/gl_registry_helpers.h +10748 -0
- data/std/c/glfw.mt +499 -0
- data/std/c/libc.mt +167 -0
- data/std/c/libuv.mt +1491 -0
- data/std/c/math.mt +22 -0
- data/std/c/math_bindgen.h +31 -0
- data/std/c/miniaudio.mt +3420 -0
- data/std/c/pcre2.mt +735 -0
- data/std/c/process.mt +58 -0
- data/std/c/process_support.h +1147 -0
- data/std/c/raygui.mt +1602 -0
- data/std/c/raylib.mt +1245 -0
- data/std/c/raymath.mt +163 -0
- data/std/c/rlgl.mt +434 -0
- data/std/c/rpng.mt +60 -0
- data/std/c/rres.mt +187 -0
- data/std/c/sdl3.mt +4132 -0
- data/std/c/sqlite3.mt +951 -0
- data/std/c/stb_image.mt +52 -0
- data/std/c/stb_image_resize2.mt +128 -0
- data/std/c/stb_image_write.mt +19 -0
- data/std/c/stb_rect_pack.mt +38 -0
- data/std/c/stb_truetype.mt +175 -0
- data/std/c/stb_vorbis.mt +31 -0
- data/std/c/stdio.mt +90 -0
- data/std/c/steamworks.h +5769 -0
- data/std/c/steamworks.mt +4771 -0
- data/std/c/string.mt +16 -0
- data/std/c/string_bindgen.h +36 -0
- data/std/c/sync.mt +33 -0
- data/std/c/sync_support.h +219 -0
- data/std/c/terminal.mt +24 -0
- data/std/c/terminal_support.h +408 -0
- data/std/c/time.mt +26 -0
- data/std/c/tls.mt +24 -0
- data/std/c/tls_support.h +626 -0
- data/std/c/tracy.mt +53 -0
- data/std/c/zlib.mt +20 -0
- data/std/c/zlib_support.h +239 -0
- data/std/c/zstd.mt +231 -0
- data/std/cell.mt +60 -0
- data/std/cgltf.mt +115 -0
- data/std/cjson.mt +90 -0
- data/std/cli.mt +768 -0
- data/std/cookie.mt +198 -0
- data/std/counter.mt +160 -0
- data/std/crypto.mt +74 -0
- data/std/cstring.mt +13 -0
- data/std/ctype.mt +52 -0
- data/std/curl/runtime.mt +261 -0
- data/std/curl.mt +38 -0
- data/std/deque.mt +348 -0
- data/std/encoding.mt +93 -0
- data/std/enet.mt +133 -0
- data/std/env.mt +41 -0
- data/std/errno.mt +47 -0
- data/std/flecs.mt +978 -0
- data/std/fmt.mt +271 -0
- data/std/fs.linux.mt +696 -0
- data/std/fs.windows.mt +711 -0
- data/std/fsm.mt +267 -0
- data/std/gl.mt +2062 -0
- data/std/glfw.mt +471 -0
- data/std/goap.mt +342 -0
- data/std/graph.mt +521 -0
- data/std/gzip.mt +77 -0
- data/std/hash.mt +429 -0
- data/std/http/server.mt +810 -0
- data/std/http.mt +1227 -0
- data/std/intern.mt +53 -0
- data/std/jobs.mt +288 -0
- data/std/json.mt +627 -0
- data/std/libc.mt +34 -0
- data/std/libuv.mt +554 -0
- data/std/linear_algebra.mt +243 -0
- data/std/linked_map.mt +372 -0
- data/std/linked_map_view.mt +49 -0
- data/std/linked_set.mt +117 -0
- data/std/log.mt +69 -0
- data/std/map.mt +418 -0
- data/std/math.mt +24 -0
- data/std/mem/arena.mt +137 -0
- data/std/mem/endian.mt +51 -0
- data/std/mem/heap.mt +298 -0
- data/std/mem/pool.mt +164 -0
- data/std/mem/stack.mt +47 -0
- data/std/mem/tracking.mt +119 -0
- data/std/miniaudio.mt +1299 -0
- data/std/multiset.mt +191 -0
- data/std/net/channel.mt +727 -0
- data/std/net/clock.mt +237 -0
- data/std/net/discovery.mt +277 -0
- data/std/net/lobby.mt +813 -0
- data/std/net/manager.mt +539 -0
- data/std/net/mux.mt +839 -0
- data/std/net/nat.mt +122 -0
- data/std/net/packet.mt +219 -0
- data/std/net/punch.mt +162 -0
- data/std/net/rpc.mt +119 -0
- data/std/net/session.mt +1157 -0
- data/std/net/stun.mt +252 -0
- data/std/net/sync.mt +183 -0
- data/std/net/turn.mt +459 -0
- data/std/net.mt +2944 -0
- data/std/oauth2.mt +408 -0
- data/std/option.mt +51 -0
- data/std/ordered_map.mt +497 -0
- data/std/ordered_set.mt +348 -0
- data/std/path.mt +368 -0
- data/std/pcre2/runtime.mt +35 -0
- data/std/pcre2.mt +78 -0
- data/std/priority_queue.mt +55 -0
- data/std/process.mt +837 -0
- data/std/queue.mt +55 -0
- data/std/random.mt +146 -0
- data/std/raygui.mt +99 -0
- data/std/raylib/debug_console.mt +71 -0
- data/std/raylib/easing.mt +388 -0
- data/std/raylib/packed_assets.mt +255 -0
- data/std/raylib/runtime.mt +30 -0
- data/std/raylib/tracy_gpu.mt +37 -0
- data/std/raylib.mt +1510 -0
- data/std/raymath.mt +153 -0
- data/std/result.mt +89 -0
- data/std/rlgl.mt +268 -0
- data/std/rpng.mt +46 -0
- data/std/rres.mt +36 -0
- data/std/sdl3/runtime.mt +54 -0
- data/std/sdl3.mt +1731 -0
- data/std/serialize.mt +68 -0
- data/std/set.mt +124 -0
- data/std/spatial.mt +176 -0
- data/std/sqlite3.mt +151 -0
- data/std/stack.mt +55 -0
- data/std/stb_image.mt +47 -0
- data/std/stb_image_resize2.mt +41 -0
- data/std/stb_image_write.mt +17 -0
- data/std/stb_rect_pack.mt +15 -0
- data/std/stb_truetype.mt +77 -0
- data/std/stb_vorbis.mt +16 -0
- data/std/stdio.mt +88 -0
- data/std/steamworks.mt +1542 -0
- data/std/str.mt +293 -0
- data/std/string.mt +234 -0
- data/std/sync.mt +194 -0
- data/std/tar.mt +704 -0
- data/std/terminal.mt +1002 -0
- data/std/testing.mt +266 -0
- data/std/thread.mt +120 -0
- data/std/time.mt +105 -0
- data/std/tls.mt +616 -0
- data/std/toml.mt +1310 -0
- data/std/tracy.mt +42 -0
- data/std/uri.mt +118 -0
- data/std/url.mt +372 -0
- data/std/vec.mt +433 -0
- data/std/zstd.mt +94 -0
- metadata +187 -2
data/std/tar.mt
ADDED
|
@@ -0,0 +1,704 @@
|
|
|
1
|
+
import std.bytes as bytes
|
|
2
|
+
import std.fs as fs
|
|
3
|
+
import std.gzip as gzip
|
|
4
|
+
import std.mem.heap as heap
|
|
5
|
+
import std.path as path_ops
|
|
6
|
+
import std.str as text
|
|
7
|
+
import std.string as string
|
|
8
|
+
import std.vec as vec
|
|
9
|
+
|
|
10
|
+
const block_size: ptr_uint = 512
|
|
11
|
+
const file_type_flag: ubyte = 48
|
|
12
|
+
const directory_type_flag: ubyte = 53
|
|
13
|
+
const zero_byte: ubyte = 0
|
|
14
|
+
const slash_byte: ubyte = 47
|
|
15
|
+
const space_byte: ubyte = 32
|
|
16
|
+
|
|
17
|
+
public struct Error:
|
|
18
|
+
message: string.String
|
|
19
|
+
|
|
20
|
+
enum EntryKind: int
|
|
21
|
+
file = 0
|
|
22
|
+
directory = 1
|
|
23
|
+
|
|
24
|
+
struct UstarPath:
|
|
25
|
+
prefix_start: ptr_uint
|
|
26
|
+
prefix_len: ptr_uint
|
|
27
|
+
name_start: ptr_uint
|
|
28
|
+
name_len: ptr_uint
|
|
29
|
+
|
|
30
|
+
struct ParsedEntry:
|
|
31
|
+
path: string.String
|
|
32
|
+
kind: EntryKind
|
|
33
|
+
mode: int
|
|
34
|
+
size: ptr_uint
|
|
35
|
+
data_offset: ptr_uint
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
function error_message(message: str) -> Error:
|
|
39
|
+
return Error(message = string.String.from_str(message))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
function take_vec_bytes(values: ref[vec.Vec[ubyte]]) -> bytes.Bytes:
|
|
43
|
+
let data = values.data
|
|
44
|
+
let len = values.len
|
|
45
|
+
values.data = null
|
|
46
|
+
values.len = 0
|
|
47
|
+
values.capacity = 0
|
|
48
|
+
|
|
49
|
+
if data == null:
|
|
50
|
+
if len != 0:
|
|
51
|
+
fatal(c"tar.take_vec_bytes missing storage")
|
|
52
|
+
|
|
53
|
+
return bytes.Bytes.empty()
|
|
54
|
+
|
|
55
|
+
return bytes.Bytes(data = data, len = len)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
function take_fs_error(raw: fs.Error) -> Error:
|
|
59
|
+
let message = string.String.from_str(raw.message.as_str())
|
|
60
|
+
var owned = raw
|
|
61
|
+
owned.release()
|
|
62
|
+
return Error(message = message)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
function take_gzip_error(raw: gzip.Error) -> Error:
|
|
66
|
+
let message = string.String.from_str(raw.message.as_str())
|
|
67
|
+
var owned = raw
|
|
68
|
+
owned.release()
|
|
69
|
+
return Error(message = message)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
function release_string_values(values: ref[vec.Vec[string.String]]) -> void:
|
|
73
|
+
var index: ptr_uint = 0
|
|
74
|
+
while index < values.len():
|
|
75
|
+
let value_ptr = values.get(index) else:
|
|
76
|
+
fatal(c"tar.release_string_values missing value")
|
|
77
|
+
|
|
78
|
+
unsafe:
|
|
79
|
+
read(value_ptr).release()
|
|
80
|
+
|
|
81
|
+
index += 1
|
|
82
|
+
|
|
83
|
+
values.release()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
function compare_text(left: str, right: str) -> int:
|
|
87
|
+
var index: ptr_uint = 0
|
|
88
|
+
var shared_len = left.len
|
|
89
|
+
if right.len < shared_len:
|
|
90
|
+
shared_len = right.len
|
|
91
|
+
|
|
92
|
+
while index < shared_len:
|
|
93
|
+
let left_value = left.byte_at(index)
|
|
94
|
+
let right_value = right.byte_at(index)
|
|
95
|
+
if left_value < right_value:
|
|
96
|
+
return -1
|
|
97
|
+
if left_value > right_value:
|
|
98
|
+
return 1
|
|
99
|
+
index += 1
|
|
100
|
+
|
|
101
|
+
if left.len < right.len:
|
|
102
|
+
return -1
|
|
103
|
+
if left.len > right.len:
|
|
104
|
+
return 1
|
|
105
|
+
|
|
106
|
+
return 0
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
function sort_string_values(values: ref[vec.Vec[string.String]]) -> void:
|
|
110
|
+
var index: ptr_uint = 1
|
|
111
|
+
while index < values.len():
|
|
112
|
+
var current = index
|
|
113
|
+
while current > 0:
|
|
114
|
+
let previous_ptr = values.get(current - 1) else:
|
|
115
|
+
fatal(c"tar.sort_string_values missing previous value")
|
|
116
|
+
let current_ptr = values.get(current) else:
|
|
117
|
+
fatal(c"tar.sort_string_values missing current value")
|
|
118
|
+
|
|
119
|
+
var should_swap = false
|
|
120
|
+
unsafe:
|
|
121
|
+
should_swap = compare_text(read(previous_ptr).as_str(), read(current_ptr).as_str()) > 0
|
|
122
|
+
if should_swap:
|
|
123
|
+
values.swap(current - 1, current)
|
|
124
|
+
else:
|
|
125
|
+
break
|
|
126
|
+
|
|
127
|
+
current -= 1
|
|
128
|
+
|
|
129
|
+
index += 1
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
function copy_sorted_entry_names(entries: fs.Entries, include_hidden: bool) -> Result[vec.Vec[string.String], Error]:
|
|
133
|
+
var values = vec.Vec[string.String].create()
|
|
134
|
+
|
|
135
|
+
var index: ptr_uint = 0
|
|
136
|
+
while index < entries.len():
|
|
137
|
+
match entries.get(index):
|
|
138
|
+
Option.none:
|
|
139
|
+
release_string_values(ref_of(values))
|
|
140
|
+
return Result[
|
|
141
|
+
vec.Vec[string.String],
|
|
142
|
+
Error
|
|
143
|
+
].failure(error = error_message("tar list entries missing name"))
|
|
144
|
+
Option.some as payload:
|
|
145
|
+
if include_hidden or not payload.value.starts_with("."):
|
|
146
|
+
values.push(string.String.from_str(payload.value))
|
|
147
|
+
|
|
148
|
+
index += 1
|
|
149
|
+
|
|
150
|
+
sort_string_values(ref_of(values))
|
|
151
|
+
return Result[vec.Vec[string.String], Error].success(value = values)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
function append_zero_span(output: ref[vec.Vec[ubyte]], count: ptr_uint) -> void:
|
|
155
|
+
if count == 0:
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
var zeroes = zero[array[ubyte, 512]]
|
|
159
|
+
output.append_span(span[ubyte](data = ptr_of(zeroes[0]), len = count))
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
function append_padding(output: ref[vec.Vec[ubyte]], size_bytes: ptr_uint) -> void:
|
|
163
|
+
let remainder = size_bytes % block_size
|
|
164
|
+
if remainder == 0:
|
|
165
|
+
return
|
|
166
|
+
|
|
167
|
+
append_zero_span(output, block_size - remainder)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
function append_end_blocks(output: ref[vec.Vec[ubyte]]) -> void:
|
|
171
|
+
append_zero_span(output, block_size)
|
|
172
|
+
append_zero_span(output, block_size)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
function split_ustar_path(path: str) -> Result[UstarPath, Error]:
|
|
176
|
+
if path.len == 0:
|
|
177
|
+
return Result[UstarPath, Error].failure(error = error_message("tar entry path cannot be empty"))
|
|
178
|
+
if path.len <= 100:
|
|
179
|
+
return Result[UstarPath, Error].success(value = UstarPath(
|
|
180
|
+
prefix_start = 0,
|
|
181
|
+
prefix_len = 0,
|
|
182
|
+
name_start = 0,
|
|
183
|
+
name_len = path.len
|
|
184
|
+
))
|
|
185
|
+
if path.len > 255:
|
|
186
|
+
return Result[UstarPath, Error].failure(error = error_message("tar entry path exceeds ustar length limits"))
|
|
187
|
+
|
|
188
|
+
var index = path.len
|
|
189
|
+
while index > 0:
|
|
190
|
+
index -= 1
|
|
191
|
+
if path.byte_at(index) == slash_byte:
|
|
192
|
+
let prefix_len = index
|
|
193
|
+
let name_start = index + 1
|
|
194
|
+
let name_len = path.len - name_start
|
|
195
|
+
if prefix_len != 0 and prefix_len <= 155 and name_len <= 100:
|
|
196
|
+
return Result[UstarPath, Error].success(value = UstarPath(
|
|
197
|
+
prefix_start = 0,
|
|
198
|
+
prefix_len = prefix_len,
|
|
199
|
+
name_start = name_start,
|
|
200
|
+
name_len = name_len
|
|
201
|
+
))
|
|
202
|
+
|
|
203
|
+
return Result[
|
|
204
|
+
UstarPath,
|
|
205
|
+
Error
|
|
206
|
+
].failure(error = error_message("tar entry path cannot be represented in ustar format"))
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
function write_path_field(buffer: ptr[ubyte], offset: ptr_uint, value: str, start: ptr_uint, len: ptr_uint) -> void:
|
|
210
|
+
var index: ptr_uint = 0
|
|
211
|
+
while index < len:
|
|
212
|
+
unsafe:
|
|
213
|
+
read(buffer + offset + index) = value.byte_at(start + index)
|
|
214
|
+
index += 1
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
function write_octal_digits(buffer: ptr[ubyte], offset: ptr_uint, digits_len: ptr_uint, value: ptr_uint) -> bool:
|
|
218
|
+
var digits = zero[array[ubyte, 32]]
|
|
219
|
+
var count: ptr_uint = 0
|
|
220
|
+
var remaining = value
|
|
221
|
+
|
|
222
|
+
if remaining == 0:
|
|
223
|
+
digits[0] = 48
|
|
224
|
+
count = 1
|
|
225
|
+
else:
|
|
226
|
+
while remaining != 0:
|
|
227
|
+
if count >= 32:
|
|
228
|
+
return false
|
|
229
|
+
|
|
230
|
+
let digit = remaining % 8
|
|
231
|
+
digits[count] = ubyte<-(48z + digit)
|
|
232
|
+
remaining /= 8
|
|
233
|
+
count += 1
|
|
234
|
+
|
|
235
|
+
if count > digits_len:
|
|
236
|
+
return false
|
|
237
|
+
|
|
238
|
+
var index: ptr_uint = 0
|
|
239
|
+
while index < digits_len:
|
|
240
|
+
unsafe:
|
|
241
|
+
read(buffer + offset + index) = 48
|
|
242
|
+
index += 1
|
|
243
|
+
|
|
244
|
+
index = 0
|
|
245
|
+
var out_index = digits_len
|
|
246
|
+
while index < count:
|
|
247
|
+
out_index -= 1
|
|
248
|
+
unsafe:
|
|
249
|
+
read(buffer + offset + out_index) = digits[index]
|
|
250
|
+
index += 1
|
|
251
|
+
|
|
252
|
+
return true
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
function write_octal_field(buffer: ptr[ubyte], offset: ptr_uint, field_len: ptr_uint, value: ptr_uint) -> bool:
|
|
256
|
+
if field_len < 2:
|
|
257
|
+
return false
|
|
258
|
+
|
|
259
|
+
if not write_octal_digits(buffer, offset, field_len - 1, value):
|
|
260
|
+
return false
|
|
261
|
+
|
|
262
|
+
unsafe:
|
|
263
|
+
read(buffer + offset + field_len - 1) = zero_byte
|
|
264
|
+
return true
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
function write_checksum_field(buffer: ptr[ubyte], offset: ptr_uint, value: ptr_uint) -> bool:
|
|
268
|
+
if not write_octal_digits(buffer, offset, 6, value):
|
|
269
|
+
return false
|
|
270
|
+
|
|
271
|
+
unsafe:
|
|
272
|
+
read(buffer + offset + 6) = zero_byte
|
|
273
|
+
read(buffer + offset + 7) = space_byte
|
|
274
|
+
return true
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
function header_checksum(buffer: ptr[ubyte]) -> ptr_uint:
|
|
278
|
+
var sum: ptr_uint = 0
|
|
279
|
+
var index: ptr_uint = 0
|
|
280
|
+
while index < block_size:
|
|
281
|
+
unsafe:
|
|
282
|
+
sum += ptr_uint<-read(buffer + index)
|
|
283
|
+
index += 1
|
|
284
|
+
|
|
285
|
+
return sum
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
function append_header(
|
|
289
|
+
output: ref[vec.Vec[ubyte]],
|
|
290
|
+
archive_path: str,
|
|
291
|
+
kind: EntryKind,
|
|
292
|
+
mode: int,
|
|
293
|
+
size_bytes: ptr_uint
|
|
294
|
+
) -> Result[bool, Error]:
|
|
295
|
+
let parts = split_ustar_path(archive_path)?
|
|
296
|
+
var header = zero[array[ubyte, 512]]
|
|
297
|
+
let header_ptr = ptr_of(header[0])
|
|
298
|
+
|
|
299
|
+
write_path_field(header_ptr, 0, archive_path, parts.name_start, parts.name_len)
|
|
300
|
+
if not write_octal_field(header_ptr, 100, 8, ptr_uint<-(mode & 4095)):
|
|
301
|
+
return Result[bool, Error].failure(error = error_message("tar mode exceeds field capacity"))
|
|
302
|
+
if not write_octal_field(header_ptr, 108, 8, 0):
|
|
303
|
+
return Result[bool, Error].failure(error = error_message("tar uid exceeds field capacity"))
|
|
304
|
+
if not write_octal_field(header_ptr, 116, 8, 0):
|
|
305
|
+
return Result[bool, Error].failure(error = error_message("tar gid exceeds field capacity"))
|
|
306
|
+
|
|
307
|
+
var stored_size: ptr_uint = 0
|
|
308
|
+
if kind == EntryKind.file:
|
|
309
|
+
stored_size = size_bytes
|
|
310
|
+
|
|
311
|
+
if not write_octal_field(header_ptr, 124, 12, stored_size):
|
|
312
|
+
return Result[bool, Error].failure(error = error_message("tar size exceeds field capacity"))
|
|
313
|
+
if not write_octal_field(header_ptr, 136, 12, 0):
|
|
314
|
+
return Result[bool, Error].failure(error = error_message("tar mtime exceeds field capacity"))
|
|
315
|
+
|
|
316
|
+
var checksum_index: ptr_uint = 148
|
|
317
|
+
while checksum_index < 156:
|
|
318
|
+
header[checksum_index] = space_byte
|
|
319
|
+
checksum_index += 1
|
|
320
|
+
|
|
321
|
+
if kind == EntryKind.directory:
|
|
322
|
+
header[156] = directory_type_flag
|
|
323
|
+
else:
|
|
324
|
+
header[156] = file_type_flag
|
|
325
|
+
|
|
326
|
+
header[257] = 117
|
|
327
|
+
header[258] = 115
|
|
328
|
+
header[259] = 116
|
|
329
|
+
header[260] = 97
|
|
330
|
+
header[261] = 114
|
|
331
|
+
header[263] = 48
|
|
332
|
+
header[264] = 48
|
|
333
|
+
|
|
334
|
+
if parts.prefix_len != 0:
|
|
335
|
+
write_path_field(header_ptr, 345, archive_path, parts.prefix_start, parts.prefix_len)
|
|
336
|
+
|
|
337
|
+
if not write_checksum_field(header_ptr, 148, header_checksum(header_ptr)):
|
|
338
|
+
return Result[bool, Error].failure(error = error_message("tar checksum exceeds field capacity"))
|
|
339
|
+
|
|
340
|
+
output.append_array(header)
|
|
341
|
+
return Result[bool, Error].success(value = true)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
function append_file_entry(source_path: str, archive_path: str, output: ref[vec.Vec[ubyte]]) -> Result[bool, Error]:
|
|
345
|
+
match fs.metadata(source_path):
|
|
346
|
+
Result.failure as payload:
|
|
347
|
+
return Result[bool, Error].failure(error = take_fs_error(payload.error))
|
|
348
|
+
Result.success as metadata_payload:
|
|
349
|
+
let metadata = metadata_payload.value
|
|
350
|
+
match append_header(output, archive_path, EntryKind.file, metadata.mode, metadata.size):
|
|
351
|
+
Result.failure as payload:
|
|
352
|
+
return Result[bool, Error].failure(error = payload.error)
|
|
353
|
+
Result.success:
|
|
354
|
+
pass
|
|
355
|
+
|
|
356
|
+
match fs.read_bytes(source_path):
|
|
357
|
+
Result.failure as payload:
|
|
358
|
+
return Result[bool, Error].failure(error = take_fs_error(payload.error))
|
|
359
|
+
Result.success as payload:
|
|
360
|
+
var data = payload.value
|
|
361
|
+
defer data.release()
|
|
362
|
+
output.append_span(data.as_span())
|
|
363
|
+
append_padding(output, data.len)
|
|
364
|
+
return Result[bool, Error].success(value = true)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
function append_directory_tree(
|
|
368
|
+
source_path: str,
|
|
369
|
+
archive_path: str,
|
|
370
|
+
include_hidden: bool,
|
|
371
|
+
output: ref[vec.Vec[ubyte]]
|
|
372
|
+
) -> Result[bool, Error]:
|
|
373
|
+
if archive_path.len != 0:
|
|
374
|
+
match fs.metadata(source_path):
|
|
375
|
+
Result.failure as payload:
|
|
376
|
+
return Result[bool, Error].failure(error = take_fs_error(payload.error))
|
|
377
|
+
Result.success as payload:
|
|
378
|
+
let metadata = payload.value
|
|
379
|
+
match append_header(output, archive_path, EntryKind.directory, metadata.mode, 0):
|
|
380
|
+
Result.failure as header_payload:
|
|
381
|
+
return Result[bool, Error].failure(error = header_payload.error)
|
|
382
|
+
Result.success:
|
|
383
|
+
pass
|
|
384
|
+
|
|
385
|
+
match fs.list_entries(source_path):
|
|
386
|
+
Result.failure as payload:
|
|
387
|
+
return Result[bool, Error].failure(error = take_fs_error(payload.error))
|
|
388
|
+
Result.success as payload:
|
|
389
|
+
var entries = payload.value
|
|
390
|
+
defer entries.release()
|
|
391
|
+
|
|
392
|
+
match copy_sorted_entry_names(entries, include_hidden):
|
|
393
|
+
Result.failure as names_payload:
|
|
394
|
+
return Result[bool, Error].failure(error = names_payload.error)
|
|
395
|
+
Result.success as names_payload:
|
|
396
|
+
var names = names_payload.value
|
|
397
|
+
defer release_string_values(ref_of(names))
|
|
398
|
+
|
|
399
|
+
var index: ptr_uint = 0
|
|
400
|
+
while index < names.len():
|
|
401
|
+
let name_ptr = names.get(index) else:
|
|
402
|
+
return Result[bool, Error].failure(error = error_message("tar entry name missing"))
|
|
403
|
+
|
|
404
|
+
unsafe:
|
|
405
|
+
let child_name = read(name_ptr).as_str()
|
|
406
|
+
var child_source = path_ops.join(source_path, child_name)
|
|
407
|
+
var child_archive = string.String.create()
|
|
408
|
+
if archive_path.len == 0:
|
|
409
|
+
child_archive = string.String.from_str(child_name)
|
|
410
|
+
else:
|
|
411
|
+
child_archive = path_ops.join(archive_path, child_name)
|
|
412
|
+
|
|
413
|
+
if fs.is_directory(child_source.as_str()):
|
|
414
|
+
match append_directory_tree(
|
|
415
|
+
child_source.as_str(),
|
|
416
|
+
child_archive.as_str(),
|
|
417
|
+
include_hidden,
|
|
418
|
+
output
|
|
419
|
+
):
|
|
420
|
+
Result.failure as child_payload:
|
|
421
|
+
child_source.release()
|
|
422
|
+
child_archive.release()
|
|
423
|
+
return Result[bool, Error].failure(error = child_payload.error)
|
|
424
|
+
Result.success:
|
|
425
|
+
pass
|
|
426
|
+
else if fs.is_file(child_source.as_str()):
|
|
427
|
+
match append_file_entry(child_source.as_str(), child_archive.as_str(), output):
|
|
428
|
+
Result.failure as child_payload:
|
|
429
|
+
child_source.release()
|
|
430
|
+
child_archive.release()
|
|
431
|
+
return Result[bool, Error].failure(error = child_payload.error)
|
|
432
|
+
Result.success:
|
|
433
|
+
pass
|
|
434
|
+
|
|
435
|
+
child_source.release()
|
|
436
|
+
child_archive.release()
|
|
437
|
+
|
|
438
|
+
index += 1
|
|
439
|
+
|
|
440
|
+
return Result[bool, Error].success(value = true)
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
function block_is_zero(input: span[ubyte], offset: ptr_uint) -> bool:
|
|
444
|
+
var index: ptr_uint = 0
|
|
445
|
+
while index < block_size:
|
|
446
|
+
unsafe:
|
|
447
|
+
if read(input.data + offset + index) != 0:
|
|
448
|
+
return false
|
|
449
|
+
index += 1
|
|
450
|
+
|
|
451
|
+
return true
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
function padded_size(size_bytes: ptr_uint) -> ptr_uint:
|
|
455
|
+
let remainder = size_bytes % block_size
|
|
456
|
+
if remainder == 0:
|
|
457
|
+
return size_bytes
|
|
458
|
+
|
|
459
|
+
return size_bytes + (block_size - remainder)
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
function parse_octal_field(input: span[ubyte], offset: ptr_uint, field_len: ptr_uint) -> Result[ptr_uint, Error]:
|
|
463
|
+
var index: ptr_uint = 0
|
|
464
|
+
while index < field_len:
|
|
465
|
+
unsafe:
|
|
466
|
+
let current = read(input.data + offset + index)
|
|
467
|
+
if current == zero_byte or current == space_byte:
|
|
468
|
+
index += 1
|
|
469
|
+
continue
|
|
470
|
+
break
|
|
471
|
+
|
|
472
|
+
var value: ptr_uint = 0
|
|
473
|
+
while index < field_len:
|
|
474
|
+
unsafe:
|
|
475
|
+
let current = read(input.data + offset + index)
|
|
476
|
+
if current == zero_byte or current == space_byte:
|
|
477
|
+
break
|
|
478
|
+
if current < 48 or current > 55:
|
|
479
|
+
return Result[
|
|
480
|
+
ptr_uint,
|
|
481
|
+
Error
|
|
482
|
+
].failure(error = error_message("tar numeric field contains non-octal digits"))
|
|
483
|
+
let digit = ptr_uint<-(current - 48ub)
|
|
484
|
+
if value > (heap.ptr_uint_max - digit) / 8:
|
|
485
|
+
return Result[
|
|
486
|
+
ptr_uint,
|
|
487
|
+
Error
|
|
488
|
+
].failure(error = error_message("tar numeric field exceeds ptr_uint range"))
|
|
489
|
+
value = value * 8 + digit
|
|
490
|
+
|
|
491
|
+
index += 1
|
|
492
|
+
|
|
493
|
+
return Result[ptr_uint, Error].success(value = value)
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
function read_text_field(input: span[ubyte], offset: ptr_uint, field_len: ptr_uint) -> Result[string.String, Error]:
|
|
497
|
+
var used: ptr_uint = 0
|
|
498
|
+
while used < field_len:
|
|
499
|
+
unsafe:
|
|
500
|
+
if read(input.data + offset + used) == zero_byte:
|
|
501
|
+
break
|
|
502
|
+
used += 1
|
|
503
|
+
|
|
504
|
+
if used == 0:
|
|
505
|
+
return Result[string.String, Error].success(value = string.String.create())
|
|
506
|
+
|
|
507
|
+
let copied = heap.must_alloc[ubyte](used)
|
|
508
|
+
unsafe:
|
|
509
|
+
heap.copy_bytes(copied, input.data + offset, used)
|
|
510
|
+
|
|
511
|
+
let owned = string.String(data = copied, len = used, capacity = used, owns_storage = true)
|
|
512
|
+
match text.utf8_byte_span_as_str(span[ubyte](data = copied, len = used)):
|
|
513
|
+
Option.some:
|
|
514
|
+
return Result[string.String, Error].success(value = owned)
|
|
515
|
+
Option.none:
|
|
516
|
+
var invalid = owned
|
|
517
|
+
invalid.release()
|
|
518
|
+
return Result[string.String, Error].failure(error = error_message("tar text field must be valid UTF-8"))
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
function parse_entry(input: span[ubyte], offset: ptr_uint) -> Result[ParsedEntry, Error]:
|
|
522
|
+
var name = read_text_field(input, offset + 0, 100)?
|
|
523
|
+
defer name.release()
|
|
524
|
+
|
|
525
|
+
var prefix = read_text_field(input, offset + 345, 155)?
|
|
526
|
+
defer prefix.release()
|
|
527
|
+
|
|
528
|
+
let name_text = name.as_str()
|
|
529
|
+
if name_text.len == 0:
|
|
530
|
+
return Result[ParsedEntry, Error].failure(error = error_message("tar entry missing name"))
|
|
531
|
+
|
|
532
|
+
var full_path = string.String.create()
|
|
533
|
+
if prefix.is_empty():
|
|
534
|
+
full_path = string.String.from_str(name_text)
|
|
535
|
+
else:
|
|
536
|
+
full_path = path_ops.join(prefix.as_str(), name_text)
|
|
537
|
+
|
|
538
|
+
match parse_octal_field(input, offset + 100, 8):
|
|
539
|
+
Result.failure as payload:
|
|
540
|
+
full_path.release()
|
|
541
|
+
return Result[ParsedEntry, Error].failure(error = payload.error)
|
|
542
|
+
Result.success as mode_payload:
|
|
543
|
+
match parse_octal_field(input, offset + 124, 12):
|
|
544
|
+
Result.failure as payload:
|
|
545
|
+
full_path.release()
|
|
546
|
+
return Result[ParsedEntry, Error].failure(error = payload.error)
|
|
547
|
+
Result.success as size_payload:
|
|
548
|
+
var kind = EntryKind.file
|
|
549
|
+
unsafe:
|
|
550
|
+
let type_flag = read(input.data + offset + 156)
|
|
551
|
+
if type_flag == directory_type_flag:
|
|
552
|
+
kind = EntryKind.directory
|
|
553
|
+
else if type_flag != file_type_flag and type_flag != zero_byte:
|
|
554
|
+
full_path.release()
|
|
555
|
+
return Result[
|
|
556
|
+
ParsedEntry,
|
|
557
|
+
Error
|
|
558
|
+
].failure(error = error_message("tar entry type is not supported"))
|
|
559
|
+
|
|
560
|
+
let data_offset = offset + block_size
|
|
561
|
+
if size_payload.value > input.len - data_offset:
|
|
562
|
+
full_path.release()
|
|
563
|
+
return Result[
|
|
564
|
+
ParsedEntry,
|
|
565
|
+
Error
|
|
566
|
+
].failure(error = error_message("tar entry payload exceeds archive bounds"))
|
|
567
|
+
|
|
568
|
+
return Result[ParsedEntry, Error].success(value = ParsedEntry(
|
|
569
|
+
path = full_path,
|
|
570
|
+
kind = kind,
|
|
571
|
+
mode = int<-mode_payload.value,
|
|
572
|
+
size = size_payload.value,
|
|
573
|
+
data_offset = data_offset
|
|
574
|
+
))
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
function checked_destination_path(destination_root: str, relative_path: str) -> Result[string.String, Error]:
|
|
578
|
+
if relative_path.len == 0:
|
|
579
|
+
return Result[string.String, Error].failure(error = error_message("tar extract path cannot be empty"))
|
|
580
|
+
if path_ops.is_absolute(relative_path):
|
|
581
|
+
return Result[string.String, Error].failure(error = error_message("tar extract path must be relative"))
|
|
582
|
+
|
|
583
|
+
var destination_path = path_ops.join(destination_root, relative_path)
|
|
584
|
+
match path_ops.relative_path(destination_path.as_str(), destination_root):
|
|
585
|
+
Option.none:
|
|
586
|
+
destination_path.release()
|
|
587
|
+
return Result[
|
|
588
|
+
string.String,
|
|
589
|
+
Error
|
|
590
|
+
].failure(error = error_message("tar extract path escapes destination root"))
|
|
591
|
+
Option.some as payload:
|
|
592
|
+
var normalized = payload.value
|
|
593
|
+
defer normalized.release()
|
|
594
|
+
let normalized_path = normalized.as_str()
|
|
595
|
+
let escapes_parent = normalized_path.len == 2 and normalized_path.byte_at(0) == 46 and normalized_path.byte_at(1) == 46
|
|
596
|
+
if escapes_parent or normalized_path.starts_with("../"):
|
|
597
|
+
destination_path.release()
|
|
598
|
+
return Result[
|
|
599
|
+
string.String,
|
|
600
|
+
Error
|
|
601
|
+
].failure(error = error_message("tar extract path escapes destination root"))
|
|
602
|
+
|
|
603
|
+
return Result[string.String, Error].success(value = destination_path)
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
extending Error:
|
|
607
|
+
public editable function release() -> void:
|
|
608
|
+
this.message.release()
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
extending ParsedEntry:
|
|
612
|
+
public editable function release() -> void:
|
|
613
|
+
this.path.release()
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
public function archive_directory(
|
|
617
|
+
root_path: str,
|
|
618
|
+
archive_root_name: str,
|
|
619
|
+
include_hidden: bool
|
|
620
|
+
) -> Result[bytes.Bytes, Error]:
|
|
621
|
+
if not fs.is_directory(root_path):
|
|
622
|
+
return Result[
|
|
623
|
+
bytes.Bytes,
|
|
624
|
+
Error
|
|
625
|
+
].failure(error = error_message("tar.archive_directory requires a directory source"))
|
|
626
|
+
if archive_root_name.len != 0 and path_ops.is_absolute(archive_root_name):
|
|
627
|
+
return Result[bytes.Bytes, Error].failure(error = error_message("tar archive root name must be relative"))
|
|
628
|
+
|
|
629
|
+
var output = vec.Vec[ubyte].create()
|
|
630
|
+
match append_directory_tree(root_path, archive_root_name, include_hidden, ref_of(output)):
|
|
631
|
+
Result.failure as payload:
|
|
632
|
+
output.release()
|
|
633
|
+
return Result[bytes.Bytes, Error].failure(error = payload.error)
|
|
634
|
+
Result.success:
|
|
635
|
+
pass
|
|
636
|
+
|
|
637
|
+
append_end_blocks(ref_of(output))
|
|
638
|
+
return Result[bytes.Bytes, Error].success(value = take_vec_bytes(ref_of(output)))
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
public function archive_directory_gzip(
|
|
642
|
+
root_path: str,
|
|
643
|
+
archive_root_name: str,
|
|
644
|
+
include_hidden: bool
|
|
645
|
+
) -> Result[bytes.Bytes, Error]:
|
|
646
|
+
match archive_directory(root_path, archive_root_name, include_hidden):
|
|
647
|
+
Result.failure as payload:
|
|
648
|
+
return Result[bytes.Bytes, Error].failure(error = payload.error)
|
|
649
|
+
Result.success as payload:
|
|
650
|
+
var archive = payload.value
|
|
651
|
+
defer archive.release()
|
|
652
|
+
match gzip.compress_bytes(archive.as_span()):
|
|
653
|
+
Result.failure as gzip_payload:
|
|
654
|
+
return Result[bytes.Bytes, Error].failure(error = take_gzip_error(gzip_payload.error))
|
|
655
|
+
Result.success as gzip_payload:
|
|
656
|
+
return Result[bytes.Bytes, Error].success(value = gzip_payload.value)
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
public function extract(archive: span[ubyte], destination_root: str) -> Result[bool, Error]:
|
|
660
|
+
if archive.len % block_size != 0:
|
|
661
|
+
return Result[bool, Error].failure(error = error_message("tar archive size must be a multiple of 512 bytes"))
|
|
662
|
+
|
|
663
|
+
fs.create_directories(destination_root).map_error(take_fs_error)?
|
|
664
|
+
|
|
665
|
+
var offset: ptr_uint = 0
|
|
666
|
+
while offset < archive.len:
|
|
667
|
+
if block_is_zero(archive, offset):
|
|
668
|
+
return Result[bool, Error].success(value = true)
|
|
669
|
+
|
|
670
|
+
var entry = parse_entry(archive, offset)?
|
|
671
|
+
defer entry.release()
|
|
672
|
+
|
|
673
|
+
var destination_path = checked_destination_path(destination_root, entry.path.as_str())?
|
|
674
|
+
defer destination_path.release()
|
|
675
|
+
|
|
676
|
+
if entry.kind == EntryKind.directory:
|
|
677
|
+
fs.create_directories(destination_path.as_str()).map_error(take_fs_error)?
|
|
678
|
+
|
|
679
|
+
fs.set_permissions(destination_path.as_str(), entry.mode).map_error(take_fs_error)?
|
|
680
|
+
else:
|
|
681
|
+
let parent = path_ops.dirname(destination_path.as_str())
|
|
682
|
+
fs.create_directories(parent).map_error(take_fs_error)?
|
|
683
|
+
|
|
684
|
+
let file_data = unsafe: span[ubyte](
|
|
685
|
+
data = archive.data + entry.data_offset,
|
|
686
|
+
len = entry.size
|
|
687
|
+
)
|
|
688
|
+
fs.write_bytes(destination_path.as_str(), file_data).map_error(take_fs_error)?
|
|
689
|
+
|
|
690
|
+
fs.set_permissions(destination_path.as_str(), entry.mode).map_error(take_fs_error)?
|
|
691
|
+
|
|
692
|
+
let entry_payload_size = padded_size(entry.size)
|
|
693
|
+
if offset > heap.ptr_uint_max - block_size - entry_payload_size:
|
|
694
|
+
return Result[bool, Error].failure(error = error_message("tar archive offset overflow"))
|
|
695
|
+
|
|
696
|
+
offset += block_size + entry_payload_size
|
|
697
|
+
|
|
698
|
+
return Result[bool, Error].success(value = true)
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
public function extract_gzip(archive: span[ubyte], destination_root: str) -> Result[bool, Error]:
|
|
702
|
+
var decoded = gzip.decompress_bytes(archive).map_error(take_gzip_error)?
|
|
703
|
+
defer decoded.release()
|
|
704
|
+
return extract(decoded.as_span(), destination_root)
|