def wait_for_objects(object_list, timeout=0, wait_for_all=False): """Wait until list of object are in signaled state. :param object_list: a list of handles :type object_list: list[int] :param timeout: maximum waiting time in seconds. If 0 then maximum waiting time is set to infinity :type timeout: int :param wait_for_all: if True wait for all object to be signaled. If False wait for one object to be signaled :type wait_for_all: bool :return: index in the object list of the signaled object or None in case of timeout :rtype: int | None :raise: WindowsError """ from e3.os.windows.native_api import NT, Wait from ctypes.wintypes import HANDLE if timeout == 0: timeout = Wait.INFINITE else: timeout = int(timeout * 1000) size = len(object_list) handle_array = HANDLE * size handles = handle_array(*object_list) object_index = NT.WaitForMultipleObjects(size, handles, wait_for_all, timeout) if object_index == Wait.TIMEOUT: return None elif object_index == Wait.FAILED: # defensive code raise WindowsError("error while waiting for objects") elif Wait.ABANDONED <= object_index \ < Wait.ABANDONED + size: # defensive code return object_index - Wait.ABANDONED elif Wait.OBJECT <= object_index < Wait.OBJECT + size: return object_index else: # defensive code raise WindowsError("unknown error while waiting for objects")
def uid(self): """Retrieve the ID of the file. On NTFS system we are sure that this ID is unique on the given volume :return: the uid :rtype: int ;raise: NTException """ result = FileInfo.Internal() status = NT.QueryInformationFile(self.handle, pointer(self.io_status), pointer(result), sizeof(result), FileInfo.Internal.class_id) if status < 0: # defensive code # we should already have raised an error here when trying # to open the file raise NTException(status=status, message='cannot find file uid', origin="NTFile.uid") return result.index_number
def rename(self, filename, replace=False): """Move file. :param filename: target location :type filename: unicode :param replace: if True replace the target file if it exists :type replace: bool :raise: NTException """ target = u"\\??\\%s" % os.path.abspath(filename) target = target.encode('utf_16_le') s = "?PL%ss" % len(target) b = create_string_buffer(struct.calcsize(s)) b.raw = struct.pack(s, replace, 0, len(target), target) status = NT.SetInformationFile(self.handle, pointer(self.io_status), b, struct.calcsize(s), FileInfo.Rename.class_id) if status < 0: raise NTException(status=status, message="move of %s to %s failed" % (self.path, filename), origin="NTFile.rename")
def unlink(self): """Remove file safely. :raise: NTException """ open_options = self.open_options is_in_trash = False # First we need to check that file is not mark readonly try: self.read_attributes_internal() except NTException as e: if e.status == Status.OBJECT_NAME_NOT_FOUND: return elif e.status == Status.DELETE_PENDING: return else: raise if self.is_readonly: # Try to remove the readonly flag self.basic_info.file_attributes.attr &= ~FileAttribute.READONLY self.write_attributes() # set our access modes desired_access = Access.DELETE shared_access = Share.DELETE if self.is_dir: desired_access |= Access.LIST_DIRECTORY | Access.SYNCHRONIZE open_options |= OpenOptions.SYNCHRONOUS_IO_NON_ALERT try_counter = 10 # Open the file for deletion while try_counter > 0: try: self.open(desired_access, shared_access, open_options) break except NTException as e: if e.status == Status.SHARING_VIOLATION: # File is already open elsewhere for a non delete operation # Try a few times to open it with relaxed share settings shared_access = Share.ALL elif e.status == Status.DELETE_PENDING: # defensive code # file is already pending deletion (just after our call # to read_attributes) so consider the deletion # is done and return return else: # defensive code # We don't know what to do here so just fail raise # Wait a few ms before attempting again to open the file NT.Sleep(5) try_counter -= 1 if try_counter == 0: raise NTException( status=1, message="cannot open file %s for deletion" % self.path, origin="NTFile.unlink", ) # From there we assume that the file has been opened try: if shared_access == Share.ALL: # The file is also opened elsewhere for a non delete operation # In that case we will try to move it to the trash # first check that the directory is empty if self.is_dir and not self.is_dir_empty: raise NTException( status=1, message="directory not empty: %s" % self.path, origin="NTFile.unlink", ) self.move_to_trash() is_in_trash = True # If the file has been moved away then we try to delete it in the # trash but it is not necessary to try as hard as the goal is to # just keep the trash space used as low as possible. if is_in_trash: try_counter = 5 else: try_counter = 20 while try_counter > 0: try: self.dispose() break except NTException as e: if e.status == Status.DIRECTORY_NOT_EMPTY: # The directory is not empty but that might be because # of remaining files in PENDING_DELETE status. Our # is_dir_empty method return empty if the directory # contains only files pending for deletion if not self.is_dir_empty: raise NTException( status=e.status, message="dir %s is not empty" % self.path, origin="NTFile.unlink", ) elif e.status == Status.CANNOT_DELETE: # defensive code # At this stage we are sure that the file is not # read_only but it seems that we can get this error # when the file has been mapped to memory. if not is_in_trash: try: self.move_to_trash() is_in_trash = True try_counter = min(try_counter, 5) except NTException: pass else: # defensive code # Unknown error. If the file has been moved away # consider it success. Otherwise reraise exception if is_in_trash: break raise NT.Sleep(5) try_counter -= 1 finally: self.close()
def iterate_on_dir(self, fun, default_result=None): """Iterate on directory. :param fun: function called on each entry (. are .. are skipped) :param default_result: default return value :return: last return value or fun or default_result """ result = default_result s_size = struct.calcsize("LLL") b_size = 100 * 1024 b = create_string_buffer(b_size) status = NT.QueryDirectoryFile( self.handle, None, None, None, pointer(self.io_status), b, b_size, FileInfo.Names.class_id, False, None, True, ) if status == Status.NO_MORE_FILES: # defensive code # In theory this case should not occurs at it means that the # directory does not even have the . and .. entries. In practice # it can occurs (probably because of an intermediate state). # In that case behave as if the directory is empty return result if status < 0: raise NTException( status=status, message="can't read dir %s" % self.path, origin="NTFile.iterate_on_dir", ) while status >= 0 and status != Status.NO_MORE_FILES: pos = 0 while True: off, _, size = struct.unpack_from("LLL", b.raw, pos) name = b.raw[pos + s_size:pos + s_size + size].decode("utf-16-le") if name != "." and name != "..": result, should_exit = fun(name, self) if should_exit: return result if off == 0: break pos += off status = NT.QueryDirectoryFile( self.handle, None, None, None, pointer(self.io_status), b, b_size, FileInfo.Names.class_id, False, None, False, ) return result