xref: /openbsd-src/usr.bin/gencat/gencat.c (revision b2ea75c1b17e1a9a339660e7ed45cd24946b230e)
1 /*	$OpenBSD: gencat.c,v 1.6 2000/09/27 23:53:29 danh Exp $	*/
2 /*	$NetBSD: gencat.c,v 1.9 1998/10/09 17:00:56 itohy Exp $	*/
3 
4 /*-
5  * Copyright (c) 1996 The NetBSD Foundation, Inc.
6  * All rights reserved.
7  *
8  * This code is derived from software contributed to The NetBSD Foundation
9  * by J.T. Conklin.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  * 3. All advertising materials mentioning features or use of this software
20  *    must display the following acknowledgment:
21  *        This product includes software developed by the NetBSD
22  *	  Foundation, Inc. and its contributors.
23  * 4. Neither the name of The NetBSD Foundation nor the names of its
24  *    contributors may be used to endorse or promote products derived
25  *    from this software without specific prior written permission.
26  *
27  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
28  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
29  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
30  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
31  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37  * POSSIBILITY OF SUCH DAMAGE.
38  */
39 
40 #include <sys/cdefs.h>
41 #ifndef lint
42 static char rcsid[] =
43     "$OpenBSD: gencat.c,v 1.6 2000/09/27 23:53:29 danh Exp $";
44 #endif /* not lint */
45 
46 /***********************************************************
47 Copyright 1990, by Alfalfa Software Incorporated, Cambridge, Massachusetts.
48 
49                         All Rights Reserved
50 
51 Permission to use, copy, modify, and distribute this software and its
52 documentation for any purpose and without fee is hereby granted,
53 provided that the above copyright notice appear in all copies and that
54 both that copyright notice and this permission notice appear in
55 supporting documentation, and that Alfalfa's name not be used in
56 advertising or publicity pertaining to distribution of the software
57 without specific, written prior permission.
58 
59 ALPHALPHA DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
60 ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
61 ALPHALPHA BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
62 ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
63 WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
64 ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
65 SOFTWARE.
66 
67 If you make any modifications, bugfixes or other changes to this software
68 we'd appreciate it if you could send a copy to us so we can keep things
69 up-to-date.  Many thanks.
70 				Kee Hinckley
71 				Alfalfa Software, Inc.
72 				267 Allston St., #3
73 				Cambridge, MA 02139  USA
74 				nazgul@alfalfa.com
75 
76 ******************************************************************/
77 
78 #define _NLS_PRIVATE
79 
80 /* ensure 8-bit cleanliness */
81 #define ISSPACE(c) \
82     (isascii((unsigned char)c) && isspace((unsigned char)c))
83 
84 #include <sys/queue.h>
85 #include <ctype.h>
86 #include <err.h>
87 #include <fcntl.h>
88 #include <nl_types.h>
89 #include <stdio.h>
90 #include <stdlib.h>
91 #include <string.h>
92 #include <unistd.h>
93 
94 struct _msgT {
95 	long    msgId;
96 	char   *str;
97         LIST_ENTRY(_msgT) entries;
98 };
99 
100 struct _setT {
101 	long    setId;
102         LIST_HEAD(msghead, _msgT) msghead;
103         LIST_ENTRY(_setT) entries;
104 };
105 
106 LIST_HEAD(sethead, _setT) sethead;
107 static struct _setT *curSet;
108 
109 static char *curline = NULL;
110 static long lineno = 0;
111 
112 extern	char	*__progname;		/* from crt0.o */
113 
114 static	char   *cskip __P((char *));
115 static	void	error __P((char *, char *));
116 static	void	nomem __P((void));
117 static	char   *getline __P((int));
118 static	char   *getmsg __P((int, char *, char));
119 static	void	warning __P((char *, char *));
120 static	char   *wskip __P((char *));
121 static	char   *xstrdup __P((const char *));
122 static	void   *xmalloc __P((size_t));
123 static	void   *xrealloc __P((void *, size_t));
124 
125 void	MCParse __P((int fd));
126 void	MCWriteCat __P((int fd));
127 void	MCDelMsg __P((int msgId));
128 void	MCAddMsg __P((int msgId, const char *msg));
129 void	MCAddSet __P((int setId));
130 void	MCDelSet __P((int setId));
131 int	main __P((int, char **));
132 void	usage __P((void));
133 
134 
135 void
136 usage()
137 {
138 	fprintf(stderr, "Usage: %s catfile msgfile ...\n", __progname);
139 	exit(1);
140 }
141 
142 int
143 main(argc, argv)
144 	int     argc;
145 	char   *argv[];
146 {
147 	int     ofd, ifd;
148 	char   *catfile = NULL;
149 	int     c;
150 
151 	while ((c = getopt(argc, argv, "")) != -1) {
152 		switch (c) {
153 		case '?':
154 		default:
155 			usage();
156 			/* NOTREACHED */
157 		}
158 	}
159 	argc -= optind;
160 	argv += optind;
161 
162 	if (argc < 2) {
163 		usage();
164 		/* NOTREACHED */
165 	}
166 	catfile = *argv++;
167 
168 	for (; *argv; argv++) {
169 		if ((ifd = open(*argv, O_RDONLY)) < 0)
170 			err(1, "Unable to read %s", *argv);
171 		MCParse(ifd);
172 		close(ifd);
173 	}
174 
175 	if ((ofd = open(catfile, O_WRONLY | O_TRUNC | O_CREAT, 0666)) < 0)
176 		err(1, "Unable to create a new %s", catfile);
177 	MCWriteCat(ofd);
178 	exit(0);
179 }
180 
181 static void
182 warning(cptr, msg)
183 	char   *cptr;
184 	char   *msg;
185 {
186 	fprintf(stderr, "%s: %s on line %ld\n", __progname, msg, lineno);
187 	fprintf(stderr, "%s\n", curline);
188 	if (cptr) {
189 		char   *tptr;
190 		for (tptr = curline; tptr < cptr; ++tptr)
191 			putc(' ', stderr);
192 		fprintf(stderr, "^\n");
193 	}
194 }
195 
196 static void
197 error(cptr, msg)
198 	char   *cptr;
199 	char   *msg;
200 {
201 	warning(cptr, msg);
202 	exit(1);
203 }
204 
205 static void
206 nomem()
207 {
208 	error(NULL, "out of memory");
209 }
210 
211 static void *
212 xmalloc(len)
213 	size_t  len;
214 {
215 	void   *p;
216 
217 	if ((p = malloc(len)) == NULL)
218 		nomem();
219 	return (p);
220 }
221 
222 static void *
223 xrealloc(ptr, size)
224 	void   *ptr;
225 	size_t  size;
226 {
227 	if ((ptr = realloc(ptr, size)) == NULL)
228 		nomem();
229 	return (ptr);
230 }
231 
232 static char *
233 xstrdup(str)
234 	const char   *str;
235 {
236 	char *nstr;
237 
238 	if ((nstr = strdup(str)) == NULL)
239 		nomem();
240 	return (nstr);
241 }
242 
243 static char *
244 getline(fd)
245 	int     fd;
246 {
247 	static long curlen = BUFSIZ;
248 	static char buf[BUFSIZ], *bptr = buf, *bend = buf;
249 	char   *cptr, *cend;
250 	long    buflen;
251 
252 	if (!curline) {
253 		curline = xmalloc(curlen);
254 	}
255 	++lineno;
256 
257 	cptr = curline;
258 	cend = curline + curlen;
259 	for (;;) {
260 		for (; bptr < bend && cptr < cend; ++cptr, ++bptr) {
261 			if (*bptr == '\n') {
262 				*cptr = '\0';
263 				++bptr;
264 				return (curline);
265 			} else
266 				*cptr = *bptr;
267 		}
268 		if (bptr == bend) {
269 			buflen = read(fd, buf, BUFSIZ);
270 			if (buflen <= 0) {
271 				if (cptr > curline) {
272 					*cptr = '\0';
273 					return (curline);
274 				}
275 				return (NULL);
276 			}
277 			bend = buf + buflen;
278 			bptr = buf;
279 		}
280 		if (cptr == cend) {
281 			cptr = curline = xrealloc(curline, curlen *= 2);
282 			cend = curline + curlen;
283 		}
284 	}
285 }
286 
287 static char *
288 wskip(cptr)
289 	char   *cptr;
290 {
291 	if (!*cptr || !ISSPACE(*cptr)) {
292 		warning(cptr, "expected a space");
293 		return (cptr);
294 	}
295 	while (*cptr && ISSPACE(*cptr))
296 		++cptr;
297 	return (cptr);
298 }
299 
300 static char *
301 cskip(cptr)
302 	char   *cptr;
303 {
304 	if (!*cptr || ISSPACE(*cptr)) {
305 		warning(cptr, "wasn't expecting a space");
306 		return (cptr);
307 	}
308 	while (*cptr && !ISSPACE(*cptr))
309 		++cptr;
310 	return (cptr);
311 }
312 
313 static char *
314 getmsg(fd, cptr, quote)
315 	int     fd;
316 	char   *cptr;
317 	char    quote;
318 {
319 	static char *msg = NULL;
320 	static long msglen = 0;
321 	long    clen, i;
322 	char   *tptr;
323 
324 	if (quote && *cptr == quote) {
325 		++cptr;
326 	}
327 
328 	clen = strlen(cptr) + 1;
329 	if (clen > msglen) {
330 		if (msglen)
331 			msg = xrealloc(msg, clen);
332 		else
333 			msg = xmalloc(clen);
334 		msglen = clen;
335 	}
336 	tptr = msg;
337 
338 	while (*cptr) {
339 		if (quote && *cptr == quote) {
340 			char   *tmp;
341 			tmp = cptr + 1;
342 			if (*tmp && (!ISSPACE(*tmp) || *wskip(tmp))) {
343 				warning(cptr, "unexpected quote character, ignoring");
344 				*tptr++ = *cptr++;
345 			} else {
346 				*cptr = '\0';
347 			}
348 		} else
349 			if (*cptr == '\\') {
350 				++cptr;
351 				switch (*cptr) {
352 				case '\0':
353 					cptr = getline(fd);
354 					if (!cptr)
355 						error(NULL, "premature end of file");
356 					msglen += strlen(cptr);
357 					i = tptr - msg;
358 					msg = xrealloc(msg, msglen);
359 					tptr = msg + i;
360 					break;
361 				case 'n':
362 					*tptr++ = '\n';
363 					++cptr;
364 					break;
365 				case 't':
366 					*tptr++ = '\t';
367 					++cptr;
368 					break;
369 				case 'v':
370 					*tptr++ = '\v';
371 					++cptr;
372 					break;
373 				case 'b':
374 					*tptr++ = '\b';
375 					++cptr;
376 					break;
377 				case 'r':
378 					*tptr++ = '\r';
379 					++cptr;
380 					break;
381 				case 'f':
382 					*tptr++ = '\f';
383 					++cptr;
384 					break;
385 				case '\\':
386 					*tptr++ = '\\';
387 					++cptr;
388 					break;
389 				case '"':
390 					/* FALLTHROUGH */
391 				case '\'':
392 					/*
393 					 * While it isn't necessary to
394 					 * escape ' and ", let's accept
395 					 * them escaped and not complain.
396 					 * (XPG4 states that '\' should be
397 					 * ignored when not used in a
398 					 * valid escape sequence)
399 					 */
400 					*tptr++ = '"';
401 					++cptr;
402 					break;
403 				default:
404 					if (quote && *cptr == quote) {
405 						*tptr++ = *cptr++;
406 					} else if (isdigit((unsigned char) *cptr)) {
407 						*tptr = 0;
408 						for (i = 0; i < 3; ++i) {
409 							if (!isdigit((unsigned char) *cptr))
410 								break;
411 							if (*cptr > '7')
412 								warning(cptr, "octal number greater than 7?!");
413 							*tptr *= 8;
414 							*tptr += (*cptr - '0');
415 							++cptr;
416 						}
417 					} else {
418 						warning(cptr, "unrecognized escape sequence; ignoring esacpe character");
419 					}
420 					break;
421 				}
422 			} else {
423 				*tptr++ = *cptr++;
424 			}
425 	}
426 	*tptr = '\0';
427 	return (msg);
428 }
429 
430 void
431 MCParse(fd)
432 	int     fd;
433 {
434 	char   *cptr, *str;
435 	int     setid, msgid = 0;
436 	char    quote = 0;
437 
438 	/* XXX: init sethead? */
439 
440 	while ((cptr = getline(fd))) {
441 		if (*cptr == '$') {
442 			++cptr;
443 			if (strncmp(cptr, "set", 3) == 0) {
444 				cptr += 3;
445 				cptr = wskip(cptr);
446 				setid = atoi(cptr);
447 				MCAddSet(setid);
448 				msgid = 0;
449 			} else if (strncmp(cptr, "delset", 6) == 0) {
450 				cptr += 6;
451 				cptr = wskip(cptr);
452 				setid = atoi(cptr);
453 				MCDelSet(setid);
454 			} else if (strncmp(cptr, "quote", 5) == 0) {
455 				cptr += 5;
456 				if (!*cptr)
457 					quote = 0;
458 				else {
459 					cptr = wskip(cptr);
460 					if (!*cptr)
461 						quote = 0;
462 					else
463 						quote = *cptr;
464 				}
465 			} else if (ISSPACE(*cptr)) {
466 				;
467 			} else {
468 				if (*cptr) {
469 					cptr = wskip(cptr);
470 					if (*cptr)
471 						warning(cptr, "unrecognized line");
472 				}
473 			}
474 		} else {
475 			/*
476 			 * First check for (and eat) empty lines....
477 			 */
478 			if (!*cptr)
479 				continue;
480 			/*
481 			 * We have a digit? Start of a message. Else,
482 			 * syntax error.
483 			 */
484 			if (isdigit((unsigned char) *cptr)) {
485 				msgid = atoi(cptr);
486 				cptr = cskip(cptr);
487 				cptr = wskip(cptr);
488 				/* if (*cptr) ++cptr; */
489 			} else {
490 				warning(cptr, "neither blank line nor start of a message id");
491 				continue;
492 			}
493 			/*
494 			 * If we have a message ID, but no message,
495 			 * then this means "delete this message id
496 			 * from the catalog".
497 			 */
498 			if (!*cptr) {
499 				MCDelMsg(msgid);
500 			} else {
501 				str = getmsg(fd, cptr, quote);
502 				MCAddMsg(msgid, str);
503 			}
504 		}
505 	}
506 }
507 
508 /*
509  * Write message catalog.
510  *
511  * The message catalog is first converted from its internal to its
512  * external representation in a chunk of memory allocated for this
513  * purpose.  Then the completed catalog is written.  This approach
514  * avoids additional housekeeping variables and/or a lot of seeks
515  * that would otherwise be required.
516  */
517 void
518 MCWriteCat(fd)
519 	int     fd;
520 {
521 	int     nsets;		/* number of sets */
522 	int     nmsgs;		/* number of msgs */
523 	int     string_size;	/* total size of string pool */
524 	int     msgcat_size;	/* total size of message catalog */
525 	void   *msgcat;		/* message catalog data */
526 	struct _nls_cat_hdr *cat_hdr;
527 	struct _nls_set_hdr *set_hdr;
528 	struct _nls_msg_hdr *msg_hdr;
529 	char   *strings;
530 	struct _setT *set;
531 	struct _msgT *msg;
532 	int     msg_index;
533 	int     msg_offset;
534 
535 	/* determine number of sets, number of messages, and size of the
536 	 * string pool */
537 	nsets = 0;
538 	nmsgs = 0;
539 	string_size = 0;
540 
541 	for (set = sethead.lh_first; set != NULL;
542 	    set = set->entries.le_next) {
543 		nsets++;
544 
545 		for (msg = set->msghead.lh_first; msg != NULL;
546 		    msg = msg->entries.le_next) {
547 			nmsgs++;
548 			string_size += strlen(msg->str) + 1;
549 		}
550 	}
551 
552 #ifdef DEBUG
553 	printf("number of sets: %d\n", nsets);
554 	printf("number of msgs: %d\n", nmsgs);
555 	printf("string pool size: %d\n", string_size);
556 #endif
557 
558 	/* determine size and then allocate buffer for constructing external
559 	 * message catalog representation */
560 	msgcat_size = sizeof(struct _nls_cat_hdr)
561 	    + (nsets * sizeof(struct _nls_set_hdr))
562 	    + (nmsgs * sizeof(struct _nls_msg_hdr))
563 	    + string_size;
564 
565 	msgcat = xmalloc(msgcat_size);
566 	memset(msgcat, '\0', msgcat_size);
567 
568 	/* fill in msg catalog header */
569 	cat_hdr = (struct _nls_cat_hdr *) msgcat;
570 	cat_hdr->__magic = htonl(_NLS_MAGIC);
571 	cat_hdr->__nsets = htonl(nsets);
572 	cat_hdr->__mem = htonl(msgcat_size - sizeof(struct _nls_cat_hdr));
573 	cat_hdr->__msg_hdr_offset =
574 	    htonl(nsets * sizeof(struct _nls_set_hdr));
575 	cat_hdr->__msg_txt_offset =
576 	    htonl(nsets * sizeof(struct _nls_set_hdr) +
577 	    nmsgs * sizeof(struct _nls_msg_hdr));
578 
579 	/* compute offsets for set & msg header tables and string pool */
580 	set_hdr = (struct _nls_set_hdr *) ((char *) msgcat +
581 	    sizeof(struct _nls_cat_hdr));
582 	msg_hdr = (struct _nls_msg_hdr *) ((char *) msgcat +
583 	    sizeof(struct _nls_cat_hdr) +
584 	    nsets * sizeof(struct _nls_set_hdr));
585 	strings = (char *) msgcat +
586 	    sizeof(struct _nls_cat_hdr) +
587 	    nsets * sizeof(struct _nls_set_hdr) +
588 	    nmsgs * sizeof(struct _nls_msg_hdr);
589 
590 	msg_index = 0;
591 	msg_offset = 0;
592 	for (set = sethead.lh_first; set != NULL;
593 	    set = set->entries.le_next) {
594 
595 		nmsgs = 0;
596 		for (msg = set->msghead.lh_first; msg != NULL;
597 		    msg = msg->entries.le_next) {
598 			int     msg_len = strlen(msg->str) + 1;
599 
600 			msg_hdr->__msgno = htonl(msg->msgId);
601 			msg_hdr->__msglen = htonl(msg_len);
602 			msg_hdr->__offset = htonl(msg_offset);
603 
604 			memcpy(strings, msg->str, msg_len);
605 			strings += msg_len;
606 			msg_offset += msg_len;
607 
608 			nmsgs++;
609 			msg_hdr++;
610 		}
611 
612 		set_hdr->__setno = htonl(set->setId);
613 		set_hdr->__nmsgs = htonl(nmsgs);
614 		set_hdr->__index = htonl(msg_index);
615 		msg_index += nmsgs;
616 		set_hdr++;
617 	}
618 
619 	/* write out catalog.  XXX: should this be done in small chunks? */
620 	write(fd, msgcat, msgcat_size);
621 }
622 
623 void
624 MCAddSet(setId)
625 	int     setId;
626 {
627 	struct _setT *p, *q;
628 
629 	if (setId <= 0) {
630 		error(NULL, "setId's must be greater than zero");
631 		/* NOTREACHED */
632 	}
633 #if 0
634 	/* XXX */
635 	if (setId > NL_SETMAX) {
636 		error(NULL, "setId %d exceeds limit (%d)");
637 		/* NOTREACHED */
638 	}
639 #endif
640 
641 	p = sethead.lh_first;
642 	q = NULL;
643 	for (; p != NULL && p->setId < setId; q = p, p = p->entries.le_next);
644 
645 	if (p && p->setId == setId) {
646 		;
647 	} else {
648 		p = xmalloc(sizeof(struct _setT));
649 		memset(p, '\0', sizeof(struct _setT));
650 		LIST_INIT(&p->msghead);
651 
652 		p->setId = setId;
653 
654 		if (q == NULL) {
655 			LIST_INSERT_HEAD(&sethead, p, entries);
656 		} else {
657 			LIST_INSERT_AFTER(q, p, entries);
658 		}
659 	}
660 
661 	curSet = p;
662 }
663 
664 void
665 MCAddMsg(msgId, str)
666 	int     msgId;
667 	const char *str;
668 {
669 	struct _msgT *p, *q;
670 
671 	if (!curSet)
672 		error(NULL, "can't specify a message when no set exists");
673 
674 	if (msgId <= 0) {
675 		error(NULL, "msgId's must be greater than zero");
676 		/* NOTREACHED */
677 	}
678 #if 0
679 	/* XXX */
680 	if (msgId > NL_SETMAX) {
681 		error(NULL, "msgId %d exceeds limit (%d)");
682 		/* NOTREACHED */
683 	}
684 #endif
685 
686 	p = curSet->msghead.lh_first;
687 	q = NULL;
688 	for (; p != NULL && p->msgId < msgId; q = p, p = p->entries.le_next);
689 
690 	if (p && p->msgId == msgId) {
691 		free(p->str);
692 	} else {
693 		p = xmalloc(sizeof(struct _msgT));
694 		memset(p, '\0', sizeof(struct _msgT));
695 
696 		if (q == NULL) {
697 			LIST_INSERT_HEAD(&curSet->msghead, p, entries);
698 		} else {
699 			LIST_INSERT_AFTER(q, p, entries);
700 		}
701 	}
702 
703 	p->msgId = msgId;
704 	p->str = xstrdup(str);
705 }
706 
707 void
708 MCDelSet(setId)
709 	int     setId;
710 {
711 	struct _setT *set;
712 	struct _msgT *msg;
713 
714 	set = sethead.lh_first;
715 	for (; set != NULL && set->setId < setId; set = set->entries.le_next);
716 
717 	if (set && set->setId == setId) {
718 
719 		msg = set->msghead.lh_first;
720 		while (msg) {
721 			free(msg->str);
722 			LIST_REMOVE(msg, entries);
723 		}
724 
725 		LIST_REMOVE(set, entries);
726 		return;
727 	}
728 	warning(NULL, "specified set doesn't exist");
729 }
730 
731 void
732 MCDelMsg(msgId)
733 	int     msgId;
734 {
735 	struct _msgT *msg;
736 
737 	if (!curSet)
738 		error(NULL, "you can't delete a message before defining the set");
739 
740 	msg = curSet->msghead.lh_first;
741 	for (; msg != NULL && msg->msgId < msgId; msg = msg->entries.le_next);
742 
743 	if (msg && msg->msgId == msgId) {
744 		free(msg->str);
745 		LIST_REMOVE(msg, entries);
746 		return;
747 	}
748 	warning(NULL, "specified msg doesn't exist");
749 }
750