This is an automated email from the git hooks/post-receive script.

git pushed a commit to branch vtorri_ecore_exe
in repository efl.

View the commit online.

commit 8c56b1e52cf6fde66a6a88c0071254484ec71e1d
Author: Vincent Torri <vto...@outlook.fr>
AuthorDate: Sat Dec 7 10:23:40 2024 +0100

    Windows: add shebang support for ecore_exe on Windowswq
---
 src/lib/ecore/Ecore_Common.h    |   8 +-
 src/lib/ecore/ecore_exe_eo.h    |   3 +-
 src/lib/ecore/ecore_exe_win32.c | 294 +++++++++++++++++++++++++++++++++++-----
 3 files changed, 272 insertions(+), 33 deletions(-)

diff --git a/src/lib/ecore/Ecore_Common.h b/src/lib/ecore/Ecore_Common.h
index af4950c6cd..78cec394ed 100644
--- a/src/lib/ecore/Ecore_Common.h
+++ b/src/lib/ecore/Ecore_Common.h
@@ -1117,7 +1117,11 @@ EAPI int ecore_exe_run_priority_get(void);
  * to write or read on the pipe that connects you with the spawned process.
  * If you need to do that use ecore_exe_pipe_run() with the
  * appropriated flags.
- *
+ * @note On Windows, you should surround each argument with double quotes
+ * to avoid misinterpreted command line, especially if the path contains
+ * a space, like 'c:\Program Files\foo'. You should also escape all special
+ * characters.
+ * @see ecore_exe_pipe_run()
  */
 EAPI Ecore_Exe *ecore_exe_run(const char *exe_cmd, const void *data);
 
@@ -1145,6 +1149,8 @@ EAPI Ecore_Exe *ecore_exe_run(const char *exe_cmd, const void *data);
  * @param   flags   The flag parameters for how to deal with inter-process I/O
  * @param   data    Data to attach to the returned process handle.
  * @return  A process handle to the spawned process.
+ * @note On Windows, see the note of ecore_exe_pipe().
+ * @see ecore_exe_pipe()
  */
 EAPI Ecore_Exe *ecore_exe_pipe_run(const char *exe_cmd, Ecore_Exe_Flags flags, const void *data);
 
diff --git a/src/lib/ecore/ecore_exe_eo.h b/src/lib/ecore/ecore_exe_eo.h
index a4ab65e635..0ced97ebf0 100644
--- a/src/lib/ecore/ecore_exe_eo.h
+++ b/src/lib/ecore/ecore_exe_eo.h
@@ -58,7 +58,8 @@ typedef enum
                                             * Ecore_Exe_Event_Data_Line */
   ECORE_EXE_PIPE_AUTO = 32, /**< stdout and stderr are buffered automatically */
   ECORE_EXE_RESPAWN = 64, /**< FIXME: Exe is restarted if it dies */
-  ECORE_EXE_USE_SH = 128, /**< Use /bin/sh to run the command. */
+  ECORE_EXE_USE_SH = 128, /**< Use /bin/sh to run the command.
+                           * Unused on Windows. */
   ECORE_EXE_NOT_LEADER = 256, /**< Do not use setsid() to set the executed
                                * process as its own session leader */
   ECORE_EXE_TERM_WITH_PARENT = 512, /**< Makes child receive SIGTERM when parent
diff --git a/src/lib/ecore/ecore_exe_win32.c b/src/lib/ecore/ecore_exe_win32.c
index 8002db20e3..379e0c60b8 100644
--- a/src/lib/ecore/ecore_exe_win32.c
+++ b/src/lib/ecore/ecore_exe_win32.c
@@ -22,7 +22,8 @@
  * [X] error event
  * [X] data event buffered
  * [X] del event
- * [ ] batch files
+ * [X] batch files (not recommended by MSDN)
+ * [X] shebang
  * [X] exit code
  * [X] inherited env var
  */
@@ -384,19 +385,232 @@ _impl_ecore_exe_run_priority_get(void)
      }
 }
 
+static char *
+_ecore_exe_win32_child_get(const char *cmd)
+{
+   char *child;
+   const char *iter = cmd;
+   const char *iter2;
+
+   while (*iter == ' ')
+     iter++;
+
+   if (*iter == '\"')
+     {
+        iter++;
+        iter2 = iter;
+        while (*iter2)
+          {
+             if (*iter2 == '\"' && *(iter2 - 1) != '\\')
+               break;
+
+             iter2++;
+          }
+
+        if (*iter2 == 0) /* unbalanced " */
+          return NULL;
+     }
+   else
+     {
+        iter2 = iter + 1;
+
+        while (*iter2 && *iter2 != ' ')
+          iter2++;
+     }
+
+   child = calloc(iter2 - iter + 1, sizeof(char));
+   if (!child)
+     return NULL;
+
+   memcpy(child, iter, iter2 - iter);
+
+   return child;
+}
+
+static char *
+_ecore_exe_win32_shebang_interpreter_get(const char *child)
+{
+   Eina_File *file;
+   Eina_Iterator *it;
+   Eina_File_Line *line;
+   const char *iter;
+   char *interpreter = NULL;
+   size_t sz;
+
+   file = eina_file_open(child, EINA_FALSE);
+   if (!file)
+     return NULL;
+
+   it = eina_file_map_lines(file);
+   if (!it)
+     goto close_file;
+
+   if (!eina_iterator_next(it, (void **)(void *)&line))
+     goto free_iterator;
+
+   /*
+    * shebang spec:
+    * #!
+    * optional spaces
+    * optional /usr/bin/env
+    * at least one space
+    * interpreter
+    */
+
+   if (line->length < 2)
+     goto free_iterator;
+
+   iter = line->start;
+   if ((iter[0] != '#') && (iter[1] != '!'))
+     goto free_iterator;
+
+   iter += 2;
+
+   /* skip possible spaces after #! */
+   while (iter < line->end)
+     {
+        if (*iter != ' ')
+          break;
+        iter++;
+     }
+
+   /*
+    * Check if "/usr/bin/env" is used.
+    * If so, skip it (useless on Windows as the command is searched in PATH).
+    * Note that length of "/usr/bin/env" is 12.
+    */
+   if ((line->end - iter >= 12) &&
+       (strncmp(iter, "/usr/bin/env", 12) == 0))
+     iter += 12;
+
+   /* skip possible spaces after #! */
+   while (iter < line->end)
+     {
+        if (*iter != ' ')
+          break;
+        iter++;
+     }
+
+   if (*iter == '/')
+     {
+        const char *i;
+
+        /* get the last '/' (parse from last char) */
+        i = line->end;
+        while (i != iter)
+          {
+             if (*i == '/')
+               break;
+             i--;
+          }
+
+        if (i == iter)
+          {
+             goto free_iterator;
+          }
+
+        i++;
+        if (i > line->end)
+          goto free_iterator;
+
+        iter = i;
+     }
+
+   sz = line->end - iter;
+   /* quote it --> + 2 for the 2 " */
+   interpreter = malloc(sz + 3);
+   if (!interpreter)
+     goto free_iterator;
+
+   interpreter[0] = '\"';
+   memcpy(interpreter + 1, iter, sz);
+   interpreter[sz + 1] = '\"';
+   interpreter[sz + 2] = '\0';
+
+ free_iterator:
+   eina_iterator_free(it);
+ close_file:
+   eina_file_close(file);
+
+   return interpreter;
+}
+
+static char *
+_ecore_exe_win32_batch_cmd_get(const char *exe_cmd)
+{
+   char *cmd = NULL;
+   size_t len;
+
+   if (!exe_cmd || !*exe_cmd)
+     return NULL;
+
+   len = strlen(exe_cmd);
+   cmd = (char *)calloc(len + 16, sizeof(char));
+   if (cmd)
+     {
+        char *iter = cmd;
+
+        memcpy(iter, "\"cmd.exe\" \"/C\" ", 15);
+        iter += 15;
+        memcpy (iter, exe_cmd, len + 1);
+
+        DBG("Batch cmd: '%s'", cmd);
+     }
+
+   return cmd;
+}
+
+static char *
+_ecore_exe_win32_shebang_cmd_get(const char *exe_cmd, const char *child)
+{
+   char *cmd = NULL;
+   char *interpreter;
+   char *iter;
+   size_t len;
+   size_t len2;
+
+   if (!exe_cmd || !*exe_cmd || !child || !*child)
+     return NULL;
+
+   interpreter = _ecore_exe_win32_shebang_interpreter_get(child);
+   if (!interpreter)
+     return NULL;
+
+   len = strlen(exe_cmd);
+   len2 = strlen(interpreter);
+   cmd = (char *)calloc(len + len2 + 2, sizeof(char));
+   if (!cmd)
+     {
+        free(interpreter);
+        return NULL;
+     }
+
+   iter = cmd;
+   memcpy(iter, interpreter, len2);
+   free(interpreter);
+   iter += len2;
+   *iter = ' ';
+   iter++;
+   memcpy (iter, exe_cmd, len + 1);
+
+   DBG("Shebang cmd: '%s'", cmd);
+
+   return cmd;
+}
+
 Eo *
 _impl_ecore_exe_efl_object_finalize(Eo *obj, Ecore_Exe_Data *exe)
 {
-   char exe_cmd_buf[32768];
    SECURITY_ATTRIBUTES sa;
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
+   char *cmd = NULL;
+   char *child = NULL;
    HANDLE child_pipe_read = NULL;
    HANDLE child_pipe_error = NULL;
-   const char *shell = NULL;
    Ecore_Exe_Event_Add *e;
    Ecore_Exe_Flags flags;
-   Eina_Bool use_sh = EINA_FALSE;
+   BOOL ret;
 
    EINA_MAIN_LOOP_CHECK_RETURN_VAL(NULL);
 
@@ -409,28 +623,6 @@ _impl_ecore_exe_efl_object_finalize(Eo *obj, Ecore_Exe_Data *exe)
      /* We need something to auto pipe. */
      flags |= ECORE_EXE_PIPE_READ | ECORE_EXE_PIPE_ERROR;
 
-   if ((flags & ECORE_EXE_USE_SH)) use_sh = EINA_TRUE;
-   else use_sh = eina_str_has_extension(exe->cmd, ".bat");
-
-   if (use_sh)
-     {
-        int len;
-
-        shell = "cmd.exe";
-        len = snprintf(exe_cmd_buf, sizeof(exe_cmd_buf), "/c %s", exe->cmd);
-        if (len >= (int)sizeof(exe_cmd_buf))
-          exe_cmd_buf[sizeof(exe_cmd_buf) - 1] = '\0';
-     }
-   else
-     {
-        int len;
-
-        /* FIXME : faster with memset() but one must be careful with size */
-        len = snprintf(exe_cmd_buf, sizeof(exe_cmd_buf), "%s", exe->cmd);
-        if (len >= (int)sizeof(exe_cmd_buf))
-          exe_cmd_buf[sizeof(exe_cmd_buf) - 1] = '\0';
-     }
-
    /* stdout, stderr and stdin pipes */
    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
    sa.bInheritHandle = EINA_TRUE;
@@ -503,12 +695,50 @@ _impl_ecore_exe_efl_object_finalize(Eo *obj, Ecore_Exe_Data *exe)
    si.hStdError = child_pipe_error;
    si.dwFlags |= STARTF_USESTDHANDLES;
 
-   DBG("CreateProcess: shell:%s child:%s", use_sh ? "yes" : "no", exe_cmd_buf);
-   if (!CreateProcess(shell, exe_cmd_buf, NULL, NULL, EINA_TRUE,
-                      run_pri | CREATE_SUSPENDED, NULL, NULL, &si, &pi))
+   /*
+    * Creation of command line.
+    * Don't use an Eina_Strbuf as CreateProcess() can modify the string
+    * and eina_strbuf_string_get() returns a const string
+    */
+
+   /* Try first with cmd if the extension is .bat */
+   child = _ecore_exe_win32_child_get(exe->cmd);
+   if (eina_str_has_extension(child, ".bat"))
      {
-        WRN("Failed to create process %s: %s",
-            exe_cmd_buf, evil_last_error_get());
+        cmd = _ecore_exe_win32_batch_cmd_get(exe->cmd);
+        if (!cmd) goto error;
+
+        ret = CreateProcess(NULL, cmd,
+                            NULL, NULL, EINA_TRUE,
+                            run_pri | CREATE_SUSPENDED, NULL, NULL,
+                            &si, &pi);
+     }
+   else if ((cmd = _ecore_exe_win32_shebang_cmd_get(exe->cmd, child)))
+     {
+        ret = CreateProcess(NULL, cmd,
+                            NULL, NULL, EINA_TRUE,
+                            run_pri | CREATE_SUSPENDED, NULL, NULL,
+                            &si, &pi);
+     }
+   else
+     {
+        size_t len = strlen(exe->cmd);
+
+        cmd = (char *)calloc(len + 1, sizeof(char));
+        if (!cmd) goto error;
+
+        memcpy(cmd, exe->cmd, len + 1);
+        DBG("CreateProcess: child: '%s'", cmd);
+        ret = CreateProcess(NULL, cmd,
+                            NULL, NULL, EINA_TRUE,
+                            run_pri | CREATE_SUSPENDED, NULL, NULL,
+                            &si, &pi);
+     }
+
+   if (!ret)
+     {
+        WRN("Failed to create process '%s': %s",
+            cmd, evil_last_error_get());
         goto error;
      }
 
@@ -559,6 +789,8 @@ _impl_ecore_exe_efl_object_finalize(Eo *obj, Ecore_Exe_Data *exe)
    return obj;
 
 error:
+   free(child);
+   free(cmd);
    return NULL;
 }
 

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.

Reply via email to