New dd conv= symbols nocreat, excl, fdatasync, fsync,
authorJim Meyering <jim@meyering.net>
Thu, 8 Apr 2004 10:22:05 +0000 (10:22 +0000)
committerJim Meyering <jim@meyering.net>
Thu, 8 Apr 2004 10:22:05 +0000 (10:22 +0000)
and new dd options iflag= and oflag=.

(usage): Document.
(fdatasync) [!HAVE_FDATASYNC]: New macro.
(C_NOCREAT, C_EXCL, C_FDATASYNC, C_FSYNC): New macros.
(input_flags, output_flags): New vars.
(LONGEST_SYMBOL): New macro.
(struct symbol_value): Renamed from struct conversion.  Members
symbol and value renamed from convname and conversion.  The
symbol value is now an array instead of a pointer; this saves
a bit of space and time in practice.  All uses changed.
(conversions): Add nocreat, excl, fdatasync, fsync.  Now const.
(flags): New constant array.
(iflag_error_msgid, oflag_error_msgid): New constants.
(parse_symbols): Renamed from parse_conversion and generalized
to handle either conversion or flag symbols.
(scanargs): Adjust uses of parse_symbols accodingly.  Add
support for iflag= and oflag=.  Reject attempts to use
both excl and nocreat.
(set_fd_flags): New function.
(dd_copy): Just return X rather than calling quit (X), since our
caller invokes quit with the returned value.  Add support for
fdatasync and fsync.
(main): Add support for iflags=, oflags=, and new conv= symbols.

src/dd.c

index c49183187891d6850bad521bb2cf367ed216a66e..9b72a63124caaf60a8325ee3280fc5a625aeb24e 100644 (file)
--- a/src/dd.c
+++ b/src/dd.c
 # define S_TYPEISSHM(Stat_ptr) 0
 #endif
 
+#if ! HAVE_FDATASYNC
+# define fdatasync(fd) (errno = ENOSYS, -1)
+#endif
+
 #define ROUND_UP_OFFSET(X, M) ((M) - 1 - (((X) + (M) - 1) % (M)))
 #define PTR_ALIGN(Ptr, M) ((Ptr) \
                           + ROUND_UP_OFFSET ((char *)(Ptr) - (char *)0, (M)))
 #define C_SYNC 02000
 /* Use separate input and output buffers, and combine partial input blocks. */
 #define C_TWOBUFS 04000
+#define C_NOCREAT 010000
+#define C_EXCL 020000
+#define C_FDATASYNC 040000
+#define C_FSYNC 0100000
 
 /* The name this program was run with. */
 char *program_name;
@@ -111,6 +119,10 @@ static uintmax_t max_records = (uintmax_t) -1;
 /* Bit vector of conversions to apply. */
 static int conversions_mask = 0;
 
+/* Open flags for the input and output files.  */
+static int input_flags = 0;
+static int output_flags = 0;
+
 /* If nonzero, filter characters through the translation table.  */
 static int translation_needed = 0;
 
@@ -143,13 +155,18 @@ static size_t oc = 0;
 /* Index into current line, for `conv=block' and `conv=unblock'.  */
 static size_t col = 0;
 
-struct conversion
+/* A longest symbol in the struct symbol_values table below.  */
+#define LONGEST_SYMBOL "fdatasync"
+
+/* A symbol and the corresponding integer value.  */
+struct symbol_value
 {
-  char *convname;
-  int conversion;
+  char symbol[sizeof LONGEST_SYMBOL];
+  int value;
 };
 
-static struct conversion conversions[] =
+/* Conversion symbols, for conv="...".  */
+static struct symbol_value const conversions[] =
 {
   {"ascii", C_ASCII | C_TWOBUFS},      /* EBCDIC to ASCII. */
   {"ebcdic", C_EBCDIC | C_TWOBUFS},    /* ASCII to EBCDIC. */
@@ -160,9 +177,26 @@ static struct conversion conversions[] =
   {"ucase", C_UCASE | C_TWOBUFS},      /* Translate lower to upper case. */
   {"swab", C_SWAB | C_TWOBUFS},        /* Swap bytes of input. */
   {"noerror", C_NOERROR},      /* Ignore i/o errors. */
+  {"nocreat", C_NOCREAT},      /* Do not create output file.  */
+  {"excl", C_EXCL},            /* Fail if the output file already exists.  */
   {"notrunc", C_NOTRUNC},      /* Do not truncate output file. */
   {"sync", C_SYNC},            /* Pad input records to ibs with NULs. */
-  {NULL, 0}
+  {"fdatasync", C_FDATASYNC},  /* Synchronize output data before finishing.  */
+  {"fsync", C_FSYNC},          /* Also synchronize output metadata.  */
+  {"", 0}
+};
+
+/* Flags, for iflag="..." and oflag="...".  */
+static struct symbol_value const flags[] =
+{
+  {"append",   O_APPEND},
+  {"direct",   O_DIRECT},
+  {"dsync",    O_DSYNC},
+  {"noctty",   O_NOCTTY},
+  {"nofollow", O_NOFOLLOW},
+  {"nonblock", O_NONBLOCK},
+  {"sync",     O_SYNC},
+  {"",         0}
 };
 
 /* Translation table formed by applying successive transformations. */
@@ -290,14 +324,16 @@ Copy a file, converting and formatting according to the options.\n\
 \n\
   bs=BYTES        force ibs=BYTES and obs=BYTES\n\
   cbs=BYTES       convert BYTES bytes at a time\n\
-  conv=KEYWORDS   convert the file as per the comma separated keyword list\n\
+  conv=CONVS      convert the file as per the comma separated symbol list\n\
   count=BLOCKS    copy only BLOCKS input blocks\n\
   ibs=BYTES       read BYTES bytes at a time\n\
 "), stdout);
       fputs (_("\
   if=FILE         read from FILE instead of stdin\n\
+  iflag=FLAGS     read as per the comma separated symbol list\n\
   obs=BYTES       write BYTES bytes at a time\n\
   of=FILE         write to FILE instead of stdout\n\
+  oflag=FLAGS     write as per the comma separated symbol list\n\
   seek=BLOCKS     skip BLOCKS obs-sized blocks at start of output\n\
   skip=BLOCKS     skip BLOCKS ibs-sized blocks at start of input\n\
 "), stdout);
@@ -308,7 +344,8 @@ Copy a file, converting and formatting according to the options.\n\
 BLOCKS and BYTES may be followed by the following multiplicative suffixes:\n\
 xM M, c 1, w 2, b 512, kB 1000, K 1024, MB 1000*1000, M 1024*1024,\n\
 GB 1000*1000*1000, G 1024*1024*1024, and so on for T, P, E, Z, Y.\n\
-Each KEYWORD may be:\n\
+\n\
+Each CONV symbol may be:\n\
 \n\
 "), stdout);
       fputs (_("\
@@ -320,15 +357,38 @@ Each KEYWORD may be:\n\
   lcase     change upper case to lower case\n\
 "), stdout);
       fputs (_("\
+  nocreat   do not create the output file\n\
+  excl      fail if the output file already exists\n\
   notrunc   do not truncate the output file\n\
   ucase     change lower case to upper case\n\
   swab      swap every pair of input bytes\n\
   noerror   continue after read errors\n\
   sync      pad every input block with NULs to ibs-size; when used\n\
               with block or unblock, pad with spaces rather than NULs\n\
+  fdatasync physically write output file data before finishing\n\
+  fsync     likewise, but also write metadata\n\
 "), stdout);
       fputs (_("\
 \n\
+Each FLAG symbol may be:\n\
+\n\
+  append    append mode (makes sense only for output)\n\
+"), stdout);
+      if (O_DIRECT)
+       fputs (_("  direct    use direct I/O for data\n"), stdout);
+      if (O_DSYNC)
+       fputs (_("  dsync     use synchronized I/O for data\n"), stdout);
+      if (O_SYNC)
+       fputs (_("  sync      likewise, but also for metadata\n"), stdout);
+      if (O_NONBLOCK)
+       fputs (_("  nonblock  use non-blocking I/O\n"), stdout);
+      if (O_NOFOLLOW)
+       fputs (_("  nofollow  do not follow symlinks\n"), stdout);
+      if (O_NOCTTY)
+       fputs (_("  noctty    do not assign controlling terminal from file\n"),
+              stdout);
+      fputs (_("\
+\n\
 Note that sending a SIGUSR1 signal to a running `dd' process makes it\n\
 print to standard error the number of records read and written so far,\n\
 then to resume copying.\n\
@@ -486,33 +546,47 @@ write_output (void)
   oc = 0;
 }
 
-/* Interpret one "conv=..." option.
+/* Diagnostics for invalid iflag="..." and oflag="..." symbols.  */
+static char const iflag_error_msgid[] = N_("invalid input flag: %s");
+static char const oflag_error_msgid[] = N_("invalid output flag: %s");
+
+/* Interpret one "conv=..." or similar option STR according to the
+   symbols in TABLE, returning the flags specified.  If the option
+   cannot be parsed, use ERROR_MSGID to generate a diagnostic.
    As a by product, this function replaces each `,' in STR with a NUL byte.  */
 
-static void
-parse_conversion (char *str)
+static int
+parse_symbols (char *str, struct symbol_value const *table,
+              char const *error_msgid)
 {
-  char *new;
-  int i;
+  int value = 0;
 
   do
     {
-      new = strchr (str, ',');
+      struct symbol_value const *entry;
+      char *new = strchr (str, ',');
       if (new != NULL)
        *new++ = '\0';
-      for (i = 0; conversions[i].convname != NULL; i++)
-       if (STREQ (conversions[i].convname, str))
-         {
-           conversions_mask |= conversions[i].conversion;
-           break;
-         }
-      if (conversions[i].convname == NULL)
+      for (entry = table; ; entry++)
        {
-         error (0, 0, _("invalid conversion: %s"), quote (str));
-         usage (EXIT_FAILURE);
+         if (! entry->symbol[0])
+           {
+             error (0, 0, _(error_msgid), quote (str));
+             usage (EXIT_FAILURE);
+           }
+         if (STREQ (entry->symbol, str))
+           {
+             if (! entry->value)
+               error (EXIT_FAILURE, 0, _(error_msgid), quote (str));
+             value |= entry->value;
+             break;
+           }
        }
       str = new;
-  } while (new != NULL);
+    }
+  while (str);
+
+  return value;
 }
 
 /* Return the value of STR, interpreted as a non-negative decimal integer,
@@ -574,7 +648,12 @@ scanargs (int argc, char **argv)
       else if (STREQ (name, "of"))
        output_file = val;
       else if (STREQ (name, "conv"))
-       parse_conversion (val);
+       conversions_mask |= parse_symbols (val, conversions,
+                                          N_("invalid conversion: %s"));
+      else if (STREQ (name, "iflag"))
+       input_flags |= parse_symbols (val, flags, iflag_error_msgid);
+      else if (STREQ (name, "oflag"))
+       output_flags |= parse_symbols (val, flags, oflag_error_msgid);
       else
        {
          int invalid = 0;
@@ -638,6 +717,12 @@ scanargs (int argc, char **argv)
     output_blocksize = DEFAULT_BLOCKSIZE;
   if (conversion_blocksize == 0)
     conversions_mask &= ~(C_BLOCK | C_UNBLOCK);
+
+  if (input_flags & (O_DSYNC | O_SYNC))
+    input_flags |= O_RSYNC;
+
+  if ((conversions_mask & (C_EXCL | C_NOCREAT)) == (C_EXCL | C_NOCREAT))
+    error (EXIT_FAILURE, 0, _("cannot combine excl and nocreat"));
 }
 
 /* Fix up translation table. */
@@ -928,6 +1013,22 @@ copy_with_unblock (char const *buf, size_t nread)
     }
 }
 
+/* Set the file descriptor flags for FD that correspond to the nonzero bits
+   in FLAGS.  The file's name is NAME.  */
+
+static void
+set_fd_flags (int fd, int flags, char const *name)
+{
+  if (flags)
+    {
+      int old_flags = fcntl (fd, F_GETFL);
+      int new_flags = old_flags | flags;
+      if (old_flags < 0
+         || (new_flags != old_flags && fcntl (fd, F_SETFL, new_flags) == -1))
+       error (EXIT_FAILURE, errno, _("setting flags for %s"), quote (name));
+    }
+}
+
 /* The main loop.  */
 
 static int
@@ -994,7 +1095,7 @@ dd_copy (void)
     }
 
   if (max_records == 0)
-    quit (exit_status);
+    return exit_status;
 
   while (1)
     {
@@ -1061,7 +1162,7 @@ dd_copy (void)
          if (nwritten != n_bytes_read)
            {
              error (0, errno, _("writing %s"), quote (output_file));
-             quit (EXIT_FAILURE);
+             return EXIT_FAILURE;
            }
          else if (n_bytes_read == input_blocksize)
            w_full++;
@@ -1122,7 +1223,7 @@ dd_copy (void)
       if (nwritten != oc)
        {
          error (0, errno, _("writing %s"), quote (output_file));
-         quit (EXIT_FAILURE);
+         return EXIT_FAILURE;
        }
     }
 
@@ -1130,6 +1231,24 @@ dd_copy (void)
   if (real_obuf)
     free (real_obuf);
 
+  if ((conversions_mask & C_FDATASYNC) && fdatasync (STDOUT_FILENO) != 0)
+    {
+      if (errno != ENOSYS && errno != EINVAL)
+       {
+         error (0, errno, "fdatasync %s", quote (output_file));
+         exit_status = EXIT_FAILURE;
+       }
+      conversions_mask |= C_FSYNC;
+    }
+
+  if (conversions_mask & C_FSYNC)
+    while (fsync (STDOUT_FILENO) != 0)
+      if (errno != EINTR)
+       {
+         error (0, errno, "fsync %s", quote (output_file));
+         return EXIT_FAILURE;
+       }
+
   return exit_status;
 }
 
@@ -1174,19 +1293,29 @@ main (int argc, char **argv)
 
   apply_translations ();
 
-  if (input_file != NULL)
+  if (input_file == NULL)
     {
-      if (open_fd (STDIN_FILENO, input_file, O_RDONLY, 0) < 0)
-       error (EXIT_FAILURE, errno, _("opening %s"), quote (input_file));
+      input_file = _("standard input");
+      set_fd_flags (STDIN_FILENO, input_flags, input_file);
     }
   else
-    input_file = _("standard input");
+    {
+      if (open_fd (STDIN_FILENO, input_file, O_RDONLY | input_flags, 0) < 0)
+       error (EXIT_FAILURE, errno, _("opening %s"), quote (input_file));
+    }
 
-  if (output_file != NULL)
+  if (output_file == NULL)
+    {
+      output_file = _("standard output");
+      set_fd_flags (STDOUT_FILENO, output_flags, output_file);
+    }
+  else
     {
       mode_t perms = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
       int opts
-       = (O_CREAT
+       = (output_flags
+          | (conversions_mask & C_NOCREAT ? 0 : O_CREAT)
+          | (conversions_mask & C_EXCL ? O_EXCL : 0)
           | (seek_records || (conversions_mask & C_NOTRUNC) ? 0 : O_TRUNC));
 
       /* Open the output file with *read* access only if we might
@@ -1210,8 +1339,8 @@ main (int argc, char **argv)
                   quote (output_file));
 
          /* Complain only when ftruncate fails on a regular file, a
-            directory, or a shared memory object, as the 2000-08
-            POSIX draft specifies ftruncate's behavior only for these
+            directory, or a shared memory object, as
+            POSIX 1003.1-2003 specifies ftruncate's behavior only for these
             file types.  For example, do not complain when Linux 2.4
             ftruncate fails on /dev/fd0.  */
          if (ftruncate (STDOUT_FILENO, o) != 0
@@ -1227,10 +1356,6 @@ main (int argc, char **argv)
        }
 #endif
     }
-  else
-    {
-      output_file = _("standard output");
-    }
 
   install_handler (SIGINT, interrupt_handler);
   install_handler (SIGQUIT, interrupt_handler);