]> TLD Linux GIT Repositories - packages/coreutils.git/blob - coreutils-advcopy.patch
- merged 9.5 from PLD
[packages/coreutils.git] / coreutils-advcopy.patch
1 diff -crB coreutils-8.21/src/copy.c coreutils-8.21-patch0.5/src/copy.c
2 *** coreutils-8.21/src/copy.c   2013-02-07 10:37:05.000000000 +0100
3 --- coreutils-8.21-patch0.5/src/copy.c  2013-02-23 12:53:51.000000000 +0100
4 ***************
5 *** 135,140 ****
6 --- 135,190 ----
7     return err;
8   }
9   
10 + /* BEGIN progress mod */
11 + static void file_progress_bar ( char * _cDest, int _iBarLength, int _iProgress, int _iTotal )
12 + {
13 +   // write number to progress bar
14 +   float fPercent = ( float ) _iProgress / ( float ) _iTotal * 100.f;
15 +   sprintf ( _cDest + ( _iBarLength - 6 ), "%4.1f", fPercent );
16 +   // remove zero
17 +   _cDest[_iBarLength - 2] = ' ';
18
19 +   // fill rest with '-'
20 +   int i;
21 +   for ( i = 1; i <= _iBarLength - 9; i++ )
22 +   {
23 +     if ( fPercent > ( float ) ( i - 1 ) / ( _iBarLength - 10 ) * 100.f )
24 +       _cDest[i] = '|';
25 +     else
26 +       _cDest[i] = '-';
27 +   }
28 + }
29
30 + int file_size_format ( char * _cDst, int _iSize, int _iCounter )
31 + {
32 +   int iCounter = _iCounter;
33 +   double dSize = ( double ) _iSize;
34 +   while ( dSize >= 1000. )
35 +   {
36 +     dSize /= 1024.;
37 +     iCounter++;
38 +   }
39
40 +   /* get unit */
41 +   char * sUnit;
42 +   if ( iCounter == 0 )
43 +     sUnit = "B";
44 +   else if ( iCounter == 1 )
45 +     sUnit = "KiB";
46 +   else if ( iCounter == 2 )
47 +     sUnit = "MiB";
48 +   else if ( iCounter == 3 )
49 +     sUnit = "GiB";
50 +   else if ( iCounter == 4 )
51 +     sUnit = "TiB";
52 +   else
53 +     sUnit = "N/A";
54
55 +   /* write number */
56 +   return sprintf ( _cDst, "%5.1f %s", dSize, sUnit );
57 + }
58 + /* END progress mod */
59
60   /* Copy the regular file open on SRC_FD/SRC_NAME to DST_FD/DST_NAME,
61      honoring the MAKE_HOLES setting and using the BUF_SIZE-byte buffer
62      BUF for temporary storage.  Copy no more than MAX_N_READ bytes.
63 ***************
64 *** 151,163 ****
65                bool make_holes,
66                char const *src_name, char const *dst_name,
67                uintmax_t max_n_read, off_t *total_n_read,
68 !              bool *last_write_made_hole)
69   {
70     *last_write_made_hole = false;
71     *total_n_read = 0;
72   
73     while (max_n_read)
74       {
75         bool make_hole = false;
76   
77         ssize_t n_read = read (src_fd, buf, MIN (max_n_read, buf_size));
78 --- 201,369 ----
79                bool make_holes,
80                char const *src_name, char const *dst_name,
81                uintmax_t max_n_read, off_t *total_n_read,
82 !              bool *last_write_made_hole
83 !             )
84   {
85 +   /* BEGIN progress mod */
86 +   /* create a field of 6 lines */
87 +   char ** cProgressField = ( char ** ) calloc ( 6, sizeof ( char * ) );
88 +   /* get console width */
89 +   int iBarLength = 80;
90 +   struct winsize win;
91 +   if ( ioctl (STDOUT_FILENO, TIOCGWINSZ, (char *) &win) == 0 && win.ws_col > 0 )
92 +       iBarLength = win.ws_col;
93 +   /* create rows */
94 +   int it;
95 +   for ( it = 0; it < 6; it++ )
96 +   {
97 +     cProgressField[it] = ( char * ) malloc ( iBarLength + 1 );
98 +     /* init with spaces */
99 +     int j;
100 +     for ( j = 0; j < iBarLength; j++ )
101 +       cProgressField[it][j] = ' ';
102 +     cProgressField[it][iBarLength] = '\0';
103 +   }
104
105 +   /* global progress bar? */
106 +   if ( g_iTotalSize )
107 +   {
108 +     /* init global progress bar */
109 +     cProgressField[2][0] = '[';
110 +     cProgressField[2][iBarLength - 8] = ']';
111 +     cProgressField[2][iBarLength - 7] = ' ';
112 +     cProgressField[2][iBarLength - 1] = '%';
113
114 +     /* total size */
115 +     cProgressField[1][iBarLength - 11] = '/';
116 +     file_size_format ( cProgressField[1] + iBarLength - 9, g_iTotalSize, 1 );
117
118 +     /* show how many files were written */
119 +     int sum_length = sprintf ( cProgressField[1], "%d files copied so far...", g_iFilesCopied );
120 +     cProgressField[1][sum_length] = ' ';
121 +   }
122
123 +   /* truncate filename? */
124 +   int fn_length;
125 +   if ( strlen ( src_name ) > iBarLength - 22 )
126 +     fn_length =
127 +       sprintf ( cProgressField[4], "...%s", src_name + ( strlen ( src_name ) - iBarLength + 25 ) );
128 +   else
129 +     fn_length = sprintf ( cProgressField[4], "%s", src_name );
130 +   cProgressField[4][fn_length] = ' ';
131
132 +   /* filesize */
133 +   int file_size = max_n_read;
134 +   struct stat file_stat;
135 +   if (fstat(src_fd, & file_stat) == 0)
136 +     file_size = file_stat.st_size;
137 +   cProgressField[4][iBarLength - 11] = '/';
138 +   file_size_format ( cProgressField[4] + iBarLength - 9, file_size, 0 );
139
140 +   int iCountDown = 1;
141 +   char * sProgressBar = cProgressField[5];
142 +   sProgressBar[0] = '[';
143 +   sProgressBar[iBarLength - 8] = ']';
144 +   sProgressBar[iBarLength - 7] = ' ';
145 +   sProgressBar[iBarLength - 1] = '%';
146
147 +   /* this will always save the time in between */
148 +   struct timeval last_time;
149 +   gettimeofday ( & last_time, NULL );
150 +   int last_size = g_iTotalWritten;
151 +   /* END progress mod */
152 +   
153     *last_write_made_hole = false;
154     *total_n_read = 0;
155   
156     while (max_n_read)
157       {
158 +     /* BEGIN progress mod */
159 +     if (progress) {
160 +       /* update countdown */
161 +       iCountDown--;
162 +       if ( iCountDown < 0 )
163 +       {
164 +         /* average copy speed is assumed to be around 10 MiB/s, just to be safe.
165 +          * the status should be updated about 10 times per second, or approximately
166 +          * once per 1 MiB transferred. */
167 +         iCountDown = 1024 * 1024 / buf_size;
168 +         /* must be greater than 0 */
169 +         if (iCountDown < 1)
170 +           iCountDown = 1;
171 +         /* limit */
172 +         if (iCountDown > 100)
173 +           iCountDown = 100;
174 +       }
175
176 +       /* just print one line with the percentage, but not always */
177 +       if ( iCountDown == 0 )
178 +       {
179 +         /* calculate current speed */
180 +         struct timeval cur_time;
181 +         gettimeofday ( & cur_time, NULL );
182 +         int cur_size = g_iTotalWritten + *total_n_read / 1024;
183 +         int usec_elapsed = cur_time.tv_usec - last_time.tv_usec;
184 +         double sec_elapsed = ( double ) usec_elapsed / 1000000.f;
185 +         sec_elapsed += ( double ) ( cur_time.tv_sec - last_time.tv_sec );
186 +         int copy_speed = ( int ) ( ( double ) ( cur_size - last_size )
187 +           / sec_elapsed );
188 +         if (copy_speed < 0)
189 +           copy_speed = 0;
190 +         char s_copy_speed[20];
191 +         file_size_format ( s_copy_speed, copy_speed, 1 );
192 +         /* update vars */
193 +         last_time = cur_time;
194 +         last_size = cur_size;
195
196 +         /* how much time has passed since the start? */
197 +         int isec_elapsed = cur_time.tv_sec - g_oStartTime.tv_sec;
198 +         int sec_remaining = ( int ) ( ( double ) isec_elapsed / cur_size
199 +           * g_iTotalSize ) - isec_elapsed;
200 +         int min_remaining = sec_remaining / 60;
201 +         sec_remaining -= min_remaining * 60;
202 +         int hours_remaining = min_remaining / 60;
203 +         min_remaining -= hours_remaining * 60;
204 +         /* print out */
205 +         sprintf ( cProgressField[3],
206 +           "Copying at %s/s (about %dh %dm %ds remaining)", s_copy_speed,
207 +           hours_remaining, min_remaining, sec_remaining );
208
209 +         int fs_len;
210 +         if ( g_iTotalSize )
211 +         {
212 +           /* global progress bar */
213 +           file_progress_bar ( cProgressField[2], iBarLength,
214 +                               g_iTotalWritten + *total_n_read / 1024, g_iTotalSize );
215
216 +           /* print the global status */
217 +           fs_len = file_size_format ( cProgressField[1] + iBarLength - 21,
218 +                                           g_iTotalWritten + *total_n_read / 1024, 1 );
219 +           cProgressField[1][iBarLength - 21 + fs_len] = ' ';
220 +         }
221
222 +         /* current progress bar */
223 +         file_progress_bar ( sProgressBar, iBarLength, *total_n_read, file_size );
224
225 +         /* print the status */
226 +         fs_len = file_size_format ( cProgressField[4] + iBarLength - 21, *total_n_read, 0 );
227 +         cProgressField[4][iBarLength - 21 + fs_len] = ' ';
228
229 +         /* print the field */
230 +         for ( it = g_iTotalSize ? 0 : 3; it < 6; it++ )
231 +         {
232 +           printf ( "\033[K%s\n", cProgressField[it] );
233 +           if ( strlen ( cProgressField[it] ) < iBarLength )
234 +             printf ( "" );
235 +         }
236 +         if ( g_iTotalSize )
237 +           printf ( "\r\033[6A" );
238 +         else
239 +           printf ( "\r\033[3A" );
240 +         fflush ( stdout );
241 +       }
242 +     }
243 +     /* END progress mod */
244 +       
245         bool make_hole = false;
246   
247         ssize_t n_read = read (src_fd, buf, MIN (max_n_read, buf_size));
248 ***************
249 *** 215,220 ****
250 --- 421,439 ----
251   
252         *last_write_made_hole = make_hole;
253       }
254 +     
255 +   if (progress) {
256 +     /* BEGIN progress mod */
257 +     /* update total size */
258 +     g_iTotalWritten += *total_n_read / 1024;
259 +     g_iFilesCopied++;
260
261 +     int i;
262 +     for ( i = 0; i < 6; i++ )
263 +       free ( cProgressField[i] );
264 +     free ( cProgressField );
265 +     /* END progress mod */
266 +   }
267   
268     return true;
269   }
270 diff -crB coreutils-8.21/src/copy.h coreutils-8.21-patch0.5/src/copy.h
271 *** coreutils-8.21/src/copy.h   2013-01-31 01:46:24.000000000 +0100
272 --- coreutils-8.21-patch0.5/src/copy.h  2013-02-23 12:53:51.000000000 +0100
273 ***************
274 *** 227,232 ****
275 --- 227,235 ----
276     /* If true, create symbolic links instead of copying files.
277        Create destination directories as usual. */
278     bool symbolic_link;
279 +   
280 +   /* If true, draw a nice progress bar on screen */
281 +   bool progress_bar;
282   
283     /* If true, do not copy a nondirectory that has an existing destination
284        with the same or newer modification time. */
285 ***************
286 *** 286,289 ****
287 --- 289,303 ----
288   bool chown_failure_ok (struct cp_options const *) _GL_ATTRIBUTE_PURE;
289   mode_t cached_umask (void);
290   
291 + /* BEGIN progress mod */
292 + int file_size_format ( char * _cDst, int _iSize, int _iCounter );
293
294 + long g_iTotalSize;
295 + long g_iTotalWritten;
296 + int g_iFilesCopied;
297 + struct timeval g_oStartTime;
298 + int g_iTotalFiles;
299 + bool progress;
300 + /* END progress mod */
301
302   #endif
303 diff -crB coreutils-8.21/src/cp.c coreutils-8.21-patch0.5/src/cp.c
304 *** coreutils-8.21/src/cp.c     2013-02-07 10:37:05.000000000 +0100
305 --- coreutils-8.21-patch0.5/src/cp.c    2013-02-23 12:53:51.000000000 +0100
306 ***************
307 *** 141,146 ****
308 --- 141,147 ----
309     {"target-directory", required_argument, NULL, 't'},
310     {"update", no_argument, NULL, 'u'},
311     {"verbose", no_argument, NULL, 'v'},
312 +   {"progress-bar", no_argument, NULL, 'g'},
313     {GETOPT_HELP_OPTION_DECL},
314     {GETOPT_VERSION_OPTION_DECL},
315     {NULL, 0, NULL, 0}
316 ***************
317 *** 178,183 ****
318 --- 179,185 ----
319     -f, --force                  if an existing destination file cannot be\n\
320                                    opened, remove it and try again (this option\n\
321                                    is ignored when the -n option is also used)\n\
322 +   -g, --progress-bar           add progress-bar\n\
323     -i, --interactive            prompt before overwrite (overrides a previous -n\
324   \n\
325                                     option)\n\
326 ***************
327 *** 617,622 ****
328 --- 619,675 ----
329           error (EXIT_FAILURE, 0, _("target %s is not a directory"),
330                  quote (file[n_files - 1]));
331       }
332 +     
333 +   /* BEGIN progress mod */
334 +   struct timeval start_time;
335 +   if (progress) {
336 +     g_iTotalSize = 0;
337 +     g_iFilesCopied = 0;
338 +     g_iTotalWritten = 0;
339
340 +     /* save time */
341 +     gettimeofday ( & start_time, NULL );
342 +     g_oStartTime = start_time;
343
344 +     printf ( "Calculating total size... \r" );
345 +     fflush ( stdout );
346 +     long iTotalSize = 0;
347 +     int iFiles = n_files;
348 +     if ( ! target_directory )
349 +       iFiles = n_files - 1;
350 +     int j;
351 +     for (j = 0; j < iFiles; j++)
352 +     {
353 +       /* call du -s for each file */
354 +       /* create command */
355 +       char command[1024];
356 +       sprintf ( command, "du -s \"%s\"", file[j] );
357 +       /* TODO: replace all quote signs in file[i] */
358
359 +       FILE *fp;
360 +       char output[1024];
361
362 +       /* run command */
363 +       fp = popen(command, "r");
364 +       if (fp == NULL || fgets(output, sizeof(output)-1, fp) == NULL) {
365 +         printf("failed to run du.\n" );
366 +       }
367 +       else
368 +       {
369 +         /* isolate size */
370 +         strchr ( output, '\t' )[0] = '\0';
371 +         iTotalSize += atol ( output );
372
373 +         printf ( "Calculating total size... %ld\r", iTotalSize );
374 +         fflush ( stdout );
375 +       }
376
377 +       /* close */
378 +       pclose(fp);
379 +     }
380 +     g_iTotalSize = iTotalSize;
381 +   }
382 +   /* END progress mod */
383   
384     if (target_directory)
385       {
386 ***************
387 *** 759,764 ****
388 --- 812,857 ----
389   
390         ok = copy (source, new_dest, 0, x, &unused, NULL);
391       }
392 +     
393 +   /* BEGIN progress mod */
394 +   if (progress) {
395 +     /* remove everything */
396 +     int i;
397 +     if ( g_iTotalSize )
398 +     {
399 +       for ( i = 0; i < 6; i++ )
400 +         printf ( "\033[K\n" );
401 +       printf ( "\r\033[6A" );
402 +     }
403 +     else
404 +     {
405 +       for ( i = 0; i < 3; i++ )
406 +         printf ( "\033[K\n" );
407 +       printf ( "\r\033[3A" );
408 +     }
409
410 +     /* save time */
411 +     struct timeval end_time;
412 +     gettimeofday ( & end_time, NULL );
413 +     int usec_elapsed = end_time.tv_usec - start_time.tv_usec;
414 +     double sec_elapsed = ( double ) usec_elapsed / 1000000.f;
415 +     sec_elapsed += ( double ) ( end_time.tv_sec - start_time.tv_sec );
416
417 +     /* get total size */
418 +     char sTotalWritten[20];
419 +     file_size_format ( sTotalWritten, g_iTotalSize, 1 );
420 +     /* TODO: using g_iTotalWritten would be more correct, but is less accurate */
421
422 +     /* calculate speed */
423 +     int copy_speed = ( int ) ( ( double ) g_iTotalWritten / sec_elapsed );
424 +     char s_copy_speed[20];
425 +     file_size_format ( s_copy_speed, copy_speed, 1 );
426
427 +     /* good-bye message */
428 +     printf ( "%d files (%s) copied in %.1f seconds (%s/s).\n", g_iFilesCopied, sTotalWritten,
429 +              sec_elapsed, s_copy_speed );
430 +   }
431 +   /* END progress mod */
432   
433     return ok;
434   }
435 ***************
436 *** 793,798 ****
437 --- 886,892 ----
438     x->recursive = false;
439     x->sparse_mode = SPARSE_AUTO;
440     x->symbolic_link = false;
441 +   x->progress_bar = false;
442     x->set_mode = false;
443     x->mode = 0;
444   
445 ***************
446 *** 933,939 ****
447        we'll actually use backup_suffix_string.  */
448     backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX");
449   
450 !   while ((c = getopt_long (argc, argv, "abdfHilLnprst:uvxPRS:T",
451                              long_opts, NULL))
452            != -1)
453       {
454 --- 1027,1033 ----
455        we'll actually use backup_suffix_string.  */
456     backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX");
457   
458 !   while ((c = getopt_long (argc, argv, "abdfgHilLnprst:uvxPRS:T",
459                              long_opts, NULL))
460            != -1)
461       {
462 ***************
463 *** 990,995 ****
464 --- 1084,1093 ----
465             x.unlink_dest_after_failed_open = true;
466             break;
467   
468 +         case 'g':
469 +           progress = true;
470 +           break;
471
472           case 'H':
473             x.dereference = DEREF_COMMAND_LINE_ARGUMENTS;
474             break;
475 diff -crB coreutils-8.21/src/mv.c coreutils-8.21-patch0.5/src/mv.c
476 *** coreutils-8.21/src/mv.c     2013-02-07 10:37:05.000000000 +0100
477 --- coreutils-8.21-patch0.5/src/mv.c    2013-03-12 21:23:58.000000000 +0100
478 ***************
479 *** 64,69 ****
480 --- 64,70 ----
481     {"target-directory", required_argument, NULL, 't'},
482     {"update", no_argument, NULL, 'u'},
483     {"verbose", no_argument, NULL, 'v'},
484 +   {"progress-bar", no_argument, NULL, 'g'},
485     {GETOPT_HELP_OPTION_DECL},
486     {GETOPT_VERSION_OPTION_DECL},
487     {NULL, 0, NULL, 0}
488 ***************
489 *** 165,171 ****
490     bool copy_into_self;
491     bool rename_succeeded;
492     bool ok = copy (source, dest, false, x, &copy_into_self, &rename_succeeded);
493
494     if (ok)
495       {
496         char const *dir_to_remove;
497 --- 166,172 ----
498     bool copy_into_self;
499     bool rename_succeeded;
500     bool ok = copy (source, dest, false, x, &copy_into_self, &rename_succeeded);
501 !   
502     if (ok)
503       {
504         char const *dir_to_remove;
505 ***************
506 *** 300,305 ****
507 --- 301,307 ----
508   \n\
509     -b                           like --backup but does not accept an argument\n\
510     -f, --force                  do not prompt before overwriting\n\
511 +   -g, --progress-bar           add progress-bar\n\
512     -i, --interactive            prompt before overwrite\n\
513     -n, --no-clobber             do not overwrite an existing file\n\
514   If you specify more than one of -i, -f, -n, only the final one takes effect.\n\
515 ***************
516 *** 368,374 ****
517        we'll actually use backup_suffix_string.  */
518     backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX");
519   
520 !   while ((c = getopt_long (argc, argv, "bfint:uvS:T", long_options, NULL))
521            != -1)
522       {
523         switch (c)
524 --- 370,376 ----
525        we'll actually use backup_suffix_string.  */
526     backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX");
527   
528 !   while ((c = getopt_long (argc, argv, "bfint:uvgS:T", long_options, NULL))
529            != -1)
530       {
531         switch (c)
532 ***************
533 *** 414,419 ****
534 --- 416,424 ----
535           case 'v':
536             x.verbose = true;
537             break;
538 +         case 'g':
539 +           progress = true;
540 +           break;
541           case 'S':
542             make_backups = true;
543             backup_suffix_string = optarg;
544 ***************
545 *** 476,481 ****
546 --- 481,537 ----
547                      : no_backups);
548   
549     hash_init ();
550 +   
551 +   /* BEGIN progress mod */
552 +   struct timeval start_time;
553
554 +   if(progress) {
555 +     g_iTotalSize = 0;
556 +     g_iFilesCopied = 0;
557 +     g_iTotalWritten = 0;
558
559 +     gettimeofday (& start_time, NULL);
560 +     g_oStartTime = start_time;
561
562 +     printf ("Calculating total size... \r");
563 +     fflush (stdout);
564 +     long iTotalSize = 0;
565 +     int iFiles = n_files;
566 +     if ( !target_directory )
567 +       iFiles = 1;
568 +     int j;
569 +     for (j = 0; j < iFiles; j++)
570 +     {
571 +       /* call du -s for each file */
572 +       /* create command */
573 +       char command[1024];
574 +       sprintf ( command, "du -s \"%s\"", file[j] );
575 +       /* TODO: replace all quote signs in file[i] */
576
577 +       FILE *fp;
578 +       char output[1024];
579
580 +       /* run command */
581 +       fp = popen(command, "r");
582 +       if (fp == NULL || fgets(output, sizeof(output)-1, fp) == NULL) {
583 +         printf("failed to run du.\n" );
584 +       }
585 +       else
586 +       {
587 +         /* isolate size */
588 +         strchr ( output, '\t' )[0] = '\0';
589 +         iTotalSize += atol ( output );
590
591 +         printf ( "Calculating total size... %ld\r", iTotalSize );
592 +         fflush ( stdout );
593 +       }
594
595 +       /* close */
596 +       pclose(fp);
597 +     }
598 +     g_iTotalSize = iTotalSize;
599 +   }
600 +   /* END progress mod */
601   
602     if (target_directory)
603       {
604 ***************
605 *** 493,498 ****
606 --- 549,594 ----
607       }
608     else
609       ok = movefile (file[0], file[1], false, &x);
610 +   
611 +   /* BEGIN progress mod */
612 +   if (progress) {
613 +     /* remove everything */
614 +     int i;
615 +     if ( g_iTotalSize )
616 +     {
617 +       for ( i = 0; i < 6; i++ )
618 +         printf ( "\033[K\n" );
619 +       printf ( "\r\033[6A" );
620 +     }
621 +     else
622 +     {
623 +       for ( i = 0; i < 3; i++ )
624 +         printf ( "\033[K\n" );
625 +       printf ( "\r\033[3A" );
626 +     }
627
628 +     /* save time */
629 +     struct timeval end_time;
630 +     gettimeofday ( & end_time, NULL );
631 +     int usec_elapsed = end_time.tv_usec - start_time.tv_usec;
632 +     double sec_elapsed = ( double ) usec_elapsed / 1000000.f;
633 +     sec_elapsed += ( double ) ( end_time.tv_sec - start_time.tv_sec );
634
635 +     /* get total size */
636 +     char sTotalWritten[20];
637 +     file_size_format ( sTotalWritten, g_iTotalSize, 1 );
638 +     /* TODO: using g_iTotalWritten would be more correct, but is less accurate */
639
640 +     /* calculate speed */
641 +     int copy_speed = ( int ) ( ( double ) g_iTotalWritten / sec_elapsed );
642 +     char s_copy_speed[20];
643 +     file_size_format ( s_copy_speed, copy_speed, 1 );
644
645 +     /* good-bye message */
646 +     printf ( "%d files (%s) moved in %.1f seconds (%s/s).\n", g_iFilesCopied, sTotalWritten,
647 +              sec_elapsed, s_copy_speed );
648 +   }
649 +   /* END progress mod */
650   
651     exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
652   }