v / vlib / os
Raw file | 882 loc (822 sloc) | 20.66 KB | Latest commit hash ef5be22f8
1module os
2
3/// Eof error means that we reach the end of the file.
4pub struct Eof {
5 Error
6}
7
8// NotExpected is a generic error that means that we receave a not expecte error.
9pub struct NotExpected {
10 cause string
11 code int
12}
13
14fn (err NotExpected) msg() string {
15 return err.cause
16}
17
18fn (err NotExpected) code() int {
19 return err.code
20}
21
22pub struct File {
23mut:
24 cfile voidptr // Using void* instead of FILE*
25pub:
26 fd int
27pub mut:
28 is_opened bool
29}
30
31struct FileInfo {
32 name string
33 size int
34}
35
36fn C.fseeko(&C.FILE, u64, int) int
37
38fn C._fseeki64(&C.FILE, u64, int) int
39
40fn C.getc(&C.FILE) int
41
42fn C.freopen(&char, &char, &C.FILE) &C.FILE
43
44fn C._wfreopen(&u16, &u16, &C.FILE) &C.FILE
45
46fn 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.
55pub 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.
133pub 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.
158pub 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
192pub 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
201pub 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
210pub 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
219pub 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.
225pub 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.
240pub 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.
264pub 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.
285pub 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.
312pub 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.
320pub 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]
359pub 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]
366pub 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]
392pub 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.
418fn 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).
443pub 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.
448pub 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.
460pub 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.
499pub 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.
533pub 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.
557pub 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.
563pub fn (mut f File) flush() {
564 if !f.is_opened {
565 return
566 }
567 C.fflush(f.cfile)
568}
569
570pub struct FileNotOpenedError {
571 Error
572}
573
574pub fn (err FileNotOpenedError) msg() string {
575 return 'os: file not opened'
576}
577
578pub struct SizeOfTypeIs0Error {
579 Error
580}
581
582pub fn (err SizeOfTypeIs0Error) msg() string {
583 return 'os: size of type is 0'
584}
585
586fn error_file_not_opened() IError {
587 return &FileNotOpenedError{}
588}
589
590fn error_size_of_type_0() IError {
591 return &SizeOfTypeIs0Error{}
592}
593
594// read_struct reads a single struct of type `T`
595pub 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
610pub 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`
641pub 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`
658pub 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`
704pub 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
723pub 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`
760pub 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`
779pub 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
832pub 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.
848pub 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.
873pub 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}