test-spawn.c
Go to the documentation of this file.
1 
2 /* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to
6  * deal in the Software without restriction, including without limitation the
7  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
8  * sell copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in
12  * all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20  * IN THE SOFTWARE.
21  */
22 
23 #include "uv.h"
24 #include "task.h"
25 #include <errno.h>
26 #include <fcntl.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 
31 #ifdef _WIN32
32 # if defined(__MINGW32__)
33 # include <basetyps.h>
34 # endif
35 # include <shellapi.h>
36 # include <wchar.h>
37 #else
38 # include <unistd.h>
39 # include <sys/wait.h>
40 #endif
41 
42 
43 static int close_cb_called;
44 static int exit_cb_called;
48 static char exepath[1024];
49 static size_t exepath_size = 1024;
50 static char* args[5];
51 static int no_term_signal;
52 #ifndef _WIN32
53 static int timer_counter;
54 #endif
56 
57 #define OUTPUT_SIZE 1024
58 static char output[OUTPUT_SIZE];
59 static int output_used;
60 
61 
62 static void close_cb(uv_handle_t* handle) {
63  printf("close_cb\n");
65 }
66 
68  int64_t exit_status,
69  int term_signal) {
70  printf("exit_cb\n");
72  ASSERT(exit_status == 1);
73  ASSERT(term_signal == 0);
75 }
76 
77 
79  int64_t exit_status,
80  int term_signal) {
81  ASSERT(0 && "fail_cb called");
82 }
83 
84 
86  int64_t exit_status,
87  int term_signal) {
88  int err;
89 
90  printf("exit_cb\n");
92 #ifdef _WIN32
93  ASSERT(exit_status == 1);
94 #else
95  ASSERT(exit_status == 0);
96 #endif
97 #if defined(__APPLE__) || defined(__MVS__)
98  /*
99  * At least starting with Darwin Kernel Version 16.4.0, sending a SIGTERM to a
100  * process that is still starting up kills it with SIGKILL instead of SIGTERM.
101  * See: https://github.com/libuv/libuv/issues/1226
102  */
103  ASSERT(no_term_signal || term_signal == SIGTERM || term_signal == SIGKILL);
104 #else
105  ASSERT(no_term_signal || term_signal == SIGTERM);
106 #endif
108 
109  /*
110  * Sending signum == 0 should check if the
111  * child process is still alive, not kill it.
112  * This process should be dead.
113  */
114  err = uv_kill(process->pid, 0);
115  ASSERT(err == UV_ESRCH);
116 }
117 
119  int64_t exit_status,
120  int term_signal) {
121  printf("detach_cb\n");
122  exit_cb_called++;
123 }
124 
126  size_t suggested_size,
127  uv_buf_t* buf) {
128  buf->base = output + output_used;
129  buf->len = OUTPUT_SIZE - output_used;
130 }
131 
132 
133 static void on_read(uv_stream_t* tcp, ssize_t nread, const uv_buf_t* buf) {
134  if (nread > 0) {
135  output_used += nread;
136  } else if (nread < 0) {
137  ASSERT(nread == UV_EOF);
139  }
140 }
141 
142 
143 #ifndef _WIN32
144 static void on_read_once(uv_stream_t* tcp, ssize_t nread, const uv_buf_t* buf) {
145  uv_read_stop(tcp);
146  on_read(tcp, nread, buf);
147 }
148 #endif
149 
150 
151 static void write_cb(uv_write_t* req, int status) {
152  ASSERT(status == 0);
154 }
155 
156 
158  /* Note spawn_helper1 defined in test/run-tests.c */
159  int r = uv_exepath(exepath, &exepath_size);
160  ASSERT(r == 0);
161  exepath[exepath_size] = '\0';
162  args[0] = exepath;
163  args[1] = test;
164  args[2] = NULL;
165  args[3] = NULL;
166  args[4] = NULL;
167  options.file = exepath;
168  options.args = args;
170  options.flags = 0;
171 }
172 
173 
174 static void timer_cb(uv_timer_t* handle) {
175  uv_process_kill(&process, /* SIGTERM */ 15);
177 }
178 
179 
180 #ifndef _WIN32
182  ++timer_counter;
183 }
184 #endif
185 
186 
187 TEST_IMPL(spawn_fails) {
188  int r;
189 
191  options.file = options.args[0] = "program-that-had-better-not-exist";
192 
194  ASSERT(r == UV_ENOENT || r == UV_EACCES);
196  uv_close((uv_handle_t*) &process, NULL);
198 
200  return 0;
201 }
202 
203 
204 #ifndef _WIN32
205 TEST_IMPL(spawn_fails_check_for_waitpid_cleanup) {
206  int r;
207  int status;
208  int err;
209 
211  options.file = options.args[0] = "program-that-had-better-not-exist";
212 
214  ASSERT(r == UV_ENOENT || r == UV_EACCES);
217 
218  /* verify the child is successfully cleaned up within libuv */
219  do
220  err = waitpid(process.pid, &status, 0);
221  while (err == -1 && errno == EINTR);
222 
223  ASSERT(err == -1);
224  ASSERT(errno == ECHILD);
225 
226  uv_close((uv_handle_t*) &process, NULL);
228 
230  return 0;
231 }
232 #endif
233 
234 
235 TEST_IMPL(spawn_empty_env) {
236  char* env[1];
237 
238  /* The autotools dynamic library build requires the presence of
239  * DYLD_LIBARY_PATH (macOS) or LD_LIBRARY_PATH/LIBPATH (other Unices)
240  * in the environment, but of course that doesn't work with
241  * the empty environment that we're testing here.
242  */
243  if (NULL != getenv("DYLD_LIBRARY_PATH") ||
244  NULL != getenv("LD_LIBRARY_PATH") ||
245  NULL != getenv("LIBPATH")) {
246  RETURN_SKIP("doesn't work with DYLD_LIBRARY_PATH/LD_LIBRARY_PATH/LIBPATH");
247  }
248 
249  init_process_options("spawn_helper1", exit_cb);
250  options.env = env;
251  env[0] = NULL;
252 
255 
256  ASSERT(exit_cb_called == 1);
257  ASSERT(close_cb_called == 1);
258 
260  return 0;
261 }
262 
263 
264 TEST_IMPL(spawn_exit_code) {
265  int r;
266 
267  init_process_options("spawn_helper1", exit_cb);
268 
270  ASSERT(r == 0);
271 
273  ASSERT(r == 0);
274 
275  ASSERT(exit_cb_called == 1);
276  ASSERT(close_cb_called == 1);
277 
279  return 0;
280 }
281 
282 
283 TEST_IMPL(spawn_stdout) {
284  int r;
285  uv_pipe_t out;
286  uv_stdio_container_t stdio[2];
287 
288  init_process_options("spawn_helper2", exit_cb);
289 
291  options.stdio = stdio;
295  options.stdio_count = 2;
296 
298  ASSERT(r == 0);
299 
301  ASSERT(r == 0);
302 
304  ASSERT(r == 0);
305 
306  ASSERT(exit_cb_called == 1);
307  ASSERT(close_cb_called == 2); /* Once for process once for the pipe. */
308  printf("output is: %s", output);
309  ASSERT(strcmp("hello world\n", output) == 0);
310 
312  return 0;
313 }
314 
315 
316 TEST_IMPL(spawn_stdout_to_file) {
317  int r;
318  uv_file file;
319  uv_fs_t fs_req;
320  uv_stdio_container_t stdio[2];
321  uv_buf_t buf;
322 
323  /* Setup. */
324  unlink("stdout_file");
325 
326  init_process_options("spawn_helper2", exit_cb);
327 
328  r = uv_fs_open(NULL, &fs_req, "stdout_file", O_CREAT | O_RDWR,
329  S_IRUSR | S_IWUSR, NULL);
330  ASSERT(r != -1);
332 
333  file = r;
334 
335  options.stdio = stdio;
338  options.stdio[1].data.fd = file;
339  options.stdio_count = 2;
340 
342  ASSERT(r == 0);
343 
345  ASSERT(r == 0);
346 
347  ASSERT(exit_cb_called == 1);
348  ASSERT(close_cb_called == 1);
349 
350  buf = uv_buf_init(output, sizeof(output));
351  r = uv_fs_read(NULL, &fs_req, file, &buf, 1, 0, NULL);
352  ASSERT(r == 12);
354 
355  r = uv_fs_close(NULL, &fs_req, file, NULL);
356  ASSERT(r == 0);
358 
359  printf("output is: %s", output);
360  ASSERT(strcmp("hello world\n", output) == 0);
361 
362  /* Cleanup. */
363  unlink("stdout_file");
364 
366  return 0;
367 }
368 
369 
370 TEST_IMPL(spawn_stdout_and_stderr_to_file) {
371  int r;
372  uv_file file;
373  uv_fs_t fs_req;
374  uv_stdio_container_t stdio[3];
375  uv_buf_t buf;
376 
377  /* Setup. */
378  unlink("stdout_file");
379 
380  init_process_options("spawn_helper6", exit_cb);
381 
382  r = uv_fs_open(NULL, &fs_req, "stdout_file", O_CREAT | O_RDWR,
383  S_IRUSR | S_IWUSR, NULL);
384  ASSERT(r != -1);
386 
387  file = r;
388 
389  options.stdio = stdio;
392  options.stdio[1].data.fd = file;
394  options.stdio[2].data.fd = file;
395  options.stdio_count = 3;
396 
398  ASSERT(r == 0);
399 
401  ASSERT(r == 0);
402 
403  ASSERT(exit_cb_called == 1);
404  ASSERT(close_cb_called == 1);
405 
406  buf = uv_buf_init(output, sizeof(output));
407  r = uv_fs_read(NULL, &fs_req, file, &buf, 1, 0, NULL);
408  ASSERT(r == 27);
410 
411  r = uv_fs_close(NULL, &fs_req, file, NULL);
412  ASSERT(r == 0);
414 
415  printf("output is: %s", output);
416  ASSERT(strcmp("hello world\nhello errworld\n", output) == 0);
417 
418  /* Cleanup. */
419  unlink("stdout_file");
420 
422  return 0;
423 }
424 
425 
426 TEST_IMPL(spawn_stdout_and_stderr_to_file2) {
427 #ifndef _WIN32
428  int r;
429  uv_file file;
430  uv_fs_t fs_req;
431  uv_stdio_container_t stdio[3];
432  uv_buf_t buf;
433 
434  /* Setup. */
435  unlink("stdout_file");
436 
437  init_process_options("spawn_helper6", exit_cb);
438 
439  /* Replace stderr with our file */
440  r = uv_fs_open(NULL,
441  &fs_req,
442  "stdout_file",
443  O_CREAT | O_RDWR,
444  S_IRUSR | S_IWUSR,
445  NULL);
446  ASSERT(r != -1);
448  file = dup2(r, STDERR_FILENO);
449  ASSERT(file != -1);
450 
451  options.stdio = stdio;
454  options.stdio[1].data.fd = file;
456  options.stdio[2].data.fd = file;
457  options.stdio_count = 3;
458 
460  ASSERT(r == 0);
461 
463  ASSERT(r == 0);
464 
465  ASSERT(exit_cb_called == 1);
466  ASSERT(close_cb_called == 1);
467 
468  buf = uv_buf_init(output, sizeof(output));
469  r = uv_fs_read(NULL, &fs_req, file, &buf, 1, 0, NULL);
470  ASSERT(r == 27);
472 
473  r = uv_fs_close(NULL, &fs_req, file, NULL);
474  ASSERT(r == 0);
476 
477  printf("output is: %s", output);
478  ASSERT(strcmp("hello world\nhello errworld\n", output) == 0);
479 
480  /* Cleanup. */
481  unlink("stdout_file");
482 
484  return 0;
485 #else
486  RETURN_SKIP("Unix only test");
487 #endif
488 }
489 
490 
491 TEST_IMPL(spawn_stdout_and_stderr_to_file_swap) {
492 #ifndef _WIN32
493  int r;
494  uv_file stdout_file;
495  uv_file stderr_file;
496  uv_fs_t fs_req;
497  uv_stdio_container_t stdio[3];
498  uv_buf_t buf;
499 
500  /* Setup. */
501  unlink("stdout_file");
502  unlink("stderr_file");
503 
504  init_process_options("spawn_helper6", exit_cb);
505 
506  /* open 'stdout_file' and replace STDOUT_FILENO with it */
507  r = uv_fs_open(NULL,
508  &fs_req,
509  "stdout_file",
510  O_CREAT | O_RDWR,
511  S_IRUSR | S_IWUSR,
512  NULL);
513  ASSERT(r != -1);
515  stdout_file = dup2(r, STDOUT_FILENO);
516  ASSERT(stdout_file != -1);
517 
518  /* open 'stderr_file' and replace STDERR_FILENO with it */
519  r = uv_fs_open(NULL, &fs_req, "stderr_file", O_CREAT | O_RDWR,
520  S_IRUSR | S_IWUSR, NULL);
521  ASSERT(r != -1);
523  stderr_file = dup2(r, STDERR_FILENO);
524  ASSERT(stderr_file != -1);
525 
526  /* now we're going to swap them: the child process' stdout will be our
527  * stderr_file and vice versa */
528  options.stdio = stdio;
531  options.stdio[1].data.fd = stderr_file;
533  options.stdio[2].data.fd = stdout_file;
534  options.stdio_count = 3;
535 
537  ASSERT(r == 0);
538 
540  ASSERT(r == 0);
541 
542  ASSERT(exit_cb_called == 1);
543  ASSERT(close_cb_called == 1);
544 
545  buf = uv_buf_init(output, sizeof(output));
546 
547  /* check the content of stdout_file */
548  r = uv_fs_read(NULL, &fs_req, stdout_file, &buf, 1, 0, NULL);
549  ASSERT(r >= 15);
551 
552  r = uv_fs_close(NULL, &fs_req, stdout_file, NULL);
553  ASSERT(r == 0);
555 
556  printf("output is: %s", output);
557  ASSERT(strncmp("hello errworld\n", output, 15) == 0);
558 
559  /* check the content of stderr_file */
560  r = uv_fs_read(NULL, &fs_req, stderr_file, &buf, 1, 0, NULL);
561  ASSERT(r >= 12);
563 
564  r = uv_fs_close(NULL, &fs_req, stderr_file, NULL);
565  ASSERT(r == 0);
567 
568  printf("output is: %s", output);
569  ASSERT(strncmp("hello world\n", output, 12) == 0);
570 
571  /* Cleanup. */
572  unlink("stdout_file");
573  unlink("stderr_file");
574 
576  return 0;
577 #else
578  RETURN_SKIP("Unix only test");
579 #endif
580 }
581 
582 
583 TEST_IMPL(spawn_stdin) {
584  int r;
585  uv_pipe_t out;
586  uv_pipe_t in;
588  uv_buf_t buf;
589  uv_stdio_container_t stdio[2];
590  char buffer[] = "hello-from-spawn_stdin";
591 
592  init_process_options("spawn_helper3", exit_cb);
593 
596  options.stdio = stdio;
601  options.stdio_count = 2;
602 
604  ASSERT(r == 0);
605 
606  buf.base = buffer;
607  buf.len = sizeof(buffer);
608  r = uv_write(&write_req, (uv_stream_t*)&in, &buf, 1, write_cb);
609  ASSERT(r == 0);
610 
612  ASSERT(r == 0);
613 
615  ASSERT(r == 0);
616 
617  ASSERT(exit_cb_called == 1);
618  ASSERT(close_cb_called == 3); /* Once for process twice for the pipe. */
619  ASSERT(strcmp(buffer, output) == 0);
620 
622  return 0;
623 }
624 
625 
626 TEST_IMPL(spawn_stdio_greater_than_3) {
627  int r;
628  uv_pipe_t pipe;
629  uv_stdio_container_t stdio[4];
630 
631  init_process_options("spawn_helper5", exit_cb);
632 
633  uv_pipe_init(uv_default_loop(), &pipe, 0);
634  options.stdio = stdio;
639  options.stdio[3].data.stream = (uv_stream_t*)&pipe;
640  options.stdio_count = 4;
641 
643  ASSERT(r == 0);
644 
646  ASSERT(r == 0);
647 
649  ASSERT(r == 0);
650 
651  ASSERT(exit_cb_called == 1);
652  ASSERT(close_cb_called == 2); /* Once for process once for the pipe. */
653  printf("output from stdio[3] is: %s", output);
654  ASSERT(strcmp("fourth stdio!\n", output) == 0);
655 
657  return 0;
658 }
659 
660 
662  uv_tcp_t tcp;
664  int r;
665 
667  ASSERT(r == 0);
668 
669 #ifdef _WIN32
670  handle = _get_osfhandle(3);
671 #else
672  handle = 3;
673 #endif
674  r = uv_tcp_open(&tcp, handle);
675  ASSERT(r == 0);
676 
677  /* Make sure that we can listen on a socket that was
678  * passed down from the parent process
679  */
680  r = uv_listen((uv_stream_t*)&tcp, SOMAXCONN, NULL);
681  ASSERT(r == 0);
682 
683  return 1;
684 }
685 
686 
687 TEST_IMPL(spawn_tcp_server) {
688  uv_stdio_container_t stdio[4];
689  struct sockaddr_in addr;
690  int fd;
691  int r;
692 #ifdef _WIN32
694 #endif
695 
696  init_process_options("spawn_tcp_server_helper", exit_cb);
697 
698  ASSERT(0 == uv_ip4_addr("127.0.0.1", TEST_PORT, &addr));
699 
700  fd = -1;
701  r = uv_tcp_init_ex(uv_default_loop(), &tcp_server, AF_INET);
702  ASSERT(r == 0);
703  r = uv_tcp_bind(&tcp_server, (const struct sockaddr*) &addr, 0);
704  ASSERT(r == 0);
705 #ifdef _WIN32
707  fd = _open_osfhandle((intptr_t) handle, 0);
708 #else
709  r = uv_fileno((uv_handle_t*)&tcp_server, &fd);
710  #endif
711  ASSERT(r == 0);
712  ASSERT(fd > 0);
713 
714  options.stdio = stdio;
716  options.stdio[0].data.fd = 0;
718  options.stdio[1].data.fd = 1;
720  options.stdio[2].data.fd = 2;
722  options.stdio[3].data.fd = fd;
723  options.stdio_count = 4;
724 
726  ASSERT(r == 0);
727 
729  ASSERT(r == 0);
730 
731  ASSERT(exit_cb_called == 1);
732  ASSERT(close_cb_called == 1);
733 
735  return 0;
736 }
737 
738 
739 TEST_IMPL(spawn_ignored_stdio) {
740  int r;
741 
742  init_process_options("spawn_helper6", exit_cb);
743 
744  options.stdio = NULL;
745  options.stdio_count = 0;
746 
748  ASSERT(r == 0);
749 
751  ASSERT(r == 0);
752 
753  ASSERT(exit_cb_called == 1);
754  ASSERT(close_cb_called == 1);
755 
757  return 0;
758 }
759 
760 
761 TEST_IMPL(spawn_and_kill) {
762  int r;
763 
764  init_process_options("spawn_helper4", kill_cb);
765 
767  ASSERT(r == 0);
768 
770  ASSERT(r == 0);
771 
772  r = uv_timer_start(&timer, timer_cb, 500, 0);
773  ASSERT(r == 0);
774 
776  ASSERT(r == 0);
777 
778  ASSERT(exit_cb_called == 1);
779  ASSERT(close_cb_called == 2); /* Once for process and once for timer. */
780 
782  return 0;
783 }
784 
785 
786 TEST_IMPL(spawn_preserve_env) {
787  int r;
788  uv_pipe_t out;
789  uv_stdio_container_t stdio[2];
790 
791  init_process_options("spawn_helper7", exit_cb);
792 
794  options.stdio = stdio;
798  options.stdio_count = 2;
799 
800  r = putenv("ENV_TEST=testval");
801  ASSERT(r == 0);
802 
803  /* Explicitly set options.env to NULL to test for env clobbering. */
804  options.env = NULL;
805 
807  ASSERT(r == 0);
808 
810  ASSERT(r == 0);
811 
813  ASSERT(r == 0);
814 
815  ASSERT(exit_cb_called == 1);
816  ASSERT(close_cb_called == 2);
817 
818  printf("output is: %s", output);
819  ASSERT(strcmp("testval", output) == 0);
820 
822  return 0;
823 }
824 
825 
826 TEST_IMPL(spawn_detached) {
827  int r;
828 
829  init_process_options("spawn_helper4", detach_failure_cb);
830 
832 
834  ASSERT(r == 0);
835 
837 
839  ASSERT(r == 0);
840 
841  ASSERT(exit_cb_called == 0);
842 
844 
845  r = uv_kill(process.pid, 0);
846  ASSERT(r == 0);
847 
848  r = uv_kill(process.pid, 15);
849  ASSERT(r == 0);
850 
852  return 0;
853 }
854 
855 TEST_IMPL(spawn_and_kill_with_std) {
856  int r;
857  uv_pipe_t in, out, err;
859  char message[] = "Nancy's joining me because the message this evening is "
860  "not my message but ours.";
861  uv_buf_t buf;
862  uv_stdio_container_t stdio[3];
863 
864  init_process_options("spawn_helper4", kill_cb);
865 
866  r = uv_pipe_init(uv_default_loop(), &in, 0);
867  ASSERT(r == 0);
868 
869  r = uv_pipe_init(uv_default_loop(), &out, 0);
870  ASSERT(r == 0);
871 
872  r = uv_pipe_init(uv_default_loop(), &err, 0);
873  ASSERT(r == 0);
874 
875  options.stdio = stdio;
882  options.stdio_count = 3;
883 
885  ASSERT(r == 0);
886 
887  buf = uv_buf_init(message, sizeof message);
888  r = uv_write(&write, (uv_stream_t*) &in, &buf, 1, write_cb);
889  ASSERT(r == 0);
890 
892  ASSERT(r == 0);
893 
895  ASSERT(r == 0);
896 
898  ASSERT(r == 0);
899 
900  r = uv_timer_start(&timer, timer_cb, 500, 0);
901  ASSERT(r == 0);
902 
904  ASSERT(r == 0);
905 
906  ASSERT(exit_cb_called == 1);
907  ASSERT(close_cb_called == 5); /* process x 1, timer x 1, stdio x 3. */
908 
910  return 0;
911 }
912 
913 
914 TEST_IMPL(spawn_and_ping) {
916  uv_pipe_t in, out;
917  uv_buf_t buf;
918  uv_stdio_container_t stdio[2];
919  int r;
920 
921  init_process_options("spawn_helper3", exit_cb);
922  buf = uv_buf_init("TEST", 4);
923 
926  options.stdio = stdio;
931  options.stdio_count = 2;
932 
934  ASSERT(r == 0);
935 
936  /* Sending signum == 0 should check if the
937  * child process is still alive, not kill it.
938  */
939  r = uv_process_kill(&process, 0);
940  ASSERT(r == 0);
941 
942  r = uv_write(&write_req, (uv_stream_t*)&in, &buf, 1, write_cb);
943  ASSERT(r == 0);
944 
946  ASSERT(r == 0);
947 
948  ASSERT(exit_cb_called == 0);
949 
951  ASSERT(r == 0);
952 
953  ASSERT(exit_cb_called == 1);
954  ASSERT(strcmp(output, "TEST") == 0);
955 
957  return 0;
958 }
959 
960 
961 TEST_IMPL(spawn_same_stdout_stderr) {
963  uv_pipe_t in, out;
964  uv_buf_t buf;
965  uv_stdio_container_t stdio[3];
966  int r;
967 
968  init_process_options("spawn_helper3", exit_cb);
969  buf = uv_buf_init("TEST", 4);
970 
973  options.stdio = stdio;
978  options.stdio_count = 2;
979 
981  ASSERT(r == 0);
982 
983  /* Sending signum == 0 should check if the
984  * child process is still alive, not kill it.
985  */
986  r = uv_process_kill(&process, 0);
987  ASSERT(r == 0);
988 
989  r = uv_write(&write_req, (uv_stream_t*)&in, &buf, 1, write_cb);
990  ASSERT(r == 0);
991 
993  ASSERT(r == 0);
994 
995  ASSERT(exit_cb_called == 0);
996 
998  ASSERT(r == 0);
999 
1000  ASSERT(exit_cb_called == 1);
1001  ASSERT(strcmp(output, "TEST") == 0);
1002 
1004  return 0;
1005 }
1006 
1007 
1008 TEST_IMPL(spawn_closed_process_io) {
1009  uv_pipe_t in;
1011  uv_buf_t buf;
1012  uv_stdio_container_t stdio[2];
1013  static char buffer[] = "hello-from-spawn_stdin\n";
1014 
1015  init_process_options("spawn_helper3", exit_cb);
1016 
1018  options.stdio = stdio;
1021  options.stdio_count = 1;
1022 
1023  close(0); /* Close process stdin. */
1024 
1026 
1027  buf = uv_buf_init(buffer, sizeof(buffer));
1028  ASSERT(0 == uv_write(&write_req, (uv_stream_t*) &in, &buf, 1, write_cb));
1029 
1031 
1032  ASSERT(exit_cb_called == 1);
1033  ASSERT(close_cb_called == 2); /* process, child stdin */
1034 
1036  return 0;
1037 }
1038 
1039 
1040 TEST_IMPL(kill) {
1041  int r;
1042 
1043 #ifdef _WIN32
1044  no_term_signal = 1;
1045 #endif
1046 
1047  init_process_options("spawn_helper4", kill_cb);
1048 
1049  /* Verify that uv_spawn() resets the signal disposition. */
1050 #ifndef _WIN32
1051  {
1052  sigset_t set;
1053  sigemptyset(&set);
1054  sigaddset(&set, SIGTERM);
1055  ASSERT(0 == pthread_sigmask(SIG_BLOCK, &set, NULL));
1056  }
1057  ASSERT(SIG_ERR != signal(SIGTERM, SIG_IGN));
1058 #endif
1059 
1061  ASSERT(r == 0);
1062 
1063 #ifndef _WIN32
1064  {
1065  sigset_t set;
1066  sigemptyset(&set);
1067  sigaddset(&set, SIGTERM);
1068  ASSERT(0 == pthread_sigmask(SIG_UNBLOCK, &set, NULL));
1069  }
1070  ASSERT(SIG_ERR != signal(SIGTERM, SIG_DFL));
1071 #endif
1072 
1073  /* Sending signum == 0 should check if the
1074  * child process is still alive, not kill it.
1075  */
1076  r = uv_kill(process.pid, 0);
1077  ASSERT(r == 0);
1078 
1079  /* Kill the process. */
1080  r = uv_kill(process.pid, /* SIGTERM */ 15);
1081  ASSERT(r == 0);
1082 
1084  ASSERT(r == 0);
1085 
1086  ASSERT(exit_cb_called == 1);
1087  ASSERT(close_cb_called == 1);
1088 
1090  return 0;
1091 }
1092 
1093 
1094 #ifdef _WIN32
1095 TEST_IMPL(spawn_detect_pipe_name_collisions_on_windows) {
1096  int r;
1097  uv_pipe_t out;
1098  char name[64];
1099  HANDLE pipe_handle;
1100  uv_stdio_container_t stdio[2];
1101 
1102  init_process_options("spawn_helper2", exit_cb);
1103 
1105  options.stdio = stdio;
1109  options.stdio_count = 2;
1110 
1111  /* Create a pipe that'll cause a collision. */
1112  snprintf(name,
1113  sizeof(name),
1114  "\\\\.\\pipe\\uv\\%p-%d",
1115  &out,
1116  GetCurrentProcessId());
1117  pipe_handle = CreateNamedPipeA(name,
1118  PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED,
1119  PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
1120  10,
1121  65536,
1122  65536,
1123  0,
1124  NULL);
1126 
1128  ASSERT(r == 0);
1129 
1131  ASSERT(r == 0);
1132 
1134  ASSERT(r == 0);
1135 
1136  ASSERT(exit_cb_called == 1);
1137  ASSERT(close_cb_called == 2); /* Once for process once for the pipe. */
1138  printf("output is: %s", output);
1139  ASSERT(strcmp("hello world\n", output) == 0);
1140 
1142  return 0;
1143 }
1144 
1145 
1146 #if !defined(USING_UV_SHARED)
1147 int make_program_args(char** args, int verbatim_arguments, WCHAR** dst_ptr);
1148 WCHAR* quote_cmd_arg(const WCHAR *source, WCHAR *target);
1149 
1150 TEST_IMPL(argument_escaping) {
1151  const WCHAR* test_str[] = {
1152  L"",
1153  L"HelloWorld",
1154  L"Hello World",
1155  L"Hello\"World",
1156  L"Hello World\\",
1157  L"Hello\\\"World",
1158  L"Hello\\World",
1159  L"Hello\\\\World",
1160  L"Hello World\\",
1161  L"c:\\path\\to\\node.exe --eval \"require('c:\\\\path\\\\to\\\\test.js')\""
1162  };
1163  const int count = sizeof(test_str) / sizeof(*test_str);
1164  WCHAR** test_output;
1165  WCHAR* command_line;
1166  WCHAR** cracked;
1167  size_t total_size = 0;
1168  int i;
1169  int num_args;
1170  int result;
1171 
1172  char* verbatim[] = {
1173  "cmd.exe",
1174  "/c",
1175  "c:\\path\\to\\node.exe --eval \"require('c:\\\\path\\\\to\\\\test.js')\"",
1176  NULL
1177  };
1178  WCHAR* verbatim_output;
1179  WCHAR* non_verbatim_output;
1180 
1181  test_output = calloc(count, sizeof(WCHAR*));
1182  ASSERT(test_output != NULL);
1183  for (i = 0; i < count; ++i) {
1184  test_output[i] = calloc(2 * (wcslen(test_str[i]) + 2), sizeof(WCHAR));
1185  quote_cmd_arg(test_str[i], test_output[i]);
1186  wprintf(L"input : %s\n", test_str[i]);
1187  wprintf(L"output: %s\n", test_output[i]);
1188  total_size += wcslen(test_output[i]) + 1;
1189  }
1190  command_line = calloc(total_size + 1, sizeof(WCHAR));
1191  ASSERT(command_line != NULL);
1192  for (i = 0; i < count; ++i) {
1193  wcscat(command_line, test_output[i]);
1194  wcscat(command_line, L" ");
1195  }
1196  command_line[total_size - 1] = L'\0';
1197 
1198  wprintf(L"command_line: %s\n", command_line);
1199 
1200  cracked = CommandLineToArgvW(command_line, &num_args);
1201  for (i = 0; i < num_args; ++i) {
1202  wprintf(L"%d: %s\t%s\n", i, test_str[i], cracked[i]);
1203  ASSERT(wcscmp(test_str[i], cracked[i]) == 0);
1204  }
1205 
1206  LocalFree(cracked);
1207  for (i = 0; i < count; ++i) {
1208  free(test_output[i]);
1209  }
1210  free(test_output);
1211 
1212  result = make_program_args(verbatim, 1, &verbatim_output);
1213  ASSERT(result == 0);
1214  result = make_program_args(verbatim, 0, &non_verbatim_output);
1215  ASSERT(result == 0);
1216 
1217  wprintf(L" verbatim_output: %s\n", verbatim_output);
1218  wprintf(L"non_verbatim_output: %s\n", non_verbatim_output);
1219 
1220  ASSERT(wcscmp(verbatim_output,
1221  L"cmd.exe /c c:\\path\\to\\node.exe --eval "
1222  L"\"require('c:\\\\path\\\\to\\\\test.js')\"") == 0);
1223  ASSERT(wcscmp(non_verbatim_output,
1224  L"cmd.exe /c \"c:\\path\\to\\node.exe --eval "
1225  L"\\\"require('c:\\\\path\\\\to\\\\test.js')\\\"\"") == 0);
1226 
1227  free(verbatim_output);
1228  free(non_verbatim_output);
1229 
1230  return 0;
1231 }
1232 
1233 int make_program_env(char** env_block, WCHAR** dst_ptr);
1234 
1235 TEST_IMPL(environment_creation) {
1236  size_t i;
1237  char* environment[] = {
1238  "FOO=BAR",
1239  "SYSTEM=ROOT", /* substring of a supplied var name */
1240  "SYSTEMROOTED=OMG", /* supplied var name is a substring */
1241  "TEMP=C:\\Temp",
1242  "INVALID",
1243  "BAZ=QUX",
1244  "B_Z=QUX",
1245  "B\xe2\x82\xacZ=QUX",
1246  "B\xf0\x90\x80\x82Z=QUX",
1247  "B\xef\xbd\xa1Z=QUX",
1248  "B\xf0\xa3\x91\x96Z=QUX",
1249  "BAZ", /* repeat, invalid variable */
1250  NULL
1251  };
1252  WCHAR* wenvironment[] = {
1253  L"BAZ=QUX",
1254  L"B_Z=QUX",
1255  L"B\x20acZ=QUX",
1256  L"B\xd800\xdc02Z=QUX",
1257  L"B\xd84d\xdc56Z=QUX",
1258  L"B\xff61Z=QUX",
1259  L"FOO=BAR",
1260  L"SYSTEM=ROOT", /* substring of a supplied var name */
1261  L"SYSTEMROOTED=OMG", /* supplied var name is a substring */
1262  L"TEMP=C:\\Temp",
1263  };
1264  WCHAR* from_env[] = {
1265  /* list should be kept in sync with list
1266  * in process.c, minus variables in wenvironment */
1267  L"HOMEDRIVE",
1268  L"HOMEPATH",
1269  L"LOGONSERVER",
1270  L"PATH",
1271  L"USERDOMAIN",
1272  L"USERNAME",
1273  L"USERPROFILE",
1274  L"SYSTEMDRIVE",
1275  L"SYSTEMROOT",
1276  L"WINDIR",
1277  /* test for behavior in the absence of a
1278  * required-environment variable: */
1279  L"ZTHIS_ENV_VARIABLE_DOES_NOT_EXIST",
1280  };
1281  int found_in_loc_env[ARRAY_SIZE(wenvironment)] = {0};
1282  int found_in_usr_env[ARRAY_SIZE(from_env)] = {0};
1283  WCHAR *expected[ARRAY_SIZE(from_env)];
1284  int result;
1285  WCHAR* str;
1286  WCHAR* prev;
1287  WCHAR* env;
1288 
1289  for (i = 0; i < ARRAY_SIZE(from_env); i++) {
1290  /* copy expected additions to environment locally */
1291  size_t len = GetEnvironmentVariableW(from_env[i], NULL, 0);
1292  if (len == 0) {
1293  found_in_usr_env[i] = 1;
1294  str = malloc(1 * sizeof(WCHAR));
1295  *str = 0;
1296  expected[i] = str;
1297  } else {
1298  size_t name_len = wcslen(from_env[i]);
1299  str = malloc((name_len+1+len) * sizeof(WCHAR));
1300  wmemcpy(str, from_env[i], name_len);
1301  expected[i] = str;
1302  str += name_len;
1303  *str++ = L'=';
1304  GetEnvironmentVariableW(from_env[i], str, len);
1305  }
1306  }
1307 
1308  result = make_program_env(environment, &env);
1309  ASSERT(result == 0);
1310 
1311  for (str = env, prev = NULL; *str; prev = str, str += wcslen(str) + 1) {
1312  int found = 0;
1313 #if 0
1314  _cputws(str);
1315  putchar('\n');
1316 #endif
1317  for (i = 0; i < ARRAY_SIZE(wenvironment) && !found; i++) {
1318  if (!wcscmp(str, wenvironment[i])) {
1319  ASSERT(!found_in_loc_env[i]);
1320  found_in_loc_env[i] = 1;
1321  found = 1;
1322  }
1323  }
1324  for (i = 0; i < ARRAY_SIZE(expected) && !found; i++) {
1325  if (!wcscmp(str, expected[i])) {
1326  ASSERT(!found_in_usr_env[i]);
1327  found_in_usr_env[i] = 1;
1328  found = 1;
1329  }
1330  }
1331  if (prev) { /* verify sort order -- requires Vista */
1332 #if _WIN32_WINNT >= 0x0600 && \
1333  (!defined(__MINGW32__) || defined(__MINGW64_VERSION_MAJOR))
1334  ASSERT(CompareStringOrdinal(prev, -1, str, -1, TRUE) == 1);
1335 #endif
1336  }
1337  ASSERT(found); /* verify that we expected this variable */
1338  }
1339 
1340  /* verify that we found all expected variables */
1341  for (i = 0; i < ARRAY_SIZE(wenvironment); i++) {
1342  ASSERT(found_in_loc_env[i]);
1343  }
1344  for (i = 0; i < ARRAY_SIZE(expected); i++) {
1345  ASSERT(found_in_usr_env[i]);
1346  }
1347 
1348  return 0;
1349 }
1350 #endif
1351 
1352 /* Regression test for issue #909 */
1353 TEST_IMPL(spawn_with_an_odd_path) {
1354  int r;
1355 
1356  char newpath[2048];
1357  char *path = getenv("PATH");
1358  ASSERT(path != NULL);
1359  snprintf(newpath, 2048, ";.;%s", path);
1360  SetEnvironmentVariable("PATH", newpath);
1361 
1363  options.file = options.args[0] = "program-that-had-better-not-exist";
1365  ASSERT(r == UV_ENOENT || r == UV_EACCES);
1367  uv_close((uv_handle_t*) &process, NULL);
1369 
1371  return 0;
1372 }
1373 #endif
1374 
1375 #ifndef _WIN32
1376 TEST_IMPL(spawn_setuid_setgid) {
1377  int r;
1378  struct passwd* pw;
1379  char uidstr[10];
1380  char gidstr[10];
1381 
1382  /* if not root, then this will fail. */
1383  uv_uid_t uid = getuid();
1384  if (uid != 0) {
1385  RETURN_SKIP("It should be run as root user");
1386  }
1387 
1388  init_process_options("spawn_helper_setuid_setgid", exit_cb);
1389 
1390  /* become the "nobody" user. */
1391  pw = getpwnam("nobody");
1392  ASSERT(pw != NULL);
1393  options.uid = pw->pw_uid;
1394  options.gid = pw->pw_gid;
1395  snprintf(uidstr, sizeof(uidstr), "%d", pw->pw_uid);
1396  snprintf(gidstr, sizeof(gidstr), "%d", pw->pw_gid);
1397  options.args[2] = uidstr;
1398  options.args[3] = gidstr;
1400 
1402  if (r == UV_EACCES)
1403  RETURN_SKIP("user 'nobody' cannot access the test runner");
1404 
1405  ASSERT(r == 0);
1406 
1408  ASSERT(r == 0);
1409 
1410  ASSERT(exit_cb_called == 1);
1411  ASSERT(close_cb_called == 1);
1412 
1414  return 0;
1415 }
1416 #endif
1417 
1418 
1419 #ifndef _WIN32
1420 TEST_IMPL(spawn_setuid_fails) {
1421  int r;
1422 
1423  /* if root, become nobody. */
1424  /* On IBMi PASE, there is no nobody user. */
1425 #ifndef __PASE__
1426  uv_uid_t uid = getuid();
1427  if (uid == 0) {
1428  struct passwd* pw;
1429  pw = getpwnam("nobody");
1430  ASSERT(pw != NULL);
1431  ASSERT(0 == setgid(pw->pw_gid));
1432  ASSERT(0 == setuid(pw->pw_uid));
1433  }
1434 #endif /* !__PASE__ */
1435 
1436  init_process_options("spawn_helper1", fail_cb);
1437 
1439  /* On IBMi PASE, there is no root user. User may grant
1440  * root-like privileges, including setting uid to 0.
1441  */
1442 #if defined(__PASE__)
1443  options.uid = -1;
1444 #else
1445  options.uid = 0;
1446 #endif
1447 
1448  /* These flags should be ignored on Unices. */
1453 
1455 #if defined(__CYGWIN__) || defined(__PASE__)
1456  ASSERT(r == UV_EINVAL);
1457 #else
1458  ASSERT(r == UV_EPERM);
1459 #endif
1460 
1462  ASSERT(r == 0);
1463 
1464  ASSERT(close_cb_called == 0);
1465 
1467  return 0;
1468 }
1469 
1470 
1471 TEST_IMPL(spawn_setgid_fails) {
1472  int r;
1473 
1474  /* if root, become nobody. */
1475  /* On IBMi PASE, there is no nobody user. */
1476 #ifndef __PASE__
1477  uv_uid_t uid = getuid();
1478  if (uid == 0) {
1479  struct passwd* pw;
1480  pw = getpwnam("nobody");
1481  ASSERT(pw != NULL);
1482  ASSERT(0 == setgid(pw->pw_gid));
1483  ASSERT(0 == setuid(pw->pw_uid));
1484  }
1485 #endif /* !__PASE__ */
1486 
1487  init_process_options("spawn_helper1", fail_cb);
1488 
1490  /* On IBMi PASE, there is no root user. User may grant
1491  * root-like privileges, including setting gid to 0.
1492  */
1493 #if defined(__MVS__) || defined(__PASE__)
1494  options.gid = -1;
1495 #else
1496  options.gid = 0;
1497 #endif
1498 
1500 #if defined(__CYGWIN__) || defined(__MVS__) || defined(__PASE__)
1501  ASSERT(r == UV_EINVAL);
1502 #else
1503  ASSERT(r == UV_EPERM);
1504 #endif
1505 
1507  ASSERT(r == 0);
1508 
1509  ASSERT(close_cb_called == 0);
1510 
1512  return 0;
1513 }
1514 #endif
1515 
1516 
1517 #ifdef _WIN32
1518 
1519 static void exit_cb_unexpected(uv_process_t* process,
1520  int64_t exit_status,
1521  int term_signal) {
1522  ASSERT(0 && "should not have been called");
1523 }
1524 
1525 
1526 TEST_IMPL(spawn_setuid_fails) {
1527  int r;
1528 
1529  init_process_options("spawn_helper1", exit_cb_unexpected);
1530 
1532  options.uid = (uv_uid_t) -42424242;
1533 
1535  ASSERT(r == UV_ENOTSUP);
1536 
1538  ASSERT(r == 0);
1539 
1540  ASSERT(close_cb_called == 0);
1541 
1543  return 0;
1544 }
1545 
1546 
1547 TEST_IMPL(spawn_setgid_fails) {
1548  int r;
1549 
1550  init_process_options("spawn_helper1", exit_cb_unexpected);
1551 
1553  options.gid = (uv_gid_t) -42424242;
1554 
1556  ASSERT(r == UV_ENOTSUP);
1557 
1559  ASSERT(r == 0);
1560 
1561  ASSERT(close_cb_called == 0);
1562 
1564  return 0;
1565 }
1566 #endif
1567 
1568 
1569 TEST_IMPL(spawn_auto_unref) {
1570  init_process_options("spawn_helper1", NULL);
1574  uv_close((uv_handle_t*) &process, NULL);
1578  return 0;
1579 }
1580 
1581 
1582 #ifndef _WIN32
1583 TEST_IMPL(spawn_fs_open) {
1584  int fd;
1585  uv_fs_t fs_req;
1586  uv_pipe_t in;
1588  uv_buf_t buf;
1589  uv_stdio_container_t stdio[1];
1590 
1591  fd = uv_fs_open(NULL, &fs_req, "/dev/null", O_RDWR, 0, NULL);
1592  ASSERT(fd >= 0);
1594 
1595  init_process_options("spawn_helper8", exit_cb);
1596 
1597  ASSERT(0 == uv_pipe_init(uv_default_loop(), &in, 0));
1598 
1599  options.stdio = stdio;
1602  options.stdio_count = 1;
1603 
1605 
1606  buf = uv_buf_init((char*) &fd, sizeof(fd));
1607  ASSERT(0 == uv_write(&write_req, (uv_stream_t*) &in, &buf, 1, write_cb));
1608 
1610  ASSERT(0 == uv_fs_close(NULL, &fs_req, fd, NULL));
1611 
1612  ASSERT(exit_cb_called == 1);
1613  ASSERT(close_cb_called == 2); /* One for `in`, one for process */
1614 
1616  return 0;
1617 }
1618 #endif /* !_WIN32 */
1619 
1620 
1621 #ifndef _WIN32
1622 TEST_IMPL(closed_fd_events) {
1623  uv_stdio_container_t stdio[3];
1625  int fd[2];
1626 
1627  /* create a pipe and share it with a child process */
1628  ASSERT(0 == pipe(fd));
1629 
1630  /* spawn_helper4 blocks indefinitely. */
1631  init_process_options("spawn_helper4", exit_cb);
1632  options.stdio_count = 3;
1633  options.stdio = stdio;
1635  options.stdio[0].data.fd = fd[0];
1638 
1641 
1642  /* read from the pipe with uv */
1644  ASSERT(0 == uv_pipe_open(&pipe_handle, fd[0]));
1645  fd[0] = -1;
1646 
1648 
1649  ASSERT(1 == write(fd[1], "", 1));
1650 
1652 
1653  /* should have received just one byte */
1654  ASSERT(output_used == 1);
1655 
1656  /* close the pipe and see if we still get events */
1658 
1659  ASSERT(1 == write(fd[1], "", 1));
1660 
1662  ASSERT(0 == uv_timer_start(&timer, timer_counter_cb, 10, 0));
1663 
1664  /* see if any spurious events interrupt the timer */
1665  if (1 == uv_run(uv_default_loop(), UV_RUN_ONCE))
1666  /* have to run again to really trigger the timer */
1668 
1669  ASSERT(timer_counter == 1);
1670 
1671  /* cleanup */
1672  ASSERT(0 == uv_process_kill(&process, /* SIGTERM */ 15));
1673  ASSERT(0 == close(fd[1]));
1674 
1676  return 0;
1677 }
1678 #endif /* !_WIN32 */
1679 
1680 TEST_IMPL(spawn_reads_child_path) {
1681  int r;
1682  int len;
1683  char file[64];
1684  char path[1024];
1685  char* env[3];
1686 
1687  /* Need to carry over the dynamic linker path when the test runner is
1688  * linked against libuv.so, see https://github.com/libuv/libuv/issues/85.
1689  */
1690 #if defined(__APPLE__)
1691  static const char dyld_path_var[] = "DYLD_LIBRARY_PATH";
1692 #elif defined __MVS__
1693  static const char dyld_path_var[] = "LIBPATH";
1694 #else
1695  static const char dyld_path_var[] = "LD_LIBRARY_PATH";
1696 #endif
1697 
1698  /* Set up the process, but make sure that the file to run is relative and
1699  * requires a lookup into PATH. */
1700  init_process_options("spawn_helper1", exit_cb);
1701 
1702  /* Set up the PATH env variable */
1703  for (len = strlen(exepath);
1704  exepath[len - 1] != '/' && exepath[len - 1] != '\\';
1705  len--);
1706  strcpy(file, exepath + len);
1707  exepath[len] = 0;
1708  strcpy(path, "PATH=");
1709  strcpy(path + 5, exepath);
1710 #if defined(__CYGWIN__) || defined(__MSYS__)
1711  /* Carry over the dynamic linker path in case the test runner
1712  is linked against cyguv-1.dll or msys-uv-1.dll, see above. */
1713  {
1714  char* syspath = getenv("PATH");
1715  if (syspath != NULL) {
1716  strcat(path, ":");
1717  strcat(path, syspath);
1718  }
1719  }
1720 #endif
1721 
1722  env[0] = path;
1723  env[1] = getenv(dyld_path_var);
1724  env[2] = NULL;
1725 
1726  if (env[1] != NULL) {
1727  static char buf[1024 + sizeof(dyld_path_var)];
1728  snprintf(buf, sizeof(buf), "%s=%s", dyld_path_var, env[1]);
1729  env[1] = buf;
1730  }
1731 
1732  options.file = file;
1733  options.args[0] = file;
1734  options.env = env;
1735 
1737  ASSERT(r == 0);
1738 
1740  ASSERT(r == 0);
1741 
1742  ASSERT(exit_cb_called == 1);
1743  ASSERT(close_cb_called == 1);
1744 
1746  return 0;
1747 }
1748 
1749 #ifndef _WIN32
1750 static int mpipe(int *fds) {
1751  if (pipe(fds) == -1)
1752  return -1;
1753  if (fcntl(fds[0], F_SETFD, FD_CLOEXEC) == -1 ||
1754  fcntl(fds[1], F_SETFD, FD_CLOEXEC) == -1) {
1755  close(fds[0]);
1756  close(fds[1]);
1757  return -1;
1758  }
1759  return 0;
1760 }
1761 #else
1762 static int mpipe(int *fds) {
1763  SECURITY_ATTRIBUTES attr;
1764  HANDLE readh, writeh;
1765  attr.nLength = sizeof(attr);
1766  attr.lpSecurityDescriptor = NULL;
1767  attr.bInheritHandle = FALSE;
1768  if (!CreatePipe(&readh, &writeh, &attr, 0))
1769  return -1;
1770  fds[0] = _open_osfhandle((intptr_t)readh, 0);
1771  fds[1] = _open_osfhandle((intptr_t)writeh, 0);
1772  if (fds[0] == -1 || fds[1] == -1) {
1773  CloseHandle(readh);
1774  CloseHandle(writeh);
1775  return -1;
1776  }
1777  return 0;
1778 }
1779 #endif /* !_WIN32 */
1780 
1781 TEST_IMPL(spawn_inherit_streams) {
1783  uv_stdio_container_t child_stdio[2];
1784  int fds_stdin[2];
1785  int fds_stdout[2];
1786  uv_pipe_t pipe_stdin_child;
1787  uv_pipe_t pipe_stdout_child;
1788  uv_pipe_t pipe_stdin_parent;
1789  uv_pipe_t pipe_stdout_parent;
1790  unsigned char ubuf[OUTPUT_SIZE - 1];
1791  uv_buf_t buf;
1792  unsigned int i;
1793  int r;
1794  int bidir;
1796  uv_loop_t* loop;
1797 
1798  init_process_options("spawn_helper9", exit_cb);
1799 
1800  loop = uv_default_loop();
1801  ASSERT(uv_pipe_init(loop, &pipe_stdin_child, 0) == 0);
1802  ASSERT(uv_pipe_init(loop, &pipe_stdout_child, 0) == 0);
1803  ASSERT(uv_pipe_init(loop, &pipe_stdin_parent, 0) == 0);
1804  ASSERT(uv_pipe_init(loop, &pipe_stdout_parent, 0) == 0);
1805 
1806  ASSERT(mpipe(fds_stdin) != -1);
1807  ASSERT(mpipe(fds_stdout) != -1);
1808 
1809  ASSERT(uv_pipe_open(&pipe_stdin_child, fds_stdin[0]) == 0);
1810  ASSERT(uv_pipe_open(&pipe_stdout_child, fds_stdout[1]) == 0);
1811  ASSERT(uv_pipe_open(&pipe_stdin_parent, fds_stdin[1]) == 0);
1812  ASSERT(uv_pipe_open(&pipe_stdout_parent, fds_stdout[0]) == 0);
1813  ASSERT(uv_is_readable((uv_stream_t*) &pipe_stdin_child));
1814  ASSERT(uv_is_writable((uv_stream_t*) &pipe_stdout_child));
1815  ASSERT(uv_is_writable((uv_stream_t*) &pipe_stdin_parent));
1816  ASSERT(uv_is_readable((uv_stream_t*) &pipe_stdout_parent));
1817  /* Some systems (SVR4) open a bidirectional pipe, most don't. */
1818  bidir = uv_is_writable((uv_stream_t*) &pipe_stdin_child);
1819  ASSERT(uv_is_readable((uv_stream_t*) &pipe_stdout_child) == bidir);
1820  ASSERT(uv_is_readable((uv_stream_t*) &pipe_stdin_parent) == bidir);
1821  ASSERT(uv_is_writable((uv_stream_t*) &pipe_stdout_parent) == bidir);
1822 
1823  child_stdio[0].flags = UV_INHERIT_STREAM;
1824  child_stdio[0].data.stream = (uv_stream_t *)&pipe_stdin_child;
1825 
1826  child_stdio[1].flags = UV_INHERIT_STREAM;
1827  child_stdio[1].data.stream = (uv_stream_t *)&pipe_stdout_child;
1828 
1829  options.stdio = child_stdio;
1830  options.stdio_count = 2;
1831 
1832  ASSERT(uv_spawn(loop, &child_req, &options) == 0);
1833 
1834  uv_close((uv_handle_t*)&pipe_stdin_child, NULL);
1835  uv_close((uv_handle_t*)&pipe_stdout_child, NULL);
1836 
1837  buf = uv_buf_init((char*)ubuf, sizeof ubuf);
1838  for (i = 0; i < sizeof ubuf; ++i)
1839  ubuf[i] = i & 255u;
1840  memset(output, 0, sizeof ubuf);
1841 
1842  r = uv_write(&write_req,
1843  (uv_stream_t*)&pipe_stdin_parent,
1844  &buf,
1845  1,
1846  write_cb);
1847  ASSERT(r == 0);
1848 
1849  r = uv_read_start((uv_stream_t*)&pipe_stdout_parent, on_alloc, on_read);
1850  ASSERT(r == 0);
1851 
1853  ASSERT(r == 0);
1854 
1855  ASSERT(exit_cb_called == 1);
1856  ASSERT(close_cb_called == 3);
1857 
1858  r = memcmp(ubuf, output, sizeof ubuf);
1859  ASSERT(r == 0);
1860 
1862  return 0;
1863 }
1864 
1865 TEST_IMPL(spawn_quoted_path) {
1866 #ifndef _WIN32
1867  RETURN_SKIP("Test for Windows");
1868 #else
1869  char* quoted_path_env[2];
1870  args[0] = "not_existing";
1871  args[1] = NULL;
1872  options.file = args[0];
1873  options.args = args;
1875  options.flags = 0;
1876  /* We test if search_path works correctly with semicolons in quoted path. We
1877  * will use an invalid drive, so we are sure no executable is spawned. */
1878  quoted_path_env[0] = "PATH=\"xyz:\\test;\";xyz:\\other";
1879  quoted_path_env[1] = NULL;
1880  options.env = quoted_path_env;
1881 
1882  /* We test if libuv will not segfault. */
1884 
1886  return 0;
1887 #endif
1888 }
1889 
1890 /* Helper for child process of spawn_inherit_streams */
1891 #ifndef _WIN32
1893  char buf[1024];
1894  char* pbuf;
1895  for (;;) {
1896  ssize_t r, w, c;
1897  do {
1898  r = read(0, buf, sizeof buf);
1899  } while (r == -1 && errno == EINTR);
1900  if (r == 0) {
1901  return;
1902  }
1903  ASSERT(r > 0);
1904  c = r;
1905  pbuf = buf;
1906  while (c) {
1907  do {
1908  w = write(1, pbuf, (size_t)c);
1909  } while (w == -1 && errno == EINTR);
1910  ASSERT(w >= 0);
1911  pbuf = pbuf + w;
1912  c = c - w;
1913  }
1914  }
1915 }
1916 #else
1917 void spawn_stdin_stdout(void) {
1918  char buf[1024];
1919  char* pbuf;
1920  HANDLE h_stdin = GetStdHandle(STD_INPUT_HANDLE);
1921  HANDLE h_stdout = GetStdHandle(STD_OUTPUT_HANDLE);
1922  ASSERT(h_stdin != INVALID_HANDLE_VALUE);
1923  ASSERT(h_stdout != INVALID_HANDLE_VALUE);
1924  for (;;) {
1925  DWORD n_read;
1926  DWORD n_written;
1927  DWORD to_write;
1928  if (!ReadFile(h_stdin, buf, sizeof buf, &n_read, NULL)) {
1929  ASSERT(GetLastError() == ERROR_BROKEN_PIPE);
1930  return;
1931  }
1932  to_write = n_read;
1933  pbuf = buf;
1934  while (to_write) {
1935  ASSERT(WriteFile(h_stdout, pbuf, to_write, &n_written, NULL));
1936  to_write -= n_written;
1937  pbuf += n_written;
1938  }
1939  }
1940 }
1941 #endif /* !_WIN32 */
xds_interop_client.str
str
Definition: xds_interop_client.py:487
timer_cb
static void timer_cb(uv_timer_t *handle)
Definition: test-spawn.c:174
TRUE
const BOOL TRUE
Definition: undname.c:48
async_greeter_server_with_graceful_shutdown.loop
loop
Definition: async_greeter_server_with_graceful_shutdown.py:59
output
static char output[OUTPUT_SIZE]
Definition: test-spawn.c:58
_gevent_test_main.result
result
Definition: _gevent_test_main.py:96
timer_counter_cb
static void timer_counter_cb(uv_timer_t *handle)
Definition: test-spawn.c:181
uv_process_options_s
Definition: uv.h:940
make_program_args
int make_program_args(char **args, int verbatim_arguments, WCHAR **dst_ptr)
Definition: win/process.c:526
gen_build_yaml.out
dictionary out
Definition: src/benchmark/gen_build_yaml.py:24
uv_process_s
Definition: uv.h:1037
ARRAY_SIZE
#define ARRAY_SIZE(array)
Definition: bloaty.cc:101
uv_fs_open
UV_EXTERN int uv_fs_open(uv_loop_t *loop, uv_fs_t *req, const char *path, int flags, int mode, uv_fs_cb cb)
Definition: unix/fs.c:1812
on_read_once
static void on_read_once(uv_stream_t *tcp, ssize_t nread, const uv_buf_t *buf)
Definition: test-spawn.c:144
task.h
on_alloc
static void on_alloc(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf)
Definition: test-spawn.c:125
args
static char * args[5]
Definition: test-spawn.c:50
generate.env
env
Definition: generate.py:37
uv_process_options_s::gid
uv_gid_t gid
Definition: uv.h:982
memset
return memset(p, 0, total)
file
const grpc_generator::File * file
Definition: python_private_generator.h:38
UV_READABLE_PIPE
@ UV_READABLE_PIPE
Definition: uv.h:921
close_cb
static void close_cb(uv_handle_t *handle)
Definition: test-spawn.c:62
uv_pipe_init
UV_EXTERN int uv_pipe_init(uv_loop_t *, uv_pipe_t *handle, int ipc)
Definition: unix/pipe.c:33
UV_PROCESS_SETUID
@ UV_PROCESS_SETUID
Definition: uv.h:994
kill_cb
static void kill_cb(uv_process_t *process, int64_t exit_status, int term_signal)
Definition: test-spawn.c:85
write
#define write
Definition: test-fs.c:47
cpp.utils.ReadFile
def ReadFile(filename, print_error=True)
Definition: bloaty/third_party/googletest/googlemock/scripts/generator/cpp/utils.py:30
uv_kill
UV_EXTERN int uv_kill(int pid, int signum)
Definition: unix/process.c:582
UV_IGNORE
@ UV_IGNORE
Definition: uv.h:911
test
Definition: spinlock_test.cc:36
string.h
buf
voidpf void * buf
Definition: bloaty/third_party/zlib/contrib/minizip/ioapi.h:136
output_used
static int output_used
Definition: test-spawn.c:59
uv_listen
UV_EXTERN int uv_listen(uv_stream_t *stream, int backlog, uv_connection_cb cb)
Definition: unix/stream.c:656
printf
_Use_decl_annotations_ int __cdecl printf(const char *_Format,...)
Definition: cs_driver.c:91
uv_connect_s::handle
uv_stream_t * handle
Definition: uv.h:583
uv_process_options_s::exit_cb
uv_exit_cb exit_cb
Definition: uv.h:941
error_ref_leak.err
err
Definition: error_ref_leak.py:35
exepath
static char exepath[1024]
Definition: test-spawn.c:48
ASSERT
#define ASSERT(expr)
Definition: task.h:102
init_process_options
static void init_process_options(char *test, uv_exit_cb exit_cb)
Definition: test-spawn.c:157
tcp
static uv_tcp_t tcp
Definition: test-connection-fail.c:29
file
Definition: bloaty/third_party/zlib/examples/gzappend.c:170
u
OPENSSL_EXPORT pem_password_cb void * u
Definition: pem.h:351
no_term_signal
static int no_term_signal
Definition: test-spawn.c:51
uv_process_options_s::uid
uv_uid_t uid
Definition: uv.h:981
status
absl::Status status
Definition: rls.cc:251
uv_uid_t
uid_t uv_uid_t
Definition: unix.h:167
setup.name
name
Definition: setup.py:542
exit_cb
static void exit_cb(uv_process_t *process, int64_t exit_status, int term_signal)
Definition: test-spawn.c:67
write_req
Definition: benchmark-tcp-write-batch.c:31
check_documentation.path
path
Definition: check_documentation.py:57
uv_run
UV_EXTERN int uv_run(uv_loop_t *, uv_run_mode mode)
Definition: unix/core.c:361
uv_fs_s
Definition: uv.h:1294
uv_is_closing
UV_EXTERN int uv_is_closing(const uv_handle_t *handle)
Definition: unix/core.c:319
process
static uv_process_t process
Definition: test-spawn.c:45
message
char * message
Definition: libuv/docs/code/tty-gravity/main.c:12
uv_unref
UV_EXTERN void uv_unref(uv_handle_t *)
Definition: uv-common.c:522
uv_tcp_bind
UV_EXTERN int uv_tcp_bind(uv_tcp_t *handle, const struct sockaddr *addr, unsigned int flags)
Definition: uv-common.c:277
TEST_PORT
#define TEST_PORT
Definition: task.h:53
uv_close
UV_EXTERN void uv_close(uv_handle_t *handle, uv_close_cb close_cb)
Definition: unix/core.c:112
uv_stream_s
Definition: uv.h:491
uv_os_sock_t
int uv_os_sock_t
Definition: unix.h:127
mpipe
static int mpipe(int *fds)
Definition: test-spawn.c:1750
close_cb_called
static int close_cb_called
Definition: test-spawn.c:43
uv_tcp_open
UV_EXTERN int uv_tcp_open(uv_tcp_t *handle, uv_os_sock_t sock)
Definition: unix/tcp.c:267
uv_ip4_addr
UV_EXTERN int uv_ip4_addr(const char *ip, int port, struct sockaddr_in *addr)
Definition: uv-common.c:221
uv_process_options_s::file
const char * file
Definition: uv.h:942
child_req
uv_process_t child_req
Definition: libuv/docs/code/cgi/main.c:8
UV_WRITABLE_PIPE
@ UV_WRITABLE_PIPE
Definition: uv.h:922
in
const char * in
Definition: third_party/abseil-cpp/absl/strings/internal/str_format/parser_test.cc:391
uv_default_loop
UV_EXTERN uv_loop_t * uv_default_loop(void)
Definition: uv-common.c:733
c
void c(T a)
Definition: miscompile_with_no_unique_address_test.cc:40
ssize_t
intptr_t ssize_t
Definition: win.h:27
uv_stdio_container_s::flags
uv_stdio_flags flags
Definition: uv.h:932
uv_process_options_s::stdio
uv_stdio_container_t * stdio
Definition: uv.h:975
gen_stats_data.found
bool found
Definition: gen_stats_data.py:61
timer
static uv_timer_t timer
Definition: test-spawn.c:46
int64_t
signed __int64 int64_t
Definition: stdint-msvc2008.h:89
uv_process_options_s::args
char ** args
Definition: uv.h:949
SIGKILL
#define SIGKILL
Definition: win.h:87
signal
static void signal(notification *n)
Definition: alts_tsi_handshaker_test.cc:107
uv_write
UV_EXTERN int uv_write(uv_write_t *req, uv_stream_t *handle, const uv_buf_t bufs[], unsigned int nbufs, uv_write_cb cb)
Definition: unix/stream.c:1492
uv_is_readable
UV_EXTERN int uv_is_readable(const uv_stream_t *handle)
Definition: unix/stream.c:1606
fs_req
Definition: test-thread.c:41
req
static uv_connect_t req
Definition: test-connection-fail.c:30
UV_RUN_DEFAULT
@ UV_RUN_DEFAULT
Definition: uv.h:254
OUTPUT_SIZE
#define OUTPUT_SIZE
Definition: test-spawn.c:57
uv_process_options_s::stdio_count
int stdio_count
Definition: uv.h:974
uv_process_options_s::flags
unsigned int flags
Definition: uv.h:964
UV_INHERIT_FD
@ UV_INHERIT_FD
Definition: uv.h:913
UV_INHERIT_STREAM
@ UV_INHERIT_STREAM
Definition: uv.h:914
uv_is_active
UV_EXTERN int uv_is_active(const uv_handle_t *handle)
Definition: unix/core.c:418
uv_read_start
UV_EXTERN int uv_read_start(uv_stream_t *, uv_alloc_cb alloc_cb, uv_read_cb read_cb)
Definition: unix/stream.c:1555
close
#define close
Definition: test-fs.c:48
intptr_t
_W64 signed int intptr_t
Definition: stdint-msvc2008.h:118
uv_process_s::pid
int pid
Definition: uv.h:1040
uv_tcp_init
UV_EXTERN int uv_tcp_init(uv_loop_t *, uv_tcp_t *handle)
Definition: unix/tcp.c:143
uv_exit_cb
void(* uv_exit_cb)(uv_process_t *, int64_t exit_status, int term_signal)
Definition: uv.h:323
buffer
char buffer[1024]
Definition: libuv/docs/code/idle-compute/main.c:8
spawn_stdin_stdout
void spawn_stdin_stdout(void)
Definition: test-spawn.c:1892
uv_stdio_container_s
Definition: uv.h:931
uv_file
int uv_file
Definition: unix.h:126
options
static uv_process_options_t options
Definition: test-spawn.c:47
uv_timer_s
Definition: uv.h:850
uv_stdio_container_s::fd
int fd
Definition: uv.h:936
uv_fs_close
UV_EXTERN int uv_fs_close(uv_loop_t *loop, uv_fs_t *req, uv_file file, uv_fs_cb cb)
Definition: unix/fs.c:1651
uv_tcp_s
Definition: uv.h:544
uv_spawn
UV_EXTERN int uv_spawn(uv_loop_t *loop, uv_process_t *handle, const uv_process_options_t *options)
Definition: unix/process.c:408
uv_exepath
UV_EXTERN int uv_exepath(char *buffer, size_t *size)
Definition: aix-common.c:79
spawn_tcp_server_helper
int spawn_tcp_server_helper(void)
Definition: test-spawn.c:661
uv_process_options_s::env
char ** env
Definition: uv.h:954
TEST_IMPL
TEST_IMPL(spawn_fails)
Definition: test-spawn.c:187
uv_stdio_container_s::stream
uv_stream_t * stream
Definition: uv.h:935
uv_fs_read
UV_EXTERN int uv_fs_read(uv_loop_t *loop, uv_fs_t *req, uv_file file, const uv_buf_t bufs[], unsigned int nbufs, int64_t offset, uv_fs_cb cb)
Definition: unix/fs.c:1826
uv.h
exepath_size
static size_t exepath_size
Definition: test-spawn.c:49
MAKE_VALGRIND_HAPPY
#define MAKE_VALGRIND_HAPPY()
Definition: task.h:229
UV_PROCESS_WINDOWS_HIDE
@ UV_PROCESS_WINDOWS_HIDE
Definition: uv.h:1019
uv_tcp_init_ex
UV_EXTERN int uv_tcp_init_ex(uv_loop_t *, uv_tcp_t *handle, unsigned int flags)
Definition: unix/tcp.c:114
update_failure_list.test
test
Definition: bloaty/third_party/protobuf/conformance/update_failure_list.py:69
attr
OPENSSL_EXPORT X509_ATTRIBUTE * attr
Definition: x509.h:1666
uv_buf_t
Definition: unix.h:121
FALSE
const BOOL FALSE
Definition: undname.c:47
read
int read(izstream &zs, T *x, Items items)
Definition: bloaty/third_party/zlib/contrib/iostream2/zstream.h:115
uv_fileno
UV_EXTERN int uv_fileno(const uv_handle_t *handle, uv_os_fd_t *fd)
Definition: unix/core.c:755
UV_PROCESS_SETGID
@ UV_PROCESS_SETGID
Definition: uv.h:1000
tcp_server
static uv_tcp_t tcp_server
Definition: test-spawn.c:55
detach_failure_cb
static void detach_failure_cb(uv_process_t *process, int64_t exit_status, int term_signal)
Definition: test-spawn.c:118
uv_os_fd_t
int uv_os_fd_t
Definition: unix.h:128
count
int * count
Definition: bloaty/third_party/googletest/googlemock/test/gmock_stress_test.cc:96
UV_PROCESS_WINDOWS_HIDE_GUI
@ UV_PROCESS_WINDOWS_HIDE_GUI
Definition: uv.h:1031
L
lua_State * L
Definition: upb/upb/bindings/lua/main.c:35
fix_build_deps.r
r
Definition: fix_build_deps.py:491
pipe_handle
static uv_pipe_t pipe_handle
Definition: test-pipe-connect-prepare.c:39
env
Definition: env.py:1
exit_cb_called
static int exit_cb_called
Definition: test-spawn.c:44
cpp.gmock_class.set
set
Definition: bloaty/third_party/googletest/googlemock/scripts/generator/cpp/gmock_class.py:44
INVALID_HANDLE_VALUE
#define INVALID_HANDLE_VALUE
Definition: bloaty/third_party/zlib/contrib/minizip/iowin32.c:21
UV_PROCESS_DETACHED
@ UV_PROCESS_DETACHED
Definition: uv.h:1014
UV_RUN_ONCE
@ UV_RUN_ONCE
Definition: uv.h:255
uv_pipe_s
Definition: uv.h:757
uv_buf_init
UV_EXTERN uv_buf_t uv_buf_init(char *base, unsigned int len)
Definition: uv-common.c:157
UV_CREATE_PIPE
@ UV_CREATE_PIPE
Definition: uv.h:912
uv_write_s
Definition: uv.h:522
unlink
#define unlink
Definition: test-fs-copyfile.c:33
uv_fs_req_cleanup
UV_EXTERN void uv_fs_req_cleanup(uv_fs_t *req)
Definition: unix/fs.c:2024
RETURN_SKIP
#define RETURN_SKIP(explanation)
Definition: task.h:262
uv_stdio_container_s::data
union uv_stdio_container_s::@399 data
UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS
@ UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS
Definition: uv.h:1006
handle
static csh handle
Definition: test_arm_regression.c:16
write_cb
static void write_cb(uv_write_t *req, int status)
Definition: test-spawn.c:151
uv_handle_s
Definition: uv.h:441
uv_timer_start
UV_EXTERN int uv_timer_start(uv_timer_t *handle, uv_timer_cb cb, uint64_t timeout, uint64_t repeat)
Definition: timer.c:66
uv_loop_s
Definition: uv.h:1767
len
int len
Definition: abseil-cpp/absl/base/internal/low_level_alloc_test.cc:46
uv_timer_init
UV_EXTERN int uv_timer_init(uv_loop_t *, uv_timer_t *handle)
Definition: timer.c:58
write_req
uv_write_t write_req
Definition: libuv/docs/code/tty-gravity/main.c:9
uv_is_writable
UV_EXTERN int uv_is_writable(const uv_stream_t *handle)
Definition: unix/stream.c:1611
fail_cb
static void fail_cb(uv_process_t *process, int64_t exit_status, int term_signal)
Definition: test-spawn.c:78
UV_PROCESS_WINDOWS_HIDE_CONSOLE
@ UV_PROCESS_WINDOWS_HIDE_CONSOLE
Definition: uv.h:1025
uv_process_kill
UV_EXTERN int uv_process_kill(uv_process_t *, int signum)
Definition: unix/process.c:577
quote_cmd_arg
WCHAR * quote_cmd_arg(const WCHAR *source, WCHAR *target)
Definition: win/process.c:453
uv_read_stop
UV_EXTERN int uv_read_stop(uv_stream_t *)
Definition: unix/stream.c:1590
getenv
#define getenv(ptr)
Definition: ares_private.h:106
setup.target
target
Definition: third_party/bloaty/third_party/protobuf/python/setup.py:179
uv_pipe_open
UV_EXTERN int uv_pipe_open(uv_pipe_t *, uv_file file)
Definition: unix/pipe.c:137
addr
struct sockaddr_in addr
Definition: libuv/docs/code/tcp-echo-server/main.c:10
uv_gid_t
gid_t uv_gid_t
Definition: unix.h:166
timer_counter
static int timer_counter
Definition: test-spawn.c:53
make_program_env
int make_program_env(char *env_block[], WCHAR **dst_ptr)
Definition: win/process.c:682
errno.h
on_read
static void on_read(uv_stream_t *tcp, ssize_t nread, const uv_buf_t *buf)
Definition: test-spawn.c:133
uv_process_get_pid
UV_EXTERN uv_pid_t uv_process_get_pid(const uv_process_t *)
Definition: uv-data-getter-setters.c:68
i
uint64_t i
Definition: abseil-cpp/absl/container/btree_benchmark.cc:230
test_str
const char test_str[]
Definition: test_generated_code.cc:43


grpc
Author(s):
autogenerated on Thu Mar 13 2025 03:01:30