SYMLINK_LIB=no migration tool
(c) 2017 Michał Górny
Licensed under 2-clause BSD license


TL;DR: Run 'unsymlink-lib' as root and follow the on-screen instructions.


== Goal ==

The tools are meant to help safely migrating live amd64 (or alike)
system from the SYMLINK_LIB=yes to SYMLINK_LIB=no layout.

The old (SYMLINK_LIB=yes) layout looks like this:

  lib -> lib64   -- mostly libexec-like, plus 32-bit ld.so
  lib64          -- 64-bit stuff
  lib32          -- 32-bit stuff

The new (SYMLINK_LIB=no) layout looks like this:

  lib            -- libexec-like + 32-bit stuff
  lib64          -- 64-bit stuff

Therefore, the migration involves splitting lib and lib64, and merging
lib32 into the former. Since lib contains key system components
including kernel modules, gcc, portage, a unsafe migration procedure
could result in serious, hard-to-fix breakage.


== Procedure ==

The following steps are used to accomplish a safe migration:

1. A new temporary 'lib.new' directory is created. All files
and directories belonging to the 'lib' directory (based on recorded file
lists) are *copied* into this directory. All files and directories from
lib32 are copied into this directory. Therefore, it resembles the new
'lib' directory.

2. The 'lib' symlink is replaced with a symlink to 'lib.new'. User can
now reboot to test whether the migrated system will work. If it does not
boot, reverting is as simple as restoring the old 'lib' symlink
(and removing the failed 'lib.new' variant, as part of cleanup).

3. If the system works, the migration can continue. The 'lib' symlink is
removed, and 'lib.new' is renamed into 'lib'. 'lib32' is removed
and replaced by a symlink to 'lib'. The stray files are removed from
'lib64'.

4. Eventually, once user rebuilds gcc (and possibly other packages
relying on 'lib32' directory), the 'lib32' symlink can be removed.


== Dependencies ==

The tool is written as a Python script. It should work with Python 2.7
up to 3.6. It should also work with newer versions of Python but this
depends on how incompatible they will be.

The analysis phase requires Portage package manager installed. After
this phase is complete, Portage is no longer required, i.e. the tool
will work fine if it becomes broken in the process.

The tool assumes `cp` executable compatible with GNU coreutils, capable
of preserving all file properties.


== How to use? ==

The tool is designed to use a three-step semi-automatic procedure.
The steps are:

1. installed file analysis (`--analyze`),

2. basic migration (`--migrate`),

3a. post-migration cleanup (`--finish`), or

3b. migration rollback (`--rollback`).

During the analysis phase, the tool queries Portage for the list of all
installed files. The action plan is constructed (detailing which files
are relocated where) and the files are checked for potential collisions.
If the analysis phase yields no errors, the tool prints resulting split
(i.e. 'check for obvious mistakes' list) and writes a state file to
the home directory.

The migration phase constructs a 'lib.new' directory as outlined above,
and if no error occurred updates the 'lib' symlink appropriately.
At this point, the user is asked to test the new setup. The migration
can either be finished or reverted afterwards.

The post-migration cleanup phase replaces the 'lib' symlink with
the real directory, and replaces the 'lib32' directory with a symlink,
effectively completing the migration. Rollback becomes no longer
possible. The user is asked to rebuild packages depending on 'lib32'.

The migration rollback phase restores the old target of the 'lib'
symlink, and removes the unsuccessful 'lib.new' directory. It can be
used to quickly fix a system that has been broken by the migration.


== Use on Prefix systems ==

To migrate a Prefix system, pass the path to the system as ``--root``
option.  If you are using unprivileged Prefix, you may use
``--unprivileged`` to disable root privilege checks.


== Failure protection ==

The script tries to be resilient to failures, and avoid causing damage
to the system at all cost. However, there are a few cases of potential
damage. Notably:

1. Any failures during the analysis phase do not affect the live system.
If the analysis fails, state file is simply not written (note that if
there is an older state file around, it will not be removed).

2. Any failures during 'lib.new' creation (migration phase) result
in aborted migration with no damage to the live system. The 'lib.new'
directory is left for debugging purposes. It can be cleaned up via
the '--force-rollback' action.

3. A failure during the actual migration, post-migration cleanup
or rollback is fatal, and requires manual action. Depending
on the exact stage, '--force-rollback' or '--resume-finish' actions
are provided for recovery.
