26f73985a103fe165f03ecc2a53b93d5592b88c6
[nit.git] / lib / core / file.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2004-2008 Jean Privat <jean@pryen.org>
4 # Copyright 2008 Floréal Morandat <morandat@lirmm.fr>
5 # Copyright 2008 Jean-Sébastien Gélinas <calestar@gmail.com>
6 #
7 # This file is free software, which comes along with NIT. This software is
8 # distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
9 # without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
10 # PARTICULAR PURPOSE. You can modify it is you want, provided this header
11 # is kept unaltered, and a notification of the changes is added.
12 # You are allowed to redistribute it and sell it, alone or is a part of
13 # another product.
14
15 # File manipulations (create, read, write, etc.)
16 module file
17
18 intrude import stream
19 intrude import text::ropes
20 import text
21 import time
22 import gc
23
24 in "C Header" `{
25 #include <dirent.h>
26 #include <string.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <unistd.h>
30 #include <stdio.h>
31 #include <errno.h>
32 #ifndef _WIN32
33 #include <poll.h>
34 #endif
35 `}
36
37 in "C" `{
38 #ifdef _WIN32
39 #include <windows.h>
40 #endif
41 `}
42
43 # `Stream` used to interact with a File or FileDescriptor
44 abstract class FileStream
45 super Stream
46 # The path of the file.
47 var path: nullable String = null
48
49 # The FILE *.
50 private var file: nullable NativeFile = null
51
52 # The status of a file. see POSIX stat(2).
53 #
54 # var f = new FileReader.open("/etc/issue")
55 # assert f.file_stat.is_file
56 #
57 # Return null in case of error
58 fun file_stat: nullable FileStat
59 do
60 var stat = _file.as(not null).file_stat
61 if stat.address_is_null then return null
62 return new FileStat(stat)
63 end
64
65 # File descriptor of this file
66 fun fd: Int do return _file.as(not null).fileno
67
68 redef fun close
69 do
70 var file = _file
71 if file == null then return
72 if file.address_is_null then
73 if last_error != null then return
74 last_error = new IOError("Cannot close unopened file")
75 return
76 end
77 var i = file.io_close
78 if i != 0 then
79 last_error = new IOError("Close failed due to error {sys.errno.strerror}")
80 end
81 _file = null
82 end
83
84 # Sets the buffering mode for the current FileStream
85 #
86 # If the buf_size is <= 0, its value will be 512 by default
87 #
88 # The mode is any of the buffer_mode enumeration in `Sys`:
89 #
90 # * `buffer_mode_full`
91 # * `buffer_mode_line`
92 # * `buffer_mode_none`
93 fun set_buffering_mode(buf_size, mode: Int) do
94 if buf_size <= 0 then buf_size = 512
95 if _file.as(not null).set_buffering_type(buf_size, mode) != 0 then
96 last_error = new IOError("Error while changing buffering type for FileStream, returned error {sys.errno.strerror}")
97 end
98 end
99 end
100
101 # `Stream` that can read from a File
102 class FileReader
103 super FileStream
104 super PollableReader
105 # Misc
106
107 # Open the same file again.
108 # The original path is reused, therefore the reopened file can be a different file.
109 #
110 # var f = new FileReader.open("/etc/issue")
111 # var l = f.read_line
112 # f.reopen
113 # assert l == f.read_line
114 fun reopen
115 do
116 var fl = _file
117 if fl != null and not fl.address_is_null then close
118 last_error = null
119 _file = new NativeFile.io_open_read(path.as(not null).to_cstring)
120 if _file.as(not null).address_is_null then
121 last_error = new IOError("Cannot open `{path.as(not null)}`: {sys.errno.strerror}")
122 return
123 end
124 end
125
126 redef fun raw_read_byte
127 do
128 var nb = _file.as(not null).io_read(write_buffer, 1)
129 if last_error == null and _file.as(not null).ferror then
130 last_error = new IOError("Cannot read `{path.as(not null)}`: {sys.errno.strerror}")
131 end
132 if nb == 0 then return -1
133 return write_buffer[0].to_i
134 end
135
136 redef fun raw_read_bytes(cstr, max)
137 do
138 var nb = _file.as(not null).io_read(cstr, max)
139 if last_error == null and _file.as(not null).ferror then
140 last_error = new IOError("Cannot read `{path.as(not null)}`: {sys.errno.strerror}")
141 end
142 return nb
143 end
144
145 redef fun eof do
146 var fl = _file
147 if fl == null then return true
148 if fl.address_is_null then return true
149 if last_error != null then return true
150 if super then
151 if last_error != null then return true
152 return fl.feof
153 end
154 return false
155 end
156
157 # Open the file at `path` for reading.
158 #
159 # var f = new FileReader.open("/etc/issue")
160 # assert not f.eof
161 # f.close
162 #
163 # In case of error, `last_error` is set
164 #
165 # f = new FileReader.open("/fail/does not/exist")
166 # assert f.eof
167 # assert f.last_error != null
168 init open(path: String)
169 do
170 self.path = path
171 _file = new NativeFile.io_open_read(path.to_cstring)
172 if _file.as(not null).address_is_null then
173 last_error = new IOError("Cannot open `{path}`: {sys.errno.strerror}")
174 end
175 end
176
177 # Creates a new File stream from a file descriptor
178 #
179 # This is a low-level method.
180 init from_fd(fd: Int) do
181 self.path = ""
182 _file = fd.fd_to_stream(read_only)
183 if _file.as(not null).address_is_null then
184 last_error = new IOError("Error: Converting fd {fd} to stream failed with '{sys.errno.strerror}'")
185 end
186 end
187
188 redef fun poll_in
189 do
190 var res = native_poll_in(fd)
191 if res == -1 then
192 last_error = new IOError(errno.to_s)
193 return false
194 else return res > 0
195 end
196
197 private fun native_poll_in(fd: Int): Int `{
198 #ifndef _WIN32
199 struct pollfd fds = {(int)fd, POLLIN, 0};
200 return poll(&fds, 1, 0);
201 #else
202 return 0;
203 #endif
204 `}
205 end
206
207 # `Stream` that can write to a File
208 class FileWriter
209 super FileStream
210 super Writer
211
212 redef fun write_bytes_from_cstring(cs, len) do
213 if last_error != null then return
214 if not _is_writable then
215 last_error = new IOError("cannot write to non-writable stream")
216 return
217 end
218 write_native(cs, 0, len)
219 end
220
221 redef fun write(s)
222 do
223 if last_error != null then return
224 if not _is_writable then
225 last_error = new IOError("cannot write to non-writable stream")
226 return
227 end
228 s.write_native_to(self)
229 end
230
231 redef fun write_byte(value)
232 do
233 if last_error != null then return
234 if not _is_writable then
235 last_error = new IOError("Cannot write to non-writable stream")
236 return
237 end
238 if _file.as(not null).address_is_null then
239 last_error = new IOError("Writing on a null stream")
240 _is_writable = false
241 return
242 end
243
244 var err = _file.as(not null).write_byte(value)
245 if err != 1 then
246 # Big problem
247 last_error = new IOError("Problem writing a byte: {err}")
248 end
249 end
250
251 redef fun close
252 do
253 super
254 _is_writable = false
255 end
256 redef var is_writable = false
257
258 # Write `len` bytes from `native`.
259 private fun write_native(native: CString, from, len: Int)
260 do
261 if last_error != null then return
262 if not _is_writable then
263 last_error = new IOError("Cannot write to non-writable stream")
264 return
265 end
266 if _file.as(not null).address_is_null then
267 last_error = new IOError("Writing on a null stream")
268 _is_writable = false
269 return
270 end
271 var err = _file.as(not null).io_write(native, from, len)
272 if err != len then
273 # Big problem
274 last_error = new IOError("Problem in writing : {err} {len} \n")
275 end
276 end
277
278 # Open the file at `path` for writing.
279 init open(path: String)
280 do
281 _file = new NativeFile.io_open_write(path.to_cstring)
282 self.path = path
283 _is_writable = true
284 if _file.as(not null).address_is_null then
285 last_error = new IOError("Cannot open `{path}`: {sys.errno.strerror}")
286 is_writable = false
287 end
288 end
289
290 # Creates a new File stream from a file descriptor
291 init from_fd(fd: Int) do
292 self.path = ""
293 _file = fd.fd_to_stream(wipe_write)
294 _is_writable = true
295 if _file.as(not null).address_is_null then
296 last_error = new IOError("Error: Opening stream from file descriptor {fd} failed with '{sys.errno.strerror}'")
297 _is_writable = false
298 end
299 end
300 end
301
302 redef class Int
303 # Creates a file stream from a file descriptor `fd` using the file access `mode`.
304 #
305 # NOTE: The `mode` specified must be compatible with the one used in the file descriptor.
306 private fun fd_to_stream(mode: CString): NativeFile `{
307 return fdopen((int)self, mode);
308 `}
309 end
310
311 # Constant for read-only file streams
312 private fun read_only: CString do return once "r".to_cstring
313
314 # Constant for write-only file streams
315 #
316 # If a stream is opened on a file with this method,
317 # it will wipe the previous file if any.
318 # Else, it will create the file.
319 private fun wipe_write: CString do return once "w".to_cstring
320
321 ###############################################################################
322
323 # Standard input stream.
324 #
325 # The class of the default value of `sys.stdin`.
326 class Stdin
327 super FileReader
328
329 init do
330 _file = new NativeFile.native_stdin
331 path = "/dev/stdin"
332 end
333 end
334
335 # Standard output stream.
336 #
337 # The class of the default value of `sys.stdout`.
338 class Stdout
339 super FileWriter
340 init do
341 _file = new NativeFile.native_stdout
342 path = "/dev/stdout"
343 _is_writable = true
344 set_buffering_mode(256, sys.buffer_mode_line)
345 end
346 end
347
348 # Standard error stream.
349 #
350 # The class of the default value of `sys.stderr`.
351 class Stderr
352 super FileWriter
353 init do
354 _file = new NativeFile.native_stderr
355 path = "/dev/stderr"
356 _is_writable = true
357 end
358 end
359
360 ###############################################################################
361
362 redef class Writable
363 # Like `write_to` but take care of creating the file
364 fun write_to_file(filepath: String)
365 do
366 var stream = new FileWriter.open(filepath)
367 write_to(stream)
368 stream.close
369 end
370 end
371
372 # Utility class to access file system services.
373 #
374 # Usually created with `Text::to_path`.
375 #
376 # `Path` objects does not necessarily represent existing files in a file system.
377 # They are sate-less objects that efficiently represent path information.
378 # They also provide an easy to use API on file-system services and are used to store their error status (see `last_error`)
379 class Path
380
381 private var path: String
382
383 # Path to this file
384 redef fun to_s do return path
385
386 # Short name of the file at `to_s`
387 #
388 # ~~~
389 # var path = "/tmp/somefile".to_path
390 # assert path.filename == "somefile"
391 # ~~~
392 #
393 # The result does not depend of the file system, thus is cached for efficiency.
394 var filename: String = path.basename is lazy
395
396 # The path simplified by removing useless `.`, removing `//`, and resolving `..`
397 #
398 # ~~~
399 # var path = "somedir/./tmp/../somefile".to_path
400 # assert path.simplified.to_s == "somedir/somefile"
401 # ~~~
402 #
403 # See `String:simplify_path` for details.
404 #
405 # The result does not depend of the file system, thus is cached for efficiency.
406 var simplified: Path is lazy do
407 var res = path.simplify_path.to_path
408 res.simplified = res
409 return res
410 end
411
412 # Return the directory part of the path.
413 #
414 # ~~~
415 # var path = "/foo/bar/baz".to_path
416 # assert path.dir.to_s == "/foo/bar"
417 # assert path.dir.dir.to_s == "/foo"
418 # assert path.dir.dir.dir.to_s == "/"
419 # ~~~
420 #
421 # See `String:dirname` for details.
422 #
423 # The result does not depend of the file system, thus is cached for efficiency.
424 var dir: Path is lazy do
425 return path.dirname.to_path
426 end
427
428 # Last error produced by I/O operations.
429 #
430 # ~~~
431 # var path = "/does/not/exists".to_path
432 # assert path.last_error == null
433 # path.read_all
434 # assert path.last_error != null
435 # ~~~
436 #
437 # Since `Path` objects are stateless, `last_error` is reset on most operations and reflect its status.
438 var last_error: nullable IOError = null is writable
439
440 # Does the file at `path` exists?
441 #
442 # If the file does not exists, `last_error` is set to the information.
443 fun exists: Bool do return stat != null
444
445 # Information on the file at `self` following symbolic links
446 #
447 # Returns `null` if there is no file at `self`.
448 # `last_error` is updated to contains the error information on error, and null on success.
449 #
450 # assert "/etc/".to_path.stat.is_dir
451 # assert "/etc/issue".to_path.stat.is_file
452 # assert "/fail/does not/exist".to_path.stat == null
453 #
454 # ~~~
455 # var p = "/tmp/".to_path
456 # var stat = p.stat
457 # if stat != null then # Does `p` exist?
458 # print "It's size is {stat.size}"
459 # if stat.is_dir then print "It's a directory"
460 # else
461 # print p.last_error.to_s
462 # end
463 # ~~~
464 fun stat: nullable FileStat
465 do
466 var stat = path.to_cstring.file_stat
467 if stat.address_is_null then
468 last_error = new IOError("Cannot open `{path}`: {sys.errno.strerror}")
469 return null
470 end
471 last_error = null
472 return new FileStat(stat)
473 end
474
475 # Information on the file or link at `self`
476 #
477 # Do not follow symbolic links.
478 fun link_stat: nullable FileStat
479 do
480 var stat = path.to_cstring.file_lstat
481 if stat.address_is_null then
482 last_error = new IOError("Cannot open `{path}`: {sys.errno.strerror}")
483 return null
484 end
485 last_error = null
486 return new FileStat(stat)
487 end
488
489 # Delete a file from the file system.
490 #
491 # `last_error` is updated to contains the error information on error, and null on success.
492 fun delete
493 do
494 var res = path.to_cstring.file_delete
495 if not res then
496 last_error = new IOError("Cannot delete `{path}`: {sys.errno.strerror}")
497 else
498 last_error = null
499 end
500 end
501
502 # Copy content of file at `path` to `dest`.
503 #
504 # `last_error` is updated to contains the error information on error, and null on success.
505 fun copy(dest: Path)
506 do
507 last_error = null
508 var input = open_ro
509 var output = dest.open_wo
510
511 var buffer = new CString(4096)
512 while not input.eof do
513 var read = input.read_bytes_to_cstring(buffer, 4096)
514 output.write_bytes_from_cstring(buffer, read)
515 end
516
517 input.close
518 output.close
519 last_error = input.last_error or else output.last_error
520 end
521
522 # Open this file for reading.
523 #
524 # ~~~
525 # var file = "/etc/issue".to_path.open_ro
526 # print file.read_line
527 # file.close
528 # ~~~
529 #
530 # Note that it is the user's responsibility to close the stream.
531 # Therefore, for simple use case, look at `read_all` or `each_line`.
532 #
533 # ENSURE `last_error == result.last_error`
534 fun open_ro: FileReader
535 do
536 var res = new FileReader.open(path)
537 last_error = res.last_error
538 return res
539 end
540
541 # Open this file for writing
542 #
543 # ~~~
544 # var file = "bla.log".to_path.open_wo
545 # file.write "Blabla\n"
546 # file.close
547 # ~~~
548 #
549 # Note that it is the user's responsibility to close the stream.
550 # Therefore, for simple use case, look at `Writable::write_to_file`.
551 #
552 # ENSURE `last_error == result.last_error`
553 fun open_wo: FileWriter
554 do
555 var res = new FileWriter.open(path)
556 last_error = res.last_error
557 return res
558 end
559
560 # Read all the content of the file as a string.
561 #
562 # ~~~
563 # var content = "/etc/issue".to_path.read_all
564 # print content
565 # ~~~
566 #
567 # `last_error` is updated to contains the error information on error, and null on success.
568 # In case of error, the result might be empty or truncated.
569 #
570 # See `Reader::read_all` for details.
571 fun read_all: String do return read_all_bytes.to_s
572
573 # Read all the content on the file as a raw sequence of bytes.
574 #
575 # ~~~
576 # var content = "/etc/issue".to_path.read_all_bytes
577 # print content.to_s
578 # ~~~
579 #
580 # `last_error` is updated to contains the error information on error, and null on success.
581 # In case of error, the result might be empty or truncated.
582 fun read_all_bytes: Bytes
583 do
584 var s = open_ro
585 var res = s.read_all_bytes
586 s.close
587 last_error = s.last_error
588 return res
589 end
590
591 # Read all the lines of the file
592 #
593 # ~~~
594 # var lines = "/etc/passwd".to_path.read_lines
595 #
596 # print "{lines.length} users"
597 #
598 # for l in lines do
599 # var fields = l.split(":")
600 # print "name={fields[0]} uid={fields[2]}"
601 # end
602 # ~~~
603 #
604 # `last_error` is updated to contains the error information on error, and null on success.
605 # In case of error, the result might be empty or truncated.
606 #
607 # See `Reader::read_lines` for details.
608 fun read_lines: Array[String]
609 do
610 var s = open_ro
611 var res = s.read_lines
612 s.close
613 last_error = s.last_error
614 return res
615 end
616
617 # Return an iterator on each line of the file
618 #
619 # ~~~
620 # for l in "/etc/passwd".to_path.each_line do
621 # var fields = l.split(":")
622 # print "name={fields[0]} uid={fields[2]}"
623 # end
624 # ~~~
625 #
626 # Note: the stream is automatically closed at the end of the file (see `LineIterator::close_on_finish`)
627 #
628 # `last_error` is updated to contains the error information on error, and null on success.
629 #
630 # See `Reader::each_line` for details.
631 fun each_line: LineIterator
632 do
633 var s = open_ro
634 var res = s.each_line
635 res.close_on_finish = true
636 last_error = s.last_error
637 return res
638 end
639
640 # Correctly join `self` with `subpath` using the directory separator.
641 #
642 # Using a standard "{self}/{path}" does not work in the following cases:
643 #
644 # * `self` is empty.
645 # * `path` starts with `'/'`.
646 #
647 # This method ensures that the join is valid.
648 #
649 # var hello = "hello".to_path
650 # assert (hello/"world").to_s == "hello/world"
651 # assert ("hel/lo".to_path / "wor/ld").to_s == "hel/lo/wor/ld"
652 # assert ("".to_path / "world").to_s == "world"
653 # assert (hello / "/world").to_s == "/world"
654 # assert ("hello/".to_path / "world").to_s == "hello/world"
655 fun /(subpath: String): Path do return new Path(path / subpath)
656
657 # Lists the files contained within the directory at `path`.
658 #
659 # var files = "/etc".to_path.files
660 # assert files.has("/etc/issue".to_path)
661 #
662 # `last_error` is updated to contains the error information on error, and null on success.
663 # In case of error, the result might be empty or truncated.
664 #
665 # var path = "/etc/issue".to_path
666 # files = path.files
667 # assert files.is_empty
668 # assert path.last_error != null
669 fun files: Array[Path]
670 do
671 last_error = null
672 var res = new Array[Path]
673 var d = new NativeDir.opendir(path.to_cstring)
674 if d.address_is_null then
675 last_error = new IOError("Cannot list directory `{path}`: {sys.errno.strerror}")
676 return res
677 end
678
679 loop
680 var de = d.readdir
681 if de.address_is_null then
682 # readdir cannot fail, so null means end of list
683 break
684 end
685 var name = de.to_s
686 if name == "." or name == ".." then continue
687 res.add self / name
688 end
689 d.closedir
690
691 return res
692 end
693
694 # Is `self` the path to an existing directory ?
695 #
696 # ~~~nit
697 # assert ".".to_path.is_dir
698 # assert not "/etc/issue".to_path.is_dir
699 # assert not "/should/not/exist".to_path.is_dir
700 # ~~~
701 fun is_dir: Bool do
702 var st = stat
703 if st == null then return false
704 return st.is_dir
705 end
706
707 # Recursively delete a directory and all of its content
708 #
709 # Does not go through symbolic links and may get stuck in a cycle if there
710 # is a cycle in the file system.
711 #
712 # `last_error` is updated with the first encountered error, or null on success.
713 # The method does not stop on the first error and tries to remove the most files and directories.
714 #
715 # ~~~
716 # var path = "/does/not/exists/".to_path
717 # path.rmdir
718 # assert path.last_error != null
719 #
720 # path = "/tmp/path/to/create".to_path
721 # path.to_s.mkdir
722 # assert path.exists
723 # path.rmdir
724 # assert path.last_error == null
725 # ~~~
726 fun rmdir
727 do
728 var first_error = null
729 for file in self.files do
730 var stat = file.link_stat
731 if stat == null then
732 if first_error == null then first_error = file.last_error
733 continue
734 end
735 if stat.is_dir then
736 # Recursively rmdir
737 file.rmdir
738 else
739 file.delete
740 end
741 if first_error == null then first_error = file.last_error
742 end
743
744 # Delete the directory itself if things are fine
745 if first_error == null then
746 if not path.to_cstring.rmdir then
747 first_error = new IOError("Cannot remove `{self}`: {sys.errno.strerror}")
748 end
749 end
750 self.last_error = first_error
751 end
752
753 redef fun ==(other) do return other isa Path and simplified.path == other.simplified.path
754 redef fun hash do return simplified.path.hash
755 end
756
757 # Information on a file
758 #
759 # Created by `Path::stat` and `Path::link_stat`.
760 #
761 # The information within this class is gathered when the instance is initialized
762 # it will not be updated if the targeted file is modified.
763 class FileStat
764 super Finalizable
765
766 # TODO private init
767
768 # The low-level status of a file
769 #
770 # See: POSIX stat(2)
771 private var stat: NativeFileStat
772
773 private var finalized = false
774
775 redef fun finalize
776 do
777 if not finalized then
778 stat.free
779 finalized = true
780 end
781 end
782
783 # Returns the last access time in seconds since Epoch
784 fun last_access_time: Int
785 do
786 assert not finalized
787 return stat.atime
788 end
789
790 # Returns the last access time
791 #
792 # alias for `last_access_time`
793 fun atime: Int do return last_access_time
794
795 # Returns the last modification time in seconds since Epoch
796 fun last_modification_time: Int
797 do
798 assert not finalized
799 return stat.mtime
800 end
801
802 # Returns the last modification time
803 #
804 # alias for `last_modification_time`
805 fun mtime: Int do return last_modification_time
806
807
808 # Size of the file at `path`
809 fun size: Int
810 do
811 assert not finalized
812 return stat.size
813 end
814
815 # Is self a regular file and not a device file, pipe, socket, etc.?
816 fun is_file: Bool
817 do
818 assert not finalized
819 return stat.is_reg
820 end
821
822 # Alias for `is_file`
823 fun is_reg: Bool do return is_file
824
825 # Is this a directory?
826 fun is_dir: Bool
827 do
828 assert not finalized
829 return stat.is_dir
830 end
831
832 # Is this a symbolic link?
833 fun is_link: Bool
834 do
835 assert not finalized
836 return stat.is_lnk
837 end
838
839 # FIXME Make the following POSIX only? or implement in some other way on Windows
840
841 # Returns the last status change time in seconds since Epoch
842 fun last_status_change_time: Int
843 do
844 assert not finalized
845 return stat.ctime
846 end
847
848 # Returns the last status change time
849 #
850 # alias for `last_status_change_time`
851 fun ctime: Int do return last_status_change_time
852
853 # Returns the permission bits of file
854 fun mode: Int
855 do
856 assert not finalized
857 return stat.mode
858 end
859
860 # Is this a character device?
861 fun is_chr: Bool
862 do
863 assert not finalized
864 return stat.is_chr
865 end
866
867 # Is this a block device?
868 fun is_blk: Bool
869 do
870 assert not finalized
871 return stat.is_blk
872 end
873
874 # Is this a FIFO pipe?
875 fun is_fifo: Bool
876 do
877 assert not finalized
878 return stat.is_fifo
879 end
880
881 # Is this a UNIX socket
882 fun is_sock: Bool
883 do
884 assert not finalized
885 return stat.is_sock
886 end
887 end
888
889 redef class Text
890 # Access file system related services on the path at `self`
891 fun to_path: Path do return new Path(to_s)
892
893 private fun write_native_to(s: FileWriter)
894 do
895 for i in substrings do s.write_native(i.to_cstring, 0, i.byte_length)
896 end
897
898 # return true if a file with this names exists
899 fun file_exists: Bool do return to_cstring.file_exists
900
901 # The status of a file. see POSIX stat(2).
902 fun file_stat: nullable FileStat
903 do
904 var stat = to_cstring.file_stat
905 if stat.address_is_null then return null
906 return new FileStat(stat)
907 end
908
909 # The status of a file or of a symlink. see POSIX lstat(2).
910 fun file_lstat: nullable FileStat
911 do
912 var stat = to_cstring.file_lstat
913 if stat.address_is_null then return null
914 return new FileStat(stat)
915 end
916
917 # Remove a file, return true if success
918 fun file_delete: Bool do return to_cstring.file_delete
919
920 # Copy content of file at `self` to `dest`
921 fun file_copy_to(dest: String) do to_path.copy(dest.to_path)
922
923 # Remove the trailing `extension`.
924 #
925 # `extension` usually starts with a dot but could be anything.
926 #
927 # assert "file.txt".strip_extension(".txt") == "file"
928 # assert "file.txt".strip_extension("le.txt") == "fi"
929 # assert "file.txt".strip_extension("xt") == "file.t"
930 #
931 # If `extension == null`, the rightmost extension is stripped, including the last dot.
932 #
933 # assert "file.txt".strip_extension == "file"
934 #
935 # If `extension` is not present, `self` is returned unmodified.
936 #
937 # assert "file.txt".strip_extension(".tar.gz") == "file.txt"
938 fun strip_extension(extension: nullable String): String
939 do
940 if extension == null then
941 extension = file_extension
942 if extension == null then
943 return self.to_s
944 else extension = ".{extension}"
945 end
946
947 if has_suffix(extension) then
948 return substring(0, length - extension.length).to_s
949 end
950 return self.to_s
951 end
952
953 # Extract the basename of a path and strip the `extension`
954 #
955 # The extension is stripped only if `extension != null`.
956 #
957 # assert "/path/to/a_file.ext".basename(".ext") == "a_file"
958 # assert "path/to/a_file.ext".basename(".ext") == "a_file"
959 # assert "path/to/a_file.ext".basename == "a_file.ext"
960 # assert "path/to".basename(".ext") == "to"
961 # assert "path/to/".basename(".ext") == "to"
962 # assert "path/to".basename == "to"
963 # assert "path".basename == "path"
964 # assert "/path".basename == "path"
965 # assert "/".basename == "/"
966 # assert "".basename == ""
967 #
968 # On Windows, '\' are replaced by '/':
969 #
970 # ~~~nitish
971 # assert "C:\\path\\to\\a_file.ext".basename(".ext") == "a_file"
972 # assert "C:\\".basename == "C:"
973 # ~~~
974 fun basename(extension: nullable String): String
975 do
976 var n = self
977 if is_windows then n = n.replace("\\", "/")
978
979 var l = length - 1 # Index of the last char
980 while l > 0 and self.chars[l] == '/' do l -= 1 # remove all trailing `/`
981 if l == 0 then return "/"
982 var pos = chars.last_index_of_from('/', l)
983 if pos >= 0 then
984 n = substring(pos+1, l-pos)
985 end
986
987 if extension != null then
988 return n.strip_extension(extension)
989 else return n.to_s
990 end
991
992 # Extract the dirname of a path
993 #
994 # assert "/path/to/a_file.ext".dirname == "/path/to"
995 # assert "path/to/a_file.ext".dirname == "path/to"
996 # assert "path/to".dirname == "path"
997 # assert "path/to/".dirname == "path"
998 # assert "path".dirname == "."
999 # assert "/path".dirname == "/"
1000 # assert "/".dirname == "/"
1001 # assert "".dirname == "."
1002 #
1003 # On Windows, '\' are replaced by '/':
1004 #
1005 # ~~~nitish
1006 # assert "C:\\path\\to\\a_file.ext".dirname == "C:/path/to"
1007 # assert "C:\\file".dirname == "C:"
1008 # ~~~
1009 fun dirname: String
1010 do
1011 var s = self
1012 if is_windows then s = s.replace("\\", "/")
1013
1014 var l = length - 1 # Index of the last char
1015 while l > 0 and s.chars[l] == '/' do l -= 1 # remove all trailing `/`
1016 var pos = s.chars.last_index_of_from('/', l)
1017 if pos > 0 then
1018 return s.substring(0, pos).to_s
1019 else if pos == 0 then
1020 return "/"
1021 else
1022 return "."
1023 end
1024 end
1025
1026 # Return the canonicalized absolute pathname (see POSIX function `realpath`)
1027 #
1028 # Require: `file_exists`
1029 fun realpath: String do
1030 var cs = to_cstring.file_realpath
1031 assert file_exists
1032 var res = cs.to_s
1033 cs.free
1034 return res
1035 end
1036
1037 # Simplify a file path by remove useless `.`, removing `//`, and resolving `..`
1038 #
1039 # * `..` are not resolved if they start the path
1040 # * starting `.` is simplified unless the path is empty
1041 # * starting `/` is not removed
1042 # * trailing `/` is removed
1043 #
1044 # Note that the method only work on the string:
1045 #
1046 # * no I/O access is performed
1047 # * the validity of the path is not checked
1048 #
1049 # ~~~
1050 # assert "some/./complex/../../path/from/../to/a////file//".simplify_path == "path/to/a/file"
1051 # assert "../dir/file".simplify_path == "../dir/file"
1052 # assert "dir/../../".simplify_path == ".."
1053 # assert "dir/..".simplify_path == "."
1054 # assert "//absolute//path/".simplify_path == "/absolute/path"
1055 # assert "//absolute//../".simplify_path == "/"
1056 # assert "/".simplify_path == "/"
1057 # assert "../".simplify_path == ".."
1058 # assert "./".simplify_path == "."
1059 # assert "././././././".simplify_path == "."
1060 # assert "./../dir".simplify_path == "../dir"
1061 # assert "./dir".simplify_path == "dir"
1062 # ~~~
1063 #
1064 # On Windows, '\' are replaced by '/':
1065 #
1066 # ~~~nitish
1067 # assert "C:\\some\\.\\complex\\../../path/to/a_file.ext".simplify_path == "C:/path/to/a_file.ext"
1068 # assert "C:\\".simplify_path == "C:"
1069 # ~~~
1070 fun simplify_path: String
1071 do
1072 var s = self
1073 if is_windows then s = s.replace("\\", "/")
1074 var a = s.split_with("/")
1075 var a2 = new Array[String]
1076 for x in a do
1077 if x == "." and not a2.is_empty then continue # skip `././`
1078 if x == "" and not a2.is_empty then continue # skip `//`
1079 if x == ".." and not a2.is_empty and a2.last != ".." then
1080 if a2.last == "." then # do not skip `./../`
1081 a2.pop # reduce `./../` in `../`
1082 else # reduce `dir/../` in `/`
1083 a2.pop
1084 continue
1085 end
1086 else if not a2.is_empty and a2.last == "." then
1087 a2.pop # reduce `./dir` in `dir`
1088 end
1089 a2.push(x)
1090 end
1091 if a2.is_empty then return "."
1092 if a2.length == 1 and a2.first == "" then return "/"
1093 return a2.join("/")
1094 end
1095
1096 # Correctly join two path using the directory separator.
1097 #
1098 # Using a standard "{self}/{path}" does not work in the following cases:
1099 #
1100 # * `self` is empty.
1101 # * `path` starts with `'/'`.
1102 #
1103 # This method ensures that the join is valid.
1104 #
1105 # assert "hello".join_path("world") == "hello/world"
1106 # assert "hel/lo".join_path("wor/ld") == "hel/lo/wor/ld"
1107 # assert "".join_path("world") == "world"
1108 # assert "hello".join_path("/world") == "/world"
1109 # assert "hello/".join_path("world") == "hello/world"
1110 # assert "hello/".join_path("/world") == "/world"
1111 #
1112 # Note: You may want to use `simplify_path` on the result.
1113 #
1114 # Note: This method works only with POSIX paths.
1115 fun join_path(path: Text): String
1116 do
1117 if path.is_empty then return self.to_s
1118 if self.is_empty then return path.to_s
1119 if path.chars[0] == '/' then return path.to_s
1120 if self.last == '/' then return "{self}{path}"
1121 return "{self}/{path}"
1122 end
1123
1124 # Convert the path (`self`) to a program name.
1125 #
1126 # Ensure the path (`self`) will be treated as-is by POSIX shells when it is
1127 # used as a program name. In order to do that, prepend `./` if needed.
1128 #
1129 # assert "foo".to_program_name == "./foo"
1130 # assert "/foo".to_program_name == "/foo"
1131 # assert "".to_program_name == "./" # At least, your shell will detect the error.
1132 fun to_program_name: String do
1133 if self.has_prefix("/") then
1134 return self.to_s
1135 else
1136 return "./{self}"
1137 end
1138 end
1139
1140 # Alias for `join_path`
1141 #
1142 # assert "hello" / "world" == "hello/world"
1143 # assert "hel/lo" / "wor/ld" == "hel/lo/wor/ld"
1144 # assert "" / "world" == "world"
1145 # assert "/hello" / "/world" == "/world"
1146 #
1147 # This operator is quite useful for chaining changes of path.
1148 # The next one being relative to the previous one.
1149 #
1150 # var a = "foo"
1151 # var b = "/bar"
1152 # var c = "baz/foobar"
1153 # assert a/b/c == "/bar/baz/foobar"
1154 fun /(path: Text): String do return join_path(path)
1155
1156 # Returns the relative path needed to go from `self` to `dest`.
1157 #
1158 # assert "/foo/bar".relpath("/foo/baz") == "../baz"
1159 # assert "/foo/bar".relpath("/baz/bar") == "../../baz/bar"
1160 #
1161 # If `self` or `dest` is relative, they are considered relatively to `getcwd`.
1162 #
1163 # In some cases, the result is still independent of the current directory:
1164 #
1165 # assert "foo/bar".relpath("..") == "../../.."
1166 #
1167 # In other cases, parts of the current directory may be exhibited:
1168 #
1169 # var p = "../foo/bar".relpath("baz")
1170 # var c = getcwd.basename
1171 # assert p == "../../{c}/baz"
1172 #
1173 # For path resolution independent of the current directory (eg. for paths in URL),
1174 # or to use an other starting directory than the current directory,
1175 # just force absolute paths:
1176 #
1177 # var start = "/a/b/c/d"
1178 # var p2 = (start/"../foo/bar").relpath(start/"baz")
1179 # assert p2 == "../../d/baz"
1180 #
1181 #
1182 # Neither `self` or `dest` has to be real paths or to exist in directories since
1183 # the resolution is only done with string manipulations and without any access to
1184 # the underlying file system.
1185 #
1186 # If `self` and `dest` are the same directory, the empty string is returned:
1187 #
1188 # assert "foo".relpath("foo") == ""
1189 # assert "foo/../bar".relpath("bar") == ""
1190 #
1191 # The empty string and "." designate both the current directory:
1192 #
1193 # assert "".relpath("foo/bar") == "foo/bar"
1194 # assert ".".relpath("foo/bar") == "foo/bar"
1195 # assert "foo/bar".relpath("") == "../.."
1196 # assert "/" + "/".relpath(".") == getcwd
1197 fun relpath(dest: String): String
1198 do
1199 # TODO windows support
1200 var cwd = getcwd
1201 var from = (cwd/self).simplify_path.split("/")
1202 if from.last.is_empty then from.pop # case for the root directory
1203 var to = (cwd/dest).simplify_path.split("/")
1204 if to.last.is_empty then to.pop # case for the root directory
1205
1206 # Remove common prefixes
1207 while not from.is_empty and not to.is_empty and from.first == to.first do
1208 from.shift
1209 to.shift
1210 end
1211
1212 # Result is going up in `from` with ".." then going down following `to`
1213 var from_len = from.length
1214 if from_len == 0 then return to.join("/")
1215 var up = "../"*(from_len-1) + ".."
1216 if to.is_empty then return up
1217 var res = up + "/" + to.join("/")
1218 return res
1219 end
1220
1221 # Create a directory (and all intermediate directories if needed)
1222 #
1223 # The optional `mode` parameter specifies the permissions of the directory,
1224 # the default value is `0o777`.
1225 #
1226 # Return an error object in case of error.
1227 #
1228 # assert "/etc/".mkdir != null
1229 fun mkdir(mode: nullable Int): nullable Error
1230 do
1231 mode = mode or else 0o777
1232 var s = self
1233 if is_windows then s = s.replace("\\", "/")
1234
1235 var dirs = s.split_with("/")
1236 var path = new FlatBuffer
1237 if dirs.is_empty then return null
1238 if dirs[0].is_empty then
1239 # it was a starting /
1240 path.add('/')
1241 end
1242 var error: nullable Error = null
1243 for i in [0 .. dirs.length - 1[ do
1244 var d = dirs[i]
1245 if d.is_empty then continue
1246 path.append(d)
1247 path.add('/')
1248 if path.file_exists then continue
1249 var res = path.to_cstring.file_mkdir(mode)
1250 if not res and error == null then
1251 error = new IOError("Cannot create directory `{path}`: {sys.errno.strerror}")
1252 end
1253 end
1254 var res = s.to_cstring.file_mkdir(mode)
1255 if not res and error == null then
1256 error = new IOError("Cannot create directory `{path}`: {sys.errno.strerror}")
1257 end
1258 return error
1259 end
1260
1261 # Delete a directory and all of its content, return `true` on success
1262 #
1263 # Does not go through symbolic links and may get stuck in a cycle if there
1264 # is a cycle in the filesystem.
1265 #
1266 # Return an error object in case of error.
1267 #
1268 # assert "/fail/does not/exist".rmdir != null
1269 fun rmdir: nullable Error
1270 do
1271 var p = to_path
1272 p.rmdir
1273 return p.last_error
1274 end
1275
1276 # Change the current working directory
1277 #
1278 # "/etc".chdir
1279 # assert getcwd == "/etc"
1280 # "..".chdir
1281 # assert getcwd == "/"
1282 #
1283 # Return an error object in case of error.
1284 #
1285 # assert "/etc".chdir == null
1286 # assert "/fail/does no/exist".chdir != null
1287 # assert getcwd == "/etc" # unchanger
1288 fun chdir: nullable Error
1289 do
1290 var res = to_cstring.file_chdir
1291 if res then return null
1292 var error = new IOError("Cannot change directory to `{self}`: {sys.errno.strerror}")
1293 return error
1294 end
1295
1296 # Return right-most extension (without the dot)
1297 #
1298 # Only the last extension is returned.
1299 # There is no special case for combined extensions.
1300 #
1301 # assert "file.txt".file_extension == "txt"
1302 # assert "file.tar.gz".file_extension == "gz"
1303 #
1304 # For file without extension, `null` is returned.
1305 # Hoever, for trailing dot, `""` is returned.
1306 #
1307 # assert "file".file_extension == null
1308 # assert "file.".file_extension == ""
1309 #
1310 # The starting dot of hidden files is never considered.
1311 #
1312 # assert ".file.txt".file_extension == "txt"
1313 # assert ".file".file_extension == null
1314 fun file_extension: nullable String
1315 do
1316 var last_slash = chars.last_index_of('.')
1317 if last_slash > 0 then
1318 return substring( last_slash+1, length ).to_s
1319 else
1320 return null
1321 end
1322 end
1323
1324 # Returns entries contained within the directory represented by self.
1325 #
1326 # var files = "/etc".files
1327 # assert files.has("issue")
1328 #
1329 # Returns an empty array in case of error
1330 #
1331 # files = "/etc/issue".files
1332 # assert files.is_empty
1333 #
1334 # TODO find a better way to handle errors and to give them back to the user.
1335 fun files: Array[String]
1336 do
1337 var res = new Array[String]
1338 var d = new NativeDir.opendir(to_cstring)
1339 if d.address_is_null then return res
1340
1341 loop
1342 var de = d.readdir
1343 if de.address_is_null then break
1344 var name = de.to_s
1345 if name == "." or name == ".." then continue
1346 res.add name
1347 end
1348 d.closedir
1349
1350 return res
1351 end
1352 end
1353
1354 redef class FlatString
1355 redef fun write_native_to(s)
1356 do
1357 s.write_native(items, first_byte, byte_length)
1358 end
1359
1360 redef fun file_extension do
1361 var its = _items
1362 var p = last_byte
1363 var c = its[p]
1364 var st = _first_byte
1365 while p >= st and c != '.'.ascii do
1366 p -= 1
1367 c = its[p]
1368 end
1369 if p <= st then return null
1370 var ls = last_byte
1371 return new FlatString.with_infos(its, ls - p, p + 1)
1372 end
1373
1374 redef fun basename(extension) do
1375 var s = self
1376 if is_windows then s = s.replace("\\", "/").as(FlatString)
1377
1378 var bname
1379 var l = s.last_byte
1380 var its = s._items
1381 var min = s._first_byte
1382 var sl = '/'.ascii
1383 while l > min and its[l] == sl do l -= 1
1384 if l == min then return "/"
1385 var ns = l
1386 while ns >= min and its[ns] != sl do ns -= 1
1387 bname = new FlatString.with_infos(its, l - ns, ns + 1)
1388
1389 return if extension != null then bname.strip_extension(extension) else bname
1390 end
1391 end
1392
1393 redef class CString
1394 private fun file_exists: Bool `{
1395 #ifdef _WIN32
1396 DWORD attribs = GetFileAttributesA(self);
1397 return attribs != INVALID_FILE_ATTRIBUTES;
1398 #else
1399 FILE *hdl = fopen(self,"r");
1400 if(hdl != NULL){
1401 fclose(hdl);
1402 }
1403 return hdl != NULL;
1404 #endif
1405 `}
1406
1407 private fun file_stat: NativeFileStat `{
1408 struct stat buff;
1409 if(stat(self, &buff) != -1) {
1410 struct stat* stat_element;
1411 stat_element = malloc(sizeof(struct stat));
1412 return memcpy(stat_element, &buff, sizeof(struct stat));
1413 }
1414 return 0;
1415 `}
1416
1417 private fun file_lstat: NativeFileStat `{
1418 #ifdef _WIN32
1419 // FIXME use a higher level abstraction to support WIN32
1420 return NULL;
1421 #else
1422 struct stat* stat_element;
1423 int res;
1424 stat_element = malloc(sizeof(struct stat));
1425 res = lstat(self, stat_element);
1426 if (res == -1) return NULL;
1427 return stat_element;
1428 #endif
1429 `}
1430
1431 private fun file_mkdir(mode: Int): Bool `{
1432 #ifdef _WIN32
1433 return !mkdir(self);
1434 #else
1435 return !mkdir(self, mode);
1436 #endif
1437 `}
1438
1439 private fun rmdir: Bool `{ return !rmdir(self); `}
1440
1441 private fun file_delete: Bool `{
1442 return remove(self) == 0;
1443 `}
1444
1445 private fun file_chdir: Bool `{ return !chdir(self); `}
1446
1447 private fun file_realpath: CString `{
1448 #ifdef _WIN32
1449 DWORD len = GetFullPathName(self, 0, NULL, NULL);
1450 char *buf = malloc(len+1); // FIXME don't leak memory
1451 len = GetFullPathName(self, len+1, buf, NULL);
1452 return buf;
1453 #else
1454 return realpath(self, NULL);
1455 #endif
1456 `}
1457 end
1458
1459 # This class is system dependent ... must reify the vfs
1460 private extern class NativeFileStat `{ struct stat * `}
1461
1462 # Returns the permission bits of file
1463 fun mode: Int `{ return self->st_mode; `}
1464
1465 # Returns the last access time
1466 fun atime: Int `{ return self->st_atime; `}
1467
1468 # Returns the last status change time
1469 fun ctime: Int `{ return self->st_ctime; `}
1470
1471 # Returns the last modification time
1472 fun mtime: Int `{ return self->st_mtime; `}
1473
1474 # Returns the size
1475 fun size: Int `{ return self->st_size; `}
1476
1477 # Returns true if it is a regular file (not a device file, pipe, sockect, ...)
1478 fun is_reg: Bool `{ return S_ISREG(self->st_mode); `}
1479
1480 # Returns true if it is a directory
1481 fun is_dir: Bool `{ return S_ISDIR(self->st_mode); `}
1482
1483 # Returns true if it is a character device
1484 fun is_chr: Bool `{ return S_ISCHR(self->st_mode); `}
1485
1486 # Returns true if it is a block device
1487 fun is_blk: Bool `{ return S_ISBLK(self->st_mode); `}
1488
1489 # Returns true if the type is fifo
1490 fun is_fifo: Bool `{ return S_ISFIFO(self->st_mode); `}
1491
1492 # Returns true if the type is a link
1493 fun is_lnk: Bool `{
1494 #ifdef _WIN32
1495 return 0;
1496 #else
1497 return S_ISLNK(self->st_mode);
1498 #endif
1499 `}
1500
1501 # Returns true if the type is a socket
1502 fun is_sock: Bool `{
1503 #ifdef _WIN32
1504 return 0;
1505 #else
1506 return S_ISSOCK(self->st_mode);
1507 #endif
1508 `}
1509 end
1510
1511 # Instance of this class are standard FILE * pointers
1512 private extern class NativeFile `{ FILE* `}
1513 fun io_read(buf: CString, len: Int): Int `{
1514 return fread(buf, 1, len, self);
1515 `}
1516
1517 fun io_write(buf: CString, from, len: Int): Int `{
1518 size_t res = fwrite(buf+from, 1, len, self);
1519 #ifdef _WIN32
1520 // Force flushing buffer because end of line does not trigger a flush
1521 fflush(self);
1522 #endif
1523 return (long)res;
1524 `}
1525
1526 fun write_byte(value: Byte): Int `{
1527 unsigned char b = (unsigned char)value;
1528 return fwrite(&b, 1, 1, self);
1529 `}
1530
1531 fun io_close: Int `{ return fclose(self); `}
1532
1533 fun file_stat: NativeFileStat `{
1534 struct stat buff;
1535 if(fstat(fileno(self), &buff) != -1) {
1536 struct stat* stat_element;
1537 stat_element = malloc(sizeof(struct stat));
1538 return memcpy(stat_element, &buff, sizeof(struct stat));
1539 }
1540 return 0;
1541 `}
1542
1543 fun ferror: Bool `{ return ferror(self); `}
1544
1545 fun feof: Bool `{ return feof(self); `}
1546
1547 fun fileno: Int `{ return fileno(self); `}
1548
1549 # Flushes the buffer, forcing the write operation
1550 fun flush: Int `{ return fflush(self); `}
1551
1552 # Used to specify how the buffering will be handled for the current stream.
1553 fun set_buffering_type(buf_length, mode: Int): Int `{
1554 return setvbuf(self, NULL, (int)mode, buf_length);
1555 `}
1556
1557 new io_open_read(path: CString) `{ return fopen(path, "r"); `}
1558
1559 new io_open_write(path: CString) `{ return fopen(path, "w"); `}
1560
1561 new native_stdin `{ return stdin; `}
1562
1563 new native_stdout `{ return stdout; `}
1564
1565 new native_stderr `{ return stderr; `}
1566 end
1567
1568 # Standard `DIR*` pointer
1569 private extern class NativeDir `{ DIR* `}
1570
1571 # Open a directory
1572 new opendir(path: CString) `{ return opendir(path); `}
1573
1574 # Close a directory
1575 fun closedir `{ closedir(self); `}
1576
1577 # Read the next directory entry
1578 fun readdir: CString `{
1579 struct dirent *de;
1580 de = readdir(self);
1581 if (!de) return NULL;
1582 return de->d_name;
1583 `}
1584 end
1585
1586 redef class Sys
1587
1588 # Standard input
1589 var stdin: PollableReader = new Stdin is protected writable, lazy
1590
1591 # Standard output
1592 var stdout: Writer = new Stdout is protected writable, lazy
1593
1594 # Standard output for errors
1595 var stderr: Writer = new Stderr is protected writable, lazy
1596
1597 # Enumeration for buffer mode full (flushes when buffer is full)
1598 fun buffer_mode_full: Int `{ return _IOFBF; `}
1599
1600 # Enumeration for buffer mode line (flushes when a `\n` is encountered)
1601 fun buffer_mode_line: Int `{ return _IONBF; `}
1602
1603 # Enumeration for buffer mode none (flushes ASAP when something is written)
1604 fun buffer_mode_none: Int `{ return _IOLBF; `}
1605
1606 # returns first available stream to read or write to
1607 # return null on interruption (possibly a signal)
1608 protected fun poll( streams : Sequence[FileStream] ) : nullable FileStream
1609 do
1610 var in_fds = new Array[Int]
1611 var out_fds = new Array[Int]
1612 var fd_to_stream = new HashMap[Int,FileStream]
1613 for s in streams do
1614 var fd = s.fd
1615 if s isa FileReader then in_fds.add( fd )
1616 if s isa FileWriter then out_fds.add( fd )
1617
1618 fd_to_stream[fd] = s
1619 end
1620
1621 var polled_fd = intern_poll( in_fds, out_fds )
1622
1623 if polled_fd == null then
1624 return null
1625 else
1626 return fd_to_stream[polled_fd]
1627 end
1628 end
1629
1630 private fun intern_poll(in_fds: Array[Int], out_fds: Array[Int]): nullable Int
1631 import Array[Int].length, Array[Int].[], Int.as(nullable Int) `{
1632 #ifndef _WIN32
1633 // FIXME use a higher level abstraction to support WIN32
1634
1635 int in_len, out_len, total_len;
1636 struct pollfd *c_fds;
1637 int i;
1638 int first_polled_fd = -1;
1639 int result;
1640
1641 in_len = (int)Array_of_Int_length( in_fds );
1642 out_len = (int)Array_of_Int_length( out_fds );
1643 total_len = in_len + out_len;
1644 c_fds = malloc( sizeof(struct pollfd) * total_len );
1645
1646 /* input streams */
1647 for ( i=0; i<in_len; i ++ ) {
1648 int fd = (int)Array_of_Int__index( in_fds, i );
1649
1650 c_fds[i].fd = fd;
1651 c_fds[i].events = POLLIN;
1652 }
1653
1654 /* output streams */
1655 for ( i=0; i<out_len; i ++ ) {
1656 int fd = (int)Array_of_Int__index( out_fds, i );
1657
1658 c_fds[i].fd = fd;
1659 c_fds[i].events = POLLOUT;
1660 }
1661
1662 /* poll all fds, unlimited timeout */
1663 result = poll( c_fds, total_len, -1 );
1664
1665 if ( result > 0 ) {
1666 /* analyse results */
1667 for ( i=0; i<total_len; i++ )
1668 if ( c_fds[i].revents & c_fds[i].events || /* awaited event */
1669 c_fds[i].revents & POLLHUP ) /* closed */
1670 {
1671 first_polled_fd = c_fds[i].fd;
1672 break;
1673 }
1674
1675 return Int_as_nullable( first_polled_fd );
1676 }
1677 else if ( result < 0 )
1678 fprintf( stderr, "Error in Stream:poll: %s\n", strerror( errno ) );
1679 #endif
1680
1681 return null_Int();
1682 `}
1683
1684 end
1685
1686 # Print `objects` on the standard output (`stdout`).
1687 fun printn(objects: Object...)
1688 do
1689 sys.stdout.write(objects.plain_to_s)
1690 end
1691
1692 # Print an `object` on the standard output (`stdout`) and add a newline.
1693 fun print(object: Object)
1694 do
1695 sys.stdout.write(object.to_s)
1696 sys.stdout.write("\n")
1697 end
1698
1699 # Print `object` on the error output (`stderr` or a log system)
1700 fun print_error(object: Object)
1701 do
1702 sys.stderr.write object.to_s
1703 sys.stderr.write "\n"
1704 end
1705
1706 # Read a character from the standard input (`stdin`).
1707 fun getc: Char
1708 do
1709 var c = sys.stdin.read_char
1710 if c == null then return '\1'
1711 return c
1712 end
1713
1714 # Read a line from the standard input (`stdin`).
1715 fun gets: String
1716 do
1717 return sys.stdin.read_line
1718 end
1719
1720 # Return the working (current) directory
1721 fun getcwd: String do return native_getcwd.to_s
1722
1723 private fun native_getcwd: CString `{ return getcwd(NULL, 0); `}