xref: /freebsd/contrib/nvi/ex/ex_read.c (revision 110d525ec6188f3c9dc4f54c4bc1cced2f7184cd)
1b8ba871bSPeter Wemm /*-
2b8ba871bSPeter Wemm  * Copyright (c) 1992, 1993, 1994
3b8ba871bSPeter Wemm  *	The Regents of the University of California.  All rights reserved.
4b8ba871bSPeter Wemm  * Copyright (c) 1992, 1993, 1994, 1995, 1996
5b8ba871bSPeter Wemm  *	Keith Bostic.  All rights reserved.
6b8ba871bSPeter Wemm  *
7b8ba871bSPeter Wemm  * See the LICENSE file for redistribution information.
8b8ba871bSPeter Wemm  */
9b8ba871bSPeter Wemm 
10b8ba871bSPeter Wemm #include "config.h"
11b8ba871bSPeter Wemm 
12b8ba871bSPeter Wemm #include <sys/types.h>
13b8ba871bSPeter Wemm #include <sys/queue.h>
14b8ba871bSPeter Wemm #include <sys/stat.h>
15b8ba871bSPeter Wemm 
16b8ba871bSPeter Wemm #include <bitstring.h>
17b8ba871bSPeter Wemm #include <ctype.h>
18b8ba871bSPeter Wemm #include <errno.h>
19b8ba871bSPeter Wemm #include <limits.h>
20b8ba871bSPeter Wemm #include <stdio.h>
21b8ba871bSPeter Wemm #include <stdlib.h>
22b8ba871bSPeter Wemm #include <string.h>
23b8ba871bSPeter Wemm 
24b8ba871bSPeter Wemm #include "../common/common.h"
25b8ba871bSPeter Wemm #include "../vi/vi.h"
26b8ba871bSPeter Wemm 
27b8ba871bSPeter Wemm /*
28b8ba871bSPeter Wemm  * ex_read --	:read [file]
29b8ba871bSPeter Wemm  *		:read [!cmd]
30b8ba871bSPeter Wemm  *	Read from a file or utility.
31b8ba871bSPeter Wemm  *
32b8ba871bSPeter Wemm  * !!!
33b8ba871bSPeter Wemm  * Historical vi wouldn't undo a filter read, for no apparent reason.
34b8ba871bSPeter Wemm  *
35*c271fa92SBaptiste Daroussin  * PUBLIC: int ex_read(SCR *, EXCMD *);
36b8ba871bSPeter Wemm  */
37b8ba871bSPeter Wemm int
ex_read(SCR * sp,EXCMD * cmdp)38f0957ccaSPeter Wemm ex_read(SCR *sp, EXCMD *cmdp)
39b8ba871bSPeter Wemm {
40b8ba871bSPeter Wemm 	enum { R_ARG, R_EXPANDARG, R_FILTER } which;
41b8ba871bSPeter Wemm 	struct stat sb;
42f0957ccaSPeter Wemm 	CHAR_T *arg = NULL;
43f0957ccaSPeter Wemm 	char *name = NULL;
44f0957ccaSPeter Wemm 	size_t nlen;
45b8ba871bSPeter Wemm 	EX_PRIVATE *exp;
46b8ba871bSPeter Wemm 	FILE *fp;
47b8ba871bSPeter Wemm 	FREF *frp;
48b8ba871bSPeter Wemm 	GS *gp;
49b8ba871bSPeter Wemm 	MARK rm;
50b8ba871bSPeter Wemm 	recno_t nlines;
51f0957ccaSPeter Wemm 	size_t arglen = 0;
52b8ba871bSPeter Wemm 	int argc, rval;
53b8ba871bSPeter Wemm 	char *p;
54b8ba871bSPeter Wemm 
55b8ba871bSPeter Wemm 	gp = sp->gp;
56b8ba871bSPeter Wemm 
57b8ba871bSPeter Wemm 	/*
58b8ba871bSPeter Wemm 	 * 0 args: read the current pathname.
59b8ba871bSPeter Wemm 	 * 1 args: check for "read !arg".
60b8ba871bSPeter Wemm 	 */
61b8ba871bSPeter Wemm 	switch (cmdp->argc) {
62b8ba871bSPeter Wemm 	case 0:
63b8ba871bSPeter Wemm 		which = R_ARG;
64b8ba871bSPeter Wemm 		break;
65b8ba871bSPeter Wemm 	case 1:
66b8ba871bSPeter Wemm 		arg = cmdp->argv[0]->bp;
67b8ba871bSPeter Wemm 		arglen = cmdp->argv[0]->len;
68b8ba871bSPeter Wemm 		if (*arg == '!') {
69b8ba871bSPeter Wemm 			++arg;
70b8ba871bSPeter Wemm 			--arglen;
71b8ba871bSPeter Wemm 			which = R_FILTER;
72b8ba871bSPeter Wemm 
73b8ba871bSPeter Wemm 			/* Secure means no shell access. */
74b8ba871bSPeter Wemm 			if (O_ISSET(sp, O_SECURE)) {
75f0957ccaSPeter Wemm 				ex_wemsg(sp, cmdp->cmd->name, EXM_SECURE_F);
76b8ba871bSPeter Wemm 				return (1);
77b8ba871bSPeter Wemm 			}
78b8ba871bSPeter Wemm 		} else
79b8ba871bSPeter Wemm 			which = R_EXPANDARG;
80b8ba871bSPeter Wemm 		break;
81b8ba871bSPeter Wemm 	default:
82b8ba871bSPeter Wemm 		abort();
83b8ba871bSPeter Wemm 		/* NOTREACHED */
84b8ba871bSPeter Wemm 	}
85b8ba871bSPeter Wemm 
86b8ba871bSPeter Wemm 	/* Load a temporary file if no file being edited. */
87b8ba871bSPeter Wemm 	if (sp->ep == NULL) {
88b8ba871bSPeter Wemm 		if ((frp = file_add(sp, NULL)) == NULL)
89b8ba871bSPeter Wemm 			return (1);
90b8ba871bSPeter Wemm 		if (file_init(sp, frp, NULL, 0))
91b8ba871bSPeter Wemm 			return (1);
92b8ba871bSPeter Wemm 	}
93b8ba871bSPeter Wemm 
94b8ba871bSPeter Wemm 	switch (which) {
95b8ba871bSPeter Wemm 	case R_FILTER:
96b8ba871bSPeter Wemm 		/*
97b8ba871bSPeter Wemm 		 * File name and bang expand the user's argument.  If
98b8ba871bSPeter Wemm 		 * we don't get an additional argument, it's illegal.
99b8ba871bSPeter Wemm 		 */
100b8ba871bSPeter Wemm 		argc = cmdp->argc;
101b8ba871bSPeter Wemm 		if (argv_exp1(sp, cmdp, arg, arglen, 1))
102b8ba871bSPeter Wemm 			return (1);
103b8ba871bSPeter Wemm 		if (argc == cmdp->argc) {
104b8ba871bSPeter Wemm 			ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE);
105b8ba871bSPeter Wemm 			return (1);
106b8ba871bSPeter Wemm 		}
107b8ba871bSPeter Wemm 		argc = cmdp->argc - 1;
108b8ba871bSPeter Wemm 
109b8ba871bSPeter Wemm 		/* Set the last bang command. */
110b8ba871bSPeter Wemm 		exp = EXP(sp);
111b8ba871bSPeter Wemm 		free(exp->lastbcomm);
112b8ba871bSPeter Wemm 		if ((exp->lastbcomm =
113f0957ccaSPeter Wemm 		    v_wstrdup(sp, cmdp->argv[argc]->bp,
114f0957ccaSPeter Wemm 				cmdp->argv[argc]->len)) == NULL) {
115b8ba871bSPeter Wemm 			msgq(sp, M_SYSERR, NULL);
116b8ba871bSPeter Wemm 			return (1);
117b8ba871bSPeter Wemm 		}
118b8ba871bSPeter Wemm 
119b8ba871bSPeter Wemm 		/*
120b8ba871bSPeter Wemm 		 * Vi redisplayed the user's argument if it changed, ex
121b8ba871bSPeter Wemm 		 * always displayed a !, plus the user's argument if it
122b8ba871bSPeter Wemm 		 * changed.
123b8ba871bSPeter Wemm 		 */
124b8ba871bSPeter Wemm 		if (F_ISSET(sp, SC_VI)) {
125b8ba871bSPeter Wemm 			if (F_ISSET(cmdp, E_MODIFY))
126b8ba871bSPeter Wemm 				(void)vs_update(sp, "!", cmdp->argv[argc]->bp);
127b8ba871bSPeter Wemm 		} else {
128b8ba871bSPeter Wemm 			if (F_ISSET(cmdp, E_MODIFY))
129b8ba871bSPeter Wemm 				(void)ex_printf(sp,
130f0957ccaSPeter Wemm 				    "!"WS"\n", cmdp->argv[argc]->bp);
131b8ba871bSPeter Wemm 			else
132b8ba871bSPeter Wemm 				(void)ex_puts(sp, "!\n");
133b8ba871bSPeter Wemm 			(void)ex_fflush(sp);
134b8ba871bSPeter Wemm 		}
135b8ba871bSPeter Wemm 
136b8ba871bSPeter Wemm 		/*
137b8ba871bSPeter Wemm 		 * Historically, filter reads as the first ex command didn't
138b8ba871bSPeter Wemm 		 * wait for the user. If SC_SCR_EXWROTE not already set, set
139b8ba871bSPeter Wemm 		 * the don't-wait flag.
140b8ba871bSPeter Wemm 		 */
141b8ba871bSPeter Wemm 		if (!F_ISSET(sp, SC_SCR_EXWROTE))
142b8ba871bSPeter Wemm 			F_SET(sp, SC_EX_WAIT_NO);
143b8ba871bSPeter Wemm 
144b8ba871bSPeter Wemm 		/*
145b8ba871bSPeter Wemm 		 * Switch into ex canonical mode.  The reason to restore the
146b8ba871bSPeter Wemm 		 * original terminal modes for read filters is so that users
147b8ba871bSPeter Wemm 		 * can do things like ":r! cat /dev/tty".
148b8ba871bSPeter Wemm 		 *
149b8ba871bSPeter Wemm 		 * !!!
150b8ba871bSPeter Wemm 		 * We do not output an extra <newline>, so that we don't touch
151b8ba871bSPeter Wemm 		 * the screen on a normal read.
152b8ba871bSPeter Wemm 		 */
153b8ba871bSPeter Wemm 		if (F_ISSET(sp, SC_VI)) {
154b8ba871bSPeter Wemm 			if (gp->scr_screen(sp, SC_EX)) {
155f0957ccaSPeter Wemm 				ex_wemsg(sp, cmdp->cmd->name, EXM_NOCANON_F);
156b8ba871bSPeter Wemm 				return (1);
157b8ba871bSPeter Wemm 			}
158b8ba871bSPeter Wemm 			/*
159b8ba871bSPeter Wemm 			 * !!!
160b8ba871bSPeter Wemm 			 * Historically, the read command doesn't switch to
161b8ba871bSPeter Wemm 			 * the alternate X11 xterm screen, if doing a filter
162b8ba871bSPeter Wemm 			 * read -- don't set SA_ALTERNATE.
163b8ba871bSPeter Wemm 			 */
164b8ba871bSPeter Wemm 			F_SET(sp, SC_SCR_EX | SC_SCR_EXWROTE);
165b8ba871bSPeter Wemm 		}
166b8ba871bSPeter Wemm 
167b8ba871bSPeter Wemm 		if (ex_filter(sp, cmdp, &cmdp->addr1,
168b8ba871bSPeter Wemm 		    NULL, &rm, cmdp->argv[argc]->bp, FILTER_READ))
169b8ba871bSPeter Wemm 			return (1);
170b8ba871bSPeter Wemm 
171b8ba871bSPeter Wemm 		/* The filter version of read set the autoprint flag. */
172b8ba871bSPeter Wemm 		F_SET(cmdp, E_AUTOPRINT);
173b8ba871bSPeter Wemm 
174b8ba871bSPeter Wemm 		/*
175b8ba871bSPeter Wemm 		 * If in vi mode, move to the first nonblank.  Might have
176b8ba871bSPeter Wemm 		 * switched into ex mode, so saved the original SC_VI value.
177b8ba871bSPeter Wemm 		 */
178b8ba871bSPeter Wemm 		sp->lno = rm.lno;
179b8ba871bSPeter Wemm 		if (F_ISSET(sp, SC_VI)) {
180b8ba871bSPeter Wemm 			sp->cno = 0;
181b8ba871bSPeter Wemm 			(void)nonblank(sp, sp->lno, &sp->cno);
182b8ba871bSPeter Wemm 		}
183b8ba871bSPeter Wemm 		return (0);
184b8ba871bSPeter Wemm 	case R_ARG:
185b8ba871bSPeter Wemm 		name = sp->frp->name;
186b8ba871bSPeter Wemm 		break;
187b8ba871bSPeter Wemm 	case R_EXPANDARG:
188b8ba871bSPeter Wemm 		if (argv_exp2(sp, cmdp, arg, arglen))
189b8ba871bSPeter Wemm 			return (1);
190b8ba871bSPeter Wemm 		/*
191b8ba871bSPeter Wemm 		 *  0 args: impossible.
192b8ba871bSPeter Wemm 		 *  1 args: impossible (I hope).
193b8ba871bSPeter Wemm 		 *  2 args: read it.
194b8ba871bSPeter Wemm 		 * >2 args: object, too many args.
195b8ba871bSPeter Wemm 		 *
196b8ba871bSPeter Wemm 		 * The 1 args case depends on the argv_sexp() function refusing
197b8ba871bSPeter Wemm 		 * to return success without at least one non-blank character.
198b8ba871bSPeter Wemm 		 */
199b8ba871bSPeter Wemm 		switch (cmdp->argc) {
200b8ba871bSPeter Wemm 		case 0:
201b8ba871bSPeter Wemm 		case 1:
202b8ba871bSPeter Wemm 			abort();
203b8ba871bSPeter Wemm 			/* NOTREACHED */
204b8ba871bSPeter Wemm 		case 2:
205f0957ccaSPeter Wemm 			INT2CHAR(sp, cmdp->argv[1]->bp, cmdp->argv[1]->len + 1,
206f0957ccaSPeter Wemm 				 name, nlen);
207b8ba871bSPeter Wemm 			/*
208b8ba871bSPeter Wemm 			 * !!!
209b8ba871bSPeter Wemm 			 * Historically, the read and write commands renamed
210b8ba871bSPeter Wemm 			 * "unnamed" files, or, if the file had a name, set
211b8ba871bSPeter Wemm 			 * the alternate file name.
212b8ba871bSPeter Wemm 			 */
213b8ba871bSPeter Wemm 			if (F_ISSET(sp->frp, FR_TMPFILE) &&
214b8ba871bSPeter Wemm 			    !F_ISSET(sp->frp, FR_EXNAMED)) {
215f0957ccaSPeter Wemm 				if ((p = strdup(name)) != NULL) {
216b8ba871bSPeter Wemm 					free(sp->frp->name);
217b8ba871bSPeter Wemm 					sp->frp->name = p;
218b8ba871bSPeter Wemm 				}
219b8ba871bSPeter Wemm 				/*
220b8ba871bSPeter Wemm 				 * The file has a real name, it's no longer a
221b8ba871bSPeter Wemm 				 * temporary, clear the temporary file flags.
222b8ba871bSPeter Wemm 				 */
223b8ba871bSPeter Wemm 				F_CLR(sp->frp, FR_TMPEXIT | FR_TMPFILE);
224b8ba871bSPeter Wemm 				F_SET(sp->frp, FR_NAMECHANGE | FR_EXNAMED);
225b8ba871bSPeter Wemm 
226b8ba871bSPeter Wemm 				/* Notify the screen. */
227b8ba871bSPeter Wemm 				(void)sp->gp->scr_rename(sp, sp->frp->name, 1);
228f0957ccaSPeter Wemm 				name = sp->frp->name;
229f0957ccaSPeter Wemm 			} else {
230b8ba871bSPeter Wemm 				set_alt_name(sp, name);
231f0957ccaSPeter Wemm 				name = sp->alt_name;
232f0957ccaSPeter Wemm 			}
233b8ba871bSPeter Wemm 			break;
234b8ba871bSPeter Wemm 		default:
235f0957ccaSPeter Wemm 			ex_wemsg(sp, cmdp->argv[0]->bp, EXM_FILECOUNT);
236b8ba871bSPeter Wemm 			return (1);
237b8ba871bSPeter Wemm 
238b8ba871bSPeter Wemm 		}
239b8ba871bSPeter Wemm 		break;
240b8ba871bSPeter Wemm 	}
241b8ba871bSPeter Wemm 
242b8ba871bSPeter Wemm 	/*
243b8ba871bSPeter Wemm 	 * !!!
244b8ba871bSPeter Wemm 	 * Historically, vi did not permit reads from non-regular files, nor
245b8ba871bSPeter Wemm 	 * did it distinguish between "read !" and "read!", so there was no
246b8ba871bSPeter Wemm 	 * way to "force" it.  We permit reading from named pipes too, since
247b8ba871bSPeter Wemm 	 * they didn't exist when the original implementation of vi was done
248b8ba871bSPeter Wemm 	 * and they seem a reasonable addition.
249b8ba871bSPeter Wemm 	 */
250b8ba871bSPeter Wemm 	if ((fp = fopen(name, "r")) == NULL || fstat(fileno(fp), &sb)) {
251b8ba871bSPeter Wemm 		msgq_str(sp, M_SYSERR, name, "%s");
252b8ba871bSPeter Wemm 		return (1);
253b8ba871bSPeter Wemm 	}
254b8ba871bSPeter Wemm 	if (!S_ISFIFO(sb.st_mode) && !S_ISREG(sb.st_mode)) {
255b8ba871bSPeter Wemm 		(void)fclose(fp);
256b8ba871bSPeter Wemm 		msgq(sp, M_ERR,
257b8ba871bSPeter Wemm 		    "145|Only regular files and named pipes may be read");
258b8ba871bSPeter Wemm 		return (1);
259b8ba871bSPeter Wemm 	}
260b8ba871bSPeter Wemm 
261b8ba871bSPeter Wemm 	/* Try and get a lock. */
262f0957ccaSPeter Wemm 	if (file_lock(sp, NULL, fileno(fp), 0) == LOCK_UNAVAIL)
263b8ba871bSPeter Wemm 		msgq(sp, M_ERR, "146|%s: read lock was unavailable", name);
264b8ba871bSPeter Wemm 
265b8ba871bSPeter Wemm 	rval = ex_readfp(sp, name, fp, &cmdp->addr1, &nlines, 0);
266b8ba871bSPeter Wemm 
267b8ba871bSPeter Wemm 	/*
268b8ba871bSPeter Wemm 	 * In vi, set the cursor to the first line read in, if anything read
269b8ba871bSPeter Wemm 	 * in, otherwise, the address.  (Historic vi set it to the line after
270b8ba871bSPeter Wemm 	 * the address regardless, but since that line may not exist we don't
271b8ba871bSPeter Wemm 	 * bother.)
272b8ba871bSPeter Wemm 	 *
273b8ba871bSPeter Wemm 	 * In ex, set the cursor to the last line read in, if anything read in,
274b8ba871bSPeter Wemm 	 * otherwise, the address.
275b8ba871bSPeter Wemm 	 */
276b8ba871bSPeter Wemm 	if (F_ISSET(sp, SC_VI)) {
277b8ba871bSPeter Wemm 		sp->lno = cmdp->addr1.lno;
278b8ba871bSPeter Wemm 		if (nlines)
279b8ba871bSPeter Wemm 			++sp->lno;
280b8ba871bSPeter Wemm 	} else
281b8ba871bSPeter Wemm 		sp->lno = cmdp->addr1.lno + nlines;
282b8ba871bSPeter Wemm 	return (rval);
283b8ba871bSPeter Wemm }
284b8ba871bSPeter Wemm 
285b8ba871bSPeter Wemm /*
286b8ba871bSPeter Wemm  * ex_readfp --
287b8ba871bSPeter Wemm  *	Read lines into the file.
288b8ba871bSPeter Wemm  *
289*c271fa92SBaptiste Daroussin  * PUBLIC: int ex_readfp(SCR *, char *, FILE *, MARK *, recno_t *, int);
290b8ba871bSPeter Wemm  */
291b8ba871bSPeter Wemm int
ex_readfp(SCR * sp,char * name,FILE * fp,MARK * fm,recno_t * nlinesp,int silent)292f0957ccaSPeter Wemm ex_readfp(SCR *sp, char *name, FILE *fp, MARK *fm, recno_t *nlinesp, int silent)
293b8ba871bSPeter Wemm {
294b8ba871bSPeter Wemm 	EX_PRIVATE *exp;
295b8ba871bSPeter Wemm 	GS *gp;
296b8ba871bSPeter Wemm 	recno_t lcnt, lno;
297b8ba871bSPeter Wemm 	size_t len;
298b8ba871bSPeter Wemm 	u_long ccnt;			/* XXX: can't print off_t portably. */
299b8ba871bSPeter Wemm 	int nf, rval;
300b8ba871bSPeter Wemm 	char *p;
301f0957ccaSPeter Wemm 	size_t wlen;
302f0957ccaSPeter Wemm 	CHAR_T *wp;
303b8ba871bSPeter Wemm 
304b8ba871bSPeter Wemm 	gp = sp->gp;
305b8ba871bSPeter Wemm 	exp = EXP(sp);
306b8ba871bSPeter Wemm 
307b8ba871bSPeter Wemm 	/*
308b8ba871bSPeter Wemm 	 * Add in the lines from the output.  Insertion starts at the line
309b8ba871bSPeter Wemm 	 * following the address.
310b8ba871bSPeter Wemm 	 */
311b8ba871bSPeter Wemm 	ccnt = 0;
312b8ba871bSPeter Wemm 	lcnt = 0;
313b8ba871bSPeter Wemm 	p = "147|Reading...";
314b8ba871bSPeter Wemm 	for (lno = fm->lno; !ex_getline(sp, fp, &len); ++lno, ++lcnt) {
315b8ba871bSPeter Wemm 		if ((lcnt + 1) % INTERRUPT_CHECK == 0) {
316b8ba871bSPeter Wemm 			if (INTERRUPTED(sp))
317b8ba871bSPeter Wemm 				break;
318b8ba871bSPeter Wemm 			if (!silent) {
319b8ba871bSPeter Wemm 				gp->scr_busy(sp, p,
320b8ba871bSPeter Wemm 				    p == NULL ? BUSY_UPDATE : BUSY_ON);
321b8ba871bSPeter Wemm 				p = NULL;
322b8ba871bSPeter Wemm 			}
323b8ba871bSPeter Wemm 		}
324f0957ccaSPeter Wemm 		FILE2INT5(sp, exp->ibcw, exp->ibp, len, wp, wlen);
325f0957ccaSPeter Wemm 		if (db_append(sp, 1, lno, wp, wlen))
326b8ba871bSPeter Wemm 			goto err;
327b8ba871bSPeter Wemm 		ccnt += len;
328b8ba871bSPeter Wemm 	}
329b8ba871bSPeter Wemm 
330b8ba871bSPeter Wemm 	if (ferror(fp) || fclose(fp))
331b8ba871bSPeter Wemm 		goto err;
332b8ba871bSPeter Wemm 
333b8ba871bSPeter Wemm 	/* Return the number of lines read in. */
334b8ba871bSPeter Wemm 	if (nlinesp != NULL)
335b8ba871bSPeter Wemm 		*nlinesp = lcnt;
336b8ba871bSPeter Wemm 
337b8ba871bSPeter Wemm 	if (!silent) {
338b8ba871bSPeter Wemm 		p = msg_print(sp, name, &nf);
339b8ba871bSPeter Wemm 		msgq(sp, M_INFO,
340f0957ccaSPeter Wemm 		    "148|%s: %lu lines, %lu characters", p,
341f0957ccaSPeter Wemm 		    (u_long)lcnt, ccnt);
342b8ba871bSPeter Wemm 		if (nf)
343b8ba871bSPeter Wemm 			FREE_SPACE(sp, p, 0);
344b8ba871bSPeter Wemm 	}
345b8ba871bSPeter Wemm 
346b8ba871bSPeter Wemm 	rval = 0;
347b8ba871bSPeter Wemm 	if (0) {
348b8ba871bSPeter Wemm err:		msgq_str(sp, M_SYSERR, name, "%s");
349b8ba871bSPeter Wemm 		(void)fclose(fp);
350b8ba871bSPeter Wemm 		rval = 1;
351b8ba871bSPeter Wemm 	}
352b8ba871bSPeter Wemm 
353b8ba871bSPeter Wemm 	if (!silent)
354b8ba871bSPeter Wemm 		gp->scr_busy(sp, NULL, BUSY_OFF);
355b8ba871bSPeter Wemm 	return (rval);
356b8ba871bSPeter Wemm }
357