Package: subterfugue Version: 0.2.1a-9.5 Please find attached an sf trick I wrote for handling annoying programs which insist on plain files. I originally wrote this before discovering ffmpeg's image2pipe format :-) and amazingly it works.
Usage is eg: sf -t FakePipe:debug=1 -t Scratch ffmpeg -y -f image2 ... It is Copyright (C)2007 Ian Jackson. I hereby licence it under the GNU GPL, version 2 or at your option any later version. Ian.
# make program read from named pipes exactly once each while # it thinks they're seekable files from Trick import Trick import Memory import fnmatch import stat import os import sys import scratch import shutil class FakePipe(Trick): def usage(self): return """ Mediate attempts to open and read named pipes. Restrictions on the sf client: * The sf client opens a named pipe (only ever for reading). * All of the reads, fstats and seeks of each named pipe must occur on a single fd onto the pipe obtained from a single call to open. Consequences - if these restrictions are true, then: * Other processes will see the sf client open the pipe once and read all of the data from it in one go. * The data from the pipe will be spooled to a file alongside the pipe; the filename <name-of-pipe>.~fake-pipe-file~ is reserved. These files may or may not be deleted but disk space for their data will only be used at times when this is necessary. * The sf client will see not a pipe but a plain file containing the relevant data. Infelicities: * The sf client may see a file with a zero link count in fstat. * If the sf client calls [l]stat it will see the actual pipe and not the file. The `pattern' parameter is a glob pattern which specifies affected `open's. """ def __init__(self,options): self.pattern = '*' self.suffix = '.~fake-pipe-file' self.pids_fds = {} self.debug = 0 if options.has_key('pattern'): self.pattern = options['pattern'] if options.has_key('debug'): self.debug = int(options['debug']) def d(self, l, m): if self.debug < l: return print >>sys.stderr, "FakePipe:", m class Entry: pass def callbefore(self, pid, call, args): getarg = Memory.getMemory(pid).get_string if (call == 'open' and args[1] & (os.O_RDONLY | os.O_RDWR | os.O_WRONLY) == os.O_RDONLY): fn = getarg(args[0]) if not fnmatch.fnmatch(fn, self.pattern): return try: statinfo = os.stat(fn) except OSError, e: self.d(3, 'opening "%s" - error %s' % (fn, e)) return if not stat.S_ISFIFO(statinfo.st_mode): self.d(3, 'opening "%s" - not a pipe' % fn); return state = self.Entry() state.fn = fn state.nf = fn+self.suffix self.d(2, 'opening "%s" -> "%s" ...' % (fn, state.nf)) (state.tofree, nfc) = scratch.alloc_str(state.nf) state.copied = False args[0] = nfc args[1] = os.O_RDWR | os.O_CREAT; args[2] = 0600; return (state, None,None, args) if (call == 'read' or call == 'fstat' or fnmatch.fnmatch(call, '*lseek*')): fd = args[0] try: state = self.pids_fds[pid][fd] except KeyError: self.d(3, 'examining %s %d - not ours' % (call, fd)) return if state.copied: self.d(2, 'examining %s %d - already copied' % (call, fd)) return self.d(1, 'examining %s %d, copying ...' % (call, fd)) try: real = open(state.fn, 'r') fake = open(state.nf, 'w') shutil.copyfileobj(real,fake) real.close() fake.close() except IOError, e: self.d(1, 'examining %s %d: copy-fail %s' % (call, fd, e)) return (None, e.errno, None, None) self.d(2, 'examining %s %d - copy ok' % (call, fd)) state.copied = True if (call == 'close'): return (args[0], None,None,None) if (call == 'dup2'): return (args[1], None,None,None) def callafter(self, pid, call, result, state): if (call == 'open' and state is not None): self.d(1, 'opening "%s" ... result=%d' % (state.fn, result)) scratch.free(state.tofree) state.tofree = None if (result >= 0): try: pf = self.pids_fds[pid] except KeyError: pf = {}; self.pids_fds[pid] = pf pf[result] = state if (call == 'close' or call == 'dup2'): self.do_close(call, pid, state) def do_close(self, call, pid, fd): try: ostate = self.pids_fds[pid][fd] except KeyError: self.d(3, 'closing %s %d - not ours' % (call, fd)) return if ostate.copied: self.d(1, 'closing %s %d - copied, deleting' % (call, fd)) os.remove(ostate.nf) else: self.d(2, 'closing %s %d - not copied' % (call, fd)) del self.pids_fds[pid][fd]