1/*
2 * message.c --- print e2fsck messages (with compression)
3 *
4 * Copyright 1996, 1997 by Theodore Ts'o
5 *
6 * %Begin-Header%
7 * This file may be redistributed under the terms of the GNU Public
8 * License.
9 * %End-Header%
10 *
11 * print_e2fsck_message() prints a message to the user, using
12 * compression techniques and expansions of abbreviations.
13 *
14 * The following % expansions are supported:
15 *
16 * 	%b	<blk>			block number
17 * 	%B	<blkcount>		integer
18 * 	%c	<blk2>			block number
19 * 	%Di	<dirent>->ino		inode number
20 * 	%Dn	<dirent>->name		string
21 * 	%Dr	<dirent>->rec_len
22 * 	%Dl	<dirent>->name_len
23 * 	%Dt	<dirent>->filetype
24 * 	%d	<dir> 			inode number
25 * 	%g	<group>			integer
26 * 	%i	<ino>			inode number
27 * 	%Is	<inode> -> i_size
28 * 	%IS	<inode> -> i_extra_isize
29 * 	%Ib	<inode> -> i_blocks
30 * 	%Il	<inode> -> i_links_count
31 * 	%Im	<inode> -> i_mode
32 * 	%IM	<inode> -> i_mtime
33 * 	%IF	<inode> -> i_faddr
34 * 	%If	<inode> -> i_file_acl
35 * 	%Id	<inode> -> i_dir_acl
36 * 	%Iu	<inode> -> i_uid
37 * 	%Ig	<inode> -> i_gid
38 *	%It	<inode type>
39 * 	%j	<ino2>			inode number
40 * 	%m	<com_err error message>
41 * 	%N	<num>
42 *	%p	ext2fs_get_pathname of directory <ino>
43 * 	%P	ext2fs_get_pathname of <dirent>->ino with <ino2> as
44 * 			the containing directory.  (If dirent is NULL
45 * 			then return the pathname of directory <ino2>)
46 * 	%q	ext2fs_get_pathname of directory <dir>
47 * 	%Q	ext2fs_get_pathname of directory <ino> with <dir> as
48 * 			the containing directory.
49 * 	%s	<str>			miscellaneous string
50 * 	%S	backup superblock
51 * 	%X	<num> hexadecimal format
52 *
53 * The following '@' expansions are supported:
54 *
55 * 	@a	extended attribute
56 * 	@A	error allocating
57 * 	@b	block
58 * 	@B	bitmap
59 * 	@c	compress
60 * 	@C	conflicts with some other fs block
61 * 	@D	deleted
62 * 	@d	directory
63 * 	@e	entry
64 * 	@E	Entry '%Dn' in %p (%i)
65 * 	@f	filesystem
66 * 	@F	for @i %i (%Q) is
67 * 	@g	group
68 * 	@h	HTREE directory inode
69 * 	@i	inode
70 * 	@I	illegal
71 * 	@j	journal
72 * 	@l	lost+found
73 * 	@L	is a link
74 *	@m	multiply-claimed
75 *	@n	invalid
76 * 	@o	orphaned
77 * 	@p	problem in
78 * 	@r	root inode
79 * 	@s	should be
80 * 	@S	superblock
81 * 	@u	unattached
82 * 	@v	device
83 * 	@z	zero-length
84 */
85
86#include <stdlib.h>
87#include <unistd.h>
88#include <string.h>
89#include <ctype.h>
90#include <termios.h>
91
92#include "e2fsck.h"
93
94#include "problem.h"
95
96#ifdef __GNUC__
97#define _INLINE_ __inline__
98#else
99#define _INLINE_
100#endif
101
102/*
103 * This structure defines the abbreviations used by the text strings
104 * below.  The first character in the string is the index letter.  An
105 * abbreviation of the form '@<i>' is expanded by looking up the index
106 * letter <i> in the table below.
107 */
108static const char *abbrevs[] = {
109	N_("aextended attribute"),
110	N_("Aerror allocating"),
111	N_("bblock"),
112	N_("Bbitmap"),
113	N_("ccompress"),
114	N_("Cconflicts with some other fs @b"),
115	N_("iinode"),
116	N_("Iillegal"),
117	N_("jjournal"),
118	N_("Ddeleted"),
119	N_("ddirectory"),
120	N_("eentry"),
121	N_("E@e '%Dn' in %p (%i)"),
122	N_("ffilesystem"),
123	N_("Ffor @i %i (%Q) is"),
124	N_("ggroup"),
125	N_("hHTREE @d @i"),
126	N_("llost+found"),
127	N_("Lis a link"),
128	N_("mmultiply-claimed"),
129	N_("ninvalid"),
130	N_("oorphaned"),
131	N_("pproblem in"),
132	N_("rroot @i"),
133	N_("sshould be"),
134	N_("Ssuper@b"),
135	N_("uunattached"),
136	N_("vdevice"),
137	N_("zzero-length"),
138	"@@",
139	0
140	};
141
142/*
143 * Give more user friendly names to the "special" inodes.
144 */
145#define num_special_inodes	11
146static const char *special_inode_name[] =
147{
148	N_("<The NULL inode>"),			/* 0 */
149	N_("<The bad blocks inode>"),		/* 1 */
150	"/",					/* 2 */
151	N_("<The ACL index inode>"),		/* 3 */
152	N_("<The ACL data inode>"),		/* 4 */
153	N_("<The boot loader inode>"),		/* 5 */
154	N_("<The undelete directory inode>"),	/* 6 */
155	N_("<The group descriptor inode>"),	/* 7 */
156	N_("<The journal inode>"),		/* 8 */
157	N_("<Reserved inode 9>"),		/* 9 */
158	N_("<Reserved inode 10>"),		/* 10 */
159};
160
161/*
162 * This function does "safe" printing.  It will convert non-printable
163 * ASCII characters using '^' and M- notation.
164 */
165static void safe_print(const char *cp, int len)
166{
167	unsigned char	ch;
168
169	if (len < 0)
170		len = strlen(cp);
171
172	while (len--) {
173		ch = *cp++;
174		if (ch > 128) {
175			fputs("M-", stdout);
176			ch -= 128;
177		}
178		if ((ch < 32) || (ch == 0x7f)) {
179			fputc('^', stdout);
180			ch ^= 0x40; /* ^@, ^A, ^B; ^? for DEL */
181		}
182		fputc(ch, stdout);
183	}
184}
185
186
187/*
188 * This function prints a pathname, using the ext2fs_get_pathname
189 * function
190 */
191static void print_pathname(ext2_filsys fs, ext2_ino_t dir, ext2_ino_t ino)
192{
193	errcode_t	retval;
194	char		*path;
195
196	if (!dir && (ino < num_special_inodes)) {
197		fputs(_(special_inode_name[ino]), stdout);
198		return;
199	}
200
201	retval = ext2fs_get_pathname(fs, dir, ino, &path);
202	if (retval)
203		fputs("???", stdout);
204	else {
205		safe_print(path, -1);
206		ext2fs_free_mem(&path);
207	}
208}
209
210/*
211 * This function handles the '@' expansion.  We allow recursive
212 * expansion; an @ expression can contain further '@' and '%'
213 * expressions.
214 */
215static _INLINE_ void expand_at_expression(e2fsck_t ctx, char ch,
216					  struct problem_context *pctx,
217					  int *first, int recurse)
218{
219	const char **cpp, *str;
220
221	/* Search for the abbreviation */
222	for (cpp = abbrevs; *cpp; cpp++) {
223		if (ch == *cpp[0])
224			break;
225	}
226	if (*cpp && recurse < 10) {
227		str = _(*cpp) + 1;
228		if (*first && islower(*str)) {
229			*first = 0;
230			fputc(toupper(*str++), stdout);
231		}
232		print_e2fsck_message(ctx, str, pctx, *first, recurse+1);
233	} else
234		printf("@%c", ch);
235}
236
237/*
238 * This function expands '%IX' expressions
239 */
240static _INLINE_ void expand_inode_expression(char ch,
241					     struct problem_context *ctx)
242{
243	struct ext2_inode	*inode;
244	struct ext2_inode_large	*large_inode;
245	const char *		time_str;
246	time_t			t;
247	int			do_gmt = -1;
248
249	if (!ctx || !ctx->inode)
250		goto no_inode;
251
252	inode = ctx->inode;
253	large_inode = (struct ext2_inode_large *) inode;
254
255	switch (ch) {
256	case 's':
257		if (LINUX_S_ISDIR(inode->i_mode))
258			printf("%u", inode->i_size);
259		else {
260#ifdef EXT2_NO_64_TYPE
261			if (inode->i_size_high)
262				printf("0x%x%08x", inode->i_size_high,
263				       inode->i_size);
264			else
265				printf("%u", inode->i_size);
266#else
267			printf("%llu", inode->i_size |
268				       ((long long)inode->i_size_high << 32));
269#endif
270		}
271		break;
272	case 'S':
273		printf("%u", large_inode->i_extra_isize);
274		break;
275	case 'b':
276		printf("%u", inode->i_blocks);
277		break;
278	case 'l':
279		printf("%d", inode->i_links_count);
280		break;
281	case 'm':
282		printf("0%o", inode->i_mode);
283		break;
284	case 'M':
285		/* The diet libc doesn't respect the TZ environemnt variable */
286		if (do_gmt == -1) {
287			time_str = getenv("TZ");
288			if (!time_str)
289				time_str = "";
290			do_gmt = !strcmp(time_str, "GMT");
291		}
292		t = inode->i_mtime;
293		time_str = asctime(do_gmt ? gmtime(&t) : localtime(&t));
294		printf("%.24s", time_str);
295		break;
296	case 'F':
297		printf("%u", inode->i_faddr);
298		break;
299	case 'f':
300		printf("%u", inode->i_file_acl);
301		break;
302	case 'd':
303		printf("%u", (LINUX_S_ISDIR(inode->i_mode) ?
304			      inode->i_dir_acl : 0));
305		break;
306	case 'u':
307		printf("%d", inode_uid(*inode));
308		break;
309	case 'g':
310		printf("%d", inode_gid(*inode));
311		break;
312	case 't':
313		if (LINUX_S_ISREG(inode->i_mode))
314			printf(_("regular file"));
315		else if (LINUX_S_ISDIR(inode->i_mode))
316			printf(_("directory"));
317		else if (LINUX_S_ISCHR(inode->i_mode))
318			printf(_("character device"));
319		else if (LINUX_S_ISBLK(inode->i_mode))
320			printf(_("block device"));
321		else if (LINUX_S_ISFIFO(inode->i_mode))
322			printf(_("named pipe"));
323		else if (LINUX_S_ISLNK(inode->i_mode))
324			printf(_("symbolic link"));
325		else if (LINUX_S_ISSOCK(inode->i_mode))
326			printf(_("socket"));
327		else
328			printf(_("unknown file type with mode 0%o"),
329			       inode->i_mode);
330		break;
331	default:
332	no_inode:
333		printf("%%I%c", ch);
334		break;
335	}
336}
337
338/*
339 * This function expands '%dX' expressions
340 */
341static _INLINE_ void expand_dirent_expression(char ch,
342					      struct problem_context *ctx)
343{
344	struct ext2_dir_entry	*dirent;
345	int	len;
346
347	if (!ctx || !ctx->dirent)
348		goto no_dirent;
349
350	dirent = ctx->dirent;
351
352	switch (ch) {
353	case 'i':
354		printf("%u", dirent->inode);
355		break;
356	case 'n':
357		len = dirent->name_len & 0xFF;
358		if (len > EXT2_NAME_LEN)
359			len = EXT2_NAME_LEN;
360		if (len > dirent->rec_len)
361			len = dirent->rec_len;
362		safe_print(dirent->name, len);
363		break;
364	case 'r':
365		printf("%u", dirent->rec_len);
366		break;
367	case 'l':
368		printf("%u", dirent->name_len & 0xFF);
369		break;
370	case 't':
371		printf("%u", dirent->name_len >> 8);
372		break;
373	default:
374	no_dirent:
375		printf("%%D%c", ch);
376		break;
377	}
378}
379
380static _INLINE_ void expand_percent_expression(ext2_filsys fs, char ch,
381					       struct problem_context *ctx)
382{
383	if (!ctx)
384		goto no_context;
385
386	switch (ch) {
387	case '%':
388		fputc('%', stdout);
389		break;
390	case 'b':
391		printf("%u", ctx->blk);
392		break;
393	case 'B':
394#ifdef EXT2_NO_64_TYPE
395		printf("%d", ctx->blkcount);
396#else
397		printf("%lld", (long long)ctx->blkcount);
398#endif
399		break;
400	case 'c':
401		printf("%u", ctx->blk2);
402		break;
403	case 'd':
404		printf("%u", ctx->dir);
405		break;
406	case 'g':
407		printf("%d", ctx->group);
408		break;
409	case 'i':
410		printf("%u", ctx->ino);
411		break;
412	case 'j':
413		printf("%u", ctx->ino2);
414		break;
415	case 'm':
416		printf("%s", error_message(ctx->errcode));
417		break;
418	case 'N':
419#ifdef EXT2_NO_64_TYPE
420		printf("%u", ctx->num);
421#else
422		printf("%llu", (long long)ctx->num);
423#endif
424		break;
425	case 'p':
426		print_pathname(fs, ctx->ino, 0);
427		break;
428	case 'P':
429		print_pathname(fs, ctx->ino2,
430			       ctx->dirent ? ctx->dirent->inode : 0);
431		break;
432	case 'q':
433		print_pathname(fs, ctx->dir, 0);
434		break;
435	case 'Q':
436		print_pathname(fs, ctx->dir, ctx->ino);
437		break;
438	case 'S':
439		printf("%u", get_backup_sb(NULL, fs, NULL, NULL));
440		break;
441	case 's':
442		printf("%s", ctx->str ? ctx->str : "NULL");
443		break;
444	case 'X':
445#ifdef EXT2_NO_64_TYPE
446		printf("0x%x", ctx->num);
447#else
448		printf("0x%llx", (long long)ctx->num);
449#endif
450		break;
451	default:
452	no_context:
453		printf("%%%c", ch);
454		break;
455	}
456}
457
458void print_e2fsck_message(e2fsck_t ctx, const char *msg,
459			  struct problem_context *pctx, int first,
460			  int recurse)
461{
462	ext2_filsys fs = ctx->fs;
463	const char *	cp;
464	int		i;
465
466	e2fsck_clear_progbar(ctx);
467	for (cp = msg; *cp; cp++) {
468		if (cp[0] == '@') {
469			cp++;
470			expand_at_expression(ctx, *cp, pctx, &first, recurse);
471		} else if (cp[0] == '%' && cp[1] == 'I') {
472			cp += 2;
473			expand_inode_expression(*cp, pctx);
474		} else if (cp[0] == '%' && cp[1] == 'D') {
475			cp += 2;
476			expand_dirent_expression(*cp, pctx);
477		} else if ((cp[0] == '%')) {
478			cp++;
479			expand_percent_expression(fs, *cp, pctx);
480		} else {
481			for (i=0; cp[i]; i++)
482				if ((cp[i] == '@') || cp[i] == '%')
483					break;
484			printf("%.*s", i, cp);
485			cp += i-1;
486		}
487		first = 0;
488	}
489}
490