-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathMaxLogic.Linux.CLI.pas
More file actions
170 lines (141 loc) · 4.7 KB
/
Copy pathMaxLogic.Linux.CLI.pas
File metadata and controls
170 lines (141 loc) · 4.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
Unit MaxLogic.Linux.CLI;
{$IF NOT((Defined(MsWindows) AND Defined(UseWSL)) OR Defined(POSIX))}
This is a Posix/Linux specific unit.
But there is a way to uses it on windows:
- you need WSL
- define the compiler directive: `UseWSL`
{$IFEND}
Interface
Uses
System.SysUtils,
// set the `UseWSL` conditional on windows to enforce a emulation using the windows subsystem for linux on windows,
{$IF Defined(MsWindows) AND Defined(UseWSL)}
MaxLogic.ioUtils,
{$IFEND}
{$IFDEF POSIX}
Posix.Base, Posix.Fcntl,
{$IFEND}
System.Classes,
System.Generics.Collections;
Type
TStrProc = TProc<String>;
/// <summary>
/// executes a linux command.
/// it will Retrieve the terminal output and call the aBufferReady each time we have a chunk of data
/// attention: THE LINES WILL END WITH #10, SO YOU MIGHT WANT TO CALL tRIMlEFT ON THE RESULT
/// Returns the exit code or -1 if the process failed
/// </summary>
function LinuxCmd(Const aCommand: String; aBufferReady: TStrProc; aBufferSize: integer = 512 * 1024): integer; overload;
/// <summary>
/// a simplified version of the above.
/// best SUITED FOR COMMANDS THAT RETURN A SINGLE LINE AS OUTPUT
/// Please note, that this might not be suiteable for commands that will Retrieve large amount of text
/// attention: A right trim will be performed on the result to remove the trailing line break
/// </summary>
Function LinuxCmd(Const aCommand: String): String; Overload;
Function LinuxCmd(Const aCommand: String; out aExitCode: integer): String; Overload;
{$IFDEF POSIX}
// following are internal methods. They could be in the implementation section but might be used somewhere else too. so I decided to put them here
Type
TStreamHandle = pointer;
/// <summary>
/// Man Page: http://man7.org/linux/man-pages/man3/fgets.3p.html
/// </summary>
Function fgets(buffer: pointer; size: int32; Stream: TStreamHandle): pointer; Cdecl; External libc Name _PU + 'fgets';
/// <summary>
/// Man Page: http://man7.org/linux/man-pages/man3/popen.3.html
/// </summary>
Function popen(Const command: MarshaledAString; Const _type: MarshaledAString): TStreamHandle; Cdecl; External libc Name _PU + 'popen';
/// <summary>
/// Man Page: http://man7.org/linux/man-pages/man3/pclose.3p.html
/// </summary>
Function pclose(filehandle: TStreamHandle): int32; Cdecl; External libc Name _PU + 'pclose';
/// <summary>
/// Utility function to return a buffer of ASCII-Z data as a string.
/// </summary>
Function BufferToString(buffer: pointer; MaxSize: uint32): String;
{$ENDIF}
Implementation
Uses
StrUtils;
{$IF Defined(MsWindows) AND Defined(UseWSL)}
function LinuxCmd(Const aCommand: String; aBufferReady: TStrProc; aBufferSize: integer = 512 * 1024): Integer;
begin
MaxLogic.ioUtils.ExecuteFile(
'bash ' + AnsiQuotedStr(aCommand, '`'),
'', // working dir
Result,
// stdOut
aBufferReady,
// errOut
nil, // the linuxCmd does not capture the ErrOut at all... so let us be consequent and do not capture it as well
True);
end;
{$IFEND}
{$IFDEF POSIX}
Function BufferToString(buffer: pointer; MaxSize: uint32): String;
Var
cursor: ^uint8;
EndOfBuffer: nativeuint;
Begin
Result := '';
If Not assigned(buffer) Then
exit;
cursor := buffer;
EndOfBuffer := nativeuint(cursor) + MaxSize;
While (nativeuint(cursor) < EndOfBuffer) And (cursor^ <> 0) Do
Begin
Result := Result + chr(cursor^);
cursor := pointer(succ(nativeuint(cursor)));
End;
End;
function LinuxCmd(Const aCommand: String; aBufferReady: TStrProc; aBufferSize: integer = 512 * 1024): integer;
Var
Handle: TStreamHandle;
Data: Array Of uint8;
s: String;
ansi: AnsiString;
Begin
Result := -1;
SetLength(Data, aBufferSize);
ansi := AnsiString(aCommand);
Handle := popen(pAnsiChar(ansi), 'r');
If Handle = nil Then
Begin
raise Exception.Create('Failed to execute command: ' + aCommand);
exit;
end;
Try
While fgets(@Data[0], Sizeof(Data), Handle) <> Nil Do
Begin
s := BufferToString(@Data[0], Sizeof(Data));
If assigned(aBufferReady) Then
aBufferReady(s);
End;
Finally
// pclose(handle); returns a value that if the process exited normally the top 8 bits are the exit code. So quick and dirty: ExitCode := pclose(handle) div 256; See: stackoverflow.com/questions/15058876
Result := pclose(Handle);
if Result <> -1 then
Result := Result div 256;
End;
end;
{$ENDIF}
Function LinuxCmd(Const aCommand: String): String;
var
lExitCode: integer;
begin
Result := LinuxCmd(aCommand, lExitCode);
end;
Function LinuxCmd(Const aCommand: String; out aExitCode: integer): String;
Var
s: String;
Begin
s := '';
aExitCode := LinuxCmd(aCommand,
procedure(aText: String)
Begin
s := s + aText;
End);
Result := TrimRight(s);
End;
End.