diff options
author | Al Viro | 2017-06-27 21:32:36 -0400 |
---|---|---|
committer | Al Viro | 2017-07-04 13:13:49 -0400 |
commit | 0d0606060baefdb13d3d80dba1b4c816b0676e16 (patch) | |
tree | 1a2546ad468325ea3f6391b62e1c942cba37ef41 /ipc | |
parent | cc1a7c4bae28215d042fb9f00dcb77dd65abafdf (diff) |
mqueue: move compat syscalls to native ones
... and stop messing with compat_alloc_user_space() and friends
[braino fix from Colin King folded in]
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Diffstat (limited to 'ipc')
-rw-r--r-- | ipc/Makefile | 3 | ||||
-rw-r--r-- | ipc/compat_mq.c | 138 | ||||
-rw-r--r-- | ipc/mqueue.c | 349 |
3 files changed, 262 insertions, 228 deletions
diff --git a/ipc/Makefile b/ipc/Makefile index 86c7300ecdf5..9c200e544434 100644 --- a/ipc/Makefile +++ b/ipc/Makefile @@ -5,8 +5,7 @@ obj-$(CONFIG_SYSVIPC_COMPAT) += compat.o obj-$(CONFIG_SYSVIPC) += util.o msgutil.o msg.o sem.o shm.o syscall.o obj-$(CONFIG_SYSVIPC_SYSCTL) += ipc_sysctl.o -obj_mq-$(CONFIG_COMPAT) += compat_mq.o -obj-$(CONFIG_POSIX_MQUEUE) += mqueue.o msgutil.o $(obj_mq-y) +obj-$(CONFIG_POSIX_MQUEUE) += mqueue.o msgutil.o obj-$(CONFIG_IPC_NS) += namespace.o obj-$(CONFIG_POSIX_MQUEUE_SYSCTL) += mq_sysctl.o diff --git a/ipc/compat_mq.c b/ipc/compat_mq.c deleted file mode 100644 index ef6f91cc4490..000000000000 --- a/ipc/compat_mq.c +++ /dev/null @@ -1,138 +0,0 @@ -/* - * ipc/compat_mq.c - * 32 bit emulation for POSIX message queue system calls - * - * Copyright (C) 2004 IBM Deutschland Entwicklung GmbH, IBM Corporation - * Author: Arnd Bergmann <arnd@arndb.de> - */ - -#include <linux/compat.h> -#include <linux/fs.h> -#include <linux/kernel.h> -#include <linux/mqueue.h> -#include <linux/syscalls.h> - -#include <linux/uaccess.h> - -struct compat_mq_attr { - compat_long_t mq_flags; /* message queue flags */ - compat_long_t mq_maxmsg; /* maximum number of messages */ - compat_long_t mq_msgsize; /* maximum message size */ - compat_long_t mq_curmsgs; /* number of messages currently queued */ - compat_long_t __reserved[4]; /* ignored for input, zeroed for output */ -}; - -static inline int get_compat_mq_attr(struct mq_attr *attr, - const struct compat_mq_attr __user *uattr) -{ - if (!access_ok(VERIFY_READ, uattr, sizeof *uattr)) - return -EFAULT; - - return __get_user(attr->mq_flags, &uattr->mq_flags) - | __get_user(attr->mq_maxmsg, &uattr->mq_maxmsg) - | __get_user(attr->mq_msgsize, &uattr->mq_msgsize) - | __get_user(attr->mq_curmsgs, &uattr->mq_curmsgs); -} - -static inline int put_compat_mq_attr(const struct mq_attr *attr, - struct compat_mq_attr __user *uattr) -{ - if (clear_user(uattr, sizeof *uattr)) - return -EFAULT; - - return __put_user(attr->mq_flags, &uattr->mq_flags) - | __put_user(attr->mq_maxmsg, &uattr->mq_maxmsg) - | __put_user(attr->mq_msgsize, &uattr->mq_msgsize) - | __put_user(attr->mq_curmsgs, &uattr->mq_curmsgs); -} - -COMPAT_SYSCALL_DEFINE4(mq_open, const char __user *, u_name, - int, oflag, compat_mode_t, mode, - struct compat_mq_attr __user *, u_attr) -{ - void __user *p = NULL; - if (u_attr && oflag & O_CREAT) { - struct mq_attr attr; - - memset(&attr, 0, sizeof(attr)); - - p = compat_alloc_user_space(sizeof(attr)); - if (get_compat_mq_attr(&attr, u_attr) || - copy_to_user(p, &attr, sizeof(attr))) - return -EFAULT; - } - return sys_mq_open(u_name, oflag, mode, p); -} - -COMPAT_SYSCALL_DEFINE5(mq_timedsend, mqd_t, mqdes, - const char __user *, u_msg_ptr, - compat_size_t, msg_len, unsigned int, msg_prio, - const struct compat_timespec __user *, u_abs_timeout) -{ - struct timespec __user *u_ts; - - if (compat_convert_timespec(&u_ts, u_abs_timeout)) - return -EFAULT; - - return sys_mq_timedsend(mqdes, u_msg_ptr, msg_len, - msg_prio, u_ts); -} - -COMPAT_SYSCALL_DEFINE5(mq_timedreceive, mqd_t, mqdes, - char __user *, u_msg_ptr, - compat_size_t, msg_len, unsigned int __user *, u_msg_prio, - const struct compat_timespec __user *, u_abs_timeout) -{ - struct timespec __user *u_ts; - - if (compat_convert_timespec(&u_ts, u_abs_timeout)) - return -EFAULT; - - return sys_mq_timedreceive(mqdes, u_msg_ptr, msg_len, - u_msg_prio, u_ts); -} - -COMPAT_SYSCALL_DEFINE2(mq_notify, mqd_t, mqdes, - const struct compat_sigevent __user *, u_notification) -{ - struct sigevent __user *p = NULL; - if (u_notification) { - struct sigevent n; - p = compat_alloc_user_space(sizeof(*p)); - if (get_compat_sigevent(&n, u_notification)) - return -EFAULT; - if (n.sigev_notify == SIGEV_THREAD) - n.sigev_value.sival_ptr = compat_ptr(n.sigev_value.sival_int); - if (copy_to_user(p, &n, sizeof(*p))) - return -EFAULT; - } - return sys_mq_notify(mqdes, p); -} - -COMPAT_SYSCALL_DEFINE3(mq_getsetattr, mqd_t, mqdes, - const struct compat_mq_attr __user *, u_mqstat, - struct compat_mq_attr __user *, u_omqstat) -{ - struct mq_attr mqstat; - struct mq_attr __user *p = compat_alloc_user_space(2 * sizeof(*p)); - long ret; - - memset(&mqstat, 0, sizeof(mqstat)); - - if (u_mqstat) { - if (get_compat_mq_attr(&mqstat, u_mqstat) || - copy_to_user(p, &mqstat, sizeof(mqstat))) - return -EFAULT; - } - ret = sys_mq_getsetattr(mqdes, - u_mqstat ? p : NULL, - u_omqstat ? p + 1 : NULL); - if (ret) - return ret; - if (u_omqstat) { - if (copy_from_user(&mqstat, p + 1, sizeof(mqstat)) || - put_compat_mq_attr(&mqstat, u_omqstat)) - return -EFAULT; - } - return 0; -} diff --git a/ipc/mqueue.c b/ipc/mqueue.c index e8d41ff57241..c9ff943f19ab 100644 --- a/ipc/mqueue.c +++ b/ipc/mqueue.c @@ -668,14 +668,12 @@ static void __do_notify(struct mqueue_inode_info *info) } static int prepare_timeout(const struct timespec __user *u_abs_timeout, - ktime_t *expires, struct timespec *ts) + struct timespec *ts) { if (copy_from_user(ts, u_abs_timeout, sizeof(struct timespec))) return -EFAULT; if (!timespec_valid(ts)) return -EINVAL; - - *expires = timespec_to_ktime(*ts); return 0; } @@ -770,23 +768,19 @@ static struct file *do_open(struct path *path, int oflag) return dentry_open(path, oflag, current_cred()); } -SYSCALL_DEFINE4(mq_open, const char __user *, u_name, int, oflag, umode_t, mode, - struct mq_attr __user *, u_attr) +static int do_mq_open(const char __user *u_name, int oflag, umode_t mode, + struct mq_attr *attr) { struct path path; struct file *filp; struct filename *name; - struct mq_attr attr; int fd, error; struct ipc_namespace *ipc_ns = current->nsproxy->ipc_ns; struct vfsmount *mnt = ipc_ns->mq_mnt; struct dentry *root = mnt->mnt_root; int ro; - if (u_attr && copy_from_user(&attr, u_attr, sizeof(struct mq_attr))) - return -EFAULT; - - audit_mq_open(oflag, mode, u_attr ? &attr : NULL); + audit_mq_open(oflag, mode, attr); if (IS_ERR(name = getname(u_name))) return PTR_ERR(name); @@ -819,9 +813,8 @@ SYSCALL_DEFINE4(mq_open, const char __user *, u_name, int, oflag, umode_t, mode, goto out; } audit_inode_parent_hidden(name, root); - filp = do_create(ipc_ns, d_inode(root), - &path, oflag, mode, - u_attr ? &attr : NULL); + filp = do_create(ipc_ns, d_inode(root), &path, + oflag, mode, attr); } } else { if (d_really_is_negative(path.dentry)) { @@ -851,6 +844,16 @@ out_putname: return fd; } +SYSCALL_DEFINE4(mq_open, const char __user *, u_name, int, oflag, umode_t, mode, + struct mq_attr __user *, u_attr) +{ + struct mq_attr attr; + if (u_attr && copy_from_user(&attr, u_attr, sizeof(struct mq_attr))) + return -EFAULT; + + return do_mq_open(u_name, oflag, mode, u_attr ? &attr : NULL); +} + SYSCALL_DEFINE1(mq_unlink, const char __user *, u_name) { int err; @@ -957,9 +960,9 @@ static inline void pipelined_receive(struct wake_q_head *wake_q, sender->state = STATE_READY; } -SYSCALL_DEFINE5(mq_timedsend, mqd_t, mqdes, const char __user *, u_msg_ptr, - size_t, msg_len, unsigned int, msg_prio, - const struct timespec __user *, u_abs_timeout) +static int do_mq_timedsend(mqd_t mqdes, const char __user *u_msg_ptr, + size_t msg_len, unsigned int msg_prio, + struct timespec *ts) { struct fd f; struct inode *inode; @@ -968,22 +971,19 @@ SYSCALL_DEFINE5(mq_timedsend, mqd_t, mqdes, const char __user *, u_msg_ptr, struct msg_msg *msg_ptr; struct mqueue_inode_info *info; ktime_t expires, *timeout = NULL; - struct timespec ts; struct posix_msg_tree_node *new_leaf = NULL; int ret = 0; DEFINE_WAKE_Q(wake_q); - if (u_abs_timeout) { - int res = prepare_timeout(u_abs_timeout, &expires, &ts); - if (res) - return res; - timeout = &expires; - } - if (unlikely(msg_prio >= (unsigned long) MQ_PRIO_MAX)) return -EINVAL; - audit_mq_sendrecv(mqdes, msg_len, msg_prio, timeout ? &ts : NULL); + if (ts) { + expires = timespec_to_ktime(*ts); + timeout = &expires; + } + + audit_mq_sendrecv(mqdes, msg_len, msg_prio, ts); f = fdget(mqdes); if (unlikely(!f.file)) { @@ -1078,9 +1078,9 @@ out: return ret; } -SYSCALL_DEFINE5(mq_timedreceive, mqd_t, mqdes, char __user *, u_msg_ptr, - size_t, msg_len, unsigned int __user *, u_msg_prio, - const struct timespec __user *, u_abs_timeout) +static int do_mq_timedreceive(mqd_t mqdes, char __user *u_msg_ptr, + size_t msg_len, unsigned int __user *u_msg_prio, + struct timespec *ts) { ssize_t ret; struct msg_msg *msg_ptr; @@ -1089,17 +1089,14 @@ SYSCALL_DEFINE5(mq_timedreceive, mqd_t, mqdes, char __user *, u_msg_ptr, struct mqueue_inode_info *info; struct ext_wait_queue wait; ktime_t expires, *timeout = NULL; - struct timespec ts; struct posix_msg_tree_node *new_leaf = NULL; - if (u_abs_timeout) { - int res = prepare_timeout(u_abs_timeout, &expires, &ts); - if (res) - return res; + if (ts) { + expires = timespec_to_ktime(*ts); timeout = &expires; } - audit_mq_sendrecv(mqdes, msg_len, 0, timeout ? &ts : NULL); + audit_mq_sendrecv(mqdes, msg_len, 0, ts); f = fdget(mqdes); if (unlikely(!f.file)) { @@ -1183,42 +1180,62 @@ out: return ret; } +SYSCALL_DEFINE5(mq_timedsend, mqd_t, mqdes, const char __user *, u_msg_ptr, + size_t, msg_len, unsigned int, msg_prio, + const struct timespec __user *, u_abs_timeout) +{ + struct timespec ts, *p = NULL; + if (u_abs_timeout) { + int res = prepare_timeout(u_abs_timeout, &ts); + if (res) + return res; + p = &ts; + } + return do_mq_timedsend(mqdes, u_msg_ptr, msg_len, msg_prio, p); +} + +SYSCALL_DEFINE5(mq_timedreceive, mqd_t, mqdes, char __user *, u_msg_ptr, + size_t, msg_len, unsigned int __user *, u_msg_prio, + const struct timespec __user *, u_abs_timeout) +{ + struct timespec ts, *p = NULL; + if (u_abs_timeout) { + int res = prepare_timeout(u_abs_timeout, &ts); + if (res) + return res; + p = &ts; + } + return do_mq_timedreceive(mqdes, u_msg_ptr, msg_len, u_msg_prio, p); +} + /* * Notes: the case when user wants us to deregister (with NULL as pointer) * and he isn't currently owner of notification, will be silently discarded. * It isn't explicitly defined in the POSIX. */ -SYSCALL_DEFINE2(mq_notify, mqd_t, mqdes, - const struct sigevent __user *, u_notification) +static int do_mq_notify(mqd_t mqdes, const struct sigevent *notification) { int ret; struct fd f; struct sock *sock; struct inode *inode; - struct sigevent notification; struct mqueue_inode_info *info; struct sk_buff *nc; - if (u_notification) { - if (copy_from_user(¬ification, u_notification, - sizeof(struct sigevent))) - return -EFAULT; - } - - audit_mq_notify(mqdes, u_notification ? ¬ification : NULL); + audit_mq_notify(mqdes, notification); nc = NULL; sock = NULL; - if (u_notification != NULL) { - if (unlikely(notification.sigev_notify != SIGEV_NONE && - notification.sigev_notify != SIGEV_SIGNAL && - notification.sigev_notify != SIGEV_THREAD)) + if (notification != NULL) { + if (unlikely(notification->sigev_notify != SIGEV_NONE && + notification->sigev_notify != SIGEV_SIGNAL && + notification->sigev_notify != SIGEV_THREAD)) return -EINVAL; - if (notification.sigev_notify == SIGEV_SIGNAL && - !valid_signal(notification.sigev_signo)) { + if (notification->sigev_notify == SIGEV_SIGNAL && + !valid_signal(notification->sigev_signo)) { return -EINVAL; } - if (notification.sigev_notify == SIGEV_THREAD) { + if (notification->sigev_notify == SIGEV_THREAD) { long timeo; /* create the notify skb */ @@ -1228,7 +1245,7 @@ SYSCALL_DEFINE2(mq_notify, mqd_t, mqdes, goto out; } if (copy_from_user(nc->data, - notification.sigev_value.sival_ptr, + notification->sigev_value.sival_ptr, NOTIFY_COOKIE_LEN)) { ret = -EFAULT; goto out; @@ -1238,7 +1255,7 @@ SYSCALL_DEFINE2(mq_notify, mqd_t, mqdes, skb_put(nc, NOTIFY_COOKIE_LEN); /* and attach it to the socket */ retry: - f = fdget(notification.sigev_signo); + f = fdget(notification->sigev_signo); if (!f.file) { ret = -EBADF; goto out; @@ -1278,7 +1295,7 @@ retry: ret = 0; spin_lock(&info->lock); - if (u_notification == NULL) { + if (notification == NULL) { if (info->notify_owner == task_tgid(current)) { remove_notification(info); inode->i_atime = inode->i_ctime = current_time(inode); @@ -1286,7 +1303,7 @@ retry: } else if (info->notify_owner != NULL) { ret = -EBUSY; } else { - switch (notification.sigev_notify) { + switch (notification->sigev_notify) { case SIGEV_NONE: info->notify.sigev_notify = SIGEV_NONE; break; @@ -1298,8 +1315,8 @@ retry: info->notify.sigev_notify = SIGEV_THREAD; break; case SIGEV_SIGNAL: - info->notify.sigev_signo = notification.sigev_signo; - info->notify.sigev_value = notification.sigev_value; + info->notify.sigev_signo = notification->sigev_signo; + info->notify.sigev_value = notification->sigev_value; info->notify.sigev_notify = SIGEV_SIGNAL; break; } @@ -1320,44 +1337,49 @@ out: return ret; } -SYSCALL_DEFINE3(mq_getsetattr, mqd_t, mqdes, - const struct mq_attr __user *, u_mqstat, - struct mq_attr __user *, u_omqstat) +SYSCALL_DEFINE2(mq_notify, mqd_t, mqdes, + const struct sigevent __user *, u_notification) +{ + struct sigevent n, *p = NULL; + if (u_notification) { + if (copy_from_user(&n, u_notification, sizeof(struct sigevent))) + return -EFAULT; + p = &n; + } + return do_mq_notify(mqdes, p); +} + +static int do_mq_getsetattr(int mqdes, struct mq_attr *new, struct mq_attr *old) { - int ret; - struct mq_attr mqstat, omqstat; struct fd f; struct inode *inode; struct mqueue_inode_info *info; - if (u_mqstat != NULL) { - if (copy_from_user(&mqstat, u_mqstat, sizeof(struct mq_attr))) - return -EFAULT; - if (mqstat.mq_flags & (~O_NONBLOCK)) - return -EINVAL; - } + if (new && (new->mq_flags & (~O_NONBLOCK))) + return -EINVAL; f = fdget(mqdes); - if (!f.file) { - ret = -EBADF; - goto out; - } + if (!f.file) + return -EBADF; - inode = file_inode(f.file); if (unlikely(f.file->f_op != &mqueue_file_operations)) { - ret = -EBADF; - goto out_fput; + fdput(f); + return -EBADF; } + + inode = file_inode(f.file); info = MQUEUE_I(inode); spin_lock(&info->lock); - omqstat = info->attr; - omqstat.mq_flags = f.file->f_flags & O_NONBLOCK; - if (u_mqstat) { - audit_mq_getsetattr(mqdes, &mqstat); + if (old) { + *old = info->attr; + old->mq_flags = f.file->f_flags & O_NONBLOCK; + } + if (new) { + audit_mq_getsetattr(mqdes, new); spin_lock(&f.file->f_lock); - if (mqstat.mq_flags & O_NONBLOCK) + if (new->mq_flags & O_NONBLOCK) f.file->f_flags |= O_NONBLOCK; else f.file->f_flags &= ~O_NONBLOCK; @@ -1367,17 +1389,168 @@ SYSCALL_DEFINE3(mq_getsetattr, mqd_t, mqdes, } spin_unlock(&info->lock); + fdput(f); + return 0; +} - ret = 0; - if (u_omqstat != NULL && copy_to_user(u_omqstat, &omqstat, - sizeof(struct mq_attr))) - ret = -EFAULT; +SYSCALL_DEFINE3(mq_getsetattr, mqd_t, mqdes, + const struct mq_attr __user *, u_mqstat, + struct mq_attr __user *, u_omqstat) +{ + int ret; + struct mq_attr mqstat, omqstat; + struct mq_attr *new = NULL, *old = NULL; -out_fput: - fdput(f); -out: - return ret; + if (u_mqstat) { + new = &mqstat; + if (copy_from_user(new, u_mqstat, sizeof(struct mq_attr))) + return -EFAULT; + } + if (u_omqstat) + old = &omqstat; + + ret = do_mq_getsetattr(mqdes, new, old); + if (ret || !old) + return ret; + + if (copy_to_user(u_omqstat, old, sizeof(struct mq_attr))) + return -EFAULT; + return 0; +} + +#ifdef CONFIG_COMPAT + +struct compat_mq_attr { + compat_long_t mq_flags; /* message queue flags */ + compat_long_t mq_maxmsg; /* maximum number of messages */ + compat_long_t mq_msgsize; /* maximum message size */ + compat_long_t mq_curmsgs; /* number of messages currently queued */ + compat_long_t __reserved[4]; /* ignored for input, zeroed for output */ +}; + +static inline int get_compat_mq_attr(struct mq_attr *attr, + const struct compat_mq_attr __user *uattr) +{ + struct compat_mq_attr v; + + if (copy_from_user(&v, uattr, sizeof(*uattr))) + return -EFAULT; + + memset(attr, 0, sizeof(*attr)); + attr->mq_flags = v.mq_flags; + attr->mq_maxmsg = v.mq_maxmsg; + attr->mq_msgsize = v.mq_msgsize; + attr->mq_curmsgs = v.mq_curmsgs; + return 0; +} + +static inline int put_compat_mq_attr(const struct mq_attr *attr, + struct compat_mq_attr __user *uattr) +{ + struct compat_mq_attr v; + + memset(&v, 0, sizeof(v)); + v.mq_flags = attr->mq_flags; + v.mq_maxmsg = attr->mq_maxmsg; + v.mq_msgsize = attr->mq_msgsize; + v.mq_curmsgs = attr->mq_curmsgs; + if (copy_to_user(uattr, &v, sizeof(*uattr))) + return -EFAULT; + return 0; +} + +COMPAT_SYSCALL_DEFINE4(mq_open, const char __user *, u_name, + int, oflag, compat_mode_t, mode, + struct compat_mq_attr __user *, u_attr) +{ + struct mq_attr attr, *p = NULL; + if (u_attr && oflag & O_CREAT) { + p = &attr; + if (get_compat_mq_attr(&attr, u_attr)) + return -EFAULT; + } + return do_mq_open(u_name, oflag, mode, p); +} + +static int compat_prepare_timeout(const struct compat_timespec __user *p, + struct timespec *ts) +{ + if (compat_get_timespec(ts, p)) + return -EFAULT; + if (!timespec_valid(ts)) + return -EINVAL; + return 0; +} + +COMPAT_SYSCALL_DEFINE5(mq_timedsend, mqd_t, mqdes, + const char __user *, u_msg_ptr, + compat_size_t, msg_len, unsigned int, msg_prio, + const struct compat_timespec __user *, u_abs_timeout) +{ + struct timespec ts, *p = NULL; + if (u_abs_timeout) { + int res = compat_prepare_timeout(u_abs_timeout, &ts); + if (res) + return res; + p = &ts; + } + return do_mq_timedsend(mqdes, u_msg_ptr, msg_len, msg_prio, p); +} + +COMPAT_SYSCALL_DEFINE5(mq_timedreceive, mqd_t, mqdes, + char __user *, u_msg_ptr, + compat_size_t, msg_len, unsigned int __user *, u_msg_prio, + const struct compat_timespec __user *, u_abs_timeout) +{ + struct timespec ts, *p = NULL; + if (u_abs_timeout) { + int res = compat_prepare_timeout(u_abs_timeout, &ts); + if (res) + return res; + p = &ts; + } + return do_mq_timedreceive(mqdes, u_msg_ptr, msg_len, u_msg_prio, p); +} + +COMPAT_SYSCALL_DEFINE2(mq_notify, mqd_t, mqdes, + const struct compat_sigevent __user *, u_notification) +{ + struct sigevent n, *p = NULL; + if (u_notification) { + if (get_compat_sigevent(&n, u_notification)) + return -EFAULT; + if (n.sigev_notify == SIGEV_THREAD) + n.sigev_value.sival_ptr = compat_ptr(n.sigev_value.sival_int); + p = &n; + } + return do_mq_notify(mqdes, p); +} + +COMPAT_SYSCALL_DEFINE3(mq_getsetattr, mqd_t, mqdes, + const struct compat_mq_attr __user *, u_mqstat, + struct compat_mq_attr __user *, u_omqstat) +{ + int ret; + struct mq_attr mqstat, omqstat; + struct mq_attr *new = NULL, *old = NULL; + + if (u_mqstat) { + new = &mqstat; + if (get_compat_mq_attr(new, u_mqstat)) + return -EFAULT; + } + if (u_omqstat) + old = &omqstat; + + ret = do_mq_getsetattr(mqdes, new, old); + if (ret || !old) + return ret; + + if (put_compat_mq_attr(old, u_omqstat)) + return -EFAULT; + return 0; } +#endif static const struct inode_operations mqueue_dir_inode_operations = { .lookup = simple_lookup, |