Home | History | Annotate | Download | only in cros
      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 
      5 import logging
      6 import os
      7 import sys
      8 
      9 import dbus
     10 
     11 from autotest_lib.client.cros import upstart
     12 
     13 def _proto_to_blob(proto):
     14     return dbus.ByteArray(proto.SerializeToString())
     15 
     16 class 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