Merge branch 'master' of ssh://git.sv.gnu.org/srv/git/nmh
authorLyndon Nerenberg <lyndon@orthanc.ca>
Sun, 16 Sep 2012 21:39:27 +0000 (14:39 -0700)
committerLyndon Nerenberg <lyndon@orthanc.ca>
Sun, 16 Sep 2012 21:39:27 +0000 (14:39 -0700)
docs/README.developers
docs/pending-release-notes
man/mhstore.man
test/mhstore/test-mhstore
test/post/test-post-basic
test/post/test-post-common.sh
uip/mhstore.c
uip/mhstoresbr.c
uip/post.c
uip/sendsbr.c
uip/whatnowsbr.c

index bae6d9c..9038cd2 100644 (file)
@@ -6,7 +6,19 @@ This file is intended to provide a few tips for anyone doing development on nmh.
 Developers who learn things "the hard way" about the nmh codebase (as opposed to
 local info best encoded in a comment) are encouraged to share their wisdom here.
 
-The topics are organized alphabetically.
+Following a commit checklist, the topics are organized alphabetically.
+
+----------------
+commit checklist
+----------------
+
+1. code updated?
+2. test added?
+3. man page and other documentation updated?
+4. docs/pending-release-notes updated?
+5. should commit message reference bug report?
+6. update/close bug report (with commit id)?
+7. notify nmh-users?
 
 
 -------------------------
index c286c3d..468da54 100644 (file)
@@ -22,6 +22,8 @@ NEW FEATURES
   pid.time@localname, where time is in seconds, and matches previous
   behavior.  The random style replaces the localname with some
   (pseudo)random bytes and uses microsecond-resolution time.
+- Added -clobber switch to mhstore(1) to control overwriting of
+  existing files.
 
 ----------------------------
 OBSOLETE/DEPRECATED FEATURES
index 470714c..be99f0d 100644 (file)
@@ -19,6 +19,8 @@ mhstore \- store contents of MIME messages into files
 .IR content ]
 \&...
 .RB [ \-auto " | " \-noauto ]
+.RB [ \-clobber
+.IR always " | " auto " | " suffix " | " ask " | " never ]
 .RB [ \-rcache
 .IR policy ]
 .RB [ \-wcache
@@ -268,6 +270,72 @@ mhstore-store-application/PostScript: %m%P.ps
 .fi
 .RE
 .PP
+.SS "Overwriting Existing Files"
+The
+.B \-clobber
+switch controls whether
+.B mhstore
+should overwrite existing files.  The allowed values for this switch
+and corresponding behavior when
+.B mhstore
+encounters an existing file are:
+.PP
+.RS 5
+.nf
+.ta \w'suffix  'u
+always    Overwrite existing file (default)
+auto      Create new file of form name-n.extension
+suffix    Create new file of form name.extension.n
+ask       Prompt the user to specify whether or not to overwrite
+          the existing file
+never     Do not overwrite existing file
+.fi
+.RE
+.PP
+With
+.I auto
+and
+.IR suffix ,
+.I n
+is the lowest unused number, starting from one, in the same form.  If
+a filename does not have an extension (following a '.'), then
+.I auto
+and
+.I suffix
+create a new file of the form
+.I name-n
+and
+.IR name.n ,
+respectively.  With
+.I never
+and
+.IR ask ,
+the exit status of
+.B mhstore
+will be the number of files that were requested but not stored.
+.PP
+With
+.IR ask ,
+if standard input is connected to a terminal,
+the user is prompted to respond
+.IR yes ,
+.IR no ,
+or
+.I rename
+to whether the file should be overwritten.  The responses
+can be abbreviated.  If the user responds with
+.IR rename ,
+then
+.B mhstore
+prompts the user for the name of the new file to be created.  If it is
+a relative path name (does not begin with '/'), then it is relative to
+the current directory.  If it is an absolute or relative path to a
+directory that does not exist, the user will be prompted whether to
+create the directory.  If standard input is not connected to a
+terminal,
+.I ask
+behaves the same as
+.IR always .
 .SS "Reassembling Messages of Type message/partial"
 .B mhstore
 is also able to reassemble messages that have been
@@ -462,6 +530,7 @@ mhbuild(1), mhlist(1), mhshow(1), sendfiles(1)
 .RB ` +folder "' defaults to the current folder"
 .RB ` msgs "' defaults to cur"
 .RB ` \-noauto '
+.RB ` \-clobber\ always '
 .RB ` \-nocheck '
 .RB ` \-rcache\ ask '
 .RB ` \-wcache\ ask '
index 55cea9f..421580b 100755 (executable)
@@ -239,6 +239,45 @@ EOF
 run_test 'mhstore last' 'storing message 15 as file 15.txt'
 check $expected 15.txt
 
+# check -clobber always
+folder +inbox 7 > /dev/null
+touch 7.txt
+cat > $expected <<EOF
+This is message number 7
+EOF
+run_test 'mhstore' 'storing message 7 as file 7.txt'
+check $expected 7.txt 'keep first'
+run_test 'mhstore -clobber always' 'storing message 7 as file 7.txt'
+check $expected 7.txt 'keep first'
+
+# check -clobber auto
+touch 7.txt
+run_test 'mhstore -clobber auto' 'storing message 7 as file 7-1.txt'
+check $expected 7-1.txt 'keep first'
+touch 7-1.txt
+run_test 'mhstore -clobber auto' 'storing message 7 as file 7-2.txt'
+check $expected 7-2.txt 'keep first'
+
+# check -clobber suffix
+run_test 'mhstore -clobber suffix' 'storing message 7 as file 7.txt.1'
+check $expected 7.txt.1 'keep first'
+touch 7.txt.1
+run_test 'mhstore -clobber suffix' 'storing message 7 as file 7.txt.2'
+check $expected 7.txt.2 'keep first'
+
+# Don't check -clobber ask because it requires connection to a
+# terminal, and this test won't always be run with one.
+
+# check -clobber never.  Its exit status is the number of files not overwritten.
+run_test 'mhstore -clobber never' \
+         "mhstore: will not overwrite `pwd`/7.txt with -clobber never"
+set +e
+mhstore -clobber never >/dev/null 2>&1
+run_test "echo $?" 1
+set -e
+
+/bin/rm -f 7.txt 7-1.txt 7.txt.1
+
 # check with relative nmh-storage profile component
 storagedir=storagedir
 dir="$MH_TEST_DIR/Mail/inbox/$storagedir"
index a2e4fac..74ef5e1 100755 (executable)
@@ -56,9 +56,50 @@ This is a blank test
 EOF
 
 run_test "send -draft -server 127.0.0.1 -port $localport" \
-       "post: message has no From: header
+         "post: message has no From: header
 post: See default components files for examples
 post: re-format message and try again
 send: message not delivered to anyone"
 
+#
+# Make sure that empty Nmh-* header lines are ignored, and that post
+# warns about non-empty ones.
+#
+cat > "${MH_TEST_DIR}/Mail/draft" <<EOF
+From: Mr Nobody <nobody@example.com>
+To: Somebody Else <somebody@example.com>
+Nmh-Attachment:
+Nmh-Unused: suppress this line
+Subject: Test
+
+This is a test
+.
+EOF
+
+cat > "${testname}.expected" <<EOF
+EHLO nosuchhost.example.com
+MAIL FROM:<nobody@example.com>
+RCPT TO:<somebody@example.com>
+DATA
+From: Mr Nobody <nobody@example.com>
+To: Somebody Else <somebody@example.com>
+Subject: Test
+Date:
+
+This is a test
+..
+.
+QUIT
+EOF
+
+cat > "${testname}.expected_send_output" <<EOF
+post: ignoring header line -- Nmh-Unused: suppress this line
+EOF
+
+test_post "${testname}.actual" "${testname}.expected" \
+          >${testname}.send_output 2>&1
+
+check "${testname}.send_output" "${testname}.expected_send_output"
+
+
 exit ${failed:-0}
index bd37c7d..1118d9c 100755 (executable)
@@ -33,7 +33,7 @@ test_post ()
     # retry a few times if it fails...
     status=1
     for i in 0 1 2 3 4 5 6 7 8 9; do
-        if send -draft -server 127.0.0.1 -port $localport $3 >/dev/null 2>&1
+        if send -draft -server 127.0.0.1 -port $localport $3
         then
             status=0
             break
index e491dd9..fa30e03 100644 (file)
@@ -47,16 +47,21 @@ static struct swit switches[] = {
     { "version", 0 },
 #define        HELPSW                 12
     { "help", 0 },
+#define        CLOBBERSW              13
+    { "clobber always|auto|suffix|ask|never", 0 },
 
 /*
  * switches for debugging
  */
-#define        DEBUGSW                13
+#define        DEBUGSW                14
     { "debug", -5 },
     { NULL, 0 }
 };
 
 
+int save_clobber_policy (const char *);
+extern int files_not_clobbered;
+
 /* mhparse.c */
 extern char *tmp;      /* directory to place temp files */
 
@@ -213,6 +218,14 @@ do_cache:
            case NVERBSW: 
                verbosw = 0;
                continue;
+            case CLOBBERSW:
+               if (!(cp = *argp++) || *cp == '-')
+                   adios (NULL, "missing argument to %s", argp[-2]);
+                if (save_clobber_policy (cp)) {
+                  adios (NULL, "invalid argument, %s, to %s", argp[-1],
+                         argp[-2]);
+                }
+               continue;
            case DEBUGSW:
                debugsw = 1;
                continue;
@@ -375,7 +388,7 @@ do_cache:
        context_save ();                  /* save the context file  */
     }
 
-    done (0);
+    done (files_not_clobbered);
     return 1;
 }
 
index a604471..ca03bda 100644 (file)
@@ -75,7 +75,7 @@ static int output_content_folder (char *, char *);
 static int parse_format_string (CT, char *, char *, int, char *);
 static void get_storeproc (CT);
 static int copy_some_headers (FILE *, CT);
-
+static char *clobber_check (char *);
 
 /*
  * Main entry point to store content
@@ -589,7 +589,9 @@ store_content (CT ct, CT p)
        return show_content_aux (ct, 1, 0, buffer + 1, dir);
 
     /* record the filename */
-    ct->c_storage = add (buffer, NULL);
+    if ((ct->c_storage = clobber_check (add (buffer, NULL))) == NULL) {
+      return NOTOK;
+    }
 
 got_filename:
     /* flush the output stream */
@@ -1076,3 +1078,230 @@ copy_some_headers (FILE *out, CT ct)
 
     return OK;
 }
+
+/******************************************************************************/
+/* -clobber support */
+
+enum clobber_policy_t {
+  NMH_CLOBBER_ALWAYS,
+  NMH_CLOBBER_AUTO,
+  NMH_CLOBBER_SUFFIX,
+  NMH_CLOBBER_ASK,
+  NMH_CLOBBER_NEVER
+};
+
+static enum clobber_policy_t clobber_policy = NMH_CLOBBER_ALWAYS;
+
+int files_not_clobbered = 0;
+
+int
+save_clobber_policy (const char *value) {
+  if (! mh_strcasecmp (value, "always")) {
+    clobber_policy = NMH_CLOBBER_ALWAYS;
+  } else if (! mh_strcasecmp (value, "auto")) {
+    clobber_policy = NMH_CLOBBER_AUTO;
+  } else if (! mh_strcasecmp (value, "suffix")) {
+    clobber_policy = NMH_CLOBBER_SUFFIX;
+  } else if (! mh_strcasecmp (value, "ask")) {
+    clobber_policy = NMH_CLOBBER_ASK;
+  } else if (! mh_strcasecmp (value, "never")) {
+    clobber_policy = NMH_CLOBBER_NEVER;
+  } else {
+    return 1;
+  }
+
+  return 0;
+}
+
+
+static char *
+next_version (char *file, enum clobber_policy_t clobber_policy) {
+  const size_t max_versions = 1000000;
+  /* 8 = log max_versions  +  one for - or .  +  one for null terminator */
+  const size_t buflen = strlen (file) + 8;
+  char *buffer = mh_xmalloc (buflen);
+  size_t version;
+
+  char *extension = NULL;
+  if (clobber_policy == NMH_CLOBBER_AUTO  &&
+      ((extension = strrchr (file, '.')) != NULL)) {
+    *extension++ = '\0';
+  }
+
+  for (version = 1; version < max_versions; ++version) {
+    int fd;
+
+    switch (clobber_policy) {
+      case NMH_CLOBBER_AUTO: {
+        snprintf (buffer, buflen, "%s-%ld%s%s", file, (long) version,
+                  extension == NULL  ?  ""  :  ".",
+                  extension == NULL  ?  ""  :  extension);
+        break;
+      }
+
+      case NMH_CLOBBER_SUFFIX:
+        snprintf (buffer, buflen, "%s.%ld", file, (long) version);
+        break;
+
+      default:
+        /* Should never get here. */
+        advise (NULL, "will not overwrite %s, invalid clobber policy", buffer);
+        free (buffer);
+        ++files_not_clobbered;
+        return NULL;
+    }
+
+    /* Actually (try to) create the file here to avoid a race
+       condition on file naming + creation.  This won't solve the
+       problem with old NFS that doesn't support O_EXCL, though.
+       Let the umask strip off permissions from 0666 as desired.
+       That's what fopen () would do if it was creating the file. */
+    if ((fd = open (buffer, O_CREAT | O_EXCL,
+                    S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP |
+                    S_IROTH | S_IWOTH)) >= 0) {
+      close (fd);
+      break;
+    }
+  }
+
+  free (file);
+
+  if (version >= max_versions) {
+    advise (NULL, "will not overwrite %s, too many versions", buffer);
+    free (buffer);
+    buffer = NULL;
+    ++files_not_clobbered;
+  }
+
+  return buffer;
+}
+
+
+static char *
+clobber_check (char *original_file) {
+  /* clobber policy        return value
+   * --------------        ------------
+   *   -always                 file
+   *   -auto           file-<digits>.extension
+   *   -suffix             file.<digits>
+   *   -ask          file, 0, or another filename/path
+   *   -never                   0
+   */
+
+  char *file;
+  char *cwd = NULL;
+  int check_again;
+
+  if (clobber_policy == NMH_CLOBBER_ASK) {
+    /* Save cwd for possible use in loop below. */
+    char *slash;
+
+    cwd = add (original_file, NULL);
+    slash = strrchr (cwd, '/');
+
+    if (slash) {
+      *slash = '\0';
+    } else {
+      /* original_file wasn't a full path, which shouldn't happen. */
+      cwd = NULL;
+    }
+  }
+
+  do {
+    struct stat st;
+
+    file = original_file;
+    check_again = 0;
+
+    switch (clobber_policy) {
+      case NMH_CLOBBER_ALWAYS:
+        break;
+
+      case NMH_CLOBBER_SUFFIX:
+      case NMH_CLOBBER_AUTO:
+        if (stat (file, &st) == OK) {
+          file = next_version (original_file, clobber_policy);
+        }
+        break;
+
+      case NMH_CLOBBER_ASK:
+        if (stat (file, &st) == OK) {
+          enum answers { NMH_YES, NMH_NO, NMH_RENAME };
+          static struct swit answer[4] = {
+            { "yes", 0 }, { "no", 0 }, { "rename", 0 }, { NULL, 0 } };
+          char **ans;
+
+          if (isatty (fileno (stdin))) {
+            char *prompt =
+              concat ("Overwrite \"", file, "\" [y/n/rename]? ", NULL);
+            ans = getans (prompt, answer);
+            free (prompt);
+          } else {
+            /* Overwrite, that's what nmh used to do.  And warn. */
+            advise (NULL, "-clobber ask but no tty, so overwrite %s", file);
+            break;
+          }
+
+          switch ((enum answers) smatch (*ans, answer)) {
+            case NMH_YES:
+              break;
+            case NMH_NO:
+              free (file);
+              file = NULL;
+              ++files_not_clobbered;
+              break;
+            case NMH_RENAME: {
+              char buf[PATH_MAX];
+              printf ("Enter filename or full path of the new file: ");
+              if (fgets (buf, sizeof buf, stdin) == NULL  ||
+                  buf[0] == '\0') {
+                file = NULL;
+                ++files_not_clobbered;
+              } else {
+                char *newline = strchr (buf, '\n');
+                if (newline) {
+                  *newline = '\0';
+                }
+              }
+
+              free (file);
+
+              if (buf[0] == '/') {
+                /* Full path, use it. */
+                file = add (buf, NULL);
+              } else {
+                /* Relative path. */
+                file = cwd  ?  concat (cwd, "/", buf, NULL)  :  add (buf, NULL);
+              }
+
+              check_again = 1;
+              break;
+            }
+          }
+        }
+        break;
+
+      case NMH_CLOBBER_NEVER:
+        if (stat (file, &st) == OK) {
+          /* Keep count of files that would have been clobbered,
+             and return that as process exit status. */
+          advise (NULL, "will not overwrite %s with -clobber never", file);
+          free (file);
+          file = NULL;
+          ++files_not_clobbered;
+        }
+        break;
+    }
+
+    original_file = file;
+  } while (check_again);
+
+  if (cwd) {
+    free (cwd);
+  }
+
+  return file;
+}
+
+/* -clobber support */
+/******************************************************************************/
index 526b5e4..ff75197 100644 (file)
@@ -709,7 +709,21 @@ putfmt (char *name, char *str, FILE *out)
     }
 
     if ((i = get_header (name, hdrtab)) == NOTOK) {
-       fprintf (out, "%s: %s", name, str);
+       if (strncasecmp (name, "nmh-", 4)) {
+           fprintf (out, "%s: %s", name, str);
+       } else {
+           /* Filter out all Nmh-* headers, because Norm asked.  They
+              should never have reached this point.  Warn about any
+              that are non-empty. */
+           if (strcmp (str, "\n")) {
+               char *newline = strchr (str, '\n');
+               if (newline) *newline = '\0';
+               if (! whomsw) {
+                   advise (NULL, "ignoring header line -- %s: %s", name, str);
+               }
+           }
+       }
+
        return;
     }
 
index 08dc766..dfd974f 100644 (file)
@@ -209,19 +209,27 @@ attach(char *attachment_header_field_name, char *draft_file_name,
     field = (char *)mh_xmalloc(field_size = 256);
 
     /*
-     * Scan the draft file for a header field name that matches the -attach
-     * argument.  The existence of one indicates that the draft has attachments.
-     * Bail out if there are no attachments because we're done.  Read to the
-     * end of the headers even if we have no attachments.
+     * Scan the draft file for a header field name, with a non-empty
+     * body, that matches the -attach argument.  The existence of one
+     * indicates that the draft has attachments.  Bail out if there
+     * are no attachments because we're done.  Read to the end of the
+     * headers even if we have no attachments.
      */
 
     length = strlen(attachment_header_field_name);
 
     has_attachment = 0;
 
-    while (get_line() != EOF && *field != '\0' && *field != '-')
-       if (strncasecmp(field, attachment_header_field_name, length) == 0 && field[length] == ':')
-           has_attachment = 1;
+    while (get_line() != EOF && *field != '\0' && *field != '-') {
+       if (strncasecmp(field, attachment_header_field_name, length) == 0 &&
+           field[length] == ':') {
+           for (p = field + length + 1; *p == ' ' || *p == '\t'; p++)
+               ;
+           if (strlen (p) > 0) {
+               has_attachment = 1;
+           }
+       }
+    }
 
     if (has_attachment == 0)
        return (DONE);
@@ -267,14 +275,16 @@ attach(char *attachment_header_field_name, char *draft_file_name,
     }
 
     /*
-     * Start at the beginning of the draft file.  Copy all non-attachment header fields
-     * to the temporary composition file.  Then add the dashed line separator.
+     * Start at the beginning of the draft file.  Copy all
+     * non-attachment header fields to the temporary composition
+     * file.  Then add the dashed line separator.
      */
 
     rewind(draft_file);
 
     while (get_line() != EOF && *field != '\0' && *field != '-')
-       if (strncasecmp(field, attachment_header_field_name, length) != 0 || field[length] != ':')
+       if (strncasecmp(field, attachment_header_field_name, length) != 0 ||
+            field[length] != ':')
            (void)fprintf(composition_file, "%s\n", field);
 
     (void)fputs("--------\n", composition_file);
@@ -301,8 +311,9 @@ attach(char *attachment_header_field_name, char *draft_file_name,
                                         "text/plain");
 
     /*
-     * Now, go back to the beginning of the draft file and look for header fields
-     * that specify attachments.  Add a mhbuild MIME composition file for each.
+     * Now, go back to the beginning of the draft file and look for
+     * header fields that specify attachments.  Add a mhbuild MIME
+     * composition file for each.
      */
 
     if ((fp = fopen (p = etcpath ("mhn.defaults"), "r"))) {
@@ -313,22 +324,36 @@ attach(char *attachment_header_field_name, char *draft_file_name,
     rewind(draft_file);
 
     while (get_line() != EOF && *field != '\0' && *field != '-') {
-       if (strncasecmp(field, attachment_header_field_name, length) == 0 && field[length] == ':') {
+       if (strncasecmp(field, attachment_header_field_name, length) == 0 &&
+            field[length] == ':') {
            for (p = field + length + 1; *p == ' ' || *p == '\t'; p++)
                ;
 
-           /* Don't set the default content type so take
-              make_mime_composition_file_entry() will try to infer it
-              from the file type. */
-           make_mime_composition_file_entry(p, attachformat, 0);
+           /* Skip empty attachment_header_field_name lines. */
+           if (strlen (p) > 0) {
+               struct stat st;
+               if (stat (p, &st) == OK) {
+                   if (S_ISREG (st.st_mode)) {
+                     /* Don't set the default content type so take
+                        make_mime_composition_file_entry() will try
+                        to infer it from the file type. */
+                       make_mime_composition_file_entry(p, attachformat, 0);
+                   } else {
+                       adios (NULL, "unable to attach %s, not a plain file",
+                              p);
+                   }
+               } else {
+                 adios (NULL, "unable to access file \"%s\"", p);
+               }
+           }
        }
     }
 
     (void)fclose(composition_file);
 
     /*
-     * We're ready to roll!  Run mhbuild on the composition file.  Note that mhbuild
-     * is in the context as buildmimeproc.
+     * We're ready to roll!  Run mhbuild on the composition file.
+     * Note that mhbuild is in the context as buildmimeproc.
      */
 
     (void)sprintf(buf, "%s %s", buildmimeproc, composition_file_name);
index 121a79b..d54877e 100644 (file)
@@ -97,7 +97,7 @@ static struct swit aleqs[] = {
 #define        PWDCMDSW                      11
     { "pwd", 0 },
 #define        LSCMDSW                       12
-    { "ls", 0 },
+    { "ls", 2 },
 #define        ATTACHCMDSW                   13
     { "attach", 0 },
 #define        DETACHCMDSW                   14
@@ -120,7 +120,7 @@ static int buildfile (char **, char *);
 static int check_draft (char *);
 static int whomfile (char **, char *);
 static int removefile (char *);
-static void writelscmd(char *, int, char **);
+static void writelscmd(char *, int, char *, char **);
 static void writesomecmd(char *buf, int bufsz, char *cmd, char *trailcmd, char **argp);
 static FILE* popen_in_dir(const char *dir, const char *cmd, const char *type);
 static int system_in_dir(const char *dir, const char *cmd);
@@ -336,7 +336,7 @@ WhatNow (int argc, char **argv)
             */
 
            if (*(argp+1) == (char *)0) {
-               (void)sprintf(buf, "$SHELL -c \"cd;pwd\"");
+               (void)sprintf(buf, "$SHELL -c \"cd&&pwd\"");
            }
            else {
                writesomecmd(buf, BUFSIZ, "cd", "pwd", argp);
@@ -366,7 +366,7 @@ WhatNow (int argc, char **argv)
             *  Use the user's shell so that we can take advantage of any
             *  syntax that the user is accustomed to.
             */
-           writelscmd(buf, sizeof(buf), argp);
+           writelscmd(buf, sizeof(buf), "", argp);
            (void)system_in_dir(cwd, buf);
            break;
 
@@ -431,7 +431,7 @@ WhatNow (int argc, char **argv)
             *  Build a command line that causes the user's shell to list the file name
             *  arguments.  This handles and wildcard expansion, tilde expansion, etc.
             */
-           writelscmd(buf, sizeof(buf), argp);
+           writelscmd(buf, sizeof(buf), "-d", argp);
 
            /*
             *  Read back the response from the shell, which contains a number of lines
@@ -520,7 +520,7 @@ WhatNow (int argc, char **argv)
             * We feed all the file names to the shell at once, otherwise you can't
             * provide a file name with a space in it.
             */
-           writelscmd(buf, sizeof(buf), argp);
+           writelscmd(buf, sizeof(buf), "-d", argp);
            if ((f = popen_in_dir(cwd, buf, "r")) != (FILE *)0) {
                while (fgets(shell, sizeof (shell), f) != (char *)0) {
                    *(strchr(shell, '\n')) = '\0';
@@ -561,9 +561,9 @@ writesomecmd(char *buf, int bufsz, char *cmd, char *trailcmd, char **argp)
      * new C99 mandated 'number of chars that would have been written'
      */
     /* length checks here and inside the loop allow for the
-     * trailing ';', trailcmd, '"' and NUL
+     * trailing "&&", trailcmd, '"' and NUL
      */
-    int trailln = strlen(trailcmd) + 3;
+    int trailln = strlen(trailcmd) + 4;
     if (ln < 0 || ln + trailln > bufsz)
        adios((char *)0, "arguments too long");
     
@@ -579,9 +579,9 @@ writesomecmd(char *buf, int bufsz, char *cmd, char *trailcmd, char **argp)
        cp += ln;
     }
     if (*trailcmd) {
-       *cp++ = ';';
+       *cp++ = '&'; *cp++ = '&';
        strcpy(cp, trailcmd);
-       cp += trailln - 3;
+       cp += trailln - 4;
     }
     *cp++ = '"';
     *cp = 0;
@@ -592,9 +592,11 @@ writesomecmd(char *buf, int bufsz, char *cmd, char *trailcmd, char **argp)
  * arguments.  This handles and wildcard expansion, tilde expansion, etc.
  */
 static void
-writelscmd(char *buf, int bufsz, char **argp)
+writelscmd(char *buf, int bufsz, char *lsoptions, char **argp)
 {
-    writesomecmd(buf, bufsz, "ls", "", argp);
+  char *lscmd = concat ("ls ", lsoptions, " --", NULL);
+  writesomecmd(buf, bufsz, lscmd, "", argp);
+  free (lscmd);
 }
 
 /* Like system(), but run the command in directory dir.