def execve(self, pid): enter = self.arg dbg.infom("!", "#R<re-execute#>: %s" % str(enter)) fn = enter.args["filename"].path argv = enter.args["argv"] envp = enter.args["envp"] [ret, root, pwd, fds] = self.ret.aux # set sequence number as execve()'s sid self.seq = enter.sid & ((1 << 48) - 1) + 1 # XXX # - setup fds self.proc = rerun.Ptrace([fn] + argv[1:], envp, kutil.get(pwd), kutil.get(root) if root.ino != 2 else None, self.uids) # enter/exit should be execve() too (r, p) = self.proc.load(True, None) (r, p) = self.proc.load(False, None) assert r.usage & EXIT and r.nr == syscall.NR_execve # virtualize pid self.pids[pid] = r.pid dbg.rerun("vpid:%s -> pid:%s (active)" % (pid, r.pid)) # enable rerun module self.state = "active"
def process_action(self): self.next = self.pick_action dbg.infom( '*', '%s:%s' % (prettify(self.action), "same" if self.action.equiv() else "#R<changed#>")) # dbg.infom('*', '[%s]%s:%s' % (prettify(self.action.tic), # prettify(self.action), # "S" if self.action.equiv() else "#R<D#>")) # ipopov: conjecture: # The purpose of this if/elif/else chain is to prevent the redo # from continuing if any of the statements in the elif's return to # the effect that other nodes have been updated during this pass # through process_action(). Why do we care? Because this class is # implemented as a state machine, and processing maintains the # invariant that the action being redone now is the earliest one. # But connect() and prepare_redo() may affect the invariant. if (not self.action.cancel and self.action.equiv() and all([ self.cur_state[n] >= self.action for n in self.action.outputs | set([self.action.actor]) ])): dbg.info('skipping', self.action) for n in self.action.inputs: self.set_cs(n, max(self.cur_state[n], self.action)) self.set_color(self.action, self.colors['skipped']) elif not self.action.connect(): dbg.debug('retrying after connect') elif not self.prepare_redo(): dbg.debug('retrying after prepare') elif self.action.cancel: if hasattr(self.action, 'cancel_hook'): self.action.cancel_hook() dbg.info('cancelled action', self.action) self.next = self.update_state self.set_color(self.action, self.colors['cancelled']) else: dbg.info('#G<redoing#> ', prettify(self.action)) self.save_ckpts(self.action.outputs, self.action) try: self.action.redo() except mgrapi.ReschedulePendingException: dbg.info('rescheduling %s' % prettify(self.action)) return else: self.next = self.update_state self.set_color(self.action, self.colors['redone'])
def dump_graph(self): """Print, through dbg.infom(), the nodes and actions that this instance contains. (ipopov)""" for o in self.cur_state: dbg.infom('%s' % (o.__class__.__name__)) for a in self.actions(o): dbg.infom(' -> %s' % (a.__class__.__name__)) dbg.infom(' -> actor %s' % (a.actor.__class__.__name__))
def rollback(self, c): assert (isinstance(c, FileCheckpoint)) # XXX. hack to ignore /proc entries if any(c.d_pn.startswith(d) for d in ["/proc", "/dev"]): return cp_ino = None # ino of the directory _entry_ cpn = c.pn() if cpn: cp_ino = self._get_inode(cpn, self.name_in_dir) dev_pn = kutil.fsget(self.dev) dir_pn = kutil.iget(dev_pn, self.ino) cur_ino = self._get_inode(dir_pn, self.name_in_dir) if cp_ino == cur_ino: return if cur_ino is not None: # backup src = os.path.join(dir_pn, self.name_in_dir) dst = os.path.join(dev_pn, ".inodes", str(cur_ino)) dbg.infom("!", "#R<rename#>: %s -> %s" % (src, dst)) dbg.infom("readers %s" % str(self.readers)) dbg.infom("writers %s" % str(self.writers)) if not runopts.dryrun: os.rename(src, dst) if cp_ino is not None: src = kutil.iget(dev_pn, cp_ino) dst = os.path.join(dir_pn, self.name_in_dir) dbg.infom("!", "#R<link#>: %s -> %s" % (src, dst)) if not runopts.dryrun: os.link(src, dst) kutil.dcache_flush()
def rollback(self, c): assert (isinstance(c, FileCheckpoint)) # XXX. hack to ignore /proc and /dev entries if any(c.d_pn.startswith(d) for d in ["/proc", "/dev"]): return None cpn = c.pn() try: dev_pn = kutil.fsget(self.dev) ino_pn = kutil.iget(dev_pn, self.ino) except kutil.MissingInode: if cpn is None: return dbg.infom("!", "#R<rollback#> %s(%s) -> %s" % (self.ino, ino_pn, cpn)) dbg.infom("readers %s" % str(self.readers)) dbg.infom("writers %s" % str(self.writers)) if runopts.dryrun: return cstat = os.lstat(cpn) if cpn else None istat = os.lstat(ino_pn) if (cstat and stat.S_IFMT(istat.st_mode) != stat.S_IFMT(cstat.st_mode)): raise Exception('inode changed types', cpn, ino_pn, cstat, istat) if stat.S_ISREG(istat.st_mode): if cpn: with open(cpn, 'r') as f1: f1data = f1.read() else: f1data = '' with open(ino_pn, 'r') as f2: f2data = f2.read() if f1data != f2data: with open(ino_pn, 'w') as f2: f2.write(f1data) elif stat.S_ISLNK(istat.st_mode): pass ## no way to change existing symlink elif stat.S_ISDIR(istat.st_mode): # XXX. pass else: raise Exception('cannot handle inode type', stat.S_IFMT(istat.st_mode))
def redo(self): """Execute another action of this action's actor. If the greedy heuristic matches the current action to the action just executed, we're fine. Otherwise, call parse_record() to create a new node in the action history graph. """ dbg.syscall("redoing") rerun = self.actor.rerun if not rerun: dbg.syscall("no rerun instance bound to ProcSyscall %s;" "returning prematurely" % self) return # following history while rerunning if rerun.is_following(): (r, p) = rerun.next(True, self.actor.pid) if r == None: dbg.syscall('Aborting syscall; rerun returned no record') return assert r.usage & ENTER # special treatment of wait4 # (see note in wait4.doc) # The following if statement seems pretty impenetrable. What I think it # does is the following. If a process under rerun tries to execute a # wait4, but that doesn't match up with the expected syscall from the # previous execution, call code.interact() and handle it manually. if (r.nr == syscall.NR_wait4 and self.argsnode.origdata.nr != syscall.NR_wait4): dbg.rerun('entering interactive') code.interact(local=locals()) # wait4() again, at this time we should be able # to fetch child exit status (r, p) = rerun.next(False, self.actor.pid) assert r.usage & ENTER and r.nr == syscall.NR_wait4 # this is a real system call we should execute # on this action node (r, p) = rerun.next(True, self.actor.pid) assert r.usage & ENTER # match up if (self.argsnode.origdata.nr == r.nr and check_args(r, self.argsnode.origdata)): # update arguments self.argsnode.data = deepcopy(self.argsnode.origdata) self.argsnode.data.args = r.args self.argsnode.rerun = rerun # failed: need to explore/create new nodes/edges else: dbg.infom( "!", "#R<mismatched#> syscalls: %s vs %s" % (self.argsnode.origdata, r)) assert self.argsnode.data == None ts = 0 for a in rerun.actors: t = max(a.actions) if ts < t.tac: ts = t.tac rerun.detach(r, ts) # Actually create a new node to correspond to what was just # executed. self.osloader.parse_record(r) # rerunning but creating new nodes/edges elif rerun.is_state("detached"): # if detached node if rerun.wait_r == self.argsnode.origdata: rerun.resume() #rerun.activate() # ipopov: ???? if rerun.is_expanding(): self.argsnode.data = self.argsnode.origdata self.argsnode.rerun = rerun