Browse Source

MAPREDUCE-2178. Race condition in LinuxTaskController permissions handling. Contributed by Todd Lipcon and Benoy Antony.

git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/branches/branch-0.22@1346214 13f79535-47bb-0310-9956-ffa450edef68
Konstantin Shvachko 13 years ago
parent
commit
12842330ef
73 changed files with 5331 additions and 5086 deletions
  1. 3 0
      mapreduce/CHANGES.txt
  2. 31 60
      mapreduce/build.xml
  3. 11 12
      mapreduce/src/c++/task-controller/Makefile.am
  4. 21 34
      mapreduce/src/c++/task-controller/configure.ac
  5. 114 62
      mapreduce/src/c++/task-controller/impl/configuration.c
  6. 16 33
      mapreduce/src/c++/task-controller/impl/configuration.h
  7. 192 0
      mapreduce/src/c++/task-controller/impl/main.c
  8. 1045 0
      mapreduce/src/c++/task-controller/impl/task-controller.c
  9. 154 0
      mapreduce/src/c++/task-controller/impl/task-controller.h
  10. 0 1300
      mapreduce/src/c++/task-controller/task-controller.c
  11. 0 148
      mapreduce/src/c++/task-controller/task-controller.h
  12. 763 0
      mapreduce/src/c++/task-controller/test/test-task-controller.c
  13. 0 243
      mapreduce/src/c++/task-controller/tests/test-task-controller.c
  14. 1 1
      mapreduce/src/contrib/streaming/src/java/org/apache/hadoop/streaming/PipeMapRed.java
  15. 35 7
      mapreduce/src/java/org/apache/hadoop/mapred/Child.java
  16. 38 40
      mapreduce/src/java/org/apache/hadoop/mapred/CleanupQueue.java
  17. 205 166
      mapreduce/src/java/org/apache/hadoop/mapred/DefaultTaskController.java
  18. 15 2
      mapreduce/src/java/org/apache/hadoop/mapred/IsolationRunner.java
  19. 7 2
      mapreduce/src/java/org/apache/hadoop/mapred/JobInProgress.java
  20. 563 0
      mapreduce/src/java/org/apache/hadoop/mapred/JobLocalizer.java
  21. 295 335
      mapreduce/src/java/org/apache/hadoop/mapred/JvmManager.java
  22. 199 535
      mapreduce/src/java/org/apache/hadoop/mapred/LinuxTaskController.java
  23. 17 10
      mapreduce/src/java/org/apache/hadoop/mapred/LocalJobRunner.java
  24. 4 2
      mapreduce/src/java/org/apache/hadoop/mapred/MapTask.java
  25. 5 2
      mapreduce/src/java/org/apache/hadoop/mapred/MapTaskRunner.java
  26. 4 3
      mapreduce/src/java/org/apache/hadoop/mapred/ReduceTask.java
  27. 3 2
      mapreduce/src/java/org/apache/hadoop/mapred/ReduceTaskRunner.java
  28. 3 1
      mapreduce/src/java/org/apache/hadoop/mapred/Task.java
  29. 202 357
      mapreduce/src/java/org/apache/hadoop/mapred/TaskController.java
  30. 30 11
      mapreduce/src/java/org/apache/hadoop/mapred/TaskLog.java
  31. 16 25
      mapreduce/src/java/org/apache/hadoop/mapred/TaskMemoryManagerThread.java
  32. 68 85
      mapreduce/src/java/org/apache/hadoop/mapred/TaskRunner.java
  33. 299 332
      mapreduce/src/java/org/apache/hadoop/mapred/TaskTracker.java
  34. 9 0
      mapreduce/src/java/org/apache/hadoop/mapred/TaskUmbilicalProtocol.java
  35. 19 11
      mapreduce/src/java/org/apache/hadoop/mapred/UserLogCleaner.java
  36. 2 2
      mapreduce/src/java/org/apache/hadoop/mapreduce/JobContext.java
  37. 1 1
      mapreduce/src/java/org/apache/hadoop/mapreduce/JobSubmissionFiles.java
  38. 66 200
      mapreduce/src/java/org/apache/hadoop/mapreduce/filecache/DistributedCache.java
  39. 64 29
      mapreduce/src/java/org/apache/hadoop/mapreduce/filecache/TaskDistributedCacheManager.java
  40. 212 144
      mapreduce/src/java/org/apache/hadoop/mapreduce/filecache/TrackerDistributedCacheManager.java
  41. 2 2
      mapreduce/src/java/org/apache/hadoop/mapreduce/lib/chain/ChainMapContextImpl.java
  42. 2 2
      mapreduce/src/java/org/apache/hadoop/mapreduce/lib/chain/ChainReduceContextImpl.java
  43. 2 2
      mapreduce/src/java/org/apache/hadoop/mapreduce/lib/map/WrappedMapper.java
  44. 2 2
      mapreduce/src/java/org/apache/hadoop/mapreduce/lib/reduce/WrappedReducer.java
  45. 1 1
      mapreduce/src/java/org/apache/hadoop/mapreduce/security/TokenCache.java
  46. 4 18
      mapreduce/src/java/org/apache/hadoop/mapreduce/server/tasktracker/Localizer.java
  47. 4 2
      mapreduce/src/java/org/apache/hadoop/mapreduce/task/JobContextImpl.java
  48. 72 8
      mapreduce/src/java/org/apache/hadoop/mapreduce/util/MRAsyncDiskService.java
  49. 0 352
      mapreduce/src/java/org/apache/hadoop/mapreduce/util/ProcessTree.java
  50. 32 108
      mapreduce/src/java/org/apache/hadoop/mapreduce/util/ProcfsBasedProcessTree.java
  51. 0 34
      mapreduce/src/java/org/apache/hadoop/util/ProcessTree.java
  52. 8 2
      mapreduce/src/java/org/apache/hadoop/util/ProcfsBasedProcessTree.java
  53. 6 23
      mapreduce/src/test/mapred/org/apache/hadoop/mapred/ClusterWithLinuxTaskController.java
  54. 2 0
      mapreduce/src/test/mapred/org/apache/hadoop/mapred/TestDebugScript.java
  55. 0 30
      mapreduce/src/test/mapred/org/apache/hadoop/mapred/TestJobExecutionAsDifferentUser.java
  56. 6 71
      mapreduce/src/test/mapred/org/apache/hadoop/mapred/TestJobKillAndFail.java
  57. 1 1
      mapreduce/src/test/mapred/org/apache/hadoop/mapred/TestJobRetire.java
  58. 52 12
      mapreduce/src/test/mapred/org/apache/hadoop/mapred/TestJvmManager.java
  59. 10 7
      mapreduce/src/test/mapred/org/apache/hadoop/mapred/TestKillSubProcesses.java
  60. 16 10
      mapreduce/src/test/mapred/org/apache/hadoop/mapred/TestLinuxTaskController.java
  61. 20 4
      mapreduce/src/test/mapred/org/apache/hadoop/mapred/TestMapRed.java
  62. 0 1
      mapreduce/src/test/mapred/org/apache/hadoop/mapred/TestMiniMRWithDFS.java
  63. 18 7
      mapreduce/src/test/mapred/org/apache/hadoop/mapred/TestSequenceFileInputFormat.java
  64. 7 0
      mapreduce/src/test/mapred/org/apache/hadoop/mapred/TestTaskCommit.java
  65. 25 27
      mapreduce/src/test/mapred/org/apache/hadoop/mapred/TestTaskTrackerLocalization.java
  66. 3 3
      mapreduce/src/test/mapred/org/apache/hadoop/mapred/TestTaskTrackerMemoryManager.java
  67. 2 1
      mapreduce/src/test/mapred/org/apache/hadoop/mapred/TestTrackerDistributedCacheManagerWithLinuxTaskController.java
  68. 49 22
      mapreduce/src/test/mapred/org/apache/hadoop/mapred/TestUserLogCleanup.java
  69. 21 2
      mapreduce/src/test/mapred/org/apache/hadoop/mapred/UtilsForTests.java
  70. 186 102
      mapreduce/src/test/mapred/org/apache/hadoop/mapreduce/filecache/TestTrackerDistributedCacheManager.java
  71. 23 21
      mapreduce/src/test/mapred/org/apache/hadoop/mapreduce/util/TestProcfsBasedProcessTree.java
  72. 5 0
      mapreduce/src/test/mapred/testshell/ExternalMapReduce.java
  73. 18 12
      mapreduce/src/test/unit/org/apache/hadoop/mapred/TestTaskTrackerDirectories.java

+ 3 - 0
mapreduce/CHANGES.txt

@@ -36,6 +36,9 @@ Release 0.22.1 - Unreleased
     MAPREDUCE-4243. Modify mapreduce build to include task-controller target.
     (Benoy Antony via shv)
 
+    MAPREDUCE-2178. Race condition in LinuxTaskController permissions handling.
+    (Todd Lipcon, Benoy Antony via shv)
+
 Release 0.22.0 - 2011-11-29
 
   INCOMPATIBLE CHANGES

+ 31 - 60
mapreduce/build.xml

@@ -172,9 +172,10 @@
     stored for compilation -->
   <property name="build.c++.task-controller" 
     value="${build.c++}/task-controller" />
-  <!-- the default install dir is build directory override it using
-   -Dtask-controller.install.dir=$HADOOP_HOME/bin -->
-  <property name="task-controller.install.dir" value="${dist.dir}/bin" />
+  <property name="task-controller.prefix.dir" value="${dist.dir}" />
+  <!-- the configuration directory for the linux task controller -->
+  <property name="hadoop.conf.dir" value="/etc/hadoop"/>
+
   <!-- end of task-controller properties -->
 	
   <!-- IVY properteis set here -->
@@ -2263,67 +2264,37 @@
   </target>
 
   <!-- taskcontroller targets -->
-  <target name="init-task-controller-build">
-    <antcall target="create-c++-task-controller-configure" inheritAll="true"/>
-    <mkdir dir="${build.c++.task-controller}" />
-    <copy todir="${build.c++.task-controller}">
-      <fileset dir="${c++.task-controller.src}" includes="*.c"/>
-      <fileset dir="${c++.task-controller.src}" includes="*.h"/>
-    </copy>
-    <chmod file="${c++.task-controller.src}/configure" perm="ugo+x"/> 
-    <condition property="task-controller.conf.dir.passed">
-      <not>
-        <equals arg1="${hadoop.conf.dir}" arg2="$${hadoop.conf.dir}"/>
-      </not>
-    </condition>
-  </target>
-  <target name="configure-task-controller" depends="init,
-          init-task-controller-build,
-          task-controller-configuration-with-confdir,
-          task-controller-configuration-with-no-confdir">
-  </target>
-  <target name="task-controller-configuration-with-confdir" 
-          if="task-controller.conf.dir.passed" >
-    <exec executable="${c++.task-controller.src}/configure" 
-          dir="${build.c++.task-controller}" failonerror="yes">
-      <arg value="--prefix=${task-controller.install.dir}" />
-      <arg value="--with-confdir=${hadoop.conf.dir}" />
-    </exec>
-  </target>
-  <target name="task-controller-configuration-with-no-confdir" 
-          unless="task-controller.conf.dir.passed">
-    <exec executable="${c++.task-controller.src}/configure" 
-          dir="${build.c++.task-controller}" failonerror="yes">
-      <arg value="--prefix=${task-controller.install.dir}" />
-    </exec>
-  </target>
-  <!--
-    * Create the installation directory.
-    * Do a make install.
-   -->
-  <target name="task-controller" depends="configure-task-controller">
-    <mkdir dir="${task-controller.install.dir}" />
-    <exec executable="${make.cmd}" dir="${build.c++.task-controller}" 
-        searchpath="yes" failonerror="yes">
-      <arg value="install" />
+  <target name="task-controller" depends="init">
+    <exec executable="autoreconf"
+         dir="${c++.task-controller.src}"
+         searchpath="yes" failonerror="yes">
+      <arg value="-i"/>
     </exec>
+    <mkdir dir="${build.c++.task-controller}" />
+    <exec executable="${c++.task-controller.src}/configure"
+          dir="${build.c++.task-controller}">
+      <arg value="--prefix=${task-controller.prefix.dir}"/>
+      <env key="CFLAGS"
+           value="-DHADOOP_CONF_DIR=${hadoop.conf.dir}"/>
+     </exec>
+    <!-- delete main in case HADOOP_CONF_DIR is different -->
+    <delete file="${build.c++.task-controller}/impl/main.o"
+            quiet="true" failonerror="false"/>
+    <exec executable="make"
+          dir="${build.c++.task-controller}"
+          searchpath="yes" failonerror="yes">
+       <arg value="install"/>
+     </exec>
   </target>
-  <target name="test-task-controller" depends="task-controller">
-    <copy todir="${build.c++.task-controller}" verbose="true">
-      <fileset dir="${c++.task-controller.src}" includes="tests/"/>
-    </copy>
-    <exec executable="${make.cmd}" dir="${build.c++.task-controller}" 
-        searchpath="yes" failonerror="yes">
-      <arg value="clean" />
-      <arg value="test" />
-    </exec>
-    <exec executable="${build.c++.task-controller}/tests/test-task-controller"
-        dir="${build.c++.task-controller}/tests/"
-        failonerror="yes">
+
+  <target name="test-task-controller" depends="init,task-controller">
+    <exec executable="make"
+          dir="${build.c++.task-controller}"
+          searchpath="yes" failonerror="yes">
+      <arg value="check"/>
     </exec>
   </target>
-  <!-- end of task-controller targets -->
-  
+  <!-- end of task-controller targets --> 
   <!-- Begining of fault-injection targets-->
   <import file="${test.src.dir}/aop/build/aop.xml"/>
   

+ 11 - 12
mapreduce/src/c++/task-controller/Makefile.am

@@ -14,20 +14,19 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-ACLOCAL_AMFLAGS = -I ../utils/m4 
-AM_CFLAGS = -Wall
-
-bindir = $(exec_prefix)
+AM_CFLAGS=-I$(srcdir)/impl -Wall -g -Werror
 
+# Define the programs that need to be built
 bin_PROGRAMS = task-controller
-check_PROGRAMS = tests/test-task-controller
-TESTS = $(check_PROGRAMS)
+check_PROGRAMS = test-task-controller
+
+TESTS = test-task-controller
 
-task_controller_SOURCES = main.c task-controller.c configuration.c \
-                          task-controller.h
+# Define the sources for the common files
+common_SOURCES = impl/configuration.c impl/task-controller.c
 
-tests_test_task_controller_SOURCES = tests/test-task-controller.c \
-                                     task-controller.c configuration.c task-controller.h
+# Define the sources for the real executable
+task_controller_SOURCES = $(common_SOURCES) impl/main.c
 
-test:	$(check_PROGRAMS)
-	@echo Done with $<
+# Define the sources for the test executable
+test_task_controller_SOURCES = $(common_SOURCES) test/test-task-controller.c

+ 21 - 34
mapreduce/src/c++/task-controller/configure.ac

@@ -1,7 +1,3 @@
-#                                               -*- Autoconf -*-
-# Process this file with autoconf to produce a configure script.
-
-#
 # Licensed to the Apache Software Foundation (ASF) under one
 # or more contributor license agreements.  See the NOTICE file
 # distributed with this work for additional information
@@ -18,51 +14,42 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
+#                                               -*- Autoconf -*-
+# Process this file with autoconf to produce a configure script.
 
 AC_PREREQ(2.59)
-AC_INIT([task-controller],[0.1])
-
-#changing default prefix value to empty string, so that binary does not
-#gets installed within system
-AC_PREFIX_DEFAULT(.)
+AC_INIT(linux-task-controller, 1.0.0, mapreduce-dev@hadoop.apache.org)
+AC_GNU_SOURCE
+AC_SYS_LARGEFILE
 
-#add new argument called -with-confdir
-AC_ARG_WITH(confdir,[--with-confdir path to hadoop conf dir])
-AC_CONFIG_SRCDIR([task-controller.h])
-AC_CONFIG_AUX_DIR([config])
-AC_CONFIG_MACRO_DIR([../utils/m4])
 AM_INIT_AUTOMAKE([subdir-objects foreign no-dist])
 
+AC_CONFIG_SRCDIR([impl/task-controller.c])
+AC_CONFIG_FILES([Makefile])
+
+AC_PREFIX_DEFAULT(`pwd`/../install)
+
+CHECK_INSTALL_CFLAG
+HADOOP_UTILS_SETUP
+
 # Checks for programs.
 AC_PROG_CC
+AM_PROG_CC_C_O
+AC_PROG_LIBTOOL
 
 # Checks for libraries.
 
 # Checks for header files.
-AC_HEADER_STDC
-AC_CHECK_HEADERS([stdlib.h string.h unistd.h fcntl.h])
-
-#check for HADOOP_CONF_DIR
+AC_LANG(C)
+AC_CHECK_HEADERS([unistd.h])
 
-
-if test "$with_confdir" != ""
-then
-AC_DEFINE_UNQUOTED(HADOOP_CONF_DIR, ["$with_confdir"], [Location of Hadoop configuration])
-fi
 # Checks for typedefs, structures, and compiler characteristics.
+AC_HEADER_STDBOOL
 AC_C_CONST
-AC_TYPE_PID_T
-AC_TYPE_MODE_T
+AC_TYPE_OFF_T
 AC_TYPE_SIZE_T
+AC_FUNC_STRERROR_R
 
 # Checks for library functions.
-AC_FUNC_MALLOC
-AC_FUNC_REALLOC
-AC_FUNC_CHOWN
-AC_CHECK_FUNCS([strerror memset mkdir rmdir strdup])
-
-AC_CONFIG_FILES([Makefile])
+AC_CHECK_FUNCS([mkdir uname])
 AC_OUTPUT
-
-AC_HEADER_STDBOOL
-AC_PROG_MAKE_SET

+ 114 - 62
mapreduce/src/c++/task-controller/configuration.c → mapreduce/src/c++/task-controller/impl/configuration.c

@@ -16,10 +16,32 @@
  * limitations under the License.
  */
 
+// ensure we get the posix version of dirname by including this first
+#include <libgen.h> 
+
 #include "configuration.h"
+#include "task-controller.h"
+
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
 
+#define INCREMENT_SIZE 1000
+#define MAX_SIZE 10
 
-char * hadoop_conf_dir;
+struct confentry {
+  const char *key;
+  const char *value;
+};
+
+struct configuration {
+  int size;
+  struct confentry **confdetails;
+};
 
 struct configuration config={.size=0, .confdetails=NULL};
 
@@ -41,39 +63,69 @@ void free_configurations() {
   config.size = 0;
 }
 
+/**
+ * Is the file/directory only writable by root.
+ * Returns 1 if true
+ */
+static int is_only_root_writable(const char *file) {
+  struct stat file_stat;
+  if (stat(file, &file_stat) != 0) {
+    fprintf(LOGFILE, "Can't stat file %s - %s\n", file, strerror(errno));
+    return 0;
+  }
+  if (file_stat.st_uid != 0) {
+    fprintf(LOGFILE, "File %s must be owned by root, but is owned by %d\n",
+            file, file_stat.st_uid);
+    return 0;
+  }
+  if ((file_stat.st_mode & (S_IWGRP | S_IWOTH)) != 0) {
+    fprintf(LOGFILE, 
+	    "File %s must not be world or group writable, but is %03o\n",
+	    file, file_stat.st_mode & (~S_IFMT));
+    return 0;
+  }
+  return 1;
+}
+
+/**
+ * Ensure that the configuration file and all of the containing directories
+ * are only writable by root. Otherwise, an attacker can change the 
+ * configuration and potentially cause damage.
+ * returns 0 if permissions are ok
+ */
+int check_configuration_permissions(const char* file_name) {
+  // copy the input so that we can modify it with dirname
+  char* dir = strdup(file_name);
+  char* buffer = dir;
+  do {
+    if (!is_only_root_writable(dir)) {
+      free(buffer);
+      return -1;
+    }
+    dir = dirname(dir);
+  } while (strcmp(dir, "/") != 0);
+  free(buffer);
+  return 0;
+}
+
 //function used to load the configurations present in the secure config
-void get_configs() {
+void read_config(const char* file_name) {
+  fprintf(LOGFILE, "Reading task controller config from %s\n" , file_name);
   FILE *conf_file;
   char *line;
   char *equaltok;
   char *temp_equaltok;
   size_t linesize = 1000;
   int size_read = 0;
-  int str_len = 0;
-  char *file_name = NULL;
-
-#ifndef HADOOP_CONF_DIR
-  str_len = strlen(CONF_FILE_PATTERN) + strlen(hadoop_conf_dir);
-  file_name = (char *) malloc(sizeof(char) * (str_len + 1));
-#else
-  str_len = strlen(CONF_FILE_PATTERN) + strlen(HADOOP_CONF_DIR);
-  file_name = (char *) malloc(sizeof(char) * (str_len + 1));
-#endif
 
   if (file_name == NULL) {
-    fprintf(LOGFILE, "Malloc failed :Out of memory \n");
-    return;
+    fprintf(LOGFILE, "Null configuration filename passed in\n");
+    exit(INVALID_CONFIG_FILE);
   }
-  memset(file_name,'\0',str_len +1);
-#ifndef HADOOP_CONF_DIR
-  snprintf(file_name,str_len, CONF_FILE_PATTERN, hadoop_conf_dir);
-#else
-  snprintf(file_name, str_len, CONF_FILE_PATTERN, HADOOP_CONF_DIR);
-#endif
 
-#ifdef DEBUG
-  fprintf(LOGFILE, "get_configs :Conf file name is : %s \n", file_name);
-#endif
+  #ifdef DEBUG
+    fprintf(LOGFILE, "read_config :Conf file name is : %s \n", file_name);
+  #endif
 
   //allocate space for ten configuration items.
   config.confdetails = (struct confentry **) malloc(sizeof(struct confentry *)
@@ -82,14 +134,13 @@ void get_configs() {
   conf_file = fopen(file_name, "r");
   if (conf_file == NULL) {
     fprintf(LOGFILE, "Invalid conf file provided : %s \n", file_name);
-    free(file_name);
-    return;
+    exit(INVALID_CONFIG_FILE);
   }
   while(!feof(conf_file)) {
     line = (char *) malloc(linesize);
     if(line == NULL) {
       fprintf(LOGFILE, "malloc failed while reading configuration file.\n");
-      goto cleanup;
+      exit(OUT_OF_MEMORY);
     }
     size_read = getline(&line,&linesize,conf_file);
     //feof returns true only after we read past EOF.
@@ -98,8 +149,9 @@ void get_configs() {
     if (size_read == -1) {
       if(!feof(conf_file)){
         fprintf(LOGFILE, "getline returned error.\n");
-        goto cleanup;
+        exit(INVALID_CONFIG_FILE);
       }else {
+        free(line);
         break;
       }
     }
@@ -125,9 +177,9 @@ void get_configs() {
       goto cleanup;
     }
 
-#ifdef DEBUG
-    fprintf(LOGFILE, "get_configs : Adding conf key : %s \n", equaltok);
-#endif
+    #ifdef DEBUG
+      fprintf(LOGFILE, "read_config : Adding conf key : %s \n", equaltok);
+    #endif
 
     memset(config.confdetails[config.size], 0, sizeof(struct confentry));
     config.confdetails[config.size]->key = (char *) malloc(
@@ -146,9 +198,9 @@ void get_configs() {
       continue;
     }
 
-#ifdef DEBUG
-    fprintf(LOGFILE, "get_configs : Adding conf value : %s \n", equaltok);
-#endif
+    #ifdef DEBUG
+      fprintf(LOGFILE, "read_config : Adding conf value : %s \n", equaltok);
+    #endif
 
     config.confdetails[config.size]->value = (char *) malloc(
             sizeof(char) * (strlen(equaltok)+1));
@@ -169,8 +221,12 @@ void get_configs() {
 
   //close the file
   fclose(conf_file);
+
+  if (config.size == 0) {
+    fprintf(LOGFILE, "Invalid configuration provided in %s\n", file_name);
+    exit(INVALID_CONFIG_FILE);
+  }
   //clean up allocated file name
-  free(file_name);
   return;
   //free spaces alloced.
   cleanup:
@@ -178,7 +234,6 @@ void get_configs() {
     free(line);
   }
   fclose(conf_file);
-  free(file_name);
   free_configurations();
   return;
 }
@@ -189,15 +244,8 @@ void get_configs() {
  * array, next time onwards used the populated array.
  *
  */
-const char * get_value(const char* key) {
+char * get_value(const char* key) {
   int count;
-  if (config.size == 0) {
-    get_configs();
-  }
-  if (config.size == 0) {
-    fprintf(LOGFILE, "Invalid configuration provided\n");
-    return NULL;
-  }
   for (count = 0; count < config.size; count++) {
     if (strcmp(config.confdetails[count]->key, key) == 0) {
       return strdup(config.confdetails[count]->value);
@@ -210,36 +258,40 @@ const char * get_value(const char* key) {
  * Function to return an array of values for a key.
  * Value delimiter is assumed to be a comma.
  */
-const char ** get_values(const char * key) {
-  const char ** toPass = NULL;
-  const char *value = get_value(key);
+char ** get_values(const char * key) {
+  char ** toPass = NULL;
+  char *value = get_value(key);
   char *tempTok = NULL;
   char *tempstr = NULL;
   int size = 0;
-  int len;
+  int toPassSize = MAX_SIZE;
+
   //first allocate any array of 10
   if(value != NULL) {
-    toPass = (const char **) malloc(sizeof(char *) * MAX_SIZE);
+    toPass = (char **) malloc(sizeof(char *) * toPassSize);
     tempTok = strtok_r((char *)value, ",", &tempstr);
-    if (tempTok != NULL) {
-      while (1) {
-        toPass[size++] = tempTok;
-        tempTok = strtok_r(NULL, ",", &tempstr);
-        if(tempTok == NULL){
-          break;
-        }
-        if((size % MAX_SIZE) == 0) {
-          toPass = (const char **) realloc(toPass,(sizeof(char *) *
-              (MAX_SIZE * ((size/MAX_SIZE) +1))));
-        }
+    while (tempTok != NULL) {
+      toPass[size++] = tempTok;
+      if(size == toPassSize) {
+        toPassSize += MAX_SIZE;
+        toPass = (char **) realloc(toPass,(sizeof(char *) *
+                                           (MAX_SIZE * toPassSize)));
       }
-    } else {
-      toPass[size] = (char *)value;
+      tempTok = strtok_r(NULL, ",", &tempstr);
     }
   }
-  if(size > 0) {
+  if (size > 0) {
     toPass[size] = NULL;
   }
   return toPass;
 }
 
+// free an entry set of values
+void free_values(char** values) {
+  if (*values != NULL) {
+    free(*values);
+  }
+  if (values != NULL) {
+    free(values);
+  }
+}

+ 16 - 33
mapreduce/src/c++/task-controller/configuration.h → mapreduce/src/c++/task-controller/impl/configuration.h

@@ -16,44 +16,27 @@
  * limitations under the License.
  */
 
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-#include <unistd.h>
-
-#define INCREMENT_SIZE 1000
-#define MAX_SIZE 10
-
-struct confentry {
-  const char *key;
-  const char *value;
-};
+/**
+ * Ensure that the configuration file and all of the containing directories
+ * are only writable by root. Otherwise, an attacker can change the 
+ * configuration and potentially cause damage.
+ * returns 0 if permissions are ok
+ */
+int check_configuration_permissions(const char* file_name);
 
+// read the given configuration file
+void read_config(const char* config_file);
 
-struct configuration {
-  int size;
-  struct confentry **confdetails;
-};
+//method exposed to get the configurations
+char *get_value(const char* key);
 
-FILE *LOGFILE;
+//function to return array of values pointing to the key. Values are
+//comma seperated strings.
+char ** get_values(const char* key);
 
-#ifdef HADOOP_CONF_DIR
-  #define CONF_FILE_PATTERN "%s/taskcontroller.cfg"
-#else
-  #define CONF_FILE_PATTERN "%s/conf/taskcontroller.cfg"
-#endif
+// free the memory returned by get_values
+void free_values(char** values);
 
-extern struct configuration config;
-//configuration file contents
-#ifndef HADOOP_CONF_DIR
-  extern char *hadoop_conf_dir;
-#endif
-//method exposed to get the configurations
-const char * get_value(const char* key);
 //method to free allocated configuration
 void free_configurations();
 
-//function to return array of values pointing to the key. Values are
-//comma seperated strings.
-const char ** get_values(const char* key);

+ 192 - 0
mapreduce/src/c++/task-controller/impl/main.c

@@ -0,0 +1,192 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "configuration.h"
+#include "task-controller.h"
+
+#include <errno.h>
+#include <grp.h>
+#include <limits.h>
+#include <unistd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#define _STRINGIFY(X) #X
+#define STRINGIFY(X) _STRINGIFY(X)
+#define CONF_FILENAME "taskcontroller.cfg"
+
+void display_usage(FILE *stream) {
+  fprintf(stream,
+      "Usage: task-controller user command command-args\n");
+  fprintf(stream, "Commands:\n");
+  fprintf(stream, "   initialize job: %2d jobid credentials cmd args\n",
+	  INITIALIZE_JOB);
+  fprintf(stream, "   launch task:    %2d jobid taskid task-script\n",
+	  LAUNCH_TASK_JVM);
+  fprintf(stream, "   signal task:    %2d task-pid signal\n",
+	  SIGNAL_TASK);
+  fprintf(stream, "   delete as user: %2d relative-path\n",
+	  DELETE_AS_USER);
+  fprintf(stream, "   delete log:     %2d relative-path\n",
+	  DELETE_LOG_AS_USER);
+}
+
+int main(int argc, char **argv) {
+  //Minimum number of arguments required to run the task-controller
+  if (argc < 4) {
+    display_usage(stdout);
+    return INVALID_ARGUMENT_NUMBER;
+  }
+
+  LOGFILE = stdout;
+  int command;
+  const char * job_id = NULL;
+  const char * task_id = NULL;
+  const char * cred_file = NULL;
+  const char * script_file = NULL;
+  const char * current_dir = NULL;
+  const char * job_xml = NULL;
+
+  int exit_code = 0;
+
+  char * dir_to_be_deleted = NULL;
+
+  char *executable_file = get_executable();
+
+#ifndef HADOOP_CONF_DIR
+  #error HADOOP_CONF_DIR must be defined
+#endif
+
+  char *orig_conf_file = STRINGIFY(HADOOP_CONF_DIR) "/" CONF_FILENAME;
+  char *conf_file = realpath(orig_conf_file, NULL);
+
+  if (conf_file == NULL) {
+    fprintf(LOGFILE, "Configuration file %s not found.\n", orig_conf_file);
+    return INVALID_CONFIG_FILE;
+  }
+  if (check_configuration_permissions(conf_file) != 0) {
+    return INVALID_CONFIG_FILE;
+  }
+  read_config(conf_file);
+  free(conf_file);
+
+  // look up the task tracker group in the config file
+  char *tt_group = get_value(TT_GROUP_KEY);
+  if (tt_group == NULL) {
+    fprintf(LOGFILE, "Can't get configured value for %s.\n", TT_GROUP_KEY);
+    exit(INVALID_CONFIG_FILE);
+  }
+  struct group *group_info = getgrnam(tt_group);
+  if (group_info == NULL) {
+    fprintf(LOGFILE, "Can't get group information for %s - %s.\n", tt_group,
+            strerror(errno));
+    exit(INVALID_CONFIG_FILE);
+  }
+  set_tasktracker_uid(getuid(), group_info->gr_gid);
+  // if we are running from a setuid executable, make the real uid root
+  setuid(0);
+  // set the real and effective group id to the task tracker group
+  setgid(group_info->gr_gid);
+
+  if (check_taskcontroller_permissions(executable_file) != 0) {
+    fprintf(LOGFILE, "Invalid permissions on task-controller binary.\n");
+    return INVALID_TASKCONTROLLER_PERMISSIONS;
+  }
+
+  //checks done for user name
+  if (argv[optind] == NULL) {
+    fprintf(LOGFILE, "Invalid user name \n");
+    return INVALID_USER_NAME;
+  }
+  int ret = set_user(argv[optind]);
+  if (ret != 0) {
+    return ret;
+  }
+
+  optind = optind + 1;
+  command = atoi(argv[optind++]);
+
+  fprintf(LOGFILE, "main : command provided %d\n",command);
+  fprintf(LOGFILE, "main : user is %s\n", user_detail->pw_name);
+
+  switch (command) {
+  case INITIALIZE_JOB:
+    if (argc < 7) {
+      fprintf(LOGFILE, "Too few arguments (%d vs 7) for initialize job\n",
+	      argc);
+      return INVALID_ARGUMENT_NUMBER;
+    }
+    job_id = argv[optind++];
+    cred_file = argv[optind++];
+    job_xml = argv[optind++];
+    exit_code = initialize_job(user_detail->pw_name, job_id, cred_file,
+                               job_xml, argv + optind);
+    break;
+  case LAUNCH_TASK_JVM:
+    if (argc < 7) {
+      fprintf(LOGFILE, "Too few arguments (%d vs 7) for launch task\n",
+	      argc);
+      return INVALID_ARGUMENT_NUMBER;
+    }
+    job_id = argv[optind++];
+    task_id = argv[optind++];
+    current_dir = argv[optind++];
+    script_file = argv[optind++];
+    exit_code = run_task_as_user(user_detail->pw_name, job_id, task_id, 
+                                 current_dir, script_file);
+    break;
+  case SIGNAL_TASK:
+    if (argc < 5) {
+      fprintf(LOGFILE, "Too few arguments (%d vs 5) for signal task\n",
+	      argc);
+      return INVALID_ARGUMENT_NUMBER;
+    } else {
+      char* end_ptr = NULL;
+      char* option = argv[optind++];
+      int task_pid = strtol(option, &end_ptr, 10);
+      if (option == end_ptr || *end_ptr != '\0') {
+        fprintf(LOGFILE, "Illegal argument for task pid %s\n", option);
+        return INVALID_ARGUMENT_NUMBER;
+      }
+      option = argv[optind++];
+      int signal = strtol(option, &end_ptr, 10);
+      if (option == end_ptr || *end_ptr != '\0') {
+        fprintf(LOGFILE, "Illegal argument for signal %s\n", option);
+        return INVALID_ARGUMENT_NUMBER;
+      }
+      exit_code = signal_user_task(user_detail->pw_name, task_pid, signal);
+    }
+    break;
+  case DELETE_AS_USER:
+    dir_to_be_deleted = argv[optind++];
+    exit_code= delete_as_user(user_detail->pw_name, dir_to_be_deleted, 
+                              argv + optind);
+    break;
+  case DELETE_LOG_AS_USER:
+    dir_to_be_deleted = argv[optind++];
+    exit_code= delete_log_directory(dir_to_be_deleted);
+    break;
+  default:
+    exit_code = INVALID_COMMAND_PROVIDED;
+  }
+  fclose(LOGFILE);
+  return exit_code;
+}

+ 1045 - 0
mapreduce/src/c++/task-controller/impl/task-controller.c

@@ -0,0 +1,1045 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "configuration.h"
+#include "task-controller.h"
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <fts.h>
+#include <errno.h>
+#include <grp.h>
+#include <unistd.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#define USER_DIR_PATTERN "%s/taskTracker/%s"
+
+#define TT_JOB_DIR_PATTERN USER_DIR_PATTERN "/jobcache/%s"
+
+#define ATTEMPT_DIR_PATTERN TT_JOB_DIR_PATTERN "/%s/work"
+
+#define TASK_SCRIPT "taskjvm.sh"
+
+#define TT_LOCAL_TASK_DIR_PATTERN    "%s/taskTracker/%s/jobcache/%s/%s"
+
+#define TT_SYS_DIR_KEY "mapred.local.dir"
+
+#define TT_LOG_DIR_KEY "hadoop.log.dir"
+
+#define JOB_FILENAME "job.xml"
+
+#define CREDENTIALS_FILENAME "jobToken"
+
+#define MIN_USERID_KEY "min.user.id"
+
+static const int DEFAULT_MIN_USERID = 1000;
+
+#define BANNED_USERS_KEY "banned.users"
+
+static const char* DEFAULT_BANNED_USERS[] = {"mapred", "hdfs", "bin", 0};
+
+//struct to store the user details
+struct passwd *user_detail = NULL;
+
+FILE* LOGFILE = NULL;
+
+static uid_t tt_uid = -1;
+static gid_t tt_gid = -1;
+
+void set_tasktracker_uid(uid_t user, gid_t group) {
+  tt_uid = user;
+  tt_gid = group;
+}
+
+/**
+ * get the executable filename.
+ */
+char* get_executable() {
+  char buffer[PATH_MAX];
+  snprintf(buffer, PATH_MAX, "/proc/%u/exe", getpid());
+  char *filename = malloc(PATH_MAX);
+  ssize_t len = readlink(buffer, filename, PATH_MAX);
+  if (len == -1) {
+    fprintf(stderr, "Can't get executable name from %s - %s\n", buffer,
+            strerror(errno));
+    exit(-1);
+  } else if (len >= PATH_MAX) {
+    fprintf(LOGFILE, "Executable name %.*s is longer than %d characters.\n",
+            PATH_MAX, filename, PATH_MAX);
+    exit(-1);
+  }
+  filename[len] = '\0';
+  return filename;
+}
+
+/**
+ * Check the permissions on taskcontroller to make sure that security is
+ * promisable. For this, we need task-controller binary to
+ *    * be user-owned by root
+ *    * be group-owned by a configured special group.
+ *    * others do not have any permissions
+ *    * be setuid/setgid
+ */
+int check_taskcontroller_permissions(char *executable_file) {
+
+  errno = 0;
+  char * resolved_path = realpath(executable_file, NULL);
+  if (resolved_path == NULL) {
+    fprintf(LOGFILE,
+        "Error resolving the canonical name for the executable : %s!",
+        strerror(errno));
+    return -1;
+  }
+
+  struct stat filestat;
+  errno = 0;
+  if (stat(resolved_path, &filestat) != 0) {
+    fprintf(LOGFILE, 
+            "Could not stat the executable : %s!.\n", strerror(errno));
+    return -1;
+  }
+
+  uid_t binary_euid = filestat.st_uid; // Binary's user owner
+  gid_t binary_gid = filestat.st_gid; // Binary's group owner
+
+  // Effective uid should be root
+  if (binary_euid != 0) {
+    fprintf(LOGFILE,
+        "The task-controller binary should be user-owned by root.\n");
+    return -1;
+  }
+
+  if (binary_gid != getgid()) {
+    fprintf(LOGFILE, "The configured tasktracker group %d is different from"
+            " the group of the executable %d\n", getgid(), binary_gid);
+    return -1;
+  }
+
+  // check others do not have read/write/execute permissions
+  if ((filestat.st_mode & S_IROTH) == S_IROTH || (filestat.st_mode & S_IWOTH)
+      == S_IWOTH || (filestat.st_mode & S_IXOTH) == S_IXOTH) {
+    fprintf(LOGFILE,
+            "The task-controller binary should not have read or write or"
+            " execute for others.\n");
+    return -1;
+  }
+
+  // Binary should be setuid/setgid executable
+  if ((filestat.st_mode & S_ISUID) == 0) {
+    fprintf(LOGFILE, "The task-controller binary should be set setuid.\n");
+    return -1;
+  }
+
+  return 0;
+}
+
+/**
+ * Change the effective user id to limit damage.
+ */
+static int change_effective_user(uid_t user, gid_t group) {
+  if (geteuid() == user) {
+    return 0;
+  }
+  if (seteuid(0) != 0) {
+    return -1;
+  }
+  if (setegid(group) != 0) {
+    fprintf(LOGFILE, "Failed to set effective group id %d - %s\n", group,
+            strerror(errno));
+    return -1;
+  }
+  if (seteuid(user) != 0) {
+    fprintf(LOGFILE, "Failed to set effective user id %d - %s\n", user,
+            strerror(errno));
+    return -1;
+  }
+  return 0;
+}
+
+/**
+ * Change the real and effective user and group to abandon the super user
+ * priviledges.
+ */
+int change_user(uid_t user, gid_t group) {
+  if (user == getuid() && user == geteuid() && 
+      group == getgid() && group == getegid()) {
+    return 0;
+  }
+
+  if (seteuid(0) != 0) {
+    fprintf(LOGFILE, "unable to reacquire root - %s\n", strerror(errno));
+    fprintf(LOGFILE, "Real: %d:%d; Effective: %d:%d\n",
+	    getuid(), getgid(), geteuid(), getegid());
+    return SETUID_OPER_FAILED;
+  }
+  if (setgid(group) != 0) {
+    fprintf(LOGFILE, "unable to set group to %d - %s\n", group, 
+            strerror(errno));
+    fprintf(LOGFILE, "Real: %d:%d; Effective: %d:%d\n",
+	    getuid(), getgid(), geteuid(), getegid());
+    return SETUID_OPER_FAILED;
+  }
+  if (setuid(user) != 0) {
+    fprintf(LOGFILE, "unable to set user to %d - %s\n", user, strerror(errno));
+    fprintf(LOGFILE, "Real: %d:%d; Effective: %d:%d\n",
+	    getuid(), getgid(), geteuid(), getegid());
+    return SETUID_OPER_FAILED;
+  }
+
+  return 0;
+}
+
+/**
+ * Utility function to concatenate argB to argA using the concat_pattern.
+ */
+char *concatenate(char *concat_pattern, char *return_path_name, 
+                  int numArgs, ...) {
+  va_list ap;
+  va_start(ap, numArgs);
+  int strlen_args = 0;
+  char *arg = NULL;
+  int j;
+  for (j = 0; j < numArgs; j++) {
+    arg = va_arg(ap, char*);
+    if (arg == NULL) {
+      fprintf(LOGFILE, "One of the arguments passed for %s in null.\n",
+          return_path_name);
+      return NULL;
+    }
+    strlen_args += strlen(arg);
+  }
+  va_end(ap);
+
+  char *return_path = NULL;
+  int str_len = strlen(concat_pattern) + strlen_args + 1;
+
+  return_path = (char *) malloc(str_len);
+  if (return_path == NULL) {
+    fprintf(LOGFILE, "Unable to allocate memory for %s.\n", return_path_name);
+    return NULL;
+  }
+  va_start(ap, numArgs);
+  vsnprintf(return_path, str_len, concat_pattern, ap);
+  va_end(ap);
+  return return_path;
+}
+
+/**
+ * Get the job-directory path from tt_root, user name and job-id
+ */
+char *get_job_directory(const char * tt_root, const char *user,
+                        const char *jobid) {
+  return concatenate(TT_JOB_DIR_PATTERN, "job_dir_path", 3, tt_root, user,
+      jobid);
+}
+
+/**
+ * Get the user directory of a particular user
+ */
+char *get_user_directory(const char *tt_root, const char *user) {
+  return concatenate(USER_DIR_PATTERN, "user_dir_path", 2, tt_root, user);
+}
+
+char *get_job_work_directory(const char *job_dir) {
+  return concatenate("%s/work", "job work", 1, job_dir);
+}
+
+/**
+ * Get the attempt directory for the given attempt_id
+ */
+char *get_attempt_work_directory(const char *tt_root, const char *user,
+				 const char *job_id, const char *attempt_id) {
+  return concatenate(ATTEMPT_DIR_PATTERN, "attempt_dir_path", 4,
+                     tt_root, user, job_id, attempt_id);
+}
+
+char *get_task_launcher_file(const char* work_dir) {
+  return concatenate("%s/%s", "task launcher", 2, work_dir, TASK_SCRIPT);
+}
+
+/**
+ * Get the job log directory.
+ * Ensures that the result is a realpath and that it is underneath the 
+ * tt log root.
+ */
+char* get_job_log_directory(const char* jobid) {
+  char* log_dir = get_value(TT_LOG_DIR_KEY);
+  if (log_dir == NULL) {
+    fprintf(LOGFILE, "Log directory %s is not configured.\n", TT_LOG_DIR_KEY);
+    return NULL;
+  }
+  char *result = concatenate("%s/userlogs/%s", "job log dir", 2, log_dir, 
+                             jobid);
+  if (result == NULL) {
+    fprintf(LOGFILE, "failed to get memory in get_job_log_directory for %s"
+            " and %s\n", log_dir, jobid);
+  }
+  free(log_dir);
+  return result;
+}
+
+/*
+ * Get a user subdirectory.
+ */
+char *get_user_subdirectory(const char *tt_root,
+                            const char *user,
+                            const char *subdir) {
+  char * user_dir = get_user_directory(tt_root, user);
+  char * result = concatenate("%s/%s", "user subdir", 2,
+                              user_dir, subdir);
+  free(user_dir);
+  return result;
+}
+
+/**
+ * Ensure that the given path and all of the parent directories are created
+ * with the desired permissions.
+ */
+int mkdirs(const char* path, mode_t perm) {
+  char *buffer = strdup(path);
+  char *token;
+  int cwd = open("/", O_RDONLY);
+  if (cwd == -1) {
+    fprintf(LOGFILE, "Can't open / in %s - %s\n", path, strerror(errno));
+    free(buffer);
+    return -1;
+  }
+  for(token = strtok(buffer, "/"); token != NULL; token = strtok(NULL, "/")) {
+    if (mkdirat(cwd, token, perm) != 0) {
+      if (errno != EEXIST) {
+        fprintf(LOGFILE, "Can't create directory %s in %s - %s\n", 
+                token, path, strerror(errno));
+        close(cwd);
+        free(buffer);
+        return -1;
+      }
+    }
+    int new_dir = openat(cwd, token, O_RDONLY);
+    close(cwd);
+    cwd = new_dir;
+    if (cwd == -1) {
+      fprintf(LOGFILE, "Can't open %s in %s - %s\n", token, path, 
+              strerror(errno));
+      free(buffer);
+      return -1;
+    }
+  }
+  free(buffer);
+  close(cwd);
+  return 0;
+}
+
+/**
+ * Function to prepare the attempt directories for the task JVM.
+ * It creates the task work and log directories.
+ */
+static int create_attempt_directories(const char* user, const char *job_id, 
+					const char *task_id) {
+  // create dirs as 0750
+  const mode_t perms = S_IRWXU | S_IRGRP | S_IXGRP;
+  if (job_id == NULL || task_id == NULL || user == NULL) {
+    fprintf(LOGFILE, 
+            "Either task_id is null or the user passed is null.\n");
+    return -1;
+  }
+  int result = 0;
+
+  char **local_dir = get_values(TT_SYS_DIR_KEY);
+
+  if (local_dir == NULL) {
+    fprintf(LOGFILE, "%s is not configured.\n", TT_SYS_DIR_KEY);
+    return -1;
+  }
+
+  char **local_dir_ptr;
+  for(local_dir_ptr = local_dir; *local_dir_ptr != NULL; ++local_dir_ptr) {
+    char *task_dir = get_attempt_work_directory(*local_dir_ptr, user, job_id, 
+                                                task_id);
+    if (task_dir == NULL) {
+      free_values(local_dir);
+      return -1;
+    }
+    if (mkdirs(task_dir, perms) != 0) {
+      // continue on to create other task directories
+      free(task_dir);
+    } else {
+      free(task_dir);
+    }
+  }
+  free_values(local_dir);
+
+  // also make the directory for the task logs
+  char *job_task_name = malloc(strlen(job_id) + strlen(task_id) + 2);
+  if (job_task_name == NULL) {
+    fprintf(LOGFILE, "Malloc of job task name failed\n");
+    result = -1;
+  } else {
+    sprintf(job_task_name, "%s/%s", job_id, task_id);
+    char *log_dir = get_job_log_directory(job_task_name);
+    free(job_task_name);
+    if (log_dir == NULL) {
+      result = -1;
+    } else if (mkdirs(log_dir, perms) != 0) {
+      result = -1;
+    }
+    free(log_dir);
+  }
+  return result;
+}
+
+/**
+ * Load the user information for a given user name.
+ */
+static struct passwd* get_user_info(const char* user) {
+  int string_size = sysconf(_SC_GETPW_R_SIZE_MAX);
+  void* buffer = malloc(string_size + sizeof(struct passwd));
+  struct passwd *result = NULL;
+  if (getpwnam_r(user, buffer, buffer + sizeof(struct passwd), string_size,
+		 &result) != 0) {
+    free(buffer);
+    fprintf(LOGFILE, "Can't get user information %s - %s\n", user,
+	    strerror(errno));
+    return NULL;
+  }
+  return result;
+}
+
+/**
+ * Is the user a real user account?
+ * Checks:
+ *   1. Not root
+ *   2. UID is above the minimum configured.
+ *   3. Not in banned user list
+ * Returns NULL on failure
+ */
+struct passwd* check_user(const char *user) {
+  if (strcmp(user, "root") == 0) {
+    fprintf(LOGFILE, "Running as root is not allowed\n");
+    return NULL;
+  }
+  char *min_uid_str = get_value(MIN_USERID_KEY);
+  int min_uid = DEFAULT_MIN_USERID;
+  if (min_uid_str != NULL) {
+    char *end_ptr = NULL;
+    min_uid = strtol(min_uid_str, &end_ptr, 10);
+    if (min_uid_str == end_ptr || *end_ptr != '\0') {
+      fprintf(LOGFILE, "Illegal value of %s for %s in configuration\n", 
+	      min_uid_str, MIN_USERID_KEY);
+      free(min_uid_str);
+      return NULL;
+    }
+    free(min_uid_str);
+  }
+  struct passwd *user_info = get_user_info(user);
+  if (NULL == user_info) {
+    fprintf(LOGFILE, "User %s not found\n", user);
+    return NULL;
+  }
+  if (user_info->pw_uid < min_uid) {
+    fprintf(LOGFILE, "Requested user %s has id %d, which is below the "
+	    "minimum allowed %d\n", user, user_info->pw_uid, min_uid);
+    free(user_info);
+    return NULL;
+  }
+  char **banned_users = get_values(BANNED_USERS_KEY);
+  char **banned_user = (banned_users == NULL) ? 
+    (char**) DEFAULT_BANNED_USERS : banned_users;
+  for(; *banned_user; ++banned_user) {
+    if (strcmp(*banned_user, user) == 0) {
+      free(user_info);
+      fprintf(LOGFILE, "Requested user %s is banned\n", user);
+      return NULL;
+    }
+  }
+  if (banned_users != NULL) {
+    free_values(banned_users);
+  }
+  return user_info;
+}
+
+/**
+ * function used to populate and user_details structure.
+ */
+int set_user(const char *user) {
+  // free any old user
+  if (user_detail != NULL) {
+    free(user_detail);
+    user_detail = NULL;
+  }
+  user_detail = check_user(user);
+  if (user_detail == NULL) {
+    return -1;
+  }
+  return change_effective_user(user_detail->pw_uid, user_detail->pw_gid);
+}
+
+/**
+ * Change the ownership of the given file or directory to the new user.
+ */
+static int change_owner(const char* path, uid_t user, gid_t group) {
+  if (geteuid() == user && getegid() == group) {
+    return 0;
+  } else {
+    uid_t old_user = geteuid();
+    gid_t old_group = getegid();
+    if (change_effective_user(0, group) != 0) {
+      return -1;
+    }
+    if (chown(path, user, group) != 0) {
+      fprintf(LOGFILE, "Can't chown %s to %d:%d - %s\n", path, user, group,
+	      strerror(errno));
+      return -1;
+    }
+    return change_effective_user(old_user, old_group);
+  }
+}
+
+/**
+ * Create a top level directory for the user.
+ * It assumes that the parent directory is *not* writable by the user.
+ * It creates directories with 02700 permissions owned by the user
+ * and with the group set to the task tracker group.
+ * return non-0 on failure
+ */
+int create_directory_for_user(const char* path) {
+  // set 2750 permissions and group sticky bit
+  mode_t permissions = S_IRWXU | S_IRGRP | S_IXGRP | S_ISGID;
+  uid_t user = geteuid();
+  gid_t group = getegid();
+  int ret = 0;
+  ret = change_effective_user(tt_uid, tt_gid);
+  if (ret == 0) {
+    if (mkdir(path, permissions) == 0) {
+      // need to reassert the group sticky bit
+      if (chmod(path, permissions) != 0) {
+        fprintf(LOGFILE, "Can't chmod %s to add the sticky bit - %s\n",
+                path, strerror(errno));
+        ret = -1;
+      } else if (change_owner(path, user, tt_gid) != 0) {
+        ret = -1;
+      }
+    } else if (errno == EEXIST) {
+      struct stat file_stat;
+      if (stat(path, &file_stat) != 0) {
+        fprintf(LOGFILE, "Can't stat directory %s - %s\n", path, 
+                strerror(errno));
+        ret = -1;
+      } else {
+        if (file_stat.st_uid != user ||
+            file_stat.st_gid != tt_gid) {
+          fprintf(LOGFILE, "Directory %s owned by wrong user or group. "
+                  "Expected %d:%d and found %d:%d.\n",
+                  path, user, tt_gid, file_stat.st_uid, file_stat.st_gid);
+          ret = -1;
+        }
+      }
+    } else {
+      fprintf(LOGFILE, "Failed to create directory %s - %s\n", path,
+              strerror(errno));
+      ret = -1;
+    }
+  }
+  if (change_effective_user(user, group) != 0) {
+    ret = -1;
+  }
+  return ret;
+}
+                            
+/**
+ * Open a file as the tasktracker and return a file descriptor for it.
+ * Returns -1 on error
+ */
+static int open_file_as_task_tracker(const char* filename) {
+  uid_t user = geteuid();
+  gid_t group = getegid();
+  if (change_effective_user(tt_uid, tt_gid) != 0) {
+    return -1;
+  }
+  int result = open(filename, O_RDONLY);
+  if (result == -1) {
+    fprintf(LOGFILE, "Can't open file %s as task tracker - %s\n", filename,
+	    strerror(errno));
+  }
+  if (change_effective_user(user, group)) {
+    result = -1;
+  }
+  return result;
+}
+
+/**
+ * Copy a file from a fd to a given filename.
+ * The new file must not exist and it is created with permissions perm.
+ * The input stream is closed.
+ * Return 0 if everything is ok.
+ */
+static int copy_file(int input, const char* in_filename, 
+		     const char* out_filename, mode_t perm) {
+  const int buffer_size = 128*1024;
+  char buffer[buffer_size];
+  int out_fd = open(out_filename, O_WRONLY|O_CREAT|O_EXCL|O_NOFOLLOW, perm);
+  if (out_fd == -1) {
+    fprintf(LOGFILE, "Can't open %s for output - %s\n", out_filename, 
+            strerror(errno));
+    return -1;
+  }
+  ssize_t len = read(input, buffer, buffer_size);
+  while (len > 0) {
+    ssize_t pos = 0;
+    while (pos < len) {
+      ssize_t write_result = write(out_fd, buffer + pos, len - pos);
+      if (write_result <= 0) {
+	fprintf(LOGFILE, "Error writing to %s - %s\n", out_filename,
+		strerror(errno));
+	close(out_fd);
+	return -1;
+      }
+      pos += write_result;
+    }
+    len = read(input, buffer, buffer_size);
+  }
+  if (len < 0) {
+    fprintf(LOGFILE, "Failed to read file %s - %s\n", in_filename, 
+	    strerror(errno));
+    close(out_fd);
+    return -1;
+  }
+  if (close(out_fd) != 0) {
+    fprintf(LOGFILE, "Failed to close file %s - %s\n", out_filename, 
+	    strerror(errno));
+    return -1;
+  }
+  close(input);
+  return 0;
+}
+
+/**
+ * Function to initialize the user directories of a user.
+ */
+int initialize_user(const char *user) {
+  char **local_dir = get_values(TT_SYS_DIR_KEY);
+  if (local_dir == NULL) {
+    fprintf(LOGFILE, "%s is not configured.\n", TT_SYS_DIR_KEY);
+    return INVALID_TT_ROOT;
+  }
+
+  char *user_dir;
+  char **local_dir_ptr = local_dir;
+  int failed = 0;
+  for(local_dir_ptr = local_dir; *local_dir_ptr != 0; ++local_dir_ptr) {
+    user_dir = get_user_directory(*local_dir_ptr, user);
+    if (user_dir == NULL) {
+      fprintf(LOGFILE, "Couldn't get userdir directory for %s.\n", user);
+      failed = 1;
+      break;
+    }
+    if (create_directory_for_user(user_dir) != 0) {
+      failed = 1;
+    }
+    free(user_dir);
+  }
+  free_values(local_dir);
+  return failed ? INITIALIZE_USER_FAILED : 0;
+}
+
+/**
+ * Function to prepare the job directories for the task JVM.
+ */
+int initialize_job(const char *user, const char *jobid, 
+		   const char* credentials, const char* job_xml,
+                   char* const* args) {
+  if (jobid == NULL || user == NULL) {
+    fprintf(LOGFILE, "Either jobid is null or the user passed is null.\n");
+    return INVALID_ARGUMENT_NUMBER;
+  }
+
+  // create the user directory
+  int result = initialize_user(user);
+  if (result != 0) {
+    return result;
+  }
+
+  // create the log directory for the job
+  char *job_log_dir = get_job_log_directory(jobid);
+  if (job_log_dir == NULL) {
+    return -1;
+  }
+  result = create_directory_for_user(job_log_dir);
+  free(job_log_dir);
+  if (result != 0) {
+    return -1;
+  }
+
+  // open up the credentials file
+  int cred_file = open_file_as_task_tracker(credentials);
+  if (cred_file == -1) {
+    return -1;
+  }
+
+  int job_file = open_file_as_task_tracker(job_xml);
+  if (job_file == -1) {
+    return -1;
+  }
+
+  // give up root privs
+  if (change_user(user_detail->pw_uid, user_detail->pw_gid) != 0) {
+    return -1;
+  }
+
+  // 750
+  mode_t permissions = S_IRWXU | S_IRGRP | S_IXGRP;
+  char **tt_roots = get_values(TT_SYS_DIR_KEY);
+
+  if (tt_roots == NULL) {
+    return INVALID_CONFIG_FILE;
+  }
+
+  char **tt_root;
+  char *primary_job_dir = NULL;
+  for(tt_root=tt_roots; *tt_root != NULL; ++tt_root) {
+    char *job_dir = get_job_directory(*tt_root, user, jobid);
+    if (job_dir == NULL) {
+      // try the next one
+    } else if (mkdirs(job_dir, permissions) != 0) {
+      free(job_dir);
+    } else if (primary_job_dir == NULL) {
+      primary_job_dir = job_dir;
+    } else {
+      free(job_dir);
+    }
+  }
+  free_values(tt_roots);
+  if (primary_job_dir == NULL) {
+    fprintf(LOGFILE, "Did not create any job directories\n");
+    return -1;
+  }
+
+  char *cred_file_name = concatenate("%s/%s", "cred file", 2,
+				     primary_job_dir, CREDENTIALS_FILENAME);
+  if (cred_file_name == NULL) {
+    return -1;
+  }
+  if (copy_file(cred_file, credentials, cred_file_name, S_IRUSR|S_IWUSR) != 0){
+    return -1;
+  }
+  char *job_file_name = concatenate("%s/%s", "job file", 2,
+				     primary_job_dir, JOB_FILENAME);
+  if (job_file_name == NULL) {
+    return -1;
+  }
+  if (copy_file(job_file, job_xml, job_file_name,
+        S_IRUSR|S_IWUSR|S_IRGRP) != 0) {
+    return -1;
+  }
+  fclose(stdin);
+  fflush(LOGFILE);
+  if (LOGFILE != stdout) {
+    fclose(stdout);
+  }
+  fclose(stderr);
+  chdir(primary_job_dir);
+  execvp(args[0], args);
+  fprintf(LOGFILE, "Failure to exec job initialization process - %s\n",
+	  strerror(errno));
+  return -1;
+}
+
+/*
+ * Function used to launch a task as the provided user. It does the following :
+ * 1) Creates attempt work dir and log dir to be accessible by the child
+ * 2) Copies the script file from the TT to the work directory
+ * 3) Sets up the environment
+ * 4) Does an execlp on the same in order to replace the current image with
+ *    task image.
+ */
+int run_task_as_user(const char *user, const char *job_id, 
+                     const char *task_id, const char *work_dir,
+                     const char *script_name) {
+  int exit_code = -1;
+  char *task_script_path = NULL;
+  if (create_attempt_directories(user, job_id, task_id) != 0) {
+    goto cleanup;
+  }
+  int task_file_source = open_file_as_task_tracker(script_name);
+  if (task_file_source == -1) {
+    goto cleanup;
+  }
+  task_script_path = get_task_launcher_file(work_dir);
+  if (task_script_path == NULL) {
+    exit_code = OUT_OF_MEMORY;
+    goto cleanup;
+  }
+  if (copy_file(task_file_source, script_name,task_script_path,S_IRWXU) != 0) {
+    goto cleanup;
+  }
+
+  //change the user
+  fcloseall();
+  umask(0027);
+  if (chdir(work_dir) != 0) {
+    fprintf(LOGFILE, "Can't change directory to %s -%s\n", work_dir,
+	    strerror(errno));
+    goto cleanup;
+  }
+  if (change_user(user_detail->pw_uid, user_detail->pw_gid) != 0) {
+    exit_code = SETUID_OPER_FAILED;
+    goto cleanup;
+  }
+
+  if (execlp(task_script_path, task_script_path, NULL) != 0) {
+    fprintf(LOGFILE, "Couldn't execute the task jvm file %s - %s", 
+            task_script_path, strerror(errno));
+    exit_code = UNABLE_TO_EXECUTE_TASK_SCRIPT;
+    goto cleanup;
+  }
+  exit_code = 0;
+
+ cleanup:
+  free(task_script_path);
+  return exit_code;
+}
+
+/**
+ * Function used to signal a task launched by the user.
+ * The function sends appropriate signal to the process group
+ * specified by the task_pid.
+ */
+int signal_user_task(const char *user, int pid, int sig) {
+  if(pid <= 0) {
+    return INVALID_TASK_PID;
+  }
+
+  if (change_user(user_detail->pw_uid, user_detail->pw_gid) != 0) {
+    return SETUID_OPER_FAILED;
+  }
+
+  //Don't continue if the process-group is not alive anymore.
+  int has_group = 1;
+  if (kill(-pid,0) < 0) {
+    if (kill(pid, 0) < 0) {
+      if (errno == ESRCH) {
+        return INVALID_TASK_PID;
+      }
+      fprintf(LOGFILE, "Error signalling task %d with %d - %s\n",
+	      pid, sig, strerror(errno));
+      return -1;
+    } else {
+      has_group = 0;
+    }
+  }
+
+  if (kill((has_group ? -1 : 1) * pid, sig) < 0) {
+    if(errno != ESRCH) {
+      fprintf(LOGFILE, 
+              "Error signalling process group %d with signal %d - %s\n", 
+              -pid, sig, strerror(errno));
+      return UNABLE_TO_KILL_TASK;
+    } else {
+      return INVALID_TASK_PID;
+    }
+  }
+  fprintf(LOGFILE, "Killing process %s%d with %d\n",
+	  (has_group ? "group " :""), pid, sig);
+  return 0;
+}
+
+/**
+ * Delete a final directory as the task tracker user.
+ */
+static int rmdir_as_tasktracker(const char* path) {
+  int user_uid = geteuid();
+  int user_gid = getegid();
+  int ret = change_effective_user(tt_uid, tt_gid);
+  if (ret == 0) {
+    if (rmdir(path) != 0) {
+      fprintf(LOGFILE, "rmdir of %s failed - %s\n", path, strerror(errno));
+      ret = -1;
+    }
+  }
+  // always change back
+  if (change_effective_user(user_uid, user_gid) != 0) {
+    ret = -1;
+  }
+  return ret;
+}
+
+/**
+ * Recursively delete the given path.
+ * full_path : the path to delete
+ * needs_tt_user: the top level directory must be deleted by the tt user.
+ */
+static int delete_path(const char *full_path, 
+                       int needs_tt_user) {
+  int exit_code = 0;
+
+  if (full_path == NULL) {
+    fprintf(LOGFILE, "Path is null\n");
+    exit_code = UNABLE_TO_BUILD_PATH; // may be malloc failed
+  } else {
+    char *(paths[]) = {strdup(full_path), 0};
+    if (paths[0] == NULL) {
+      fprintf(LOGFILE, "Malloc failed in delete_path\n");
+      return -1;
+    }
+    // check to make sure the directory exists
+    if (access(full_path, F_OK) != 0) {
+      if (errno == ENOENT) {
+        free(paths[0]);
+        return 0;
+      }
+    }
+    FTS* tree = fts_open(paths, FTS_PHYSICAL | FTS_XDEV, NULL);
+    FTSENT* entry = NULL;
+    int ret = 0;
+
+    if (tree == NULL) {
+      fprintf(LOGFILE,
+              "Cannot open file traversal structure for the path %s:%s.\n", 
+              full_path, strerror(errno));
+      free(paths[0]);
+      return -1;
+    }
+    while (((entry = fts_read(tree)) != NULL) && exit_code == 0) {
+      switch (entry->fts_info) {
+
+      case FTS_DP:        // A directory being visited in post-order
+        if (!needs_tt_user ||
+            strcmp(entry->fts_path, full_path) != 0) {
+          if (rmdir(entry->fts_accpath) != 0) {
+            fprintf(LOGFILE, "Couldn't delete directory %s - %s\n", 
+                    entry->fts_path, strerror(errno));
+            exit_code = -1;
+          }
+        }
+        break;
+
+      case FTS_F:         // A regular file
+      case FTS_SL:        // A symbolic link
+      case FTS_SLNONE:    // A broken symbolic link
+      case FTS_DEFAULT:   // Unknown type of file
+        if (unlink(entry->fts_accpath) != 0) {
+          fprintf(LOGFILE, "Couldn't delete file %s - %s\n", entry->fts_path,
+                  strerror(errno));
+          exit_code = -1;
+        }
+        break;
+
+      case FTS_DNR:       // Unreadable directory
+        fprintf(LOGFILE, "Unreadable directory %s. Skipping..\n", 
+                entry->fts_path);
+        break;
+
+      case FTS_D:         // A directory in pre-order
+        // if the directory isn't readable, chmod it
+        if ((entry->fts_statp->st_mode & 0200) == 0) {
+          fprintf(LOGFILE, "Unreadable directory %s, chmoding.\n", 
+                  entry->fts_path);
+          if (chmod(entry->fts_accpath, 0700) != 0) {
+            fprintf(LOGFILE, "Error chmoding %s - %s, continuing\n", 
+                    entry->fts_path, strerror(errno));
+          }
+        }
+        break;
+
+      case FTS_NS:        // A file with no stat(2) information
+        // usually a root directory that doesn't exist
+        fprintf(LOGFILE, "Directory not found %s\n", entry->fts_path);
+        break;
+
+      case FTS_DC:        // A directory that causes a cycle
+      case FTS_DOT:       // A dot directory
+      case FTS_NSOK:      // No stat information requested
+        break;
+
+      case FTS_ERR:       // Error return
+        fprintf(LOGFILE, "Error traversing directory %s - %s\n", 
+                entry->fts_path, strerror(entry->fts_errno));
+        exit_code = -1;
+        break;
+        break;
+      default:
+        exit_code = -1;
+        break;
+      }
+    }
+    ret = fts_close(tree);
+    if (exit_code == 0 && ret != 0) {
+      fprintf(LOGFILE, "Error in fts_close while deleting %s\n", full_path);
+      exit_code = -1;
+    }
+    if (needs_tt_user) {
+      // If the delete failed, try a final rmdir as root on the top level.
+      // That handles the case where the top level directory is in a directory
+      // that is owned by the task tracker.
+      exit_code = rmdir_as_tasktracker(full_path);
+    }
+    free(paths[0]);
+  }
+  return exit_code;
+}
+
+/**
+ * Delete the given directory as the user from each of the tt_root directories
+ * user: the user doing the delete
+ * subdir: the subdir to delete (if baseDirs is empty, this is treated as
+           an absolute path)
+ * baseDirs: (optional) the baseDirs where the subdir is located
+ */
+int delete_as_user(const char *user,
+                   const char *subdir,
+                   char* const* baseDirs) {
+  int ret = 0;
+
+  char** ptr;
+
+  if (baseDirs == NULL || *baseDirs == NULL) {
+    return delete_path(subdir, strlen(subdir) == 0);
+  }
+  // do the delete
+  for(ptr = (char**)baseDirs; *ptr != NULL; ++ptr) {
+    char* full_path = concatenate("%s/%s", "user subdir", 2,
+                              *ptr, subdir);
+    if (full_path == NULL) {
+      return -1;
+    }
+    int this_ret = delete_path(full_path, strlen(subdir) == 0);
+    free(full_path);
+    // delete as much as we can, but remember the error
+    if (this_ret != 0) {
+      ret = this_ret;
+    }
+  }
+  return ret;
+}
+
+/**
+ * delete a given log directory
+ */
+int delete_log_directory(const char *subdir) {
+  char* log_subdir = get_job_log_directory(subdir);
+  int ret = -1;
+  if (log_subdir != NULL) {
+    ret = delete_path(log_subdir, strchr(subdir, '/') == NULL);
+  }
+  free(log_subdir);
+  return ret;
+}

+ 154 - 0
mapreduce/src/c++/task-controller/impl/task-controller.h

@@ -0,0 +1,154 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <pwd.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+//command definitions
+enum command {
+  INITIALIZE_JOB = 0,
+  LAUNCH_TASK_JVM = 1,
+  SIGNAL_TASK = 2,
+  DELETE_AS_USER = 3,
+  DELETE_LOG_AS_USER = 4
+};
+
+enum errorcodes {
+  INVALID_ARGUMENT_NUMBER = 1,
+  INVALID_USER_NAME, //2
+  INVALID_COMMAND_PROVIDED, //3
+  SUPER_USER_NOT_ALLOWED_TO_RUN_TASKS, //4
+  INVALID_TT_ROOT, //5
+  SETUID_OPER_FAILED, //6
+  UNABLE_TO_EXECUTE_TASK_SCRIPT, //7
+  UNABLE_TO_KILL_TASK, //8
+  INVALID_TASK_PID, //9
+  ERROR_RESOLVING_FILE_PATH, //10
+  RELATIVE_PATH_COMPONENTS_IN_FILE_PATH, //11
+  UNABLE_TO_STAT_FILE, //12
+  FILE_NOT_OWNED_BY_TASKTRACKER, //13
+  PREPARE_ATTEMPT_DIRECTORIES_FAILED, //14
+  INITIALIZE_JOB_FAILED, //15
+  PREPARE_TASK_LOGS_FAILED, //16
+  INVALID_TT_LOG_DIR, //17
+  OUT_OF_MEMORY, //18
+  INITIALIZE_DISTCACHEFILE_FAILED, //19
+  INITIALIZE_USER_FAILED, //20
+  UNABLE_TO_BUILD_PATH, //21
+  INVALID_TASKCONTROLLER_PERMISSIONS, //22
+  PREPARE_JOB_LOGS_FAILED, //23
+  INVALID_CONFIG_FILE, // 24
+};
+
+#define TT_GROUP_KEY "mapreduce.tasktracker.group"
+
+extern struct passwd *user_detail;
+
+// the log file for error messages
+extern FILE *LOGFILE;
+
+// get the executable's filename
+char* get_executable();
+
+int check_taskcontroller_permissions(char *executable_file);
+
+/**
+ * delete a given log directory as a user
+ */
+int delete_log_directory(const char *log_dir);
+
+// initialize the job directory
+int initialize_job(const char *user, const char *jobid,
+                   const char *credentials, 
+                   const char *job_xml, char* const* args);
+
+// run the task as the user
+int run_task_as_user(const char * user, const char *jobid, const char *taskid,
+                     const char *work_dir, const char *script_name);
+
+// send a signal as the user
+int signal_user_task(const char *user, int pid, int sig);
+
+// delete a directory (or file) recursively as the user. The directory
+// could optionally be relative to the baseDir set of directories (if the same
+// directory appears on multiple disk volumes, the disk volumes should be passed
+// as the baseDirs). If baseDirs is not specified, then dir_to_be_deleted is 
+// assumed as the absolute path
+int delete_as_user(const char *user,
+                   const char *dir_to_be_deleted,
+                   char* const* baseDirs);
+
+// set the task tracker's uid and gid
+void set_tasktracker_uid(uid_t user, gid_t group);
+
+/**
+ * Is the user a real user account?
+ * Checks:
+ *   1. Not root
+ *   2. UID is above the minimum configured.
+ *   3. Not in banned user list
+ * Returns NULL on failure
+ */
+struct passwd* check_user(const char *user);
+
+// set the user
+int set_user(const char *user);
+
+// methods to get the directories
+
+char *get_user_directory(const char *tt_root, const char *user);
+
+char *get_job_directory(const char * tt_root, const char *user,
+                        const char *jobid);
+
+char *get_attempt_work_directory(const char *tt_root, const char *user,
+				 const char *job_dir, const char *attempt_id);
+
+char *get_task_launcher_file(const char* work_dir);
+
+/**
+ * Get the job log directory.
+ * Ensures that the result is a realpath and that it is underneath the 
+ * tt log root.
+ */
+char* get_job_log_directory(const char* jobid);
+
+char *get_task_log_dir(const char *log_dir, const char *job_id, 
+                       const char *attempt_id);
+
+/**
+ * Ensure that the given path and all of the parent directories are created
+ * with the desired permissions.
+ */
+int mkdirs(const char* path, mode_t perm);
+
+/**
+ * Function to initialize the user directories of a user.
+ */
+int initialize_user(const char *user);
+
+/**
+ * Create a top level directory for the user.
+ * It assumes that the parent directory is *not* writable by the user.
+ * It creates directories with 02700 permissions owned by the user
+ * and with the group set to the task tracker group.
+ * return non-0 on failure
+ */
+int create_directory_for_user(const char* path);
+
+int change_user(uid_t user, gid_t group);

+ 0 - 1300
mapreduce/src/c++/task-controller/task-controller.c

@@ -1,1300 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#include "task-controller.h"
-
-//struct to store the user details
-struct passwd *user_detail = NULL;
-
-//LOGFILE
-FILE *LOGFILE;
-
-//placeholder for global cleanup operations
-void cleanup() {
-  free_configurations();
-}
-
-//change the user to passed user for executing/killing tasks
-int change_user(const char * user) {
-  if (get_user_details(user) < 0) {
-    return -1;
-  }
-
-  if(initgroups(user_detail->pw_name, user_detail->pw_gid) != 0) {
-    fprintf(LOGFILE, "unable to initgroups : %s\n", strerror(errno));
-	  cleanup();
-	  return SETUID_OPER_FAILED;
-  }
-
-  errno = 0;
-
-  setgid(user_detail->pw_gid);
-  if (errno != 0) {
-    fprintf(LOGFILE, "unable to setgid : %s\n", strerror(errno));
-    cleanup();
-    return SETUID_OPER_FAILED;
-  }
-
-  setegid(user_detail->pw_gid);
-  if (errno != 0) {
-    fprintf(LOGFILE, "unable to setegid : %s\n", strerror(errno));
-    cleanup();
-    return SETUID_OPER_FAILED;
-  }
-
-  setuid(user_detail->pw_uid);
-  if (errno != 0) {
-    fprintf(LOGFILE, "unable to setuid : %s\n", strerror(errno));
-    cleanup();
-    return SETUID_OPER_FAILED;
-  }
-
-  seteuid(user_detail->pw_uid);
-  if (errno != 0) {
-    fprintf(LOGFILE, "unable to seteuid : %s\n", strerror(errno));
-    cleanup();
-    return SETUID_OPER_FAILED;
-  }
-  return 0;
-}
-
-/**
- * Checks the passed value for the variable config_key against the values in
- * the configuration.
- * Returns 0 if the passed value is found in the configuration,
- *        -1 otherwise
- */
-int check_variable_against_config(const char *config_key,
-    const char *passed_value) {
-
-  if (config_key == NULL || passed_value == NULL) {
-    return -1;
-  }
-
-  int found = -1;
-
-  const char **config_value = get_values(config_key);
-
-  if (config_value == NULL) {
-    fprintf(LOGFILE, "%s is not configured.\n", config_key);
-    return -1;
-  }
-
-  char *full_config_value = (char *)get_value(config_key);
-
-  char **config_val_ptr = (char **) config_value;
-  while (*config_val_ptr != NULL) {
-    if (strcmp(*config_val_ptr, passed_value) == 0) {
-      found = 0;
-      break;
-    }
-    config_val_ptr++;
-  }
-
-  if (found != 0) {
-    fprintf(
-        LOGFILE,
-        "Invalid value passed: \
-        Configured value of %s is %s. \
-        Passed value is %s.\n",
-        config_key, full_config_value, passed_value);
-  }
-  free(full_config_value);
-  free(config_value);
-  return found;
-}
-
-/**
- * Utility function to concatenate argB to argA using the concat_pattern
- */
-char *concatenate(char *concat_pattern, char *return_path_name, int numArgs,
-    ...) {
-  va_list ap;
-  va_start(ap, numArgs);
-  int strlen_args = 0;
-  char *arg = NULL;
-  int j;
-  for (j = 0; j < numArgs; j++) {
-    arg = va_arg(ap, char*);
-    if (arg == NULL) {
-      fprintf(LOGFILE, "One of the arguments passed for %s in null.\n",
-          return_path_name);
-      return NULL;
-    }
-    strlen_args += strlen(arg);
-  }
-  va_end(ap);
-
-  char *return_path = NULL;
-  int str_len = strlen(concat_pattern) + strlen_args;
-
-  return_path = (char *) malloc(sizeof(char) * (str_len + 1));
-  if (return_path == NULL) {
-    fprintf(LOGFILE, "Unable to allocate memory for %s.\n", return_path_name);
-    return NULL;
-  }
-  memset(return_path, '\0', str_len + 1);
-  va_start(ap, numArgs);
-  vsnprintf(return_path, str_len, concat_pattern, ap);
-  va_end(ap);
-  return return_path;
-}
-
-/**
- * Get the job-directory path from tt_root, user name and job-id
- */
-char *get_job_directory(const char * tt_root, const char *user,
-    const char *jobid) {
-  return concatenate(TT_JOB_DIR_PATTERN, "job_dir_path", 3, tt_root, user,
-      jobid);
-}
-
-/**
- * Get the user directory of a particular user
- */
-char *get_user_directory(const char *tt_root, const char *user) {
-  return concatenate(USER_DIR_PATTERN, "user_dir_path", 2, tt_root, user);
-}
-
-/**
- * Get the distributed cache directory for a particular user
- */
-char *get_distributed_cache_directory(const char *tt_root, const char *user,
-    const char* unique_string) {
-  return concatenate(USER_DISTRIBUTED_CACHE_DIR_PATTERN, 
-      "dist_cache_unique_path", 3, tt_root, user, unique_string);
-}
-
-char *get_job_work_directory(const char *job_dir) {
-  return concatenate(JOB_DIR_TO_JOB_WORK_PATTERN, "job_work_dir_path", 2,
-      job_dir, "");
-}
-/**
- * Get the attempt directory for the given attempt_id
- */
-char *get_attempt_directory(const char *job_dir, const char *attempt_id) {
-  return concatenate(JOB_DIR_TO_ATTEMPT_DIR_PATTERN, "attempt_dir_path", 2,
-      job_dir, attempt_id);
-}
-
-/*
- * Get the path to the task launcher file which is created by the TT
- */
-char *get_task_launcher_file(const char *job_dir, const char *attempt_dir) {
-  return concatenate(TASK_SCRIPT_PATTERN, "task_script_path", 2, job_dir,
-      attempt_dir);
-}
-
-/*
- * Builds the full path of the dir(localTaskDir or localWorkDir)
- * tt_root : is the base path(i.e. mapred-local-dir) sent to task-controller
- * dir_to_be_deleted : is either taskDir($taskId) OR taskWorkDir($taskId/work)
- */
-char *get_task_dir_path(const char *tt_root, const char *user,
-                        const char *jobid, const char *dir_to_be_deleted) {
-  return concatenate(TT_LOCAL_TASK_DIR_PATTERN, "task_dir_full_path", 4,
-                     tt_root, user, jobid, dir_to_be_deleted);
-}
-
-/**
- * Get the log directory for the given attempt.
- */
-char *get_task_log_dir(const char *log_dir, const char *job_id, 
-    const char *attempt_id) {
-  return concatenate(ATTEMPT_LOG_DIR_PATTERN, "task_log_dir", 3, log_dir,
-      job_id, attempt_id);
-}
-
-/**
- * Get the log directory for the given job.
- */
-char *get_job_log_dir(const char *log_dir, const char *job_id) {
-  return concatenate(JOB_LOG_DIR_PATTERN, "job_log_dir", 2, log_dir, job_id);
-}
-
-/**
- * Get the job ACLs file for the given job log dir.
- */
-char *get_job_acls_file(const char *log_dir) {
-  return concatenate(JOB_LOG_DIR_TO_JOB_ACLS_FILE_PATTERN, "job_acls_file",
-                     1, log_dir);
-}
-
-/**
- * Function to check if the passed tt_root is present in mapreduce.cluster.local.dir
- * the task-controller is configured with.
- */
-int check_tt_root(const char *tt_root) {
-  return check_variable_against_config(TT_SYS_DIR_KEY, tt_root);
-}
-
-/**
- * Function to check if the constructed path and absolute path of the task
- * launcher file resolve to one and same. This is done so as to avoid
- * security pitfalls because of relative path components in the file name.
- */
-int check_path_for_relative_components(char *path) {
-  char * resolved_path = (char *) canonicalize_file_name(path);
-  if (resolved_path == NULL) {
-    fprintf(LOGFILE,
-        "Error resolving the path: %s. Passed path: %s\n",
-        strerror(errno), path);
-    return ERROR_RESOLVING_FILE_PATH;
-  }
-  if (strcmp(resolved_path, path) != 0) {
-    fprintf(LOGFILE,
-        "Relative path components in the path: %s. Resolved path: %s\n",
-        path, resolved_path);
-    free(resolved_path);
-    return RELATIVE_PATH_COMPONENTS_IN_FILE_PATH;
-  }
-  free(resolved_path);
-  return 0;
-}
-
-/**
- * Function to change the owner/group of a given path.
- */
-static int change_owner(const char *path, uid_t uid, gid_t gid) {
-  int exit_code = chown(path, uid, gid);
-  if (exit_code != 0) {
-    fprintf(LOGFILE, "chown %d:%d for path %s failed: %s.\n", uid, gid, path,
-        strerror(errno));
-  }
-  return exit_code;
-}
-
-/**
- * Function to change the mode of a given path.
- */
-static int change_mode(const char *path, mode_t mode) {
-  int exit_code = chmod(path, mode);
-  if (exit_code != 0) {
-    fprintf(LOGFILE, "chmod %d of path %s failed: %s.\n", mode, path,
-        strerror(errno));
-  }
-  return exit_code;
-}
-
-/**
- * Function to change permissions of the given path. It does the following
- * recursively:
- *    1) changes the owner/group of the paths to the passed owner/group
- *    2) changes the file permission to the passed file_mode and directory
- *       permission to the passed dir_mode
- *
- * should_check_ownership : boolean to enable checking of ownership of each path
- */
-static int secure_path(const char *path, uid_t uid, gid_t gid,
-    mode_t file_mode, mode_t dir_mode, int should_check_ownership) {
-  FTS *tree = NULL; // the file hierarchy
-  FTSENT *entry = NULL; // a file in the hierarchy
-  char *paths[] = { (char *) path, NULL };//array needs to be NULL-terminated
-  int process_path = 0;
-  int dir = 0;
-  int error_code = 0;
-  int done = 0;
-
-  // Get physical locations and don't resolve the symlinks.
-  // Don't change directory while walking the directory.
-  int ftsoptions = FTS_PHYSICAL | FTS_NOCHDIR;
-
-  tree = fts_open(paths, ftsoptions, NULL);
-  if (tree == NULL) {
-    fprintf(LOGFILE,
-        "Cannot open file traversal structure for the path %s:%s.\n", path,
-        strerror(errno));
-    return -1;
-  }
-
-  while (((entry = fts_read(tree)) != NULL) && !done) {
-    dir = 0;
-    switch (entry->fts_info) {
-    case FTS_D:
-      // A directory being visited in pre-order.
-      // We change ownership of directories in post-order.
-      // so ignore the pre-order visit.
-      process_path = 0;
-      break;
-    case FTS_DC:
-      // A directory that causes a cycle in the tree
-      // We don't expect cycles, ignore.
-      process_path = 0;
-      break;
-    case FTS_DNR:
-      // A directory which cannot be read
-      // Ignore and set error code.
-      process_path = 0;
-      error_code = -1;
-      break;
-    case FTS_DOT:
-      // "."  or ".."
-      process_path = 0;
-      break;
-    case FTS_F:
-      // A regular file
-      process_path = 1;
-      break;
-    case FTS_DP:
-      // A directory being visited in post-order
-      if (entry->fts_level == 0) {
-        // root directory. Done with traversing.
-        done = 1;
-      }
-      process_path = 1;
-      dir = 1;
-      break;
-    case FTS_SL:
-      // A symbolic link
-      // We don't want to change-ownership(and set-permissions) for the file/dir
-      // pointed to by any symlink.
-      process_path = 0;
-      break;
-    case FTS_SLNONE:
-      // A symbolic link with a nonexistent target
-      process_path = 0;
-      break;
-    case FTS_NS:
-      // A  file for which no stat(2) information was available
-      // Ignore and set error code
-      process_path = 0;
-      error_code = -1;
-      break;
-    case FTS_ERR:
-      // An error return. Ignore and set error code.
-      process_path = 0;
-      error_code = -1;
-      break;
-    case FTS_DEFAULT:
-      // File that doesn't belong to any of the above type. Ignore.
-      process_path = 0;
-      break;
-    default:
-      // None of the above. Ignore and set error code
-      process_path = 0;
-      error_code = -1;
-    }
-
-    if (error_code != 0) {
-      break;
-    }
-    if (!process_path) {
-      continue;
-    }
-    error_code = secure_single_path(entry->fts_path, uid, gid,
-      (dir ? dir_mode : file_mode), should_check_ownership);
-
-  }
-  if (fts_close(tree) != 0) {
-    fprintf(LOGFILE, "couldn't close file traversal structure:%s.\n",
-        strerror(errno));
-  }
-  return error_code;
-}
-
-/**
- * Function to change ownership and permissions of the given path. 
- * This call sets ownership and permissions just for the path, not recursive.  
- */
-int secure_single_path(char *path, uid_t uid, gid_t gid,
-    mode_t perm, int should_check_ownership) {
-  int error_code = 0;
-  if (should_check_ownership && 
-      (check_ownership(path, uid, gid) != 0)) {
-    fprintf(LOGFILE,
-      "Invalid file path. %s not user/group owned by the tasktracker.\n", path);
-    error_code = -1;
-  } else if (change_owner(path, uid, gid) != 0) {
-    fprintf(LOGFILE, "couldn't change the ownership of %s\n", path);
-    error_code = -3;
-  } else if (change_mode(path, perm) != 0) {
-    fprintf(LOGFILE, "couldn't change the permissions of %s\n", path);
-    error_code = -3;
-  }
-  return error_code;
-}
-
-/**
- * Function to prepare the attempt directories for the task JVM.
- * This is done by changing the ownership of the attempt directory recursively
- * to the job owner. We do the following:
- *  *  sudo chown user:mapred -R taskTracker/$user/jobcache/$jobid/$attemptid/
- *  *  sudo chmod 2770 -R taskTracker/$user/jobcache/$jobid/$attemptid/
- */
-int prepare_attempt_directories(const char *job_id, const char *attempt_id,
-    const char *user) {
-  if (job_id == NULL || attempt_id == NULL || user == NULL) {
-    fprintf(LOGFILE, "Either attempt_id is null or the user passed is null.\n");
-    return INVALID_ARGUMENT_NUMBER;
-  }
-
-  gid_t tasktracker_gid = getegid(); // the group permissions of the binary.
-
-  if (get_user_details(user) < 0) {
-    fprintf(LOGFILE, "Couldn't get the user details of %s.\n", user);
-    return INVALID_USER_NAME;
-  }
-
-  char **local_dir = (char **) get_values(TT_SYS_DIR_KEY);
-
-  if (local_dir == NULL) {
-    fprintf(LOGFILE, "%s is not configured.\n", TT_SYS_DIR_KEY);
-    cleanup();
-    return PREPARE_ATTEMPT_DIRECTORIES_FAILED;
-  }
-
-  char *full_local_dir_str = (char *) get_value(TT_SYS_DIR_KEY);
-#ifdef DEBUG
-  fprintf(LOGFILE, "Value from config for %s is %s.\n", TT_SYS_DIR_KEY,
-      full_local_dir_str);
-#endif
-
-  char *job_dir;
-  char *attempt_dir;
-  char **local_dir_ptr = local_dir;
-  int failed = 0;
-  while (*local_dir_ptr != NULL) {
-    job_dir = get_job_directory(*local_dir_ptr, user, job_id);
-    if (job_dir == NULL) {
-      fprintf(LOGFILE, "Couldn't get job directory for %s.\n", job_id);
-      failed = 1;
-      break;
-    }
-
-    // prepare attempt-dir in each of the mapreduce.cluster.local.dir
-    attempt_dir = get_attempt_directory(job_dir, attempt_id);
-    if (attempt_dir == NULL) {
-      fprintf(LOGFILE, "Couldn't get attempt directory for %s.\n", attempt_id);
-      failed = 1;
-      free(job_dir);
-      break;
-    }
-
-    struct stat filestat;
-    if (stat(attempt_dir, &filestat) != 0) {
-      if (errno == ENOENT) {
-#ifdef DEBUG
-        fprintf(LOGFILE,
-            "attempt_dir %s doesn't exist. Not doing anything.\n", attempt_dir);
-#endif
-      } else {
-        // stat failed because of something else!
-        fprintf(LOGFILE, "Failed to stat the attempt_dir %s\n", attempt_dir);
-        failed = 1;
-        free(attempt_dir);
-        free(job_dir);
-        break;
-      }
-    } else if (secure_path(attempt_dir, user_detail->pw_uid,
-               tasktracker_gid, S_IRWXU | S_IRWXG, S_ISGID | S_IRWXU | S_IRWXG,
-               1) != 0) {
-      // No setgid on files and setgid on dirs, 770
-      fprintf(LOGFILE, "Failed to secure the attempt_dir %s\n", attempt_dir);
-      failed = 1;
-      free(attempt_dir);
-      free(job_dir);
-      break;
-    }
-
-    local_dir_ptr++;
-    free(attempt_dir);
-    free(job_dir);
-  }
-  free(local_dir);
-  free(full_local_dir_str);
-
-  cleanup();
-  if (failed) {
-    return PREPARE_ATTEMPT_DIRECTORIES_FAILED;
-  }
-  return 0;
-}
-
-/**
- * Function to prepare the job log dir(and job acls file in it) for the child.
- * It gives the user ownership of the job's log-dir to the user and
- * group ownership to the user running tasktracker(i.e. tt_user).
- *
- *   *  sudo chown user:mapred log-dir/userlogs/$jobid
- *   *    if user is not $tt_user,
- *   *      sudo chmod 2570 log-dir/userlogs/$jobid
- *   *    else
- *   *      sudo chmod 2770 log-dir/userlogs/$jobid
- *   *  sudo chown user:mapred log-dir/userlogs/$jobid/job-acls.xml
- *   *    if user is not $tt_user,
- *   *      sudo chmod 2570 log-dir/userlogs/$jobid/job-acls.xml
- *   *    else
- *   *      sudo chmod 2770 log-dir/userlogs/$jobid/job-acls.xml 
- */
-int prepare_job_logs(const char *log_dir, const char *job_id,
-    mode_t permissions) {
-
-  char *job_log_dir = get_job_log_dir(log_dir, job_id);
-  if (job_log_dir == NULL) {
-    fprintf(LOGFILE, "Couldn't get job log directory %s.\n", job_log_dir);
-    return -1;
-  }
-
-  struct stat filestat;
-  if (stat(job_log_dir, &filestat) != 0) {
-    if (errno == ENOENT) {
-#ifdef DEBUG
-      fprintf(LOGFILE, "job_log_dir %s doesn't exist. Not doing anything.\n",
-          job_log_dir);
-#endif
-      free(job_log_dir);
-      return 0;
-    } else {
-      // stat failed because of something else!
-      fprintf(LOGFILE, "Failed to stat the job log dir %s\n", job_log_dir);
-      free(job_log_dir);
-      return -1;
-    }
-  }
-
-  gid_t tasktracker_gid = getegid(); // the group permissions of the binary.
-  // job log directory should not be set permissions recursively
-  // because, on tt restart/reinit, it would contain directories of earlier run
-  if (secure_single_path(job_log_dir, user_detail->pw_uid, tasktracker_gid,
-      S_ISGID | permissions, 1) != 0) {
-    fprintf(LOGFILE, "Failed to secure the log_dir %s\n", job_log_dir);
-    free(job_log_dir);
-    return -1;
-  }
-
-  //set ownership and permissions for job_log_dir/job-acls.xml, if exists.
-  char *job_acls_file = get_job_acls_file(job_log_dir);
-  if (job_acls_file == NULL) {
-    fprintf(LOGFILE, "Couldn't get job acls file %s.\n", job_acls_file);
-    free(job_log_dir);
-    return -1; 
-  }
-
-  struct stat filestat1;
-  if (stat(job_acls_file, &filestat1) != 0) {
-    if (errno == ENOENT) {
-#ifdef DEBUG
-      fprintf(LOGFILE, "job_acls_file %s doesn't exist. Not doing anything.\n",
-          job_acls_file);
-#endif
-      free(job_acls_file);
-      free(job_log_dir);
-      return 0;
-    } else {
-      // stat failed because of something else!
-      fprintf(LOGFILE, "Failed to stat the job_acls_file %s\n", job_acls_file);
-      free(job_acls_file);
-      free(job_log_dir);
-      return -1;
-    }
-  }
-
-  if (secure_single_path(job_acls_file, user_detail->pw_uid, tasktracker_gid,
-      permissions, 1) != 0) {
-    fprintf(LOGFILE, "Failed to secure the job acls file %s\n", job_acls_file);
-    free(job_acls_file);
-    free(job_log_dir);
-    return -1;
-  }
-  free(job_acls_file);
-  free(job_log_dir);
-  return 0;
-}
-
-/**
- * Function to prepare the task logs for the child. It gives the user
- * ownership of the attempt's log-dir to the user and group ownership to the
- * user running tasktracker.
- *     *  sudo chown user:mapred log-dir/userlogs/$jobid/$attemptid
- *     *  sudo chmod -R 2770 log-dir/userlogs/$jobid/$attemptid
- */
-int prepare_task_logs(const char *log_dir, const char *job_id, 
-    const char *task_id) {
-
-  char *task_log_dir = get_task_log_dir(log_dir, job_id, task_id);
-  if (task_log_dir == NULL) {
-    fprintf(LOGFILE, "Couldn't get task_log directory %s.\n", task_log_dir);
-    return -1;
-  }
-
-  struct stat filestat;
-  if (stat(task_log_dir, &filestat) != 0) {
-    if (errno == ENOENT) {
-      // See TaskRunner.java to see that an absent log-dir doesn't fail the task.
-#ifdef DEBUG
-      fprintf(LOGFILE, "task_log_dir %s doesn't exist. Not doing anything.\n",
-          task_log_dir);
-#endif
-      free(task_log_dir);
-      return 0;
-    } else {
-      // stat failed because of something else!
-      fprintf(LOGFILE, "Failed to stat the task_log_dir %s\n", task_log_dir);
-      free(task_log_dir);
-      return -1;
-    }
-  }
-
-  gid_t tasktracker_gid = getegid(); // the group permissions of the binary.
-  if (secure_path(task_log_dir, user_detail->pw_uid, tasktracker_gid,
-      S_IRWXU | S_IRWXG, S_ISGID | S_IRWXU | S_IRWXG, 1) != 0) {
-    // setgid on dirs but not files, 770. As of now, there are no files though
-    fprintf(LOGFILE, "Failed to secure the log_dir %s\n", task_log_dir);
-    free(task_log_dir);
-    return -1;
-  }
-  free(task_log_dir);
-  return 0;
-}
-
-//function used to populate and user_details structure.
-int get_user_details(const char *user) {
-  if (user_detail == NULL) {
-    user_detail = getpwnam(user);
-    if (user_detail == NULL) {
-      fprintf(LOGFILE, "Invalid user\n");
-      return -1;
-    }
-  }
-  return 0;
-}
-
-/*
- * Function to check if the TaskTracker actually owns the file.
- * Or it has right ownership already. 
- */
-int check_ownership(char *path, uid_t uid, gid_t gid) {
-  struct stat filestat;
-  if (stat(path, &filestat) != 0) {
-    return UNABLE_TO_STAT_FILE;
-  }
-  // check user/group. User should be TaskTracker user, group can either be
-  // TaskTracker's primary group or the special group to which binary's
-  // permissions are set.
-  // Or it can be the user/group owned by uid and gid passed. 
-  if ((getuid() != filestat.st_uid || (getgid() != filestat.st_gid && getegid()
-      != filestat.st_gid)) &&
-      ((uid != filestat.st_uid) || (gid != filestat.st_gid))) {
-    return FILE_NOT_OWNED_BY_TASKTRACKER;
-  }
-  return 0;
-}
-
-/**
- * Function to initialize the user directories of a user.
- * It does the following:
- *     *  sudo chown user:mapred -R taskTracker/$user
- *     *  if user is not $tt_user,
- *     *    sudo chmod 2570 -R taskTracker/$user
- *     *  else // user is tt_user
- *     *    sudo chmod 2770 -R taskTracker/$user
- * This is done once per every user on the TaskTracker.
- */
-int initialize_user(const char *user) {
-
-  if (user == NULL) {
-    fprintf(LOGFILE, "user passed is null.\n");
-    return INVALID_ARGUMENT_NUMBER;
-  }
-
-  if (get_user_details(user) < 0) {
-    fprintf(LOGFILE, "Couldn't get the user details of %s", user);
-    return INVALID_USER_NAME;
-  }
-
-  gid_t tasktracker_gid = getegid(); // the group permissions of the binary.
-
-  char **local_dir = (char **) get_values(TT_SYS_DIR_KEY);
-  if (local_dir == NULL) {
-    fprintf(LOGFILE, "%s is not configured.\n", TT_SYS_DIR_KEY);
-    cleanup();
-    return INVALID_TT_ROOT;
-  }
-
-  char *full_local_dir_str = (char *) get_value(TT_SYS_DIR_KEY);
-#ifdef DEBUG
-  fprintf(LOGFILE, "Value from config for %s is %s.\n", TT_SYS_DIR_KEY,
-      full_local_dir_str);
-#endif
-
-  int is_tt_user = (user_detail->pw_uid == getuid());
-  
-  // for tt_user, set 770 permissions; otherwise set 570
-  mode_t permissions = is_tt_user ? (S_IRWXU | S_IRWXG)
-                                  : (S_IRUSR | S_IXUSR | S_IRWXG);
-  char *user_dir;
-  char **local_dir_ptr = local_dir;
-  int failed = 0;
-  while (*local_dir_ptr != NULL) {
-    user_dir = get_user_directory(*local_dir_ptr, user);
-    if (user_dir == NULL) {
-      fprintf(LOGFILE, "Couldn't get userdir directory for %s.\n", user);
-      failed = 1;
-      break;
-    }
-
-    struct stat filestat;
-    if (stat(user_dir, &filestat) != 0) {
-      if (errno == ENOENT) {
-#ifdef DEBUG
-        fprintf(LOGFILE, "user_dir %s doesn't exist. Not doing anything.\n",
-            user_dir);
-#endif
-      } else {
-        // stat failed because of something else!
-        fprintf(LOGFILE, "Failed to stat the user_dir %s\n",
-            user_dir);
-        failed = 1;
-        free(user_dir);
-        break;
-      }
-    } else if (secure_path(user_dir, user_detail->pw_uid,
-        tasktracker_gid, permissions, S_ISGID | permissions, 1) != 0) {
-      // No setgid on files and setgid on dirs,
-      // 770 for tt_user and 570 for any other user
-      fprintf(LOGFILE, "Failed to secure the user_dir %s\n",
-              user_dir);
-      failed = 1;
-      free(user_dir);
-      break;
-    }
-
-    local_dir_ptr++;
-    free(user_dir);
-  }
-  free(local_dir);
-  free(full_local_dir_str);
-  cleanup();
-  if (failed) {
-    return INITIALIZE_USER_FAILED;
-  }
-  return 0;
-}
-
-/**
- * Function to prepare the job directories for the task JVM.
- * We do the following:
- *     *  sudo chown user:mapred -R taskTracker/$user/jobcache/$jobid
- *     *  sudo chown user:mapred -R logs/userlogs/$jobid
- *     *  if user is not $tt_user,
- *     *    sudo chmod 2570 -R taskTracker/$user/jobcache/$jobid
- *     *    sudo chmod 2570 -R logs/userlogs/$jobid
- *     *  else // user is tt_user
- *     *    sudo chmod 2770 -R taskTracker/$user/jobcache/$jobid
- *     *    sudo chmod 2770 -R logs/userlogs/$jobid
- *     *
- *     *  For any user, sudo chmod 2770 taskTracker/$user/jobcache/$jobid/work
- */
-int initialize_job(const char *jobid, const char *user) {
-  if (jobid == NULL || user == NULL) {
-    fprintf(LOGFILE, "Either jobid is null or the user passed is null.\n");
-    return INVALID_ARGUMENT_NUMBER;
-  }
-
-  if (get_user_details(user) < 0) {
-    fprintf(LOGFILE, "Couldn't get the user details of %s", user);
-    return INVALID_USER_NAME;
-  }
-
-  gid_t tasktracker_gid = getegid(); // the group permissions of the binary.
-
-  char **local_dir = (char **) get_values(TT_SYS_DIR_KEY);
-  if (local_dir == NULL) {
-    fprintf(LOGFILE, "%s is not configured.\n", TT_SYS_DIR_KEY);
-    cleanup();
-    return INVALID_TT_ROOT;
-  }
-
-  char *full_local_dir_str = (char *) get_value(TT_SYS_DIR_KEY);
-#ifdef DEBUG
-  fprintf(LOGFILE, "Value from config for %s is %s.\n", TT_SYS_DIR_KEY,
-      full_local_dir_str);
-#endif
-
-  int is_tt_user = (user_detail->pw_uid == getuid());
-  
-  // for tt_user, set 770 permissions; for any other user, set 570 for job-dir
-  mode_t permissions = is_tt_user ? (S_IRWXU | S_IRWXG)
-                                  : (S_IRUSR | S_IXUSR | S_IRWXG);
-  char *job_dir, *job_work_dir;
-  char **local_dir_ptr = local_dir;
-  int failed = 0;
-  while (*local_dir_ptr != NULL) {
-    job_dir = get_job_directory(*local_dir_ptr, user, jobid);
-    if (job_dir == NULL) {
-      fprintf(LOGFILE, "Couldn't get job directory for %s.\n", jobid);
-      failed = 1;
-      break;
-    }
-
-    struct stat filestat;
-    if (stat(job_dir, &filestat) != 0) {
-      if (errno == ENOENT) {
-#ifdef DEBUG
-        fprintf(LOGFILE, "job_dir %s doesn't exist. Not doing anything.\n",
-            job_dir);
-#endif
-      } else {
-        // stat failed because of something else!
-        fprintf(LOGFILE, "Failed to stat the job_dir %s\n", job_dir);
-        failed = 1;
-        free(job_dir);
-        break;
-      }
-    } else if (secure_path(job_dir, user_detail->pw_uid, tasktracker_gid,
-               permissions, S_ISGID | permissions, 1) != 0) {
-      // No setgid on files and setgid on dirs,
-      // 770 for tt_user and 570 for any other user
-      fprintf(LOGFILE, "Failed to secure the job_dir %s\n", job_dir);
-      failed = 1;
-      free(job_dir);
-      break;
-    } else if (!is_tt_user) {
-      // For tt_user, we don't need this as we already set 2770 for
-      // job-work-dir because of "chmod -R" done above
-      job_work_dir = get_job_work_directory(job_dir);
-      if (job_work_dir == NULL) {
-        fprintf(LOGFILE, "Couldn't get job-work directory for %s.\n", jobid);
-        failed = 1;
-        break;
-      }
-
-      // Set 2770 on the job-work directory
-      if (stat(job_work_dir, &filestat) != 0) {
-        if (errno == ENOENT) {
-#ifdef DEBUG
-          fprintf(LOGFILE,
-              "job_work_dir %s doesn't exist. Not doing anything.\n",
-              job_work_dir);
-#endif
-          free(job_work_dir);
-        } else {
-          // stat failed because of something else!
-          fprintf(LOGFILE, "Failed to stat the job_work_dir %s\n",
-              job_work_dir);
-          failed = 1;
-          free(job_work_dir);
-          free(job_dir);
-          break;
-        }
-      } else if (change_mode(job_work_dir, S_ISGID | S_IRWXU | S_IRWXG) != 0) {
-        fprintf(LOGFILE,
-            "couldn't change the permissions of job_work_dir %s\n",
-            job_work_dir);
-        failed = 1;
-        free(job_work_dir);
-        free(job_dir);
-        break;
-      }
-    }
-
-    local_dir_ptr++;
-    free(job_dir);
-  }
-  free(local_dir);
-  free(full_local_dir_str);
-  int exit_code = 0;
-  if (failed) {
-    exit_code = INITIALIZE_JOB_FAILED;
-    goto cleanup;
-  }
-
-  char *log_dir = (char *) get_value(TT_LOG_DIR_KEY);
-  if (log_dir == NULL) {
-    fprintf(LOGFILE, "Log directory is not configured.\n");
-    exit_code = INVALID_TT_LOG_DIR;
-    goto cleanup;
-  }
-
-  if (prepare_job_logs(log_dir, jobid, permissions) != 0) {
-    fprintf(LOGFILE, "Couldn't prepare job logs directory %s for %s.\n",
-        log_dir, jobid);
-    exit_code = PREPARE_JOB_LOGS_FAILED;
-  }
-
-  cleanup:
-  // free configurations
-  cleanup();
-  if (log_dir != NULL) {
-    free(log_dir);
-  }
-  return exit_code;
-}
-
-/**
- * Function to initialize the distributed cache file for a user.
- * It does the following:
- *     *  sudo chown user:mapred -R taskTracker/$user/distcache/<randomdir>
- *     *  if user is not $tt_user,
- *     *    sudo chmod 2570 -R taskTracker/$user/distcache/<randomdir>
- *     *  else // user is tt_user
- *     *    sudo chmod 2770 -R taskTracker/$user/distcache/<randomdir>
- * This is done once per localization. Tasks reusing JVMs just create
- * symbolic links themselves and so there isn't anything specific to do in
- * that case.
- */
-int initialize_distributed_cache_file(const char *tt_root, 
-    const char *unique_string, const char *user) {
-  if (tt_root == NULL) {
-    fprintf(LOGFILE, "tt_root passed is null.\n");
-    return INVALID_ARGUMENT_NUMBER;
-  }
-  if (unique_string == NULL) {
-    fprintf(LOGFILE, "unique_string passed is null.\n");
-    return INVALID_ARGUMENT_NUMBER;
-  }
- 
-  if (user == NULL) {
-    fprintf(LOGFILE, "user passed is null.\n");
-    return INVALID_ARGUMENT_NUMBER;
-  }
-
-  if (get_user_details(user) < 0) {
-    fprintf(LOGFILE, "Couldn't get the user details of %s", user);
-    return INVALID_USER_NAME;
-  }
-  //Check tt_root
-  if (check_tt_root(tt_root) < 0) {
-    fprintf(LOGFILE, "invalid tt root passed %s\n", tt_root);
-    cleanup();
-    return INVALID_TT_ROOT;
-  }
-
-  // set permission on the unique directory
-  char *localized_unique_dir = get_distributed_cache_directory(tt_root, user,
-      unique_string);
-  if (localized_unique_dir == NULL) {
-    fprintf(LOGFILE, "Couldn't get unique distcache directory for %s.\n", user);
-    cleanup();
-    return INITIALIZE_DISTCACHEFILE_FAILED;
-  }
-
-  gid_t binary_gid = getegid(); // the group permissions of the binary.
-  
-  int is_tt_user = (user_detail->pw_uid == getuid());
-  
-  // for tt_user, set 770 permissions; for any other user, set 570
-  mode_t permissions = is_tt_user ? (S_IRWXU | S_IRWXG)
-                                  : (S_IRUSR | S_IXUSR | S_IRWXG);
-  int failed = 0;
-  struct stat filestat;
-  if (stat(localized_unique_dir, &filestat) != 0) {
-    // stat on distcache failed because of something
-    fprintf(LOGFILE, "Failed to stat the localized_unique_dir %s\n",
-        localized_unique_dir);
-    failed = INITIALIZE_DISTCACHEFILE_FAILED;
-  } else if (secure_path(localized_unique_dir, user_detail->pw_uid,
-        binary_gid, permissions, S_ISGID | permissions, 1) != 0) {
-    // No setgid on files and setgid on dirs,
-    // 770 for tt_user and 570 for any other user
-    fprintf(LOGFILE, "Failed to secure the localized_unique_dir %s\n",
-        localized_unique_dir);
-    failed = INITIALIZE_DISTCACHEFILE_FAILED;
-  }
-  free(localized_unique_dir);
-  cleanup();
-  return failed;
-}
-
-/**
- * Function used to initialize task. Prepares attempt_dir, jars_dir and
- * log_dir to be accessible by the child
- */
-int initialize_task(const char *jobid, const char *taskid, const char *user) {
-  int exit_code = 0;
-#ifdef DEBUG
-  fprintf(LOGFILE, "job-id passed to initialize_task : %s.\n", jobid);
-  fprintf(LOGFILE, "task-d passed to initialize_task : %s.\n", taskid);
-#endif
-
-  if (prepare_attempt_directories(jobid, taskid, user) != 0) {
-    fprintf(LOGFILE,
-        "Couldn't prepare the attempt directories for %s of user %s.\n",
-        taskid, user);
-    exit_code = PREPARE_ATTEMPT_DIRECTORIES_FAILED;
-    goto cleanup;
-  }
-
-  char *log_dir = (char *) get_value(TT_LOG_DIR_KEY);
-  if (log_dir == NULL) {
-    fprintf(LOGFILE, "Log directory is not configured.\n");
-    exit_code = INVALID_TT_LOG_DIR;
-    goto cleanup;
-  }
-
-  if (prepare_task_logs(log_dir, jobid, taskid) != 0) {
-    fprintf(LOGFILE, "Couldn't prepare task logs directory %s for %s.\n",
-        log_dir, taskid);
-    exit_code = PREPARE_TASK_LOGS_FAILED;
-  }
-
-  cleanup:
-  // free configurations
-  cleanup();
-  if (log_dir != NULL) {
-    free(log_dir);
-  }
-  return exit_code;
-}
-
-/*
- * Function used to launch a task as the provided user.
- */
-int run_task_as_user(const char * user, const char *jobid, const char *taskid,
-    const char *tt_root) {
-  return run_process_as_user(user, jobid, taskid, tt_root, LAUNCH_TASK_JVM);
-}
-
-/*
- * Function that is used as a helper to launch task JVMs and debug scripts.
- * Not meant for launching any other process. It does the following :
- * 1) Checks if the tt_root passed is found in mapreduce.cluster.local.dir
- * 2) Prepares attempt_dir and log_dir to be accessible by the task JVMs
- * 3) Uses get_task_launcher_file to fetch the task script file path
- * 4) Does an execlp on the same in order to replace the current image with
- * task image.
- */
-int run_process_as_user(const char * user, const char * jobid, 
-const char *taskid, const char *tt_root, int command) {
-  if (command != LAUNCH_TASK_JVM && command != RUN_DEBUG_SCRIPT) {
-    return INVALID_COMMAND_PROVIDED;
-  }
-  if (jobid == NULL || taskid == NULL || tt_root == NULL) {
-    return INVALID_ARGUMENT_NUMBER;
-  }
-  
-  if (command == LAUNCH_TASK_JVM) {
-    fprintf(LOGFILE, "run_process_as_user launching a JVM for task :%s.\n", taskid);
-  } else if (command == RUN_DEBUG_SCRIPT) {
-    fprintf(LOGFILE, "run_process_as_user launching a debug script for task :%s.\n", taskid);
-  }
-
-#ifdef DEBUG
-  fprintf(LOGFILE, "Job-id passed to run_process_as_user : %s.\n", jobid);
-  fprintf(LOGFILE, "task-d passed to run_process_as_user : %s.\n", taskid);
-  fprintf(LOGFILE, "tt_root passed to run_process_as_user : %s.\n", tt_root);
-#endif
-
-  //Check tt_root before switching the user, as reading configuration
-  //file requires privileged access.
-  if (check_tt_root(tt_root) < 0) {
-    fprintf(LOGFILE, "invalid tt root passed %s\n", tt_root);
-    cleanup();
-    return INVALID_TT_ROOT;
-  }
-
-  int exit_code = 0;
-  char *job_dir = NULL, *task_script_path = NULL;
-
-  if (command == LAUNCH_TASK_JVM && 
-     (exit_code = initialize_task(jobid, taskid, user)) != 0) {
-    fprintf(LOGFILE, "Couldn't initialise the task %s of user %s.\n", taskid,
-        user);
-    goto cleanup;
-  }
-
-  job_dir = get_job_directory(tt_root, user, jobid);
-  if (job_dir == NULL) {
-    fprintf(LOGFILE, "Couldn't obtain job_dir for %s in %s.\n", jobid, tt_root);
-    exit_code = OUT_OF_MEMORY;
-    goto cleanup;
-  }
-
-  task_script_path = get_task_launcher_file(job_dir, taskid);
-  if (task_script_path == NULL) {
-    fprintf(LOGFILE, "Couldn't obtain task_script_path in %s.\n", job_dir);
-    exit_code = OUT_OF_MEMORY;
-    goto cleanup;
-  }
-
-  errno = 0;
-  exit_code = check_path_for_relative_components(task_script_path);
-  if(exit_code != 0) {
-    goto cleanup;
-  }
-
-  //change the user
-  fcloseall();
-  free(job_dir);
-  umask(0007);
-  if (change_user(user) != 0) {
-    exit_code = SETUID_OPER_FAILED;
-    goto cleanup;
-  }
-
-  errno = 0;
-  cleanup();
-  execlp(task_script_path, task_script_path, NULL);
-  if (errno != 0) {
-    free(task_script_path);
-    if (command == LAUNCH_TASK_JVM) {
-      fprintf(LOGFILE, "Couldn't execute the task jvm file: %s", strerror(errno));
-      exit_code = UNABLE_TO_EXECUTE_TASK_SCRIPT;
-    } else if (command == RUN_DEBUG_SCRIPT) {
-      fprintf(LOGFILE, "Couldn't execute the task debug script file: %s", strerror(errno));
-      exit_code = UNABLE_TO_EXECUTE_DEBUG_SCRIPT;
-    }
-  }
-
-  return exit_code;
-
-cleanup:
-  if (job_dir != NULL) {
-    free(job_dir);
-  }
-  if (task_script_path != NULL) {
-    free(task_script_path);
-  }
-  // free configurations
-  cleanup();
-  return exit_code;
-}
-/*
- * Function used to launch a debug script as the provided user. 
- */
-int run_debug_script_as_user(const char * user, const char *jobid, const char *taskid,
-    const char *tt_root) {
-  return run_process_as_user(user, jobid, taskid, tt_root, RUN_DEBUG_SCRIPT);
-}
-/**
- * Function used to terminate/kill a task launched by the user,
- * or dump the process' stack (by sending SIGQUIT).
- * The function sends appropriate signal to the process group
- * specified by the task_pid.
- */
-int kill_user_task(const char *user, const char *task_pid, int sig) {
-  int pid = 0;
-
-  if(task_pid == NULL) {
-    return INVALID_ARGUMENT_NUMBER;
-  }
-
-#ifdef DEBUG
-  fprintf(LOGFILE, "user passed to kill_user_task : %s.\n", user);
-  fprintf(LOGFILE, "task-pid passed to kill_user_task : %s.\n", task_pid);
-  fprintf(LOGFILE, "signal passed to kill_user_task : %d.\n", sig);
-#endif
-
-  pid = atoi(task_pid);
-
-  if(pid <= 0) {
-    return INVALID_TASK_PID;
-  }
-
-  fcloseall();
-  if (change_user(user) != 0) {
-    cleanup();
-    return SETUID_OPER_FAILED;
-  }
-
-  //Don't continue if the process-group is not alive anymore.
-  if(kill(-pid,0) < 0) {
-    errno = 0;
-    cleanup();
-    return 0;
-  }
-
-  if (kill(-pid, sig) < 0) {
-    if(errno != ESRCH) {
-      fprintf(LOGFILE, "Error is %s\n", strerror(errno));
-      cleanup();
-      return UNABLE_TO_KILL_TASK;
-    }
-    errno = 0;
-  }
-  cleanup();
-  return 0;
-}
-
-/**
- * Enables the path for deletion by changing the owner, group and permissions
- * of the specified path and all the files/directories in the path recursively.
- *     *  sudo chown user:mapred -R full_path
- *     *  sudo chmod 2770 -R full_path
- * Before changing permissions, makes sure that the given path doesn't contain
- * any relative components.
- * tt_root : is the base path(i.e. mapred-local-dir) sent to task-controller
- * full_path : is either jobLocalDir, taskDir OR taskWorkDir that is to be 
- *             deleted
- */
-static int enable_path_for_cleanup(const char *tt_root, const char *user,
-                                   char *full_path) {
-  int exit_code = 0;
-  gid_t tasktracker_gid = getegid(); // the group permissions of the binary.
-
-  if (check_tt_root(tt_root) < 0) {
-    fprintf(LOGFILE, "invalid tt root passed %s\n", tt_root);
-    cleanup();
-    return INVALID_TT_ROOT;
-  }
- 
-  if (full_path == NULL) {
-    fprintf(LOGFILE,
-            "Could not build the full path. Not deleting the dir %s\n",
-            full_path);
-    exit_code = UNABLE_TO_BUILD_PATH; // may be malloc failed
-  }
-     // Make sure that the path given is not having any relative components
-  else if ((exit_code = check_path_for_relative_components(full_path)) != 0) {
-    fprintf(LOGFILE,
-    "Not changing permissions. Path may contain relative components.\n",
-         full_path);
-  }
-  else if (get_user_details(user) < 0) {
-    fprintf(LOGFILE, "Couldn't get the user details of %s.\n", user);
-    exit_code = INVALID_USER_NAME;
-  }
-  else if (exit_code = secure_path(full_path, user_detail->pw_uid,
-               tasktracker_gid,
-               S_IRWXU | S_IRWXG, S_ISGID | S_IRWXU | S_IRWXG, 0) != 0) {
-    // No setgid on files and setgid on dirs, 770.
-    // set 770 permissions for user, TTgroup for all files/directories in
-    // 'full_path' recursively sothat deletion of path by TaskTracker succeeds.
-
-    fprintf(LOGFILE, "Failed to set permissions for %s\n", full_path);
-  }
-
-  if (full_path != NULL) {
-    free(full_path);
-  }
-  // free configurations
-  cleanup();
-  return exit_code;
-}
-
-/**
- * Enables the task work-dir/local-dir path for deletion.
- * tt_root : is the base path(i.e. mapred-local-dir) sent to task-controller
- * dir_to_be_deleted : is either taskDir OR taskWorkDir that is to be deleted
- */
-int enable_task_for_cleanup(const char *tt_root, const char *user,
-           const char *jobid, const char *dir_to_be_deleted) {
-  char *full_path = get_task_dir_path(tt_root, user, jobid, dir_to_be_deleted);
-  return enable_path_for_cleanup(tt_root, user, full_path);
-}
-
-/**
- * Enables the jobLocalDir for deletion.
- * tt_root : is the base path(i.e. mapred-local-dir) sent to task-controller
- * user    : owner of the job
- * jobid   : id of the job for which the cleanup is needed.
- */
-int enable_job_for_cleanup(const char *tt_root, const char *user, 
-                           const char *jobid) {
-  char *full_path = get_job_directory(tt_root, user, jobid);
-  return enable_path_for_cleanup(tt_root, user, full_path);
-}

+ 0 - 148
mapreduce/src/c++/task-controller/task-controller.h

@@ -1,148 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <string.h>
-#include <errno.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <pwd.h>
-#include <assert.h>
-#include <getopt.h>
-#include <sys/stat.h>
-#include <sys/signal.h>
-#include <getopt.h>
-#include <grp.h>
-#include <dirent.h>
-#include <fcntl.h>
-#include <fts.h>
-
-#include "configuration.h"
-
-//command definitions
-enum command {
-  INITIALIZE_USER,
-  INITIALIZE_JOB,
-  INITIALIZE_DISTRIBUTEDCACHE_FILE,
-  LAUNCH_TASK_JVM,
-  INITIALIZE_TASK,
-  TERMINATE_TASK_JVM,
-  KILL_TASK_JVM,
-  RUN_DEBUG_SCRIPT,
-  SIGQUIT_TASK_JVM,
-  ENABLE_TASK_FOR_CLEANUP,
-  ENABLE_JOB_FOR_CLEANUP
-};
-
-enum errorcodes {
-  INVALID_ARGUMENT_NUMBER = 1,
-  INVALID_USER_NAME, //2
-  INVALID_COMMAND_PROVIDED, //3
-  SUPER_USER_NOT_ALLOWED_TO_RUN_TASKS, //4
-  INVALID_TT_ROOT, //5
-  SETUID_OPER_FAILED, //6
-  UNABLE_TO_EXECUTE_TASK_SCRIPT, //7
-  UNABLE_TO_KILL_TASK, //8
-  INVALID_TASK_PID, //9
-  ERROR_RESOLVING_FILE_PATH, //10
-  RELATIVE_PATH_COMPONENTS_IN_FILE_PATH, //11
-  UNABLE_TO_STAT_FILE, //12
-  FILE_NOT_OWNED_BY_TASKTRACKER, //13
-  PREPARE_ATTEMPT_DIRECTORIES_FAILED, //14
-  INITIALIZE_JOB_FAILED, //15
-  PREPARE_TASK_LOGS_FAILED, //16
-  INVALID_TT_LOG_DIR, //17
-  OUT_OF_MEMORY, //18
-  INITIALIZE_DISTCACHEFILE_FAILED, //19
-  INITIALIZE_USER_FAILED, //20
-  UNABLE_TO_EXECUTE_DEBUG_SCRIPT, //21
-  INVALID_CONF_DIR, //22
-  UNABLE_TO_BUILD_PATH, //23
-  INVALID_TASKCONTROLLER_PERMISSIONS, //24
-  PREPARE_JOB_LOGS_FAILED, //25
-};
-
-#define USER_DIR_PATTERN "%s/taskTracker/%s"
-
-#define TT_JOB_DIR_PATTERN USER_DIR_PATTERN"/jobcache/%s"
-
-#define USER_DISTRIBUTED_CACHE_DIR_PATTERN USER_DIR_PATTERN"/distcache/%s"
-
-#define JOB_DIR_TO_JOB_WORK_PATTERN "%s/work"
-
-#define JOB_DIR_TO_ATTEMPT_DIR_PATTERN "%s/%s"
-
-#define JOB_LOG_DIR_PATTERN "%s/userlogs/%s"
-
-#define JOB_LOG_DIR_TO_JOB_ACLS_FILE_PATTERN "%s/job-acls.xml"
-
-#define ATTEMPT_LOG_DIR_PATTERN JOB_LOG_DIR_PATTERN"/%s"
-
-#define TASK_SCRIPT_PATTERN "%s/%s/taskjvm.sh"
-
-#define TT_LOCAL_TASK_DIR_PATTERN    "%s/taskTracker/%s/jobcache/%s/%s"
-
-#define TT_SYS_DIR_KEY "mapreduce.cluster.local.dir"
-
-#define TT_LOG_DIR_KEY "hadoop.log.dir"
-
-#define TT_GROUP_KEY "mapreduce.tasktracker.group"
-
-#ifndef HADOOP_CONF_DIR
-  #define EXEC_PATTERN "/bin/task-controller"
-  extern char * hadoop_conf_dir;
-#endif
-
-extern struct passwd *user_detail;
-
-extern FILE *LOGFILE;
-
-int run_task_as_user(const char * user, const char *jobid, const char *taskid,
-    const char *tt_root);
-
-int run_debug_script_as_user(const char * user, const char *jobid, const char *taskid,
-    const char *tt_root);
-
-int initialize_user(const char *user);
-
-int initialize_task(const char *jobid, const char *taskid, const char *user);
-
-int initialize_job(const char *jobid, const char *user);
-
-int initialize_distributed_cache_file(const char *tt_root, 
-    const char* unique_string, const char *user);
-
-int kill_user_task(const char *user, const char *task_pid, int sig);
-
-int enable_task_for_cleanup(const char *tt_root, const char *user,
-                            const char *jobid, const char *dir_to_be_deleted);
-
-int enable_job_for_cleanup(const char *tt_root, const char *user,
-                           const char *jobid);
-
-int prepare_attempt_directory(const char *attempt_dir, const char *user);
-
-// The following functions are exposed for testing
-
-int check_variable_against_config(const char *config_key,
-    const char *passed_value);
-
-int get_user_details(const char *user);
-
-char *get_task_launcher_file(const char *job_dir, const char *attempt_dir);

+ 763 - 0
mapreduce/src/c++/task-controller/test/test-task-controller.c

@@ -0,0 +1,763 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "configuration.h"
+#include "task-controller.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#define TEST_ROOT "/tmp/test-task-controller"
+#define DONT_TOUCH_FILE "dont-touch-me"
+
+static char* username = NULL;
+
+/**
+ * Run the command using the effective user id.
+ * It can't use system, since bash seems to copy the real user id into the
+ * effective id.
+ */
+void run(const char *cmd) {
+  fflush(stdout);
+  fflush(stderr);
+  pid_t child = fork();
+  if (child == -1) {
+    printf("FAIL: failed to fork - %s\n", strerror(errno));
+  } else if (child == 0) {
+    char *cmd_copy = strdup(cmd);
+    char *ptr;
+    int words = 1;
+    for(ptr = strchr(cmd_copy, ' ');  ptr; ptr = strchr(ptr+1, ' ')) {
+      words += 1;
+    }
+    char **argv = malloc(sizeof(char *) * (words + 1));
+    ptr = strtok(cmd_copy, " ");
+    int i = 0;
+    argv[i++] = ptr;
+    while (ptr != NULL) {
+      ptr = strtok(NULL, " ");
+      argv[i++] = ptr;
+    }
+    if (execvp(argv[0], argv) != 0) {
+      printf("FAIL: exec failed in child %s - %s\n", cmd, strerror(errno));
+      exit(42);
+    }
+  } else {
+    int status = 0;
+    if (waitpid(child, &status, 0) <= 0) {
+      printf("FAIL: failed waiting for child process %s pid %d - %s\n", 
+	     cmd, child, strerror(errno));
+      exit(1);
+    }
+    if (!WIFEXITED(status)) {
+      printf("FAIL: process %s pid %d did not exit\n", cmd, child);
+      exit(1);
+    }
+    if (WEXITSTATUS(status) != 0) {
+      printf("FAIL: process %s pid %d exited with error status %d\n", cmd, 
+	     child, WEXITSTATUS(status));
+      exit(1);
+    }
+  }
+}
+
+int write_config_file(char *file_name) {
+  FILE *file;
+  file = fopen(file_name, "w");
+  if (file == NULL) {
+    printf("Failed to open %s.\n", file_name);
+    return EXIT_FAILURE;
+  }
+  fprintf(file, "mapred.local.dir=" TEST_ROOT "/local-1");
+  int i;
+  for(i=2; i < 5; ++i) {
+    fprintf(file, "," TEST_ROOT "/local-%d", i);
+  }
+  fprintf(file, "\n");
+  fprintf(file, "hadoop.log.dir=" TEST_ROOT "/logs\n");
+  fclose(file);
+  return 0;
+}
+
+void create_tt_roots() {
+  char** tt_roots = get_values("mapred.local.dir");
+  char** tt_root;
+  for(tt_root=tt_roots; *tt_root != NULL; ++tt_root) {
+    if (mkdir(*tt_root, 0755) != 0) {
+      printf("FAIL: Can't create directory %s - %s\n", *tt_root,
+	     strerror(errno));
+      exit(1);
+    }
+    char buffer[100000];
+    sprintf(buffer, "%s/taskTracker", *tt_root);
+    if (mkdir(buffer, 0755) != 0) {
+      printf("FAIL: Can't create directory %s - %s\n", buffer,
+	     strerror(errno));
+      exit(1);
+    }
+  }
+  free_values(tt_roots);
+}
+
+void test_get_user_directory() {
+  char *user_dir = get_user_directory("/tmp", "user");
+  char *expected = "/tmp/taskTracker/user";
+  if (strcmp(user_dir, expected) != 0) {
+    printf("test_get_user_directory expected %s got %s\n", user_dir, expected);
+    exit(1);
+  }
+  free(user_dir);
+}
+
+void test_get_job_directory() {
+  char *expected = "/tmp/taskTracker/user/jobcache/job_200906101234_0001";
+  char *job_dir = (char *) get_job_directory("/tmp", "user",
+      "job_200906101234_0001");
+  if (strcmp(job_dir, expected) != 0) {
+    exit(1);
+  }
+  free(job_dir);
+}
+
+void test_get_attempt_directory() {
+  char *attempt_dir = get_attempt_work_directory("/tmp", "owen", "job_1",
+						 "attempt_1");
+  char *expected = "/tmp/taskTracker/owen/jobcache/job_1/attempt_1/work";
+  if (strcmp(attempt_dir, expected) != 0) {
+    printf("Fail get_attempt_work_directory got %s expected %s\n",
+	   attempt_dir, expected);
+  }
+  free(attempt_dir);
+}
+
+void test_get_task_launcher_file() {
+  char *expected_file = ("/tmp/taskTracker/user/jobcache/job_200906101234_0001"
+			 "/taskjvm.sh");
+  char *job_dir = get_job_directory("/tmp", "user",
+                                    "job_200906101234_0001");
+  char *task_file =  get_task_launcher_file(job_dir);
+  if (strcmp(task_file, expected_file) != 0) {
+    printf("failure to match expected task file %s vs %s\n", task_file,
+           expected_file);
+    exit(1);
+  }
+  free(job_dir);
+  free(task_file);
+}
+
+void test_get_job_log_dir() {
+  char *expected = TEST_ROOT "/logs/userlogs/job_200906101234_0001";
+  char *logdir = get_job_log_directory("job_200906101234_0001");
+  if (strcmp(logdir, expected) != 0) {
+    printf("Fail get_job_log_dir got %s expected %s\n", logdir, expected);
+    exit(1);
+  }
+  free(logdir);
+}
+
+void test_get_task_log_dir() {
+  char *logdir = get_job_log_directory("job_5/task_4");
+  char *expected = TEST_ROOT "/logs/userlogs/job_5/task_4";
+  if (strcmp(logdir, expected) != 0) {
+    printf("FAIL: get_task_log_dir expected %s got %s\n", logdir, expected);
+  }
+  free(logdir);
+}
+
+void test_check_user() {
+  printf("\nTesting test_check_user\n");
+  struct passwd *user = check_user(username);
+  if (user == NULL) {
+    printf("FAIL: failed check for user %s\n", username);
+    exit(1);
+  }
+  free(user);
+  if (check_user("lp") != NULL) {
+    printf("FAIL: failed check for system user lp\n");
+    exit(1);
+  }
+  if (check_user("root") != NULL) {
+    printf("FAIL: failed check for system user root\n");
+    exit(1);
+  }
+  if (check_user("mapred") != NULL) {
+    printf("FAIL: failed check for hadoop user mapred\n");
+    exit(1);
+  }
+}
+
+void test_check_configuration_permissions() {
+  printf("\nTesting check_configuration_permissions\n");
+  if (check_configuration_permissions("/etc/passwd") != 0) {
+    printf("FAIL: failed permission check on /etc/passwd\n");
+    exit(1);
+  }
+  if (check_configuration_permissions(TEST_ROOT) == 0) {
+    printf("FAIL: failed permission check on %s\n", TEST_ROOT);
+    exit(1);
+  }
+}
+
+void test_delete_task() {
+  if (initialize_user(username)) {
+    printf("FAIL: failed to initialized user %s\n", username);
+    exit(1);
+  }
+  char* job_dir = get_job_directory(TEST_ROOT "/local-2", username, "job_1");
+  char* dont_touch = get_job_directory(TEST_ROOT "/local-2", username, 
+                                       DONT_TOUCH_FILE);
+  char* task_dir = get_attempt_work_directory(TEST_ROOT "/local-2", 
+					      username, "job_1", "task_1");
+  char buffer[100000];
+  sprintf(buffer, "mkdir -p %s/who/let/the/dogs/out/who/who", task_dir);
+  run(buffer);
+  sprintf(buffer, "touch %s", dont_touch);
+  run(buffer);
+
+  // soft link to the canary file from the task directory
+  sprintf(buffer, "ln -s %s %s/who/softlink", dont_touch, task_dir);
+  run(buffer);
+  // hard link to the canary file from the task directory
+  sprintf(buffer, "ln %s %s/who/hardlink", dont_touch, task_dir);
+  run(buffer);
+  // create a dot file in the task directory
+  sprintf(buffer, "touch %s/who/let/.dotfile", task_dir);
+  run(buffer);
+  // create a no permission file
+  sprintf(buffer, "touch %s/who/let/protect", task_dir);
+  run(buffer);
+  sprintf(buffer, "chmod 000 %s/who/let/protect", task_dir);
+  run(buffer);
+  // create a no permission directory
+  sprintf(buffer, "chmod 000 %s/who/let", task_dir);
+  run(buffer);
+
+  // delete task directory
+  int ret = delete_as_user(username, "jobcache/job_1/task_1");
+  if (ret != 0) {
+    printf("FAIL: return code from delete_as_user is %d\n", ret);
+    exit(1);
+  }
+
+  // check to make sure the task directory is gone
+  if (access(task_dir, R_OK) == 0) {
+    printf("FAIL: failed to delete the directory - %s\n", task_dir);
+    exit(1);
+  }
+  // check to make sure the job directory is not gone
+  if (access(job_dir, R_OK) != 0) {
+    printf("FAIL: accidently deleted the directory - %s\n", job_dir);
+    exit(1);
+  }
+  // but that the canary is not gone
+  if (access(dont_touch, R_OK) != 0) {
+    printf("FAIL: accidently deleted file %s\n", dont_touch);
+    exit(1);
+  }
+  sprintf(buffer, "chmod -R 700 %s", job_dir);
+  run(buffer);
+  sprintf(buffer, "rm -fr %s", job_dir);
+  run(buffer);
+  free(job_dir);
+  free(task_dir);
+  free(dont_touch);
+}
+
+void test_delete_job() {
+  char* job_dir = get_job_directory(TEST_ROOT "/local-2", username, "job_2");
+  char* dont_touch = get_job_directory(TEST_ROOT "/local-2", username, 
+                                       DONT_TOUCH_FILE);
+  char* task_dir = get_attempt_work_directory(TEST_ROOT "/local-2", 
+					      username, "job_2", "task_1");
+  char buffer[100000];
+  sprintf(buffer, "mkdir -p %s/who/let/the/dogs/out/who/who", task_dir);
+  run(buffer);
+  sprintf(buffer, "touch %s", dont_touch);
+  run(buffer);
+
+  // soft link to the canary file from the task directory
+  sprintf(buffer, "ln -s %s %s/who/softlink", dont_touch, task_dir);
+  run(buffer);
+  // hard link to the canary file from the task directory
+  sprintf(buffer, "ln %s %s/who/hardlink", dont_touch, task_dir);
+  run(buffer);
+  // create a dot file in the task directory
+  sprintf(buffer, "touch %s/who/let/.dotfile", task_dir);
+  run(buffer);
+  // create a no permission file
+  sprintf(buffer, "touch %s/who/let/protect", task_dir);
+  run(buffer);
+  sprintf(buffer, "chmod 000 %s/who/let/protect", task_dir);
+  run(buffer);
+  // create a no permission directory
+  sprintf(buffer, "chmod 000 %s/who/let", task_dir);
+  run(buffer);
+
+  // delete task directory
+  int ret = delete_as_user(username, "jobcache/job_2");
+  if (ret != 0) {
+    printf("FAIL: return code from delete_as_user is %d\n", ret);
+    exit(1);
+  }
+
+  // check to make sure the task directory is gone
+  if (access(task_dir, R_OK) == 0) {
+    printf("FAIL: failed to delete the directory - %s\n", task_dir);
+    exit(1);
+  }
+  // check to make sure the job directory is gone
+  if (access(job_dir, R_OK) == 0) {
+    printf("FAIL: didn't delete the directory - %s\n", job_dir);
+    exit(1);
+  }
+  // but that the canary is not gone
+  if (access(dont_touch, R_OK) != 0) {
+    printf("FAIL: accidently deleted file %s\n", dont_touch);
+    exit(1);
+  }
+  free(job_dir);
+  free(task_dir);
+  free(dont_touch);
+}
+
+
+void test_delete_user() {
+  printf("\nTesting delete_user\n");
+  char* job_dir = get_job_directory(TEST_ROOT "/local-1", username, "job_3");
+  if (mkdirs(job_dir, 0700) != 0) {
+    exit(1);
+  }
+  char buffer[100000];
+  sprintf(buffer, "%s/local-1/taskTracker/%s", TEST_ROOT, username);
+  if (access(buffer, R_OK) != 0) {
+    printf("FAIL: directory missing before test\n");
+    exit(1);
+  }
+  if (delete_as_user(username, "") != 0) {
+    exit(1);
+  }
+  if (access(buffer, R_OK) == 0) {
+    printf("FAIL: directory not deleted\n");
+    exit(1);
+  }
+  if (access(TEST_ROOT "/local-1", R_OK) != 0) {
+    printf("FAIL: local-1 directory does not exist\n");
+    exit(1);
+  }
+  free(job_dir);
+}
+
+void test_delete_log_directory() {
+  printf("\nTesting delete_log_directory\n");
+  char *job_log_dir = get_job_log_directory("job_1");
+  if (job_log_dir == NULL) {
+    exit(1);
+  }
+  if (create_directory_for_user(job_log_dir) != 0) {
+    exit(1);
+  }
+  free(job_log_dir);
+  char *task_log_dir = get_job_log_directory("job_1/task_2");
+  if (task_log_dir == NULL) {
+    exit(1);
+  }
+  if (mkdirs(task_log_dir, 0700) != 0) {
+    exit(1);
+  }
+  if (access(TEST_ROOT "/logs/userlogs/job_1/task_2", R_OK) != 0) {
+    printf("FAIL: can't access task directory - %s\n", strerror(errno));
+    exit(1);
+  }
+  if (delete_log_directory("job_1/task_2") != 0) {
+    printf("FAIL: can't delete task directory\n");
+    exit(1);
+  }
+  if (access(TEST_ROOT "/logs/userlogs/job_1/task_2", R_OK) == 0) {
+    printf("FAIL: task directory not deleted\n");
+    exit(1);
+  }
+  if (access(TEST_ROOT "/logs/userlogs/job_1", R_OK) != 0) {
+    printf("FAIL: job directory not deleted - %s\n", strerror(errno));
+    exit(1);
+  }
+  if (delete_log_directory("job_1") != 0) {
+    printf("FAIL: can't delete task directory\n");
+    exit(1);
+  }
+  if (access(TEST_ROOT "/logs/userlogs/job_1", R_OK) == 0) {
+    printf("FAIL: job directory not deleted\n");
+    exit(1);
+  }
+  free(task_log_dir);
+}
+
+void run_test_in_child(const char* test_name, void (*func)()) {
+  printf("\nRunning test %s in child process\n", test_name);
+  fflush(stdout);
+  fflush(stderr);
+  pid_t child = fork();
+  if (child == -1) {
+    printf("FAIL: fork failed\n");
+    exit(1);
+  } else if (child == 0) {
+    func();
+    exit(0);
+  } else {
+    int status = 0;
+    if (waitpid(child, &status, 0) == -1) {
+      printf("FAIL: waitpid %d failed - %s\n", child, strerror(errno));
+      exit(1);
+    }
+    if (!WIFEXITED(status)) {
+      printf("FAIL: child %d didn't exit - %d\n", child, status);
+      exit(1);
+    }
+    if (WEXITSTATUS(status) != 0) {
+      printf("FAIL: child %d exited with bad status %d\n",
+	     child, WEXITSTATUS(status));
+      exit(1);
+    }
+  }
+}
+
+void test_signal_task() {
+  printf("\nTesting signal_task\n");
+  fflush(stdout);
+  fflush(stderr);
+  pid_t child = fork();
+  if (child == -1) {
+    printf("FAIL: fork failed\n");
+    exit(1);
+  } else if (child == 0) {
+    if (change_user(user_detail->pw_uid, user_detail->pw_gid) != 0) {
+      exit(1);
+    }
+    sleep(3600);
+    exit(0);
+  } else {
+    printf("Child task launched as %d\n", child);
+    if (signal_user_task(username, child, SIGQUIT) != 0) {
+      exit(1);
+    }
+    int status = 0;
+    if (waitpid(child, &status, 0) == -1) {
+      printf("FAIL: waitpid failed - %s\n", strerror(errno));
+      exit(1);
+    }
+    if (!WIFSIGNALED(status)) {
+      printf("FAIL: child wasn't signalled - %d\n", status);
+      exit(1);
+    }
+    if (WTERMSIG(status) != SIGQUIT) {
+      printf("FAIL: child was killed with %d instead of %d\n", 
+	     WTERMSIG(status), SIGQUIT);
+      exit(1);
+    }
+  }
+}
+
+void test_signal_task_group() {
+  printf("\nTesting group signal_task\n");
+  fflush(stdout);
+  fflush(stderr);
+  pid_t child = fork();
+  if (child == -1) {
+    printf("FAIL: fork failed\n");
+    exit(1);
+  } else if (child == 0) {
+    setpgrp();
+    if (change_user(user_detail->pw_uid, user_detail->pw_gid) != 0) {
+      exit(1);
+    }
+    sleep(3600);
+    exit(0);
+  }
+  printf("Child task launched as %d\n", child);
+  if (signal_user_task(username, child, SIGKILL) != 0) {
+    exit(1);
+  }
+  int status = 0;
+  if (waitpid(child, &status, 0) == -1) {
+    printf("FAIL: waitpid failed - %s\n", strerror(errno));
+    exit(1);
+  }
+  if (!WIFSIGNALED(status)) {
+    printf("FAIL: child wasn't signalled - %d\n", status);
+    exit(1);
+  }
+  if (WTERMSIG(status) != SIGKILL) {
+    printf("FAIL: child was killed with %d instead of %d\n", 
+	   WTERMSIG(status), SIGKILL);
+    exit(1);
+  }
+}
+
+void test_init_job() {
+  printf("\nTesting init job\n");
+  if (seteuid(0) != 0) {
+    printf("FAIL: seteuid to root failed - %s\n", strerror(errno));
+    exit(1);
+  }
+  FILE* creds = fopen(TEST_ROOT "/creds.txt", "w");
+  if (creds == NULL) {
+    printf("FAIL: failed to create credentials file - %s\n", strerror(errno));
+    exit(1);
+  }
+  if (fprintf(creds, "secret key\n") < 0) {
+    printf("FAIL: fprintf failed - %s\n", strerror(errno));
+    exit(1);
+  }
+  if (fclose(creds) != 0) {
+    printf("FAIL: fclose failed - %s\n", strerror(errno));
+    exit(1);
+  }
+  FILE* job_xml = fopen(TEST_ROOT "/job.xml", "w");
+  if (job_xml == NULL) {
+    printf("FAIL: failed to create job file - %s\n", strerror(errno));
+    exit(1);
+  }
+  if (fprintf(job_xml, "<jobconf/>\n") < 0) {
+    printf("FAIL: fprintf failed - %s\n", strerror(errno));
+    exit(1);
+  }
+  if (fclose(job_xml) != 0) {
+    printf("FAIL: fclose failed - %s\n", strerror(errno));
+    exit(1);
+  }
+  if (seteuid(user_detail->pw_uid) != 0) {
+    printf("FAIL: failed to seteuid back to user - %s\n", strerror(errno));
+    exit(1);
+  }
+  fflush(stdout);
+  fflush(stderr);
+  pid_t child = fork();
+  if (child == -1) {
+    printf("FAIL: failed to fork process for init_job - %s\n", 
+	   strerror(errno));
+    exit(1);
+  } else if (child == 0) {
+    char *final_pgm[] = {"touch", "my-touch-file", 0};
+    if (initialize_job(username, "job_4", TEST_ROOT "/creds.txt", 
+                       TEST_ROOT "/job.xml", final_pgm) != 0) {
+      printf("FAIL: failed in child\n");
+      exit(42);
+    }
+    // should never return
+    exit(1);
+  }
+  int status = 0;
+  if (waitpid(child, &status, 0) <= 0) {
+    printf("FAIL: failed waiting for process %d - %s\n", child, 
+	   strerror(errno));
+    exit(1);
+  }
+  if (access(TEST_ROOT "/logs/userlogs/job_4", R_OK) != 0) {
+    printf("FAIL: failed to create job log directory\n");
+    exit(1);
+  }
+  char* job_dir = get_job_directory(TEST_ROOT "/local-1", username, "job_4");
+  if (access(job_dir, R_OK) != 0) {
+    printf("FAIL: failed to create job directory %s\n", job_dir);
+    exit(1);
+  }
+  char buffer[100000];
+  sprintf(buffer, "%s/jobToken", job_dir);
+  if (access(buffer, R_OK) != 0) {
+    printf("FAIL: failed to create credentials %s\n", buffer);
+    exit(1);
+  }
+  sprintf(buffer, "%s/my-touch-file", job_dir);
+  if (access(buffer, R_OK) != 0) {
+    printf("FAIL: failed to create touch file %s\n", buffer);
+    exit(1);
+  }
+  free(job_dir);
+  job_dir = get_job_log_directory("job_4");
+  if (access(job_dir, R_OK) != 0) {
+    printf("FAIL: failed to create job log directory %s\n", job_dir);
+    exit(1);
+  }
+  free(job_dir);
+}
+
+void test_run_task() {
+  printf("\nTesting run task\n");
+  if (seteuid(0) != 0) {
+    printf("FAIL: seteuid to root failed - %s\n", strerror(errno));
+    exit(1);
+  }
+  const char* script_name = TEST_ROOT "/task-script";
+  FILE* script = fopen(script_name, "w");
+  if (script == NULL) {
+    printf("FAIL: failed to create script file - %s\n", strerror(errno));
+    exit(1);
+  }
+  if (seteuid(user_detail->pw_uid) != 0) {
+    printf("FAIL: failed to seteuid back to user - %s\n", strerror(errno));
+    exit(1);
+  }
+  if (fprintf(script, "#!/bin/bash\n"
+                     "touch foobar\n"
+                     "exit 0") < 0) {
+    printf("FAIL: fprintf failed - %s\n", strerror(errno));
+    exit(1);
+  }
+  if (fclose(script) != 0) {
+    printf("FAIL: fclose failed - %s\n", strerror(errno));
+    exit(1);
+  }
+  fflush(stdout);
+  fflush(stderr);
+  char* task_dir = get_attempt_work_directory(TEST_ROOT "/local-1", 
+					      username, "job_4", "task_1");
+  pid_t child = fork();
+  if (child == -1) {
+    printf("FAIL: failed to fork process for init_job - %s\n", 
+	   strerror(errno));
+    exit(1);
+  } else if (child == 0) {
+    if (run_task_as_user(username, "job_4", "task_1", 
+                         task_dir, script_name) != 0) {
+      printf("FAIL: failed in child\n");
+      exit(42);
+    }
+    // should never return
+    exit(1);
+  }
+  int status = 0;
+  if (waitpid(child, &status, 0) <= 0) {
+    printf("FAIL: failed waiting for process %d - %s\n", child, 
+	   strerror(errno));
+    exit(1);
+  }
+  if (access(TEST_ROOT "/logs/userlogs/job_4/task_1", R_OK) != 0) {
+    printf("FAIL: failed to create task log directory\n");
+    exit(1);
+  }
+  if (access(task_dir, R_OK) != 0) {
+    printf("FAIL: failed to create task directory %s\n", task_dir);
+    exit(1);
+  }
+  char buffer[100000];
+  sprintf(buffer, "%s/foobar", task_dir);
+  if (access(buffer, R_OK) != 0) {
+    printf("FAIL: failed to create touch file %s\n", buffer);
+    exit(1);
+  }
+  free(task_dir);
+  task_dir = get_job_log_directory("job_4/task_1");
+  if (access(task_dir, R_OK) != 0) {
+    printf("FAIL: failed to create job log directory %s\n", task_dir);
+    exit(1);
+  }
+  free(task_dir);
+}
+
+int main(int argc, char **argv) {
+  LOGFILE = stdout;
+  int my_username = 0;
+
+  // clean up any junk from previous run
+  system("chmod -R u=rwx " TEST_ROOT "; rm -fr " TEST_ROOT);
+  
+  if (mkdirs(TEST_ROOT "/logs/userlogs", 0755) != 0) {
+    exit(1);
+  }
+  
+  if (write_config_file(TEST_ROOT "/test.cfg") != 0) {
+    exit(1);
+  }
+  read_config(TEST_ROOT "/test.cfg");
+
+  create_tt_roots();
+
+  if (getuid() == 0 && argc == 2) {
+    username = argv[1];
+  } else {
+    username = strdup(getpwuid(getuid())->pw_name);
+    my_username = 1;
+  }
+  set_tasktracker_uid(geteuid(), getegid());
+
+  if (set_user(username)) {
+    exit(1);
+  }
+
+  printf("\nStarting tests\n");
+
+  printf("\nTesting get_user_directory()\n");
+  test_get_user_directory();
+
+  printf("\nTesting get_job_directory()\n");
+  test_get_job_directory();
+
+  printf("\nTesting get_attempt_directory()\n");
+  test_get_attempt_directory();
+
+  printf("\nTesting get_task_launcher_file()\n");
+  test_get_task_launcher_file();
+
+  printf("\nTesting get_job_log_dir()\n");
+  test_get_job_log_dir();
+
+  test_check_configuration_permissions();
+
+  printf("\nTesting get_task_log_dir()\n");
+  test_get_task_log_dir();
+
+  printf("\nTesting delete_task()\n");
+  test_delete_task();
+
+  printf("\nTesting delete_job()\n");
+  test_delete_job();
+
+  test_delete_user();
+
+  test_check_user();
+
+  test_delete_log_directory();
+
+  // the tests that change user need to be run in a subshell, so that
+  // when they change user they don't give up our privs
+  run_test_in_child("test_signal_task", test_signal_task);
+  run_test_in_child("test_signal_task_group", test_signal_task_group);
+
+  // init job and run task can't be run if you aren't testing as root
+  if (getuid() == 0) {
+    // these tests do internal forks so that the change_owner and execs
+    // don't mess up our process.
+    test_init_job();
+    test_run_task();
+  }
+
+  seteuid(0);
+  run("rm -fr " TEST_ROOT);
+  printf("\nFinished tests\n");
+
+  if (my_username) {
+    free(username);
+  }
+  free_configurations();
+  return 0;
+}

+ 0 - 243
mapreduce/src/c++/task-controller/tests/test-task-controller.c

@@ -1,243 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#include "../task-controller.h"
-
-#define HADOOP_CONF_DIR "/tmp"
-
-int write_config_file(char *file_name) {
-  FILE *file;
-  char const *str =
-      "mapreduce.cluster.local.dir=/tmp/testing1,/tmp/testing2,/tmp/testing3,/tmp/testing4\n";
-
-  file = fopen(file_name, "w");
-  if (file == NULL) {
-    printf("Failed to open %s.\n", file_name);
-    return EXIT_FAILURE;
-  }
-  fwrite(str, 1, strlen(str), file);
-  fclose(file);
-  return 0;
-}
-
-void test_check_variable_against_config() {
-
-  // A temporary configuration directory
-  char *conf_dir_templ = "/tmp/test-task-controller-conf-dir-XXXXXX";
-
-  // To accomodate "/conf/taskcontroller.cfg"
-  char template[strlen(conf_dir_templ) + strlen("/conf/taskcontroller.cfg")];
-
-  strcpy(template, conf_dir_templ);
-  char *temp_dir = mkdtemp(template);
-  if (temp_dir == NULL) {
-    printf("Couldn't create a temporary dir for conf.\n");
-    goto cleanup;
-  }
-
-  // Set the configuration directory
-  hadoop_conf_dir = strdup(temp_dir);
-
-  // create the configuration directory
-  strcat(template, "/conf");
-  char *conf_dir = strdup(template);
-  mkdir(conf_dir, S_IRWXU);
-
-  // create the configuration file
-  strcat(template, "/taskcontroller.cfg");
-  if (write_config_file(template) != 0) {
-    printf("Couldn't write the configuration file.\n");
-    goto cleanup;
-  }
-
-  // Test obtaining a value for a key from the config
-  char *config_values[4] = { "/tmp/testing1", "/tmp/testing2",
-      "/tmp/testing3", "/tmp/testing4" };
-  char *value = (char *) get_value("mapreduce.cluster.local.dir");
-  if (strcmp(value, "/tmp/testing1,/tmp/testing2,/tmp/testing3,/tmp/testing4")
-      != 0) {
-    printf("Obtaining a value for a key from the config failed.\n");
-    goto cleanup;
-  }
-
-  // Test the parsing of a multiple valued key from the config
-  char **values = (char **)get_values("mapreduce.cluster.local.dir");
-  char **values_ptr = values;
-  int i = 0;
-  while (*values_ptr != NULL) {
-    printf(" value : %s\n", *values_ptr);
-    if (strcmp(*values_ptr, config_values[i++]) != 0) {
-      printf("Configured values are not read out properly. Test failed!");
-      goto cleanup;;
-    }
-    values_ptr++;
-  }
-
-  if (check_variable_against_config("mapreduce.cluster.local.dir", "/tmp/testing5") == 0) {
-    printf("Configuration should not contain /tmp/testing5! \n");
-    goto cleanup;
-  }
-
-  if (check_variable_against_config("mapreduce.cluster.local.dir", "/tmp/testing4") != 0) {
-    printf("Configuration should contain /tmp/testing4! \n");
-    goto cleanup;
-  }
-
-  cleanup: if (value != NULL) {
-    free(value);
-  }
-  if (values != NULL) {
-    free(values);
-  }
-  if (hadoop_conf_dir != NULL) {
-    free(hadoop_conf_dir);
-  }
-  unlink(template);
-  rmdir(conf_dir);
-  rmdir(hadoop_conf_dir);
-}
-
-void test_get_user_directory() {
-  char *user_dir = (char *) get_user_directory("/tmp", "user");
-  printf("user_dir obtained is %s\n", user_dir);
-  int ret = 0;
-  if (strcmp(user_dir, "/tmp/taskTracker/user") != 0) {
-    ret = -1;
-  }
-  free(user_dir);
-  assert(ret == 0);
-}
-
-void test_get_job_directory() {
-  char *job_dir = (char *) get_job_directory("/tmp", "user",
-      "job_200906101234_0001");
-  printf("job_dir obtained is %s\n", job_dir);
-  int ret = 0;
-  if (strcmp(job_dir, "/tmp/taskTracker/user/jobcache/job_200906101234_0001")
-      != 0) {
-    ret = -1;
-  }
-  free(job_dir);
-  assert(ret == 0);
-}
-
-void test_get_attempt_directory() {
-  char *job_dir = (char *) get_job_directory("/tmp", "user",
-      "job_200906101234_0001");
-  printf("job_dir obtained is %s\n", job_dir);
-  char *attempt_dir = (char *) get_attempt_directory(job_dir,
-      "attempt_200906101234_0001_m_000000_0");
-  printf("attempt_dir obtained is %s\n", attempt_dir);
-  int ret = 0;
-  if (strcmp(
-      attempt_dir,
-      "/tmp/taskTracker/user/jobcache/job_200906101234_0001/attempt_200906101234_0001_m_000000_0")
-      != 0) {
-    ret = -1;
-  }
-  free(job_dir);
-  free(attempt_dir);
-  assert(ret == 0);
-}
-
-void test_get_task_launcher_file() {
-  char *job_dir = (char *) get_job_directory("/tmp", "user",
-      "job_200906101234_0001");
-  char *task_file = (char *) get_task_launcher_file(job_dir,
-      "attempt_200906112028_0001_m_000000_0");
-  printf("task_file obtained is %s\n", task_file);
-  int ret = 0;
-  if (strcmp(
-      task_file,
-      "/tmp/taskTracker/user/jobcache/job_200906101234_0001/attempt_200906112028_0001_m_000000_0/taskjvm.sh")
-      != 0) {
-    ret = -1;
-  }
-  free(task_file);
-  assert(ret == 0);
-}
-
-void test_get_job_log_dir() {
-  char *logdir = (char *) get_job_log_dir("/tmp/testing",
-    "job_200906101234_0001");
-  printf("logdir obtained is %s\n", logdir);
-  int ret = 0;
-  if (strcmp(logdir, "/tmp/testing/userlogs/job_200906101234_0001") != 0) {
-    ret = -1;
-  }
-  free(logdir);
-  assert(ret == 0);
-}
-
-void test_get_job_acls_file() {
-  char *job_acls_file = (char *) get_job_acls_file(
-    "/tmp/testing/userlogs/job_200906101234_0001");
-  printf("job acls file obtained is %s\n", job_acls_file);
-  int ret = 0;
-  if (strcmp(job_acls_file,
-    "/tmp/testing/userlogs/job_200906101234_0001/job-acls.xml") != 0) {
-    ret = -1;
-  }
-  free(job_acls_file);
-  assert(ret == 0);
-}
-
-void test_get_task_log_dir() {
-  char *logdir = (char *) get_task_log_dir("/tmp/testing",
-    "job_200906101234_0001", "attempt_200906112028_0001_m_000000_0");
-  printf("logdir obtained is %s\n", logdir);
-  int ret = 0;
-  if (strcmp(logdir,
-      "/tmp/testing/userlogs/job_200906101234_0001/attempt_200906112028_0001_m_000000_0")
-      != 0) {
-    ret = -1;
-  }
-  free(logdir);
-  assert(ret == 0);
-}
-
-int main(int argc, char **argv) {
-  printf("\nStarting tests\n");
-  LOGFILE = stdout;
-
-  printf("\nTesting check_variable_against_config()\n");
-  test_check_variable_against_config();
-
-  printf("\nTesting get_user_directory()\n");
-  test_get_user_directory();
-
-  printf("\nTesting get_job_directory()\n");
-  test_get_job_directory();
-
-  printf("\nTesting get_attempt_directory()\n");
-  test_get_attempt_directory();
-
-  printf("\nTesting get_task_launcher_file()\n");
-  test_get_task_launcher_file();
-
-  printf("\nTesting get_job_log_dir()\n");
-  test_get_job_log_dir();
-
-  printf("\nTesting get_job_acls_file()\n");
-  test_get_job_acls_file();
-
-  printf("\nTesting get_task_log_dir()\n");
-  test_get_task_log_dir();
-
-  printf("\nFinished tests\n");
-  return 0;
-}

+ 1 - 1
mapreduce/src/contrib/streaming/src/java/org/apache/hadoop/streaming/PipeMapRed.java

@@ -220,7 +220,7 @@ public abstract class PipeMapRed {
     } catch (IOException e) {
       LOG.error("configuration exception", e);
       throw new RuntimeException("configuration exception", e);
-    } catch (InterruptedException e)  {
+    } catch (InterruptedException e) {
       LOG.error("configuration exception", e);
       throw new RuntimeException("configuration exception", e);
     }

+ 35 - 7
mapreduce/src/java/org/apache/hadoop/mapred/Child.java

@@ -24,11 +24,14 @@ import java.io.IOException;
 import java.io.PrintStream;
 import java.net.InetSocketAddress;
 import java.security.PrivilegedExceptionAction;
+import java.util.Arrays;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.hadoop.fs.FSError;
 import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.LocalDirAllocator;
+import org.apache.hadoop.fs.Path;
 import org.apache.hadoop.io.Text;
 import org.apache.hadoop.ipc.RPC;
 import org.apache.hadoop.mapreduce.JobContext;
@@ -57,6 +60,7 @@ class Child {
 
   static volatile TaskAttemptID taskid = null;
   static volatile boolean isCleanup;
+  static String cwd;
 
   public static void main(String[] args) throws Throwable {
     LOG.debug("Child starting");
@@ -74,7 +78,13 @@ class Child {
     int jvmIdInt = Integer.parseInt(args[4]);
     JVMId jvmId = new JVMId(firstTaskid.getJobID(),
         firstTaskid.getTaskType() == TaskType.MAP,jvmIdInt);
-    
+
+    cwd = System.getenv().get(TaskRunner.HADOOP_WORK_DIR);
+    if (cwd == null) {
+      throw new IOException("Environment variable " +
+                             TaskRunner.HADOOP_WORK_DIR + " is not set");
+    }
+
     //load token cache storage
     String jobTokenFile = 
       System.getenv().get(UserGroupInformation.HADOOP_TOKEN_FILE_LOCATION);
@@ -176,10 +186,6 @@ class Child {
         isCleanup = task.isTaskCleanupTask();
         // reset the statistics for the task
         FileSystem.clearStatistics();
-
-        //create the index file so that the log files 
-        //are viewable immediately
-        TaskLog.syncLogs(logLocation, taskid, isCleanup);
         
         // Create the job-conf and set credentials
         final JobConf job = new JobConf(task.getJobFile());
@@ -192,12 +198,19 @@ class Child {
         // setup the child's Configs.LOCAL_DIR. The child is now sandboxed and
         // can only see files down and under attemtdir only.
         TaskRunner.setupChildMapredLocalDirs(task, job);
+        
+        // setup the child's attempt directories
+        localizeTask(task, job, logLocation);
 
         //setupWorkDir actually sets up the symlinks for the distributed
         //cache. After a task exits we wipe the workdir clean, and hence
         //the symlinks have to be rebuilt.
-        TaskRunner.setupWorkDir(job, new File(".").getAbsoluteFile());
-
+        TaskRunner.setupWorkDir(job, new File(cwd));
+        
+        //create the index file so that the log files 
+        //are viewable immediately
+        TaskLog.syncLogs(logLocation, taskid, isCleanup);
+        
         numTasksToExecute = job.getNumTasksToExecutePerJvm();
         assert(numTasksToExecute != 0);
 
@@ -284,4 +297,19 @@ class Child {
       LogManager.shutdown();
     }
   }
+  static void localizeTask(Task task, JobConf jobConf, String logLocation)
+  throws IOException{
+
+     // Do the task-type specific localization
+     task.localizeConfiguration(jobConf);
+
+     //write the localized task jobconf
+     LocalDirAllocator lDirAlloc =
+       new LocalDirAllocator(JobConf.MAPRED_LOCAL_DIR_PROPERTY);
+     Path localTaskFile =
+       lDirAlloc.getLocalPathForWrite(TaskTracker.JOBFILE, jobConf);
+     JobLocalizer.writeLocalJobFile(localTaskFile, jobConf);
+     task.setJobFile(localTaskFile.toString());
+     task.setConf(jobConf);
+   }
 }

+ 38 - 40
mapreduce/src/java/org/apache/hadoop/mapred/CleanupQueue.java

@@ -19,20 +19,26 @@
 package org.apache.hadoop.mapred;
 
 import java.io.IOException;
+import java.security.PrivilegedExceptionAction;
 import java.util.concurrent.LinkedBlockingQueue;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
-import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.security.UserGroupInformation;
 
 class CleanupQueue {
 
   public static final Log LOG =
     LogFactory.getLog(CleanupQueue.class);
 
-  private static PathCleanupThread cleanupThread;
+  private static final PathCleanupThread cleanupThread =
+    new PathCleanupThread();
+  private static final CleanupQueue inst = new CleanupQueue();
+
+  public static CleanupQueue getInstance() { return inst; }
 
   /**
    * Create a singleton path-clean-up queue. It can be used to delete
@@ -42,59 +48,53 @@ class CleanupQueue {
    * {@link CleanupQueue#addToQueue(PathDeletionContext...)} to add paths for
    * deletion.
    */
-  public CleanupQueue() {
-    synchronized (PathCleanupThread.class) {
-      if (cleanupThread == null) {
-        cleanupThread = new PathCleanupThread();
-      }
-    }
-  }
+  protected CleanupQueue() { }
   
   /**
    * Contains info related to the path of the file/dir to be deleted
    */
   static class PathDeletionContext {
-    String fullPath;// full path of file or dir
-    FileSystem fs;
+    final Path fullPath;// full path of file or dir
+    final Configuration conf;
 
-    public PathDeletionContext(FileSystem fs, String fullPath) {
-      this.fs = fs;
+    public PathDeletionContext(Path fullPath, Configuration conf) {
       this.fullPath = fullPath;
+      this.conf = conf;
     }
     
-    protected String getPathForCleanup() {
+    protected Path getPathForCleanup() {
       return fullPath;
     }
 
     /**
-     * Makes the path(and its subdirectories recursively) fully deletable
+     * Deletes the path (and its subdirectories recursively)
+     * @throws IOException, InterruptedException 
      */
-    protected void enablePathForCleanup() throws IOException {
-      // Do nothing by default.
-      // Subclasses can override to provide enabling for deletion.
+    protected void deletePath() throws IOException, InterruptedException {
+      final Path p = getPathForCleanup();
+      UserGroupInformation.getLoginUser().doAs(
+          new PrivilegedExceptionAction<Object>() {
+            public Object run() throws IOException {
+             p.getFileSystem(conf).delete(p, true);
+             return null;
+            }
+          });
+    }
+
+    @Override
+    public String toString() {
+      final Path p = getPathForCleanup();
+      return (null == p) ? "undefined" : p.toString();
     }
   }
 
   /**
    * Adds the paths to the queue of paths to be deleted by cleanupThread.
    */
-  void addToQueue(PathDeletionContext... contexts) {
+  public void addToQueue(PathDeletionContext... contexts) {
     cleanupThread.addToQueue(contexts);
   }
 
-  protected static boolean deletePath(PathDeletionContext context)
-            throws IOException {
-    context.enablePathForCleanup();
-
-    if (LOG.isDebugEnabled()) {
-      LOG.debug("Trying to delete " + context.fullPath);
-    }
-    if (context.fs.exists(new Path(context.fullPath))) {
-      return context.fs.delete(new Path(context.fullPath), true);
-    }
-    return true;
-  }
-
   // currently used by tests only
   protected boolean isQueueEmpty() {
     return (cleanupThread.queue.size() == 0);
@@ -128,18 +128,16 @@ class CleanupQueue {
       while (true) {
         try {
           context = queue.take();
+          context.deletePath();
           // delete the path.
-          if (!deletePath(context)) {
-            LOG.warn("CleanupThread:Unable to delete path " + context.fullPath);
-          }
-          else if (LOG.isDebugEnabled()) {
-            LOG.debug("DELETED " + context.fullPath);
+          if (LOG.isDebugEnabled()) {
+            LOG.debug("DELETED " + context);
           }
         } catch (InterruptedException t) {
-          LOG.warn("Interrupted deletion of " + context.fullPath);
+          LOG.warn("Interrupted deletion of " + context);
           return;
-        } catch (Exception e) {
-          LOG.warn("Error deleting path " + context.fullPath + ": " + e);
+        } catch (Throwable e) {
+          LOG.warn("Error deleting path " + context, e);
         } 
       }
     }

+ 205 - 166
mapreduce/src/java/org/apache/hadoop/mapred/DefaultTaskController.java

@@ -18,21 +18,22 @@
 
 package org.apache.hadoop.mapred;
 
+import java.io.File;
 import java.io.IOException;
+import java.net.InetSocketAddress;
 import java.util.List;
 
-import org.apache.hadoop.fs.FileUtil;
-import org.apache.hadoop.mapred.CleanupQueue.PathDeletionContext;
-import org.apache.hadoop.mapred.JvmManager.JvmEnv;
-import org.apache.hadoop.mapreduce.util.ProcessTree;
-import org.apache.hadoop.util.Shell;
-import org.apache.hadoop.util.Shell.ShellCommandExecutor;
-
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
-import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileSystem;
 import org.apache.hadoop.fs.FileUtil;
+import org.apache.hadoop.fs.LocalDirAllocator;
 import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.mapreduce.JobID;
+import org.apache.hadoop.mapreduce.server.tasktracker.Localizer;
+import org.apache.hadoop.util.Shell.ExitCodeException;
+import org.apache.hadoop.util.Shell.ShellCommandExecutor;
 
 /**
  * The default implementation for controlling tasks.
@@ -42,188 +43,226 @@ import org.apache.hadoop.fs.Path;
  * many of the initializing or cleanup methods are not required here.
  * 
  * <br/>
- * 
+ *  NOTE: This class is internal only class and not intended for users!!
  */
-@InterfaceAudience.Private
 public class DefaultTaskController extends TaskController {
 
   private static final Log LOG = 
       LogFactory.getLog(DefaultTaskController.class);
-  /**
-   * Launch a new JVM for the task.
-   * 
-   * This method launches the new JVM for the task by executing the
-   * the JVM command using the {@link Shell.ShellCommandExecutor}
-   */
-  void launchTaskJVM(TaskController.TaskControllerContext context) 
-                                      throws IOException {
-    initializeTask(context);
-
-    JvmEnv env = context.env;
-    List<String> wrappedCommand = 
-      TaskLog.captureOutAndError(env.setup, env.vargs, env.stdout, env.stderr,
-          env.logSize, true);
-    ShellCommandExecutor shexec = 
-        new ShellCommandExecutor(wrappedCommand.toArray(new String[0]), 
-                                  env.workDir, env.env);
-    // set the ShellCommandExecutor for later use.
-    context.shExec = shexec;
-    shexec.execute();
-  }
-    
-  /**
-   * Initialize the task environment.
-   * 
-   * Since tasks are launched as the tasktracker user itself, this
-   * method has no action to perform.
-   */
-  void initializeTask(TaskController.TaskControllerContext context) {
-    // The default task controller does not need to set up
-    // any permissions for proper execution.
-    // So this is a dummy method.
-    return;
+  private FileSystem fs;
+  public void setConf(Configuration conf) {
+    super.setConf(conf);
+    try {
+      fs = FileSystem.getLocal(conf).getRaw();
+    } catch (IOException ie) {
+      throw new RuntimeException("Failed getting LocalFileSystem", ie);
+    }
   }
 
-  /*
-   * No need to do anything as we don't need to do as we dont need anything
-   * extra from what TaskTracker has done.
+  /**
+   * Create all of the directories for the task and launches the child jvm.
+   * @param user the user name
+   * @param attemptId the attempt id
+   * @throws IOException
    */
   @Override
-  void initializeJob(JobInitializationContext context) {
-  }
+  public int launchTask(String user, 
+      String jobId,
+      String attemptId,
+      List<String> setup,
+      List<String> jvmArguments,
+      File currentWorkDirectory,
+      String stdout,
+      String stderr) throws IOException {
 
-  @Override
-  void terminateTask(TaskControllerContext context) {
-    ShellCommandExecutor shexec = context.shExec;
-    if (shexec != null) {
-      Process process = shexec.getProcess();
-      if (Shell.WINDOWS) {
-        // Currently we don't use setsid on WINDOWS. 
-        //So kill the process alone.
-        if (process != null) {
-          process.destroy();
-        }
+    ShellCommandExecutor shExec = null;
+    try {
+      FileSystem localFs = FileSystem.getLocal(getConf());
+
+      //create the attempt dirs
+      new Localizer(localFs, 
+          getConf().getStrings(JobConf.MAPRED_LOCAL_DIR_PROPERTY)).
+          initializeAttemptDirs(user, jobId, attemptId);
+
+      // create the working-directory of the task 
+      if (!currentWorkDirectory.mkdir()) {
+        throw new IOException("Mkdirs failed to create " 
+            + currentWorkDirectory.toString());
+      }
+
+      //mkdir the loglocation
+      String logLocation = TaskLog.getAttemptDir(jobId, attemptId).toString();
+      if (!localFs.mkdirs(new Path(logLocation))) {
+        throw new IOException("Mkdirs failed to create " 
+            + logLocation);
       }
-      else { // In addition to the task JVM, kill its subprocesses also.
-        String pid = context.pid;
-        if (pid != null) {
-          if(ProcessTree.isSetsidAvailable) {
-            ProcessTree.terminateProcessGroup(pid);
-          }else {
-            ProcessTree.terminateProcess(pid);
-          }
-        }
+      //read the configuration for the job
+      FileSystem rawFs = FileSystem.getLocal(getConf()).getRaw();
+      long logSize = 0; //TODO: Ref BUG:2854624
+      // get the JVM command line.
+      String cmdLine = 
+        TaskLog.buildCommandLine(setup, jvmArguments,
+            new File(stdout), new File(stderr), logSize, true);
+
+      // write the command to a file in the
+      // task specific cache directory
+      // TODO copy to user dir
+      Path p = new Path(allocator.getLocalPathForWrite(
+          TaskTracker.getPrivateDirTaskScriptLocation(user, jobId, attemptId),
+          getConf()), COMMAND_FILE);
+
+      String commandFile = writeCommand(cmdLine, rawFs, p);
+      rawFs.setPermission(p, TaskController.TASK_LAUNCH_SCRIPT_PERMISSION);
+      shExec = new ShellCommandExecutor(new String[]{
+          "bash", "-c", commandFile},
+          currentWorkDirectory);
+      shExec.execute();
+    } catch (Exception e) {
+      if (shExec == null) {
+        return -1;
       }
+      int exitCode = shExec.getExitCode();
+      LOG.warn("Exit code from task is : " + exitCode);
+      LOG.info("Output from DefaultTaskController's launchTask follows:");
+      logOutput(shExec.getOutput());
+      return exitCode;
     }
+    return 0;
   }
   
+  /**
+   * This routine initializes the local file system for running a job.
+   * Details:
+   * <ul>
+   * <li>Copies the credentials file from the TaskTracker's private space to
+   * the job's private space </li>
+   * <li>Creates the job work directory and set 
+   * {@link TaskTracker#JOB_LOCAL_DIR} in the configuration</li>
+   * <li>Downloads the job.jar, unjars it, and updates the configuration to 
+   * reflect the localized path of the job.jar</li>
+   * <li>Creates a base JobConf in the job's private space</li>
+   * <li>Sets up the distributed cache</li>
+   * <li>Sets up the user logs directory for the job</li>
+   * </ul>
+   * This method must be invoked in the access control context of the job owner 
+   * user. This is because the distributed cache is also setup here and the 
+   * access to the hdfs files requires authentication tokens in case where 
+   * security is enabled.
+   * @param user the user in question (the job owner)
+   * @param jobid the ID of the job in question
+   * @param credentials the path to the credentials file that the TaskTracker
+   * downloaded
+   * @param jobConf the path to the job configuration file that the TaskTracker
+   * downloaded
+   * @param taskTracker the connection to the task tracker
+   * @throws IOException
+   * @throws InterruptedException
+   */
   @Override
-  void killTask(TaskControllerContext context) {
-    ShellCommandExecutor shexec = context.shExec;
-    if (shexec != null) {
-      if (Shell.WINDOWS) {
-        //We don't do send kill process signal in case of windows as 
-        //already we have done a process.destroy() in terminateTaskJVM()
-        return;
-      }
-      String pid = context.pid;
-      if (pid != null) {
-        if(ProcessTree.isSetsidAvailable) {
-          ProcessTree.killProcessGroup(pid);
-        } else {
-          ProcessTree.killProcess(pid);
-        }
-      }
-    }
-  }
+  public void initializeJob(String user, String jobid, 
+      Path credentials, Path jobConf, 
+      TaskUmbilicalProtocol taskTracker,
+      InetSocketAddress ttAddr
+  ) throws IOException, InterruptedException {
+    final LocalDirAllocator lDirAlloc = allocator;
+    FileSystem localFs = FileSystem.getLocal(getConf());
+    JobLocalizer localizer = new JobLocalizer((JobConf)getConf(), user, jobid);
+    localizer.createLocalDirs();
+    localizer.createUserDirs();
+    localizer.createJobDirs();
+
+    JobConf jConf = new JobConf(jobConf);
+    localizer.createWorkDir(jConf);
+    //copy the credential file
+    Path localJobTokenFile = lDirAlloc.getLocalPathForWrite(
+        TaskTracker.getLocalJobTokenFile(user, jobid), getConf());
+    FileUtil.copy(
+        localFs, credentials, localFs, localJobTokenFile, false, getConf());
 
-  @Override
-  void dumpTaskStack(TaskControllerContext context) {
-    ShellCommandExecutor shexec = context.shExec;
-    if (shexec != null) {
-      if (Shell.WINDOWS) {
-        // We don't use signals in Windows.
-        return;
-      }
-      String pid = context.pid;
-      if (pid != null) {
-        // Send SIGQUIT to get a stack dump
-        if (ProcessTree.isSetsidAvailable) {
-          ProcessTree.sigQuitProcessGroup(pid);
-        } else {
-          ProcessTree.sigQuitProcess(pid);
-        }
-      }
-    }
-  }
 
+    //setup the user logs dir
+    localizer.initializeJobLogDir();
+
+    // Download the job.jar for this job from the system FS
+    // setup the distributed cache
+    // write job acls
+    // write localized config
+    localizer.localizeJobFiles(JobID.forName(jobid), jConf, localJobTokenFile, 
+        taskTracker);
+  }
   @Override
-  public void initializeDistributedCacheFile(DistributedCacheFileContext context)
-      throws IOException {
-    Path localizedUniqueDir = context.getLocalizedUniqueDir();
+  public boolean signalTask(String user, int taskPid, Signal signal)
+  throws IOException {
+    final int sigpid = TaskController.isSetsidAvailable
+    ? -1 * taskPid
+        : taskPid;
+    try {
+      sendSignal(sigpid, Signal.NULL);
+    } catch (ExitCodeException e) {
+      return false;
+    }
     try {
-      // Setting recursive execute permission on localized dir
-      LOG.info("Doing chmod on localdir :" + localizedUniqueDir);
-      FileUtil.chmod(localizedUniqueDir.toString(), "+x", true);
-    } catch (InterruptedException ie) {
-      LOG.warn("Exception in doing chmod on" + localizedUniqueDir, ie);
-      throw new IOException(ie);
+      sendSignal(sigpid, signal);
+    } catch (IOException e) {
+      try {
+        sendSignal(sigpid, Signal.NULL);
+      } catch (IOException ignore) {
+        return false;
+      }
+      throw e;
     }
+    return true;
   }
+    /**
+     * Send a specified signal to the specified pid
+     *
+     * @param pid the pid of the process [group] to signal.
+     * @param signal signal to send
+     * (for logging).
+     */
+    protected void sendSignal(int pid, Signal signal) throws IOException {
+      ShellCommandExecutor shexec = null;
+      String[] arg = { "kill", "-" + signal.getValue(), Integer.toString(pid) };
+      shexec = new ShellCommandExecutor(arg);
+      shexec.execute();
+    }
 
-  @Override
-  public void initializeUser(InitializationContext context) {
-    // Do nothing.
-  }
-  
-  @Override
-  void runDebugScript(DebugScriptContext context) throws IOException {
-    List<String>  wrappedCommand = TaskLog.captureDebugOut(context.args, 
-        context.stdout);
-    // run the script.
-    ShellCommandExecutor shexec = 
-      new ShellCommandExecutor(wrappedCommand.toArray(new String[0]), context.workDir);
-    shexec.execute();
-    int exitCode = shexec.getExitCode();
-    if (exitCode != 0) {
-      throw new IOException("Task debug script exit with nonzero status of " 
-          + exitCode + ".");
+    /**
+     * Delete the user's files under all of the task tracker root directories.
+     * @param user the user name
+     * @param subDir the path relative to base directories
+     * @param baseDirs the base directories (absolute paths)
+     * @throws IOException
+     */
+    @Override
+    public void deleteAsUser(String user, 
+        String subDir, 
+        String... baseDirs) throws IOException {
+      if (baseDirs == null || baseDirs.length == 0) {
+        LOG.info("Deleting absolute path : " + subDir);
+        fs.delete(new Path(subDir), true);
+        return;
+      }
+      for (String baseDir : baseDirs) {
+        LOG.info("Deleting path : "+  baseDir + Path.SEPARATOR  +subDir);
+        fs.delete(new Path(baseDir + Path.SEPARATOR + subDir), true);
+      }
     }
-  }
 
-  /**
-   * Enables the task for cleanup by changing permissions of the specified path
-   * in the local filesystem
-   */
-  @Override
-  void enableTaskForCleanup(PathDeletionContext context)
-         throws IOException {
-    enablePathForCleanup(context);
-  }
-  
-  /**
-   * Enables the job for cleanup by changing permissions of the specified path
-   * in the local filesystem
-   */
-  @Override
-  void enableJobForCleanup(PathDeletionContext context)
-         throws IOException {
-    enablePathForCleanup(context);
-  }
-  
-  /**
-   * Enables the path for cleanup by changing permissions of the specified path
-   * in the local filesystem
-   */
-  private void enablePathForCleanup(PathDeletionContext context)
-         throws IOException {
-    try {
-      FileUtil.chmod(context.fullPath, "u+rwx", true);
-    } catch(InterruptedException e) {
-      LOG.warn("Interrupted while setting permissions for " + context.fullPath +
-          " for deletion.");
-    } catch(IOException ioe) {
-      LOG.warn("Unable to change permissions of " + context.fullPath);
+    /**
+    * Delete the user's files under the userlogs directory.
+    * @param user the user to work as
+    * @param subDir the path under the userlogs directory.
+    * @throws IOException
+     */
+    @Override
+   public void deleteLogAsUser(String user, 
+                               String subDir) throws IOException {
+     Path dir = new Path(TaskLog.getUserLogDir().getAbsolutePath(), subDir);
+     fs.delete(dir, true);
     }
-  }
+    @Override
+    public void setup(LocalDirAllocator allocator) {
+      this.allocator = allocator;
+     }
 }

+ 15 - 2
mapreduce/src/java/org/apache/hadoop/mapred/IsolationRunner.java

@@ -121,6 +121,13 @@ public class IsolationRunner {
         SortedRanges.Range range) throws IOException {
       LOG.info("Task " + taskid + " reportedNextRecordRange " + range);
     }
+
+    @Override
+    public void 
+    updatePrivateDistributedCacheSizes(org.apache.hadoop.mapreduce.JobID jobId,
+                                       long[] sizes){
+      // NOTHING
+    }
   }
   
   private ClassLoader makeClassLoader(JobConf conf, 
@@ -181,9 +188,15 @@ public class IsolationRunner {
     // setup the local and user working directories
     FileSystem local = FileSystem.getLocal(conf);
     LocalDirAllocator lDirAlloc = new LocalDirAllocator(MRConfig.LOCAL_DIR);
+    Path workDirName;
+    boolean workDirExists = lDirAlloc.ifExists(MRConstants.WORKDIR, conf);
+    if (workDirExists) {
+      workDirName = TaskRunner.formWorkDir(lDirAlloc, conf);
+    } else {
+      workDirName = lDirAlloc.getLocalPathForWrite(MRConstants.WORKDIR, conf);
+    }
 
-    File workDirName = TaskRunner.formWorkDir(lDirAlloc, taskId, false, conf);
-    local.setWorkingDirectory(new Path(workDirName.toString()));
+    local.setWorkingDirectory(workDirName);
     FileSystem.get(conf).setWorkingDirectory(conf.getWorkingDirectory());
     
     // set up a classloader with the right classpath

+ 7 - 2
mapreduce/src/java/org/apache/hadoop/mapred/JobInProgress.java

@@ -478,6 +478,11 @@ public class JobInProgress {
       this.submitHostName = conf.getJobSubmitHostName();
       this.submitHostAddress = conf.getJobSubmitHostAddress();
 
+      this.nonLocalMaps = new LinkedList<TaskInProgress>();
+      this.nonLocalRunningMaps = new LinkedHashSet<TaskInProgress>();
+      this.runningMapCache = new IdentityHashMap<Node, Set<TaskInProgress>>();
+      this.nonRunningReduces = new LinkedList<TaskInProgress>();
+      this.runningReduces = new LinkedHashSet<TaskInProgress>();
       this.slowTaskThreshold = Math.max(0.0f, conf.getFloat(
           MRJobConfig.SPECULATIVE_SLOWTASK_THRESHOLD, 1.0f));
       this.speculativeCap = conf.getFloat(MRJobConfig.SPECULATIVECAP, 0.1f);
@@ -3334,8 +3339,8 @@ public class JobInProgress {
          }
 
          Path tempDir = jobtracker.getSystemDirectoryForJob(getJobID());
-         new CleanupQueue().addToQueue(new PathDeletionContext(
-             jobtracker.getFileSystem(), tempDir.toUri().getPath())); 
+         CleanupQueue.getInstance().addToQueue(
+             new PathDeletionContext(tempDir, conf)); 
        } catch (IOException e) {
          LOG.warn("Error cleaning up "+profile.getJobID()+": "+e);
        }

+ 563 - 0
mapreduce/src/java/org/apache/hadoop/mapred/JobLocalizer.java

@@ -0,0 +1,563 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.mapred;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.security.PrivilegedExceptionAction;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileStatus;
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.FileUtil;
+import org.apache.hadoop.fs.LocalDirAllocator;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.fs.permission.FsPermission;
+import org.apache.hadoop.io.IOUtils;
+import org.apache.hadoop.io.Text;
+import org.apache.hadoop.ipc.RPC;
+import org.apache.hadoop.mapreduce.JobID;
+import org.apache.hadoop.mapreduce.filecache.DistributedCache;
+import org.apache.hadoop.mapreduce.filecache.TaskDistributedCacheManager;
+import org.apache.hadoop.mapreduce.filecache.TrackerDistributedCacheManager;
+import org.apache.hadoop.mapreduce.security.TokenCache;
+import org.apache.hadoop.mapreduce.security.token.JobTokenIdentifier;
+import org.apache.hadoop.security.Credentials;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hadoop.security.token.Token;
+import org.apache.hadoop.security.token.TokenIdentifier;
+import org.apache.hadoop.util.RunJar;
+
+/**
+ * Internal class responsible for initializing the job, not intended for users.
+ * Creates the following hierarchy:
+ *   <li>$mapred.local.dir/taskTracker/$user</li>
+ *   <li>$mapred.local.dir/taskTracker/$user/jobcache</li>
+ *   <li>$mapred.local.dir/taskTracker/$user/jobcache/$jobid/work</li>
+ *   <li>$mapred.local.dir/taskTracker/$user/jobcache/$jobid/jars</li>
+ *   <li>$mapred.local.dir/taskTracker/$user/jobcache/$jobid/jars/job.jar</li>
+ *   <li>$mapred.local.dir/taskTracker/$user/jobcache/$jobid/job.xml</li>
+ *   <li>$mapred.local.dir/taskTracker/$user/jobcache/$jobid/jobToken</li>
+ *   <li>$mapred.local.dir/taskTracker/$user/distcache</li>
+ */
+public class JobLocalizer {
+
+  static final Log LOG = LogFactory.getLog(JobLocalizer.class);
+
+  private static final FsPermission urwx =
+    FsPermission.createImmutable((short) 0700);
+  private static final FsPermission urwx_gx =
+    FsPermission.createImmutable((short) 0710);
+  private static final FsPermission urw_gr =
+    FsPermission.createImmutable((short) 0640);
+
+  private final String user;
+  private final String jobid;
+  private final FileSystem lfs;
+  private final List<Path> localDirs;
+  private final LocalDirAllocator lDirAlloc;
+  private final JobConf ttConf;
+
+  private final String JOBDIR;
+  private final String DISTDIR;
+  private final String WORKDIR;
+  private final String JARDST;
+  private final String JOBCONF;
+  private final String JOBTOKEN;
+  private static final String JOB_LOCAL_CTXT = "mapred.job.local.dir";
+
+  public JobLocalizer(JobConf ttConf, String user, String jobid)
+      throws IOException {
+    this(ttConf, user, jobid,
+        ttConf.getStrings(JobConf.MAPRED_LOCAL_DIR_PROPERTY));
+  }
+
+  public JobLocalizer(JobConf ttConf, String user, String jobid,
+      String... localDirs) throws IOException {
+    if (null == user) {
+      throw new IOException("Cannot initialize for null user");
+    }
+    this.user = user;
+    if (null == jobid) {
+      throw new IOException("Cannot initialize for null jobid");
+    }
+    this.jobid = jobid;
+    this.ttConf = ttConf;
+    lfs = FileSystem.getLocal(ttConf).getRaw();
+    this.localDirs = createPaths(user, localDirs);
+    ttConf.setStrings(JOB_LOCAL_CTXT, localDirs);
+    Collections.shuffle(this.localDirs);
+    lDirAlloc = new LocalDirAllocator(JOB_LOCAL_CTXT);
+    JOBDIR = TaskTracker.JOBCACHE + Path.SEPARATOR + jobid;
+    DISTDIR = JOBDIR + "/" + TaskTracker.DISTCACHEDIR;
+    WORKDIR = JOBDIR + "/work";
+    JARDST = JOBDIR + "/" + TaskTracker.JARSDIR + "/job.jar";
+    JOBCONF = JOBDIR + "/" + TaskTracker.JOBFILE;
+    JOBTOKEN = JOBDIR + "/" + TaskTracker.JOB_TOKEN_FILE;
+  }
+
+  private static List<Path> createPaths(String user, final String[] str)
+      throws IOException {
+    if (null == str || 0 == str.length) {
+      throw new IOException("mapred.local.dir contains no entries");
+    }
+    final List<Path> ret = new ArrayList<Path>(str.length);
+    for (int i = 0; i < str.length; ++i) {
+      final Path p = new Path(str[i], TaskTracker.getUserDir(user));
+      ret.add(p);
+      str[i] = p.toString();
+    }
+    return ret;
+  }
+
+  public void createLocalDirs() throws IOException {
+    boolean userDirStatus = false;
+    // create all directories as rwx------
+    for (Path localDir : localDirs) {
+      // create $mapred.local.dir/taskTracker/$user
+      if (!lfs.mkdirs(localDir, urwx)) {
+        LOG.warn("Unable to create the user directory : " + localDir);
+        continue;
+      }
+      userDirStatus = true;
+    }
+    if (!userDirStatus) {
+      throw new IOException("Not able to initialize user directories "
+          + "in any of the configured local directories for user " + user);
+    }
+  }
+
+  /**
+   * Initialize the local directories for a particular user on this TT. This
+   * involves creation and setting permissions of the following directories
+   * <ul>
+   * <li>$mapred.local.dir/taskTracker/$user</li>
+   * <li>$mapred.local.dir/taskTracker/$user/jobcache</li>
+   * <li>$mapred.local.dir/taskTracker/$user/distcache</li>
+   * </ul>
+   */
+  public void createUserDirs() throws IOException {
+    LOG.info("Initializing user " + user + " on this TT.");
+
+    boolean jobCacheDirStatus = false;
+    boolean distributedCacheDirStatus = false;
+
+    // create all directories as rwx------
+    for (Path localDir : localDirs) {
+      // create $mapred.local.dir/taskTracker/$user/jobcache
+      final Path jobDir =
+        new Path(localDir, TaskTracker.JOBCACHE);
+      if (!lfs.mkdirs(jobDir, urwx)) {
+        LOG.warn("Unable to create job cache directory : " + jobDir);
+      } else {
+        jobCacheDirStatus = true;
+      }
+      // create $mapred.local.dir/taskTracker/$user/distcache
+      final Path distDir =
+        new Path(localDir, TaskTracker.DISTCACHEDIR);
+      if (!lfs.mkdirs(distDir, urwx)) {
+        LOG.warn("Unable to create distributed-cache directory : " + distDir);
+      } else {
+        distributedCacheDirStatus = true;
+      }
+    }
+    if (!jobCacheDirStatus) {
+      throw new IOException("Not able to initialize job-cache directories "
+          + "in any of the configured local directories for user " + user);
+    }
+    if (!distributedCacheDirStatus) {
+      throw new IOException(
+          "Not able to initialize distributed-cache directories "
+              + "in any of the configured local directories for user "
+              + user);
+    }
+  }
+
+  /**
+   * Prepare the job directories for a given job. To be called by the job
+   * localization code, only if the job is not already localized.
+   * <br>
+   * Here, we set 700 permissions on the job directories created on all disks.
+   * This we do so as to avoid any misuse by other users till the time
+   * {@link TaskController#initializeJob} is run at a
+   * later time to set proper private permissions on the job directories. <br>
+   */
+  public void createJobDirs() throws IOException {
+    boolean initJobDirStatus = false;
+    for (Path localDir : localDirs) {
+      Path fullJobDir = new Path(localDir, JOBDIR);
+      if (lfs.exists(fullJobDir)) {
+        // this will happen on a partial execution of localizeJob. Sometimes
+        // copying job.xml to the local disk succeeds but copying job.jar might
+        // throw out an exception. We should clean up and then try again.
+        lfs.delete(fullJobDir, true);
+      }
+      // create $mapred.local.dir/taskTracker/$user/jobcache/$jobid
+      if (!lfs.mkdirs(fullJobDir, urwx)) {
+        LOG.warn("Not able to create job directory " + fullJobDir.toString());
+      } else {
+        initJobDirStatus = true;
+      }
+    }
+    if (!initJobDirStatus) {
+      throw new IOException("Not able to initialize job directories "
+          + "in any of the configured local directories for job "
+          + jobid.toString());
+    }
+  }
+
+  /**
+   * Create job log directory and set appropriate permissions for the directory.
+   */
+  public void initializeJobLogDir() throws IOException {
+    Path jobUserLogDir = new Path(TaskLog.getJobDir(jobid).toURI().toString());
+    if (!lfs.mkdirs(jobUserLogDir, urwx_gx)) {
+      throw new IOException(
+          "Could not create job user log directory: " + jobUserLogDir);
+    }
+  }
+
+  /**
+   * Download the job jar file from FS to the local file system and unjar it.
+   * Set the local jar file in the passed configuration.
+   *
+   * @param localJobConf
+   * @throws IOException
+   */
+  private void localizeJobJarFile(JobConf localJobConf)
+      throws IOException, InterruptedException {
+    // copy Jar file to the local FS and unjar it.
+    String jarFile = localJobConf.getJar();
+    FileStatus status = null;
+    long jarFileSize = -1;
+    if (jarFile != null) {
+      Path jarFilePath = new Path(jarFile);
+      FileSystem userFs = jarFilePath.getFileSystem(localJobConf);
+      try {
+        status = userFs.getFileStatus(jarFilePath);
+        jarFileSize = status.getLen();
+      } catch (FileNotFoundException fe) {
+        jarFileSize = -1;
+      }
+      // Here we check for five times the size of jarFileSize to accommodate for
+      // unjarring the jar file in the jars directory
+      Path localJarFile =
+        lDirAlloc.getLocalPathForWrite(JARDST, 5 * jarFileSize, ttConf);
+
+      //Download job.jar
+      userFs.copyToLocalFile(jarFilePath, localJarFile);
+      localJobConf.setJar(localJarFile.toString());
+      // Also un-jar the job.jar files. We un-jar it so that classes inside
+      // sub-directories, for e.g., lib/, classes/ are available on class-path
+      RunJar.unJar(new File(localJarFile.toString()),
+          new File(localJarFile.getParent().toString()));
+      FileUtil.chmod(localJarFile.getParent().toString(), "ugo+rx", true);
+    }
+  }
+
+  /**
+   * The permissions to use for the private distributed cache objects.
+   * It is already protected by the user directory, so keep the group and other
+   * the same so that LocalFileSystem will use the java File methods to
+   * set permission.
+   */
+  private static final FsPermission privateCachePerms =
+    FsPermission.createImmutable((short) 0755);
+  
+  /**
+   * Given a list of objects, download each one.
+   * @param conf the job's configuration
+   * @param sources the list of objects to download from
+   * @param dests the list of paths to download them to
+   * @param times the desired modification times
+   * @param isPublic are the objects in the public cache?
+   * @param isArchive are these archive files?
+   * @throws IOException
+   * @return for archives, return the list of each of the sizes.
+   */
+  private static long[] downloadPrivateCacheObjects(Configuration conf,
+                                             URI[] sources,
+                                             Path[] dests,
+                                             long[] times,
+                                             boolean[] isPublic,
+                                             boolean isArchive
+                                             ) throws IOException,
+                                                      InterruptedException {
+    if (null == sources || null == dests || null == times || null == isPublic) {
+      return null;
+    }
+    if (sources.length != dests.length ||
+        sources.length != times.length ||
+        sources.length != isPublic.length) {
+      throw new IOException("Distributed cache entry arrays have different " +
+                            "lengths: " + sources.length + ", " + dests.length +
+                            ", " + times.length + ", " + isPublic.length);
+    }
+    long[] result = new long[sources.length];
+    for(int i=0; i < sources.length; i++) {
+      // public objects are already downloaded by the Task Tracker, we
+      // only need to handle the private ones here
+      if (!isPublic[i]) {
+        result[i] = 
+          TrackerDistributedCacheManager.downloadCacheObject(conf, sources[i], 
+                                                             dests[i], 
+                                                             times[i], 
+                                                             isArchive, 
+                                                             privateCachePerms);
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Download the parts of the distributed cache that are private.
+   * @param conf the job's configuration
+   * @throws IOException
+   * @return the size of the archive objects
+   */
+  public static long[] downloadPrivateCache(Configuration conf)
+      throws InterruptedException, IOException {
+    downloadPrivateCacheObjects(conf,
+                                DistributedCache.getCacheFiles(conf),
+                                DistributedCache.getLocalCacheFiles(conf),
+                                DistributedCache.getFileTimestamps(conf),
+                                TrackerDistributedCacheManager.
+                                  getFileVisibilities(conf),
+                                false);
+    return 
+      downloadPrivateCacheObjects(conf,
+                                  DistributedCache.getCacheArchives(conf),
+                                  DistributedCache.getLocalCacheArchives(conf),
+                                  DistributedCache.getArchiveTimestamps(conf),
+                                  TrackerDistributedCacheManager.
+                                    getArchiveVisibilities(conf),
+                                  true);
+  }
+
+  public void localizeJobFiles(JobID jobid, JobConf jConf,
+      Path localJobTokenFile, TaskUmbilicalProtocol taskTracker)
+      throws IOException, InterruptedException {
+    localizeJobFiles(jobid, jConf,
+        lDirAlloc.getLocalPathForWrite(JOBCONF, ttConf), localJobTokenFile,
+        taskTracker);
+  }
+
+  public void localizeJobFiles(final JobID jobid, JobConf jConf,
+      Path localJobFile, Path localJobTokenFile,
+      final TaskUmbilicalProtocol taskTracker) 
+  throws IOException, InterruptedException {
+    // Download the job.jar for this job from the system FS
+    localizeJobJarFile(jConf);
+
+    jConf.set(JOB_LOCAL_CTXT, ttConf.get(JOB_LOCAL_CTXT));
+
+    //update the config some more
+    jConf.set(TokenCache.JOB_TOKENS_FILENAME, localJobTokenFile.toString());
+    jConf.set(JobConf.MAPRED_LOCAL_DIR_PROPERTY, 
+        ttConf.get(JobConf.MAPRED_LOCAL_DIR_PROPERTY));
+    TaskTracker.resetNumTasksPerJvm(jConf);
+
+    //setup the distributed cache
+    final long[] sizes = downloadPrivateCache(jConf);
+    if (sizes != null) {
+      //the following doAs is required because the DefaultTaskController
+      //calls the localizeJobFiles method in the context of the TaskTracker
+      //process. The JVM authorization check would fail without this
+      //doAs. In the LinuxTC case, this doesn't harm.
+      UserGroupInformation ugi = 
+        UserGroupInformation.createRemoteUser(jobid.toString());
+      ugi.doAs(new PrivilegedExceptionAction<Object>() { 
+        public Object run() throws IOException {
+          taskTracker.updatePrivateDistributedCacheSizes(jobid, sizes);
+          return null;
+        }
+      });
+    }
+
+    // Create job-acls.xml file in job userlog dir and write the needed
+    // info for authorization of users for viewing task logs of this job.
+    writeJobACLs(jConf, new Path(TaskLog.getJobDir(jobid).toURI().toString()));
+
+    //write the updated jobConf file in the job directory
+    JobLocalizer.writeLocalJobFile(localJobFile, jConf);
+  }
+
+  /**
+   *  Creates job-acls.xml under the given directory logDir and writes
+   *  job-view-acl, queue-admins-acl, jobOwner name and queue name into this
+   *  file.
+   *  queue name is the queue to which the job was submitted to.
+   *  queue-admins-acl is the queue admins ACL of the queue to which this
+   *  job was submitted to.
+   * @param conf   job configuration
+   * @param logDir job userlog dir
+   * @throws IOException
+   */
+  private void writeJobACLs(JobConf conf, Path logDir) throws IOException {
+    JobConf aclConf = new JobConf(false);
+
+    // set the job view acl in aclConf
+    String jobViewACL = conf.get(JobContext.JOB_ACL_VIEW_JOB, " ");
+    aclConf.set(JobContext.JOB_ACL_VIEW_JOB, jobViewACL);
+
+    // set the job queue name in aclConf
+    String queue = conf.getQueueName();
+    aclConf.setQueueName(queue);
+
+    // set the queue admins acl in aclConf
+    String qACLName = QueueManager.toFullPropertyName(queue,
+        QueueACL.ADMINISTER_JOBS.getAclName());
+    String queueAdminsACL = conf.get(qACLName, " ");
+    aclConf.set(qACLName, queueAdminsACL);
+
+    // set jobOwner as user.name in aclConf
+    aclConf.set("user.name", user);
+
+    OutputStream out = null;
+    Path aclFile = new Path(logDir, TaskTracker.jobACLsFile);
+    try {
+      out = lfs.create(aclFile);
+      aclConf.writeXml(out);
+    } finally {
+      IOUtils.cleanup(LOG, out);
+    }
+    lfs.setPermission(aclFile, urw_gr);
+  }
+
+  public void createWorkDir(JobConf jConf) throws IOException {
+    // create $mapred.local.dir/taskTracker/$user/jobcache/$jobid/work
+    final Path workDir = lDirAlloc.getLocalPathForWrite(WORKDIR, ttConf);
+    if (!lfs.mkdirs(workDir)) {
+      throw new IOException("Mkdirs failed to create "
+          + workDir.toString());
+    }
+    jConf.set(TaskTracker.JOB_LOCAL_DIR, workDir.toUri().getPath());
+  }
+
+  public Path findCredentials() throws IOException {
+    return lDirAlloc.getLocalPathToRead(JOBTOKEN, ttConf);
+  }
+
+  public int runSetup(String user, String jobid, Path localJobTokenFile,
+                      TaskUmbilicalProtocol taskTracker) 
+  throws IOException, InterruptedException {
+    // load user credentials, configuration
+    // ASSUME
+    // let $x = $mapred.local.dir
+    // forall $x, exists $x/$user
+    // exists $x/$user/jobcache/$jobid/job.xml
+    // exists $x/$user/jobcache/$jobid/jobToken
+    // exists $logdir/userlogs/$jobid
+    final Path localJobFile = lDirAlloc.getLocalPathToRead(JOBCONF, ttConf);
+    final JobConf cfgJob = new JobConf(localJobFile);
+    createWorkDir(cfgJob);
+    localizeJobFiles(JobID.forName(jobid), cfgJob, localJobFile,
+        localJobTokenFile, taskTracker);
+
+    // $mapred.local.dir/taskTracker/$user/distcache
+    return 0;
+  }
+
+  public static void main(String[] argv)
+      throws IOException, InterruptedException {
+    // $logdir
+    // let $x = $root/tasktracker for some $mapred.local.dir
+    //   create $x/$user/jobcache/$jobid/work
+    //   fetch  $x/$user/jobcache/$jobid/jars/job.jar
+    //   setup  $x/$user/distcache
+    //   verify $x/distcache
+    //   write  $x/$user/jobcache/$jobid/job.xml
+    final String user = argv[0];
+    final String jobid = argv[1];
+    final InetSocketAddress ttAddr = 
+      new InetSocketAddress(argv[2], Integer.parseInt(argv[3]));
+    final String uid = UserGroupInformation.getCurrentUser().getShortUserName();
+    if (!user.equals(uid)) {
+      LOG.warn("Localization running as " + uid + " not " + user);
+    }
+
+    // Pull in user's tokens to complete setup
+    final JobConf conf = new JobConf();
+    final JobLocalizer localizer =
+      new JobLocalizer(conf, user, jobid);
+    final Path jobTokenFile = localizer.findCredentials();
+    final Credentials creds = TokenCache.loadTokens(
+        jobTokenFile.toUri().toString(), conf);
+    LOG.debug("Loaded tokens from " + jobTokenFile);
+    UserGroupInformation ugi = UserGroupInformation.createRemoteUser(user);
+    for (Token<? extends TokenIdentifier> token : creds.getAllTokens()) {
+      ugi.addToken(token);
+    }
+    
+    UserGroupInformation ugiJob = UserGroupInformation.createRemoteUser(jobid);
+    Token<JobTokenIdentifier> jt = TokenCache.getJobToken(creds);
+    jt.setService(new Text(ttAddr.getAddress().getHostAddress() + ":"
+        + ttAddr.getPort()));
+    ugiJob.addToken(jt);
+
+    final TaskUmbilicalProtocol taskTracker = 
+      ugiJob.doAs(new PrivilegedExceptionAction<TaskUmbilicalProtocol>() {
+        public TaskUmbilicalProtocol run() throws IOException {
+          TaskUmbilicalProtocol taskTracker =
+            (TaskUmbilicalProtocol) RPC.getProxy(TaskUmbilicalProtocol.class,
+                TaskUmbilicalProtocol.versionID,
+                ttAddr, conf);
+          return taskTracker;
+        }
+      });
+    System.exit(
+      ugi.doAs(new PrivilegedExceptionAction<Integer>() {
+        public Integer run() {
+          try {
+            return localizer.runSetup(user, jobid, jobTokenFile, taskTracker);
+          } catch (Throwable e) {
+            e.printStackTrace(System.out);
+            return -1;
+          }
+        }
+      }));
+  }
+
+  /**
+   * Write the task specific job-configuration file.
+   * @throws IOException
+   */
+  public static void writeLocalJobFile(Path jobFile, JobConf conf)
+      throws IOException {
+    FileSystem localFs = FileSystem.getLocal(conf);
+    localFs.delete(jobFile);
+    OutputStream out = null;
+    try {
+      out = FileSystem.create(localFs, jobFile, urw_gr);
+      conf.writeXml(out);
+    } finally {
+      IOUtils.cleanup(LOG, out);
+    }
+  }
+
+}

+ 295 - 335
mapreduce/src/java/org/apache/hadoop/mapred/JvmManager.java

@@ -1,20 +1,20 @@
 /**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
 
 package org.apache.hadoop.mapred;
 
@@ -30,280 +30,264 @@ import java.util.Vector;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
-import org.apache.hadoop.mapred.TaskController.TaskControllerContext;
+import org.apache.hadoop.fs.FileUtil;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.mapred.CleanupQueue.PathDeletionContext;
+import org.apache.hadoop.mapred.TaskController;
+import org.apache.hadoop.mapred.TaskController.DelayedProcessKiller;
 import org.apache.hadoop.mapred.TaskTracker.TaskInProgress;
 import org.apache.hadoop.mapreduce.TaskType;
 import org.apache.hadoop.mapreduce.server.tasktracker.TTConfig;
-import org.apache.hadoop.util.Shell.ShellCommandExecutor;
 import org.apache.hadoop.util.StringUtils;
-import org.apache.hadoop.mapreduce.util.ProcessTree;
+import static org.apache.hadoop.mapred.TaskController.Signal;
 
 class JvmManager {
 
-  public static final Log LOG =
-    LogFactory.getLog(JvmManager.class);
+public static final Log LOG =
+  LogFactory.getLog(JvmManager.class);
 
-  private JvmManagerForType mapJvmManager;
+private JvmManagerForType mapJvmManager;
 
-  private JvmManagerForType reduceJvmManager;
-  
-  public JvmEnv constructJvmEnv(List<String> setup, Vector<String>vargs,
-      File stdout,File stderr,long logSize, File workDir, 
-      Map<String,String> env, JobConf conf) {
-    return new JvmEnv(setup,vargs,stdout,stderr,logSize,workDir,env,conf);
-  }
-  
-  public JvmManager(TaskTracker tracker) {
-    mapJvmManager = new JvmManagerForType(tracker.getMaxCurrentMapTasks(), 
-        true, tracker);
-    reduceJvmManager = new JvmManagerForType(tracker.getMaxCurrentReduceTasks(),
-        false, tracker);
-  }
+private JvmManagerForType reduceJvmManager;
 
-  JvmManagerForType getJvmManagerForType(TaskType type) {
-    if (type.equals(TaskType.MAP)) {
-      return mapJvmManager;
-    } else if (type.equals(TaskType.REDUCE)) {
-      return reduceJvmManager;
-    }
-    return null;
-  }
-  
-  public void stop() {
-    mapJvmManager.stop();
-    reduceJvmManager.stop();
+public JvmEnv constructJvmEnv(List<String> setup, Vector<String>vargs,
+    File stdout,File stderr,long logSize, File workDir, 
+    JobConf conf) {
+  return new JvmEnv(setup,vargs,stdout,stderr,workDir,conf);
+}
+
+public JvmManager(TaskTracker tracker) {
+  mapJvmManager = new JvmManagerForType(tracker.getMaxCurrentMapTasks(), 
+      true, tracker);
+  reduceJvmManager = new JvmManagerForType(tracker.getMaxCurrentReduceTasks(),
+      false, tracker);
+}
+
+JvmManagerForType getJvmManagerForType(TaskType type) {
+  if (type.equals(TaskType.MAP)) {
+    return mapJvmManager;
+  } else if (type.equals(TaskType.REDUCE)) {
+    return reduceJvmManager;
   }
+  return null;
+}
 
-  public boolean isJvmKnown(JVMId jvmId) {
-    if (jvmId.isMapJVM()) {
-      return mapJvmManager.isJvmknown(jvmId);
-    } else {
-      return reduceJvmManager.isJvmknown(jvmId);
-    }
+public void stop() throws IOException, InterruptedException {
+  mapJvmManager.stop();
+  reduceJvmManager.stop();
+}
+
+public boolean isJvmKnown(JVMId jvmId) {
+  if (jvmId.isMapJVM()) {
+    return mapJvmManager.isJvmknown(jvmId);
+  } else {
+    return reduceJvmManager.isJvmknown(jvmId);
   }
+}
 
-  /*
-   * Saves pid of the given taskJvm
-   */
-  void setPidToJvm(JVMId jvmId, String pid) {
-    if (jvmId.isMapJVM()) {
-      mapJvmManager.setPidForJvm(jvmId, pid);
-    }
-    else {
-      reduceJvmManager.setPidForJvm(jvmId, pid);
-    }
+/*
+ * Saves pid of the given taskJvm
+ */
+void setPidToJvm(JVMId jvmId, String pid) {
+  if (jvmId.isMapJVM()) {
+    mapJvmManager.setPidForJvm(jvmId, pid);
   }
-  
-  /*
-   * Returns the pid of the task
-   */
-  String getPid(TaskRunner t) {
-    if (t != null && t.getTask() != null) {
-      if (t.getTask().isMapTask()) {
-        return mapJvmManager.getPidByRunningTask(t);
-      } else {
-        return reduceJvmManager.getPidByRunningTask(t);
-      }
-    }
-    return null;
+  else {
+    reduceJvmManager.setPidForJvm(jvmId, pid);
   }
-  
-  public void launchJvm(TaskRunner t, JvmEnv env) {
+}
+
+/*
+ * Returns the pid of the task
+ */
+String getPid(TaskRunner t) {
+  if (t != null && t.getTask() != null) {
     if (t.getTask().isMapTask()) {
-      mapJvmManager.reapJvm(t, env);
+      return mapJvmManager.getPidByRunningTask(t);
     } else {
-      reduceJvmManager.reapJvm(t, env);
+      return reduceJvmManager.getPidByRunningTask(t);
     }
   }
+  return null;
+}
 
-  public TaskInProgress getTaskForJvm(JVMId jvmId)
-      throws IOException {
-    if (jvmId.isMapJVM()) {
-      return mapJvmManager.getTaskForJvm(jvmId);
-    } else {
-      return reduceJvmManager.getTaskForJvm(jvmId);
-    }
-  }
-  public void taskFinished(TaskRunner tr) {
-    if (tr.getTask().isMapTask()) {
-      mapJvmManager.taskFinished(tr);
-    } else {
-      reduceJvmManager.taskFinished(tr);
-    }
+public void launchJvm(TaskRunner t, JvmEnv env)
+    throws IOException, InterruptedException {
+  if (t.getTask().isMapTask()) {
+    mapJvmManager.reapJvm(t, env);
+  } else {
+    reduceJvmManager.reapJvm(t, env);
   }
+}
 
-  public void taskKilled(TaskRunner tr) {
-    if (tr.getTask().isMapTask()) {
-      mapJvmManager.taskKilled(tr);
-    } else {
-      reduceJvmManager.taskKilled(tr);
-    }
+public TaskInProgress getTaskForJvm(JVMId jvmId)
+    throws IOException {
+  if (jvmId.isMapJVM()) {
+    return mapJvmManager.getTaskForJvm(jvmId);
+  } else {
+    return reduceJvmManager.getTaskForJvm(jvmId);
   }
-
-  void dumpStack(TaskRunner tr) {
-    if (tr.getTask().isMapTask()) {
-      mapJvmManager.dumpStack(tr);
-    } else {
-      reduceJvmManager.dumpStack(tr);
-    }
+}
+public void taskFinished(TaskRunner tr) {
+  if (tr.getTask().isMapTask()) {
+    mapJvmManager.taskFinished(tr);
+  } else {
+    reduceJvmManager.taskFinished(tr);
   }
+}
 
-  public void killJvm(JVMId jvmId) {
-    if (jvmId.isMap) {
-      mapJvmManager.killJvm(jvmId);
-    } else {
-      reduceJvmManager.killJvm(jvmId);
-    }
-  }  
-
-  /**
-   * Adds the task's work dir to the cleanup queue of taskTracker for
-   * asynchronous deletion of work dir.
-   * @param tracker taskTracker
-   * @param task    the task whose work dir needs to be deleted
-   * @throws IOException
-   */
-  static void deleteWorkDir(TaskTracker tracker, Task task) throws IOException {
-    tracker.getCleanupThread().addToQueue(
-        TaskTracker.buildTaskControllerTaskPathDeletionContexts(
-          tracker.getLocalFileSystem(),
-          tracker.getLocalFiles(tracker.getJobConf(), ""),
-          task, true /* workDir */,
-          tracker.getTaskController()));
+public void taskKilled(TaskRunner tr
+                       ) throws IOException, InterruptedException {
+  if (tr.getTask().isMapTask()) {
+    mapJvmManager.taskKilled(tr);
+  } else {
+    reduceJvmManager.taskKilled(tr);
   }
+}
 
-  static class JvmManagerForType {
-    //Mapping from the JVM IDs to running Tasks
-    Map <JVMId,TaskRunner> jvmToRunningTask = 
-      new HashMap<JVMId, TaskRunner>();
-    //Mapping from the tasks to JVM IDs
-    Map <TaskRunner,JVMId> runningTaskToJvm = 
-      new HashMap<TaskRunner, JVMId>();
-    //Mapping from the JVM IDs to Reduce JVM processes
-    Map <JVMId, JvmRunner> jvmIdToRunner = 
-      new HashMap<JVMId, JvmRunner>();
-    
-    int maxJvms;
-    boolean isMap;
-    
-    TaskTracker tracker;
-    
-    Random rand = new Random(System.currentTimeMillis());
-
-    public JvmManagerForType(int maxJvms, boolean isMap, 
-        TaskTracker tracker) {
-      this.maxJvms = maxJvms;
-      this.isMap = isMap;
-      this.tracker = tracker;
-    }
-
-    synchronized public void setRunningTaskForJvm(JVMId jvmId, 
-        TaskRunner t) {
-      jvmToRunningTask.put(jvmId, t);
-      runningTaskToJvm.put(t,jvmId);
-      jvmIdToRunner.get(jvmId).setTaskRunner(t);
-    }
-    
-    synchronized public TaskInProgress getTaskForJvm(JVMId jvmId)
-        throws IOException {
-      if (jvmToRunningTask.containsKey(jvmId)) {
-        //Incase of JVM reuse, tasks are returned to previously launched
-        //JVM via this method. However when a new task is launched
-        //the task being returned has to be initialized.
-        TaskRunner taskRunner = jvmToRunningTask.get(jvmId);
-        JvmRunner jvmRunner = jvmIdToRunner.get(jvmId);
-        Task task = taskRunner.getTaskInProgress().getTask();
-
-        // Initialize task dirs
-        TaskControllerContext context =
-            new TaskController.TaskControllerContext();
-        context.env = jvmRunner.env;
-        context.task = task;
-        // If we are returning the same task as which the JVM was launched
-        // we don't initialize task once again.
-        if (!jvmRunner.env.conf.get(JobContext.TASK_ATTEMPT_ID).equals(
-            task.getTaskID().toString())) {
-          try {
-            tracker.getTaskController().initializeTask(context);
-          } catch (IOException e) {
-            LOG.warn("Failed to initialize the new task "
-                + task.getTaskID().toString() + " to be given to JVM with id "
-                + jvmId);
-            throw e;
-          }
-        }
+public void killJvm(JVMId jvmId) throws IOException, InterruptedException {
+  if (jvmId.isMap) {
+    mapJvmManager.killJvm(jvmId);
+  } else {
+    reduceJvmManager.killJvm(jvmId);
+  }
+}  
 
-        return taskRunner.getTaskInProgress();
-      }
-      return null;
-    }
+/**
+ * Adds the task's work dir to the cleanup queue of taskTracker for
+ * asynchronous deletion of work dir.
+ * @param tracker taskTracker
+ * @param task    the task whose work dir needs to be deleted
+ */
+static void deleteWorkDir(TaskTracker tracker, Task task) {
+  String user = task.getUser();
+  String jobid = task.getJobID().toString();
+  String taskid = task.getTaskID().toString();
+  String workDir = TaskTracker.getTaskWorkDir(user, jobid, taskid, 
+                                              task.isTaskCleanupTask());
+  tracker.getCleanupThread().addToQueue(
+   new TaskController.DeletionContext(tracker.getTaskController(), false,
+                                      user, 
+                                      workDir, tracker.getLocalDirs()));
+                                         
+}
 
-    synchronized String getPidByRunningTask(TaskRunner t) {
-      JVMId id = runningTaskToJvm.get(t);
-      if (id != null) {
-        return jvmIdToRunner.get(id).getPid();
-      }
-      return null;
-    }
+static class JvmManagerForType {
+  //Mapping from the JVM IDs to running Tasks
+  Map <JVMId,TaskRunner> jvmToRunningTask = 
+    new HashMap<JVMId, TaskRunner>();
+  //Mapping from the tasks to JVM IDs
+  Map <TaskRunner,JVMId> runningTaskToJvm = 
+    new HashMap<TaskRunner, JVMId>();
+  //Mapping from the JVM IDs to Reduce JVM processes
+  Map <JVMId, JvmRunner> jvmIdToRunner = 
+    new HashMap<JVMId, JvmRunner>();
+  //Mapping from the JVM IDs to process IDs
+  Map <JVMId, String> jvmIdToPid =
+    new HashMap<JVMId, String>();
+  
+  final int maxJvms;
+  final boolean isMap;
+  final TaskTracker tracker;
+  final long sleeptimeBeforeSigkill;
+  final Random rand = new Random();
+
+  static final String DELAY_BEFORE_KILL_KEY =
+    "mapred.tasktracker.tasks.sleeptime-before-sigkill";
+  // number of milliseconds to wait between TERM and KILL.
+  private static final long DEFAULT_SLEEPTIME_BEFORE_SIGKILL = 250;
+
+  public JvmManagerForType(int maxJvms, boolean isMap, 
+      TaskTracker tracker) {
+    this.maxJvms = maxJvms;
+    this.isMap = isMap;
+    this.tracker = tracker;
+    sleeptimeBeforeSigkill =
+      tracker.getJobConf().getLong(DELAY_BEFORE_KILL_KEY,
+                                   DEFAULT_SLEEPTIME_BEFORE_SIGKILL);
+  }
 
-    synchronized void setPidForJvm(JVMId jvmId, String pid) {
-      JvmRunner runner = jvmIdToRunner.get(jvmId);
-      assert runner != null : "Task must have a runner to set a pid";
-      runner.setPid(pid);
-    }
-    
-    synchronized public boolean isJvmknown(JVMId jvmId) {
-      return jvmIdToRunner.containsKey(jvmId);
-    }
+  synchronized public void setRunningTaskForJvm(JVMId jvmId, 
+      TaskRunner t) {
+    jvmToRunningTask.put(jvmId, t);
+    runningTaskToJvm.put(t,jvmId);
+    jvmIdToRunner.get(jvmId).setBusy(true);
+  }
+  
+  synchronized public TaskInProgress getTaskForJvm(JVMId jvmId)
+      throws IOException {
+    final TaskRunner taskRunner = jvmToRunningTask.get(jvmId);
+    return null == taskRunner ? null : taskRunner.getTaskInProgress();
+    //if (jvmToRunningTask.containsKey(jvmId)) {
+    //  //Incase of JVM reuse, tasks are returned to previously launched
+    //  //JVM via this method. However when a new task is launched
+    //  //the task being returned has to be initialized.
+    //  TaskRunner taskRunner = jvmToRunningTask.get(jvmId);
+    //  // TODO retained for MAPREDUCE-1100
+    //  JvmRunner jvmRunner = jvmIdToRunner.get(jvmId);
+    //  Task task = taskRunner.getTaskInProgress().getTask();
+
+    //  return taskRunner.getTaskInProgress();
+    //}
+    //return null;
+  }
 
-    synchronized public void taskFinished(TaskRunner tr) {
-      JVMId jvmId = runningTaskToJvm.remove(tr);
-      if (jvmId != null) {
-        jvmToRunningTask.remove(jvmId);
-        JvmRunner jvmRunner;
-        if ((jvmRunner = jvmIdToRunner.get(jvmId)) != null) {
-          jvmRunner.taskRan();
-        }
-      }
+  synchronized String getPidByRunningTask(TaskRunner t) {
+    JVMId id = runningTaskToJvm.get(t);
+    if (id != null) {
+      return jvmIdToPid.get(id);
     }
+    return null;
+  }
 
-    synchronized public void taskKilled(TaskRunner tr) {
-      JVMId jvmId = runningTaskToJvm.remove(tr);
-      if (jvmId != null) {
-        jvmToRunningTask.remove(jvmId);
-        killJvm(jvmId);
-      }
-    }
+  synchronized void setPidForJvm(JVMId jvmId, String pid) {
+    JvmRunner runner = jvmIdToRunner.get(jvmId);
+    assert runner != null : "Task must have a runner to set a pid";
+    jvmIdToPid.put(jvmId, pid);
+  }
+  
+  synchronized public boolean isJvmknown(JVMId jvmId) {
+    return jvmIdToRunner.containsKey(jvmId);
+  }
 
-    synchronized public void killJvm(JVMId jvmId) {
+  synchronized public void taskFinished(TaskRunner tr) {
+    JVMId jvmId = runningTaskToJvm.remove(tr);
+    if (jvmId != null) {
+      jvmToRunningTask.remove(jvmId);
       JvmRunner jvmRunner;
       if ((jvmRunner = jvmIdToRunner.get(jvmId)) != null) {
-        killJvmRunner(jvmRunner);
+        jvmRunner.taskRan();
       }
     }
-    
-    private synchronized void killJvmRunner(JvmRunner jvmRunner) {
-      jvmRunner.kill();
-      removeJvm(jvmRunner.jvmId);
-    }
+  }
 
-    void dumpStack(TaskRunner tr) {
-      JvmRunner jvmRunner = null;
-      synchronized (this) {
-        JVMId jvmId = runningTaskToJvm.get(tr);
-        if (null != jvmId) {
-          jvmRunner = jvmIdToRunner.get(jvmId);
-        }
-      }
+  synchronized public void taskKilled(TaskRunner tr
+                                      ) throws IOException,
+                                               InterruptedException {
+    JVMId jvmId = runningTaskToJvm.remove(tr);
+    if (jvmId != null) {
+      jvmToRunningTask.remove(jvmId);
+      killJvm(jvmId);
+    }
+  }
 
-      // Don't want to hold JvmManager lock while dumping stacks for one
-      // task.
-      if (null != jvmRunner) {
-        jvmRunner.dumpChildStacks();
-      }
+  synchronized public void killJvm(JVMId jvmId)
+      throws IOException, InterruptedException {
+    JvmRunner jvmRunner;
+    if ((jvmRunner = jvmIdToRunner.get(jvmId)) != null) {
+      killJvmRunner(jvmRunner);
     }
+  }
+  
+  private synchronized void killJvmRunner(JvmRunner jvmRunner)
+      throws IOException, InterruptedException {
+    jvmRunner.kill();
+    removeJvm(jvmRunner.jvmId);
+  }
 
-    synchronized public void stop() {
+
+    synchronized public void stop()
+        throws IOException, InterruptedException {
       //since the kill() method invoked later on would remove
       //an entry from the jvmIdToRunner map, we create a
       //copy of the values and iterate over it (if we don't
@@ -320,7 +304,7 @@ class JvmManager {
       jvmIdToRunner.remove(jvmId);
     }
     private synchronized void reapJvm( 
-        TaskRunner t, JvmEnv env) {
+        TaskRunner t, JvmEnv env) throws IOException, InterruptedException {
       if (t.getTaskInProgress().wasKilled()) {
         //the task was killed in-flight
         //no need to do the rest of the operations
@@ -409,7 +393,7 @@ class JvmManager {
 
     private synchronized void spawnNewJvm(JobID jobId, JvmEnv env,  
         TaskRunner t) {
-      JvmRunner jvmRunner = new JvmRunner(env,jobId);
+      JvmRunner jvmRunner = new JvmRunner(env, jobId, t.getTask());
       jvmIdToRunner.put(jvmRunner.jvmId, jvmRunner);
       //spawn the JVM in a new thread. Note that there will be very little
       //extra overhead of launching the new thread for a new JVM since
@@ -443,83 +427,90 @@ class JvmManager {
       volatile int numTasksRan;
       final int numTasksToRun;
       JVMId jvmId;
-      private ShellCommandExecutor shexec; // shell terminal for running the task
-      //context used for starting JVM
-      private TaskControllerContext initalContext;
+      volatile boolean busy = true;
+      private Task firstTask;
       
-      public JvmRunner(JvmEnv env, JobID jobId) {
+      public JvmRunner(JvmEnv env, JobID jobId, Task firstTask) {
         this.env = env;
         this.jvmId = new JVMId(jobId, isMap, rand.nextInt());
         this.numTasksToRun = env.conf.getNumTasksToExecutePerJvm();
-
-        this.initalContext = new TaskControllerContext();
-        initalContext.sleeptimeBeforeSigkill = tracker.getJobConf()
-          .getLong(TTConfig.TT_SLEEP_TIME_BEFORE_SIG_KILL,
-                   ProcessTree.DEFAULT_SLEEPTIME_BEFORE_SIGKILL);
+        this.firstTask = firstTask;
         LOG.info("In JvmRunner constructed JVM ID: " + jvmId);
       }
+
+      @Override
       public void run() {
-        runChild(env);
+        try {
+          runChild(env);
+        } catch (InterruptedException ie) {
+          return;
+        } catch (IOException e) {
+          LOG.warn("Caught IOException in JVMRunner", e);
+        } catch (Throwable e) {
+          LOG.error("Caught Throwable in JVMRunner. Aborting TaskTracker.", e);
+          System.exit(1);
+        } finally {
+          // TODO MR-1100
+          //jvmFinished();
+        }
       }
 
-      public void runChild(JvmEnv env) {
+      public void runChild(JvmEnv env)
+          throws IOException, InterruptedException {
+        int exitCode = 0;
         try {
           env.vargs.add(Integer.toString(jvmId.getId()));
-          //Launch the task controller to run task JVM
-          initalContext.env = env;
-          tracker.getTaskController().launchTaskJVM(initalContext);
+          TaskRunner runner = jvmToRunningTask.get(jvmId);
+          if (runner != null) {
+            Task task = runner.getTask();
+            //Launch the task controller to run task JVM
+            String user = task.getUser();
+            TaskAttemptID taskAttemptId = task.getTaskID();
+            String taskAttemptIdStr = task.isTaskCleanupTask() ? 
+                (taskAttemptId.toString() + TaskTracker.TASK_CLEANUP_SUFFIX) :
+                  taskAttemptId.toString(); 
+                exitCode = tracker.getTaskController().launchTask(user,
+                    jvmId.jobId.toString(), taskAttemptIdStr, env.setup,
+                    env.vargs, env.workDir, env.stdout.toString(),
+                    env.stderr.toString());
+          }
         } catch (IOException ioe) {
           // do nothing
           // error and output are appropriately redirected
         } finally { // handle the exit code
-          shexec = initalContext.shExec;
-          if (shexec == null) {
-            return;
-          }
-
+          // although the process has exited before we get here,
+          // make sure the entire process group has also been killed.
           kill();
 
-          int exitCode = shexec.getExitCode();
           updateOnJvmExit(jvmId, exitCode);
           LOG.info("JVM : " + jvmId + " exited with exit code " + exitCode
               + ". Number of tasks it ran: " + numTasksRan);
-          try {
-            // In case of jvm-reuse,
-            //the task jvm cleans up the common workdir for every 
-            //task at the beginning of each task in the task JVM.
-            //For the last task, we do it here.
-            if (env.conf.getNumTasksToExecutePerJvm() != 1) {
-              deleteWorkDir(tracker, initalContext.task);
-            }
-          } catch (IOException ie){}
-        }
-      }
-
-      synchronized void setPid(String pid) {
-        assert initalContext != null;
-        initalContext.pid = pid;
-      }
-
-      synchronized String getPid() {
-        if (initalContext != null) {
-          return initalContext.pid;
-        } else {
-          return null;
+          deleteWorkDir(tracker, firstTask);
         }
       }
 
       /** 
-       * Kills the process. Also kills its subprocesses if the process(root of subtree
-       * of processes) is created using setsid.
+       * Kills the process. Also kills its subprocesses if the process(root of
+       * subtree of processes) is created using setsid.
        */
-      synchronized void kill() {
+      synchronized void kill() throws IOException, InterruptedException {
         if (!killed) {
           TaskController controller = tracker.getTaskController();
           // Check inital context before issuing a kill to prevent situations
           // where kill is issued before task is launched.
-          if (initalContext != null && initalContext.env != null) {
-            // Destroy the task jvm
-            controller.destroyTaskJVM(initalContext);
+          String pidStr = jvmIdToPid.get(jvmId);
+          if (pidStr != null) {
+            String user = env.conf.getUser();
+            int pid = Integer.parseInt(pidStr);
+            // start a thread that will kill the process dead
+            if (sleeptimeBeforeSigkill > 0) {
+              controller.signalTask(user, pid, Signal.QUIT);
+              controller.signalTask(user, pid, Signal.TERM);
+              new DelayedProcessKiller(user, pid, sleeptimeBeforeSigkill,
+                  Signal.KILL, tracker.getTaskController()).start();
+            } else {
+              controller.signalTask(user, pid, Signal.KILL);
+            }
           } else {
             LOG.info(String.format("JVM Not killed %s but just removed", jvmId
                 .toString()));
@@ -528,46 +519,19 @@ class JvmManager {
         }
       }
 
-      /** Send a signal to the JVM requesting that it dump a stack trace,
-       * and wait for a timeout interval to give this signal time to be
-       * processed.
-       */
-      void dumpChildStacks() {
-        if (!killed) {
-          TaskController controller = tracker.getTaskController();
-          // Check inital context before issuing a signal to prevent situations
-          // where signal is issued before task is launched.
-          if (initalContext != null && initalContext.env != null) {
-            // signal the task jvm
-            controller.dumpTaskStack(initalContext);
-
-            // We're going to kill the jvm with SIGKILL after this,
-            // so we should wait for a few seconds first to ensure that
-            // the SIGQUIT has time to be processed.
-            try {
-              Thread.sleep(initalContext.sleeptimeBeforeSigkill);
-            } catch (InterruptedException e) {
-              LOG.warn("Sleep interrupted : " +
-                  StringUtils.stringifyException(e));
-            }
-          }
-        }
-      }
-
-      public synchronized void taskRan() {
-        initalContext.task = null;
+      public void taskRan() {
+        busy = false;
         numTasksRan++;
       }
       
       public boolean ranAll() {
         return(numTasksRan == numTasksToRun);
       }
-      public synchronized void setTaskRunner(TaskRunner runner) {
-        initalContext.task = runner.getTask();
-        assert initalContext.task != null;
+      public void setBusy(boolean busy) {
+        this.busy = busy;
       }
       public synchronized boolean isBusy() {
-        return initalContext.task != null;
+        return busy;
       }
     }
   }  
@@ -577,19 +541,15 @@ class JvmManager {
     File stdout;
     File stderr;
     File workDir;
-    long logSize;
     JobConf conf;
-    Map<String, String> env;
 
-    public JvmEnv(List<String> setup, Vector<String> vargs, File stdout, 
-        File stderr, long logSize, File workDir, Map<String,String> env,
-        JobConf conf) {
+    public JvmEnv(List <String> setup, Vector<String> vargs, File stdout, 
+        File stderr, File workDir, JobConf conf) {
       this.setup = setup;
       this.vargs = vargs;
       this.stdout = stdout;
       this.stderr = stderr;
       this.workDir = workDir;
-      this.env = env;
       this.conf = conf;
     }
   }

+ 199 - 535
mapreduce/src/java/org/apache/hadoop/mapred/LinuxTaskController.java

@@ -14,30 +14,27 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
- */
+*/
 package org.apache.hadoop.mapred;
 
-import java.io.BufferedWriter;
 import java.io.File;
-import java.io.FileWriter;
 import java.io.IOException;
-import java.io.PrintWriter;
+import java.net.InetSocketAddress;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
 
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.fs.FileSystem;
-import org.apache.hadoop.fs.LocalFileSystem;
-import org.apache.hadoop.fs.FileUtil;
+import org.apache.hadoop.fs.LocalDirAllocator;
 import org.apache.hadoop.fs.Path;
-import org.apache.hadoop.mapred.CleanupQueue.PathDeletionContext;
-import org.apache.hadoop.mapred.JvmManager.JvmEnv;
-import org.apache.hadoop.util.StringUtils;
 import org.apache.hadoop.util.Shell.ExitCodeException;
 import org.apache.hadoop.util.Shell.ShellCommandExecutor;
+import org.apache.hadoop.util.StringUtils;
+import static org.apache.hadoop.mapred.TaskController.Signal;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
 
 /**
  * A {@link TaskController} that runs the task JVMs as the user 
@@ -48,8 +45,8 @@ import org.apache.hadoop.util.Shell.ShellCommandExecutor;
  * JVM and killing it when needed, and also initializing and
  * finalizing the task environment. 
  * <p> The setuid executable is launched using the command line:</p>
- * <p>task-controller mapreduce.job.user.name command command-args, where</p>
- * <p>mapreduce.job.user.name is the name of the owner who submits the job</p>
+ * <p>task-controller user-name command command-args, where</p>
+ * <p>user-name is the name of the owner who submits the job</p>
  * <p>command is one of the cardinal value of the 
  * {@link LinuxTaskController.TaskControllerCommands} enumeration</p>
  * <p>command-args depends on the command being launched.</p>
@@ -62,52 +59,78 @@ class LinuxTaskController extends TaskController {
 
   private static final Log LOG = 
             LogFactory.getLog(LinuxTaskController.class);
-
-  // Name of the executable script that will contain the child
-  // JVM command line. See writeCommand for details.
-  private static final String COMMAND_FILE = "taskjvm.sh";
   
   // Path to the setuid executable.
-  private static String taskControllerExe;
+  private String taskControllerExe;
+  private static final String TASK_CONTROLLER_EXEC_KEY =
+    "mapreduce.tasktracker.task-controller.exe";
   
-  static {
-    // the task-controller is expected to be under the $HADOOP_HOME/bin
-    // directory.
-    File hadoopBin = new File(System.getenv("HADOOP_HOME"), "bin");
-    taskControllerExe = 
-        new File(hadoopBin, "task-controller").getAbsolutePath();
+  @Override
+  public void setConf(Configuration conf) {
+    super.setConf(conf);
+    taskControllerExe = getTaskControllerExecutablePath(conf);
   }
-  
+
   public LinuxTaskController() {
     super();
   }
-  
+
+  protected String getTaskControllerExecutablePath(Configuration conf) {
+    File hadoopBin = new File(System.getenv("HADOOP_HOME"), "bin");
+    String defaultTaskController =
+      new File(hadoopBin, "task-controller").getAbsolutePath();
+    return null == conf
+      ? defaultTaskController
+      : conf.get(TASK_CONTROLLER_EXEC_KEY, defaultTaskController);
+  }
+
   /**
    * List of commands that the setuid script will execute.
    */
-  enum TaskControllerCommands {
-    INITIALIZE_USER,
-    INITIALIZE_JOB,
-    INITIALIZE_DISTRIBUTEDCACHE_FILE,
-    LAUNCH_TASK_JVM,
-    INITIALIZE_TASK,
-    TERMINATE_TASK_JVM,
-    KILL_TASK_JVM,
-    RUN_DEBUG_SCRIPT,
-    SIGQUIT_TASK_JVM,
-    ENABLE_TASK_FOR_CLEANUP,
-    ENABLE_JOB_FOR_CLEANUP
+  enum Commands {
+    INITIALIZE_JOB(0),
+    LAUNCH_TASK_JVM(1),
+    SIGNAL_TASK(2),
+    DELETE_AS_USER(3),
+    DELETE_LOG_AS_USER(4);
+
+    private int value;
+    Commands(int value) {
+      this.value = value;
+    }
+    int getValue() {
+      return value;
+    }
+  }
+
+  /**
+   * Result codes returned from the C task-controller.
+   * These must match the values in task-controller.h.
+   */
+  enum ResultCode {
+    OK(0),
+    INVALID_USER_NAME(2),
+    INVALID_TASK_PID(9),
+    INVALID_TASKCONTROLLER_PERMISSIONS(22),
+    INVALID_CONFIG_FILE(24);
+
+    private final int value;
+    ResultCode(int value) {
+      this.value = value;
+    }
+    int getValue() {
+      return value;
+    }
   }
 
   @Override
-  public void setup() throws IOException {
-    super.setup();
-    
+  public void setup(LocalDirAllocator allocator) throws IOException {
+
     // Check the permissions of the task-controller binary by running it plainly.
-    // If permissions are correct, it returns an error code 1, else it returns 
+    // If permissions are correct, it returns an error code 1, else it returns
     // 24 or something else if some other bugs are also present.
     String[] taskControllerCmd =
-        new String[] { getTaskControllerExecutablePath() };
+        new String[] { taskControllerExe };
     ShellCommandExecutor shExec = new ShellCommandExecutor(taskControllerCmd);
     try {
       shExec.execute();
@@ -120,52 +143,96 @@ class LinuxTaskController extends TaskController {
           + "permissions/ownership with exit code " + exitCode, e);
       }
     }
+    this.allocator = allocator;
   }
+  
 
-  /**
-   * Launch a task JVM that will run as the owner of the job.
-   * 
-   * This method launches a task JVM by executing a setuid executable that will
-   * switch to the user and run the task. Also does initialization of the first
-   * task in the same setuid process launch.
-   */
   @Override
-  void launchTaskJVM(TaskController.TaskControllerContext context) 
-                                        throws IOException {
-    JvmEnv env = context.env;
-    // get the JVM command line.
-    String cmdLine = 
-      TaskLog.buildCommandLine(env.setup, env.vargs, env.stdout, env.stderr,
-          env.logSize, true);
-
-    StringBuffer sb = new StringBuffer();
-    //export out all the environment variable before child command as
-    //the setuid/setgid binaries would not be getting, any environmental
-    //variables which begin with LD_*.
-    for(Entry<String, String> entry : env.env.entrySet()) {
-      sb.append("export ");
-      sb.append(entry.getKey());
-      sb.append("=");
-      sb.append(entry.getValue());
-      sb.append("\n");
+  public void initializeJob(String user, String jobid, Path credentials,
+                            Path jobConf, TaskUmbilicalProtocol taskTracker,
+                            InetSocketAddress ttAddr
+                            ) throws IOException, InterruptedException {
+    List<String> command = new ArrayList<String>(
+      Arrays.asList(taskControllerExe, 
+                    user, 
+                    Integer.toString(Commands.INITIALIZE_JOB.getValue()),
+                    jobid,
+                    credentials.toUri().getPath().toString(),
+                    jobConf.toUri().getPath().toString()));
+    File jvm =                                  // use same jvm as parent
+      new File(new File(System.getProperty("java.home"), "bin"), "java");
+    command.add(jvm.toString());
+    command.add("-classpath");
+    command.add(System.getProperty("java.class.path"));
+    command.add("-Dhadoop.log.dir=" + TaskLog.getBaseLogDir());
+    command.add("-Dhadoop.root.logger=INFO,console");
+    command.add(JobLocalizer.class.getName());  // main of JobLocalizer
+    command.add(user);
+    command.add(jobid);
+    // add the task tracker's reporting address
+    command.add(ttAddr.getHostName());
+    command.add(Integer.toString(ttAddr.getPort()));
+    String[] commandArray = command.toArray(new String[0]);
+    ShellCommandExecutor shExec = new ShellCommandExecutor(commandArray);
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("initializeJob: " + Arrays.toString(commandArray));
+    }
+    try {
+      shExec.execute();
+      if (LOG.isDebugEnabled()) {
+        logOutput(shExec.getOutput());
+      }
+    } catch (ExitCodeException e) {
+      int exitCode = shExec.getExitCode();
+      logOutput(shExec.getOutput());
+      throw new IOException("Job initialization failed (" + exitCode + ")", e);
     }
-    sb.append(cmdLine);
-    // write the command to a file in the
-    // task specific cache directory
-    writeCommand(sb.toString(), getTaskCacheDirectory(context, 
-        context.env.workDir));
-    
-    // Call the taskcontroller with the right parameters.
-    List<String> launchTaskJVMArgs = buildLaunchTaskArgs(context, 
-        context.env.workDir);
-    ShellCommandExecutor shExec =  buildTaskControllerExecutor(
-                                    TaskControllerCommands.LAUNCH_TASK_JVM, 
-                                    env.conf.getUser(),
-                                    launchTaskJVMArgs, env.workDir, env.env);
-    context.shExec = shExec;
+  }
+
+  @Override
+  public int launchTask(String user, 
+                                  String jobId,
+                                  String attemptId,
+                                  List<String> setup,
+                                  List<String> jvmArguments,
+                                  File currentWorkDirectory,
+                                  String stdout,
+                                  String stderr) throws IOException {
+
+    ShellCommandExecutor shExec = null;
     try {
+      FileSystem rawFs = FileSystem.getLocal(getConf()).getRaw();
+      long logSize = 0; //TODO, Ref BUG:2854624
+      // get the JVM command line.
+      String cmdLine = 
+        TaskLog.buildCommandLine(setup, jvmArguments,
+            new File(stdout), new File(stderr), logSize, true);
+
+      // write the command to a file in the
+      // task specific cache directory
+      Path p = new Path(allocator.getLocalPathForWrite(
+          TaskTracker.getPrivateDirTaskScriptLocation(user, jobId, attemptId),
+          getConf()), COMMAND_FILE);
+      String commandFile = writeCommand(cmdLine, rawFs, p); 
+
+      String[] command = 
+        new String[]{taskControllerExe, 
+          user,
+          Integer.toString(Commands.LAUNCH_TASK_JVM.getValue()),
+          jobId,
+          attemptId,
+          currentWorkDirectory.toString(),
+          commandFile};
+      shExec = new ShellCommandExecutor(command);
+
+      if (LOG.isDebugEnabled()) {
+        LOG.debug("launchTask: " + Arrays.toString(command));
+      }
       shExec.execute();
     } catch (Exception e) {
+      if (shExec == null) {
+        return -1;
+      }
       int exitCode = shExec.getExitCode();
       LOG.warn("Exit code from task is : " + exitCode);
       // 143 (SIGTERM) and 137 (SIGKILL) exit codes means the task was
@@ -177,481 +244,78 @@ class LinuxTaskController extends TaskController {
         LOG.info("Output from LinuxTaskController's launchTaskJVM follows:");
         logOutput(shExec.getOutput());
       }
-      throw new IOException(e);
-    }
-    if (LOG.isDebugEnabled()) {
-      LOG.info("Output from LinuxTaskController's launchTaskJVM follows:");
-      logOutput(shExec.getOutput());
-    }
-  }
-  
-  /**
-   * Launch the debug script process that will run as the owner of the job.
-   * 
-   * This method launches the task debug script process by executing a setuid
-   * executable that will switch to the user and run the task. 
-   */
-  @Override
-  void runDebugScript(DebugScriptContext context) throws IOException {
-    String debugOut = FileUtil.makeShellPath(context.stdout);
-    String cmdLine = TaskLog.buildDebugScriptCommandLine(context.args, debugOut);
-    writeCommand(cmdLine, getTaskCacheDirectory(context, context.workDir));
-    // Call the taskcontroller with the right parameters.
-    List<String> launchTaskJVMArgs = buildLaunchTaskArgs(context, context.workDir);
-    runCommand(TaskControllerCommands.RUN_DEBUG_SCRIPT, context.task.getUser(), 
-        launchTaskJVMArgs, context.workDir, null);
-  }
-  /**
-   * Helper method that runs a LinuxTaskController command
-   * 
-   * @param taskControllerCommand
-   * @param user
-   * @param cmdArgs
-   * @param env
-   * @throws IOException
-   */
-  private void runCommand(TaskControllerCommands taskControllerCommand, 
-      String user, List<String> cmdArgs, File workDir, Map<String, String> env)
-      throws IOException {
-
-    ShellCommandExecutor shExec =
-        buildTaskControllerExecutor(taskControllerCommand, user, cmdArgs, 
-                                    workDir, env);
-    try {
-      shExec.execute();
-    } catch (Exception e) {
-      LOG.warn("Exit code from " + taskControllerCommand.toString() + " is : "
-          + shExec.getExitCode());
-      LOG.warn("Exception thrown by " + taskControllerCommand.toString() + " : "
-          + StringUtils.stringifyException(e));
-      LOG.info("Output from LinuxTaskController's " 
-               + taskControllerCommand.toString() + " follows:");
-      logOutput(shExec.getOutput());
-      throw new IOException(e);
+      return exitCode;
     }
     if (LOG.isDebugEnabled()) {
-      LOG.info("Output from LinuxTaskController's " 
-               + taskControllerCommand.toString() + " follows:");
+      LOG.debug("Output from LinuxTaskController's launchTask follows:");
       logOutput(shExec.getOutput());
     }
+    return 0;
   }
 
-  /**
-   * Returns list of arguments to be passed while initializing a new task. See
-   * {@code buildTaskControllerExecutor(TaskControllerCommands, String, 
-   * List<String>, JvmEnv)} documentation.
-   * 
-   * @param context
-   * @return Argument to be used while launching Task VM
-   */
-  private List<String> buildInitializeTaskArgs(TaskExecContext context) {
-    List<String> commandArgs = new ArrayList<String>(3);
-    String taskId = context.task.getTaskID().toString();
-    String jobId = getJobId(context);
-    commandArgs.add(jobId);
-    if (!context.task.isTaskCleanupTask()) {
-      commandArgs.add(taskId);
-    } else {
-      commandArgs.add(taskId + TaskTracker.TASK_CLEANUP_SUFFIX);
-    }
-    return commandArgs;
-  }
-
-  @Override
-  void initializeTask(TaskControllerContext context)
-      throws IOException {
-    if (LOG.isDebugEnabled()) {
-      LOG.debug("Going to do " 
-                + TaskControllerCommands.INITIALIZE_TASK.toString()
-                + " for " + context.task.getTaskID().toString());
-    }
-    runCommand(TaskControllerCommands.INITIALIZE_TASK, 
-        context.env.conf.getUser(),
-        buildInitializeTaskArgs(context), context.env.workDir, context.env.env);
-  }
-
-  /**
-   * Builds the args to be passed to task-controller for enabling of task for
-   * cleanup. Last arg in this List is either $attemptId or $attemptId/work
-   */
-  private List<String> buildTaskCleanupArgs(
-      TaskControllerTaskPathDeletionContext context) {
-    List<String> commandArgs = new ArrayList<String>(3);
-    commandArgs.add(context.mapredLocalDir.toUri().getPath());
-    commandArgs.add(context.task.getJobID().toString());
-
-    String workDir = "";
-    if (context.isWorkDir) {
-      workDir = "/work";
-    }
-    if (context.task.isTaskCleanupTask()) {
-      commandArgs.add(context.task.getTaskID() + TaskTracker.TASK_CLEANUP_SUFFIX
-                      + workDir);
-    } else {
-      commandArgs.add(context.task.getTaskID() + workDir);
-    }
-
-    return commandArgs;
-  }
-
-  /**
-   * Builds the args to be passed to task-controller for enabling of job for
-   * cleanup. Last arg in this List is $jobid.
-   */
-  private List<String> buildJobCleanupArgs(
-      TaskControllerJobPathDeletionContext context) {
-    List<String> commandArgs = new ArrayList<String>(2);
-    commandArgs.add(context.mapredLocalDir.toUri().getPath());
-    commandArgs.add(context.jobId.toString());
-
-    return commandArgs;
-  }
-  
-  /**
-   * Enables the task for cleanup by changing permissions of the specified path
-   * in the local filesystem
-   */
-  @Override
-  void enableTaskForCleanup(PathDeletionContext context)
-      throws IOException {
-    if (context instanceof TaskControllerTaskPathDeletionContext) {
-      TaskControllerTaskPathDeletionContext tContext =
-        (TaskControllerTaskPathDeletionContext) context;
-      enablePathForCleanup(tContext, 
-                           TaskControllerCommands.ENABLE_TASK_FOR_CLEANUP,
-                           buildTaskCleanupArgs(tContext));
-    }
-    else {
-      throw new IllegalArgumentException("PathDeletionContext provided is not "
-          + "TaskControllerTaskPathDeletionContext.");
-    }
-  }
-
-  /**
-   * Enables the job for cleanup by changing permissions of the specified path
-   * in the local filesystem
-   */
   @Override
-  void enableJobForCleanup(PathDeletionContext context)
-      throws IOException {
-    if (context instanceof TaskControllerJobPathDeletionContext) {
-      TaskControllerJobPathDeletionContext tContext =
-        (TaskControllerJobPathDeletionContext) context;
-      enablePathForCleanup(tContext, 
-                           TaskControllerCommands.ENABLE_JOB_FOR_CLEANUP,
-                           buildJobCleanupArgs(tContext));
-    } else {
-      throw new IllegalArgumentException("PathDeletionContext provided is not "
-                  + "TaskControllerJobPathDeletionContext.");
-    }
-  }
-  
-  /**
-   * Enable a path for cleanup
-   * @param c {@link TaskControllerPathDeletionContext} for the path to be 
-   *          cleaned up
-   * @param command {@link TaskControllerCommands} for task/job cleanup
-   * @param cleanupArgs arguments for the {@link LinuxTaskController} to enable 
-   *                    path cleanup
-   */
-  private void enablePathForCleanup(TaskControllerPathDeletionContext c,
-                                    TaskControllerCommands command,
-                                    List<String> cleanupArgs) {
-    if (LOG.isDebugEnabled()) {
-      LOG.debug("Going to do " + command.toString() + " for " + c.fullPath);
-    }
-
-    if ( c.user != null && c.fs instanceof LocalFileSystem) {
-      try {
-        runCommand(command, c.user, cleanupArgs, null, null);
-      } catch(IOException e) {
-        LOG.warn("Unable to change permissions for " + c.fullPath);
-      }
-    }
-    else {
-      throw new IllegalArgumentException("Either user is null or the " 
-                  + "file system is not local file system.");
-    }
-  }
-
-  private void logOutput(String output) {
-    String shExecOutput = output;
-    if (shExecOutput != null) {
-      for (String str : shExecOutput.split("\n")) {
-        LOG.info(str);
-      }
-    }
-  }
-
-  private String getJobId(TaskExecContext context) {
-    String taskId = context.task.getTaskID().toString();
-    TaskAttemptID tId = TaskAttemptID.forName(taskId);
-    String jobId = tId.getJobID().toString();
-    return jobId;
-  }
-
-  /**
-   * Returns list of arguments to be passed while launching task VM.
-   * See {@code buildTaskControllerExecutor(TaskControllerCommands, 
-   * String, List<String>, JvmEnv)} documentation.
-   * @param context
-   * @return Argument to be used while launching Task VM
-   */
-  private List<String> buildLaunchTaskArgs(TaskExecContext context, 
-      File workDir) {
-    List<String> commandArgs = new ArrayList<String>(3);
-    LOG.debug("getting the task directory as: " 
-        + getTaskCacheDirectory(context, workDir));
-    LOG.debug("getting the tt_root as " +getDirectoryChosenForTask(
-        new File(getTaskCacheDirectory(context, workDir)), 
-        context) );
-    commandArgs.add(getDirectoryChosenForTask(
-        new File(getTaskCacheDirectory(context, workDir)), 
-        context));
-    commandArgs.addAll(buildInitializeTaskArgs(context));
-    return commandArgs;
-  }
-
-  // Get the directory from the list of directories configured
-  // in Configs.LOCAL_DIR chosen for storing data pertaining to
-  // this task.
-  private String getDirectoryChosenForTask(File directory,
-      TaskExecContext context) {
-    String jobId = getJobId(context);
-    String taskId = context.task.getTaskID().toString();
-    for (String dir : mapredLocalDirs) {
-      File mapredDir = new File(dir);
-      File taskDir =
-          new File(mapredDir, TaskTracker.getTaskWorkDir(context.task
-              .getUser(), jobId, taskId, context.task.isTaskCleanupTask()))
-              .getParentFile();
-      if (directory.equals(taskDir)) {
-        return dir;
-      }
-    }
-
-    LOG.error("Couldn't parse task cache directory correctly");
-    throw new IllegalArgumentException("invalid task cache directory "
-        + directory.getAbsolutePath());
-  }
-
-  /**
-   * Builds the command line for launching/terminating/killing task JVM.
-   * Following is the format for launching/terminating/killing task JVM
-   * <br/>
-   * For launching following is command line argument:
-   * <br/>
-   * {@code mapreduce.job.user.name command tt-root job_id task_id} 
-   * <br/>
-   * For terminating/killing task jvm.
-   * {@code mapreduce.job.user.name command tt-root task-pid}
-   * 
-   * @param command command to be executed.
-   * @param userName mapreduce.job.user.name
-   * @param cmdArgs list of extra arguments
-   * @param workDir working directory for the task-controller
-   * @param env JVM environment variables.
-   * @return {@link ShellCommandExecutor}
-   * @throws IOException
-   */
-  private ShellCommandExecutor buildTaskControllerExecutor(
-      TaskControllerCommands command, String userName, List<String> cmdArgs,
-      File workDir, Map<String, String> env)
-      throws IOException {
-    String[] taskControllerCmd = new String[3 + cmdArgs.size()];
-    taskControllerCmd[0] = getTaskControllerExecutablePath();
-    taskControllerCmd[1] = userName;
-    taskControllerCmd[2] = String.valueOf(command.ordinal());
-    int i = 3;
-    for (String cmdArg : cmdArgs) {
-      taskControllerCmd[i++] = cmdArg;
+  public void deleteAsUser(String user, String subDir, String... baseDirs) 
+  throws IOException {
+    List<String> command = new ArrayList<String>(
+        Arrays.asList(
+                   taskControllerExe, 
+                   user,
+                   Integer.toString(Commands.DELETE_AS_USER.getValue()),
+                   subDir));
+    for (String baseDir : baseDirs) {
+      command.add(baseDir);
     }
+    String[] commandArray = command.toArray(new String[0]);
+    ShellCommandExecutor shExec = new ShellCommandExecutor(commandArray);
     if (LOG.isDebugEnabled()) {
-      for (String cmd : taskControllerCmd) {
-        LOG.debug("taskctrl command = " + cmd);
-      }
-    }
-    ShellCommandExecutor shExec = null;
-    if(workDir != null && workDir.exists()) {
-      shExec = new ShellCommandExecutor(taskControllerCmd,
-          workDir, env);
-    } else {
-      shExec = new ShellCommandExecutor(taskControllerCmd);
-    }
-    
-    return shExec;
-  }
-  
-  // Return the task specific directory under the cache.
-  private String getTaskCacheDirectory(TaskExecContext context, 
-      File workDir) {
-    // In the case of JVM reuse, the task specific directory
-    // is different from what is set with respect with
-    // env.workDir. Hence building this from the taskId everytime.
-    String taskId = context.task.getTaskID().toString();
-    File cacheDirForJob = workDir.getParentFile().getParentFile();
-    if(context.task.isTaskCleanupTask()) {
-      taskId = taskId + TaskTracker.TASK_CLEANUP_SUFFIX;
+      LOG.debug("deleteAsUser: " + Arrays.toString(commandArray));
     }
-    return new File(cacheDirForJob, taskId).getAbsolutePath(); 
-  }
-  
-  // Write the JVM command line to a file under the specified directory
-  // Note that the JVM will be launched using a setuid executable, and
-  // could potentially contain strings defined by a user. Hence, to
-  // prevent special character attacks, we write the command line to
-  // a file and execute it.
-  private void writeCommand(String cmdLine, 
-                                      String directory) throws IOException {
-    
-    PrintWriter pw = null;
-    String commandFile = directory + File.separator + COMMAND_FILE;
-    LOG.info("Writing commands to " + commandFile);
-    LOG.info("--------Commands Begin--------");
-    LOG.info(cmdLine);
-    LOG.info("--------Commands End--------");
-    try {
-      FileWriter fw = new FileWriter(commandFile);
-      BufferedWriter bw = new BufferedWriter(fw);
-      pw = new PrintWriter(bw);
-      pw.write(cmdLine);
-    } catch (IOException ioe) {
-      LOG.error("Caught IOException while writing JVM command line to file. "
-                + ioe.getMessage());
-    } finally {
-      if (pw != null) {
-        pw.close();
-      }
-      // set execute permissions for all on the file.
-      File f = new File(commandFile);
-      if (f.exists()) {
-        f.setReadable(true, false);
-        f.setExecutable(true, false);
-      }
-    }
-  }
-
-  private List<String> buildInitializeJobCommandArgs(
-      JobInitializationContext context) {
-    List<String> initJobCmdArgs = new ArrayList<String>();
-    initJobCmdArgs.add(context.jobid.toString());
-    return initJobCmdArgs;
-  }
-
-  @Override
-  void initializeJob(JobInitializationContext context)
-      throws IOException {
-    LOG.debug("Going to initialize job " + context.jobid.toString()
-        + " on the TT");
-    runCommand(TaskControllerCommands.INITIALIZE_JOB, context.user,
-        buildInitializeJobCommandArgs(context), context.workDir, null);
+    shExec.execute();
   }
 
   @Override
-  public void initializeDistributedCacheFile(DistributedCacheFileContext context)
-      throws IOException {
+  public void deleteLogAsUser(String user, String subDir) throws IOException {
+    String[] command = 
+      new String[]{taskControllerExe, 
+                   user,
+                   Integer.toString(Commands.DELETE_LOG_AS_USER.getValue()),
+                   subDir};
+    ShellCommandExecutor shExec = new ShellCommandExecutor(command);
     if (LOG.isDebugEnabled()) {
-      LOG.debug("Going to initialize distributed cache for " + context.user
-          + " with localizedBaseDir " + context.localizedBaseDir + 
-          " and uniqueString " + context.uniqueString);
+      LOG.debug("deleteLogAsUser: " + Arrays.toString(command));
     }
-    List<String> args = new ArrayList<String>();
-    // Here, uniqueString might start with '-'. Adding -- in front of the 
-    // arguments indicates that they are non-option parameters.
-    args.add("--");
-    args.add(context.localizedBaseDir.toString());
-    args.add(context.uniqueString);
-    runCommand(TaskControllerCommands.INITIALIZE_DISTRIBUTEDCACHE_FILE, 
-        context.user, args, context.workDir, null);
+    shExec.execute();
   }
 
   @Override
-  public void initializeUser(InitializationContext context)
-      throws IOException {
-    LOG.debug("Going to initialize user directories for " + context.user
-        + " on the TT");
-    runCommand(TaskControllerCommands.INITIALIZE_USER, context.user,
-        new ArrayList<String>(), context.workDir, null);
-  }
-
-  /**
-   * API which builds the command line to be pass to LinuxTaskController
-   * binary to terminate/kill the task. See 
-   * {@code buildTaskControllerExecutor(TaskControllerCommands, 
-   * String, List<String>, JvmEnv)} documentation.
-   * 
-   * 
-   * @param context context of task which has to be passed kill signal.
-   * 
-   */
-  private List<String> buildKillTaskCommandArgs(TaskControllerContext 
-      context){
-    List<String> killTaskJVMArgs = new ArrayList<String>();
-    killTaskJVMArgs.add(context.pid);
-    return killTaskJVMArgs;
-  }
-  
-  /**
-   * Convenience method used to sending appropriate signal to the task
-   * VM
-   * @param context
-   * @param command
-   * @throws IOException
-   */
-  protected void signalTask(TaskControllerContext context,
-      TaskControllerCommands command) throws IOException{
-    if(context.task == null) {
-      LOG.info("Context task is null; not signaling the JVM");
-      return;
+  public boolean signalTask(String user, int taskPid, 
+                         Signal signal) throws IOException {
+    String[] command = 
+      new String[]{taskControllerExe, 
+                   user,
+                   Integer.toString(Commands.SIGNAL_TASK.getValue()),
+                   Integer.toString(taskPid),
+                   Integer.toString(signal.getValue())};
+    ShellCommandExecutor shExec = new ShellCommandExecutor(command);
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("signalTask: " + Arrays.toString(command));
     }
-    ShellCommandExecutor shExec = buildTaskControllerExecutor(
-        command, context.env.conf.getUser(), 
-        buildKillTaskCommandArgs(context), context.env.workDir,
-        context.env.env);
     try {
       shExec.execute();
-    } catch (Exception e) {
-      LOG.warn("Output from task-contoller is : " + shExec.getOutput());
-      throw new IOException(e);
-    }
-  }
-  
-  @Override
-  void terminateTask(TaskControllerContext context) {
-    try {
-      signalTask(context, TaskControllerCommands.TERMINATE_TASK_JVM);
-    } catch (Exception e) {
-      LOG.warn("Exception thrown while sending kill to the Task VM " + 
-          StringUtils.stringifyException(e));
-    }
-  }
-  
-  @Override
-  void killTask(TaskControllerContext context) {
-    try {
-      signalTask(context, TaskControllerCommands.KILL_TASK_JVM);
-    } catch (Exception e) {
-      LOG.warn("Exception thrown while sending destroy to the Task VM " + 
-          StringUtils.stringifyException(e));
-    }
-  }
-
-  @Override
-  void dumpTaskStack(TaskControllerContext context) {
-    try {
-      signalTask(context, TaskControllerCommands.SIGQUIT_TASK_JVM);
-    } catch (Exception e) {
-      LOG.warn("Exception thrown while sending SIGQUIT to the Task VM " +
-          StringUtils.stringifyException(e));
+    } catch (ExitCodeException e) {
+      int ret_code = shExec.getExitCode();
+      if (ret_code == ResultCode.INVALID_TASK_PID.getValue()) {
+        return false;
+      }
+      logOutput(shExec.getOutput());
+      throw new IOException("Problem signalling task " + taskPid + " with " +
+                            signal + "; exit = " + ret_code);
     }
-  }
-
-  protected String getTaskControllerExecutablePath() {
-    return taskControllerExe;
+    return true;
   }
 
   @Override
-  String getRunAsUser(JobConf conf) {
+  public String getRunAsUser(JobConf conf) {
     return conf.getUser();
   }
-}
+}

+ 17 - 10
mapreduce/src/java/org/apache/hadoop/mapred/LocalJobRunner.java

@@ -38,7 +38,6 @@ import org.apache.hadoop.classification.InterfaceAudience;
 import org.apache.hadoop.classification.InterfaceStability;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.fs.FileSystem;
-import org.apache.hadoop.fs.LocalDirAllocator;
 import org.apache.hadoop.fs.Path;
 import org.apache.hadoop.io.Text;
 import org.apache.hadoop.mapreduce.ClusterMetrics;
@@ -79,6 +78,7 @@ public class LocalJobRunner implements ClientProtocol {
   private AtomicInteger map_tasks = new AtomicInteger(0);
   private int reduce_tasks = 0;
   final Random rand = new Random();
+  private final TaskController taskController = new DefaultTaskController();
   
   private JobTrackerInstrumentation myMetrics = null;
 
@@ -116,7 +116,7 @@ public class LocalJobRunner implements ClientProtocol {
     private FileSystem localFs;
     boolean killed = false;
     
-    private TrackerDistributedCacheManager trackerDistributerdCacheManager;
+    private TrackerDistributedCacheManager trackerDistributedCacheManager;
     private TaskDistributedCacheManager taskDistributedCacheManager;
 
     public long getProtocolVersion(String protocol, long clientVersion) {
@@ -134,14 +134,12 @@ public class LocalJobRunner implements ClientProtocol {
 
       // Manage the distributed cache.  If there are files to be copied,
       // this will trigger localFile to be re-written again.
-      this.trackerDistributerdCacheManager =
-          new TrackerDistributedCacheManager(conf, new DefaultTaskController());
+      this.trackerDistributedCacheManager =
+        new TrackerDistributedCacheManager(conf);
       this.taskDistributedCacheManager = 
-          trackerDistributerdCacheManager.newTaskDistributedCacheManager(conf);
-      taskDistributedCacheManager.setup(
-          new LocalDirAllocator(MRConfig.LOCAL_DIR), 
-          new File(systemJobDir.toString()),
-          "archive", "archive");
+        trackerDistributedCacheManager.newTaskDistributedCacheManager(
+            jobid, conf);
+      taskDistributedCacheManager.setupCache(conf, "archive", "archive");
       
       if (DistributedCache.getSymlink(conf)) {
         // This is not supported largely because, 
@@ -458,7 +456,7 @@ public class LocalJobRunner implements ClientProtocol {
           localFs.delete(localJobFile, true);              // delete local copy
           // Cleanup distributed cache
           taskDistributedCacheManager.release();
-          trackerDistributerdCacheManager.purgeCache();
+          trackerDistributedCacheManager.purgeCache();
         } catch (IOException e) {
           LOG.warn("Error cleaning up "+id+": "+e);
         }
@@ -532,6 +530,14 @@ public class LocalJobRunner implements ClientProtocol {
     public boolean ping(TaskAttemptID taskid) throws IOException {
       return true;
     }
+
+    @Override
+    public void updatePrivateDistributedCacheSizes(
+                                                   org.apache.hadoop.mapreduce.JobID jobId,
+                                                   long[] sizes)
+                                                                throws IOException {
+      trackerDistributedCacheManager.setArchiveSizes(jobId, sizes);
+    }
     
     public boolean canCommit(TaskAttemptID taskid) 
     throws IOException {
@@ -578,6 +584,7 @@ public class LocalJobRunner implements ClientProtocol {
     this.fs = FileSystem.getLocal(conf);
     this.conf = conf;
     myMetrics = new JobTrackerMetricsInst(null, new JobConf(conf));
+    taskController.setConf(conf);
   }
 
   // JobSubmissionProtocol methods

+ 4 - 2
mapreduce/src/java/org/apache/hadoop/mapred/MapTask.java

@@ -129,8 +129,10 @@ class MapTask extends Task {
   
   @Override
   public TaskRunner createRunner(TaskTracker tracker, 
-      TaskTracker.TaskInProgress tip) {
-    return new MapTaskRunner(tip, tracker, this.conf);
+                                 TaskTracker.TaskInProgress tip,
+                                 TaskTracker.RunningJob rjob
+                                 ) throws IOException {
+    return new MapTaskRunner(tip, tracker, this.conf, rjob);
   }
 
   @Override

+ 5 - 2
mapreduce/src/java/org/apache/hadoop/mapred/MapTaskRunner.java

@@ -17,14 +17,17 @@
  */
 package org.apache.hadoop.mapred;
 
+import java.io.IOException;
+
 import org.apache.hadoop.mapred.TaskTracker.TaskInProgress;
 import org.apache.log4j.Level;
 
 /** Runs a map task. */
 class MapTaskRunner extends TaskRunner {
   
-  public MapTaskRunner(TaskInProgress task, TaskTracker tracker, JobConf conf) {
-    super(task, tracker, conf);
+  public MapTaskRunner(TaskInProgress task, TaskTracker tracker, JobConf conf,
+      TaskTracker.RunningJob rjob) throws IOException {
+    super(task, tracker, conf, rjob);
   }
   
   @Override

+ 4 - 3
mapreduce/src/java/org/apache/hadoop/mapred/ReduceTask.java

@@ -141,9 +141,10 @@ public class ReduceTask extends Task {
   }
 
   @Override
-  public TaskRunner createRunner(TaskTracker tracker, TaskInProgress tip) 
-  throws IOException {
-    return new ReduceTaskRunner(tip, tracker, this.conf);
+  public TaskRunner createRunner(TaskTracker tracker, TaskInProgress tip,
+                                 TaskTracker.RunningJob rjob
+                                 ) throws IOException {
+    return new ReduceTaskRunner(tip, tracker, this.conf, rjob);
   }
 
   @Override

+ 3 - 2
mapreduce/src/java/org/apache/hadoop/mapred/ReduceTaskRunner.java

@@ -26,9 +26,10 @@ import org.apache.log4j.Level;
 class ReduceTaskRunner extends TaskRunner {
 
   public ReduceTaskRunner(TaskInProgress task, TaskTracker tracker, 
-                          JobConf conf) throws IOException {
+                          JobConf conf, TaskTracker.RunningJob rjob
+                          ) throws IOException {
     
-    super(task, tracker, conf);
+    super(task, tracker, conf, rjob);
   }
 
   public void close() throws IOException {

+ 3 - 1
mapreduce/src/java/org/apache/hadoop/mapred/Task.java

@@ -468,7 +468,9 @@ abstract public class Task implements Writable, Configurable {
   /** Return an approprate thread runner for this task. 
    * @param tip TODO*/
   public abstract TaskRunner createRunner(TaskTracker tracker, 
-      TaskTracker.TaskInProgress tip) throws IOException;
+                                          TaskTracker.TaskInProgress tip, 
+                                          TaskTracker.RunningJob rjob
+                                          ) throws IOException;
 
   /** The number of milliseconds between progress reports. */
   public static final int PROGRESS_INTERVAL = 3000;

+ 202 - 357
mapreduce/src/java/org/apache/hadoop/mapred/TaskController.java

@@ -14,11 +14,13 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
- */
+*/
 package org.apache.hadoop.mapred;
 
 import java.io.File;
 import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.InetSocketAddress;
 import java.util.List;
 
 import org.apache.commons.logging.Log;
@@ -26,16 +28,12 @@ import org.apache.commons.logging.LogFactory;
 import org.apache.hadoop.conf.Configurable;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.LocalDirAllocator;
 import org.apache.hadoop.fs.Path;
 import org.apache.hadoop.fs.permission.FsPermission;
-import org.apache.hadoop.mapred.CleanupQueue.PathDeletionContext;
-import org.apache.hadoop.mapred.JvmManager.JvmEnv;
+
 import org.apache.hadoop.mapreduce.MRConfig;
-import org.apache.hadoop.util.DiskChecker;
-import org.apache.hadoop.util.StringUtils;
 import org.apache.hadoop.util.Shell.ShellCommandExecutor;
-import org.apache.hadoop.classification.InterfaceAudience;
-import org.apache.hadoop.classification.InterfaceStability;
 
 /**
  * Controls initialization, finalization and clean up of tasks, and
@@ -47,402 +45,249 @@ import org.apache.hadoop.classification.InterfaceStability;
  * performing the actual actions.
  * 
  * <br/>
+ * 
+ * NOTE: This class is internal only class and not intended for users!!
  */
-@InterfaceAudience.Private
 public abstract class TaskController implements Configurable {
   
+  /**
+   * The constants for the signals.
+   */
+  public enum Signal {
+    NULL(0, "NULL"), QUIT(3, "SIGQUIT"), 
+    KILL(9, "SIGKILL"), TERM(15, "SIGTERM");
+    private final int value;
+    private final String str;
+    private Signal(int value, String str) {
+      this.str = str;
+      this.value = value;
+    }
+    public int getValue() {
+      return value;
+    }
+    @Override
+    public String toString() {
+      return str;
+    }
+  }
+
   private Configuration conf;
-  
+
   public static final Log LOG = LogFactory.getLog(TaskController.class);
   
+  //Name of the executable script that will contain the child
+  // JVM command line. See writeCommand for details.
+  protected static final String COMMAND_FILE = "taskjvm.sh";
+  
+  protected LocalDirAllocator allocator;
+
+  final public static FsPermission TASK_LAUNCH_SCRIPT_PERMISSION =
+  FsPermission.createImmutable((short) 0700); // rwx--------
+  
   public Configuration getConf() {
     return conf;
   }
 
-  // The list of directory paths specified in the variable Configs.LOCAL_DIR
-  // This is used to determine which among the list of directories is picked up
-  // for storing data for a particular task.
-  protected String[] mapredLocalDirs;
-
   public void setConf(Configuration conf) {
     this.conf = conf;
-    mapredLocalDirs = conf.getTrimmedStrings(MRConfig.LOCAL_DIR);
   }
 
   /**
-   * Sets up the permissions of the following directories on all the configured
-   * disks:
-   * <ul>
-   * <li>mapreduce.cluster.local.directories</li>
-   * <li>Hadoop log directories</li>
-   * </ul>
+   * Does initialization and setup.
+   * @param allocator the local dir allocator to use
    */
-  public void setup() throws IOException {
-    FileSystem localFs = FileSystem.getLocal(conf);
-
-    for (String localDir : this.mapredLocalDirs) {
-      // Set up the mapreduce.cluster.local.directories.
-      File mapredlocalDir = new File(localDir);
-      if (!mapredlocalDir.isDirectory() && !mapredlocalDir.mkdirs()) {
-        LOG.warn("Unable to create mapreduce.cluster.local.directory : "
-            + mapredlocalDir.getPath());
-      } else {
-        localFs.setPermission(new Path(mapredlocalDir.getCanonicalPath()),
-                              new FsPermission((short)0755));
-      }
-    }
-
-    // Set up the user log directory
-    File taskLog = TaskLog.getUserLogDir();
-    if (!taskLog.isDirectory() && !taskLog.mkdirs()) {
-      LOG.warn("Unable to create taskLog directory : " + taskLog.getPath());
-    } else {
-      localFs.setPermission(new Path(taskLog.getCanonicalPath()),
-                            new FsPermission((short)0755));
-    }
-    DiskChecker.checkDir(TaskLog.getUserLogDir());
-  }
-
+  public abstract void setup(LocalDirAllocator allocator) throws IOException;
+  
   /**
-   * Take task-controller specific actions to initialize job. This involves
-   * setting appropriate permissions to job-files so as to secure the files to
-   * be accessible only by the user's tasks.
-   * 
+   * Create all of the directories necessary for the job to start and download
+   * all of the job and private distributed cache files.
+   * Creates both the user directories and the job log directory.
+   * @param user the user name
+   * @param jobid the job
+   * @param credentials a filename containing the job secrets
+   * @param jobConf the path to the localized configuration file
+   * @param taskTracker the connection the task tracker
+   * @param ttAddr the tasktracker's RPC address
    * @throws IOException
    */
-  abstract void initializeJob(JobInitializationContext context) throws IOException;
-
+  public abstract void initializeJob(String user, String jobid, 
+                                     Path credentials, Path jobConf,
+                                     TaskUmbilicalProtocol taskTracker,
+                                     InetSocketAddress ttAddr) 
+  throws IOException, InterruptedException;
+  
   /**
-   * Take task-controller specific actions to initialize the distributed cache
-   * file. This involves setting appropriate permissions for these files so as
-   * to secure them to be accessible only their owners.
-   * 
-   * @param context
+   * Create all of the directories for the task and launches the child jvm.
+   * @param user the user name
+   * @param jobId the jobId in question
+   * @param attemptId the attempt id (cleanup attempts have .cleanup suffix)
+   * @param setup list of shell commands to execute before the jvm
+   * @param jvmArguments list of jvm arguments
+   * @param currentWorkDirectory the full path of the cwd for the task
+   * @param stdout the file to redirect stdout to
+   * @param stderr the file to redirect stderr to
+   * @return the exit code for the task
    * @throws IOException
    */
-  public abstract void initializeDistributedCacheFile(DistributedCacheFileContext context)
-      throws IOException;
-
-  /**
-   * Launch a task JVM
-   * 
-   * This method defines how a JVM will be launched to run a task. Each
-   * task-controller should also do an
-   * {@link #initializeTask(TaskControllerContext)} inside this method so as to
-   * initialize the task before launching it. This is for reasons of
-   * task-controller specific optimizations w.r.t combining initialization and
-   * launching of tasks.
-   * 
-   * @param context the context associated to the task
-   */
-  abstract void launchTaskJVM(TaskControllerContext context)
-                                      throws IOException;
-
+  public abstract
+  int launchTask(String user, 
+                 String jobId,
+                 String attemptId,
+                 List<String> setup,
+                 List<String> jvmArguments,
+                 File currentWorkDirectory,
+                 String stdout,
+                 String stderr) throws IOException;
+  
   /**
-   * Top level cleanup a task JVM method.
-   * <ol>
-   * <li>Sends a graceful termiante signal to task JVM to allow subprocesses
-   * to cleanup.</li>
-   * <li>Sends a forceful kill signal to task JVM, terminating all its
-   * sub-processes forcefully.</li>
-   * </ol>
-   *
-   * @param context the task for which kill signal has to be sent.
+   * Send a signal to a task pid as the user. Always signal the process group.
+   * An implementation may elect to signal the pid directly if the former is
+   * unavailable or fails.
+   * @param user the user name
+   * @param taskPid the pid of the task
+   * @param signal the id of the signal to send
+   * @return false if the process does not exist
+   * @throws IOException If the task controller failed to signal the process
+   * (group), but the process exists.
    */
-  final void destroyTaskJVM(TaskControllerContext context) {
-    // Send SIGTERM to try to ask for a polite exit.
-    terminateTask(context);
-
-    try {
-      Thread.sleep(context.sleeptimeBeforeSigkill);
-    } catch (InterruptedException e) {
-      LOG.warn("Sleep interrupted : " +
-          StringUtils.stringifyException(e));
-    }
-
-    killTask(context);
-  }
-
-  /** Perform initializing actions required before a task can run.
-    * 
-    * For instance, this method can be used to setup appropriate
-    * access permissions for files and directories that will be
-    * used by tasks. Tasks use the job cache, log, and distributed cache
-    * directories and files as part of their functioning. Typically,
-    * these files are shared between the daemon and the tasks
-    * themselves. So, a TaskController that is launching tasks
-    * as different users can implement this method to setup
-    * appropriate ownership and permissions for these directories
-    * and files.
-    */
-  abstract void initializeTask(TaskControllerContext context)
-      throws IOException;
-
-  static class TaskExecContext {
-    // task being executed
-    Task task;
-  }
+  public abstract boolean signalTask(String user, int taskPid, 
+                                  Signal signal) throws IOException;
+  
   /**
-   * Contains task information required for the task controller.  
+   * Delete the user's files under all of the task tracker root directories.
+   * @param user the user name
+   * @param subDir the path relative to base directories
+   * @param baseDirs the base directories (absolute paths)
+   * @throws IOException
    */
-  static class TaskControllerContext extends TaskExecContext {
-    ShellCommandExecutor shExec;     // the Shell executor executing the JVM for this task.
-
-    // Information used only when this context is used for launching new tasks.
-    JvmEnv env;     // the JVM environment for the task.
-
-    // Information used only when this context is used for destroying a task jvm.
-    String pid; // process handle of task JVM.
-    long sleeptimeBeforeSigkill; // waiting time before sending SIGKILL to task JVM after sending SIGTERM
-  }
-
+  public abstract void deleteAsUser(String user, 
+                                    String subDir,
+                                    String... baseDirs) throws IOException;
+  
   /**
-   * Contains info related to the path of the file/dir to be deleted. This info
-   * is needed by task-controller to build the full path of the file/dir
+   * Delete the user's files under the userlogs directory.
+   * @param user the user to work as
+   * @param subDir the path under the userlogs directory.
+   * @throws IOException
    */
-  static abstract class TaskControllerPathDeletionContext 
-  extends PathDeletionContext {
-    TaskController taskController;
-    String user;
-
-    /**
-     * mapredLocalDir is the base dir under which to-be-deleted jobLocalDir, 
-     * taskWorkDir or taskAttemptDir exists. fullPath of jobLocalDir, 
-     * taskAttemptDir or taskWorkDir is built using mapredLocalDir, jobId, 
-     * taskId, etc.
-     */
-    Path mapredLocalDir;
-
-    public TaskControllerPathDeletionContext(FileSystem fs, Path mapredLocalDir,
-                                             TaskController taskController,
-                                             String user) {
-      super(fs, null);
-      this.taskController = taskController;
-      this.mapredLocalDir = mapredLocalDir;
+  public abstract void deleteLogAsUser(String user, 
+                                       String subDir) throws IOException;
+  
+  static class DeletionContext extends CleanupQueue.PathDeletionContext {
+    private TaskController controller;
+    private boolean isLog;
+    private String user;
+    private String subDir;
+    private String[] baseDirs;
+    DeletionContext(TaskController controller, boolean isLog, String user, 
+                    String subDir, String[] baseDirs) {
+      super(null, null);
+      this.controller = controller;
+      this.isLog = isLog;
       this.user = user;
-    }
-
-    @Override
-    protected String getPathForCleanup() {
-      if (fullPath == null) {
-        fullPath = buildPathForDeletion();
-      }
-      return fullPath;
-    }
-
-    /**
-     * Return the component of the path under the {@link #mapredLocalDir} to be 
-     * cleaned up. Its the responsibility of the class that extends 
-     * {@link TaskControllerPathDeletionContext} to provide the correct 
-     * component. For example 
-     *  - For task related cleanups, either the task-work-dir or task-local-dir
-     *    might be returned depending on jvm reuse.
-     *  - For job related cleanup, simply the job-local-dir might be returned.
-     */
-    abstract protected String getPath();
-    
-    /**
-     * Builds the path of taskAttemptDir OR taskWorkDir based on
-     * mapredLocalDir, jobId, taskId, etc
-     */
-    String buildPathForDeletion() {
-      return mapredLocalDir.toUri().getPath() + Path.SEPARATOR + getPath();
-    }
-  }
-
-  /** Contains info related to the path of the file/dir to be deleted. This info
-   * is needed by task-controller to build the full path of the task-work-dir or
-   * task-local-dir depending on whether the jvm is reused or not.
-   */
-  static class TaskControllerTaskPathDeletionContext 
-  extends TaskControllerPathDeletionContext {
-    final Task task;
-    final boolean isWorkDir;
-    
-    public TaskControllerTaskPathDeletionContext(FileSystem fs, 
-        Path mapredLocalDir, Task task, boolean isWorkDir, 
-        TaskController taskController) {
-      super(fs, mapredLocalDir, taskController, task.getUser());
-      this.task = task;
-      this.isWorkDir = isWorkDir;
+      this.subDir = subDir;
+      this.baseDirs = baseDirs;
     }
     
-    /**
-     * Returns the taskWorkDir or taskLocalDir based on whether 
-     * {@link TaskControllerTaskPathDeletionContext} is configured to delete
-     * the workDir.
-     */
     @Override
-    protected String getPath() {
-      String subDir = (isWorkDir) ? TaskTracker.getTaskWorkDir(task.getUser(),
-          task.getJobID().toString(), task.getTaskID().toString(),
-          task.isTaskCleanupTask())
-        : TaskTracker.getLocalTaskDir(task.getUser(),
-          task.getJobID().toString(), task.getTaskID().toString(),
-          task.isTaskCleanupTask());
-      return subDir;
-    }
-
-    /**
-     * Makes the path(and its subdirectories recursively) fully deletable by
-     * setting proper permissions(770) by task-controller
-     */
-    @Override
-    protected void enablePathForCleanup() throws IOException {
-      getPathForCleanup();// allow init of fullPath, if not inited already
-      if (fs.exists(new Path(fullPath))) {
-        taskController.enableTaskForCleanup(this);
+    protected void deletePath() throws IOException {
+      if (isLog) {
+        controller.deleteLogAsUser(user, subDir);
+      } else {
+        controller.deleteAsUser(user, subDir, baseDirs);
       }
     }
-  }
 
-  /** Contains info related to the path of the file/dir to be deleted. This info
-   * is needed by task-controller to build the full path of the job-local-dir.
-   */
-  static class TaskControllerJobPathDeletionContext 
-  extends TaskControllerPathDeletionContext {
-    final JobID jobId;
-    
-    public TaskControllerJobPathDeletionContext(FileSystem fs, 
-        Path mapredLocalDir, JobID id, String user, 
-        TaskController taskController) {
-      super(fs, mapredLocalDir, taskController, user);
-      this.jobId = id;
-    }
-    
-    /**
-     * Returns the jobLocalDir of the job to be cleaned up.
-     */
-    @Override
-    protected String getPath() {
-      return TaskTracker.getLocalJobDir(user, jobId.toString());
-    }
-    
-    /**
-     * Makes the path(and its sub-directories recursively) fully deletable by
-     * setting proper permissions(770) by task-controller
-     */
     @Override
-    protected void enablePathForCleanup() throws IOException {
-      getPathForCleanup();// allow init of fullPath, if not inited already
-      if (fs.exists(new Path(fullPath))) {
-        taskController.enableJobForCleanup(this);
-      }
+    public String toString() {
+      return (isLog ? "log(" : "dir(") +
+        user + "," + subDir + ")";
     }
   }
   
-  @InterfaceAudience.Private
-  @InterfaceStability.Unstable
-  public static class InitializationContext {
-    public File workDir;
-    public String user;
-    
-    public InitializationContext() {
-    }
-    
-    public InitializationContext(String user, File workDir) {
-      this.user = user;
-      this.workDir = workDir;
+   /**
+    * Returns the local unix user that a given job will run as.
+    */
+   public String getRunAsUser(JobConf conf) {
+     return System.getProperty("user.name");
+   }
+
+  //Write the JVM command line to a file under the specified directory
+  // Note that the JVM will be launched using a setuid executable, and
+  // could potentially contain strings defined by a user. Hence, to
+  // prevent special character attacks, we write the command line to
+  // a file and execute it.
+  protected static String writeCommand(String cmdLine, FileSystem fs,
+      Path commandFile) throws IOException {
+    PrintWriter pw = null;
+    LOG.info("Writing commands to " + commandFile);
+    try {
+      pw = new PrintWriter(FileSystem.create(
+            fs, commandFile, TASK_LAUNCH_SCRIPT_PERMISSION));
+      pw.write(cmdLine);
+    } catch (IOException ioe) {
+      LOG.error("Caught IOException while writing JVM command line to file. ",
+          ioe);
+    } finally {
+      if (pw != null) {
+        pw.close();
+      }
     }
+    return commandFile.makeQualified(fs).toUri().getPath();
   }
   
-  /**
-   * This is used for initializing the private localized files in distributed
-   * cache. Initialization would involve changing permission, ownership and etc.
-   */
-  @InterfaceAudience.Private
-  @InterfaceStability.Unstable
-  public static class DistributedCacheFileContext extends InitializationContext {
-    // base directory under which file has been localized
-    Path localizedBaseDir;
-    // the unique string used to construct the localized path
-    String uniqueString;
-
-    public DistributedCacheFileContext(String user, File workDir,
-        Path localizedBaseDir, String uniqueString) {
-      super(user, workDir);
-      this.localizedBaseDir = localizedBaseDir;
-      this.uniqueString = uniqueString;
-    }
-
-    public Path getLocalizedUniqueDir() {
-      return new Path(localizedBaseDir, new Path(TaskTracker
-          .getPrivateDistributedCacheDir(user), uniqueString));
+  protected void logOutput(String output) {
+    String shExecOutput = output;
+    if (shExecOutput != null) {
+      for (String str : shExecOutput.split("\n")) {
+        LOG.info(str);
+      }
     }
   }
 
-  static class JobInitializationContext extends InitializationContext {
-    JobID jobid;
-  }
-  
-  static class DebugScriptContext extends TaskExecContext {
-    List<String> args;
-    File workDir;
-    File stdout;
+  public static final boolean isSetsidAvailable = isSetsidSupported();
+  private static boolean isSetsidSupported() {
+    ShellCommandExecutor shexec = null;
+    boolean setsidSupported = true;
+    try {
+      String[] args = {"setsid", "bash", "-c", "echo $$"};
+      shexec = new ShellCommandExecutor(args);
+      shexec.execute();
+    } catch (IOException ioe) {
+      LOG.warn("setsid is not available on this machine. So not using it.");
+      setsidSupported = false;
+    } finally { // handle the exit code
+      LOG.info("setsid exited with exit code " + shexec.getExitCode());
+    }
+    return setsidSupported;
   }
 
-  /**
-   * Sends a graceful terminate signal to taskJVM and it sub-processes. 
-   *   
-   * @param context task context
-   */
-  abstract void terminateTask(TaskControllerContext context);
-  
-  /**
-   * Sends a KILL signal to forcefully terminate the taskJVM and its
-   * sub-processes.
-   * 
-   * @param context task context
-   */
-  abstract void killTask(TaskControllerContext context);
-
-
-  /**
-   * Sends a QUIT signal to direct the task JVM (and sub-processes) to
-   * dump their stack to stdout.
-   *
-   * @param context task context.
-   */
-  abstract void dumpTaskStack(TaskControllerContext context);
-
-  /**
-   * Initialize user on this TaskTracer in a TaskController specific manner.
-   * 
-   * @param context
-   * @throws IOException
-   */
-  public abstract void initializeUser(InitializationContext context)
-      throws IOException;
-  
-  /**
-   * Launch the task debug script
-   * 
-   * @param context
-   * @throws IOException
-   */
-  abstract void runDebugScript(DebugScriptContext context) 
-      throws IOException;
-  
-  /**
-   * Enable the task for cleanup by changing permissions of the path
-   * @param context   path deletion context
-   * @throws IOException
-   */
-  abstract void enableTaskForCleanup(PathDeletionContext context)
-      throws IOException;
-  
-  /**
-   * Enable the job for cleanup by changing permissions of the path
-   * @param context   path deletion context
-   * @throws IOException
-   */
-  abstract void enableJobForCleanup(PathDeletionContext context)
-    throws IOException;
-
-  /**
-   * Returns the local unix user that a given job will run as.
-   */
-  String getRunAsUser(JobConf conf) {
-    return System.getProperty("user.name");
+  public static class DelayedProcessKiller extends Thread {
+    private final String user;
+    private final int pid;
+    private final long delay;
+    private final Signal signal;
+    private final TaskController taskController;
+    public DelayedProcessKiller(String user, int pid, long delay, Signal signal,
+        TaskController taskController) {
+      this.user = user;
+      this.pid = pid;
+      this.delay = delay;
+      this.signal = signal;
+      this.taskController = taskController;
+      setName("Task killer for " + pid);
+      setDaemon(false);
+    }
+    @Override
+    public void run() {
+      try {
+        Thread.sleep(delay);
+        taskController.signalTask(user, pid, signal);
+      } catch (InterruptedException e) {
+        return;
+      } catch (IOException e) {
+        LOG.warn("Exception when killing task " + pid, e);
+      }
+    }
   }
 }

+ 30 - 11
mapreduce/src/java/org/apache/hadoop/mapred/TaskLog.java

@@ -43,8 +43,8 @@ import org.apache.hadoop.fs.FileUtil;
 import org.apache.hadoop.fs.Path;
 import org.apache.hadoop.io.SecureIOUtils;
 import org.apache.hadoop.mapreduce.JobID;
-import org.apache.hadoop.mapreduce.util.ProcessTree;
 import org.apache.hadoop.util.Shell;
+import org.apache.hadoop.util.StringUtils;
 import org.apache.log4j.Appender;
 import org.apache.log4j.LogManager;
 import org.apache.log4j.Logger;
@@ -168,7 +168,14 @@ public class TaskLog {
 
   static File getAttemptDir(TaskAttemptID taskid, boolean isCleanup) {
     String cleanupSuffix = isCleanup ? ".cleanup" : "";
-    return new File(getJobDir(taskid.getJobID()), taskid + cleanupSuffix);
+    return getAttemptDir(taskid.getJobID().toString(), 
+        taskid.toString() + cleanupSuffix);
+  }
+  
+  static File getAttemptDir(String jobid, String taskid) {
+    // taskid should be fully formed and it should have the optional 
+    // .cleanup suffix
+    return new File(getJobDir(jobid), taskid);
   }
   private static long prevOutLength;
   private static long prevErrLength;
@@ -487,21 +494,23 @@ public class TaskLog {
     
     String stdout = FileUtil.makeShellPath(stdoutFilename);
     String stderr = FileUtil.makeShellPath(stderrFilename);    
-    StringBuffer mergedCmd = new StringBuffer();
+    StringBuilder mergedCmd = new StringBuilder();
     
     // Export the pid of taskJvm to env variable JVM_PID.
     // Currently pid is not used on Windows
     if (!Shell.WINDOWS) {
-      mergedCmd.append(" export JVM_PID=`echo $$` ; ");
+      mergedCmd.append("export JVM_PID=`echo $$` ; ");
     }
 
-    if (setup != null && setup.size() > 0) {
-      mergedCmd.append(addCommand(setup, false));
-      mergedCmd.append(";");
+    if (setup != null) {
+      for (String s : setup) {
+        mergedCmd.append(s);
+        mergedCmd.append("\n");
+      }
     }
     if (tailLength > 0) {
       mergedCmd.append("(");
-    } else if(ProcessTree.isSetsidAvailable && useSetsid &&
+    } else if(TaskController.isSetsidAvailable && useSetsid &&
         !Shell.WINDOWS) {
       mergedCmd.append("exec setsid ");
     } else {
@@ -574,7 +583,7 @@ public class TaskLog {
    */
   public static String addCommand(List<String> cmd, boolean isExecutable) 
   throws IOException {
-    StringBuffer command = new StringBuffer();
+    StringBuilder command = new StringBuilder();
     for(String s: cmd) {
     	command.append('\'');
       if (isExecutable) {
@@ -623,11 +632,21 @@ public class TaskLog {
   /**
    * Get the user log directory for the job jobid.
    * 
-   * @param jobid
+   * @param jobid string representation of the jobid
+   * @return user log directory for the job
+   */
+  public static File getJobDir(String jobid) {
+    return new File(getUserLogDir(), jobid);
+  }
+  
+  /**
+   * Get the user log directory for the job jobid.
+   * 
+   * @param jobid the jobid object
    * @return user log directory for the job
    */
   public static File getJobDir(JobID jobid) {
-    return new File(getUserLogDir(), jobid.toString());
+    return getJobDir(jobid.toString());
   }
 
 } // TaskLog

+ 16 - 25
mapreduce/src/java/org/apache/hadoop/mapred/TaskMemoryManagerThread.java

@@ -34,7 +34,6 @@ import org.apache.hadoop.mapred.TaskTracker;
 import org.apache.hadoop.mapred.TaskTracker.TaskInProgress;
 import org.apache.hadoop.mapreduce.server.tasktracker.TTConfig;
 import org.apache.hadoop.mapreduce.util.ProcfsBasedProcessTree;
-import org.apache.hadoop.mapreduce.util.ProcessTree;
 import org.apache.hadoop.util.StringUtils;
 
 /**
@@ -46,14 +45,13 @@ class TaskMemoryManagerThread extends Thread {
   private static Log LOG = LogFactory.getLog(TaskMemoryManagerThread.class);
 
   private TaskTracker taskTracker;
-  private long monitoringInterval;
-
-  private long maxMemoryAllowedForAllTasks;
   private long maxRssMemoryAllowedForAllTasks;
 
-  private Map<TaskAttemptID, ProcessTreeInfo> processTreeInfoMap;
-  private Map<TaskAttemptID, ProcessTreeInfo> tasksToBeAdded;
-  private List<TaskAttemptID> tasksToBeRemoved;
+  private final long monitoringInterval;
+  private final long maxMemoryAllowedForAllTasks;
+  private final Map<TaskAttemptID, ProcessTreeInfo> processTreeInfoMap;
+  private final Map<TaskAttemptID, ProcessTreeInfo> tasksToBeAdded;
+  private final List<TaskAttemptID> tasksToBeRemoved;
 
   private static final String MEMORY_USAGE_STRING =
     "Memory usage of ProcessTree %s for task-id %s : Virutal %d bytes, " +
@@ -91,7 +89,8 @@ class TaskMemoryManagerThread extends Thread {
     this.monitoringInterval = monitoringInterval;
   }
 
-  public void addTask(TaskAttemptID tid, long memLimit, long memLimitPhysical) {
+  public void addTask(TaskAttemptID tid, long memLimit,
+      long memLimitPhysical) {
     synchronized (tasksToBeAdded) {
       LOG.debug("Tracking ProcessTree " + tid + " for the first time");
       ProcessTreeInfo ptInfo =
@@ -204,19 +203,10 @@ class TaskMemoryManagerThread extends Thread {
             if (pId != null) {
               // pId will be null, either if the JVM is not spawned yet or if
               // the JVM is removed from jvmIdToPid
-              long sleeptimeBeforeSigkill =
-                  taskTracker
-                      .getJobConf()
-                      .getLong(
-                          TTConfig.TT_SLEEP_TIME_BEFORE_SIG_KILL,
-                          ProcessTree.DEFAULT_SLEEPTIME_BEFORE_SIGKILL);
-
-              // create process tree object
-              ProcfsBasedProcessTree pt =
-                  new ProcfsBasedProcessTree(pId,
-                      ProcessTree.isSetsidAvailable, sleeptimeBeforeSigkill);
               LOG.debug("Tracking ProcessTree " + pId + " for the first time");
 
+              ProcfsBasedProcessTree pt = new ProcfsBasedProcessTree(pId,
+                  TaskController.isSetsidAvailable);
               ptInfo.setPid(pId);
               ptInfo.setProcessTree(pt);
             }
@@ -280,9 +270,13 @@ class TaskMemoryManagerThread extends Thread {
             // Virtual or physical memory over limit. Fail the task and remove
             // the corresponding process tree
             LOG.warn(msg);
+            // warn if not a leader
+            if (!pTree.checkPidPgrpidForMatch()) {
+              LOG.error("Killed task process with PID " + pId +
+                  " but it is not a process group leader.");
+            }
+            // kill the task
             taskTracker.cleanUpOverMemoryTask(tid, true, msg);
-            // Now destroy the ProcessTree, remove it from monitoring map.
-            pTree.destroy(true/*in the background*/);
             it.remove();
             LOG.info("Removed ProcessTree with root " + pId);
           } else {
@@ -294,8 +288,7 @@ class TaskMemoryManagerThread extends Thread {
         } catch (Exception e) {
           // Log the exception and proceed to the next task.
           LOG.warn("Uncaught exception in TaskMemoryManager "
-              + "while managing memory of " + tid + " : "
-              + StringUtils.stringifyException(e));
+              + "while managing memory of " + tid, e);
         }
       }
 
@@ -518,8 +511,6 @@ class TaskMemoryManagerThread extends Thread {
     taskTracker.cleanUpOverMemoryTask(tid, false, msg);
     // Now destroy the ProcessTree, remove it from monitoring map.
     ProcessTreeInfo ptInfo = processTreeInfoMap.get(tid);
-    ProcfsBasedProcessTree pTree = ptInfo.getProcessTree();
-    pTree.destroy(true/*in the background*/);
     processTreeInfoMap.remove(tid);
     LOG.info("Removed ProcessTree with root " + ptInfo.getPID());
   }

+ 68 - 85
mapreduce/src/java/org/apache/hadoop/mapred/TaskRunner.java

@@ -20,16 +20,16 @@ package org.apache.hadoop.mapred;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
-import java.io.OutputStream;
 import java.io.PrintStream;
 import java.net.InetSocketAddress;
 import java.net.URI;
-import java.security.PrivilegedExceptionAction;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Random;
 import java.util.Vector;
+import java.util.Map.Entry;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
@@ -67,21 +67,30 @@ abstract class TaskRunner extends Thread {
   private boolean exitCodeSet = false;
   
   private static String SYSTEM_PATH_SEPARATOR = System.getProperty("path.separator");
+  static final String HADOOP_WORK_DIR = "HADOOP_WORK_DIR";
 
   
   private TaskTracker tracker;
   private TaskDistributedCacheManager taskDistributedCacheManager;
+  private String[] localdirs;
+  final private static Random rand;
+  static {
+    rand = new Random();
+  }
 
   protected JobConf conf;
   JvmManager jvmManager;
 
   public TaskRunner(TaskTracker.TaskInProgress tip, TaskTracker tracker, 
-      JobConf conf) {
+                    JobConf conf, TaskTracker.RunningJob rjob
+                    ) throws IOException {
     this.tip = tip;
     this.t = tip.getTask();
     this.tracker = tracker;
     this.conf = conf;
     this.jvmManager = tracker.getJvmManagerInstance();
+    this.localdirs = conf.getLocalDirs();
+    taskDistributedCacheManager = rjob.distCacheMgr;
   }
 
   public Task getTask() { return t; }
@@ -154,27 +163,13 @@ abstract class TaskRunner extends Thread {
       //before preparing the job localize 
       //all the archives
       TaskAttemptID taskid = t.getTaskID();
-      final LocalDirAllocator lDirAlloc = new LocalDirAllocator(MRConfig.LOCAL_DIR);
-      final File workDir = formWorkDir(lDirAlloc, taskid, t.isTaskCleanupTask(), conf);
-
-      // We don't create any symlinks yet, so presence/absence of workDir
-      // actually on the file system doesn't matter.
-      tip.getUGI().doAs(new PrivilegedExceptionAction<Void>() {
-        public Void run() throws IOException {
-          taskDistributedCacheManager = 
-            tracker.getTrackerDistributedCacheManager()
-            .newTaskDistributedCacheManager(conf);
-          taskDistributedCacheManager.setup(lDirAlloc, workDir, TaskTracker
-              .getPrivateDistributedCacheDir(conf.getUser()), 
-          TaskTracker.getPublicDistributedCacheDir());
-          return null;
-        }
-      });
-
-      // Set up the child task's configuration. After this call, no localization
-      // of files should happen in the TaskTracker's process space. Any changes to
-      // the conf object after this will NOT be reflected to the child.
-      setupChildTaskConfiguration(lDirAlloc);
+    //simply get the location of the workDir and pass it to the child. The
+    //child will do the actual dir creation
+    final File workDir =
+     new File(new Path(localdirs[rand.nextInt(localdirs.length)],
+         TaskTracker.getTaskWorkDir(t.getUser(), taskid.getJobID().toString(),
+         taskid.toString(),
+         t.isTaskCleanupTask())).toString());
 
       // Build classpath
       List<String> classPaths =
@@ -189,7 +184,7 @@ abstract class TaskRunner extends Thread {
       tracker.addToMemoryManager(t.getTaskID(), t.isMapTask(), conf);
 
       // set memory limit using ulimit if feasible and necessary ...
-      List<String> setup = getVMSetupCmd();
+      String setup = getVMSetupCmd();
 
       // Set up the redirection of the task's stdout and stderr streams
       File[] logFiles = prepareLogFiles(taskid, t.isTaskCleanupTask());
@@ -202,7 +197,20 @@ abstract class TaskRunner extends Thread {
       errorInfo = getVMEnvironment(errorInfo, workDir, conf, env,
                                    taskid, logSize);
 
-      launchJvmAndWait(setup, vargs, stdout, stderr, logSize, workDir, env);
+      // flatten the env as a set of export commands
+      List <String> setupCmds = new ArrayList<String>();
+      for(Entry<String, String> entry : env.entrySet()) {
+        StringBuffer sb = new StringBuffer();
+        sb.append("export ");
+        sb.append(entry.getKey());
+        sb.append("=\"");
+        sb.append(entry.getValue());
+        sb.append("\"");
+        setupCmds.add(sb.toString());
+      }
+      setupCmds.add(setup);
+
+      launchJvmAndWait(setupCmds, vargs, stdout, stderr, logSize, workDir);
       tracker.getTaskTrackerInstrumentation().reportTaskEnd(t.getTaskID());
       if (exitCodeSet) {
         if (!killed && exitCode != 0) {
@@ -231,14 +239,6 @@ abstract class TaskRunner extends Thread {
         LOG.warn(t.getTaskID()+" Reporting Diagnostics", e);
       }
     } finally {
-      try{
-        if (taskDistributedCacheManager != null) {
-          taskDistributedCacheManager.release();
-        }
-      }catch(IOException ie){
-        LOG.warn("Error releasing caches : Cache files might not have been cleaned up");
-      }
-
       // It is safe to call TaskTracker.TaskInProgress.reportTaskFinished with
       // *false* since the task has either
       // a) SUCCEEDED - which means commit has been done
@@ -247,11 +247,11 @@ abstract class TaskRunner extends Thread {
     }
   }
 
-  void launchJvmAndWait(List<String> setup, Vector<String> vargs, File stdout,
-      File stderr, long logSize, File workDir, Map<String, String> env)
-      throws InterruptedException {
+  void launchJvmAndWait(List <String> setup, Vector<String> vargs, File stdout,
+      File stderr, long logSize, File workDir)
+      throws InterruptedException, IOException {
     jvmManager.launchJvm(this, jvmManager.constructJvmEnv(setup, vargs, stdout,
-        stderr, logSize, workDir, env, conf));
+        stderr, logSize, workDir, conf));
     synchronized (lock) {
       while (!done) {
         lock.wait();
@@ -303,7 +303,7 @@ abstract class TaskRunner extends Thread {
                 .isTaskCleanupTask()), conf);
 
     // write the child's task configuration file to the local disk
-    writeLocalTaskFile(localTaskFile.toString(), conf);
+    JobLocalizer.writeLocalJobFile(localTaskFile, conf);
 
     // Set the final job file in the task. The child needs to know the correct
     // path to job.xml. So set this path accordingly.
@@ -313,21 +313,21 @@ abstract class TaskRunner extends Thread {
   /**
    * @return
    */
-  private List<String> getVMSetupCmd() {
-
-    int ulimit = getChildUlimit(conf);
+  private String getVMSetupCmd() {
+    final int ulimit = getChildUlimit(conf);
     if (ulimit <= 0) {
-      return null;
+      return "";
     }
-    List<String> setup = null;
-    String[] ulimitCmd = Shell.getUlimitMemoryCommand(ulimit);
-    if (ulimitCmd != null) {
-      setup = new ArrayList<String>();
-      for (String arg : ulimitCmd) {
-        setup.add(arg);
-      }
+    String setup[] = Shell.getUlimitMemoryCommand(ulimit);
+    StringBuilder command = new StringBuilder();
+    for (String str : setup) {
+      command.append('\'');
+      command.append(str);
+      command.append('\'');
+      command.append(" ");
     }
-    return setup;
+    command.append("\n");
+    return command.toString();
   }
 
   /**
@@ -423,7 +423,7 @@ abstract class TaskRunner extends Thread {
       vargs.add(javaOptsSplit[i]);
     }
 
-    Path childTmpDir = createChildTmpDir(workDir, conf);
+    Path childTmpDir = createChildTmpDir(workDir, conf, false);
     vargs.add("-Djava.io.tmpdir=" + childTmpDir);
 
     // Add classpath.
@@ -473,7 +473,7 @@ abstract class TaskRunner extends Thread {
    * @throws IOException
    */
   static Path createChildTmpDir(File workDir,
-      JobConf conf)
+      JobConf conf, boolean createDir)
       throws IOException {
 
     // add java.io.tmpdir given by mapreduce.task.tmp.dir
@@ -483,10 +483,13 @@ abstract class TaskRunner extends Thread {
     // if temp directory path is not absolute, prepend it with workDir.
     if (!tmpDir.isAbsolute()) {
       tmpDir = new Path(workDir.toString(), tmp);
-
-      FileSystem localFs = FileSystem.getLocal(conf);
-      if (!localFs.mkdirs(tmpDir) && localFs.getFileStatus(tmpDir).isFile()) {
-        throw new IOException("Mkdirs failed to create " + tmpDir.toString());
+      if (createDir) {
+        FileSystem localFs = FileSystem.getLocal(conf);
+        if (!localFs.mkdirs(tmpDir) && 
+            !localFs.getFileStatus(tmpDir).isDir()) {
+          throw new IOException("Mkdirs failed to create " +
+              tmpDir.toString());
+        }
       }
     }
     return tmpDir;
@@ -533,6 +536,7 @@ abstract class TaskRunner extends Thread {
       ldLibraryPath.append(oldLdLibraryPath);
     }
     env.put("LD_LIBRARY_PATH", ldLibraryPath.toString());
+    env.put(HADOOP_WORK_DIR, workDir.toString());
     
     // put jobTokenFile name into env
     String jobTokenFile = conf.get(TokenCache.JOB_TOKENS_FILENAME);
@@ -592,25 +596,6 @@ abstract class TaskRunner extends Thread {
     return errorInfo;
   }
 
-  /**
-   * Write the task specific job-configuration file.
-   * 
-   * @param localFs
-   * @throws IOException
-   */
-  private static void writeLocalTaskFile(String jobFile, JobConf conf)
-      throws IOException {
-    Path localTaskFile = new Path(jobFile);
-    FileSystem localFs = FileSystem.getLocal(conf);
-    localFs.delete(localTaskFile, true);
-    OutputStream out = localFs.create(localTaskFile);
-    try {
-      conf.writeXml(out);
-    } finally {
-      out.close();
-    }
-  }
-
   /**
    * Prepare the Configs.LOCAL_DIR for the child. The child is sand-boxed now.
    * Whenever it uses LocalDirAllocator from now on inside the child, it will
@@ -635,15 +620,11 @@ abstract class TaskRunner extends Thread {
   }
 
   /** Creates the working directory pathname for a task attempt. */ 
-  static File formWorkDir(LocalDirAllocator lDirAlloc, 
-      TaskAttemptID task, boolean isCleanup, JobConf conf) 
+  static Path formWorkDir(LocalDirAllocator lDirAlloc, JobConf conf) 
       throws IOException {
     Path workDir =
-        lDirAlloc.getLocalPathToRead(TaskTracker.getTaskWorkDir(
-            conf.getUser(), task.getJobID().toString(), task.toString(),
-            isCleanup), conf);
-
-    return new File(workDir.toString());
+        lDirAlloc.getLocalPathToRead(MRConstants.WORKDIR, conf);
+    return workDir;
   }
 
   private static void appendSystemClasspaths(List<String> classPaths) {
@@ -735,7 +716,7 @@ abstract class TaskRunner extends Thread {
       }
     }
 
-    createChildTmpDir(workDir, conf);
+    createChildTmpDir(workDir, conf, true);
   }
 
   /**
@@ -759,8 +740,10 @@ abstract class TaskRunner extends Thread {
 
   /**
    * Kill the child process
+   * @throws InterruptedException 
+   * @throws IOException 
    */
-  public void kill() {
+  public void kill() throws IOException, InterruptedException {
     killed = true;
     jvmManager.taskKilled(this);
     signalDone();

File diff suppressed because it is too large
+ 299 - 332
mapreduce/src/java/org/apache/hadoop/mapred/TaskTracker.java


+ 9 - 0
mapreduce/src/java/org/apache/hadoop/mapred/TaskUmbilicalProtocol.java

@@ -161,4 +161,13 @@ public interface TaskUmbilicalProtocol extends VersionedProtocol {
                                                        TaskAttemptID id) 
   throws IOException;
 
+  /**
+   * The job initializer needs to report the sizes of the archive
+   * objects in the private distributed cache.
+   * @param jobId the job to update
+   * @param sizes the array of sizes that were computed
+   * @throws IOException
+   */
+  void updatePrivateDistributedCacheSizes(org.apache.hadoop.mapreduce.JobID jobId,
+                                          long[] sizes) throws IOException;
 }

+ 19 - 11
mapreduce/src/java/org/apache/hadoop/mapred/UserLogCleaner.java

@@ -32,6 +32,9 @@ import org.apache.commons.logging.LogFactory;
 import org.apache.hadoop.classification.InterfaceAudience;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.mapred.CleanupQueue.PathDeletionContext;
+import org.apache.hadoop.mapreduce.JobID;
 import org.apache.hadoop.mapreduce.MRJobConfig;
 import org.apache.hadoop.mapreduce.server.tasktracker.TTConfig;
 import org.apache.hadoop.mapreduce.util.MRAsyncDiskService;
@@ -53,15 +56,19 @@ class UserLogCleaner extends Thread {
   private Map<JobID, Long> completedJobs = Collections
       .synchronizedMap(new HashMap<JobID, Long>());
   private final long threadSleepTime;
-  private MRAsyncDiskService logAsyncDisk;
   private Clock clock;
+  private TaskController taskController;
+  private CleanupQueue cleanupQueue;
+  private FileSystem localFs;
 
-  UserLogCleaner(Configuration conf) throws IOException {
+  UserLogCleaner(Configuration conf, TaskController taskController) 
+  throws IOException {
     threadSleepTime = conf.getLong(TTConfig.TT_USERLOGCLEANUP_SLEEPTIME,
         DEFAULT_THREAD_SLEEP_TIME);
-    logAsyncDisk = new MRAsyncDiskService(FileSystem.getLocal(conf), TaskLog
-        .getUserLogDir().toString());
     setClock(new Clock());
+    localFs = FileSystem.getLocal(conf);
+    this.taskController = taskController;
+    cleanupQueue = CleanupQueue.getInstance();
   }
 
   void setClock(Clock clock) {
@@ -100,7 +107,7 @@ class UserLogCleaner extends Thread {
         // see if the job is old enough
         if (entry.getValue().longValue() <= now) {
           // add the job logs directory to for delete
-          deleteLogPath(TaskLog.getJobDir(entry.getKey()).getAbsolutePath());
+          deleteLogPath(entry.getKey().toString());
           completedJobIter.remove();
         }
       }
@@ -125,16 +132,12 @@ class UserLogCleaner extends Thread {
         // add all the log dirs to taskLogsMnonitor.
         long now = clock.getTime();
         for (String logDir : logDirs) {
-          if (logDir.equals(logAsyncDisk.TOBEDELETED)) {
-            // skip this
-            continue;
-          }
           JobID jobid = null;
           try {
             jobid = JobID.forName(logDir);
           } catch (IllegalArgumentException ie) {
             // if the directory is not a jobid, delete it immediately
-            deleteLogPath(new File(userLogDir, logDir).getAbsolutePath());
+            deleteLogPath(logDir);
             continue;
           }
           // add the job log directory with default retain hours, if it is not
@@ -197,6 +200,11 @@ class UserLogCleaner extends Thread {
    */
   private void deleteLogPath(String logPath) throws IOException {
     LOG.info("Deleting user log path " + logPath);
-    logAsyncDisk.moveAndDeleteAbsolutePath(logPath);
+    String logRoot = TaskLog.getUserLogDir().toString();
+    String user = localFs.getFileStatus(new Path(logRoot, logPath)).getOwner();
+    PathDeletionContext item = 
+      new TaskController.DeletionContext(taskController, true, user, logPath,
+                                         null);
+    cleanupQueue.addToQueue(item);
   }
 }

+ 2 - 2
mapreduce/src/java/org/apache/hadoop/mapreduce/JobContext.java

@@ -265,7 +265,7 @@ public interface JobContext extends MRJobConfig {
    * @return a string array of timestamps 
    * @throws IOException
    */
-  public String[] getArchiveTimestamps();
+  public long[] getArchiveTimestamps();
 
   /**
    * Get the timestamps of the files.  Used by internal
@@ -273,7 +273,7 @@ public interface JobContext extends MRJobConfig {
    * @return a string array of timestamps 
    * @throws IOException
    */
-  public String[] getFileTimestamps();
+  public long[] getFileTimestamps();
 
   /** 
    * Get the configured number of maximum attempts that will be made to run a

+ 1 - 1
mapreduce/src/java/org/apache/hadoop/mapreduce/JobSubmissionFiles.java

@@ -118,4 +118,4 @@ public class JobSubmissionFiles {
     return stagingArea;
   }
   
-}
+}

+ 66 - 200
mapreduce/src/java/org/apache/hadoop/mapreduce/filecache/DistributedCache.java

@@ -26,9 +26,6 @@ import org.apache.hadoop.conf.*;
 import org.apache.hadoop.util.*;
 import org.apache.hadoop.fs.*;
 import org.apache.hadoop.fs.FileSystem;
-import org.apache.hadoop.mapred.DefaultTaskController;
-import org.apache.hadoop.mapreduce.Job;
-import org.apache.hadoop.mapreduce.JobContext;
 import org.apache.hadoop.mapreduce.MRJobConfig;
 
 import java.net.URI;
@@ -134,175 +131,6 @@ import java.net.URI;
 @Deprecated
 @InterfaceAudience.Private
 public class DistributedCache {
-  /**
-   * Get the locally cached file or archive; it could either be 
-   * previously cached (and valid) or copy it from the {@link FileSystem} now.
-   * 
-   * @param cache the cache to be localized, this should be specified as 
-   * new URI(scheme://scheme-specific-part/absolute_path_to_file#LINKNAME).
-   * @param conf The Confguration file which contains the filesystem
-   * @param baseDir The base cache Dir where you wnat to localize the files/archives
-   * @param fileStatus The file status on the dfs.
-   * @param isArchive if the cache is an archive or a file. In case it is an
-   *  archive with a .zip or .jar or .tar or .tgz or .tar.gz extension it will
-   *  be unzipped/unjarred/untarred automatically 
-   *  and the directory where the archive is unzipped/unjarred/untarred is
-   *  returned as the Path.
-   *  In case of a file, the path to the file is returned
-   * @param confFileStamp this is the hdfs file modification timestamp to verify that the 
-   * file to be cached hasn't changed since the job started
-   * @param currentWorkDir this is the directory where you would want to create symlinks 
-   * for the locally cached files/archives
-   * @return the path to directory where the archives are unjarred in case of archives,
-   * the path to the file where the file is copied locally 
-   * @throws IOException
-   * @deprecated Internal to MapReduce framework. 
-   * Use TrackerDistributedCacheManager instead.
-   */
-  @Deprecated
-  public static Path getLocalCache(URI cache, Configuration conf, 
-                                   Path baseDir, FileStatus fileStatus,
-                                   boolean isArchive, long confFileStamp,
-                                   Path currentWorkDir) 
-      throws IOException {
-    return getLocalCache(cache, conf, baseDir, fileStatus, isArchive, 
-        confFileStamp, currentWorkDir, true);
-  }
-
-  /**
-   * Get the locally cached file or archive; it could either be 
-   * previously cached (and valid) or copy it from the {@link FileSystem} now.
-   * 
-   * @param cache the cache to be localized, this should be specified as 
-   * new URI(scheme://scheme-specific-part/absolute_path_to_file#LINKNAME).
-   * @param conf The Confguration file which contains the filesystem
-   * @param baseDir The base cache Dir where you wnat to localize the files/archives
-   * @param fileStatus The file status on the dfs.
-   * @param isArchive if the cache is an archive or a file. In case it is an
-   *  archive with a .zip or .jar or .tar or .tgz or .tar.gz extension it will
-   *  be unzipped/unjarred/untarred automatically 
-   *  and the directory where the archive is unzipped/unjarred/untarred is
-   *  returned as the Path.
-   *  In case of a file, the path to the file is returned
-   * @param confFileStamp this is the hdfs file modification timestamp to verify that the 
-   * file to be cached hasn't changed since the job started
-   * @param currentWorkDir this is the directory where you would want to create symlinks 
-   * for the locally cached files/archives
-   * @param honorSymLinkConf if this is false, then the symlinks are not
-   * created even if conf says so (this is required for an optimization in task
-   * launches
-   * @return the path to directory where the archives are unjarred in case of archives,
-   * the path to the file where the file is copied locally 
-   * @throws IOException
-   * @deprecated Internal to MapReduce framework. 
-   * Use TrackerDistributedCacheManager instead.
-   */
-  @Deprecated
-  public static Path getLocalCache(URI cache, Configuration conf, 
-      Path baseDir, FileStatus fileStatus,
-      boolean isArchive, long confFileStamp,
-      Path currentWorkDir, boolean honorSymLinkConf) throws IOException {
-
-    return new TrackerDistributedCacheManager(conf, new DefaultTaskController())
-        .getLocalCache(cache, conf, baseDir.toString(), fileStatus, isArchive,
-            confFileStamp, currentWorkDir, honorSymLinkConf, false);
-  }
-
-  /**
-   * Get the locally cached file or archive; it could either be 
-   * previously cached (and valid) or copy it from the {@link FileSystem} now.
-   * 
-   * @param cache the cache to be localized, this should be specified as 
-   * new URI(scheme://scheme-specific-part/absolute_path_to_file#LINKNAME).
-   * @param conf The Confguration file which contains the filesystem
-   * @param baseDir The base cache Dir where you wnat to localize the files/archives
-   * @param isArchive if the cache is an archive or a file. In case it is an 
-   *  archive with a .zip or .jar or .tar or .tgz or .tar.gz extension it will 
-   *  be unzipped/unjarred/untarred automatically 
-   *  and the directory where the archive is unzipped/unjarred/untarred 
-   *  is returned as the Path.
-   *  In case of a file, the path to the file is returned
-   * @param confFileStamp this is the hdfs file modification timestamp to verify that the 
-   * file to be cached hasn't changed since the job started
-   * @param currentWorkDir this is the directory where you would want to create symlinks 
-   * for the locally cached files/archives
-   * @return the path to directory where the archives are unjarred in case of archives,
-   * the path to the file where the file is copied locally 
-   * @throws IOException
-   * @deprecated Internal to MapReduce framework.  
-   * Use TrackerDistributedCacheManager instead.
-   */
-  @Deprecated
-  public static Path getLocalCache(URI cache, Configuration conf, 
-                                   Path baseDir, boolean isArchive,
-                                   long confFileStamp, Path currentWorkDir) 
-      throws IOException {
-    return getLocalCache(cache, conf, 
-                         baseDir, null, isArchive,
-                         confFileStamp, currentWorkDir);
-  }
-
-  /**
-   * This is the opposite of getlocalcache. When you are done with
-   * using the cache, you need to release the cache
-   * @param cache The cache URI to be released
-   * @param conf configuration which contains the filesystem the cache 
-   * is contained in.
-   * @throws IOException
-   * @deprecated Internal to MapReduce framework. 
-   * Use TrackerDistributedCacheManager instead.
-   */
-  @Deprecated
-  public static void releaseCache(URI cache, Configuration conf)
-      throws IOException {
-	// find the timestamp of the uri
-    URI[] archives = DistributedCache.getCacheArchives(conf);
-    URI[] files = DistributedCache.getCacheFiles(conf);
-    String[] archivesTimestamps =
-          DistributedCache.getArchiveTimestamps(conf);
-    String[] filesTimestamps =
-          DistributedCache.getFileTimestamps(conf);
-    String timestamp = null;
-    if (archives != null) {
-      for (int i = 0; i < archives.length; i++) {
-        if (archives[i].equals(cache)) {
-          timestamp = archivesTimestamps[i];
-          break;
-        }
-      }
-    }
-    if (timestamp == null && files != null) {
-      for (int i = 0; i < files.length; i++) {
-        if (files[i].equals(cache)) {
-          timestamp = filesTimestamps[i];
-          break;
-        }
-      }
-    }
-    if (timestamp == null) {
-      throw new IOException("TimeStamp of the uri couldnot be found");
-    }
-    new TrackerDistributedCacheManager(conf, new DefaultTaskController())
-           .releaseCache(cache, conf, Long.parseLong(timestamp),
-            TrackerDistributedCacheManager.getLocalizedCacheOwner(false));
-  }
-  
-  /**
-   * Returns the relative path of the dir this cache will be localized in
-   * relative path that this cache will be localized in. For
-   * hdfs://hostname:port/absolute_path -- the relative path is
-   * hostname/absolute path -- if it is just /absolute_path -- then the
-   * relative path is hostname of DFS this mapred cluster is running
-   * on/absolute_path
-   * @deprecated Internal to MapReduce framework.  Use DistributedCacheManager
-   * instead.
-   */
-  @Deprecated
-  public static String makeRelative(URI cache, Configuration conf)
-      throws IOException {
-    return new TrackerDistributedCacheManager(conf, new DefaultTaskController())
-        .makeRelative(cache, conf);
-  }
 
   /**
    * Returns mtime of a given cache file on hdfs.
@@ -361,11 +189,22 @@ public class DistributedCache {
     conf.set(MRJobConfig.CACHE_FILES, sfiles);
   }
 
+  private static Path[] parsePaths(String[] strs) {
+    if (strs == null) {
+      return null;
+    }
+    Path[] result = new Path[strs.length];
+    for(int i=0; i < strs.length; ++i) {
+      result[i] = new Path(strs[i]);
+    }
+    return result;
+  }
+
   /**
    * Get cache archives set in the Configuration.  Used by
    * internal DistributedCache and MapReduce code.
    * @param conf The configuration which contains the archives
-   * @return A URI array of the caches set in the Configuration
+   * @return An array of the caches set in the Configuration
    * @throws IOException
    * @deprecated Use {@link JobContext#getCacheArchives()} instead
    */
@@ -378,7 +217,7 @@ public class DistributedCache {
    * Get cache files set in the Configuration.  Used by internal
    * DistributedCache and MapReduce code.
    * @param conf The configuration which contains the files
-   * @return A URI array of the files set in the Configuration
+   * @return Am array of the files set in the Configuration
    * @throws IOException
    * @deprecated Use {@link JobContext#getCacheFiles()} instead
    */
@@ -416,31 +255,47 @@ public class DistributedCache {
     return StringUtils.stringToPath(conf.getStrings(MRJobConfig.CACHE_LOCALFILES));
   }
 
+  /**
+   * Parse a list of strings into longs.
+   * @param strs the list of strings to parse
+   * @return a list of longs that were parsed. same length as strs.
+   */
+  private static long[] parseTimestamps(String[] strs) {
+    if (strs == null) {
+      return null;
+    }
+    long[] result = new long[strs.length];
+    for(int i=0; i < strs.length; ++i) {
+      result[i] = Long.parseLong(strs[i]);
+    }
+    return result;
+  }
+
   /**
    * Get the timestamps of the archives.  Used by internal
    * DistributedCache and MapReduce code.
    * @param conf The configuration which stored the timestamps
-   * @return a string array of timestamps 
+   * @return a long array of timestamps 
    * @throws IOException
    * @deprecated Use {@link JobContext#getArchiveTimestamps()} instead
    */
   @Deprecated
-  public static String[] getArchiveTimestamps(Configuration conf) {
-    return conf.getStrings(MRJobConfig.CACHE_ARCHIVES_TIMESTAMPS);
+  public static long[] getArchiveTimestamps(Configuration conf) {
+    return
+      parseTimestamps(conf.getStrings(MRJobConfig.CACHE_ARCHIVES_TIMESTAMPS));
   }
 
-
   /**
    * Get the timestamps of the files.  Used by internal
    * DistributedCache and MapReduce code.
    * @param conf The configuration which stored the timestamps
-   * @return a string array of timestamps 
+   * @return a long array of timestamps 
    * @throws IOException
    * @deprecated Use {@link JobContext#getFileTimestamps()} instead
    */
   @Deprecated
-  public static String[] getFileTimestamps(Configuration conf) {
-    return conf.getStrings(MRJobConfig.CACHE_FILE_TIMESTAMPS);
+  public static long[] getFileTimestamps(Configuration conf) {
+    return parseTimestamps(conf.getStrings(MRJobConfig.CACHE_FILE_TIMESTAMPS));
   }
 
   /**
@@ -511,8 +366,8 @@ public class DistributedCache {
   @Deprecated
   public static void addCacheArchive(URI uri, Configuration conf) {
     String archives = conf.get(MRJobConfig.CACHE_ARCHIVES);
-    conf.set(MRJobConfig.CACHE_ARCHIVES, archives == null ? uri.toString()
-             : archives + "," + uri.toString());
+    conf.set(MRJobConfig.CACHE_ARCHIVES,
+        archives == null ? uri.toString() : archives + "," + uri.toString());
   }
   
   /**
@@ -525,8 +380,32 @@ public class DistributedCache {
   @Deprecated
   public static void addCacheFile(URI uri, Configuration conf) {
     String files = conf.get(MRJobConfig.CACHE_FILES);
-    conf.set(MRJobConfig.CACHE_FILES, files == null ? uri.toString() : files + ","
-             + uri.toString());
+    conf.set(MRJobConfig.CACHE_FILES,
+        files == null ? uri.toString() : files + "," + uri.toString());
+  }
+  
+  /**
+   * Add a archive that has been localized to the conf.  Used
+   * by internal DistributedCache code.
+   * @param conf The conf to modify to contain the localized caches
+   * @param str a comma separated list of local archives
+   */
+  public static void addLocalArchives(Configuration conf, String str) {
+    String archives = conf.get(MRJobConfig.CACHE_LOCALARCHIVES);
+    conf.set(MRJobConfig.CACHE_LOCALARCHIVES,
+        archives == null ? str : archives + "," + str);
+  }
+
+  /**
+   * Add a file that has been localized to the conf..  Used
+   * by internal DistributedCache code.
+   * @param conf The conf to modify to contain the localized caches
+   * @param str a comma separated list of local files
+   */
+  public static void addLocalFiles(Configuration conf, String str) {
+    String files = conf.get(MRJobConfig.CACHE_LOCALFILES);
+    conf.set(MRJobConfig.CACHE_LOCALFILES,
+        files == null ? str : files + "," + str);
   }
 
   /**
@@ -541,8 +420,8 @@ public class DistributedCache {
   public static void addFileToClassPath(Path file, Configuration conf)
     throws IOException {
     String classpath = conf.get(MRJobConfig.CLASSPATH_FILES);
-    conf.set(MRJobConfig.CLASSPATH_FILES, classpath == null ? file.toString()
-             : classpath + "," + file.toString());
+    conf.set(MRJobConfig.CLASSPATH_FILES,
+        classpath == null ? file.toString() : classpath + "," + file);
     FileSystem fs = FileSystem.get(conf);
     URI uri = fs.makeQualified(file).toUri();
 
@@ -654,17 +533,4 @@ public class DistributedCache {
   public static boolean checkURIs(URI[]  uriFiles, URI[] uriArchives){
     return TrackerDistributedCacheManager.checkURIs(uriFiles, uriArchives);
   }
-
-  /**
-   * Clear the entire contents of the cache and delete the backing files. This
-   * should only be used when the server is reinitializing, because the users
-   * are going to lose their files.
-   * @deprecated Internal to MapReduce framework. 
-   * Use TrackerDistributedCacheManager instead.
-   */
-  @Deprecated
-  public static void purgeCache(Configuration conf) throws IOException {
-    new TrackerDistributedCacheManager(conf, new DefaultTaskController())
-        .purgeCache();
-  }
 }

+ 64 - 29
mapreduce/src/java/org/apache/hadoop/mapreduce/filecache/TaskDistributedCacheManager.java

@@ -30,8 +30,11 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.mapreduce.filecache.DistributedCache;
+import org.apache.hadoop.mapreduce.filecache.TrackerDistributedCacheManager.CacheStatus;
 import org.apache.hadoop.fs.FileStatus;
 import org.apache.hadoop.fs.FileSystem;
 import org.apache.hadoop.fs.LocalDirAllocator;
@@ -48,10 +51,9 @@ import org.apache.hadoop.classification.InterfaceAudience;
 @InterfaceAudience.Private
 public class TaskDistributedCacheManager {
   private final TrackerDistributedCacheManager distributedCacheManager;
-  private final Configuration taskConf;
   private final List<CacheFile> cacheFiles = new ArrayList<CacheFile>();
   private final List<String> classPaths = new ArrayList<String>();
- 
+  
   private boolean setupCalled = false;
 
   /**
@@ -75,9 +77,9 @@ public class TaskDistributedCacheManager {
     boolean localized = false;
     /** The owner of the localized file. Relevant only on the tasktrackers */
     final String owner;
-
-    private CacheFile(URI uri, FileType type, boolean isPublic, long timestamp, 
-        boolean classPath) throws IOException {
+    private CacheStatus status;
+    CacheFile(URI uri, FileType type, boolean isPublic, long timestamp, 
+                      boolean classPath) throws IOException {
       this.uri = uri;
       this.type = type;
       this.isPublic = isPublic;
@@ -87,13 +89,29 @@ public class TaskDistributedCacheManager {
           TrackerDistributedCacheManager.getLocalizedCacheOwner(isPublic);
     }
 
+    /**
+     * Set the status for this cache file.
+     * @param status
+     */
+    public void setStatus(CacheStatus status) {
+      this.status = status;
+    }
+    
+    /**
+     * Get the status for this cache file.
+     * @return the status object
+     */
+    public CacheStatus getStatus() {
+      return status;
+    }
+
     /**
      * Converts the scheme used by DistributedCache to serialize what files to
      * cache in the configuration into CacheFile objects that represent those 
      * files.
      */
     private static List<CacheFile> makeCacheFiles(URI[] uris, 
-        String[] timestamps, String cacheVisibilities[], Path[] paths,
+        long[] timestamps, boolean cacheVisibilities[], Path[] paths, 
         FileType type) throws IOException {
       List<CacheFile> ret = new ArrayList<CacheFile>();
       if (uris != null) {
@@ -109,9 +127,8 @@ public class TaskDistributedCacheManager {
         for (int i = 0; i < uris.length; ++i) {
           URI u = uris[i];
           boolean isClassPath = (null != classPaths.get(u.getPath()));
-          long t = Long.parseLong(timestamps[i]);
-          ret.add(new CacheFile(u, type, Boolean.valueOf(cacheVisibilities[i]),
-              t, isClassPath));
+          ret.add(new CacheFile(u, type, cacheVisibilities[i],
+              timestamps[i], isClassPath));
         }
       }
       return ret;
@@ -130,7 +147,6 @@ public class TaskDistributedCacheManager {
       TrackerDistributedCacheManager distributedCacheManager,
       Configuration taskConf) throws IOException {
     this.distributedCacheManager = distributedCacheManager;
-    this.taskConf = taskConf;
     
     this.cacheFiles.addAll(
         CacheFile.makeCacheFiles(DistributedCache.getCacheFiles(taskConf),
@@ -147,36 +163,42 @@ public class TaskDistributedCacheManager {
   }
 
   /**
-   * Retrieve files into the local cache and updates the task configuration 
-   * (which has been passed in via the constructor).
+   * Retrieve public distributed cache files into the local cache and updates
+   * the task configuration (which has been passed in via the constructor).
+   * The private distributed cache is just looked at and the paths where the
+   * files/archives should go to is decided here. The actual localization is
+   * done by {@link JobLocalizer}.
    * 
    * It is the caller's responsibility to re-write the task configuration XML
    * file, if necessary.
    */
-  public void setup(LocalDirAllocator lDirAlloc, File workDir, 
-      String privateCacheSubdir, String publicCacheSubDir) throws IOException {
+  public void setupCache(Configuration taskConf, String publicCacheSubdir, 
+      String privateCacheSubdir) throws IOException {
     setupCalled = true;
       
-    if (cacheFiles.isEmpty()) {
-      return;
-    }
-
     ArrayList<Path> localArchives = new ArrayList<Path>();
     ArrayList<Path> localFiles = new ArrayList<Path>();
-    Path workdirPath = new Path(workDir.getAbsolutePath());
 
     for (CacheFile cacheFile : cacheFiles) {
       URI uri = cacheFile.uri;
       FileSystem fileSystem = FileSystem.get(uri, taskConf);
       FileStatus fileStatus = fileSystem.getFileStatus(new Path(uri.getPath()));
-      String cacheSubdir = publicCacheSubDir;
-      if (!cacheFile.isPublic) {
-        cacheSubdir = privateCacheSubdir;
+      Path p;
+      try {
+        if (cacheFile.isPublic) {
+          p = distributedCacheManager.getLocalCache(uri, taskConf,
+              publicCacheSubdir, fileStatus, 
+              cacheFile.type == CacheFile.FileType.ARCHIVE,
+              cacheFile.timestamp, cacheFile.isPublic, cacheFile);
+        } else {
+          p = distributedCacheManager.getLocalCache(uri, taskConf,
+              privateCacheSubdir, fileStatus, 
+              cacheFile.type == CacheFile.FileType.ARCHIVE,
+              cacheFile.timestamp, cacheFile.isPublic, cacheFile);
+        }
+      } catch (InterruptedException e) {
+        throw new IOException("Interrupted localizing cache file", e);
       }
-      Path p = distributedCacheManager.getLocalCache(uri, taskConf,
-          cacheSubdir, fileStatus, 
-          cacheFile.type == CacheFile.FileType.ARCHIVE,
-          cacheFile.timestamp, workdirPath, false, cacheFile.isPublic);
       cacheFile.setLocalized(true);
 
       if (cacheFile.type == CacheFile.FileType.ARCHIVE) {
@@ -191,10 +213,14 @@ public class TaskDistributedCacheManager {
 
     // Update the configuration object with localized data.
     if (!localArchives.isEmpty()) {
+      // TODO verify
+//      DistributedCache.addLocalArchives(taskConf, 
       TrackerDistributedCacheManager.setLocalArchives(taskConf, 
         stringifyPathList(localArchives));
     }
     if (!localFiles.isEmpty()) {
+      // TODO verify
+//      DistributedCache.addLocalFiles(taskConf, stringifyPathList(localFiles));
       TrackerDistributedCacheManager.setLocalFiles(taskConf,
         stringifyPathList(localFiles));
     }
@@ -239,9 +265,18 @@ public class TaskDistributedCacheManager {
    */
   public void release() throws IOException {
     for (CacheFile c : cacheFiles) {
-      if (c.getLocalized()) {
-        distributedCacheManager.releaseCache(c.uri, taskConf, c.timestamp,
-            c.owner);
+      if (c.getLocalized() && c.status != null) {
+        distributedCacheManager.releaseCache(c.status);
+      }
+    }
+  }
+
+  public void setSizes(long[] sizes) throws IOException {
+    int i = 0;
+    for (CacheFile c: cacheFiles) {
+      if (!c.isPublic && c.type == CacheFile.FileType.ARCHIVE &&
+    	  c.status != null) {
+        distributedCacheManager.setSize(c.status, sizes[i++]);
       }
     }
   }

+ 212 - 144
mapreduce/src/java/org/apache/hadoop/mapreduce/filecache/TrackerDistributedCacheManager.java

@@ -20,7 +20,11 @@ package org.apache.hadoop.mapreduce.filecache;
 import java.io.File;
 import java.io.IOException;
 import java.net.URI;
+import java.text.DateFormat;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedList;
@@ -31,13 +35,9 @@ import java.util.TreeMap;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+
+import org.apache.hadoop.classification.InterfaceAudience;
 import org.apache.hadoop.conf.Configuration;
-import org.apache.hadoop.mapred.TaskController;
-import org.apache.hadoop.mapred.TaskController.DistributedCacheFileContext;
-import org.apache.hadoop.mapreduce.MRJobConfig;
-import org.apache.hadoop.mapreduce.security.TokenCache;
-import org.apache.hadoop.mapreduce.server.tasktracker.TTConfig;
-import org.apache.hadoop.mapreduce.util.MRAsyncDiskService;
 import org.apache.hadoop.fs.FileStatus;
 import org.apache.hadoop.fs.FileSystem;
 import org.apache.hadoop.fs.FileUtil;
@@ -46,10 +46,16 @@ import org.apache.hadoop.fs.LocalFileSystem;
 import org.apache.hadoop.fs.Path;
 import org.apache.hadoop.fs.permission.FsAction;
 import org.apache.hadoop.fs.permission.FsPermission;
+import org.apache.hadoop.mapred.TaskController;
+import org.apache.hadoop.mapreduce.JobID;
+import org.apache.hadoop.mapreduce.MRJobConfig;
+import org.apache.hadoop.mapreduce.filecache.TaskDistributedCacheManager.CacheFile;
+import org.apache.hadoop.mapreduce.security.TokenCache;
+import org.apache.hadoop.mapreduce.server.tasktracker.TTConfig;
+import org.apache.hadoop.mapreduce.util.MRAsyncDiskService;
 import org.apache.hadoop.security.Credentials;
 import org.apache.hadoop.security.UserGroupInformation;
 import org.apache.hadoop.util.RunJar;
-import org.apache.hadoop.classification.InterfaceAudience;
 
 /**
  * Manages a single machine's instance of a cross-job
@@ -63,6 +69,11 @@ public class TrackerDistributedCacheManager {
   // cacheID to cacheStatus mapping
   private TreeMap<String, CacheStatus> cachedArchives = 
     new TreeMap<String, CacheStatus>();
+  private Map<JobID, TaskDistributedCacheManager> jobArchives =
+    Collections.synchronizedMap(
+        new HashMap<JobID, TaskDistributedCacheManager>());
+  private static final FsPermission PUBLIC_CACHE_OBJECT_PERM =
+    FsPermission.createImmutable((short) 0755);
 
   // default total cache size (10GB)
   private static final long DEFAULT_CACHE_SIZE = 10737418240L;
@@ -76,24 +87,20 @@ public class TrackerDistributedCacheManager {
   private final LocalFileSystem localFs;
   
   private LocalDirAllocator lDirAllocator;
-  
-  private TaskController taskController;
-  
+
   private Configuration trackerConf;
   
-  private Random random = new Random();
+  private static final Random random = new Random();
 
   private MRAsyncDiskService asyncDiskService;
 
   BaseDirManager baseDirManager = new BaseDirManager();
   CleanupThread cleanupThread;
 
-  public TrackerDistributedCacheManager(Configuration conf,
-      TaskController taskController) throws IOException {
+  public TrackerDistributedCacheManager(Configuration conf) throws IOException {
     this.localFs = FileSystem.getLocal(conf);
     this.trackerConf = conf;
     this.lDirAllocator = new LocalDirAllocator(TTConfig.LOCAL_DIR);
-    this.taskController = taskController;
       // setting the cache size to a default of 10GB
     this.allowedCacheSize = conf.getLong(TTConfig.TT_LOCAL_CACHE_SIZE,
           DEFAULT_CACHE_SIZE);
@@ -111,7 +118,7 @@ public class TrackerDistributedCacheManager {
   public TrackerDistributedCacheManager(Configuration conf,
       TaskController taskController, MRAsyncDiskService asyncDiskService)
       throws IOException {
-    this(conf, taskController);
+    this(conf);
     this.asyncDiskService = asyncDiskService;
   }
 
@@ -145,15 +152,15 @@ public class TrackerDistributedCacheManager {
    * archives, the path to the file where the file is copied locally
    * @throws IOException
    */
-  Path getLocalCache(URI cache, Configuration conf,
-      String subDir, FileStatus fileStatus,
-      boolean isArchive, long confFileStamp,
-      Path currentWorkDir, boolean honorSymLinkConf, boolean isPublic)
-      throws IOException {
-    String key;
-    key = getKey(cache, conf, confFileStamp, getLocalizedCacheOwner(isPublic));
+  Path getLocalCache(URI cache, Configuration conf, String subDir,
+      FileStatus fileStatus, boolean isArchive, long confFileStamp,
+      boolean isPublic, CacheFile file)
+      throws IOException, InterruptedException {
+    String user = getLocalizedCacheOwner(isPublic);
+    String key = getKey(cache, conf, confFileStamp, user);
     CacheStatus lcacheStatus;
     Path localizedPath = null;
+    Path localPath = null;
     synchronized (cachedArchives) {
       lcacheStatus = cachedArchives.get(key);
       if (lcacheStatus == null) {
@@ -161,44 +168,59 @@ public class TrackerDistributedCacheManager {
         String uniqueString = String.valueOf(random.nextLong());
         String cachePath = new Path (subDir, 
           new Path(uniqueString, makeRelative(cache, conf))).toString();
-        Path localPath = lDirAllocator.getLocalPathForWrite(cachePath,
-          fileStatus.getLen(), trackerConf);
-        lcacheStatus = new CacheStatus(new Path(localPath.toString().replace(
-          cachePath, "")), localPath, new Path(subDir), uniqueString);
+        localPath = lDirAllocator.getLocalPathForWrite(cachePath,
+          fileStatus.getLen(), trackerConf, isPublic);
+        lcacheStatus = 
+          new CacheStatus(new Path(localPath.toString().replace(cachePath, "")),
+                          localPath, new Path(subDir), uniqueString, 
+                          isPublic ? null : user);
         cachedArchives.put(key, lcacheStatus);
       }
 
-      //mark the cache for use. 
-      lcacheStatus.refcount++;
+      //mark the cache for use.
+      file.setStatus(lcacheStatus);
+      synchronized (lcacheStatus) {
+        lcacheStatus.refcount++;
+      }
     }
     
-    boolean initSuccessful = false;
     try {
       // do the localization, after releasing the global lock
       synchronized (lcacheStatus) {
         if (!lcacheStatus.isInited()) {
-          FileSystem fs = FileSystem.get(cache, conf);
-          checkStampSinceJobStarted(conf, fs, cache, confFileStamp,
-              lcacheStatus, fileStatus);
-          localizedPath = localizeCache(conf, cache, confFileStamp,
-              lcacheStatus, isArchive, isPublic);
+          if (isPublic) {
+            // TODO verify covered
+            //checkStampSinceJobStarted(conf, fs, cache, confFileStamp,
+            //    lcacheStatus, fileStatus);
+            localizedPath = localizePublicCacheObject(conf, cache,
+                confFileStamp, lcacheStatus, fileStatus, isArchive);
+          } else {
+            localizedPath = localPath;
+            if (!isArchive) {
+              //for private archives, the lengths come over RPC from the 
+              //JobLocalizer since the JobLocalizer is the one who expands
+              //archives and gets the total length
+              lcacheStatus.size = fileStatus.getLen();
+
+              // Increase the size and sub directory count of the cache
+              // from baseDirSize and baseDirNumberSubDir.
+              baseDirManager.addCacheUpdate(lcacheStatus);
+            }
+          }
           lcacheStatus.initComplete();
         } else {
           localizedPath = checkCacheStatusValidity(conf, cache, confFileStamp,
               lcacheStatus, fileStatus, isArchive);
         }
-        createSymlink(conf, cache, lcacheStatus, isArchive, currentWorkDir,
-            honorSymLinkConf);
       }
-      initSuccessful = true;
-      return localizedPath;
-    } finally {
-      if (!initSuccessful) {
-        synchronized (cachedArchives) {
-          lcacheStatus.refcount--;
-        }
+    } catch (IOException ie) {
+      synchronized (lcacheStatus) {
+        // release this cache
+        lcacheStatus.refcount -= 1;
+        throw ie;
       }
     }
+    return localizedPath;
   }
 
   /**
@@ -211,37 +233,30 @@ public class TrackerDistributedCacheManager {
    * is contained in.
    * @throws IOException
    */
-  void releaseCache(URI cache, Configuration conf, long timeStamp,
-      String owner) throws IOException {
-    String key = getKey(cache, conf, timeStamp, owner);
-    synchronized (cachedArchives) {
-      CacheStatus lcacheStatus = cachedArchives.get(key);
-      if (lcacheStatus == null) {
-        LOG.warn("Cannot find localized cache: " + cache + 
-                 " (key: " + key + ") in releaseCache!");
-        return;
+  void releaseCache(CacheStatus status) throws IOException {
+    synchronized (status) {
+      status.refcount--;
+    }
+  }
+
+  void setSize(CacheStatus status, long size) throws IOException {
+    if (size != 0) {
+      synchronized (status) {
+        status.size = size;
+        baseDirManager.addCacheUpdate(status);
       }
-      
-      // decrement ref count 
-      lcacheStatus.refcount--;
     }
   }
 
   /*
    * This method is called from unit tests. 
    */
-  int getReferenceCount(URI cache, Configuration conf, long timeStamp,
-      String owner) throws IOException {
-    String key = getKey(cache, conf, timeStamp, owner);
-    synchronized (cachedArchives) {
-      CacheStatus lcacheStatus = cachedArchives.get(key);
-      if (lcacheStatus == null) {
-        throw new IOException("Cannot find localized cache: " + cache);
-      }
-      return lcacheStatus.refcount;
+  int getReferenceCount(CacheStatus status) throws IOException {
+    synchronized (status) {
+      return status.refcount;
     }
   }
-  
+
   /**
    * Get the user who should "own" the localized distributed cache file.
    * If the cache is public, the tasktracker user is the owner. If private,
@@ -266,6 +281,7 @@ public class TrackerDistributedCacheManager {
    */
   private static void deleteLocalPath(MRAsyncDiskService asyncDiskService,
       LocalFileSystem fs, Path path) throws IOException {
+    // TODO need to make asyncDiskService use taskController
     boolean deleted = false;
     if (asyncDiskService != null) {
       // Try to delete using asyncDiskService
@@ -419,53 +435,72 @@ public class TrackerDistributedCacheManager {
     return cacheStatus.localizedLoadPath;
   }
   
-  private void createSymlink(Configuration conf, URI cache,
-      CacheStatus cacheStatus, boolean isArchive,
-      Path currentWorkDir, boolean honorSymLinkConf) throws IOException {
-    boolean doSymlink = honorSymLinkConf && DistributedCache.getSymlink(conf);
-    if(cache.getFragment() == null) {
-      doSymlink = false;
-    }
-    String link = 
-      currentWorkDir.toString() + Path.SEPARATOR + cache.getFragment();
-    File flink = new File(link);
-    if (doSymlink){
-      if (!flink.exists()) {
-        FileUtil.symLink(cacheStatus.localizedLoadPath.toString(), link);
-      }
-    }
+  private static Path createRandomPath(Path base) throws IOException {
+    return new Path(base.toString() + "-work-" + random.nextLong());
   }
-  
-  // the method which actually copies the caches locally and unjars/unzips them
-  // and does chmod for the files
-  Path localizeCache(Configuration conf,
-                                    URI cache, long confFileStamp,
-                                    CacheStatus cacheStatus,
-                                    boolean isArchive, boolean isPublic)
-  throws IOException {
-    FileSystem fs = FileSystem.get(cache, conf);
+
+  /**
+   * Download a given path to the local file system.
+   * @param conf the job's configuration
+   * @param source the source to copy from
+   * @param destination where to copy the file. must be local fs
+   * @param desiredTimestamp the required modification timestamp of the source
+   * @param isArchive is this an archive that should be expanded
+   * @param permission the desired permissions of the file.
+   * @return for archives, the number of bytes in the unpacked directory
+   * @throws IOException
+   */
+  public static long downloadCacheObject(Configuration conf,
+                                         URI source,
+                                         Path destination,
+                                         long desiredTimestamp,
+                                         boolean isArchive,
+                                         FsPermission permission
+                                         ) throws IOException,
+                                                  InterruptedException {
+    FileSystem sourceFs = FileSystem.get(source, conf);
     FileSystem localFs = FileSystem.getLocal(conf);
+    
+    Path sourcePath = new Path(source.getPath());
+    long modifiedTime = 
+      sourceFs.getFileStatus(sourcePath).getModificationTime();
+    if (modifiedTime != desiredTimestamp) {
+      DateFormat df = DateFormat.getDateTimeInstance(DateFormat.SHORT, 
+                                                     DateFormat.SHORT);
+      throw new IOException("The distributed cache object " + source + 
+                            " changed during the job from " + 
+                            df.format(new Date(desiredTimestamp)) + " to " +
+                            df.format(new Date(modifiedTime)));
+    }
+    
     Path parchive = null;
     if (isArchive) {
-      parchive = new Path(cacheStatus.localizedLoadPath,
-        new Path(cacheStatus.localizedLoadPath.getName()));
+      parchive = new Path(destination, destination.getName());
     } else {
-      parchive = cacheStatus.localizedLoadPath;
+      parchive = destination;
     }
-
-    if (!localFs.mkdirs(parchive.getParent())) {
-      throw new IOException("Mkdirs failed to create directory " +
-          cacheStatus.localizedLoadPath.toString());
+    // if the file already exists, we are done
+    if (localFs.exists(parchive)) {
+      return 0;
     }
-
-    String cacheId = cache.getPath();
-    fs.copyToLocalFile(new Path(cacheId), parchive);
+    // the final directory for the object
+    Path finalDir = parchive.getParent();
+    // the work directory for the object
+    Path workDir = createRandomPath(finalDir);
+    LOG.info("Creating " + destination.getName() + " in " + workDir + " with " + 
+            permission);
+    if (!localFs.mkdirs(workDir, permission)) {
+      throw new IOException("Mkdirs failed to create directory " + workDir);
+    }
+    Path workFile = new Path(workDir, parchive.getName());
+    sourceFs.copyToLocalFile(sourcePath, workFile);
+    localFs.setPermission(workFile, permission);
     if (isArchive) {
-      String tmpArchive = parchive.toString().toLowerCase();
-      File srcFile = new File(parchive.toString());
-      File destDir = new File(parchive.getParent().toString());
+      String tmpArchive = workFile.getName().toLowerCase();
+      File srcFile = new File(workFile.toString());
+      File destDir = new File(workDir.toString());
       LOG.info(String.format("Extracting %s to %s",
-          srcFile.toString(), destDir.toString()));
+               srcFile.toString(), destDir.toString()));
       if (tmpArchive.endsWith(".jar")) {
         RunJar.unJar(srcFile, destDir);
       } else if (tmpArchive.endsWith(".zip")) {
@@ -479,47 +514,48 @@ public class TrackerDistributedCacheManager {
         // else will not do anyhting
         // and copy the file into the dir as it is
       }
+      FileUtil.chmod(destDir.toString(), "ugo+rx", true);
+    }
+    // promote the output to the final location
+    if (!localFs.rename(workDir, finalDir)) {
+      localFs.delete(workDir, true);
+      if (!localFs.exists(finalDir)) {
+        throw new IOException("Failed to promote distributed cache object " +
+                              workDir + " to " + finalDir);
+      }
+      // someone else promoted first
+      return 0;
     }
 
+    LOG.info(String.format("Cached %s as %s",
+             source.toString(), destination.toString()));
     long cacheSize = 
       FileUtil.getDU(new File(parchive.getParent().toString()));
-    cacheStatus.size = cacheSize;
+    return cacheSize;
+  }
+
+  //the method which actually copies the caches locally and unjars/unzips them
+  // and does chmod for the files
+  Path localizePublicCacheObject(Configuration conf,
+                                 URI cache, long confFileStamp,
+                                 CacheStatus cacheStatus,
+                                 FileStatus fileStatus,
+                                 boolean isArchive
+                                 ) throws IOException, InterruptedException {
+    long size = downloadCacheObject(conf, cache, cacheStatus.localizedLoadPath,
+                                    confFileStamp, isArchive, 
+                                    PUBLIC_CACHE_OBJECT_PERM);
+    cacheStatus.size = size;
+
     // Increase the size and sub directory count of the cache
     // from baseDirSize and baseDirNumberSubDir.
     baseDirManager.addCacheUpdate(cacheStatus);
 
-    // set proper permissions for the localized directory
-    setPermissions(conf, cacheStatus, isPublic);
-
-    // update cacheStatus to reflect the newly cached file
-    cacheStatus.mtime = getTimestamp(conf, cache);
-
     LOG.info(String.format("Cached %s as %s",
              cache.toString(), cacheStatus.localizedLoadPath));
     return cacheStatus.localizedLoadPath;
   }
 
-  private void setPermissions(Configuration conf, CacheStatus cacheStatus,
-      boolean isPublic) throws IOException {
-    if (isPublic) {
-      Path localizedUniqueDir = cacheStatus.getLocalizedUniqueDir();
-      LOG.info("Doing chmod on localdir :" + localizedUniqueDir);
-      try {
-        FileUtil.chmod(localizedUniqueDir.toString(), "ugo+rx", true);
-      } catch (InterruptedException e) {
-        LOG.warn("Exception in chmod" + e.toString());
-        throw new IOException(e);
-      }
-    } else {
-      // invoke taskcontroller to set permissions
-      DistributedCacheFileContext context = new DistributedCacheFileContext(
-          conf.get(MRJobConfig.USER_NAME), new File(cacheStatus.localizedBaseDir
-              .toString()), cacheStatus.localizedBaseDir,
-          cacheStatus.uniqueString);
-      taskController.initializeDistributedCacheFile(context);
-    }
-  }
-
   private static boolean isTarFile(String filename) {
     return (filename.endsWith(".tgz") || filename.endsWith(".tar.gz") ||
            filename.endsWith(".tar"));
@@ -553,12 +589,18 @@ public class TrackerDistributedCacheManager {
                                           CacheStatus lcacheStatus,
                                           FileStatus fileStatus)
   throws IOException {
-    long dfsFileStamp = checkStampSinceJobStarted(conf, fs, cache,
-        confFileStamp, lcacheStatus, fileStatus);
-    if (dfsFileStamp != lcacheStatus.mtime) {
-      return false;
+    long dfsFileStamp;
+    if (fileStatus != null) {
+      dfsFileStamp = fileStatus.getModificationTime();
+    } else {
+      dfsFileStamp = getTimestamp(conf, cache);
     }
 
+    if (dfsFileStamp != confFileStamp) {
+      LOG.fatal("File: " + cache + " has changed on HDFS since job started");
+      throw new IOException("File: " + cache +
+                            " has changed on HDFS since job started");
+    }
     return true;
   }
 
@@ -607,7 +649,6 @@ public class TrackerDistributedCacheManager {
     // individual cacheStatus lock.
     //
     long size;              //the size of this cache.
-    long mtime;             // the cache-file modification time
     boolean inited = false; // is it initialized ?
 
     //
@@ -618,19 +659,21 @@ public class TrackerDistributedCacheManager {
     final Path subDir;
     // unique string used in the construction of local load path
     final String uniqueString;
+    // The user that owns the cache entry or null if it is public
+    final String user;
     // the local load path of this cache
     final Path localizedLoadPath;
     //the base dir where the cache lies
     final Path localizedBaseDir;
 
     public CacheStatus(Path baseDir, Path localLoadPath, Path subDir,
-        String uniqueString) {
+        String uniqueString, String user) {
       super();
       this.localizedLoadPath = localLoadPath;
       this.refcount = 0;
-      this.mtime = -1;
       this.localizedBaseDir = baseDir;
       this.size = 0;
+      this.user = user;
       this.subDir = subDir;
       this.uniqueString = uniqueString;
     }
@@ -673,8 +716,22 @@ public class TrackerDistributedCacheManager {
   }
 
   public TaskDistributedCacheManager newTaskDistributedCacheManager(
-      Configuration taskConf) throws IOException {
-    return new TaskDistributedCacheManager(this, taskConf);
+      JobID jobId, Configuration taskConf) throws IOException {
+    TaskDistributedCacheManager result =
+      new TaskDistributedCacheManager(this, taskConf);
+    jobArchives.put(jobId, result);
+    return result;
+  }
+ 
+  public void deleteTaskDistributedCacheManager(JobID jobId) {
+    jobArchives.remove(jobId);
+  }
+  
+  public void setArchiveSizes(JobID jobId, long[] sizes) throws IOException {
+    TaskDistributedCacheManager mgr = jobArchives.get(jobId);
+    if (mgr != null) {
+      mgr.setSizes(sizes);
+    }
   }
 
   /**
@@ -787,6 +844,17 @@ public class TrackerDistributedCacheManager {
     }
   }
   
+  private static boolean[] parseBooleans(String[] strs) {
+    if (null == strs) {
+      return null;
+    }
+    boolean[] result = new boolean[strs.length];
+    for(int i=0; i < strs.length; ++i) {
+      result[i] = Boolean.parseBoolean(strs[i]);
+    }
+    return result;
+  }
+
   /**
    * Get the booleans on whether the files are public or not.  Used by 
    * internal DistributedCache and MapReduce code.
@@ -794,8 +862,8 @@ public class TrackerDistributedCacheManager {
    * @return a string array of booleans 
    * @throws IOException
    */
-  static String[] getFileVisibilities(Configuration conf) {
-    return conf.getStrings(MRJobConfig.CACHE_FILE_VISIBILITIES);
+  public static boolean[] getFileVisibilities(Configuration conf) {
+    return parseBooleans(conf.getStrings(MRJobConfig.CACHE_FILE_VISIBILITIES));
   }
 
   /**
@@ -804,8 +872,8 @@ public class TrackerDistributedCacheManager {
    * @param conf The configuration which stored the timestamps
    * @return a string array of booleans 
    */
-  static String[] getArchiveVisibilities(Configuration conf) {
-    return conf.getStrings(MRJobConfig.CACHE_ARCHIVES_VISIBILITIES);
+  public static boolean[] getArchiveVisibilities(Configuration conf) {
+    return parseBooleans(conf.getStrings(MRJobConfig.CACHE_ARCHIVES_VISIBILITIES));
   }
 
   /**

+ 2 - 2
mapreduce/src/java/org/apache/hadoop/mapreduce/lib/chain/ChainMapContextImpl.java

@@ -131,7 +131,7 @@ class ChainMapContextImpl<KEYIN, VALUEIN, KEYOUT, VALUEOUT> implements
   }
 
   @Override
-  public String[] getArchiveTimestamps() {
+  public long[] getArchiveTimestamps() {
     return base.getArchiveTimestamps();
   }
 
@@ -162,7 +162,7 @@ class ChainMapContextImpl<KEYIN, VALUEIN, KEYOUT, VALUEOUT> implements
   }
 
   @Override
-  public String[] getFileTimestamps() {
+  public long[] getFileTimestamps() {
     return base.getFileTimestamps();
   }
 

+ 2 - 2
mapreduce/src/java/org/apache/hadoop/mapreduce/lib/chain/ChainReduceContextImpl.java

@@ -124,7 +124,7 @@ class ChainReduceContextImpl<KEYIN, VALUEIN, KEYOUT, VALUEOUT> implements
   }
 
   @Override
-  public String[] getArchiveTimestamps() {
+  public long[] getArchiveTimestamps() {
     return base.getArchiveTimestamps();
   }
 
@@ -155,7 +155,7 @@ class ChainReduceContextImpl<KEYIN, VALUEIN, KEYOUT, VALUEOUT> implements
   }
 
   @Override
-  public String[] getFileTimestamps() {
+  public long[] getFileTimestamps() {
     return base.getFileTimestamps();
   }
 

+ 2 - 2
mapreduce/src/java/org/apache/hadoop/mapreduce/lib/map/WrappedMapper.java

@@ -133,7 +133,7 @@ public class WrappedMapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT>
     }
 
     @Override
-    public String[] getArchiveTimestamps() {
+    public long[] getArchiveTimestamps() {
       return mapContext.getArchiveTimestamps();
     }
 
@@ -164,7 +164,7 @@ public class WrappedMapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT>
     }
 
     @Override
-    public String[] getFileTimestamps() {
+    public long[] getFileTimestamps() {
       return mapContext.getFileTimestamps();
     }
 

+ 2 - 2
mapreduce/src/java/org/apache/hadoop/mapreduce/lib/reduce/WrappedReducer.java

@@ -126,7 +126,7 @@ public class WrappedReducer<KEYIN, VALUEIN, KEYOUT, VALUEOUT>
     }
 
     @Override
-    public String[] getArchiveTimestamps() {
+    public long[] getArchiveTimestamps() {
       return reduceContext.getArchiveTimestamps();
     }
 
@@ -157,7 +157,7 @@ public class WrappedReducer<KEYIN, VALUEIN, KEYOUT, VALUEOUT>
     }
 
     @Override
-    public String[] getFileTimestamps() {
+    public long[] getFileTimestamps() {
       return reduceContext.getFileTimestamps();
     }
 

+ 1 - 1
mapreduce/src/java/org/apache/hadoop/mapreduce/security/TokenCache.java

@@ -176,7 +176,7 @@ public class TokenCache {
    * @throws IOException
    */
   @InterfaceAudience.Private
-  public static Credentials loadTokens(String jobTokenFile, JobConf conf) 
+  public static Credentials loadTokens(String jobTokenFile, Configuration conf) 
   throws IOException {
     Path localJobTokenFile = new Path ("file:///" + jobTokenFile);
 

+ 4 - 18
mapreduce/src/java/org/apache/hadoop/mapreduce/server/tasktracker/Localizer.java

@@ -17,7 +17,6 @@
  */
 package org.apache.hadoop.mapreduce.server.tasktracker;
 
-import java.io.File;
 import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
@@ -28,13 +27,11 @@ import org.apache.commons.logging.LogFactory;
 import org.apache.hadoop.classification.InterfaceAudience;
 import org.apache.hadoop.classification.InterfaceStability;
 import org.apache.hadoop.fs.FileSystem;
-import org.apache.hadoop.fs.FileUtil;
 import org.apache.hadoop.fs.Path;
 import org.apache.hadoop.fs.permission.FsPermission;
 import org.apache.hadoop.mapred.TaskController;
 import org.apache.hadoop.mapred.TaskLog;
 import org.apache.hadoop.mapred.TaskTracker;
-import org.apache.hadoop.mapred.TaskController.InitializationContext;
 import org.apache.hadoop.mapreduce.JobID;
 
 @InterfaceAudience.Private
@@ -45,19 +42,16 @@ public class Localizer {
 
   private FileSystem fs;
   private String[] localDirs;
-  private TaskController taskController;
 
   /**
    * Create a Localizer instance
    * 
    * @param fileSys
    * @param lDirs
-   * @param tc
    */
-  public Localizer(FileSystem fileSys, String[] lDirs, TaskController tc) {
+  public Localizer(FileSystem fileSys, String[] lDirs) {
     fs = fileSys;
     localDirs = lDirs;
-    taskController = tc;
   }
 
   // Data-structure for synchronizing localization of user directories.
@@ -162,13 +156,6 @@ public class Localizer {
                 + user);
       }
 
-      // Now, run the task-controller specific code to initialize the
-      // user-directories.
-      InitializationContext context = new InitializationContext();
-      context.user = user;
-      context.workDir = null;
-      taskController.initializeUser(context);
-
       // Localization of the user is done
       localizedUser.set(true);
     }
@@ -181,7 +168,7 @@ public class Localizer {
    * <br>
    * Here, we set 700 permissions on the job directories created on all disks.
    * This we do so as to avoid any misuse by other users till the time
-   * {@link TaskController#initializeJob(JobInitializationContext)} is run at a
+   * {@link TaskController#initializeJob} is run at a
    * later time to set proper private permissions on the job directories. <br>
    * 
    * @param user
@@ -228,16 +215,15 @@ public class Localizer {
    * @param user
    * @param jobId
    * @param attemptId
-   * @param isCleanupAttempt
    * @throws IOException
    */
   public void initializeAttemptDirs(String user, String jobId,
-      String attemptId, boolean isCleanupAttempt)
+      String attemptId)
       throws IOException {
 
     boolean initStatus = false;
     String attemptDirPath =
-        TaskTracker.getLocalTaskDir(user, jobId, attemptId, isCleanupAttempt);
+        TaskTracker.getLocalTaskDir(user, jobId, attemptId);
 
     for (String localDir : localDirs) {
       Path localAttemptDir = new Path(localDir, attemptDirPath);

+ 4 - 2
mapreduce/src/java/org/apache/hadoop/mapreduce/task/JobContextImpl.java

@@ -334,7 +334,8 @@ public class JobContextImpl implements JobContext {
    * @return a string array of timestamps 
    * @throws IOException
    */
-  public String[] getArchiveTimestamps() {
+  @Override
+  public long[] getArchiveTimestamps() {
     return DistributedCache.getArchiveTimestamps(conf);
   }
 
@@ -344,7 +345,8 @@ public class JobContextImpl implements JobContext {
    * @return a string array of timestamps 
    * @throws IOException
    */
-  public String[] getFileTimestamps() {
+  @Override
+  public long[] getFileTimestamps() {
     return DistributedCache.getFileTimestamps(conf);
   }
 

+ 72 - 8
mapreduce/src/java/org/apache/hadoop/mapreduce/util/MRAsyncDiskService.java

@@ -30,6 +30,7 @@ import org.apache.hadoop.fs.FileStatus;
 import org.apache.hadoop.fs.FileSystem;
 import org.apache.hadoop.fs.Path;
 import org.apache.hadoop.mapred.JobConf;
+import org.apache.hadoop.mapred.TaskController;
 import org.apache.hadoop.util.AsyncDiskService;
 import org.apache.hadoop.util.StringUtils;
 import org.apache.hadoop.classification.InterfaceAudience;
@@ -57,6 +58,8 @@ public class MRAsyncDiskService {
   
   AsyncDiskService asyncDiskService;
   
+  TaskController taskController;
+  
   public static final String TOBEDELETED = "toBeDeleted";
   
   /**
@@ -64,14 +67,18 @@ public class MRAsyncDiskService {
    * root directories).
    * 
    * The AsyncDiskServices uses one ThreadPool per volume to do the async disk
-   * operations.
+   * operations. A {@link TaskController} is passed that will be used to do
+   * the deletes
    * 
    * @param localFileSystem The localFileSystem used for deletions.
+   * @param taskController The taskController that should be used for the 
+   * delete operations
    * @param nonCanonicalVols The roots of the file system volumes, which may
    * be absolte paths, or paths relative to the ${user.dir} system property
    * ("cwd").
    */
-  public MRAsyncDiskService(FileSystem localFileSystem,
+  public MRAsyncDiskService(FileSystem localFileSystem, 
+      TaskController taskController,
       String... nonCanonicalVols) throws IOException {
     
     this.localFileSystem = localFileSystem;
@@ -84,6 +91,8 @@ public class MRAsyncDiskService {
     
     asyncDiskService = new AsyncDiskService(this.volumes);
     
+    this.taskController = taskController;
+    
     // Create one ThreadPool per volume
     for (int v = 0 ; v < volumes.length; v++) {
       // Create the root for file deletion
@@ -109,12 +118,30 @@ public class MRAsyncDiskService {
               + " because it's outside of " + volumes[v]);
         }
         DeleteTask task = new DeleteTask(volumes[v], absoluteFilename,
-            relative);
+            relative, files[f].getOwner());
         execute(volumes[v], task);
       }
     }
   }
   
+  /**
+   * Create a AsyncDiskServices with a set of volumes (specified by their
+   * root directories).
+   * 
+   * The AsyncDiskServices uses one ThreadPool per volume to do the async disk
+   * operations.
+   * 
+   * @param localFileSystem The localFileSystem used for deletions.
+   * @param nonCanonicalVols The roots of the file system volumes, which may
+   * be absolte paths, or paths relative to the ${user.dir} system property
+   * ("cwd").
+   */ 
+  public MRAsyncDiskService(FileSystem localFileSystem, 
+      String... nonCanonicalVols) throws IOException {
+    this(localFileSystem, null, nonCanonicalVols);
+  }
+  
+  
   /**
    * Initialize MRAsyncDiskService based on conf.
    * @param conf  local file system and local dirs will be read from conf 
@@ -174,6 +201,8 @@ public class MRAsyncDiskService {
     String originalPath;
     /** The file name after the move */
     String pathToBeDeleted;
+    /** The owner of the file */
+    String owner;
     
     /**
      * Delete a file/directory (recursively if needed).
@@ -181,11 +210,14 @@ public class MRAsyncDiskService {
      * @param originalPath  The original name, relative to volume root.
      * @param pathToBeDeleted  The name after the move, relative to volume root,
      *                         containing TOBEDELETED.
+     * @param owner         The owner of the file
      */
-    DeleteTask(String volume, String originalPath, String pathToBeDeleted) {
+    DeleteTask(String volume, String originalPath, String pathToBeDeleted, 
+        String owner) {
       this.volume = volume;
       this.originalPath = originalPath;
       this.pathToBeDeleted = pathToBeDeleted;
+      this.owner = owner;
     }
     
     @Override
@@ -201,7 +233,12 @@ public class MRAsyncDiskService {
       Exception e = null;
       try {
         Path absolutePathToBeDeleted = new Path(volume, pathToBeDeleted);
-        success = localFileSystem.delete(absolutePathToBeDeleted, true);
+        if (taskController != null & owner != null) {
+          taskController.deleteAsUser(owner, 
+                                      absolutePathToBeDeleted.toString());
+        } else {
+          success = localFileSystem.delete(absolutePathToBeDeleted, true);
+        }
       } catch (Exception ex) {
         e = ex;
       }
@@ -262,8 +299,9 @@ public class MRAsyncDiskService {
       // Return false in case that the file is not found.  
       return false;
     }
-
-    DeleteTask task = new DeleteTask(volume, pathName, newPathName);
+    FileStatus status = localFileSystem.getFileStatus(target);
+    DeleteTask task = new DeleteTask(volume, pathName, newPathName, 
+                                     status.getOwner());
     execute(volume, task);
     return true;
   }
@@ -371,5 +409,31 @@ public class MRAsyncDiskService {
     throw new IOException("Cannot delete " + absolutePathName
         + " because it's outside of all volumes.");
   }
-  
+  /**
+   * Move the path name to a temporary location and then delete it.
+   * 
+   * Note that if there is no volume that contains this path, the path
+   * will stay as it is, and the function will return false.
+   *  
+   * This functions returns when the moves are done, but not necessarily all
+   * deletions are done. This is usually good enough because applications 
+   * won't see the path name under the old name anyway after the move. 
+   * 
+   * @param volume              The disk volume
+   * @param absolutePathName    The path name from root "/"
+   * @throws IOException        If the move failed
+   * @return   false if we are unable to move the path name
+   */
+  public boolean moveAndDeleteAbsolutePath(String volume, 
+                                           String absolutePathName)
+  throws IOException {
+    String relative = getRelativePathName(absolutePathName, volume);
+    if (relative == null) {
+      // This should never happen
+      throw new IOException("Cannot delete " + absolutePathName
+          + " because it's outside of " + volume);
+    }
+    return moveAndDeleteRelativePath(volume, relative);
+  }
+
 }

+ 0 - 352
mapreduce/src/java/org/apache/hadoop/mapreduce/util/ProcessTree.java

@@ -1,352 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.hadoop.mapreduce.util;
-
-import java.io.IOException;
-import java.util.Arrays;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-import org.apache.hadoop.classification.InterfaceAudience;
-import org.apache.hadoop.classification.InterfaceStability;
-import org.apache.hadoop.util.StringUtils;
-import org.apache.hadoop.util.Shell.ExitCodeException;
-import org.apache.hadoop.util.Shell.ShellCommandExecutor;
-
-/** 
- * Process tree related operations
- */
-@InterfaceAudience.Private
-@InterfaceStability.Unstable
-public class ProcessTree {
-
-  private static final Log LOG = LogFactory.getLog(ProcessTree.class);
-
-  public static final long DEFAULT_SLEEPTIME_BEFORE_SIGKILL = 5000L;
-
-  private static final int SIGQUIT = 3;
-  private static final int SIGTERM = 15;
-  private static final int SIGKILL = 9;
-
-  private static final String SIGQUIT_STR = "SIGQUIT";
-  private static final String SIGTERM_STR = "SIGTERM";
-  private static final String SIGKILL_STR = "SIGKILL";
-
-
-  public static final boolean isSetsidAvailable = isSetsidSupported();
-  private static boolean isSetsidSupported() {
-    ShellCommandExecutor shexec = null;
-    boolean setsidSupported = true;
-    try {
-      String[] args = {"setsid", "bash", "-c", "echo $$"};
-      shexec = new ShellCommandExecutor(args);
-      shexec.execute();
-    } catch (IOException ioe) {
-      LOG.warn("setsid is not available on this machine. So not using it.");
-      setsidSupported = false;
-    } finally { // handle the exit code
-      LOG.info("setsid exited with exit code " + shexec.getExitCode());
-    }
-    return setsidSupported;
-  }
-
-  /**
-   * Destroy the process-tree.
-   * @param pid process id of the root process of the subtree of processes
-   *            to be killed
-   * @param sleeptimeBeforeSigkill The time to wait before sending SIGKILL
-   *                               after sending SIGTERM
-   * @param isProcessGroup pid is a process group leader or not
-   * @param inBackground Process is to be killed in the back ground with
-   *                     a separate thread
-   */
-  public static void destroy(String pid, long sleeptimeBeforeSigkill,
-                             boolean isProcessGroup, boolean inBackground) {
-    if(isProcessGroup) {
-      destroyProcessGroup(pid, sleeptimeBeforeSigkill, inBackground);
-    }
-    else {
-      //TODO: Destroy all the processes in the subtree in this case also.
-      // For the time being, killing only the root process.
-      destroyProcess(pid, sleeptimeBeforeSigkill, inBackground);
-    }
-  }
-
-  /** Destroy the process.
-   * @param pid Process id of to-be-killed-process
-   * @param sleeptimeBeforeSigkill The time to wait before sending SIGKILL
-   *                               after sending SIGTERM
-   * @param inBackground Process is to be killed in the back ground with
-   *                     a separate thread
-   */
-  protected static void destroyProcess(String pid, long sleeptimeBeforeSigkill,
-                                    boolean inBackground) {
-    terminateProcess(pid);
-    sigKill(pid, false, sleeptimeBeforeSigkill, inBackground);
-  }
-
-  /** Destroy the process group.
-   * @param pgrpId Process group id of to-be-killed-processes
-   * @param sleeptimeBeforeSigkill The time to wait before sending SIGKILL
-   *                               after sending SIGTERM
-   * @param inBackground Process group is to be killed in the back ground with
-   *                     a separate thread
-   */
-  protected static void destroyProcessGroup(String pgrpId,
-                       long sleeptimeBeforeSigkill, boolean inBackground) {
-    terminateProcessGroup(pgrpId);
-    sigKill(pgrpId, true, sleeptimeBeforeSigkill, inBackground);
-  }
-
-
-  /**
-   * Send a specified signal to the specified pid
-   *
-   * @param pid the pid of the process [group] to signal.
-   * @param signalNum the signal to send.
-   * @param signalName the human-readable description of the signal
-   * (for logging).
-   */
-  private static void sendSignal(String pid, int signalNum, String signalName) {
-    ShellCommandExecutor shexec = null;
-    try {
-      String[] args = { "kill", "-" + signalNum, pid };
-      shexec = new ShellCommandExecutor(args);
-      shexec.execute();
-    } catch (IOException ioe) {
-      LOG.warn("Error executing shell command " + ioe);
-    } finally {
-      if (pid.startsWith("-")) {
-        LOG.info("Sending signal to all members of process group " + pid
-            + ": " + signalName + ". Exit code " + shexec.getExitCode());
-      } else {
-        LOG.info("Signaling process " + pid
-            + " with " + signalName + ". Exit code " + shexec.getExitCode());
-      }
-    }
-  }
-
-  /**
-   * Send a specified signal to the process, if it is alive.
-   *
-   * @param pid the pid of the process to signal.
-   * @param signalNum the signal to send.
-   * @param signalName the human-readable description of the signal
-   * (for logging).
-   * @param alwaysSignal if true then send signal even if isAlive(pid) is false
-   */
-  private static void maybeSignalProcess(String pid, int signalNum,
-      String signalName, boolean alwaysSignal) {
-    // If process tree is not alive then don't signal, unless alwaysSignal
-    // forces it so.
-    if (alwaysSignal || ProcessTree.isAlive(pid)) {
-      sendSignal(pid, signalNum, signalName);
-    }
-  }
-
-  private static void maybeSignalProcessGroup(String pgrpId, int signalNum,
-      String signalName, boolean alwaysSignal) {
-
-    if (alwaysSignal || ProcessTree.isProcessGroupAlive(pgrpId)) {
-      // signaling a process group means using a negative pid.
-      sendSignal("-" + pgrpId, signalNum, signalName);
-    }
-  }
-
-  /**
-   * Sends terminate signal to the process, allowing it to gracefully exit.
-   * 
-   * @param pid pid of the process to be sent SIGTERM
-   */
-  public static void terminateProcess(String pid) {
-    maybeSignalProcess(pid, SIGTERM, SIGTERM_STR, true);
-  }
-
-  /**
-   * Sends terminate signal to all the process belonging to the passed process
-   * group, allowing the group to gracefully exit.
-   * 
-   * @param pgrpId process group id
-   */
-  public static void terminateProcessGroup(String pgrpId) {
-    maybeSignalProcessGroup(pgrpId, SIGTERM, SIGTERM_STR, true);
-  }
-
-  /**
-   * Kills the process(OR process group) by sending the signal SIGKILL
-   * in the current thread
-   * @param pid Process id(OR process group id) of to-be-deleted-process
-   * @param isProcessGroup Is pid a process group id of to-be-deleted-processes
-   * @param sleepTimeBeforeSigKill wait time before sending SIGKILL after
-   *  sending SIGTERM
-   */
-  private static void sigKillInCurrentThread(String pid, boolean isProcessGroup,
-      long sleepTimeBeforeSigKill) {
-    // Kill the subprocesses of root process(even if the root process is not
-    // alive) if process group is to be killed.
-    if (isProcessGroup || ProcessTree.isAlive(pid)) {
-      try {
-        // Sleep for some time before sending SIGKILL
-        Thread.sleep(sleepTimeBeforeSigKill);
-      } catch (InterruptedException i) {
-        LOG.warn("Thread sleep is interrupted.");
-      }
-      if(isProcessGroup) {
-        killProcessGroup(pid);
-      } else {
-        killProcess(pid);
-      }
-    }  
-  }
-  
-
-  /** Kills the process(OR process group) by sending the signal SIGKILL
-   * @param pid Process id(OR process group id) of to-be-deleted-process
-   * @param isProcessGroup Is pid a process group id of to-be-deleted-processes
-   * @param sleeptimeBeforeSigkill The time to wait before sending SIGKILL
-   *                               after sending SIGTERM
-   * @param inBackground Process is to be killed in the back ground with
-   *                     a separate thread
-   */
-  private static void sigKill(String pid, boolean isProcessGroup,
-                        long sleeptimeBeforeSigkill, boolean inBackground) {
-
-    if(inBackground) { // use a separate thread for killing
-      SigKillThread sigKillThread = new SigKillThread(pid, isProcessGroup,
-                                                      sleeptimeBeforeSigkill);
-      sigKillThread.setDaemon(true);
-      sigKillThread.start();
-    }
-    else {
-      sigKillInCurrentThread(pid, isProcessGroup, sleeptimeBeforeSigkill);
-    }
-  }
-
-  /**
-   * Sends kill signal to process, forcefully terminating the process.
-   * 
-   * @param pid process id
-   */
-  public static void killProcess(String pid) {
-    maybeSignalProcess(pid, SIGKILL, SIGKILL_STR, false);
-  }
-
-  /**
-   * Sends SIGQUIT to process; Java programs will dump their stack to
-   * stdout.
-   *
-   * @param pid process id
-   */
-  public static void sigQuitProcess(String pid) {
-    maybeSignalProcess(pid, SIGQUIT, SIGQUIT_STR, false);
-  }
-
-  /**
-   * Sends kill signal to all process belonging to same process group,
-   * forcefully terminating the process group.
-   * 
-   * @param pgrpId process group id
-   */
-  public static void killProcessGroup(String pgrpId) {
-    maybeSignalProcessGroup(pgrpId, SIGKILL, SIGKILL_STR, false);
-  }
-
-  /**
-   * Sends SIGQUIT to all processes belonging to the same process group,
-   * ordering all processes in the group to send their stack dump to
-   * stdout.
-   *
-   * @param pgrpId process group id
-   */
-  public static void sigQuitProcessGroup(String pgrpId) {
-    maybeSignalProcessGroup(pgrpId, SIGQUIT, SIGQUIT_STR, false);
-  }
-
-  /**
-   * Is the process with PID pid still alive?
-   * This method assumes that isAlive is called on a pid that was alive not
-   * too long ago, and hence assumes no chance of pid-wrapping-around.
-   * 
-   * @param pid pid of the process to check.
-   * @return true if process is alive.
-   */
-  public static boolean isAlive(String pid) {
-    ShellCommandExecutor shexec = null;
-    try {
-      String[] args = { "kill", "-0", pid };
-      shexec = new ShellCommandExecutor(args);
-      shexec.execute();
-    } catch (ExitCodeException ee) {
-      return false;
-    } catch (IOException ioe) {
-      LOG.warn("Error executing shell command "
-          + Arrays.toString(shexec.getExecString()) + ioe);
-      return false;
-    }
-    return (shexec.getExitCode() == 0 ? true : false);
-  }
-  
-  /**
-   * Is the process group with  still alive?
-   * 
-   * This method assumes that isAlive is called on a pid that was alive not
-   * too long ago, and hence assumes no chance of pid-wrapping-around.
-   * 
-   * @param pgrpId process group id
-   * @return true if any of process in group is alive.
-   */
-  public static boolean isProcessGroupAlive(String pgrpId) {
-    ShellCommandExecutor shexec = null;
-    try {
-      String[] args = { "kill", "-0", "-"+pgrpId };
-      shexec = new ShellCommandExecutor(args);
-      shexec.execute();
-    } catch (ExitCodeException ee) {
-      return false;
-    } catch (IOException ioe) {
-      LOG.warn("Error executing shell command "
-          + Arrays.toString(shexec.getExecString()) + ioe);
-      return false;
-    }
-    return (shexec.getExitCode() == 0 ? true : false);
-  }
-  
-
-  /**
-   * Helper thread class that kills process-tree with SIGKILL in background
-   */
-  static class SigKillThread extends Thread {
-    private String pid = null;
-    private boolean isProcessGroup = false;
-
-    private long sleepTimeBeforeSigKill = DEFAULT_SLEEPTIME_BEFORE_SIGKILL;
-
-    private SigKillThread(String pid, boolean isProcessGroup, long interval) {
-      this.pid = pid;
-      this.isProcessGroup = isProcessGroup;
-      this.setName(this.getClass().getName() + "-" + pid);
-      sleepTimeBeforeSigKill = interval;
-    }
-
-    public void run() {
-      sigKillInCurrentThread(pid, isProcessGroup, sleepTimeBeforeSigKill);
-    }
-  }
-}

+ 32 - 108
mapreduce/src/java/org/apache/hadoop/mapreduce/util/ProcfsBasedProcessTree.java

@@ -35,15 +35,18 @@ import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.hadoop.classification.InterfaceAudience;
 import org.apache.hadoop.classification.InterfaceStability;
+import org.apache.hadoop.mapred.TaskController;
+import org.apache.hadoop.mapred.DefaultTaskController;
 import org.apache.hadoop.util.Shell.ShellCommandExecutor;
 import org.apache.hadoop.util.StringUtils;
+import static org.apache.hadoop.mapred.TaskController.Signal;
 
 /**
  * A Proc file-system based ProcessTree. Works only on Linux.
  */
 @InterfaceAudience.Private
 @InterfaceStability.Unstable
-public class ProcfsBasedProcessTree extends ProcessTree {
+public class ProcfsBasedProcessTree {
 
   static final Log LOG = LogFactory
       .getLog(ProcfsBasedProcessTree.class);
@@ -91,20 +94,19 @@ public class ProcfsBasedProcessTree extends ProcessTree {
   // to a test directory.
   private String procfsDir;
   
-  private Integer pid = -1;
+  private final Integer pid;
   private Long cpuTime = 0L;
   private boolean setsidUsed = false;
-  private long sleeptimeBeforeSigkill = DEFAULT_SLEEPTIME_BEFORE_SIGKILL;
 
-  private Map<Integer, ProcessInfo> processTree = new HashMap<Integer, ProcessInfo>();
+  private Map<Integer, ProcessInfo> processTree =
+    new HashMap<Integer, ProcessInfo>();
 
   public ProcfsBasedProcessTree(String pid) {
-    this(pid, false, DEFAULT_SLEEPTIME_BEFORE_SIGKILL);
+    this(pid, false);
   }
 
-  public ProcfsBasedProcessTree(String pid, boolean setsidUsed,
-                                long sigkillInterval) {
-    this(pid, setsidUsed, sigkillInterval, PROCFS);
+  public ProcfsBasedProcessTree(String pid, boolean setsidUsed) {
+    this(pid, setsidUsed, PROCFS);
   }
 
   /**
@@ -115,29 +117,14 @@ public class ProcfsBasedProcessTree extends ProcessTree {
    * 
    * @param pid root of the process tree
    * @param setsidUsed true, if setsid was used for the root pid
-   * @param sigkillInterval how long to wait between a SIGTERM and SIGKILL 
-   *                        when killing a process tree
    * @param procfsDir the root of a proc file system - only used for testing. 
    */
   public ProcfsBasedProcessTree(String pid, boolean setsidUsed,
-                                long sigkillInterval, String procfsDir) {
+      String procfsDir) {
     this.pid = getValidPID(pid);
     this.setsidUsed = setsidUsed;
-    sleeptimeBeforeSigkill = sigkillInterval;
     this.procfsDir = procfsDir;
   }
-  
-  /**
-   * Sets SIGKILL interval
-   * @deprecated Use {@link ProcfsBasedProcessTree#ProcfsBasedProcessTree(
-   *                  String, boolean, long)} instead
-   * @param interval The time to wait before sending SIGKILL
-   *                 after sending SIGTERM
-   */
-  @Deprecated
-  public void setSigKillInterval(long interval) {
-    sleeptimeBeforeSigkill = interval;
-  }
 
   /**
    * Checks if the ProcfsBasedProcessTree is available on this system.
@@ -238,112 +225,49 @@ public class ProcfsBasedProcessTree extends ProcessTree {
 
   /**
    * Is the root-process alive?
-   * 
    * @return true if the root-process is alive, false otherwise.
    */
-  public boolean isAlive() {
-    if (pid == -1) {
-      return false;
-    } else {
-      return isAlive(pid.toString());
-    }
+  boolean isAlive(int pid, TaskController taskController) {
+    try {
+      return taskController.signalTask(null, pid, Signal.NULL);
+    } catch (IOException ignored) { }
+    return false;
+  }
+
+  boolean isAlive(TaskController taskController) {
+    return isAlive(pid, taskController);
   }
 
   /**
    * Is any of the subprocesses in the process-tree alive?
-   * 
    * @return true if any of the processes in the process-tree is
    *           alive, false otherwise.
    */
-  public boolean isAnyProcessInTreeAlive() {
+  boolean isAnyProcessInTreeAlive(TaskController taskController) {
     for (Integer pId : processTree.keySet()) {
-      if (isAlive(pId.toString())) {
+      if (isAlive(pId, taskController)) {
         return true;
       }
     }
     return false;
   }
 
+
   /** Verify that the given process id is same as its process group id.
    * @param pidStr Process id of the to-be-verified-process
    * @param procfsDir  Procfs root dir
    */
-  static boolean checkPidPgrpidForMatch(String pidStr, String procfsDir) {
-    Integer pId = Integer.parseInt(pidStr);
-    // Get information for this process
-    ProcessInfo pInfo = new ProcessInfo(pId);
-    pInfo = constructProcessInfo(pInfo, procfsDir);
-    if (pInfo == null) {
-      // process group leader may have finished execution, but we still need to
-      // kill the subProcesses in the process group.
-      return true;
-    }
-
-    //make sure that pId and its pgrpId match
-    if (!pInfo.getPgrpId().equals(pId)) {
-      LOG.warn("Unexpected: Process with PID " + pId +
-               " is not a process group leader.");
-      return false;
-    }
-    if (LOG.isDebugEnabled()) {
-      LOG.debug(pId + " is a process group leader, as expected.");
-    }
-    return true;
-  }
-
-  /** Make sure that the given pid is a process group leader and then
-   * destroy the process group.
-   * @param pgrpId   Process group id of to-be-killed-processes
-   * @param interval The time to wait before sending SIGKILL
-   *                 after sending SIGTERM
-   * @param inBackground Process is to be killed in the back ground with
-   *                     a separate thread
-   */
-  public static void assertAndDestroyProcessGroup(String pgrpId, long interval,
-                       boolean inBackground)
-         throws IOException {
-    // Make sure that the pid given is a process group leader
-    if (!checkPidPgrpidForMatch(pgrpId, PROCFS)) {
-      throw new IOException("Process with PID " + pgrpId  +
-                          " is not a process group leader.");
-    }
-    destroyProcessGroup(pgrpId, interval, inBackground);
+  public boolean checkPidPgrpidForMatch() {
+    return checkPidPgrpidForMatch(pid, PROCFS);
   }
 
-  /**
-   * Destroy the process-tree.
-   */
-  public void destroy() {
-    destroy(true);
-  }
-  
-  /**
-   * Destroy the process-tree.
-   * @param inBackground Process is to be killed in the back ground with
-   *                     a separate thread
-   */
-  public void destroy(boolean inBackground) {
-    LOG.debug("Killing ProcfsBasedProcessTree of " + pid);
-    if (pid == -1) {
-      return;
-    }
-    if (isAlive(pid.toString())) {
-      if (isSetsidAvailable && setsidUsed) {
-        // In this case, we know that pid got created using setsid. So kill the
-        // whole processGroup.
-        try {
-          assertAndDestroyProcessGroup(pid.toString(), sleeptimeBeforeSigkill,
-                              inBackground);
-        } catch (IOException e) {
-          LOG.warn(StringUtils.stringifyException(e));
-        }
-      }
-      else {
-        //TODO: Destroy all the processes in the subtree in this case also.
-        // For the time being, killing only the root process.
-        destroyProcess(pid.toString(), sleeptimeBeforeSigkill, inBackground);
-      }
-    }
+  static boolean checkPidPgrpidForMatch(int _pid, String procfs) {
+    // Get information for this process
+    ProcessInfo pInfo = new ProcessInfo(_pid);
+    pInfo = constructProcessInfo(pInfo, procfs);
+    // null if process group leader finished execution; issue no warning
+    // make sure that pid and its pgrpId match
+    return pInfo == null || pInfo.getPgrpId().equals(_pid);
   }
 
   private static final String PROCESSTREE_DUMP_FORMAT =

+ 0 - 34
mapreduce/src/java/org/apache/hadoop/util/ProcessTree.java

@@ -1,34 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.hadoop.util;
-
-import org.apache.hadoop.classification.InterfaceAudience;
-import org.apache.hadoop.classification.InterfaceStability;
-
-/**
- * Process tree related operations
- * 
- * @deprecated Use {@link org.apache.hadoop.mapreduce.util.ProcessTree} instead
- */
-@Deprecated
-@InterfaceAudience.Private
-@InterfaceStability.Unstable
-public class ProcessTree extends org.apache.hadoop.mapreduce.util.ProcessTree {
-  // Inherits everything from the super class
-}

+ 8 - 2
mapreduce/src/java/org/apache/hadoop/util/ProcfsBasedProcessTree.java

@@ -38,14 +38,20 @@ public class ProcfsBasedProcessTree extends
     super(pid);
   }
 
+  /**
+   * @param sigkillInterval Has no effect
+   */
   public ProcfsBasedProcessTree(String pid, boolean setsidUsed,
       long sigkillInterval) {
-    super(pid, setsidUsed, sigkillInterval);
+    super(pid, setsidUsed);
   }
 
+  /**
+   * @param sigkillInterval Has no effect
+   */
   public ProcfsBasedProcessTree(String pid, boolean setsidUsed,
       long sigkillInterval, String procfsDir) {
-    super(pid, setsidUsed, sigkillInterval, procfsDir);
+    super(pid, setsidUsed, procfsDir);
   }
 
   public ProcfsBasedProcessTree getProcessTree() {

+ 6 - 23
mapreduce/src/test/mapred/org/apache/hadoop/mapred/ClusterWithLinuxTaskController.java

@@ -30,6 +30,7 @@ import org.apache.commons.logging.LogFactory;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.fs.FileStatus;
 import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.LocalDirAllocator;
 import org.apache.hadoop.fs.Path;
 import org.apache.hadoop.fs.permission.FsAction;
 import org.apache.hadoop.fs.permission.FsPermission;
@@ -80,41 +81,23 @@ public class ClusterWithLinuxTaskController extends TestCase {
         + "/task-controller";
     
     @Override
-    public void setup() throws IOException {
+    public void setup(LocalDirAllocator allocator) throws IOException {
       getConf().set(TTConfig.TT_GROUP, taskTrackerSpecialGroup);
 
       // write configuration file
       configurationFile = createTaskControllerConf(System
           .getProperty(TASKCONTROLLER_PATH), getConf());
-      super.setup();
+      super.setup(allocator);
     }
 
     @Override
-    protected String getTaskControllerExecutablePath() {
-      return new File(taskControllerExePath).getAbsolutePath();
+    protected String getTaskControllerExecutablePath(Configuration conf) {
+      return taskControllerExePath;
     }
 
     void setTaskControllerExe(String execPath) {
       this.taskControllerExePath = execPath;
     }
-
-    volatile static int attemptedSigQuits = 0;
-    volatile static int failedSigQuits = 0;
-
-    /** Work like LinuxTaskController, but also count the number of
-      * attempted and failed SIGQUIT sends via the task-controller
-      * executable.
-      */
-    @Override
-    void dumpTaskStack(TaskControllerContext context) {
-      attemptedSigQuits++;
-      try {
-        signalTask(context, TaskControllerCommands.SIGQUIT_TASK_JVM);
-      } catch (Exception e) {
-        LOG.warn("Execution sending SIGQUIT: " + StringUtils.stringifyException(e));
-        failedSigQuits++;
-      }
-    }
   }
 
   // cluster instances which sub classes can use
@@ -275,7 +258,7 @@ public class ClusterWithLinuxTaskController extends TestCase {
       if (ugi.indexOf(",") > 1) {
         return true;
       }
-      LOG.info("Invalid taskcontroller-ugi : " + ugi); 
+      LOG.info("Invalid taskcontroller-ugi (requires \"user,group\"): " + ugi); 
       return false;
     }
     LOG.info("Invalid taskcontroller-ugi : " + ugi);

+ 2 - 0
mapreduce/src/test/mapred/org/apache/hadoop/mapred/TestDebugScript.java

@@ -35,7 +35,9 @@ import static org.junit.Assert.*;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.Ignore;
 
+@Ignore("The debug script is broken in the current build.")
 public class TestDebugScript {
 
   // base directory which is used by the debug script

+ 0 - 30
mapreduce/src/test/mapred/org/apache/hadoop/mapred/TestJobExecutionAsDifferentUser.java

@@ -23,7 +23,6 @@ import java.security.PrivilegedExceptionAction;
 
 import org.apache.hadoop.fs.FileSystem;
 import org.apache.hadoop.fs.Path;
-import org.apache.hadoop.mapreduce.Job;
 import org.apache.hadoop.mapreduce.SleepJob;
 import org.apache.hadoop.util.ToolRunner;
 
@@ -108,33 +107,4 @@ public class TestJobExecutionAsDifferentUser extends
     });
   }
 
-  /** Ensure that SIGQUIT can be properly sent by the LinuxTaskController
-   * if a task times out.
-   */
-  public void testTimeoutStackTrace() throws Exception {
-    if (!shouldRun()) {
-      return;
-    }
-
-    // Run a job that should timeout and trigger a SIGQUIT.
-    startCluster();
-    jobOwner.doAs(new PrivilegedExceptionAction<Object>() {
-      public Object run() throws Exception {
-        JobConf conf = getClusterConf();
-        conf.setInt(JobContext.TASK_TIMEOUT, 10000);
-        conf.setInt(Job.COMPLETION_POLL_INTERVAL_KEY, 50);
-        SleepJob sleepJob = new SleepJob();
-        sleepJob.setConf(conf);
-        Job job = sleepJob.createJob(1, 0, 30000, 1, 0, 0);
-        job.setMaxMapAttempts(1);
-        int prevNumSigQuits = MyLinuxTaskController.attemptedSigQuits;
-        job.waitForCompletion(true);
-        assertTrue("Did not detect a new SIGQUIT!",
-            prevNumSigQuits < MyLinuxTaskController.attemptedSigQuits);
-        assertEquals("A SIGQUIT attempt failed!", 0,
-            MyLinuxTaskController.failedSigQuits);
-        return null;
-      }
-    });
-  }
 }

+ 6 - 71
mapreduce/src/test/mapred/org/apache/hadoop/mapred/TestJobKillAndFail.java

@@ -18,20 +18,12 @@
 
 package org.apache.hadoop.mapred;
 
-import java.io.BufferedReader;
-import java.io.FileInputStream;
 import java.io.File;
-import java.io.InputStreamReader;
 import java.io.IOException;
 
 import junit.framework.TestCase;
 
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
 import org.apache.hadoop.fs.Path;
-import org.apache.hadoop.mapreduce.Job;
-import org.apache.hadoop.mapreduce.TaskType;
-import org.apache.hadoop.mapreduce.SleepJob;
 
 /**
  * A JUnit test to test Kill Job & Fail Job functionality with local file
@@ -39,96 +31,39 @@ import org.apache.hadoop.mapreduce.SleepJob;
  */
 public class TestJobKillAndFail extends TestCase {
 
-  static final Log LOG = LogFactory.getLog(TestJobKillAndFail.class);
 
   private static String TEST_ROOT_DIR = new File(System.getProperty(
       "test.build.data", "/tmp")).toURI().toString().replace(' ', '+');
 
-  /**
-   * TaskController instance that just sets a flag when a stack dump
-   * is performed in a child thread.
-   */
-  static class MockStackDumpTaskController extends DefaultTaskController {
 
-    static volatile int numStackDumps = 0;
-
-    static final Log LOG = LogFactory.getLog(TestJobKillAndFail.class);
-
-    public MockStackDumpTaskController() {
-      LOG.info("Instantiated MockStackDumpTC");
-    }
-
-    @Override
-    void dumpTaskStack(TaskControllerContext context) {
-      LOG.info("Got stack-dump request in TaskController");
-      MockStackDumpTaskController.numStackDumps++;
-      super.dumpTaskStack(context);
-    }
-
-  }
-
-  /** If a task was killed, then dumpTaskStack() should have been
-    * called. Test whether or not the counter was incremented
-    * and succeed/fail based on this. */
-  private void checkForStackDump(boolean expectDump, int lastNumDumps) {
-    int curNumDumps = MockStackDumpTaskController.numStackDumps;
-
-    LOG.info("curNumDumps=" + curNumDumps + "; lastNumDumps=" + lastNumDumps
-        + "; expect=" + expectDump);
-
-    if (expectDump) {
-      assertTrue("No stack dump recorded!", lastNumDumps < curNumDumps);
-    } else {
-      assertTrue("Stack dump happened anyway!", lastNumDumps == curNumDumps);
-    }
-  }
-
-  public void testJobFailAndKill() throws Exception {
+  public void testJobFailAndKill() throws IOException {
     MiniMRCluster mr = null;
     try {
       JobConf jtConf = new JobConf();
       jtConf.set("mapred.jobtracker.instrumentation", 
           JTInstrumentation.class.getName());
-      jtConf.set("mapreduce.tasktracker.taskcontroller",
-          MockStackDumpTaskController.class.getName());
       mr = new MiniMRCluster(2, "file:///", 3, null, null, jtConf);
       JTInstrumentation instr = (JTInstrumentation) 
         mr.getJobTrackerRunner().getJobTracker().getInstrumentation();
 
       // run the TCs
       JobConf conf = mr.createJobConf();
-      conf.setInt(Job.COMPLETION_POLL_INTERVAL_KEY, 50);
       
       Path inDir = new Path(TEST_ROOT_DIR + "/failkilljob/input");
       Path outDir = new Path(TEST_ROOT_DIR + "/failkilljob/output");
-      RunningJob runningJob = UtilsForTests.runJobFail(conf, inDir, outDir);
+      RunningJob job = UtilsForTests.runJobFail(conf, inDir, outDir);
       // Checking that the Job got failed
-      assertEquals(runningJob.getJobState(), JobStatus.FAILED);
+      assertEquals(job.getJobState(), JobStatus.FAILED);
       assertTrue(instr.verifyJob());
       assertEquals(1, instr.failed);
       instr.reset();
 
-      int prevNumDumps = MockStackDumpTaskController.numStackDumps;
-      runningJob = UtilsForTests.runJobKill(conf, inDir, outDir);
+      job = UtilsForTests.runJobKill(conf, inDir, outDir);
       // Checking that the Job got killed
-      assertTrue(runningJob.isComplete());
-      assertEquals(runningJob.getJobState(), JobStatus.KILLED);
+      assertTrue(job.isComplete());
+      assertEquals(job.getJobState(), JobStatus.KILLED);
       assertTrue(instr.verifyJob());
       assertEquals(1, instr.killed);
-      // check that job kill does not put a stacktrace in task logs.
-      checkForStackDump(false, prevNumDumps);
-
-      // Test that a task that times out does have a stack trace
-      conf = mr.createJobConf();
-      conf.setInt(JobContext.TASK_TIMEOUT, 10000);
-      conf.setInt(Job.COMPLETION_POLL_INTERVAL_KEY, 50);
-      SleepJob sleepJob = new SleepJob();
-      sleepJob.setConf(conf);
-      Job job = sleepJob.createJob(1, 0, 30000, 1,0, 0);
-      job.setMaxMapAttempts(1);
-      prevNumDumps = MockStackDumpTaskController.numStackDumps;
-      job.waitForCompletion(true);
-      checkForStackDump(true, prevNumDumps);
     } finally {
       if (mr != null) {
         mr.shutdown();

+ 1 - 1
mapreduce/src/test/mapred/org/apache/hadoop/mapred/TestJobRetire.java

@@ -194,7 +194,7 @@ public class TestJobRetire extends TestCase {
     }
     
     @Override
-    public synchronized void shutdown() throws IOException {
+    public synchronized void shutdown() throws IOException, InterruptedException {
       alive = false;
       super.shutdown();
     }

+ 52 - 12
mapreduce/src/test/mapred/org/apache/hadoop/mapred/TestJvmManager.java

@@ -31,12 +31,19 @@ import java.util.concurrent.TimeUnit;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.fs.FileUtil;
-import org.apache.hadoop.mapred.JvmManager.JvmManagerForType;
+import org.apache.hadoop.fs.LocalDirAllocator;
+import org.apache.hadoop.fs.Path;
 import org.apache.hadoop.mapred.JvmManager.JvmManagerForType.JvmRunner;
+import org.apache.hadoop.mapred.JvmManager.JvmManagerForType;
+import org.apache.hadoop.mapred.TaskTracker.RunningJob;
 import org.apache.hadoop.mapred.TaskTracker.TaskInProgress;
+import org.apache.hadoop.mapred.UtilsForTests.InlineCleanupQueue;
 import org.apache.hadoop.mapreduce.TaskType;
 import org.apache.hadoop.mapreduce.server.tasktracker.TTConfig;
+import org.apache.hadoop.mapreduce.filecache.TrackerDistributedCacheManager;
+import org.apache.hadoop.security.UserGroupInformation;
 import org.junit.After;
 import static org.junit.Assert.*;
 import org.junit.Before;
@@ -52,6 +59,8 @@ public class TestJvmManager {
   private TaskTracker tt;
   private JvmManager jvmManager;
   private JobConf ttConf;
+  private boolean threadCaughtException = false;
+  private String user;
 
   @Before
   public void setUp() {
@@ -64,15 +73,23 @@ public class TestJvmManager {
   }
 
   public TestJvmManager() throws Exception {
+    user = UserGroupInformation.getCurrentUser().getShortUserName();
     tt = new TaskTracker();
     ttConf = new JobConf();
     ttConf.setLong(TTConfig.TT_SLEEP_TIME_BEFORE_SIG_KILL, 2000);
     tt.setConf(ttConf);
     tt.setMaxMapSlots(MAP_SLOTS);
     tt.setMaxReduceSlots(REDUCE_SLOTS);
-    tt.setTaskController(new DefaultTaskController());
+    TaskController dtc;
+    tt.setTaskController((dtc = new DefaultTaskController()));
+    Configuration conf = new Configuration();
+    dtc.setConf(conf);
+    LocalDirAllocator ldirAlloc = new LocalDirAllocator("mapred.local.dir");
+    tt.getTaskController().setup(ldirAlloc);
+    JobID jobId = new JobID("test", 0);
     jvmManager = new JvmManager(tt);
     tt.setJvmManagerInstance(jvmManager);
+    tt.setCleanupThread(new InlineCleanupQueue());
   }
 
   // write a shell script to execute the command.
@@ -107,16 +124,22 @@ public class TestJvmManager {
     // launch a jvm
     JobConf taskConf = new JobConf(ttConf);
     TaskAttemptID attemptID = new TaskAttemptID("test", 0, TaskType.MAP, 0, 0);
-    Task task = new MapTask(null, attemptID, 0, null, 1);
+    Task task = new MapTask(null, attemptID, 0, null, MAP_SLOTS);
+    task.setUser(user);
     task.setConf(taskConf);
     TaskInProgress tip = tt.new TaskInProgress(task, taskConf);
     File pidFile = new File(TEST_DIR, "pid");
-    final TaskRunner taskRunner = task.createRunner(tt, tip);
+    RunningJob rjob = new RunningJob(attemptID.getJobID());
+    TaskController taskController = new DefaultTaskController();
+    taskController.setConf(ttConf);
+    rjob.distCacheMgr = 
+      new TrackerDistributedCacheManager(ttConf).
+      newTaskDistributedCacheManager(attemptID.getJobID(), taskConf);
+    final TaskRunner taskRunner = task.createRunner(tt, tip, rjob);
     // launch a jvm which sleeps for 60 seconds
     final Vector<String> vargs = new Vector<String>(2);
     vargs.add(writeScript("SLEEP", "sleep 60\n", pidFile).getAbsolutePath());
     final File workDir = new File(TEST_DIR, "work");
-    workDir.mkdir();
     final File stdout = new File(TEST_DIR, "stdout");
     final File stderr = new File(TEST_DIR, "stderr");
 
@@ -125,10 +148,13 @@ public class TestJvmManager {
       public void run() {
         try {
           taskRunner.launchJvmAndWait(null, vargs, stdout, stderr, 100,
-              workDir, null);
+              workDir);
         } catch (InterruptedException e) {
           e.printStackTrace();
           return;
+        } catch (IOException e) {
+          e.printStackTrace();
+          setThreadCaughtException();
         }
       }
     };
@@ -156,7 +182,14 @@ public class TestJvmManager {
     final JvmRunner jvmRunner = mapJvmManager.jvmIdToRunner.get(jvmid);
     Thread killer = new Thread() {
       public void run() {
-        jvmRunner.kill();
+        try {
+          jvmRunner.kill();
+        } catch (IOException e) {
+          e.printStackTrace();
+          setThreadCaughtException();
+        } catch (InterruptedException e) {
+          e.printStackTrace();
+        }
       }
     };
     killer.start();
@@ -171,23 +204,27 @@ public class TestJvmManager {
 
     // launch another jvm and see it finishes properly
     attemptID = new TaskAttemptID("test", 0, TaskType.MAP, 0, 1);
-    task = new MapTask(null, attemptID, 0, null, 1);
+    task = new MapTask(null, attemptID, 0, null, MAP_SLOTS);
+    task.setUser(user);
     task.setConf(taskConf);
     tip = tt.new TaskInProgress(task, taskConf);
-    TaskRunner taskRunner2 = task.createRunner(tt, tip);
+    TaskRunner taskRunner2 = task.createRunner(tt, tip, rjob);
     // build dummy vargs to call ls
     Vector<String> vargs2 = new Vector<String>(1);
     vargs2.add(writeScript("LS", "ls", pidFile).getAbsolutePath());
     File workDir2 = new File(TEST_DIR, "work2");
-    workDir.mkdir();
     File stdout2 = new File(TEST_DIR, "stdout2");
     File stderr2 = new File(TEST_DIR, "stderr2");
-    taskRunner2.launchJvmAndWait(null, vargs2, stdout2, stderr2, 100, workDir2,
-        null);
+    taskRunner2.launchJvmAndWait(null, vargs2, stdout2, stderr2, 100, workDir2);
     // join all the threads
     killer.join();
     jvmRunner.join();
     launcher.join();
+    assertFalse("Thread caught unexpected IOException", 
+                 threadCaughtException);
+  }
+  private void setThreadCaughtException() {
+    threadCaughtException = true;
   }
 
 
@@ -198,6 +235,8 @@ public class TestJvmManager {
    */
   @Test
   public void testForRaces() throws Exception {
+    fail("TODO: re-enable test after 2178 merge");
+    /*
     JvmManagerForType mapJvmManager = jvmManager
         .getJvmManagerForType(TaskType.MAP);
 
@@ -248,6 +287,7 @@ public class TestJvmManager {
     if (failed.get() != null) {
       throw new RuntimeException(failed.get());
     }
+  */
   }
 
   /**

+ 10 - 7
mapreduce/src/test/mapred/org/apache/hadoop/mapred/TestKillSubProcesses.java

@@ -33,10 +33,10 @@ import org.apache.hadoop.fs.permission.FsAction;
 import org.apache.hadoop.fs.permission.FsPermission;
 import org.apache.hadoop.io.Writable;
 import org.apache.hadoop.io.WritableComparable;
+import org.apache.hadoop.mapred.TaskController;
 import org.apache.hadoop.mapreduce.util.TestProcfsBasedProcessTree;
 
 import org.apache.hadoop.util.StringUtils;
-import org.apache.hadoop.util.ProcessTree;
 import org.apache.hadoop.util.Shell;
 import org.apache.hadoop.util.Shell.ExitCodeException;
 import org.apache.hadoop.util.Shell.ShellCommandExecutor;
@@ -151,6 +151,8 @@ public class TestKillSubProcesses extends TestCase {
         break;
       }
     }
+    final TaskController tc =
+      mr.getTaskTrackerRunner(0).getTaskTracker().getTaskController();
 
     pid = null;
     jobClient = new JobClient(conf);
@@ -195,7 +197,7 @@ public class TestKillSubProcesses extends TestCase {
     }
 
     // Checking if the descendant processes of map task are alive
-    if(ProcessTree.isSetsidAvailable) {
+    if(TaskController.isSetsidAvailable) {
       String childPid = TestProcfsBasedProcessTree.getPidFromPidFile(
                                scriptDirName + "/childPidFile" + 0);
       while(childPid == null) {
@@ -243,11 +245,11 @@ public class TestKillSubProcesses extends TestCase {
     }
 
     // Checking if the map task got killed or not
-    assertTrue(!ProcessTree.isAlive(pid));
+    assertTrue(!isAlive(pid));
     LOG.info("The map task is not alive after Job is completed, as expected.");
 
     // Checking if the descendant processes of map task are killed properly
-    if(ProcessTree.isSetsidAvailable) {
+    if(TaskController.isSetsidAvailable) {
       for(int i=0; i <= numLevelsOfSubProcesses; i++) {
         String childPid = TestProcfsBasedProcessTree.getPidFromPidFile(
                                scriptDirName + "/childPidFile" + i);
@@ -310,9 +312,10 @@ public class TestKillSubProcesses extends TestCase {
       return;
     }
     
-    JobConf conf=null;
     try {
-      mr = new MiniMRCluster(1, "file:///", 1);
+      JobConf conf = new JobConf();
+      conf.setLong(JvmManager.JvmManagerForType.DELAY_BEFORE_KILL_KEY, 0L);
+      mr = new MiniMRCluster(1, "file:///", 1, null, null, conf);
 
       // run the TCs
       conf = mr.createJobConf();
@@ -354,7 +357,7 @@ public class TestKillSubProcesses extends TestCase {
    * Runs a recursive shell script to create a chain of subprocesses
    */
   private static void runChildren(JobConf conf) throws IOException {
-    if (ProcessTree.isSetsidAvailable) {
+    if (TaskController.isSetsidAvailable) {
       FileSystem fs = FileSystem.getLocal(conf);
 
       if (fs.exists(scriptDir)) {

+ 16 - 10
mapreduce/src/test/mapred/org/apache/hadoop/mapred/TestLinuxTaskController.java

@@ -22,22 +22,27 @@ import java.io.IOException;
 
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.fs.FileUtil;
+import org.apache.hadoop.fs.LocalDirAllocator;
 import org.apache.hadoop.mapreduce.MRConfig;
 import org.apache.hadoop.mapreduce.server.tasktracker.TTConfig;
 import org.apache.hadoop.security.Groups;
 import org.apache.hadoop.security.UserGroupInformation;
+
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 
+import static org.apache.hadoop.mapred.LinuxTaskController.ResultCode.*;
+
 import junit.framework.TestCase;
 
+@Ignore("Negative test relies on properties fixed during TC compilation")
 public class TestLinuxTaskController extends TestCase {
-  private static int INVALID_TASKCONTROLLER_PERMISSIONS = 24;
   private static File testDir = new File(System.getProperty("test.build.data",
       "/tmp"), TestLinuxTaskController.class.getName());
-  private static String taskControllerPath = System
-      .getProperty(ClusterWithLinuxTaskController.TASKCONTROLLER_PATH);
+  private static String taskControllerPath =
+    System.getProperty(ClusterWithLinuxTaskController.TASKCONTROLLER_PATH);
 
   @Before
   protected void setUp() throws Exception {
@@ -51,9 +56,8 @@ public class TestLinuxTaskController extends TestCase {
 
   public static class MyLinuxTaskController extends LinuxTaskController {
     String taskControllerExePath = taskControllerPath + "/task-controller";
-
     @Override
-    protected String getTaskControllerExecutablePath() {
+    protected String getTaskControllerExecutablePath(Configuration conf) {
       return taskControllerExePath;
     }
   }
@@ -64,16 +68,18 @@ public class TestLinuxTaskController extends TestCase {
       // task controller setup should fail validating permissions.
       Throwable th = null;
       try {
-        controller.setup();
+        controller.setup(new LocalDirAllocator("mapred.local.dir"));
       } catch (IOException ie) {
         th = ie;
       }
       assertNotNull("No exception during setup", th);
-      assertTrue("Exception message does not contain exit code"
-          + INVALID_TASKCONTROLLER_PERMISSIONS, th.getMessage().contains(
-          "with exit code " + INVALID_TASKCONTROLLER_PERMISSIONS));
+      assertTrue("Exception message \"" + th.getMessage() +
+            "\" does not contain exit code " +
+            INVALID_TASKCONTROLLER_PERMISSIONS.getValue(),
+          th.getMessage().contains(
+            "with exit code " + INVALID_TASKCONTROLLER_PERMISSIONS.getValue()));
     } else {
-      controller.setup();
+      controller.setup(new LocalDirAllocator("mapred.local.dir"));
     }
 
   }

+ 20 - 4
mapreduce/src/test/mapred/org/apache/hadoop/mapred/TestMapRed.java

@@ -45,8 +45,10 @@ import org.apache.hadoop.mapred.lib.IdentityMapper;
 import org.apache.hadoop.mapred.lib.IdentityReducer;
 import org.apache.hadoop.util.Tool;
 import org.apache.hadoop.util.ToolRunner;
-import org.junit.Test;
 
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -85,6 +87,17 @@ import static org.junit.Assert.assertFalse;
  *
  **********************************************************/
 public class TestMapRed extends Configured implements Tool {
+
+  static final Path TESTDIR =
+    new Path(System.getProperty("test.build.data", "/tmp"),
+        TestMapRed.class.getSimpleName());
+
+  @Before
+  public void removeTestdir() throws IOException {
+    final FileSystem rfs = FileSystem.getLocal(new Configuration()).getRaw();
+    rfs.delete(TESTDIR, true);
+  }
+
   /**
    * Modified to make it a junit test.
    * The RandomGen Job does the actual work of creating
@@ -370,7 +383,8 @@ public class TestMapRed extends Configured implements Tool {
                                 boolean includeCombine
                                 ) throws Exception {
     JobConf conf = new JobConf(TestMapRed.class);
-    Path testdir = new Path("build/test/test.mapred.compress");
+    Path testdir = new Path(System.getProperty("test.build.data", "/tmp"),
+        "test.mapred.compress");
     Path inDir = new Path(testdir, "in");
     Path outDir = new Path(testdir, "out");
     FileSystem fs = FileSystem.get(conf);
@@ -461,7 +475,8 @@ public class TestMapRed extends Configured implements Tool {
     // Write the answer key to a file.  
     //
     FileSystem fs = FileSystem.get(conf);
-    Path testdir = new Path("mapred.loadtest");
+    final Path testdir = new Path(System.getProperty("test.build.data", "/tmp"),
+        "mapred.loadtest");
     if (!fs.mkdirs(testdir)) {
       throw new IOException("Mkdirs failed to create " + testdir.toString());
     }
@@ -723,7 +738,8 @@ public class TestMapRed extends Configured implements Tool {
   public void runJob(int items) {
     try {
       JobConf conf = new JobConf(TestMapRed.class);
-      Path testdir = new Path("build/test/test.mapred.spill");
+      Path testdir = new Path(System.getProperty("build.test.data", "/tmp"),
+          "test.mapred.spill");
       Path inDir = new Path(testdir, "in");
       Path outDir = new Path(testdir, "out");
       FileSystem fs = FileSystem.get(conf);

+ 0 - 1
mapreduce/src/test/mapred/org/apache/hadoop/mapred/TestMiniMRWithDFS.java

@@ -134,7 +134,6 @@ public class TestMiniMRWithDFS extends TestCase {
           .isDirectory());
       LOG.info("Verifying contents of " + MRConfig.LOCAL_DIR + " "
           + localDir.getAbsolutePath());
-
       // Verify contents(user-dir) of tracker-sub-dir
       File trackerSubDir = new File(localDir, TaskTracker.SUBDIR);
       if (trackerSubDir.isDirectory()) {

+ 18 - 7
mapreduce/src/test/mapred/org/apache/hadoop/mapred/TestSequenceFileInputFormat.java

@@ -20,7 +20,10 @@ package org.apache.hadoop.mapred;
 
 import java.io.*;
 import java.util.*;
-import junit.framework.TestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import static org.junit.Assert.*;
 
 import org.apache.commons.logging.*;
 
@@ -28,17 +31,26 @@ import org.apache.hadoop.fs.*;
 import org.apache.hadoop.io.*;
 import org.apache.hadoop.conf.*;
 
-public class TestSequenceFileInputFormat extends TestCase {
+public class TestSequenceFileInputFormat {
   private static final Log LOG = FileInputFormat.LOG;
 
   private static int MAX_LENGTH = 10000;
   private static Configuration conf = new Configuration();
+  static final Path TESTDIR =
+    new Path(System.getProperty("test.build.data", "/tmp"),
+        TestSequenceFileInputFormat.class.getSimpleName());
+
+  @Before
+  public void removeTestdir() throws IOException {
+    final FileSystem rfs = FileSystem.getLocal(new Configuration()).getRaw();
+    rfs.delete(TESTDIR, true);
+  }
 
+  @Test
   public void testFormat() throws Exception {
     JobConf job = new JobConf(conf);
     FileSystem fs = FileSystem.getLocal(conf);
-    Path dir = new Path(System.getProperty("test.build.data",".") + "/mapred");
-    Path file = new Path(dir, "test.seq");
+    Path file = new Path(TESTDIR, "test.seq").makeQualified(fs);
     
     Reporter reporter = Reporter.NULL;
     
@@ -46,9 +58,7 @@ public class TestSequenceFileInputFormat extends TestCase {
     //LOG.info("seed = "+seed);
     Random random = new Random(seed);
 
-    fs.delete(dir, true);
-
-    FileInputFormat.setInputPaths(job, dir);
+    FileInputFormat.setInputPaths(job, TESTDIR);
 
     // for a variety of lengths
     for (int length = 0; length < MAX_LENGTH;
@@ -108,6 +118,7 @@ public class TestSequenceFileInputFormat extends TestCase {
         assertEquals("Some keys in no partition.", length, bits.cardinality());
       }
 
+      fs.delete(TESTDIR, true);
     }
   }
 

+ 7 - 0
mapreduce/src/test/mapred/org/apache/hadoop/mapred/TestTaskCommit.java

@@ -161,6 +161,13 @@ public class TestTaskCommit extends HadoopTestCase {
         throws IOException {
       return 0;
     }
+
+    @Override
+    public void 
+    updatePrivateDistributedCacheSizes(org.apache.hadoop.mapreduce.JobID jobId,
+                                       long[] sizes) throws IOException {
+      // NOTHING
+    }
   }
   
   /**

+ 25 - 27
mapreduce/src/test/mapred/org/apache/hadoop/mapred/TestTaskTrackerLocalization.java

@@ -34,10 +34,11 @@ import org.apache.hadoop.fs.FileSystem;
 import org.apache.hadoop.fs.FileUtil;
 import org.apache.hadoop.fs.LocalDirAllocator;
 import org.apache.hadoop.fs.Path;
-import org.apache.hadoop.mapreduce.MRConfig;
 import org.apache.hadoop.mapreduce.Job;
+import org.apache.hadoop.mapreduce.MRConfig;
 import org.apache.hadoop.mapreduce.MRJobConfig;
 import org.apache.hadoop.mapreduce.TaskType;
+import org.apache.hadoop.mapreduce.filecache.TrackerDistributedCacheManager;
 import org.apache.hadoop.mapreduce.security.TokenCache;
 import org.apache.hadoop.mapreduce.server.tasktracker.Localizer;
 import org.apache.hadoop.mapreduce.util.MRAsyncDiskService;
@@ -47,19 +48,19 @@ import org.apache.hadoop.security.Credentials;
 import org.apache.hadoop.security.UserGroupInformation;
 import org.apache.hadoop.util.Shell;
 import org.apache.hadoop.mapred.JvmManager.JvmEnv;
-import org.apache.hadoop.mapred.TaskController.JobInitializationContext;
-import org.apache.hadoop.mapred.TaskController.TaskControllerContext;
 import org.apache.hadoop.mapred.TaskTracker.RunningJob;
 import org.apache.hadoop.mapred.TaskTracker.TaskInProgress;
 import org.apache.hadoop.mapred.UtilsForTests.InlineCleanupQueue;
 
 import junit.framework.TestCase;
+import org.junit.Ignore;
 
 /**
  * Test to verify localization of a job and localization of a task on a
  * TaskTracker.
  * 
  */
+@Ignore // test relies on deprecated functionality/lifecycle
 public class TestTaskTrackerLocalization extends TestCase {
 
   private static File TEST_ROOT_DIR = 
@@ -181,7 +182,14 @@ public class TestTaskTrackerLocalization extends TestCase {
     // Set up the TaskTracker
     tracker = new TaskTracker();
     tracker.setConf(trackerFConf);
-    tracker.setTaskLogCleanupThread(new UserLogCleaner(trackerFConf));
+    // setup task controller
+    taskController = createTaskController();
+    taskController.setConf(trackerFConf);
+    taskController.setup(lDirAlloc);
+    tracker.setTaskController(taskController);
+    tracker.setLocalizer(new Localizer(tracker.getLocalFileSystem(),localDirs));
+    tracker.setTaskLogCleanupThread(new UserLogCleaner(trackerFConf, 
+                                        taskController));
     initializeTracker();
   }
 
@@ -203,13 +211,6 @@ public class TestTaskTrackerLocalization extends TestCase {
     tracker.setTaskTrackerInstrumentation(
         TaskTracker.createInstrumentation(tracker, trackerFConf));
 
-    // setup task controller
-    taskController = createTaskController();
-    taskController.setConf(trackerFConf);
-    taskController.setup();
-    tracker.setTaskController(taskController);
-    tracker.setLocalizer(new Localizer(tracker.getLocalFileSystem(), localDirs,
-        taskController));
   }
 
   protected TaskController createTaskController() {
@@ -642,13 +643,20 @@ public class TestTaskTrackerLocalization extends TestCase {
         + " is not created in any of the configured dirs!!",
         attemptWorkDir != null);
 
-    TaskRunner runner = task.createRunner(tracker, tip);
+    RunningJob rjob = new RunningJob(jobId);
+    TaskController taskController = new DefaultTaskController();
+    taskController.setConf(trackerFConf);
+    rjob.distCacheMgr = 
+      new TrackerDistributedCacheManager(trackerFConf).
+      newTaskDistributedCacheManager(jobId, trackerFConf);
+
+    TaskRunner runner = task.createRunner(tracker, tip, rjob);
     tip.setTaskRunner(runner);
 
     // /////// Few more methods being tested
     runner.setupChildTaskConfiguration(lDirAlloc);
     TaskRunner.createChildTmpDir(new File(attemptWorkDir.toUri().getPath()),
-        localizedJobConf);
+        localizedJobConf, false);
     attemptLogFiles = runner.prepareLogFiles(task.getTaskID(),
         task.isTaskCleanupTask());
 
@@ -666,16 +674,6 @@ public class TestTaskTrackerLocalization extends TestCase {
     TaskRunner.setupChildMapredLocalDirs(task, localizedTaskConf);
     // ///////
 
-    // Initialize task via TaskController
-    TaskControllerContext taskContext =
-        new TaskController.TaskControllerContext();
-    taskContext.env =
-        new JvmEnv(null, null, null, null, -1, new File(localizedJobConf
-            .get(TaskTracker.JOB_LOCAL_DIR)), null, localizedJobConf);
-    taskContext.task = task;
-    // /////////// The method being tested
-    taskController.initializeTask(taskContext);
-    // ///////////
   }
 
   protected void checkTaskLocalization()
@@ -734,13 +732,13 @@ public class TestTaskTrackerLocalization extends TestCase {
     out.writeBytes("dummy input");
     out.close();
     // no write permission for subDir and subDir/file
+    int ret = 0;
     try {
-      int ret = 0;
       if((ret = FileUtil.chmod(subDir.toUri().getPath(), "a=rx", true)) != 0) {
         LOG.warn("chmod failed for " + subDir + ";retVal=" + ret);
       }
-    } catch(InterruptedException e) {
-      LOG.warn("Interrupted while doing chmod for " + subDir);
+    } catch (InterruptedException e) {
+      throw new IOException("chmod interrupted", e);
     }
   }
 
@@ -772,7 +770,7 @@ public class TestTaskTrackerLocalization extends TestCase {
     InlineCleanupQueue cleanupQueue = new InlineCleanupQueue();
     tracker.setCleanupThread(cleanupQueue);
 
-    tip.removeTaskFiles(needCleanup, taskId);
+    tip.removeTaskFiles(needCleanup);
 
     if (jvmReuse) {
       // work dir should still exist and cleanup queue should be empty

+ 3 - 3
mapreduce/src/test/mapred/org/apache/hadoop/mapred/TestTaskTrackerMemoryManager.java

@@ -485,7 +485,7 @@ public class TestTaskTrackerMemoryManager {
       // tree rooted at 100 is over limit immediately, as it is
       // twice over the mem limit.
       ProcfsBasedProcessTree pTree = new ProcfsBasedProcessTree(
-                                          "100", true, 100L, 
+                                          "100", true,
                                           procfsRootDir.getAbsolutePath());
       pTree.getProcessTree();
       assertTrue("tree rooted at 100 should be over limit " +
@@ -493,7 +493,7 @@ public class TestTaskTrackerMemoryManager {
                   test.isProcessTreeOverLimit(pTree, "dummyId", limit));
       
       // the tree rooted at 200 is initially below limit.
-      pTree = new ProcfsBasedProcessTree("200", true, 100L,
+      pTree = new ProcfsBasedProcessTree("200", true,
                                           procfsRootDir.getAbsolutePath());
       pTree.getProcessTree();
       assertFalse("tree rooted at 200 shouldn't be over limit " +
@@ -506,7 +506,7 @@ public class TestTaskTrackerMemoryManager {
                   test.isProcessTreeOverLimit(pTree, "dummyId", limit));
       
       // the tree rooted at 600 is never over limit.
-      pTree = new ProcfsBasedProcessTree("600", true, 100L,
+      pTree = new ProcfsBasedProcessTree("600", true,
                                             procfsRootDir.getAbsolutePath());
       pTree.getProcessTree();
       assertFalse("tree rooted at 600 should never be over limit.",

+ 2 - 1
mapreduce/src/test/mapred/org/apache/hadoop/mapred/TestTrackerDistributedCacheManagerWithLinuxTaskController.java

@@ -25,6 +25,7 @@ import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
 import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.LocalDirAllocator;
 import org.apache.hadoop.fs.Path;
 import org.apache.hadoop.mapred.ClusterWithLinuxTaskController.MyLinuxTaskController;
 import org.apache.hadoop.mapreduce.filecache.TestTrackerDistributedCacheManager;
@@ -64,7 +65,7 @@ public class TestTrackerDistributedCacheManagerWithLinuxTaskController extends
     String execPath = path + "/task-controller";
     ((MyLinuxTaskController)taskController).setTaskControllerExe(execPath);
     taskController.setConf(conf);
-    taskController.setup();
+    taskController.setup(new LocalDirAllocator("mapred.local.dir"));
   }
 
   @Override

+ 49 - 22
mapreduce/src/test/mapred/org/apache/hadoop/mapred/TestUserLogCleanup.java

@@ -27,7 +27,8 @@ import org.apache.hadoop.mapred.UtilsForTests.FakeClock;
 import org.apache.hadoop.mapreduce.MRConfig;
 import org.apache.hadoop.mapreduce.MRJobConfig;
 import org.apache.hadoop.mapreduce.server.tasktracker.Localizer;
-import org.apache.hadoop.mapreduce.util.MRAsyncDiskService;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hadoop.util.ReflectionUtils;
 
 import static org.junit.Assert.*;
 
@@ -47,12 +48,18 @@ public class TestUserLogCleanup {
   private JobID jobid4 = new JobID(jtid, 4);
   private File foo = new File(TaskLog.getUserLogDir(), "foo");
   private File bar = new File(TaskLog.getUserLogDir(), "bar");
+  private TaskController taskController;
 
   public TestUserLogCleanup() throws IOException {
     Configuration conf = new Configuration();
-    localizer = new Localizer(FileSystem.get(conf), conf
-        .getStrings(MRConfig.LOCAL_DIR), new DefaultTaskController());
-    taskLogCleanupThread = new UserLogCleaner(conf);
+    localizer =
+      new Localizer(FileSystem.get(conf), conf.getStrings(MRConfig.LOCAL_DIR));
+    Class<? extends TaskController> taskControllerClass =
+      conf.getClass("mapred.task.tracker.task-controller",
+                     DefaultTaskController.class, TaskController.class);
+    taskController = 
+      (TaskController) ReflectionUtils.newInstance(taskControllerClass, conf);
+    taskLogCleanupThread = new UserLogCleaner(conf, taskController);
     taskLogCleanupThread.setClock(myClock);
     tt = new TaskTracker();
     tt.setConf(new JobConf(conf));
@@ -66,11 +73,10 @@ public class TestUserLogCleanup {
   }
 
   private File localizeJob(JobID jobid) throws IOException {
+    String user = UserGroupInformation.getCurrentUser().getShortUserName();
+    new JobLocalizer(tt.getJobConf(), user, 
+                     jobid.toString()).initializeJobLogDir();
     File jobUserlog = TaskLog.getJobDir(jobid);
-
-    JobConf conf = new JobConf();
-    // localize job log directory
-    tt.initializeJobLogDir(jobid, conf);
     assertTrue(jobUserlog + " directory is not created.", jobUserlog.exists());
     return jobUserlog;
   }
@@ -103,16 +109,17 @@ public class TestUserLogCleanup {
 
     // add the job for deletion with one hour as retain hours
     jobFinished(jobid2, 1);
-
     // remove old logs and see jobid1 is not removed and jobid2 is removed
     myClock.advance(ONE_HOUR);
     taskLogCleanupThread.processCompletedJobs();
+    retry(jobUserlog2);
+      
     assertTrue(jobUserlog1 + " got deleted", jobUserlog1.exists());
-    assertFalse(jobUserlog2 + " still exists.", jobUserlog2.exists());
-
+    assertFalse(jobUserlog2 + " still exists.", jobUserlog2.exists()); 
     myClock.advance(ONE_HOUR);
     // remove old logs and see jobid1 is removed now
     taskLogCleanupThread.processCompletedJobs();
+    retry(jobUserlog1);
     assertFalse(jobUserlog1 + " still exists.", jobUserlog1.exists());
   }
 
@@ -151,18 +158,18 @@ public class TestUserLogCleanup {
     Configuration conf = new Configuration();
     conf.setInt(MRJobConfig.USER_LOG_RETAIN_HOURS, 3);
     taskLogCleanupThread.clearOldUserLogs(conf);
+    retry(foo, bar);
     assertFalse(foo.exists());
     assertFalse(bar.exists());
     assertTrue(jobUserlog1.exists());
     assertTrue(jobUserlog2.exists());
     assertTrue(jobUserlog3.exists());
     assertTrue(jobUserlog4.exists());
-    assertTrue(new File(TaskLog.getUserLogDir(), MRAsyncDiskService.TOBEDELETED)
-        .exists());
 
     myClock.advance(ONE_HOUR);
     // time is now 2.
     taskLogCleanupThread.processCompletedJobs();
+    retry(jobUserlog1);
     assertFalse(jobUserlog1.exists());
     assertTrue(jobUserlog2.exists());
     assertTrue(jobUserlog3.exists());
@@ -175,29 +182,31 @@ public class TestUserLogCleanup {
     jobFinished(jobid3, 3);
 
     // mimic localizeJob for jobid4
-    jobUserlog4 = localizeJob(jobid4);
+    //jobUserlog4 = localizeJob(jobid4);
 
     // do cleanup
     myClock.advance(2 * ONE_HOUR);
     // time is now 4.
     taskLogCleanupThread.processCompletedJobs();
+    retry(jobUserlog1, jobUserlog2, jobUserlog4);
 
     // jobid2 will be deleted
     assertFalse(jobUserlog1.exists());
     assertFalse(jobUserlog2.exists());
     assertTrue(jobUserlog3.exists());
-    assertTrue(jobUserlog4.exists());
+    assertFalse(jobUserlog4.exists());
 
     myClock.advance(ONE_HOUR);
     // time is now 5.
     // do cleanup again
     taskLogCleanupThread.processCompletedJobs();
+    retry(jobUserlog1, jobUserlog2, jobUserlog3);
 
     // jobid3 will be deleted
     assertFalse(jobUserlog1.exists());
     assertFalse(jobUserlog2.exists());
     assertFalse(jobUserlog3.exists());
-    assertTrue(jobUserlog4.exists());
+    assertFalse(jobUserlog4.exists());
   }
 
   /**
@@ -232,7 +241,7 @@ public class TestUserLogCleanup {
     // job directories will be added with 3 hours as retain hours. 
     Configuration conf = new Configuration();
     conf.setInt(MRJobConfig.USER_LOG_RETAIN_HOURS, 3);
-    taskLogCleanupThread = new UserLogCleaner(conf);
+    taskLogCleanupThread = new UserLogCleaner(conf, taskController);
     myClock = new FakeClock(); // clock is reset.
     taskLogCleanupThread.setClock(myClock);
     taskLogCleanupThread.clearOldUserLogs(conf);
@@ -243,8 +252,6 @@ public class TestUserLogCleanup {
     assertTrue(jobUserlog2.exists());
     assertTrue(jobUserlog3.exists());
     assertTrue(jobUserlog4.exists());
-    assertTrue(new File(TaskLog.getUserLogDir(), MRAsyncDiskService.TOBEDELETED)
-        .exists());
 
     myClock.advance(ONE_HOUR);
     // time is now 1.
@@ -267,22 +274,42 @@ public class TestUserLogCleanup {
     myClock.advance(2 * ONE_HOUR);
     // time is now 3.
     taskLogCleanupThread.processCompletedJobs();
+    retry(jobUserlog1, jobUserlog2, jobUserlog4);
 
     // jobid1 and jobid2 will be deleted
     assertFalse(jobUserlog1.exists());
     assertFalse(jobUserlog2.exists());
     assertTrue(jobUserlog3.exists());
-    assertTrue(jobUserlog4.exists());
+    assertFalse(jobUserlog4.exists());
 
     myClock.advance(ONE_HOUR);
     // time is now 4.
     // do cleanup again
     taskLogCleanupThread.processCompletedJobs();
-
+    retry(jobUserlog1, jobUserlog2, jobUserlog3, jobUserlog4);
+    
     // jobid3 will be deleted
     assertFalse(jobUserlog1.exists());
     assertFalse(jobUserlog2.exists());
     assertFalse(jobUserlog3.exists());
-    assertTrue(jobUserlog4.exists());
+    assertFalse(jobUserlog4.exists());
+  }
+  
+  private void retry(File... jobDirs) {
+    //since the deletion is done by a thread, we poll for sometime
+    short retries = 0;
+    while (retries++ < 20) {
+      boolean exist = false;
+      for (File dir : jobDirs) {
+        if (dir.exists()) {
+          exist = true;
+        }
+      }
+      if (exist) {
+        try {
+          Thread.sleep(500);
+        } catch (InterruptedException ie){}
+      } else return;
+    }
   }
 }

+ 21 - 2
mapreduce/src/test/mapred/org/apache/hadoop/mapred/UtilsForTests.java

@@ -47,6 +47,7 @@ import org.apache.hadoop.io.Writable;
 import org.apache.hadoop.io.LongWritable;
 import org.apache.hadoop.io.WritableComparable;
 import org.apache.hadoop.io.SequenceFile.CompressionType;
+import org.apache.hadoop.mapred.CleanupQueue.PathDeletionContext;
 import org.apache.hadoop.mapred.SortValidator.RecordStatsChecker.NonSplitableSequenceFileInputFormat;
 import org.apache.hadoop.mapred.lib.IdentityMapper;
 import org.apache.hadoop.mapred.lib.IdentityReducer;
@@ -661,7 +662,7 @@ public class UtilsForTests {
    * asynchronously.
    */
   public static class InlineCleanupQueue extends CleanupQueue {
-    List<String> stalePaths = new ArrayList<String>();
+    List<Path> stalePaths = new ArrayList<Path>();
 
     public InlineCleanupQueue() {
       // do nothing
@@ -671,19 +672,37 @@ public class UtilsForTests {
     public void addToQueue(PathDeletionContext... contexts) {
       // delete paths in-line
       for (PathDeletionContext context : contexts) {
+        Exception exc = null;
         try {
           if (!deletePath(context)) {
             LOG.warn("Stale path " + context.fullPath);
             stalePaths.add(context.fullPath);
           }
         } catch (IOException e) {
+          exc = e;
+        } catch (InterruptedException ie) {
+          exc = ie;
+        }
+        if (exc != null) {
           LOG.warn("Caught exception while deleting path "
               + context.fullPath);
-          LOG.info(StringUtils.stringifyException(e));
+          LOG.info(StringUtils.stringifyException(exc));
           stalePaths.add(context.fullPath);
         }
       }
     }
+    static boolean deletePath(PathDeletionContext context) 
+    throws IOException, InterruptedException {
+      if (LOG.isDebugEnabled()) {
+        LOG.debug("Trying to delete " + context.fullPath);
+      }
+//      FileSystem fs = context.fullPath.getFileSystem(context.conf);
+//      if (fs.exists(context.fullPath)) {
+//        return fs.delete(context.fullPath, true);
+//      }
+      context.deletePath();
+      return true;
+    }
   }
   
   static class FakeClock extends Clock {

+ 186 - 102
mapreduce/src/test/mapred/org/apache/hadoop/mapreduce/filecache/TestTrackerDistributedCacheManager.java

@@ -32,8 +32,12 @@ import javax.security.auth.login.LoginException;
 
 import junit.framework.TestCase;
 
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.mapred.DefaultTaskController;
+import org.apache.hadoop.mapred.JobID;
+import org.apache.hadoop.mapred.JobLocalizer;
 import org.apache.hadoop.mapred.TaskController;
 import org.apache.hadoop.mapred.TaskTracker;
 import org.apache.hadoop.mapreduce.Cluster;
@@ -41,6 +45,9 @@ import org.apache.hadoop.mapreduce.Job;
 import org.apache.hadoop.mapreduce.MRConfig;
 import org.apache.hadoop.mapreduce.MRJobConfig;
 import org.apache.hadoop.mapreduce.filecache.DistributedCache;
+import org.apache.hadoop.mapreduce.filecache.TaskDistributedCacheManager.CacheFile;
+import org.apache.hadoop.mapreduce.filecache.TrackerDistributedCacheManager.CacheStatus;
+import org.apache.hadoop.mapreduce.filecache.TestTrackerDistributedCacheManager;
 import org.apache.hadoop.fs.FileStatus;
 import org.apache.hadoop.fs.FileSystem;
 import org.apache.hadoop.fs.FileUtil;
@@ -54,10 +61,10 @@ import org.apache.hadoop.mapreduce.filecache.TrackerDistributedCacheManager;
 import org.apache.hadoop.mapreduce.server.tasktracker.TTConfig;
 import org.apache.hadoop.security.UserGroupInformation;
 import org.apache.hadoop.util.ReflectionUtils;
-import org.mortbay.log.Log;
 
 public class TestTrackerDistributedCacheManager extends TestCase {
-
+  private static final Log LOG =
+    LogFactory.getLog(TestTrackerDistributedCacheManager.class);
   protected String TEST_ROOT_DIR =
       new File(System.getProperty("test.build.data", "/tmp"),
           TestTrackerDistributedCacheManager.class.getSimpleName())
@@ -68,10 +75,12 @@ public class TestTrackerDistributedCacheManager extends TestCase {
 
   private static final int TEST_FILE_SIZE = 4 * 1024; // 4K
   private static final int LOCAL_CACHE_LIMIT = 5 * 1024; //5K
-  private static final int LOCAL_CACHE_SUBDIR = 2;
+  private static final int LOCAL_CACHE_SUBDIR = 1;
   protected Configuration conf;
   protected Path firstCacheFile;
+  protected Path firstCacheFilePublic;
   protected Path secondCacheFile;
+  protected Path secondCacheFilePublic;
   private FileSystem fs;
 
   protected LocalDirAllocator localDirAllocator = 
@@ -119,18 +128,22 @@ public class TestTrackerDistributedCacheManager extends TestCase {
         taskControllerClass, conf);
 
     // setup permissions for mapred local dir
-    taskController.setup();
+    taskController.setup(localDirAllocator);
 
     // Create the temporary cache files to be used in the tests.
     firstCacheFile = new Path(TEST_ROOT_DIR, "firstcachefile");
     secondCacheFile = new Path(TEST_ROOT_DIR, "secondcachefile");
+    firstCacheFilePublic = new Path(TEST_ROOT_DIR, "firstcachefileOne");
+    secondCacheFilePublic = new Path(TEST_ROOT_DIR, "secondcachefileOne");
+    createPublicTempFile(firstCacheFilePublic);
+    createPublicTempFile(secondCacheFilePublic);
     createPrivateTempFile(firstCacheFile);
     createPrivateTempFile(secondCacheFile);
   }
   
   protected void refreshConf(Configuration conf) throws IOException {
     taskController.setConf(conf);
-    taskController.setup();
+    taskController.setup(localDirAllocator);
   }
 
   /**
@@ -148,7 +161,8 @@ public class TestTrackerDistributedCacheManager extends TestCase {
    * @throws IOException
    * @throws LoginException
    */
-  public void testManagerFlow() throws IOException, LoginException {
+  public void testManagerFlow()
+      throws IOException, LoginException, InterruptedException {
     if (!canRun()) {
       return;
     }
@@ -158,6 +172,7 @@ public class TestTrackerDistributedCacheManager extends TestCase {
     Configuration subConf = new Configuration(conf);
     String userName = getJobOwnerName();
     subConf.set(MRJobConfig.USER_NAME, userName);
+    JobID jobid = new JobID("jt",1);
     DistributedCache.addCacheFile(firstCacheFile.toUri(), subConf);
     DistributedCache.addFileToClassPath(secondCacheFile, subConf);
     TrackerDistributedCacheManager.determineTimestamps(subConf);
@@ -171,15 +186,18 @@ public class TestTrackerDistributedCacheManager extends TestCase {
 
     // ****** Imitate TaskRunner code.
     TrackerDistributedCacheManager manager = 
-      new TrackerDistributedCacheManager(conf, taskController);
+      new TrackerDistributedCacheManager(conf);
     TaskDistributedCacheManager handle =
-      manager.newTaskDistributedCacheManager(subConf);
+      manager.newTaskDistributedCacheManager(jobid, subConf);
     assertNull(null, DistributedCache.getLocalCacheFiles(subConf));
     File workDir = new File(new Path(TEST_ROOT_DIR, "workdir").toString());
-    handle.setup(localDirAllocator, workDir, TaskTracker
-        .getPrivateDistributedCacheDir(userName), 
-        TaskTracker.getPublicDistributedCacheDir());
-    // ****** End of imitating TaskRunner code
+    handle.setupCache(subConf, TaskTracker.getPublicDistributedCacheDir(), 
+        TaskTracker.getPrivateDistributedCacheDir(userName));
+    JobLocalizer.downloadPrivateCache(subConf);
+    // DOESN'T ACTUALLY HAPPEN IN THE TaskRunner (THIS IS A TODO)
+//    handle.setupPrivateCache(localDirAllocator, TaskTracker
+//        .getPrivateDistributedCacheDir(userName));
+//    // ****** End of imitating TaskRunner code
 
     Path[] localCacheFiles = DistributedCache.getLocalCacheFiles(subConf);
     assertNotNull(null, localCacheFiles);
@@ -208,18 +226,21 @@ public class TestTrackerDistributedCacheManager extends TestCase {
       TrackerDistributedCacheManager {
     public FakeTrackerDistributedCacheManager(Configuration conf)
         throws IOException {
-      super(conf, taskController);
+      super(conf);
     }
 
     @Override
-    Path localizeCache(Configuration conf, URI cache, long confFileStamp,
-        CacheStatus cacheStatus, boolean isArchive, boolean isPublic)
-    throws IOException {
-      if (cache.equals(firstCacheFile.toUri())) {
+    Path localizePublicCacheObject(Configuration conf, URI cache, 
+        long confFileStamp,
+        CacheStatus cacheStatus, 
+        FileStatus fileStatus, 
+        boolean isArchive) throws IOException, InterruptedException {
+      if (cache.equals(firstCacheFilePublic.toUri())) {
         throw new IOException("fake fail");
       }
-      return super.localizeCache(conf, cache, confFileStamp, cacheStatus,
-          isArchive, isPublic);
+      return super.localizePublicCacheObject(conf, cache, confFileStamp, 
+          cacheStatus, fileStatus, 
+          isArchive);
     }
   }
 
@@ -230,57 +251,58 @@ public class TestTrackerDistributedCacheManager extends TestCase {
     }
     TrackerDistributedCacheManager manager = 
       new FakeTrackerDistributedCacheManager(conf);
-    Cluster cluster = new Cluster(conf);
+
     String userName = getJobOwnerName();
     File workDir = new File(new Path(TEST_ROOT_DIR, "workdir").toString());
 
     // Configures a job with a regular file
-    Job job1 = Job.getInstance(cluster, conf);
-    job1.setUser(userName);
-    job1.addCacheFile(secondCacheFile.toUri());
+    Job job1 = new Job(conf);
     Configuration conf1 = job1.getConfiguration();
+    conf1.set("user.name", userName);
+    DistributedCache.addCacheFile(secondCacheFile.toUri(), conf1);
+    
     TrackerDistributedCacheManager.determineTimestamps(conf1);
     TrackerDistributedCacheManager.determineCacheVisibilities(conf1);
 
     // Task localizing for first job
     TaskDistributedCacheManager handle = manager
-        .newTaskDistributedCacheManager(conf1);
-    handle.setup(localDirAllocator, workDir, TaskTracker
-          .getPrivateDistributedCacheDir(userName), 
-          TaskTracker.getPublicDistributedCacheDir());
+        .newTaskDistributedCacheManager(new JobID("jt", 1), conf1);
+    handle.setupCache(conf1, TaskTracker.getPublicDistributedCacheDir(), 
+        TaskTracker.getPrivateDistributedCacheDir(userName));
+    JobLocalizer.downloadPrivateCache(conf1);
     handle.release();
     for (TaskDistributedCacheManager.CacheFile c : handle.getCacheFiles()) {
-      assertEquals(0, manager.getReferenceCount(c.uri, conf1, c.timestamp,
-          c.owner));
+      assertEquals(0, manager.getReferenceCount(c.getStatus()));
     }
     
     Path thirdCacheFile = new Path(TEST_ROOT_DIR, "thirdcachefile");
     createPrivateTempFile(thirdCacheFile);
     
     // Configures another job with three regular files.
-    Job job2 = Job.getInstance(cluster, conf);
-    job2.setUser(userName);
+    Job job2 = new Job(conf);
+    Configuration conf2 = job2.getConfiguration();
+    conf2.set("user.name", userName);
     // add a file that would get failed to localize
-    job2.addCacheFile(firstCacheFile.toUri());
+    DistributedCache.addCacheFile(firstCacheFilePublic.toUri(), conf2);
     // add a file that is already localized by different job
-    job2.addCacheFile(secondCacheFile.toUri());
+    DistributedCache.addCacheFile(secondCacheFile.toUri(), conf2);
     // add a file that is never localized
-    job2.addCacheFile(thirdCacheFile.toUri());
-    Configuration conf2 = job2.getConfiguration();
+    DistributedCache.addCacheFile(thirdCacheFile.toUri(), conf2);
+    
     TrackerDistributedCacheManager.determineTimestamps(conf2);
     TrackerDistributedCacheManager.determineCacheVisibilities(conf2);
 
     // Task localizing for second job
     // localization for the "firstCacheFile" will fail.
-    handle = manager.newTaskDistributedCacheManager(conf2);
+    handle = manager.newTaskDistributedCacheManager(new JobID("jt", 2), conf2);
     Throwable th = null;
     try {
-      handle.setup(localDirAllocator, workDir, TaskTracker
-          .getPrivateDistributedCacheDir(userName), 
-          TaskTracker.getPublicDistributedCacheDir());
+      handle.setupCache(conf2, TaskTracker.getPublicDistributedCacheDir(),
+          TaskTracker.getPrivateDistributedCacheDir(userName));
+      JobLocalizer.downloadPrivateCache(conf2);
     } catch (IOException e) {
       th = e;
-      Log.info("Exception during setup", e);
+      LOG.info("Exception during setup", e);
     }
     assertNotNull(th);
     assertTrue(th.getMessage().contains("fake fail"));
@@ -288,15 +310,15 @@ public class TestTrackerDistributedCacheManager extends TestCase {
     th = null;
     for (TaskDistributedCacheManager.CacheFile c : handle.getCacheFiles()) {
       try {
-        assertEquals(0, manager.getReferenceCount(c.uri, conf2, c.timestamp,
-            c.owner));
-      } catch (IOException ie) {
+        int refcount = manager.getReferenceCount(c.getStatus());
+        LOG.info("checking refcount " + c.uri + " of " + refcount);
+        assertEquals(0, refcount);
+      } catch (NullPointerException ie) {
         th = ie;
-        Log.info("Exception getting reference count for " + c.uri, ie);
+        LOG.info("Exception getting reference count for " + c.uri, ie);
       }
     }
     assertNotNull(th);
-    assertTrue(th.getMessage().contains(thirdCacheFile.getName()));
     fs.delete(thirdCacheFile, false);
   }
   
@@ -361,7 +383,7 @@ public class TestTrackerDistributedCacheManager extends TestCase {
   private Path checkLocalizedPath(boolean visibility) 
   throws IOException, LoginException, InterruptedException {
     TrackerDistributedCacheManager manager = 
-      new TrackerDistributedCacheManager(conf, taskController);
+      new TrackerDistributedCacheManager(conf);
     Cluster cluster = new Cluster(conf);
     String userName = getJobOwnerName();
     File workDir = new File(TEST_ROOT_DIR, "workdir");
@@ -381,10 +403,10 @@ public class TestTrackerDistributedCacheManager extends TestCase {
 
     // Task localizing for job
     TaskDistributedCacheManager handle = manager
-        .newTaskDistributedCacheManager(conf1);
-    handle.setup(localDirAllocator, workDir, TaskTracker
-          .getPrivateDistributedCacheDir(userName), 
-          TaskTracker.getPublicDistributedCacheDir());
+        .newTaskDistributedCacheManager(new JobID("jt", 1), conf1);
+    handle.setupCache(conf1, TaskTracker.getPublicDistributedCacheDir(),
+        TaskTracker.getPrivateDistributedCacheDir(userName));
+    JobLocalizer.downloadPrivateCache(conf1);
     TaskDistributedCacheManager.CacheFile c = handle.getCacheFiles().get(0);
     String distCacheDir;
     if (visibility) {
@@ -394,9 +416,8 @@ public class TestTrackerDistributedCacheManager extends TestCase {
     }
     Path localizedPath =
       manager.getLocalCache(cacheFile.toUri(), conf1, distCacheDir,
-          fs.getFileStatus(cacheFile), false,
-          c.timestamp, new Path(TEST_ROOT_DIR), false,
-          visibility);
+                            fs.getFileStatus(cacheFile), false,
+    		                c.timestamp, visibility, c);
     assertTrue("Cache file didn't get localized in the expected directory. " +
         "Expected localization to happen within " + 
         ROOT_MAPRED_LOCAL_DIR + "/" + distCacheDir +
@@ -504,56 +525,94 @@ public class TestTrackerDistributedCacheManager extends TestCase {
     Configuration conf2 = new Configuration(conf);
     conf2.set(MRConfig.LOCAL_DIR, ROOT_MAPRED_LOCAL_DIR.toString());
     conf2.setLong(TTConfig.TT_LOCAL_CACHE_SIZE, LOCAL_CACHE_LIMIT);
-    conf2.setLong(TTConfig.TT_LOCAL_CACHE_SUBDIRS_LIMIT, LOCAL_CACHE_SUBDIR);
     conf2.setLong(TTConfig.TT_DISTRIBUTED_CACHE_CHECK_PERIOD, 200); // 200 ms
     refreshConf(conf2);
     TrackerDistributedCacheManager manager = 
-        new TrackerDistributedCacheManager(conf2, taskController);
+        new TrackerDistributedCacheManager(conf2);
     manager.startCleanupThread();
     FileSystem localfs = FileSystem.getLocal(conf2);
     String userName = getJobOwnerName();
     conf2.set(MRJobConfig.USER_NAME, userName);
 
     // We first test the size limit
-    Path localCache = manager.getLocalCache(firstCacheFile.toUri(), conf2, 
+    FileStatus stat = fs.getFileStatus(firstCacheFilePublic);
+    CacheFile cfile1 = new CacheFile(firstCacheFilePublic.toUri(), 
+    		                         CacheFile.FileType.REGULAR, true, 
+    		                         stat.getModificationTime(),
+    		                         true); 
+    Path firstLocalCache = manager.getLocalCache(firstCacheFilePublic.toUri(), conf2, 
         TaskTracker.getPrivateDistributedCacheDir(userName),
-        fs.getFileStatus(firstCacheFile), false,
-        getFileStamp(firstCacheFile), new Path(TEST_ROOT_DIR), false, false);
-    manager.releaseCache(firstCacheFile.toUri(), conf2,
-        getFileStamp(firstCacheFile), 
-        TrackerDistributedCacheManager.getLocalizedCacheOwner(false));
+        fs.getFileStatus(firstCacheFilePublic), false,
+        fs.getFileStatus(firstCacheFilePublic).getModificationTime(), true,
+        cfile1);
+    manager.releaseCache(cfile1.getStatus());
     //in above code,localized a file of size 4K and then release the cache 
     // which will cause the cache be deleted when the limit goes out. 
     // The below code localize another cache which's designed to
     //sweep away the first cache.
-    manager.getLocalCache(secondCacheFile.toUri(), conf2, 
+    stat = fs.getFileStatus(secondCacheFilePublic);
+    CacheFile cfile2 = new CacheFile(secondCacheFilePublic.toUri(), 
+    		                         CacheFile.FileType.REGULAR, true, 
+    		                         stat.getModificationTime(),
+    		                         true); 
+    assertTrue("DistributedCache currently doesn't have cached file",
+        localfs.exists(firstLocalCache));
+    Path secondLocalCache = manager.getLocalCache(secondCacheFilePublic.toUri(), conf2, 
         TaskTracker.getPrivateDistributedCacheDir(userName),
-        fs.getFileStatus(secondCacheFile), false, 
-        getFileStamp(secondCacheFile), new Path(TEST_ROOT_DIR), false, false);
-    checkCacheDeletion(localfs, localCache, "DistributedCache failed " +
+        fs.getFileStatus(secondCacheFilePublic), false, 
+        fs.getFileStatus(secondCacheFilePublic).getModificationTime(), true,
+        cfile2);
+    checkCacheDeletion(localfs, firstLocalCache, "DistributedCache failed " +
         "deleting old cache when the cache store is full.");
+    manager.stopCleanupThread();
     // Now we test the number of sub directories limit
+    conf2.setLong(TTConfig.TT_LOCAL_CACHE_SUBDIRS_LIMIT, LOCAL_CACHE_SUBDIR);
+    conf2.setLong(TTConfig.TT_LOCAL_CACHE_SIZE, LOCAL_CACHE_LIMIT * 10);
+    conf2.setLong(TTConfig.TT_DISTRIBUTED_CACHE_CHECK_PERIOD, 200); // 200 ms
+    manager = 
+      new TrackerDistributedCacheManager(conf2);
+    manager.startCleanupThread();
     // Create the temporary cache files to be used in the tests.
     Path thirdCacheFile = new Path(TEST_ROOT_DIR, "thirdcachefile");
     Path fourthCacheFile = new Path(TEST_ROOT_DIR, "fourthcachefile");
     // Adding two more small files, so it triggers the number of sub directory
     // limit but does not trigger the file size limit.
-    createTempFile(thirdCacheFile, 1);
-    createTempFile(fourthCacheFile, 1);
+    createPrivateTempFile(thirdCacheFile);
+    createPrivateTempFile(fourthCacheFile);
+    DistributedCache.setCacheFiles(new URI[]{thirdCacheFile.toUri()}, conf2);
+    TrackerDistributedCacheManager.determineCacheVisibilities(conf2);
+    TrackerDistributedCacheManager.determineTimestamps(conf2);
+    stat = fs.getFileStatus(thirdCacheFile);
+    CacheFile cfile3 = new CacheFile(thirdCacheFile.toUri(), 
+            CacheFile.FileType.REGULAR, false, 
+            stat.getModificationTime(),
+            true); 
     Path thirdLocalCache = manager.getLocalCache(thirdCacheFile.toUri(), conf2,
         TaskTracker.getPrivateDistributedCacheDir(userName),
         fs.getFileStatus(thirdCacheFile), false,
-        getFileStamp(thirdCacheFile), new Path(TEST_ROOT_DIR), false, false);
+        fs.getFileStatus(thirdCacheFile).getModificationTime(), 
+        false, cfile3);
+    DistributedCache.setLocalFiles(conf2, thirdLocalCache.toString());
+    JobLocalizer.downloadPrivateCache(conf2);
     // Release the third cache so that it can be deleted while sweeping
-    manager.releaseCache(thirdCacheFile.toUri(), conf2,
-        getFileStamp(thirdCacheFile), 
-        TrackerDistributedCacheManager.getLocalizedCacheOwner(false));
+    manager.releaseCache(cfile3.getStatus());
     // Getting the fourth cache will make the number of sub directories becomes
     // 3 which is greater than 2. So the released cache will be deleted.
-    manager.getLocalCache(fourthCacheFile.toUri(), conf2, 
+    stat = fs.getFileStatus(fourthCacheFile);
+    CacheFile cfile4 = new CacheFile(fourthCacheFile.toUri(), 
+            CacheFile.FileType.REGULAR, false, 
+            stat.getModificationTime(),
+            true); 
+    assertTrue("DistributedCache currently doesn't have cached file",
+        localfs.exists(thirdLocalCache));
+    DistributedCache.setCacheFiles(new URI[]{fourthCacheFile.toUri()}, conf2);
+    DistributedCache.setLocalFiles(conf2, thirdCacheFile.toUri().toString());
+    TrackerDistributedCacheManager.determineCacheVisibilities(conf2);
+    TrackerDistributedCacheManager.determineTimestamps(conf2);
+    Path fourthLocalCache = manager.getLocalCache(fourthCacheFile.toUri(), conf2, 
         TaskTracker.getPrivateDistributedCacheDir(userName),
         fs.getFileStatus(fourthCacheFile), false, 
-        getFileStamp(fourthCacheFile), new Path(TEST_ROOT_DIR), false, false);
+        fs.getFileStatus(fourthCacheFile).getModificationTime(), false, cfile4);
     checkCacheDeletion(localfs, thirdLocalCache,
         "DistributedCache failed deleting old" +
         " cache when the cache exceeds the number of sub directories limit.");
@@ -587,17 +646,20 @@ public class TestTrackerDistributedCacheManager extends TestCase {
       return;
     }
     TrackerDistributedCacheManager manager =
-      new TrackerDistributedCacheManager(conf, taskController);
+      new TrackerDistributedCacheManager(conf);
     conf.set("fs.fakefile.impl", conf.get("fs.file.impl"));
     String userName = getJobOwnerName();
     conf.set(MRJobConfig.USER_NAME, userName);
     Path fileToCache = new Path("fakefile:///"
         + firstCacheFile.toUri().getPath());
+    CacheFile file = new CacheFile(fileToCache.toUri(), 
+    		                       CacheFile.FileType.REGULAR, 
+    		                       false, 0, false);
     Path result = manager.getLocalCache(fileToCache.toUri(), conf,
         TaskTracker.getPrivateDistributedCacheDir(userName),
         fs.getFileStatus(firstCacheFile), false,
-        getFileStamp(firstCacheFile),
-        new Path(TEST_ROOT_DIR), false, false);
+        System.currentTimeMillis(),
+        false, file);
     assertNotNull("DistributedCache cached file on non-default filesystem.",
         result);
   }
@@ -632,6 +694,8 @@ public class TestTrackerDistributedCacheManager extends TestCase {
   protected void tearDown() throws IOException {
     new File(firstCacheFile.toString()).delete();
     new File(secondCacheFile.toString()).delete();
+    new File(firstCacheFilePublic.toString()).delete();
+    new File(secondCacheFilePublic.toString()).delete();
     FileUtil.fullyDelete(new File(TEST_ROOT_DIR));
   }
 
@@ -652,9 +716,13 @@ public class TestTrackerDistributedCacheManager extends TestCase {
     }
     
     public FileStatus getFileStatus(Path p) throws IOException {
+      FileStatus rawFileStatus = super.getFileStatus(p);
       File f = pathToFile(p);
-      return new FileStatus(f.length(), f.isDirectory(), 1, 128,
-      f.lastModified() + increment, makeQualified(new Path(f.getPath())));
+      FileStatus status = new FileStatus(f.length(), f.isDirectory(), 1, 128,
+             f.lastModified() + increment, 0,
+             rawFileStatus.getPermission(), rawFileStatus.getOwner(), 
+             rawFileStatus.getGroup(), makeQualified(new Path(f.getPath())));
+      return status;
     }
     
     void advanceClock(long millis) {
@@ -672,7 +740,7 @@ public class TestTrackerDistributedCacheManager extends TestCase {
     String userName = getJobOwnerName();
 
     TrackerDistributedCacheManager manager = 
-      new TrackerDistributedCacheManager(myConf, taskController);
+      new TrackerDistributedCacheManager(myConf);
     // ****** Imitate JobClient code
     // Configures a task/job with both a regular file and a "classpath" file.
     Configuration subConf = new Configuration(myConf);
@@ -684,14 +752,16 @@ public class TestTrackerDistributedCacheManager extends TestCase {
 
     // ****** Imitate TaskRunner code.
     TaskDistributedCacheManager handle =
-      manager.newTaskDistributedCacheManager(subConf);
+      manager.newTaskDistributedCacheManager(new JobID("jt", 1), subConf);
     assertNull(null, DistributedCache.getLocalCacheFiles(subConf));
     File workDir = new File(new Path(TEST_ROOT_DIR, "workdir").toString());
-    handle.setup(localDirAllocator, workDir, TaskTracker
-        .getPrivateDistributedCacheDir(userName), 
-        TaskTracker.getPublicDistributedCacheDir());
+    handle.setupCache(subConf, TaskTracker.getPublicDistributedCacheDir(), 
+        TaskTracker.getPrivateDistributedCacheDir(userName));
+    //TODO this doesn't really happen in the TaskRunner
+//    handle.setupPrivateCache(localDirAllocator, TaskTracker
+//        .getPrivateDistributedCacheDir(userName));
     // ****** End of imitating TaskRunner code
-
+    JobLocalizer.downloadPrivateCache(subConf);
     Path[] localCacheFiles = DistributedCache.getLocalCacheFiles(subConf);
     assertNotNull(null, localCacheFiles);
     assertEquals(1, localCacheFiles.length);
@@ -709,8 +779,10 @@ public class TestTrackerDistributedCacheManager extends TestCase {
     // running a task of the same job
     Throwable th = null;
     try {
-      handle.setup(localDirAllocator, workDir, TaskTracker
-          .getPrivateDistributedCacheDir(userName), TaskTracker.getPublicDistributedCacheDir());
+      handle.setupCache(subConf, TaskTracker.getPublicDistributedCacheDir(),
+          TaskTracker.getPrivateDistributedCacheDir(userName));
+//      handle.setupPrivateCache(localDirAllocator, TaskTracker
+//          .getPrivateDistributedCacheDir(userName));
     } catch (IOException ie) {
       th = ie;
     }
@@ -723,21 +795,22 @@ public class TestTrackerDistributedCacheManager extends TestCase {
     // running a task of the same job on another TaskTracker which has never
     // initialized the cache
     TrackerDistributedCacheManager manager2 = 
-      new TrackerDistributedCacheManager(myConf, taskController);
+      new TrackerDistributedCacheManager(myConf);
     TaskDistributedCacheManager handle2 =
-      manager2.newTaskDistributedCacheManager(subConf);
+      manager2.newTaskDistributedCacheManager(new JobID("jt", 1), subConf);
     File workDir2 = new File(new Path(TEST_ROOT_DIR, "workdir2").toString());
     th = null;
     try {
-      handle2.setup(localDirAllocator, workDir2, TaskTracker
+      handle2.setupCache(subConf, TaskTracker
           .getPrivateDistributedCacheDir(userName), 
           TaskTracker.getPublicDistributedCacheDir());
+      JobLocalizer.downloadPrivateCache(subConf);
     } catch (IOException ie) {
       th = ie;
     }
     assertNotNull("Throwable is null", th);
     assertTrue("Exception message does not match",
-        th.getMessage().contains("has changed on HDFS since job started"));
+        th.getMessage().contains("changed during the job from"));
     // release
     handle.release();
     
@@ -749,9 +822,10 @@ public class TestTrackerDistributedCacheManager extends TestCase {
     TrackerDistributedCacheManager.determineCacheVisibilities(subConf2);
     
     handle =
-      manager.newTaskDistributedCacheManager(subConf2);
-    handle.setup(localDirAllocator, workDir, TaskTracker
-        .getPrivateDistributedCacheDir(userName), TaskTracker.getPublicDistributedCacheDir());
+      manager.newTaskDistributedCacheManager(new JobID("jt", 2), subConf2);
+    handle.setupCache(subConf2, TaskTracker.getPublicDistributedCacheDir(), 
+        TaskTracker.getPrivateDistributedCacheDir(userName));
+    JobLocalizer.downloadPrivateCache(subConf2);
     Path[] localCacheFiles2 = DistributedCache.getLocalCacheFiles(subConf2);
     assertNotNull(null, localCacheFiles2);
     assertEquals(1, localCacheFiles2.length);
@@ -781,26 +855,36 @@ public class TestTrackerDistributedCacheManager extends TestCase {
     String userName = getJobOwnerName();
     conf.set(MRJobConfig.USER_NAME, userName);
     TrackerDistributedCacheManager manager = 
-        new TrackerDistributedCacheManager(conf, taskController);
+        new TrackerDistributedCacheManager(conf);
     FileSystem localfs = FileSystem.getLocal(conf);
+    long now = System.currentTimeMillis();
 
     Path[] localCache = new Path[2];
-    localCache[0] = manager.getLocalCache(firstCacheFile.toUri(), conf, 
+    FileStatus stat = fs.getFileStatus(firstCacheFile);
+    CacheFile file = new CacheFile(firstCacheFilePublic.toUri(), 
+    		                       CacheFile.FileType.REGULAR, true, 
+    		                       stat.getModificationTime(), false);
+    localCache[0] = manager.getLocalCache(firstCacheFilePublic.toUri(), conf, 
         TaskTracker.getPrivateDistributedCacheDir(userName),
-        fs.getFileStatus(firstCacheFile), false,
-        getFileStamp(firstCacheFile), new Path(TEST_ROOT_DIR), false, false);
+        fs.getFileStatus(firstCacheFilePublic), false,
+        fs.getFileStatus(firstCacheFilePublic).getModificationTime(), true,
+        file);
     FsPermission myPermission = new FsPermission((short)0600);
     Path myFile = new Path(localCache[0].getParent(), "myfile.txt");
     if (FileSystem.create(localfs, myFile, myPermission) == null) {
       throw new IOException("Could not create " + myFile);
     }
     try {
-      localCache[1] = manager.getLocalCache(secondCacheFile.toUri(), conf, 
+      stat = fs.getFileStatus(secondCacheFilePublic);
+      file = new CacheFile(secondCacheFilePublic.toUri(),
+    		               CacheFile.FileType.REGULAR,
+    		               true, stat.getModificationTime(), false);
+      localCache[1] = manager.getLocalCache(secondCacheFilePublic.toUri(), conf, 
           TaskTracker.getPrivateDistributedCacheDir(userName),
-          fs.getFileStatus(secondCacheFile), false, 
-          getFileStamp(secondCacheFile), new Path(TEST_ROOT_DIR), false,
-          false);
-      FileStatus stat = localfs.getFileStatus(myFile);
+          fs.getFileStatus(secondCacheFilePublic), false, 
+          fs.getFileStatus(secondCacheFilePublic).getModificationTime(), true,
+          file);
+      stat = localfs.getFileStatus(myFile);
       assertTrue(stat.getPermission().equals(myPermission));
       // validate permissions of localized files.
       checkFilePermissions(localCache);

+ 23 - 21
mapreduce/src/test/mapred/org/apache/hadoop/mapreduce/util/TestProcfsBasedProcessTree.java

@@ -35,9 +35,12 @@ import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.hadoop.fs.FileUtil;
 import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.mapred.DefaultTaskController;
+import org.apache.hadoop.mapred.TaskController;
 import org.apache.hadoop.util.StringUtils;
 import org.apache.hadoop.util.Shell.ExitCodeException;
 import org.apache.hadoop.util.Shell.ShellCommandExecutor;
+import static org.apache.hadoop.mapred.TaskController.Signal;
 
 import junit.framework.TestCase;
 
@@ -60,7 +63,7 @@ public class TestProcfsBasedProcessTree extends TestCase {
     public void run() {
       try {
         Vector<String> args = new Vector<String>();
-        if(ProcessTree.isSetsidAvailable) {
+        if(TaskController.isSetsidAvailable) {
           args.add("setsid");
         }
         args.add("bash");
@@ -95,7 +98,7 @@ public class TestProcfsBasedProcessTree extends TestCase {
     return getPidFromPidFile(pidFile);
   }
 
-  public void testProcessTree() {
+  public void testProcessTree() throws Exception {
 
     try {
       if (!ProcfsBasedProcessTree.isAvailable()) {
@@ -149,8 +152,7 @@ public class TestProcfsBasedProcessTree extends TestCase {
     String pid = getRogueTaskPID();
     LOG.info("Root process pid: " + pid);
     ProcfsBasedProcessTree p = new ProcfsBasedProcessTree(pid,
-                               ProcessTree.isSetsidAvailable,
-                               ProcessTree.DEFAULT_SLEEPTIME_BEFORE_SIGKILL);
+                               TaskController.isSetsidAvailable);
     p = p.getProcessTree(); // initialize
     LOG.info("ProcessTree: " + p.toString());
 
@@ -171,13 +173,14 @@ public class TestProcfsBasedProcessTree extends TestCase {
     String processTreeDump = p.getProcessTreeDump();
 
     // destroy the process and all its subprocesses
-    p.destroy(true/*in the background*/);
-
-    if(ProcessTree.isSetsidAvailable) {// whole processtree should be gone
-      assertEquals(false, p.isAnyProcessInTreeAlive());
-    }
-    else {// process should be gone
-      assertFalse("ProcessTree must have been gone", p.isAlive());
+    TaskController tc = new DefaultTaskController();
+    tc.signalTask(null, Integer.valueOf(pid), Signal.KILL);
+
+    if (TaskController.isSetsidAvailable) { // whole processtree should be gone
+      assertFalse("Proceesses in process group live",
+          p.isAnyProcessInTreeAlive(tc));
+    } else {// process should be gone
+      assertFalse("ProcessTree must have been gone", p.isAlive(tc));
     }
 
     LOG.info("Process-tree dump follows: \n" + processTreeDump);
@@ -204,7 +207,7 @@ public class TestProcfsBasedProcessTree extends TestCase {
 
     // ProcessTree is gone now. Any further calls should be sane.
     p = p.getProcessTree();
-    assertFalse("ProcessTree must have been gone", p.isAlive());
+    assertFalse("ProcessTree must have been gone", p.isAlive(tc));
     assertTrue("Cumulative vmem for the gone-process is "
         + p.getCumulativeVmem() + " . It should be zero.", p
         .getCumulativeVmem() == 0);
@@ -333,8 +336,8 @@ public class TestProcfsBasedProcessTree extends TestCase {
       
       // crank up the process tree class.
       ProcfsBasedProcessTree processTree = 
-          new ProcfsBasedProcessTree("100", true, 100L, 
-                                  procfsRootDir.getAbsolutePath());
+          new ProcfsBasedProcessTree("100", true,
+              procfsRootDir.getAbsolutePath());
       // build the process tree.
       processTree.getProcessTree();
       
@@ -406,8 +409,8 @@ public class TestProcfsBasedProcessTree extends TestCase {
       
       // crank up the process tree class.
       ProcfsBasedProcessTree processTree = 
-          new ProcfsBasedProcessTree("100", true, 100L, 
-                                  procfsRootDir.getAbsolutePath());
+          new ProcfsBasedProcessTree("100", true,
+              procfsRootDir.getAbsolutePath());
       // build the process tree.
       processTree.getProcessTree();
       
@@ -498,11 +501,11 @@ public class TestProcfsBasedProcessTree extends TestCase {
       
       // crank up the process tree class.
       ProcfsBasedProcessTree processTree = new ProcfsBasedProcessTree(
-                        pid, true, 100L, procfsRootDir.getAbsolutePath());
+                        pid, true, procfsRootDir.getAbsolutePath());
 
       // Let us not create stat file for pid 100.
       assertTrue(ProcfsBasedProcessTree.checkPidPgrpidForMatch(
-                            pid, procfsRootDir.getAbsolutePath()));
+            Integer.valueOf(pid), procfsRootDir.getAbsolutePath()));
     } finally {
       FileUtil.fullyDelete(procfsRootDir);
     }
@@ -551,9 +554,8 @@ public class TestProcfsBasedProcessTree extends TestCase {
       writeStatFiles(procfsRootDir, pids, procInfos);
       writeCmdLineFiles(procfsRootDir, pids, cmdLines);
 
-      ProcfsBasedProcessTree processTree =
-          new ProcfsBasedProcessTree("100", true, 100L, procfsRootDir
-              .getAbsolutePath());
+      ProcfsBasedProcessTree processTree = new ProcfsBasedProcessTree(
+          "100", true, procfsRootDir.getAbsolutePath());
       // build the process tree.
       processTree.getProcessTree();
 

+ 5 - 0
mapreduce/src/test/mapred/testshell/ExternalMapReduce.java

@@ -18,6 +18,7 @@
 
 package testshell;
 
+import java.io.File;
 import java.io.IOException;
 import java.util.Iterator;
 
@@ -91,6 +92,10 @@ public class ExternalMapReduce extends Configured implements Tool {
       if (ret != 0) {
         throw new IOException("files_tmp does not exist");
       }
+      File file = new File("./ziplink/test.txt");
+      if (!file.canExecute()) {
+        throw new IOException("ziplink/test.txt is not executable");
+      }
     }
   }
 

+ 18 - 12
mapreduce/src/test/unit/org/apache/hadoop/mapred/TestTaskTrackerDirectories.java

@@ -26,11 +26,12 @@ import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.fs.FileStatus;
 import org.apache.hadoop.fs.FileSystem;
 import org.apache.hadoop.fs.FileUtil;
+import org.apache.hadoop.fs.LocalDirAllocator;
 import org.apache.hadoop.fs.Path;
 import org.apache.hadoop.fs.RawLocalFileSystem;
-import org.apache.hadoop.mapreduce.MRConfig;
 import org.junit.Test;
 import org.junit.Before;
+import org.mockito.Mockito;
 
 /**
  * Tests for the correct behavior of the TaskTracker starting up with
@@ -55,8 +56,8 @@ public class TestTaskTrackerDirectories {
         TEST_DIR + "/local2"
     };
     
-    conf.setStrings(MRConfig.LOCAL_DIR, dirs);
-    setupTaskController(conf);
+    conf.setStrings("mapred.local.dir", dirs);
+    setupTaskTracker(conf);
 
     for (String dir : dirs) {
       checkDir(dir);
@@ -73,8 +74,8 @@ public class TestTaskTrackerDirectories {
     new File(dirs[0]).mkdirs();
     FileUtil.chmod(dirs[0], "000");
 
-    conf.setStrings(MRConfig.LOCAL_DIR, dirs);
-    setupTaskController(conf);
+    conf.setStrings("mapred.local.dir", dirs);
+    setupTaskTracker(conf);
     
     for (String dir : dirs) {
       checkDir(dir);
@@ -86,7 +87,7 @@ public class TestTaskTrackerDirectories {
     File dir = TaskLog.getUserLogDir();
     FileUtil.fullyDelete(dir);
     
-    setupTaskController(new Configuration());
+    setupTaskTracker(new Configuration());
     
     checkDir(dir.getAbsolutePath());
   }
@@ -103,7 +104,7 @@ public class TestTaskTrackerDirectories {
         dir.createNewFile());
 
     try {
-      setupTaskController(new Configuration());
+      setupTaskTracker(new Configuration());
       fail("Didn't throw!");
     } catch (IOException ioe) {
       System.err.println("Got expected exception");
@@ -118,15 +119,20 @@ public class TestTaskTrackerDirectories {
     dir.mkdirs();
     FileUtil.chmod(dir.getAbsolutePath(), "000");
     
-    setupTaskController(new Configuration());
+    setupTaskTracker(new Configuration());
     
     checkDir(dir.getAbsolutePath());
   }
   
-  private void setupTaskController(Configuration conf) throws IOException {
-    TaskController tc = new DefaultTaskController();
-    tc.setConf(conf);
-    tc.setup();
+  private void setupTaskTracker(Configuration conf) throws Exception {
+    JobConf ttConf = new JobConf(conf);
+    // Doesn't matter what we give here - we won't actually
+    // connect to it.
+    TaskTracker tt = new TaskTracker();
+    tt.setConf(ttConf);
+    tt.setTaskController(Mockito.mock(TaskController.class));
+    fail("TODO: update this test case after 2178");
+    // tt.initializeDirectories();
   }
 
   private void checkDir(String dir) throws IOException {

Some files were not shown because too many files changed in this diff