-
Notifications
You must be signed in to change notification settings - Fork 2
/
cash.mli
4850 lines (4067 loc) · 224 KB
/
cash.mli
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
(***********************************************************************)
(* Cash *)
(* *)
(* Bruno Verlyck, projet Cristal, INRIA Rocquencourt *)
(* *)
(* Copyright 2002 Institut National de Recherche en Informatique et *)
(* en Automatique. All rights reserved. This file is distributed *)
(* under the terms of the GNU Lesser General Public License. *)
(* *)
(* Cash is based on Scsh, by Olin Shivers. *)
(***********************************************************************)
(** The Caml Shell *)
(** Cash is a Unix shell that is embedded within Objective Caml. It's a Caml
implementation of (an as large as possible subset of) the API of Scsh, the
Scheme Shell by Olin Shivers. See the
{{:ftp://ftp.scsh.net/pub/scsh/0.5/scsh-manual.ps.gz}Scsh manual } of which this
manual is a mere adaptation. ({i This is to check that Olin "did it. \[He\]
did it all by \[him\]self" --- you should at least read his foreword,
`Acknowledgments'. In no way prior knowledge of Scsh is necessary to use
Cash.}) *)
(** Cash is designed for writing useful standalone Unix programs and shell
scripts --- it spans a wide range of application, from ``script'' applications
usually handled with perl or sh, to more standard systems applications usually
written in C.
Cash has two components: a process notation for running programs and setting up
pipelines and redirections (not yet implemented), and a complete syscall library
for low-level access to the operating system. This manual gives a complete
description of Cash. (A general discussion of the design principles behind Scsh
can be found in the paper
{{:ftp://ftp.scsh.net/pub/scsh/papers/scsh-paper.ps.gz}``A Scheme Shell'' }. *)
(** {2 Copyright & source-code license} *)
(** Cash open-source and can be freely redistributed; it is distributed under
the terms of the GNU Lesser General Public License version 2.1 (see the file
[LGPL] in the distribution). *)
(** {2 Caveats} *)
(** It is important to note what Cash is {i not}, as well as what it is. Cash
is primarily designed for the writing of shell scripts --- programming. It is
not a very comfortable system for interactive command use: the current release
lacks job control, command-line editing, a terse, convenient command syntax, and
it does not read in an initialisation file analogous to [login] or [.profile].
However, Cash has a version string: *)
value version : string;
(** {2 Naming conventions} *)
(** Following Scsh, we use a general naming scheme that consistently employs a
set of abbreviations. This is intended to make it easier to remember the names
of things. *)
(** {3 Common naming conventions} *)
(** Some of the common conventions we share with Scsh are:
- [fdes] or [fd] means ``file descriptor,'' a small integer used in Unix to
represent I/O channels.
- [call_with_...] Procedures that call their argument on some computed value are
usually named [call_with_...], {i e.g.}, [call_with_fdes_in] {i in_channel}
{i proc}, which calls {i proc} on {i in_channel}'s file descriptor, returning
whatever {i proc} returns. The abbreviated name means ``call with file
descriptor from in_channel.''
- [with_...] Procedures that call their argument in some special dynamic context
frequently have names of the form [with_...]. For example, [with_env] {i env}
{i thunk}. These functions set the process environment body, execute their
thunk, and then return after resetting the environment to its original state.
- [create_...] or [delete_...] Procedures that create (resp. delete) objects in
the file system (files, directories, temp files, fifos, {i etc.}), begin with
[create_...] (resp. [delete_...]).
- [low_...] These are lower-level Cash primitives that are not commonly used
(but for implementing the higher-level ones).
- [..._info...] Data structures packaging up information about various OS
entities frequently end in ...[_info]. Examples:
[user_info_...], [file_info_...], [group_info_...], and [host_info_...].
*)
(** {3 Cash own naming conventions} *)
(** This paragraph is intended for users already familiar with Scsh, to help
them to find the corresponding procedures' names while translating their
scripts :-). You may skip it it you wish.*)
(** We had to extend Scsh's conventions for two sorts of reasons: first, Caml
being statically typed, Scsh's polymorphic procedures are often present in 2 to
4 versions in Cash, suffixed by a type tag indicative of the type of the main
argument this procedure operates on. The most common tags are:
- ..._in : argument is an in_channel
- ..._out: argument is an out_channel
- ..._fd : argument is a file descriptor
- ..._fn : argument is a file name
*)
(** Second, Caml is much stricter about the lexical syntax of its identifiers
than Lisp like languages, so we generally translated them this way:
- [from->to] becomes [to_of_from], staying in Caml's naming philosophy
- [predicate?] becomes [is_predicate]. Combined with the first extension, this
gives, {i e.g.}, [file-directory?] comes in the 4 flavors
[is_file_directory_fn], [is_file_directory_fd], [is_file_directory_in] and
[is_file_directory_out].
- [!], the Scheme marker for side-effecting procedures, becomes [_bang] (for
lack of a better translation).
There are several exceptions: [file-exists?] becomes the slightly more euphonic
[is_file_existing_fn], [is_file_existing_fd], {i etc.} and [file-not-exist?] is
[file_not_exists_fn], [file_not_exists_fd], {i etc.} because it doesn't yield a
bool in Cash and is no more really a predicate. [move->fdes] has the 3 versions
[move_fd_to_fdes], [move_in_channel_to_fdes] and [move_out_channel_to_fdes].
There are more around [dup]-ing procedures (see section {!Cash.unixio}).
All this generally makes identifiers even longer than the Scheme ones (sorry for
this), except for [(current-input-port)] or [(with-current-input-port ...)]
which become the more civilized [stdin] and [with_stdin ...]. *)
(** {2 A word about Unix standards} *)
(** "The wonderful thing about Unix standards is that there are so many to
choose from." You may be totally bewildered about the multitude of various
standards that exist. Rest assured that this nowhere in this manual will you
encounter an attempt to spell it all out for you; you could not read and
internalise such a twisted account without bleeding from the nose and ears.
However, you might keep in mind the following simple fact: of all the standards,
Posix is the least common denominator. So when this manual repeatedly refers to
Posix, the point is ``the thing we are describing should be portable just about
anywhere.'' Cash sticks to Posix when at all possible; its major departure is
symbolic links, which aren't in Posix (see --- it really {e is} a least common
denominator). *)
(* p 15. *)
(** {1 Process notation} *)
(** Scsh has a notation for controlling Unix processes that takes the form of
s-expressions; this notation can then be embedded inside of standard Scheme
code. This notation is not yet done for Cash. If you want to have a feeling of
what it'll resemble to, refer to the
{{:ftp://ftp.scsh.net/pub/scsh/0.5/scsh-manual.ps.gz}Scsh manual }, chapter
2. Thus we skip directly to the basic blocks on top of which this notation is
built (after a little advertising for the Scsh API).
*)
(** {2 Procedures and syntax extensions} *)
(** It is a general design principle in Scsh/Cash that all functionality made
available through special syntax is also available in a straightforward
procedural form. So there are procedural equivalents for all of the process
notation. In this way, the programmer is not restricted by the particular
details of the syntax. *) (* XXX completer. *)
(** Having a solid procedural foundation also allows for general notational
experimentation using Camlp4 macros. For example, the programmer can build his
own pipeline notation on top of the [fork] and [fork_with_pipe] procedures.
Chapter {!Cash.syscalls} gives the full story on all the procedures in the
syscall library. *)
(** {2 Interfacing process output to Caml} *)
(** There is a family of procedures that can be used to capture the output of
processes as Caml data. *)
(** [run_with_...] all fork off subprocesses, collecting the process' output to
stdout in some form or another. The subprocess runs with file descriptor 1 and
the current [stdout] channel bound to a pipe. *)
(** Value is an in_channel open on process's [stdout].
Returns immediately after forking child. *)
value run_with_in_channel : (unit -> unit) -> in_channel;
(** Value is an out_channel open on process's [stdin].
Returns immediately after forking child. *)
value run_with_out_channel : (unit -> unit) -> out_channel;
(** Value is name of a temp file containing process's output.
Returns when process exits. *)
value run_with_file : (unit -> unit) -> string;
(** Value is a string containing process' output.
Returns when eof read. *)
value run_with_string : (unit -> unit) -> string;
(** Splits process' output into a list of newline-delimited strings. Returns
when eof read. The delimiting newlines are not included in the strings
returned. *)
value run_with_strings : (unit -> unit) -> list string;
(** In sexp procedures below, `data', `object' and `item' should conform to some
Lisp/Scheme syntax. See sexp.mli and atomo.mll for details on the supported
syntax. [Sexp.simple] values can be printed with [Sexp.display]. *)
(** Reads a single object from process' stdout with [Sexp.read].
Returns as soon as the read completes . *)
value run_with_sexp : (unit -> unit) -> Sexp.simple;
(** Repeatedly reads objects from process' stdout with [Sexp.read].
Returns accumulated list upon eof. *)
value run_with_sexps : (unit -> unit) -> list Sexp.simple;
(** The following procedures are also of utility for generally parsing
input streams in Cash. *)
(** Reads the channel until eof, then returns the accumulated string. *)
value string_of_in_channel : in_channel -> string;
(** Repeatedly reads data from the channel until eof, then returns the accumulated
list of items in a schemeish form. Note: you can read one item with
[Sexp.read]. *)
value sexp_list_of_in_channel : in_channel -> list Sexp.simple;
(** Repeatedly reads newline-terminated strings from the channel until eof, then
returns the accumulated list of strings. The delimiting newlines are not part
of the returned strings. *)
value string_list_of_in_channel : in_channel -> list string;
(** Generalises these two procedures.
It uses a reader to repeatedly read objects from a channel.
It accumulates these objects into a list, which is returned upon eof. *)
value list_of_in_channel : (in_channel -> 'a) -> in_channel -> list 'a;
(** The [string_list_of_in_channel] and [sexp_list_of_in_channel] procedures
are trivial to define, being merely [list_of_in_channel] curried with the
appropriate parsers:
{[ let string_list_of_in_channel = list_of_in_channel input_line
let sexp_list_of_in_channel = list_of_in_channel Sexp.read ]}
*)
(** The following compositions also hold:
{[ run_with_string thunk = run_with_in_channel o string_of_in_channel
run_with_strings thunk = run_with_in_channel o string_list_of_in_channel
run_with_sexp thunk = run_with_in_channel o Sexp.read
run_with_sexps thunk = run_with_in_channel o sexp_list_of_in_channel ]}
*)
(* p 16. *)
(**
[fold_in_channel] {i ichan reader op seed} can be used to perform a variety of
iterative operations over an input stream. It repeatedly uses {i reader} to
read an object from {i ichan}. If the first read returns eof, then the entire
[fold_in_channel] operation returns the seed. If the first read operation
returns some other value {i v}, then {i op} is applied to {i v} and the seed:
{i op v seed}.
This should return a new seed value, and the reduction then loops, reading a new
value from the channel, and so forth.
For example, [list_of_in_channel] {i reader channel} could be (and in fact is)
defined as
{[ List.rev (fold_in_channel channel reader (::) \[\])
]}
An imperative way to look at [fold_in_channel] is to say that it abstracts the
idea of a loop over a stream of values read from some channel, where the seed
value expresses the loop state. *)
value fold_in_channel : in_channel -> (in_channel -> 'a) -> ('a -> 'b -> 'b) -> 'b -> 'b;
(* p 17. *)
(** {2 More complex process operations} *)
(** The procedures in the previous section provide for the common case, where
the programmer is only interested in the output of the process. These
procedures provide more complicated facilities for manipulating processes. *)
(** The type of a process object; it encapsulates the subprocess' process id and
exit code; it is the value passed to the {!Cash.wait} system call (which gives access
to the exit code when it is ready). See also {!Cash.pid_of_proc} *)
type proc = Proc_3_4.proc;
(** This procedure can be used if the programmer also wishes access to the
process' pid, exit status, or other information. It forks off a subprocess,
returning two values: a channel open on the process' stdout (and current
[stdout]), and the subprocess's process object. *)
value run_with_inchan_plus_proc : (unit -> unit) -> (in_channel * proc);
(** For example, to uncompress a tech report, reading the uncompressed data into
Cash, and also be able to track the exit status of the decompression process,
use the following:
{[ let (chan, child) =
run_with_inchan_plus_proc (fun () -> exec_path "zcat" ["tr91-145.tex.Z"\]}) in
let paper = string_of_in_channel chan in
let status = wait child in
(* ...use paper, status and child here... *) ]}
*) (** Note that you must {i first} do the [string_of_in_channel] and {i then}
do the [wait] --- the other way around may lock up when the zcat fills up its
output pipe buffer. *)
(** This procedure is the dual of the preceding: the program has to write to the
child's stdin. It forks off a subprocess, returning two values: a channel open
on the process' stdin (and current [stdin]), and the subprocess's process
object. (Be prepared to SIGPIPE). *)
value run_with_outchan_plus_proc : (unit -> unit) -> (out_channel * proc);
(** {2 Multiple stream capture} *)
(** The Unix view of file descriptors. See {!Cash.gen_io} for explanations
about how file descriptors are managed by Cash. *)
type fd = int;
(** Occasionally, the programmer may want to capture multiple distinct output
streams from a process. For instance, he may wish to read the stdout and stderr
streams into two distinct strings. This is accomplished with the
[run_with_collecting] procedure. *)
(** Run processes that produce multiple output streams and return channels open
on these streams. To avoid issues of deadlock, [run_with_collecting] doesn't use
pipes. Instead, it first runs the process with output to temp files, then
returns channels open on the temp files. For example,
{[ run_with_collecting [1; 2] (fun () -> exec_path "ls" []) ]}
runs [ls] with stdout (fd 1) and stderr (fd 2) redirected to temporary
files.
When the [ls] is done, [run_with_collecting] returns three values: the
[ls] process' exit status, and two channels open on the temporary files. The
files are deleted before [run_with_collecting] returns, so when the channels are
closed, they vanish. *)
value run_with_collecting :
list fd -> (unit -> unit) -> (Unix.process_status * list in_channel);
(**
For example, if Kaiming has his mailbox protected, then
{[ let (status, fds) =
run_with_collecting [1; 2]
(fun () -> exec_path "cat" ["/usr/kmshea/mbox"]) in
(status, List.map string_of_in_channel fds) ]}
might produce
{v - : (Unix.process_status * list string) =
(Unix.WEXITED 1, [""; "cat: /usr/kmshea/mbox: Permission denied\n"]) v}
*)
(* p 19. *)
(* Syntax
or (||) *)
(* p 20. *)
(* Syntax
and (&&) *)
(** {2 Process filters} *)
(** These procedures are useful for forking off processes to filter text streams. *)
(** Returns a procedure that when called, repeatedly reads a character from the
current [stdin], applies its first argument {i filter} to the character, and
writes the result to the current [stdout]. The procedure returns upon
reaching eof on [stdin]. *)
value char_filter : (char -> char) -> unit -> unit;
(** Returns a procedure that when called, repeatedly reads a string from the
current [stdin], applies its first argument {i filter} to the string, and
writes the result to the current [stdout]. The procedure returns upon
reaching eof on [stdin].
The optional [buflen] argument controls the number of characters each
internal read operation requests; this means that [filter] will never be
applied to a string longer than [buflen] chars. The default [buflen] value
is 1024. *)
value string_filter : ?buflen: int -> (string -> string) -> unit -> unit;
(** {1:syscalls System calls} *)
(** Cash aims at providing essentially complete access to the basic Unix kernel
services: processes, files, signals and so forth. As the [Unix] module provides
a fairly good Posix interface, Cash often relies on it to give an extended
interface. In particular, it uncovers the opaque [Unix.file_descr] type, and
all the necessary connections with the so-called `revealed' channels. Cash adds
very few restrictions to the way [Pervasives], [Unix] and [Cash] functions
(especially I/O) may be freely intermixed. {i E.g.}, [Unix.read] on a
[Unix.file_descr] obtained by [Unix.in_channel_of_descr] still needs careful
synchonization with [Unix.lseek] and/or [seek_in/tell_in] if Pervasives I/O is
to be interleaved. *)
(* p 21. *)
(** {2 Errors} *)
(** The [Unix] module already raises exceptions for any errno <> 0, so if any
syscall returns, it succeeded. Note that Cash should automatically retry any
interrupted system call it defines, so they never raise [Unix.EINTR]. *)
value errno_error : Unix.error -> string -> string -> 'a;
(** Raises a [Unix] error exception for [Unix.error] argument.
This is just for compatibility with Scsh. *)
(* p 22. *)
(* with-errno-handler: use try. *)
value unwind_protect : (unit -> 'a) -> ('b -> unit) -> 'b -> 'a;
(** [unwind_protect] {i thunk protect ed}, named after a similar functionality
of Lisp, calls thunk, then, before returning its result (be it a value or an
exception), ensures that {i protect} is applied to {i ed}. It can be used,
{i e.g.}, to ensure that a file is closed after an action, regardless of any
exception this action may raise. *)
(** {2:gen_io I/O} *)
(** {3 Pervasives I/O operations} *)
(** Contrarily to Scsh, when using file descriptors, Cash doesn't attempt to
bypass the underlying I/O system, which is reasonably efficient. So to use
[Pervasives] primitives on file descriptors, you should use [in_channel_of_fd] or
[out_channel_of_fd] to get the proper channel. [Unix read] and [write] primitives are
still available for those who {i really} want them. *)
(* p 24. *)
(** {3:withstd Channel manipulation and standard channels} *)
value close_fd_after : fd -> (fd -> 'a) -> 'a;
value close_in_after : in_channel -> (in_channel -> 'a) -> 'a;
value close_out_after : out_channel -> (out_channel -> 'a) -> 'a;
(** [close_..._after] {i channel/fd consumer} return {i (consumer channel/fd)},
but close the channel (or file descriptor) on return. *)
value with_stdin : in_channel -> (unit -> 'a) -> 'a;
value with_stdout : out_channel -> (unit -> 'a) -> 'a;
value with_stderr : out_channel -> (unit -> 'a) -> 'a;
(** These procedures install the given channel as the [stdin], [stdout], and
[stderr] channel, respectively, for the duration of a call to their 2d argument. *)
(* p 25. *)
value set_stdin : in_channel -> unit;
value set_stdout : out_channel -> unit;
value set_stderr : out_channel -> unit;
(** These procedures set the standard I/O channels to new values, the old ones being
abandoned in the great bit bucket --- no flush, no close. *)
(** NOTE: The six procedures above don't change the file descriptor associated
to their channel argument, so {i e.g.}, [stdout] may be associated to another
file descriptor than 1. Use [(out_channel_of_fd 1)] to get a non-side-effected
channel. So you can go fishin' in the great bit bucket... [flush_all] will
flush those channels too. *)
value close_in : in_channel -> bool;
value close_out : out_channel -> bool;
(**
Closing a channel or file descriptor: the 3 procedures around return true if they
closed an open channel/fd (this differs from [Pervasives.close_\{in,out\}]). If
the channel was already closed, they return false; this is not an error. *)
value close_fd : fd -> bool;
(**
If the [fd] arg to close_fd has a channel allocated to it, the channel is
shifted to a new file descriptor created with [{in,out}_channel_of_dup_fd fd]
before closing the [fd]. The channel then has its revealed count set to zero.
This reflects the design criteria that channels are not associated with file
descriptors, but with open files.
To close a file descriptor, and any associated channel it might have, you
must instead say one of (as appropriate):
{[ close_in (in_channel_of_fd fd)
close_out (out_channel_of_fd fd)]}
*)
(** These two procedures are used to synchronise Unix' standard I/O
file descriptors and Caml's current I/O channels. *)
value stdchans_to_stdio : unit -> unit;
(**
This causes the standard I/O file descriptors (0, 1, and 2) to
take their values from the current standard I/O channels. It is exactly
equivalent to the series of redirections:
{[ fdes_of_dup_in ~newfd:0 stdin;
fdes_of_dup_out ~newfd:1 stdout;
fdes_of_dup_out ~newfd:2 stderr ]}
Why not [move_..._to_fdes]? Because [stdout] and [stderr] might be the same channel. *)
value stdio_to_stdchans : unit -> unit;
(**
This causes the bindings of the current standard I/O channels
to be changed to channels constructed over the standard I/O file descriptors.
It is exactly equivalent to the series of assignments:
{[ set_stdin (in_channel_of_fd 0);
set_stdout (out_channel_of_fd 1);
set_stderr (out_channel_of_fd 2)]}
However, you are more likely to find the dynamic-extent variant,
[with_stdio_channels], below, to be of use in general programming. *)
(* p 26. *)
value with_stdio_chans : (unit -> 'a) -> 'a;
(** Binds the standard channels [stdin], [stdout], and [stderr] to be channels
on file descriptors 0, 1, 2, and then calls its 1st argument.
[with_stdio_chans thunk] is equivalent to:
{[ with_stdin (in_channel_of_fd 0)
(fun () -> with_stdout (out_channel_of_fd 1)
(fun () -> with_stderr (out_channel_of_fd 2) thunk))]} *)
(** {3 String channels} *)
(** Ocaml has no string channels, but Cash emulates them with temp files. *)
value make_string_in_channel : string -> in_channel;
(** Returns a channel that reads characters from the supplied string. *)
value make_string_out_channel : unit -> out_channel;
(** A string output channel is a channel that collects the characters given to
it into a string (well, a temp file, in fact). *)
value string_out_channel_output : ?close: bool -> out_channel -> string;
(** The accumulated string is retrieved by applying [string_out_channel_output]
to the channel. You can call this even on a closed channel. However, as the
emulation maintains a hidden input channel on the temp file, you can use a {i
~close:true} argument to close both channels, and free the underlying disk
storage. This will also make the out_channel unrecognised as a string output
channel. *)
value call_with_string_out_channel : ?close: bool -> (out_channel -> unit) -> string;
(** The first arg {i procedure} value is called on a channel. When it returns,
[call_with_string_out_channel] returns a string containing the characters that
were written to that channel during the execution of {i procedure}. *)
(** {3 Revealed channels and file descriptors} *)
(** The material in this section and the following one is not critical for most
applications. You may safely skim or completely skip this section on a first
reading.
Caml doesn't specify what happens to the file descriptor when a channel is
garbage-collected: is it closed or not ? In the following discussion, we
suppose the same behaviour as many Scheme implementations which close channels
when they collect them. Anyway, the same arguments apply when exec'ing another
program.
Dealing with Unix file descriptors in a Caml environment is difficult. In Unix,
open files are part of the process environment, and are referenced by small
integers called {e file descriptors}. Open file descriptors are the fundamental
way I/O redirections are passed to subprocesses, since file descriptors are
preserved across fork's and exec's.
Caml, on the other hand, uses channels for specifying I/O sources. Channels are
garbage-collected Caml objects, not integers. When a channel becomes
unreachable, it can be collected (and the associated file descriptor may be
closed). Because file descriptors are just integers, it's impossible to garbage
collect them --- one wouldn't be able to collect an unreachable channel on file
descriptor 3 unless there were no 3's in the system, and you could further prove
that your program would never again compute a 3. This is difficult at best.
If a Caml program only used Caml channels, and never actually used file
descriptors, this would not be a problem. But Caml code must descend to the file
descriptor level in at least two circumstances:
- when interfacing to foreign code
- when interfacing to a subprocess.
This causes a problem. Suppose we have a Caml channel constructed on top of file
descriptor 2. We intend to fork off a program that will inherit this file
descriptor. If we drop references to the channel, the garbage collector could
prematurely close file 2 before we fork the subprocess. The interface described
below is intended to fix this and other problems arising from the mismatch
between channels and file descriptors.
The Caml runtime maintains a list of open channels, from which one can retrieve
the Caml channel allocated for a given file descriptor. Cash imposes the further
restriction that there is at most one open channel for each open file
descriptor. This is not enforced by the [Unix] module's functions
[{in,out}_channel_of_descr], which will happily allocate a new channel each time
they are called, each with its own buffer, but the system only knows one
position in the file --- the Caml runtime behaviour can be understood, but only
with serious insight (and the sources). So, for [Cash.{in,out}_channel_of_fd]
to be able to give an unambiguous answer (i.e. the previously opened channel on
this fd, not anyone of those already opened, nor a new one --- except if there
was none ---), you should only use the Cash versions. In any case, if there are
more than one channel opened on a file descriptor, these functions will signal
an error. This is nearly the only incompatibility between [Unix] and [Cash]
(the second being that [Unix.open_file] doesn't call [set_close_on_exec]). *)
(** The channel data structure has one Cash-specific field besides the
descriptor: {i revealed}. When a channel is closed with [(close_{in,out}
channel)], the channel's file descriptor is closed, it is unlinked from the open
channel list, and the channel's {i descriptor} field is reset to some "no fd"
value.
When a file descriptor is closed with [(close_fd fdes)], any associated channel
is shifted to a new file descriptor created with [({in,out}_channel_of_dup_fd
fdes)]. The channel has its revealed count reset to zero (and hence becomes
eligible for closing on exec or GC). See discussion below. To really put a
stake through a descriptor's heart without waiting for associated channels to be
closed, you must say one of {[
close_in (in_channel_of_fd fdes)
close_out (out_channel_of_fd fdes)
]}
The {i revealed} field is an aid to garbage collection. It is an integer
semaphore. If it is zero, the channel's file descriptor can be closed when the
channel is collected. Essentially, the {i revealed} field reflects whether or
not the channel's file descriptor has escaped to the Caml user. If the Caml user
doesn't know what file descriptor is associated with a given channel, then he
can't possibly retain an ``integer handle'' on the channel after dropping
pointers to the channel itself, so the garbage collector is free to close the
file.
Channels allocated with [open_in] and [open_out] are unrevealed channels --- {i
i.e.}, {i revealed} is initialised to 0. No one knows the channel's file
descriptor, so the file descriptor can be closed when the channel is collected.
The functions [{in,out}_channel_of_fd] and [fd_of_{in,out}_channel] are used to
shift back and forth between file descriptors and channels. When
[fd_of_{in,out}_channel] reveals a channel's file descriptor, it increments the
channel's {i revealed} field. When the user is through with the file
descriptor, he can call [release_{in,out}_channel_handle] {i channel}, which
decrements the count. The functions [call_with_fdes_{in,out}] {i channel proc}
automate this protocol. If {i proc} throws out of the [call_with_fdes_...]
application, the exception is caught, the descriptor handle released, then the
exception is re-raised. When the user maps a file descriptor to a channel with
[{in,out}_channel_of_fd], the channel has its revealed field incremented.
Not all file descriptors are created by requests to make channels. Some are
inherited on process invocation via [exec(2)], and are simply part of the global
environment. Subprocesses may depend upon them, so if a channel is later
allocated for these file descriptors, is should be considered as a revealed
channel. For example, when the Caml shell's process starts up, it opens channels
on file descriptors 0, 1, and 2 for the initial values of [stdin], [stdout], and
[stderr]. These channels are initialised with {i revealed} set to 1, so that
[stdin], [stdout], and [stderr] are not closed even if the user drops the
channel.
*)(**
Unrevealed file channels have the nice property that they can be closed when all
pointers to the channel are dropped. This can happen during gc, or at an
[exec()] --- since all memory is dropped at an [exec()]. No one knows the
file descriptor associated with the channel, so the exec'd process certainly
can't refer to it.
This facility preserves the transparent may-close-on-collect property for file
channels that are used in straightforward ways, yet allows access to the
underlying Unix substrate without interference from the garbage collector. This
is critical, since shell programming absolutely requires access to the Unix file
descriptors, as their numerical values are a critical part of the process
interface.
A channel's underlying file descriptor can be shifted around with [dup(2)] when
convenient. That is, the actual file descriptor on top of which a channel is
constructed can be shifted around underneath the channel by the Cash runtime
when necessary. This is important, because when the user is setting up file
descriptors prior to a [exec(2)], he may explicitly use a file descriptor that
has already been allocated to some channel. In this case, the Cash runtime just
shifts the channel's file descriptor to some new location with [dup], freeing up
its old descriptor. This prevents errors from happening in the following
scenario. Suppose we have a file open on channel {i f}. Now we want to run a
program that reads input on file 0, writes output to file 1, errors to file 2,
and logs execution information on file 3. We want to run this program with input
from {i f}. So we write (in an sh-like syntax, since Cash pipeline syntax is
not fixed for now --- here, {i $f$} denotes a Caml input channel):
*) (**
[ <<run "/usr/shivers/bin/prog 1>output.txt 2>error.log 3>trace.log 0<]{i $f$}[">>]
Now, suppose by ill chance that, unbeknownst to us, when the operating system
opened {i f}'s file, it allocated descriptor 3 for it. If we blindly redirect
[trace.log] into file descriptor 3, we'll clobber {i f} ! However, the
channel-shuffling machinery saves us: when the [<<run ...>>] form tries to dup
[trace.log]'s file descriptor to 3, [dup] will notice that file descriptor 3 is
already associated with an unrevealed channel ({i i.e.}, {i f}). So, it will
first move {i f} to some other file descriptor. This keeps {i f} alive and well
so that it can subsequently be dup'd into descriptor 0 for [prog]'s stdin.
The channel-shifting machinery makes the following guarantee: a channel is only
moved when the underlying file descriptor is closed, either by a [close()] or a
[dup2()] operation. Also when explicitly asked by {!Cash.with_stdin},
[set_stdout] and consorts. Otherwise a channel/file-descriptor association is
stable.
Under normal circumstances, all this machinery just works behind the scenes to
keep things straightened out. The only time the user has to think about it is
when he starts accessing file descriptors from channels, which he should almost
never have to do. If a user starts asking what file descriptors have been
allocated to what channels, he has to take responsibility for managing this
information.
*)
(** {3 Channel-mapping machinery} *)
(** The procedures provided in this section are almost never needed.
You may safely skim or completely skip this section on a first reading.
Here are the routines for manipulating channels in Cash. The important points to
remember are:
- A channel is associated with an open file, not a particular file descriptor.
- The association between a channel and a particular file descriptor is never
changed {i except} when requested by [set_std...] or [with_std...]
(see {!Cash.withstd}), or when the file descriptor is explicitly closed.
``Closing'' includes being used as the target of a [dup2], so the set of
procedures below that close their targets are [close_...], two-argument
[dup_...], and [move_..._to_fdes]. If the target file descriptor of one of
these routines has an allocated channel, the channel will be shifted to
another freshly-allocated file descriptor, and marked as unrevealed, thus
preserving the channel but freeing its old file descriptor.
These rules are what is necessary to ``make things work out'' with no surprises
in the general case. *)
(* p 30. *)
value in_channel_of_fd : fd -> in_channel;
value out_channel_of_fd : fd -> out_channel;
value fd_of_in_channel : in_channel -> fd;
value fd_of_out_channel : out_channel -> fd;
(** These increment the channel's revealed count. *)
external in_channel_revealed : in_channel -> int = "chan_revealed_count";
external out_channel_revealed : out_channel -> int = "chan_revealed_count";
(** Return the channel's revealed count. *)
external release_in_channel_handle : in_channel -> unit = "release_chan_handle";
external release_out_channel_handle : out_channel -> unit = "release_chan_handle";
(** Decrement the channel's revealed count. *)
(* Scsh uses the inverse order -- don't ask me why I changed it. *)
value call_with_fdes_in : (fd -> 'a) -> in_channel -> 'a;
value call_with_fdes_out : (fd -> 'a) -> out_channel -> 'a;
(** [call_with_fdes_...] {i consumer channel} calls {i consumer} on the file
descriptor underlying {i channel}; takes care of revealed bookkeeping.
While {i consumer} is running, the {i channel}'s revealed count is incremented. *)
(** Mapping [fd] -> [fd] and [channel] -> [channel]. *)
value move_fd_to_fdes : fd -> fd -> fd;
(** [move_fd_to_fdes] {i fd target-fd}: if [fd] is a file-descriptor not equal
to {i target-fd}, dup it to {i target-fd} and close it. Returns {i target-fd}. *)
value move_in_channel_to_fdes : in_channel -> fd -> in_channel;
value move_out_channel_to_fdes : out_channel -> fd -> out_channel;
(** [move_{in,out}_channel_to_fdes] {i channel target-fd}: {i channel} is
shifted to {i target-fd}, by duping its underlying file-descriptor if necessary.
{i channel}'s original file descriptor is closed (if it was different from {i
target-fd}). Returns the channel. This operation resets {i channel}'s revealed
count to 1. *)
(** In all cases when {i fd} or {i channel} is actually shifted, if there is a
channel already using {i target-fd}, it is first relocated to some other file
descriptor. *)
(* p 31. *)
(** {3:unixio Unix I/O} *)
(** The 9 next procedures provide the functionality of C's [dup()] and
[dup2()]. The X_of_dup_Y ones convert any fd/channel to any other kind, and
X_of_dup_X is named dup_X. *)
value dup_fd : ?newfd: fd -> fd -> fd;
value fdes_of_dup_in : ?newfd: fd -> in_channel -> fd;
value fdes_of_dup_out : ?newfd: fd -> out_channel -> fd;
value in_channel_of_dup_fd : ?newfd: fd -> fd -> in_channel;
value dup_in : ?newfd: fd -> in_channel -> in_channel;
value in_channel_of_dup_out : ?newfd: fd -> out_channel -> in_channel;
value out_channel_of_dup_fd : ?newfd: fd -> fd -> out_channel;
value out_channel_of_dup_in : ?newfd: fd -> in_channel -> out_channel;
value dup_out : ?newfd: fd -> out_channel -> out_channel;
(** These procedures use the Unix [dup()] syscall to replicate their fd/channel
last argument. If a [newfd] file descriptor is given, it is used as the target
of the dup operation, i.e., the operation is a [dup2()]. In this case,
procedures that return a channel (such as [dup_in]) will return one with the
revealed count set to one. For example, [dup_in ~newfd:5 stdin] produces a new
channel with underlying file descriptor 5, whose revealed count is 1. If
[newfd] is not specified, then the operating system chooses the file descriptor,
and any returned channel is marked as unrevealed.
If the [newfd] target is given, and some channel is already using that file
descriptor, the channel is first quietly shifted (with another [dup]) to some
other file descriptor (zeroing its revealed count).
Since Caml doesn't provide read/write channels, [{in,out}_channel_of_dup_in] can
be useful for getting an output version of an input channel, or {i vice versa}.
For example, if [p] is an input channel open on a tty, and we would like to do
output to that tty, we can simply use [out_channel_of_dup_in p] to produce an
equivalent output channel for the tty. However, you are responsible for the
open modes of the channel when doing so. *)
(** Positioning modes for [seek_...] *)
type seek_command =
Unix.seek_command ==
[ SEEK_SET (** positions are relative to the beginning of the file *)
| SEEK_CUR (** positions are relative to the current position *)
| SEEK_END ](** positions are relative to the end of the file *)
;
value seek_fd : ?whence: seek_command -> fd -> int -> int;
value seek_in : ?whence: seek_command -> in_channel -> int -> int;
value seek_out : ?whence: seek_command -> out_channel -> int -> int;
(** Reposition the I/O cursor for a file descriptor or channel. This gives the
[Unix.lseek] functionality applied to fd's and channels. Not all such values
are seekable; this is dependent on the OS implementation. The return value is
the resulting position of the I/O cursor in the I/O stream. *)
value tell_fd : fd -> int;
value tell_in : in_channel -> int;
value tell_out : out_channel -> int;
(** Return the position of the I/O cursor in the the I/O stream. Not all file
descriptors or channels support cursor-reporting; this is dependent on the OS
implementation. *)
type file_perm = int;
(* p 32. *)
(** The flags to [open_file_...]. *)
type open_flag =
Unix.open_flag ==
[ O_RDONLY (** Open for reading *)
| O_WRONLY (** Open for writing *)
| O_RDWR (** Open for reading and writing *)
| O_NONBLOCK (** Open in non-blocking mode *)
| O_APPEND (** Open for append *)
| O_CREAT (** Create if nonexistent *)
| O_TRUNC (** Truncate to 0 length if existing *)
| O_EXCL (** Fail if existing *)
| O_NOCTTY (** Don't make this dev a controlling tty *)
| O_DSYNC (** Writes complete as `Synchronised I/O data integrity completion' *)
| O_SYNC (** Writes complete as `Synchronised I/O file integrity completion' *)
| O_RSYNC ] (** Reads complete as writes (depending on O_SYNC/O_DSYNC) *)
;
value open_file_out :
?perms: file_perm -> string -> list open_flag -> out_channel;
value open_file_in :
?perms: file_perm -> string -> list open_flag -> in_channel;
(** [open_file_...] {i ~perms fname flags}: {i perms} defaults to [0o666].
{i flags} is a list of [open_flag]s. You must use exactly one of the
[O_RDONLY], [O_WRONLY], or [O_RDWR] flags.
Caml do not have input/output channels, so it's one or the other. (You can
hack simultaneous I/O on a file by opening it R/W, taking the result input
channel, and duping it to an output channel with [out_channel_of_dup_in].) *)
value open_fdes : ?perms: file_perm -> string -> list open_flag -> fd;
(** Same as [open_file_{in,out}], but returns a file descriptor. *)
value open_input_file : ?flags: (list open_flag) -> string -> in_channel;
value open_output_file :
?flags: (list open_flag) -> ?perms: file_perm -> string -> out_channel;
(** These are equivalent to [open_file_...], after adding the
read/write mode to the [flags] argument to [O_RDONLY] or [O_WRONLY],
respectively (so don't use them). [Flags] defaults to [] for
[open_input_file], and [\[O_CREAT; O_TRUNC\]] for [open_output_file]. These
procedures are for compatibility with Scsh, and the defaults make the
procedures backwards-compatible with their Scheme standard unary definitions.
*)
value openfile : string -> list open_flag -> file_perm -> Unix.file_descr;
(** For Cash proper operation, you must use this openfile in place of the Unix
one; ours calls [Unix.set_close_on_exec] on the returned file_descr, to make
all the channel-mapping machinery work smoothly. *)
(** The 6 procedures below are for compatibility with Scheme. *)
value with_input_from_file : string -> (unit -> 'a) -> 'a;
value with_output_to_file : string -> (unit -> 'a) -> 'a;
value with_errors_to_file : string -> (unit -> 'a) -> 'a;
(** The file named by the first argument is opened for input or output, an input
or output channel connected to it is made into [stdin], [stdout] or [stderr]
(see [with_std...] in {!Cash.withstd}) and the thunk is called. When it
returns, the channel is closed and the previous default is restored. The value
yielded by the thunk is returned.
Note: the open functions used are [Pervasives.open_{in,out}]. If you need
[open_file_{in,out}], it's easy enough to cook up your own wrapper with
[unwind_protect] and [with_std...]
*)
value call_with_input_file : string -> (in_channel -> 'a) -> 'a;
value call_with_output_file : string -> (out_channel -> 'a) -> 'a;
value call_with_fdes_fn :
?perms: file_perm -> string -> list open_flag -> (fd -> 'a) -> 'a;
(** [call_with_...] {i .. filename .. proc} call {i proc} with a channel or fd
opened on {i filename}. This channel or fd is closed before returning the
value yielded by {i proc}. The open procedures used are
[Pervasives.open_{in,out}] and {!Cash.open_fdes}. *)
type fdes_flags = [ FD_CLOEXEC ];
value fdes_flags_fd : fd -> list fdes_flags;
value fdes_flags_in : in_channel -> list fdes_flags;
value fdes_flags_out : out_channel -> list fdes_flags;
value set_fdes_flags_fd : fd -> list fdes_flags -> unit;
value set_fdes_flags_in : in_channel -> list fdes_flags -> unit;
value set_fdes_flags_out : out_channel -> list fdes_flags -> unit;
(** These procedures allow reading and writing of an open file's flags. The
only such flag defined by Posix is [FD_CLOEXEC]; your Unix implementation may
provide others.
These procedures should not be particularly useful to the programmer, as the
Cash runtime already provides automatic control of the close-on-exec property,
as long as you don't use [Unix.open_file] without using [Unix.set_close_on_exec]
immediately ({!Cash.openfile} does it for you) -- this is the second
incompatibility with [Unix] (the first being [Unix.{in,out}_channel_of_descr]).
Unrevealed channels always have their file descriptors marked close-on-exec, as
they can be closed when the Cash process execs a new program. Whenever the user
reveals or unreveals a channel's file descriptor, the runtime automatically sets
or clears the flag for the programmer. Programmers that manipulate this flag
should be aware of these extra, automatic operations. *)
(* p 33. *)
value fdes_status_fd : fd -> list open_flag;
value fdes_status_in : in_channel -> list open_flag;
value fdes_status_out : out_channel -> list open_flag;
value set_fdes_status_fd : fd -> list open_flag -> unit;
value set_fdes_status_in : in_channel -> list open_flag -> unit;
value set_fdes_status_out : out_channel -> list open_flag -> unit;
(** These procedures allow reading and writing of an open file's status flags
(see table below). Note that this file-descriptor state is shared between file
descriptors created by [dup_..] (and [fork...]) --- if you create channel {i b}
by applying [dup_...] to channel {i a}, and change {i b}'s status flags, you
will also have changed {i a}'s status flags. *)
(** Status flags for [open_file_...], [fdes_status_...] and
[set_fdes_status_...]. *)
(* The [O_ACCMODE] value is not an actual flag, but a bit mask used to select
the field for the [O_RDONLY], [O_WRONLY] and [O_RDWR] bits. *)
(**
{v
Allowed operations Status flag
--------------------------------------------------------------
Open+Get+Set These flags can be used O_APPEND
in open_file_..., O_NONBLOCK
fdes_status_... and O_SYNC (...SYNC aren't
set_fdes_status_... calls O_DSYNC impl. in 3.04)
O_RSYNC
--------------------------------------------------------------
Open+Get These flags can be used O_RDONLY
in open_file_... and O_WRONLY
fdes_status_... calls, but are O_RDWR
ignored by set_fdes_status_...
--------------------------------------------------------------
Open These flags are only relevant O_CREAT
for open_file_... calls; they O_EXCL
are ignored by fdes_status_... O_NOCTTY (not impl. in 3.04)
and set_fdes_status_... calls. O_TRUNC
v}
*)
value pipe : unit -> (in_channel * out_channel);
(** Returns two channels, the read and write end-points of a Unix pipe. *)
(** [fold_input] {i f init reader source} is a folder in the sort of
List.fold_left, but instead of folding a 'a list, you give it a {i reader}
function (such as [read_line]), and a {i source} (as [stdin]). The elements
to fold are computed by applying {i read_line} to the {i source}. For
directories, see {!Cash.fold_directory} and {!Cash.directory_files}. *)
value fold_input : ('a -> 'b -> 'a) -> 'a -> ('c -> 'b) -> 'c -> 'a;
(** Note about the [read_string...] procedures: the ones with a [?src:fd]
argument (default to fd 0, and) directly use [Unix.read]. But those with a
[?src:in_channel] argument (default to [stdin], and) use [Pervasives.input];
this is to permit seamless mixing of calls to these procedures and Pervasives
I/O operations. For large blocks of data, it might be more efficient to use
[read_string ~src:(fd_of_in_channel chan)], since there's no buffering, but
mixing this with Pervasives may require synchronization ([seek_in]) which is to
your charge. Similar considerations apply to write_string... *)
type error_packet =
[ Sys__error of string
| Unix__error of (Unix.error * string * string) ]
;
exception
String_io_error of (error_packet * string * string * int * int * int);
(** This is the exception raised by the
read_string.../write_string... procedures. It contains the original
error_packet, the name of the procedure, the string the operation has been
attempted on, the start index, the index upto which data has already been
read/written, the end_ index. *)
value read_string : ?src: fd -> int -> string;
value read_string_in : ?src: in_channel -> int -> string;
value read_string_bang : ?src: fd -> ?start: int -> ?end_: int -> string -> int;
value read_string_bang_in :
?src: in_channel -> ?start: int -> ?end_: int -> string -> int;
(** These calls read exactly as much data as you requested, unless there is not
enough data (eof). [read_string_bang...] {i str} reads the data into string {i
str} at the indices in the half-open interval \[{i start},{i end_}); the default
interval is the whole string: {i start} [= 0] and {i end_} [= String.length] {i
str}. They will persistently retry on partial reads and when interrupted until
(1) error, (2) eof, or (3) the input request is completely satisfied. Partial
reads can occur when reading from an intermittent source, such as a pipe or tty.
[read_string{,in}] returns the string read; [read_string_bang...] returns the
number of characters read. They both raise [End_of_file] at eof. A request to
read zero bytes returns immediately, with no eof check.
The values of [start] and [end_] must specify a well-defined interval in [str],
i.e., [0 <= start <= end_ <= String.length str].
Any partially-read data is included in the error exception packet. Error
returns on non-blocking input are considered an error. *)
(* p 34. *)
value read_string_partial : ?src: fd -> int -> string;
value read_string_partial_in : ?src: in_channel -> int -> string;
value read_string_bang_partial : ?src: fd -> ?start: int -> ?end_: int -> string -> int;
value read_string_bang_partial_in :
?src: in_channel -> ?start: int -> ?end_: int -> string -> int;
(** These are atomic best-effort/forward-progress calls. Best effort: they may
read less than you request if there is a lesser amount of data immediately
available ({i e.g.}, because you are reading from a pipe or a tty). Forward
progress: if no data is immediately available ({i e.g.}, empty pipe), they will
block. Therefore, if you request an {i n > 0} byte read, while you may not get
everything you asked for, you will always get something (barring eof).
There is one case in which the forward-progress guarantee is cancelled: when the
programmer explicitly sets the channel to non-blocking i/o. In this case, if no
data is immediately available, the procedure will not block, but will