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