diff --git a/include/sys/ioctl.h b/include/sys/ioctl.h index 96394fc0..db0874ce 100644 --- a/include/sys/ioctl.h +++ b/include/sys/ioctl.h @@ -21,31 +21,24 @@ #include #include +#include + #ifdef __cplusplus extern "C" { #endif -#define IOCPARM_MASK 0x1fff -#define IOCPARM_LEN(x) (((x) >> 16) & IOCPARM_MASK) -#define IOCBASECMD(x) ((x) & ~(IOCPARM_MASK << 16)) -#define IOCGROUP(x) (((x) >> 8) & 0xff) - - -#define IOC_VOID 0x20000000 -#define IOC_OUT 0x40000000 -#define IOC_IN 0x80000000 -#define IOC_INOUT (IOC_IN | IOC_OUT) -#define IOC_DIRMASK 0xe0000000 +/* convenience macro to get sub-pointer `field` in a packed ioctl structure that is read-only */ +#define IOC_NESTED_GET_PTR_FIELD(request, subptr, val, field) ioctl_getPointerField(request, subptr, val, offsetof(typeof(*val), field)) -#define _IOC(inout, group, num, len) ((unsigned long)(inout | ((len & IOCPARM_MASK) << 16) | ((group) << 8) | (num))) -#define _IO(g, n) _IOC(IOC_VOID, (g), (n), 0) -#define _IOV(g, n, t) _IOC(IOC_VOID, (g), (n), sizeof(t)) /* IOW with passing by value */ -#define _IOR(g, n, t) _IOC(IOC_OUT, (g), (n), sizeof(t)) -#define _IOW(g, n, t) _IOC(IOC_IN, (g), (n), sizeof(t)) -#define _IOWR(g, n, t) _IOC(IOC_INOUT, (g), (n), sizeof(t)) +#define _IO(g, n) _IOC(IOC_VOID, (g), (n), 0) +#define _IOV(g, n, t) _IOC(IOC_VOID, (g), (n), sizeof(t)) /* IOW with passing by value */ +#define _IOR(g, n, t) _IOC(IOC_OUT, (g), (n), sizeof(t)) +#define _IOW(g, n, t) _IOC(IOC_IN, (g), (n), sizeof(t)) +#define _IOWR(g, n, t) _IOC(IOC_INOUT, (g), (n), sizeof(t)) +#define _IOC_NESTED(inout, g, n, t) _IOC((inout) | IOC_NESTED, (g), (n), sizeof(t)) /* passed structure is nested */ #define TIOCGPTN _IOR('T', 0x30, unsigned int) @@ -77,6 +70,11 @@ static inline pid_t ioctl_getSenderPid(const msg_t *msg) void ioctl_setResponse(msg_t *msg, unsigned long request, int err, const void *data); +/* get value pointed to by a sub-pointer at offset in nested ioctl structure + pointed to by val and place it in subptr */ +int ioctl_getPointerField(unsigned long request, void **subptr, const void *val, size_t offset); + + #ifdef __cplusplus } #endif diff --git a/include/sys/sockios.h b/include/sys/sockios.h index 9c4c9367..15c108a1 100644 --- a/include/sys/sockios.h +++ b/include/sys/sockios.h @@ -20,37 +20,37 @@ #include #include -#define SOCK_IOC_TYPE 'S' +#define SOCK_IOC_TYPE 'S' /* Socket configuration controls. */ -#define SIOCGIFNAME _IOWR(SOCK_IOC_TYPE, 0x10, struct ifreq) /* get name of interface with given index */ -#define SIOCGIFCONF _IOWR(SOCK_IOC_TYPE, 0x12, struct ifconf) /* get iface list */ -#define SIOCGIFFLAGS _IOWR(SOCK_IOC_TYPE, 0x13, struct ifreq) /* get interface flags */ -#define SIOCSIFFLAGS _IOW( SOCK_IOC_TYPE, 0x14, struct ifreq) /* Set interface flags */ -#define SIOCGIFADDR _IOWR(SOCK_IOC_TYPE, 0x15, struct ifreq) /* get device address */ -#define SIOCSIFADDR _IOW( SOCK_IOC_TYPE, 0x16, struct ifreq) /* Set device address */ -#define SIOCGIFDSTADDR _IOWR(SOCK_IOC_TYPE, 0x17, struct ifreq) /* get remote address (point-to-point interfaces) */ -#define SIOCSIFDSTADDR _IOW( SOCK_IOC_TYPE, 0x18, struct ifreq) /* set remote address (point-to-point interfaces) */ -#define SIOCGIFBRDADDR _IOWR(SOCK_IOC_TYPE, 0x19, struct ifreq) /* get broadcast address */ -#define SIOCSIFBRDADDR _IOW( SOCK_IOC_TYPE, 0x1a, struct ifreq) /* set broadcast address */ -#define SIOCGIFNETMASK _IOWR(SOCK_IOC_TYPE, 0x1b, struct ifreq) /* get network mask */ -#define SIOCSIFNETMASK _IOW( SOCK_IOC_TYPE, 0x1c, struct ifreq) /* set network mask */ -#define SIOCGIFMETRIC _IOWR(SOCK_IOC_TYPE, 0x1d, struct ifreq) /* get metric */ -#define SIOCSIFMETRIC _IOW( SOCK_IOC_TYPE, 0x1e, struct ifreq) /* set metric */ -#define SIOCGIFMTU _IOWR(SOCK_IOC_TYPE, 0x21, struct ifreq) /* get MTU size */ -#define SIOCSIFMTU _IOW( SOCK_IOC_TYPE, 0x22, struct ifreq) /* set MTU size */ -#define SIOCSIFHWADDR _IOW( SOCK_IOC_TYPE, 0x24, struct ifreq) /* set interface MAC address */ -#define SIOCGIFHWADDR _IOWR(SOCK_IOC_TYPE, 0x27, struct ifreq) /* get interface MAC address */ -#define SIOCADDMULTI _IOWR(SOCK_IOC_TYPE, 0x31, struct ifreq) /* add multicast address */ -#define SIOCDELMULTI _IOWR(SOCK_IOC_TYPE, 0x32, struct ifreq) /* remove multicast address */ -#define SIOCGIFINDEX _IOWR(SOCK_IOC_TYPE, 0x33, struct ifreq) /* get index of interface with given name */ +#define SIOCGIFNAME _IOWR(SOCK_IOC_TYPE, 0x10, struct ifreq) /* get name of interface with given index */ +#define SIOCGIFCONF _IOC_NESTED(IOC_INOUT, SOCK_IOC_TYPE, 0x12, struct ifconf) /* get iface list */ +#define SIOCGIFFLAGS _IOWR(SOCK_IOC_TYPE, 0x13, struct ifreq) /* get interface flags */ +#define SIOCSIFFLAGS _IOW(SOCK_IOC_TYPE, 0x14, struct ifreq) /* Set interface flags */ +#define SIOCGIFADDR _IOWR(SOCK_IOC_TYPE, 0x15, struct ifreq) /* get device address */ +#define SIOCSIFADDR _IOW(SOCK_IOC_TYPE, 0x16, struct ifreq) /* Set device address */ +#define SIOCGIFDSTADDR _IOWR(SOCK_IOC_TYPE, 0x17, struct ifreq) /* get remote address (point-to-point interfaces) */ +#define SIOCSIFDSTADDR _IOW(SOCK_IOC_TYPE, 0x18, struct ifreq) /* set remote address (point-to-point interfaces) */ +#define SIOCGIFBRDADDR _IOWR(SOCK_IOC_TYPE, 0x19, struct ifreq) /* get broadcast address */ +#define SIOCSIFBRDADDR _IOW(SOCK_IOC_TYPE, 0x1a, struct ifreq) /* set broadcast address */ +#define SIOCGIFNETMASK _IOWR(SOCK_IOC_TYPE, 0x1b, struct ifreq) /* get network mask */ +#define SIOCSIFNETMASK _IOW(SOCK_IOC_TYPE, 0x1c, struct ifreq) /* set network mask */ +#define SIOCGIFMETRIC _IOWR(SOCK_IOC_TYPE, 0x1d, struct ifreq) /* get metric */ +#define SIOCSIFMETRIC _IOW(SOCK_IOC_TYPE, 0x1e, struct ifreq) /* set metric */ +#define SIOCGIFMTU _IOWR(SOCK_IOC_TYPE, 0x21, struct ifreq) /* get MTU size */ +#define SIOCSIFMTU _IOW(SOCK_IOC_TYPE, 0x22, struct ifreq) /* set MTU size */ +#define SIOCSIFHWADDR _IOW(SOCK_IOC_TYPE, 0x24, struct ifreq) /* set interface MAC address */ +#define SIOCGIFHWADDR _IOWR(SOCK_IOC_TYPE, 0x27, struct ifreq) /* get interface MAC address */ +#define SIOCADDMULTI _IOWR(SOCK_IOC_TYPE, 0x31, struct ifreq) /* add multicast address */ +#define SIOCDELMULTI _IOWR(SOCK_IOC_TYPE, 0x32, struct ifreq) /* remove multicast address */ +#define SIOCGIFINDEX _IOWR(SOCK_IOC_TYPE, 0x33, struct ifreq) /* get index of interface with given name */ /* unused but needed by busybox ifconfig */ -#define SIOCGIFTXQLEN _IOWR(SOCK_IOC_TYPE, 0x42, struct ifreq) /* get the tx queue length */ -#define SIOCSIFTXQLEN _IOWR(SOCK_IOC_TYPE, 0x43, struct ifreq) /* set the tx queue length */ +#define SIOCGIFTXQLEN _IOWR(SOCK_IOC_TYPE, 0x42, struct ifreq) /* get the tx queue length */ +#define SIOCSIFTXQLEN _IOWR(SOCK_IOC_TYPE, 0x43, struct ifreq) /* set the tx queue length */ /* Routing table calls. */ -#define SIOCADDRT _IOW(SOCK_IOC_TYPE, 0x44, struct rtentry) /* add routing table entry */ -#define SIOCDELRT _IOW(SOCK_IOC_TYPE, 0x45, struct rtentry) /* delete routing table entry */ +#define SIOCADDRT _IOC_NESTED(IOC_IN, SOCK_IOC_TYPE, 0x44, struct rtentry) /* add routing table entry */ +#define SIOCDELRT _IOC_NESTED(IOC_IN, SOCK_IOC_TYPE, 0x45, struct rtentry) /* delete routing table entry */ -#endif // LIBPHOENIX_SYS_SOCKIOS_H +#endif // LIBPHOENIX_SYS_SOCKIOS_H diff --git a/sys/ioctl.c b/sys/ioctl.c index ad74da9b..5040b7ee 100644 --- a/sys/ioctl.c +++ b/sys/ioctl.c @@ -13,18 +13,16 @@ * %LICENSE% */ -#include - +#include #include #include #include +#include #include -/* SIOCGIFCONF handling */ #include -#include -#include -#include + +#include "../unistd/ioctl-helper.h" const void *ioctl_unpack(msg_t *msg, unsigned long *request, id_t *id) @@ -47,7 +45,7 @@ const void *ioctl_unpackEx(msg_t *msg, unsigned long *request, id_t *id, void ** *id = msg->oid.id; } - size = IOCPARM_LEN(ioctl->request); + size = ioctl->size; if ((ioctl->request & IOC_IN) != 0) { if (size <= (sizeof(msg->i.raw) - sizeof(ioctl_in_t))) { @@ -62,31 +60,6 @@ const void *ioctl_unpackEx(msg_t *msg, unsigned long *request, id_t *id, void ** size = min(size, sizeof(void *)); (void)memcpy(&data, ioctl->data, size); } - - /* ioctl special case: arg is structure with pointer - has to be custom-packed into message */ - if (ioctl->request == SIOCGIFCONF) { - struct ifconf *ifc = (struct ifconf *)data; - if (ifc->ifc_len > 0) { - ifc->ifc_buf = msg->o.data; - } - } - else if ((ioctl->request == SIOCADDRT) || (ioctl->request == SIOCDELRT)) { - /* input data is read only so we have allocate the in_data if - * we want to change it. TODO: find better place to allocate and free - * message */ - struct rtentry *rt = malloc(msg->i.size); - if (rt == NULL) { - return NULL; - } - (void)memcpy(rt, msg->i.data, msg->i.size); - rt->rt_dev = malloc(msg->o.size); - if (rt->rt_dev == NULL) { - free(rt); - return NULL; - } - (void)memcpy(rt->rt_dev, msg->o.data, msg->o.size); - data = (void *)rt; - } else { } diff --git a/unistd/Makefile b/unistd/Makefile index 192278d0..3f5d4183 100644 --- a/unistd/Makefile +++ b/unistd/Makefile @@ -4,5 +4,5 @@ # Copyright 2020 Phoenix Systems # -OBJS += $(addprefix $(PREFIX_O)unistd/, alarm.o conf.o crypt.o dir.o file.o hostname.o getlogin.o getopt.o getpass.o group.o \ +OBJS += $(addprefix $(PREFIX_O)unistd/, alarm.o conf.o crypt.o dir.o ioctl-helper.o file.o hostname.o getlogin.o getopt.o getpass.o group.o \ hostid.o pause.o pwd.o sys.o user.o) diff --git a/unistd/file.c b/unistd/file.c index bce8b792..98caa6f5 100644 --- a/unistd/file.c +++ b/unistd/file.c @@ -29,6 +29,7 @@ #include #include "posix/utils.h" +#include "ioctl-helper.h" extern ssize_t sys_read(int fildes, void *buf, size_t nbyte, off_t offset); @@ -644,20 +645,53 @@ int fcntl(int fd, int cmd, ...) } -extern int sys_ioctl(int fildes, unsigned long request, void *val); +extern int sys_ioctl(int fildes, unsigned long request, void *val, size_t size); int ioctl(int fildes, unsigned long request, ...) { va_list ap; void *val; + void *packed; + size_t size = IOCPARM_LEN(request); + int ret; + + if ((request & IOC_INOUT) != 0 || size > 0) { + /* assume `args` is one structure passed by pointer */ + va_start(ap, request); + val = va_arg(ap, void *); + va_end(ap); + } + else { + val = NULL; + } - /* FIXME: handle varargs properly */ - va_start(ap, request); - val = va_arg(ap, void *); - va_end(ap); + if ((request & IOC_NESTED) != 0) { + ret = ioctl_serialize(request, val, &packed, &size); + if (ret < 0) { + ioctl_free(request, packed); + return SET_ERRNO(ret); + } - return SET_ERRNO(sys_ioctl(fildes, request, val)); + if (packed != NULL) { + ret = sys_ioctl(fildes, request, packed, size); + } + else { + ret = sys_ioctl(fildes, request, val, size); + } + + if (ret < 0) { + ioctl_free(request, packed); + return SET_ERRNO(ret); + } + + ioctl_deserialize(request, val, packed); + + return SET_ERRNO(ret); + } + else { + return SET_ERRNO(sys_ioctl(fildes, request, val, size)); + } } diff --git a/unistd/ioctl-helper.c b/unistd/ioctl-helper.c new file mode 100644 index 00000000..f40ea271 --- /dev/null +++ b/unistd/ioctl-helper.c @@ -0,0 +1,314 @@ +/* + * Phoenix-RTOS + * + * libphoenix + * + * ioctl special case input serialization/deserialization + * + * Copyright 2025 Phoenix Systems + * Author: Julian Uziemblo + * + * This file is part of Phoenix-RTOS. + * + * %LICENSE% + */ +#include +#include +#include +#include +#include + +#include "ioctl-helper.h" + +#define PTR_ADD(ptr, offset) ((void *)(((uint8_t *)(ptr)) + (offset))) +#define PTR_ADD_AND_DEREF(ptr, offset) (*(void **)PTR_ADD(ptr, offset)) +#define SIZEOF_ARRAY(arr) (sizeof(arr) / sizeof(arr[0])) + + +typedef struct { + const size_t offset; + /* returns: < 0 on error; 0 when no serialization needed; > 0 the size of subptr */ + ssize_t (*const getSize)(const void *val); +} subptr_t; + + +typedef struct { + const subptr_t *const subptrs; + const size_t nsubptrs; +} nestedLayout_t; + + +typedef struct { + const unsigned long request; + void **packed; + size_t *packedSize; + void *ioctlVal; + size_t ioctlValSize; + const nestedLayout_t *layout; +} ioctl_ctx_t; + + +typedef int (*ioctl_op_t)(ioctl_ctx_t *ctx); + + +/* + * Extendable support for nested structures in ioctl commands + * + * To support new nested ioctl: + * 1) Define `Subptrs[]` - an array of `subptr_t` structures + * representing the inner layout of the nested ioctl structure. Each + * entry corresponds to one sub-pointer and describes its offset in + * the structure and provides a function to calculate the size of the + * pointed-to structure (it shall return: 0 when no serialization is + * needed, negative value on error and positive size value on success). + * 2) Define `Layout`. This structure consists of a pointer + * to the defined above sub-pointers array and its size. + * 3) Add a branch to the switch statement in `ioctl_dispatchOp` function + * with the ioctl's request number as a matcher that assigns created + * layout structure to the `layout` pointer variable. + */ + + +static ssize_t ioctl_rtDevSize(const void *val) +{ + const struct rtentry *rt = val; + if (rt->rt_dev == NULL) { + return -EINVAL; + } + return strlen(rt->rt_dev) + 1; +} + + +static const subptr_t rtentrySubptrs[] = { { + .offset = offsetof(struct rtentry, rt_dev), + .getSize = ioctl_rtDevSize, +} }; + + +static const nestedLayout_t rtentryLayout = { + .subptrs = rtentrySubptrs, + .nsubptrs = SIZEOF_ARRAY(rtentrySubptrs), +}; + + +static ssize_t ioctl_ifcBufLen(const void *val) +{ + const struct ifconf *ifc = val; + size_t ifc_buf_len = ifc->ifc_len; + if (ifc->ifc_buf == NULL) { + if (ifc_buf_len == 0) { + /* device returns the needed length + -> no packing/unpacking needed */ + return 0; + } + else { + return -EINVAL; + } + } + + return ifc_buf_len; +} + + +static const subptr_t ifconfSubptrs[] = { { + .offset = offsetof(struct ifconf, ifc_buf), + .getSize = ioctl_ifcBufLen, +} }; + + +static const nestedLayout_t ifconfLayout = { + .subptrs = ifconfSubptrs, + .nsubptrs = SIZEOF_ARRAY(ifconfSubptrs), +}; + + +static inline int ioctl_dispatchOp(unsigned long request, void **packed, size_t *packedSize, void *ioctlVal, size_t ioctlValSize, ioctl_op_t op) +{ + /* IOC_NESTED checked before this in ioctl() */ + const nestedLayout_t *layout; + + switch (request) { + case SIOCADDRT: + case SIOCDELRT: + layout = &rtentryLayout; + break; + + case SIOCGIFCONF: + layout = &ifconfLayout; + break; + + default: + return -EOPNOTSUPP; + } + + ioctl_ctx_t ctx = { + .request = request, + .packed = packed, + .packedSize = packedSize, + .ioctlVal = ioctlVal, + .ioctlValSize = ioctlValSize, + .layout = layout, + }; + + return op(&ctx); +} + + +/* + * Common routines + */ + + +static inline ssize_t ioctl_totalSizeNeeded(ioctl_ctx_t *ctx) +{ + ssize_t size; + size_t totalSize = ctx->ioctlValSize; + + for (size_t i = 0; i < ctx->layout->nsubptrs; i++) { + size = ctx->layout->subptrs[i].getSize(ctx->ioctlVal); + if (size < 0) { + return size; + } + totalSize += size; + } + + return totalSize; +} + + +static int ioctl_opSerialize(ioctl_ctx_t *ctx) +{ + size_t totalSize; + size_t offset = 0; + + ssize_t size = ioctl_totalSizeNeeded(ctx); + if (size < 0) { + return size; + } + totalSize = size; + + if (totalSize == ctx->ioctlValSize) { + /* no packing needed - ioctl is already the right size */ + return 0; + } + + void *mem = malloc(totalSize); + if (mem == NULL) { + return -ENOMEM; + } + + (void)memcpy(PTR_ADD(mem, offset), ctx->ioctlVal, ctx->ioctlValSize); + offset += ctx->ioctlValSize; + + for (size_t i = 0; i < ctx->layout->nsubptrs; i++) { + size = ctx->layout->subptrs[i].getSize(ctx->ioctlVal); + if (size < 0) { + free(mem); + return size; + } + (void)memcpy(PTR_ADD(mem, offset), PTR_ADD_AND_DEREF(ctx->ioctlVal, ctx->layout->subptrs[i].offset), size); + offset += size; + } + + *ctx->packed = mem; + *ctx->packedSize = totalSize; + + return 0; +} + + +static int ioctl_opDeserialize(ioctl_ctx_t *ctx) +{ + if (*ctx->packed == NULL) { + /* no unpacking needed - val wasn't packed */ + return 0; + } + + size_t offset = ctx->ioctlValSize; + ssize_t size; + + for (size_t i = 0; i < ctx->layout->nsubptrs; i++) { + size = ctx->layout->subptrs[i].getSize(ctx->ioctlVal); + if (size < 0) { + return size; + } + if (size == 0) { + continue; + } + /* copy old pointer value to helper variable */ + void *subptr = PTR_ADD_AND_DEREF(ctx->ioctlVal, ctx->layout->subptrs[i].offset); + + if (subptr != NULL) { + /* set pointer value in allocated structure to copy the right pointer at the end */ + PTR_ADD_AND_DEREF(*ctx->packed, ctx->layout->subptrs[i].offset) = subptr; + + /* copy current substructure */ + (void)memcpy(subptr, PTR_ADD(*ctx->packed, offset), size); + offset += size; + } + } + + /* copy back the main struct */ + (void)memcpy(ctx->ioctlVal, *ctx->packed, ctx->ioctlValSize); + + return 0; +} + + +static int ioctl_opFree(ioctl_ctx_t *ctx) +{ + if (*ctx->packed != NULL) { + free(*ctx->packed); + } + return 0; +} + + +static int ioctl_opGetPointerField(ioctl_ctx_t *ctx) +{ + ssize_t size; + size_t offset = IOCPARM_LEN(ctx->request); + + for (size_t i = 0; i < ctx->layout->nsubptrs; i++) { + if (ctx->layout->subptrs[i].offset == ctx->ioctlValSize) { + *ctx->packed = PTR_ADD(ctx->ioctlVal, offset); + return 0; + } + size = ctx->layout->subptrs[i].getSize(ctx->ioctlVal); + if (size < 0) { + return size; + } + offset += size; + } + + return -ENODATA; +} + + +void ioctl_free(unsigned long request, void *packed) +{ + (void)ioctl_dispatchOp(request, &packed, NULL, NULL, 0, ioctl_opFree); +} + + +int ioctl_serialize(unsigned long request, void *ioctlVal, void **packed, size_t *packedSize) +{ + /* set to NULL so if we don't allocate the memory, we won't call free on it */ + *packed = NULL; + return ioctl_dispatchOp(request, packed, packedSize, ioctlVal, IOCPARM_LEN(request), ioctl_opSerialize); +} + + +void ioctl_deserialize(unsigned long request, void *ioctlVal, void *packed) +{ + if (ioctl_dispatchOp(request, &packed, NULL, ioctlVal, IOCPARM_LEN(request), ioctl_opDeserialize) != -EOPNOTSUPP) { + ioctl_free(request, packed); + } +} + + +int ioctl_getPointerField(unsigned long request, void **subptr, const void *val, size_t offset) +{ + /* ioctl_opGetPointerField doesn't change val: const signalizes to user that val will not be changed */ + return ioctl_dispatchOp(request, subptr, NULL, (void *)val, offset, ioctl_opGetPointerField); +} diff --git a/unistd/ioctl-helper.h b/unistd/ioctl-helper.h new file mode 100644 index 00000000..3c73dbfa --- /dev/null +++ b/unistd/ioctl-helper.h @@ -0,0 +1,36 @@ +/* + * Phoenix-RTOS + * + * libphoenix + * + * ioctl special case input serialization/deserialization + * + * Copyright 2025 Phoenix Systems + * Author: Julian Uziembło + * + * This file is part of Phoenix-RTOS. + * + * %LICENSE% + */ + +#ifndef _LIBPHOENIX_IOCTL_HELPER_H_ +#define _LIBPHOENIX_IOCTL_HELPER_H_ + +#include + + +/* deallocates memory if it was allocated by ioctl_serialize */ +void ioctl_free(unsigned long request, void *packed); + + +/* serialize the request's structure into packed. + allocates memory in special cases to be freed by ioctl_free */ +int ioctl_serialize(unsigned long request, void *ioctlVal, void **packed, size_t *packedSize); + + +/* deserialize the request's structure from packed back into ioctlVal. + deallocates memory if it was allocated by ioctl_serialize */ +void ioctl_deserialize(unsigned long request, void *ioctlVal, void *packed); + + +#endif /* _LIBPHOENIX_IOCTL_HELPER_H_ */