1 /* gifdiff.c - Gifdiff compares GIF images for identical appearance.
2    Copyright (C) 1998-2021 Eddie Kohler, ekohler@gmail.com
3    This file is part of gifdiff, in the gifsicle package.
4 
5    Gifdiff is free software. It is distributed under the GNU Public License,
6    version 2; you can copy, distribute, or alter it at will, as long
7    as this notice is kept intact and this source code is made available. There
8    is no warranty, express or implied. */
9 
10 #include <config.h>
11 #include <lcdfgif/gif.h>
12 #include <lcdf/clp.h>
13 #include <stdarg.h>
14 #include <stdio.h>
15 #include <string.h>
16 #include <errno.h>
17 #if HAVE_UNISTD_H
18 # include <unistd.h>
19 #endif
20 
21 #define QUIET_OPT               300
22 #define HELP_OPT                301
23 #define VERSION_OPT             302
24 #define IGNORE_REDUNDANCY_OPT   303
25 #define REDUNDANCY_OPT          304
26 #define IGNORE_BACKGROUND_OPT   305
27 #define BACKGROUND_OPT          306
28 
29 const Clp_Option options[] = {
30   { "help", 'h', HELP_OPT, 0, 0 },
31   { "brief", 'q', QUIET_OPT, 0, Clp_Negate },
32   { "redudancy", 0, REDUNDANCY_OPT, 0, Clp_Negate },
33   { "ignore-redundancy", 'w', IGNORE_REDUNDANCY_OPT, 0, Clp_Negate },
34   { "bg", 0, BACKGROUND_OPT, 0, Clp_Negate },
35   { "ignore-bg", 0, IGNORE_BACKGROUND_OPT, 0, Clp_Negate },
36   { "background", 0, BACKGROUND_OPT, 0, Clp_Negate },
37   { "ignore-background", 'B', IGNORE_BACKGROUND_OPT, 0, Clp_Negate },
38   { "version", 'v', VERSION_OPT, 0, 0 }
39 };
40 
41 const char *program_name;
42 
43 static const char *filename1;
44 static const char *filename2;
45 
46 static unsigned screen_width, screen_height;
47 #define TRANSP (0)
48 
49 static uint16_t *gdata[2];
50 static uint16_t *glast[2];
51 static uint16_t *scratch;
52 static uint16_t *line;
53 
54 static int brief;
55 static int ignore_redundancy;
56 static int ignore_background;
57 
58 static Clp_Parser* clp;
59 
60 
61 static void
combine_colormaps(Gif_Colormap * gfcm,Gif_Colormap * newcm)62 combine_colormaps(Gif_Colormap *gfcm, Gif_Colormap *newcm)
63 {
64   int i, gfcm_ncol = gfcm ? gfcm->ncol : 0;
65   for (i = 0; i < gfcm_ncol; i++) {
66     Gif_Color *c = &gfcm->col[i];
67     c->pixel = Gif_AddColor(newcm, c, 1);
68   }
69 }
70 
71 static void
fill_area(uint16_t * data,int l,int t,int w,int h,uint16_t val)72 fill_area(uint16_t *data, int l, int t, int w, int h, uint16_t val)
73 {
74   int x;
75   data += screen_width * t + l;
76   for (; h > 0; --h) {
77     for (x = w; x > 0; --x)
78       *data++ = val;
79     data += screen_width - w;
80   }
81 }
82 
83 static void
copy_area(uint16_t * dst,const uint16_t * src,int l,int t,int w,int h)84 copy_area(uint16_t *dst, const uint16_t *src, int l, int t, int w, int h)
85 {
86   dst += screen_width * t + l;
87   src += screen_width * t + l;
88   for (; h > 0; --h, dst += screen_width, src += screen_width)
89     memcpy(dst, src, sizeof(uint16_t) * w);
90 }
91 
92 static void
expand_bounds(int * lf,int * tp,int * rt,int * bt,const Gif_Image * gfi)93 expand_bounds(int *lf, int *tp, int *rt, int *bt, const Gif_Image *gfi)
94 {
95   int empty = (*lf >= *rt || *tp >= *bt);
96   if (empty || gfi->left < *lf)
97     *lf = gfi->left;
98   if (empty || gfi->top < *tp)
99     *tp = gfi->top;
100   if (empty || gfi->left + gfi->width > *rt)
101     *rt = gfi->left + gfi->width;
102   if (empty || gfi->top + gfi->height > *bt)
103     *bt = gfi->top + gfi->height;
104 }
105 
106 
107 static int
apply_image(int is_second,Gif_Stream * gfs,int imageno,uint16_t background)108 apply_image(int is_second, Gif_Stream *gfs, int imageno, uint16_t background)
109 {
110   int i, x, y, any_change;
111   Gif_Image *gfi = gfs->images[imageno];
112   Gif_Image *pgfi = imageno ? gfs->images[imageno - 1] : 0;
113   int width = gfi->width;
114   uint16_t map[256];
115   uint16_t *data = gdata[is_second];
116   uint16_t *last = glast[is_second];
117   Gif_Colormap *gfcm = gfi->local ? gfi->local : gfs->global;
118   int gfcm_ncol = gfcm ? gfcm->ncol : 0;
119 
120   /* set up colormap */
121   for (i = 0; i < gfcm_ncol; ++i)
122     map[i] = gfcm->col[i].pixel;
123   for (i = gfcm_ncol; i < 256; ++i)
124     map[i] = 1;
125   if (gfi->transparent >= 0 && gfi->transparent < 256)
126     map[gfi->transparent] = TRANSP;
127 
128   /* if this image's disposal is 'previous', save the post-disposal version in
129      'scratch' */
130   if (gfi->disposal == GIF_DISPOSAL_PREVIOUS) {
131     copy_area(scratch, data, gfi->left, gfi->top, gfi->width, gfi->height);
132     if (pgfi && pgfi->disposal == GIF_DISPOSAL_PREVIOUS)
133       copy_area(scratch, last, pgfi->left, pgfi->top, pgfi->width, pgfi->height);
134     else if (pgfi && pgfi->disposal == GIF_DISPOSAL_BACKGROUND)
135       fill_area(scratch, pgfi->left, pgfi->top, pgfi->width, pgfi->height, background);
136   }
137 
138   /* uncompress and clip */
139   Gif_UncompressImage(gfs, gfi);
140   Gif_ClipImage(gfi, 0, 0, screen_width, screen_height);
141 
142   any_change = imageno == 0;
143   {
144     int lf = 0, tp = 0, rt = 0, bt = 0;
145     expand_bounds(&lf, &tp, &rt, &bt, gfi);
146     if (pgfi && pgfi->disposal == GIF_DISPOSAL_PREVIOUS)
147       expand_bounds(&lf, &tp, &rt, &bt, pgfi);
148     else if (pgfi && pgfi->disposal == GIF_DISPOSAL_BACKGROUND) {
149       expand_bounds(&lf, &tp, &rt, &bt, pgfi);
150       fill_area(last, pgfi->left, pgfi->top, pgfi->width, pgfi->height, background);
151     } else
152       pgfi = 0;
153     for (y = tp; y < bt; ++y) {
154       uint16_t *outd = data + screen_width * y + lf;
155       if (!any_change)
156         memcpy(line, outd, (rt - lf) * sizeof(uint16_t));
157       if (pgfi && y >= pgfi->top && y < pgfi->top + pgfi->height)
158         memcpy(outd + pgfi->left - lf,
159                last + screen_width * y + pgfi->left,
160                pgfi->width * sizeof(uint16_t));
161       if (y >= gfi->top && y < gfi->top + gfi->height) {
162         uint16_t *xoutd = outd + gfi->left - lf;
163         const uint8_t *ind = gfi->img[y - gfi->top];
164         for (x = 0; x < width; ++x, ++ind, ++xoutd)
165           if (map[*ind] != TRANSP)
166             *xoutd = map[*ind];
167       }
168       if (!any_change && memcmp(line, outd, (rt - lf) * sizeof(uint16_t)) != 0)
169         any_change = 1;
170     }
171   }
172 
173   Gif_ReleaseUncompressedImage(gfi);
174   Gif_ReleaseCompressedImage(gfi);
175 
176   /* switch 'glast' with 'scratch' if necessary */
177   if (gfi->disposal == GIF_DISPOSAL_PREVIOUS) {
178     uint16_t *x = scratch;
179     scratch = glast[is_second];
180     glast[is_second] = x;
181   }
182 
183   return any_change;
184 }
185 
186 
187 #define SAME 0
188 #define DIFFERENT 1
189 static int was_different;
190 
191 static void
different(const char * format,...)192 different(const char *format, ...)
193 {
194   va_list val;
195   va_start(val, format);
196   if (!brief) {
197     vfprintf(stdout, format, val);
198     fputc('\n', stdout);
199   }
200   va_end(val);
201   was_different = 1;
202 }
203 
204 
205 static void
name_loopcount(int loopcount,char * buf)206 name_loopcount(int loopcount, char *buf)
207 {
208   if (loopcount < 0)
209     strcpy(buf, "none");
210   else if (loopcount == 0)
211     strcpy(buf, "forever");
212   else
213     sprintf(buf, "%d", loopcount);
214 }
215 
216 static void
name_delay(int delay,char * buf)217 name_delay(int delay, char *buf)
218 {
219   if (delay == 0)
220     strcpy(buf, "none");
221   else
222     sprintf(buf, "%d.%02ds", delay / 100, delay % 100);
223 }
224 
225 static void
name_color(int color,Gif_Colormap * gfcm,char * buf)226 name_color(int color, Gif_Colormap *gfcm, char *buf)
227 {
228   if (color == TRANSP)
229     strcpy(buf, "transparent");
230   else {
231     Gif_Color *c = &gfcm->col[color];
232     sprintf(buf, "#%02X%02X%02X", c->gfc_red, c->gfc_green, c->gfc_blue);
233   }
234 }
235 
236 
237 int
compare(Gif_Stream * s1,Gif_Stream * s2)238 compare(Gif_Stream *s1, Gif_Stream *s2)
239 {
240   Gif_Colormap *newcm;
241   int imageno1, imageno2, background1, background2;
242   char buf1[256], buf2[256], fbuf[256];
243 
244   was_different = 0;
245 
246   /* Compare image counts and screen sizes. If either of these differs, quit
247      early. */
248   Gif_CalculateScreenSize(s1, 0);
249   Gif_CalculateScreenSize(s2, 0);
250 
251   if (s1->screen_width != s2->screen_width
252       || s1->screen_height != s2->screen_height) {
253     different("screen sizes differ: <%dx%d >%dx%d", s1->screen_width,
254               s1->screen_height, s2->screen_width, s2->screen_height);
255     return DIFFERENT;
256   }
257 
258   if (s1->screen_width == 0 || s1->screen_height == 0
259       || s2->screen_width == 0 || s2->screen_height == 0) {
260     /* paranoia -- don't think this can happen */
261     different("zero screen sizes");
262     return DIFFERENT;
263   }
264 
265   if (s1->nimages == 0 || s2->nimages == 0) {
266     if (s1->nimages != s2->nimages) {
267       different("frame counts differ: <#%d >#%d", s1->nimages, s2->nimages);
268       return DIFFERENT;
269     } else
270       return SAME;
271   }
272 
273   /* Create arrays for the image data */
274   screen_width = s1->screen_width;
275   screen_height = s1->screen_height;
276 
277   gdata[0] = Gif_NewArray(uint16_t, screen_width * screen_height);
278   gdata[1] = Gif_NewArray(uint16_t, screen_width * screen_height);
279   glast[0] = Gif_NewArray(uint16_t, screen_width * screen_height);
280   glast[1] = Gif_NewArray(uint16_t, screen_width * screen_height);
281   scratch = Gif_NewArray(uint16_t, screen_width * screen_height);
282   line = Gif_NewArray(uint16_t, screen_width);
283 
284   /* Merge all distinct colors from the two images into one colormap, setting
285      the 'pixel' slots in the images' colormaps to the corresponding values
286      in the merged colormap. Don't forget transparency */
287   newcm = Gif_NewFullColormap(1, 256);
288   combine_colormaps(s1->global, newcm);
289   combine_colormaps(s2->global, newcm);
290   for (imageno1 = 0; imageno1 < s1->nimages; ++imageno1)
291     combine_colormaps(s1->images[imageno1]->local, newcm);
292   for (imageno2 = 0; imageno2 < s2->nimages; ++imageno2)
293     combine_colormaps(s2->images[imageno2]->local, newcm);
294 
295   /* Choose the background values */
296   background1 = background2 = TRANSP;
297   if ((s1->nimages == 0 || s1->images[0]->transparent < 0)
298       && s1->global && s1->background < s1->global->ncol)
299       background1 = s1->global->col[ s1->background ].pixel;
300   if ((s2->nimages == 0 || s2->images[0]->transparent < 0)
301       && s2->global && s2->background < s2->global->ncol)
302       background2 = s2->global->col[ s2->background ].pixel;
303 
304   /* Clear screens */
305   fill_area(gdata[0], 0, 0, screen_width, screen_height, TRANSP);
306   fill_area(gdata[1], 0, 0, screen_width, screen_height, TRANSP);
307 
308   /* Loopcounts differ? */
309   if (s1->loopcount != s2->loopcount) {
310     name_loopcount(s1->loopcount, buf1);
311     name_loopcount(s2->loopcount, buf2);
312     different("loop counts differ: <%s >%s", buf1, buf2);
313   }
314 
315   /* Loop over frames, comparing image data and delays */
316   apply_image(0, s1, 0, background1);
317   apply_image(1, s2, 0, background2);
318   imageno1 = imageno2 = 0;
319   while (imageno1 != s1->nimages && imageno2 != s2->nimages) {
320     int fi1 = imageno1, fi2 = imageno2,
321       delay1 = s1->images[fi1]->delay, delay2 = s2->images[fi2]->delay;
322 
323     /* get message right */
324     if (imageno1 == imageno2)
325       sprintf(fbuf, "#%d", imageno1);
326     else
327       sprintf(fbuf, "<#%d >#%d", imageno1, imageno2);
328 
329     /* compare pixels */
330     if (memcmp(gdata[0], gdata[1],
331                screen_width * screen_height * sizeof(uint16_t)) != 0) {
332       unsigned d, c = screen_width * screen_height;
333       uint16_t *d1 = gdata[0], *d2 = gdata[1];
334       for (d = 0; d < c; d++, d1++, d2++)
335         if (*d1 != *d2) {
336           name_color(*d1, newcm, buf1);
337           name_color(*d2, newcm, buf2);
338           different("frame %s pixels differ: %d,%d <%s >%s",
339                     fbuf, d % screen_width, d / screen_width, buf1, buf2);
340           break;
341         }
342     }
343 
344     /* compare background */
345     if (!ignore_background && background1 != background2
346         && (imageno1 == 0 || s1->images[imageno1 - 1]->disposal == GIF_DISPOSAL_BACKGROUND)
347         && (imageno2 == 0 || s2->images[imageno2 - 1]->disposal == GIF_DISPOSAL_BACKGROUND)) {
348         unsigned d, c = screen_width * screen_height;
349         uint16_t *d1 = gdata[0], *d2 = gdata[1];
350         for (d = 0; d < c; ++d, ++d1, ++d2)
351             if (*d1 == TRANSP || *d2 == TRANSP) {
352                 name_color(background1, newcm, buf1);
353                 name_color(background2, newcm, buf2);
354                 different("frame %s background pixels differ: %d,%d <%s >%s",
355                           fbuf, d % screen_width, d / screen_width, buf1, buf2);
356                 background1 = background2 = TRANSP;
357                 break;
358             }
359     }
360 
361     /* move to next images, skipping redundancy */
362     for (++imageno1;
363          imageno1 < s1->nimages && !apply_image(0, s1, imageno1, background1);
364          ++imageno1)
365       delay1 += s1->images[imageno1]->delay;
366     for (++imageno2;
367          imageno2 < s2->nimages && !apply_image(1, s2, imageno2, background2);
368          ++imageno2)
369       delay2 += s2->images[imageno2]->delay;
370 
371     if (!ignore_redundancy) {
372       fi1 = (imageno1 - fi1) - (imageno2 - fi2);
373       for (; fi1 > 0; --fi1)
374         different("extra redundant frame: <#%d", imageno1 - fi1);
375       for (; fi1 < 0; ++fi1)
376         different("extra redundant frame: >#%d", imageno2 + fi1);
377     }
378 
379     if (delay1 != delay2) {
380       name_delay(delay1, buf1);
381       name_delay(delay2, buf2);
382       different("frame %s delays differ: <%s >%s", fbuf, buf1, buf2);
383     }
384   }
385 
386   if (imageno1 != s1->nimages || imageno2 != s2->nimages)
387     different("frame counts differ: <#%d >#%d", s1->nimages, s2->nimages);
388 
389   /* That's it! */
390   Gif_DeleteColormap(newcm);
391   Gif_DeleteArray(gdata[0]);
392   Gif_DeleteArray(gdata[1]);
393   Gif_DeleteArray(glast[0]);
394   Gif_DeleteArray(glast[1]);
395   Gif_DeleteArray(scratch);
396   Gif_DeleteArray(line);
397 
398   return was_different ? DIFFERENT : SAME;
399 }
400 
401 
short_usage(void)402 void short_usage(void) {
403     Clp_fprintf(clp, stderr, "Usage: %s [OPTION]... FILE1 FILE2\n\
404 Try %<%s --help%> for more information.\n",
405                 program_name, program_name);
406 }
407 
usage(void)408 void usage(void) {
409     Clp_fprintf(clp, stdout, "\
410 %<Gifdiff%> compares two GIF files (either images or animations) for identical\n\
411 visual appearance. An animation and an optimized version of the same animation\n\
412 should compare as the same. Gifdiff exits with status 0 if the images are\n\
413 the same, 1 if they%,re different, and 2 if there was some error.\n\
414 \n\
415 Usage: %s [OPTION]... FILE1 FILE2\n\n", program_name);
416     Clp_fprintf(clp, stdout, "\
417 Options:\n\
418   -q, --brief                   Don%,t report detailed differences.\n\
419   -w, --ignore-redundancy       Ignore differences in redundant frames.\n\
420   -B, --ignore-background       Ignore differences in background colors.\n\
421   -h, --help                    Print this message and exit.\n\
422   -v, --version                 Print version number and exit.\n\
423 \n\
424 Report bugs to <ekohler@gmail.com>.\n");
425 }
426 
427 
fatal_error(const char * format,...)428 void fatal_error(const char* format, ...) {
429     char buf[BUFSIZ];
430     int n = snprintf(buf, BUFSIZ, "%s: ", program_name);
431     va_list val;
432     va_start(val, format);
433     Clp_vsnprintf(clp, buf + n, BUFSIZ - n, format, val);
434     va_end(val);
435     fputs(buf, stderr);
436     exit(2);                    /* exit(2) for trouble */
437 }
438 
error(const char * format,...)439 void error(const char* format, ...) {
440     char buf[BUFSIZ];
441     int n = snprintf(buf, BUFSIZ, "%s: ", program_name);
442     va_list val;
443     va_start(val, format);
444     Clp_vsnprintf(clp, buf + n, BUFSIZ - n, format, val);
445     va_end(val);
446     fputs(buf, stderr);
447 }
448 
449 static int gifread_error_count;
450 
451 static void
gifread_error(Gif_Stream * gfs,Gif_Image * gfi,int is_error,const char * message)452 gifread_error(Gif_Stream* gfs, Gif_Image* gfi,
453               int is_error, const char *message)
454 {
455   static int last_is_error = 0;
456   static int last_which_image = 0;
457   static char last_message[256];
458   static int different_error_count = 0;
459   static int same_error_count = 0;
460   int which_image = Gif_ImageNumber(gfs, gfi);
461   const char *filename = gfs->landmark;
462   if (which_image < 0)
463     which_image = gfs->nimages;
464 
465   if (gifread_error_count == 0) {
466     last_which_image = -1;
467     last_message[0] = 0;
468     different_error_count = 0;
469   }
470 
471   gifread_error_count++;
472   if (last_message[0] && different_error_count <= 10
473       && (last_which_image != which_image || message == 0
474           || strcmp(message, last_message) != 0)) {
475     const char *etype = last_is_error ? "error" : "warning";
476     error("While reading %<%s%> frame #%d:\n", filename, last_which_image);
477     if (same_error_count == 1)
478       error("  %s: %s\n", etype, last_message);
479     else if (same_error_count > 0)
480       error("  %s: %s (%d times)\n", etype, last_message, same_error_count);
481     same_error_count = 0;
482     last_message[0] = 0;
483   }
484 
485   if (message) {
486     if (last_message[0] == 0)
487       different_error_count++;
488     same_error_count++;
489     strcpy(last_message, message);
490     last_which_image = which_image;
491     last_is_error = is_error;
492   } else
493     last_message[0] = 0;
494 
495   if (different_error_count == 11 && message) {
496     error("(more errors while reading %<%s%>)\n", filename);
497     different_error_count++;
498   }
499 }
500 
501 static Gif_Stream *
read_stream(const char ** filename)502 read_stream(const char **filename)
503 {
504     FILE *f;
505     Gif_Stream *gfs;
506     if (*filename == 0) {
507 #if 0
508         /* Since gifdiff always takes explicit filename arguments,
509            allow explicit reads from terminal. */
510 #ifndef OUTPUT_GIF_TO_TERMINAL
511         if (isatty(fileno(stdin))) {
512             fatal_error("<stdin>: is a terminal\n");
513             return NULL;
514         }
515 #endif
516 #endif
517         f = stdin;
518 #if defined(_MSDOS) || defined(_WIN32)
519         _setmode(_fileno(stdin), _O_BINARY);
520 #elif defined(__DJGPP__)
521         setmode(fileno(stdin), O_BINARY);
522 #elif defined(__EMX__)
523         _fsetmode(stdin, "b");
524 #endif
525         *filename = "<stdin>";
526     } else {
527         f = fopen(*filename, "rb");
528         if (!f)
529             fatal_error("%s: %s\n", *filename, strerror(errno));
530     }
531     gifread_error_count = 0;
532     gfs = Gif_FullReadFile(f, GIF_READ_COMPRESSED, *filename, gifread_error);
533     if (!gfs)
534         fatal_error("%s: file not in GIF format\n", *filename);
535     return gfs;
536 }
537 
538 int
main(int argc,char * argv[])539 main(int argc, char *argv[])
540 {
541   int how_many_inputs = 0;
542   int status;
543   const char **inputp;
544   Gif_Stream *gfs1, *gfs2;
545 
546   clp = Clp_NewParser(argc, (const char * const *)argv,
547                       sizeof(options) / sizeof(options[0]), options);
548 
549   program_name = Clp_ProgramName(clp);
550   brief = 0;
551 
552   while (1) {
553     int opt = Clp_Next(clp);
554     switch (opt) {
555 
556      case HELP_OPT:
557       usage();
558       exit(0);
559       break;
560 
561      case VERSION_OPT:
562       printf("gifdiff (LCDF Gifsicle) %s\n", VERSION);
563       printf("Copyright (C) 1998-2021 Eddie Kohler\n\
564 This is free software; see the source for copying conditions.\n\
565 There is NO warranty, not even for merchantability or fitness for a\n\
566 particular purpose.\n");
567       exit(0);
568       break;
569 
570      case QUIET_OPT:
571       brief = !clp->negated;
572       break;
573 
574      case IGNORE_REDUNDANCY_OPT:
575       ignore_redundancy = !clp->negated;
576       break;
577 
578      case REDUNDANCY_OPT:
579       ignore_redundancy = !!clp->negated;
580       break;
581 
582      case IGNORE_BACKGROUND_OPT:
583       ignore_background = !clp->negated;
584       break;
585 
586      case BACKGROUND_OPT:
587       ignore_background = !!clp->negated;
588       break;
589 
590      case Clp_NotOption:
591       if (how_many_inputs == 2) {
592         error("too many file arguments\n");
593         goto bad_option;
594       }
595       inputp = (how_many_inputs == 0 ? &filename1 : &filename2);
596       how_many_inputs++;
597       if (strcmp(clp->vstr, "-") == 0)
598         *inputp = 0;
599       else
600         *inputp = clp->vstr;
601       break;
602 
603      bad_option:
604      case Clp_BadOption:
605       short_usage();
606       exit(1);
607       break;
608 
609      case Clp_Done:
610       goto done;
611 
612     }
613   }
614 
615  done:
616 
617   if (how_many_inputs < 2)
618     fatal_error("need exactly 2 file arguments\n");
619   if (filename1 == 0 && filename2 == 0)
620     fatal_error("can%,t read both files from stdin\n");
621 
622   gfs1 = read_stream(&filename1);
623   gfs2 = read_stream(&filename2);
624 
625   status = (compare(gfs1, gfs2) == DIFFERENT);
626   if (status == 1 && brief)
627     printf("GIF files %s and %s differ\n", filename1, filename2);
628 
629   Gif_DeleteStream(gfs1);
630   Gif_DeleteStream(gfs2);
631   return status;
632 }
633