def _generatePath(self, fusepath, opcodes=None): ''' Return the path to use for all file operations, based upon the current state of the world generated by _genCacheOpcodes. Returns: None Raises: Nothing ''' if opcodes == None: opcodes = self._genCacheOpcodes(fusepath) self._debug('Opcodes are: %s' % opcodes) for opcode in opcodes: if opcode == 'enoent': self._debug('ENOENT on %s' % fusepath) raise OSError(errno.ENOENT, os.strerror(errno.ENOENT)) if opcode == 'use-fs': fspath = tsumufs.fsPathOf(fusepath) self._debug('Returning fs path for %s -> %s' % (fusepath, fspath)) return fspath if opcode == 'use-cache': cachepath = tsumufs.cachePathOf(fusepath) self._debug('Returning cache path for %s -> %s' % (fusepath, cachepath)) return cachepath
def _cacheDir(self, fusepath): ''' Cache the directory referenced by path. If the directory should not be cached to disk (as specified in the cachespec) then only the contents of the directory hash table will be stored in the _cachedFiles hash. Returns: None Raises: OSError - when an error operating on the filesystem occurs. ''' self.lockFile(fusepath) try: fspath = tsumufs.fsPathOf(fusepath) cachepath = tsumufs.cachePathOf(fusepath) self._debug('fspath = %s' % fspath) self._debug('cachepath = %s' % cachepath) if fusepath == '/': self._debug('Asking to cache root -- skipping the cache to ' 'disk operation, but caching data in memory.') else: try: os.mkdir(cachepath) except OSError, e: # Skip EEXIST errors -- if it already exists, it may have files in it # already. Simply copy the stat and chown it again, then cache the # listdir operation as well. if e.errno != errno.EEXIST: raise # If activated, populating database will compare the filesystem dirents # to the database dirents, and add to database any files that could be # created bypassing the CouchedFilesystem api, directly on the filesystem. # The performance overhead raised by this operation increase when the number # of file to add to database is higher. if tsumufs.populateDb: try: self._debug('Discovering directory %s contents and populate database.' % fusepath) dirents = [ doc.filename for doc in tsumufs.fsOverlay.listdir(fusepath) ] for filename in os.listdir(fspath): if filename not in dirents: tsumufs.fsOverlay.populate(os.path.join(fusepath, filename)) except OSError, e: self._debug('Cannot list directory %s (%s)' % (fusepath, e.strerror))
def write(self, new_data, offset): self._debug("opcode: write | path: %s | offset: %d | buf: %s" % (self._path, offset, repr(new_data))) # Three cases here: # - The file didn't exist prior to our write. # - The file existed, but was extended. # - The file existed, and an existing block was overwritten. fspath = tsumufs.fsPathOf(self._path) statgoo = self._manager.statFile(self._path) if not self._isNewFile: self._debug("Reading offset %d, length %d from %s." % (offset, len(new_data), self._path)) old_data = self._manager.readFile(self._path, offset, len(new_data), os.O_RDONLY) self._debug("From cacheManager.readFile got %s" % repr(old_data)) # Pad missing chunks on the old_data stream with NULLs, as fs # would. Unfortunately during resyncing, we'll have to consider regions # past the end of a file to be NULLs as well. This allows us to merge data # regions cleanly without rehacking the model. if len(old_data) < len(new_data): self._debug( ("New data is past end of file by %d bytes. " "Padding with nulls.") % (len(new_data) - len(old_data)) ) old_data += "\x00" * (len(new_data) - len(old_data)) else: self._debug("We're a new file -- not adding a change record to log.") try: if self._manager.writeFile(self._path, offset, new_data, self._fdFlags, self._fdMode): if not self._isNewFile: self._debug( "Adding change to synclog [ %s | %d | %d | %s ]" % (self._path, offset, offset + len(new_data), repr(old_data)) ) tsumufs.syncLog.addChange(self._path, offset, offset + len(new_data), old_data) self._debug("Wrote %d bytes to cache." % len(new_data)) return len(new_data) except OSError, e: self._debug("OSError caught: errno %d: %s" % (e.errno, e.strerror)) return -e.errno
def _cacheFile(self, fusepath): ''' Cache the file referenced by path. This method locks the file for reading, determines what type it is, and attempts to cache it. Note that if there was an issue reading from the fsMount, this method will mark the fs mount as being unavailble. Note: The touch cache isn't implemented here at the moment. As a result, the entire cache is considered permacache for now. Note: fs error checking and disable are not handled here for the moment. Any errors that would ordinarily shut down the fs mount are just reported as normal OSErrors, aside from ENOENT. Returns: Nothing. Raises: OSError if there was an issue attempting to copy the file across to cache. ''' # TODO(jtg): Add support for storing the UID/GID self.lockFile(fusepath) try: fspath = tsumufs.fsPathOf(fusepath) cachepath = tsumufs.cachePathOf(fusepath) document = tsumufs.fsOverlay[fusepath] if stat.S_ISDIR(document.mode): # Caching a directory to disk -- call cacheDir instead. self._debug('Request to cache a directory -- calling _cacheDir') self._cacheDir(fusepath) else: self._debug('Caching file %s to disk.' % fusepath) if (stat.S_ISREG(document.mode) or stat.S_ISFIFO(document.mode) or stat.S_ISSOCK(document.mode) or stat.S_ISCHR(document.mode) or stat.S_ISBLK(document.mode)): try: shutil.copyfileobj(tsumufs.fsMount.open(fusepath, os.O_RDONLY | os.O_BINARY), open(cachepath, "wb")) except AttributeError, e: shutil.copyfileobj(tsumufs.fsMount.open(fusepath, os.O_RDONLY), open(cachepath, "w")) elif stat.S_ISLNK(document.mode): dest = os.readlink(fspath) try: os.unlink(cachepath) except OSError, e: if e.errno != errno.ENOENT: raise os.symlink(dest, cachepath)