@@@ dvdrip-upload: change settings while i'm stealing someone else's internet
[dvdrip] / lib.h
diff --git a/lib.h b/lib.h
index 1f08848..7c4a0cb 100644 (file)
--- a/lib.h
+++ b/lib.h
@@ -1,9 +1,38 @@
+/* -*-c-*-
+ *
+ * Common functions for the DVDrip C utilities.
+ *
+ * (c) 2022 Mark Wooding
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the DVD ripping toolset.
+ *
+ * DVDrip is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * DVDrip is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DVDrip.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
 #ifndef LIB_H
 #define LIB_H
 
+/*----- Preliminaries -----------------------------------------------------*/
+
 #define _GNU_SOURCE
 #define _FILE_OFFSET_BITS 64
 
+/*----- Header files ------------------------------------------------------*/
+
 #include <assert.h>
 #include <ctype.h>
 #include <errno.h>
 
 #include "multiprogress.h"
 
+/*----- Various macros with wide applicability ----------------------------*/
+
+/* `ctype.h' functions are troublesome: in particular, they don't handle
+ * negative characters properly, if they're a thing that your platform
+ * believes in.  So we have these macros which fix things up properly.
+ */
 #define CTYPE_HACK(fn, ch) fn((unsigned char)(ch))
 #define ISDIGIT(ch) CTYPE_HACK(isdigit, ch)
 #define ISSPACE(ch) CTYPE_HACK(isspace, ch)
 
+/* It's easy to screw up the `foocmp' functions by leaving off the comparison
+ * with zero.  These macros make it impossible to forget, and put the
+ * relation in the right place syntactically.
+ */
 #define STRCMP(a, op, b) (strcmp((a), (b)) op 0)
 #define STRNCMP(a, op, b, n) (strncmp((a), (b), (n)) op 0)
+#define MEMCMP(a, op, b, n) (memcmp((a), (b), (n)) op 0)
 
+/* Suppress some code unless we're debugging. */
 #ifdef DEBUG
 #  define D(x) x
 #else
 #  define D(x)
 #endif
 
+/* Count the number of elements in an array. */
 #define N(v) (sizeof(v)/sizeof((v)[0]))
 
-#define SECTORSZ 2048
+/* Function attributes.  If you're not using GCC to build then you'll need to
+ * say something different here.
+ */
+#if (defined(__GNUC__) && (__GNUC__ > 2 ||                             \
+                          (__GNUC__ == 2 && __GNUC_MINOR__ >= 5))) ||  \
+    (defined(__clang__) && (__clang_major__ > 3 || \
+                           (__clang_major__ == 3 && __clang_minor__ >= 3)))
+#  define PRINTF_LIKE(fmt, dots) __attribute__((format(printf, fmt, dots)))
+#  define NORETURN __attribute__((noreturn))
+#else
+#  define PRINTF_LIKE(fmt, dots)
+#  define NORETURN
+#endif
+
+/*----- Definitions for low-level DVD access ------------------------------*/
+
+#define SECTORSZ 2048                  /* the DVD sector size */
 #define SECTORS(n) (((n) + (SECTORSZ - 1))/SECTORSZ)
-typedef uint_least32_t secaddr;
-#define PRIuSEC PRIuLEAST32
-#define SECLIMIT 0x00400000
+                               /* convert bytes to * sectors, rounding up */
+typedef uint_least32_t secaddr;                /* a type for sector numbers */
+#define PRIuSEC PRIuLEAST32            /* how to print a sector number  */
+#define SECLIMIT 0x00400000            /* upper bound on sector numbers */
 
-#define PRINTF_LIKE(fmt, dots) __attribute__((format(printf, fmt, dots)))
-#define NORETURN __attribute__((noreturn))
+/*----- Diagnostics -------------------------------------------------------*/
 
-extern const char *prog;
+extern const char *prog;         /* the program name; set with `set_prog' */
 
 extern void set_prog(const char *p);
+       /* Set the program name to P, stripping any directory names. */
+
 extern void vmoan(const char *fmt, va_list ap);
+extern void vmoan_syserr(int err, const char *fmt, va_list ap);
+       /* Low-level warning reporting.  See `moan' and `moan_syserr'. */
+
 extern PRINTF_LIKE(1, 2) void moan(const char *fmt, ...);
+extern PRINTF_LIKE(2, 3) void moan_syserr(int err, const char *fmt, ...);
+       /* Print a warning message, given as a `printf'-like format string
+        * FMT and arguments, to standard error.  If ERR is nonzero, then
+        * append a colon and the human-readable description of the `errno'
+        * value ERR.
+        */
+
 extern PRINTF_LIKE(1, 2) NORETURN void bail(const char *fmt, ...);
 extern PRINTF_LIKE(2, 3) NORETURN
   void bail_syserr(int err, const char *fmt, ...);
+       /* Like the corresponding `moan' functions, except that they also
+        * exit with status code 2.
+        */
+
+/*----- Resizing buffers --------------------------------------------------*/
+
+struct buf {
+  /* A buffer for a string which can grow automatically. */
+
+  char *p;                             /* pointer to the buffer */
+  size_t n, sz;                                /* string length, buffer size */
+};
+#define BUF_INIT { 0, 0, 0 }
+
+static inline void buf_rewind(struct buf *b) { b->n = 0; }
+       /* Throw away the current contents of B so that new stuff gets added
+        * to the beginning.
+        */
+
+static inline void buf_free(struct buf *b)
+  { free(b->p); b->p = 0; b->n = b->sz = 0; }
+       /* Release the memory allocated for B.  The buffer can be reused
+        * immediately and/or freed again safely.
+        */
+
+extern void buf__grow(struct buf *b);
+       /* Make B's buffer larger, so that (at least) one extra byte can be
+        * written to it.  (Internal to `buf_putc'.)
+        */
+
+static inline void buf_putc(struct buf *b, int ch)
+  { if (b->n >= b->sz) buf__grow(b); b->p[b->n++] = ch; }
+       /* Append the character CH to the buffer B. */
+
+static inline void buf_putz(struct buf *b)
+  { if (b->n >= b->sz) buf__grow(b); b->p[b->n] = 0; }
+       /* Append a zero byte to B without increasing the string length, so
+        * that a future `buf_putc' will overwrite it.
+        */
+
+/*----- Resizing vectors --------------------------------------------------*/
+
+#define DEFVEC(vtype, etype)                                           \
+       typedef struct { etype *v; size_t n, sz; } vtype
+#define VEC_INIT { 0, 0, 0 }
+       /* Define VTYPE as a (structured) type for vectors holding elements
+        * of ETYPE.
+        *
+        * A vector V has `V.n' elements, addressed as `V.v[0]' up to
+        * `V.v[V.n - 1]'.
+        */
+
+#define VEC_FREE(vv) do {                                              \
+  free((vv)->v); (vv)->v 0; (vv)->n = (vv)->sz = 0;                    \
+} while (0)
+       /* Free the vector VV.  It's safe to free a vector multiple times. */
+
+extern void *vec__grow(void *p, size_t esz, size_t *sz_inout);
+       /* Extend the buffer P, which currently has space for *SZ_INOUT
+        * elements, each ESZ bytes in size, so that there's space for at
+        * least one one more; return the new buffer address, and update
+        * *SZ_INOUT with the new size.
+        */
+
+#define VEC_PUSH(p, vv) do {                                           \
+  if ((vv)->n >= (vv)->sz)                                             \
+    (vv)->v = vec__grow((vv)->v, sizeof(*(vv)->v), &(vv)->sz);         \
+  (p) = &(vv)->v[(vv)->n++];                                           \
+} while (0)
+       /* Add an initialized element to the end of vector VV, storing its
+        * address in P.
+        */
+
+/*----- Parsing utilities -------------------------------------------------*/
+
+#define PNF_JUNK 1u
+extern double parse_float(const char **p_inout, unsigned f,
+                         double min, double max, const char *what);
+extern long parse_int(const char **p_inout, unsigned f,
+                     long min, long max, const char *what);
+       /* Parse a number starting at *P_IN OUT, advancing that pointer past
+        * it, and return the resulting value.  If no number can be read from
+        * the string, or the resulting number is not between MIN and MAX
+        * inclusive, or the `PNF_JUNK' bit is clear in F and the number is
+        * followed by anything other than whitespace, then report a fatal
+        * error, quoting WHAT as having been expected.
+        */
+
+/*----- System utilities --------------------------------------------------*/
+
+extern double tvdiff(const struct timeval *tv_lo,
+                    const struct timeval *tv_hi);
+       /* Return the (signed) difference from TV_LO to TV_HI, as a floating-
+        * point number of seconds.
+        */
 
 extern void sit(double t);
+       /* Do nothing for T seconds.  As implied by the type, T may be
+        * fractional.
+        */
+
+extern int read_line(FILE *fp, struct buf *b);
+       /* Read a line from FP, appending it to the buffer B, leaving the
+        * string in B null-terminated (as if by `buf_putz').  Return 0 on
+        * success, or -1 if nothing was read (not even an empty line) before
+        * we encountered end-of-file or a read error.
+        */
+
+extern void carefully_write(int fd, const void *buf, size_t sz);
+       /* Write SZ bytes to file descriptor FD, starting at BUF.  Report a
+        * fatal error if this fails.  Correctly handles short writes and
+        * `EINTR'.
+        */
+
+extern void open_file_on_demand(const char *file, FILE **fp_inout,
+                               const char *what);
+       /* If *FP_INOUT is not null, then do nothing.  Otherwise, open FILE
+        * for writing, storing the resulting stream handle in *FP_INOUT; if
+        * this can't be done then report a fatal error, quoting WHAT as the
+        * kind of file.
+        */
+
+extern void check_write(FILE *fp, const char *what);
+       /* Flush any remaining output to FP and check that there were no
+        * errors.  If there were problems, report a fatal error quoting WHAT
+        * as the kind of file.
+        */
+
+extern void carefully_fclose(FILE *fp, const char *what);
+       /* Flush output to FP and close it, reporting fatal errors as for
+        * `check_write'.  If FP is null, then do nothing.
+        */
+
+extern off_t device_size(int fd, const char *file, int *blksz_out);
+       /* Determine the size of the device referred to by FD.  Specifically,
+        * if FD is a regular file, then this is simply the size of the file;
+        * if FD is a block device, then this is the size of the block
+        * device.  Return the resulting size, and, in the case of a block
+        * device only, store the block size in *BLKSZ_OUT.  (Hence,
+        * *BLKSZ_OUT will be left unchanged if FD is open on a regular
+        * file.)  If FD refers to any other kind of object then report a
+        * fatal error quoting FILE as the name of the device.
+        */
+
+/*----- Progress utilities ------------------------------------------------*/
+
+struct banner_progress_item {
+       /* A progress item which simply shows a banner message. */
+  struct progress_item _base;
+  const char *msg;
+};
+
+extern struct progress_state progress; /* the shared progress reporter */
+
+extern void show_banner(const char *msg);
+extern void hide_banner(void);
+       /* Show or hide a banner reporting a message.  If a banner is already
+        * showing, then `show_banner' just changes the message.
+        */
+
+/*----- DVD utilities -----------------------------------------------------*/
+
+extern int open_dvd(const char *device, int mode,
+                   int *fd_out, dvd_reader_t **dvd_out);
+       /* Open the DEVICE.  If FD_OUT is not null, then open a file
+        * descriptor onto the device, with the given open(2)-style MODE,
+        * storing the descriptor in *FD_OUT; if DVD_OUT is not null, then
+        * open a `libdvdread' handle onto the devie and store it in
+        * *DVD_OUT.  If both are null, then why are you calling this
+        * function?  Returns 0 on success or -1 on failure.
+        *
+        * If DEVICE refers to an actual block device, and no medium is
+        * currently inserted, then put up a banner prompting the user and
+        * wait for a medium to be inserted.  Other problems are reported to
+        * stderr.
+        */
 
 enum { RAW, IFO, VOB, BUP };
 typedef uint_least32_t ident;
+       /* A convenient name for interesting files on a DVD.  It consists of
+        * three components:
+        *
+        *   * A `kind', which is `RAW', `IFO', `VOB', or `BUP'.  `RAW' is a
+        *     special token which refers to the whole disc; the other kinds
+        *     refer to files in the DVD filesystem with the corresponding
+        *     extensions.
+        *
+        *   * A `title', which is a number between 0 and 99 inclusive.
+        *     Title zero refers to the video management information and its
+        *     corresponding menu; nonzero numbers refer to video titlesets.
+        *
+        *   * A `part', which is only relevant for `VOB' files; part 0
+        *     refers to the menu data, while parts 1 to 9 inclusive refer to
+        *     portions of the video titleset itself.
+        *
+        * Components which aren't applicable must be zero, so that idents
+        * can conveniently be compared as integers (so, specifically, the
+        * title, if kind is `RAW', and the part, if kind is not `VOB' or
+        * title is zero.
+        */
 
 static inline ident mkident(unsigned kind, unsigned title, unsigned part)
   { return (((ident)kind << 0) | ((ident)title << 8) | ((ident)part << 16)); }
 static inline unsigned id_kind(ident id) { return ((id >> 0)&0x0ff); }
 static inline unsigned id_title(ident id) { return ((id >> 8)&0x0ff); }
 static inline unsigned id_part(ident id) { return ((id >> 16)&0x0ff); }
+       /* Functions for constructing and picking apart the fields of an
+        * ident.
+        */
 
 #define MAXFNSZ (1 + 8 + 1 + 12 + 1)
 extern void store_filename(char *buf, ident id);
+       /* Store in BUF the filename corresponding to the ident ID.  The
+        * filename will be at most `MAXFNSZ' bytes long, including the
+        * terminating zero.
+        */
 
-struct banner_progress_item {
-  struct progress_item _base;
-  const char *msg;
-};
-
-extern struct progress_state progress;
-
-extern void show_banner(const char *msg);
-extern void hide_banner(void);
+#define DIF_MUSTVOLINF 1u
+#define DIF_MUSTIFOHASH 2u
+#define MAXIDSZ (32 + 1 + 32 + 1 + 32 + 1)
+extern int dvd_id(char *p, dvd_reader_t *dvd, unsigned f, const char *file);
+       /* Determine a (hopefully) unique identifier for DVD.  The identifier
+        * consists of two parts:
+        *
+        *   * the volume name and serial number, from the volume
+        *     information, and
+        *
+        *   * a cryptographic hash of the `.IFO' files on the disc.
+        *
+        * The identifier is written, as plain text, at P, and consists of at
+        * most `MAXIDSZ' bytes, including the terminating zero.
+        *
+        * It's possible that determining either, or both, of these might
+        * fail: the behaviour is controlled by the `DIF_MUSTVOLINF' and
+        * `DIF_MUSTIFOHASH' flags in F: if the volume name/serial number, or
+        * `.IFO' hash, respectively, can't be determined, and the
+        * corresponding flag is clear, then a placeholder error message is
+        * written to the output buffer in place of the correct data; if the
+        * flag is set, then a warning message is printed to standard error
+        * and -1 is returned.  In practice, the `.IFO' hash is more likely
+        * to be computed successfully, and probably more likely to actually
+        * be unique.
+        *
+        * Returns zero if the identifier was successfully determined, within
+        * the parameters set by the flags.
+        */
 
-extern void open_dvd(const char *device,
-                    int *fd_out, dvd_reader_t **dvd_out);
+/*----- That's all, folks -------------------------------------------------*/
 
 #endif