mkmakefile.c revision 73193
1/*
2 * Copyright (c) 1993, 19801990
3 *	The Regents of the University of California.  All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 *    must display the following acknowledgement:
15 *	This product includes software developed by the University of
16 *	California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 *    may be used to endorse or promote products derived from this software
19 *    without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#ifndef lint
35#if 0
36static char sccsid[] = "@(#)mkmakefile.c	8.1 (Berkeley) 6/6/93";
37#endif
38static const char rcsid[] =
39  "$FreeBSD: head/usr.sbin/config/mkmakefile.c 73193 2001-02-28 01:38:01Z peter $";
40#endif /* not lint */
41
42/*
43 * Build the makefile for the system, from
44 * the information in the files files and the
45 * additional files for the machine being compiled to.
46 */
47
48#include <ctype.h>
49#include <err.h>
50#include <stdio.h>
51#include <string.h>
52#include <sys/param.h>
53#include "y.tab.h"
54#include "config.h"
55#include "configvers.h"
56
57#define next_word(fp, wd) \
58	{ char *word = get_word(fp); \
59	  if (word == (char *)EOF) \
60		return; \
61	  else \
62		wd = word; \
63	}
64#define next_quoted_word(fp, wd) \
65	{ char *word = get_quoted_word(fp); \
66	  if (word == (char *)EOF) \
67		return; \
68	  else \
69		wd = word; \
70	}
71
72static struct file_list *fcur;
73
74static char *tail(char *);
75static void do_clean(FILE *);
76static void do_rules(FILE *);
77static void do_xxfiles(char *, FILE *);
78static void do_objs(FILE *);
79static void do_before_depend(FILE *);
80static int opteq(const char *, const char *);
81static void read_files(void);
82
83/*
84 * Lookup a file, by name.
85 */
86static struct file_list *
87fl_lookup(char *file)
88{
89	struct file_list *fp;
90
91	for (fp = ftab ; fp != 0; fp = fp->f_next) {
92		if (eq(fp->f_fn, file))
93			return (fp);
94	}
95	return (0);
96}
97
98/*
99 * Lookup a file, by final component name.
100 */
101static struct file_list *
102fltail_lookup(char *file)
103{
104	struct file_list *fp;
105
106	for (fp = ftab ; fp != 0; fp = fp->f_next) {
107		if (eq(tail(fp->f_fn), tail(file)))
108			return (fp);
109	}
110	return (0);
111}
112
113/*
114 * Make a new file list entry
115 */
116static struct file_list *
117new_fent(void)
118{
119	struct file_list *fp;
120
121	fp = (struct file_list *) malloc(sizeof *fp);
122	bzero(fp, sizeof *fp);
123	if (fcur == 0)
124		fcur = ftab = fp;
125	else
126		fcur->f_next = fp;
127	fcur = fp;
128	return (fp);
129}
130
131/*
132 * Build the makefile from the skeleton
133 */
134void
135makefile(void)
136{
137	FILE *ifp, *ofp;
138	char line[BUFSIZ];
139	struct opt *op;
140	int versreq;
141	char *s;
142
143	read_files();
144	snprintf(line, sizeof(line), "../../conf/Makefile.%s", machinename);
145	ifp = fopen(line, "r");
146	if (ifp == 0) {
147		snprintf(line, sizeof(line), "Makefile.%s", machinename);
148		ifp = fopen(line, "r");
149	}
150	if (ifp == 0)
151		err(1, "%s", line);
152	ofp = fopen(path("Makefile.new"), "w");
153	if (ofp == 0)
154		err(1, "%s", path("Makefile.new"));
155	fprintf(ofp, "KERN_IDENT=%s\n", raisestr(ident));
156	fprintf(ofp, "IDENT=");
157	if (profiling)
158		fprintf(ofp, " -DGPROF");
159
160	if (cputype == 0) {
161		printf("cpu type must be specified\n");
162		exit(1);
163	}
164	fprintf(ofp, "\n");
165	for (op = mkopt; op; op = op->op_next)
166		fprintf(ofp, "%s=%s\n", op->op_name, op->op_value);
167	if (debugging)
168		fprintf(ofp, "DEBUG=-g\n");
169	if (profiling) {
170		fprintf(ofp, "PROF=-pg\n");
171		fprintf(ofp, "PROFLEVEL=%d\n", profiling);
172	}
173	if (*srcdir != '\0')
174		fprintf(ofp,"S=%s\n", srcdir);
175	while (fgets(line, BUFSIZ, ifp) != 0) {
176		if (*line != '%') {
177			fprintf(ofp, "%s", line);
178			continue;
179		}
180		if (eq(line, "%BEFORE_DEPEND\n"))
181			do_before_depend(ofp);
182		else if (eq(line, "%OBJS\n"))
183			do_objs(ofp);
184		else if (strncmp(line, "%FILES.", 7) == 0)
185			do_xxfiles(line, ofp);
186		else if (eq(line, "%RULES\n"))
187			do_rules(ofp);
188		else if (eq(line, "%CLEAN\n"))
189			do_clean(ofp);
190		else if (strncmp(line, "%VERSREQ=", sizeof("%VERSREQ=") - 1) == 0) {
191			versreq = atoi(line + sizeof("%VERSREQ=") - 1);
192			if (versreq != CONFIGVERS) {
193				fprintf(stderr, "ERROR: version of config(8) does not match kernel!\n");
194				fprintf(stderr, "config version = %d, ", CONFIGVERS);
195				fprintf(stderr, "version required = %d\n\n", versreq);
196				fprintf(stderr, "Make sure that /usr/src/usr.sbin/config is in sync\n");
197				fprintf(stderr, "with your /usr/src/sys and install a new config binary\n");
198				fprintf(stderr, "before trying this again.\n\n");
199				fprintf(stderr, "If running the new config fails check your config\n");
200				fprintf(stderr, "file against the GENERIC or LINT config files for\n");
201				fprintf(stderr, "changes in config syntax, or option/device naming\n");
202				fprintf(stderr, "conventions\n\n");
203				exit(1);
204			}
205		} else
206			fprintf(stderr,
207			    "Unknown %% construct in generic makefile: %s",
208			    line);
209	}
210	(void) fclose(ifp);
211	(void) fclose(ofp);
212	moveifchanged(path("Makefile.new"), path("Makefile"));
213
214	if (hints) {
215		ifp = fopen(hints, "r");
216		if (ifp == NULL)
217			err(1, "%s", hints);
218	} else {
219		ifp = NULL;
220	}
221	ofp = fopen(path("hints.c.new"), "w");
222	if (ofp == NULL)
223		err(1, "%s", path("hints.c.new"));
224	if (hintmode == 0) {
225		snprintf(line, sizeof(line), "%s.hints", PREFIX);
226		ifp = fopen(line, "r");
227		if (ifp)
228			hintmode = 2;
229	}
230	fprintf(ofp, "int hintmode = %d;\n", hintmode);
231	fprintf(ofp, "char static_hints[] = {\n");
232	if (ifp) {
233		while (fgets(line, BUFSIZ, ifp) != 0) {
234			/* zap trailing CR and/or LF */
235			while ((s = rindex(line, '\n')) != NULL)
236				*s = '\0';
237			while ((s = rindex(line, '\r')) != NULL)
238				*s = '\0';
239			/* remove # comments */
240			s = index(line, '#');
241			if (s)
242				*s = '\0';
243			/* remove any whitespace and " characters */
244			s = line;
245			while (*s) {
246				if (*s == ' ' || *s == '\t' || *s == '"') {
247					while (*s) {
248						s[0] = s[1];
249						s++;
250					}
251					/* start over */
252					s = line;
253					continue;
254				}
255				s++;
256			}
257			/* anything left? */
258			if (*line == '\0')
259				continue;
260			fprintf(ofp, "\"%s\\0\"\n", line);
261		}
262	}
263	fprintf(ofp, "\"\\0\"\n};\n");
264	if (ifp)
265		fclose(ifp);
266	fclose(ofp);
267	moveifchanged(path("hints.c.new"), path("hints.c"));
268}
269
270/*
271 * Read in the information about files used in making the system.
272 * Store it in the ftab linked list.
273 */
274static void
275read_files(void)
276{
277	FILE *fp;
278	struct file_list *tp, *pf;
279	struct device *dp;
280	struct opt *op;
281	char *wd, *this, *needs, *special, *depends, *clean, *warning;
282	char fname[MAXPATHLEN];
283	int nreqs, first = 1, configdep, isdup, std, filetype,
284	    imp_rule, no_obj, needcount, before_depend, mandatory;
285
286	ftab = 0;
287	if (ident == NULL) {
288		printf("no ident line specified\n");
289		exit(1);
290	}
291	(void) snprintf(fname, sizeof(fname), "../../conf/files");
292openit:
293	fp = fopen(fname, "r");
294	if (fp == 0)
295		err(1, "%s", fname);
296next:
297	/*
298	 * filename    [ standard | mandatory | optional | count ]
299	 *	[ config-dependent ]
300	 *	[ dev* | profiling-routine ] [ no-obj ]
301	 *	[ compile-with "compile rule" [no-implicit-rule] ]
302	 *      [ dependency "dependency-list"] [ before-depend ]
303	 *	[ clean "file-list"] [ warning "text warning" ]
304	 */
305	wd = get_word(fp);
306	if (wd == (char *)EOF) {
307		(void) fclose(fp);
308		if (first == 1) {
309			first++;
310			(void) snprintf(fname, sizeof(fname),
311			    "../../conf/files.%s", machinename);
312			fp = fopen(fname, "r");
313			if (fp != 0)
314				goto next;
315			(void) snprintf(fname, sizeof(fname),
316			    "files.%s", machinename);
317			goto openit;
318		}
319		if (first == 2) {
320			first++;
321			(void) snprintf(fname, sizeof(fname),
322			    "files.%s", raisestr(ident));
323			fp = fopen(fname, "r");
324			if (fp != 0)
325				goto next;
326		}
327		return;
328	}
329	if (wd == 0)
330		goto next;
331	if (wd[0] == '#')
332	{
333		while (((wd = get_word(fp)) != (char *)EOF) && wd)
334			;
335		goto next;
336	}
337	this = ns(wd);
338	next_word(fp, wd);
339	if (wd == 0) {
340		printf("%s: No type for %s.\n",
341		    fname, this);
342		exit(1);
343	}
344	if ((pf = fl_lookup(this)) && (pf->f_type != INVISIBLE || pf->f_flags))
345		isdup = ISDUP;
346	else
347		isdup = 0;
348	tp = 0;
349	if (first == 3 && pf == 0 && (tp = fltail_lookup(this)) != 0) {
350		if (tp->f_type != INVISIBLE || tp->f_flags)
351			printf("%s: Local file %s overrides %s.\n",
352			    fname, this, tp->f_fn);
353		else
354			printf("%s: Local file %s could override %s"
355			    " with a different kernel configuration.\n",
356			    fname, this, tp->f_fn);
357	}
358	nreqs = 0;
359	special = 0;
360	depends = 0;
361	clean = 0;
362	warning = 0;
363	configdep = 0;
364	needs = 0;
365	std = mandatory = 0;
366	imp_rule = 0;
367	no_obj = 0;
368	needcount = 0;
369	before_depend = 0;
370	filetype = NORMAL;
371	if (eq(wd, "standard")) {
372		std = 1;
373	/*
374	 * If an entry is marked "mandatory", config will abort if it's
375	 * not called by a configuration line in the config file.  Apart
376	 * from this, the device is handled like one marked "optional".
377	 */
378	} else if (eq(wd, "mandatory")) {
379		mandatory = 1;
380	} else if (eq(wd, "count")) {
381		needcount = 1;
382	} else if (!eq(wd, "optional")) {
383		printf("%s: %s must be count, optional, mandatory or standard\n",
384		       fname, this);
385		exit(1);
386	}
387nextparam:
388	next_word(fp, wd);
389	if (wd == 0)
390		goto doneparam;
391	if (eq(wd, "config-dependent")) {
392		configdep++;
393		goto nextparam;
394	}
395	if (eq(wd, "no-obj")) {
396		no_obj++;
397		goto nextparam;
398	}
399	if (eq(wd, "no-implicit-rule")) {
400		if (special == 0) {
401			printf("%s: alternate rule required when "
402			       "\"no-implicit-rule\" is specified.\n",
403			       fname);
404		}
405		imp_rule++;
406		goto nextparam;
407	}
408	if (eq(wd, "before-depend")) {
409		before_depend++;
410		goto nextparam;
411	}
412	if (eq(wd, "dependency")) {
413		next_quoted_word(fp, wd);
414		if (wd == 0) {
415			printf("%s: %s missing compile command string.\n",
416			       fname, this);
417			exit(1);
418		}
419		depends = ns(wd);
420		goto nextparam;
421	}
422	if (eq(wd, "clean")) {
423		next_quoted_word(fp, wd);
424		if (wd == 0) {
425			printf("%s: %s missing clean file list.\n",
426			       fname, this);
427			exit(1);
428		}
429		clean = ns(wd);
430		goto nextparam;
431	}
432	if (eq(wd, "compile-with")) {
433		next_quoted_word(fp, wd);
434		if (wd == 0) {
435			printf("%s: %s missing compile command string.\n",
436			       fname, this);
437			exit(1);
438		}
439		special = ns(wd);
440		goto nextparam;
441	}
442	if (eq(wd, "warning")) {
443		next_quoted_word(fp, wd);
444		if (wd == 0) {
445			printf("%s: %s missing warning text string.\n",
446				fname, this);
447			exit(1);
448		}
449		warning = ns(wd);
450		goto nextparam;
451	}
452	nreqs++;
453	if (eq(wd, "local")) {
454		filetype = LOCAL;
455		goto nextparam;
456	}
457	if (eq(wd, "no-depend")) {
458		filetype = NODEPEND;
459		goto nextparam;
460	}
461	if (eq(wd, "profiling-routine")) {
462		filetype = PROFILING;
463		goto nextparam;
464	}
465	if (needs == 0 && nreqs == 1)
466		needs = ns(wd);
467	if (isdup)
468		goto invis;
469	for (dp = dtab; dp != 0; dp = dp->d_next)
470		if (eq(dp->d_name, wd)) {
471			if (std && dp->d_count <= 0)
472				dp->d_count = 1;
473			goto nextparam;
474		}
475	if (mandatory) {
476		printf("%s: mandatory device \"%s\" not found\n",
477		       fname, wd);
478		exit(1);
479	}
480	if (std) {
481		printf("standard entry %s has a device keyword - %s!\n",
482		       this, wd);
483		exit(1);
484	}
485	for (op = opt; op != 0; op = op->op_next)
486		if (op->op_value == 0 && opteq(op->op_name, wd)) {
487			if (nreqs == 1) {
488				free(needs);
489				needs = 0;
490			}
491			goto nextparam;
492		}
493invis:
494	while ((wd = get_word(fp)) != 0)
495		;
496	if (tp == 0)
497		tp = new_fent();
498	tp->f_fn = this;
499	tp->f_type = INVISIBLE;
500	tp->f_needs = needs;
501	tp->f_flags |= isdup;
502	if (needcount)
503		tp->f_flags |= NEED_COUNT;
504	tp->f_special = special;
505	tp->f_depends = depends;
506	tp->f_clean = clean;
507	tp->f_warn = warning;
508	goto next;
509
510doneparam:
511	if (std == 0 && nreqs == 0) {
512		printf("%s: what is %s optional on?\n",
513		    fname, this);
514		exit(1);
515	}
516
517	if (wd) {
518		printf("%s: syntax error describing %s\n",
519		    fname, this);
520		exit(1);
521	}
522	if (filetype == PROFILING && profiling == 0)
523		goto next;
524	if (tp == 0)
525		tp = new_fent();
526	tp->f_fn = this;
527	tp->f_type = filetype;
528	tp->f_flags &= ~ISDUP;
529	if (configdep)
530		tp->f_flags |= CONFIGDEP;
531	if (imp_rule)
532		tp->f_flags |= NO_IMPLCT_RULE;
533	if (no_obj)
534		tp->f_flags |= NO_OBJ;
535	if (before_depend)
536		tp->f_flags |= BEFORE_DEPEND;
537	if (imp_rule)
538		tp->f_flags |= NO_IMPLCT_RULE;
539	if (no_obj)
540		tp->f_flags |= NO_OBJ;
541	if (needcount)
542		tp->f_flags |= NEED_COUNT;
543	tp->f_needs = needs;
544	tp->f_special = special;
545	tp->f_depends = depends;
546	tp->f_clean = clean;
547	tp->f_warn = warning;
548	if (pf && pf->f_type == INVISIBLE)
549		pf->f_flags |= ISDUP;		/* mark as duplicate */
550	goto next;
551}
552
553static int
554opteq(const char *cp, const char *dp)
555{
556	char c, d;
557
558	for (; ; cp++, dp++) {
559		if (*cp != *dp) {
560			c = isupper(*cp) ? tolower(*cp) : *cp;
561			d = isupper(*dp) ? tolower(*dp) : *dp;
562			if (c != d)
563				return (0);
564		}
565		if (*cp == 0)
566			return (1);
567	}
568}
569
570static void
571do_before_depend(FILE *fp)
572{
573	struct file_list *tp;
574	int lpos, len;
575
576	fputs("BEFORE_DEPEND=", fp);
577	lpos = 15;
578	for (tp = ftab; tp; tp = tp->f_next)
579		if (tp->f_flags & BEFORE_DEPEND) {
580			len = strlen(tp->f_fn);
581			if ((len = 3 + len) + lpos > 72) {
582				lpos = 8;
583				fputs("\\\n\t", fp);
584			}
585			if (tp->f_flags & NO_IMPLCT_RULE)
586				fprintf(fp, "%s ", tp->f_fn);
587			else
588				fprintf(fp, "$S/%s ", tp->f_fn);
589			lpos += len + 1;
590		}
591	if (lpos != 8)
592		putc('\n', fp);
593}
594
595static void
596do_objs(FILE *fp)
597{
598	struct file_list *tp;
599	int lpos, len;
600	char *cp, och, *sp;
601
602	fprintf(fp, "OBJS=");
603	lpos = 6;
604	for (tp = ftab; tp != 0; tp = tp->f_next) {
605		if (tp->f_type == INVISIBLE || tp->f_flags & NO_OBJ)
606			continue;
607		sp = tail(tp->f_fn);
608		cp = sp + (len = strlen(sp)) - 1;
609		och = *cp;
610		*cp = 'o';
611		if (len + lpos > 72) {
612			lpos = 8;
613			fprintf(fp, "\\\n\t");
614		}
615		fprintf(fp, "%s ", sp);
616		lpos += len + 1;
617		*cp = och;
618	}
619	if (lpos != 8)
620		putc('\n', fp);
621}
622
623static void
624do_xxfiles(char *tag, FILE *fp)
625{
626	struct file_list *tp;
627	int lpos, len, slen;
628	char *suff, *SUFF;
629
630	if (tag[strlen(tag) - 1] == '\n')
631		tag[strlen(tag) - 1] = '\0';
632
633	suff = ns(tag + 7);
634	SUFF = ns(suff);
635	raisestr(SUFF);
636	slen = strlen(suff);
637
638	fprintf(fp, "%sFILES=", SUFF);
639	lpos = 8;
640	for (tp = ftab; tp; tp = tp->f_next)
641		if (tp->f_type != INVISIBLE && tp->f_type != NODEPEND) {
642			len = strlen(tp->f_fn);
643			if (tp->f_fn[len - slen - 1] != '.')
644				continue;
645			if (strcasecmp(&tp->f_fn[len - slen], suff) != 0)
646				continue;
647			if ((len = 3 + len) + lpos > 72) {
648				lpos = 8;
649				fputs("\\\n\t", fp);
650			}
651			if (tp->f_type != LOCAL)
652				fprintf(fp, "$S/%s ", tp->f_fn);
653			else
654				fprintf(fp, "%s ", tp->f_fn);
655			lpos += len + 1;
656		}
657	if (lpos != 8)
658		putc('\n', fp);
659}
660
661static char *
662tail(char *fn)
663{
664	char *cp;
665
666	cp = rindex(fn, '/');
667	if (cp == 0)
668		return (fn);
669	return (cp+1);
670}
671
672/*
673 * Create the makerules for each file
674 * which is part of the system.
675 */
676static void
677do_rules(FILE *f)
678{
679	char *cp, *np, och, *tp;
680	struct file_list *ftp;
681	char *special;
682
683	for (ftp = ftab; ftp != 0; ftp = ftp->f_next) {
684		if (ftp->f_type == INVISIBLE)
685			continue;
686		if (ftp->f_warn)
687			printf("WARNING: %s\n", ftp->f_warn);
688		cp = (np = ftp->f_fn) + strlen(ftp->f_fn) - 1;
689		och = *cp;
690		if (ftp->f_flags & NO_IMPLCT_RULE) {
691			if (ftp->f_depends)
692				fprintf(f, "%s: %s\n", np, ftp->f_depends);
693			else
694				fprintf(f, "%s: \n", np);
695		}
696		else {
697			*cp = '\0';
698			if (och == 'o') {
699				fprintf(f, "%so:\n\t-cp $S/%so .\n\n",
700					tail(np), np);
701				continue;
702			}
703			if (ftp->f_depends)
704				fprintf(f, "%so: $S/%s%c %s\n", tail(np),
705					np, och, ftp->f_depends);
706			else
707				fprintf(f, "%so: $S/%s%c\n", tail(np),
708					np, och);
709		}
710		tp = tail(np);
711		special = ftp->f_special;
712		if (special == 0) {
713			const char *ftype = NULL;
714			static char cmd[128];
715
716			switch (ftp->f_type) {
717
718			case NORMAL:
719				ftype = "NORMAL";
720				break;
721
722			case PROFILING:
723				if (!profiling)
724					continue;
725				ftype = "PROFILE";
726				break;
727
728			default:
729				printf("config: don't know rules for %s\n", np);
730				break;
731			}
732			(void)snprintf(cmd, sizeof(cmd), "${%s_%c%s}", ftype, toupper(och),
733				      ftp->f_flags & CONFIGDEP? "_C" : "");
734			special = cmd;
735		}
736		*cp = och;
737		fprintf(f, "\t%s\n\n", special);
738	}
739}
740
741static void
742do_clean(FILE *fp)
743{
744	struct file_list *tp;
745	int lpos, len;
746
747	fputs("CLEAN=", fp);
748	lpos = 7;
749	for (tp = ftab; tp; tp = tp->f_next)
750		if (tp->f_clean) {
751			len = strlen(tp->f_clean);
752			if (len + lpos > 72) {
753				lpos = 8;
754				fputs("\\\n\t", fp);
755			}
756			fprintf(fp, "%s ", tp->f_clean);
757			lpos += len + 1;
758		}
759	if (lpos != 8)
760		putc('\n', fp);
761}
762
763char *
764raisestr(char *str)
765{
766	char *cp = str;
767
768	while (*str) {
769		if (islower(*str))
770			*str = toupper(*str);
771		str++;
772	}
773	return (cp);
774}
775