1 // Copyright 2014 The Chromium 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.
5 #include <CoreFoundation/CoreFoundation.h>
6 #include <IOKit/IOBSD.h>
7 #include <IOKit/IOKitLib.h>
8 #include <IOKit/storage/IOBlockStorageDevice.h>
9 #include <IOKit/storage/IOMedia.h>
10 #include <IOKit/storage/IOStorageProtocolCharacteristics.h>
11 #include <sys/socket.h>
13 #include "base/command_line.h"
14 #include "base/files/scoped_file.h"
15 #include "base/mac/scoped_cftyperef.h"
16 #include "base/mac/scoped_ioobject.h"
17 #include "base/posix/eintr_wrapper.h"
18 #include "base/process/kill.h"
19 #include "base/process/launch.h"
20 #include "base/strings/stringprintf.h"
21 #include "base/strings/sys_string_conversions.h"
22 #include "chrome/common/extensions/image_writer/image_writer_util_mac.h"
23 #include "chrome/utility/image_writer/disk_unmounter_mac.h"
24 #include "chrome/utility/image_writer/error_messages.h"
25 #include "chrome/utility/image_writer/image_writer.h"
27 namespace image_writer
{
29 static const char kAuthOpenPath
[] = "/usr/libexec/authopen";
31 bool ImageWriter::IsValidDevice() {
32 base::ScopedCFTypeRef
<CFStringRef
> cf_bsd_name(
33 base::SysUTF8ToCFStringRef(device_path_
.value()));
34 CFMutableDictionaryRef matching
= IOServiceMatching(kIOMediaClass
);
35 CFDictionaryAddValue(matching
, CFSTR(kIOMediaWholeKey
), kCFBooleanTrue
);
36 CFDictionaryAddValue(matching
, CFSTR(kIOMediaWritableKey
), kCFBooleanTrue
);
37 CFDictionaryAddValue(matching
, CFSTR(kIOBSDNameKey
), cf_bsd_name
);
39 io_service_t disk_obj
=
40 IOServiceGetMatchingService(kIOMasterPortDefault
, matching
);
41 base::mac::ScopedIOObject
<io_service_t
> iterator_ref(disk_obj
);
44 CFMutableDictionaryRef dict
;
45 if (IORegistryEntryCreateCFProperties(
46 disk_obj
, &dict
, kCFAllocatorDefault
, 0) != KERN_SUCCESS
) {
47 LOG(ERROR
) << "Unable to get properties of disk object.";
50 base::ScopedCFTypeRef
<CFMutableDictionaryRef
> dict_ref(dict
);
52 CFBooleanRef cf_removable
= base::mac::GetValueFromDictionary
<CFBooleanRef
>(
53 dict
, CFSTR(kIOMediaRemovableKey
));
54 bool removable
= CFBooleanGetValue(cf_removable
);
56 bool is_usb
= extensions::IsUsbDevice(disk_obj
);
58 return removable
|| is_usb
;
64 void ImageWriter::UnmountVolumes(const base::Closure
& continuation
) {
65 if (unmounter_
== NULL
) {
66 unmounter_
.reset(new DiskUnmounterMac());
73 &ImageWriter::Error
, base::Unretained(this), error::kUnmountVolumes
));
76 bool ImageWriter::OpenDevice() {
77 base::LaunchOptions options
= base::LaunchOptions();
80 // Create a socket pair for communication.
82 int result
= socketpair(AF_UNIX
, SOCK_STREAM
, 0, sockets
);
84 PLOG(ERROR
) << "Unable to allocate socket pair.";
87 base::ScopedFD
parent_socket(sockets
[0]);
88 base::ScopedFD
child_socket(sockets
[1]);
90 // Map the client socket to the client's STDOUT.
91 base::FileHandleMappingVector fd_map
;
92 fd_map
.push_back(std::pair
<int, int>(child_socket
.get(), STDOUT_FILENO
));
93 options
.fds_to_remap
= &fd_map
;
95 // Find the file path to open.
96 base::FilePath real_device_path
;
97 if (device_path_
.IsAbsolute()) {
98 // This only occurs for tests where the device path is mocked with a
100 real_device_path
= device_path_
;
102 // Get the raw device file. Writes need to be in multiples of
103 // DAMediaBlockSize (usually 512). This is fine since WriteChunk() writes in
104 // multiples of kMemoryAlignment.
106 base::FilePath("/dev").Append("r" + device_path_
.BaseName().value());
109 // Build the command line.
110 std::string rdwr
= base::StringPrintf("%d", O_RDWR
);
112 base::CommandLine cmd_line
= base::CommandLine(base::FilePath(kAuthOpenPath
));
113 cmd_line
.AppendSwitch("-stdoutpipe");
114 // Using AppendSwitchNative will use an equal-symbol which we don't want.
115 cmd_line
.AppendArg("-o");
116 cmd_line
.AppendArg(rdwr
);
117 cmd_line
.AppendArgPath(real_device_path
);
119 // Launch the process.
120 base::Process process
= base::LaunchProcess(cmd_line
, options
);
121 if (!process
.IsValid()) {
122 LOG(ERROR
) << "Failed to launch authopen process.";
126 // Receive a file descriptor from authopen which sends a single FD via
127 // sendmsg and the SCM_RIGHTS extension.
129 const size_t kDataBufferSize
= sizeof(struct cmsghdr
) + sizeof(int);
130 char data_buffer
[kDataBufferSize
];
132 struct iovec io_vec
[1];
133 io_vec
[0].iov_base
= data_buffer
;
134 io_vec
[0].iov_len
= kDataBufferSize
;
136 const socklen_t kCmsgSocketSize
=
137 static_cast<socklen_t
>(CMSG_SPACE(sizeof(int)));
138 char cmsg_socket
[kCmsgSocketSize
];
140 struct msghdr message
= {0};
141 message
.msg_iov
= io_vec
;
142 message
.msg_iovlen
= 1;
143 message
.msg_control
= cmsg_socket
;
144 message
.msg_controllen
= kCmsgSocketSize
;
146 ssize_t size
= HANDLE_EINTR(recvmsg(parent_socket
.get(), &message
, 0));
148 struct cmsghdr
* cmsg_socket_header
= CMSG_FIRSTHDR(&message
);
150 if (cmsg_socket_header
&& cmsg_socket_header
->cmsg_level
== SOL_SOCKET
&&
151 cmsg_socket_header
->cmsg_type
== SCM_RIGHTS
) {
152 fd
= *reinterpret_cast<int*>(CMSG_DATA(cmsg_socket_header
));
156 device_file_
= base::File(fd
);
158 // Wait for the child.
159 int child_exit_status
;
160 if (!process
.WaitForExit(&child_exit_status
)) {
161 LOG(ERROR
) << "Unable to wait for child.";
165 if (child_exit_status
) {
166 LOG(ERROR
) << "Child process returned failure.";
170 return device_file_
.IsValid();
173 } // namespace image_writer