Index: functions.sh
===================================================================
--- functions.sh	(revision 0)
+++ functions.sh	(revision 0)
@@ -0,0 +1,105 @@
+need_root()
+{
+    local msg=0
+    local msgstr="ERROR: Run these commands as root and restart the command:"
+    local x
+    while [ ! -z "$1" ]; do
+        if [ "$1" = "setuid_prog" ]; then
+            x=( `ls -ld "$WORK/userchroot"` )
+            if [ "${x[2]}" != "root" ]; then
+                if [ $msg -eq 0 ]; then msg=1; echo $msgstr; fi
+                echo "  chown root:root $WORK/userchroot"
+                echo "  chmod u=rwxs,g=x,o=x $WORK/userchroot"
+            fi
+            x=( `ls -ld "$WORK/userchroot_owner"` )
+            if [ "${x[2]}" != "root" ]; then
+                if [ $msg -eq 0 ]; then msg=1; echo $msgstr; fi
+                echo "  chown root:root $WORK/userchroot_owner"
+                echo "  chmod u=rwxs,g=x,o=x $WORK/userchroot_owner"
+            fi
+        fi
+        if [ "$1" = "devnodes" ]; then
+            if [ ! -c "$CHROOT/dev/null" ]; then
+                if [ $msg -eq 0 ]; then msg=1; echo $msgstr; fi
+                echo "  mknod -m 666 $CHROOT/dev/null c 1 3"
+            fi
+            if [ ! -c "$CHROOT/dev/zero" ]; then
+                if [ $msg -eq 0 ]; then msg=1; echo $msgstr; fi
+                echo "  mknod -m 666 $CHROOT/dev/zero c 1 5"
+            fi
+        fi
+        if [ "$1" = "proc" ]; then
+            if [ ! -d "$CHROOT/proc/self" ]; then
+                if [ $msg -eq 0 ]; then msg=1; echo $msgstr; fi
+                echo "  mount -t proc chroot_proc $CHROOT/proc"
+            fi
+        fi
+        if [ "$1" = "no_proc" ]; then
+            if [ -d "$CHROOT/proc/self" ]; then
+                if [ $msg -eq 0 ]; then msg=1; echo $msgstr; fi
+                echo "  umount $CHROOT/proc"
+            fi
+        fi
+        shift
+    done
+    if [ $msg -eq 1 ]; then exit 1; fi
+}
+
+chroot_exec()
+{
+    USER=${chroot_user} HOME=/home "$WORK/userchroot" "$@"
+}
+
+chroot_exec_owner()
+{
+    USER=`whoami` HOME=/ "$WORK/userchroot_owner" "$@"
+}
+
+clean_dir()
+{
+    local dir="$1"
+    local dir_owner="$2"
+    local only_owner="$3"
+    local fields
+    local file
+    local file_owner
+
+    if [ "$dir_owner" = "$chroot_user" ]; then
+        chroot_exec / /bin/chmod u+rwx "$dir"
+        chroot_exec / /bin/ls -1a "$dir" | while read -r file; do
+            if [ "$file" = "." ] || [ "$file" = ".." ]; then continue; fi
+            fields=( `chroot_exec / /bin/ls -ld "$dir/$file"` )
+            file_owner="${fields[3]}"
+            if chroot_exec / /usr/bin/test -d "$dir/$file" -a ! -L "$dir/$file"; then
+                clean_dir "$dir/$file" "$chroot_user" "$only_owner"
+                if [ -z "$only_owner" ] || [ "$file_owner" = "$chroot_user" ]; then
+                    echo "<<<F dir  $dir/$file"
+                    chroot_exec / /bin/rmdir "$dir/$file"
+                fi
+            else
+                if [ -z "$only_owner" ] || [ "$file_owner" = "$chroot_user" ]; then
+                    echo "<<<F file $dir/$file"
+                    chroot_exec / /bin/rm "$dir/$file"
+                fi
+            fi
+        done
+    else
+        ls -1a "$CHROOT/$dir" | while read -r file; do
+            if [ "$file" = "." ] || [ "$file" = ".." ]; then continue; fi
+            fields=( `ls -ld "$CHROOT/$dir/$file"` )
+            file_owner="${fields[3]}"
+            if [ -d "$CHROOT/$dir/$file" -a ! -L "$CHROOT/$dir/$file" ]; then
+                clean_dir "$dir/$file" "${file_owner}" "$only_owner"
+                if [ -z "$only_owner" ] || [ "$file_owner" = "$chroot_user" ]; then
+                    echo "<<<  dir  $dir/$file"
+                    rmdir "$CHROOT/$dir/$file"
+                fi
+            else
+                if [ -z "$only_owner" ] || [ "$file_owner" = "$chroot_user" ]; then
+                    echo "<<<  file $dir/$file"
+                    rm "$CHROOT/$dir/$file"
+                fi
+            fi
+        done
+    fi
+}
Index: config
===================================================================
--- config	(revision 0)
+++ config	(revision 0)
@@ -0,0 +1,30 @@
+# Set this to true for continuous build
+loop=true
+
+# The user patches will be compiled as in the chroot
+# It MUST be different from the user running the script!
+chroot_user=xxxxx
+
+
+CHROOT_CONTENTS_BASE=`echo \
+/bin/{sh,bash} \
+/bin/{ls,cp,dd,ln,mkdir,mkfifo,mv,rm,rmdir,sync,touch,cat} \
+/bin/{cut,head,tail,wc,sort,grep} \
+/bin/{basename,date,echo,env,expr,false,pwd,sleep,true,uname,yes,sed,chmod} \
+/usr/bin/{make,gmake,ldd,install,join,nl,link,nice,nohup,stat,tee,test,unlink,uniq,cmp,dirname,basename,patch}`
+
+CHROOT_CONTENTS_DEPS=`echo \
+/usr/bin/{flex,m4,bison,yacc.bison,pkg-config} \
+/usr/lib/libfl.a \
+/usr/share/bison \
+/usr/lib/libX*.so \
+/usr/bin/{freetype,ncurses5}-config \
+/lib/lib{z,ncurses}.so \
+/usr/lib/lib{z,ncurses,freetype,ssl,crypto,jpeg,png}.so \
+/usr/lib/pkgconfig/{xf86*,xcursor,xi,xrandr}.pc`
+
+CHROOT_CONTENTS_OPTIONAL=`echo \
+/usr/bin/{xslt,sane}-config \
+/usr/lib/lib{hal,sane,xslt,xml2,dbus-1,lcms,fontconfig,lber,ldap,ldap_r,cups,cupsimage,GL,GLU}.so \
+/usr/lib/pkgconfig/{xinerama,xcomposite,hal,hal-storage,lcms,fontconfig,libxslt,libxml-2.0,dbus-1,lcms}.pc \
+/usr/lib/dbus-1.0/include/dbus`
Index: patchwatcher.sh
===================================================================
--- patchwatcher.sh	(revision 129)
+++ patchwatcher.sh	(working copy)
@@ -31,42 +31,212 @@
 # modern machine.
 
 set -e
-set -x
+#set -x
 
-# Set this to true on first run and after debugging
-initialize=false
-# Set this to true for continuous build
-loop=false
+. ./config
+. ./functions.sh
 
 TOP=`pwd`
 PATCHES=$TOP/patches
 WORK=$TOP/wine-continuous-workdir
-if $initialize
-then
-    rm -rf $WORK
-else
-    # Recover from run aborted with ^C
-    if test -d $WORK/golden
-    then
-        rm -rf $WORK/active
-        mv $WORK/golden $WORK/active
+CHROOT="$WORK/chroot"
+
+do_file()
+{
+    target="$1"
+    file="$2"
+    ignore="$3"
+
+    if [ "$ignore" = "ignore" ] && [ ! -e "$file" ] && [ ! -L "$file" ]; then
+        echo "No  $File"
+        return
     fi
-fi
-mkdir -p $PATCHES/mimemail
 
+    if [ -e "$target/$file" ] || [ -L "$target/$file" ]; then return; fi
+
+    # copy file
+    dir=`dirname $file`
+    mkdir -p "$target/$dir"
+    cp -rP "$file" "$target/$dir"
+
+    if [ -L "$file" ]; then
+        link=`readlink "$file"`
+        echo "Link $file -> $link"
+        if [ "${link:0:1}" != "/" ]; then
+            link="`dirname $file`/$link"
+        fi
+        do_file "$target" "$link"
+    else
+        if [ -d "$file" ]; then
+            echo "Dir  $file"
+        elif [ -x "$file" ]; then
+            echo "Bin  $file"
+            do_libraries "$target" "$file"
+        else
+            echo "File $file"
+        fi
+    fi
+}
+
+do_libraries()
+{
+    target="$1"
+    file="$2"
+
+    ldd "$file" | while read -r LINE; do
+        fields=( ${LINE//:/ } )
+        numfields=${#fields[@]}
+
+        library=""
+        if [ "${fields[0]}" != "not" ] && [ "${fields[0]}" != "statically" ]; then
+            if [ $numfields -eq 2 ]; then
+                library="${fields[0]}"
+            elif [ $numfields -eq 4 ]; then
+                library="${fields[2]}"
+            fi
+        fi
+
+        if [ ! -z "$library" ]; then
+            do_file "$target" "$library"
+        fi
+    done
+}
+
+initialize_chroot()
+{
+    if [ -d "$CHROOT" ] || [ -f "$WORK/userchroot" ]; then
+        echo "ERROR: chroot already exists"
+        exit 1
+    fi
+    need_root no_proc
+
+    echo "Creating chroot ..."
+
+    # resolve user name into uid and gid
+    uid=""
+    owner_uid=""
+    while read -r LINE; do
+        fields=( ${LINE//:/ } )
+        if [ "${fields[0]}" = "$chroot_user" ]; then
+            uid="${fields[2]}"
+            gid="${fields[3]}"
+        elif [ "${fields[0]}" = `whoami` ]; then
+            owner_uid="${fields[2]}"
+            owner_gid="${fields[3]}"
+        fi
+    done < /etc/passwd
+    if [ -z "$uid" ]; then
+        echo "ERROR: Chroot user '$chroot_user' does not exist"
+        exit 1
+    fi
+    if [ -z "$owner_uid" ]; then
+        echo "ERROR: Owner does not exist"
+        exit 1
+    fi
+    echo "Found user $chroot_user: uid $uid gid $gid"
+
+    if [ -f /etc/gentoo-release ]; then
+        compiler_string=`gcc-config -c`
+        fields=( ${compiler_string//-/ } )
+        count=${#fields[@]}
+        gcc_version=${fields[$count-1]}
+        chost=`echo $compiler_string | sed "s/-${gcc_version}\$//g"`
+        CHROOT_CONTENTS_DISTRO="/usr/include /usr/share/aclocal /usr/lib/opengl"
+        CHROOT_CONTENTS_DISTRO="${CHROOT_CONTENTS_DISTRO} /usr/${chost} /usr/libexec/gcc/${chost} /usr/lib/gcc/${chost}/${gcc_version} /usr/lib/binutils/${chost}"
+        CHROOT_CONTENTS_DISTRO="${CHROOT_CONTENTS_DISTRO} "`echo /usr/bin/${chost}-*`
+        CHROOT_CONTENTS_DISTRO="${CHROOT_CONTENTS_DISTRO} "`echo /usr/bin/{addr2line,ar,as,c++,c++filt,cpp,g++,gcc,gprof,ld,nm,objcopy,objdump,ranlib,readelf,size,strings,strip}`
+        CHROOT_CONTENTS_DISTRO="${CHROOT_CONTENTS_DISTRO} "`echo /usr/lib/crt*.o`
+        CHROOT_CONTENTS_DISTRO="${CHROOT_CONTENTS_DISTRO} "`echo /usr/lib/lib{c,m,dl,pthread}.so /usr/lib/lib{c,pthread}_nonshared.a`
+        LDPATH_DISTRO="/usr/lib/gcc/${chost}/${gcc_version} "`echo /usr/lib/binutils/${chost}/*`
+    else
+        echo "ERROR: Your distribution is not supported"
+        exit
+    fi
+
+    # create chroot tree
+    mkdir -p "$WORK/chroot/"{proc,dev,etc,proc}
+    mkdir -m 777 "$WORK/chroot/"{home,tmp}
+    for file in $CHROOT_CONTENTS_BASE $CHROOT_CONTENTS_DEPS $CHROOT_CONTENTS_DISTRO; do
+        do_file "$WORK/chroot" "$file"
+    done
+    for file in $CHROOT_CONTENTS_OPTIONAL; do
+        do_file "$WORK/chroot" "$file" optional
+    done
+    echo -n > "$WORK/chroot/etc/ld.so.conf"
+    for p in $LDPATH_DISTRO; do
+        echo "$p" >> "$WORK/chroot/etc/ld.so.conf"
+    done
+    /sbin/ldconfig -r "$WORK/chroot"
+
+    # compile setuid chroot wrapper
+    gcc "-DCHROOT_PATH=\"$WORK/chroot\"" "-DCHROOT_NEWDIR=\"/\"" -DCHROOT_UID=$uid -DCHROOT_GID=$gid -o $WORK/userchroot $TOP/userchroot.c
+    gcc "-DCHROOT_PATH=\"$WORK/chroot\"" "-DCHROOT_NEWDIR=\"/\"" -DCHROOT_UID=$owner_uid -DCHROOT_GID=$owner_gid -o $WORK/userchroot_owner $TOP/userchroot.c
+
+    echo "Chroot created. To test it, use the 'chroot-shell' command"
+}
+
+clean_chroot()
+{
+    if [ -d "$CHROOT" ]; then
+        if [ ! -f "$CHROOT/cleaned" ] && [ -f "$WORK/userchroot" ]; then
+            need_root setuid_prog devnodes proc
+            echo "Cleaning foreign files in chroot ..."
+            clean_dir "/home" `whoami`
+            clean_dir "/tmp" `whoami`
+            if [ -d "$CHROOT/$WORK/active" ]; then
+                clean_dir "/$WORK/active" `whoami`
+            fi
+            touch "$CHROOT/cleaned"
+        fi
+        need_root no_proc
+        echo "Removing chroot ..."
+        rm -rf "$CHROOT"
+    fi
+    if [ -f "$WORK/userchroot" ]; then
+        echo "Removing wrapper ..."
+        rm "$WORK/userchroot"
+    fi
+
+    if [ -f "$WORK/userchroot_owner" ]; then
+        echo "Removing owner wrapper ..."
+        rm "$WORK/userchroot_owner"
+    fi
+}
+
+build_wine()
+{
+    rm -rf "$CHROOT/wine.build"
+    ln -s wine.orig "$CHROOT/wine.build"
+    chroot_exec_owner /wine.build /bin/sh -c "./configure"
+    chroot_exec_owner /wine.build /bin/sh -c "make depend"
+    chroot_exec_owner /wine.build /bin/sh -c "make -j3"
+    rm "$CHROOT/wine.build"
+}
+
+build_wine_noconf()
+{
+    rm -rf "$CHROOT/wine.build"
+    ln -s wine.orig "$CHROOT/wine.build"
+    chroot_exec_owner /wine.build /bin/sh -c "make -j3"
+    rm "$CHROOT/wine.build"
+}
+
 initialize_tree()
 {
-    cd $WORK
-    git clone git://source.winehq.org/git/wine.git active
-    cd active
-    ./configure
-    make depend
-    make -j3
+    need_root setuid_prog devnodes proc
+    echo "Initializing tree ..."
+    cd "$CHROOT"
+    rm -rf wine.orig
+    git clone git://source.winehq.org/git/wine.git wine.orig
+    build_wine
+    cd "$WORK"
 }
 
 refresh_tree()
 {
-    cd $WORK/active
+    need_root setuid_prog devnodes proc
+    echo "Refreshing tree ..."
+    cd "$CHROOT/wine.orig"
     # Recover from any accidental damage
     git diff > git.diff && patch -R -p1 < git.diff
     # Grab latest source
@@ -74,19 +244,21 @@
     cat git.log
     if ! grep -q "Already up-to-date." < git.log
     then
-       make -j3
+       build_wine_noconf
     fi
+    cd "$WORK"
 }
 
 retrieve_patches()
 {
+    echo "Retrieving patches ..."
     cd $PATCHES
     LAST=`ls *.patch | tail -1 | sed 's/\.patch$//'`
     NEXT=`expr $LAST + 1`
-    if ! perl $TOP/get-patches.pl $NEXT
-    then
-        echo "No patches to retrieve"
-        sleep 60
+    if ! perl $TOP/get-next-patch.pl $NEXT; then
+        echo "No patches"
+    else
+        echo "Patches retrieved"
     fi
 }
 
@@ -142,17 +314,39 @@
     cat $NEXT.patch
 
     cd $WORK
-    rm -rf golden
-    mv active golden
-    cp -a golden active
-    cd active
-    if ! patch -p1 < $PATCHES/$NEXT.patch > $PATCHES/$NEXT.log 2>&1
+
+    # copy patch to chroot
+    rm -rf $WORK/chroot/patches
+    mkdir -p $WORK/chroot/patches
+    cp $PATCHES/$NEXT.patch $WORK/chroot/patches/
+
+    # prepare source
+    if [ -L "$CHROOT/wine.build" ]; then
+        echo "ERROR: finish original wine build!"
+        exit 1
+    fi
+    if [ -d "$CHROOT/wine.build" ]; then
+        echo "Cleaning source in chroot ..."
+        clean_dir "/wine.build" `whoami` $chroot_user
+    fi
+    echo "Syncing source in chroot ..."
+    mkdir -p "$CHROOT/w ine.build"
+    rsync -ac "$CHROOT/wine.orig/" "$CHROOT/wine.build"
+    chmod -R u=rwX,g=rwX,o=rwX "$CHROOT/wine.build"
+
+    # clean home
+    echo "Cleaning chroot ..."
+    clean_dir /home `whoami`
+    clean_dir /tmp `whoami`
+
+    echo "Trying patch ..."
+    if ! chroot_exec /wine.build /bin/sh -c "patch -p1 < /patches/$NEXT.patch" > $PATCHES/$NEXT.log 2>&1
     then
        report_results patch $PATCHES/$NEXT.patch  $PATCHES/$NEXT.log
     else
        # TODO: need to run configure?
        # Note: don't use parallel build, we want to email a nice clean log
-       if ! make 2>&1 | perl $TOP/trim-build-log.pl >> $PATCHES/$NEXT.log || ! grep "^Wine build complete" $PATCHES/$NEXT.log 
+       if ! chroot_exec /wine.build /bin/sh -c "make" 2>&1 | perl $TOP/trim-build-log.pl >> $PATCHES/$NEXT.log || ! grep "^Wine build complete" $PATCHES/$NEXT.log 
        then
            report_results build $PATCHES/$NEXT.patch  $PATCHES/$NEXT.log
        else
@@ -160,9 +354,6 @@
        fi
        cat $PATCHES/$NEXT.log
     fi
-    cd $WORK
-    rm -rf active
-    mv golden active
     return 0
 }
 
@@ -175,15 +366,42 @@
      retrieve_patches
      while try_one_patch
      do
-         sleep 1
          $loop || break
      done
      $loop || break
+     echo "Waiting ..."
+     sleep 30
   done
 }
 
-if $initialize
-then
-    initialize_tree
-fi
-continuous_build
+while [ ! -z "$1" ]; do
+    arg="$1"
+    shift
+
+    if [ "$arg" = "initialize" ]; then
+        clean_chroot
+        rm -rf $WORK
+        mkdir -p $WORK
+        initialize_chroot
+        initialize_tree
+    elif [ "$arg" = "initialize_chroot" ]; then
+        initialize_chroot
+    elif [ "$arg" = "clean_chroot" ]; then
+        clean_chroot
+    elif [ "$arg" = "chroot-shell" ]; then
+        need_root setuid_prog devnodes proc
+        chroot_exec /home /bin/bash
+    elif [ "$arg" = "chroot-shell-owner" ]; then
+        need_root setuid_prog devnodes proc
+        chroot_exec_owner / /bin/bash
+    elif [ "$arg" = "initialize_tree" ]; then
+        initialize_tree
+    elif [ "$arg" = "refresh_tree" ]; then
+        refresh_tree
+    elif [ "$arg" = "run" ]; then
+        continuous_build
+    else
+        echo "ERROR: unknown command '$arg'"
+        exit
+    fi
+done
Index: userchroot.c
===================================================================
--- userchroot.c	(revision 0)
+++ userchroot.c	(revision 0)
@@ -0,0 +1,64 @@
+#include <unistd.h>
+#include <sys/types.h>
+#include <stdio.h>
+
+// these should be defined at compile time
+/*
+#define CHROOT_PATH "/home/ambro/chroot"
+#define CHROOT_UID 100
+#define CHROOT_GID 100
+*/
+
+//#define DEBUG printf
+#define DEBUG 
+
+extern char** environ;
+
+int main(int argc, char** argv) {
+  DEBUG("starting (pid=%u)\n", (unsigned)getpid());
+
+  if (argc < 3) {
+    fprintf(stderr, "usage: $0 <exec-folder> <exec-name> [exec-params]\n", argv[0]);
+    return 1;
+  }
+
+  DEBUG("uid=%u euid=%u\n", (unsigned)getuid(), (unsigned)geteuid());
+  DEBUG("gid=%u egid=%u\n", (unsigned)getgid(), (unsigned)getegid());
+
+  if (geteuid()!=0 && getegid()!=0) {
+    fprintf(stderr, "no root privs (suid missing?)\n");
+    return 2;
+  }
+
+  if (chroot(CHROOT_PATH) != 0) {
+    perror("failed to chroot");
+    return 4;
+  }
+
+  if (chdir(argv[1]) != 0) {
+    perror("failed to switch working directory");
+    return 5;
+  }
+
+  DEBUG("Restoring privileges\n");
+
+  if (setgid(CHROOT_GID)!=0 || setuid(CHROOT_UID)!=0) {
+    fprintf(stderr, "Failed to drop privileges\n");
+    return 6;
+  }
+
+  DEBUG("uid=%u euid=%u\n", (unsigned)getuid(), (unsigned)geteuid());
+  DEBUG("gid=%u egid=%u\n", (unsigned)getgid(), (unsigned)getegid());
+
+  if (access(argv[2], X_OK) != 0) {
+    perror("target missing?");
+    return 7;
+  }
+  
+  if (execve(argv[2], &argv[2], environ) != 0) {
+    perror("failed to execve");
+    return 8;
+  }
+  
+  return 9;
+}
