blob: c43a670338e40fae9a1ecfd5a4f461e0f70e2e79 [file] [log] [blame]
Austin Schuh812d0d12021-11-04 20:16:48 -07001// Copyright (c) FIRST and other WPILib contributors.
2// Open Source Software; you can modify and/or share it under the terms of
3// the WPILib BSD license file in the root directory of this project.
Brian Silverman8fce7482020-01-05 13:18:21 -08004
5#ifdef __APPLE__
6#include <util.h>
7#elif !defined(_WIN32)
8#include <pty.h>
9#endif
10
Austin Schuh812d0d12021-11-04 20:16:48 -070011#include <cstdio>
12
13#include "fmt/format.h"
Brian Silverman8fce7482020-01-05 13:18:21 -080014#include "wpi/MathExtras.h"
15#include "wpi/SmallVector.h"
Austin Schuh812d0d12021-11-04 20:16:48 -070016#include "wpi/StringExtras.h"
Brian Silverman8fce7482020-01-05 13:18:21 -080017#include "wpi/raw_uv_ostream.h"
18#include "wpi/timestamp.h"
19#include "wpi/uv/Loop.h"
20#include "wpi/uv/Pipe.h"
21#include "wpi/uv/Process.h"
22#include "wpi/uv/Signal.h"
23#include "wpi/uv/Tcp.h"
24#include "wpi/uv/Tty.h"
25#include "wpi/uv/Udp.h"
26#include "wpi/uv/util.h"
27
28namespace uv = wpi::uv;
29
30static uint64_t startTime = wpi::Now();
31
32static bool NewlineBuffer(std::string& rem, uv::Buffer& buf, size_t len,
33 wpi::SmallVectorImpl<uv::Buffer>& bufs, bool tcp,
34 uint16_t tcpSeq) {
35 // scan for last newline
Austin Schuh812d0d12021-11-04 20:16:48 -070036 std::string_view str(buf.base, len);
Brian Silverman8fce7482020-01-05 13:18:21 -080037 size_t idx = str.rfind('\n');
Austin Schuh812d0d12021-11-04 20:16:48 -070038 if (idx == std::string_view::npos) {
Brian Silverman8fce7482020-01-05 13:18:21 -080039 // no newline yet, just keep appending to remainder
40 rem += str;
41 return false;
42 }
43
44 // build output
45 wpi::raw_uv_ostream out(bufs, 4096);
Austin Schuh812d0d12021-11-04 20:16:48 -070046 std::string_view toCopy = wpi::slice(str, 0, idx + 1);
Brian Silverman8fce7482020-01-05 13:18:21 -080047 if (tcp) {
48 // Header is 2 byte len, 1 byte type, 4 byte timestamp, 2 byte sequence num
49 uint32_t ts = wpi::FloatToBits((wpi::Now() - startTime) * 1.0e-6);
50 uint16_t len = rem.size() + toCopy.size() + 1 + 4 + 2;
51 const uint8_t header[] = {static_cast<uint8_t>((len >> 8) & 0xff),
52 static_cast<uint8_t>(len & 0xff),
53 12,
54 static_cast<uint8_t>((ts >> 24) & 0xff),
55 static_cast<uint8_t>((ts >> 16) & 0xff),
56 static_cast<uint8_t>((ts >> 8) & 0xff),
57 static_cast<uint8_t>(ts & 0xff),
58 static_cast<uint8_t>((tcpSeq >> 8) & 0xff),
59 static_cast<uint8_t>(tcpSeq & 0xff)};
Austin Schuh812d0d12021-11-04 20:16:48 -070060 out << wpi::span<const uint8_t>(header);
Brian Silverman8fce7482020-01-05 13:18:21 -080061 }
62 out << rem << toCopy;
63
64 // reset remainder
Austin Schuh812d0d12021-11-04 20:16:48 -070065 rem = wpi::slice(str, idx + 1, std::string_view::npos);
Brian Silverman8fce7482020-01-05 13:18:21 -080066 return true;
67}
68
69static void CopyUdp(uv::Stream& in, std::shared_ptr<uv::Udp> out,
70 bool broadcast) {
71 sockaddr_in addr;
72 if (broadcast) {
73 out->SetBroadcast(true);
74 uv::NameToAddr("0.0.0.0", 6666, &addr);
75 } else {
76 uv::NameToAddr("127.0.0.1", 6666, &addr);
77 }
78
79 in.data.connect(
80 [rem = std::make_shared<std::string>(), outPtr = out.get(), addr](
81 uv::Buffer& buf, size_t len) {
82 // build buffers
83 wpi::SmallVector<uv::Buffer, 4> bufs;
Austin Schuh812d0d12021-11-04 20:16:48 -070084 if (!NewlineBuffer(*rem, buf, len, bufs, false, 0)) {
85 return;
86 }
Brian Silverman8fce7482020-01-05 13:18:21 -080087
88 // send output
89 outPtr->Send(addr, bufs, [](auto bufs2, uv::Error) {
Austin Schuh812d0d12021-11-04 20:16:48 -070090 for (auto buf : bufs2) {
91 buf.Deallocate();
92 }
Brian Silverman8fce7482020-01-05 13:18:21 -080093 });
94 },
95 out);
96}
97
98static void CopyTcp(uv::Stream& in, std::shared_ptr<uv::Stream> out) {
99 struct StreamData {
100 std::string rem;
101 uint16_t seq = 0;
102 };
103 in.data.connect(
104 [data = std::make_shared<StreamData>(), outPtr = out.get()](
105 uv::Buffer& buf, size_t len) {
106 // build buffers
107 wpi::SmallVector<uv::Buffer, 4> bufs;
Austin Schuh812d0d12021-11-04 20:16:48 -0700108 if (!NewlineBuffer(data->rem, buf, len, bufs, true, data->seq++)) {
Brian Silverman8fce7482020-01-05 13:18:21 -0800109 return;
Austin Schuh812d0d12021-11-04 20:16:48 -0700110 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800111
112 // send output
113 outPtr->Write(bufs, [](auto bufs2, uv::Error) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700114 for (auto buf : bufs2) {
115 buf.Deallocate();
116 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800117 });
118 },
119 out);
120}
121
122static void CopyStream(uv::Stream& in, std::shared_ptr<uv::Stream> out) {
123 in.data.connect([out](uv::Buffer& buf, size_t len) {
124 uv::Buffer buf2 = buf.Dup();
125 buf2.len = len;
Austin Schuh812d0d12021-11-04 20:16:48 -0700126 out->Write({buf2}, [](auto bufs, uv::Error) {
127 for (auto buf : bufs) {
128 buf.Deallocate();
129 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800130 });
131 });
132}
133
134int main(int argc, char* argv[]) {
135 // parse arguments
136 int programArgc = 1;
137 bool useUdp = false;
138 bool broadcastUdp = false;
139 bool err = false;
140
141 while (programArgc < argc && argv[programArgc][0] == '-') {
Austin Schuh812d0d12021-11-04 20:16:48 -0700142 if (std::string_view(argv[programArgc]) == "-u") {
Brian Silverman8fce7482020-01-05 13:18:21 -0800143 useUdp = true;
Austin Schuh812d0d12021-11-04 20:16:48 -0700144 } else if (std::string_view(argv[programArgc]) == "-b") {
Brian Silverman8fce7482020-01-05 13:18:21 -0800145 useUdp = true;
146 broadcastUdp = true;
147 } else {
Austin Schuh812d0d12021-11-04 20:16:48 -0700148 fmt::print(stderr, "unrecognized command line option {}\n",
149 argv[programArgc]);
Brian Silverman8fce7482020-01-05 13:18:21 -0800150 err = true;
151 }
152 ++programArgc;
153 }
154
155 if (err || (argc - programArgc) < 1) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700156 std::fputs(argv[0], stderr);
157 std::fputs(
158 " [-ub] program [arguments ...]\n"
159 " -u send udp to localhost port 6666 instead of using tcp\n"
160 " -b broadcast udp to port 6666 instead of using tcp\n",
161 stderr);
Brian Silverman8fce7482020-01-05 13:18:21 -0800162 return EXIT_FAILURE;
163 }
164
165 uv::Process::DisableStdioInheritance();
166
167 auto loop = uv::Loop::Create();
168 loop->error.connect(
Austin Schuh812d0d12021-11-04 20:16:48 -0700169 [](uv::Error err) { fmt::print(stderr, "uv ERROR: {}\n", err.str()); });
Brian Silverman8fce7482020-01-05 13:18:21 -0800170
171 // create pipes to communicate with child
172 auto stdinPipe = uv::Pipe::Create(loop);
173 auto stdoutPipe = uv::Pipe::Create(loop);
174 auto stderrPipe = uv::Pipe::Create(loop);
175
176 // create tty to pass from our console to child's
177 auto stdinTty = uv::Tty::Create(loop, 0, true);
178 auto stdoutTty = uv::Tty::Create(loop, 1, false);
179 auto stderrTty = uv::Tty::Create(loop, 2, false);
180
181 // pass through our console to child's (bidirectional)
Austin Schuh812d0d12021-11-04 20:16:48 -0700182 if (stdinTty) {
183 CopyStream(*stdinTty, stdinPipe);
184 }
185 if (stdoutTty) {
186 CopyStream(*stdoutPipe, stdoutTty);
187 }
188 if (stderrTty) {
189 CopyStream(*stderrPipe, stderrTty);
190 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800191
192 // when our stdin closes, also close child stdin
Austin Schuh812d0d12021-11-04 20:16:48 -0700193 if (stdinTty) {
194 stdinTty->end.connect([stdinPipe] { stdinPipe->Close(); });
195 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800196
197 if (useUdp) {
198 auto udp = uv::Udp::Create(loop);
199 // tee stdout and stderr
200 CopyUdp(*stdoutPipe, udp, broadcastUdp);
201 CopyUdp(*stderrPipe, udp, broadcastUdp);
202 } else {
203 auto tcp = uv::Tcp::Create(loop);
204
205 // bind to listen address and port
206 tcp->Bind("", 1740);
207
208 // when we get a connection, accept it
209 tcp->connection.connect([srv = tcp.get(), stdoutPipe, stderrPipe] {
210 auto tcp = srv->Accept();
Austin Schuh812d0d12021-11-04 20:16:48 -0700211 if (!tcp) {
212 return;
213 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800214
215 // close on error
216 tcp->error.connect([s = tcp.get()](wpi::uv::Error err) { s->Close(); });
217
218 // tee stdout and stderr
219 CopyTcp(*stdoutPipe, tcp);
220 CopyTcp(*stderrPipe, tcp);
221 });
222
223 // start listening for incoming connections
224 tcp->Listen();
225 }
226
227 // build process options
228 wpi::SmallVector<uv::Process::Option, 8> options;
229
230 // hook up pipes to child
231 options.emplace_back(
232 uv::Process::StdioCreatePipe(0, *stdinPipe, UV_READABLE_PIPE));
233#ifndef _WIN32
234 // create a PTY so the child does unbuffered output
235 int parentfd, childfd;
236 if (openpty(&parentfd, &childfd, nullptr, nullptr, nullptr) == 0) {
237 stdoutPipe->Open(parentfd);
238 options.emplace_back(uv::Process::StdioInherit(1, childfd));
239 } else {
240 options.emplace_back(
241 uv::Process::StdioCreatePipe(1, *stdoutPipe, UV_WRITABLE_PIPE));
242 }
243#else
244 options.emplace_back(
245 uv::Process::StdioCreatePipe(1, *stdoutPipe, UV_WRITABLE_PIPE));
246#endif
247 options.emplace_back(
248 uv::Process::StdioCreatePipe(2, *stderrPipe, UV_WRITABLE_PIPE));
249
250 // pass our args as the child args (argv[1] becomes child argv[0], etc)
Austin Schuh812d0d12021-11-04 20:16:48 -0700251 for (int i = programArgc; i < argc; ++i) {
252 options.emplace_back(argv[i]);
253 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800254
255 auto proc = uv::Process::SpawnArray(loop, argv[programArgc], options);
256 if (!proc) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700257 std::fputs("could not start subprocess\n", stderr);
Brian Silverman8fce7482020-01-05 13:18:21 -0800258 return EXIT_FAILURE;
259 }
260 proc->exited.connect([](int64_t status, int) { std::exit(status); });
261
262 // start reading
Austin Schuh812d0d12021-11-04 20:16:48 -0700263 if (stdinTty) {
264 stdinTty->StartRead();
265 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800266 stdoutPipe->StartRead();
267 stderrPipe->StartRead();
268
269 // pass various signals to child
270 auto sigHandler = [proc](int signum) { proc->Kill(signum); };
271 for (int signum : {SIGINT, SIGHUP, SIGTERM}) {
272 auto sig = uv::Signal::Create(loop);
273 sig->Start(signum);
274 sig->signal.connect(sigHandler);
275 }
276
277 // run the loop!
278 loop->Run();
279}