// $Id: OS_NS_unistd.cpp 96985 2013-04-11 15:50:32Z huangh $

#include "ace/OS_NS_unistd.h"

#if !defined (ACE_HAS_INLINED_OSCALLS)
# include "ace/OS_NS_unistd.inl"
#endif /* ACE_HAS_INLINED_OSCALLS */

#include "ace/Base_Thread_Adapter.h"
#include "ace/OS_NS_stdlib.h"
#include "ace/OS_NS_ctype.h"
#include "ace/Default_Constants.h"
#include "ace/OS_Memory.h"
#include "ace/OS_NS_Thread.h"
#include "ace/Object_Manager_Base.h"
#include "ace/Auto_Ptr.h"
#include "ace/os_include/sys/os_pstat.h"
#include "ace/os_include/sys/os_sysctl.h"

#if defined ACE_HAS_VXCPULIB
# include "vxCpuLib.h"
# include "cpuset.h"
#endif /* ACE_HAS_VXCPULIB */

ACE_BEGIN_VERSIONED_NAMESPACE_DECL

int
ACE_OS::argv_to_string (ACE_TCHAR **argv,
                        ACE_TCHAR *&buf,
                        bool substitute_env_args,
                        bool quote_args)
{
  if (argv == 0 || argv[0] == 0)
    return 0;

  int argc;
  for (argc = 0; argv[argc] != 0; ++argc)
    continue;

  return argv_to_string (argc,
                         argv,
                         buf,
                         substitute_env_args,
                         quote_args);
}

int
ACE_OS::argv_to_string (int argc,
                        ACE_TCHAR **argv,
                        ACE_TCHAR *&buf,
                        bool substitute_env_args,
                        bool quote_args)
{
#if defined (ACE_LACKS_STRENVDUP)
  ACE_UNUSED_ARG (substitute_env_args);
#endif /* ACE_LACKS_STRENVDUP */

  if (argc <= 0 || argv == 0 || argv[0] == 0)
    return 0;

  size_t buf_len = 0;

  // Determine the length of the buffer.

  ACE_TCHAR **argv_p = argv;

  for (int i = 0; i < argc; ++i)
    {
#if !defined (ACE_LACKS_STRENVDUP)
      // Account for environment variables.
      if (substitute_env_args
          && ACE_OS::strchr (argv[i], ACE_TEXT ('$')) != 0)
        {
          if (argv_p == argv)
            {
              argv_p = (ACE_TCHAR **) ACE_OS::malloc (argc * sizeof (ACE_TCHAR *));
              if (argv_p == 0)
                {
                  errno = ENOMEM;
                  return 0;
                }
              ACE_OS::memcpy (argv_p, argv, argc * sizeof (ACE_TCHAR *));
            }
          argv_p[i] = ACE_OS::strenvdup (argv[i]);
          if (argv_p[i] == 0)
            {
              ACE_OS::free (argv_p);
              errno = ENOMEM;
              return 0;
            }
        }
#endif /* ACE_LACKS_STRENVDUP */
      // If must quote, we only do it if the arg contains spaces, or
      // is empty. Perhaps a check for other c | ord(c) <= 32 is in
      // order?
      if (quote_args
          && (ACE_OS::strchr (argv_p[i], ACE_TEXT (' ')) != 0
              || ACE_OS::strchr (argv_p[i], ACE_TEXT ('\t')) != 0
              || ACE_OS::strchr (argv_p[i], ACE_TEXT ('\n')) != 0
              || *argv_p[i] == 0))
        {
          if (argv_p == argv)
            {
              argv_p = (ACE_TCHAR **) ACE_OS::malloc (argc * sizeof (ACE_TCHAR *));
              if (argv_p == 0)
                {
                  errno = ENOMEM;
                  return 0;
                }
              ACE_OS::memcpy (argv_p, argv, argc * sizeof (ACE_TCHAR *));
            }
          int quotes = 0;
          ACE_TCHAR *temp = argv_p[i];
          if (ACE_OS::strchr (temp, ACE_TEXT ('"')) != 0)
            {
              for (int j = 0; temp[j] != 0; ++j)
                if (temp[j] == ACE_TEXT ('"'))
                  ++quotes;
            }
          argv_p[i] =
            (ACE_TCHAR *) ACE_OS::malloc ((ACE_OS::strlen (temp) + quotes + 3)
                                          * sizeof (ACE_TCHAR));
          if (argv_p[i] == 0)
            {
              ACE_OS::free (argv_p);
              errno = ENOMEM;
              return 0;
            }
          ACE_TCHAR *end = argv_p[i];

          *end++ = ACE_TEXT ('"');

          if (quotes > 0)
            {
              for (ACE_TCHAR *p = temp;
                   *p != 0;
                   *end++ = *p++)
                if (*p == ACE_TEXT ('"'))
                  *end++ = ACE_TEXT ('\\');

              *end++ = ACE_TEXT ('\0');
            }
          else
            end = ACE_OS::strecpy (end, temp);

          end[-1] = ACE_TEXT ('"');

          *end = ACE_TEXT ('\0');
          if (temp != argv[i])
            ACE_OS::free (temp);
        }
      buf_len += ACE_OS::strlen (argv_p[i]);

      // Add one for the extra space between each string.
      buf_len++;
    }

  // Step through all argv params and copy each one into buf; separate
  // each param with white space.

  ACE_NEW_RETURN (buf,
                  ACE_TCHAR[buf_len + 1],
                  0);

  // Initial null charater to make it a null string.
  buf[0] = ACE_TEXT ('\0');
  ACE_TCHAR *end = buf;

  for (int i = 0; i < argc; ++i)
    {
      end = ACE_OS::strecpy (end, argv_p[i]);
      if (argv_p[i] != argv[i])
        ACE_OS::free (argv_p[i]);

      // Replace the null char that strecpy put there with white
      // space.
      end[-1] = ACE_TEXT (' ');
    }
  // Null terminate the string.
  *end = ACE_TEXT ('\0');

  if (argv_p != argv)
    ACE_OS::free (argv_p);

  // The number of arguments.
  return argc;
}

int
ACE_OS::execl (const char * /* path */, const char * /* arg0 */, ...)
{
  ACE_OS_TRACE ("ACE_OS::execl");
  ACE_NOTSUP_RETURN (-1);
  // Need to write this code.
  // ACE_OSCALL_RETURN (::execv (path, argv), int, -1);
}

int
ACE_OS::execle (const char * /* path */, const char * /* arg0 */, ...)
{
  ACE_OS_TRACE ("ACE_OS::execle");
  ACE_NOTSUP_RETURN (-1);
  // Need to write this code.
  //  ACE_OSCALL_RETURN (::execve (path, argv, envp), int, -1);
}

int
ACE_OS::execlp (const char * /* file */, const char * /* arg0 */, ...)
{
  ACE_OS_TRACE ("ACE_OS::execlp");
  ACE_NOTSUP_RETURN (-1);
  // Need to write this code.
  //  ACE_OSCALL_RETURN (::execvp (file, argv), int, -1);
}

pid_t
ACE_OS::fork (const ACE_TCHAR *program_name)
{
  ACE_OS_TRACE ("ACE_OS::fork");
# if defined (ACE_LACKS_FORK)
  ACE_UNUSED_ARG (program_name);
  ACE_NOTSUP_RETURN (pid_t (-1));
# else
  pid_t const pid =
# if defined (ACE_HAS_STHREADS)
    ::fork1 ();
#else
    ::fork ();
#endif /* ACE_HAS_STHREADS */

#if !defined (ACE_HAS_MINIMAL_ACE_OS) && !defined (ACE_HAS_THREADS)

  // ACE_Base_Thread_Adapter::sync_log_msg() is used to update the
  // program name and process id in ACE's log framework.  However, we
  // can't invoke it from (the child process of) threaded programs
  // because it calls async signal unsafe functions, which will result
  // in undefined behavior (only async signal safe functions can be
  // called after fork() until an exec()).
  //
  // This is no great loss.  Using the ACE log framework in the child
  // process will undoubtedly call async signal unsafe functions too.
  // So it doesn't really matter that the program name and process id
  // will not be updated.

  if (pid == 0)
    ACE_Base_Thread_Adapter::sync_log_msg (program_name);

#else

  ACE_UNUSED_ARG (program_name);

#endif /* ! ACE_HAS_MINIMAL_ACE_OS && !ACE_HAS_THREADS */

  return pid;
# endif /* ACE_WIN32 */
}

// Create a contiguous command-line argument buffer with each arg
// separated by spaces.

pid_t
ACE_OS::fork_exec (ACE_TCHAR *argv[])
{
# if defined (ACE_WIN32)

  ACE_TCHAR *buf = 0;
  ACE_Auto_Basic_Array_Ptr<ACE_TCHAR> safe_ptr (buf);
  if (ACE_OS::argv_to_string (argv, buf) != -1)
    {
      PROCESS_INFORMATION process_info;
#   if !defined (ACE_HAS_WINCE)
      ACE_TEXT_STARTUPINFO startup_info;
      ACE_OS::memset ((void *) &startup_info,
                      0,
                      sizeof startup_info);
      startup_info.cb = sizeof startup_info;

      if (ACE_TEXT_CreateProcess (0,
                                  buf,
                                  0, // No process attributes.
                                  0,  // No thread attributes.
                                  TRUE, // Allow handle inheritance.
                                  0, // Don't create a new console window.
                                  0, // No environment.
                                  0, // No current directory.
                                  &startup_info,
                                  &process_info))
#   else
      if (ACE_TEXT_CreateProcess (0,
                                  buf,
                                  0, // No process attributes.
                                  0,  // No thread attributes.
                                  FALSE, // Can's inherit handles on CE
                                  0, // Don't create a new console window.
                                  0, // No environment.
                                  0, // No current directory.
                                  0, // Can't use startup info on CE
                                  &process_info))
#   endif /* ! ACE_HAS_WINCE */
        {
          // Free resources allocated in kernel.
          ACE_OS::close (process_info.hThread);
          ACE_OS::close (process_info.hProcess);
          // Return new process id.
          return process_info.dwProcessId;
        }
    }

  // CreateProcess failed.
  return -1;
# else
      pid_t const result = ACE_OS::fork ();

#   if defined (ACE_USES_WCHAR)
      // Wide-char builds need to convert the command-line args to
      // narrow char strings for execv ().
      char **cargv = 0;
      int arg_count;
#   endif /* ACE_HAS_WCHAR */

      switch (result)
        {
        case -1:
          // Error.
          return -1;
        case 0:
          // Child process.
#   if defined (ACE_USES_WCHAR)
          for (arg_count = 0; argv[arg_count] != 0; ++arg_count)
            ;
          ++arg_count;    // Need a 0-pointer end-of-array marker
          ACE_NEW_NORETURN (cargv, char*[arg_count]);
          if (cargv == 0)
            ACE_OS::exit (errno);
          --arg_count;    // Back to 0-indexed
          cargv[arg_count] = 0;
          while (--arg_count >= 0)
            cargv[arg_count] = ACE_Wide_To_Ascii::convert (argv[arg_count]);
          // Don't worry about freeing the cargv or the strings it points to.
          // Either the process will be replaced, or we'll exit.
          if (ACE_OS::execv (cargv[0], cargv) == -1)
            ACE_OS::exit (errno);
#   else
          if (ACE_OS::execv (argv[0], argv) == -1)
            {
              // The OS layer should not print stuff out
              // ACELIB_ERROR ((LM_ERROR,
              //             "%p Exec failed\n"));

              // If the execv fails, this child needs to exit.
              ACE_OS::exit (errno);
            }
#   endif /* ACE_HAS_WCHAR */

        default:
          // Server process.  The fork succeeded.
          return result;
        }
# endif /* ACE_WIN32 */
}

long
ACE_OS::num_processors (void)
{
  ACE_OS_TRACE ("ACE_OS::num_processors");

#if defined (ACE_HAS_PHARLAP)
  return 1;
#elif defined (ACE_WIN32)
  SYSTEM_INFO sys_info;
  ::GetSystemInfo (&sys_info);
  return sys_info.dwNumberOfProcessors;
#elif defined (ACE_HAS_VXCPULIB)
  return vxCpuConfiguredGet();
#elif defined (_SC_NPROCESSORS_CONF)
  return ::sysconf (_SC_NPROCESSORS_CONF);
#elif defined (ACE_HAS_SYSCTL)
  int num_processors = 0;
  int mib[2] = { CTL_HW, HW_NCPU };
  size_t len = sizeof (num_processors);
  if (::sysctl (mib, 2, &num_processors, &len, 0, 0) != -1)
    return num_processors;
  else
    return -1;
#elif defined (__hpux)
  struct pst_dynamic psd;
  if (::pstat_getdynamic (&psd, sizeof (psd), (size_t) 1, 0) != -1)
    return psd.psd_max_proc_cnt;
  else
    return -1;
#else
  ACE_NOTSUP_RETURN (-1);
#endif
}

long
ACE_OS::num_processors_online (void)
{
  ACE_OS_TRACE ("ACE_OS::num_processors_online");

#if defined (ACE_HAS_PHARLAP)
  return 1;
#elif defined (ACE_WIN32)
  SYSTEM_INFO sys_info;
  ::GetSystemInfo (&sys_info);
  long active_processors = 0;
  DWORD_PTR mask = sys_info.dwActiveProcessorMask;
  while (mask != 0)
    {
      if (mask & 1)
        ++active_processors;
      mask >>= 1;
    }
  return active_processors;
#elif defined (ACE_HAS_VXCPULIB)
  long num_cpu = 0;
  cpuset_t cpuset;
  CPUSET_ZERO (cpuset);
  cpuset = vxCpuEnabledGet();
  unsigned int const maxcpu = vxCpuConfiguredGet();
  for (unsigned int i =0; i < maxcpu; i++)
    {
      if (CPUSET_ISSET (cpuset, i))
        {
          ++num_cpu;
        }
    }
  return num_cpu;
#elif defined (_SC_NPROCESSORS_ONLN)
  return ::sysconf (_SC_NPROCESSORS_ONLN);
#elif defined (ACE_HAS_SYSCTL)
  int num_processors;
  int mib[2] = { CTL_HW, HW_NCPU };
  size_t len = sizeof (num_processors);
  if (::sysctl (mib, 2, &num_processors, &len, 0, 0) != -1)
    return num_processors;
  else
    return -1;
#elif defined (__hpux)
  struct pst_dynamic psd;
  if (::pstat_getdynamic (&psd, sizeof (psd), (size_t) 1, 0) != -1)
    return psd.psd_proc_cnt;
  else
    return -1;
#else
  ACE_NOTSUP_RETURN (-1);
#endif
}

ssize_t
ACE_OS::read_n (ACE_HANDLE handle,
                void *buf,
                size_t len,
                size_t *bt)
{
  size_t temp;
  size_t &bytes_transferred = bt == 0 ? temp : *bt;
  ssize_t n = 0;

  for (bytes_transferred = 0;
       bytes_transferred < len;
       bytes_transferred += n)
    {
      n = ACE_OS::read (handle,
                        (char *) buf + bytes_transferred,
                        len - bytes_transferred);

      if (n == -1 || n == 0)
        {
          return n;
        }
    }

  return ACE_Utils::truncate_cast<ssize_t> (bytes_transferred);
}

ssize_t
ACE_OS::pread (ACE_HANDLE handle,
               void *buf,
               size_t nbytes,
               ACE_OFF_T offset)
{
# if defined (ACE_HAS_P_READ_WRITE)
#   if defined (ACE_WIN32)

  ACE_OS_GUARD

  // Remember the original file pointer position
  LONG original_high_position = 0;
  DWORD original_low_position = ::SetFilePointer (handle,
                                                  0,
                                                  &original_high_position,
                                                  FILE_CURRENT);

  if (original_low_position == INVALID_SET_FILE_POINTER
      && GetLastError () != NO_ERROR)
    {
      ACE_OS::set_errno_to_last_error ();
      return -1;
    }

  // Go to the correct position
  LONG low_offset = ACE_LOW_PART (offset);
  LONG high_offset = ACE_HIGH_PART (offset);
  DWORD altered_position = ::SetFilePointer (handle,
                                             low_offset,
                                             &high_offset,
                                             FILE_BEGIN);
  if (altered_position == INVALID_SET_FILE_POINTER
      && GetLastError () != NO_ERROR)
    {
      ACE_OS::set_errno_to_last_error ();
      return -1;
    }

  DWORD bytes_read;

#     if defined (ACE_HAS_WIN32_OVERLAPPED_IO)

  OVERLAPPED overlapped;
  overlapped.Internal = 0;
  overlapped.InternalHigh = 0;
  overlapped.Offset = low_offset;
  overlapped.OffsetHigh = high_offset;
  overlapped.hEvent = 0;

  BOOL result = ::ReadFile (handle,
                            buf,
                            static_cast <DWORD> (nbytes),
                            &bytes_read,
                            &overlapped);

  if (result == FALSE)
    {
      if (::GetLastError () != ERROR_IO_PENDING)
        return -1;

      else
        {
          result = ::GetOverlappedResult (handle,
                                          &overlapped,
                                          &bytes_read,
                                          TRUE);
          if (result == FALSE)
            return -1;
        }
    }

#     else /* ACE_HAS_WIN32_OVERLAPPED_IO */

  BOOL result = ::ReadFile (handle,
                            buf,
                            nbytes,
                            &bytes_read,
                            0);
  if (result == FALSE)
    return -1;

#     endif /* ACE_HAS_WIN32_OVERLAPPED_IO */

  // Reset the original file pointer position
  if (::SetFilePointer (handle,
                        original_low_position,
                        &original_high_position,
                        FILE_BEGIN) == INVALID_SET_FILE_POINTER
      && GetLastError () != NO_ERROR)
    {
      ACE_OS::set_errno_to_last_error ();
      return -1;
    }

  return (ssize_t) bytes_read;

#   else /* ACE_WIN32 */

  return ::pread (handle, buf, nbytes, offset);

#   endif /* ACE_WIN32 */

# else /* ACE_HAS_P_READ_WRITE */

  ACE_OS_GUARD

  // Remember the original file pointer position
  ACE_OFF_T original_position = ACE_OS::lseek (handle,
                                               0,
                                               SEEK_CUR);

  if (original_position == -1)
    return -1;

  // Go to the correct position
  ACE_OFF_T altered_position = ACE_OS::lseek (handle, offset, SEEK_SET);

  if (altered_position == -1)
    return -1;

  ssize_t const bytes_read = ACE_OS::read (handle, buf, nbytes);

  if (bytes_read == -1)
    return -1;

  if (ACE_OS::lseek (handle,
                     original_position,
                     SEEK_SET) == -1)
    return -1;

  return bytes_read;

# endif /* ACE_HAD_P_READ_WRITE */
}

ssize_t
ACE_OS::pwrite (ACE_HANDLE handle,
                const void *buf,
                size_t nbytes,
                ACE_OFF_T offset)
{
# if defined (ACE_HAS_P_READ_WRITE)
#   if defined (ACE_WIN32)

  ACE_OS_GUARD

  // Remember the original file pointer position
  LONG original_high_position = 0;
  DWORD original_low_position = ::SetFilePointer (handle,
                                                  0,
                                                  &original_high_position,
                                                  FILE_CURRENT);

  if (original_low_position == INVALID_SET_FILE_POINTER
      && GetLastError () != NO_ERROR)
    {
      ACE_OS::set_errno_to_last_error ();
      return -1;
    }

  DWORD bytes_written;
  LONG low_offset = ACE_LOW_PART (offset);
  LONG high_offset = ACE_HIGH_PART (offset);

#     if defined (ACE_HAS_WIN32_OVERLAPPED_IO)

  OVERLAPPED overlapped;
  overlapped.Internal = 0;
  overlapped.InternalHigh = 0;
  overlapped.Offset = low_offset;
  overlapped.OffsetHigh = high_offset;
  overlapped.hEvent = 0;

  BOOL result = ::WriteFile (handle,
                             buf,
                             static_cast <DWORD> (nbytes),
                             &bytes_written,
                             &overlapped);

  if (result == FALSE)
    {
      if (::GetLastError () != ERROR_IO_PENDING)
        {
          return -1;
        }
      else
        {
          result = ::GetOverlappedResult (handle,
                                          &overlapped,
                                          &bytes_written,
                                          TRUE);
          if (result == FALSE)
            return -1;
        }
    }

#     else /* ACE_HAS_WIN32_OVERLAPPED_IO */

  if (::SetFilePointer (handle,
                        low_offset,
                        &high_offset,
                        FILE_BEGIN) == INVALID_SET_FILE_POINTER
                        && ::GetLastError () != NO_ERROR)
    {
      ACE_OS::set_errno_to_last_error ();
      return -1;
    }

  BOOL result = ::WriteFile (handle,
                             buf,
                             nbytes,
                             &bytes_written,
                             0);
  if (result == FALSE)
    return -1;

#     endif /* ACE_HAS_WIN32_OVERLAPPED_IO */

  // Reset the original file pointer position
  if (::SetFilePointer (handle,
                        original_low_position,
                        &original_high_position,
                        FILE_BEGIN) == INVALID_SET_FILE_POINTER
      && GetLastError () != NO_ERROR)
    {
      ACE_OS::set_errno_to_last_error ();
      return -1;
    }

  return (ssize_t) bytes_written;

#   else /* ACE_WIN32 */
#     if defined (ACE_HAS_NON_CONST_PWRITE)
  return ::pwrite (handle, const_cast<void*> (buf), nbytes, offset);
#     else
  return ::pwrite (handle, buf, nbytes, offset);
#     endif
#   endif /* ACE_WIN32 */
# else /* ACE_HAS_P_READ_WRITE */

  ACE_OS_GUARD

  // Remember the original file pointer position
  ACE_OFF_T original_position = ACE_OS::lseek (handle,
                                               0,
                                               SEEK_CUR);
  if (original_position == -1)
    return -1;

  // Go to the correct position
  ACE_OFF_T altered_position = ACE_OS::lseek (handle,
                                              offset,
                                              SEEK_SET);
  if (altered_position == -1)
    return -1;

  ssize_t const bytes_written = ACE_OS::write (handle,
                                               buf,
                                               nbytes);
  if (bytes_written == -1)
    return -1;

  if (ACE_OS::lseek (handle,
                     original_position,
                     SEEK_SET) == -1)
    return -1;

  return bytes_written;
# endif /* ACE_HAS_P_READ_WRITE */
}

int
ACE_OS::string_to_argv (ACE_TCHAR *buf,
                        int &argc,
                        ACE_TCHAR **&argv,
                        bool substitute_env_args)
{
#if defined (ACE_LACKS_STRENVDUP)
  ACE_UNUSED_ARG (substitute_env_args);
#endif /* ACE_LACKS_STRENVDUP */

  // Reset the number of arguments
  argc = 0;

  if (buf == 0)
    return -1;

  ACE_TCHAR *cp = buf;

  // First pass: count arguments.

  // '#' is the start-comment token..
  while (*cp != ACE_TEXT ('\0') && *cp != ACE_TEXT ('#'))
    {
      // Skip whitespace..
      while (ACE_OS::ace_isspace (*cp))
        ++cp;

      // Increment count and move to next whitespace..
      if (*cp != ACE_TEXT ('\0'))
        ++argc;

      while (*cp != ACE_TEXT ('\0') && !ACE_OS::ace_isspace (*cp))
        {
          // Grok quotes....
          if (*cp == ACE_TEXT ('\'') || *cp == ACE_TEXT ('"'))
            {
              ACE_TCHAR quote = *cp;

              // Scan past the string..
              for (++cp; *cp != ACE_TEXT ('\0')
                   && (*cp != quote || cp[-1] == ACE_TEXT ('\\')); ++cp)
                continue;

              // '\0' implies unmatched quote..
              if (*cp == ACE_TEXT ('\0'))
                {
                  --argc;
                  break;
                }
              else
                ++cp;
            }
          else
            ++cp;
        }
    }

  // Second pass: copy arguments.
  ACE_TCHAR arg[ACE_DEFAULT_ARGV_BUFSIZ];
  ACE_TCHAR *argp = arg;

  // Make sure that the buffer we're copying into is always large
  // enough.
  if (cp - buf >= ACE_DEFAULT_ARGV_BUFSIZ)
    ACE_NEW_RETURN (argp,
                    ACE_TCHAR[cp - buf + 1],
                    -1);

  // Make a new argv vector of argc + 1 elements.
  ACE_NEW_RETURN (argv,
                  ACE_TCHAR *[argc + 1],
                  -1);

  ACE_TCHAR *ptr = buf;

  for (int i = 0; i < argc; ++i)
    {
      // Skip whitespace..
      while (ACE_OS::ace_isspace (*ptr))
        ++ptr;

      // Copy next argument and move to next whitespace..
      cp = argp;
      while (*ptr != ACE_TEXT ('\0') && !ACE_OS::ace_isspace (*ptr))
        if (*ptr == ACE_TEXT ('\'') || *ptr == ACE_TEXT ('"'))
          {
            ACE_TCHAR quote = *ptr++;

            while (*ptr != ACE_TEXT ('\0')
                   && (*ptr != quote || ptr[-1] == ACE_TEXT ('\\')))
              {
                if (*ptr == quote && ptr[-1] == ACE_TEXT ('\\')) --cp;
                *cp++ = *ptr++;
              }

            if (*ptr == quote)
              ++ptr;
          }
        else
          *cp++ = *ptr++;

      *cp = ACE_TEXT ('\0');

#if !defined (ACE_LACKS_STRENVDUP)
      // Check for environment variable substitution here.
      if (substitute_env_args) {
          argv[i] = ACE_OS::strenvdup (argp);

          if (argv[i] == 0)
            {
              if (argp != arg)
                delete [] argp;
              errno = ENOMEM;
              return -1;
            }
      }
      else
#endif /* ACE_LACKS_STRENVDUP */
        {
          argv[i] = ACE_OS::strdup (argp);

          if (argv[i] == 0)
            {
              if (argp != arg)
                {
                  delete [] argp;
                }

              errno = ENOMEM;
              return -1;
            }
        }
    }

  if (argp != arg)
    {
      delete [] argp;
    }

  argv[argc] = 0;
  return 0;
}

// Write <len> bytes from <buf> to <handle> (uses the <write>
// system call on UNIX and the <WriteFile> call on Win32).

ssize_t
ACE_OS::write_n (ACE_HANDLE handle,
                 const void *buf,
                 size_t len,
                 size_t *bt)
{
  size_t temp;
  size_t &bytes_transferred = bt == 0 ? temp : *bt;
  ssize_t n;

  for (bytes_transferred = 0;
       bytes_transferred < len;
       bytes_transferred += n)
    {
      n = ACE_OS::write (handle,
                         (char *) buf + bytes_transferred,
                         len - bytes_transferred);

      if (n == -1 || n == 0)
        {
          return n;
        }
    }

  return ACE_Utils::truncate_cast<ssize_t> (bytes_transferred);
}

ACE_END_VERSIONED_NAMESPACE_DECL
