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