| // Copyright 2020 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "runtime_probe/functions/usb_camera.h" |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <fcntl.h> |
| #include <linux/videodev2.h> |
| #include <sys/ioctl.h> |
| #include <sys/stat.h> |
| |
| #include <base/files/file_enumerator.h> |
| #include <base/files/file_util.h> |
| #include <base/json/json_writer.h> |
| #include <base/strings/string_util.h> |
| #include <base/strings/stringprintf.h> |
| #include <base/values.h> |
| |
| namespace runtime_probe { |
| |
| namespace { |
| constexpr char kDevVideoPath[] = "/dev/video*"; |
| |
| struct FieldType { |
| std::string key_name; |
| std::string file_name; |
| bool always_upper_case; |
| }; |
| const std::vector<FieldType> kRequiredFields{ |
| {"usb_vendor_id", "idVendor", false}, |
| {"usb_product_id", "idProduct", false}}; |
| const std::vector<FieldType> kOptionalFields{ |
| {"usb_manufacturer", "manufacturer", false}, |
| {"usb_product", "product", false}, |
| {"usb_bcd_device", "bcdDevice", false}, |
| {"usb_removable", "removable", true}}; |
| |
| bool IsCaptureDevice(const base::FilePath& path) { |
| int32_t fd = open(path.value().c_str(), O_RDONLY); |
| if (fd == -1) { |
| LOG(ERROR) << "Failed to open " << path; |
| return false; |
| } |
| |
| v4l2_capability cap; |
| if (ioctl(fd, VIDIOC_QUERYCAP, &cap, 1) == -1) { |
| LOG(ERROR) << "Failed to execute ioctl to query the V4L2 capability"; |
| return false; |
| } |
| if (close(fd) == -1) { |
| LOG(ERROR) << "Failed to close " << path; |
| } |
| |
| uint32_t mask = (cap.capabilities & V4L2_CAP_DEVICE_CAPS) ? cap.device_caps |
| : cap.capabilities; |
| return (mask & (V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_CAPTURE_MPLANE)) && |
| !(mask & (V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_VIDEO_OUTPUT_MPLANE)) && |
| !(mask & (V4L2_CAP_VIDEO_M2M | V4L2_CAP_VIDEO_M2M_MPLANE)); |
| } |
| |
| bool ReadSysfs(const base::FilePath& path, |
| const FieldType& field, |
| std::string* content) { |
| base::FilePath field_path(base::StringPrintf( |
| "/sys/class/video4linux/%s/device/../%s", path.BaseName().value().c_str(), |
| field.file_name.c_str())); |
| base::FilePath normalized_path; |
| if (!base::NormalizeFilePath(field_path, &normalized_path)) { |
| return false; |
| } |
| if (!base::ReadFileToString(normalized_path, content)) { |
| LOG(ERROR) << "Failed to read the file " << normalized_path; |
| return false; |
| } |
| base::TrimString(*content, " \n", content); |
| if (field.always_upper_case) { |
| *content = base::ToUpperASCII(*content); |
| } |
| return true; |
| } |
| |
| bool ReadUsbSysfs(const base::FilePath& path, base::Value* res) { |
| std::string content; |
| for (const auto& field : kRequiredFields) { |
| if (!ReadSysfs(path, field, &content)) { |
| LOG(ERROR) << "Failed to read the required field " << field.key_name; |
| return false; |
| } |
| res->SetStringKey(field.key_name, content); |
| } |
| for (const auto& field : kOptionalFields) { |
| std::string content; |
| if (ReadSysfs(path, field, &content)) { |
| res->SetStringKey(field.key_name, content); |
| } |
| } |
| return true; |
| } |
| |
| bool ExploreAsUsbCamera(const base::FilePath& path, base::Value* res) { |
| return IsCaptureDevice(path) && ReadUsbSysfs(path, res); |
| } |
| |
| } // namespace |
| |
| UsbCameraFunction::DataType UsbCameraFunction::Eval() const { |
| auto json_output = InvokeHelperToJSON(); |
| if (!json_output) { |
| LOG(ERROR) << "Failed to invoke helper to retrieve usb camera results."; |
| return {}; |
| } |
| if (!json_output->is_list()) { |
| LOG(ERROR) << "Failed to parse json output as list."; |
| return {}; |
| } |
| |
| return DataType(json_output->TakeList()); |
| } |
| |
| int UsbCameraFunction::EvalInHelper(std::string* output) const { |
| base::Value result(base::Value::Type::LIST); |
| |
| base::FilePath glob_path = base::FilePath(kDevVideoPath); |
| const auto glob_root = glob_path.DirName(); |
| const auto glob_pattern = glob_path.BaseName(); |
| base::FileEnumerator path_it(glob_root, false, |
| base::FileEnumerator::FileType::FILES, |
| glob_pattern.value()); |
| for (auto video_path = path_it.Next(); !video_path.empty(); |
| video_path = path_it.Next()) { |
| base::Value res(base::Value::Type::DICTIONARY); |
| res.SetStringKey("path", video_path.value()); |
| if (ExploreAsUsbCamera(video_path, &res)) { |
| res.SetStringKey("bus_type", "usb"); |
| result.Append(std::move(res)); |
| } |
| } |
| |
| if (!base::JSONWriter::Write(result, output)) { |
| LOG(ERROR) << "Failed to serialize usb camera result to json string"; |
| return -1; |
| } |
| return 0; |
| } |
| |
| } // namespace runtime_probe |