1 /*
2 * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
3 *
4 * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
5 * and others.
6 *
7 * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
8 * Portions Copyright (C) 1989-1992, Brian Berliner
9 *
10 * You may distribute under the terms of the GNU General Public License as
11 * specified in the README file that comes with the CVS source distribution.
12 */
13
14 #include "cvs.h"
15 #include "getline.h"
16 #include "history.h"
17
18 /*
19 * Parse the INFOFILE file for the specified REPOSITORY. Invoke CALLPROC for
20 * the first line in the file that matches the REPOSITORY, or if ALL != 0, any
21 * lines matching "ALL", or if no lines match, the last line matching
22 * "DEFAULT".
23 *
24 * Return 0 for success, -1 if there was not an INFOFILE, and >0 for failure.
25 */
26 int
Parse_Info(const char * infofile,const char * repository,CALLPROC callproc,int opt,void * closure)27 Parse_Info (const char *infofile, const char *repository, CALLPROC callproc,
28 int opt, void *closure)
29 {
30 int err = 0;
31 FILE *fp_info;
32 char *infopath;
33 char *line = NULL;
34 size_t line_allocated = 0;
35 char *default_value = NULL;
36 int default_line = 0;
37 char *expanded_value;
38 bool callback_done;
39 int line_number;
40 char *cp, *exp, *value;
41 const char *srepos;
42 const char *regex_err;
43
44 assert (repository);
45
46 if (!current_parsed_root)
47 {
48 /* XXX - should be error maybe? */
49 error (0, 0, "CVSROOT variable not set");
50 return 1;
51 }
52
53 /* find the info file and open it */
54 infopath = Xasprintf ("%s/%s/%s", current_parsed_root->directory,
55 CVSROOTADM, infofile);
56 fp_info = CVS_FOPEN (infopath, "r");
57 if (!fp_info)
58 {
59 /* If no file, don't do anything special. */
60 if (!existence_error (errno))
61 error (0, errno, "cannot open %s", infopath);
62 free (infopath);
63 return 0;
64 }
65
66 /* strip off the CVSROOT if repository was absolute */
67 srepos = Short_Repository (repository);
68
69 TRACE (TRACE_FUNCTION, "Parse_Info (%s, %s, %s)",
70 infopath, srepos, (opt & PIOPT_ALL) ? "ALL" : "not ALL");
71
72 /* search the info file for lines that match */
73 callback_done = false;
74 line_number = 0;
75 while (getline (&line, &line_allocated, fp_info) >= 0)
76 {
77 line_number++;
78
79 /* skip lines starting with # */
80 if (line[0] == '#')
81 continue;
82
83 /* skip whitespace at beginning of line */
84 for (cp = line; *cp && isspace ((unsigned char) *cp); cp++)
85 ;
86
87 /* if *cp is null, the whole line was blank */
88 if (*cp == '\0')
89 continue;
90
91 /* the regular expression is everything up to the first space */
92 for (exp = cp; *cp && !isspace ((unsigned char) *cp); cp++)
93 ;
94 if (*cp != '\0')
95 *cp++ = '\0';
96
97 /* skip whitespace up to the start of the matching value */
98 while (*cp && isspace ((unsigned char) *cp))
99 cp++;
100
101 /* no value to match with the regular expression is an error */
102 if (*cp == '\0')
103 {
104 error (0, 0, "syntax error at line %d file %s; ignored",
105 line_number, infopath);
106 continue;
107 }
108 value = cp;
109
110 /* strip the newline off the end of the value */
111 cp = strrchr (value, '\n');
112 if (cp) *cp = '\0';
113
114 /*
115 * At this point, exp points to the regular expression, and value
116 * points to the value to call the callback routine with. Evaluate
117 * the regular expression against srepos and callback with the value
118 * if it matches.
119 */
120
121 /* save the default value so we have it later if we need it */
122 if (strcmp (exp, "DEFAULT") == 0)
123 {
124 if (default_value)
125 {
126 error (0, 0, "Multiple `DEFAULT' lines (%d and %d) in %s file",
127 default_line, line_number, infofile);
128 free (default_value);
129 }
130 default_value = xstrdup (value);
131 default_line = line_number;
132 continue;
133 }
134
135 /*
136 * For a regular expression of "ALL", do the callback always We may
137 * execute lots of ALL callbacks in addition to *one* regular matching
138 * callback or default
139 */
140 if (strcmp (exp, "ALL") == 0)
141 {
142 if (!(opt & PIOPT_ALL))
143 error (0, 0, "Keyword `ALL' is ignored at line %d in %s file",
144 line_number, infofile);
145 else if ((expanded_value =
146 expand_path (value, current_parsed_root->directory,
147 true, infofile, line_number)))
148 {
149 err += callproc (repository, expanded_value, closure);
150 free (expanded_value);
151 }
152 else
153 err++;
154 continue;
155 }
156
157 if (callback_done)
158 /* only first matching, plus "ALL"'s */
159 continue;
160
161 /* see if the repository matched this regular expression */
162 regex_err = re_comp (exp);
163 if (regex_err)
164 {
165 error (0, 0, "bad regular expression at line %d file %s: %s",
166 line_number, infofile, regex_err);
167 continue;
168 }
169 if (re_exec (srepos) == 0)
170 continue; /* no match */
171
172 /* it did, so do the callback and note that we did one */
173 expanded_value = expand_path (value, current_parsed_root->directory,
174 true, infofile, line_number);
175 if (expanded_value)
176 {
177 err += callproc (repository, expanded_value, closure);
178 free (expanded_value);
179 }
180 else
181 err++;
182 callback_done = true;
183 }
184 if (ferror (fp_info))
185 error (0, errno, "cannot read %s", infopath);
186 if (fclose (fp_info) < 0)
187 error (0, errno, "cannot close %s", infopath);
188
189 /* if we fell through and didn't callback at all, do the default */
190 if (!callback_done && default_value)
191 {
192 expanded_value = expand_path (default_value,
193 current_parsed_root->directory,
194 true, infofile, line_number);
195 if (expanded_value)
196 {
197 err += callproc (repository, expanded_value, closure);
198 free (expanded_value);
199 }
200 else
201 err++;
202 }
203
204 /* free up space if necessary */
205 if (default_value) free (default_value);
206 free (infopath);
207 if (line) free (line);
208
209 return err;
210 }
211
212
213
214 /* Print a warning and return false if P doesn't look like a string specifying
215 * something that can be converted into a size_t.
216 *
217 * Sets *VAL to the parsed value when it is found to be valid. *VAL will not
218 * be altered when false is returned.
219 */
220 static bool
readSizeT(const char * infopath,const char * option,const char * p,size_t * val)221 readSizeT (const char *infopath, const char *option, const char *p,
222 size_t *val)
223 {
224 const char *q;
225 size_t num, factor = 1;
226
227 if (!strcasecmp ("unlimited", p))
228 {
229 *val = SIZE_MAX;
230 return true;
231 }
232
233 /* Record the factor character (kilo, mega, giga, tera). */
234 if (!isdigit (p[strlen(p) - 1]))
235 {
236 switch (p[strlen(p) - 1])
237 {
238 case 'T':
239 factor = xtimes (factor, 1024);
240 case 'G':
241 factor = xtimes (factor, 1024);
242 case 'M':
243 factor = xtimes (factor, 1024);
244 case 'k':
245 factor = xtimes (factor, 1024);
246 break;
247 default:
248 error (0, 0,
249 "%s: Unknown %s factor: `%c'",
250 infopath, option, p[strlen(p)]);
251 return false;
252 }
253 TRACE (TRACE_DATA, "readSizeT(): Found factor %u for %s",
254 factor, option);
255 }
256
257 /* Verify that *q is a number. */
258 q = p;
259 while (q < p + strlen(p) - 1 /* Checked last character above. */)
260 {
261 if (!isdigit(*q))
262 {
263 error (0, 0,
264 "%s: %s must be a postitive integer, not '%s'",
265 infopath, option, p);
266 return false;
267 }
268 q++;
269 }
270
271 /* Compute final value. */
272 num = strtoul (p, NULL, 10);
273 if (num == ULONG_MAX || num > SIZE_MAX)
274 /* Don't return an error, just max out. */
275 num = SIZE_MAX;
276
277 TRACE (TRACE_DATA, "readSizeT(): read number %u for %s", num, option);
278 *val = xtimes (strtoul (p, NULL, 10), factor);
279 TRACE (TRACE_DATA, "readSizeT(): returnning %u for %s", *val, option);
280 return true;
281 }
282
283
284
285 /* Allocate and initialize a new config struct. */
286 static inline struct config *
new_config(void)287 new_config (void)
288 {
289 struct config *new = xcalloc (1, sizeof (struct config));
290
291 TRACE (TRACE_FLOW, "new_config ()");
292
293 new->logHistory = xstrdup (ALL_HISTORY_REC_TYPES);
294 new->RereadLogAfterVerify = LOGMSG_REREAD_ALWAYS;
295 new->UserAdminOptions = xstrdup ("k");
296 new->MaxCommentLeaderLength = 20;
297 #ifdef SERVER_SUPPORT
298 new->MaxCompressionLevel = 9;
299 #endif /* SERVER_SUPPORT */
300 #ifdef PROXY_SUPPORT
301 new->MaxProxyBufferSize = (size_t)(8 * 1024 * 1024); /* 8 megabytes,
302 * by default.
303 */
304 #endif /* PROXY_SUPPORT */
305 #ifdef AUTH_SERVER_SUPPORT
306 new->system_auth = true;
307 #endif /* AUTH_SERVER_SUPPORT */
308
309 return new;
310 }
311
312
313
314 void
free_config(struct config * data)315 free_config (struct config *data)
316 {
317 if (data->keywords) free_keywords (data->keywords);
318 free (data);
319 }
320
321
322
323 /* Return true if this function has already been called for line LN of file
324 * INFOPATH.
325 */
326 bool
parse_error(const char * infopath,unsigned int ln)327 parse_error (const char *infopath, unsigned int ln)
328 {
329 static List *errors = NULL;
330 char *nodename = NULL;
331
332 if (!errors)
333 errors = getlist();
334
335 nodename = Xasprintf ("%s/%u", infopath, ln);
336 if (findnode (errors, nodename))
337 {
338 free (nodename);
339 return true;
340 }
341
342 push_string (errors, nodename);
343 return false;
344 }
345
346
347
348 #ifdef ALLOW_CONFIG_OVERRIDE
349 const char * const allowed_config_prefixes[] = { ALLOW_CONFIG_OVERRIDE };
350 #endif /* ALLOW_CONFIG_OVERRIDE */
351
352
353
354 /* Parse the CVS config file. The syntax right now is a bit ad hoc
355 * but tries to draw on the best or more common features of the other
356 * *info files and various unix (or non-unix) config file syntaxes.
357 * Lines starting with # are comments. Settings are lines of the form
358 * KEYWORD=VALUE. There is currently no way to have a multi-line
359 * VALUE (would be nice if there was, probably).
360 *
361 * CVSROOT is the $CVSROOT directory
362 * (current_parsed_root->directory might not be set yet, so this
363 * function takes the cvsroot as a function argument).
364 *
365 * RETURNS
366 * Always returns a fully initialized config struct, which on error may
367 * contain only the defaults.
368 *
369 * ERRORS
370 * Calls error(0, ...) on errors in addition to the return value.
371 *
372 * xmalloc() failures are fatal, per usual.
373 */
374 struct config *
parse_config(const char * cvsroot,const char * path)375 parse_config (const char *cvsroot, const char *path)
376 {
377 const char *infopath;
378 char *freeinfopath = NULL;
379 FILE *fp_info;
380 char *line = NULL;
381 unsigned int ln; /* Input file line counter. */
382 char *buf = NULL;
383 size_t buf_allocated = 0;
384 size_t len;
385 char *p;
386 struct config *retval;
387 int rescan = 0;
388 /* PROCESSING Whether config keys are currently being processed for
389 * this root.
390 * PROCESSED Whether any keys have been processed for this root.
391 * This is initialized to true so that any initial keys
392 * may be processed as global defaults.
393 */
394 bool processing = true;
395 bool processed = true;
396
397 TRACE (TRACE_FUNCTION, "parse_config (%s)", cvsroot);
398
399 #ifdef ALLOW_CONFIG_OVERRIDE
400 if (path)
401 {
402 const char * const *prefix;
403 char *npath = xcanonicalize_file_name (path);
404 bool approved = false;
405 for (prefix = allowed_config_prefixes; *prefix != NULL; prefix++)
406 {
407 char *nprefix;
408
409 if (!isreadable (*prefix)) continue;
410 nprefix = xcanonicalize_file_name (*prefix);
411 if (!strncmp (nprefix, npath, strlen (nprefix))
412 && (((*prefix)[strlen (*prefix)] != '/'
413 && strlen (npath) == strlen (nprefix))
414 || ((*prefix)[strlen (*prefix)] == '/'
415 && npath[strlen (nprefix)] == '/')))
416 approved = true;
417 free (nprefix);
418 if (approved) break;
419 }
420 if (!approved)
421 error (1, 0, "Invalid path to config file specified: `%s'",
422 path);
423 infopath = path;
424 free (npath);
425 }
426 else
427 #endif
428 infopath = freeinfopath =
429 Xasprintf ("%s/%s/%s", cvsroot, CVSROOTADM, CVSROOTADM_CONFIG);
430
431 retval = new_config ();
432
433 again:
434 fp_info = CVS_FOPEN (infopath, "r");
435 if (!fp_info)
436 {
437 /* If no file, don't do anything special. */
438 if (!existence_error (errno))
439 {
440 /* Just a warning message; doesn't affect return
441 value, currently at least. */
442 error (0, errno, "cannot open %s", infopath);
443 }
444 if (freeinfopath) free (freeinfopath);
445 return retval;
446 }
447
448 ln = 0; /* Have not read any lines yet. */
449 while (getline (&buf, &buf_allocated, fp_info) >= 0)
450 {
451 ln++; /* Keep track of input file line number for error messages. */
452
453 line = buf;
454
455 /* Skip leading white space. */
456 while (isspace (*line)) line++;
457
458 /* Skip comments. */
459 if (line[0] == '#')
460 continue;
461
462 /* Is there any kind of written standard for the syntax of this
463 sort of config file? Anywhere in POSIX for example (I guess
464 makefiles are sort of close)? Red Hat Linux has a bunch of
465 these too (with some GUI tools which edit them)...
466
467 Along the same lines, we might want a table of keywords,
468 with various types (boolean, string, &c), as a mechanism
469 for making sure the syntax is consistent. Any good examples
470 to follow there (Apache?)? */
471
472 /* Strip the trailing newline. There will be one unless we
473 read a partial line without a newline, and then got end of
474 file (or error?). */
475
476 len = strlen (line) - 1;
477 if (line[len] == '\n')
478 line[len--] = '\0';
479
480 /* Skip blank lines. */
481 if (line[0] == '\0')
482 continue;
483
484 TRACE (TRACE_DATA, "parse_info() examining line: `%s'", line);
485
486 /* Check for a root specification. */
487 if (line[0] == '[' && line[len] == ']')
488 {
489 cvsroot_t *tmproot;
490
491 line++[len] = '\0';
492 tmproot = parse_cvsroot (line);
493
494 /* Ignoring method. */
495 if (!tmproot
496 #if defined CLIENT_SUPPORT || defined SERVER_SUPPORT
497 || (tmproot->method != local_method
498 && (!tmproot->hostname || !isThisHost (tmproot->hostname)))
499 #endif /* CLIENT_SUPPORT || SERVER_SUPPORT */
500 || !isSamePath (tmproot->directory, cvsroot))
501 {
502 if (processed) processing = false;
503 }
504 else
505 {
506 TRACE (TRACE_FLOW, "Matched root section`%s'", line);
507 processing = true;
508 processed = false;
509 }
510
511 continue;
512 }
513
514 /* There is data on this line. */
515
516 /* Even if the data is bad or ignored, consider data processed for
517 * this root.
518 */
519 processed = true;
520
521 if (!processing)
522 /* ...but it is for a different root. */
523 continue;
524
525 /* The first '=' separates keyword from value. */
526 p = strchr (line, '=');
527 if (!p)
528 {
529 if (!parse_error (infopath, ln))
530 error (0, 0,
531 "%s [%d]: syntax error: missing `=' between keyword and value",
532 infopath, ln);
533 continue;
534 }
535
536 *p++ = '\0';
537
538 if (strcmp (line, "RCSBIN") == 0)
539 {
540 /* This option used to specify the directory for RCS
541 executables. But since we don't run them any more,
542 this is a noop. Silently ignore it so that a
543 repository can work with either new or old CVS. */
544 ;
545 }
546 else if (strcmp (line, "SystemAuth") == 0)
547 #ifdef AUTH_SERVER_SUPPORT
548 readBool (infopath, "SystemAuth", p, &retval->system_auth);
549 #else
550 {
551 /* Still parse the syntax but ignore the option. That way the same
552 * config file can be used for local and server.
553 */
554 bool dummy;
555 readBool (infopath, "SystemAuth", p, &dummy);
556 }
557 #endif
558 else if (strcmp (line, "LocalKeyword") == 0 ||
559 strcmp (line, "tag") == 0)
560 RCS_setlocalid (infopath, ln, &retval->keywords, p);
561 else if (strcmp (line, "KeywordExpand") == 0 ||
562 strcmp (line, "tagexpand") == 0)
563 RCS_setincexc (&retval->keywords, p);
564 else if (strcmp (line, "PreservePermissions") == 0)
565 {
566 #ifdef PRESERVE_PERMISSIONS_SUPPORT
567 readBool (infopath, "PreservePermissions", p,
568 &retval->preserve_perms);
569 #else
570 if (!parse_error (infopath, ln))
571 error (0, 0, "\
572 %s [%u]: warning: this CVS does not support PreservePermissions",
573 infopath, ln);
574 #endif
575 }
576 else if (strcmp (line, "TopLevelAdmin") == 0)
577 readBool (infopath, "TopLevelAdmin", p, &retval->top_level_admin);
578 else if (strcmp (line, "LockDir") == 0)
579 {
580 if (retval->lock_dir)
581 free (retval->lock_dir);
582 retval->lock_dir = expand_path (p, cvsroot, false, infopath, ln);
583 /* Could try some validity checking, like whether we can
584 opendir it or something, but I don't see any particular
585 reason to do that now rather than waiting until lock.c. */
586 }
587 else if (strcmp (line, "HistoryLogPath") == 0)
588 {
589 if (retval->HistoryLogPath) free (retval->HistoryLogPath);
590
591 /* Expand ~ & $VARs. */
592 retval->HistoryLogPath = expand_path (p, cvsroot, false,
593 infopath, ln);
594
595 if (retval->HistoryLogPath && !ISABSOLUTE (retval->HistoryLogPath))
596 {
597 error (0, 0, "%s [%u]: HistoryLogPath must be absolute.",
598 infopath, ln);
599 free (retval->HistoryLogPath);
600 retval->HistoryLogPath = NULL;
601 }
602 }
603 else if (strcmp (line, "HistorySearchPath") == 0)
604 {
605 if (retval->HistorySearchPath) free (retval->HistorySearchPath);
606 retval->HistorySearchPath = expand_path (p, cvsroot, false,
607 infopath, ln);
608
609 if (retval->HistorySearchPath
610 && !ISABSOLUTE (retval->HistorySearchPath))
611 {
612 error (0, 0, "%s [%u]: HistorySearchPath must be absolute.",
613 infopath, ln);
614 free (retval->HistorySearchPath);
615 retval->HistorySearchPath = NULL;
616 }
617 }
618 else if (strcmp (line, "LogHistory") == 0)
619 {
620 if (strcmp (p, "all") != 0)
621 {
622 static bool gotone = false;
623 if (gotone)
624 error (0, 0, "\
625 %s [%u]: warning: duplicate LogHistory entry found.",
626 infopath, ln);
627 else
628 gotone = true;
629 free (retval->logHistory);
630 retval->logHistory = xstrdup (p);
631 }
632 }
633 else if (strcmp (line, "RereadLogAfterVerify") == 0)
634 {
635 if (!strcasecmp (p, "never"))
636 retval->RereadLogAfterVerify = LOGMSG_REREAD_NEVER;
637 else if (!strcasecmp (p, "always"))
638 retval->RereadLogAfterVerify = LOGMSG_REREAD_ALWAYS;
639 else if (!strcasecmp (p, "stat"))
640 retval->RereadLogAfterVerify = LOGMSG_REREAD_STAT;
641 else
642 {
643 bool tmp;
644 if (readBool (infopath, "RereadLogAfterVerify", p, &tmp))
645 {
646 if (tmp)
647 retval->RereadLogAfterVerify = LOGMSG_REREAD_ALWAYS;
648 else
649 retval->RereadLogAfterVerify = LOGMSG_REREAD_NEVER;
650 }
651 }
652 }
653 else if (strcmp (line, "TmpDir") == 0)
654 {
655 if (retval->TmpDir) free (retval->TmpDir);
656 retval->TmpDir = expand_path (p, cvsroot, false, infopath, ln);
657 /* Could try some validity checking, like whether we can
658 * opendir it or something, but I don't see any particular
659 * reason to do that now rather than when the first function
660 * tries to create a temp file.
661 */
662 }
663 else if (strcmp (line, "UserAdminOptions") == 0)
664 retval->UserAdminOptions = xstrdup (p);
665 else if (strcmp (line, "UseNewInfoFmtStrings") == 0)
666 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
667 readBool (infopath, "UseNewInfoFmtStrings", p,
668 &retval->UseNewInfoFmtStrings);
669 #else /* !SUPPORT_OLD_INFO_FMT_STRINGS */
670 {
671 bool dummy;
672 if (readBool (infopath, "UseNewInfoFmtStrings", p, &dummy)
673 && !dummy)
674 error (1, 0,
675 "%s [%u]: Old style info format strings not supported by this executable.",
676 infopath, ln);
677 }
678 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
679 else if (strcmp (line, "ImportNewFilesToVendorBranchOnly") == 0)
680 readBool (infopath, "ImportNewFilesToVendorBranchOnly", p,
681 &retval->ImportNewFilesToVendorBranchOnly);
682 else if (strcmp (line, "PrimaryServer") == 0)
683 retval->PrimaryServer = parse_cvsroot (p);
684 #ifdef PROXY_SUPPORT
685 else if (!strcmp (line, "MaxProxyBufferSize"))
686 readSizeT (infopath, "MaxProxyBufferSize", p,
687 &retval->MaxProxyBufferSize);
688 #endif /* PROXY_SUPPORT */
689 else if (!strcmp (line, "MaxCommentLeaderLength"))
690 readSizeT (infopath, "MaxCommentLeaderLength", p,
691 &retval->MaxCommentLeaderLength);
692 else if (!strcmp (line, "UseArchiveCommentLeader"))
693 readBool (infopath, "UseArchiveCommentLeader", p,
694 &retval->UseArchiveCommentLeader);
695 #ifdef SERVER_SUPPORT
696 else if (!strcmp (line, "MinCompressionLevel"))
697 readSizeT (infopath, "MinCompressionLevel", p,
698 &retval->MinCompressionLevel);
699 else if (!strcmp (line, "MaxCompressionLevel"))
700 readSizeT (infopath, "MaxCompressionLevel", p,
701 &retval->MaxCompressionLevel);
702 #endif /* SERVER_SUPPORT */
703 else
704 /* We may be dealing with a keyword which was added in a
705 subsequent version of CVS. In that case it is a good idea
706 to complain, as (1) the keyword might enable a behavior like
707 alternate locking behavior, in which it is dangerous and hard
708 to detect if some CVS's have it one way and others have it
709 the other way, (2) in general, having us not do what the user
710 had in mind when they put in the keyword violates the
711 principle of least surprise. Note that one corollary is
712 adding new keywords to your CVSROOT/config file is not
713 particularly recommended unless you are planning on using
714 the new features. */
715 if (!parse_error (infopath, ln))
716 error (0, 0, "%s [%u]: unrecognized keyword `%s'",
717 infopath, ln, line);
718 }
719 if (ferror (fp_info))
720 error (0, errno, "cannot read %s", infopath);
721 if (fclose (fp_info) < 0)
722 error (0, errno, "cannot close %s", infopath);
723 if (freeinfopath) free (freeinfopath);
724 if (buf) free (buf);
725
726 if (path == NULL && !rescan++) {
727 infopath = freeinfopath =
728 Xasprintf ("%s/%s/%s", cvsroot, CVSROOTADM, CVSROOTADM_OPTIONS);
729 buf = NULL;
730 goto again;
731 }
732
733 return retval;
734 }
735