1 #include "php_swoole_coroutine_system.h"
2
3 #include "ext/standard/file.h"
4 #include <sys/file.h>
5
6 #include <string>
7
8 using swoole::TimerNode;
9 using swoole::Coroutine;
10 using swoole::PHPCoroutine;
11 using swoole::Reactor;
12 using swoole::Event;
13 using swoole::coroutine::Socket;
14 using swoole::coroutine::System;
15 using swoole::String;
16
17 static zend_class_entry *swoole_coroutine_system_ce;
18
19 // clang-format off
20
21 static const zend_function_entry swoole_coroutine_system_methods[] =
22 {
23 ZEND_FENTRY(gethostbyname, ZEND_FN(swoole_coroutine_gethostbyname), arginfo_swoole_coroutine_system_gethostbyname, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
24 ZEND_FENTRY(dnsLookup, ZEND_FN(swoole_async_dns_lookup_coro), arginfo_swoole_coroutine_system_dnsLookup, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
25 PHP_ME(swoole_coroutine_system, exec, arginfo_swoole_coroutine_system_exec, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
26 PHP_ME(swoole_coroutine_system, sleep, arginfo_swoole_coroutine_system_sleep, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
27 PHP_ME(swoole_coroutine_system, getaddrinfo, arginfo_swoole_coroutine_system_getaddrinfo, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
28 PHP_ME(swoole_coroutine_system, statvfs, arginfo_swoole_coroutine_system_statvfs, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
29 PHP_ME(swoole_coroutine_system, readFile, arginfo_swoole_coroutine_system_readFile, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
30 PHP_ME(swoole_coroutine_system, writeFile, arginfo_swoole_coroutine_system_writeFile, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
31 PHP_ME(swoole_coroutine_system, wait, arginfo_swoole_coroutine_system_wait, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
32 PHP_ME(swoole_coroutine_system, waitPid, arginfo_swoole_coroutine_system_waitPid, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
33 PHP_ME(swoole_coroutine_system, waitSignal, arginfo_swoole_coroutine_system_waitSignal, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
34 PHP_ME(swoole_coroutine_system, waitEvent, arginfo_swoole_coroutine_system_waitEvent, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
35 /* Deprecated file methods */
36 PHP_ME(swoole_coroutine_system, fread, arginfo_swoole_coroutine_system_fread, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC | ZEND_ACC_DEPRECATED)
37 PHP_ME(swoole_coroutine_system, fwrite, arginfo_swoole_coroutine_system_fwrite, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC | ZEND_ACC_DEPRECATED)
38 PHP_ME(swoole_coroutine_system, fgets, arginfo_swoole_coroutine_system_fgets, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC | ZEND_ACC_DEPRECATED)
39 PHP_FE_END
40 };
41
42 // clang-format on
43
php_swoole_coroutine_system_minit(int module_number)44 void php_swoole_coroutine_system_minit(int module_number) {
45 SW_INIT_CLASS_ENTRY_BASE(swoole_coroutine_system,
46 "Swoole\\Coroutine\\System",
47 nullptr,
48 "Co\\System",
49 swoole_coroutine_system_methods,
50 nullptr);
51 SW_SET_CLASS_CREATE(swoole_coroutine_system, sw_zend_create_object_deny);
52 }
53
PHP_METHOD(swoole_coroutine_system,sleep)54 PHP_METHOD(swoole_coroutine_system, sleep) {
55 double seconds;
56
57 ZEND_PARSE_PARAMETERS_START(1, 1)
58 Z_PARAM_DOUBLE(seconds)
59 ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
60
61 if (UNEXPECTED(seconds < SW_TIMER_MIN_SEC)) {
62 php_swoole_fatal_error(E_WARNING, "Timer must be greater than or equal to " ZEND_TOSTR(SW_TIMER_MIN_SEC));
63 RETURN_FALSE;
64 }
65 RETURN_BOOL(System::sleep(seconds) == 0);
66 }
67
co_socket_read(int fd,zend_long length,INTERNAL_FUNCTION_PARAMETERS)68 static void co_socket_read(int fd, zend_long length, INTERNAL_FUNCTION_PARAMETERS) {
69 php_swoole_check_reactor();
70 Socket _socket(fd, SW_SOCK_RAW);
71
72 zend_string *buf = zend_string_alloc(length + 1, 0);
73 size_t nbytes = length <= 0 ? SW_BUFFER_SIZE_STD : length;
74 ssize_t n = _socket.read(ZSTR_VAL(buf), nbytes);
75 if (n < 0) {
76 ZVAL_FALSE(return_value);
77 zend_string_free(buf);
78 } else if (n == 0) {
79 ZVAL_EMPTY_STRING(return_value);
80 zend_string_free(buf);
81 } else {
82 ZSTR_VAL(buf)[n] = 0;
83 ZSTR_LEN(buf) = n;
84 ZVAL_STR(return_value, buf);
85 }
86 _socket.move_fd();
87 }
88
co_socket_write(int fd,char * str,size_t l_str,INTERNAL_FUNCTION_PARAMETERS)89 static void co_socket_write(int fd, char *str, size_t l_str, INTERNAL_FUNCTION_PARAMETERS) {
90 php_swoole_check_reactor();
91 Socket _socket(fd, SW_SOCK_RAW);
92
93 ssize_t n = _socket.write(str, l_str);
94 if (n < 0) {
95 swoole_set_last_error(errno);
96 ZVAL_FALSE(return_value);
97 } else {
98 ZVAL_LONG(return_value, n);
99 }
100 _socket.move_fd();
101 }
102
PHP_METHOD(swoole_coroutine_system,fread)103 PHP_METHOD(swoole_coroutine_system, fread) {
104 Coroutine::get_current_safe();
105
106 zval *handle;
107 zend_long length = 0;
108
109 ZEND_PARSE_PARAMETERS_START(1, 2)
110 Z_PARAM_RESOURCE(handle)
111 Z_PARAM_OPTIONAL
112 Z_PARAM_LONG(length)
113 ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
114
115 int async;
116 int fd = php_swoole_convert_to_fd_ex(handle, &async);
117 if (fd < 0) {
118 RETURN_FALSE;
119 }
120
121 if (async) {
122 co_socket_read(fd, length, INTERNAL_FUNCTION_PARAM_PASSTHRU);
123 return;
124 }
125
126 if (length <= 0) {
127 struct stat file_stat;
128 if (swoole_coroutine_fstat(fd, &file_stat) < 0) {
129 swoole_set_last_error(errno);
130 RETURN_FALSE;
131 }
132 off_t _seek = swoole_coroutine_lseek(fd, 0, SEEK_CUR);
133 if (_seek < 0) {
134 swoole_set_last_error(errno);
135 RETURN_FALSE;
136 }
137 if (_seek >= file_stat.st_size) {
138 length = SW_BUFFER_SIZE_STD;
139 } else {
140 length = file_stat.st_size - _seek;
141 }
142 }
143
144 char *buf = (char *) emalloc(length + 1);
145 if (!buf) {
146 RETURN_FALSE;
147 }
148 buf[length] = 0;
149 int ret = -1;
150 swoole_trace("fd=%d, length=%ld", fd, length);
151 php_swoole_check_reactor();
152 bool async_success = swoole::coroutine::async([&]() {
153 while (1) {
154 ret = read(fd, buf, length);
155 if (ret < 0 && errno == EINTR) {
156 continue;
157 }
158 break;
159 }
160 });
161
162 if (async_success && ret >= 0) {
163 // TODO: Optimization: reduce memory copy
164 ZVAL_STRINGL(return_value, buf, ret);
165 } else {
166 ZVAL_FALSE(return_value);
167 }
168
169 efree(buf);
170 }
171
PHP_METHOD(swoole_coroutine_system,fgets)172 PHP_METHOD(swoole_coroutine_system, fgets) {
173 Coroutine::get_current_safe();
174
175 zval *handle;
176 php_stream *stream;
177
178 ZEND_PARSE_PARAMETERS_START(1, 1)
179 Z_PARAM_RESOURCE(handle)
180 ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
181
182 int async;
183 int fd = php_swoole_convert_to_fd_ex(handle, &async);
184 if (fd < 0) {
185 RETURN_FALSE;
186 }
187
188 if (async == 1) {
189 php_swoole_fatal_error(E_WARNING, "only support file resources");
190 RETURN_FALSE;
191 }
192
193 php_stream_from_res(stream, Z_RES_P(handle));
194
195 FILE *file;
196 if (stream->stdiocast) {
197 file = stream->stdiocast;
198 } else {
199 if (php_stream_cast(stream, PHP_STREAM_AS_STDIO, (void **) &file, 1) != SUCCESS || file == nullptr) {
200 RETURN_FALSE;
201 }
202 }
203
204 if (stream->readbuf == nullptr) {
205 stream->readbuflen = stream->chunk_size;
206 stream->readbuf = (uchar *) emalloc(stream->chunk_size);
207 }
208
209 if (!stream->readbuf) {
210 RETURN_FALSE;
211 }
212
213 int ret = 0;
214 swoole_trace("fd=%d, length=%ld", fd, stream->readbuflen);
215 php_swoole_check_reactor();
216 bool async_success = swoole::coroutine::async([&]() {
217 char *data = fgets((char *) stream->readbuf, stream->readbuflen, file);
218 if (data == nullptr) {
219 ret = -1;
220 stream->eof = 1;
221 }
222 });
223
224 if (async_success && ret != -1) {
225 ZVAL_STRING(return_value, (char *) stream->readbuf);
226 } else {
227 ZVAL_FALSE(return_value);
228 }
229 }
230
PHP_METHOD(swoole_coroutine_system,fwrite)231 PHP_METHOD(swoole_coroutine_system, fwrite) {
232 Coroutine::get_current_safe();
233
234 zval *handle;
235 char *str;
236 size_t l_str;
237 zend_long length = 0;
238
239 ZEND_PARSE_PARAMETERS_START(2, 3)
240 Z_PARAM_RESOURCE(handle)
241 Z_PARAM_STRING(str, l_str)
242 Z_PARAM_OPTIONAL
243 Z_PARAM_LONG(length)
244 ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
245
246 int async;
247 int fd = php_swoole_convert_to_fd_ex(handle, &async);
248 if (fd < 0) {
249 RETURN_FALSE;
250 }
251
252 if (async) {
253 co_socket_write(
254 fd, str, (length <= 0 || (size_t) length > l_str) ? l_str : length, INTERNAL_FUNCTION_PARAM_PASSTHRU);
255 return;
256 }
257
258 if (length <= 0 || (size_t) length > l_str) {
259 length = l_str;
260 }
261
262 char *buf = estrndup(str, length);
263
264 if (!buf) {
265 RETURN_FALSE;
266 }
267
268 int ret = -1;
269 swoole_trace("fd=%d, length=%ld", fd, length);
270 php_swoole_check_reactor();
271 bool async_success = swoole::coroutine::async([&]() {
272 while (1) {
273 ret = write(fd, buf, length);
274 if (ret < 0 && errno == EINTR) {
275 continue;
276 }
277 break;
278 }
279 });
280
281 if (async_success && ret >= 0) {
282 ZVAL_LONG(return_value, ret);
283 } else {
284 ZVAL_FALSE(return_value);
285 }
286
287 efree(buf);
288 }
289
PHP_METHOD(swoole_coroutine_system,readFile)290 PHP_METHOD(swoole_coroutine_system, readFile) {
291 char *filename;
292 size_t l_filename;
293 zend_long flags = 0;
294
295 ZEND_PARSE_PARAMETERS_START(1, 2)
296 Z_PARAM_STRING(filename, l_filename)
297 Z_PARAM_OPTIONAL
298 Z_PARAM_LONG(flags)
299 ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
300
301 auto result = System::read_file(filename, flags & LOCK_EX);
302 if (result == nullptr) {
303 RETURN_FALSE;
304 } else {
305 RETVAL_STRINGL(result->str, result->length);
306 }
307 }
308
PHP_METHOD(swoole_coroutine_system,writeFile)309 PHP_METHOD(swoole_coroutine_system, writeFile) {
310 char *filename;
311 size_t l_filename;
312 char *data;
313 size_t l_data;
314 zend_long flags = 0;
315
316 ZEND_PARSE_PARAMETERS_START(2, 3)
317 Z_PARAM_STRING(filename, l_filename)
318 Z_PARAM_STRING(data, l_data)
319 Z_PARAM_OPTIONAL
320 Z_PARAM_LONG(flags)
321 ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
322
323 int _flags = 0;
324 if (flags & PHP_FILE_APPEND) {
325 _flags |= O_APPEND;
326 } else {
327 _flags |= O_TRUNC;
328 }
329
330 ssize_t retval = System::write_file(filename, data, l_data, flags & LOCK_EX, _flags);
331 if (retval < 0) {
332 RETURN_FALSE;
333 } else {
334 RETURN_LONG(retval);
335 }
336 }
337
PHP_FUNCTION(swoole_coroutine_gethostbyname)338 PHP_FUNCTION(swoole_coroutine_gethostbyname) {
339 Coroutine::get_current_safe();
340
341 char *domain_name;
342 size_t l_domain_name;
343 zend_long family = AF_INET;
344 double timeout = -1;
345
346 if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|ld", &domain_name, &l_domain_name, &family, &timeout) == FAILURE) {
347 RETURN_FALSE;
348 }
349
350 if (l_domain_name == 0) {
351 php_swoole_fatal_error(E_WARNING, "domain name is empty");
352 RETURN_FALSE;
353 }
354
355 if (family != AF_INET && family != AF_INET6) {
356 php_swoole_fatal_error(E_WARNING, "unknown protocol family, must be AF_INET or AF_INET6");
357 RETURN_FALSE;
358 }
359
360 std::string address = System::gethostbyname(std::string(domain_name, l_domain_name), family, timeout);
361 if (address.empty()) {
362 RETURN_FALSE;
363 } else {
364 RETURN_STRINGL(address.c_str(), address.length());
365 }
366 }
367
PHP_FUNCTION(swoole_clear_dns_cache)368 PHP_FUNCTION(swoole_clear_dns_cache) {
369 System::clear_dns_cache();
370 }
371
PHP_METHOD(swoole_coroutine_system,getaddrinfo)372 PHP_METHOD(swoole_coroutine_system, getaddrinfo) {
373 char *hostname;
374 size_t l_hostname;
375 zend_long family = AF_INET;
376 zend_long socktype = SOCK_STREAM;
377 zend_long protocol = IPPROTO_TCP;
378 char *service = nullptr;
379 size_t l_service = 0;
380 double timeout = -1;
381
382 if (zend_parse_parameters(ZEND_NUM_ARGS(),
383 "s|lllsd",
384 &hostname,
385 &l_hostname,
386 &family,
387 &socktype,
388 &protocol,
389 &service,
390 &l_service,
391 &timeout) == FAILURE) {
392 RETURN_FALSE;
393 }
394
395 if (l_hostname == 0) {
396 php_swoole_fatal_error(E_WARNING, "hostname is empty");
397 RETURN_FALSE;
398 }
399
400 if (family != AF_INET && family != AF_INET6) {
401 php_swoole_fatal_error(E_WARNING, "unknown protocol family, must be AF_INET or AF_INET6");
402 RETURN_FALSE;
403 }
404
405 std::string str_service(service ? service : "");
406 std::vector<std::string> result = System::getaddrinfo(hostname, family, socktype, protocol, str_service, timeout);
407
408 if (result.empty()) {
409 RETURN_FALSE;
410 }
411
412 array_init(return_value);
413 for (auto i = result.begin(); i != result.end(); i++) {
414 add_next_index_stringl(return_value, i->c_str(), i->length());
415 }
416 }
417
PHP_METHOD(swoole_coroutine_system,statvfs)418 PHP_METHOD(swoole_coroutine_system, statvfs) {
419 char *path;
420 size_t l_path;
421
422 ZEND_PARSE_PARAMETERS_START(1, 1)
423 Z_PARAM_STRING(path, l_path)
424 ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
425
426 struct statvfs _stat;
427 swoole_coroutine_statvfs(path, &_stat);
428
429 array_init(return_value);
430 add_assoc_long(return_value, "bsize", _stat.f_bsize);
431 add_assoc_long(return_value, "frsize", _stat.f_frsize);
432 add_assoc_long(return_value, "blocks", _stat.f_blocks);
433 add_assoc_long(return_value, "bfree", _stat.f_bfree);
434 add_assoc_long(return_value, "bavail", _stat.f_bavail);
435 add_assoc_long(return_value, "files", _stat.f_files);
436 add_assoc_long(return_value, "ffree", _stat.f_ffree);
437 add_assoc_long(return_value, "favail", _stat.f_favail);
438 add_assoc_long(return_value, "fsid", _stat.f_fsid);
439 add_assoc_long(return_value, "flag", _stat.f_flag);
440 add_assoc_long(return_value, "namemax", _stat.f_namemax);
441 }
442
PHP_METHOD(swoole_coroutine_system,exec)443 PHP_METHOD(swoole_coroutine_system, exec) {
444 char *command;
445 size_t command_len;
446 zend_bool get_error_stream = 0;
447
448 ZEND_PARSE_PARAMETERS_START(1, 2)
449 Z_PARAM_STRING(command, command_len)
450 Z_PARAM_OPTIONAL
451 Z_PARAM_BOOL(get_error_stream)
452 ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
453
454 if (php_swoole_signal_isset_handler(SIGCHLD)) {
455 php_swoole_error(E_WARNING, "The signal [SIGCHLD] is registered, cannot execute swoole_coroutine_exec");
456 RETURN_FALSE;
457 }
458
459 Coroutine::get_current_safe();
460
461 pid_t pid;
462 int fd = swoole_shell_exec(command, &pid, get_error_stream);
463 if (fd < 0) {
464 php_swoole_error(E_WARNING, "Unable to execute '%s'", command);
465 RETURN_FALSE;
466 }
467
468 String *buffer = new String(1024);
469 Socket socket(fd, SW_SOCK_UNIX_STREAM);
470 while (1) {
471 ssize_t retval = socket.read(buffer->str + buffer->length, buffer->size - buffer->length);
472 if (retval > 0) {
473 buffer->length += retval;
474 if (buffer->length == buffer->size) {
475 if (!buffer->extend()) {
476 break;
477 }
478 }
479 } else {
480 break;
481 }
482 }
483 socket.close();
484
485 zval zdata;
486 if (buffer->length == 0) {
487 ZVAL_EMPTY_STRING(&zdata);
488 } else {
489 ZVAL_STRINGL(&zdata, buffer->str, buffer->length);
490 }
491 delete buffer;
492
493 int status;
494 pid_t _pid = swoole_coroutine_waitpid(pid, &status, 0);
495 if (_pid > 0) {
496 array_init(return_value);
497 add_assoc_long(return_value, "code", WEXITSTATUS(status));
498 add_assoc_long(return_value, "signal", WTERMSIG(status));
499 add_assoc_zval(return_value, "output", &zdata);
500 } else {
501 zval_ptr_dtor(&zdata);
502 RETVAL_FALSE;
503 }
504 }
505
swoole_coroutine_system_wait(INTERNAL_FUNCTION_PARAMETERS,pid_t pid,double timeout)506 static void swoole_coroutine_system_wait(INTERNAL_FUNCTION_PARAMETERS, pid_t pid, double timeout) {
507 int status;
508
509 Coroutine::get_current_safe();
510
511 if (pid < 0) {
512 pid = System::wait(&status, timeout);
513 } else {
514 pid = System::waitpid(pid, &status, 0, timeout);
515 }
516 if (pid > 0) {
517 array_init(return_value);
518 add_assoc_long(return_value, "pid", pid);
519 add_assoc_long(return_value, "code", WEXITSTATUS(status));
520 add_assoc_long(return_value, "signal", WTERMSIG(status));
521 } else {
522 swoole_set_last_error(errno);
523 RETURN_FALSE;
524 }
525 }
526
PHP_METHOD(swoole_coroutine_system,wait)527 PHP_METHOD(swoole_coroutine_system, wait) {
528 double timeout = -1;
529
530 ZEND_PARSE_PARAMETERS_START(0, 1)
531 Z_PARAM_OPTIONAL
532 Z_PARAM_DOUBLE(timeout)
533 ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
534
535 swoole_coroutine_system_wait(INTERNAL_FUNCTION_PARAM_PASSTHRU, -1, timeout);
536 }
537
PHP_METHOD(swoole_coroutine_system,waitPid)538 PHP_METHOD(swoole_coroutine_system, waitPid) {
539 zend_long pid;
540 double timeout = -1;
541
542 ZEND_PARSE_PARAMETERS_START(1, 2)
543 Z_PARAM_LONG(pid)
544 Z_PARAM_OPTIONAL
545 Z_PARAM_DOUBLE(timeout)
546 ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
547
548 swoole_coroutine_system_wait(INTERNAL_FUNCTION_PARAM_PASSTHRU, pid, timeout);
549 }
550
PHP_METHOD(swoole_coroutine_system,waitSignal)551 PHP_METHOD(swoole_coroutine_system, waitSignal) {
552 zend_long signo;
553 double timeout = -1;
554
555 ZEND_PARSE_PARAMETERS_START(1, 2)
556 Z_PARAM_LONG(signo)
557 Z_PARAM_OPTIONAL
558 Z_PARAM_DOUBLE(timeout)
559 ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
560
561 if (!System::wait_signal(signo, timeout)) {
562 if (swoole_get_last_error() == EBUSY) {
563 php_swoole_fatal_error(E_WARNING, "Unable to wait signal, async signal listener has been registered");
564 } else if (swoole_get_last_error() == EINVAL) {
565 php_swoole_fatal_error(E_WARNING, "Invalid signal [" ZEND_LONG_FMT "]", signo);
566 }
567 errno = swoole_get_last_error();
568 RETURN_FALSE;
569 }
570
571 RETURN_TRUE;
572 }
573
PHP_METHOD(swoole_coroutine_system,waitEvent)574 PHP_METHOD(swoole_coroutine_system, waitEvent) {
575 zval *zfd;
576 zend_long events = SW_EVENT_READ;
577 double timeout = -1;
578
579 ZEND_PARSE_PARAMETERS_START(1, 3)
580 Z_PARAM_ZVAL(zfd)
581 Z_PARAM_OPTIONAL
582 Z_PARAM_LONG(events)
583 Z_PARAM_DOUBLE(timeout)
584 ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
585
586 int fd = php_swoole_convert_to_fd(zfd);
587 if (fd < 0) {
588 php_swoole_fatal_error(E_WARNING, "unknown fd type");
589 RETURN_FALSE;
590 }
591
592 events = System::wait_event(fd, events, timeout);
593 if (events < 0) {
594 RETURN_FALSE;
595 }
596
597 RETURN_LONG(events);
598 }
599