/* * exifrescue -- salvage EXIF and/or JFIF files from disk image * ディスクイメージファイル、あるいはローデバイスを読んで * JPEGファイルをサルベージする * * ※ wipe-out で全消去したデバイスやイメージからは * ※ 逆立ちしてもJPEGファイルをサルベージできません * * revision history * 0.0: Sep. 22, 2011 by Dai ISHIJIMA * 0.1: Aug. 9, 2013 (offset, strtol) * * example: * % exifrescue disk.img * % exifrescue -v -l 4m -m 8g /dev/da0s1 */ #include #include #include #include #ifdef NO_OFF_T_FSEEKO #define off_t long #define fseeko fseek #define ftello ftell #endif #define YES 1 #define NO 0 char *prog; #define shift --argc; ++argv #define MAXBUF 65536 #define MAXPATH 4096 #define BLKSIZ (1 * 1024) #define MINLEN (64 * 1024) #define MAXLEN (2 * 1024 * 1024) #define OFSLEN 0 #define IMGSIZ ((long long int)8 * 1024 * 1024 * 1024) #define IS_EXIF 'e' #define IS_JFIF 'j' #define SOI 0xffd8 #define FORMOFS 6 #define FORMLEN 4 #define EXIF "Exif" #define JFIF "JFIF" #define hi(x) (((x) >> 8) & 0xff) #define lo(x) ((x) & 0xff) #define hilo(x,y) (((x) << 8) + (y)) /* JPEGの先頭マーカー (SOI) か? */ int is_soi(unsigned char *buf) { if (hilo(buf[0], buf[1]) == SOI) { return(YES); } return(NO); } /* JPEGファイルの先頭か */ int isheader(unsigned char *buf, int len, int chkeof) { int n; if (buf == NULL) { return(-1); } if (is_soi(buf)) { if (strncasecmp(EXIF, (char *)(&buf[FORMOFS]), FORMLEN) == 0) { return(IS_EXIF); } if (strncasecmp(JFIF, (char *)(&buf[FORMOFS]), FORMLEN) == 0) { return(IS_JFIF); } } if (chkeof) { /* 00 か ff が連続していたらJPEGの終了と判断 */ for (n = 1; n < len; n++) { if (buf[n] != buf[0]) { return(0); } } if ((buf[0] == 0) || (buf[0] == 0xff)) { return(-1); } } return(0); } /* イメージファイルを先頭からスキャンしてJPEG画像を復元 */ void salvage(FILE *fp, int blksiz, int minlen, int maxlen, int ofslen, off_t imgsiz, int chkeof, char *path, int verbose) { off_t pos; off_t nextpos, startpos; int len; FILE *ofp; unsigned char buf[MAXBUF]; int type; char filnam[MAXPATH]; int n; n = 0; if (imgsiz <= 0) { if (fseeko(fp, 0, SEEK_END) < 0) { fprintf(stderr, "%s: not a seekable stream\n", prog); exit(1); } imgsiz = ftello(fp); } if (verbose > 0) { fprintf(stderr, "%s: imgsiz: %lld\n", prog, (long long int)imgsiz); } if (imgsiz <= 0) { fprintf(stderr, "%s: imgsiz: %lld, assumed %lld\n", prog, (long long int)imgsiz, IMGSIZ); imgsiz = IMGSIZ; } fseeko(fp, ofslen, SEEK_SET); nextpos = startpos = pos = ftello(fp); if (pos < 0) { /* fseek/fseekoできないときは負のはず */ fprintf(stderr, "%s: not a seekable stream\n", prog); exit(1); } if (verbose > 0) { fprintf(stderr, "%s: start at %08lx\n", prog, (unsigned long)pos); } len = fread(buf, 1, blksiz, fp); ofp = NULL; while (len > 0) { type = isheader(buf, len, chkeof); if ((type == IS_EXIF) || (type == IS_JFIF)) { /* JPEGファイル発見 */ if (verbose > 0) { fprintf(stderr, "%s: JPEG found, type %c, at %08lx (%d%%)\n", prog, type, (unsigned long)pos, (unsigned int)(100 * pos / imgsiz)); } if (ofp == NULL) { /* ファイルに出力 */ nextpos = startpos = pos; sprintf(filnam, "%s%c%07lx.jpg", path, type, (unsigned long)(pos / BLKSIZ)); if (verbose > 0) { fprintf(stderr, "%s: open %s\n", prog, filnam); } if ((ofp = fopen(filnam, "w")) == NULL) { fprintf(stderr, "%s: can't open %s\n", prog, filnam); exit(1); } } else { /* ファイル出力中なら次の開始位置を記憶 */ if (pos - startpos < minlen) { if (nextpos <= startpos) { nextpos = pos; if (verbose > 1) { fprintf(stderr, "%s: set next pos: %08lx\n", prog, (unsigned long)nextpos); } } } else { /* ある程度の長さを出力していたら閉じる */ if (verbose > 1) { fprintf(stderr, "%s: file closed at %08lx\n", prog, (unsigned long)pos); } fclose(ofp); ++n; ofp = NULL; if (nextpos <= startpos) { nextpos = pos; } fseeko(fp, nextpos, SEEK_SET); } } } if (ofp != NULL) { /* ファイル出力中 */ if (type >= 0) { fwrite(buf, 1, len, ofp); } if ((pos - startpos > maxlen) || (type < 0)) { /* ファイルの終端に達したと判断したら閉じる */ if (verbose > 1) { if (type < 0) { fprintf(stderr, "%s: detect EOF\n", prog); } fprintf(stderr, "%s: file closed at %08lx\n", prog, (unsigned long)pos); } fclose(ofp); ++n; ofp = NULL; if (nextpos > startpos) { fseeko(fp, nextpos, SEEK_SET); } } } if ((pos = ftello(fp)) < 0) { fprintf(stderr, "%s: not a seekable stream\n", prog); exit(1); } len = fread(buf, 1, blksiz, fp); } if (ofp != NULL) { fclose(ofp); ++n; } if (verbose > 1) { fprintf(stderr, "%s: wrote %d file(s)\n", prog, n); } } void usage() { fprintf(stderr, "Usage: %s [option] [disk image]\n", prog); fprintf(stderr, " options:\n"); fprintf(stderr, "\t-b # set block size [%d]\n", BLKSIZ); fprintf(stderr, "\t-s # set minimum length [%d]\n", MINLEN); fprintf(stderr, "\t-l # set maximum length [%d]\n", MAXLEN); fprintf(stderr, "\t-m # set disk image length (auto detect)\n"); fprintf(stderr, "\t-o # set offset length [%d]\n", OFSLEN); fprintf(stderr, "\t-x # same as -o, but uses strtol()\n"); fprintf(stderr, "\t-e detect end of file\n"); fprintf(stderr, "\t-p path set output path [current directory]\n"); fprintf(stderr, "\t-v be verbose\n"); } /* k や m がついていたら、kバイト、Mバイトと解釈 */ long long int bytesize(char *s) { long long int n; n = atoi(s); if (s[strlen(s) - 1] == 'k') { n *= 1024; } if (s[strlen(s) - 1] == 'm') { n *= (1024 * 1024); } if (s[strlen(s) - 1] == 'g') { n *= (1024 * 1024 * 1024); } return(n); } int main(int argc, char *argv[]) { int blksiz, minlen, maxlen, ofslen; char path[MAXPATH]; FILE *fp; int chkeof; int verbose; off_t imgsiz; prog = *argv; shift; blksiz = minlen = maxlen = 0; ofslen = -1; verbose = 0; chkeof = NO; strcpy(path, ""); while ((argc > 0) && (argv[0][0] == '-')) { if (argv[0][1] == 'b') { shift; blksiz = bytesize(*argv); } else if (argv[0][1] == 's') { shift; minlen = bytesize(*argv); } else if (argv[0][1] == 'l') { shift; maxlen = bytesize(*argv); } else if (argv[0][1] == 'm') { shift; imgsiz = bytesize(*argv); } else if (argv[0][1] == 'o') { shift; ofslen = bytesize(*argv); } else if (argv[0][1] == 'x') { shift; ofslen = strtol(*argv, (char **)NULL, 0); } else if (argv[0][1] == 'y') { shift; ofslen = strtol(*argv, (char **)NULL, 0) - FORMOFS; } else if (argv[0][1] == 'e') { chkeof = !chkeof; } else if (argv[0][1] == 'p') { shift; strcpy(path, *argv); } else if (argv[0][1] == 'v') { ++verbose; } else { usage(); exit(1); } shift; } if (blksiz <= 0) { blksiz = BLKSIZ; } if (minlen <= 0) { minlen = MINLEN; } if (maxlen <= 0) { maxlen = MAXLEN; } if (ofslen < 0) { ofslen = OFSLEN; } if ((strlen(path) > 0) && (path[strlen(path) - 1] != '/')) { strcat(path, "/"); } if (argc > 0) { while (argc > 0) { if ((fp = fopen(*argv, "r")) == NULL) { fprintf(stderr, "%s: can't open %s\n", prog, *argv); exit(1); } if (verbose > 0) { fprintf(stderr, "%s: file %s, -b=%d -s=%d -l=%d -o=%d -e=%d -p=%s\n", prog, *argv, blksiz, minlen, maxlen, ofslen, chkeof, path); } salvage(fp, blksiz, minlen, maxlen, ofslen, imgsiz, chkeof, path, verbose); shift; } } else { salvage(stdin, blksiz, minlen, maxlen, ofslen, imgsiz, chkeof, path, verbose); } exit(0); } /* Local Variables: */ /* compile-command:"cc -Wall -o exifrescue exifrescue.c" */ /* End: */