X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/fc0e9f360dd51c5c508426846f2caed9f1c2ec66..9b5642f1d360ca1572dff5291c9be72913d4a623:/services/fuse/arvados_fuse/fusedir.py diff --git a/services/fuse/arvados_fuse/fusedir.py b/services/fuse/arvados_fuse/fusedir.py index a2e3ac139e..1406c4e4ad 100644 --- a/services/fuse/arvados_fuse/fusedir.py +++ b/services/fuse/arvados_fuse/fusedir.py @@ -2,11 +2,6 @@ # # SPDX-License-Identifier: AGPL-3.0 -from __future__ import absolute_import -from __future__ import division -from future.utils import viewitems -from future.utils import itervalues -from builtins import dict import apiclient import arvados import errno @@ -41,7 +36,7 @@ class Directory(FreshBase): and the value referencing a File or Directory object. """ - def __init__(self, parent_inode, inodes, apiconfig): + def __init__(self, parent_inode, inodes, apiconfig, enable_write): """parent_inode is the integer inode number""" super(Directory, self).__init__() @@ -54,6 +49,7 @@ class Directory(FreshBase): self.apiconfig = apiconfig self._entries = {} self._mtime = time.time() + self._enable_write = enable_write def forward_slash_subst(self): if not hasattr(self, '_fsns'): @@ -196,7 +192,7 @@ class Directory(FreshBase): def in_use(self): if super(Directory, self).in_use(): return True - for v in itervalues(self._entries): + for v in self._entries.values(): if v.in_use(): return True return False @@ -204,7 +200,7 @@ class Directory(FreshBase): def has_ref(self, only_children): if super(Directory, self).has_ref(only_children): return True - for v in itervalues(self._entries): + for v in self._entries.values(): if v.has_ref(False): return True return False @@ -226,7 +222,7 @@ class Directory(FreshBase): # Find self on the parent in order to invalidate this path. # Calling the public items() method might trigger a refresh, # which we definitely don't want, so read the internal dict directly. - for k,v in viewitems(parent._entries): + for k,v in parent._entries.items(): if v is self: self.inodes.invalidate_entry(parent, k) break @@ -274,10 +270,12 @@ class CollectionDirectoryBase(Directory): """ - def __init__(self, parent_inode, inodes, apiconfig, collection): - super(CollectionDirectoryBase, self).__init__(parent_inode, inodes, apiconfig) + def __init__(self, parent_inode, inodes, apiconfig, enable_write, collection, collection_root): + super(CollectionDirectoryBase, self).__init__(parent_inode, inodes, apiconfig, enable_write) self.apiconfig = apiconfig self.collection = collection + self.collection_root = collection_root + self.collection_record_file = None def new_entry(self, name, item, mtime): name = self.sanitize_filename(name) @@ -289,59 +287,105 @@ class CollectionDirectoryBase(Directory): item.fuse_entry.dead = False self._entries[name] = item.fuse_entry elif isinstance(item, arvados.collection.RichCollectionBase): - self._entries[name] = self.inodes.add_entry(CollectionDirectoryBase(self.inode, self.inodes, self.apiconfig, item)) + self._entries[name] = self.inodes.add_entry(CollectionDirectoryBase(self.inode, self.inodes, self.apiconfig, self._enable_write, item, self.collection_root)) self._entries[name].populate(mtime) else: - self._entries[name] = self.inodes.add_entry(FuseArvadosFile(self.inode, item, mtime)) + self._entries[name] = self.inodes.add_entry(FuseArvadosFile(self.inode, item, mtime, self._enable_write)) item.fuse_entry = self._entries[name] def on_event(self, event, collection, name, item): + # These are events from the Collection object (ADD/DEL/MOD) + # emitted by operations on the Collection object (like + # "mkdirs" or "remove"), and by "update", which we need to + # synchronize with our FUSE objects that are assigned inodes. if collection == self.collection: name = self.sanitize_filename(name) - _logger.debug("collection notify %s %s %s %s", event, collection, name, item) - with llfuse.lock: - if event == arvados.collection.ADD: - self.new_entry(name, item, self.mtime()) - elif event == arvados.collection.DEL: - ent = self._entries[name] - del self._entries[name] - self.inodes.invalidate_entry(self, name) - self.inodes.del_entry(ent) - elif event == arvados.collection.MOD: - if hasattr(item, "fuse_entry") and item.fuse_entry is not None: - self.inodes.invalidate_inode(item.fuse_entry) - elif name in self._entries: - self.inodes.invalidate_inode(self._entries[name]) + + # + # It's possible for another thread to have llfuse.lock and + # be waiting on collection.lock. Meanwhile, we released + # llfuse.lock earlier in the stack, but are still holding + # on to the collection lock, and now we need to re-acquire + # llfuse.lock. If we don't release the collection lock, + # we'll deadlock where we're holding the collection lock + # waiting for llfuse.lock and the other thread is holding + # llfuse.lock and waiting for the collection lock. + # + # The correct locking order here is to take llfuse.lock + # first, then the collection lock. + # + # Since collection.lock is an RLock, it might be locked + # multiple times, so we need to release it multiple times, + # keep a count, then re-lock it the correct number of + # times. + # + lockcount = 0 + try: + while True: + self.collection.lock.release() + lockcount += 1 + except RuntimeError: + pass + + try: + with llfuse.lock: + with self.collection.lock: + if event == arvados.collection.ADD: + self.new_entry(name, item, self.mtime()) + elif event == arvados.collection.DEL: + ent = self._entries[name] + del self._entries[name] + self.inodes.invalidate_entry(self, name) + self.inodes.del_entry(ent) + elif event == arvados.collection.MOD: + if hasattr(item, "fuse_entry") and item.fuse_entry is not None: + self.inodes.invalidate_inode(item.fuse_entry) + elif name in self._entries: + self.inodes.invalidate_inode(self._entries[name]) + + if self.collection_record_file is not None: + self.collection_record_file.invalidate() + self.inodes.invalidate_inode(self.collection_record_file) + finally: + while lockcount > 0: + self.collection.lock.acquire() + lockcount -= 1 def populate(self, mtime): self._mtime = mtime - self.collection.subscribe(self.on_event) - for entry, item in viewitems(self.collection): - self.new_entry(entry, item, self.mtime()) + with self.collection.lock: + self.collection.subscribe(self.on_event) + for entry, item in self.collection.items(): + self.new_entry(entry, item, self.mtime()) def writable(self): - return self.collection.writable() + return self._enable_write and self.collection.writable() @use_counter def flush(self): - with llfuse.lock_released: - self.collection.root_collection().save() + self.collection_root.flush() @use_counter @check_update def create(self, name): + if not self.writable(): + raise llfuse.FUSEError(errno.EROFS) with llfuse.lock_released: self.collection.open(name, "w").close() @use_counter @check_update def mkdir(self, name): + if not self.writable(): + raise llfuse.FUSEError(errno.EROFS) with llfuse.lock_released: self.collection.mkdirs(name) @use_counter @check_update def unlink(self, name): + if not self.writable(): + raise llfuse.FUSEError(errno.EROFS) with llfuse.lock_released: self.collection.remove(name) self.flush() @@ -349,6 +393,8 @@ class CollectionDirectoryBase(Directory): @use_counter @check_update def rmdir(self, name): + if not self.writable(): + raise llfuse.FUSEError(errno.EROFS) with llfuse.lock_released: self.collection.remove(name) self.flush() @@ -356,6 +402,9 @@ class CollectionDirectoryBase(Directory): @use_counter @check_update def rename(self, name_old, name_new, src): + if not self.writable(): + raise llfuse.FUSEError(errno.EROFS) + if not isinstance(src, CollectionDirectoryBase): raise llfuse.FUSEError(errno.EPERM) @@ -385,12 +434,10 @@ class CollectionDirectoryBase(Directory): class CollectionDirectory(CollectionDirectoryBase): """Represents the root of a directory tree representing a collection.""" - def __init__(self, parent_inode, inodes, api, num_retries, collection_record=None, explicit_collection=None): - super(CollectionDirectory, self).__init__(parent_inode, inodes, api.config, None) + def __init__(self, parent_inode, inodes, api, num_retries, enable_write, collection_record=None, explicit_collection=None): + super(CollectionDirectory, self).__init__(parent_inode, inodes, api.config, enable_write, None, self) self.api = api self.num_retries = num_retries - self.collection_record_file = None - self.collection_record = None self._poll = True try: self._poll_time = (api._rootDesc.get('blobSignatureTtl', 60*60*2) // 2) @@ -406,78 +453,90 @@ class CollectionDirectory(CollectionDirectoryBase): self._mtime = 0 self._manifest_size = 0 if self.collection_locator: - self._writable = (uuid_pattern.match(self.collection_locator) is not None) + self._writable = (uuid_pattern.match(self.collection_locator) is not None) and enable_write self._updating_lock = threading.Lock() def same(self, i): return i['uuid'] == self.collection_locator or i['portable_data_hash'] == self.collection_locator def writable(self): - return self.collection.writable() if self.collection is not None else self._writable + return self._enable_write and (self.collection.writable() if self.collection is not None else self._writable) + + @use_counter + def flush(self): + if not self.writable(): + return + with llfuse.lock_released: + with self._updating_lock: + if self.collection.committed(): + self.collection.update() + else: + self.collection.save() + self.new_collection_record(self.collection.api_response()) def want_event_subscribe(self): return (uuid_pattern.match(self.collection_locator) is not None) - # Used by arv-web.py to switch the contents of the CollectionDirectory - def change_collection(self, new_locator): - """Switch the contents of the CollectionDirectory. - - Must be called with llfuse.lock held. - """ - - self.collection_locator = new_locator - self.collection_record = None - self.update() - def new_collection(self, new_collection_record, coll_reader): if self.inode: self.clear() - - self.collection_record = new_collection_record - - if self.collection_record: - self._mtime = convertTime(self.collection_record.get('modified_at')) - self.collection_locator = self.collection_record["uuid"] - if self.collection_record_file is not None: - self.collection_record_file.update(self.collection_record) - self.collection = coll_reader + self.new_collection_record(new_collection_record) self.populate(self.mtime()) + def new_collection_record(self, new_collection_record): + if not new_collection_record: + raise Exception("invalid new_collection_record") + self._mtime = convertTime(new_collection_record.get('modified_at')) + self._manifest_size = len(new_collection_record["manifest_text"]) + self.collection_locator = new_collection_record["uuid"] + if self.collection_record_file is not None: + self.collection_record_file.invalidate() + self.inodes.invalidate_inode(self.collection_record_file) + _logger.debug("%s invalidated collection record file", self) + self.fresh() + def uuid(self): return self.collection_locator @use_counter - def update(self, to_record_version=None): + def update(self): try: - if self.collection_record is not None and portable_data_hash_pattern.match(self.collection_locator): + if self.collection is not None and portable_data_hash_pattern.match(self.collection_locator): + # It's immutable, nothing to update return True if self.collection_locator is None: + # No collection locator to retrieve from self.fresh() return True + new_collection_record = None try: with llfuse.lock_released: self._updating_lock.acquire() if not self.stale(): - return + return True - _logger.debug("Updating collection %s inode %s to record version %s", self.collection_locator, self.inode, to_record_version) + _logger.debug("Updating collection %s inode %s", self.collection_locator, self.inode) + coll_reader = None if self.collection is not None: - if self.collection.known_past_version(to_record_version): - _logger.debug("%s already processed %s", self.collection_locator, to_record_version) - else: - self.collection.update() + # Already have a collection object + self.collection.update() + new_collection_record = self.collection.api_response() else: + # Create a new collection object if uuid_pattern.match(self.collection_locator): coll_reader = arvados.collection.Collection( self.collection_locator, self.api, self.api.keep, - num_retries=self.num_retries) + num_retries=self.num_retries, + get_threads=(self.api.keep.block_cache.cache_max // (64 * 1024 * 1024))) else: coll_reader = arvados.collection.CollectionReader( self.collection_locator, self.api, self.api.keep, - num_retries=self.num_retries) + num_retries=self.num_retries, + get_threads=(self.api.keep.block_cache.cache_max // (64 * 1024 * 1024)) + ) new_collection_record = coll_reader.api_response() or {} # If the Collection only exists in Keep, there will be no API # response. Fill in the fields we need. @@ -487,15 +546,17 @@ class CollectionDirectory(CollectionDirectoryBase): new_collection_record["portable_data_hash"] = new_collection_record["uuid"] if 'manifest_text' not in new_collection_record: new_collection_record['manifest_text'] = coll_reader.manifest_text() + if 'storage_classes_desired' not in new_collection_record: + new_collection_record['storage_classes_desired'] = coll_reader.storage_classes_desired() - if self.collection_record is None or self.collection_record["portable_data_hash"] != new_collection_record.get("portable_data_hash"): - self.new_collection(new_collection_record, coll_reader) - - self._manifest_size = len(coll_reader.manifest_text()) - _logger.debug("%s manifest_size %i", self, self._manifest_size) # end with llfuse.lock_released, re-acquire lock - self.fresh() + if new_collection_record is not None: + if coll_reader is not None: + self.new_collection(new_collection_record, coll_reader) + else: + self.new_collection_record(new_collection_record) + return True finally: self._updating_lock.release() @@ -503,22 +564,29 @@ class CollectionDirectory(CollectionDirectoryBase): _logger.error("Error fetching collection '%s': %s", self.collection_locator, e) except arvados.errors.ArgumentError as detail: _logger.warning("arv-mount %s: error %s", self.collection_locator, detail) - if self.collection_record is not None and "manifest_text" in self.collection_record: - _logger.warning("arv-mount manifest_text is: %s", self.collection_record["manifest_text"]) + if new_collection_record is not None and "manifest_text" in new_collection_record: + _logger.warning("arv-mount manifest_text is: %s", new_collection_record["manifest_text"]) except Exception: _logger.exception("arv-mount %s: error", self.collection_locator) - if self.collection_record is not None and "manifest_text" in self.collection_record: - _logger.error("arv-mount manifest_text is: %s", self.collection_record["manifest_text"]) + if new_collection_record is not None and "manifest_text" in new_collection_record: + _logger.error("arv-mount manifest_text is: %s", new_collection_record["manifest_text"]) self.invalidate() return False + @use_counter + def collection_record(self): + self.flush() + return self.collection.api_response() + @use_counter @check_update def __getitem__(self, item): if item == '.arvados#collection': if self.collection_record_file is None: - self.collection_record_file = ObjectFile(self.inode, self.collection_record) + self.collection_record_file = FuncToJSONFile( + self.inode, self.collection_record) self.inodes.add_entry(self.collection_record_file) + self.invalidate() # use lookup as a signal to force update return self.collection_record_file else: return super(CollectionDirectory, self).__getitem__(item) @@ -530,8 +598,9 @@ class CollectionDirectory(CollectionDirectoryBase): return super(CollectionDirectory, self).__contains__(k) def invalidate(self): - self.collection_record = None - self.collection_record_file = None + if self.collection_record_file is not None: + self.collection_record_file.invalidate() + self.inodes.invalidate_inode(self.collection_record_file) super(CollectionDirectory, self).invalidate() def persisted(self): @@ -571,23 +640,42 @@ class TmpCollectionDirectory(CollectionDirectoryBase): def save_new(self): pass - def __init__(self, parent_inode, inodes, api_client, num_retries): + def __init__(self, parent_inode, inodes, api_client, num_retries, enable_write, storage_classes=None): collection = self.UnsaveableCollection( api_client=api_client, keep_client=api_client.keep, - num_retries=num_retries) + num_retries=num_retries, + storage_classes_desired=storage_classes) + # This is always enable_write=True because it never tries to + # save to the backend super(TmpCollectionDirectory, self).__init__( - parent_inode, inodes, api_client.config, collection) - self.collection_record_file = None + parent_inode, inodes, api_client.config, True, collection, self) self.populate(self.mtime()) def on_event(self, *args, **kwargs): super(TmpCollectionDirectory, self).on_event(*args, **kwargs) - if self.collection_record_file: + if self.collection_record_file is None: + return + + # See discussion in CollectionDirectoryBase.on_event + lockcount = 0 + try: + while True: + self.collection.lock.release() + lockcount += 1 + except RuntimeError: + pass + + try: with llfuse.lock: - self.collection_record_file.invalidate() - self.inodes.invalidate_inode(self.collection_record_file) - _logger.debug("%s invalidated collection record", self) + with self.collection.lock: + self.collection_record_file.invalidate() + self.inodes.invalidate_inode(self.collection_record_file) + _logger.debug("%s invalidated collection record", self) + finally: + while lockcount > 0: + self.collection.lock.acquire() + lockcount -= 1 def collection_record(self): with llfuse.lock_released: @@ -595,6 +683,7 @@ class TmpCollectionDirectory(CollectionDirectoryBase): "uuid": None, "manifest_text": self.collection.manifest_text(), "portable_data_hash": self.collection.portable_data_hash(), + "storage_classes_desired": self.collection.storage_classes_desired(), } def __contains__(self, k): @@ -617,6 +706,9 @@ class TmpCollectionDirectory(CollectionDirectoryBase): def writable(self): return True + def flush(self): + pass + def want_event_subscribe(self): return False @@ -653,11 +745,12 @@ and the directory will appear if it exists. """.lstrip() - def __init__(self, parent_inode, inodes, api, num_retries, pdh_only=False): - super(MagicDirectory, self).__init__(parent_inode, inodes, api.config) + def __init__(self, parent_inode, inodes, api, num_retries, enable_write, pdh_only=False, storage_classes=None): + super(MagicDirectory, self).__init__(parent_inode, inodes, api.config, enable_write) self.api = api self.num_retries = num_retries self.pdh_only = pdh_only + self.storage_classes = storage_classes def __setattr__(self, name, value): super(MagicDirectory, self).__setattr__(name, value) @@ -669,7 +762,8 @@ and the directory will appear if it exists. # If we're the root directory, add an identical by_id subdirectory. if self.inode == llfuse.ROOT_INODE: self._entries['by_id'] = self.inodes.add_entry(MagicDirectory( - self.inode, self.inodes, self.api, self.num_retries, self.pdh_only)) + self.inode, self.inodes, self.api, self.num_retries, self._enable_write, + self.pdh_only)) def __contains__(self, k): if k in self._entries: @@ -687,10 +781,11 @@ and the directory will appear if it exists. if project[u'items_available'] == 0: return False e = self.inodes.add_entry(ProjectDirectory( - self.inode, self.inodes, self.api, self.num_retries, project[u'items'][0])) + self.inode, self.inodes, self.api, self.num_retries, self._enable_write, + project[u'items'][0], storage_classes=self.storage_classes)) else: e = self.inodes.add_entry(CollectionDirectory( - self.inode, self.inodes, self.api, self.num_retries, k)) + self.inode, self.inodes, self.api, self.num_retries, self._enable_write, k)) if e.update(): if k not in self._entries: @@ -724,8 +819,8 @@ and the directory will appear if it exists. class TagsDirectory(Directory): """A special directory that contains as subdirectories all tags visible to the user.""" - def __init__(self, parent_inode, inodes, api, num_retries, poll_time=60): - super(TagsDirectory, self).__init__(parent_inode, inodes, api.config) + def __init__(self, parent_inode, inodes, api, num_retries, enable_write, poll_time=60): + super(TagsDirectory, self).__init__(parent_inode, inodes, api.config, enable_write) self.api = api self.num_retries = num_retries self._poll = True @@ -746,7 +841,8 @@ class TagsDirectory(Directory): self.merge(tags['items']+[{"name": n} for n in self._extra], lambda i: i['name'], lambda a, i: a.tag == i['name'], - lambda i: TagDirectory(self.inode, self.inodes, self.api, self.num_retries, i['name'], poll=self._poll, poll_time=self._poll_time)) + lambda i: TagDirectory(self.inode, self.inodes, self.api, self.num_retries, self._enable_write, + i['name'], poll=self._poll, poll_time=self._poll_time)) @use_counter @check_update @@ -780,9 +876,9 @@ class TagDirectory(Directory): to the user that are tagged with a particular tag. """ - def __init__(self, parent_inode, inodes, api, num_retries, tag, + def __init__(self, parent_inode, inodes, api, num_retries, enable_write, tag, poll=False, poll_time=60): - super(TagDirectory, self).__init__(parent_inode, inodes, api.config) + super(TagDirectory, self).__init__(parent_inode, inodes, api.config, enable_write) self.api = api self.num_retries = num_retries self.tag = tag @@ -804,15 +900,15 @@ class TagDirectory(Directory): self.merge(taggedcollections['items'], lambda i: i['head_uuid'], lambda a, i: a.collection_locator == i['head_uuid'], - lambda i: CollectionDirectory(self.inode, self.inodes, self.api, self.num_retries, i['head_uuid'])) + lambda i: CollectionDirectory(self.inode, self.inodes, self.api, self.num_retries, self._enable_write, i['head_uuid'])) class ProjectDirectory(Directory): """A special directory that contains the contents of a project.""" - def __init__(self, parent_inode, inodes, api, num_retries, project_object, - poll=True, poll_time=3): - super(ProjectDirectory, self).__init__(parent_inode, inodes, api.config) + def __init__(self, parent_inode, inodes, api, num_retries, enable_write, project_object, + poll=True, poll_time=3, storage_classes=None): + super(ProjectDirectory, self).__init__(parent_inode, inodes, api.config, enable_write) self.api = api self.num_retries = num_retries self.project_object = project_object @@ -823,18 +919,20 @@ class ProjectDirectory(Directory): self._updating_lock = threading.Lock() self._current_user = None self._full_listing = False + self.storage_classes = storage_classes def want_event_subscribe(self): return True def createDirectory(self, i): if collection_uuid_pattern.match(i['uuid']): - return CollectionDirectory(self.inode, self.inodes, self.api, self.num_retries, i) + return CollectionDirectory(self.inode, self.inodes, self.api, self.num_retries, self._enable_write, i) elif group_uuid_pattern.match(i['uuid']): - return ProjectDirectory(self.inode, self.inodes, self.api, self.num_retries, i, self._poll, self._poll_time) + return ProjectDirectory(self.inode, self.inodes, self.api, self.num_retries, self._enable_write, + i, self._poll, self._poll_time, self.storage_classes) elif link_uuid_pattern.match(i['uuid']): if i['head_kind'] == 'arvados#collection' or portable_data_hash_pattern.match(i['head_uuid']): - return CollectionDirectory(self.inode, self.inodes, self.api, self.num_retries, i['head_uuid']) + return CollectionDirectory(self.inode, self.inodes, self.api, self.num_retries, self._enable_write, i['head_uuid']) else: return None elif uuid_pattern.match(i['uuid']): @@ -901,11 +999,13 @@ class ProjectDirectory(Directory): uuid=self.project_uuid, filters=[["uuid", "is_a", "arvados#group"], ["groups.group_class", "in", ["project","filter"]]])) - contents.extend(arvados.util.keyset_list_all(self.api.groups().contents, + contents.extend(filter(lambda i: i["current_version_uuid"] == i["uuid"], + arvados.util.keyset_list_all(self.api.groups().contents, order_key="uuid", num_retries=self.num_retries, uuid=self.project_uuid, - filters=[["uuid", "is_a", "arvados#collection"]])) + filters=[["uuid", "is_a", "arvados#collection"]]))) + # end with llfuse.lock_released, re-acquire lock @@ -969,6 +1069,8 @@ class ProjectDirectory(Directory): @use_counter @check_update def writable(self): + if not self._enable_write: + return False with llfuse.lock_released: if not self._current_user: self._current_user = self.api.users().current().execute(num_retries=self.num_retries) @@ -980,11 +1082,21 @@ class ProjectDirectory(Directory): @use_counter @check_update def mkdir(self, name): + if not self.writable(): + raise llfuse.FUSEError(errno.EROFS) + try: with llfuse.lock_released: - self.api.collections().create(body={"owner_uuid": self.project_uuid, - "name": name, - "manifest_text": ""}).execute(num_retries=self.num_retries) + c = { + "owner_uuid": self.project_uuid, + "name": name, + "manifest_text": "" } + if self.storage_classes is not None: + c["storage_classes_desired"] = self.storage_classes + try: + self.api.collections().create(body=c).execute(num_retries=self.num_retries) + except Exception as e: + raise self.invalidate() except apiclient_errors.Error as error: _logger.error(error) @@ -993,6 +1105,9 @@ class ProjectDirectory(Directory): @use_counter @check_update def rmdir(self, name): + if not self.writable(): + raise llfuse.FUSEError(errno.EROFS) + if name not in self: raise llfuse.FUSEError(errno.ENOENT) if not isinstance(self[name], CollectionDirectory): @@ -1006,6 +1121,9 @@ class ProjectDirectory(Directory): @use_counter @check_update def rename(self, name_old, name_new, src): + if not self.writable(): + raise llfuse.FUSEError(errno.EROFS) + if not isinstance(src, ProjectDirectory): raise llfuse.FUSEError(errno.EPERM) @@ -1078,15 +1196,16 @@ class ProjectDirectory(Directory): class SharedDirectory(Directory): """A special directory that represents users or groups who have shared projects with me.""" - def __init__(self, parent_inode, inodes, api, num_retries, exclude, - poll=False, poll_time=60): - super(SharedDirectory, self).__init__(parent_inode, inodes, api.config) + def __init__(self, parent_inode, inodes, api, num_retries, enable_write, exclude, + poll=False, poll_time=60, storage_classes=None): + super(SharedDirectory, self).__init__(parent_inode, inodes, api.config, enable_write) self.api = api self.num_retries = num_retries self.current_user = api.users().current().execute(num_retries=num_retries) self._poll = True self._poll_time = poll_time self._updating_lock = threading.Lock() + self.storage_classes = storage_classes @use_counter def update(self): @@ -1156,8 +1275,6 @@ class SharedDirectory(Directory): obr = objects[r] if obr.get("name"): contents[obr["name"]] = obr - #elif obr.get("username"): - # contents[obr["username"]] = obr elif "first_name" in obr: contents[u"{} {}".format(obr["first_name"], obr["last_name"])] = obr @@ -1169,10 +1286,11 @@ class SharedDirectory(Directory): # end with llfuse.lock_released, re-acquire lock - self.merge(viewitems(contents), + self.merge(contents.items(), lambda i: i[0], lambda a, i: a.uuid() == i[1]['uuid'], - lambda i: ProjectDirectory(self.inode, self.inodes, self.api, self.num_retries, i[1], poll=self._poll, poll_time=self._poll_time)) + lambda i: ProjectDirectory(self.inode, self.inodes, self.api, self.num_retries, self._enable_write, + i[1], poll=self._poll, poll_time=self._poll_time, storage_classes=self.storage_classes)) except Exception: _logger.exception("arv-mount shared dir error") finally: