## winix-text-file-for-os-g--premicrothread.pkg
#
# Here we combine the platform-specific code passed in as wxd
# with our platform-independent body code to produce a full
# platform-specific text-file I/O implementation.
#
# In other words, the function of this package is to factor
# out the code in common between posix and winix textfiles,
# to avoid code duplication.
#
# At the moment it looks like some posix-specific code has
# crept into the body of this generic. (My fault, I expect.)
# This needs to be fixed if we start actually supporting
# win32 again.
# -- CrT 2012-03-06
#
# This version targets monothreaded code, so threadkit provides an alternate:
#
#
src/lib/std/src/io/winix-text-file-for-os-g.pkg# Compiled by:
#
src/lib/std/src/standard-core.sublib#
# QUESTION: what operations should raise exceptions when the stream is
# closed? XXX BUGGO FIXME
stipulate
package at = run_at__premicrothread; # run_at__premicrothread is from
src/lib/std/src/nj/run-at--premicrothread.pkg package ci = mythryl_callable_c_library_interface; # mythryl_callable_c_library_interface is from
src/lib/std/src/unsafe/mythryl-callable-c-library-interface.pkg package eow = io_startup_and_shutdown__premicrothread; # "eow" == "end of world" # io_startup_and_shutdown__premicrothread is from
src/lib/std/src/io/io-startup-and-shutdown--premicrothread.pkg package hth = hostthread; # hostthread is from
src/lib/std/src/hostthread.pkg package int = int_guts; # int_guts is from
src/lib/std/src/int-guts.pkg package iox = io_exceptions; # io_exceptions is from
src/lib/std/src/io/io-exceptions.pkg package ns = number_string; # number_string is from
src/lib/std/src/number-string.pkg package psx = posixlib; # posixlib is from
src/lib/std/src/psx/posixlib.pkg package rt = runtime; # runtime is from
src/lib/core/init/runtime.pkg package stc = string_chartype; # string_chartype is from
src/lib/std/src/string-chartype.pkg package str = string_guts; # string_guts is from
src/lib/std/src/string-guts.pkg package u1w = one_word_unt_guts; # one_word_unt_guts is from
src/lib/std/src/one-word-unt-guts.pkg #
package rvc = vector_of_chars; # vector_of_chars is from
src/lib/std/src/vector-of-chars.pkg package vsc = vector_slice_of_chars; # vector_slice_of_chars is from
src/lib/std/src/vector-slice-of-chars.pkg package wsc = rw_vector_slice_of_chars; # rw_vector_slice_of_chars is from
src/lib/std/src/rw-vector-slice-of-chars.pkg package wvc = rw_vector_of_chars; # rw_vector_of_chars is from
src/lib/std/src/rw-vector-of-chars.pkg #
package wnx = winix_guts; # winix_guts is from
src/lib/std/src/posix/winix-guts.pkg package wty = winix_types; # winix_types is from
src/lib/std/src/posix/winix-types.pkgherein
# Winix_Text_File_For_Os__Premicrothread is from
src/lib/std/src/io/winix-text-file-for-os--premicrothread.api # This generic is invoked by:
#
#
src/lib/std/src/posix/winix-text-file-for-posix--premicrothread.pkg #
src/lib/std/src/win32/winix-text-file-for-win32--premicrothread.pkg #
generic package winix_text_file_for_os_g__premicrothread (
# ========================================
#
# On unix below argument will be # winix_text_file_io_driver_for_posix__premicrothread is from
src/lib/std/src/posix/winix-text-file-io-driver-for-posix--premicrothread.pkg # On win32 below argument will be # win32_text_file_io_driver_for_win32__premicrothread is from
src/lib/std/src/win32/winix-text-file-io-driver-for-win32--premicrothread.pkg package wxd # "wxd" == "WiniX file i/o Driver".
:
api {
include api Winix_Extended_File_Io_Driver_For_Os__Premicrothread; # Winix_Extended_File_Io_Driver_For_Os__Premicrothread is from
src/lib/std/src/io/winix-extended-file-io-driver-for-os--premicrothread.api stdin: Void -> drv::Filereader;
stdout: Void -> drv::Filewriter;
stderr: Void -> drv::Filewriter;
#
string_reader: String -> drv::Filereader;
}
where
drv == winix_base_text_file_io_driver_for_posix__premicrothread;
)
: (weak) Winix_Text_File_For_Os__Premicrothread # Winix_Text_File_For_Os__Premicrothread is from
src/lib/std/src/io/winix-text-file-for-os--premicrothread.api {
package drv = wxd::drv; # Exported to clients.
some_element = '\000';
#
# An element for initializing buffers.
# # Fast, but unsafe version (from vector_of_chars):
# vecSub = inline_t::vector_of_chars::get
# arrUpdate = inline_t::rw_vector_of_chars::update
#
# # Fast vector extract operation.
# # This should never be called with a length of 0.
#
# fun vecExtract (v, base, optLen)
# =
# ( len = rvc::length v;
#
# fun newVec n = let
# newV = Assembly::a::make_string n
# fun fill i = if (i < n)
# then (
# inline_t::vector_of_chars::update (newV, i, vecSub (v, base+i));
# fill (i+1))
# else ()
# in
# fill 0; newV
# end;
#
# case (base, optLen)
# (0, NULL) => v;
# (_, NULL) => newVec (len - base);
# (_, THE n) => newVec n;
# esac
# )
vec_extract = vsc::to_vector o vsc::make_slice;
vec_get = rvc::get;
rw_vec_set = wvc::set;
burst_substring = substring::burst_substring;
empty = "";
# Return TRUE iff we can stat the given filename:
#
fun exists (filename: String) # Or directory name or whatever.
=
{ psx::stat filename;
TRUE; # If we can 'stat' it, it exists. So far as we're concerned. :-)
}
except
rt::RUNTIME_EXCEPTION _
=
FALSE; # If we cannot 'stat' it, it doesn't exist.
package pur { # "pur" == "pure" (I/O). Exported to clients.
#
Vector = rvc::Vector;
Element = rvc::Element;
#
Filereader = drv::Filereader;
Filewriter = drv::Filewriter;
File_Position = drv::File_Position;
# *** Functional input streams ***
#
# We represent an Input_Stream by a pointer to a buffer and an offset
# into the buffer. The buffers are chained by the "next" field from
# the beginning of the stream towards the end. If the "next" field
# is LAST, then it refers to an empty buffer (consuming the EOF marker
# involves moving the stream from immediately in front of the LAST
# to the empty buffer). A "next" field of TERMINATED marks a
# terminated stream. We also have the invariant that the "last_nextref"
# field of the "global_file_stuff" record points to a 'next' REF that is either
# NO_NEXT or TERMINATED.
Input_Stream = INPUT_STREAM (Input_Buffer, Int)
also
Input_Buffer
=
INPUT_BUFFER
{
data: Vector, # The actual input text for this buffer.
file_position: Null_Or( File_Position ), # Offset of 'data' contents within file as a whole.
#
next: Ref( Next ), # Next input buffer in the stream, if any.
global_file_stuff: Global_File_Stuff # Everything purtaining to the input stream as a whole goes in this record.
} # All input buffers in a given stream share a single global_file_stuff record.
also
Next
= NEXT Input_Buffer # Forward link to additional data.
| LAST Input_Buffer
# End-of-stream marker.
| NO_NEXT
# Placeholder for forward link.
| TERMINATED
# Termination of the stream.
also
Global_File_Stuff
=
GLOBAL_FILE_STUFF
{
filereader: Filereader, # This provides our low-level platform-dependent file I/O functionality.
#
read_vector: Int -> Vector,
#
get_file_position: Void -> Null_Or( File_Position ),
#
clean_tag: eow::Tag,
#
is_closed: Ref( Bool ),
last_nextref: Ref( Ref(Next) ) # Points to the NEXT cell of the last buffer.
};
#
fun global_file_stuff_of_ibuf (INPUT_BUFFER i)
=
i.global_file_stuff;
#
fun best_io_quantum_of_ibuf buf
=
{ (global_file_stuff_of_ibuf buf)
->
GLOBAL_FILE_STUFF { filereader => drv::FILEREADER rd, ... };
rd.best_io_quantum;
};
#
fun read_vector (INPUT_BUFFER { global_file_stuff => GLOBAL_FILE_STUFF i, ... } )
=
i.read_vector;
#
fun raise_io_exception (GLOBAL_FILE_STUFF { filereader => drv::FILEREADER { filename, ... }, ... }, op, cause)
=
raise exception iox::IO { op, name => filename, cause };
#
fun extend_stream (read_fn, ml_op, buf as INPUT_BUFFER { next, global_file_stuff, ... } ) # Read 4K or so from the file and append it to the buffer list as a new INPUT_BUFFER.
=
{ global_file_stuff -> GLOBAL_FILE_STUFF { get_file_position, last_nextref, ... };
#
file_position = get_file_position ();
data = read_fn (best_io_quantum_of_ibuf buf);
new_next = REF NO_NEXT;
buf' = INPUT_BUFFER { file_position, data, global_file_stuff, next => new_next };
result = if (rvc::length data == 0) LAST buf';
else NEXT buf';
fi;
next := result;
last_nextref := new_next;
result;
}
except
ex = raise_io_exception (global_file_stuff, ml_op, ex);
#
fun get_next_buffer (read_fn, ml_op) (buf as INPUT_BUFFER { next, global_file_stuff, ... } )
=
case *next
#
TERMINATED => LAST buf;
NO_NEXT => extend_stream (read_fn, ml_op, buf);
next => next;
esac;
# Read a chunk that is at least
# the specified size:
#
fun read_chunk buf
=
{ (global_file_stuff_of_ibuf buf)
->
GLOBAL_FILE_STUFF { read_vector, filereader => drv::FILEREADER { best_io_quantum, ... }, ... };
case (best_io_quantum - 1)
#
0 => (\\ n = read_vector n);
#
k => (\\ n # Round up to next multiple of best_io_quantum.
=
read_vector (int::quot((n+k), best_io_quantum) * best_io_quantum));
esac;
};
#
fun generalized_input get_buf
=
get
where
fun get (INPUT_STREAM (buf as INPUT_BUFFER { data, ... }, pos))
=
{ len = rvc::length data;
#
if (pos < len)
#
( vec_extract (data, pos, NULL),
INPUT_STREAM (buf, len)
);
else
case (get_buf buf)
#
LAST buf => (empty, INPUT_STREAM (buf, 0));
NEXT rest => get (INPUT_STREAM (rest, 0));
_ => raise exception DIE "bogus get_buf";
esac;
fi;
};
end;
# Terminate an input stream:
#
fun terminate (GLOBAL_FILE_STUFF { last_nextref, clean_tag, ... } )
=
case *last_nextref
#
m as REF NO_NEXT
=>
{ eow::drop_stream_startup_and_shutdown_actions clean_tag;
#
m := TERMINATED;
};
m as REF TERMINATED
=>
();
_ => raise exception MATCH; # Quiet compiler.
esac;
#
fun read (stream as INPUT_STREAM (buf, _))
=
generalized_input
(get_next_buffer (read_vector buf, "read"))
stream;
#
fun read_one (INPUT_STREAM (buf, pos))
=
{ buf -> INPUT_BUFFER { data, next, ... };
if (pos < rvc::length data)
#
THE (vec_get (data, pos), INPUT_STREAM (buf, pos+1));
else
case *next
#
NEXT buf => read_one (INPUT_STREAM (buf, 0));
LAST _ => NULL;
#
NO_NEXT
=>
case (extend_stream (read_vector buf, "read_one", buf))
#
NEXT rest => read_one (INPUT_STREAM (rest, 0));
_ => NULL;
esac;
TERMINATED
=>
NULL;
esac;
fi;
};
#
fun read_n (INPUT_STREAM (buf, pos), n)
=
{ fun join (item, (list, stream))
=
(item ! list, stream);
#
fun input_list (buf as INPUT_BUFFER { data, ... }, i, n)
=
{ len = rvc::length data;
remain = len-i;
if (remain >= n)
#
([vec_extract (data, i, THE n)], INPUT_STREAM (buf, i+n));
else
join (
vec_extract (data, i, NULL),
next_buf (buf, n-remain)
);
fi;
}
also
fun next_buf (buf as INPUT_BUFFER { next, data, ... }, n)
=
case *next
#
NEXT buf => input_list (buf, 0, n);
LAST buf => ([], INPUT_STREAM (buf, 0));
#
NO_NEXT
=>
case (extend_stream (read_vector buf, "read_n", buf))
#
NEXT rest => input_list (rest, 0, n);
#
_ => ([], INPUT_STREAM (buf, rvc::length data));
esac;
TERMINATED
=>
([], INPUT_STREAM (buf, rvc::length data));
esac;
my (data, stream)
=
input_list (buf, pos, n);
(rvc::cat data, stream);
};
#
fun read_all (stream as INPUT_STREAM (buf, _))
=
{
(global_file_stuff_of_ibuf buf)
->
GLOBAL_FILE_STUFF { filereader => drv::FILEREADER { avail, ... }, ... };
# Read a chunk that is as large as the available input.
# Note that for systems that use CR-LF for '\n', the
# size will be too large, but this should be okay.
#
fun big_chunk _
=
read_chunk buf delta
where
delta = case (avail ())
#
NULL => best_io_quantum_of_ibuf buf;
THE n => n;
esac;
end;
big_input
=
generalized_input (get_next_buffer (big_chunk, "read_all"));
#
fun loop (v, stream)
=
if (rvc::length v == 0)
#
([], stream);
else
(loop (big_input stream))
->
(l, stream');
(v ! l, stream');
fi;
(loop (big_input stream))
->
(data, stream');
(rvc::cat data, stream');
};
#
fun close_input (INPUT_STREAM (buf, _))
=
case (global_file_stuff_of_ibuf buf)
#
GLOBAL_FILE_STUFF { is_closed => REF TRUE, ... }
=>
();
global_file_stuff as GLOBAL_FILE_STUFF { is_closed, filereader => drv::FILEREADER { close, ... }, ... }
=>
{ terminate global_file_stuff;
#
is_closed := TRUE;
#
close ()
except
ex = raise_io_exception (global_file_stuff, "close_input", ex);
};
esac;
#
fun end_of_stream (INPUT_STREAM (buf, pos))
=
case buf
#
INPUT_BUFFER { next=>REF (NEXT _), ... } => FALSE;
INPUT_BUFFER { next=>REF (LAST _), ... } => TRUE;
#
INPUT_BUFFER { next, data, global_file_stuff=>GLOBAL_FILE_STUFF { is_closed, ... }, ... }
=>
if (pos != rvc::length data)
#
FALSE;
else
case (*next, *is_closed)
#
(NO_NEXT, FALSE)
=>
case (extend_stream (read_vector buf, "end_of_stream", buf))
#
LAST _ => TRUE;
_ => FALSE;
esac;
_ => TRUE;
esac;
fi;
esac;
#
fun make_instream (filereader, data)
=
{ filereader -> drv::FILEREADER { read_vector, get_file_position, set_file_position, ... };
#
read_vector' = read_vector;
get_file_position
=
case (get_file_position, set_file_position)
#
(THE f, THE _) => \\ () = THE (f());
_ => \\ () = NULL;
esac;
next = REF NO_NEXT;
is_closed = REF FALSE;
clean_tag = eow::note_stream_startup_and_shutdown_actions
{
init => \\ () = is_closed := TRUE, # Executed at STARTUP_PHASE_11_OF_HEAP_MADE_BY_*_TO_DISK by run() in
src/lib/std/src/io/io-startup-and-shutdown--premicrothread.pkg flush => \\ () = (),
close => \\ () = is_closed := TRUE # Executed at SHUTDOWN_PHASE_6_CLOSE_OPEN_FILES by run() in
src/lib/std/src/io/io-startup-and-shutdown--premicrothread.pkg };
global_file_stuff
=
GLOBAL_FILE_STUFF
{
filereader,
get_file_position,
read_vector => read_vector',
#
is_closed,
last_nextref => REF next,
clean_tag
};
# What should we do about the position when there is initial data?
# Suggestion: When building a stream with supplied initial data,
# nothing can be said about the positions inside that initial
# data (who knows where that data even came from!).
file_position
=
if (rvc::length data == 0) get_file_position ();
else NULL;
fi;
INPUT_STREAM(
INPUT_BUFFER { file_position, data, global_file_stuff, next },
0
);
};
#
fun get_reader (INPUT_STREAM (buf, pos))
=
{ buf -> INPUT_BUFFER
{ data,
global_file_stuff as GLOBAL_FILE_STUFF { filereader, ... },
next,
...
};
#
fun get_data (NEXT (INPUT_BUFFER { data, next, ... } ))
=>
data ! get_data *next;
get_data _
=>
[];
end;
terminate global_file_stuff;
if (pos < rvc::length data)
#
( filereader,
rvc::cat (vec_extract (data, pos, NULL) ! get_data *next)
);
else
( filereader,
rvc::cat (get_data *next)
);
fi;
};
# Get the underlying file position of a stream:
#
fun file_position_in (INPUT_STREAM (buf, pos))
=
case buf
#
INPUT_BUFFER { file_position=>NULL, global_file_stuff, ... }
=>
raise_io_exception (global_file_stuff, "filePosIn", iox::RANDOM_ACCESS_IO_NOT_SUPPORTED);
INPUT_BUFFER { file_position => THE base, global_file_stuff, ... }
=>
{ global_file_stuff -> GLOBAL_FILE_STUFF { filereader => drv::FILEREADER rd, read_vector, ... };
#
case (rd.get_file_position, rd.set_file_position)
#
( THE get_file_position,
THE set_file_position
)
=>
{ tmp_pos = get_file_position ();
#
fun read_n 0
=>
();
read_n n
=>
case (rvc::length (read_vector n))
#
0 => raise_io_exception (global_file_stuff, "filePosIn", DIE "bogus position");
k => read_n (n-k);
esac;
end;
set_file_position base;
read_n pos;
get_file_position ()
then
set_file_position tmp_pos;
};
_ => raise exception DIE "filePosIn: impossible";
esac;
};
esac;
# Operations only for text streams:
#
fun read_line (INPUT_STREAM (buf as INPUT_BUFFER { data, next, ... }, pos))
=
{ fun join (item, (list, stream))
=
(item ! list, stream);
#
fun next_buf (buf as INPUT_BUFFER { next, data, ... } )
=
{ fun last ()
=
(["\n"], INPUT_STREAM (buf, rvc::length data));
case *next
#
NEXT buf => scan_data (buf, 0);
LAST buf => last ();
#
NO_NEXT
=>
case (extend_stream (read_vector buf, "read_line", buf))
#
LAST _ => last ();
NEXT rest => scan_data (rest, 0);
_ => raise exception MATCH;
esac;
TERMINATED
=>
last ();
esac;
}
also
fun scan_data (buf as INPUT_BUFFER { data, next, ... }, i)
=
{ len = rvc::length data;
#
fun scan j
=
if (j == len)
#
join (vec_extract (data, i, NULL), next_buf buf);
else
if (vec_get (data, j) == '\n')
#
([vec_extract (data, i, THE (j+1-i))], INPUT_STREAM (buf, j+1));
else
scan (j+1);
fi;
fi;
scan i;
};
my (data, stream)
=
if (rvc::length data == pos)
#
case (get_next_buffer (read_vector buf, "read_line") buf)
#
LAST buf => ([""], INPUT_STREAM (buf, 0));
_ => next_buf buf;
esac;
else
scan_data (buf, pos);
fi;
result_vec = rvc::cat data;
if (rvc::length result_vec == 0) NULL;
else THE (result_vec, stream);
fi;
};
##########################################
# Output streams
Output_Stream
=
OUTPUT_STREAM
{
buffer: wvc::Rw_Vector,
first_free_byte_in_buffer: Ref( Int ),
#
is_closed: Ref( Bool ),
buffering_mode: Ref( iox::Buffering_Mode ), # NO_BUFFERING
| LINE_BUFFERING | BLOCK_BUFFERING;
#
filewriter: Filewriter,
#
write_rw_vector: wsc::Slice -> Void,
write_vector: vsc::Slice -> Void,
clean_tag: eow::Tag
};
#
fun raise_io_exception (OUTPUT_STREAM { filewriter => drv::FILEWRITER { filename, ... }, ... }, op, cause)
=
raise exception iox::IO { op, name => filename, cause };
#
fun is_nl '\n' => TRUE;
is_nl _ => FALSE;
end;
#
fun raise_exception_if_output_stream_is_closed (stream as OUTPUT_STREAM { is_closed => REF TRUE, ... }, ml_op)
=>
raise_io_exception (stream, ml_op, iox::CLOSED_IO_STREAM);
raise_exception_if_output_stream_is_closed _
=>
();
end;
#
fun flush_buffer (stream as OUTPUT_STREAM { buffer, first_free_byte_in_buffer, write_rw_vector, ... }, ml_op)
=
case *first_free_byte_in_buffer
#
0 => ();
#
n => { write_rw_vector (wsc::make_slice (buffer, 0, THE n));
#
first_free_byte_in_buffer := 0;
}
except x = raise_io_exception (stream, ml_op, x);
esac;
# A copy_vec that checks for newlines while it is copying.
# This is used for LINE_BUFFERING output of strings and substrings.
#
fun line_buf_copy_vec (src, src_i, src_len, dst, dst_i)
=
cpy (src_i, dst_i, FALSE)
where
stop = src_i + src_len;
#
fun cpy (src_i, dst_i, linebreak)
=
if (src_i >= stop)
#
linebreak;
else
c = vec_get (src, src_i);
rw_vec_set (dst, dst_i, c);
cpy (src_i+1, dst_i+1, linebreak or is_nl c);
fi;
end;
# A copy_vec for BLOCK_BUFFERING output of strings and substrings.
#
fun block_buf_copy_vec (from, from_i, from_len, into, at)
=
{ wsc::copy_vector { from => vsc::make_slice (from, from_i, THE from_len),
into,
at
};
FALSE;
};
#
fun write (stream as OUTPUT_STREAM output_stream, string_to_write)
=
case *buffering_mode
#
iox::NO_BUFFERING => write_direct ();
iox::LINE_BUFFERING => insert line_buf_copy_vec;
iox::BLOCK_BUFFERING => insert block_buf_copy_vec;
esac
where
#
raise_exception_if_output_stream_is_closed (stream, "write");
output_stream -> { buffer, first_free_byte_in_buffer, buffering_mode, ... };
#
fun flush ()
=
flush_buffer (stream, "write");
#
fun write_direct ()
=
{ case *first_free_byte_in_buffer
#
0 => ();
#
n => { output_stream.write_rw_vector (wsc::make_slice (buffer, 0, THE n));
#
first_free_byte_in_buffer := 0;
};
esac;
output_stream.write_vector (vsc::make_full_slice string_to_write);
}
except
ex = raise_io_exception (stream, "write", ex);
#
fun insert copy_vec
=
{ buf_len = wvc::length buffer;
data_len = rvc::length string_to_write;
if (data_len >= buf_len)
#
write_direct ();
else
i = *first_free_byte_in_buffer;
avail = buf_len - i;
if (avail < data_len)
#
wsc::copy_vector { from => vsc::make_slice (string_to_write, 0, THE avail),
into => buffer,
at => i
};
output_stream.write_rw_vector (wsc::make_full_slice buffer)
except
ex = { first_free_byte_in_buffer := buf_len;
#
raise_io_exception (stream, "write", ex);
};
needs_flush
=
copy_vec (string_to_write, avail, data_len-avail, buffer, 0);
first_free_byte_in_buffer := data_len - avail;
if needs_flush flush (); fi;
else
needs_flush
=
copy_vec (string_to_write, 0, data_len, buffer, i);
first_free_byte_in_buffer := i + data_len;
if (needs_flush or (avail == data_len))
#
flush ();
fi;
fi;
fi;
};
end; # fun write
#
fun write_one (stream as OUTPUT_STREAM { buffer, first_free_byte_in_buffer, buffering_mode, write_rw_vector, ... }, element)
=
{ raise_exception_if_output_stream_is_closed (stream, "write_one");
#
case *buffering_mode
#
iox::NO_BUFFERING
=>
{ rw_vec_set (buffer, 0, element);
#
write_rw_vector (wsc::make_slice (buffer, 0, THE 1))
except
ex = raise_io_exception (stream, "write_one", ex);
};
iox::LINE_BUFFERING
=>
{ i = *first_free_byte_in_buffer;
i' = i+1;
rw_vec_set (buffer, i, element);
first_free_byte_in_buffer := i';
if (i' == wvc::length buffer
or is_nl element
)
flush_buffer (stream, "write_one");
fi;
};
iox::BLOCK_BUFFERING
=>
{ i = *first_free_byte_in_buffer;
i' = i+1;
rw_vec_set (buffer, i, element);
first_free_byte_in_buffer := i';
if (i' == wvc::length buffer)
flush_buffer (stream, "write_one");
fi;
};
esac;
};
#
fun flush stream
=
flush_buffer (stream, "flush");
#
fun close_output (stream as OUTPUT_STREAM { filewriter => drv::FILEWRITER { filename, close, ... }, is_closed, clean_tag, ... } )
=
if (not *is_closed)
#
flush_buffer (stream, "close");
is_closed := TRUE;
eow::drop_stream_startup_and_shutdown_actions clean_tag;
# print ("close-output -- is_closed is FALSE so calling close() of '" + filename + "'. -- winix-text-file-for-os-g--premicrothread.pkg\n");
close ();
# else
# print ("close-output -- is_closed is TRUE, nothing to do for '" + filename + "'. -- winix-text-file-for-os-g--premicrothread.pkg\n");
fi;
#
fun make_outstream (wr as drv::FILEWRITER { best_io_quantum, write_rw_vector, write_vector, ... }, mode)
=
{ fun iterate (f, size, subslice) sl
=
loop sl
where
fun loop sl
=
if (size sl != 0)
#
n = f sl;
loop (subslice (sl, n, NULL));
fi;
end;
write_rw_vector'
=
case write_rw_vector
#
NULL => (\\ _ = raise exception iox::BLOCKING_IO_NOT_SUPPORTED);
THE f => iterate (f, wsc::length, wsc::make_subslice);
esac;
write_vector'
=
case write_vector
#
NULL => (\\ _ = raise exception iox::BLOCKING_IO_NOT_SUPPORTED);
THE f => iterate (f, vsc::length, vsc::make_subslice);
esac;
# Install a dummy cleaner:
#
tag = eow::note_stream_startup_and_shutdown_actions
{
init => \\ () = (),
flush => \\ () = (),
close => \\ () = ()
};
stream = OUTPUT_STREAM
{
buffer => wvc::make_rw_vector (best_io_quantum, some_element),
first_free_byte_in_buffer => REF 0,
#
is_closed => REF FALSE,
buffering_mode => REF mode,
filewriter => wr,
clean_tag => tag,
write_vector => write_vector',
write_rw_vector => write_rw_vector'
};
eow::change_stream_startup_and_shutdown_actions (
#
tag,
#
{ init => \\ () = close_output stream,
flush => \\ () = flush stream,
close => \\ () = close_output stream
}
);
stream;
};
#
fun get_writer (stream as OUTPUT_STREAM { filewriter, buffering_mode, ... } )
=
{ flush_buffer (stream, "getWriter");
#
(filewriter, *buffering_mode);
};
# Position operations on outstreams:
#
Out_Position
=
OUT_POSITION
{ pos: drv::File_Position,
stream: Output_Stream
};
#
fun get_output_position (stream as OUTPUT_STREAM { filewriter, ... } )
=
{ flush_buffer (stream, "get_output_position");
#
case filewriter
#
drv::FILEWRITER { get_file_position => THE f, ... }
=>
OUT_POSITION { pos => f(), stream }
except
ex = raise_io_exception (stream, "get_output_position", ex);
_ => raise_io_exception (stream, "get_output_position", iox::RANDOM_ACCESS_IO_NOT_SUPPORTED);
esac;
};
#
fun file_pos_out (OUT_POSITION { pos, stream } )
=
{ raise_exception_if_output_stream_is_closed (stream, "filePosOut");
#
pos;
};
#
fun set_output_position (OUT_POSITION { pos, stream as OUTPUT_STREAM { filewriter, ... } } )
=
{ raise_exception_if_output_stream_is_closed (stream, "set_output_position");
#
case filewriter
#
drv::FILEWRITER { set_file_position=>THE f, ... }
=>
(f pos)
except
ex = raise_io_exception (stream, "set_output_position", ex);
_ => raise_io_exception (stream, "get_output_position", iox::RANDOM_ACCESS_IO_NOT_SUPPORTED);
esac;
};
# Text-specific operations:
#
fun write_substring (stream as OUTPUT_STREAM output_stream, ss)
=
{ raise_exception_if_output_stream_is_closed (stream, "write_substring");
#
(burst_substring ss)
->
(v, data_start, data_len);
output_stream -> { buffer, first_free_byte_in_buffer, buffering_mode, ... };
buf_len = wvc::length buffer;
#
fun flush ()
=
flush_buffer (stream, "write_substring");
#
fun write_direct ()
=
{ case *first_free_byte_in_buffer
#
0 => ();
#
n => { output_stream.write_rw_vector (wsc::make_slice (buffer, 0, THE n));
#
first_free_byte_in_buffer := 0;
};
esac;
output_stream.write_vector
#
(vsc::make_slice
( v,
data_start,
THE data_len
) );
}
except
ex = raise_io_exception (stream, "write_substring", ex);
#
fun insert copy_vector
=
{ buf_len = wvc::length buffer;
if (data_len >= buf_len)
#
write_direct ();
else
i = *first_free_byte_in_buffer;
avail = buf_len - i;
if (avail < data_len)
#
wsc::copy_vector { from => vsc::make_slice (v, data_start, THE avail),
into => buffer,
at => i
};
output_stream.write_rw_vector (wsc::make_full_slice buffer)
except
x = { first_free_byte_in_buffer := buf_len;
#
raise_io_exception (stream, "write_substring", x);
};
needs_flush
=
copy_vector (v, data_start+avail, data_len-avail, buffer, 0);
first_free_byte_in_buffer := data_len - avail;
if needs_flush flush (); fi;
else
needs_flush = copy_vector (v, data_start, data_len, buffer, i);
first_free_byte_in_buffer := i + data_len;
if (needs_flush or avail == data_len) flush (); fi;
fi;
fi;
};
case *buffering_mode
#
iox::NO_BUFFERING => write_direct ();
iox::LINE_BUFFERING => insert line_buf_copy_vec;
iox::BLOCK_BUFFERING => insert block_buf_copy_vec;
esac;
};
#
fun set_buffering_mode (stream as OUTPUT_STREAM { buffering_mode, ... }, iox::NO_BUFFERING)
=>
{ flush_buffer (stream, "setBufferMode");
#
buffering_mode := iox::NO_BUFFERING;
};
set_buffering_mode (stream as OUTPUT_STREAM { buffering_mode, ... }, mode)
=>
{ raise_exception_if_output_stream_is_closed (stream, "setBufferMode");
#
buffering_mode := mode;
};
end;
#
fun get_buffering_mode (stream as OUTPUT_STREAM { buffering_mode, ... } ) # Commented out 2012-03-03 CrT because it is never referenced.
=
{ raise_exception_if_output_stream_is_closed (stream, "getBufferMode");
#
*buffering_mode;
};
}; # package pur ("pur" == "pure" (I/O)).
######################################################################3
# Plain file I/O
#
# We implement plain file I/O via
# simple wrappers around the above
# pure I/O implementation:
Vector = rvc::Vector;
Element = rvc::Element;
Input_Stream = Ref( pur::Input_Stream ); # A plain Input_Stream is just a refcell holding a pure Input_Stream.
Output_Stream = Ref( pur::Output_Stream ); # A plain Output_Stream is just a refvell holding a pure Output_Stream.
# Input operations:
#
fun read stream
=
{ (pur::read *stream)
->
(v, stream');
stream := stream';
v;
};
#
fun read_one stream
=
case (pur::read_one *stream)
#
THE (element, stream')
=>
{ stream := stream';
#
THE element;
};
NULL => NULL;
esac;
#
fun read_n (stream, n)
=
{ (pur::read_n (*stream, n))
->
(v, stream');
stream := stream'; v;
};
#
fun read_all (stream: Input_Stream)
=
{ (pur::read_all *stream)
->
(v, s);
stream := s;
v;
};
#
fun peek (stream: Input_Stream)
=
case (pur::read_one *stream)
#
THE (element, _) => THE element;
NULL => NULL;
esac;
#
fun close_input stream
=
{ (*stream)
->
s as pur::INPUT_STREAM (buf as pur::INPUT_BUFFER { data, ... }, _);
# Find the end of the stream:
#
fun find_eos (pur::INPUT_BUFFER { next=>REF (pur::NEXT buf), ... } )
=>
find_eos buf;
find_eos (pur::INPUT_BUFFER { next=>REF (pur::LAST buf), ... } )
=>
find_eos buf;
find_eos (buf as pur::INPUT_BUFFER { data, ... } )
=>
pur::INPUT_STREAM (buf, rvc::length data);
end;
pur::close_input s;
stream := find_eos buf;
};
#
fun end_of_stream stream
=
pur::end_of_stream *stream;
# Output operations:
#
fun write (stream, v) = pur::write(*stream, v);
fun write_one (stream, c) = pur::write_one(*stream, c);
#
fun flush stream = pur::flush *stream;
fun close_output stream = pur::close_output *stream;
#
fun get_output_position stream
=
pur::get_output_position *stream;
#
fun set_output_position (stream, p as pur::OUT_POSITION { stream=>stream', ... } )
=
{ stream := stream';
#
pur::set_output_position p;
};
#
fun make_instream (stream: pur::Input_Stream) = REF stream;
fun get_instream (stream: Input_Stream) = *stream;
fun set_instream (stream: Input_Stream, stream') = stream := stream';
#
fun make_outstream (stream: pur::Output_Stream) = REF stream;
fun get_outstream (stream: Output_Stream) = *stream;
fun set_outstream (stream: Output_Stream, stream') = stream := stream';
# Figure out the proper buffering mode for a given writer:
#
fun buffering (drv::FILEWRITER { io_descriptor=>NULL, ... } ) # Should rename to "choose_buffering_mode" or similar. XXX SUCKO FIXME.
=>
iox::BLOCK_BUFFERING;
buffering (drv::FILEWRITER { io_descriptor=>THE iod, ... } )
=>
if (wnx::io::iod_to_iodkind iod == wty::CHAR_DEVICE) iox::LINE_BUFFERING;
else iox::BLOCK_BUFFERING;
fi;
end;
######### BEGIN INTERPOLATED 'say.pkg' STUFF #######################3
server_name = REF NULL: Ref( Null_Or( String ));
log_fd = REF NULL: Ref( Null_Or( psx::File_Descriptor ) );
#
fun log' stringlist
=
case (*server_name, *log_fd)
#
(THE name, THE fd)
=>
{ string = (name + ": " + (cat stringlist));
bytes = byte::string_to_bytes string;
slice = vector_slice_of_one_byte_unts::make_slice (bytes, 0, NULL);
psx::write_vector( fd, slice );
();
};
_ => ();
esac;
######### END INTERPOLATED 'say.pkg' STUFF #######################3
# * Open files *
# fun open_for_read was originally defined here... 2007-01-19 CrT
#
fun open_for_write filename
=
{ wr = wxd::open_for_write filename;
#
make_outstream (pur::make_outstream (wr, buffering wr));
}
except
ex = { # The following produces too much noise to leave on permamently,
# but the usual error message for a missing source file is hopelessly
# vague without it. So for now we uncomment it as needed. XXX BUGGO FIXME
print (cat ["winix-text-file-for-os-g--premicrothread.pkg: open: failed to open for output: <<<", filename, ">>>\n" ]);
raise exception iox::IO { op=>"open", name=>filename, cause=>ex };
};
#
fun open_for_append filename
=
make_outstream
(pur::make_outstream
(wxd::open_for_append filename, iox::NO_BUFFERING)
)
except
cause = raise exception iox::IO { op=>"open_for_append", name=>filename, cause };
# Text stream specific operations
#
fun read_line stream
=
null_or::map
(\\ (v, s) = { stream := s; v;})
(pur::read_line *stream);
#
fun read_lines input_stream
=
read_lines' (input_stream, [])
where
fun read_lines' (s, lines_so_far)
=
case (read_line s)
#
NULL => reverse lines_so_far;
THE line => read_lines' (s, line ! lines_so_far);
esac;
end;
#
fun write_substring (stream, ss)
=
pur::write_substring (*stream, ss);
#
fun open_string src
=
make_instream (pur::make_instream (wxd::string_reader src, empty))
except
cause = raise exception iox::IO { op=>"open_for_read", name=>"<string>", cause };
# wxd ==
src/lib/std/src/posix/winix-text-file-io-driver-for-posix--premicrothread.pkg # The standard streams stdin/stdout/stderr # wxd::stdin() constructs a tio::FILEREADER with an embedded 'closed' == REF FALSE.
#
stipulate
#
fun make_std_in ()
=
{ (pur::make_instream (wxd::stdin(), empty))
->
(stream as pur::INPUT_STREAM (pur::INPUT_BUFFER { global_file_stuff => pur::GLOBAL_FILE_STUFF { clean_tag, ... }, ... }, _));
eow::change_stream_startup_and_shutdown_actions
(
clean_tag,
{ init => \\ () = (),
flush => \\ () = (),
close => \\ () = ()
}
);
stream;
};
#
fun make_std_out ()
=
{ wr = wxd::stdout ();
(pur::make_outstream (wr, buffering wr))
->
(stream as pur::OUTPUT_STREAM { clean_tag, ... } );
eow::change_stream_startup_and_shutdown_actions
(
clean_tag,
{ init => \\ () = (),
flush => \\ () = pur::flush stream,
close => \\ () = pur::flush stream
}
);
stream;
};
#
fun make_std_err ()
=
{ (pur::make_outstream (wxd::stderr(), iox::NO_BUFFERING))
->
(stream as pur::OUTPUT_STREAM { clean_tag, ... } );
eow::change_stream_startup_and_shutdown_actions
(
clean_tag,
{ init => \\ () = (),
flush => \\ () = pur::flush stream,
close => \\ () = pur::flush stream
}
);
stream;
};
herein
# These statements are at top level
# within the generic package; they
# will execute at load/link time:
#
stdin = make_instream (make_std_in ()); # make_instream just wraps a REF around arg.
stdout = make_outstream (make_std_out ()); # make_outstream just wraps a REF around arg.
stderr = make_outstream (make_std_err ());
# Establish a hook function to rebuild the I/O stack
my _ =
at::schedule
(
"winix-text-file-for-os-g--premicrothread.pkg: Make stdin/stdout/stderr", # Arbitrary label for debugging displays.
[ at::STARTUP_PHASE_4_MAKE_STDIN_STDOUT_AND_STDERR ], # When to run the function.
\\ _ = { # Ignored arg is at::STARTUP_PHASE_4_MAKE_STDIN_STDOUT_AND_STDERR.
# print "FUBAR: now making stdin/stdout/stderr -- src/lib/std/src/io/winix-text-file-for-os-g--premicrothread.pkg\n";
set_instream (stdin, make_std_in ());
set_outstream (stdout, make_std_out ());
set_outstream (stderr, make_std_err ());
# print "FUBAR: done making stdin/stdout/stderr -- src/lib/std/src/io/winix-text-file-for-os-g--premicrothread.pkg\n";
}
);
end; # with
#
fun print s # This provides the default value for print_hook_guts::print_hook from
src/lib/core/init/print-hook-guts.pkg = # The default is set in
src/lib/std/src/nj/print-hook.pkg { write (stdout, s); #
flush stdout;
};
#
fun scan_stream scan_g
=
{ scan = scan_g pur::read_one;
#
do_it
where
fun do_it stream
=
{ instrm = get_instream stream;
#
case (scan instrm)
#
NULL => NULL;
#
THE (item, instrm')
=>
{ set_instream (stream, instrm');
THE item;
};
esac;
};
end;
};
#
fun open_for_read filename
=
make_instream (pur::make_instream (wxd::open_for_read filename, empty))
except
ex = { # The following produces too much noise to leave on permamently,
# but the usual error message for a missing source file is hopelessly
# vague without it. So for now we uncomment it as needed. XXX BUGGO FIXME
# log ["winix-text-file-for-os-g--premicrothread.pkg: open_for_read: failed to open for input: '"];
# log [filename];
# log ["'\n"];
# print ("winix-text-file-for-os-g--premicrothread.pkg: psx::current_directory = " + (psx::current_directory()) + "\n");
# print "winix-text-file-for-os-g--premicrothread.pkg: open_for_read: failed to open for input: '";
# print filename;
# print "'\n";
raise exception iox::IO { op=>"open_for_read", name=>filename, cause=>ex };
};
#
fun as_lines filename
=
{ fd = open_for_read filename;
result = read_lines fd;
close_input fd;
result;
};
#
fun from_lines filename lines
=
{ fd = open_for_write filename;
#
map {. write (fd, #line); } lines;
flush fd;
close_output fd;
};
###################################################################
# Stuff from
src/lib/src/lib/thread-kit/src/lib/logger.pkg exception NO_SUCH_LOGTREE_NODE;
# Where log output goes:
#
Log_To
#
= LOG_TO_STDOUT
| LOG_TO_STDERR
| LOG_TO_NULL
| LOG_TO_FILE String
| LOG_TO_STREAM Output_Stream
;
Logtree_Node
=
LOGTREE_NODE
{
parent: Null_Or (Logtree_Node), # NULL only on root node of tree.
name: String,
#
logging: Ref( Bool ),
children: Ref( List( Logtree_Node ) )
};
# Default to printing log messages to "mythryl.log":
# until someone tells us different via 'set_logger_to':
#
log_to = REF (LOG_TO_FILE "mythryl.log");
logger_cleanup = REF (\\ () = ());
my _ = # Needed because only declarations are syntactically legal here.
at::schedule
(
"winix-text-file-for-os-g--premicrothread.pkg: Reset mythryl.log", # Arbitrary label for debugging displays.
#
[ at::STARTUP_PHASE_2_REOPEN_MYTHRYL_LOG, # When to run the function.
at::STARTUP_PHASE_14_START_BASE_IMPS
],
#
\\ _ = { # Ignored arg is at::STARTUP_PHASE_2_REOPEN_MYTHRYL_LOG
server_name := NULL;
log_fd := NULL;
log_to := LOG_TO_FILE "mythryl.log";
logger_cleanup := (\\ () = ());
}
);
# Set output for log messsages:
#
fun set_logger_to t
=
log_to := t;
#
fun logger_is_set_to ()
=
*log_to;
all_logging
=
LOGTREE_NODE
{
parent => NULL,
name => "logger::all_logging",
#
logging => REF FALSE,
children => REF []
};
#
fun for_all f
=
for'
where
fun for' (tm as LOGTREE_NODE { children, ... } )
=
{ f tm;
for_children *children;
}
also
fun for_children []
=>
();
for_children (tm ! r)
=>
{ for' tm;
for_children r;
};
end;
end;
#
fun find_logtree_node_by_name search_name
=
case (find [ all_logging ])
#
THE node => node;
NULL => raise exception NO_SUCH_LOGTREE_NODE;
esac
where
fun find [] => NULL;
#
find ((node as LOGTREE_NODE { name, children, ... }) ! rest)
=>
if (name == search_name)
#
THE node;
else
case (find *children)
#
THE node => THE node;
NULL => find rest;
esac;
fi;
end;
end;
#
fun make_logtree_leaf
{ parent => parent_node as LOGTREE_NODE parent,
name,
default
}
=
{ avoid_duplicate_children *parent.children;
new_node
=
LOGTREE_NODE
{
name,
parent => THE parent_node,
logging => REF default,
children => REF []
};
parent.children
:=
new_node ! *parent.children;
new_node;
}
where
#
fun avoid_duplicate_children []
=>
();
avoid_duplicate_children ((child_node as LOGTREE_NODE { name => name', ... } ) ! rest)
=>
if (name == name')
#
raise exception DIE ("logger::make_logtree_leaf: Already have a child '" + name + "' of node '" + parent.name + "'!" );
else
avoid_duplicate_children rest;
fi;
end;
end;
# Return the name of the node
#
fun name_of_logtree_node (LOGTREE_NODE { name => node_name, ... } )
=
node_name;
# Return the parent of the node
#
fun parent_of_logtree_node (LOGTREE_NODE { parent => node_parent, ... } )
=
node_parent;
# Return all ancestors of node.
# First element of list (if nonempty)
# will always be the root node, all_logging:
#
fun ancestors_of_logtree_node node
=
ancestors' (node, [])
where
fun ancestors' (LOGTREE_NODE { parent => NULL, ... }, resultlist)
=>
resultlist;
ancestors' (LOGTREE_NODE { parent => THE parent, name, ... }, resultlist)
=>
ancestors' (parent, name ! resultlist);
end;
end;
# Turn logging on for a logtree node and its descendents:
#
enable
=
for_all
(\\ (LOGTREE_NODE { logging, ... } )
=
logging := TRUE);
# Turn logging off for a logtree node and its descendents:
#
disable
=
for_all
(\\ (LOGTREE_NODE { logging, ... } )
=
logging := FALSE);
# Turn logging on for a node (but not for its descendents):
#
fun enable_node (LOGTREE_NODE { logging, ... } )
=
logging := TRUE;
# Return TRUE if this node is being logged
#
fun am_logging (LOGTREE_NODE { logging, ... } )
=
*logging;
standardlib_logging
=
make_logtree_leaf
{ parent => all_logging,
name => "standardlib::logging",
default => TRUE # Change to TRUE or call (log::enable standardlib_logging) to enable logging in this file.
};
#
compiler_logging
=
make_logtree_leaf
{ parent => all_logging,
name => "compiler::logging",
default => TRUE # Change to TRUE or call (log::enable compiler_logging) to enable logging in this file.
};
#
# Return a list of the registered
# nodes dominated by the given
# module, and their status.
#
fun subtree_nodes_and_log_flags root
=
reverse (list (root, []))
where
fun list (tm as LOGTREE_NODE { logging, children, ... }, l)
=
list_children (*children, (tm, *logging) ! l)
also
fun list_children ([], l) => l;
list_children (c ! r, l) => list_children (r, list (c, l));
end;
end;
# As an interactive convenience,
# print complete logtree indented:
#
fun print_logtree ()
=
print_logtree' ([all_logging], 0)
where
fun print_indent 0 => ();
print_indent i => { print " "; print_indent (i - 1); };
end;
#
fun print_logtree' ((LOGTREE_NODE { name, logging, children, ... }) ! rest, indent)
=>
{ print_indent indent;
print (*logging ?? "TRUE " :: "FALSE ");
print name;
print "\n";
print_logtree' (*children, indent+1);
print_logtree' (rest, indent);
};
print_logtree' ([], _)
=>
();
end;
end;
# NOTE: There are bookkeeping bugs when
# changing the log destination from
# LOG_TO_STREAM to something else
# (where the original destination
# was LOG_TO_FILE). XXX BUGGO FIXME
#
stipulate
#
lines_printed = REF 0;
# Extract the unix Int file descriptor
# from an Output_Stream. This is a bit
# like pulling teeth:
#
fun outstream_to_fd stream # file__premicrothread is from
src/lib/std/src/posix/file--premicrothread.pkg = # winix_base_text_file_io_driver_for_posix__premicrothread is from
src/lib/std/src/io/winix-base-text-file-io-driver-for-posix--premicrothread.pkg { purestream = get_outstream stream;
#
(pur::get_writer purestream)
->
(writer, _);
fd = case writer
#
winix_base_text_file_io_driver_for_posix__premicrothread::FILEWRITER { io_descriptor => THE iod, ... } => iod;
#
_ => raise exception DIE "logger.pkg: No iod in stream?!";
#
esac: Int;
fd;
};
# fun cfun name = ci::find_c_function { lib_name => "heap", fun_name => name };
#
# write_line_to_log = (cfun "write_line_to_log"): String -> Void; # write_line_to_log def in src/c/lib/heap/libmythryl-heap.c
# write_line_to_stderr = (cfun "write_line_to_stderr"): String -> Void; # write_line_to_stderr def in src/c/lib/heap/libmythryl-heap.c
herein
#
fun logprint_to_stderr message
=
{ heap_debug::write_line_to_stderr message; # heap_debug is from
src/lib/std/src/nj/heap-debug.pkg ();
};
fun logprint message
=
if TRUE # Using an 'if TRUE' (instead of commenting-out) protects the unused code from bitrot -- guarantees that it at least still compiles.
#
# The older implementation below is more flexible.
# But I haven't been using that flexibility in practice,
# and it uses many more syscalls, which makes it problematic
# for debugging syscall redirection (my current project) and
# also prevents log::note() from being used in secondary hostthreads,
# so for the moment I'm using this simpler alternative implementation:
# -- 2012-10-20 CrT
heap_debug::write_line_to_log message; # heap_debug is from
src/lib/std/src/nj/heap-debug.pkg ();
else
# This is the (older) stock production implementation of logprint:
#
{ fun write' stream
=
{ # Leave every fourth line blank for readability:
#
if (*lines_printed & 3 == 0)
#
write (stream, "\n");
fi;
lines_printed := 1 + *lines_printed;
write (stream, message);
flush stream;
};
# date is from
src/lib/std/src/date.pkg # time is from
src/lib/std/types-only/basis-time.pkg case (logger_is_set_to ())
#
LOG_TO_NULL => ();
#
LOG_TO_STDOUT => write' stdout;
LOG_TO_STDERR => write' stderr;
LOG_TO_STREAM stream => write' stream;
#
LOG_TO_FILE filename
=>
{ to = { logfile_is_new = not (exists filename);
#
stream = open_for_append filename;
#
fd = outstream_to_fd stream;
#
internet_socket__premicrothread::set_printif_fd fd; # Enable C-level log_if()s to this log.
if logfile_is_new # There is a race condition here, but the worst that can happen is that
# # we wind up with two logfile headers instead of one, possibly interleaved.
# # Even then, the actual logfile entries will be uncorrupted.
#
write (stream, "# (fd==" + (int::to_string fd) + ") This is a log created " + (date::strftime "%Y-%m-%d:%H:%M:%S" (date::from_time_local (time_guts::get_current_time_utc()))) + " by:\n");
write (stream, "# \n");
write (stream, "# src/lib/src/lib/thread-kit/src/lib/logger.pkg\n");
write (stream, "# \n");
write (stream, "# log_if line fields are:\n");
write (stream, "# \n");
write (stream, "# time: Timestamp in seconds\n");
write (stream, "# pid: Kernel process id for process generating the logfile line.\n");
write (stream, "# ptid: Posix-thread id for posix-thread generating the logfile line.\n");
write (stream, "# task: task_id for task to which microthread belongs which generated the logfile line.\n");
write (stream, "# tid: thread_id for microthread which generated the logfile line.\n");
write (stream, "# name: Name of microthread which generated the logfile line.\n");
write (stream, "# msg: Actual message of logfile line.\n");
write (stream, "# (foo::logging): Finest-grain switch to dis/able logging of the message.\n");
write (stream, "# \n");
write (stream, "# You can suppress such a message via: logger::disable foo::logging\n");
write (stream, "# You can reenable such a message via: logger::enable foo::logging\n");
write (stream, "# You can enable all the messages via: logger::enable file::all_logging\n");
write (stream, "# \n");
write (stream, "# If the package is not visible (or does not export its logswitch) you can use\n");
write (stream, "# \n");
write (stream, "# logger::find_logtree_node_by_name\n");
write (stream, "# \n");
write (stream, "# to get its logswitch, for example\n");
write (stream, "# \n");
write (stream, "# logger::enable (the (file::find_logtree_node_by_name \"foo::logging\"));\n");
write (stream, "# \n");
write (stream, "# \n");
write (stream, "# If you cannot access the logger package (e.g., because of the package-graph acyclicity constraint)\n");
write (stream, "# you can substitute file::enable for logger::enable (etc).\n");
write (stream, "# \n");
write (stream, "# See also: Comments in src/lib/src/lib/thread-kit/src/lib/logger.api\n");
write (stream, "############################ endofheader ############################\n");
write (stream, "\n");
write (stream, "\n");
write (stream, "\n");
fi;
# Closing the logfile at shutdown seems
# at first blush like the tidy and proper thing
# to do, but in practice it seems a dubious idea:
#
# o The exact order of events such as cleanup calls
# during shutdown is not well-defined, so we may
# easily wind up trying to log stuff after
# the log has already been closed.
#
# o Unix will close all open files at program exit()
# anyhow, and since we're doing unbuffered I/O on
# the log fd, there is not even any buffer
# flushing needing to be done.
#
# In short, there seems to be a significant downside to
# closing the stream at SHUTDOWN_PHASE_1_USER_HOOKS but no upside to speak
# of, so I've commented it out. Note that caller can
# always close the log manually if desired by calling
#
# set_logger_to LOG_TO_STDERR
#
# or such, thus implicitly closing the file.
#
# -- 2010-02-26 CrT
#
# logger_cleanup
# :=
# (\\ () = close_output stream);
LOG_TO_STREAM stream;
}
except # threakit_debug is from
src/lib/src/lib/thread-kit/src/core-thread-kit/threadkit-debug.pkg _ = { print ( "logging: Unable to open \""
+ filename
+ "\" redirecting to stdout"
);
LOG_TO_STDOUT;
};
set_logger_to to;
logprint message;
};
esac;
};
fi;
end; # stipulate
stipulate
fun drop_leading_whitespace string
=
if (str::length_in_bytes string == 0
or not (stc::is_space( string, 0)))
#
string;
else
str::implode (drop_leading_whitespace' (str::explode string))
where
fun drop_leading_whitespace' (charlist as (c ! rest))
=>
if (char::is_space c) drop_leading_whitespace' rest;
else charlist;
fi;
drop_leading_whitespace' [] => [];
end;
end;
fi;
herein
current_thread_info__hook = REF (NULL: Null_Or( Void -> (Int, String, Int) )); # Returned values are (thread_id, thread_name, task_id).
# This gets set by reset_thread_scheduler in
src/lib/src/lib/thread-kit/src/core-thread-kit/microthread-preemptive-scheduler.pkg # and reset at::STARTUP_PHASE_1_RESET_STATE_VARIABLES in same file.
#
fun make_logstring (severity, LOGTREE_NODE { name => logswitch_name, ... }, make_message_string_fn)
=
{
# Construct the 'log_if' string to print,
# and then pass it to the log imp.
#
# The point of constructing the string here,
# rather than in the log_if call, is that
# this way we avoid the work of creating it
# if we're not going to print it (i.e., if
# logging is disabled for that call).
#
# NB: The line format we generate here should
# stay synched with those in
#
#
src/lib/src/lib/thread-kit/src/lib/logger.pkg # src/c/main/error-reporting.c
# NB: If you change the time_string content/format you
# should probably make corresponding changes in log_if in
#
# src/c/main/error-reporting.c
# and
#
src/lib/src/lib/thread-kit/src/lib/logger.pkg # Get pid and left-pad with zeros to width 8: # The intention is that
# #
#
pid = psx::get_process_id (); # We don't have access to sprintf() in this library,
pid = int::to_string pid; # so we do sprintf "%8d" pid by hand.
pid = ns::pad_left '0' 8 pid;
ptid = hth::get_hostthread_ptid();
ptid = u1w::to_string ptid;
ptid = ns::pad_left '0' 8 ptid;
# time_string = date::strftime "%Y-%m-%d:%H:%M:%S" (date::from_time_local (time_guts::get_current_time_utc())); # "2010-01-05:14:17:23" or such.
time_string = time_guts::format 6 (time_guts::get_current_time_utc()); # "1262722876.273621" or such.
message_string = drop_leading_whitespace (make_message_string_fn ());
my (thread_id, thread_name, task_id)
=
case *current_thread_info__hook
#
THE f => f ();
NULL => (0, "none", 0);
esac;
tid = int::to_string thread_id;
tid = ns::pad_left '0' 8 tid;
tad = int::to_string task_id;
tad = ns::pad_left '0' 8 tad;
nam = thread_name;
pad = ns::pad_right ' ' (48 - str::length_in_bytes nam) "";
# The intent here is
#
# 1) That doing unix 'sort' on a logfile will do the right thing:
# sort first by time, then by process id, then by thread id.
#
# 2) To facilitate egrep/perl processing, e.g. doing stuff like
# egrep 'pid=021456' logfile
#
logstring = "time=" + time_string
+ " pid=" + pid
+ " ptid=" + ptid
+ " task=" + tad
+ " tid=" + tid
+ " sev=" + (int::to_string severity)
+ " name='" + nam
+ "'" + pad
+ " msg=" + message_string
+ " \t(" + logswitch_name + ")"
;
logstring;
};
#
end;
fun log_if (logtree_node as LOGTREE_NODE { logging, name, ... }) severity make_message_string_fn
=
if (*logging)
#
msg = make_logstring (severity, logtree_node, make_message_string_fn);
logprint msg;
if (severity > 4)
#
bar = "===============================================================================================================================================";
#
logprint_to_stderr bar; #
if (severity > 8) logprint_to_stderr bar; logprint_to_stderr bar; fi; # Messages for fatal errors get triple bars above.
logprint_to_stderr msg;
logprint_to_stderr bar;
if (severity > 8)
logprint_to_stderr bar; logprint_to_stderr bar; # Messages for fatal errors get triple bars below.
logprint_to_stderr "Calling exit_uncleanly(failure)"; #
wnx::process::exit_uncleanly wnx::process::failure; # A clean exit would try to run shutdown code -- probably not a good idea with the system badly broken.
fi;
fi;
();
fi;
fun log_fatal msg
=
{ bar = "===============================================================================================================================================";
#
logprint_to_stderr bar; #
logprint_to_stderr bar; #
logprint_to_stderr bar; #
logprint_to_stderr msg;
logprint_to_stderr bar;
logprint_to_stderr bar;
logprint_to_stderr bar; # Messages for fatal errors get triple bars.
logprint_to_stderr "Calling exit_uncleanly(failure)"; #
wnx::process::exit_uncleanly wnx::process::failure; # A clean exit would try to run shutdown code -- probably not a good idea with the system badly broken.
raise exception DIE msg; # Should not get here. Gives us the required X return type.
};
######### BEGIN INTERPOLATED 'say.pkg' STUFF #######################3
#
fun note make_message_string_fn = { log_if compiler_logging 0 make_message_string_fn; };
fun warn make_message_string_fn = { log_if compiler_logging 5 make_message_string_fn; };
fun fatal message_string = { log_fatal message_string; }; # WILL NOT RETURN.
fun note_in_ramlog make_message_string_fn = { heap_debug::write_line_to_ramlog (make_message_string_fn ()); };
fun note_on_stderr make_message_string_fn = { heap_debug::write_line_to_stderr (make_message_string_fn ()); };
fun say make_message_string_fn
=
{ print (make_message_string_fn () + "\n");
flush stdout;
note make_message_string_fn;
};
# my _ = # Only declarations are syntactically allowed here.
# log::log_note__hook := THE note; my _ = # log is from
src/lib/std/src/log.pkg# log::log_warn__hook := THE note; my _ = # log is from
src/lib/std/src/log.pkg# log::log_fatal__hook := THE note; # log is from
src/lib/std/src/log.pkg #
# A kludge allowing lower-level code like
#
src/lib/std/src/posix/winix-process--premicrothread.pkg # to call log() without introducing package
# dependency cycles.
my _ = # Only declarations are syntactically allowed here.
log::log_note_in_ramlog__hook := THE note_in_ramlog; my _ =
log::log_note_on_stderr__hook := THE note_on_stderr; # log is from
src/lib/std/src/log.pkg my _ = # Needed because only declarations are syntactically legal here.
at::schedule
(
"winix-text-file-for-os-g--premicrothread.pkg: Reset mythryl.log", # Arbitrary label for debugging displays.
#
[ at::STARTUP_PHASE_15_START_XKIT_IMPS ], # When to run the function. We do this late because user thunks passed to log::note may use arbitrary system resources.
#
\\ _ = { # Ignored arg is at::STARTUP_PHASE_2_REOPEN_MYTHRYL_LOG
# Blindly added the following lines (copied from above)
# because otherwise we crash on a stale file scriptor
# in the test log::note call.
# Obviously, it would be nice to better understand and
# refine this solution, but for the moment I'm content
# to be able to get back to debugging the problem at hand.
# (I'm guessing STARTUP_PHASE_5_CLOSE_STALE_OUTPUT_STREAMS
# is clobbering our STARTUP_PHASE_2_REOPEN_MYTHRYL_LOG work.)
#
server_name := NULL;
log_fd := NULL;
log_to := LOG_TO_FILE "mythryl.log";
log::log_note__hook := THE note; # log is from
src/lib/std/src/log.pkg log::log_warn__hook := THE warn; # log is from
src/lib/std/src/log.pkg log::log_fatal__hook := fatal; # log is from
src/lib/std/src/log.pkg log::log_note_in_ramlog__hook := THE note_in_ramlog; # log is from
src/lib/std/src/log.pkg log::log_note_on_stderr__hook := THE note_on_stderr; # log is from
src/lib/std/src/log.pkg }
);
#
######### END INTERPOLATED 'say.pkg' STUFF #######################3
}; # generic package winix_text_file_for_os_g__premicrothread
end;
## COPYRIGHT (c) 1995 AT&T Bell Laboratories.
## Subsequent changes by Jeff Prothero Copyright (c) 2010-2015,
## released per terms of SMLNJ-COPYRIGHT.