1 | module os |
2 | |
3 | /// Eof error means that we reach the end of the file. |
4 | pub struct Eof { |
5 | Error |
6 | } |
7 | |
8 | // NotExpected is a generic error that means that we receave a not expecte error. |
9 | pub struct NotExpected { |
10 | cause string |
11 | code int |
12 | } |
13 | |
14 | fn (err NotExpected) msg() string { |
15 | return err.cause |
16 | } |
17 | |
18 | fn (err NotExpected) code() int { |
19 | return err.code |
20 | } |
21 | |
22 | pub struct File { |
23 | mut: |
24 | cfile voidptr // Using void* instead of FILE* |
25 | pub: |
26 | fd int |
27 | pub mut: |
28 | is_opened bool |
29 | } |
30 | |
31 | struct FileInfo { |
32 | name string |
33 | size int |
34 | } |
35 | |
36 | fn C.fseeko(&C.FILE, u64, int) int |
37 | |
38 | fn C._fseeki64(&C.FILE, u64, int) int |
39 | |
40 | fn C.getc(&C.FILE) int |
41 | |
42 | fn C.freopen(&char, &char, &C.FILE) &C.FILE |
43 | |
44 | fn C._wfreopen(&u16, &u16, &C.FILE) &C.FILE |
45 | |
46 | fn fix_windows_path(path string) string { |
47 | mut p := path |
48 | $if windows { |
49 | p = path.replace('/', '\\') |
50 | } |
51 | return p |
52 | } |
53 | |
54 | // open_file tries to open or create a file with custom flags and permissions. |
55 | pub fn open_file(path string, mode string, options ...int) !File { |
56 | mut flags := 0 |
57 | mut seek_to_end := false |
58 | for m in mode { |
59 | match m { |
60 | `w` { |
61 | flags |= o_create | o_trunc | o_wronly |
62 | } |
63 | `a` { |
64 | flags |= o_create | o_append | o_wronly |
65 | seek_to_end = true |
66 | } |
67 | `r` { |
68 | flags |= o_rdonly |
69 | } |
70 | `b` { |
71 | flags |= o_binary |
72 | } |
73 | `s` { |
74 | flags |= o_sync |
75 | } |
76 | `n` { |
77 | flags |= o_nonblock |
78 | } |
79 | `c` { |
80 | flags |= o_noctty |
81 | } |
82 | `+` { |
83 | flags &= ~o_wronly |
84 | flags |= o_rdwr |
85 | } |
86 | else {} |
87 | } |
88 | } |
89 | if mode == 'r+' { |
90 | flags = o_rdwr |
91 | } |
92 | mut permission := 0o666 |
93 | if options.len > 0 { |
94 | permission = options[0] |
95 | } |
96 | $if windows { |
97 | if permission < 0o600 { |
98 | permission = 0x0100 |
99 | } else { |
100 | permission = 0x0100 | 0x0080 |
101 | } |
102 | } |
103 | p := fix_windows_path(path) |
104 | fd := $if windows { |
105 | C._wopen(p.to_wide(), flags, permission) |
106 | } $else { |
107 | C.open(&char(p.str), flags, permission) |
108 | } |
109 | if fd == -1 { |
110 | return error(posix_get_error_msg(C.errno)) |
111 | } |
112 | fdopen_mode := mode.replace('b', '') |
113 | cfile := C.fdopen(fd, &char(fdopen_mode.str)) |
114 | if isnil(cfile) { |
115 | return error('Failed to open or create file "${path}"') |
116 | } |
117 | if seek_to_end { |
118 | // ensure appending will work, even on bsd/macos systems: |
119 | $if windows { |
120 | C._fseeki64(cfile, 0, C.SEEK_END) |
121 | } $else { |
122 | C.fseeko(cfile, 0, C.SEEK_END) |
123 | } |
124 | } |
125 | return File{ |
126 | cfile: cfile |
127 | fd: fd |
128 | is_opened: true |
129 | } |
130 | } |
131 | |
132 | // open tries to open a file from a given path for reading. |
133 | pub fn open(path string) !File { |
134 | /* |
135 | $if linux { |
136 | $if !android { |
137 | fd := C.syscall(sys_open, path.str, 511) |
138 | if fd == -1 { |
139 | return error('failed to open file "$path"') |
140 | } |
141 | return File{ |
142 | fd: fd |
143 | is_opened: true |
144 | } |
145 | } |
146 | } |
147 | */ |
148 | cfile := vfopen(path, 'rb')! |
149 | fd := fileno(cfile) |
150 | return File{ |
151 | cfile: cfile |
152 | fd: fd |
153 | is_opened: true |
154 | } |
155 | } |
156 | |
157 | // create creates or opens a file at a specified location and returns a write-only `File` object. |
158 | pub fn create(path string) !File { |
159 | /* |
160 | // Note: android/termux/bionic is also a kind of linux, |
161 | // but linux syscalls there sometimes fail, |
162 | // while the libc version should work. |
163 | $if linux { |
164 | $if !android { |
165 | //$if macos { |
166 | // fd = C.syscall(398, path.str, 0x601, 0x1b6) |
167 | //} |
168 | //$if linux { |
169 | fd = C.syscall(sys_creat, path.str, 511) |
170 | //} |
171 | if fd == -1 { |
172 | return error('failed to create file "$path"') |
173 | } |
174 | file = File{ |
175 | fd: fd |
176 | is_opened: true |
177 | } |
178 | return file |
179 | } |
180 | } |
181 | */ |
182 | cfile := vfopen(path, 'wb')! |
183 | fd := fileno(cfile) |
184 | return File{ |
185 | cfile: cfile |
186 | fd: fd |
187 | is_opened: true |
188 | } |
189 | } |
190 | |
191 | // stdin - return an os.File for stdin |
192 | pub fn stdin() File { |
193 | return File{ |
194 | fd: 0 |
195 | cfile: C.stdin |
196 | is_opened: true |
197 | } |
198 | } |
199 | |
200 | // stdout - return an os.File for stdout |
201 | pub fn stdout() File { |
202 | return File{ |
203 | fd: 1 |
204 | cfile: C.stdout |
205 | is_opened: true |
206 | } |
207 | } |
208 | |
209 | // stderr - return an os.File for stderr |
210 | pub fn stderr() File { |
211 | return File{ |
212 | fd: 2 |
213 | cfile: C.stderr |
214 | is_opened: true |
215 | } |
216 | } |
217 | |
218 | // eof returns true, when the end of file has been reached |
219 | pub fn (f &File) eof() bool { |
220 | cfile := &C.FILE(f.cfile) |
221 | return C.feof(cfile) != 0 |
222 | } |
223 | |
224 | // reopen allows a `File` to be reused. It is mostly useful for reopening standard input and output. |
225 | pub fn (mut f File) reopen(path string, mode string) ! { |
226 | p := fix_windows_path(path) |
227 | mut cfile := &C.FILE(0) |
228 | $if windows { |
229 | cfile = C._wfreopen(p.to_wide(), mode.to_wide(), f.cfile) |
230 | } $else { |
231 | cfile = C.freopen(&char(p.str), &char(mode.str), f.cfile) |
232 | } |
233 | if isnil(cfile) { |
234 | return error('Failed to reopen file "${path}"') |
235 | } |
236 | f.cfile = cfile |
237 | } |
238 | |
239 | // read implements the Reader interface. |
240 | pub fn (f &File) read(mut buf []u8) !int { |
241 | if buf.len == 0 { |
242 | return Eof{} |
243 | } |
244 | // the following is needed, because on FreeBSD, C.feof is a macro: |
245 | nbytes := int(C.fread(buf.data, 1, buf.len, &C.FILE(f.cfile))) |
246 | // if no bytes were read, check for errors and end-of-file. |
247 | if nbytes <= 0 { |
248 | if C.feof(&C.FILE(f.cfile)) != 0 { |
249 | return Eof{} |
250 | } |
251 | if C.ferror(&C.FILE(f.cfile)) != 0 { |
252 | return NotExpected{ |
253 | cause: 'unexpected error from fread' |
254 | code: -1 |
255 | } |
256 | } |
257 | } |
258 | return nbytes |
259 | } |
260 | |
261 | // **************************** Write ops *************************** |
262 | // write implements the Writer interface. |
263 | // It returns how many bytes were actually written. |
264 | pub fn (mut f File) write(buf []u8) !int { |
265 | if !f.is_opened { |
266 | return error_file_not_opened() |
267 | } |
268 | /* |
269 | $if linux { |
270 | $if !android { |
271 | res := C.syscall(sys_write, f.fd, s.str, s.len) |
272 | return res |
273 | } |
274 | } |
275 | */ |
276 | written := int(C.fwrite(buf.data, 1, buf.len, f.cfile)) |
277 | if written == 0 && buf.len != 0 { |
278 | return error('0 bytes written') |
279 | } |
280 | return written |
281 | } |
282 | |
283 | // writeln writes the string `s` into the file, and appends a \n character. |
284 | // It returns how many bytes were written, including the \n character. |
285 | pub fn (mut f File) writeln(s string) !int { |
286 | if !f.is_opened { |
287 | return error_file_not_opened() |
288 | } |
289 | /* |
290 | $if linux { |
291 | $if !android { |
292 | snl := s + '\n' |
293 | C.syscall(sys_write, f.fd, snl.str, snl.len) |
294 | return |
295 | } |
296 | } |
297 | */ |
298 | // TODO perf |
299 | written := int(C.fwrite(s.str, 1, s.len, f.cfile)) |
300 | if written == 0 && s.len != 0 { |
301 | return error('0 bytes written') |
302 | } |
303 | x := C.fputs(c'\n', f.cfile) |
304 | if x < 0 { |
305 | return error('could not add newline') |
306 | } |
307 | return written + 1 |
308 | } |
309 | |
310 | // write_string writes the string `s` into the file |
311 | // It returns how many bytes were actually written. |
312 | pub fn (mut f File) write_string(s string) !int { |
313 | unsafe { f.write_full_buffer(s.str, usize(s.len))! } |
314 | return s.len |
315 | } |
316 | |
317 | // write_to implements the RandomWriter interface. |
318 | // It returns how many bytes were actually written. |
319 | // It resets the seek position to the end of the file. |
320 | pub fn (mut f File) write_to(pos u64, buf []u8) !int { |
321 | if !f.is_opened { |
322 | return error_file_not_opened() |
323 | } |
324 | $if x64 { |
325 | $if windows { |
326 | C._fseeki64(f.cfile, pos, C.SEEK_SET) |
327 | res := int(C.fwrite(buf.data, 1, buf.len, f.cfile)) |
328 | if res == 0 && buf.len != 0 { |
329 | return error('0 bytes written') |
330 | } |
331 | C._fseeki64(f.cfile, 0, C.SEEK_END) |
332 | return res |
333 | } $else { |
334 | C.fseeko(f.cfile, pos, C.SEEK_SET) |
335 | res := int(C.fwrite(buf.data, 1, buf.len, f.cfile)) |
336 | if res == 0 && buf.len != 0 { |
337 | return error('0 bytes written') |
338 | } |
339 | C.fseeko(f.cfile, 0, C.SEEK_END) |
340 | return res |
341 | } |
342 | } |
343 | $if x32 { |
344 | C.fseek(f.cfile, pos, C.SEEK_SET) |
345 | res := int(C.fwrite(buf.data, 1, buf.len, f.cfile)) |
346 | if res == 0 && buf.len != 0 { |
347 | return error('0 bytes written') |
348 | } |
349 | C.fseek(f.cfile, 0, C.SEEK_END) |
350 | return res |
351 | } |
352 | return error('Could not write to file') |
353 | } |
354 | |
355 | // write_ptr writes `size` bytes to the file, starting from the address in `data`. |
356 | // Note: write_ptr is unsafe and should be used carefully, since if you pass invalid |
357 | // pointers to it, it will cause your programs to segfault. |
358 | [unsafe] |
359 | pub fn (mut f File) write_ptr(data voidptr, size int) int { |
360 | return int(C.fwrite(data, 1, size, f.cfile)) |
361 | } |
362 | |
363 | // write_full_buffer writes a whole buffer of data to the file, starting from the |
364 | // address in `buffer`, no matter how many tries/partial writes it would take. |
365 | [unsafe] |
366 | pub fn (mut f File) write_full_buffer(buffer voidptr, buffer_len usize) ! { |
367 | if buffer_len <= usize(0) { |
368 | return |
369 | } |
370 | if !f.is_opened { |
371 | return error_file_not_opened() |
372 | } |
373 | mut ptr := &u8(buffer) |
374 | mut remaining_bytes := i64(buffer_len) |
375 | for remaining_bytes > 0 { |
376 | unsafe { |
377 | x := i64(C.fwrite(ptr, 1, remaining_bytes, f.cfile)) |
378 | ptr += x |
379 | remaining_bytes -= x |
380 | if x <= 0 { |
381 | return error('C.fwrite returned 0') |
382 | } |
383 | } |
384 | } |
385 | } |
386 | |
387 | // write_ptr_at writes `size` bytes to the file, starting from the address in `data`, |
388 | // at byte offset `pos`, counting from the start of the file (pos 0). |
389 | // Note: write_ptr_at is unsafe and should be used carefully, since if you pass invalid |
390 | // pointers to it, it will cause your programs to segfault. |
391 | [unsafe] |
392 | pub fn (mut f File) write_ptr_at(data voidptr, size int, pos u64) int { |
393 | $if x64 { |
394 | $if windows { |
395 | C._fseeki64(f.cfile, pos, C.SEEK_SET) |
396 | res := int(C.fwrite(data, 1, size, f.cfile)) |
397 | C._fseeki64(f.cfile, 0, C.SEEK_END) |
398 | return res |
399 | } $else { |
400 | C.fseeko(f.cfile, pos, C.SEEK_SET) |
401 | res := int(C.fwrite(data, 1, size, f.cfile)) |
402 | C.fseeko(f.cfile, 0, C.SEEK_END) |
403 | return res |
404 | } |
405 | } |
406 | $if x32 { |
407 | C.fseek(f.cfile, pos, C.SEEK_SET) |
408 | res := int(C.fwrite(data, 1, size, f.cfile)) |
409 | C.fseek(f.cfile, 0, C.SEEK_END) |
410 | return res |
411 | } |
412 | return 0 |
413 | } |
414 | |
415 | // **************************** Read ops *************************** |
416 | |
417 | // fread wraps C.fread and handles error and end-of-file detection. |
418 | fn fread(ptr voidptr, item_size int, items int, stream &C.FILE) !int { |
419 | nbytes := int(C.fread(ptr, item_size, items, stream)) |
420 | // If no bytes were read, check for errors and end-of-file. |
421 | if nbytes <= 0 { |
422 | // If fread encountered end-of-file return the none error. Note that fread |
423 | // may read data and encounter the end-of-file, but we shouldn't return none |
424 | // in that case which is why we only check for end-of-file if no data was |
425 | // read. The caller will get none on their next call because there will be |
426 | // no data available and the end-of-file will be encountered again. |
427 | if C.feof(stream) != 0 { |
428 | return Eof{} |
429 | } |
430 | // If fread encountered an error, return it. Note that fread and ferror do |
431 | // not tell us what the error was, so we can't return anything more specific |
432 | // than there was an error. This is because fread and ferror do not set |
433 | // errno. |
434 | if C.ferror(stream) != 0 { |
435 | return error('file read error') |
436 | } |
437 | } |
438 | return nbytes |
439 | } |
440 | |
441 | // read_bytes reads bytes from the beginning of the file. |
442 | // Utility method, same as .read_bytes_at(size, 0). |
443 | pub fn (f &File) read_bytes(size int) []u8 { |
444 | return f.read_bytes_at(size, 0) |
445 | } |
446 | |
447 | // read_bytes_at reads `size` bytes at the given position in the file. |
448 | pub fn (f &File) read_bytes_at(size int, pos u64) []u8 { |
449 | mut arr := []u8{len: size} |
450 | nreadbytes := f.read_bytes_into(pos, mut arr) or { |
451 | // return err |
452 | return [] |
453 | } |
454 | return arr[0..nreadbytes] |
455 | } |
456 | |
457 | // read_bytes_into_newline reads from the beginning of the file into the provided buffer. |
458 | // Each consecutive call on the same file continues reading where it previously ended. |
459 | // A read call is either stopped, if the buffer is full, a newline was read or EOF. |
460 | pub fn (f &File) read_bytes_into_newline(mut buf []u8) !int { |
461 | if buf.len == 0 { |
462 | return error(@FN + ': `buf.len` == 0') |
463 | } |
464 | newline := 10 |
465 | mut c := 0 |
466 | mut buf_ptr := 0 |
467 | mut nbytes := 0 |
468 | |
469 | stream := &C.FILE(f.cfile) |
470 | for (buf_ptr < buf.len) { |
471 | c = C.getc(stream) |
472 | match c { |
473 | C.EOF { |
474 | if C.feof(stream) != 0 { |
475 | return nbytes |
476 | } |
477 | if C.ferror(stream) != 0 { |
478 | return error('file read error') |
479 | } |
480 | } |
481 | newline { |
482 | buf[buf_ptr] = u8(c) |
483 | nbytes++ |
484 | return nbytes |
485 | } |
486 | else { |
487 | buf[buf_ptr] = u8(c) |
488 | buf_ptr++ |
489 | nbytes++ |
490 | } |
491 | } |
492 | } |
493 | return nbytes |
494 | } |
495 | |
496 | // read_bytes_into fills `buf` with bytes at the given position in the file. |
497 | // `buf` *must* have length greater than zero. |
498 | // Returns the number of read bytes, or an error. |
499 | pub fn (f &File) read_bytes_into(pos u64, mut buf []u8) !int { |
500 | if buf.len == 0 { |
501 | return error(@FN + ': `buf.len` == 0') |
502 | } |
503 | $if x64 { |
504 | $if windows { |
505 | // Note: fseek errors if pos == os.file_size, which we accept |
506 | C._fseeki64(f.cfile, pos, C.SEEK_SET) |
507 | nbytes := fread(buf.data, 1, buf.len, f.cfile)! |
508 | $if debug { |
509 | C._fseeki64(f.cfile, 0, C.SEEK_SET) |
510 | } |
511 | return nbytes |
512 | } $else { |
513 | C.fseeko(f.cfile, pos, C.SEEK_SET) |
514 | nbytes := fread(buf.data, 1, buf.len, f.cfile)! |
515 | $if debug { |
516 | C.fseeko(f.cfile, 0, C.SEEK_SET) |
517 | } |
518 | return nbytes |
519 | } |
520 | } |
521 | $if x32 { |
522 | C.fseek(f.cfile, pos, C.SEEK_SET) |
523 | nbytes := fread(buf.data, 1, buf.len, f.cfile)! |
524 | $if debug { |
525 | C.fseek(f.cfile, 0, C.SEEK_SET) |
526 | } |
527 | return nbytes |
528 | } |
529 | return error('Could not read file') |
530 | } |
531 | |
532 | // read_from implements the RandomReader interface. |
533 | pub fn (f &File) read_from(pos u64, mut buf []u8) !int { |
534 | if buf.len == 0 { |
535 | return 0 |
536 | } |
537 | $if x64 { |
538 | $if windows { |
539 | C._fseeki64(f.cfile, pos, C.SEEK_SET) |
540 | } $else { |
541 | C.fseeko(f.cfile, pos, C.SEEK_SET) |
542 | } |
543 | |
544 | nbytes := fread(buf.data, 1, buf.len, f.cfile)! |
545 | return nbytes |
546 | } |
547 | $if x32 { |
548 | C.fseek(f.cfile, pos, C.SEEK_SET) |
549 | nbytes := fread(buf.data, 1, buf.len, f.cfile)! |
550 | return nbytes |
551 | } |
552 | return error('Could not read file') |
553 | } |
554 | |
555 | // read_into_ptr reads at most max_size bytes from the file and writes it into ptr. |
556 | // Returns the amount of bytes read or an error. |
557 | pub fn (f &File) read_into_ptr(ptr &u8, max_size int) !int { |
558 | return fread(ptr, 1, max_size, f.cfile) |
559 | } |
560 | |
561 | // **************************** Utility ops *********************** |
562 | // flush writes any buffered unwritten data left in the file stream. |
563 | pub fn (mut f File) flush() { |
564 | if !f.is_opened { |
565 | return |
566 | } |
567 | C.fflush(f.cfile) |
568 | } |
569 | |
570 | pub struct FileNotOpenedError { |
571 | Error |
572 | } |
573 | |
574 | pub fn (err FileNotOpenedError) msg() string { |
575 | return 'os: file not opened' |
576 | } |
577 | |
578 | pub struct SizeOfTypeIs0Error { |
579 | Error |
580 | } |
581 | |
582 | pub fn (err SizeOfTypeIs0Error) msg() string { |
583 | return 'os: size of type is 0' |
584 | } |
585 | |
586 | fn error_file_not_opened() IError { |
587 | return &FileNotOpenedError{} |
588 | } |
589 | |
590 | fn error_size_of_type_0() IError { |
591 | return &SizeOfTypeIs0Error{} |
592 | } |
593 | |
594 | // read_struct reads a single struct of type `T` |
595 | pub fn (mut f File) read_struct[T](mut t T) ! { |
596 | if !f.is_opened { |
597 | return error_file_not_opened() |
598 | } |
599 | tsize := int(sizeof(*t)) |
600 | if tsize == 0 { |
601 | return error_size_of_type_0() |
602 | } |
603 | nbytes := fread(t, 1, tsize, f.cfile)! |
604 | if nbytes != tsize { |
605 | return error_with_code('incomplete struct read', nbytes) |
606 | } |
607 | } |
608 | |
609 | // read_struct_at reads a single struct of type `T` at position specified in file |
610 | pub fn (mut f File) read_struct_at[T](mut t T, pos u64) ! { |
611 | if !f.is_opened { |
612 | return error_file_not_opened() |
613 | } |
614 | tsize := int(sizeof(*t)) |
615 | if tsize == 0 { |
616 | return error_size_of_type_0() |
617 | } |
618 | mut nbytes := 0 |
619 | $if x64 { |
620 | $if windows { |
621 | C._fseeki64(f.cfile, pos, C.SEEK_SET) |
622 | nbytes = fread(t, 1, tsize, f.cfile)! |
623 | C._fseeki64(f.cfile, 0, C.SEEK_END) |
624 | } $else { |
625 | C.fseeko(f.cfile, pos, C.SEEK_SET) |
626 | nbytes = fread(t, 1, tsize, f.cfile)! |
627 | C.fseeko(f.cfile, 0, C.SEEK_END) |
628 | } |
629 | } |
630 | $if x32 { |
631 | C.fseek(f.cfile, pos, C.SEEK_SET) |
632 | nbytes = fread(t, 1, tsize, f.cfile)! |
633 | C.fseek(f.cfile, 0, C.SEEK_END) |
634 | } |
635 | if nbytes != tsize { |
636 | return error_with_code('incomplete struct read', nbytes) |
637 | } |
638 | } |
639 | |
640 | // read_raw reads and returns a single instance of type `T` |
641 | pub fn (mut f File) read_raw[T]() !T { |
642 | if !f.is_opened { |
643 | return error_file_not_opened() |
644 | } |
645 | tsize := int(sizeof(T)) |
646 | if tsize == 0 { |
647 | return error_size_of_type_0() |
648 | } |
649 | mut t := T{} |
650 | nbytes := fread(&t, 1, tsize, f.cfile)! |
651 | if nbytes != tsize { |
652 | return error_with_code('incomplete struct read', nbytes) |
653 | } |
654 | return t |
655 | } |
656 | |
657 | // read_raw_at reads and returns a single instance of type `T` starting at file byte offset `pos` |
658 | pub fn (mut f File) read_raw_at[T](pos u64) !T { |
659 | if !f.is_opened { |
660 | return error_file_not_opened() |
661 | } |
662 | tsize := int(sizeof(T)) |
663 | if tsize == 0 { |
664 | return error_size_of_type_0() |
665 | } |
666 | mut nbytes := 0 |
667 | mut t := T{} |
668 | $if x64 { |
669 | $if windows { |
670 | if C._fseeki64(f.cfile, pos, C.SEEK_SET) != 0 { |
671 | return error(posix_get_error_msg(C.errno)) |
672 | } |
673 | nbytes = fread(&t, 1, tsize, f.cfile)! |
674 | if C._fseeki64(f.cfile, 0, C.SEEK_END) != 0 { |
675 | return error(posix_get_error_msg(C.errno)) |
676 | } |
677 | } $else { |
678 | if C.fseeko(f.cfile, pos, C.SEEK_SET) != 0 { |
679 | return error(posix_get_error_msg(C.errno)) |
680 | } |
681 | nbytes = fread(&t, 1, tsize, f.cfile)! |
682 | if C.fseeko(f.cfile, 0, C.SEEK_END) != 0 { |
683 | return error(posix_get_error_msg(C.errno)) |
684 | } |
685 | } |
686 | } |
687 | $if x32 { |
688 | if C.fseek(f.cfile, pos, C.SEEK_SET) != 0 { |
689 | return error(posix_get_error_msg(C.errno)) |
690 | } |
691 | nbytes = fread(&t, 1, tsize, f.cfile)! |
692 | if C.fseek(f.cfile, 0, C.SEEK_END) != 0 { |
693 | return error(posix_get_error_msg(C.errno)) |
694 | } |
695 | } |
696 | |
697 | if nbytes != tsize { |
698 | return error_with_code('incomplete struct read', nbytes) |
699 | } |
700 | return t |
701 | } |
702 | |
703 | // write_struct writes a single struct of type `T` |
704 | pub fn (mut f File) write_struct[T](t &T) ! { |
705 | if !f.is_opened { |
706 | return error_file_not_opened() |
707 | } |
708 | tsize := int(sizeof(T)) |
709 | if tsize == 0 { |
710 | return error_size_of_type_0() |
711 | } |
712 | C.errno = 0 |
713 | nbytes := int(C.fwrite(t, 1, tsize, f.cfile)) |
714 | if C.errno != 0 { |
715 | return error(posix_get_error_msg(C.errno)) |
716 | } |
717 | if nbytes != tsize { |
718 | return error_with_code('incomplete struct write', nbytes) |
719 | } |
720 | } |
721 | |
722 | // write_struct_at writes a single struct of type `T` at position specified in file |
723 | pub fn (mut f File) write_struct_at[T](t &T, pos u64) ! { |
724 | if !f.is_opened { |
725 | return error_file_not_opened() |
726 | } |
727 | tsize := int(sizeof(T)) |
728 | if tsize == 0 { |
729 | return error_size_of_type_0() |
730 | } |
731 | C.errno = 0 |
732 | mut nbytes := 0 |
733 | $if x64 { |
734 | $if windows { |
735 | C._fseeki64(f.cfile, pos, C.SEEK_SET) |
736 | nbytes = int(C.fwrite(t, 1, tsize, f.cfile)) |
737 | C._fseeki64(f.cfile, 0, C.SEEK_END) |
738 | } $else { |
739 | C.fseeko(f.cfile, pos, C.SEEK_SET) |
740 | nbytes = int(C.fwrite(t, 1, tsize, f.cfile)) |
741 | C.fseeko(f.cfile, 0, C.SEEK_END) |
742 | } |
743 | } |
744 | $if x32 { |
745 | C.fseek(f.cfile, pos, C.SEEK_SET) |
746 | nbytes = int(C.fwrite(t, 1, tsize, f.cfile)) |
747 | C.fseek(f.cfile, 0, C.SEEK_END) |
748 | } |
749 | if C.errno != 0 { |
750 | return error(posix_get_error_msg(C.errno)) |
751 | } |
752 | if nbytes != tsize { |
753 | return error_with_code('incomplete struct write', nbytes) |
754 | } |
755 | } |
756 | |
757 | // TODO `write_raw[_at]` implementations are copy-pasted from `write_struct[_at]` |
758 | |
759 | // write_raw writes a single instance of type `T` |
760 | pub fn (mut f File) write_raw[T](t &T) ! { |
761 | if !f.is_opened { |
762 | return error_file_not_opened() |
763 | } |
764 | tsize := int(sizeof(T)) |
765 | if tsize == 0 { |
766 | return error_size_of_type_0() |
767 | } |
768 | C.errno = 0 |
769 | nbytes := int(C.fwrite(t, 1, tsize, f.cfile)) |
770 | if C.errno != 0 { |
771 | return error(posix_get_error_msg(C.errno)) |
772 | } |
773 | if nbytes != tsize { |
774 | return error_with_code('incomplete struct write', nbytes) |
775 | } |
776 | } |
777 | |
778 | // write_raw_at writes a single instance of type `T` starting at file byte offset `pos` |
779 | pub fn (mut f File) write_raw_at[T](t &T, pos u64) ! { |
780 | if !f.is_opened { |
781 | return error_file_not_opened() |
782 | } |
783 | tsize := int(sizeof(T)) |
784 | if tsize == 0 { |
785 | return error_size_of_type_0() |
786 | } |
787 | mut nbytes := 0 |
788 | |
789 | $if x64 { |
790 | $if windows { |
791 | if C._fseeki64(f.cfile, pos, C.SEEK_SET) != 0 { |
792 | return error(posix_get_error_msg(C.errno)) |
793 | } |
794 | nbytes = int(C.fwrite(t, 1, tsize, f.cfile)) |
795 | if C.errno != 0 { |
796 | return error(posix_get_error_msg(C.errno)) |
797 | } |
798 | if C._fseeki64(f.cfile, 0, C.SEEK_END) != 0 { |
799 | return error(posix_get_error_msg(C.errno)) |
800 | } |
801 | } $else { |
802 | if C.fseeko(f.cfile, pos, C.SEEK_SET) != 0 { |
803 | return error(posix_get_error_msg(C.errno)) |
804 | } |
805 | nbytes = int(C.fwrite(t, 1, tsize, f.cfile)) |
806 | if C.errno != 0 { |
807 | return error(posix_get_error_msg(C.errno)) |
808 | } |
809 | if C.fseeko(f.cfile, 0, C.SEEK_END) != 0 { |
810 | return error(posix_get_error_msg(C.errno)) |
811 | } |
812 | } |
813 | } |
814 | $if x32 { |
815 | if C.fseek(f.cfile, pos, C.SEEK_SET) != 0 { |
816 | return error(posix_get_error_msg(C.errno)) |
817 | } |
818 | nbytes = int(C.fwrite(t, 1, tsize, f.cfile)) |
819 | if C.errno != 0 { |
820 | return error(posix_get_error_msg(C.errno)) |
821 | } |
822 | if C.fseek(f.cfile, 0, C.SEEK_END) != 0 { |
823 | return error(posix_get_error_msg(C.errno)) |
824 | } |
825 | } |
826 | |
827 | if nbytes != tsize { |
828 | return error_with_code('incomplete struct write', nbytes) |
829 | } |
830 | } |
831 | |
832 | pub enum SeekMode { |
833 | start |
834 | current |
835 | end |
836 | } |
837 | |
838 | // seek moves the file cursor (if any) associated with a file |
839 | // to a new location, offset `pos` bytes from the origin. The origin |
840 | // is dependent on the `mode` and can be: |
841 | // .start -> the origin is the start of the file |
842 | // .current -> the current position/cursor in the file |
843 | // .end -> the end of the file |
844 | // If the file is not seek-able, or an error occures, the error will |
845 | // be returned to the caller. |
846 | // A successful call to the fseek() function clears the end-of-file |
847 | // indicator for the file. |
848 | pub fn (mut f File) seek(pos i64, mode SeekMode) ! { |
849 | if !f.is_opened { |
850 | return error_file_not_opened() |
851 | } |
852 | whence := int(mode) |
853 | mut res := 0 |
854 | $if x64 { |
855 | $if windows { |
856 | res = C._fseeki64(f.cfile, pos, whence) |
857 | } $else { |
858 | res = C.fseeko(f.cfile, pos, whence) |
859 | } |
860 | } |
861 | $if x32 { |
862 | res = C.fseek(f.cfile, pos, whence) |
863 | } |
864 | if res == -1 { |
865 | return error(posix_get_error_msg(C.errno)) |
866 | } |
867 | } |
868 | |
869 | // tell will return the current offset of the file cursor measured from |
870 | // the start of the file, in bytes. It is complementary to seek, i.e. |
871 | // you can use the return value as the `pos` parameter to .seek( pos, .start ), |
872 | // so that your next read will happen from the same place. |
873 | pub fn (f &File) tell() !i64 { |
874 | if !f.is_opened { |
875 | return error_file_not_opened() |
876 | } |
877 | pos := C.ftell(f.cfile) |
878 | if pos == -1 { |
879 | return error(posix_get_error_msg(C.errno)) |
880 | } |
881 | return pos |
882 | } |