After a bit of experimentation, it looks like VBoxManage is just... weird, at least when running under Debian 13.
I read through the relevant parts of the
https://wiki.freepascal.org/Executing_External_Programs wiki page (i.e. everything up to and including "Reading large output" in the TProcess section, the rest of it didn't look like it had much bearing on what I was trying to do), and carefully re-implemented the large output example, using VBoxManage rather than ls:
program project1;
{$mode objfpc}{$H+}
uses
{$IFDEF UNIX}
cthreads,
{$ENDIF}
Interfaces, Forms, Unit1, Classes, SysUtils, Process;
{$R *.res}
const
BUF_SIZE = 2048;
var
AProcess: TProcess;
OutputStream: TStream;
BytesRead: longint;
Buffer: array[1..BUF_SIZE] of byte;
begin
AProcess := TProcess.Create(nil);
AProcess.Executable := '/usr/bin/vboxmanage';
AProcess.Parameters.Add('list');
AProcess.Parameters.Add('vms');
AProcess.Options := [poUsePipes];
AProcess.Execute;
OutputStream := TMemoryStream.Create;
repeat
BytesRead := AProcess.Output.Read(Buffer, BUF_SIZE);
OutputStream.Write(Buffer, BytesRead);
until BytesRead = 0;
AProcess.Free;
with TStringList.Create do
begin
OutputStream.Position := 0;
LoadFromStream(OutputStream);
writeln(Text);
writeln('--- Number of lines = ', Count, '----');
end;
OutputStream.Free;
end.
Similar to my original code, this hangs for multiple seconds when run:
[user ~/ks-dev/TestProject]% time ./project1
WARNING: The vboxdrv kernel module is not loaded. Either there is no module
available for the current kernel (6.12.64-1.qubes.fc41.x86_64) or it failed to
load. Please recompile the kernel module and install it by
sudo /sbin/vboxconfig
You will not be able to start VMs until this problem is fixed.
"TestVM1" {50d5bc49-8ced-4c6f-976d-e006d8e18708}
"TestVM2" {6deea8ae-951b-4981-b267-8043ddcf2f4d}
"TestVM3" {52505be3-3f79-4613-b664-a1a5605ed02d}
--- Number of lines = 10----
./project1 0.01s user 0.01s system 0% cpu 5.141 total
(Ignore the bit about kernel modules; it's an artifact of my work environment and doesn't affect what I'm working on. In case it's useful, I'm running Lazarus in a Kicksecure 18 VM on a Qubes OS R4.3 host. Kicksecure 18 is based on Debian 13.)
However, if I replace "vboxmanage list vms" with "ls --recursive --all", the command executes very quickly as I would hope. I initially thought that maybe this was because VBoxManage was returning too little info and maybe that was getting Read() stuck, but that doesn't seem to be the case; if I remove the "--recursive" and just run "ls --all", it only returns a small bit of data (around 500 bytes in my instance) and still returns quickly.
After a bit more digging (and noticing the warning about handling stderr in the wiki"), I tried rewriting things to use TProcess.ReadInputStream instead, since that seems to be effectively a non-blocking variant of TProcess.Output.Read():
program project1;
{$mode objfpc}{$H+}
uses
{$IFDEF UNIX}
cthreads,
{$ENDIF}
Interfaces, Forms, Unit1, Classes, SysUtils, Process;
{$R *.res}
const
BUF_SIZE = 2048;
var
AProcess: TProcess;
OutputStream: TStream;
ErrStream: TStream;
ReadStdout: Boolean;
ReadStderr: Boolean;
Buffer: array[1..BUF_SIZE] of byte;
begin
AProcess := TProcess.Create(nil);
AProcess.Executable := '/usr/bin/vboxmanage';
AProcess.Parameters.Add('list');
AProcess.Parameters.Add('vms');
AProcess.Options := [poUsePipes];
AProcess.Execute;
OutputStream := TMemoryStream.Create;
ErrStream := TMemoryStream.Create;
repeat
ReadStdout := AProcess.ReadInputStream(AProcess.Output, OutputStream, 1);
ReadStderr := AProcess.ReadInputStream(AProcess.Stderr, ErrStream, 1);
until (not ReadStdout) and (not ReadStderr) and (not AProcess.Running);
AProcess.Free;
with TStringList.Create do
begin
OutputStream.Position := 0;
LoadFromStream(OutputStream);
writeln(Text);
writeln('--- Number of lines = ', Count, '----');
end;
OutputStream.Free;
end.
This version works quite fast ("time" says it takes about 0.137 seconds to run "vboxmanage list vms" and print the results), but it also comes at the cost of being very CPU-intensive. If I replace "vboxmanage list vms" with "sleep 10", the process naturally takes about 10 seconds to return, but it also consumes 100% of one CPU core the entire time it runs.
I also don't think the reason TProcess.Output.Read() was hanging for me is because I was failing to process stderr, since I can comment out the "ReadStderr := AProcess.ReadInputStream(AProcess.Stderr, ErrStream, 1);" line in my code and it still returns quickly.
Is there any way in Free Pascal to do the equivalent of C's select() function, but on a TStream? Or is there any way to get this to return fast without it also eating 100% of a CPU core until the process exits?