summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJonas Jelonek <jelonek.jonas@gmail.com>2024-08-16 11:36:53 +0000
committerJo-Philipp Wich <jo@mein.io>2024-09-20 09:44:34 +0200
commit56ebeeca5349773db0ff8104dbf2f99d10d13f16 (patch)
treecb654ddda28e50ffb1f6e069da59ed4c9626bcd9
parent6ea37c8fa4c4780ff99441ac787a2c9cd38c7bc6 (diff)
fs: add ioctl() file method
implements ioctl() for a given file handle on Linux. Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
-rw-r--r--lib/fs.c132
-rw-r--r--tests/custom/03_stdlib/40_proto1
2 files changed, 133 insertions, 0 deletions
diff --git a/lib/fs.c b/lib/fs.c
index d0d97ac..220bd74 100644
--- a/lib/fs.c
+++ b/lib/fs.c
@@ -61,6 +61,10 @@
#include <limits.h>
#include <fcntl.h>
+#if defined(__linux__)
+#include <sys/ioctl.h>
+#endif
+
#include "ucode/module.h"
#include "ucode/platform.h"
@@ -929,6 +933,131 @@ uc_fs_fileno(uc_vm_t *vm, size_t nargs)
return uc_fs_fileno_common(vm, nargs, "fs.file");
}
+#if defined(__linux__)
+
+/**
+ * Performs an ioctl operation on the file.
+ *
+ * The direction parameter specifies who is reading and writing,
+ * from the user's point of view. It can be one of the following values:
+ *
+ * | Direction | Description |
+ * |-----------|-----------------------------------------------------------------------------------------|
+ * | 0 | NONE - neither userspace nor kernel is writing, ioctl is executed without passing data. |
+ * | 1 | WRITE - userspace is writing and kernel is reading. |
+ * | 2 | READ - kernel is writing and userspace is reading. |
+ * | 3 | READ+WRITE - userspace is writing and kernel is writing back into the data structure. |
+ *
+ * The size parameter has a different purpose depending on the direction parameter:
+ * - direction = 0 -> the size parameter is not used
+ * - direction = 1 -> size must be the length (in bytes) of argp
+ * - direction = 2 -> expected length (in bytes) of the data returned by kernel
+ * - direction = 3 -> size is the length (in bytes) of argp, and the length of the data returned by kernel.
+ *
+ * The argp parameter should be the data to be written for direction '1' and '3', otherwise null.
+ *
+ * Returns the result of the ioctl operation; for direction '2' and '3' this is a string containing
+ * the data, otherwise a number as return code.
+ * In case of an error, null is returned and the error code is available via last_error.
+ *
+ * @function module:fs.file#ioctl
+ *
+ * @param {number} direction
+ * The direction of the ioctl operation.
+ *
+ * @param {number} type
+ * ioctl type (see https://www.kernel.org/doc/html/latest/userspace-api/ioctl/ioctl-number.html)
+ *
+ * @param {number} num
+ * ioctl sequence number.
+ *
+ * @param {number} size
+ * The size of the ioctl operation payload.
+ *
+ * @param {?string} payload
+ * The ioctl payload.
+ *
+ * @returns {?number|?string}
+ */
+static uc_value_t *
+uc_fs_ioctl(uc_vm_t *vm, size_t nargs)
+{
+ FILE *fp = uc_fn_thisval("fs.file");
+ uc_value_t *direction = uc_fn_arg(0);
+ uc_value_t *type = uc_fn_arg(1);
+ uc_value_t *num = uc_fn_arg(2);
+ uc_value_t *size = uc_fn_arg(3);
+ uc_value_t *payload = uc_fn_arg(4);
+ char *buf = NULL;
+ unsigned long req = 0;
+ unsigned int dir, ty, nr;
+ size_t sz;
+ int fd, ret;
+ bool freebuf = false;
+
+ if (!fp)
+ err_return(EBADF);
+
+ fd = fileno(fp);
+ if (fd == -1)
+ err_return(EBADF);
+
+ if (ucv_type(direction) != UC_INTEGER || ucv_type(type) != UC_INTEGER ||
+ ucv_type(num) != UC_INTEGER || ucv_type(size) != UC_INTEGER)
+ err_return(EINVAL);
+
+ dir = ucv_uint64_get(direction);
+ sz = ucv_uint64_get(size);
+ ty = ucv_uint64_get(type);
+ nr = ucv_uint64_get(num);
+
+ switch (dir) {
+ case 0: /* ioctl w/o read and write */
+ req = _IOC(_IOC_NONE, ty, nr, 0);
+ break;
+ case 1: /* ioctl write */
+ if (ucv_type(payload) != UC_STRING)
+ err_return(EINVAL);
+
+ req = _IOC(_IOC_WRITE, ty, nr, sz);
+ buf = ucv_string_get(payload);
+ break;
+ case 2: /* ioctl read */
+ req = _IOC(_IOC_READ, ty, nr, sz);
+ buf = xalloc(sz);
+ if (!buf)
+ err_return(ENOMEM);
+
+ freebuf = true;
+ break;
+ case 3: /* ioctl read+write */
+ req = _IOC((_IOC_READ|_IOC_WRITE), ty, nr, sz);
+ buf = ucv_string_get(payload);
+ break;
+ default: err_return(EINVAL);
+ }
+
+ ret = ioctl(fd, req, buf);
+ if (ret < 0) {
+ if (freebuf)
+ free(buf);
+
+ err_return(errno);
+ }
+
+ if (dir >= 2) {
+ payload = ucv_string_new_length(buf, sz);
+ if (freebuf)
+ free(buf);
+ } else {
+ payload = ucv_uint64_new(ret);
+ }
+
+ return payload;
+}
+
+#endif
+
/**
* Opens a file.
*
@@ -2665,6 +2794,9 @@ static const uc_function_list_t file_fns[] = {
{ "isatty", uc_fs_isatty },
{ "truncate", uc_fs_truncate },
{ "lock", uc_fs_lock },
+#if defined(__linux__)
+ { "ioctl", uc_fs_ioctl },
+#endif
};
static const uc_function_list_t dir_fns[] = {
diff --git a/tests/custom/03_stdlib/40_proto b/tests/custom/03_stdlib/40_proto
index d96d124..0f65910 100644
--- a/tests/custom/03_stdlib/40_proto
+++ b/tests/custom/03_stdlib/40_proto
@@ -38,6 +38,7 @@ When invoked with two arguments, returns the given value.
Hello, World!
[
{
+ "ioctl": "function ioctl(...) { [native code] }",
"lock": "function lock(...) { [native code] }",
"truncate": "function truncate(...) { [native code] }",
"isatty": "function isatty(...) { [native code] }",