Repository navigation
Expand file tree
/
Copy pathFastboot.cs
More file actions
184 lines (163 loc) · 6.71 KB
/
Copy pathFastboot.cs
File metadata and controls
184 lines (163 loc) · 6.71 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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
namespace FastbootEnhance
{
class Fastboot : IDisposable
{
/// <summary>Enough for devices, getvar, reboot and the other short commands.</summary>
public static readonly TimeSpan ShortCommand = TimeSpan.FromSeconds(60);
/// <summary>For reboots and other control commands that can wait on a slow device.</summary>
public static readonly TimeSpan LongCommand = TimeSpan.FromMinutes(15);
/// <summary>
/// For commands that write to the device. A fixed limit could kill fastboot halfway
/// through a large image on slow USB and leave the partition partly written, which is
/// worse than a command that takes its time.
/// </summary>
public static readonly TimeSpan NoLimit = Timeout.InfiniteTimeSpan;
/// <summary>
/// Resolved once against the directory the app was installed into. The old code used
/// ".\\fastboot.exe", which is relative to the *working* directory and breaks whenever
/// the app is started from a shortcut or another folder.
/// </summary>
static readonly string ExecutablePath =
Path.Combine(AppContext.BaseDirectory, "fastboot.exe");
Process process;
Timer deadline;
volatile bool timedOut;
public StreamReader stdout;
public StreamReader stderr;
public Fastboot(string serial, string action) : this(serial, action, ShortCommand)
{
}
/// <summary>
/// Starts fastboot and arms a deadline. When it passes, the process is killed, which
/// closes its pipes, so a caller blocked reading stdout or stderr is released instead
/// of waiting forever on a hung device.
/// </summary>
public Fastboot(string serial, string action, TimeSpan timeout)
{
if (!File.Exists(ExecutablePath))
throw new FileNotFoundException("fastboot.exe is missing", ExecutablePath);
process = new Process();
process.StartInfo.FileName = ExecutablePath;
process.StartInfo.Arguments = serial == null
? action
: "-s \"" + serial + "\" " + action;
process.StartInfo.WorkingDirectory = AppContext.BaseDirectory;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardInput = true;
process.StartInfo.UseShellExecute = false;
process.Start();
stdout = process.StandardOutput;
stderr = process.StandardError;
Process started = process;
deadline = new Timer(delegate
{
timedOut = true;
KillQuietly(started);
}, null, timeout, Timeout.InfiniteTimeSpan);
}
/// <summary>
/// Runs fastboot to the end and returns its exit code (null when it timed out), with
/// stdout and stderr together in <paramref name="output"/>.
/// </summary>
public static int? Run(string serial, string action, TimeSpan timeout, out string output)
{
using (Fastboot fastboot = new Fastboot(serial, action, timeout))
{
// Both pipes at once: fastboot writes its progress to stderr, and reading one
// pipe to the end while the other fills up would hang both processes.
System.Threading.Tasks.Task<string> errors = fastboot.stderr.ReadToEndAsync();
string standard = fastboot.stdout.ReadToEnd();
output = standard + errors.Result;
return fastboot.WaitForExit();
}
}
/// <summary>Serials of the phones in fastboot (bootloader or fastbootd) right now.</summary>
public static System.Collections.Generic.List<string> ListSerials()
{
System.Collections.Generic.List<string> serials = new System.Collections.Generic.List<string>();
string output;
Run(null, "devices", ShortCommand, out output);
foreach (string line in output.Split('\n'))
{
string[] parts = line.Split(new[] { '\t', ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length >= 2 && parts[1].StartsWith("fastboot", StringComparison.Ordinal))
serials.Add(parts[0].Trim());
}
return serials;
}
/// <summary>True when the deadline passed and the process was killed.</summary>
public bool TimedOut => timedOut;
/// <summary>
/// Waits for fastboot to exit after its output has been read, and returns the exit code,
/// or null when the deadline killed it. The deadline guarantees this returns.
/// </summary>
public int? WaitForExit()
{
Process current = process;
if (current == null)
return null;
current.WaitForExit();
if (timedOut)
return null;
return current.ExitCode;
}
/// <summary>
/// Stops fastboot from another thread (the app is closing) and waits briefly for it to
/// let go of the image it was sending.
/// </summary>
public void Abort()
{
Process current = process;
if (current == null)
return;
try
{
KillQuietly(current);
current.WaitForExit(5000);
}
catch (InvalidOperationException)
{
// Disposed by its owner in the meantime; it is already gone.
}
catch (System.ComponentModel.Win32Exception)
{
}
}
static void KillQuietly(Process target)
{
try
{
if (target != null && !target.HasExited)
target.Kill();
}
catch (InvalidOperationException)
{
}
catch (System.ComponentModel.Win32Exception)
{
}
}
public void Dispose()
{
Timer timer = deadline;
deadline = null;
if (timer != null)
timer.Dispose();
Process current = process;
process = null;
if (current == null)
return;
// Close() on its own leaves a hung fastboot running and holding the USB device.
KillQuietly(current);
current.Dispose();
stdout = null;
stderr = null;
}
}
}