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]

Reply via email to