chkdvdimg: Add a new program for checking DVD images.
authorMark Wooding <mdw@distorted.org.uk>
Sun, 6 Mar 2022 16:23:18 +0000 (16:23 +0000)
committerMark Wooding <mdw@distorted.org.uk>
Sun, 6 Mar 2022 20:34:04 +0000 (20:34 +0000)
Primarily I'm interested in checking that the length is right, because
one of my DVD drives sometimes reports the wrong medium size
(specifically, I think, the size for the previous medium).  Also,
sometimes there are zero sectors stuck on the end, which complicates
finding the backup UDF anchor descriptor, so provide an option to trim
these.

.gitignore
Makefile
chkdvdimg.c [new file with mode: 0644]

index 86f1d2f..96a7a15 100644 (file)
@@ -1,6 +1,7 @@
 *.dep
 *.o
 
+/chkdvdimg
 /dvd-cache-keys
 /dvd-check-keys
 /dvd-id
index f858660..836a7f4 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -49,6 +49,9 @@ PROGS                 += dvd-check-keys
 dvd-check-keys_SRCS     = dvd-check-keys.c lib.c multiprogress.c
 dvd-check-keys_LIBS     = -ldvdcss
 
+PROGS                  += chkdvdimg
+chkdvdimg_SRCS          = chkdvdimg.c lib.c multiprogress.c
+
 SCRIPTS                        += dvdrip
 SCRIPTS                        += dvdrip-upload
 SCRIPTS                        += dvdrip-monitor
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);
+}