# Copyright 2018 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. import logging import os import sys import dbus from autotest_lib.client.cros import upstart def _proto_to_blob(proto): return dbus.ByteArray(proto.SerializeToString()) class SmbProvider(object): """ Wrapper for D-Bus calls to SmbProvider Daemon The SmbProvider daemon handles calling the libsmbclient to communicate with an SMB server. This class is a wrapper to the D-Bus interface to the daemon. """ _DBUS_SERVICE_NAME = "org.chromium.SmbProvider" _DBUS_SERVICE_PATH = "/org/chromium/SmbProvider" _DBUS_INTERFACE_NAME = "org.chromium.SmbProvider" # Default timeout in seconds for D-Bus calls. _DEFAULT_TIMEOUT = 120 # Chronos user ID. _CHRONOS_UID = 1000 def __init__(self, bus_loop, proto_binding_location): """ Constructor. Creates and D-Bus connection to smbproviderd. @param bus_loop: Glib main loop object @param proto_binding_location: The location of generated python bindings for smbprovider protobufs. """ sys.path.append(proto_binding_location) self._bus_loop = bus_loop self.restart() def restart(self): """ Restarts smbproviderd and rebinds to D-Bus interface. """ logging.info('restarting smbproviderd') upstart.restart_job('smbproviderd') try: # Get the interface as Chronos since only they are allowed to send # D-Bus messages to smbproviderd. os.setresuid(self._CHRONOS_UID, self._CHRONOS_UID, 0) bus = dbus.SystemBus(self._bus_loop) proxy = bus.get_object(self._DBUS_SERVICE_NAME, self._DBUS_SERVICE_PATH) self._smbproviderd = dbus.Interface(proxy, self._DBUS_INTERFACE_NAME) finally: os.setresuid(0, 0, 0) def stop(self): """ Stops smbproviderd. """ logging.info('stopping smbproviderd') try: upstart.stop_job('smbproviderd') finally: self._smbproviderd = None def mount(self, mount_path, workgroup, username, password): """ Mounts a share. @param mount_path: Path of the share to mount. @param workgroup: Workgroup for the mount. @param username: Username for the mount. @param password: Password for the mount. @return A tuple with the ErrorType and the mount id returned the D-Bus call. """ logging.info("Mounting: %s", mount_path) from directory_entry_pb2 import MountOptionsProto from directory_entry_pb2 import MountConfigProto proto = MountOptionsProto() proto.path = mount_path proto.workgroup = workgroup proto.username = username proto.mount_config.enable_ntlm = True with self.DataFd(password) as password_fd: return self._smbproviderd.Mount(_proto_to_blob(proto), dbus.types.UnixFd(password_fd), timeout=self._DEFAULT_TIMEOUT, byte_arrays=True) def unmount(self, mount_id): """ Unmounts a share. @param mount_id: Mount ID to be umounted. @return: ErrorType from the returned D-Bus call. """ logging.info("Unmounting: %s", mount_id) from directory_entry_pb2 import UnmountOptionsProto proto = UnmountOptionsProto() proto.mount_id = mount_id return self._smbproviderd.Unmount(_proto_to_blob(proto)) def create_directory(self, mount_id, directory_path, recursive): """ Creates a directory. @param mount_id: Mount ID corresponsding to the share. @param directory_path: Path of the directory to read. @param recursive: Boolean to indicate whether directories should be created recursively. @return: ErrorType from the returned D-Bus call. """ logging.info("Creating directory: %s", directory_path) from directory_entry_pb2 import CreateDirectoryOptionsProto from directory_entry_pb2 import ERROR_OK proto = CreateDirectoryOptionsProto() proto.mount_id = mount_id proto.directory_path = directory_path proto.recursive = recursive return self._smbproviderd.CreateDirectory( _proto_to_blob(proto), timout=self._DEFAULT_TIMEOUT, byte_arrays=True) def read_directory(self, mount_id, directory_path): """ Reads a directory. @param mount_id: Mount ID corresponding to the share. @param directory_path: Path of the directory to read. @return A tuple with the ErrorType and the DirectoryEntryListProto blob string returned by the D-Bus call. """ logging.info("Reading directory: %s", directory_path) from directory_entry_pb2 import ReadDirectoryOptionsProto from directory_entry_pb2 import DirectoryEntryListProto from directory_entry_pb2 import ERROR_OK proto = ReadDirectoryOptionsProto() proto.mount_id = mount_id proto.directory_path = directory_path error, entries_blob = self._smbproviderd.ReadDirectory( _proto_to_blob(proto), timeout=self._DEFAULT_TIMEOUT, byte_arrays=True) entries = DirectoryEntryListProto() if error == ERROR_OK: entries.ParseFromString(entries_blob) return error, entries def get_metadata(self, mount_id, entry_path): """ Gets metadata for an entry. @param mount_id: Mount ID from the mounted share. @param entry_path: Path of the entry. @return A tuple with the ErrorType and the GetMetadataEntryOptionsProto blob string returned by the D-Bus call. """ logging.info("Getting metadata for %s", entry_path) from directory_entry_pb2 import GetMetadataEntryOptionsProto from directory_entry_pb2 import DirectoryEntryProto from directory_entry_pb2 import ERROR_OK proto = GetMetadataEntryOptionsProto() proto.mount_id = mount_id proto.entry_path = entry_path error, entry_blob = self._smbproviderd.GetMetadataEntry( _proto_to_blob(proto), timeout=self._DEFAULT_TIMEOUT, byte_arrays=True) entry = DirectoryEntryProto() if error == ERROR_OK: entry.ParseFromString(entry_blob) return error, entry def open_file(self, mount_id, file_path, writeable): """ Opens a file. @param mount_id: Mount ID from the mounted share. @param file_path: Path of the file to be opened. @param writeable: Whether the file should be opened with write access. @return A tuple with the ErrorType and the File ID of the opened file. """ logging.info("Opening file: %s", file_path) from directory_entry_pb2 import OpenFileOptionsProto proto = OpenFileOptionsProto() proto.mount_id = mount_id proto.file_path = file_path proto.writeable = writeable return self._smbproviderd.OpenFile(_proto_to_blob(proto), timeout=self._DEFAULT_TIMEOUT, byte_arrays=True) def close_file(self, mount_id, file_id): """ Closes a file. @param mount_id: Mount ID from the mounted share. @param file_id: ID of the file to be closed. @return ErrorType returned from the D-Bus call. """ logging.info("Closing file: %s", file_id) from directory_entry_pb2 import CloseFileOptionsProto proto = CloseFileOptionsProto() proto.mount_id = mount_id proto.file_id = file_id return self._smbproviderd.CloseFile(_proto_to_blob(proto), timeout=self._DEFAULT_TIMEOUT, byte_arrays=True) def read_file(self, mount_id, file_id, offset, length): """ Reads a file. @param mount_id: Mount ID from the mounted share. @param file_id: ID of the file to be read. @param offset: Offset to start reading. @param length: Length in bytes to read. @return A tuple with ErrorType and and a buffer containing the data read. """ logging.info("Reading file: %s", file_id) from directory_entry_pb2 import ReadFileOptionsProto from directory_entry_pb2 import ERROR_OK proto = ReadFileOptionsProto() proto.mount_id = mount_id proto.file_id = file_id proto.offset = offset proto.length = length error, fd = self._smbproviderd.ReadFile(_proto_to_blob(proto), timeout=self._DEFAULT_TIMEOUT, byte_arrays=True) data = '' if error == ERROR_OK: data = os.read(fd.take(), length) return error, data def create_file(self, mount_id, file_path): """ Creates a file. @param mount_id: Mount ID from the mounted share. @param file_path: Path of the file to be created. @return ErrorType returned from the D-Bus call. """ logging.info("Creating file: %s", file_path) from directory_entry_pb2 import CreateFileOptionsProto proto = CreateFileOptionsProto() proto.mount_id = mount_id proto.file_path = file_path return self._smbproviderd.CreateFile(_proto_to_blob(proto), timeout=self._DEFAULT_TIMEOUT, byte_arrays=True) def delete_entry(self, mount_id, entry_path, recursive): """ Deletes an entry. @param mount_id: Mount ID from the mounted share. @param entry_path: Path of the entry to be deleted. @param recursive: Boolean indicating whether the delete should be recursive for directories. @return ErrorType returned from the D-Bus call. """ logging.info("Deleting entry: %s", entry_path) from directory_entry_pb2 import DeleteEntryOptionsProto proto = DeleteEntryOptionsProto() proto.mount_id = mount_id proto.entry_path = entry_path proto.recursive = recursive return self._smbproviderd.DeleteEntry(_proto_to_blob(proto), timeout=self._DEFAULT_TIMEOUT, byte_arrays=True) def move_entry(self, mount_id, source_path, target_path): """ Moves an entry from source to target destination. @param mount_id: Mount ID from the mounted share. @param source_path: Path of the entry to be moved. @param target_path: Path of where the entry will be moved to. Target path must be a non-existent path. @return ErrorType returned from the D-Bus call. """ logging.info("Moving file to: %s", target_path) from directory_entry_pb2 import MoveEntryOptionsProto proto = MoveEntryOptionsProto() proto.mount_id = mount_id proto.source_path = source_path proto.target_path = target_path return self._smbproviderd.MoveEntry(_proto_to_blob(proto), timeout=self._DEFAULT_TIMEOUT, byte_arrays=True) def truncate(self, mount_id, file_path, length): """ Truncates a file. @param mount_id: Mount ID from the mounted share. @param file_path: Path of the file to be truncated. @param length: The new size of the file in bytes. @return ErrorType returned from the D-Bus call. """ logging.info("Truncating file: %s", file_path) from directory_entry_pb2 import TruncateOptionsProto proto = TruncateOptionsProto() proto.mount_id = mount_id proto.file_path = file_path proto.length = length return self._smbproviderd.Truncate(_proto_to_blob(proto), timeout=self._DEFAULT_TIMEOUT, byte_arrays=True) def write_file(self, mount_id, file_id, offset, data): """ Writes data to a file. @param mount_id: Mount ID from the mounted share. @param file_id: ID of the file to be written to. @param offset: Offset of the file to start writing to. @param data: Data to be written. @return ErrorType returned from the D-Bus call. """ logging.info("Writing to file: %s", file_id) from directory_entry_pb2 import WriteFileOptionsProto proto = WriteFileOptionsProto() proto.mount_id = mount_id proto.file_id = file_id proto.offset = offset proto.length = len(data) with self.DataFd(data) as data_fd: return self._smbproviderd.WriteFile(_proto_to_blob(proto), dbus.types.UnixFd(data_fd), timeout=self._DEFAULT_TIMEOUT, byte_arrays=True) class DataFd(object): """ Writes data into a file descriptor. Use in a 'with' statement to automatically close the returned file descriptor. @param data: Data string. @return A file descriptor (pipe) containing the data. """ def __init__(self, data): self._data = data self._read_fd = None def __enter__(self): """Creates the data file descriptor.""" self._read_fd, write_fd = os.pipe() os.write(write_fd, self._data) os.close(write_fd) return self._read_fd def __exit__(self, mytype, value, traceback): """Closes the data file descriptor again.""" if self._read_fd: os.close(self._read_fd)