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]