Internal RPM State Machines (largely OBSOLETE)

When updating a system RPM walks through three state machines:

  • TSM (Transaction State Machine)
  • PSM (Package State Machine)
  • FSM (File State Machine)

These state machines are chained such that the TSM enters the PSM, and the PSM enters the FSM. The TSM is responsible for the overall transaction of packages, and calls the PSM to install/erase/repackage individual packages. In turn the PSM is responsible for managing the install/erasure/repackaging of individual packages and it calls the FSM to handle the files of an individual package.

Transaction State Machine

As stated above the transaction state machine is responsible for the overall updating of the system via an rpm transaction. Each transaction (an rpmts) contains various install elements and erase elements (these are rpmte’s in the source). Unlike the PSM and the FSM, the TSM does not have programatic states that it goes through (i.e. its not really coded as a state machine) but its very helpful to think of it in terms of state machine. The source files that comprise things relating to the TSM are:

  • rpmte.c, rpmte.h - Defines methods and attributes of transaction elements.
  • rpmts.c, rpmts.h - Defines methods and attributes of transactions.
  • transaction.c - Defines high level actions that can be perfomed upon an rpmts/transaction.

Depending on how you want to think about it, you can consider the transaction state machine as having several entry points that must be manually entered (rpmtsCheck(), rpmtsOrder(), rpmtsRun()) in order, or you can think of rpmtsCheck and rpmtsOrder as just setup for the entry of the TSM via rpmtsRun(). If you take the first approach, then state machine has a high level logic of:

   Check Transaction For Dependency Satisfaction (rpmtsCheck())
   If success
      Order Transaction Elements based on dependencies (rpmtsOrder())
   if success
      Run Transaction (rpmtsRun())

Either way you look at it the major work of the TSM is done in rpmtsRun, so below is pseudo code to document its flow:

Not Done Yet

The Package State Machine

The PSM is called at various times by the TSM for one of three purposes:

  • to install a package (a transaction element from the view point of the TSM).
  • to erase a package.
  • to repackage a package that will be erased.

These “purposes” are seen by the PSM as an overall goal to achieve, which are defined by the following macros:

  • PSM_PKGINSTALL - used to install a package.
  • PSM_PKGERASE - used to erase a package.
  • PSM_PKGSAVE - used to repackage a package.

The PSM is thus entered with one of these three initial states, that then gets translated to the PSM goal. Whichever the goal the PSM at a high level looks like this:

   PSM_INIT                             # Initialize package state machine for goal
   if success PSM_PRE                   # Pre package install/erase/repackage activites (e.g. %pre)
   if success PSM_PROCESS               # Deliver/Erase/Repackage Files (FSM is entered here).
   if success PSM_POST                  # Post package install/erase/repackage activities (e.g. %post)
   PSM_FINI                             # Clean up PSM

To enter into the PSM, a call is made to rpmpsmStage() (this is found in psm.c) with the second argument being the state/stage you wish the PSM to transition too. The intial stage is, is one of the three states listed above (i.e. PSM_PKGINSTALL, PSM_PKGERASE, PSM_PKGSTAGE). After this initial entry the PSM transitions through the five major states listed above (i.e. PSM_INIT, PSM_PRE, PSM_PROCESS, PSM_POST, PSM_FINI). Beyond these major states, their are several sub states into which each of the major states can transition. They are listed below:

  • PSM_COMMIT - NOT SURE
  • PSM_CHROOT_IN - Enter chroot environment.
  • PSM_CHROOT_OUT - Exit chroot environment.
  • PSM_SCRIPT - Run a scriptlet.
  • PSM_TRIGGERS - Run triggers that are set against this package.
  • PSM_IMMED_TRIGGERS - Run triggers owned by this package.
  • PSM_RPMIO_FLAGS - Setup rpmio flags.
  • PSM_RPMDB_LOAD - Retrieves installed package header.
  • PSM_RPMDB_ADD - Adds header from package to rpmdb.
  • PSM_RPMDB_REMOVE - Removes package header from rpmdb.

The following are in the PSM, but are no-ops presently (Jeff, Is there an explanation for these? Well, I know there is, but is this dead code, or code waiting to be implemented:

  • PSM_PKGCOMMIT - Not used (Jeff could you elaborate on this?)
  • PSM_UNDO - Not used (Jeff, ditto)
  • PSM_CREATE - Not used.
  • PSM_DESTROY - Not used.
  • PSM_NOTIFY - Used by the PSM to notify consumers of librpm of package events.

The remaining subsections will list the high level logic of the major states (PSM_INIT, PSM_PRE, PSM_PROCESS, PSM_POST, PSM_FINI).

PSM_PKGINSTALL, PSM_PKGERASE, PSM_PKGSAVE

set PSM goal
transition to PSM_INIT
if OK
   transition to PSM_PRE
if OK
   transition to PSM_PROCESS
if OK
   transition to PSM_POST
transition to PSM_FINI

PSM_INIT

calculate pkg instance count
if goal PSM_PKGINSTALL
    increment pkg instance count
    Try to find matching NEVRAO header in rpmdb and save in PSM state.
    if JUSTDB  return
    if file count is <= 0 return
    strip PREFIX from files in old format relocatable packages?
    strip / from files in current format relocatable packages

if goal PSM_PKGERASE
    decrement pkg instance count
    retrieve pkg header from rpmdb
if goal PSM_PKGSAVE
   generate path to repackaged package
   open repackaged package for write

return

PSM_PRE

if test return
transition to PSM_CHROOT_IN   # This will change into the chroot, if we are not 
already there; then it returns here
if goal PSM_PKGINSTALL
   # There is a place holder for future %triggerprein
   #
   # Run %pre script if allowed
   if %pre allowed
      setup PSM to to run %pre script
      transition to PSM_SCRIPT      # Will run %pre script
if goal PSM_PKGERASE
   #
   # Run %preuntrigger's if allowed
   if %preuntrigger allowed
      setup PSM to run %preuntriggers
      transition to PSM_IMMED_TRIGGERS  # runs %preuntriggers owned by this package
      transition to PSM_TRIGGERS               # runs %preuntriggers triggered by this package
   #
   # Run %pre if allowed
   if %preun allowed
      setup PSM to run %preun
      transition to PSM_SCRIPT                   # runs %pre and returns here
if goal PSM_PGKSAVE
   get package header from rpmdb
   transition to PSM_RPMIO_FLAGS
   write package lead into repackaged package
   write package signature into repackaged package
   # 
   # Set the REMOVETID of the header to the TID of the currently running transaction.
   # This will intentionally poison the repackaged packages signature
   add REMOVETID to header
   write header to repackaged package

PSM_PROCESS

if test return 
if goal PSM_PKGINSTALL
   if justdb return
   if no files in package 
      synthesize notify callbacks       # Will result in progress hashes being printed
      return
   iterate over files in package 
      if user of file does not exit on the system 
         turn off SUID bit of file
         set user of file to 0
      if group of file does not exist on the system 
         turn off the SGID bit
         set group of file to 0
   transition to PSM_RPMIO_FLAGS   # sets compression type
   #
   # A check is made to make sure the package has a file descriptor associated with it.
   # This is probably due to the hack where one of the times rpm notify callback is called
   # the callback is required to re-open the package.  This maddness per Jeff is to deal with
   # things like switch media in anaconda.
   if fd for package does not exist return FAILURE
   # 
   # Jeff what is this cfd thing?
   if cfd for package does not exist return FAILURE
   enter FSM with goal of FSM_PKGINSTALL
   if FSM suceeded
      transition to PSM_COMMIT
   #
   # Notify consumer of 100% installation of files
   transition to PSM_NOTIFY with RPMCALLBACK_INST_PROGRESS
   if FSM failed
   transition to PSM_NOTIFY with RPMCALLBACK_INST_PROGRESS
   if FSM failed
      print RPM error
      transition to PSM_NOTIFY with RPMCALLBACK_UNPACK_ERROR
      return FAILURE
   return OK
if goal PSM_PKGERASE
   if justdb return OK
   if apply only return OK   # ???
   if no files to erase return OK
   transition to PSM_NOTIFY with RPMCALLBACK_UNINST_START
   enter FSM with goal of FSM_PKGERASE  # This will remove any files remaining owned only by this package
   transition to PSM_NOTIFY with RPMCALLBACK_UNINST_STOP
   return OK
if goal PSM_PKGSAVE
   set fileset action to FA_COPYOUT # ???
   set fileset actions to NULL
   if no fd for package return FAIL
   if no cfd for package return FAIL # ???
   enter FSM with goal of FSM_PKGBUILD
   transition to PSM_NOTIFY with RPMCALLBACK_INST_PROGRESS
   return OK

PSM_POST

if test return OK
if goal PSM_PKGINSTALL
   calculate install time
   set RPMTAG_FILESTATES in header      # ???
   set RPMTAG_INSTALLTIME in header
   set RPMTAG_INSTALLCOLOR in header
   # 
   # Remove header with same NEVRA (color?)
   transition to PSM_RPMDB_REMOVE
   #
   # Add new header to rpm db
   transition to PSM_RPMDB_ADD
   if %post allowed
      transition to PSM_SCRIPT  # Run %post script
   if %triggerin allowed
      transition to PSM_TRIGGERS                # Run %triggerin in other packages
      transition to PSM_IMMED_TRIGGERS   # Run %triggerin in this package
   if not apply only
      mark replaced files  # ???
if goal PSM_PKGERASE
   # Special note:  %postun failures do not cause PSM to return an error???
   if %postun allowed
      transition to PSM_SCRIPT    # Run %postun
   if %triggerpostun allowed
      transition to PSM_TRIGGERS   # Run %triggerpostun in other packages
   if not apply only
      transition to PSM_RPMDB_REMOVE   # Remove header from db
#
# Exit chroot
transition to PSM_CHROOT_OUT
return OK

PSM_FINI

#
# Exit chroot
transition to PSM_CHROOT_OUT
close any package fd
if PSM has had error
   display error message
   transition to PSM_NOTIFY with RPMCALLBACK_CPIO_ERROR
clean up

The File State Machine

RIP

Acknowledgements

This information originally came from James Olin Oden’s personal examination of the rpm source code.