1 /*
2 File: getfacl.c
3 (Linux Access Control List Management)
4
5 Copyright (C) 1999-2002
6 Andreas Gruenbacher, <andreas.gruenbacher@gmail.com>
7
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or (at
11 your option) any later version.
12
13 This program is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this library; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
21 USA.
22 */
23
24 #include "config.h"
25 #include <stdio.h>
26 #include <errno.h>
27 #include <sys/acl.h>
28 #include <acl/libacl.h>
29
30 #include <limits.h>
31 #include <stdlib.h>
32 #include <unistd.h>
33 #include <string.h>
34 #include <sys/stat.h>
35 #include <dirent.h>
36 #include <libgen.h>
37 #include <getopt.h>
38 #include "misc.h"
39 #include "user_group.h"
40 #include "walk_tree.h"
41
42 #define POSIXLY_CORRECT_STR "POSIXLY_CORRECT"
43
44 #if !POSIXLY_CORRECT
45 # define CMD_LINE_OPTIONS "aceEsRLPtpndvh"
46 #endif
47 #define POSIXLY_CMD_LINE_OPTIONS "d"
48
49 struct option long_options[] = {
50 #if !POSIXLY_CORRECT
51 { "access", 0, 0, 'a' },
52 { "omit-header", 0, 0, 'c' },
53 { "all-effective", 0, 0, 'e' },
54 { "no-effective", 0, 0, 'E' },
55 { "skip-base", 0, 0, 's' },
56 { "recursive", 0, 0, 'R' },
57 { "logical", 0, 0, 'L' },
58 { "physical", 0, 0, 'P' },
59 { "tabular", 0, 0, 't' },
60 { "absolute-names", 0, 0, 'p' },
61 { "numeric", 0, 0, 'n' },
62 { "one-file-system", 0, 0, 1 },
63 #endif
64 { "default", 0, 0, 'd' },
65 { "version", 0, 0, 'v' },
66 { "help", 0, 0, 'h' },
67 { NULL, 0, 0, 0 }
68 };
69
70 const char *progname;
71 const char *cmd_line_options;
72
73 int walk_flags = WALK_TREE_DEREFERENCE_TOPLEVEL;
74 int opt_print_acl;
75 int opt_print_default_acl;
76 int opt_strip_leading_slash = 1;
77 int opt_comments = 1; /* include comments */
78 int opt_skip_base; /* skip files that only have the base entries */
79 int opt_tabular; /* tabular output format (alias `showacl') */
80 #if POSIXLY_CORRECT
81 const int posixly_correct = 1; /* Posix compatible behavior! */
82 #else
83 int posixly_correct; /* Posix compatible behavior? */
84 #endif
85 int had_errors;
86 int absolute_warning; /* Absolute path warning was issued */
87 int print_options = TEXT_SOME_EFFECTIVE;
88 int opt_numeric; /* don't convert id's to symbolic names */
89
90
91 static const char *xquote(const char *str, const char *quote_chars)
92 {
93 const char *q = __acl_quote(str, quote_chars);
94 if (q == NULL) {
95 fprintf(stderr, "%s: %s\n", progname, strerror(errno));
96 exit(1);
97 }
98 return q;
99 }
100
101 struct name_list {
102 struct name_list *next;
103 char name[0];
104 };
105
106 void free_list(struct name_list *names)
107 {
108 struct name_list *next;
109
110 while (names) {
111 next = names->next;
112 free(names);
113 names = next;
114 }
115 }
116
117 struct name_list *get_list(const struct stat *st, acl_t acl)
118 {
119 struct name_list *first = NULL, *last = NULL;
120 acl_entry_t ent;
121 int ret = 0;
122
123 if (acl != NULL)
124 ret = acl_get_entry(acl, ACL_FIRST_ENTRY, &ent);
125 if (ret != 1)
126 return NULL;
127 while (ret > 0) {
128 acl_tag_t e_type;
129 id_t *id_p;
130 const char *name = "";
131 int len;
132
133 acl_get_tag_type(ent, &e_type);
134 switch(e_type) {
135 case ACL_USER_OBJ:
136 name = user_name(st->st_uid, opt_numeric);
137 break;
138
139 case ACL_USER:
140 id_p = acl_get_qualifier(ent);
141 if (id_p != NULL) {
142 name = user_name(*id_p, opt_numeric);
143 acl_free(id_p);
144 }
145 break;
146
147 case ACL_GROUP_OBJ:
148 name = group_name(st->st_gid, opt_numeric);
149 break;
150
151 case ACL_GROUP:
152 id_p = acl_get_qualifier(ent);
153 if (id_p != NULL) {
154 name = group_name(*id_p, opt_numeric);
155 acl_free(id_p);
156 }
157 break;
158 }
159 name = xquote(name, "\t\n\r");
160 len = strlen(name);
161 if (last == NULL) {
162 first = last = (struct name_list *)
163 malloc(sizeof(struct name_list) + len + 1);
164 } else {
165 last->next = (struct name_list *)
166 malloc(sizeof(struct name_list) + len + 1);
167 last = last->next;
168 }
169 if (last == NULL) {
170 free_list(first);
171 return NULL;
172 }
173 last->next = NULL;
174 strcpy(last->name, name);
175
176 ret = acl_get_entry(acl, ACL_NEXT_ENTRY, &ent);
177 }
178 return first;
179 }
180
181 int max_name_length(struct name_list *names)
182 {
183 int max_len = 0;
184 while (names != NULL) {
185 struct name_list *next = names->next;
186 int len = strlen(names->name);
187
188 if (len > max_len)
189 max_len = len;
190 names = next;
191 }
192 return max_len;
193 }
194
195 int names_width;
196
197 struct acl_perm_def {
198 acl_tag_t tag;
199 char c;
200 };
201
202 struct acl_perm_def acl_perm_defs[] = {
203 { ACL_READ, 'r' },
204 { ACL_WRITE, 'w' },
205 { ACL_EXECUTE, 'x' },
206 { 0, 0 }
207 };
208
209 #define ACL_PERMS (sizeof(acl_perm_defs) / sizeof(struct acl_perm_def) - 1)
210
211 void acl_perm_str(acl_entry_t entry, char *str)
212 {
213 acl_permset_t permset;
214 int n;
215
216 acl_get_permset(entry, &permset);
217 for (n = 0; n < (int) ACL_PERMS; n++) {
218 str[n] = (acl_get_perm(permset, acl_perm_defs[n].tag) ?
219 acl_perm_defs[n].c : '-');
220 }
221 str[n] = '\0';
222 }
223
224 void acl_mask_perm_str(acl_t acl, char *str)
225 {
226 acl_entry_t entry;
227
228 str[0] = '\0';
229 if (acl_get_entry(acl, ACL_FIRST_ENTRY, &entry) != 1)
230 return;
231 for(;;) {
232 acl_tag_t tag;
233
234 acl_get_tag_type(entry, &tag);
235 if (tag == ACL_MASK) {
236 acl_perm_str(entry, str);
237 return;
238 }
239 if (acl_get_entry(acl, ACL_NEXT_ENTRY, &entry) != 1)
240 return;
241 }
242 }
243
244 void apply_mask(char *perm, const char *mask)
245 {
246 while (*perm) {
247 if (*mask == '-' && *perm >= 'a' && *perm <= 'z')
248 *perm = *perm - 'a' + 'A';
249 perm++;
250 if (*mask)
251 mask++;
252 }
253 }
254
255 int show_line(FILE *stream, struct name_list **acl_names, acl_t acl,
256 acl_entry_t *acl_ent, const char *acl_mask,
257 struct name_list **dacl_names, acl_t dacl,
258 acl_entry_t *dacl_ent, const char *dacl_mask)
259 {
260 acl_tag_t tag_type;
261 const char *tag, *name;
262 char acl_perm[ACL_PERMS+1], dacl_perm[ACL_PERMS+1];
263
264 if (acl) {
265 acl_get_tag_type(*acl_ent, &tag_type);
266 name = (*acl_names)->name;
267 } else {
268 acl_get_tag_type(*dacl_ent, &tag_type);
269 name = (*dacl_names)->name;
270 }
271
272 switch(tag_type) {
273 case ACL_USER_OBJ:
274 tag = "USER";
275 break;
276 case ACL_USER:
277 tag = "user";
278 break;
279 case ACL_GROUP_OBJ:
280 tag = "GROUP";
281 break;
282 case ACL_GROUP:
283 tag = "group";
284 break;
285 case ACL_MASK:
286 tag = "mask";
287 break;
288 case ACL_OTHER:
289 tag = "other";
290 break;
291 default:
292 return -1;
293 }
294
295 memset(acl_perm, ' ', ACL_PERMS);
296 acl_perm[ACL_PERMS] = '\0';
297 if (acl_ent) {
298 acl_perm_str(*acl_ent, acl_perm);
299 if (tag_type != ACL_USER_OBJ && tag_type != ACL_OTHER &&
300 tag_type != ACL_MASK)
301 apply_mask(acl_perm, acl_mask);
302 }
303 memset(dacl_perm, ' ', ACL_PERMS);
304 dacl_perm[ACL_PERMS] = '\0';
305 if (dacl_ent) {
306 acl_perm_str(*dacl_ent, dacl_perm);
307 if (tag_type != ACL_USER_OBJ && tag_type != ACL_OTHER &&
308 tag_type != ACL_MASK)
309 apply_mask(dacl_perm, dacl_mask);
310 }
311
312 fprintf(stream, "%-5s %*s %*s %*s\n",
313 tag, -names_width, name,
314 -(int)ACL_PERMS, acl_perm,
315 -(int)ACL_PERMS, dacl_perm);
316
317 if (acl_names) {
318 acl_get_entry(acl, ACL_NEXT_ENTRY, acl_ent);
319 (*acl_names) = (*acl_names)->next;
320 }
321 if (dacl_names) {
322 acl_get_entry(dacl, ACL_NEXT_ENTRY, dacl_ent);
323 (*dacl_names) = (*dacl_names)->next;
324 }
325 return 0;
326 }
327
328 int do_show(FILE *stream, const char *path_p, const struct stat *st,
329 acl_t acl, acl_t dacl)
330 {
331 struct name_list *acl_names = get_list(st, acl),
332 *first_acl_name = acl_names;
333 struct name_list *dacl_names = get_list(st, dacl),
334 *first_dacl_name = dacl_names;
335
336 int acl_names_width = max_name_length(acl_names);
337 int dacl_names_width = max_name_length(dacl_names);
338 acl_entry_t acl_ent;
339 acl_entry_t dacl_ent;
340 char acl_mask[ACL_PERMS+1], dacl_mask[ACL_PERMS+1];
341 int ret;
342
343 names_width = 8;
344 if (acl_names_width > names_width)
345 names_width = acl_names_width;
346 if (dacl_names_width > names_width)
347 names_width = dacl_names_width;
348
349 acl_mask[0] = '\0';
350 if (acl) {
351 acl_mask_perm_str(acl, acl_mask);
352 ret = acl_get_entry(acl, ACL_FIRST_ENTRY, &acl_ent);
353 if (ret == 0)
354 acl = NULL;
355 if (ret < 0)
356 return ret;
357 }
358 dacl_mask[0] = '\0';
359 if (dacl) {
360 acl_mask_perm_str(dacl, dacl_mask);
361 ret = acl_get_entry(dacl, ACL_FIRST_ENTRY, &dacl_ent);
362 if (ret == 0)
363 dacl = NULL;
364 if (ret < 0)
365 return ret;
366 }
367 fprintf(stream, "# file: %s\n", xquote(path_p, "\n\r"));
368 while (acl_names != NULL || dacl_names != NULL) {
369 acl_tag_t acl_tag, dacl_tag;
370
371 if (acl)
372 acl_get_tag_type(acl_ent, &acl_tag);
373 if (dacl)
374 acl_get_tag_type(dacl_ent, &dacl_tag);
375
376 if (acl && (!dacl || acl_tag < dacl_tag)) {
377 show_line(stream, &acl_names, acl, &acl_ent, acl_mask,
378 NULL, NULL, NULL, NULL);
379 continue;
380 } else if (dacl && (!acl || dacl_tag < acl_tag)) {
381 show_line(stream, NULL, NULL, NULL, NULL,
382 &dacl_names, dacl, &dacl_ent, dacl_mask);
383 continue;
384 } else {
385 if (acl_tag == ACL_USER || acl_tag == ACL_GROUP) {
386 int id_cmp = 0;
387
388 if (acl_ent && dacl_ent) {
389 id_t *acl_id_p = acl_get_qualifier(acl_ent);
390 id_t *dacl_id_p = acl_get_qualifier(dacl_ent);
391
392 id_cmp = (*acl_id_p > *dacl_id_p) -
393 (*acl_id_p < *dacl_id_p);
394 acl_free(acl_id_p);
395 acl_free(dacl_id_p);
396 }
397
398 if (acl && (!dacl || id_cmp < 0)) {
399 show_line(stream, &acl_names, acl,
400 &acl_ent, acl_mask,
401 NULL, NULL, NULL, NULL);
402 continue;
403 } else if (dacl && (!acl || id_cmp > 0)) {
404 show_line(stream, NULL, NULL, NULL,
405 NULL, &dacl_names, dacl,
406 &dacl_ent, dacl_mask);
407 continue;
408 }
409 }
410 show_line(stream, &acl_names, acl, &acl_ent, acl_mask,
411 &dacl_names, dacl, &dacl_ent, dacl_mask);
412 }
413 }
414
415 free_list(first_acl_name);
416 free_list(first_dacl_name);
417
418 return 0;
419 }
420
421 /*
422 * Create an ACL from the file permission bits
423 * of the file PATH_P.
424 */
425 static acl_t
426 acl_get_file_mode(const char *path_p)
427 {
428 struct stat st;
429
430 if (stat(path_p, &st) != 0)
431 return NULL;
432 return acl_from_mode(st.st_mode);
433 }
434
435 static const char *
436 flagstr(mode_t mode)
437 {
438 static char str[4];
439
440 str[0] = (mode & S_ISUID) ? 's' : '-';
441 str[1] = (mode & S_ISGID) ? 's' : '-';
442 str[2] = (mode & S_ISVTX) ? 't' : '-';
443 str[3] = '\0';
444 return str;
445 }
446
447 int do_print(const char *path_p, const struct stat *st, int walk_flags, void *unused)
448 {
449 const char *default_prefix = NULL;
450 acl_t acl = NULL, default_acl = NULL;
451 int error = 0;
452
453 if (walk_flags & WALK_TREE_FAILED) {
454 fprintf(stderr, "%s: %s: %s\n", progname, xquote(path_p, "\n\r"),
455 strerror(errno));
456 return 1;
457 }
458
459 /*
460 * Symlinks can never have ACLs, so when doing a physical walk, we
461 * skip symlinks altogether, and when doing a half-logical walk, we
462 * skip all non-toplevel symlinks.
463 */
464 if ((walk_flags & WALK_TREE_SYMLINK) &&
465 ((walk_flags & WALK_TREE_PHYSICAL) ||
466 !(walk_flags & (WALK_TREE_TOPLEVEL | WALK_TREE_LOGICAL))))
467 return 0;
468
469 if (opt_print_acl) {
470 acl = acl_get_file(path_p, ACL_TYPE_ACCESS);
471 if (acl == NULL && (errno == ENOSYS || errno == ENOTSUP))
472 acl = acl_get_file_mode(path_p);
473 if (acl == NULL)
474 goto fail;
475 }
476
477 if (opt_print_default_acl && S_ISDIR(st->st_mode)) {
478 default_acl = acl_get_file(path_p, ACL_TYPE_DEFAULT);
479 if (default_acl == NULL) {
480 if (errno != ENOSYS && errno != ENOTSUP)
481 goto fail;
482 } else if (acl_entries(default_acl) == 0) {
483 acl_free(default_acl);
484 default_acl = NULL;
485 }
486 }
487
488 if (opt_skip_base &&
489 (!acl || acl_equiv_mode(acl, NULL) == 0) && !default_acl)
490 goto cleanup;
491
492 if (opt_print_acl && opt_print_default_acl)
493 default_prefix = "default:";
494
495 if (opt_strip_leading_slash) {
496 if (*path_p == '/') {
497 if (!absolute_warning) {
498 fprintf(stderr, _("%s: Removing leading "
499 "'/' from absolute path names\n"),
500 progname);
501 absolute_warning = 1;
502 }
503 while (*path_p == '/')
504 path_p++;
505 } else if (*path_p == '.' && *(path_p+1) == '/')
506 while (*++path_p == '/')
507 /* nothing */ ;
508 if (*path_p == '\0')
509 path_p = ".";
510 }
511
512 if (opt_tabular) {
513 if (do_show(stdout, path_p, st, acl, default_acl) != 0)
514 goto fail;
515 } else {
516 if (opt_comments) {
517 printf("# file: %s\n", xquote(path_p, "\n\r"));
518 printf("# owner: %s\n",
519 xquote(user_name(st->st_uid, opt_numeric), " \t\n\r"));
520 printf("# group: %s\n",
521 xquote(group_name(st->st_gid, opt_numeric), " \t\n\r"));
522 if ((st->st_mode & (S_ISVTX | S_ISUID | S_ISGID)) && !posixly_correct)
523 printf("# flags: %s\n", flagstr(st->st_mode));
524 }
525 if (acl != NULL) {
526 char *acl_text = acl_to_any_text(acl, NULL, '\n',
527 print_options);
528 if (!acl_text)
529 goto fail;
530 if (puts(acl_text) < 0) {
531 acl_free(acl_text);
532 goto fail;
533 }
534 acl_free(acl_text);
535 }
536 if (default_acl != NULL) {
537 char *acl_text = acl_to_any_text(default_acl,
538 default_prefix, '\n',
539 print_options);
540 if (!acl_text)
541 goto fail;
542 if (puts(acl_text) < 0) {
543 acl_free(acl_text);
544 goto fail;
545 }
546 acl_free(acl_text);
547 }
548 }
549 if (acl || default_acl || opt_comments)
550 printf("\n");
551
552 cleanup:
553 if (acl)
554 acl_free(acl);
555 if (default_acl)
556 acl_free(default_acl);
557 return error;
558
559 fail:
560 fprintf(stderr, "%s: %s: %s\n", progname, xquote(path_p, "\n\r"),
561 strerror(errno));
562 error = -1;
563 goto cleanup;
564 }
565
566
567 void help(void)
568 {
569 printf(_("%s %s -- get file access control lists\n"),
570 progname, VERSION);
571 printf(_("Usage: %s [-%s] file ...\n"),
572 progname, cmd_line_options);
573 #if !POSIXLY_CORRECT
574 if (posixly_correct) {
575 #endif
576 printf(_(
577 " -d, --default display the default access control list\n"));
578 #if !POSIXLY_CORRECT
579 } else {
580 printf(_(
581 " -a, --access display the file access control list only\n"
582 " -d, --default display the default access control list only\n"
583 " -c, --omit-header do not display the comment header\n"
584 " -e, --all-effective print all effective rights\n"
585 " -E, --no-effective print no effective rights\n"
586 " -s, --skip-base skip files that only have the base entries\n"
587 " -R, --recursive recurse into subdirectories\n"
588 " -L, --logical logical walk, follow symbolic links\n"
589 " -P, --physical physical walk, do not follow symbolic links\n"
590 " -t, --tabular use tabular output format\n"
591 " -n, --numeric print numeric user/group identifiers\n"
592 " --one-file-system skip files on different filesystems\n"
593 " -p, --absolute-names don't strip leading '/' in pathnames\n"));
594 }
595 #endif
596 printf(_(
597 " -v, --version print version and exit\n"
598 " -h, --help this help text\n"));
599 }
600
601 int main(int argc, char *argv[])
602 {
603 int opt;
604 char *line;
605
606 progname = basename(argv[0]);
607
608 #if POSIXLY_CORRECT
609 cmd_line_options = POSIXLY_CMD_LINE_OPTIONS;
610 #else
611 if (getenv(POSIXLY_CORRECT_STR))
612 posixly_correct = 1;
613 if (!posixly_correct)
614 cmd_line_options = CMD_LINE_OPTIONS;
615 else
616 cmd_line_options = POSIXLY_CMD_LINE_OPTIONS;
617 #endif
618
619 setlocale(LC_CTYPE, "");
620 setlocale(LC_MESSAGES, "");
621 bindtextdomain(PACKAGE, LOCALEDIR);
622 textdomain(PACKAGE);
623
624 /* Align `#effective:' comments to column 40 for tty's */
625 if (!posixly_correct && isatty(fileno(stdout)))
626 print_options |= TEXT_SMART_INDENT;
627
628 while ((opt = getopt_long(argc, argv, cmd_line_options,
629 long_options, NULL)) != -1) {
630 switch (opt) {
631 case 'a': /* acl only */
632 if (posixly_correct)
633 goto synopsis;
634 opt_print_acl = 1;
635 break;
636
637 case 'd': /* default acl only */
638 opt_print_default_acl = 1;
639 break;
640
641 case 'c': /* no comments */
642 if (posixly_correct)
643 goto synopsis;
644 opt_comments = 0;
645 break;
646
647 case 'e': /* all #effective comments */
648 if (posixly_correct)
649 goto synopsis;
650 print_options |= TEXT_ALL_EFFECTIVE;
651 break;
652
653 case 'E': /* no #effective comments */
654 if (posixly_correct)
655 goto synopsis;
656 print_options &= ~(TEXT_SOME_EFFECTIVE |
657 TEXT_ALL_EFFECTIVE);
658 break;
659
660 case 'R': /* recursive */
661 if (posixly_correct)
662 goto synopsis;
663 walk_flags |= WALK_TREE_RECURSIVE;
664 break;
665
666 case 'L': /* follow all symlinks */
667 if (posixly_correct)
668 goto synopsis;
669 walk_flags |= WALK_TREE_LOGICAL | WALK_TREE_DEREFERENCE;
670 walk_flags &= ~WALK_TREE_PHYSICAL;
671 break;
672
673 case 'P': /* skip all symlinks */
674 if (posixly_correct)
675 goto synopsis;
676 walk_flags |= WALK_TREE_PHYSICAL;
677 walk_flags &= ~(WALK_TREE_LOGICAL | WALK_TREE_DEREFERENCE |
678 WALK_TREE_DEREFERENCE_TOPLEVEL);
679 break;
680
681 case 's': /* skip files with only base entries */
682 if (posixly_correct)
683 goto synopsis;
684 opt_skip_base = 1;
685 break;
686
687 case 'p':
688 if (posixly_correct)
689 goto synopsis;
690 opt_strip_leading_slash = 0;
691 break;
692
693 case 't':
694 if (posixly_correct)
695 goto synopsis;
696 opt_tabular = 1;
697 break;
698
699 case 'n': /* numeric */
700 opt_numeric = 1;
701 print_options |= TEXT_NUMERIC_IDS;
702 break;
703
704 case 1: /* one filesystem */
705 walk_flags |= WALK_TREE_ONE_FILESYSTEM;
706 break;
707
708 case 'v': /* print version */
709 printf("%s " VERSION "\n", progname);
710 return 0;
711
712 case 'h': /* help */
713 help();
714 return 0;
715
716 case ':': /* option missing */
717 case '?': /* unknown option */
718 default:
719 goto synopsis;
720 }
721 }
722
723 if (!(opt_print_acl || opt_print_default_acl)) {
724 opt_print_acl = 1;
725 if (!posixly_correct)
726 opt_print_default_acl = 1;
727 }
728
729 if ((optind == argc) && !posixly_correct)
730 goto synopsis;
731
732 do {
733 if (optind == argc ||
734 strcmp(argv[optind], "-") == 0) {
735 while ((line = __acl_next_line(stdin)) != NULL) {
736 if (*line == '\0')
737 continue;
738
739 had_errors += walk_tree(line, walk_flags, 0,
740 do_print, NULL);
741 }
742 if (!feof(stdin)) {
743 fprintf(stderr, _("%s: Standard input: %s\n"),
744 progname, strerror(errno));
745 had_errors++;
746 }
747 } else
748 had_errors += walk_tree(argv[optind], walk_flags, 0,
749 do_print, NULL);
750 optind++;
751 } while (optind < argc);
752
753 return had_errors ? 1 : 0;
754
755 synopsis:
756 fprintf(stderr, _("Usage: %s [-%s] file ...\n"),
757 progname, cmd_line_options);
758 fprintf(stderr, _("Try `%s --help' for more information.\n"),
759 progname);
760 return 2;
761 }
762