1# Copyright 2018 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 5import logging 6import os 7import sys 8 9import dbus 10 11from autotest_lib.client.cros import upstart 12 13def _proto_to_blob(proto): 14 return dbus.ByteArray(proto.SerializeToString()) 15 16class SmbProvider(object): 17 """ 18 Wrapper for D-Bus calls to SmbProvider Daemon 19 20 The SmbProvider daemon handles calling the libsmbclient to communicate with 21 an SMB server. This class is a wrapper to the D-Bus interface to the daemon. 22 23 """ 24 25 _DBUS_SERVICE_NAME = "org.chromium.SmbProvider" 26 _DBUS_SERVICE_PATH = "/org/chromium/SmbProvider" 27 _DBUS_INTERFACE_NAME = "org.chromium.SmbProvider" 28 29 # Default timeout in seconds for D-Bus calls. 30 _DEFAULT_TIMEOUT = 120 31 32 # Chronos user ID. 33 _CHRONOS_UID = 1000 34 35 def __init__(self, bus_loop, proto_binding_location): 36 """ 37 Constructor. 38 39 Creates and D-Bus connection to smbproviderd. 40 41 @param bus_loop: Glib main loop object 42 @param proto_binding_location: The location of generated python bindings 43 for smbprovider protobufs. 44 45 """ 46 47 sys.path.append(proto_binding_location) 48 self._bus_loop = bus_loop 49 self.restart() 50 51 def restart(self): 52 """ 53 Restarts smbproviderd and rebinds to D-Bus interface. 54 55 """ 56 57 logging.info('restarting smbproviderd') 58 upstart.restart_job('smbproviderd') 59 60 try: 61 # Get the interface as Chronos since only they are allowed to send 62 # D-Bus messages to smbproviderd. 63 os.setresuid(self._CHRONOS_UID, self._CHRONOS_UID, 0) 64 65 bus = dbus.SystemBus(self._bus_loop) 66 proxy = bus.get_object(self._DBUS_SERVICE_NAME, 67 self._DBUS_SERVICE_PATH) 68 self._smbproviderd = dbus.Interface(proxy, 69 self._DBUS_INTERFACE_NAME) 70 71 finally: 72 os.setresuid(0, 0, 0) 73 74 def stop(self): 75 """ 76 Stops smbproviderd. 77 78 """ 79 80 logging.info('stopping smbproviderd') 81 82 try: 83 upstart.stop_job('smbproviderd') 84 85 finally: 86 self._smbproviderd = None 87 88 def mount(self, mount_path, workgroup, username, password): 89 """ 90 Mounts a share. 91 92 @param mount_path: Path of the share to mount. 93 @param workgroup: Workgroup for the mount. 94 @param username: Username for the mount. 95 @param password: Password for the mount. 96 97 @return A tuple with the ErrorType and the mount id returned the D-Bus 98 call. 99 100 """ 101 102 logging.info("Mounting: %s", mount_path) 103 104 from directory_entry_pb2 import MountOptionsProto 105 from directory_entry_pb2 import MountConfigProto 106 107 proto = MountOptionsProto() 108 proto.path = mount_path 109 proto.workgroup = workgroup 110 proto.username = username 111 proto.mount_config.enable_ntlm = True 112 113 with self.DataFd(password) as password_fd: 114 return self._smbproviderd.Mount(_proto_to_blob(proto), 115 dbus.types.UnixFd(password_fd), 116 timeout=self._DEFAULT_TIMEOUT, 117 byte_arrays=True) 118 119 def unmount(self, mount_id): 120 """ 121 Unmounts a share. 122 123 @param mount_id: Mount ID to be umounted. 124 125 @return: ErrorType from the returned D-Bus call. 126 127 """ 128 129 logging.info("Unmounting: %s", mount_id) 130 131 from directory_entry_pb2 import UnmountOptionsProto 132 133 proto = UnmountOptionsProto() 134 proto.mount_id = mount_id 135 136 return self._smbproviderd.Unmount(_proto_to_blob(proto)) 137 138 def create_directory(self, mount_id, directory_path, recursive): 139 """ 140 Creates a directory. 141 142 @param mount_id: Mount ID corresponsding to the share. 143 @param directory_path: Path of the directory to read. 144 @param recursive: Boolean to indicate whether directories should be 145 created recursively. 146 147 @return: ErrorType from the returned D-Bus call. 148 149 """ 150 151 logging.info("Creating directory: %s", directory_path) 152 153 from directory_entry_pb2 import CreateDirectoryOptionsProto 154 from directory_entry_pb2 import ERROR_OK 155 156 proto = CreateDirectoryOptionsProto() 157 proto.mount_id = mount_id 158 proto.directory_path = directory_path 159 proto.recursive = recursive 160 161 return self._smbproviderd.CreateDirectory( 162 _proto_to_blob(proto), 163 timout=self._DEFAULT_TIMEOUT, 164 byte_arrays=True) 165 166 167 def read_directory(self, mount_id, directory_path): 168 """ 169 Reads a directory. 170 171 @param mount_id: Mount ID corresponding to the share. 172 @param directory_path: Path of the directory to read. 173 174 @return A tuple with the ErrorType and the DirectoryEntryListProto blob 175 string returned by the D-Bus call. 176 177 """ 178 179 logging.info("Reading directory: %s", directory_path) 180 181 from directory_entry_pb2 import ReadDirectoryOptionsProto 182 from directory_entry_pb2 import DirectoryEntryListProto 183 from directory_entry_pb2 import ERROR_OK 184 185 proto = ReadDirectoryOptionsProto() 186 proto.mount_id = mount_id 187 proto.directory_path = directory_path 188 189 error, entries_blob = self._smbproviderd.ReadDirectory( 190 _proto_to_blob(proto), 191 timeout=self._DEFAULT_TIMEOUT, 192 byte_arrays=True) 193 194 entries = DirectoryEntryListProto() 195 if error == ERROR_OK: 196 entries.ParseFromString(entries_blob) 197 198 return error, entries 199 200 def get_metadata(self, mount_id, entry_path): 201 """ 202 Gets metadata for an entry. 203 204 @param mount_id: Mount ID from the mounted share. 205 @param entry_path: Path of the entry. 206 207 @return A tuple with the ErrorType and the GetMetadataEntryOptionsProto 208 blob string returned by the D-Bus call. 209 210 """ 211 212 logging.info("Getting metadata for %s", entry_path) 213 214 from directory_entry_pb2 import GetMetadataEntryOptionsProto 215 from directory_entry_pb2 import DirectoryEntryProto 216 from directory_entry_pb2 import ERROR_OK 217 218 proto = GetMetadataEntryOptionsProto() 219 proto.mount_id = mount_id 220 proto.entry_path = entry_path 221 222 error, entry_blob = self._smbproviderd.GetMetadataEntry( 223 _proto_to_blob(proto), 224 timeout=self._DEFAULT_TIMEOUT, 225 byte_arrays=True) 226 227 entry = DirectoryEntryProto() 228 if error == ERROR_OK: 229 entry.ParseFromString(entry_blob) 230 231 return error, entry 232 233 def open_file(self, mount_id, file_path, writeable): 234 """ 235 Opens a file. 236 237 @param mount_id: Mount ID from the mounted share. 238 @param file_path: Path of the file to be opened. 239 @param writeable: Whether the file should be opened with write access. 240 241 @return A tuple with the ErrorType and the File ID of the opened file. 242 243 """ 244 245 logging.info("Opening file: %s", file_path) 246 247 from directory_entry_pb2 import OpenFileOptionsProto 248 249 proto = OpenFileOptionsProto() 250 proto.mount_id = mount_id 251 proto.file_path = file_path 252 proto.writeable = writeable 253 254 return self._smbproviderd.OpenFile(_proto_to_blob(proto), 255 timeout=self._DEFAULT_TIMEOUT, 256 byte_arrays=True) 257 258 def close_file(self, mount_id, file_id): 259 """ 260 Closes a file. 261 262 @param mount_id: Mount ID from the mounted share. 263 @param file_id: ID of the file to be closed. 264 265 @return ErrorType returned from the D-Bus call. 266 267 """ 268 269 logging.info("Closing file: %s", file_id) 270 271 from directory_entry_pb2 import CloseFileOptionsProto 272 273 proto = CloseFileOptionsProto() 274 proto.mount_id = mount_id 275 proto.file_id = file_id 276 277 return self._smbproviderd.CloseFile(_proto_to_blob(proto), 278 timeout=self._DEFAULT_TIMEOUT, 279 byte_arrays=True) 280 281 def read_file(self, mount_id, file_id, offset, length): 282 """ 283 Reads a file. 284 285 @param mount_id: Mount ID from the mounted share. 286 @param file_id: ID of the file to be read. 287 @param offset: Offset to start reading. 288 @param length: Length in bytes to read. 289 290 @return A tuple with ErrorType and and a buffer containing the data 291 read. 292 293 """ 294 295 logging.info("Reading file: %s", file_id) 296 297 from directory_entry_pb2 import ReadFileOptionsProto 298 from directory_entry_pb2 import ERROR_OK 299 300 proto = ReadFileOptionsProto() 301 proto.mount_id = mount_id 302 proto.file_id = file_id 303 proto.offset = offset 304 proto.length = length 305 306 error, fd = self._smbproviderd.ReadFile(_proto_to_blob(proto), 307 timeout=self._DEFAULT_TIMEOUT, 308 byte_arrays=True) 309 310 data = '' 311 if error == ERROR_OK: 312 data = os.read(fd.take(), length) 313 314 return error, data 315 316 def create_file(self, mount_id, file_path): 317 """ 318 Creates a file. 319 320 @param mount_id: Mount ID from the mounted share. 321 @param file_path: Path of the file to be created. 322 323 @return ErrorType returned from the D-Bus call. 324 325 """ 326 327 logging.info("Creating file: %s", file_path) 328 329 from directory_entry_pb2 import CreateFileOptionsProto 330 331 proto = CreateFileOptionsProto() 332 proto.mount_id = mount_id 333 proto.file_path = file_path 334 335 return self._smbproviderd.CreateFile(_proto_to_blob(proto), 336 timeout=self._DEFAULT_TIMEOUT, 337 byte_arrays=True) 338 339 def delete_entry(self, mount_id, entry_path, recursive): 340 """ 341 Deletes an entry. 342 343 @param mount_id: Mount ID from the mounted share. 344 @param entry_path: Path of the entry to be deleted. 345 @param recursive: Boolean indicating whether the delete should be 346 recursive for directories. 347 348 @return ErrorType returned from the D-Bus call. 349 350 """ 351 352 logging.info("Deleting entry: %s", entry_path) 353 354 from directory_entry_pb2 import DeleteEntryOptionsProto 355 356 proto = DeleteEntryOptionsProto() 357 proto.mount_id = mount_id 358 proto.entry_path = entry_path 359 proto.recursive = recursive 360 361 return self._smbproviderd.DeleteEntry(_proto_to_blob(proto), 362 timeout=self._DEFAULT_TIMEOUT, 363 byte_arrays=True) 364 365 def move_entry(self, mount_id, source_path, target_path): 366 """ 367 Moves an entry from source to target destination. 368 369 @param mount_id: Mount ID from the mounted share. 370 @param source_path: Path of the entry to be moved. 371 @param target_path: Path of where the entry will be moved to. Target 372 path must be a non-existent path. 373 374 @return ErrorType returned from the D-Bus call. 375 376 """ 377 378 logging.info("Moving file to: %s", target_path) 379 380 from directory_entry_pb2 import MoveEntryOptionsProto 381 382 proto = MoveEntryOptionsProto() 383 proto.mount_id = mount_id 384 proto.source_path = source_path 385 proto.target_path = target_path 386 387 return self._smbproviderd.MoveEntry(_proto_to_blob(proto), 388 timeout=self._DEFAULT_TIMEOUT, 389 byte_arrays=True) 390 391 def truncate(self, mount_id, file_path, length): 392 """ 393 Truncates a file. 394 395 @param mount_id: Mount ID from the mounted share. 396 @param file_path: Path of the file to be truncated. 397 @param length: The new size of the file in bytes. 398 399 @return ErrorType returned from the D-Bus call. 400 401 """ 402 403 logging.info("Truncating file: %s", file_path) 404 405 from directory_entry_pb2 import TruncateOptionsProto 406 407 proto = TruncateOptionsProto() 408 proto.mount_id = mount_id 409 proto.file_path = file_path 410 proto.length = length 411 412 return self._smbproviderd.Truncate(_proto_to_blob(proto), 413 timeout=self._DEFAULT_TIMEOUT, 414 byte_arrays=True) 415 416 def write_file(self, mount_id, file_id, offset, data): 417 """ 418 Writes data to a file. 419 420 @param mount_id: Mount ID from the mounted share. 421 @param file_id: ID of the file to be written to. 422 @param offset: Offset of the file to start writing to. 423 @param data: Data to be written. 424 425 @return ErrorType returned from the D-Bus call. 426 427 """ 428 429 logging.info("Writing to file: %s", file_id) 430 431 from directory_entry_pb2 import WriteFileOptionsProto 432 433 proto = WriteFileOptionsProto() 434 proto.mount_id = mount_id 435 proto.file_id = file_id 436 proto.offset = offset 437 proto.length = len(data) 438 439 with self.DataFd(data) as data_fd: 440 return self._smbproviderd.WriteFile(_proto_to_blob(proto), 441 dbus.types.UnixFd(data_fd), 442 timeout=self._DEFAULT_TIMEOUT, 443 byte_arrays=True) 444 445 class DataFd(object): 446 """ 447 Writes data into a file descriptor. 448 449 Use in a 'with' statement to automatically close the returned file 450 descriptor. 451 452 @param data: Data string. 453 454 @return A file descriptor (pipe) containing the data. 455 456 """ 457 458 def __init__(self, data): 459 self._data = data 460 self._read_fd = None 461 462 def __enter__(self): 463 """Creates the data file descriptor.""" 464 465 self._read_fd, write_fd = os.pipe() 466 os.write(write_fd, self._data) 467 os.close(write_fd) 468 return self._read_fd 469 470 def __exit__(self, mytype, value, traceback): 471 """Closes the data file descriptor again.""" 472 473 if self._read_fd: 474 os.close(self._read_fd) 475