diff options
author | jamie <jamie@FreeBSD.org> | 2009-05-27 14:11:23 +0000 |
---|---|---|
committer | jamie <jamie@FreeBSD.org> | 2009-05-27 14:11:23 +0000 |
commit | a013e0afcbb44052a86a7977277d669d8883b7e7 (patch) | |
tree | b7f782d79e61a1bd80655a068684cb0fd9f39922 /sys/compat | |
parent | 6e53147404a7f4fb4173694bc812d9d23efd9fef (diff) | |
download | FreeBSD-src-a013e0afcbb44052a86a7977277d669d8883b7e7.zip FreeBSD-src-a013e0afcbb44052a86a7977277d669d8883b7e7.tar.gz |
Add hierarchical jails. A jail may further virtualize its environment
by creating a child jail, which is visible to that jail and to any
parent jails. Child jails may be restricted more than their parents,
but never less. Jail names reflect this hierarchy, being MIB-style
dot-separated strings.
Every thread now points to a jail, the default being prison0, which
contains information about the physical system. Prison0's root
directory is the same as rootvnode; its hostname is the same as the
global hostname, and its securelevel replaces the global securelevel.
Note that the variable "securelevel" has actually gone away, which
should not cause any problems for code that properly uses
securelevel_gt() and securelevel_ge().
Some jail-related permissions that were kept in global variables and
set via sysctls are now per-jail settings. The sysctls still exist for
backward compatibility, used only by the now-deprecated jail(2) system
call.
Approved by: bz (mentor)
Diffstat (limited to 'sys/compat')
-rw-r--r-- | sys/compat/freebsd32/freebsd32_misc.c | 164 | ||||
-rw-r--r-- | sys/compat/linux/linux_mib.c | 232 |
2 files changed, 107 insertions, 289 deletions
diff --git a/sys/compat/freebsd32/freebsd32_misc.c b/sys/compat/freebsd32/freebsd32_misc.c index a8f9b55..9301b8d 100644 --- a/sys/compat/freebsd32/freebsd32_misc.c +++ b/sys/compat/freebsd32/freebsd32_misc.c @@ -112,8 +112,6 @@ CTASSERT(sizeof(struct msghdr32) == 28); CTASSERT(sizeof(struct stat32) == 96); CTASSERT(sizeof(struct sigaction32) == 24); -extern int jail_max_af_ips; - static int freebsd32_kevent_copyout(void *arg, struct kevent *kevp, int count); static int freebsd32_kevent_copyin(void *arg, struct kevent *kevp, int count); @@ -2044,17 +2042,9 @@ freebsd32_sysctl(struct thread *td, struct freebsd32_sysctl_args *uap) int freebsd32_jail(struct thread *td, struct freebsd32_jail_args *uap) { - struct iovec optiov[10]; - struct uio opt; - char *u_path, *u_hostname, *u_name; -#ifdef INET - struct in_addr *u_ip4; -#endif -#ifdef INET6 - struct in6_addr *u_ip6; -#endif uint32_t version; int error; + struct jail j; error = copyin(uap->jail, &version, sizeof(uint32_t)); if (error) @@ -2066,45 +2056,14 @@ freebsd32_jail(struct thread *td, struct freebsd32_jail_args *uap) /* FreeBSD single IPv4 jails. */ struct jail32_v0 j32_v0; + bzero(&j, sizeof(struct jail)); error = copyin(uap->jail, &j32_v0, sizeof(struct jail32_v0)); if (error) return (error); - u_path = malloc(MAXPATHLEN + MAXHOSTNAMELEN, M_TEMP, M_WAITOK); - u_hostname = u_path + MAXPATHLEN; - opt.uio_iov = optiov; - opt.uio_iovcnt = 4; - opt.uio_offset = -1; - opt.uio_resid = -1; - opt.uio_segflg = UIO_SYSSPACE; - opt.uio_rw = UIO_READ; - opt.uio_td = td; - optiov[0].iov_base = "path"; - optiov[0].iov_len = sizeof("path"); - optiov[1].iov_base = u_path; - error = copyinstr(PTRIN(j32_v0.path), u_path, MAXPATHLEN, - &optiov[1].iov_len); - if (error) { - free(u_path, M_TEMP); - return (error); - } - optiov[2].iov_base = "host.hostname"; - optiov[2].iov_len = sizeof("host.hostname"); - optiov[3].iov_base = u_hostname; - error = copyinstr(PTRIN(j32_v0.hostname), u_hostname, - MAXHOSTNAMELEN, &optiov[3].iov_len); - if (error) { - free(u_path, M_TEMP); - return (error); - } -#ifdef INET - optiov[opt.uio_iovcnt].iov_base = "ip4.addr"; - optiov[opt.uio_iovcnt].iov_len = sizeof("ip4.addr"); - opt.uio_iovcnt++; - optiov[opt.uio_iovcnt].iov_base = &j32_v0.ip_number; - j32_v0.ip_number = htonl(j32_v0.ip_number); - optiov[opt.uio_iovcnt].iov_len = sizeof(j32_v0.ip_number); - opt.uio_iovcnt++; -#endif + CP(j32_v0, j, version); + PTRIN_CP(j32_v0, j, path); + PTRIN_CP(j32_v0, j, hostname); + j.ip4s = j32_v0.ip_number; break; } @@ -2119,109 +2078,18 @@ freebsd32_jail(struct thread *td, struct freebsd32_jail_args *uap) { /* FreeBSD multi-IPv4/IPv6,noIP jails. */ struct jail32 j32; - size_t tmplen; error = copyin(uap->jail, &j32, sizeof(struct jail32)); if (error) return (error); - tmplen = MAXPATHLEN + MAXHOSTNAMELEN + MAXHOSTNAMELEN; -#ifdef INET - if (j32.ip4s > jail_max_af_ips) - return (EINVAL); - tmplen += j32.ip4s * sizeof(struct in_addr); -#else - if (j32.ip4s > 0) - return (EINVAL); -#endif -#ifdef INET6 - if (j32.ip6s > jail_max_af_ips) - return (EINVAL); - tmplen += j32.ip6s * sizeof(struct in6_addr); -#else - if (j32.ip6s > 0) - return (EINVAL); -#endif - u_path = malloc(tmplen, M_TEMP, M_WAITOK); - u_hostname = u_path + MAXPATHLEN; - u_name = u_hostname + MAXHOSTNAMELEN; -#ifdef INET - u_ip4 = (struct in_addr *)(u_name + MAXHOSTNAMELEN); -#endif -#ifdef INET6 -#ifdef INET - u_ip6 = (struct in6_addr *)(u_ip4 + j32.ip4s); -#else - u_ip6 = (struct in6_addr *)(u_name + MAXHOSTNAMELEN); -#endif -#endif - opt.uio_iov = optiov; - opt.uio_iovcnt = 4; - opt.uio_offset = -1; - opt.uio_resid = -1; - opt.uio_segflg = UIO_SYSSPACE; - opt.uio_rw = UIO_READ; - opt.uio_td = td; - optiov[0].iov_base = "path"; - optiov[0].iov_len = sizeof("path"); - optiov[1].iov_base = u_path; - error = copyinstr(PTRIN(j32.path), u_path, MAXPATHLEN, - &optiov[1].iov_len); - if (error) { - free(u_path, M_TEMP); - return (error); - } - optiov[2].iov_base = "host.hostname"; - optiov[2].iov_len = sizeof("host.hostname"); - optiov[3].iov_base = u_hostname; - error = copyinstr(PTRIN(j32.hostname), u_hostname, - MAXHOSTNAMELEN, &optiov[3].iov_len); - if (error) { - free(u_path, M_TEMP); - return (error); - } - if (PTRIN(j32.jailname) != NULL) { - optiov[opt.uio_iovcnt].iov_base = "name"; - optiov[opt.uio_iovcnt].iov_len = sizeof("name"); - opt.uio_iovcnt++; - optiov[opt.uio_iovcnt].iov_base = u_name; - error = copyinstr(PTRIN(j32.jailname), u_name, - MAXHOSTNAMELEN, &optiov[opt.uio_iovcnt].iov_len); - if (error) { - free(u_path, M_TEMP); - return (error); - } - opt.uio_iovcnt++; - } -#ifdef INET - optiov[opt.uio_iovcnt].iov_base = "ip4.addr"; - optiov[opt.uio_iovcnt].iov_len = sizeof("ip4.addr"); - opt.uio_iovcnt++; - optiov[opt.uio_iovcnt].iov_base = u_ip4; - optiov[opt.uio_iovcnt].iov_len = - j32.ip4s * sizeof(struct in_addr); - error = copyin(PTRIN(j32.ip4), u_ip4, - optiov[opt.uio_iovcnt].iov_len); - if (error) { - free(u_path, M_TEMP); - return (error); - } - opt.uio_iovcnt++; -#endif -#ifdef INET6 - optiov[opt.uio_iovcnt].iov_base = "ip6.addr"; - optiov[opt.uio_iovcnt].iov_len = sizeof("ip6.addr"); - opt.uio_iovcnt++; - optiov[opt.uio_iovcnt].iov_base = u_ip6; - optiov[opt.uio_iovcnt].iov_len = - j32.ip6s * sizeof(struct in6_addr); - error = copyin(PTRIN(j32.ip6), u_ip6, - optiov[opt.uio_iovcnt].iov_len); - if (error) { - free(u_path, M_TEMP); - return (error); - } - opt.uio_iovcnt++; -#endif + CP(j32, j, version); + PTRIN_CP(j32, j, path); + PTRIN_CP(j32, j, hostname); + PTRIN_CP(j32, j, jailname); + CP(j32, j, ip4s); + CP(j32, j, ip6s); + PTRIN_CP(j32, j, ip4); + PTRIN_CP(j32, j, ip6); break; } @@ -2229,9 +2097,7 @@ freebsd32_jail(struct thread *td, struct freebsd32_jail_args *uap) /* Sci-Fi jails are not supported, sorry. */ return (EINVAL); } - error = kern_jail_set(td, &opt, JAIL_CREATE | JAIL_ATTACH); - free(u_path, M_TEMP); - return (error); + return (kern_jail(td, &j)); } int diff --git a/sys/compat/linux/linux_mib.c b/sys/compat/linux/linux_mib.c index f3d5ec7..58af9c5 100644 --- a/sys/compat/linux/linux_mib.c +++ b/sys/compat/linux/linux_mib.c @@ -57,16 +57,18 @@ struct linux_prison { int pr_osrel; }; +static struct linux_prison lprison0 = { + .pr_osname = "Linux", + .pr_osrelease = "2.6.16", + .pr_oss_version = 0x030600, + .pr_osrel = 2006016 +}; + static unsigned linux_osd_jail_slot; SYSCTL_NODE(_compat, OID_AUTO, linux, CTLFLAG_RW, 0, "Linux mode"); -static struct mtx osname_lock; -MTX_SYSINIT(linux_osname, &osname_lock, "linux osname", MTX_DEF); - -static char linux_osname[LINUX_MAX_UTSNAME] = "Linux"; - static int linux_sysctl_osname(SYSCTL_HANDLER_ARGS) { @@ -86,9 +88,6 @@ SYSCTL_PROC(_compat_linux, OID_AUTO, osname, 0, 0, linux_sysctl_osname, "A", "Linux kernel OS name"); -static char linux_osrelease[LINUX_MAX_UTSNAME] = "2.6.16"; -static int linux_osrel = 2006016; - static int linux_sysctl_osrelease(SYSCTL_HANDLER_ARGS) { @@ -108,8 +107,6 @@ SYSCTL_PROC(_compat_linux, OID_AUTO, osrelease, 0, 0, linux_sysctl_osrelease, "A", "Linux kernel OS release"); -static int linux_oss_version = 0x030600; - static int linux_sysctl_oss_version(SYSCTL_HANDLER_ARGS) { @@ -161,69 +158,74 @@ linux_map_osrel(char *osrelease, int *osrel) } /* - * Returns holding the prison mutex if return non-NULL. + * Find a prison with Linux info. + * Return the Linux info and the (locked) prison. */ static struct linux_prison * -linux_get_prison(struct thread *td, struct prison **prp) +linux_find_prison(struct prison *spr, struct prison **prp) { struct prison *pr; struct linux_prison *lpr; - KASSERT(td == curthread, ("linux_get_prison() called on !curthread")); - *prp = pr = td->td_ucred->cr_prison; - if (pr == NULL || !linux_osd_jail_slot) - return (NULL); - mtx_lock(&pr->pr_mtx); - lpr = osd_jail_get(pr, linux_osd_jail_slot); - if (lpr == NULL) + if (!linux_osd_jail_slot) + /* In case osd_register failed. */ + spr = &prison0; + for (pr = spr;; pr = pr->pr_parent) { + mtx_lock(&pr->pr_mtx); + lpr = (pr == &prison0) + ? &lprison0 + : osd_jail_get(pr, linux_osd_jail_slot); + if (lpr != NULL) + break; mtx_unlock(&pr->pr_mtx); + } + *prp = pr; return (lpr); } /* - * Ensure a prison has its own Linux info. The prison should be locked on - * entrance and will be locked on exit (though it may get unlocked in the - * interrim). + * Ensure a prison has its own Linux info. If lprp is non-null, point it to + * the Linux info and lock the prison. */ static int linux_alloc_prison(struct prison *pr, struct linux_prison **lprp) { + struct prison *ppr; struct linux_prison *lpr, *nlpr; int error; /* If this prison already has Linux info, return that. */ error = 0; - mtx_assert(&pr->pr_mtx, MA_OWNED); - lpr = osd_jail_get(pr, linux_osd_jail_slot); - if (lpr != NULL) + lpr = linux_find_prison(pr, &ppr); + if (ppr == pr) goto done; /* * Allocate a new info record. Then check again, in case something * changed during the allocation. */ - mtx_unlock(&pr->pr_mtx); + mtx_unlock(&ppr->pr_mtx); nlpr = malloc(sizeof(struct linux_prison), M_PRISON, M_WAITOK); - mtx_lock(&pr->pr_mtx); - lpr = osd_jail_get(pr, linux_osd_jail_slot); - if (lpr != NULL) { + lpr = linux_find_prison(pr, &ppr); + if (ppr == pr) { free(nlpr, M_PRISON); goto done; } + /* Inherit the initial values from the ancestor. */ + mtx_lock(&pr->pr_mtx); error = osd_jail_set(pr, linux_osd_jail_slot, nlpr); - if (error) - free(nlpr, M_PRISON); - else { + if (error == 0) { + bcopy(lpr, nlpr, sizeof(*lpr)); lpr = nlpr; - mtx_lock(&osname_lock); - strncpy(lpr->pr_osname, linux_osname, LINUX_MAX_UTSNAME); - strncpy(lpr->pr_osrelease, linux_osrelease, LINUX_MAX_UTSNAME); - lpr->pr_oss_version = linux_oss_version; - lpr->pr_osrel = linux_osrel; - mtx_unlock(&osname_lock); + } else { + free(nlpr, M_PRISON); + lpr = NULL; } -done: + mtx_unlock(&ppr->pr_mtx); + done: if (lprp != NULL) *lprp = lpr; + else + mtx_unlock(&pr->pr_mtx); return (error); } @@ -233,7 +235,6 @@ done: static int linux_prison_create(void *obj, void *data) { - int error; struct prison *pr = obj; struct vfsoptlist *opts = data; @@ -243,10 +244,7 @@ linux_prison_create(void *obj, void *data) * Inherit a prison's initial values from its parent * (different from NULL which also inherits changes). */ - mtx_lock(&pr->pr_mtx); - error = linux_alloc_prison(pr, NULL); - mtx_unlock(&pr->pr_mtx); - return (error); + return linux_alloc_prison(pr, NULL); } static int @@ -254,7 +252,7 @@ linux_prison_check(void *obj __unused, void *data) { struct vfsoptlist *opts = data; char *osname, *osrelease; - int error, len, oss_version; + int error, len, osrel, oss_version; /* Check that the parameters are correct. */ (void)vfs_flagopt(opts, "linux", NULL, 0); @@ -280,6 +278,11 @@ linux_prison_check(void *obj __unused, void *data) vfs_opterror(opts, "linux.osrelease too long"); return (ENAMETOOLONG); } + error = linux_map_osrel(osrelease, &osrel); + if (error != 0) { + vfs_opterror(opts, "linux.osrelease format error"); + return (error); + } } error = vfs_copyopt(opts, "linux.oss_version", &oss_version, sizeof(oss_version)); @@ -310,7 +313,7 @@ linux_prison_set(void *obj, void *data) yeslinux = 1; error = vfs_copyopt(opts, "linux.oss_version", &oss_version, sizeof(oss_version)); - gotversion = error == 0; + gotversion = (error == 0); yeslinux |= gotversion; if (nolinux) { /* "nolinux": inherit the parent's Linux info. */ @@ -322,7 +325,6 @@ linux_prison_set(void *obj, void *data) * "linux" or "linux.*": * the prison gets its own Linux info. */ - mtx_lock(&pr->pr_mtx); error = linux_alloc_prison(pr, &lpr); if (error) { mtx_unlock(&pr->pr_mtx); @@ -360,14 +362,16 @@ static int linux_prison_get(void *obj, void *data) { struct linux_prison *lpr; + struct prison *ppr; struct prison *pr = obj; struct vfsoptlist *opts = data; int error, i; - mtx_lock(&pr->pr_mtx); - /* Tell whether this prison has its own Linux info. */ - lpr = osd_jail_get(pr, linux_osd_jail_slot); - i = lpr != NULL; + static int version0; + + /* See if this prison is the one with the Linux info. */ + lpr = linux_find_prison(pr, &ppr); + i = (ppr == pr); error = vfs_setopt(opts, "linux", &i, sizeof(i)); if (error != 0 && error != ENOENT) goto done; @@ -375,39 +379,37 @@ linux_prison_get(void *obj, void *data) error = vfs_setopt(opts, "nolinux", &i, sizeof(i)); if (error != 0 && error != ENOENT) goto done; - /* - * It's kind of bogus to give the root info, but leave it to the caller - * to check the above flag. - */ - if (lpr != NULL) { - error = vfs_setopts(opts, "linux.osname", lpr->pr_osname); + if (i) { + /* + * If this prison is inheriting its Linux info, report + * empty/zero parameters. + */ + error = vfs_setopts(opts, "linux.osname", ""); if (error != 0 && error != ENOENT) goto done; - error = vfs_setopts(opts, "linux.osrelease", lpr->pr_osrelease); + error = vfs_setopts(opts, "linux.osrelease", ""); if (error != 0 && error != ENOENT) goto done; - error = vfs_setopt(opts, "linux.oss_version", - &lpr->pr_oss_version, sizeof(lpr->pr_oss_version)); + error = vfs_setopt(opts, "linux.oss_version", &version0, + sizeof(lpr->pr_oss_version)); if (error != 0 && error != ENOENT) goto done; } else { - mtx_lock(&osname_lock); - error = vfs_setopts(opts, "linux.osname", linux_osname); + error = vfs_setopts(opts, "linux.osname", lpr->pr_osname); if (error != 0 && error != ENOENT) goto done; - error = vfs_setopts(opts, "linux.osrelease", linux_osrelease); + error = vfs_setopts(opts, "linux.osrelease", lpr->pr_osrelease); if (error != 0 && error != ENOENT) goto done; error = vfs_setopt(opts, "linux.oss_version", - &linux_oss_version, sizeof(linux_oss_version)); + &lpr->pr_oss_version, sizeof(lpr->pr_oss_version)); if (error != 0 && error != ENOENT) goto done; - mtx_unlock(&osname_lock); } error = 0; done: - mtx_unlock(&pr->pr_mtx); + mtx_unlock(&ppr->pr_mtx); return (error); } @@ -434,11 +436,8 @@ linux_osd_jail_register(void) if (linux_osd_jail_slot > 0) { /* Copy the system linux info to any current prisons. */ sx_xlock(&allprison_lock); - TAILQ_FOREACH(pr, &allprison, pr_list) { - mtx_lock(&pr->pr_mtx); + TAILQ_FOREACH(pr, &allprison, pr_list) (void)linux_alloc_prison(pr, NULL); - mtx_unlock(&pr->pr_mtx); - } sx_xunlock(&allprison_lock); } } @@ -457,15 +456,9 @@ linux_get_osname(struct thread *td, char *dst) struct prison *pr; struct linux_prison *lpr; - lpr = linux_get_prison(td, &pr); - if (lpr != NULL) { - bcopy(lpr->pr_osname, dst, LINUX_MAX_UTSNAME); - mtx_unlock(&pr->pr_mtx); - } else { - mtx_lock(&osname_lock); - bcopy(linux_osname, dst, LINUX_MAX_UTSNAME); - mtx_unlock(&osname_lock); - } + lpr = linux_find_prison(td->td_ucred->cr_prison, &pr); + bcopy(lpr->pr_osname, dst, LINUX_MAX_UTSNAME); + mtx_unlock(&pr->pr_mtx); } int @@ -474,16 +467,9 @@ linux_set_osname(struct thread *td, char *osname) struct prison *pr; struct linux_prison *lpr; - lpr = linux_get_prison(td, &pr); - if (lpr != NULL) { - strlcpy(lpr->pr_osname, osname, LINUX_MAX_UTSNAME); - mtx_unlock(&pr->pr_mtx); - } else { - mtx_lock(&osname_lock); - strcpy(linux_osname, osname); - mtx_unlock(&osname_lock); - } - + lpr = linux_find_prison(td->td_ucred->cr_prison, &pr); + strlcpy(lpr->pr_osname, osname, LINUX_MAX_UTSNAME); + mtx_unlock(&pr->pr_mtx); return (0); } @@ -493,15 +479,9 @@ linux_get_osrelease(struct thread *td, char *dst) struct prison *pr; struct linux_prison *lpr; - lpr = linux_get_prison(td, &pr); - if (lpr != NULL) { - bcopy(lpr->pr_osrelease, dst, LINUX_MAX_UTSNAME); - mtx_unlock(&pr->pr_mtx); - } else { - mtx_lock(&osname_lock); - bcopy(linux_osrelease, dst, LINUX_MAX_UTSNAME); - mtx_unlock(&osname_lock); - } + lpr = linux_find_prison(td->td_ucred->cr_prison, &pr); + bcopy(lpr->pr_osrelease, dst, LINUX_MAX_UTSNAME); + mtx_unlock(&pr->pr_mtx); } int @@ -511,12 +491,9 @@ linux_kernver(struct thread *td) struct linux_prison *lpr; int osrel; - lpr = linux_get_prison(td, &pr); - if (lpr != NULL) { - osrel = lpr->pr_osrel; - mtx_unlock(&pr->pr_mtx); - } else - osrel = linux_osrel; + lpr = linux_find_prison(td->td_ucred->cr_prison, &pr); + osrel = lpr->pr_osrel; + mtx_unlock(&pr->pr_mtx); return (osrel); } @@ -527,27 +504,12 @@ linux_set_osrelease(struct thread *td, char *osrelease) struct linux_prison *lpr; int error; - lpr = linux_get_prison(td, &pr); - if (lpr != NULL) { - error = linux_map_osrel(osrelease, &lpr->pr_osrel); - if (error) { - mtx_unlock(&pr->pr_mtx); - return (error); - } + lpr = linux_find_prison(td->td_ucred->cr_prison, &pr); + error = linux_map_osrel(osrelease, &lpr->pr_osrel); + if (error == 0) strlcpy(lpr->pr_osrelease, osrelease, LINUX_MAX_UTSNAME); - mtx_unlock(&pr->pr_mtx); - } else { - mtx_lock(&osname_lock); - error = linux_map_osrel(osrelease, &linux_osrel); - if (error) { - mtx_unlock(&osname_lock); - return (error); - } - strcpy(linux_osrelease, osrelease); - mtx_unlock(&osname_lock); - } - - return (0); + mtx_unlock(&pr->pr_mtx); + return (error); } int @@ -557,12 +519,9 @@ linux_get_oss_version(struct thread *td) struct linux_prison *lpr; int version; - lpr = linux_get_prison(td, &pr); - if (lpr != NULL) { - version = lpr->pr_oss_version; - mtx_unlock(&pr->pr_mtx); - } else - version = linux_oss_version; + lpr = linux_find_prison(td->td_ucred->cr_prison, &pr); + version = lpr->pr_oss_version; + mtx_unlock(&pr->pr_mtx); return (version); } @@ -572,16 +531,9 @@ linux_set_oss_version(struct thread *td, int oss_version) struct prison *pr; struct linux_prison *lpr; - lpr = linux_get_prison(td, &pr); - if (lpr != NULL) { - lpr->pr_oss_version = oss_version; - mtx_unlock(&pr->pr_mtx); - } else { - mtx_lock(&osname_lock); - linux_oss_version = oss_version; - mtx_unlock(&osname_lock); - } - + lpr = linux_find_prison(td->td_ucred->cr_prison, &pr); + lpr->pr_oss_version = oss_version; + mtx_unlock(&pr->pr_mtx); return (0); } |