Magellan Linux

Diff of /trunk/mkinitrd-magellan/busybox/coreutils/ls.c

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 815 by niro, Sat Sep 1 22:45:15 2007 UTC revision 816 by niro, Fri Apr 24 18:33:46 2009 UTC
# Line 29  Line 29 
29   * 1. requires lstat (BSD) - how do you do it without?   * 1. requires lstat (BSD) - how do you do it without?
30   */   */
31    
32  #include "busybox.h"  #include "libbb.h"
33  #include <getopt.h>  
34    #if ENABLE_FEATURE_ASSUME_UNICODE
35    #include <wchar.h>
36    #endif
37    
38    /* This is a NOEXEC applet. Be very careful! */
39    
40    
41  enum {  enum {
42    
# Line 112  SPLIT_SUBDIR    = 2, Line 118  SPLIT_SUBDIR    = 2,
118  #define ATTR(mode) ("\00\00\01\00\01\00\01\00"\  #define ATTR(mode) ("\00\00\01\00\01\00\01\00"\
119   "\00\00\01\00\01\00\00\01" [TYPEINDEX(mode)])   "\00\00\01\00\01\00\00\01" [TYPEINDEX(mode)])
120    
 /* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */  
 #if ENABLE_FEATURE_LS_COLOR  
 static int show_color;  
 /* long option entry used only for --color, which has no short option  
  * equivalent */  
 static const struct option ls_color_opt[] = {  
  { "color", optional_argument, NULL, 1 },  
  { NULL, 0, NULL, 0 }  
 };  
 #else  
 enum { show_color = 0 };  
 #endif  
   
121  /*  /*
122   * a directory entry and its stat info are stored here   * a directory entry and its stat info are stored here
123   */   */
124  struct dnode {                  /* the basic node */  struct dnode {                  /* the basic node */
125   char *name;             /* the dir entry name */   const char *name;             /* the dir entry name */
126   char *fullname;         /* the dir entry name */   const char *fullname;         /* the dir entry name */
127   int   allocated;   int   allocated;
128   struct stat dstat;      /* the file stat info */   struct stat dstat;      /* the file stat info */
129   USE_SELINUX(security_context_t sid;)   USE_SELINUX(security_context_t sid;)
130   struct dnode *next;     /* point at the next node */   struct dnode *next;     /* point at the next node */
131  };  };
 typedef struct dnode dnode_t;  
132    
133  static struct dnode **list_dir(const char *);  static struct dnode **list_dir(const char *);
134  static struct dnode **dnalloc(int);  static struct dnode **dnalloc(int);
135  static int list_single(struct dnode *);  static int list_single(const struct dnode *);
136    
 static unsigned all_fmt;  
137    
138    struct globals {
139    #if ENABLE_FEATURE_LS_COLOR
140     smallint show_color;
141    #endif
142     smallint exit_code;
143     unsigned all_fmt;
144  #if ENABLE_FEATURE_AUTOWIDTH  #if ENABLE_FEATURE_AUTOWIDTH
145  static unsigned tabstops = COLUMN_GAP;   unsigned tabstops; // = COLUMN_GAP;
146  static unsigned terminal_width = TERMINAL_WIDTH;   unsigned terminal_width; // = TERMINAL_WIDTH;
147    #endif
148    #if ENABLE_FEATURE_LS_TIMESTAMPS
149     /* Do time() just once. Saves one syscall per file for "ls -l" */
150     time_t current_time_t;
151    #endif
152    };
153    #define G (*(struct globals*)&bb_common_bufsiz1)
154    #if ENABLE_FEATURE_LS_COLOR
155    #define show_color     (G.show_color    )
156    #else
157    enum { show_color = 0 };
158    #endif
159    #define exit_code      (G.exit_code     )
160    #define all_fmt        (G.all_fmt       )
161    #if ENABLE_FEATURE_AUTOWIDTH
162    #define tabstops       (G.tabstops      )
163    #define terminal_width (G.terminal_width)
164  #else  #else
165  enum {  enum {
166   tabstops = COLUMN_GAP,   tabstops = COLUMN_GAP,
167   terminal_width = TERMINAL_WIDTH,   terminal_width = TERMINAL_WIDTH,
168  };  };
169  #endif  #endif
170    #define current_time_t (G.current_time_t)
171    /* memset: we have to zero it out because of NOEXEC */
172    #define INIT_G() do { \
173     memset(&G, 0, sizeof(G)); \
174     USE_FEATURE_AUTOWIDTH(tabstops = COLUMN_GAP;) \
175     USE_FEATURE_AUTOWIDTH(terminal_width = TERMINAL_WIDTH;) \
176     USE_FEATURE_LS_TIMESTAMPS(time(&current_time_t);) \
177    } while (0)
178    
179    
180    #if ENABLE_FEATURE_ASSUME_UNICODE
181    /* libbb candidate */
182    static size_t mbstrlen(const char *string)
183    {
184     size_t width = mbsrtowcs(NULL /*dest*/, &string,
185     MAXINT(size_t) /*len*/, NULL /*state*/);
186     if (width == (size_t)-1)
187     return strlen(string);
188     return width;
189    }
190    #else
191    #define mbstrlen(string) strlen(string)
192    #endif
193    
 static int status = EXIT_SUCCESS;  
194    
195  static struct dnode *my_stat(char *fullname, char *name)  static struct dnode *my_stat(const char *fullname, const char *name, int force_follow)
196  {  {
197   struct stat dstat;   struct stat dstat;
198   struct dnode *cur;   struct dnode *cur;
199   USE_SELINUX(security_context_t sid = NULL;)   USE_SELINUX(security_context_t sid = NULL;)
200    
201   if (all_fmt & FOLLOW_LINKS) {   if ((all_fmt & FOLLOW_LINKS) || force_follow) {
202  #if ENABLE_SELINUX  #if ENABLE_SELINUX
203   if (is_selinux_enabled())  {   if (is_selinux_enabled())  {
204   getfilecon(fullname, &sid);   getfilecon(fullname, &sid);
205   }   }
206  #endif  #endif
207   if (stat(fullname, &dstat)) {   if (stat(fullname, &dstat)) {
208   bb_perror_msg("%s", fullname);   bb_simple_perror_msg(fullname);
209   status = EXIT_FAILURE;   exit_code = EXIT_FAILURE;
210   return 0;   return 0;
211   }   }
212   } else {   } else {
213  #if ENABLE_SELINUX  #if ENABLE_SELINUX
214   if (is_selinux_enabled()) {   if (is_selinux_enabled()) {
215   lgetfilecon(fullname,&sid);   lgetfilecon(fullname, &sid);
216   }   }
217  #endif  #endif
218   if (lstat(fullname, &dstat)) {   if (lstat(fullname, &dstat)) {
219   bb_perror_msg("%s", fullname);   bb_simple_perror_msg(fullname);
220   status = EXIT_FAILURE;   exit_code = EXIT_FAILURE;
221   return 0;   return 0;
222   }   }
223   }   }
# Line 238  static int count_dirs(struct dnode **dn, Line 274  static int count_dirs(struct dnode **dn,
274   return 0;   return 0;
275   dirs = 0;   dirs = 0;
276   for (i = 0; i < nfiles; i++) {   for (i = 0; i < nfiles; i++) {
277   char *name;   const char *name;
278   if (!S_ISDIR(dn[i]->dstat.st_mode))   if (!S_ISDIR(dn[i]->dstat.st_mode))
279   continue;   continue;
280   name = dn[i]->name;   name = dn[i]->name;
# Line 285  static void dfree(struct dnode **dnp, in Line 321  static void dfree(struct dnode **dnp, in
321   for (i = 0; i < nfiles; i++) {   for (i = 0; i < nfiles; i++) {
322   struct dnode *cur = dnp[i];   struct dnode *cur = dnp[i];
323   if (cur->allocated)   if (cur->allocated)
324   free(cur->fullname); /* free the filename */   free((char*)cur->fullname); /* free the filename */
325   free(cur); /* free the dnode */   free(cur); /* free the dnode */
326   }   }
327   free(dnp); /* free the array holding the dnode pointers */   free(dnp); /* free the array holding the dnode pointers */
# Line 317  static struct dnode **splitdnarray(struc Line 353  static struct dnode **splitdnarray(struc
353   /* copy the entrys into the file or dir array */   /* copy the entrys into the file or dir array */
354   for (d = i = 0; i < nfiles; i++) {   for (d = i = 0; i < nfiles; i++) {
355   if (S_ISDIR(dn[i]->dstat.st_mode)) {   if (S_ISDIR(dn[i]->dstat.st_mode)) {
356   char *name;   const char *name;
357   if (!(which & (SPLIT_DIR|SPLIT_SUBDIR)))   if (!(which & (SPLIT_DIR|SPLIT_SUBDIR)))
358   continue;   continue;
359   name = dn[i]->name;   name = dn[i]->name;
# Line 394  static void showfiles(struct dnode **dn, Line 430  static void showfiles(struct dnode **dn,
430   } else {   } else {
431   /* find the longest file name, use that as the column width */   /* find the longest file name, use that as the column width */
432   for (i = 0; i < nfiles; i++) {   for (i = 0; i < nfiles; i++) {
433   int len = strlen(dn[i]->name);   int len = mbstrlen(dn[i]->name);
434   if (column_width < len)   if (column_width < len)
435   column_width = len;   column_width = len;
436   }   }
# Line 449  static void showdirs(struct dnode **dn, Line 485  static void showdirs(struct dnode **dn,
485   for (i = 0; i < ndirs; i++) {   for (i = 0; i < ndirs; i++) {
486   if (all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) {   if (all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) {
487   if (!first)   if (!first)
488   puts("");   bb_putchar('\n');
489   first = 0;   first = 0;
490   printf("%s:\n", dn[i]->fullname);   printf("%s:\n", dn[i]->fullname);
491   }   }
# Line 493  static struct dnode **list_dir(const cha Line 529  static struct dnode **list_dir(const cha
529   nfiles = 0;   nfiles = 0;
530   dir = warn_opendir(path);   dir = warn_opendir(path);
531   if (dir == NULL) {   if (dir == NULL) {
532   status = EXIT_FAILURE;   exit_code = EXIT_FAILURE;
533   return NULL; /* could not open the dir */   return NULL; /* could not open the dir */
534   }   }
535   while ((entry = readdir(dir)) != NULL) {   while ((entry = readdir(dir)) != NULL) {
# Line 501  static struct dnode **list_dir(const cha Line 537  static struct dnode **list_dir(const cha
537    
538   /* are we going to list the file- it may be . or .. or a hidden file */   /* are we going to list the file- it may be . or .. or a hidden file */
539   if (entry->d_name[0] == '.') {   if (entry->d_name[0] == '.') {
540   if ((entry->d_name[1] == 0 || (   if ((!entry->d_name[1] || (entry->d_name[1] == '.' && !entry->d_name[2]))
541   entry->d_name[1] == '.'   && !(all_fmt & DISP_DOT)
542   && entry->d_name[2] == 0))   ) {
  && !(all_fmt & DISP_DOT))  
543   continue;   continue;
544     }
545   if (!(all_fmt & DISP_HIDDEN))   if (!(all_fmt & DISP_HIDDEN))
546   continue;   continue;
547   }   }
548   fullname = concat_path_file(path, entry->d_name);   fullname = concat_path_file(path, entry->d_name);
549   cur = my_stat(fullname, strrchr(fullname, '/') + 1);   cur = my_stat(fullname, bb_basename(fullname), 0);
550   if (!cur) {   if (!cur) {
551   free(fullname);   free(fullname);
552   continue;   continue;
# Line 537  static struct dnode **list_dir(const cha Line 573  static struct dnode **list_dir(const cha
573  }  }
574    
575    
576  #if ENABLE_FEATURE_LS_TIMESTAMPS  static int list_single(const struct dnode *dn)
 /* Do time() just once. Saves one syscall per file for "ls -l" */  
 /* Initialized in main() */  
 static time_t current_time_t;  
 #endif  
   
 static int list_single(struct dnode *dn)  
577  {  {
578   int i, column = 0;   int i, column = 0;
579    
# Line 657  static int list_single(struct dnode *dn) Line 687  static int list_single(struct dnode *dn)
687   fgcolor(info.st_mode));   fgcolor(info.st_mode));
688   }   }
689  #endif  #endif
690    #if ENABLE_FEATURE_ASSUME_UNICODE
691     printf("%s", dn->name);
692     column += mbstrlen(dn->name);
693    #else
694   column += printf("%s", dn->name);   column += printf("%s", dn->name);
695    #endif
696   if (show_color) {   if (show_color) {
697   printf("\033[0m");   printf("\033[0m");
698   }   }
699   break;   break;
700   case LIST_SYMLINK:   case LIST_SYMLINK:
701   if (S_ISLNK(dn->dstat.st_mode)) {   if (S_ISLNK(dn->dstat.st_mode)) {
702   char *lpath = xreadlink(dn->fullname);   char *lpath = xmalloc_readlink_or_warn(dn->fullname);
703   if (!lpath) break;   if (!lpath) break;
704   printf(" -> ");   printf(" -> ");
705  #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR  #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
# Line 700  static int list_single(struct dnode *dn) Line 735  static int list_single(struct dnode *dn)
735   return column;   return column;
736  }  }
737    
738    
739  /* "[-]Cadil1", POSIX mandated options, busybox always supports */  /* "[-]Cadil1", POSIX mandated options, busybox always supports */
740  /* "[-]gnsx", POSIX non-mandated options, busybox always supports */  /* "[-]gnsx", POSIX non-mandated options, busybox always supports */
741  /* "[-]Ak" GNU options, busybox always supports */  /* "[-]Ak" GNU options, busybox always supports */
# Line 708  static int list_single(struct dnode *dn) Line 744  static int list_single(struct dnode *dn)
744  /* "[-]SXvThw", GNU options, busybox optionally supports */  /* "[-]SXvThw", GNU options, busybox optionally supports */
745  /* "[-]K", SELinux mandated options, busybox optionally supports */  /* "[-]K", SELinux mandated options, busybox optionally supports */
746  /* "[-]e", I think we made this one up */  /* "[-]e", I think we made this one up */
747  static const char ls_options[] = "Cadil1gnsxAk"  static const char ls_options[] ALIGN1 =
748     "Cadil1gnsxAk"
749   USE_FEATURE_LS_TIMESTAMPS("cetu")   USE_FEATURE_LS_TIMESTAMPS("cetu")
750   USE_FEATURE_LS_SORTFILES("SXrv")   USE_FEATURE_LS_SORTFILES("SXrv")
751   USE_FEATURE_LS_FILETYPES("Fp")   USE_FEATURE_LS_FILETYPES("Fp")
# Line 716  static const char ls_options[] = "Cadil1 Line 753  static const char ls_options[] = "Cadil1
753   USE_FEATURE_LS_RECURSIVE("R")   USE_FEATURE_LS_RECURSIVE("R")
754   USE_FEATURE_HUMAN_READABLE("h")   USE_FEATURE_HUMAN_READABLE("h")
755   USE_SELINUX("K")   USE_SELINUX("K")
756   USE_FEATURE_AUTOWIDTH("T:w:");   USE_FEATURE_AUTOWIDTH("T:w:")
757     USE_SELINUX("Z");
758    
759  enum {  enum {
760   LIST_MASK_TRIGGER = 0,   LIST_MASK_TRIGGER = 0,
# Line 769  static const unsigned opt_flags[] = { Line 807  static const unsigned opt_flags[] = {
807  #if ENABLE_FEATURE_AUTOWIDTH  #if ENABLE_FEATURE_AUTOWIDTH
808   0, 0,                       /* T, w - ignored */   0, 0,                       /* T, w - ignored */
809  #endif  #endif
810    #if ENABLE_SELINUX
811     LIST_MODEBITS|LIST_ID_NAME|LIST_CONTEXT, /* Z */
812    #endif
813   (1U<<31)   (1U<<31)
814  };  };
815    
816    
817  int ls_main(int argc, char **argv)  /* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */
818    #if ENABLE_FEATURE_LS_COLOR
819    /* long option entry used only for --color, which has no short option
820     * equivalent */
821    static const char ls_color_opt[] ALIGN1 =
822     "color\0" Optional_argument "\xff" /* no short equivalent */
823     ;
824    #endif
825    
826    
827    int ls_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
828    int ls_main(int argc UNUSED_PARAM, char **argv)
829  {  {
830   struct dnode **dnd;   struct dnode **dnd;
831   struct dnode **dnf;   struct dnode **dnf;
# Line 781  int ls_main(int argc, char **argv) Line 833  int ls_main(int argc, char **argv)
833   struct dnode *dn;   struct dnode *dn;
834   struct dnode *cur;   struct dnode *cur;
835   unsigned opt;   unsigned opt;
836   int nfiles = 0;   int nfiles;
837   int dnfiles;   int dnfiles;
838   int dndirs;   int dndirs;
  int oi;  
  int ac;  
839   int i;   int i;
840   char **av;   /* need to initialize since --color has _an optional_ argument */
841   USE_FEATURE_AUTOWIDTH(char *tabstops_str = NULL;)   USE_FEATURE_LS_COLOR(const char *color_opt = "always";)
  USE_FEATURE_AUTOWIDTH(char *terminal_width_str = NULL;)  
  USE_FEATURE_LS_COLOR(char *color_opt;)  
842    
843   setvbuf(stdout, bb_common_bufsiz1, _IOFBF, BUFSIZ);   INIT_G();
   
 #if ENABLE_FEATURE_LS_TIMESTAMPS  
  time(&current_time_t);  
 #endif  
844    
845   all_fmt = LIST_SHORT |   all_fmt = LIST_SHORT |
846   (ENABLE_FEATURE_LS_SORTFILES * (SORT_NAME | SORT_FORWARD));   (ENABLE_FEATURE_LS_SORTFILES * (SORT_NAME | SORT_FORWARD));
847    
848  #if ENABLE_FEATURE_AUTOWIDTH  #if ENABLE_FEATURE_AUTOWIDTH
849   /* Obtain the terminal width */   /* Obtain the terminal width */
850   get_terminal_width_height(STDOUT_FILENO, &terminal_width, NULL);   get_terminal_width_height(STDIN_FILENO, &terminal_width, NULL);
851   /* Go one less... */   /* Go one less... */
852   terminal_width--;   terminal_width--;
853  #endif  #endif
# Line 811  int ls_main(int argc, char **argv) Line 855  int ls_main(int argc, char **argv)
855   /* process options */   /* process options */
856   USE_FEATURE_LS_COLOR(applet_long_options = ls_color_opt;)   USE_FEATURE_LS_COLOR(applet_long_options = ls_color_opt;)
857  #if ENABLE_FEATURE_AUTOWIDTH  #if ENABLE_FEATURE_AUTOWIDTH
858   opt = getopt32(argc, argv, ls_options, &tabstops_str, &terminal_width_str   opt_complementary = "T+:w+"; /* -T N, -w N */
859     opt = getopt32(argv, ls_options, &tabstops, &terminal_width
860   USE_FEATURE_LS_COLOR(, &color_opt));   USE_FEATURE_LS_COLOR(, &color_opt));
  if (tabstops_str)  
  tabstops = xatou(tabstops_str);  
  if (terminal_width_str)  
  terminal_width = xatou(terminal_width_str);  
861  #else  #else
862   opt = getopt32(argc, argv, ls_options  USE_FEATURE_LS_COLOR(, &color_opt));   opt = getopt32(argv, ls_options USE_FEATURE_LS_COLOR(, &color_opt));
863  #endif  #endif
864   for (i = 0; opt_flags[i] != (1U<<31); i++) {   for (i = 0; opt_flags[i] != (1U<<31); i++) {
865   if (opt & (1 << i)) {   if (opt & (1 << i)) {
# Line 836  int ls_main(int argc, char **argv) Line 877  int ls_main(int argc, char **argv)
877   all_fmt &= ~TIME_MASK;   all_fmt &= ~TIME_MASK;
878   if (flags & LIST_CONTEXT)   if (flags & LIST_CONTEXT)
879   all_fmt |= STYLE_SINGLE;   all_fmt |= STYLE_SINGLE;
880   if (LS_DISP_HR && opt == 'l')   /* huh?? opt cannot be 'l' */
881   all_fmt &= ~LS_DISP_HR;   //if (LS_DISP_HR && opt == 'l')
882     // all_fmt &= ~LS_DISP_HR;
883   all_fmt |= flags;   all_fmt |= flags;
884   }   }
885   }   }
# Line 847  int ls_main(int argc, char **argv) Line 889  int ls_main(int argc, char **argv)
889   if (ENABLE_FEATURE_LS_COLOR_IS_DEFAULT && isatty(STDOUT_FILENO)) {   if (ENABLE_FEATURE_LS_COLOR_IS_DEFAULT && isatty(STDOUT_FILENO)) {
890   char *p = getenv("LS_COLORS");   char *p = getenv("LS_COLORS");
891   /* LS_COLORS is unset, or (not empty && not "none") ? */   /* LS_COLORS is unset, or (not empty && not "none") ? */
892   if (!p || (p[0] && strcmp(p, "none")))   if (!p || (p[0] && strcmp(p, "none") != 0))
893   show_color = 1;   show_color = 1;
894   }   }
895   if (opt & (1 << i)) {  /* next flag after short options */   if (opt & (1 << i)) {  /* next flag after short options */
896   if (!color_opt || !strcmp("always", color_opt))   if (strcmp("always", color_opt) == 0)
897   show_color = 1;   show_color = 1;
898   else if (color_opt && !strcmp("never", color_opt))   else if (strcmp("never", color_opt) == 0)
899   show_color = 0;   show_color = 0;
900   else if (color_opt && !strcmp("auto", color_opt) && isatty(STDOUT_FILENO))   else if (strcmp("auto", color_opt) == 0 && isatty(STDOUT_FILENO))
901   show_color = 1;   show_color = 1;
902   }   }
903  #endif  #endif
# Line 879  int ls_main(int argc, char **argv) Line 921  int ls_main(int argc, char **argv)
921   if (!(all_fmt & STYLE_MASK))   if (!(all_fmt & STYLE_MASK))
922   all_fmt |= (isatty(STDOUT_FILENO) ? STYLE_COLUMNS : STYLE_SINGLE);   all_fmt |= (isatty(STDOUT_FILENO) ? STYLE_COLUMNS : STYLE_SINGLE);
923    
924   /*   argv += optind;
925   * when there are no cmd line args we have to supply a default "." arg.   if (!argv[0])
926   * we will create a second argv array, "av" that will hold either   *--argv = (char*)".";
  * our created "." arg, or the real cmd line args.  The av array  
  * just holds the pointers- we don't move the date the pointers  
  * point to.  
  */  
  ac = argc - optind; /* how many cmd line args are left */  
  if (ac < 1) {  
  static const char *const dotdir[] = { "." };  
   
  av = (char **) dotdir;  
  ac = 1;  
  } else {  
  av = argv + optind;  
  }  
927    
928   /* now, everything is in the av array */   if (argv[1])
929   if (ac > 1)   all_fmt |= DISP_DIRNAME; /* 2 or more items? label directories */
  all_fmt |= DISP_DIRNAME; /* 2 or more items? label directories */  
930    
931   /* stuff the command line file names into a dnode array */   /* stuff the command line file names into a dnode array */
932   dn = NULL;   dn = NULL;
933   for (oi = 0; oi < ac; oi++) {   nfiles = 0;
934   cur = my_stat(av[oi], av[oi]);   do {
935     /* ls w/o -l follows links on command line */
936     cur = my_stat(*argv, *argv, !(all_fmt & STYLE_LONG));
937     argv++;
938   if (!cur)   if (!cur)
939   continue;   continue;
940   cur->allocated = 0;   cur->allocated = 0;
941   cur->next = dn;   cur->next = dn;
942   dn = cur;   dn = cur;
943   nfiles++;   nfiles++;
944   }   } while (*argv);
945    
946   /* now that we know how many files there are   /* now that we know how many files there are
947   * allocate memory for an array to hold dnode pointers   * allocate memory for an array to hold dnode pointers
# Line 945  int ls_main(int argc, char **argv) Line 976  int ls_main(int argc, char **argv)
976   }   }
977   if (ENABLE_FEATURE_CLEAN_UP)   if (ENABLE_FEATURE_CLEAN_UP)
978   dfree(dnp, nfiles);   dfree(dnp, nfiles);
979   return status;   return exit_code;
980  }  }

Legend:
Removed from v.815  
changed lines
  Added in v.816