chkdvdimg: Add a new program for checking DVD images.
[dvdrip] / chkdvdimg.c
diff --git a/chkdvdimg.c b/chkdvdimg.c
new file mode 100644 (file)
index 0000000..0fbb48e
--- /dev/null
@@ -0,0 +1,199 @@
+#include "lib.h"
+
+static int status = 0;
+static unsigned flags = 0;
+#define F_FIX 1u
+
+static void vgripe_syserr(int st, int err, const char *fmt, va_list ap)
+  { vmoan_syserr(err, fmt, ap); if (st > status) status = st; }
+static void vgripe(int st, const char *fmt, va_list ap)
+  { vgripe_syserr(st, 0, fmt, ap); }
+PRINTF_LIKE(3, 4)
+  static void gripe_syserr(int st, int err, const char *fmt, ...)
+{
+  va_list ap;
+  va_start(ap, fmt); vgripe_syserr(st, err, fmt, ap); va_end(ap);
+}
+PRINTF_LIKE(2, 3) static void gripe(int st, const char *fmt, ...)
+  { va_list ap; va_start(ap, fmt); vgripe(st, fmt, ap); va_end(ap); }
+
+static int carefully_read(int fd, off_t off,
+                         void *p, size_t sz, const char *file)
+{
+  ssize_t n;
+  char *q = p;
+
+  if (lseek(fd, off, SEEK_SET) < 0)
+    { gripe_syserr(2, errno, "failed to seek in `%s'", file); return (-1); }
+
+  while (sz) {
+    n = read(fd, q, sz);
+    if (n < 0)
+      { gripe_syserr(2, errno, "failed to read `%s'", file); return (-1); }
+    else if (!n)
+      { gripe(2, "unexpected end-of-file reading `%s'", file); return (-1); }
+    q += n; sz -= n;
+  }
+  return (0);
+}
+
+typedef uint_least32_t u32;
+#define Pu32 PRIuLEAST32
+
+static u32 load32(const unsigned char *p)
+{
+  return (((u32)p[0] <<  0) | ((u32)p[1] <<  8) |
+         ((u32)p[2] << 16) | ((u32)p[3] << 24));
+}
+
+#define CAHF_GRIPE 1u
+#define CAHF_FULL 1u
+static int check_anchor_header(const unsigned char  *b, secaddr addr,
+                              unsigned f, const char *file)
+{
+  unsigned i, t;
+  secaddr n;
+
+  if (b[0] != 2) {
+    if (f&CAHF_GRIPE)
+      gripe(1, "anchor descriptor not found at `%s' sector %"PRIuSEC"",
+           file, addr);
+    return (-1);
+  }
+
+  for (i = 0, t = 0; i < 16; i++) if (i != 4) t += b[i];
+  t %= 256;
+  if (b[4] != t) {
+    if (f&CAHF_GRIPE)
+      gripe(1, "bad anchor descriptor header checksum (%u /= %u) "
+           "at `%s' sector %"PRIuSEC"", (unsigned)b[4], t, file, addr);
+    return (-1);
+  }
+
+  if (f&CAHF_FULL) {
+    n = load32(b + 12);
+    if (n != addr) {
+      if (f&CAHF_GRIPE)
+       gripe(1, "bad sector number in anchor descriptor "
+             "(%"PRIuSEC" /= %"PRIuSEC") in `%s'",
+             n, addr, file);
+      return (-1);
+    }
+  }
+
+  return (0);
+}
+
+static int all_zero_p(const unsigned char *p, size_t sz)
+{
+  while (sz) {
+    if (*p) return (0);
+    p++; sz--;
+  }
+  return (1);
+}
+
+static void check_img(const char *file)
+{
+  int fd = -1;
+  struct stat st;
+  unsigned char b[SECTORSZ], bb[SECTORSZ];
+  secaddr end;
+  unsigned i, j;
+
+  fd = open(file, (flags&F_FIX) ? O_RDWR : O_RDONLY);
+  if (fd < 0)
+    { gripe_syserr(2, errno, "failed to open `%s'", file); goto end; }
+  if (fstat(fd, &st)) {
+    gripe_syserr(2, errno, "failed to retrieve file status for `%s'", file);
+    goto end;
+  }
+  if (st.st_size%SECTORSZ) {
+    gripe(2, "bad length for `%s' -- not whole number of sectors", file);
+    goto end;
+  }
+  end = st.st_size/SECTORSZ;
+
+  if (carefully_read(fd, 256*SECTORSZ, b, SECTORSZ, file) ||
+      check_anchor_header(b, 256, CAHF_GRIPE | CAHF_FULL, file))
+    goto end;
+
+  for (i = 1; i < 32768; i++) {
+    if (carefully_read(fd, (off_t)(end - i)*SECTORSZ, bb, SECTORSZ, file))
+      goto end;
+    if (bb[0] || !all_zero_p(bb, SECTORSZ)) goto nonzero;
+  }
+  gripe(1, "too many trailing zero sectors: "
+       "couldn't find backup anchor descriptor");
+  goto end;
+nonzero:
+  j = i;
+  for (;;) {
+    if (bb[0] == 2 && !check_anchor_header(bb, end - j, 0, file))
+      break;
+    j++;
+    if (j > i + 256) {
+      gripe(1, "failed to find backup anchor descriptor in `%s'", file);
+      goto end;
+    }
+    if (carefully_read(fd, (off_t)(end - j)*SECTORSZ, bb, SECTORSZ, file))
+      goto end;
+  }
+  if (j > i) {
+    gripe(1, "found backup anchor descriptor at sector %"PRIuSEC" "
+         " = %"PRIuSEC" from end, = %"PRIuSEC" from trailing zeros",
+         end - j, j, j - i + 1);
+    i = j;
+  }
+
+  if (check_anchor_header(bb, end - i, CAHF_GRIPE | CAHF_FULL, file))
+    goto end;
+
+  if (MEMCMP(b + 16, != , bb + 16, SECTORSZ - 16)) {
+    gripe(1, "backup anchor descriptor in sector %"PRIuSEC" "
+         "doesn't match primary in sector 256 of `%s'",
+         end - i, file);
+    goto end;
+  }
+
+  if (i > 1) {
+    if (!(flags&F_FIX))
+      gripe(1, "%u trailing zero sectors in `%s'", i - 1, file);
+    else {
+      if (ftruncate(fd, (off_t)(end - i + 1)*SECTORSZ))
+       gripe_syserr(2, errno,
+                    "failed to truncate `%s' to %"PRIuSEC" sectors",
+                    file, end - i + 1);
+      else
+       moan("removed %u trailing zero sectors from `%s'", i - 1, file);
+    }
+  }
+
+end:
+  if (fd != -1) close(fd);
+}
+
+static void usage(FILE *fp)
+  { fprintf(fp, "usage: %s [-x] IMG ...\n", prog); }
+
+int main(int argc, char *argv[])
+{
+  int i, opt;
+  unsigned f = 0;
+#define f_bogus 1u
+
+  set_prog(argv[0]);
+  for (;;) {
+    opt = getopt(argc, argv, "hx"); if (opt < 0) break;
+    switch (opt) {
+      case 'h': usage(stdout); exit(0);
+      case 'x': flags |= F_FIX; break;
+      default: f |= f_bogus; break;
+    }
+  }
+  if (optind >= argc) f |= f_bogus;
+  if (f&f_bogus) { usage(stderr); exit(2); }
+
+  for (i = optind; i < argc; i++) check_img(argv[i]);
+  return (status);
+}