|
|
|
|
Quellcode-Bibliothek io.gi
Sprache: unbekannt
|
|
Columbo aufrufen.gi zum Wurzelverzeichnis wechselnUnknown {[0] [0] [0]}Datei anzeigen #############################################################################
##
## io.gi GAP 4 package IO
## Max Neunhoeffer
##
## Copyright (C) by Max Neunhoeffer
## This file is free software, see license information at the end.
##
## This file contains functions mid level IO providing buffering and
## easier access from the GAP level.
##
#####################################
# Some technical preparations: #
#####################################
# The family:
BindGlobal( "FileFamily", NewFamily("FileFamily", IsFile) );
# The type:
BindGlobal( "FileType",
NewType(FileFamily, IsFile and IsAttributeStoringRep));
# one can now create objects by doing:
# r := rec( ... )
# Objectify(FileType,r);
IO.LineEndChars := "\n";
IO.LineEndChar := '\n';
if ARCH_IS_WINDOWS() then
IO.LineEndChars := "\r\n";
fi;
if IsBound(IO.PIPE_BUF) then
IO.NonBlockWriteAmount := IO.PIPE_BUF;
else
IO.NonBlockWriteAmount := 4096;
fi;
BindGlobal( "IO_Error",
Objectify( NewType( IO_ResultsFamily, IO_Result ), rec( val := "IO_Error" ))
);
BindGlobal( "IO_Nothing",
Objectify( NewType( IO_ResultsFamily, IO_Result ), rec( val := "IO_Nothing"))
);
BindGlobal( "IO_OK",
Objectify( NewType( IO_ResultsFamily, IO_Result ), rec( val := "IO_OK"))
);
InstallMethod( \=, "for two IO_Results",
[ IO_Result, IO_Result ],
function(a,b) return a!.val = b!.val; end );
InstallMethod( \=, "for an IO_Result and another object",
[ IO_Result, IsObject ], ReturnFalse );
InstallMethod( \=, "for another object and an IO_Result",
[ IsObject, IO_Result], ReturnFalse );
InstallMethod( ViewObj, "for an IO_Result",
[ IO_Result ],
function(r) Print(r!.val); end );
# Store a list of open files, so we can close them on GAP exit
# To ensure all streams are flushed.
# We unbind 'dowaitpid', because at close we don't want to wait
# for hung child processes
IO.OpenFiles := Set([]);
InstallAtExit(function()
local file;
# CAUTION: Calling IO_Close below removes the file from
# IO.OpenFiles, so iterating over IO.OpenFiles in a for-loop
# would skip every second file!
while Length(IO.OpenFiles) > 0 do
file := IO.OpenFiles[1];
if IsBound(file!.dowaitpid) then
Unbind(file!.dowaitpid);
fi;
# this call removes the file from IO.OpenFiles
IO_Close(file);
od;
end
);
###########################################################################
# Now the functions to create and work with objects in the filter IsFile: #
###########################################################################
InstallGlobalFunction(IO_WrapFD,function(fd,rbuf,wbuf)
# fd: a small integer (a file descriptor).
# rbuf: either false (for unbuffered) or a size for the read buffer size
# wbuf: either false (for unbuffered) or a size for the write buffer size
# rbuf can also be a string in which case fd must be -1 and we get
# a File object that reads from that string.
# wbuf can also be a string in which case fd must be -1 and we get
# a File object that writes to that string by appending.
local f, fileObj;
f := rec(fd := fd,
rbufsize := rbuf,
wbufsize := wbuf,
closed := false);
if f.rbufsize <> false then
if IsInt(f.rbufsize) then
f.rbuf := ""; # this can grow up to r.bufsize
f.rpos := 1;
f.rdata := 0; # nothing in the buffer up to now
else
f.fd := -1;
f.rbuf := f.rbufsize;
f.rbufsize := Length(f.rbuf);
f.rpos := 1;
f.rdata := Length(f.rbuf);
fi;
fi;
if f.wbufsize <> false then
if IsInt(f.wbufsize) then
f.wbuf := "";
f.wdata := 0; # nothing in the buffer up to now
else
f.fd := -1;
f.wbuf := f.wbufsize;
f.wbufsize := infinity;
f.wdata := Length(f.wbuf);
fi;
fi;
fileObj := Objectify(FileType, f);
AddSet(IO.OpenFiles, fileObj);
return fileObj;
end );
IO.DefaultBufSize := 65536;
# A convenience function for files on disk:
InstallGlobalFunction(IO_File, function( arg )
# arguments: filename [,mode][,bufsize]
# filename is a string and mode can be:
# "r" : open for reading only (default)
# "w" : open for writing only, possibly creating/truncating
# "a" : open for appending
local fd,filename,mode,bufsize;
if Length(arg) = 1 then
filename := arg[1];
mode := "r";
bufsize := IO.DefaultBufSize;
elif Length(arg) = 2 then
filename := arg[1];
if IsString(arg[2]) then
mode := arg[2];
bufsize := IO.DefaultBufSize;
else
mode := "r";
bufsize := arg[2];
fi;
elif Length(arg) = 3 then
filename := arg[1];
mode := arg[2];
bufsize := arg[3];
else
Error("IO: Usage: IO_File( filename [,mode][,bufsize] )\n",
"with IsString(filename)");
fi;
if not(IsString(filename)) and not(IsString(mode)) then
Error("IO: Usage: IO_File( filename [,mode][,bufsize] )\n",
"with IsString(filename)");
fi;
if mode = "r" then
fd := IO_open(filename,IO.O_RDONLY,0);
if fd = fail then return fail; fi;
return IO_WrapFD(fd,bufsize,false);
elif mode = "w" then
fd := IO_open(filename,IO.O_CREAT+IO.O_WRONLY+IO.O_TRUNC,
IO.S_IRUSR+IO.S_IWUSR+IO.S_IRGRP+IO.S_IWGRP+
IO.S_IROTH+IO.S_IWOTH);
if fd = fail then return fail; fi;
return IO_WrapFD(fd,false,bufsize);
elif mode = "a" then
fd := IO_open(filename,IO.O_APPEND+IO.O_WRONLY,0);
if fd = fail then return fail; fi;
return IO_WrapFD(fd,false,bufsize);
else
Error("IO: Mode not supported!");
fi;
end );
# Add equality and order for IsFile objects.
# This ordering is not based on fds as they can be reused when files are closed.
# We use the fact that Files objects can't be copied (as they contain the cache)
InstallMethod( \=, "for two IsFile objects",
[ IsFile, IsFile ],
function(a,b) return MASTER_POINTER_NUMBER(a) < MASTER_POINTER_NUMBER(b); end );
InstallMethod( \<, "for two IsFile objects",
[ IsFile, IsFile ],
function(a,b) return MASTER_POINTER_NUMBER(a) < MASTER_POINTER_NUMBER(b); end );
# A nice View method:
InstallMethod( ViewObj, "for IsFile objects", [IsFile],
function(f)
if f!.closed then
Print("<closed file fd=");
else
Print("<file fd=");
fi;
Print(f!.fd);
if f!.rbufsize <> false then
Print(" rbufsize=",f!.rbufsize," rpos=",f!.rpos," rdata=",f!.rdata);
fi;
if f!.wbufsize <> false then
Print(" wbufsize=",f!.wbufsize," wdata=",f!.wdata);
fi;
Print(">");
end);
# Now a convenience function for closing:
InstallGlobalFunction( IO_Close, function( f )
# f must be an object of type IsFile
local i,pid,ret;
if not(IsFile(f)) then
Error("Usage: IO_Close( f ) with IsFile(f) and f open");
fi;
if f!.closed then
Error("Cannot close closed file");
fi;
RemoveSet(IO.OpenFiles, f);
# First flush if necessary:
ret := true;
if f!.wbufsize <> false and f!.wdata <> 0 then
if IO_Flush( f ) = fail then ret := fail; fi;
fi;
f!.closed := true;
f!.rbufsize := false;
f!.wbufsize := false;
# to free the memory for the buffer
f!.rbuf := fail;
f!.wbuf := fail;
if f!.fd <> -1 then
if IO_close(f!.fd) = fail then ret := fail; fi;
fi;
if HasProcessID(f) and IsBound(f!.dowaitpid) then
pid := ProcessID(f);
if IsInt(pid) then pid := [pid]; fi;
f!.results := [];
for i in [1..Length(pid)] do
f!.results[i] := IO_WaitPid(pid[i],true);
od;
fi;
return ret;
end );
InstallGlobalFunction( IO_ReadUntilEOF, function( f )
# arguments: f
# f must be an object of type IsFile
# Reads until end of file. Returns either a (non-empty) string
# or "" (if f is already at end of file) or fail if
# an error occurs.
local bytes,res;
if not(IsFile(f)) then
Error("Usage: IO_ReadUntilEOF( f ) with IsFile(f)");
fi;
if f!.closed then
Error("Tried to read from closed file.");
fi;
# Read until end of file:
if f!.rbufsize <> false and f!.rdata <> 0 then # we read buffered:
# First empty the buffer:
res := f!.rbuf{[f!.rpos..f!.rpos+f!.rdata-1]};
f!.rpos := 1;
f!.rdata := 0;
else
res := "";
fi;
# Now read on:
if f!.fd = -1 then
return res;
fi;
repeat
bytes := IO_read(f!.fd,res,Length(res),IO.NonBlockWriteAmount);
if bytes = fail then return fail; fi;
until bytes = 0;
return res;
end );
InstallGlobalFunction( IO_ReadBlock, function( f, len )
# arguments: f ,len
# f must be an object of type IsFile
# len is the length to read
# Reads length bytes. Guarantees to return length bytes or less ""
# indicating EOF or fail for an error. Blocks until enough data arrives.
# This function only returns less than length bytes, if EOF is reached
# before length bytes are read.
local amount,bytes,res;
if not(IsFile(f)) or not(IsInt(len)) then
Error("Usage: IO_ReadBlock( f [,len] ) with IsFile(f) ",
"and IsInt(len)");
fi;
if f!.closed then
Error("Tried to read from closed file.");
fi;
res := "";
# First the case of no buffer:
if f!.rbufsize = false then
while Length(res) < len do
bytes := IO_read(f!.fd,res,Length(res),len - Length(res));
if bytes = fail then return fail; fi;
if bytes = 0 then return res; fi; # this is EOF
od;
return res;
fi;
# read up to len bytes, using our buffer:
while Length(res) < len do
# First empty the buffer:
if f!.rdata > len - Length(res) then # more data available
amount := len - Length(res);
Append(res,f!.rbuf{[f!.rpos..f!.rpos+amount-1]});
f!.rpos := f!.rpos + amount;
f!.rdata := f!.rdata - amount;
return res;
elif f!.rdata > 0 then
Append(res,f!.rbuf{[f!.rpos..f!.rpos+f!.rdata-1]});
f!.rpos := 1;
f!.rdata := 0;
if Length(res) = len then return res; fi;
fi;
if f!.fd = -1 then
return res;
fi;
if len - Length(res) > f!.rbufsize then
# In this case we read the whole thing:
bytes := IO_read(f!.fd,res,Length(res),len - Length(res));
if bytes = fail then
return fail;
elif bytes = 0 then
return res;
fi;
else
# Now the buffer is empty, so refill it:
bytes := IO_read(f!.fd,f!.rbuf,0,f!.rbufsize);
if bytes = fail then
return fail;
elif bytes = 0 then
return res;
fi;
f!.rdata := bytes;
fi;
od;
return res;
end );
InstallGlobalFunction( IO_ReadLine, function( f )
# f must be an object of type IsFile
# The IO.LineEndChars are not removed at the end
local bytes,pos,res;
if not(IsFile(f)) then
Error("Usage: IO_ReadLine( f ) with IsFile(f)");
fi;
if f!.closed then
Error("Tried to read from closed file.");
fi;
if f!.rbufsize = false then
Error("IO: Readline not possible for unbuffered files.");
fi;
res := "";
while true do
# First try to find a line end within the buffer:
pos := Position(f!.rbuf,IO.LineEndChar,f!.rpos-1);
if pos <> fail and pos < f!.rpos + f!.rdata then
# The line is completely within the buffer
Append(res,f!.rbuf{[f!.rpos..pos]});
f!.rdata := f!.rdata - (pos + 1 - f!.rpos);
f!.rpos := pos + 1;
return res;
else
Append(res,f!.rbuf{[f!.rpos..f!.rpos + f!.rdata - 1]});
f!.rpos := 1;
f!.rdata := 0;
if f!.fd = -1 then
return res;
fi;
# Now read more data into buffer:
bytes := IO_read(f!.fd,f!.rbuf,0,f!.rbufsize);
if bytes = fail then return fail; fi;
if bytes = 0 then # we are at end of file
return res;
fi;
f!.rdata := bytes;
fi;
od;
end );
InstallGlobalFunction( IO_ReadLines, function (arg)
# arguments: f [,maxlines]
# f must be an object of type IsFile
# maxlines is the maximal number of lines read
# Reads lines (max. maxlines or until end of file) and returns a list
# of strings, which are the lines.
local f,l,li,max;
if Length(arg) = 1 then
f := arg[1];
max := infinity;
elif Length(arg) = 2 then
f := arg[1];
max := arg[2];
else
Error("Usage: IO_ReadLines( f [,max] ) with IsFile(f) and IsInt(max)");
fi;
if not(IsFile(f)) or not(IsInt(max) or max = infinity) then
Error("Usage: IO_ReadLines( f [,max] ) with IsFile(f) and IsInt(max)");
fi;
if f!.closed then
Error("Tried to read from closed file.");
fi;
li := [];
while Length(li) < max do
l := IO_ReadLine(f);
if l = fail then return fail; fi;
if Length(l) = 0 then
return li;
fi;
Add(li,l);
od;
return li;
end );
InstallGlobalFunction( IO_Read, function( f, len )
# arguments: f ,len
# f must be an object of type IsFile
# len is the length to read
# Reads up to length bytes. Returns at least 1 byte or "" (for EOF)
# or fail for an error. Blocks only if there is no data available
# and the file is not yet at EOF (for pipes or sockets). If IO_Select
# states that a file object is ready to read, then this function will
# not block. The function may return less than len bytes. It is *not*
# guaranteed that all available data is returned. This function is
# intended to behave very similar to IO_read except for the buffering.
local amount,bytes,res;
if not(IsFile(f)) or not(IsInt(len)) then
Error("Usage: IO_Read( f ,len ) with IsFile(f) ",
"and IsInt(len)");
fi;
if f!.closed then
Error("Tried to read from closed file.");
fi;
res := "";
# First the case of no buffer:
if f!.rbufsize = false then
bytes := IO_read(f!.fd,res,Length(res),len - Length(res));
if bytes = fail then return fail; fi;
return res;
fi;
# read up to len bytes, using our buffer:
# First empty the buffer:
while true do # will be exited
if f!.rdata > len - Length(res) then # more data available
amount := len - Length(res);
res := f!.rbuf{[f!.rpos..f!.rpos+amount-1]};
f!.rpos := f!.rpos + amount;
f!.rdata := f!.rdata - amount;
return res;
elif f!.rdata > 0 then
res := f!.rbuf{[f!.rpos..f!.rpos+f!.rdata-1]};
f!.rpos := 1;
f!.rdata := 0;
return res;
fi;
if f!.fd = -1 then
return "";
fi;
if len - Length(res) > f!.rbufsize then
# In this case we read the whole thing:
bytes := IO_read(f!.fd,res,Length(res),len - Length(res));
if bytes = fail then
return fail;
elif bytes = 0 then
return "";
else
return res;
fi;
else
# Now the buffer is empty, so refill it:
bytes := IO_read(f!.fd,f!.rbuf,0,f!.rbufsize);
if bytes = fail then
return fail;
elif bytes = 0 then
return "";
fi;
f!.rdata := bytes;
# The next loop will do it
fi;
od;
end );
InstallGlobalFunction( IO_HasData,
# Returns true or false. True means, that IO_Read will not block, i.e.,
# it will either produce data or indicate end of file. Note that for a
# file at end of file this function returns true.
function(f)
local l,nr;
if not(IsFile(f)) then
Error("Usage: IO_HasData( f ) with IsFile(f)");
fi;
if f!.closed then
Error("Tried to check for data on closed file.");
fi;
if f!.rbufsize <> false and f!.rdata <> 0 then
return true;
fi;
if f!.fd = -1 then return false; fi;
# Now use select:
l := [f!.fd];
nr := IO_select(l,[],[],0,0);
if nr = 0 then return false; fi;
return true;
end );
# The buffered write functionality:
InstallGlobalFunction( IO_Write, function( arg )
# arguments: f {,things ... }
# f must be an object of type IsFile
# all other arguments: either they are strings, in which case they are
# written directly, otherwise they are converted to strings with "String"
# and the result is being written. The result is either the number of
# bytes written or fail to indicate an error. This functions blocks
# until everything is either written to the buffer or to the actual
# file descriptor. Note that you can never be sure that this function
# returns immediately, even if IO_Select returned a certain file to
# be writable. Use IO_WriteNonBlocking for that purpose.
local bytes,f,i,pos,pos2,st,sumbytes;
if Length(arg) < 2 or not(IsFile(arg[1])) then
Error("Usage: IO_Write( f ,things ... ) with IsFile(f)");
fi;
f := arg[1];
if f!.closed then
Error("Tried to write on closed file.");
fi;
if Length(arg) = 2 and IsStringRep(arg[2]) then
# This is the main buffered Write functionality, all else delegates here:
st := arg[2];
# Do we buffer?
if f!.wbufsize = false then
# Non-buffered I/O:
pos := 0;
while pos < Length(st) do
bytes := IO_write(f!.fd,st,pos,Length(st));
if bytes = fail then return fail; fi;
pos := pos + bytes;
od;
return Length(st); # this indicates success
else # we do buffering:
pos := 0;
while pos < Length(st) do
# First fill the buffer:
if Length(st) - pos + f!.wdata < f!.wbufsize then
f!.wbuf{[f!.wdata+1..f!.wdata+Length(st)-pos]} :=
st{[pos+1..Length(st)]};
f!.wdata := f!.wdata + Length(st) - pos;
return Length(st);
else
f!.wbuf{[f!.wdata+1..f!.wbufsize]} :=
st{[pos+1..pos+f!.wbufsize-f!.wdata]};
pos := pos + f!.wbufsize - f!.wdata;
f!.wdata := f!.wbufsize;
# Now the buffer is full and pos is still < Length(st)!
fi;
# Write out the buffer:
pos2 := 0;
while pos2 < f!.wbufsize do
bytes := IO_write(f!.fd,f!.wbuf,pos2,f!.wbufsize-pos2);
if bytes = fail then return fail; fi;
pos2 := pos2 + bytes;
od;
f!.wdata := 0;
# Perhaps we can write a big chunk:
if Length(st)-pos > f!.wbufsize then
bytes := IO_write(f!.fd,st,pos,Length(st)-pos);
if bytes = fail then return fail; fi;
pos := pos + bytes;
fi;
od;
return Length(st);
fi;
fi;
sumbytes := 0;
for i in [2..Length(arg)] do
if IsStringRep(arg[i]) then
st := arg[i];
else
st := String(arg[i]);
ConvertToStringRep(st);
fi;
bytes := IO_Write(f,st); # delegate to above
if bytes = fail then return fail; fi;
sumbytes := sumbytes + bytes;
od;
return sumbytes;
end );
InstallGlobalFunction( IO_WriteLine, function( arg )
# The same as IO_write, except that a line end is written in the end
# and the buffer is flushed afterwards.
local res;
Add(arg,IO.LineEndChars);
res := CallFuncList( IO_Write, arg );
if res = fail then return fail; fi;
if IO_Flush(arg[1]) = fail then return fail; else
return res;
fi;
end );
InstallGlobalFunction( IO_WriteLines, function( f, l )
# f must be an object of type IsFile
# l must be a list. Calls IO_Write( f, o, IO.LineEndChars ) for all o in l.
local o,res,written;
if not(IsFile(f)) or not(IsList(l)) then
Error("Usage: IO_WriteLines( f, l ) with IsFile(f) and IsList(l)");
fi;
written := 0;
for o in l do
res := IO_Write(f, o, IO.LineEndChars);
if res = fail then return fail; fi;
written := written + res;
od;
if IO_Flush(f) = fail then
return fail;
else
return written;
fi;
end );
InstallGlobalFunction( IO_WriteNonBlocking,
function( f, st, pos, len )
# This function tries to write data of len bytes in the string st beginning
# at position pos+1 to f. It is guaranteed that this function does not
# block, if IO_ReadyForWrite(f) returned true or IO_Select indicated
# possibility to write. Therefore, it might write fewer characters
# than requested! The function returns the number of bytes written
# or fail in case of an error. The function can block, if the
# buffer is full and the file descriptor is not ready to write.
local bytes,pos2;
if not(IsFile(f)) or not(IsStringRep(st)) or not(IsInt(pos)) then
Error("Usage: IO_WriteNonBlocking( f, st, pos )");
fi;
if f!.closed then
Error("Tried to write on closed file.");
fi;
# Do we buffer?
if f!.wbufsize = false then
# Non-buffered I/O:
return IO_write(f!.fd,st,pos,len);
else # we do buffering:
while true do # will be left by return and run at most twice!
# First fill the buffer:
if f!.wdata < f!.wbufsize then # buffer not full
if len + f!.wdata < f!.wbufsize then
f!.wbuf{[f!.wdata+1..f!.wdata+len]} :=
st{[pos+1..pos+len]};
f!.wdata := f!.wdata + len;
return len;
else
f!.wbuf{[f!.wdata+1..f!.wbufsize]} :=
st{[pos+1..pos+f!.wbufsize-f!.wdata]};
bytes := f!.wbufsize - f!.wdata;
f!.wdata := f!.wbufsize;
# Now the buffer is full and pos is still < Length(st)!
return bytes;
fi;
fi;
# Write out the buffer:
pos2 := 0;
bytes := IO_write(f!.fd,f!.wbuf,0,Minimum(IO.NonBlockWriteAmount,
f!.wbufsize));
if bytes = fail then return fail; fi;
if bytes = f!.wbufsize then
f!.wdata := 0;
else
f!.wbuf{[1..f!.wbufsize-bytes]}
:= f!.wbuf{[bytes+1..f!.wbufsize]};
f!.wdata := f!.wdata - bytes;
fi;
# Now there is again space in the buffer and the next loop
# will succeed to write at least something.
od;
fi;
end );
InstallGlobalFunction( IO_Flush, function( f )
local res;
if not(IsFile(f)) then
Error("Usage: IO_Flush( f ) with IsFile(f)");
fi;
if f!.fd = -1 then # Nothing to do for string Files
return true;
fi;
while f!.wbufsize <> false and f!.wdata <> 0 do
res := IO_write( f!.fd, f!.wbuf, 0, f!.wdata );
if res = fail then return fail; fi;
f!.wdata := f!.wdata - res;
od;
return true;
end );
InstallGlobalFunction( IO_FlushNonBlocking, function( f )
# This function is guaranteed to make some progress but also not to
# block if IO_ReadyForWrite or IO_Select returned f to be ready for
# writing. It returns true if the buffers have been flushed and false
# otherwise. An error is signalled by fail.
local res;
if not(IsFile(f)) then
Error("Usage: IO_FlushNonBlocking( f ) with IsFile(f)");
fi;
if f!.fd = -1 or # Nothing to do for string Files
f!.wbufsize = false or
(f!.wbufsize <> false and f!.wdata = 0) then # or if buffer empty
return true;
fi;
res := IO_write( f!.fd, f!.wbuf, 0, f!.wdata );
if res = fail then return fail; fi;
if res < f!.wdata then
f!.wbuf{[1..f!.wdata-res]} := f!.wbuf{[res+1..f!.wdata]};
f!.wdata := f!.wdata - res;
return false;
else
f!.wdata := 0;
return true;
fi;
end );
InstallGlobalFunction( IO_WriteFlush,
function(arg)
local bytes;
bytes := CallFuncList(IO_Write,arg);
if bytes = fail then return fail; fi;
if IO_Flush(arg[1]) = fail then return fail; fi;
return bytes;
end );
InstallGlobalFunction( IO_ReadyForWrite,
# Returns true or false. True means, that the next IO_WriteNonBlocking
# will not block, false means, that the next IO_WriteNonBlocking might
# block.
function(f)
local l,nr;
if not(IsFile(f)) then
Error("Usage: IO_ReadyForWrite( f ) with IsFile(f)");
fi;
if f!.closed then
Error("Tried to check for write on closed file.");
fi;
if f!.wbufsize <> false and f!.wdata < f!.wbufsize then
return true;
fi;
if f!.fd = -1 then return true; fi;
# Now use select:
l := [f!.fd];
nr := IO_select([],l,[],0,0);
if nr = 0 then return false; fi;
return true;
end );
InstallGlobalFunction( IO_ReadyForFlush,
# Returns true or false. True means, that the next IO_FlushNonBlocking
# will not block, false means, that the next IO_FlushNonBlocking might
# block.
function(f)
local l,nr;
if not(IsFile(f)) then
Error("Usage: IO_ReadyForFlush( f ) with IsFile(f)");
fi;
if f!.closed then
Error("Tried to check for flush on closed file.");
fi;
if f!.wbufsize = false or f!.wdata = 0 then
return true;
fi;
if f!.fd = -1 then return true; fi;
# Now use select:
l := [f!.fd];
nr := IO_select([],l,[],0,0);
if nr = 0 then return false; fi;
return true;
end );
InstallGlobalFunction( IO_Select, function( r, w, f, e, t1, t2 )
# Provides select functionality for file descriptors and IsFile objects.
# The list f is for a test for flushability.
local ep,ee,i,nr,nrfinal,rp,rr,wp,ww;
nrfinal := 0;
rr := [];
rp := [];
for i in [1..Length(r)] do
if IsInt(r[i]) then # A file descriptor
Add(rr,r[i]);
Add(rp,i);
elif IsFile(r[i]) then
if r[i]!.rbufsize <> false and r[i]!.rdata > 0 then
nrfinal := nrfinal + 1;
else
Add(rr,r[i]!.fd);
Add(rp,i);
fi;
else
r[i] := fail;
fi;
od;
ww := [];
wp := [];
for i in [1..Length(w)] do
if IsInt(w[i]) then # A file descriptor
Add(ww,w[i]);
Add(wp,i);
elif IsFile(w[i]) then
if w[i]!.wbufsize <> false and w[i]!.wdata < w[i]!.wbufsize then
nrfinal := nrfinal + 1;
else
Add(ww,w[i]!.fd);
Add(wp,i);
fi;
else
w[i] := fail;
fi;
od;
for i in [1..Length(f)] do
if IsInt(f[i]) then # A file descriptor
Add(ww,f[i]);
Add(wp,-i);
elif IsFile(f[i]) then
Add(ww,f[i]!.fd);
Add(wp,-i);
else
f[i] := fail;
fi;
od;
ee := [];
ep := [];
for i in [1..Length(e)] do
if IsInt(e[i]) then # A file descriptor
Add(ee,e[i]);
Add(ep,i);
elif IsFile(e[i]) then
Add(ee,e[i]!.fd);
Add(ep,i);
else
e[i] := fail;
fi;
od;
if Length(rr) > 0 or Length(ww) > 0 or Length(ee) > 0 then
# we have to investigate further:
if nrfinal > 0 then
t1 := 0; # set timeout to 0 because we know we have buffers ready
t2 := 0;
fi;
# Now do the select:
repeat
nr := IO_select(rr,ww,ee,t1,t2);
if nr = fail and LastSystemError().number <> IO.EINTR then
# An error, bits and timeout are undefined
# LastSystemError is set
return fail;
fi;
# Otherwise we have an interrupted system call and want to
# restart the same system call afterwards.
until IsInt(nr);
nrfinal := nrfinal + nr;
# Now look for results:
for i in [1..Length(rr)] do
if rr[i] = fail then r[rp[i]] := fail; fi;
od;
for i in [1..Length(ww)] do
if ww[i] = fail then
if wp[i] > 0 then w[wp[i]] := fail;
else f[-wp[i]] := fail; fi;
fi;
od;
for i in [1..Length(ee)] do
if ee[i] = fail then e[ep[i]] := fail; fi;
od;
fi;
return nrfinal;
end );
# Allow access to the file descriptor:
InstallGlobalFunction( IO_GetFD, function(f)
if not(IsFile(f)) then
Error("Usage: IO_GetFD( f ) with IsFile(f)");
fi;
return f!.fd;
end );
# Allow access to the buffers:
InstallGlobalFunction( IO_GetWBuf, function(f)
if not(IsFile(f)) then
Error("Usage IO_GetWBuf( f ) with IsFile(f)");
fi;
return f!.wbuf;
end );
# Read a full directory:
InstallGlobalFunction( IO_ListDir, function( dirname )
local f,l,res;
l := [];
res := IO_opendir( dirname );
if res = fail then
return fail;
fi;
repeat
f := IO_readdir();
if IsString(f) then
Add(l,f);
fi;
until f = false or f = fail;
IO_closedir();
return l;
end );
# A helper to make pairs IP address and port for TCP and UDP transfer:
InstallGlobalFunction( IO_MakeIPAddressPort, function(ip,port)
local i,l,nr,r,res;
if Length(ip) = 4 then
res := ip;
else
l := List(SplitString(ip,"."),Int);
if Length(l) <> 4 or not(ForAll(l,IsInt)) then
r := IO_gethostbyname(ip);
if r = fail then
Error("This is not an IP address or a valid host name");
return fail;
fi;
res := r.addr[1];
else
res := " ";
for i in [1..4] do
nr := l[i];
if nr < 0 or nr > 255 then
Error("IPv4 addresses must contain numbers between 0 and 255");
fi;
res[i] := CHAR_INT(nr);
od;
fi;
fi;
if port < 0 or port > 65535 then
Error("IPv4 port numbers must be between 0 and 65535");
fi;
return IO_make_sockaddr_in(res,port);
end );
InstallGlobalFunction( IO_FindExecutable,
function(path)
if '/' in path then
if not(IsExecutableFile(path)) then
return fail;
else
return path;
fi;
else
path := Filename(DirectoriesSystemPrograms(),path);
if path = fail then return fail; fi;
if not(IsExecutableFile(path)) then return fail; fi;
return path;
fi;
end );
#############################################################################
# Two helper functions to access and change the environment: #
#############################################################################
InstallGlobalFunction( IO_Environment, function()
# Returns a record with the components corresponding to the set
# environment variables.
local l,ll,p,r,v;
l := IO_environ();
r := rec();
for v in l do
p := Position(v,'=');
if p <> fail then
r.(v{[1..p-1]}) := v{[p+1..Length(v)]};
fi;
od;
return r;
end );
InstallGlobalFunction( IO_MakeEnvList, function(r)
# Returns a list of strings for usage with execve made from the
# components of r in the form "key=value".
local k,l;
l := [];
for k in RecNames(r) do
Add(l,Concatenation(k,"=",r.(k)));
od;
return l;
end );
IO.MaxFDToClose := 64;
InstallGlobalFunction( IO_CloseAllFDs, function(exceptions)
local i;
exceptions := Set(exceptions);
for i in [0..IO.MaxFDToClose] do
if not(i in exceptions) then
IO_close(i);
fi;
od;
end );
InstallGlobalFunction( IO_ForkExecWithFDs,
function(path,argv,stdinfd,stdoutfd,stderrfd)
# This is an internal function. Does the usual fork/exec combination
# with dup2ing the three given file descriptors to standard input,
# standard output and standard error of the child process respectively.
# It installs our signal handler
local pid;
pid := IO_fork();
if pid < 0 then return fail; fi;
if pid = 0 then # the child
# First close all files
IO_CloseAllFDs([stdinfd,stdoutfd,stderrfd]);
if stdinfd <> 0 then IO_dup2(stdinfd,0); IO_close(stdinfd); fi;
if stdoutfd <> 1 then IO_dup2(stdoutfd,1); IO_close(stdoutfd); fi;
if stderrfd <> 2 then IO_dup2(stderrfd,2); IO_close(stderrfd); fi;
IO_execv(path,argv);
# The following should not happen:
IO_exit(-1);
fi;
# Now the parent:
return pid;
end );
InstallGlobalFunction( IO_Popen, function(arg)
# mode can be "w" or "r". In the first case, the standard input of the
# new process will be a pipe, the writing end is returned as a File object.
# In the second case, the standard output of the new process will be a
# pipe, the reading end is returned as a File object.
# The other (standard out or in resp.) is identical to the one of the
# calling GAP process.
# Returns fail if an error occurred.
# The process will usually die, when the pipe is closed.
# The File object will have the Attribute "ProcessID" set to the process ID.
local path,argv,mode,bufsize,fil,pid,pipe;
if Length(arg) < 3 then
Error("Usage: IO_Popen(path,arg,mode,[bufsize])");
fi;
path := arg[1];
argv := arg[2];
mode := arg[3];
if Length(arg) > 3 then
bufsize := arg[4];
else
bufsize := IO.DefaultBufSize;
fi;
path := IO_FindExecutable(path);
if path = fail then
Error("Popen: <path> must refer to an executable file.");
fi;
if mode = "r" then
pipe := IO_pipe(); if pipe = fail then return fail; fi;
pid := IO_ForkExecWithFDs(path,argv,0,pipe.towrite,2);
if pid = fail then
IO_close(pipe.toread);
IO_close(pipe.towrite);
return fail;
fi;
# Now the parent:
IO_close(pipe.towrite);
fil := IO_WrapFD(pipe.toread,bufsize,false);
SetProcessID(fil,pid);
fil!.dowaitpid := true;
return fil;
elif mode = "w" then
pipe := IO_pipe(); if pipe = fail then return fail; fi;
pid := IO_ForkExecWithFDs(path,argv,pipe.toread,1,2);
if pid = fail then
IO_close(pipe.toread);
IO_close(pipe.towrite);
return fail;
fi;
# Now the parent:
IO_close(pipe.toread);
fil := IO_WrapFD(pipe.towrite,false,bufsize);
SetProcessID(fil,pid);
fil!.dowaitpid := true;
return fil;
else
Error("mode must be \"r\" or \"w\".");
fi;
end );
InstallGlobalFunction( IO_Popen2, function(arg)
# A new child process is started. The standard in and out of it are
# pipes. The writing end of the input pipe and the reading end of the
# output pipe are returned as File objects bound to two components
# "stdin" and "stdout" of the returned record. This means, you have to
# *write* to "stdin" and read from "stdout". The stderr will be the same
# as the one of the calling GAP process.
# Returns fail if an error occurred.
# The process will usually die, when one of the pipes is closed.
local path,argv,rbufsize,wbufsize,pid,pipe,pipe2,stdin,stdout;
if Length(arg) < 2 then
Error("Usage: IO_Popen2(path,argv,[rbufsize,wbufsize])");
fi;
path := arg[1];
argv := arg[2];
if Length(arg) > 3 then
rbufsize := arg[3];
wbufsize := arg[4];
else
rbufsize := IO.DefaultBufSize;
wbufsize := IO.DefaultBufSize;
fi;
path := IO_FindExecutable(path);
if path = fail then
Error("Popen2: <path> must refer to an executable file.");
fi;
pipe := IO_pipe(); if pipe = fail then return fail; fi;
pipe2 := IO_pipe();
if pipe2 = fail then
IO_close(pipe.toread);
IO_close(pipe.towrite);
return fail;
fi;
pid := IO_ForkExecWithFDs(path,argv,pipe.toread,pipe2.towrite,2);
if pid = fail then
IO_close(pipe.toread);
IO_close(pipe.towrite);
IO_close(pipe2.toread);
IO_close(pipe2.towrite);
return fail;
fi;
# Now the parent:
IO_close(pipe.toread);
IO_close(pipe2.towrite);
stdin := IO_WrapFD(pipe.towrite,false,wbufsize);
stdout := IO_WrapFD(pipe2.toread,rbufsize,false);
SetProcessID(stdin,pid);
SetProcessID(stdout,pid);
# We only set the dowaitpid flag for the standard output!
stdout!.dowaitpid := true;
return rec(stdin := stdin, stdout := stdout, pid := pid);
end );
InstallGlobalFunction( IO_Popen3, function(arg)
# A new child process is started. The standard in and out and error are
# pipes. All three "other" ends of the pipes are returned as File
# objectes bound to the three components "stdin", "stdout", and "stderr"
# of the returned record. This means, you have to *write* to "stdin"
# and read from "stdout" and "stderr".
# Returns fail if an error occurred.
local path,argv,rbufsize,wbufsize,ebufsize,pid,pipe,pipe2,pipe3,
stderr,stdin,stdout;
if Length(arg) < 2 then
Error("Usage: IO_Popen3(path,argv,[rbufsize,wbufsize,ebufsize])");
fi;
path := arg[1];
argv := arg[2];
if Length(arg) > 4 then
rbufsize := arg[3];
wbufsize := arg[4];
ebufsize := arg[5];
else
rbufsize := IO.DefaultBufSize;
wbufsize := IO.DefaultBufSize;
ebufsize := IO.DefaultBufSize;
fi;
path := IO_FindExecutable(path);
if path = fail then
Error("Popen3: <path> must refer to an executable file.");
fi;
pipe := IO_pipe(); if pipe = fail then return fail; fi;
pipe2 := IO_pipe();
if pipe2 = fail then
IO_close(pipe.toread);
IO_close(pipe.towrite);
return fail;
fi;
pipe3 := IO_pipe();
if pipe3 = fail then
IO_close(pipe.toread);
IO_close(pipe.towrite);
IO_close(pipe2.toread);
IO_close(pipe2.towrite);
return fail;
fi;
pid := IO_ForkExecWithFDs(path,argv,pipe.toread,pipe2.towrite,pipe3.towrite);
if pid = fail then
IO_close(pipe.toread);
IO_close(pipe.towrite);
IO_close(pipe2.toread);
IO_close(pipe2.towrite);
IO_close(pipe3.toread);
IO_close(pipe3.towrite);
return fail;
fi;
# Now the parent:
IO_close(pipe.toread);
IO_close(pipe2.towrite);
IO_close(pipe3.towrite);
stdin := IO_WrapFD(pipe.towrite,false,wbufsize);
stdout := IO_WrapFD(pipe2.toread,rbufsize,false);
stderr := IO_WrapFD(pipe3.toread,ebufsize,false);
SetProcessID(stdin,pid);
SetProcessID(stdout,pid);
SetProcessID(stderr,pid);
# We only set the dowaitpid flag for the standard output!
stdout!.dowaitpid := true;
return rec(stdin := stdin, stdout := stdout, stderr := stderr, pid := pid);
end );
InstallGlobalFunction( IO_StartPipeline,
function( progs, infd, outfd, switcherror )
# progs is a list of pairs, the first entry being a path to an executable,
# the second an argument list, infd is an open file descriptor for
# reading, outfd is an open file descriptor for writing, both can be
# replaced by "open" in which case a new pipe will be opened. switcherror
# is a boolean indicating whether standard error channels are also
# switched to the output channel. This function starts up all processes
# and connects them with pipes. The input of the first is switched to
# infd and the output of the last to outfd.
# Returns a record with the following components: "pids" is a list of
# pids if everything worked. For each process where
# some error occurred the corresponding pid is replaced by fail.
# "stdin" is equal to infd (or the new file descriptor if infd was "open"),
# "stdout" is euqal to outfd (or the new file descriptor if outfd was
# "open").
local a,b,c,i,inpipe,j,outpipe,pids,pipe,pipes,r;
for i in [1..Length(progs)] do
progs[i][1] := IO_FindExecutable(progs[i][1]);
if progs[i][1] = fail then
Error("IO_StartPipeline: <paths> must refer to a executable files.");
return fail;
fi;
od;
if infd = "open" then
inpipe := IO_pipe(); if inpipe = fail then return fail; fi;
infd := inpipe.toread;
else
inpipe := false;
fi;
if outfd = "open" then
outpipe := IO_pipe();
if outpipe = fail then
IO_close(inpipe.towrite);
IO_close(inpipe.toread);
return fail;
fi;
outfd := outpipe.towrite;
else
outpipe := false;
fi;
pipes := [];
for i in [1..Length(progs)-1] do
pipe := IO_pipe();
if pipe = fail then
for j in [1..Length(pipes)] do
IO_close(pipes[j].toread);
IO_close(pipes[j].towrite);
od;
IO_close(inpipe.toread);
IO_close(inpipe.towrite);
IO_close(outpipe.toread);
IO_close(outpipe.towrite);
return fail;
fi;
Add(pipes,pipe);
od;
pids := 0*[1..Length(progs)];
for i in [1..Length(progs)] do
if i = 1 then
a := infd;
else
a := pipes[i-1].toread;
fi;
if i = Length(progs) then
b := outfd;
else
b := pipes[i].towrite;
fi;
if switcherror then
c := b;
else
c := 2;
fi;
pids[i] := IO_ForkExecWithFDs(progs[i][1],progs[i][2],a,b,c);
od;
# Now close all pipes in the parent:
for i in [1..Length(pipes)] do
IO_close(pipes[i].toread);
IO_close(pipes[i].towrite);
od;
r := rec( pids := pids );
if inpipe <> false then
IO_close(inpipe.toread);
r.stdin := inpipe.towrite;
else
IO_close(infd);
r.stdin := false;
fi;
if outpipe <> false then
IO_close(outpipe.towrite);
r.stdout := outpipe.toread;
else
IO_close(outfd);
r.stdout := false;
fi;
return r;
end );
InstallGlobalFunction( IO_StringFilterFile,
function( progs, filename )
local f,fd,i,r,s;
fd := IO_open(filename,0,IO.O_RDONLY);
if fd = fail then return fail; fi;
r := IO_StartPipeline(progs, fd, "open", false);
if r = fail or fail in r.pids then
IO_close(fd);
return fail;
fi;
f := IO_WrapFD(r.stdout,false,false);
s := IO_ReadUntilEOF(f);
IO_Close(f);
for i in r.pids do IO_WaitPid(i,true); od;
return s;
end );
InstallGlobalFunction( IO_FileFilterString,
function( arg )
local append,f,fd,filename,i,le,progs,r,st;
# Check arguments:
if Length(arg) < 3 or Length(arg) > 4 then
Error("Usage: IO_FileFilterString( filename, progs, st [,append]");
return fail;
fi;
filename := arg[1];
progs := arg[2];
st := arg[3];
if Length(arg) > 3 then
append := arg[4];
else
append := false;
fi;
if append then
fd := IO_open(filename,IO.O_WRONLY + IO.O_APPEND,
IO.S_IRUSR+IO.S_IWUSR+IO.S_IRGRP+IO.S_IWGRP+
IO.S_IROTH+IO.S_IWOTH);
else
fd := IO_open(filename,IO.O_WRONLY + IO.O_TRUNC + IO.O_CREAT,
IO.S_IRUSR+IO.S_IWUSR+IO.S_IRGRP+IO.S_IWGRP+
IO.S_IROTH+IO.S_IWOTH);
fi;
if fd = fail then return fail; fi;
r := IO_StartPipeline(progs, "open", fd, false);
if r = fail or fail in r.pids then
IO_close(fd);
return fail;
fi;
f := IO_WrapFD(r.stdin,false,false);
le := IO_Write(f,st);
IO_Close(f);
for i in r.pids do IO_WaitPid(i,true); od;
if le = fail or le < Length(st) then
return fail;
fi;
return true;
end );
InstallGlobalFunction( IO_FilteredFile,
function(arg)
# arguments: progs, filename [,mode][,bufsize]
# mode and bufsize as in IO_File, progs as for StartPipeline
local bufsize,f,fd,filename,mode,progs,r;
if Length(arg) = 2 then
progs := arg[1];
filename := arg[2];
mode := "r";
bufsize := IO.DefaultBufSize;
elif Length(arg) = 3 then
progs := arg[1];
filename := arg[2];
if IsString(arg[3]) then
mode := arg[3];
bufsize := IO.DefaultBufSize;
else
mode := "r";
bufsize := arg[3];
fi;
elif Length(arg) = 4 then
progs := arg[1];
filename := arg[2];
mode := arg[3];
bufsize := arg[4];
else
Error("IO: Usage: IO_FilteredFile( progs,filename [,mode][,bufsize] )\n",
"with IsString(filename)");
fi;
if not(IsString(filename)) and not(IsString(mode)) then
Error("IO: Usage: IO_FilteredFile( progs, filename [,mode][,bufsize] )\n",
"with IsString(filename)");
fi;
if Length(progs) = 0 then
return IO_File(filename,mode,bufsize);
fi;
if mode = "r" then
fd := IO_open(filename,IO.O_RDONLY,0);
if fd = fail then return fail; fi;
r := IO_StartPipeline(progs,fd,"open",false);
if r = fail or fail in r.pids then
IO_close(fd);
return fail;
fi;
f := IO_WrapFD(r.stdout,bufsize,false);
SetProcessID(f,r.pids);
f!.dowaitpid := true;
else
if mode = "w" then
fd := IO_open(filename,IO.O_CREAT+IO.O_WRONLY+IO.O_TRUNC,
IO.S_IRUSR+IO.S_IWUSR+IO.S_IRGRP+IO.S_IWGRP+
IO.S_IROTH+IO.S_IWOTH);
else
fd := IO_open(filename,IO.O_WRONLY + IO.O_APPEND,
IO.S_IRUSR+IO.S_IWUSR+IO.S_IRGRP+IO.S_IWGRP+
IO.S_IROTH+IO.S_IWOTH);
fi;
if fd = fail then return fail; fi;
r := IO_StartPipeline(progs, "open", fd, false);
if r = fail or fail in r.pids then
IO_close(fd);
return fail;
fi;
f := IO_WrapFD(r.stdin,false,bufsize);
SetProcessID(f,r.pids);
f!.dowaitpid := true;
fi;
return f;
end );
InstallGlobalFunction( IO_CompressedFile,
function(arg)
# arguments: filename [,mode][,bufsize]
# mode and bufsize as in IO_File
local bufsize,f,fd,filename,mode,r,ext,splitname,extension,compressor,file;
if Length(arg) = 1 then
filename := arg[1];
mode := "r";
bufsize := IO.DefaultBufSize;
elif Length(arg) = 2 then
filename := arg[1];
if IsString(arg[2]) then
mode := arg[2];
bufsize := IO.DefaultBufSize;
else
mode := "r";
bufsize := arg[2];
fi;
elif Length(arg) = 3 then
filename := arg[1];
mode := arg[2];
bufsize := arg[3];
else
Error("IO: Usage: IO_CompressedFile( filename [,mode][,bufsize] )\n",
"with IsString(filename)");
fi;
if not(IsString(filename)) and not(IsString(mode)) then
Error("IO: Usage: IO_CompressedFile( filename [,mode][,bufsize] )\n",
"with IsString(filename)");
fi;
splitname := SplitString(filename, ".");
extension := splitname[Length(splitname)];
# compressor format: ["executable", args for compression, args for uncompression]
if extension = "gz" then
compressor := ["gzip",["-9q"],["-dq"]];
elif extension = "bz2" then
compressor := ["bzip2",["-9q"],["-dq"]];
elif extension = "xz" then
# xz higher than 6 is not really worth the time / space tradeoff
compressor := ["xz",["-6q"],["-dq"]];
else
return IO_File(filename,mode,bufsize);
fi;
if mode = "r" then
file := IO_FilteredFile([[compressor[1], compressor[3]]], filename, mode, bufsize);
else
file := IO_FilteredFile([[compressor[1], compressor[2]]], filename, mode, bufsize);
fi;
if file <> fail then
return file;
fi;
# Oops, something went wrong. Let's see if the problem is the compression prog
if IO_FindExecutable(compressor[1]) = fail then
Error("Cannot find '",compressor[1],
"', required for "+extension+" files in IO_CompressedFile");
fi;
#Nope, then just return fail
return fail;
end );
InstallGlobalFunction( IO_SendStringBackground, function(f,st)
# The whole string st is send to the File object f but in the background.
# This works by forking off a child process which sends away the string
# such that the parent can go on and can already read from the other end.
# This is especially useful for piping large amounts of data through
# a program that has been started with Popen2 or Popen3.
# The component pid will be bound to the process id of the child process.
# Returns fail if an error occurred.
local pid,len;
pid := IO_fork();
if pid = -1 then
return fail;
fi;
if pid = 0 then # the child
len := IO_Write(f,st);
IO_Flush(f);
IO_Close(f);
IO_exit(0);
fi;
IO_IgnorePid(pid);
return true;
end );
InstallGlobalFunction( IO_PipeThroughWithError,
function(cmd,args,input)
local byt,chunk,err,erreof,inpos,nr,out,outeof,r,s,w,status;
# Start the coprocess:
s := IO_Popen3(cmd,args,false,false,false);
if s = fail then return fail; fi;
# Switch the one we write to to non-blocking mode, just to be sure!
IO_fcntl(s.stdin!.fd,IO.F_SETFL,IO.O_NONBLOCK);
# Here we just do I/O multiplexing, sending away input (if non-empty)
# and receiving stdout and stderr.
inpos := 0;
outeof := false;
erreof := false;
# Here we collect stderr and stdout:
err := "";
out := [];
if Length(input) = 0 then IO_Close(s.stdin); fi;
repeat
if not(outeof) then
r := [s.stdout];
else
r := [];
fi;
if not(erreof) then
Add(r,s.stderr);
fi;
if inpos < Length(input) then
w := [s.stdin];
else
w := [];
fi;
nr := IO_Select(r,w,[],[],fail,fail);
if nr = fail then # an error occurred
if inpos < Length(input) then IO_Close(s.stdin); fi;
IO_Close(s.stdout);
IO_Close(s.stderr);
return fail;
fi;
# First writing:
if Length(w) > 0 and w[1] <> fail then
byt := IO_WriteNonBlocking(s.stdin,input,inpos,Length(input)-inpos);
if byt = fail then
if LastSystemError().number <> IO.EWOULDBLOCK then
IO_Close(s.stdin);
IO_Close(s.stdout);
IO_Close(s.stderr);
return fail;
fi;
else
inpos := inpos + byt;
if inpos = Length(input) then IO_Close(s.stdin); fi;
fi;
fi;
# Now reading:
if not(outeof) and r[1] <> fail then
chunk := IO_Read(s.stdout,4096);
if chunk = "" then
outeof := true;
elif chunk = fail then
if inpos < Length(input) then IO_Close(s.stdin); fi;
IO_Close(s.stdout);
IO_Close(s.stderr);
return fail;
else
Add(out,chunk);
fi;
fi;
if not(erreof) and r[Length(r)] <> fail then
chunk := IO_Read(s.stderr,4096);
if chunk = "" then
erreof := true;
elif chunk = fail then
if inpos < Length(input) then IO_Close(s.stdin); fi;
IO_Close(s.stdout);
IO_Close(s.stderr);
return fail;
else
Append(err,chunk);
fi;
fi;
until outeof and erreof;
status := IO_WaitPid(s.pid, true);
# We have to unbind this here, as by default IO will do its own
# IO_WaitPid when we close stdout.
Unbind(s.stdout!.dowaitpid);
IO_Close(s.stdout);
IO_Close(s.stderr);
return rec( out := Concatenation(out), err := err, status := status );
end);
InstallGlobalFunction( IO_PipeThrough,
function(cmd,args,input)
local byt,chunk,inpos,nr,out,outeof,r,s,w;
# Start the coprocess:
s := IO_Popen2(cmd,args,false,false);
if s = fail then return fail; fi;
# Switch the one we write to to non-blocking mode, just to be sure!
IO_fcntl(s.stdin!.fd,IO.F_SETFL,IO.O_NONBLOCK);
# Here we just do I/O multiplexing, sending away input (if non-empty)
# and receiving stdout and stderr.
# Note that the flushing part is superfluous since we switched off
# the buffers, but still, like this, the code would also work with
# buffering.
inpos := 0;
outeof := false;
# Here we collect stdout:
out := [];
if Length(input) = 0 then IO_Close(s.stdin); fi;
repeat
if not(outeof) then
r := [s.stdout];
else
r := [];
fi;
if inpos < Length(input) then
w := [s.stdin];
else
w := [];
fi;
nr := IO_Select(r,w,[],[],fail,fail);
if nr = fail then # an error occurred
if inpos < Length(input) then IO_Close(s.stdin); fi;
IO_Close(s.stdout);
return fail;
fi;
# First writing:
if Length(w) > 0 and w[1] <> fail then
byt := IO_WriteNonBlocking(s.stdin,input,inpos,Length(input)-inpos);
if byt = fail then
if LastSystemError().number <> IO.EWOULDBLOCK then
if inpos < Length(input) then IO_Close(s.stdin); fi;
IO_Close(s.stdout);
return fail;
fi;
else
inpos := inpos + byt;
if inpos = Length(input) then IO_Close(s.stdin); fi;
fi;
fi;
# Now reading:
if not(outeof) and r[1] <> fail then
chunk := IO_Read(s.stdout,4096);
if chunk = "" then
outeof := true;
elif chunk = fail then
if inpos < Length(input) then IO_Close(s.stdin); fi;
IO_Close(s.stdout);
return fail;
else
Add(out,chunk);
fi;
fi;
until outeof;
IO_Close(s.stdout);
return Concatenation(out);
end);
if IsBoundGlobal("_IO_Defines_ChangeDirectoryCurrent") then
InstallGlobalFunction( ChangeDirectoryCurrent,
function( path )
if IO_chdir(path) = true then
GAPInfo.DirectoryCurrent := Directory(IO_getcwd());
return true;
else
return fail;
fi;
end );
Unbind(_IO_Defines_ChangeDirectoryCurrent);
fi;
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program. If not, see <https://www.gnu.org/licenses/>.
##
[ Verzeichnis aufwärts0.108unsichere Verbindung
]
|
2026-03-28
|
|
|
|
|