<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">#!/usr/bin/env python
"""
Repair damage caused by improper use of rsync (e.g. if you wanted to create a
backup copy of a system using a another system with a different uid/gid mapping
and you forgot to give --numeric-ids).

If you still have the correct uid/gids on the original system, you can create
a backup of them using (you need to be root, otherwise you might get some
permission denied errors and a incomplete list):

    uidgidfix.py backup / &gt; uidgidfix.lst

You'll get a really long list in uidgidfix.lst then (if you need to start from
a different directory than /, then just give that).

Note: You likely want to review / edit that list before applying it to the
      damaged copy of the system. This script is NOT foolproof and comes
      without any warranty - USE IT ON YOUR OWN RISK!

Testing the contents of uidgidfix.lst on the damaged system works like this:

    uidgidfix.py dryrun &lt; uidgidfix.lst

It will tell you what it WOULD change, but won't change anything.
If you are really sure you want that, run it like this (you need to be root):

    uidgidfix.py restore &lt; uidgidfix.lst

@copyright: Thomas Waldmann &lt;tw AT waldmann-edv DOT de&gt;
@license: GNU GPL v2 or any later version
"""

# root:root is likely 0:0 on all systems, so we can skip that:
SKIP_ROOT = True

import os
import sys
import errno


def backup(outfile, top):
    """
    create a uid gid pathname list
    
    @param outfile: open file to write output to
    @param top: path to start the recursion
    """
    for root, dirs, files in os.walk(top, topdown=False):
        absroot = os.path.abspath(root)
        for name in dirs + files:
            pathname = os.path.join(absroot, name)
            try:
                st = os.lstat(pathname)
                uid = st.st_uid
                gid = st.st_gid
                if not (SKIP_ROOT and uid == 0 and gid == 0):
                    outfile.write("%d %d %s\n" % (uid, gid, pathname))
            except OSError, err:
                if err.errno != errno.ENOENT:
                    # we raise any exception except if a file just vanished
                    # (happens in /proc usually)
                    raise


def restore(infile, dryrun):
    """
    read a uid gid pathname list and fix uid/gid on the local filesystem,
    if the file exists with a different uid/gid

    @param infile: open file to read list from
    @param dryrun: if True, do not change filesystem, but just tell about changes
    """
    for line in infile:
        line = line.rstrip('\n')
        uid, gid, pathname = line.split(' ', 2)
        uid = int(uid)
        gid = int(gid)
        if os.path.lexists(pathname):
            try:
                st = os.lstat(pathname)
                if uid != st.st_uid or gid != st.st_gid:
                    if dryrun:
                        print "%d:%d -&gt; %d:%d %s" % (st.st_uid, st.st_gid, uid, gid, pathname)
                    else:
                        os.lchown(pathname, uid, gid)
            except OSError, err:
                print str(err)


if __name__ == '__main__':
    try:
        cmd = sys.argv[1]
    except IndexError:
        print __doc__
    else:
        if cmd == 'backup':
            try:
                path = sys.argv[2]
            except IndexError:
                path = '.'
            backup(sys.stdout, path)
        elif cmd == 'dryrun':
            restore(sys.stdin, True)
        elif cmd == 'restore':
            restore(sys.stdin, False)

</pre></body></html>