blob: 5bfc8bacd6b3f881aa8ccf0712e4d02ecfa1ff7b [file] [log] [blame]
Sergei Datsenko16821892019-04-05 11:26:38 +11001// Copyright 2019 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "cros-disks/disk_monitor.h"
6
7#include <inttypes.h>
Sergei Datsenko16821892019-04-05 11:26:38 +11008#include <string.h>
9#include <sys/mount.h>
10#include <time.h>
11
Anand K Mistry5757f632019-09-11 13:29:41 +100012#include <utility>
13
Sergei Datsenko16821892019-04-05 11:26:38 +110014#include <base/bind.h>
Qijiang Fan713061e2021-03-08 15:45:12 +090015#include <base/check.h>
Sergei Datsenko16821892019-04-05 11:26:38 +110016#include <base/logging.h>
17#include <base/stl_util.h>
18#include <base/strings/string_piece.h>
19#include <base/strings/string_util.h>
20#include <base/strings/stringprintf.h>
21#include <base/time/time.h>
Anand K Mistry5757f632019-09-11 13:29:41 +100022#include <brillo/udev/udev.h>
23#include <brillo/udev/udev_device.h>
24#include <brillo/udev/udev_enumerate.h>
25#include <brillo/udev/udev_monitor.h>
Sergei Datsenko16821892019-04-05 11:26:38 +110026
27#include "cros-disks/device_ejector.h"
François Degros16ad1ae2019-07-17 16:02:39 +100028#include "cros-disks/quote.h"
Sergei Datsenko16821892019-04-05 11:26:38 +110029#include "cros-disks/udev_device.h"
30
31namespace cros_disks {
Sergei Datsenko16821892019-04-05 11:26:38 +110032namespace {
33
34const char kBlockSubsystem[] = "block";
35const char kMmcSubsystem[] = "mmc";
36const char kScsiSubsystem[] = "scsi";
37const char kScsiDevice[] = "scsi_device";
38const char kUdevAddAction[] = "add";
39const char kUdevChangeAction[] = "change";
40const char kUdevRemoveAction[] = "remove";
41const char kPropertyDiskEjectRequest[] = "DISK_EJECT_REQUEST";
42const char kPropertyDiskMediaChange[] = "DISK_MEDIA_CHANGE";
43
Sergei Datsenkoad8b5042020-09-21 22:39:04 +100044// Checks if the device is allowed to be used through cros-disks.
45bool IsDeviceAllowed(const UdevDevice& device,
46 const std::set<std::string>& allowlist) {
47 if (device.IsIgnored())
48 return false;
49 if (base::Contains(allowlist, device.NativePath()))
50 return true;
51 if (device.IsLoopDevice())
52 return false;
53 if (device.IsMobileBroadbandDevice())
54 return false;
55 if (device.IsOnBootDevice())
56 return false;
57 return true;
58}
59
Sergei Datsenko16821892019-04-05 11:26:38 +110060// An EnumerateBlockDevices callback that appends a Disk object, created from
61// |dev|, to |disks| if |dev| should not be ignored by cros-disks. Always
62// returns true to continue the enumeration in EnumerateBlockDevices.
Sergei Datsenkoad8b5042020-09-21 22:39:04 +100063bool AppendDiskIfNotIgnored(const std::set<std::string>& allowlist,
64 std::vector<Disk>* disks,
Anand K Mistry5757f632019-09-11 13:29:41 +100065 std::unique_ptr<brillo::UdevDevice> dev) {
Anand K Mistry5757f632019-09-11 13:29:41 +100066 UdevDevice device(std::move(dev));
Sergei Datsenkoad8b5042020-09-21 22:39:04 +100067 if (IsDeviceAllowed(device, allowlist))
Sergei Datsenko16821892019-04-05 11:26:38 +110068 disks->push_back(device.ToDisk());
Sergei Datsenko16821892019-04-05 11:26:38 +110069 return true; // Continue the enumeration.
70}
71
72// An EnumerateBlockDevices callback that checks if |dev| matches |path|. If
Sergei Datsenkoad8b5042020-09-21 22:39:04 +100073// it's a match, populates |device| and returns false to stop the enumeration in
Sergei Datsenko16821892019-04-05 11:26:38 +110074// EnumerateBlockDevices.
75bool MatchDiskByPath(const std::string& path,
Sergei Datsenkoad8b5042020-09-21 22:39:04 +100076 std::unique_ptr<UdevDevice>* device,
Anand K Mistry5757f632019-09-11 13:29:41 +100077 std::unique_ptr<brillo::UdevDevice> dev) {
Sergei Datsenko16821892019-04-05 11:26:38 +110078 DCHECK(dev);
Sergei Datsenkoad8b5042020-09-21 22:39:04 +100079 DCHECK(device);
Sergei Datsenko16821892019-04-05 11:26:38 +110080
Anand K Mistry5757f632019-09-11 13:29:41 +100081 const char* sys_path = dev->GetSysPath();
82 const char* dev_path = dev->GetDevicePath();
83 const char* dev_file = dev->GetDeviceNode();
Sergei Datsenkoad8b5042020-09-21 22:39:04 +100084 bool match = (sys_path && path == sys_path) ||
85 (dev_path && path == dev_path) || (dev_file && path == dev_file);
86 if (!match)
Sergei Datsenko16821892019-04-05 11:26:38 +110087 return true; // Not a match. Continue the enumeration.
88
Sergei Datsenkoad8b5042020-09-21 22:39:04 +100089 *device = std::make_unique<UdevDevice>(std::move(dev));
Sergei Datsenko16821892019-04-05 11:26:38 +110090 return false; // Match. Stop enumeration.
91}
92
François Degros16ad1ae2019-07-17 16:02:39 +100093// Logs a device with its properties.
Anand K Mistry5757f632019-09-11 13:29:41 +100094void LogUdevDevice(const brillo::UdevDevice& dev) {
François Degros16ad1ae2019-07-17 16:02:39 +100095 if (!VLOG_IS_ON(1))
96 return;
97
98 // Some device events (eg USB drive removal) result in devnode being null.
99 // This is gracefully handled by quote() without crashing.
Anand K Mistry5757f632019-09-11 13:29:41 +1000100 VLOG(1) << " node: " << quote(dev.GetDeviceNode());
Anand K Mistry5757f632019-09-11 13:29:41 +1000101 VLOG(1) << " devtype: " << quote(dev.GetDeviceType());
Anand K Mistry5757f632019-09-11 13:29:41 +1000102 VLOG(1) << " syspath: " << quote(dev.GetSysPath());
François Degros16ad1ae2019-07-17 16:02:39 +1000103
104 if (!VLOG_IS_ON(2))
105 return;
106
107 // Log all properties.
Anand K Mistry5757f632019-09-11 13:29:41 +1000108 for (std::unique_ptr<brillo::UdevListEntry> prop =
109 dev.GetPropertiesListEntry();
110 prop; prop = prop->GetNext()) {
111 VLOG(2) << " " << prop->GetName() << ": " << quote(prop->GetValue());
François Degros16ad1ae2019-07-17 16:02:39 +1000112 }
113}
114
Sergei Datsenkoad8b5042020-09-21 22:39:04 +1000115void LogDevice(const UdevDevice& dev) {
116 if (!VLOG_IS_ON(1))
117 return;
118
119 VLOG(1) << "path: " << quote(dev.NativePath());
120 VLOG(1) << " attributes: virtual=" << dev.IsVirtual()
121 << " loop=" << dev.IsLoopDevice() << " boot=" << dev.IsOnBootDevice()
122 << " removable=" << dev.IsOnRemovableDevice()
123 << " broadband=" << dev.IsMobileBroadbandDevice()
124 << " automount=" << dev.IsAutoMountable()
125 << " media=" << dev.IsMediaAvailable();
126}
127
Sergei Datsenko16821892019-04-05 11:26:38 +1100128} // namespace
129
Anand K Mistry5757f632019-09-11 13:29:41 +1000130DiskMonitor::DiskMonitor() : udev_(brillo::Udev::Create()) {
Sergei Datsenko16821892019-04-05 11:26:38 +1100131 CHECK(udev_) << "Failed to initialize udev";
Anand K Mistry5757f632019-09-11 13:29:41 +1000132 udev_monitor_ = udev_->CreateMonitorFromNetlink("udev");
Sergei Datsenko16821892019-04-05 11:26:38 +1100133 CHECK(udev_monitor_) << "Failed to create a udev monitor";
Anand K Mistry5757f632019-09-11 13:29:41 +1000134 udev_monitor_->FilterAddMatchSubsystemDeviceType(kBlockSubsystem, nullptr);
135 udev_monitor_->FilterAddMatchSubsystemDeviceType(kMmcSubsystem, nullptr);
136 udev_monitor_->FilterAddMatchSubsystemDeviceType(kScsiSubsystem, kScsiDevice);
137 CHECK(udev_monitor_->EnableReceiving());
Sergei Datsenko16821892019-04-05 11:26:38 +1100138}
139
Anand K Mistry5757f632019-09-11 13:29:41 +1000140DiskMonitor::~DiskMonitor() = default;
141
142int DiskMonitor::udev_monitor_fd() const {
143 return udev_monitor_->GetFileDescriptor();
Sergei Datsenko16821892019-04-05 11:26:38 +1100144}
145
146bool DiskMonitor::Initialize() {
147 // Since there are no udev add events for the devices that already exist
148 // when the disk manager starts, emulate udev add events for these devices
149 // to correctly populate |disks_detected_|.
Anand K Mistryc0ece3d2020-01-28 15:55:29 +1100150 EnumerateBlockDevices(base::BindRepeating(
151 &DiskMonitor::EmulateAddBlockDeviceEvent, base::Unretained(this)));
Sergei Datsenko16821892019-04-05 11:26:38 +1100152 return true;
153}
154
Anand K Mistry5757f632019-09-11 13:29:41 +1000155bool DiskMonitor::EmulateAddBlockDeviceEvent(
156 std::unique_ptr<brillo::UdevDevice> dev) {
Sergei Datsenko16821892019-04-05 11:26:38 +1100157 DeviceEventList events;
Anand K Mistry5757f632019-09-11 13:29:41 +1000158 LOG(INFO) << "Emulating action 'add' on device " << quote(dev->GetSysName());
159 LogUdevDevice(*dev);
160 ProcessBlockDeviceEvents(std::move(dev), kUdevAddAction, &events);
Sergei Datsenko16821892019-04-05 11:26:38 +1100161 return true; // Continue the enumeration.
162}
163
164std::vector<Disk> DiskMonitor::EnumerateDisks() const {
165 std::vector<Disk> disks;
Sergei Datsenkoad8b5042020-09-21 22:39:04 +1000166 EnumerateBlockDevices(base::BindRepeating(&AppendDiskIfNotIgnored, allowlist_,
167 base::Unretained(&disks)));
Sergei Datsenko16821892019-04-05 11:26:38 +1100168 return disks;
169}
170
171void DiskMonitor::EnumerateBlockDevices(
Anand K Mistryc0ece3d2020-01-28 15:55:29 +1100172 base::RepeatingCallback<bool(std::unique_ptr<brillo::UdevDevice> dev)>
Anand K Mistry5757f632019-09-11 13:29:41 +1000173 callback) const {
174 std::unique_ptr<brillo::UdevEnumerate> enumerate = udev_->CreateEnumerate();
175 enumerate->AddMatchSubsystem(kBlockSubsystem);
176 enumerate->ScanDevices();
Sergei Datsenko16821892019-04-05 11:26:38 +1100177
Anand K Mistry5757f632019-09-11 13:29:41 +1000178 for (std::unique_ptr<brillo::UdevListEntry> entry = enumerate->GetListEntry();
179 entry; entry = entry->GetNext()) {
180 std::unique_ptr<brillo::UdevDevice> dev =
181 udev_->CreateDeviceFromSysPath(entry->GetName());
182 if (!dev)
Sergei Datsenko16821892019-04-05 11:26:38 +1100183 continue;
184
Anand K Mistry5757f632019-09-11 13:29:41 +1000185 VLOG(1) << "Found device " << quote(dev->GetSysName());
186 LogUdevDevice(*dev);
Sergei Datsenko16821892019-04-05 11:26:38 +1100187
Anand K Mistry5757f632019-09-11 13:29:41 +1000188 bool continue_enumeration = callback.Run(std::move(dev));
Sergei Datsenko16821892019-04-05 11:26:38 +1100189 if (!continue_enumeration)
190 break;
191 }
Sergei Datsenko16821892019-04-05 11:26:38 +1100192}
193
Anand K Mistry5757f632019-09-11 13:29:41 +1000194void DiskMonitor::ProcessBlockDeviceEvents(
195 std::unique_ptr<brillo::UdevDevice> dev,
196 const char* action,
197 DeviceEventList* events) {
198 brillo::UdevDevice* raw_dev = dev.get();
199 UdevDevice device(std::move(dev));
Sergei Datsenkoad8b5042020-09-21 22:39:04 +1000200 LogDevice(device);
201 if (!IsDeviceAllowed(device, allowlist_))
Sergei Datsenko16821892019-04-05 11:26:38 +1100202 return;
203
204 bool disk_added = false;
205 bool disk_removed = false;
206 bool child_disk_removed = false;
207 if (strcmp(action, kUdevAddAction) == 0) {
208 disk_added = true;
209 } else if (strcmp(action, kUdevRemoveAction) == 0) {
210 disk_removed = true;
211 } else if (strcmp(action, kUdevChangeAction) == 0) {
212 // For removable devices like CD-ROM, an eject request event
213 // is treated as disk removal, while a media change event with
214 // media available is treated as disk insertion.
215 if (device.IsPropertyTrue(kPropertyDiskEjectRequest)) {
216 disk_removed = true;
217 } else if (device.IsPropertyTrue(kPropertyDiskMediaChange)) {
218 if (device.IsMediaAvailable()) {
219 disk_added = true;
220 } else {
221 child_disk_removed = true;
222 }
223 }
224 }
225
226 std::string device_path = device.NativePath();
227 if (disk_added) {
228 if (device.IsAutoMountable()) {
Qijiang Fan52439042020-06-17 15:34:38 +0900229 if (base::Contains(disks_detected_, device_path)) {
Sergei Datsenko16821892019-04-05 11:26:38 +1100230 // Disk already exists, so remove it and then add it again.
Sergei Datsenkoad8b5042020-09-21 22:39:04 +1000231 LOG(INFO) << "Re-add block device " << quote(device_path);
Sergei Datsenko16821892019-04-05 11:26:38 +1100232 events->push_back(DeviceEvent(DeviceEvent::kDiskRemoved, device_path));
233 } else {
234 disks_detected_[device_path] = {};
235
236 // Add the disk as a child of its parent if the parent is already
237 // added to |disks_detected_|.
Anand K Mistry5757f632019-09-11 13:29:41 +1000238 std::unique_ptr<brillo::UdevDevice> parent = raw_dev->GetParent();
Sergei Datsenko16821892019-04-05 11:26:38 +1100239 if (parent) {
Anand K Mistry5757f632019-09-11 13:29:41 +1000240 std::string parent_device_path =
241 UdevDevice(std::move(parent)).NativePath();
Qijiang Fan52439042020-06-17 15:34:38 +0900242 if (base::Contains(disks_detected_, parent_device_path)) {
Sergei Datsenko16821892019-04-05 11:26:38 +1100243 disks_detected_[parent_device_path].insert(device_path);
244 }
245 }
246 }
Sergei Datsenkoad8b5042020-09-21 22:39:04 +1000247 LOG(INFO) << "Add block device " << quote(device_path);
Sergei Datsenko16821892019-04-05 11:26:38 +1100248 events->push_back(DeviceEvent(DeviceEvent::kDiskAdded, device_path));
249 }
250 } else if (disk_removed) {
251 disks_detected_.erase(device_path);
Sergei Datsenkoad8b5042020-09-21 22:39:04 +1000252 LOG(INFO) << "Remove block device " << quote(device_path);
Sergei Datsenko16821892019-04-05 11:26:38 +1100253 events->push_back(DeviceEvent(DeviceEvent::kDiskRemoved, device_path));
254 } else if (child_disk_removed) {
255 bool no_child_disks_found = true;
Qijiang Fan52439042020-06-17 15:34:38 +0900256 if (base::Contains(disks_detected_, device_path)) {
Sergei Datsenko16821892019-04-05 11:26:38 +1100257 auto& child_disks = disks_detected_[device_path];
258 no_child_disks_found = child_disks.empty();
259 for (const auto& child_disk : child_disks) {
Sergei Datsenkoad8b5042020-09-21 22:39:04 +1000260 LOG(INFO) << "Remove child device " << quote(child_disk);
Sergei Datsenko16821892019-04-05 11:26:38 +1100261 events->push_back(DeviceEvent(DeviceEvent::kDiskRemoved, child_disk));
262 }
263 }
264 // When the device contains a full-disk partition, there are no child disks.
265 // Remove the device instead.
Sergei Datsenkoad8b5042020-09-21 22:39:04 +1000266 if (no_child_disks_found) {
267 LOG(INFO) << "Remove parent device " << quote(device_path);
Sergei Datsenko16821892019-04-05 11:26:38 +1100268 events->push_back(DeviceEvent(DeviceEvent::kDiskRemoved, device_path));
Sergei Datsenkoad8b5042020-09-21 22:39:04 +1000269 }
Sergei Datsenko16821892019-04-05 11:26:38 +1100270 }
271}
272
Anand K Mistry5757f632019-09-11 13:29:41 +1000273void DiskMonitor::ProcessMmcOrScsiDeviceEvents(
274 std::unique_ptr<brillo::UdevDevice> dev,
275 const char* action,
276 DeviceEventList* events) {
277 UdevDevice device(std::move(dev));
Sergei Datsenkoad8b5042020-09-21 22:39:04 +1000278 LogDevice(device);
279 if (!IsDeviceAllowed(device, allowlist_))
Sergei Datsenko16821892019-04-05 11:26:38 +1100280 return;
281
282 std::string device_path = device.NativePath();
283 if (strcmp(action, kUdevAddAction) == 0) {
Qijiang Fan52439042020-06-17 15:34:38 +0900284 if (base::Contains(devices_detected_, device_path)) {
Sergei Datsenkoad8b5042020-09-21 22:39:04 +1000285 LOG(INFO) << "Re-add device " << quote(device_path);
Sergei Datsenko16821892019-04-05 11:26:38 +1100286 events->push_back(DeviceEvent(DeviceEvent::kDeviceScanned, device_path));
287 } else {
288 devices_detected_.insert(device_path);
Sergei Datsenkoad8b5042020-09-21 22:39:04 +1000289 LOG(INFO) << "Add device " << quote(device_path);
Sergei Datsenko16821892019-04-05 11:26:38 +1100290 events->push_back(DeviceEvent(DeviceEvent::kDeviceAdded, device_path));
291 }
292 } else if (strcmp(action, kUdevRemoveAction) == 0) {
Qijiang Fan52439042020-06-17 15:34:38 +0900293 if (base::Contains(devices_detected_, device_path)) {
Sergei Datsenko16821892019-04-05 11:26:38 +1100294 devices_detected_.erase(device_path);
Sergei Datsenkoad8b5042020-09-21 22:39:04 +1000295 LOG(INFO) << "Remove device " << quote(device_path);
Sergei Datsenko16821892019-04-05 11:26:38 +1100296 events->push_back(DeviceEvent(DeviceEvent::kDeviceRemoved, device_path));
297 }
298 }
299}
300
301bool DiskMonitor::GetDeviceEvents(DeviceEventList* events) {
302 CHECK(events) << "Invalid device event list";
303
Anand K Mistry5757f632019-09-11 13:29:41 +1000304 std::unique_ptr<brillo::UdevDevice> dev = udev_monitor_->ReceiveDevice();
Sergei Datsenko16821892019-04-05 11:26:38 +1100305 if (!dev) {
306 LOG(WARNING) << "Ignore device event with no associated udev device.";
307 return false;
308 }
309
Anand K Mistry5757f632019-09-11 13:29:41 +1000310 const char* sys_path = dev->GetSysPath();
311 const char* subsystem = dev->GetSubsystem();
312 const char* action = dev->GetAction();
François Degros16ad1ae2019-07-17 16:02:39 +1000313
314 LOG(INFO) << "Got action " << quote(action) << " on device "
Anand K Mistry5757f632019-09-11 13:29:41 +1000315 << quote(dev->GetSysName());
316 LogUdevDevice(*dev);
François Degros16ad1ae2019-07-17 16:02:39 +1000317
Sergei Datsenko16821892019-04-05 11:26:38 +1100318 if (!sys_path || !subsystem || !action) {
Sergei Datsenko16821892019-04-05 11:26:38 +1100319 return false;
320 }
321
322 // |udev_monitor_| only monitors block, mmc, and scsi device changes, so
323 // subsystem is either "block", "mmc", or "scsi".
324 if (strcmp(subsystem, kBlockSubsystem) == 0) {
Anand K Mistry5757f632019-09-11 13:29:41 +1000325 ProcessBlockDeviceEvents(std::move(dev), action, events);
Sergei Datsenko16821892019-04-05 11:26:38 +1100326 } else {
327 // strcmp(subsystem, kMmcSubsystem) == 0 ||
328 // strcmp(subsystem, kScsiSubsystem) == 0
Anand K Mistry5757f632019-09-11 13:29:41 +1000329 ProcessMmcOrScsiDeviceEvents(std::move(dev), action, events);
Sergei Datsenko16821892019-04-05 11:26:38 +1100330 }
331
Sergei Datsenko16821892019-04-05 11:26:38 +1100332 return true;
333}
334
335bool DiskMonitor::GetDiskByDevicePath(const base::FilePath& device_path,
336 Disk* disk) const {
Sergei Datsenkoad8b5042020-09-21 22:39:04 +1000337 LOG(INFO) << "Get disk by path " << quote(device_path);
Sergei Datsenko16821892019-04-05 11:26:38 +1100338 if (device_path.empty())
339 return false;
340
Sergei Datsenkoad8b5042020-09-21 22:39:04 +1000341 std::unique_ptr<UdevDevice> device;
Anand K Mistryc0ece3d2020-01-28 15:55:29 +1100342 EnumerateBlockDevices(base::BindRepeating(
Sergei Datsenkoad8b5042020-09-21 22:39:04 +1000343 &MatchDiskByPath, device_path.value(), base::Unretained(&device)));
344 if (!device)
345 return false;
346
347 LogDevice(*device);
348 if (!IsDeviceAllowed(*device, allowlist_))
349 return false;
350
351 if (disk)
352 *disk = device->ToDisk();
353 return true;
Sergei Datsenko16821892019-04-05 11:26:38 +1100354}
355
Sergei Datsenkoad8b5042020-09-21 22:39:04 +1000356void DiskMonitor::AddDeviceToAllowlist(const base::FilePath& device) {
357 allowlist_.insert(device.value());
358}
359
360void DiskMonitor::RemoveDeviceFromAllowlist(const base::FilePath& device) {
361 allowlist_.erase(device.value());
Sergei Datsenko16821892019-04-05 11:26:38 +1100362}
363
364} // namespace cros_disks