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