diff --git a/ext/standard/io_hooks.c b/ext/standard/io_hooks.c index bdb4239027f8..207c4d31afe2 100644 --- a/ext/standard/io_hooks.c +++ b/ext/standard/io_hooks.c @@ -1347,7 +1347,7 @@ static void php_io_hooks_php_call(zend_fcall_info_cache *fcc, zval *retval, zend } /* A Done that hands data to the caller: bytes in its buffer, a descriptor, - * an exited child, a taken signal, a resolved name */ + * an exited child, a taken signal, a resolved name, directory changes */ static bool php_io_op_result_is_data(php_io_op *op, const php_io_completion_obj *c) { if (c->status != PHP_IO_DONE || c->error) { @@ -1356,6 +1356,7 @@ static bool php_io_op_result_is_data(php_io_op *op, const php_io_completion_obj switch (op->type) { case PHP_IO_OP_READ: case PHP_IO_OP_RECV: + case PHP_IO_OP_DIR_CHANGES: return c->res != 0; case PHP_IO_OP_ACCEPT: case PHP_IO_OP_WAITPID: diff --git a/main/io/php_io_hooks.c b/main/io/php_io_hooks.c index b9f8229568c6..6d9a56be586f 100644 --- a/main/io/php_io_hooks.c +++ b/main/io/php_io_hooks.c @@ -201,6 +201,15 @@ PHPAPI void php_io_op_fsync(php_io_op *op, zend_object *handle, php_socket_t fd, op->u.fsync.data_only = data_only; } +PHPAPI void php_io_op_dir_changes(php_io_op *op, php_socket_t fd, void *buf, size_t len, bool subtree) +{ + ZEND_ASSERT(((uintptr_t) buf & 3) == 0); + php_io_op_init(op, PHP_IO_OP_DIR_CHANGES, NULL, fd, 0, php_io_deadline_infinite()); + op->u.dir_changes.buf = buf; + op->u.dir_changes.len = len; + op->u.dir_changes.subtree = subtree; +} + PHPAPI void php_io_op_any(php_io_op *op, php_io_op **members, uint32_t n, php_io_op_result *results) { php_io_op_init(op, PHP_IO_OP_ANY, NULL, SOCK_ERR, 0, php_io_deadline_infinite()); @@ -1022,6 +1031,8 @@ static bool php_io_result_valid(const php_io_op *op, const php_io_op_result *res case PHP_IO_OP_RECV: case PHP_IO_OP_SEND: return result->res >= 0 && (uint64_t) result->res <= op->u.io.len; + case PHP_IO_OP_DIR_CHANGES: + return result->res >= 0 && (uint64_t) result->res <= op->u.dir_changes.len; case PHP_IO_OP_ACCEPT: return result->res >= 0; case PHP_IO_OP_WAITPID: diff --git a/main/io/php_io_ring.c b/main/io/php_io_ring.c index e38d6e1db589..89fd3df81b81 100644 --- a/main/io/php_io_ring.c +++ b/main/io/php_io_ring.c @@ -454,6 +454,12 @@ static zend_always_inline unsigned php_io_ring_io_len(php_io_op *op) return (unsigned) MIN(op->u.io.len, UINT32_MAX); } +/* What one directory change wait takes at most: ior counts in unsigned */ +static zend_always_inline unsigned php_io_ring_dir_changes_len(const php_io_op *op) +{ + return (unsigned) MIN(op->u.dir_changes.len, UINT32_MAX); +} + /* The inputs are copied and the outputs get slots of their own, so that a * record outliving the op's frame never touches it */ static void php_io_ring_req_capture(php_io_ring_req *req, php_io_op *op) @@ -515,6 +521,9 @@ static void php_io_ring_req_capture(php_io_ring_req *req, php_io_op *op) req->u.io.buf = pemalloc(MAX(php_io_ring_io_len(op), 1), 1); memcpy(req->u.io.buf, op->u.io.buf, php_io_ring_io_len(op)); break; + case PHP_IO_OP_DIR_CHANGES: + req->u.io.buf = pemalloc(MAX(php_io_ring_dir_changes_len(op), 1), 1); + break; default: break; } @@ -569,6 +578,11 @@ static void php_io_ring_req_output(php_io_ring_req *req, php_io_op *op) memcpy(op->u.io.buf, req->u.io.buf, res); } break; + case PHP_IO_OP_DIR_CHANGES: + if (res > 0) { + memcpy(op->u.dir_changes.buf, req->u.io.buf, res); + } + break; default: break; } @@ -615,6 +629,7 @@ static void php_io_ring_req_discard(php_io_ring_req *req) case PHP_IO_OP_RECV: case PHP_IO_OP_WRITE: case PHP_IO_OP_SEND: + case PHP_IO_OP_DIR_CHANGES: if (req->u.io.buf) { pefree(req->u.io.buf, 1); } @@ -1026,6 +1041,13 @@ static zend_result php_io_ring_submit_one(php_io_ring *ring, php_io_ring_req *re case PHP_IO_OP_WAITPID: case PHP_IO_OP_SIGWAIT: break; + case PHP_IO_OP_DIR_CHANGES: +#ifdef IOR_FEAT_DIR_CHANGES + if (ring->features & IOR_FEAT_DIR_CHANGES) { + break; + } +#endif + ZEND_FALLTHROUGH; default: errno = ENOTSUP; return FAILURE; @@ -1106,6 +1128,15 @@ static zend_result php_io_ring_submit_one(php_io_ring *ring, php_io_ring_req *re err = rc < 0 ? -rc : 0; break; } +#ifdef IOR_FEAT_DIR_CHANGES + case PHP_IO_OP_DIR_CHANGES: { + int rc = ior_prep_dir_changes(ctx, sqe, (ior_fd_t) op->fd, req->u.io.buf, php_io_ring_dir_changes_len(op), + op->u.dir_changes.subtree ? IOR_DIR_CHANGES_SUBTREE : 0); + err = rc < 0 ? -rc : 0; + uses_caller = true; + break; + } +#endif default: ZEND_UNREACHABLE(); } diff --git a/main/php_io_hooks.h b/main/php_io_hooks.h index d82eb5dbf43c..94227580ffa6 100644 --- a/main/php_io_hooks.h +++ b/main/php_io_hooks.h @@ -37,6 +37,7 @@ typedef enum { PHP_IO_OP_FSYNC, PHP_IO_OP_WAITPID, PHP_IO_OP_SIGWAIT, + PHP_IO_OP_DIR_CHANGES, PHP_IO_OP_ANY, } php_io_op_type; @@ -122,6 +123,9 @@ struct _php_io_op { struct { pid_t pid; int options; int *status; } waitpid; /* fd is the signalfd, taken a signal a handle recorded */ struct { const php_sigset_t *set; php_siginfo_t *info; int taken; } sigwait; + /* fd is a directory HANDLE opened with FILE_LIST_DIRECTORY and + * FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED */ + struct { void *buf; size_t len; bool subtree; } dir_changes; struct { php_io_op **ops; /* caller owned */ uint32_t n; @@ -172,6 +176,12 @@ PHPAPI void php_io_op_waitpid(php_io_op *op, zend_object *handle, pid_t pid, int php_deadline dl); PHPAPI void php_io_op_sigwait(php_io_op *op, zend_object *handle, const php_sigset_t *set, php_siginfo_t *info, php_deadline dl); +/* Windows: the next changes in a directory, ReadDirectoryChangesW()'s FILE_NOTIFY_INFORMATION + * records in buf, which must be DWORD-aligned. Done with the bytes written; after 0 (changes were + * lost) or any other completion the caller rescans. A queue that cannot wait for them answers + * Unsupported. */ +PHPAPI void php_io_op_dir_changes(php_io_op *op, php_socket_t fd, void *buf, size_t len, + bool subtree); PHPAPI void php_io_op_any(php_io_op *op, php_io_op **members, uint32_t n, php_io_op_result *results); /* The op's handle, created for a stream op on the first call and kept by the stream: borrowed */