Logo Search packages:      
Sourcecode: condor version File versions  Download package

int DaemonCore::Create_Process ( const char *  name,
ArgList const &  arglist,
priv_state  priv = PRIV_UNKNOWN,
int  reaper_id = 1,
int  want_commanand_port = TRUE,
Env const *  env = NULL,
const char *  cwd = NULL,
FamilyInfo *  family_info = NULL,
Stream sock_inherit_list[] = NULL,
int  std[] = NULL,
int  fd_inherit_list[] = NULL,
int  nice_inc = 0,
sigset_t *  sigmask = NULL,
int  job_opt_mask = 0,
size_t *  core_hard_limit = NULL 
)

Create a process. Works for NT and Unix. On Unix, a fork and exec are done. Read the source for ugly details - it takes care of most everything.

Parameters:
name The full path name of the executable. If this is a relative path name AND cwd is specified, then we prepend the result of getcwd() to 'name' and pass that to exec().
args The list of args as an ArgList object. The first arg is argv[0], the name you want to appear in 'ps'. If you don't specify args, then 'name' is used as argv[0].
priv The priv state to change into right before the exec. Default = no action.
reaper_id The reaper number to use. Default = 1.
want_command_port Well, do you? Default = TRUE. If want_command_port it TRUE, the child process will be born with a daemonCore command socket on a dynamic port. If it is FALSE, the child process will not have a command socket. If it is any integer > 1, then it will have a command socket on a port well-known port specified by want_command_port.
env Environment values to place in job environment.
cwd Current Working Directory
new_process_group Do you want to make one? Default = FALSE. This will be forced to FALSE on unix if USE_PROCESS_GROUPS is set to false in the config file.
sock_inherit_list A list of socks to inherit.
std An array of three file descriptors to map to stdin, stdout, stderr respectively. If this array is NULL, don't perform remapping. If any one of these is negative, it is ignored and *not* mapped. There's a special case if you use DC_STD_FD_PIPE as the value for any of these fds -- DaemonCore will create a pipe and register everything for you automatically. If you use this for stdin, you can use Write_Std_Pipe() to write to the stdin of the child. If you use this for std(out|err) then you can get a pointer to a MyString with all the data written by the child using Read_Std_Pipe().
nice_inc The value to be passed to nice() in the child. 0 < nice < 20, and greater numbers mean less priority. This is an addition to the current nice value.
job_opt_mask A bitmask which defines other optional behavior we might for this process. The bits are defined above, and always begin with "DCJOBOPT". In addition, each bit should also define a macro to test a mask if a given bit is set ("HAS_DCJOBOPT_...")
fd_inherit_list An array of fds which you want the child to inherit. The last element must be 0.
Returns:
On success, returns the child pid. On failure, returns FALSE.

Definition at line 6255 of file daemon_core.cpp.

References Sock::close(), Close_Pipe(), Create_Pipe(), ERRNO_EXEC_AS_ROOT, Sock::get_file_desc(), getpid(), InfoCommandSinfulStringMyself(), MyString::Length(), Register_Pipe(), Stream::reli_sock, MyString::replaceString(), Stream::safe_sock, SafeSock::serialize(), Stream::serialize(), Sock::set_inheritable(), MyString::sprintf(), and MyString::Value().

Referenced by ToolDaemonProc::StartJob(), and ScriptProc::StartJob().

{
      int i, j;
      char *ptmp;
      int inheritFds[MAX_INHERIT_FDS];
      int numInheritFds = 0;
      extern char **environ;
      MyString executable_buf;

      // For automagic DC std pipes.
      int dc_pipe_fds[3][2] = {{-1, -1}, {-1, -1}, {-1, -1}};

      //saved errno (if any) to pass back to caller
      //Currently, only stuff that would be of interest to the user
      //is saved (so the error can be reported in the user-log).
      int return_errno = 0;
      pid_t newpid = FALSE; //return FALSE to caller, by default

      MyString inheritbuf;
            // note that these are on the stack; they go away nicely
            // upon return from this function.
      ReliSock rsock;
      SafeSock ssock;
      PidEntry *pidtmp;

      /* this will be the pidfamily ancestor identification information */
      time_t time_of_fork;
      unsigned int mii;
      pid_t forker_pid;


#ifdef WIN32

            // declare these variables early so MSVS doesn't complain
            // about them being declared inside of the goto's below.
      DWORD create_process_flags = 0;
      BOOL inherit_handles = FALSE;
      char *newenv = NULL;
      MyString strArgs;
      MyString args_errors;
      int namelen = 0;
      bool bIs16Bit = FALSE;
      int first_arg_to_copy = 0;
      bool args_success = false;

#else
      int inherit_handles;
      int max_pid_retry = 0;
      static int num_pid_collisions = 0;
      int errorpipe[2];
      MyString executable_fullpath_buf;
      char const *executable_fullpath = executable;
#endif

      dprintf(D_DAEMONCORE,"In DaemonCore::Create_Process(%s,...)\n",executable ? executable : "NULL");

      // First do whatever error checking we can that is not platform specific

      // check reaper_id validity
      if ( (reaper_id < 1) || (reaper_id > maxReap)
             || (reapTable[reaper_id - 1].num == 0) ) {
            dprintf(D_ALWAYS,"Create_Process: invalid reaper_id\n");
            goto wrapup;
      }

      // check name validity
      if ( !executable ) {
            dprintf(D_ALWAYS,"Create_Process: null name to exec\n");
            goto wrapup;
      }

      inheritbuf.sprintf("%lu ",(unsigned long)mypid);

            // true = Give me a real local address, circumventing
            //  GCB's trickery if present.  As this address is
            //  intended for my own children on the same machine,
            //  this should be safe.
      inheritbuf += InfoCommandSinfulStringMyself(true);

      if ( sock_inherit_list ) {
            inherit_handles = TRUE;
            for (i = 0 ;
                   (sock_inherit_list[i] != NULL) && (i < MAX_INHERIT_SOCKS) ;
                   i++)
            {
                // PLEASE change this to a dynamic_cast when it
                // becomes available!
//            Sock *tempSock = dynamic_cast<Sock *>( sock_inherit_list[i] );
            Sock *tempSock = ((Sock *) sock_inherit_list[i] );
            if ( tempSock ) {
                inheritFds[numInheritFds] = tempSock->get_file_desc();
                numInheritFds++;
                    // make certain that this socket is inheritable
                if ( !(tempSock->set_inheritable(TRUE)) ) {
                              goto wrapup;
                }
            }
            else {
                dprintf ( D_ALWAYS, "Dynamic cast failure!\n" );
                EXCEPT( "dynamic_cast" );
            }

                  // now place the type of socket into inheritbuf
                   switch ( sock_inherit_list[i]->type() ) {
                        case Stream::reli_sock :
                              inheritbuf += " 1 ";
                              break;
                        case Stream::safe_sock :
                              inheritbuf += " 2 ";
                              break;
                        default:
                              // we only inherit safe and reli socks at this point...
                              assert(0);
                              break;
                  }
                  // now serialize object into inheritbuf
                   ptmp = sock_inherit_list[i]->serialize();
                   inheritbuf += ptmp;
                   delete []ptmp;
            }
      }
      inheritbuf += " 0";

      // if we want a command port for this child process, create
      // an inheritable tcp and a udp socket to listen on, and place
      // the info into the inheritbuf.
      if ( want_command_port != FALSE ) {
            inherit_handles = TRUE;
            SafeSock* ssock_ptr = m_wants_dc_udp ? &ssock : NULL;
            if (!InitCommandSockets(want_command_port, &rsock, ssock_ptr, false)) {
                        // error messages already printed by InitCommandSockets()
                  goto wrapup;
            }

            // now duplicate the underlying SOCKET to make it inheritable
            if ( (!rsock.set_inheritable(TRUE)) ||
                   (m_wants_dc_udp && !ssock.set_inheritable(TRUE)) ) {
                  dprintf(D_ALWAYS,"Create_Process:Failed to set command "
                              "socks inheritable\n");
                  goto wrapup;
            }

            // and now add these new command sockets to the inheritbuf
            inheritbuf += " ";
            ptmp = rsock.serialize();
            inheritbuf += ptmp;
            delete []ptmp;
            if (m_wants_dc_udp) {
                  inheritbuf += " ";
                  ptmp = ssock.serialize();
                  inheritbuf += ptmp;
                  delete []ptmp;
            }

            // now put the actual fds into the list of fds to inherit
        inheritFds[numInheritFds++] = rsock.get_file_desc();
            if (m_wants_dc_udp) {
                  inheritFds[numInheritFds++] = ssock.get_file_desc();
            }
      }
      inheritbuf += " 0";

      // now process fd_inherit_list, which allows the caller the specify
      // arbitrary file descriptors to be passed through to the child process
      // (currently only implemented on UNIX)
      if (fd_inherit_list != NULL) {
#if defined(WIN32)
            EXCEPT("Create_Process: fd_inherit_list specified, "
                       "but not implemented on Windows: programmer error");
#else
            for (int* fd_ptr = fd_inherit_list; *fd_ptr != 0; fd_ptr++) {
                  inheritFds[numInheritFds++] = *fd_ptr;
                  if (numInheritFds > MAX_INHERIT_FDS) {
                        EXCEPT("Create_Process: MAX_INHERIT_FDS (%d) reached",
                               MAX_INHERIT_FDS);
                  }
            }
#endif
      }

      /* this stuff ends up in the child's environment to help processes
            identify children/grandchildren/great-grandchildren/etc. */
      create_id(&time_of_fork, &mii);

      // if this is the first time Create_Process is being called with a
      // non-NULL family_info argument, create the ProcFamilyInterface object
      // that we'll use to interact with the procd for controlling process
      // families
      //
      if ((family_info != NULL) && (m_proc_family == NULL)) {
            m_proc_family = ProcFamilyInterface::create(get_mySubSystem()->getName());
            ASSERT(m_proc_family);
      }

            // Before we get into the platform-specific stuff, see if any
            // of the std fds are requesting a DC-managed pipe.  If so, we
            // want to create those pipes now so they can be inherited.
      for (i=0; i<=2; i++) {
            if (std && std[i] == DC_STD_FD_PIPE) {
                  if (i == 0) {
                        if (!Create_Pipe(dc_pipe_fds[i], false, false, false, true)) {
                              dprintf(D_ALWAYS|D_FAILURE, "ERROR: Create_Process: "
                                          "Can't create DC pipe for stdin.\n");
                              goto wrapup;
                        }
                              // We want to have the child inherit the read end.
                        std[i] = dc_pipe_fds[i][0];
                  }
                  else {
                        if (!Create_Pipe(dc_pipe_fds[i], true, false, true)) {
                              dprintf(D_ALWAYS|D_FAILURE, "ERROR: Create_Process: "
                                          "Can't create DC pipe for %s.\n",
                                          i == 1 ? "stdout" : "stderr");
                              goto wrapup;
                        }
                              // We want to have the child inherit the write end.
                        std[i] = dc_pipe_fds[i][1];
                  }
            }
      }


#ifdef WIN32
      // START A NEW PROCESS ON WIN32

      STARTUPINFO si;
      PROCESS_INFORMATION piProcess;

      // prepare a STARTUPINFO structure for the new process
      ZeroMemory(&si,sizeof(si));
      si.cb = sizeof(si);

      // process id for the environment ancestor history info
      forker_pid = ::GetCurrentProcessId();

      // we do _not_ want our child to inherit our file descriptors
      // unless explicitly requested.  so, set the underlying handle
      // of all files opened via the C runtime library to non-inheritable.
      //
      // TODO: find a way to do this properly for all types of handles
      // and for the total number of possible handles (and all types: 
      // files, pipes, etc.)... and if it's even required (see if _fopen 
      // sets inherit option... actually inherit option comes from 
      // CreateProcess, we should be enumerating all open handles... 
      // see sysinternals handles app for insights).
      /*
      for (i = 0; i < 100; i++) {
            SetFDInheritFlag(i,FALSE);
      }
      */

      // handle re-mapping of stdout,in,err if desired.  note we just
      // set all our file handles to non-inheritable, so for any files
      // being redirected, we need to reset to inheritable.
      if ( std ) {
            int valid = FALSE;
            HANDLE *std_handles[3] = {&si.hStdInput, &si.hStdOutput, &si.hStdError};
            for (int i = 0; i < 3; i++) {
                  if ( std[i] > -1 ) {
                        if (std[i] >= PIPE_INDEX_OFFSET) {
                              // we are handing down a DaemonCore pipe
                              int index = std[i] - PIPE_INDEX_OFFSET;
                              *std_handles[i] = (*pipeHandleTable)[index]->get_handle();
                              SetHandleInformation(*std_handles[i], HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
                              valid = TRUE;
                        }
                        else {
                              // we are handing down a C library FD
                              SetFDInheritFlag(std[i],TRUE);      // set handle inheritable
                              long longTemp = _get_osfhandle(std[i]);
                              if (longTemp != -1 ) {
                                    valid = TRUE;
                                    *std_handles[i] = (HANDLE)longTemp;
                              }
                        }
                  }
            }
            if ( valid ) {
                  si.dwFlags |= STARTF_USESTDHANDLES;
                  inherit_handles = TRUE;
            }
      }

      if (family_info != NULL) {
            create_process_flags |= CREATE_NEW_PROCESS_GROUP;
      }

    // Re-nice our child -- on WinNT, this means run it at IDLE process
      // priority class.

      // NOTE: Can't we have a more fine-grained approach than this?  I
      // think there are other priority classes, and we should probably
      // map them to ranges within the 0-20 Unix nice-values... --pfc

    if ( nice_inc > 0 ) {
            // or new_process_group with whatever we set above...
            create_process_flags |= IDLE_PRIORITY_CLASS;
      }


      // Deal with environment.

      // compiler complains about job_environ's initialization being skipped
      // by "goto wrapup", so we start a new block here
      {
            Env job_environ;

                  // if not using inheritance, start with default values for
                  // PATH and TEMP, othwise just start with our environment
            if ( HAS_DCJOBOPT_NO_ENV_INHERIT(job_opt_mask) ) {

                        // add in what is likely the system default path.  we do this
                        // here, before merging the user env, because if the user
                        // specifies a path in the job ad we want top use that instead.
                  MyString path;
                  GetEnv("PATH",path);
                  if (path.Length()) {
                        job_environ.SetEnv("PATH",path.Value());
                  }

                  // do the same for what likely is the system default TEMP
                  // directory.
                  MyString temp_path;
                  GetEnv("TEMP",temp_path);
                  if (temp_path.Length()) {
                        job_environ.SetEnv("TEMP",temp_path.Value());
                  }
            }
            else {
                  char* my_env = GetEnvironmentStrings();
                  if (my_env == NULL) {
                        dprintf(D_ALWAYS,
                                "GetEnvironmentStrings error: %u\n",
                                GetLastError());
                  }
                  else {
                        job_environ.MergeFrom(my_env);
                        if (FreeEnvironmentStrings(my_env) == FALSE) {
                              dprintf(D_ALWAYS,
                                      "FreeEnvironmentStrings error: %u\n",
                                      GetLastError());
                        }
                  }
            }

                  // now add in env vars from user
            if(env) {
                  job_environ.MergeFrom(*env);
            }

                  // next, add in default system env variables.  we do this after
                  // the user vars are in place, because we want the values for
                  // these system variables to override whatever the user said.
            const char * default_vars[] = { "SystemDrive", "SystemRoot",
                  "COMPUTERNAME", "NUMBER_OF_PROCESSORS", "OS", "COMSPEC",
                  "PROCESSOR_ARCHITECTURE", "PROCESSOR_IDENTIFIER",
                  "PROCESSOR_LEVEL", "PROCESSOR_REVISION", "PROGRAMFILES", "WINDIR",
                  "\0" };           // must end list with NULL string
            int i = 0;
            while ( default_vars[i][0] ) {
                  MyString envbuf;
                  GetEnv(default_vars[i],envbuf);
                  if (envbuf.Length()) {
                        job_environ.SetEnv(default_vars[i],envbuf.Value());
                  }
                  i++;
            }

                  // now, add in the inherit buf
            job_environ.SetEnv( EnvGetName( ENV_INHERIT ), inheritbuf.Value() );

                  // and finally, get it all back as a NULL delimited string.
                  // remember to deallocate this with delete [] since it will
                  // be allocated on the heap with new [].
            newenv = job_environ.getWindowsEnvironmentString();
      }
      // end of dealing with the environment....

      // Check if it's a 16-bit application
      bIs16Bit = false;
      LOADED_IMAGE loaded;
      // NOTE (not in MSDN docs): Even when this function fails it still
      // may have "failed" with LastError = "operation completed successfully"
      // and still filled in our structure.  It also might really have
      // failed.  So we init the part of the structure we care about and just
      // ignore the return value.
      loaded.fDOSImage = FALSE;
      MapAndLoad((char *)executable, NULL, &loaded, FALSE, TRUE);
      if (loaded.fDOSImage == TRUE)
            bIs16Bit = true;
      UnMapAndLoad(&loaded);

      // CreateProcess requires different params for 16-bit apps:
      //          NULL for the app name
      //          args begins with app name
      namelen = strlen(executable);
      if (bIs16Bit)
      {
            // surround the executable name with quotes or you'll have problems
            // when the execute directory contains spaces!
            strArgs = "\"" + MyString(executable) + MyString("\" ");

            // make sure we're only using backslashes
            strArgs.replaceString("/", "\\", 0);

            first_arg_to_copy = 1;
            args_success = args.GetArgsStringWin32(&strArgs,first_arg_to_copy,&args_errors);

            dprintf(D_ALWAYS, "Create_Process: 16-bit job detected, args=%s\n", args);


      } else if ( (stricmp(".bat",&(executable[namelen-4])) == 0) ||
                  (stricmp(".cmd",&(executable[namelen-4])) == 0) ) {

            char systemshell[MAX_PATH+1];

            // next, stuff the extra cmd.exe args in with the arguments
            strArgs = " /Q /C \"" + MyString(executable) + MyString("\" ");

            // now find out where cmd.exe lives on this box and
            // set it to our executable
            ::GetSystemDirectory(systemshell, MAX_PATH);
            strncat(systemshell, "\\cmd.exe", MAX_PATH);
            executable_buf = systemshell;
            executable = executable_buf.Value();

            // skip argv[0], since that will goof up the args to the batch
            // script.
            first_arg_to_copy = 1;
            args_success = args.GetArgsStringWin32(&strArgs,first_arg_to_copy,&args_errors);

            dprintf(D_ALWAYS, "Executable is a batch script, so executing %s %s\n",
                  executable, strArgs.Value());
      }
      else {
            first_arg_to_copy = 0;
            args_success = args.GetArgsStringWin32(&strArgs,first_arg_to_copy,&args_errors);
      }

      if(!args_success) {
            dprintf(D_ALWAYS, "ERROR: failed to produce Win32 argument string from CreateProcess: %s\n",args_errors.Value());
            goto wrapup;
      }

      BOOL cp_result, gbt_result;
      DWORD binType;
      gbt_result = GetBinaryType(executable, &binType);

      // test if the executable is either unexecutable, or if GetBinaryType()
      // thinks its a DOS 16-bit app, but in reality the actual binary
      // image isn't (this happens when the executable is bogus or corrupt).
      if ( !gbt_result || ( binType == SCS_DOS_BINARY && !bIs16Bit) ) {

            dprintf(D_ALWAYS, "ERROR: %s is not a valid Windows executable\n",
                        executable);
            cp_result = 0;

            goto wrapup;
      } else {
            dprintf(D_FULLDEBUG, "GetBinaryType() returned %d\n", binType);
      }

      // if we want to create a process family for this new process, we
      // will create the process suspended, so we can register it with the
      // procd
      //
      if (family_info != NULL) {
            create_process_flags |= CREATE_SUSPENDED;
      }

      // if we are creating a process with PRIV_USER_FINAL,
      // we need to add flag CREATE_NEW_CONSOLE to be certain the user
      // job starts in a new console windows.  This allows Condor to 
      // find the window when we want to send it a WM_CLOSE
      //
      if ( priv == PRIV_USER_FINAL ) {
            create_process_flags |= CREATE_NEW_CONSOLE;
      }     

      if ( priv != PRIV_USER_FINAL || !can_switch_ids() ) {
            cp_result = ::CreateProcess(bIs16Bit ? NULL : executable,(char*)strArgs.Value(),NULL,
                  NULL,inherit_handles, create_process_flags,newenv,cwd,&si,&piProcess);
      } else {
            // here we want to create a process as user for PRIV_USER_FINAL

                  // Get the token for the user
            HANDLE user_token = priv_state_get_handle();
            ASSERT(user_token);

                  // making this a NULL string tells NT to dynamically
                  // create a new Window Station for the process we are about
                  // to create....
            si.lpDesktop = "";

                  // Check USE_VISIBLE_DESKTOP in condor_config.  If set to TRUE,
                  // then run the job on the visible desktop, otherwise create
                  // a new non-visible desktop for the job.
            char *use_visible = param("USE_VISIBLE_DESKTOP");

            if (use_visible && (*use_visible=='T' || *use_visible=='t') ) {
                        // user wants visible desktop.
                        // place the user_token into the proper access control lists.
                  if ( GrantDesktopAccess(user_token) == 0 ) {
                              // Success!!  The user now has permission to use
                              // the visible desktop, so change si.lpDesktop
                        si.lpDesktop = "winsta0\\default";
                  } else {
                              // The system refuses to grant access to the visible
                              // desktop.  Log a message & we'll fall back on using
                              // the dynamically created non-visible desktop.
                        dprintf(D_ALWAYS,
                              "Create_Process: Unable to use visible desktop\n");
                  }
            }
            if (use_visible) free(use_visible);

                  // we need to make certain to specify CREATE_NEW_CONSOLE, because
                  // our ACLs will not let us use the current console which is
                  // restricted to LOCALSYSTEM.
                  //
                  // "Who's your Daddy ?!?!?!   JEFF B.!"

            // we set_user_priv() here because it really doesn't hurt, and more importantly,
            // if we're using an encrypted execute directory, SYSTEM can't read the user's
            // executable, so the CreateProcessAsUser() call fails. We avoid this by
            // flipping into user priv mode first, then making the call, and all is well.

            priv_state s = set_user_priv();

            cp_result = ::CreateProcessAsUser(user_token,bIs16Bit ? NULL : executable,
                  (char *)strArgs.Value(),NULL,NULL, inherit_handles,
                  create_process_flags, newenv,cwd,&si,&piProcess);

            set_priv(s);
      }

      if ( !cp_result ) {
            dprintf(D_ALWAYS,
                  "Create_Process: CreateProcess failed, errno=%d\n",GetLastError());
            goto wrapup;
      }

      // save pid info out of piProcess
      //
      newpid = piProcess.dwProcessId;

      // if requested, register a process family with the procd and unsuspend
      // the process
      //
      if (family_info != NULL) {
            ASSERT(m_proc_family != NULL);
            bool ok = Register_Family(newpid,
                                      getpid(),
                                      family_info->max_snapshot_interval,
                                      NULL,
                                      family_info->login,
                                      NULL,
                                      family_info->glexec_proxy);
            if (!ok) {
                  EXCEPT("error registering process family with procd");
            }
            if (ResumeThread(piProcess.hThread) == (DWORD)-1) {
                  EXCEPT("error resuming newly created process: %u",
                         GetLastError());
            }
      }

      // reset sockets that we had to inherit back to a
      // non-inheritable permission
      if ( sock_inherit_list ) {
            for (i = 0 ;
                   (sock_inherit_list[i] != NULL) && (i < MAX_INHERIT_SOCKS) ;
                   i++)
        {
                  ((Sock *)sock_inherit_list[i])->set_inheritable(FALSE);
            }
      }
#else
      // START A NEW PROCESS ON UNIX

            // We have to do some checks on the executable name and the
            // cwd before we fork.  We want to do these in priv state
            // specified, but in the user priv if PRIV_USER_FINAL specified
            // or in the condor priv if PRIV_CONDOR_FINAL is specified.
            // Don't do anything in PRIV_UNKNOWN case.

      priv_state current_priv;
      if ( priv != PRIV_UNKNOWN ) {
            if ( priv == PRIV_USER_FINAL ) {
                  current_priv = set_user_priv();
            } else if ( priv == PRIV_CONDOR_FINAL ) {
                  current_priv = set_condor_priv();
            } else {
                  current_priv = set_priv( priv );
            }
      }

      // First, check to see that the specified executable exists.
      if( access(executable,F_OK | X_OK) < 0 ) {
            return_errno = errno;
            dprintf( D_ALWAYS, "Create_Process: "
                         "Cannot access specified executable \"%s\": "
                         "errno = %d (%s)\n", executable, errno, strerror(errno) );
            if ( priv != PRIV_UNKNOWN ) {
                  set_priv( current_priv );
            }
            goto wrapup;
      }

      // Next, check to see that the cwd exists.
      struct stat stat_struct;
      if( cwd && (cwd[0] != '\0') ) {
            if( stat(cwd, &stat_struct) == -1 ) {
                  return_errno = errno;
                  dprintf( D_ALWAYS, "Create_Process: "
                               "Cannot access specified cwd \"%s\": "
                               "errno = %d (%s)\n", cwd, errno, strerror(errno) );
                  if ( priv != PRIV_UNKNOWN ) {
                        set_priv( current_priv );
                  }
                  goto wrapup;
            }
      }

            // Change back to the priv we came from:
      if ( priv != PRIV_UNKNOWN ) {
            set_priv( current_priv );
      }

            // if we're given a relative path (in name) AND we want to cwd
            // here, we have to prepend stuff to name make it the full path.
            // Otherwise, we change directory and execv fails.

      if( cwd && (cwd[0] != '\0') ) {

            if ( executable[0] != '/' ) {   // relative path
                  MyString currwd;
                  if ( !condor_getcwd( currwd ) ) {
                        dprintf ( D_ALWAYS, "Create_Process: getcwd failed\n" );
                        goto wrapup;
                  }

                  executable_fullpath_buf.sprintf("%s/%s", currwd.Value(), executable);
                  executable_fullpath = executable_fullpath_buf.Value();

                        // Finally, log it
                  dprintf ( D_DAEMONCORE, "Full path exec name: %s\n", executable_fullpath );
            }

      }


            // Before we fork, we want to setup some communication with
            // our child in case something goes wrong before the exec.  We
            // don't want the child to exit if the exec fails, since we
            // can't tell from the exit code if it is from our child or if
            // the binary we exec'ed happened to use the same exit code.
            // So, we use a pipe.  The trick is that we set the
            // close-on-exec flag of the pipe, so we don't leak a file
            // descriptor to the child.  The parent reads off of the pipe.
            // If it is closed before there is any data sent, then the
            // exec succeeded.  Otherwise, it reads the errno and returns
            // that to the caller.  --Jim B. (Apr 13 2000)
      if (pipe(errorpipe) < 0) {
            dprintf(D_ALWAYS,"Create_Process: pipe() failed with errno %d (%s).\n",
                        errno, strerror(errno));
            goto wrapup;
      }

      // process id for the environment ancestor history info
      forker_pid = ::getpid();

      // if the caller passed in a signal mask to apply to the child, we
      // block these signals before we fork. then in the parent, we change
      // the mask back to what it was immediately following the fork. in
      // the child, we apply the given mask again (using SIG_SETMASK rather
      // than SIG_BLOCK). the reason we take this extra step here is to
      // avoid the possibility of a signal in the passed-in mask being received
      // by the child before it has a chance to call sigprocmask
      //
      sigset_t saved_mask;
      if (sigmask != NULL) {

            // can't set the signal mask for daemon core processes
            //
            ASSERT(!want_command_port);

            if (sigprocmask(SIG_BLOCK, sigmask, &saved_mask) == -1) {
                  dprintf(D_ALWAYS,
                          "Create_Process: sigprocmask error: %s (%d)\n",
                          strerror(errno),
                          errno);
                  goto wrapup;
            }
      }

      {
                  // Create a "forkit" object to hold all the state that we need in the child.
                  // In some cases, the "fork" will actually be a clone() operation, which
                  // is why we have to package all the state into something we can pass to
                  // another function, rather than just doing it all inline here.
            CreateProcessForkit forkit(
                  errorpipe,
                  args,
                  job_opt_mask,
                  env,
                  inheritbuf,
                  forker_pid,
                  time_of_fork,
                  mii,
                  family_info,
                  cwd,
                  executable,
                  executable_fullpath,
                  std,
                  numInheritFds,
                  inheritFds,
                  nice_inc,
                  priv,
                  want_command_port,
                  sigmask,
                  core_hard_limit);

            newpid = forkit.fork_exec();
      }

      if( newpid > 0 ) // fork succeeded
      {
            // if we were told to set the child's signal mask, we ANDed
            // those signals into our own mask right before the fork (see
            // the comment right before the fork). here we set our signal
            // mask back to what it was
            //
            if (sigmask != NULL) {
                  if (sigprocmask(SIG_SETMASK, &saved_mask, NULL) == -1) {
                        EXCEPT("Create_Process: sigprocmask error: %s (%d)\n",
                               strerror(errno),
                               errno);
                  }
            }

                  // close the write end of our error pipe
            close(errorpipe[1]);

                  // check our error pipe for any problems before the exec
            int child_errno = 0;
            if (read(errorpipe[0], &child_errno, sizeof(int)) == sizeof(int)) {
                        // If we were able to read the errno from the
                        // errorpipe before it was closed, then we know the
                        // error happened before the exec.  We need to reap
                        // the child and return FALSE.
                  int child_status;
                  waitpid(newpid, &child_status, 0);
                  errno = child_errno;
                  return_errno = errno;
                  switch( errno ) {

                  case ERRNO_EXEC_AS_ROOT:
                        dprintf( D_ALWAYS, "Create_Process(%s): child "
                                     "failed because %s process was still root "
                                     "before exec()\n",
                                     executable,
                                     priv_to_string(priv) );
                        break;

                  case ERRNO_REGISTRATION_FAILED:
                        dprintf( D_ALWAYS, "Create_Process(%s): child "
                                     "failed because it failed to register itself "
                                     "with the ProcD\n",
                                     executable );
                        break;

                  case ERRNO_EXIT:
                        dprintf( D_ALWAYS, "Create_Process(%s): child "
                                     "failed because it called exit(%d).\n",
                                     executable,
                                     child_status );
                        break;

                  case ERRNO_PID_COLLISION:
                              /*
                                see the big comment in the top of the child code
                                above for why this can happen.  if it does, we
                                need to increment our counter, make sure we
                                haven't gone over our maximum allowed collisions
                                before we give up, and if not, recursively
                                re-try the whole call to Create_Process().
                              */
                        dprintf( D_ALWAYS, "Create_Process(%s): child "
                                     "failed because PID %d is still in use by "
                                     "DaemonCore\n",
                                     executable,
                                     (int)newpid );
                        num_pid_collisions++;
                        max_pid_retry = param_integer( "MAX_PID_COLLISION_RETRY",
                                                                     DEFAULT_MAX_PID_COLLISIONS );
                        if( num_pid_collisions > max_pid_retry ) {
                              dprintf( D_ALWAYS, "Create_Process: ERROR: we've had "
                                           "%d consecutive pid collisions, giving up! "
                                           "(%d PIDs being tracked internally.)\n",
                                           num_pid_collisions, pidTable->getNumElements() );
                                    // if we break out of the switch(), we'll hit
                                    // the default failure case, goto the wrapup
                                    // code, and just return failure...
                              break;
                        }
                              // if we're here, it means we had a pid collision,
                              // but it's not (yet) fatal, and we should just
                              // re-try the whole Create_Process().  however,
                              // before we do, we need to do a little bit of the
                              // default cleanup ourselves so we don't leak any
                              // memory or fds...
                        close(errorpipe[0]);
                              // we also need to close the command sockets we've
                              // got open sitting on our stack, so that if we're
                              // trying to spawn using a fixed port, we won't
                              // still be holding the port open in this lower
                              // stack frame...
                        rsock.close();
                        ssock.close();
                        dprintf( D_ALWAYS, "Re-trying Create_Process() to avoid "
                                     "PID re-use\n" );
                        return Create_Process( executable, args, priv, reaper_id,
                                               want_command_port, env, cwd,
                                               family_info,
                                               sock_inherit_list, std, fd_inherit_list,
                                               nice_inc, sigmask, job_opt_mask );
                        break;

                  default:
                        dprintf( D_ALWAYS, "Create_Process(%s): child "
                                     "failed with errno %d (%s) before exec()\n",
                                     executable,
                                     errno,
                                     strerror(errno) );
                        break;

                  }
                  close(errorpipe[0]);
                  newpid = FALSE;
                  goto wrapup;
            }
            close(errorpipe[0]);

                  // Now that we've seen if exec worked, if we are trying to
                  // create a paused process, we need to wait for the
                  // stopped child.
            if( HAS_DCJOBOPT_SUSPEND_ON_EXEC(job_opt_mask) ) {
#if defined(LINUX) && defined(TDP)
                        // NOTE: we need to be in user_priv to do this, since
                        // we're going to be sending signals and such
                  priv_state prev_priv;
                  prev_priv = set_user_priv();
                  int rval = tdp_wait_stopped_child( newpid );
                  set_priv( prev_priv );
                  if( rval == -1 ) {
                        return_errno = errno;
                        dprintf(D_ALWAYS, 
                              "Create_Process wait for '%s' failed: %d (%s)\n",
                              executable,
                              errno, strerror (errno) );
                        newpid = FALSE;
                        goto wrapup;
                  }
#else
                  dprintf(D_ALWAYS, "DCJOBOPT_SUSPEND_ON_EXEC not implemented.\n");

#endif /* LINUX && TDP */
            }
      }
      else if( newpid < 0 )// Error condition
      {
            dprintf(D_ALWAYS, "Create Process: fork() for '%s' "
                        "failed: %s (%d)\n",
                        executable,
                        strerror(errno), errno );
            close(errorpipe[0]); close(errorpipe[1]);
            newpid = FALSE;
            goto wrapup;
      }
#endif

      // Now that we have a child, store the info in our pidTable
      pidtmp = new PidEntry;
      pidtmp->pid = newpid;
      pidtmp->new_process_group = (family_info != NULL);
      if ( want_command_port != FALSE )
            strcpy(pidtmp->sinful_string,sock_to_string(rsock._sock));
      else
            pidtmp->sinful_string[0] = '\0';
      pidtmp->is_local = TRUE;
      pidtmp->parent_is_local = TRUE;
      pidtmp->reaper_id = reaper_id;
      pidtmp->hung_tid = -1;
      pidtmp->was_not_responding = FALSE;
#ifdef WIN32
      pidtmp->hProcess = piProcess.hProcess;
      pidtmp->hThread = piProcess.hThread;
      pidtmp->pipeEnd = NULL;
      pidtmp->tid = piProcess.dwThreadId;
      pidtmp->hWnd = 0;
      pidtmp->pipeReady = 0;
      pidtmp->deallocate = 0;
#endif 
            // Now, handle the DC-managed std pipes, if any.
      for (i=0; i<=2; i++) {
            if (dc_pipe_fds[i][0] != -1) {
                        // We made a DC pipe, so close the end we don't need,
                        // and stash the end we care about in the PidEntry.
                  if (i == 0) {
                              // For stdin, we close our copy of the read end
                              // and stash the write end.
                        Close_Pipe(dc_pipe_fds[i][0]);
                        pidtmp->std_pipes[i] = dc_pipe_fds[i][1];
                  }
                  else {
                              // std(out|err) is reversed: we close our copy of
                              // the write end and stash the read end.
                        Close_Pipe(dc_pipe_fds[i][1]);
                        pidtmp->std_pipes[i] = dc_pipe_fds[i][0];
                        char* pipe_desc;
                        char* pipe_handler_desc;
                        if (i == 1) {
                              pipe_desc = "DC stdout pipe";
                              pipe_handler_desc = "DC stdout pipe handler";
                        }
                        else {
                              pipe_desc = "DC stderr pipe";
                              pipe_handler_desc = "DC stderr pipe handler";
                        }
                        Register_Pipe(dc_pipe_fds[i][0], pipe_desc,
                                (PipeHandlercpp) & DaemonCore::PidEntry::pipeHandler,
                                pipe_handler_desc, pidtmp);
                  }
                        // Either way, we stashed/closed as needed, so clear
                        // out the records in dc_pipe_fds so we don't try to
                        // clean these up again during wrapup.
                  dc_pipe_fds[i][0] = dc_pipe_fds[i][1] = DC_STD_FD_NOPIPE;
            }
      }

      /* remember the family history of the new pid */
      pidenvid_init(&pidtmp->penvid);
      if (pidenvid_filter_and_insert(&pidtmp->penvid, environ) !=
            PIDENVID_OK)
      {
            EXCEPT( "Create_Process: More ancestor environment IDs found than "
                        "PIDENVID_MAX which is currently %d. Programmer Error.",
                        PIDENVID_MAX );
      }
      if (pidenvid_append_direct(&pidtmp->penvid, 
                  forker_pid, newpid, time_of_fork, mii) == PIDENVID_OVERSIZED)
      {
            EXCEPT( "Create_Process: Cannot add child pid to PidEnvID table "
                        "because there aren't enough entries. PIDENVID_MAX is "
                        "currently %d! Programmer Error.", PIDENVID_MAX );
      }

      /* add it to the pid table */
      {
         int insert_result = pidTable->insert(newpid,pidtmp);
         assert( insert_result == 0);
      }

#if !defined(WIN32)
      // here, we do any parent-side work needed to register the new process
      // with our ProcFamily logic
      //
      if ((family_info != NULL) && !m_proc_family->register_from_child()) {
            Register_Family(newpid,
                            getpid(),
                            family_info->max_snapshot_interval,
                            &pidtmp->penvid,
                            family_info->login,
                            NULL,
                            family_info->glexec_proxy);
      }
#endif

      dprintf(D_DAEMONCORE,
            "Child Process: pid %lu at %s\n",
            (unsigned long)newpid,pidtmp->sinful_string);
#ifdef WIN32
      WatchPid(pidtmp);
#endif

      // Now that child exists, we (the parent) should close up our copy of
      // the childs command listen cedar sockets.  Since these are on
      // the stack (rsock and ssock), they will get closed when we return.

 wrapup:

#ifndef WIN32
            // if we're here, it means we did NOT have a pid collision, or
            // we had too many and gave up.  either way, we should clear
            // out our static counter.
      num_pid_collisions = 0;
#else
      if(newenv) {
            delete [] newenv;
      }
#endif

            // If we created any pipes for this process and didn't yet
            // close them or stash them in the PidEntry, close them now.
      for (i=0; i<=2; i++) {
            for (j=0; j<=1; j++) {
                  if (dc_pipe_fds[i][j] != -1) {
                        Close_Pipe(dc_pipe_fds[i][j]);
                  }
            }
      }

      errno = return_errno;
      return newpid;
}


Generated by  Doxygen 1.6.0   Back to index