xref: /csrg-svn/sys/sparc/sunos/sun_misc.c (revision 69449)
155099Storek /*
263555Sbostic  * Copyright (c) 1992, 1993
363555Sbostic  *	The Regents of the University of California.  All rights reserved.
455099Storek  *
555099Storek  * This software was developed by the Computer Systems Engineering group
655099Storek  * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and
755099Storek  * contributed to Berkeley.
855099Storek  *
955505Sbostic  * All advertising materials mentioning features or use of this software
1055505Sbostic  * must display the following acknowledgement:
1155505Sbostic  *	This product includes software developed by the University of
1258995Storek  *	California, Lawrence Berkeley Laboratory.
1355505Sbostic  *
1455099Storek  * %sccs.include.redist.c%
1555099Storek  *
16*69449Smckusick  *	@(#)sun_misc.c	8.5 (Berkeley) 05/14/95
1755099Storek  *
1858995Storek  * from: $Header: sun_misc.c,v 1.16 93/04/07 02:46:27 torek Exp $
1955099Storek  */
2055099Storek 
2155099Storek /*
2255099Storek  * SunOS compatibility module.
2355099Storek  *
2455099Storek  * SunOS system calls that are implemented differently in BSD are
2555099Storek  * handled here.
2655099Storek  */
2755099Storek 
2856543Sbostic #include <sys/param.h>
2958995Storek #include <sys/systm.h>
3058995Storek #include <sys/dirent.h>
3156543Sbostic #include <sys/proc.h>
3256543Sbostic #include <sys/file.h>
3356543Sbostic #include <sys/filedesc.h>
3456543Sbostic #include <sys/ioctl.h>
3556543Sbostic #include <sys/malloc.h>
3656543Sbostic #include <sys/mbuf.h>
3756543Sbostic #include <sys/mman.h>
3856543Sbostic #include <sys/mount.h>
3956543Sbostic #include <sys/resource.h>
4056543Sbostic #include <sys/resourcevar.h>
4156543Sbostic #include <sys/signal.h>
4256543Sbostic #include <sys/signalvar.h>
4356543Sbostic #include <sys/socket.h>
4456543Sbostic #include <sys/vnode.h>
4556543Sbostic #include <sys/uio.h>
4656543Sbostic #include <sys/wait.h>
4755099Storek 
4856543Sbostic #include <miscfs/specfs/specdev.h>
4955174Storek 
5056543Sbostic #include <vm/vm.h>
5155099Storek 
5255099Storek struct sun_wait4_args {
5355099Storek 	int	pid;
5455099Storek 	int	*status;
5555099Storek 	int	options;
5655099Storek 	struct	rusage *rusage;
5755099Storek };
5855099Storek sun_wait4(p, uap, retval)
5955099Storek 	struct proc *p;
6055099Storek 	struct sun_wait4_args *uap;
6155099Storek 	int *retval;
6255099Storek {
6355099Storek 
6455099Storek 	if (uap->pid == 0)
6555099Storek 		uap->pid = WAIT_ANY;
6655099Storek 	return (wait4(p, uap, retval));
6755099Storek }
6855099Storek 
6955099Storek struct sun_creat_args {
7055099Storek 	char	*fname;
7155099Storek 	int	fmode;
7255099Storek };
7355099Storek sun_creat(p, uap, retval)
7455099Storek 	struct proc *p;
7555099Storek 	struct sun_creat_args *uap;
7655099Storek 	int *retval;
7755099Storek {
7855099Storek 	struct args {
7955099Storek 		char	*fname;
8055099Storek 		int	mode;
8155099Storek 		int	crtmode;
8255099Storek 	} openuap;
8355099Storek 
8455099Storek 	openuap.fname = uap->fname;
8555099Storek 	openuap.crtmode = uap->fmode;
8655099Storek 	openuap.mode = O_WRONLY | O_CREAT | O_TRUNC;
8755099Storek 	return (open(p, &openuap, retval));
8855099Storek }
8955099Storek 
9055099Storek struct sun_execv_args {
9155099Storek 	char	*fname;
9255099Storek 	char	**argp;
9355099Storek 	char	**envp;		/* pseudo */
9455099Storek };
9555099Storek sun_execv(p, uap, retval)
9655099Storek 	struct proc *p;
9755099Storek 	struct sun_execv_args *uap;
9855099Storek 	int *retval;
9955099Storek {
10055099Storek 
10155099Storek 	uap->envp = NULL;
10255099Storek 	return (execve(p, uap, retval));
10355099Storek }
10455099Storek 
10555099Storek struct sun_omsync_args {
10655099Storek 	caddr_t	addr;
10755099Storek 	int	len;
10855099Storek 	int	flags;
10955099Storek };
11055099Storek sun_omsync(p, uap, retval)
11155099Storek 	struct proc *p;
11255099Storek 	struct sun_omsync_args *uap;
11355099Storek 	int *retval;
11455099Storek {
11555099Storek 
11655099Storek 	if (uap->flags)
11755099Storek 		return (EINVAL);
11855099Storek 	return (msync(p, uap, retval));
11955099Storek }
12055099Storek 
12155099Storek struct sun_unmount_args {
12255099Storek 	char	*name;
12355099Storek 	int	flags;	/* pseudo */
12455099Storek };
12555099Storek sun_unmount(p, uap, retval)
12655099Storek 	struct proc *p;
12755099Storek 	struct sun_unmount_args *uap;
12855099Storek 	int *retval;
12955099Storek {
13055099Storek 
13158995Storek 	uap->flags = 0;
13255099Storek 	return (unmount(p, uap, retval));
13355099Storek }
13455099Storek 
13555099Storek static int
gettype(tptr)13655099Storek gettype(tptr)
13755099Storek 	int *tptr;
13855099Storek {
13955099Storek 	int type, error;
14055099Storek 	char in[20];
14155099Storek 
14255099Storek 	if (error = copyinstr((caddr_t)*tptr, in, sizeof in, (u_int *)0))
14355099Storek 		return (error);
14455099Storek 	if (strcmp(in, "4.2") == 0 || strcmp(in, "ufs") == 0)
14568714Smckusick 		type = 1;	/* old MOUNT_UFS */
14655099Storek 	else if (strcmp(in, "nfs") == 0)
14768714Smckusick 		type = 2;	/* old MOUNT_NFS */
14855099Storek 	else
14955099Storek 		return (EINVAL);
15055099Storek 	*tptr = type;
15155099Storek 	return (0);
15255099Storek }
15355099Storek 
15455099Storek #define	SUNM_RDONLY	0x01	/* mount fs read-only */
15555099Storek #define	SUNM_NOSUID	0x02	/* mount fs with setuid disallowed */
15655099Storek #define	SUNM_NEWTYPE	0x04	/* type is string (char *), not int */
15755099Storek #define	SUNM_GRPID	0x08	/* (bsd semantics; ignored) */
15855099Storek #define	SUNM_REMOUNT	0x10	/* update existing mount */
15955099Storek #define	SUNM_NOSUB	0x20	/* prevent submounts (rejected) */
16055099Storek #define	SUNM_MULTI	0x40	/* (ignored) */
16155099Storek #define	SUNM_SYS5	0x80	/* Sys 5-specific semantics (rejected) */
16255099Storek 
16355099Storek struct sun_mount_args {
16455099Storek 	int	type;
16555099Storek 	char	*dir;
16655099Storek 	int	flags;
16755099Storek 	caddr_t	data;
16855099Storek };
16955099Storek sun_mount(p, uap, retval)
17055099Storek 	struct proc *p;
17155099Storek 	struct sun_mount_args *uap;
17255099Storek 	int *retval;
17355099Storek {
17455099Storek 	int oflags = uap->flags, nflags, error;
17555099Storek 
17655099Storek 	if (oflags & (SUNM_NOSUB | SUNM_SYS5))
17755099Storek 		return (EINVAL);
17855099Storek 	if (oflags & SUNM_NEWTYPE && (error = gettype(&uap->type)))
17955099Storek 		return (error);
18055099Storek 	nflags = 0;
18155099Storek 	if (oflags & SUNM_RDONLY)
18255099Storek 		nflags |= MNT_RDONLY;
18355099Storek 	if (oflags & SUNM_NOSUID)
18455099Storek 		nflags |= MNT_NOSUID;
18555099Storek 	if (oflags & SUNM_REMOUNT)
18655099Storek 		nflags |= MNT_UPDATE;
18755099Storek 	uap->flags = nflags;
18855099Storek 	return (mount(p, uap, retval));
18955099Storek }
19055099Storek 
19155099Storek struct sun_sigpending_args {
19255099Storek 	int	*mask;
19355099Storek };
19455099Storek sun_sigpending(p, uap, retval)
19555099Storek 	struct proc *p;
19655099Storek 	struct sun_sigpending_args *uap;
19755099Storek 	int *retval;
19855099Storek {
19964635Sbostic 	int mask;
20055099Storek 
20164635Sbostic 	mask = p->p_siglist & p->p_sigmask;
20264635Sbostic 	return (copyout(&mask, uap->mask, sizeof(int)));
20355099Storek }
20455099Storek 
20558995Storek /*
20658995Storek  * Here is the sun layout.  (Compare the BSD layout in <sys/dirent.h>.)
20758995Storek  * We can assume big-endian, so the BSD d_type field is just the high
20858995Storek  * byte of the SunOS d_namlen field, after adjusting for the extra "long".
20958995Storek  */
21055099Storek struct sun_dirent {
21155099Storek 	long	d_off;
21255099Storek 	u_long	d_fileno;
21355099Storek 	u_short	d_reclen;
21455099Storek 	u_short	d_namlen;
21555099Storek 	char	d_name[256];
21655099Storek };
21755099Storek 
21855099Storek /*
21955099Storek  * Read Sun-style directory entries.  We suck them into kernel space so
22055099Storek  * that they can be massaged before being copied out to user code.  Like
22155099Storek  * SunOS, we squish out `empty' entries.
22255099Storek  *
22355099Storek  * This is quite ugly, but what do you expect from compatibility code?
22455099Storek  */
22555099Storek struct sun_getdents_args {
22655099Storek 	int	fd;
22755099Storek 	char	*buf;
22855099Storek 	int	nbytes;
22955099Storek };
23055099Storek sun_getdents(p, uap, retval)
23155099Storek 	struct proc *p;
23255099Storek 	register struct sun_getdents_args *uap;
23355099Storek 	int *retval;
23455099Storek {
23555099Storek 	register struct vnode *vp;
23655099Storek 	register caddr_t inp, buf;	/* BSD-format */
23755099Storek 	register int len, reclen;	/* BSD-format */
23855099Storek 	register caddr_t outp;		/* Sun-format */
23955099Storek 	register int resid;		/* Sun-format */
24055099Storek 	struct file *fp;
24155099Storek 	struct uio auio;
24255099Storek 	struct iovec aiov;
24355099Storek 	off_t off;			/* true file offset */
24455099Storek 	long soff;			/* Sun file offset */
24555099Storek 	int buflen, error, eofflag;
24658995Storek #define	BSD_DIRENT(cp) ((struct dirent *)(cp))
24755099Storek #define	SUN_RECLEN(reclen) (reclen + sizeof(long))
24855099Storek 
24955099Storek 	if ((error = getvnode(p->p_fd, uap->fd, &fp)) != 0)
25055099Storek 		return (error);
25155099Storek 	if ((fp->f_flag & FREAD) == 0)
25255099Storek 		return (EBADF);
25355099Storek 	vp = (struct vnode *)fp->f_data;
25455099Storek 	if (vp->v_type != VDIR)	/* XXX  vnode readdir op should do this */
25555099Storek 		return (EINVAL);
25655099Storek 	buflen = min(MAXBSIZE, uap->nbytes);
25755099Storek 	buf = malloc(buflen, M_TEMP, M_WAITOK);
258*69449Smckusick 	vn_lock(vp, LK_EXCLUSIVE | LK_RETRY, p)
25955099Storek 	off = fp->f_offset;
26055099Storek again:
26155099Storek 	aiov.iov_base = buf;
26255099Storek 	aiov.iov_len = buflen;
26355099Storek 	auio.uio_iov = &aiov;
26455099Storek 	auio.uio_iovcnt = 1;
26555099Storek 	auio.uio_rw = UIO_READ;
26655099Storek 	auio.uio_segflg = UIO_SYSSPACE;
26755099Storek 	auio.uio_procp = p;
26855099Storek 	auio.uio_resid = buflen;
26955099Storek 	auio.uio_offset = off;
27055099Storek 	/*
27155099Storek 	 * First we read into the malloc'ed buffer, then
27255099Storek 	 * we massage it into user space, one record at a time.
27355099Storek 	 */
27467372Smckusick 	if (error = VOP_READDIR(vp, &auio, fp->f_cred, &eofflag, (u_long *)0,0))
27555099Storek 		goto out;
27655099Storek 	inp = buf;
27755099Storek 	outp = uap->buf;
27855099Storek 	resid = uap->nbytes;
27955099Storek 	if ((len = buflen - auio.uio_resid) == 0)
28055099Storek 		goto eof;
28155099Storek 	for (; len > 0; len -= reclen) {
28258995Storek 		reclen = ((struct dirent *)inp)->d_reclen;
28355099Storek 		if (reclen & 3)
28455099Storek 			panic("sun_getdents");
28555099Storek 		off += reclen;		/* each entry points to next */
28658995Storek 		if (BSD_DIRENT(inp)->d_fileno == 0) {
28755099Storek 			inp += reclen;	/* it is a hole; squish it out */
28855099Storek 			continue;
28955099Storek 		}
29055099Storek 		if (reclen > len || resid < SUN_RECLEN(reclen)) {
29155099Storek 			/* entry too big for buffer, so just stop */
29255099Storek 			outp++;
29355099Storek 			break;
29455099Storek 		}
29558995Storek 		/*
29658995Storek 		 * Massage in place to make a Sun-shaped dirent (otherwise
29758995Storek 		 * we have to worry about touching user memory outside of
29858995Storek 		 * the copyout() call).
29958995Storek 		 */
30058995Storek 		BSD_DIRENT(inp)->d_reclen = SUN_RECLEN(reclen);
30158995Storek 		BSD_DIRENT(inp)->d_type = 0;
30255099Storek 		soff = off;
30355099Storek 		if ((error = copyout((caddr_t)&soff, outp, sizeof soff)) != 0 ||
30455099Storek 		    (error = copyout(inp, outp + sizeof soff, reclen)) != 0)
30555099Storek 			goto out;
30655099Storek 		/* advance past this real entry */
30755099Storek 		inp += reclen;
30855099Storek 		/* advance output past Sun-shaped entry */
30955099Storek 		outp += SUN_RECLEN(reclen);
31055099Storek 		resid -= SUN_RECLEN(reclen);
31155099Storek 	}
31255099Storek 	/* if we squished out the whole block, try again */
31355099Storek 	if (outp == uap->buf)
31455099Storek 		goto again;
31555099Storek 	fp->f_offset = off;		/* update the vnode offset */
31655099Storek eof:
31755099Storek 	*retval = uap->nbytes - resid;
31855099Storek out:
319*69449Smckusick 	VOP_UNLOCK(vp, 0, p);
32055099Storek 	free(buf, M_TEMP);
32155099Storek 	return (error);
32255099Storek }
32355099Storek 
32455099Storek #define	MAXDOMAINNAME	64
32555099Storek char	sun_domainname[MAXDOMAINNAME];
32655099Storek int	sun_domainnamelen = 1;
32755099Storek 
32855099Storek struct sun_getdomainname_args {
32955099Storek 	char	*name;
33055099Storek 	int	namelen;
33155099Storek };
33255099Storek sun_getdomainname(p, uap, retval)
33355099Storek 	struct proc *p;
33455099Storek 	struct sun_getdomainname_args *uap;
33555099Storek 	int *retval;
33655099Storek {
33755099Storek 	register int l = min(uap->namelen, sun_domainnamelen + 1);
33855099Storek 
33955099Storek 	return (copyout(sun_domainname, uap->name, l));
34055099Storek }
34155099Storek 
34255099Storek struct sun_setdomainname_args {
34355099Storek 	char	*name;
34455099Storek 	int	namelen;
34555099Storek };
34655099Storek sun_setdomainname(p, uap, retval)
34755099Storek 	struct proc *p;
34855099Storek 	struct sun_setdomainname_args *uap;
34955099Storek 	int *retval;
35055099Storek {
35155099Storek 	register int l = uap->namelen, error;
35255099Storek 
35355099Storek 	if (l >= MAXDOMAINNAME)
35455099Storek 		return (EINVAL);	/* ??? ENAMETOOLONG? */
35555099Storek 	if (error = suser(p->p_ucred, &p->p_acflag))
35655099Storek 		return (error);
35755099Storek 	if (error = copyin(uap->name, sun_domainname, l))
35855099Storek 		return (error);
35955099Storek 	sun_domainname[l] = 0;
36055099Storek 	return (0);
36155099Storek }
36255099Storek 
36355099Storek #define	SUN_MMAP_MASK	0xf		/* mask for SHARED/PRIVATE */
36455099Storek #define	SUN_MMAP_CANDO	0x80000000	/* if not, old mmap & cannot handle */
36555099Storek 
36655099Storek #define	DEVZERO	makedev(3, 12)		/* major,minor of /dev/zero */
36755099Storek 
36855099Storek #define	SUN_MMAP_SAME	(MAP_SHARED|MAP_PRIVATE|MAP_FIXED|MAP_INHERIT)
36955099Storek 
37055099Storek struct sun_mmap_args {
37155099Storek 	caddr_t	addr;
37255099Storek 	size_t	len;
37355099Storek 	int	prot;
37455099Storek 	int	flags;
37555099Storek 	int	fd;
37655099Storek 	long	off;		/* not off_t! */
37763554Smckusick 	quad_t	qoff;		/* created here and fed to mmap() */
37855099Storek };
sun_mmap(p,uap,retval)37955099Storek sun_mmap(p, uap, retval)
38055099Storek 	register struct proc *p;
38155099Storek 	register struct sun_mmap_args *uap;
38255099Storek 	int *retval;
38355099Storek {
38455099Storek 	register int flags;
38555099Storek 	register struct filedesc *fdp;
38655099Storek 	register struct file *fp;
38755099Storek 	register struct vnode *vp;
38855099Storek 
38955099Storek 	/*
39055099Storek 	 * Verify the arguments.
39155099Storek 	 */
39255099Storek 	flags = uap->flags;
39355099Storek 	if ((flags & SUN_MMAP_CANDO) == 0)
39455099Storek 		return (EINVAL);
39555099Storek 	if ((flags & SUN_MMAP_MASK) != MAP_SHARED &&
39655099Storek 	    (flags & SUN_MMAP_MASK) != MAP_PRIVATE)
39755099Storek 		return (EINVAL);
39855099Storek 	flags &= ~SUN_MMAP_CANDO;
39955099Storek 
40055099Storek 	/*
40155099Storek 	 * Special case: if fd refers to /dev/zero, map as MAP_ANON.  (XXX)
40255099Storek 	 */
40355099Storek 	fdp = p->p_fd;
40455099Storek 	if ((unsigned)uap->fd < fdp->fd_nfiles &&			/*XXX*/
40555099Storek 	    (fp = fdp->fd_ofiles[uap->fd]) != NULL &&			/*XXX*/
40655099Storek 	    fp->f_type == DTYPE_VNODE &&				/*XXX*/
40755099Storek 	    (vp = (struct vnode *)fp->f_data)->v_type == VCHR &&	/*XXX*/
40855099Storek 	    vp->v_rdev == DEVZERO) {					/*XXX*/
40955099Storek 		flags |= MAP_ANON;
41055099Storek 		uap->fd = -1;
41155099Storek 	}
41255099Storek 
41355099Storek 	/* All done, fix up fields and go. */
41455099Storek 	uap->flags = flags;
41555099Storek 	uap->qoff = (quad_t)uap->off;
41663554Smckusick 	return (mmap(p, uap, retval));
41755099Storek }
41855099Storek 
41955099Storek #define	MC_SYNC		1
42055099Storek #define	MC_LOCK		2
42155099Storek #define	MC_UNLOCK	3
42255099Storek #define	MC_ADVISE	4
42355099Storek #define	MC_LOCKAS	5
42455099Storek #define	MC_UNLOCKAS	6
42555099Storek 
42655099Storek struct sun_mctl_args {
42755099Storek 	caddr_t	addr;
42855099Storek 	size_t	len;
42955099Storek 	int	func;
43055099Storek 	void	*arg;
43155099Storek };
sun_mctl(p,uap,retval)43255099Storek sun_mctl(p, uap, retval)
43355099Storek 	register struct proc *p;
43455099Storek 	register struct sun_mctl_args *uap;
43555099Storek 	int *retval;
43655099Storek {
43755099Storek 
43855099Storek 	switch (uap->func) {
43955099Storek 
44055099Storek 	case MC_ADVISE:		/* ignore for now */
44155099Storek 		return (0);
44255099Storek 
44355099Storek 	case MC_SYNC:		/* translate to msync */
44455099Storek 		return (msync(p, uap, retval));
44555099Storek 
44655099Storek 	default:
44755099Storek 		return (EINVAL);
44855099Storek 	}
44955099Storek }
45055099Storek 
45155099Storek struct sun_setsockopt_args {
45255099Storek 	int	s;
45355099Storek 	int	level;
45455099Storek 	int	name;
45555099Storek 	caddr_t	val;
45655099Storek 	int	valsize;
45755099Storek };
45855099Storek sun_setsockopt(p, uap, retval)
45955099Storek 	struct proc *p;
46055099Storek 	register struct sun_setsockopt_args *uap;
46155099Storek 	int *retval;
46255099Storek {
46355099Storek 	struct file *fp;
46455099Storek 	struct mbuf *m = NULL;
46555099Storek 	int error;
46655099Storek 
46755099Storek 	if (error = getsock(p->p_fd, uap->s, &fp))
46855099Storek 		return (error);
46955099Storek #define	SO_DONTLINGER (~SO_LINGER)
47055099Storek 	if (uap->name == SO_DONTLINGER) {
47155099Storek 		m = m_get(M_WAIT, MT_SOOPTS);
47255099Storek 		if (m == NULL)
47355099Storek 			return (ENOBUFS);
47455099Storek 		mtod(m, struct linger *)->l_onoff = 0;
47555099Storek 		m->m_len = sizeof(struct linger);
47655099Storek 		return (sosetopt((struct socket *)fp->f_data, uap->level,
47755099Storek 		    SO_LINGER, m));
47855099Storek 	}
47955099Storek 	if (uap->valsize > MLEN)
48055099Storek 		return (EINVAL);
48155099Storek 	if (uap->val) {
48255099Storek 		m = m_get(M_WAIT, MT_SOOPTS);
48355099Storek 		if (m == NULL)
48455099Storek 			return (ENOBUFS);
48555099Storek 		if (error = copyin(uap->val, mtod(m, caddr_t),
48655099Storek 		    (u_int)uap->valsize)) {
48755099Storek 			(void) m_free(m);
48855099Storek 			return (error);
48955099Storek 		}
49055099Storek 		m->m_len = uap->valsize;
49155099Storek 	}
49255099Storek 	return (sosetopt((struct socket *)fp->f_data, uap->level,
49355099Storek 	    uap->name, m));
49455099Storek }
49555099Storek 
49655099Storek struct sun_fchroot_args {
49755099Storek 	int	fdes;
49855099Storek };
sun_fchroot(p,uap,retval)49955099Storek sun_fchroot(p, uap, retval)
50055099Storek 	register struct proc *p;
50155099Storek 	register struct sun_fchroot_args *uap;
50255099Storek 	int *retval;
50355099Storek {
50455099Storek 	register struct filedesc *fdp = p->p_fd;
50555099Storek 	register struct vnode *vp;
50655099Storek 	struct file *fp;
50755099Storek 	int error;
50855099Storek 
50955099Storek 	if ((error = suser(p->p_ucred, &p->p_acflag)) != 0)
51055099Storek 		return (error);
51155099Storek 	if ((error = getvnode(fdp, uap->fdes, &fp)) != 0)
51255099Storek 		return (error);
51355099Storek 	vp = (struct vnode *)fp->f_data;
514*69449Smckusick 	vn_lock(vp, LK_EXCLUSIVE | LK_RETRY, p)
51555099Storek 	if (vp->v_type != VDIR)
51655099Storek 		error = ENOTDIR;
51755099Storek 	else
51855099Storek 		error = VOP_ACCESS(vp, VEXEC, p->p_ucred, p);
519*69449Smckusick 	VOP_UNLOCK(vp, 0, p);
52055099Storek 	if (error)
52155099Storek 		return (error);
52255099Storek 	VREF(vp);
52355099Storek 	if (fdp->fd_rdir != NULL)
52455099Storek 		vrele(fdp->fd_rdir);
52555099Storek 	fdp->fd_rdir = vp;
52655099Storek 	return (0);
52755099Storek }
528