/* * exifstrip.c - remove Exif information. * Copyright (C) 2007 candy. * $Id: exifstrip.c,v 1.2 2007/10/16 05:28:45 candy Exp $ */ #include #include #include #include #include #include static char *myname; static char *filename; static int verbose; static int nocopy; static FILE *vout; static unsigned long fpos; static void * mymemmem(const void *mem_, size_t size, const void *key_, size_t ksize) { const char *mem = mem_, *key = key_; if (ksize == 0) { /* return mem; */ } else { if (size < ksize) { mem = NULL; /* 最低でも ksize は必要 */ } else { const char *org = mem; int ch = (unsigned char)*key; size -= ksize - 1; while ((mem = memchr(org, ch, size)) != NULL && memcmp(mem, key, ksize) != 0) { size -= mem - org + 1; org = mem + 1; }/* while */ } } return (void *)mem; }/* mymemmem */ static void xd(const void *p_, size_t n, unsigned long offset) { if (verbose >= 4) { const unsigned char *p = p_; int nx = 16; int i; for (i = 0; i < (n + nx - 1) / nx; i++) { int j; fprintf(vout, "%08lx", offset + i * nx); for (j = 0; j < nx; j++) { int k = i * nx + j; if (k < n) fprintf(vout, " %02x", p[k]); else fprintf(vout, " "); }/* for */ fprintf(vout, " "); for (j = 0; j < nx; j++) { int k = i * nx + j; if (k < n) { if (p[k] >= ' ' && p[k] < 0x7f) fprintf(vout, "%c", p[k]); else fprintf(vout, "."); } }/* for */ fprintf(vout, "\n"); }/* for */ } }/* xd */ enum jpeg_marker_t { JPEG_SOF0 = 0xffc0, JPEG_SOF1 = 0xffc1, JPEG_SOF2 = 0xffc2, JPEG_SOF3 = 0xffc3, JPEG_DHT = 0xffc4, JPEG_SOF5 = 0xffc5, JPEG_SOF6 = 0xffc6, JPEG_SOF7 = 0xffc7, JPEG_JPG = 0xffc8, JPEG_SOF9 = 0xffc9, JPEG_SOF10 = 0xffca, JPEG_SOF11 = 0xffcb, JPEG_DAC = 0xffcc, JPEG_SOF13 = 0xffcd, JPEG_SOF14 = 0xffce, JPEG_SOF15 = 0xffcf, JPEG_SOI = 0xffd8, JPEG_EOI = 0xffd9, JPEG_SOS = 0xffda, JPEG_DQT = 0xffdb, JPEG_DRI = 0xffdd, JPEG_APP0 = 0xffe0, JPEG_APP1 = 0xffe1, JPEG_APP2 = 0xffe2, JPEG_APP3 = 0xffe3, JPEG_APP4 = 0xffe4, JPEG_APP5 = 0xffe5, JPEG_APP6 = 0xffe6, JPEG_APP7 = 0xffe7, JPEG_APP8 = 0xffe8, JPEG_APP9 = 0xffe9, JPEG_APPa = 0xffea, JPEG_APPb = 0xffeb, JPEG_APPc = 0xffec, JPEG_APPd = 0xffed, JPEG_APPe = 0xffee, JPEG_APPf = 0xffef, JPEG_COM = 0xfffe, }; static char * jpeg_marker_str(enum jpeg_marker_t marker) { static char sbuf[32]; char *s = sbuf; switch (marker) { case JPEG_SOF0: s = "SOF0"; break; case JPEG_SOF1: s = "SOF1"; break; case JPEG_SOF2: s = "SOF2"; break; case JPEG_SOF3: s = "SOF3"; break; case JPEG_DHT: s = "DHT"; break; case JPEG_SOF5: s = "SOF5"; break; case JPEG_SOF6: s = "SOF6"; break; case JPEG_SOF7: s = "SOF7"; break; case JPEG_JPG: s = "JPG"; break; case JPEG_SOF9: s = "SOF9"; break; case JPEG_SOF10: s = "SOF10"; break; case JPEG_SOF11: s = "SOF11"; break; case JPEG_DAC: s = "DAC"; break; case JPEG_SOF13: s = "SOF13"; break; case JPEG_SOF14: s = "SOF14"; break; case JPEG_SOF15: s = "SOF15"; break; case JPEG_SOI: s = "SOI"; break; case JPEG_EOI: s = "EOI"; break; case JPEG_SOS: s = "SOS"; break; case JPEG_DQT: s = "DQT"; break; case JPEG_DRI: s = "DRI"; break; case JPEG_APP0: s = "APP0"; break; case JPEG_APP1: s = "APP1"; break; case JPEG_APP2: s = "APP2"; break; case JPEG_APP3: s = "APP3"; break; case JPEG_APP4: s = "APP4"; break; case JPEG_APP5: s = "APP5"; break; case JPEG_APP6: s = "APP6"; break; case JPEG_APP7: s = "APP7"; break; case JPEG_APP8: s = "APP8"; break; case JPEG_APP9: s = "APP9"; break; case JPEG_APPa: s = "APPa"; break; case JPEG_APPb: s = "APPb"; break; case JPEG_APPc: s = "APPc"; break; case JPEG_APPd: s = "APPd"; break; case JPEG_APPe: s = "APPe"; break; case JPEG_APPf: s = "APPf"; break; case JPEG_COM: s = "COM"; break; default: sprintf(s, "%04x", marker); break; }/* switch */ return s; }/* jpeg_marker_str */ static int jpeg_marker_print(unsigned long pos, enum jpeg_marker_t marker, int n) { if (verbose) { if (n == 0) fprintf(vout, "%08lx %04x %s\n", pos, marker, jpeg_marker_str(marker)); else fprintf(vout, "%08lx %04x %s %04x bytes\n", pos, marker, jpeg_marker_str(marker), n); } return 0; }/* jpeg_marker_print */ static unsigned long tiff_u8_get(int ii, const void *p_, int offset) { const unsigned char *p = p_; unsigned long x = p[offset + 0]; return x; }/* tiff_u8_get */ static unsigned long tiff_u16_get(int ii, const void *p_, int offset) { const unsigned char *p = p_; unsigned long x = (p[offset + 0] << 8) | p[offset + 1]; if (ii) x = htons(x); return x; }/* tiff_u16_get */ static unsigned long tiff_u32_get(int ii, const void *p_, int offset) { const unsigned char *p = p_; unsigned long x = (p[offset + 0] << 24) | (p[offset + 1] << 16) | (p[offset + 2] << 8) | p[offset + 3]; if (ii) x = htonl(x); return x; }/* tiff_u32_get */ static float tiff_sgl_get(int ii, const void *p_, int offset) { float x; const unsigned char *p = p_; if (ii && *(unsigned short *)"\x12\x34" == 0x1234) { unsigned char q[4]; q[0] = p[3]; q[1] = p[2]; q[2] = p[1]; q[3] = p[0]; x = *(float *)q; } else { x = *(float *)p; } return x; }/* tiff_sgl_get */ static double tiff_dbl_get(int ii, const void *p_, int offset) { float x; const unsigned char *p = p_; if (ii && *(unsigned short *)"\x12\x34" == 0x1234) { unsigned char q[8]; q[0] = p[7]; q[1] = p[6]; q[2] = p[5]; q[3] = p[4]; q[4] = p[3]; q[5] = p[2]; q[6] = p[1]; q[7] = p[0]; x = *(float *)q; } else { x = *(float *)p; } return x; }/* tiff_dbl_get */ enum ifd_tag_type_t { IFD_TAG_0, IFD_TAG_U8, IFD_TAG_ASCII, IFD_TAG_U16, IFD_TAG_U32, IFD_TAG_URATIONAL, IFD_TAG_I8, IFD_TAG_UNDEFINED, IFD_TAG_I16, IFD_TAG_I32, IFD_TAG_RATIONAL, IFD_TAG_SINGLE, IFD_TAG_DOUBLE, }; static char * ifd_tag_type_str(enum ifd_tag_type_t ifd_tag_type) { char *s = NULL; switch (ifd_tag_type) { case IFD_TAG_0: s = "0"; break; case IFD_TAG_U8: s = "U8"; break; case IFD_TAG_ASCII: s = "ASCII"; break; case IFD_TAG_U16: s = "U16"; break; case IFD_TAG_U32: s = "U32"; break; case IFD_TAG_URATIONAL: s = "URATIONAL"; break; case IFD_TAG_I8: s = "I8"; break; case IFD_TAG_UNDEFINED: s = "UNDEFINED"; break; case IFD_TAG_I16: s = "I16"; break; case IFD_TAG_I32: s = "I32"; break; case IFD_TAG_RATIONAL: s = "RATIONAL"; break; case IFD_TAG_SINGLE: s = "SINGLE"; break; case IFD_TAG_DOUBLE: s = "DOUBLE"; break; default: s = "?"; break; }/* switch */ return s; }/* ifd_tag_type_str */ static int ifd_tag_type_len(enum ifd_tag_type_t ifd_tag_type) { int len = 0; switch (ifd_tag_type) { case IFD_TAG_0: len = 0; break; case IFD_TAG_U8: len = 1; break; case IFD_TAG_ASCII: len = 1; break; case IFD_TAG_U16: len = 2; break; case IFD_TAG_U32: len = 4; break; case IFD_TAG_URATIONAL: len = 8; break; case IFD_TAG_I8: len = 1; break; case IFD_TAG_UNDEFINED: len = 1; break; case IFD_TAG_I16: len = 2; break; case IFD_TAG_I32: len = 4; break; case IFD_TAG_RATIONAL: len = 8; break; case IFD_TAG_SINGLE: len = 4; break; case IFD_TAG_DOUBLE: len = 8; break; default: len = 0; break; }/* switch */ return len; }/* ifd_tag_type_len */ struct ifd_tag_t { unsigned int ifd_tag_tag; enum ifd_tag_type_t ifd_tag_type; unsigned long ifd_tag_count; unsigned long ifd_tag_offset; void *ifd_tag_val; }; static int app1_IFD_tag_val(int ii, const void *p, int offset, enum ifd_tag_type_t type, void *val) { switch (type) { case IFD_TAG_I8: case IFD_TAG_U8: *(unsigned char *)val = tiff_u8_get(ii, p, offset); break; case IFD_TAG_I16: case IFD_TAG_U16: *(unsigned short *)val = tiff_u16_get(ii, p, offset); break; case IFD_TAG_I32: case IFD_TAG_U32: *(unsigned long *)val = tiff_u32_get(ii, p, offset); break; case IFD_TAG_RATIONAL: case IFD_TAG_URATIONAL: *(unsigned long *)val = tiff_u32_get(ii, p, offset); *((unsigned long *)val + 1) = tiff_u32_get(ii, p, offset + 4); break; case IFD_TAG_SINGLE: *(float *)val = tiff_sgl_get(ii, p, offset); break; case IFD_TAG_DOUBLE: *(double *)val = tiff_dbl_get(ii, p, offset); break; default: break; }/* switch */ return offset + ifd_tag_type_len(type); }/* app1_IFD_tag_val */ static void * emalloc(size_t n) { void *p = malloc(n); if (p == NULL) fprintf(stderr, "%s: %s: malloc(%d) failed\n", myname, strerror(errno), n); return p; }/* emalloc */ static int app1_IFD_tag_print(const struct ifd_tag_t *tag, unsigned long pos) { if (verbose >= 2) { int ttag = tag->ifd_tag_tag; enum ifd_tag_type_t ttype = tag->ifd_tag_type; unsigned long tcnt = tag->ifd_tag_count; unsigned long toff = tag->ifd_tag_offset; fprintf(vout, "%08lx %04x %04x %08lx %08lx: %s", pos, ttag, ttype, tcnt, toff, ifd_tag_type_str(ttype)); switch (ttype) { case IFD_TAG_ASCII: case IFD_TAG_UNDEFINED: if (tag->ifd_tag_val != NULL) { unsigned char *str = tag->ifd_tag_val; int i; fprintf(vout, ": "); for (i = 0; i < tcnt; i++) { int c = str[i]; if (c >= ' ' && c < 0x7f) fprintf(vout, "%c", c); else fprintf(vout, "\\x%02x", c); }/* for */ } break; #define print_values(TYPE, FMT) do { \ TYPE *vp = tag->ifd_tag_val; \ if (vp != NULL) { \ fprintf(vout, ":"); \ int i; \ for (i = 0; i < tcnt; i++) { \ fprintf(vout, FMT, vp[i]); \ } \ } \ } while (0) case IFD_TAG_I8: case IFD_TAG_U8: print_values(unsigned char, " %02x"); break; case IFD_TAG_I16: case IFD_TAG_U16: print_values(unsigned short, " %04x"); break; case IFD_TAG_I32: case IFD_TAG_U32: print_values(unsigned long, " %08lx"); break; case IFD_TAG_SINGLE: print_values(float, " %g"); break; case IFD_TAG_DOUBLE: print_values(double, " %g"); break; case IFD_TAG_RATIONAL: case IFD_TAG_URATIONAL: { unsigned long *vp = tag->ifd_tag_val; if (vp != NULL) { fprintf(vout, ":"); int i; for (i = 0; i < tcnt; i++) { fprintf(vout, " %lu/%lu", vp[i * 2], vp[i * 2 + 1]); } } } break; default: break; }/* switch */ fprintf(vout, "\n"); } return 0; }/* app1_IFD_tag_print */ static struct ifd_tag_t app1_IFD_tag_get(int ii, const void *p_, int size, int offset) { const unsigned char *p = p_; struct ifd_tag_t tag; int ttag = tiff_u16_get(ii, p, offset + 0); enum ifd_tag_type_t ttype = tiff_u16_get(ii, p, offset + 2); unsigned long tcnt = tiff_u32_get(ii, p, offset + 4); unsigned long toff = tiff_u32_get(ii, p, offset + 8); tag.ifd_tag_tag = ttag; tag.ifd_tag_type = ttype; tag.ifd_tag_count = tcnt; tag.ifd_tag_offset = toff; tag.ifd_tag_val = NULL; switch (ttype) { case IFD_TAG_0: break; case IFD_TAG_UNDEFINED: case IFD_TAG_ASCII: tag.ifd_tag_val = emalloc(tcnt); if (tag.ifd_tag_val != NULL) { const unsigned char *vp = (tcnt <= 4) ? p + offset + 8 : p + toff; memcpy(tag.ifd_tag_val, vp, tcnt); } break; default: { int len = ifd_tag_type_len(ttype); const unsigned char *vp = (len * tcnt <= 4) ? p + offset + 8 : p + toff; unsigned char *v = emalloc(len * tcnt); if (v != NULL) { int i; for (i = 0; i < tcnt; i++) { app1_IFD_tag_val(ii, vp, i * len, ttype, v + len * i); }/* for */ } tag.ifd_tag_val = v; } break; }/* switch */ return tag; }/* app1_IFD_tag_get */ enum ifd_0th_tag_t { IFD_TAG_NONE = 0xffff, IFD_TAG_EXIF_IFD = 0x8769, IFD_TAG_GPS_IFD = 0x8825, IFD_TAG_MAKERNOTE_IFD = 0x927c, IFD_TAG_INTEROPERABILITY_IFD = 0xa005, }; static struct ifd_tag_t * app1_IFD(int ii, const void *p_, int size, unsigned long pos, int offset, const char *name, unsigned long *next) { const unsigned char *p = p_; int ifd_count = tiff_u16_get(ii, p, offset + 0); unsigned long ifd_next = tiff_u32_get(ii, p, offset + 2 + 12 * ifd_count); *next = ifd_next; if (verbose >= 2) fprintf(vout, "%08lx %04x fields (%s) next = %08lx\n", pos, ifd_count, name, ifd_next); struct ifd_tag_t *tag = emalloc(sizeof(*tag) * (ifd_count + 1)); if (tag != NULL) { int i; for (i = 0; i < ifd_count; i++) { int j = 2 + i * 12; tag[i] = app1_IFD_tag_get(ii, p, size, offset + j); app1_IFD_tag_print(&tag[i], pos + j); }/* for */ tag[i].ifd_tag_tag = IFD_TAG_NONE; } return tag; }/* app1_IFD */ static struct ifd_tag_t * app1_IFD_tag_lookup(const struct ifd_tag_t *tag, int ttag) { const struct ifd_tag_t *t = tag; if (t != NULL) { while (t->ifd_tag_tag != IFD_TAG_NONE && t->ifd_tag_tag != ttag) t++; if (t->ifd_tag_tag != ttag) t = NULL; } return (struct ifd_tag_t *)t; }/* app1_IFD_tag_lookup */ static int app1_tiff_print(const void *p_, int size, unsigned long pos) { const unsigned char *p = p_; unsigned int p0p1 = tiff_u16_get(0, p, 0); if (p0p1 == 0x4949 || p0p1 == 0x4d4d) { /* II or MM */ int ii = p0p1 == 0x4949; int p2p3 = tiff_u16_get(ii, p, 2); unsigned long to_zero_IFD = tiff_u32_get(ii, p, 4); struct ifd_tag_t *zero_IFD; unsigned long to_first_IFD; unsigned long next; if (verbose >= 2) fprintf(vout, "%08lx %04x %04x %08lx (%c%c)\n", pos, p0p1, p2p3, to_zero_IFD, p[0], p[1]); if (p2p3 != 0x002a) fprintf(stderr, "%s: %s: Warning: 0x002a not found\n", myname, filename); zero_IFD = app1_IFD(ii, p, size, pos + 8, to_zero_IFD, "0th IFD", &to_first_IFD); if (zero_IFD != NULL) { struct ifd_tag_t *t = app1_IFD_tag_lookup(zero_IFD, IFD_TAG_EXIF_IFD); if (t != NULL) { struct ifd_tag_t *exif_IFD = app1_IFD(ii, p, size, pos + 8 + t->ifd_tag_offset, t->ifd_tag_offset, "Exif IFD", &next); if (exif_IFD != NULL) { t = app1_IFD_tag_lookup(exif_IFD, IFD_TAG_INTEROPERABILITY_IFD); if (t != NULL) { struct ifd_tag_t *interop_IFD = app1_IFD(ii, p, size, pos + 8 + t->ifd_tag_offset, t->ifd_tag_offset, "Interoperability IFD", &next); } t = app1_IFD_tag_lookup(exif_IFD, IFD_TAG_MAKERNOTE_IFD); if (0 && t != NULL) { struct ifd_tag_t *makernote_IFD = app1_IFD(ii, p, size, pos + 8 + t->ifd_tag_offset, t->ifd_tag_offset, "Makernote IFD", &next); } } } t = app1_IFD_tag_lookup(zero_IFD, IFD_TAG_GPS_IFD); if (t != NULL) { struct ifd_tag_t *gps_IFD = app1_IFD(ii, p, size, pos + 8 + t->ifd_tag_offset, t->ifd_tag_offset, "GPS IFD", &next); } } if (to_first_IFD != 0) { struct ifd_tag_t *first_IFD = app1_IFD(ii, p, size, pos + 8 + to_first_IFD, to_first_IFD, "1st IFD", &next); } } else { fprintf(stderr, "%s: %s: Error: II nor MM not found\n", myname, filename); } return 0; }/* app1_tiff_print */ static int app1_print(enum jpeg_marker_t marker, int size, const void *buf, unsigned long pos) { const unsigned char *p = buf; if (memcmp(&p[0], "\x45\x78\x69\x66\x00\x00", 6) == 0) { if (verbose >= 2) fprintf(vout, "%08lx Exif\\0\\0\n", pos); app1_tiff_print(&p[6], size - 2 - 6, pos + 6); } else { } return 0; }/* app1_print */ static int jpeg_marker_read_2(FILE *fp) { int x = -1; int err = fread(&x, 1, 2, fp); if (err == 2) { x = ntohs(x); } fpos += 2; return x; }/* jpeg_marker_read_2 */ static int jpeg_marker_read(FILE *fp, enum jpeg_marker_t *marker, int *size) { int err = -1; *marker = jpeg_marker_read_2(fp); *size = jpeg_marker_read_2(fp); if (*marker >= 0 && *size >= 0) err = 0; return err; }/* jpeg_marker_read */ static int jpeg_marker_write_2(enum jpeg_marker_t marker, FILE *ofp) { uint16_t u16 = htons(marker); int err = -1; if (nocopy) err = 0; else { if (fwrite(&u16, 1, 2, ofp) == 2) err = 0; } return err; }/* jpeg_marker_write_2 */ static int jpeg_marker_copy(FILE *fp, enum jpeg_marker_t marker, int size, FILE *ofp, int no_copy) { static char buf[65536]; int err = -1; int csize = size - 2; if (fread(buf, 1, csize, fp) == csize) { fpos += csize; if (nocopy || no_copy) { xd(buf, csize, fpos - csize); if (marker == JPEG_APP1) app1_print(marker, size, buf, fpos - csize); err = 0; } else { if (jpeg_marker_write_2(marker, ofp) == 0 && jpeg_marker_write_2(size, ofp) == 0 && fwrite(buf, 1, csize, ofp) == csize) err = 0; } } return err; }/* jpeg_marker_copy */ static int jpeg_copy(FILE *fp, FILE *ofp) { int err = -1; #define BUFSIZE (1024*1024) unsigned char *buf = malloc(BUFSIZE); if (buf != NULL) { int red; int lastch = 0; err = 0; while (err == 0 && (red = fread(buf, 1, BUFSIZE, fp)) != 0) { static unsigned char EOI[2] = {(JPEG_EOI >> 8) & 0xff, JPEG_EOI & 0xff}; int wsize = red, written; fpos += red; if (((lastch << 8) | buf[0]) == JPEG_EOI) { wsize = 1; jpeg_marker_print(fpos - red + wsize - 2, JPEG_EOI, 0); } else { unsigned char *p = mymemmem(buf, red, EOI, sizeof(EOI)); if (p != NULL) { wsize = p - buf + 2; jpeg_marker_print(fpos - red + wsize - 2, JPEG_EOI, 0); } } lastch = buf[red - 1]; if (wsize != red) { if (verbose) fprintf(vout, "%08lx trailing garbage\n", fpos - red + wsize); } if (!nocopy) { written = fwrite(buf, 1, wsize, ofp); if (written != wsize) err = -1; } }/* while */ free(buf); } return err; }/* jpeg_copy */ static int exif_strip_loop(FILE *fp, FILE *ofp) { int err = 0, done = 0; enum jpeg_marker_t marker; int size; while (err == 0 && !done && jpeg_marker_read(fp, &marker, &size) == 0) { jpeg_marker_print(fpos - 4, marker, size); switch (marker) { case JPEG_SOF0: case JPEG_SOF1: case JPEG_SOF2: case JPEG_SOF3: case JPEG_SOF5: case JPEG_SOF6: case JPEG_SOF7: case JPEG_SOF9: case JPEG_SOF10: case JPEG_SOF11: case JPEG_SOF13: case JPEG_SOF14: case JPEG_SOF15: case JPEG_DHT: case JPEG_SOI: case JPEG_DQT: case JPEG_DRI: err = jpeg_marker_copy(fp, marker, size, ofp, 0); break; case JPEG_SOS: err = jpeg_marker_copy(fp, marker, size, ofp, 0); if (err == 0) err = jpeg_copy(fp, ofp); break; case JPEG_EOI: err = jpeg_marker_write_2(marker, ofp); done = 1; break; default: err = jpeg_marker_copy(fp, marker, size, ofp, 1); break; }/* switch */ }/* while */ return err; }/* exif_strip_loop */ static int exif_strip(FILE *fp, FILE *ofp) { int err = -1; fpos = 0; if (jpeg_marker_read_2(fp) == JPEG_SOI) { jpeg_marker_print(fpos - 2, JPEG_SOI, 0); err = jpeg_marker_write_2(JPEG_SOI, ofp); if (err == 0) err = exif_strip_loop(fp, ofp); } if (err < 0) { fprintf(stderr, "%s: %s: %s\n", myname, filename, strerror(errno)); } return err; } static int fnain(FILE *fp) { return exif_strip(fp, stdout); }/* fnain */ static int nain(const char *name) { int err = -1; FILE *fp = fopen(name, "r"); filename = (char *)name; if (fp != NULL) { if (verbose) fprintf(vout, "===> %s <===\n", name); err = fnain(fp); fclose(fp); } return err; }/* nain */ static char *usage_msg = "usage: %s [-nvV] jpeg_file\n"; int main(int argc, char *argv[]) { int ex = 1, show_usage = 0; int ch; myname = argv[0]; while ((ch = getopt(argc, argv, "nvV")) != EOF) { switch (ch) { default: case 'V': show_usage++; break; case 'n': nocopy++; vout = stdout; break; case 'v': verbose++; break; }/* switch */ }/* while */ if (show_usage) { fprintf(stderr, usage_msg, myname); } else { if (argc - optind == 0) { filename = "stdin"; ex = fnain(stdin) < 0; } else { int i; ex = 0; for (i = optind; i < argc; i++) { if (nain(argv[i]) < 0) ex = 1; }/* for */ } } return ex; }/* main */