lib/standard/file: Auto-flush write streams
[nit.git] / lib / standard / 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 ropes
20 import string_search
21 import time
22
23 in "C Header" `{
24 #include <dirent.h>
25 #include <string.h>
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <unistd.h>
29 #include <stdio.h>
30 #include <poll.h>
31 #include <errno.h>
32 `}
33
34 # File Abstract Stream
35 abstract class FStream
36 super IOS
37 # The path of the file.
38 var path: nullable String = null
39
40 # The FILE *.
41 private var file: nullable NativeFile = null
42
43 fun file_stat: FileStat do return _file.file_stat
44
45 # File descriptor of this file
46 fun fd: Int do return _file.fileno
47 end
48
49 # File input stream
50 class IFStream
51 super FStream
52 super BufferedIStream
53 super PollableIStream
54 # Misc
55
56 # Open the same file again.
57 # The original path is reused, therefore the reopened file can be a different file.
58 fun reopen
59 do
60 if not eof and not _file.address_is_null then close
61 last_error = null
62 _file = new NativeFile.io_open_read(path.to_cstring)
63 if _file.address_is_null then
64 last_error = new IOError("Error: Opening file at '{path.as(not null)}' failed with '{sys.errno.strerror}'")
65 end_reached = true
66 return
67 end
68 end_reached = false
69 _buffer_pos = 0
70 _buffer.clear
71 end
72
73 redef fun close
74 do
75 if _file.address_is_null then return
76 var i = _file.io_close
77 _buffer.clear
78 end_reached = true
79 end
80
81 redef fun fill_buffer
82 do
83 var nb = _file.io_read(_buffer.items, _buffer.capacity)
84 if nb <= 0 then
85 end_reached = true
86 nb = 0
87 end
88 _buffer.length = nb
89 _buffer_pos = 0
90 end
91
92 # End of file?
93 redef var end_reached: Bool = false
94
95 # Open the file at `path` for reading.
96 init open(path: String)
97 do
98 self.path = path
99 prepare_buffer(10)
100 _file = new NativeFile.io_open_read(path.to_cstring)
101 if _file.address_is_null then
102 last_error = new IOError("Error: Opening file at '{path}' failed with '{sys.errno.strerror}'")
103 end_reached = true
104 end
105 end
106
107 init from_fd(fd: Int) do
108 self.path = ""
109 prepare_buffer(1)
110 _file = fd.fd_to_stream(read_only)
111 if _file.address_is_null then
112 last_error = new IOError("Error: Converting fd {fd} to stream failed with '{sys.errno.strerror}'")
113 end_reached = true
114 end
115 end
116 end
117
118 # File output stream
119 class OFStream
120 super FStream
121 super OStream
122
123 redef fun write(s)
124 do
125 if last_error != null then return
126 if not _is_writable then
127 last_error = new IOError("Cannot write to non-writable stream")
128 return
129 end
130 if s isa FlatText then
131 write_native(s.to_cstring, s.length)
132 else
133 for i in s.substrings do write_native(i.to_cstring, i.length)
134 end
135 _file.flush
136 end
137
138 redef fun close
139 do
140 if _file.address_is_null then
141 if last_error != null then return
142 last_error = new IOError("Cannot close unopened write stream")
143 _is_writable = false
144 return
145 end
146 var i = _file.io_close
147 if i != 0 then
148 last_error = new IOError("Close failed due to error {sys.errno.strerror}")
149 end
150 _is_writable = false
151 end
152 redef var is_writable = false
153
154 # Write `len` bytes from `native`.
155 private fun write_native(native: NativeString, len: Int)
156 do
157 if last_error != null then return
158 if not _is_writable then
159 last_error = new IOError("Cannot write to non-writable stream")
160 return
161 end
162 if _file.address_is_null then
163 last_error = new IOError("Writing on a null stream")
164 _is_writable = false
165 return
166 end
167 var err = _file.io_write(native, len)
168 if err != len then
169 # Big problem
170 last_error = new IOError("Problem in writing : {err} {len} \n")
171 end
172 end
173
174 # Open the file at `path` for writing.
175 init open(path: String)
176 do
177 _file = new NativeFile.io_open_write(path.to_cstring)
178 self.path = path
179 _is_writable = true
180 if _file.address_is_null then
181 last_error = new IOError("Error: Opening file at '{path}' failed with '{sys.errno.strerror}'")
182 is_writable = false
183 end
184 end
185
186 # Creates a new File stream from a file descriptor
187 init from_fd(fd: Int) do
188 self.path = ""
189 _file = fd.fd_to_stream(wipe_write)
190 _is_writable = true
191 if _file.address_is_null then
192 last_error = new IOError("Error: Opening stream from file descriptor {fd} failed with '{sys.errno.strerror}'")
193 _is_writable = false
194 end
195 end
196 end
197
198 redef class Int
199 # Creates a file stream from a file descriptor `fd` using the file access `mode`.
200 #
201 # NOTE: The `mode` specified must be compatible with the one used in the file descriptor.
202 private fun fd_to_stream(mode: NativeString): NativeFile is extern "file_int_fdtostream"
203 end
204
205 # Constant for read-only file streams
206 private fun read_only: NativeString do return "r".to_cstring
207
208 # Constant for write-only file streams
209 #
210 # If a stream is opened on a file with this method,
211 # it will wipe the previous file if any.
212 # Else, it will create the file.
213 private fun wipe_write: NativeString do return "w".to_cstring
214
215 ###############################################################################
216
217 class Stdin
218 super IFStream
219
220 init do
221 _file = new NativeFile.native_stdin
222 path = "/dev/stdin"
223 prepare_buffer(1)
224 end
225
226 redef fun poll_in: Bool is extern "file_stdin_poll_in"
227 end
228
229 class Stdout
230 super OFStream
231 init do
232 _file = new NativeFile.native_stdout
233 path = "/dev/stdout"
234 _is_writable = true
235 end
236 end
237
238 class Stderr
239 super OFStream
240 init do
241 _file = new NativeFile.native_stderr
242 path = "/dev/stderr"
243 _is_writable = true
244 end
245 end
246
247 ###############################################################################
248
249 redef class Streamable
250 # Like `write_to` but take care of creating the file
251 fun write_to_file(filepath: String)
252 do
253 var stream = new OFStream.open(filepath)
254 write_to(stream)
255 stream.close
256 end
257 end
258
259 redef class String
260 # return true if a file with this names exists
261 fun file_exists: Bool do return to_cstring.file_exists
262
263 # The status of a file. see POSIX stat(2).
264 fun file_stat: FileStat do return to_cstring.file_stat
265
266 # The status of a file or of a symlink. see POSIX lstat(2).
267 fun file_lstat: FileStat do return to_cstring.file_lstat
268
269 # Remove a file, return true if success
270 fun file_delete: Bool do return to_cstring.file_delete
271
272 # Copy content of file at `self` to `dest`
273 fun file_copy_to(dest: String)
274 do
275 var input = new IFStream.open(self)
276 var output = new OFStream.open(dest)
277
278 while not input.eof do
279 var buffer = input.read(1024)
280 output.write buffer
281 end
282
283 input.close
284 output.close
285 end
286
287 # Remove the trailing extension `ext`.
288 #
289 # `ext` usually starts with a dot but could be anything.
290 #
291 # assert "file.txt".strip_extension(".txt") == "file"
292 # assert "file.txt".strip_extension("le.txt") == "fi"
293 # assert "file.txt".strip_extension("xt") == "file.t"
294 #
295 # if `ext` is not present, `self` is returned unmodified.
296 #
297 # assert "file.txt".strip_extension(".tar.gz") == "file.txt"
298 fun strip_extension(ext: String): String
299 do
300 if has_suffix(ext) then
301 return substring(0, length - ext.length)
302 end
303 return self
304 end
305
306 # Extract the basename of a path and remove the extension
307 #
308 # assert "/path/to/a_file.ext".basename(".ext") == "a_file"
309 # assert "path/to/a_file.ext".basename(".ext") == "a_file"
310 # assert "path/to".basename(".ext") == "to"
311 # assert "path/to/".basename(".ext") == "to"
312 # assert "path".basename("") == "path"
313 # assert "/path".basename("") == "path"
314 # assert "/".basename("") == "/"
315 # assert "".basename("") == ""
316 fun basename(ext: String): String
317 do
318 var l = length - 1 # Index of the last char
319 while l > 0 and self.chars[l] == '/' do l -= 1 # remove all trailing `/`
320 if l == 0 then return "/"
321 var pos = chars.last_index_of_from('/', l)
322 var n = self
323 if pos >= 0 then
324 n = substring(pos+1, l-pos)
325 end
326 return n.strip_extension(ext)
327 end
328
329 # Extract the dirname of a path
330 #
331 # assert "/path/to/a_file.ext".dirname == "/path/to"
332 # assert "path/to/a_file.ext".dirname == "path/to"
333 # assert "path/to".dirname == "path"
334 # assert "path/to/".dirname == "path"
335 # assert "path".dirname == "."
336 # assert "/path".dirname == "/"
337 # assert "/".dirname == "/"
338 # assert "".dirname == "."
339 fun dirname: String
340 do
341 var l = length - 1 # Index of the last char
342 while l > 0 and self.chars[l] == '/' do l -= 1 # remove all trailing `/`
343 var pos = chars.last_index_of_from('/', l)
344 if pos > 0 then
345 return substring(0, pos)
346 else if pos == 0 then
347 return "/"
348 else
349 return "."
350 end
351 end
352
353 # Return the canonicalized absolute pathname (see POSIX function `realpath`)
354 fun realpath: String do
355 var cs = to_cstring.file_realpath
356 var res = cs.to_s_with_copy
357 # cs.free_malloc # FIXME memory leak
358 return res
359 end
360
361 # Simplify a file path by remove useless ".", removing "//", and resolving ".."
362 # ".." are not resolved if they start the path
363 # starting "/" is not removed
364 # trainling "/" is removed
365 #
366 # Note that the method only wonrk on the string:
367 # * no I/O access is performed
368 # * the validity of the path is not checked
369 #
370 # assert "some/./complex/../../path/from/../to/a////file//".simplify_path == "path/to/a/file"
371 # assert "../dir/file".simplify_path == "../dir/file"
372 # assert "dir/../../".simplify_path == ".."
373 # assert "dir/..".simplify_path == "."
374 # assert "//absolute//path/".simplify_path == "/absolute/path"
375 # assert "//absolute//../".simplify_path == "/"
376 fun simplify_path: String
377 do
378 var a = self.split_with("/")
379 var a2 = new Array[String]
380 for x in a do
381 if x == "." then continue
382 if x == "" and not a2.is_empty then continue
383 if x == ".." and not a2.is_empty and a2.last != ".." then
384 a2.pop
385 continue
386 end
387 a2.push(x)
388 end
389 if a2.is_empty then return "."
390 if a2.length == 1 and a2.first == "" then return "/"
391 return a2.join("/")
392 end
393
394 # Correctly join two path using the directory separator.
395 #
396 # Using a standard "{self}/{path}" does not work in the following cases:
397 #
398 # * `self` is empty.
399 # * `path` ends with `'/'`.
400 # * `path` starts with `'/'`.
401 #
402 # This method ensures that the join is valid.
403 #
404 # assert "hello".join_path("world") == "hello/world"
405 # assert "hel/lo".join_path("wor/ld") == "hel/lo/wor/ld"
406 # assert "".join_path("world") == "world"
407 # assert "hello".join_path("/world") == "/world"
408 # assert "hello/".join_path("world") == "hello/world"
409 # assert "hello/".join_path("/world") == "/world"
410 #
411 # Note: You may want to use `simplify_path` on the result.
412 #
413 # Note: This method works only with POSIX paths.
414 fun join_path(path: String): String
415 do
416 if path.is_empty then return self
417 if self.is_empty then return path
418 if path.chars[0] == '/' then return path
419 if self.last == '/' then return "{self}{path}"
420 return "{self}/{path}"
421 end
422
423 # Convert the path (`self`) to a program name.
424 #
425 # Ensure the path (`self`) will be treated as-is by POSIX shells when it is
426 # used as a program name. In order to do that, prepend `./` if needed.
427 #
428 # assert "foo".to_program_name == "./foo"
429 # assert "/foo".to_program_name == "/foo"
430 # assert "".to_program_name == "./" # At least, your shell will detect the error.
431 fun to_program_name: String do
432 if self.has_prefix("/") then
433 return self
434 else
435 return "./{self}"
436 end
437 end
438
439 # Alias for `join_path`
440 #
441 # assert "hello" / "world" == "hello/world"
442 # assert "hel/lo" / "wor/ld" == "hel/lo/wor/ld"
443 # assert "" / "world" == "world"
444 # assert "/hello" / "/world" == "/world"
445 #
446 # This operator is quite useful for chaining changes of path.
447 # The next one being relative to the previous one.
448 #
449 # var a = "foo"
450 # var b = "/bar"
451 # var c = "baz/foobar"
452 # assert a/b/c == "/bar/baz/foobar"
453 fun /(path: String): String do return join_path(path)
454
455 # Returns the relative path needed to go from `self` to `dest`.
456 #
457 # assert "/foo/bar".relpath("/foo/baz") == "../baz"
458 # assert "/foo/bar".relpath("/baz/bar") == "../../baz/bar"
459 #
460 # If `self` or `dest` is relative, they are considered relatively to `getcwd`.
461 #
462 # In some cases, the result is still independent of the current directory:
463 #
464 # assert "foo/bar".relpath("..") == "../../.."
465 #
466 # In other cases, parts of the current directory may be exhibited:
467 #
468 # var p = "../foo/bar".relpath("baz")
469 # var c = getcwd.basename("")
470 # assert p == "../../{c}/baz"
471 #
472 # For path resolution independent of the current directory (eg. for paths in URL),
473 # or to use an other starting directory than the current directory,
474 # just force absolute paths:
475 #
476 # var start = "/a/b/c/d"
477 # var p2 = (start/"../foo/bar").relpath(start/"baz")
478 # assert p2 == "../../d/baz"
479 #
480 #
481 # Neither `self` or `dest` has to be real paths or to exist in directories since
482 # the resolution is only done with string manipulations and without any access to
483 # the underlying file system.
484 #
485 # If `self` and `dest` are the same directory, the empty string is returned:
486 #
487 # assert "foo".relpath("foo") == ""
488 # assert "foo/../bar".relpath("bar") == ""
489 #
490 # The empty string and "." designate both the current directory:
491 #
492 # assert "".relpath("foo/bar") == "foo/bar"
493 # assert ".".relpath("foo/bar") == "foo/bar"
494 # assert "foo/bar".relpath("") == "../.."
495 # assert "/" + "/".relpath(".") == getcwd
496 fun relpath(dest: String): String
497 do
498 var cwd = getcwd
499 var from = (cwd/self).simplify_path.split("/")
500 if from.last.is_empty then from.pop # case for the root directory
501 var to = (cwd/dest).simplify_path.split("/")
502 if to.last.is_empty then to.pop # case for the root directory
503
504 # Remove common prefixes
505 while not from.is_empty and not to.is_empty and from.first == to.first do
506 from.shift
507 to.shift
508 end
509
510 # Result is going up in `from` with ".." then going down following `to`
511 var from_len = from.length
512 if from_len == 0 then return to.join("/")
513 var up = "../"*(from_len-1) + ".."
514 if to.is_empty then return up
515 var res = up + "/" + to.join("/")
516 return res
517 end
518
519 # Create a directory (and all intermediate directories if needed)
520 fun mkdir
521 do
522 var dirs = self.split_with("/")
523 var path = new FlatBuffer
524 if dirs.is_empty then return
525 if dirs[0].is_empty then
526 # it was a starting /
527 path.add('/')
528 end
529 for d in dirs do
530 if d.is_empty then continue
531 path.append(d)
532 path.add('/')
533 path.to_s.to_cstring.file_mkdir
534 end
535 end
536
537 # Delete a directory and all of its content, return `true` on success
538 #
539 # Does not go through symbolic links and may get stuck in a cycle if there
540 # is a cycle in the filesystem.
541 fun rmdir: Bool
542 do
543 var ok = true
544 for file in self.files do
545 var file_path = self.join_path(file)
546 var stat = file_path.file_lstat
547 if stat.is_dir then
548 ok = file_path.rmdir and ok
549 else
550 ok = file_path.file_delete and ok
551 end
552 stat.free
553 end
554
555 # Delete the directory itself
556 if ok then to_cstring.rmdir
557
558 return ok
559 end
560
561 # Change the current working directory
562 #
563 # "/etc".chdir
564 # assert getcwd == "/etc"
565 # "..".chdir
566 # assert getcwd == "/"
567 #
568 # TODO: errno
569 fun chdir do to_cstring.file_chdir
570
571 # Return right-most extension (without the dot)
572 #
573 # Only the last extension is returned.
574 # There is no special case for combined extensions.
575 #
576 # assert "file.txt".file_extension == "txt"
577 # assert "file.tar.gz".file_extension == "gz"
578 #
579 # For file without extension, `null` is returned.
580 # Hoever, for trailing dot, `""` is returned.
581 #
582 # assert "file".file_extension == null
583 # assert "file.".file_extension == ""
584 #
585 # The starting dot of hidden files is never considered.
586 #
587 # assert ".file.txt".file_extension == "txt"
588 # assert ".file".file_extension == null
589 fun file_extension: nullable String
590 do
591 var last_slash = chars.last_index_of('.')
592 if last_slash > 0 then
593 return substring( last_slash+1, length )
594 else
595 return null
596 end
597 end
598
599 # returns files contained within the directory represented by self
600 fun files : Set[ String ] is extern import HashSet[String], HashSet[String].add, NativeString.to_s, String.to_cstring, HashSet[String].as(Set[String]) `{
601 char *dir_path;
602 DIR *dir;
603
604 dir_path = String_to_cstring( recv );
605 if ((dir = opendir(dir_path)) == NULL)
606 {
607 perror( dir_path );
608 exit( 1 );
609 }
610 else
611 {
612 HashSet_of_String results;
613 String file_name;
614 struct dirent *de;
615
616 results = new_HashSet_of_String();
617
618 while ( ( de = readdir( dir ) ) != NULL )
619 if ( strcmp( de->d_name, ".." ) != 0 &&
620 strcmp( de->d_name, "." ) != 0 )
621 {
622 file_name = NativeString_to_s( strdup( de->d_name ) );
623 HashSet_of_String_add( results, file_name );
624 }
625
626 closedir( dir );
627 return HashSet_of_String_as_Set_of_String( results );
628 }
629 `}
630 end
631
632 redef class NativeString
633 private fun file_exists: Bool is extern "string_NativeString_NativeString_file_exists_0"
634 private fun file_stat: FileStat is extern "string_NativeString_NativeString_file_stat_0"
635 private fun file_lstat: FileStat `{
636 struct stat* stat_element;
637 int res;
638 stat_element = malloc(sizeof(struct stat));
639 res = lstat(recv, stat_element);
640 if (res == -1) return NULL;
641 return stat_element;
642 `}
643 private fun file_mkdir: Bool is extern "string_NativeString_NativeString_file_mkdir_0"
644 private fun rmdir: Bool `{ return rmdir(recv); `}
645 private fun file_delete: Bool is extern "string_NativeString_NativeString_file_delete_0"
646 private fun file_chdir is extern "string_NativeString_NativeString_file_chdir_0"
647 private fun file_realpath: NativeString is extern "file_NativeString_realpath"
648 end
649
650 # This class is system dependent ... must reify the vfs
651 extern class FileStat `{ struct stat * `}
652 # Returns the permission bits of file
653 fun mode: Int is extern "file_FileStat_FileStat_mode_0"
654 # Returns the last access time
655 fun atime: Int is extern "file_FileStat_FileStat_atime_0"
656 # Returns the last status change time
657 fun ctime: Int is extern "file_FileStat_FileStat_ctime_0"
658 # Returns the last modification time
659 fun mtime: Int is extern "file_FileStat_FileStat_mtime_0"
660 # Returns the size
661 fun size: Int is extern "file_FileStat_FileStat_size_0"
662
663 # Returns true if it is a regular file (not a device file, pipe, sockect, ...)
664 fun is_reg: Bool `{ return S_ISREG(recv->st_mode); `}
665 # Returns true if it is a directory
666 fun is_dir: Bool `{ return S_ISDIR(recv->st_mode); `}
667 # Returns true if it is a character device
668 fun is_chr: Bool `{ return S_ISCHR(recv->st_mode); `}
669 # Returns true if it is a block device
670 fun is_blk: Bool `{ return S_ISBLK(recv->st_mode); `}
671 # Returns true if the type is fifo
672 fun is_fifo: Bool `{ return S_ISFIFO(recv->st_mode); `}
673 # Returns true if the type is a link
674 fun is_lnk: Bool `{ return S_ISLNK(recv->st_mode); `}
675 # Returns true if the type is a socket
676 fun is_sock: Bool `{ return S_ISSOCK(recv->st_mode); `}
677 end
678
679 # Instance of this class are standard FILE * pointers
680 private extern class NativeFile `{ FILE* `}
681 fun io_read(buf: NativeString, len: Int): Int is extern "file_NativeFile_NativeFile_io_read_2"
682 fun io_write(buf: NativeString, len: Int): Int is extern "file_NativeFile_NativeFile_io_write_2"
683 fun io_close: Int is extern "file_NativeFile_NativeFile_io_close_0"
684 fun file_stat: FileStat is extern "file_NativeFile_NativeFile_file_stat_0"
685 fun fileno: Int `{ return fileno(recv); `}
686 # Flushes the buffer, forcing the write operation
687 fun flush: Int is extern "fflush"
688
689 new io_open_read(path: NativeString) is extern "file_NativeFileCapable_NativeFileCapable_io_open_read_1"
690 new io_open_write(path: NativeString) is extern "file_NativeFileCapable_NativeFileCapable_io_open_write_1"
691 new native_stdin is extern "file_NativeFileCapable_NativeFileCapable_native_stdin_0"
692 new native_stdout is extern "file_NativeFileCapable_NativeFileCapable_native_stdout_0"
693 new native_stderr is extern "file_NativeFileCapable_NativeFileCapable_native_stderr_0"
694 end
695
696 redef class Sys
697
698 # Standard input
699 var stdin: PollableIStream = new Stdin is protected writable
700
701 # Standard output
702 var stdout: OStream = new Stdout is protected writable
703
704 # Standard output for errors
705 var stderr: OStream = new Stderr is protected writable
706
707 # Enumeration for buffer mode full (flushes when buffer is full)
708 fun buffer_mode_full: Int is extern "file_Sys_Sys_buffer_mode_full_0"
709 # Enumeration for buffer mode line (flushes when a `\n` is encountered)
710 fun buffer_mode_line: Int is extern "file_Sys_Sys_buffer_mode_line_0"
711 # Enumeration for buffer mode none (flushes ASAP when something is written)
712 fun buffer_mode_none: Int is extern "file_Sys_Sys_buffer_mode_none_0"
713
714 # returns first available stream to read or write to
715 # return null on interruption (possibly a signal)
716 protected fun poll( streams : Sequence[FStream] ) : nullable FStream
717 do
718 var in_fds = new Array[Int]
719 var out_fds = new Array[Int]
720 var fd_to_stream = new HashMap[Int,FStream]
721 for s in streams do
722 var fd = s.fd
723 if s isa IFStream then in_fds.add( fd )
724 if s isa OFStream then out_fds.add( fd )
725
726 fd_to_stream[fd] = s
727 end
728
729 var polled_fd = intern_poll( in_fds, out_fds )
730
731 if polled_fd == null then
732 return null
733 else
734 return fd_to_stream[polled_fd]
735 end
736 end
737
738 private fun intern_poll(in_fds: Array[Int], out_fds: Array[Int]) : nullable Int is extern import Array[Int].length, Array[Int].[], Int.as(nullable Int) `{
739 int in_len, out_len, total_len;
740 struct pollfd *c_fds;
741 sigset_t sigmask;
742 int i;
743 int first_polled_fd = -1;
744 int result;
745
746 in_len = Array_of_Int_length( in_fds );
747 out_len = Array_of_Int_length( out_fds );
748 total_len = in_len + out_len;
749 c_fds = malloc( sizeof(struct pollfd) * total_len );
750
751 /* input streams */
752 for ( i=0; i<in_len; i ++ ) {
753 int fd;
754 fd = Array_of_Int__index( in_fds, i );
755
756 c_fds[i].fd = fd;
757 c_fds[i].events = POLLIN;
758 }
759
760 /* output streams */
761 for ( i=0; i<out_len; i ++ ) {
762 int fd;
763 fd = Array_of_Int__index( out_fds, i );
764
765 c_fds[i].fd = fd;
766 c_fds[i].events = POLLOUT;
767 }
768
769 /* poll all fds, unlimited timeout */
770 result = poll( c_fds, total_len, -1 );
771
772 if ( result > 0 ) {
773 /* analyse results */
774 for ( i=0; i<total_len; i++ )
775 if ( c_fds[i].revents & c_fds[i].events || /* awaited event */
776 c_fds[i].revents & POLLHUP ) /* closed */
777 {
778 first_polled_fd = c_fds[i].fd;
779 break;
780 }
781
782 return Int_as_nullable( first_polled_fd );
783 }
784 else if ( result < 0 )
785 fprintf( stderr, "Error in Stream:poll: %s\n", strerror( errno ) );
786
787 return null_Int();
788 `}
789
790 end
791
792 # Print `objects` on the standard output (`stdout`).
793 protected fun printn(objects: Object...)
794 do
795 sys.stdout.write(objects.to_s)
796 end
797
798 # Print an `object` on the standard output (`stdout`) and add a newline.
799 protected fun print(object: Object)
800 do
801 sys.stdout.write(object.to_s)
802 sys.stdout.write("\n")
803 end
804
805 # Read a character from the standard input (`stdin`).
806 protected fun getc: Char
807 do
808 return sys.stdin.read_char.ascii
809 end
810
811 # Read a line from the standard input (`stdin`).
812 protected fun gets: String
813 do
814 return sys.stdin.read_line
815 end
816
817 # Return the working (current) directory
818 protected fun getcwd: String do return file_getcwd.to_s
819 private fun file_getcwd: NativeString is extern "string_NativeString_NativeString_file_getcwd_0"